WebSocketChannel.h (13811B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et tw=80 : */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_net_WebSocketChannel_h 8 #define mozilla_net_WebSocketChannel_h 9 10 #include "nsISupports.h" 11 #include "nsIInterfaceRequestor.h" 12 #include "nsIStreamListener.h" 13 #include "nsIAsyncInputStream.h" 14 #include "nsIAsyncOutputStream.h" 15 #include "nsITimer.h" 16 #include "nsIDNSListener.h" 17 #include "nsINamed.h" 18 #include "nsIObserver.h" 19 #include "nsIProtocolProxyCallback.h" 20 #include "nsIChannelEventSink.h" 21 #include "nsIHttpChannelInternal.h" 22 #include "mozilla/net/WebSocketConnectionListener.h" 23 #include "mozilla/Mutex.h" 24 #include "BaseWebSocketChannel.h" 25 26 #include "nsCOMPtr.h" 27 #include "nsString.h" 28 #include "nsDeque.h" 29 #include "mozilla/Atomics.h" 30 31 class nsIAsyncVerifyRedirectCallback; 32 class nsIDashboardEventNotifier; 33 class nsIEventTarget; 34 class nsIHttpChannel; 35 class nsIRandomGenerator; 36 class nsISocketTransport; 37 class nsIURI; 38 39 namespace mozilla { 40 namespace net { 41 42 class OutboundMessage; 43 class OutboundEnqueuer; 44 class nsWSAdmissionManager; 45 class PMCECompression; 46 class CallOnMessageAvailable; 47 class CallOnStop; 48 class CallOnServerClose; 49 class CallAcknowledge; 50 class WebSocketEventService; 51 class WebSocketConnectionBase; 52 53 [[nodiscard]] extern nsresult CalculateWebSocketHashedSecret( 54 const nsACString& aKey, nsACString& aHash); 55 extern void ProcessServerWebSocketExtensions(const nsACString& aExtensions, 56 nsACString& aNegotiatedExtensions); 57 58 // Used to enforce "1 connecting websocket per host" rule, and reconnect delays 59 enum wsConnectingState { 60 NOT_CONNECTING = 0, // Not yet (or no longer) trying to open connection 61 CONNECTING_QUEUED, // Waiting for other ws to same host to finish opening 62 CONNECTING_DELAYED, // Delayed by "reconnect after failure" algorithm 63 CONNECTING_IN_PROGRESS // Started connection: waiting for result 64 }; 65 66 class WebSocketChannel : public BaseWebSocketChannel, 67 public nsIHttpUpgradeListener, 68 public nsIStreamListener, 69 public nsIInputStreamCallback, 70 public nsIOutputStreamCallback, 71 public nsITimerCallback, 72 public nsIDNSListener, 73 public nsIObserver, 74 public nsIProtocolProxyCallback, 75 public nsIInterfaceRequestor, 76 public nsIChannelEventSink, 77 public nsINamed, 78 public WebSocketConnectionListener { 79 friend class WebSocketFrame; 80 81 public: 82 NS_DECL_THREADSAFE_ISUPPORTS 83 NS_DECL_NSIHTTPUPGRADELISTENER 84 NS_DECL_NSIREQUESTOBSERVER 85 NS_DECL_NSISTREAMLISTENER 86 NS_DECL_NSIINPUTSTREAMCALLBACK 87 NS_DECL_NSIOUTPUTSTREAMCALLBACK 88 NS_DECL_NSITIMERCALLBACK 89 NS_DECL_NSIDNSLISTENER 90 NS_DECL_NSIPROTOCOLPROXYCALLBACK 91 NS_DECL_NSIINTERFACEREQUESTOR 92 NS_DECL_NSICHANNELEVENTSINK 93 NS_DECL_NSIOBSERVER 94 NS_DECL_NSINAMED 95 96 // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us 97 // 98 NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin, 99 JS::Handle<JS::Value> aOriginAttributes, 100 uint64_t aWindowID, nsIWebSocketListener* aListener, 101 nsISupports* aContext, JSContext* aCx) override; 102 NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin, 103 const OriginAttributes& aOriginAttributes, 104 uint64_t aWindowID, 105 nsIWebSocketListener* aListener, 106 nsISupports* aContext) override; 107 NS_IMETHOD Close(uint16_t aCode, const nsACString& aReason) override; 108 NS_IMETHOD SendMsg(const nsACString& aMsg) override; 109 NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override; 110 NS_IMETHOD SendBinaryStream(nsIInputStream* aStream, 111 uint32_t length) override; 112 NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) override; 113 114 WebSocketChannel(); 115 static void Shutdown(); 116 117 // Off main thread URI access. 118 void GetEffectiveURL(nsAString& aEffectiveURL) const override; 119 bool IsEncrypted() const override; 120 121 nsresult OnTransportAvailableInternal(); 122 void OnError(nsresult aStatus) override; 123 void OnTCPClosed() override; 124 nsresult OnDataReceived(uint8_t* aData, uint32_t aCount) override; 125 126 const static uint32_t kControlFrameMask = 0x8; 127 128 // First byte of the header 129 const static uint8_t kFinalFragBit = 0x80; 130 const static uint8_t kRsvBitsMask = 0x70; 131 const static uint8_t kRsv1Bit = 0x40; 132 const static uint8_t kRsv2Bit = 0x20; 133 const static uint8_t kRsv3Bit = 0x10; 134 const static uint8_t kOpcodeBitsMask = 0x0F; 135 136 // Second byte of the header 137 const static uint8_t kMaskBit = 0x80; 138 const static uint8_t kPayloadLengthBitsMask = 0x7F; 139 140 protected: 141 ~WebSocketChannel() override; 142 143 private: 144 friend class OutboundEnqueuer; 145 friend class nsWSAdmissionManager; 146 friend class FailDelayManager; 147 friend class CallOnMessageAvailable; 148 friend class CallOnStop; 149 friend class CallOnServerClose; 150 friend class CallAcknowledge; 151 152 // Common send code for binary + text msgs 153 [[nodiscard]] nsresult SendMsgCommon(const nsACString& aMsg, bool isBinary, 154 uint32_t length, 155 nsIInputStream* aStream = nullptr); 156 157 void EnqueueOutgoingMessage(nsDeque<OutboundMessage>& aQueue, 158 OutboundMessage* aMsg); 159 void DoEnqueueOutgoingMessage(); 160 161 void PrimeNewOutgoingMessage(); 162 void DeleteCurrentOutGoingMessage(); 163 void GeneratePong(uint8_t* payload, uint32_t len); 164 void GeneratePing(); 165 166 [[nodiscard]] nsresult OnNetworkChanged(); 167 [[nodiscard]] nsresult StartPinging(); 168 169 void BeginOpen(bool aCalledFromAdmissionManager); 170 void BeginOpenInternal(); 171 [[nodiscard]] nsresult HandleExtensions(); 172 [[nodiscard]] nsresult SetupRequest(); 173 [[nodiscard]] nsresult ApplyForAdmission(); 174 [[nodiscard]] nsresult DoAdmissionDNS(); 175 [[nodiscard]] nsresult CallStartWebsocketData(); 176 [[nodiscard]] nsresult StartWebsocketData(); 177 uint16_t ResultToCloseCode(nsresult resultCode); 178 void ReportConnectionTelemetry(nsresult aStatusCode); 179 180 void StopSession(nsresult reason); 181 void DoStopSession(nsresult reason); 182 void AbortSession(nsresult reason); 183 void ReleaseSession(); 184 void CleanupConnection(); 185 void IncrementSessionCount(); 186 void DecrementSessionCount(); 187 188 void EnsureHdrOut(uint32_t size); 189 190 static void ApplyMask(uint32_t mask, uint8_t* data, uint64_t len); 191 192 bool IsPersistentFramePtr(); 193 [[nodiscard]] nsresult ProcessInput(uint8_t* buffer, uint32_t count); 194 [[nodiscard]] bool UpdateReadBuffer(uint8_t* buffer, uint32_t count, 195 uint32_t accumulatedFragments, 196 uint32_t* available); 197 198 inline void ResetPingTimer() { 199 mPingOutstanding = 0; 200 if (mPingTimer) { 201 if (!mPingInterval) { 202 // The timer was created by forced ping and regular pinging is disabled, 203 // so cancel and null out mPingTimer. 204 mPingTimer->Cancel(); 205 mPingTimer = nullptr; 206 } else { 207 mPingTimer->SetDelay(mPingInterval); 208 } 209 } 210 } 211 212 void NotifyOnStart(); 213 214 nsCOMPtr<nsIEventTarget> mIOThread; 215 // Set in AsyncOpenNative and AsyncOnChannelRedirect, modified in 216 // DoStopSession on IO thread (.forget()). Probably ok... 217 nsCOMPtr<nsIHttpChannelInternal> mChannel; 218 nsCOMPtr<nsIHttpChannel> mHttpChannel; 219 220 nsCOMPtr<nsICancelable> mCancelable MOZ_GUARDED_BY(mMutex); 221 // Mainthread only 222 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; 223 // Set on Mainthread during AsyncOpen, used on IO thread and Mainthread 224 nsCOMPtr<nsIRandomGenerator> mRandomGenerator; 225 226 nsCString mHashedSecret; // MainThread only 227 228 // Used as key for connection managment: Initially set to hostname from URI, 229 // then to IP address (unless we're leaving DNS resolution to a proxy server) 230 // MainThread only 231 nsCString mAddress; 232 nsCString mPath; 233 int32_t mPort; // WS server port 234 // Secondary key for the connection queue. Used by nsWSAdmissionManager. 235 nsCString mOriginSuffix; // MainThread only 236 237 // Used for off main thread access to the URI string. 238 // Set on MainThread in AsyncOpenNative, used on TargetThread and IO thread 239 nsCString mHost; 240 nsString mEffectiveURL; 241 242 // Set on MainThread before multithread use, used on IO thread, cleared on 243 // IOThread 244 nsCOMPtr<nsISocketTransport> mTransport; 245 nsCOMPtr<nsIAsyncInputStream> mSocketIn; 246 nsCOMPtr<nsIAsyncOutputStream> mSocketOut; 247 RefPtr<WebSocketConnectionBase> mConnection; 248 249 // Only used on IO Thread (accessed when known-to-be-null in DoStopSession 250 // on MainThread before mDataStarted) 251 nsCOMPtr<nsITimer> mCloseTimer; 252 // set in AsyncOpenInternal on MainThread, used on IO thread. 253 // No multithread use before it's set, no changes after that. 254 uint32_t mCloseTimeout; /* milliseconds */ 255 256 nsCOMPtr<nsITimer> mOpenTimer; /* Mainthread only */ 257 uint32_t mOpenTimeout; /* milliseconds, MainThread only */ 258 wsConnectingState mConnecting; /* 0 if not connecting, MainThread only */ 259 // Set on MainThread, deleted on MainThread, used on MainThread or 260 // IO Thread (in DoStopSession). Mutex required to access off-main-thread. 261 nsCOMPtr<nsITimer> mReconnectDelayTimer MOZ_GUARDED_BY(mMutex); 262 263 // Only touched on IOThread (DoStopSession reads it on MainThread if 264 // we haven't connected yet (mDataStarted==false), and it's always null 265 // until mDataStarted=true) 266 nsCOMPtr<nsITimer> mPingTimer; 267 268 // Created in DoStopSession on IO thread (mDataStarted=true), accessed 269 // only from IO Thread 270 nsCOMPtr<nsITimer> mLingeringCloseTimer; 271 const static int32_t kLingeringCloseTimeout = 1000; 272 const static int32_t kLingeringCloseThreshold = 50; 273 274 RefPtr<WebSocketEventService> mService; // effectively const 275 276 int32_t 277 mMaxConcurrentConnections; // only used in AsyncOpenNative on MainThread 278 279 // Set on MainThread in AsyncOpenNative; then used on IO thread 280 uint64_t mInnerWindowID; 281 282 // following members are accessed only on the main thread 283 uint32_t mGotUpgradeOK : 1; 284 uint32_t mRecvdHttpUpgradeTransport : 1; 285 uint32_t : 0; // ensure these aren't mixed with the next set 286 287 // following members are accessed only on the IO thread 288 uint32_t mPingOutstanding : 1; 289 uint32_t mReleaseOnTransmit : 1; 290 uint32_t : 0; 291 292 Atomic<bool> mDataStarted; 293 // All changes to mRequestedClose happen under mutex, but since it's atomic, 294 // it can be read anywhere without a lock 295 Atomic<bool> mRequestedClose; 296 // mServer/ClientClosed are only modified on IOThread 297 Atomic<bool> mClientClosed; 298 Atomic<bool> mServerClosed; 299 // All changes to mStopped happen under mutex, but since it's atomic, it 300 // can be read anywhere without a lock 301 Atomic<bool> mStopped; 302 Atomic<bool> mCalledOnStop; 303 Atomic<bool> mTCPClosed; 304 Atomic<bool> mOpenedHttpChannel; 305 Atomic<bool> mIncrementedSessionCount; 306 Atomic<bool> mDecrementedSessionCount; 307 308 int32_t mMaxMessageSize; // set on MainThread in AsyncOpenNative, read on IO 309 // thread 310 // Set on IOThread, or on MainThread before mDataStarted. Used on IO Thread 311 // (after mDataStarted) 312 nsresult mStopOnClose; 313 uint16_t mServerCloseCode; // only used on IO thread 314 nsCString mServerCloseReason; // only used on IO thread 315 uint16_t mScriptCloseCode MOZ_GUARDED_BY(mMutex); 316 nsCString mScriptCloseReason MOZ_GUARDED_BY(mMutex); 317 318 // These are for the read buffers 319 const static uint32_t kIncomingBufferInitialSize = 16 * 1024; 320 // We're ok with keeping a buffer this size or smaller around for the life of 321 // the websocket. If a particular message needs bigger than this we'll 322 // increase the buffer temporarily, then drop back down to this size. 323 const static uint32_t kIncomingBufferStableSize = 128 * 1024; 324 325 // Set at creation, used/modified only on IO thread 326 uint8_t* mFramePtr; 327 uint8_t* mBuffer; 328 uint8_t mFragmentOpcode; 329 uint32_t mFragmentAccumulator; 330 uint32_t mBuffered; 331 uint32_t mBufferSize; 332 333 // These are for the send buffers 334 const static int32_t kCopyBreak = 1000; 335 336 // Only used on IO thread 337 OutboundMessage* mCurrentOut; 338 uint32_t mCurrentOutSent; 339 nsDeque<OutboundMessage> mOutgoingMessages; 340 nsDeque<OutboundMessage> mOutgoingPingMessages; 341 nsDeque<OutboundMessage> mOutgoingPongMessages; 342 uint32_t mHdrOutToSend; 343 uint8_t* mHdrOut; 344 uint8_t mOutHeader[kCopyBreak + 16]{0}; 345 346 // Set on MainThread in OnStartRequest (before mDataStarted), or in 347 // HandleExtensions() or OnTransportAvailableInternal(),used on IO Thread 348 // (after mDataStarted), cleared in DoStopSession on IOThread or on 349 // MainThread (if mDataStarted == false). 350 Mutex mCompressorMutex; 351 UniquePtr<PMCECompression> mPMCECompressor MOZ_GUARDED_BY(mCompressorMutex); 352 353 // Used by EnsureHdrOut, which isn't called anywhere 354 uint32_t mDynamicOutputSize; 355 uint8_t* mDynamicOutput; 356 // Set on creation and AsyncOpen, read on both threads 357 Atomic<bool> mPrivateBrowsing; 358 359 nsCOMPtr<nsIDashboardEventNotifier> 360 mConnectionLogService; // effectively const 361 362 mozilla::Mutex mMutex; 363 }; 364 365 class WebSocketSSLChannel : public WebSocketChannel { 366 public: 367 WebSocketSSLChannel() { BaseWebSocketChannel::mEncrypted = true; } 368 369 protected: 370 virtual ~WebSocketSSLChannel() = default; 371 }; 372 373 } // namespace net 374 } // namespace mozilla 375 376 #endif // mozilla_net_WebSocketChannel_h