Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/. */
7 #include "WebSocketLog.h"
8 #include "WebSocketChannel.h"
10 #include "mozilla/Atomics.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/Endian.h"
13 #include "mozilla/MathAlgorithms.h"
15 #include "nsIURI.h"
16 #include "nsIChannel.h"
17 #include "nsICryptoHash.h"
18 #include "nsIRunnable.h"
19 #include "nsIPrefBranch.h"
20 #include "nsIPrefService.h"
21 #include "nsICancelable.h"
22 #include "nsIDNSRecord.h"
23 #include "nsIDNSService.h"
24 #include "nsIStreamConverterService.h"
25 #include "nsIIOService2.h"
26 #include "nsIProtocolProxyService.h"
27 #include "nsIProxyInfo.h"
28 #include "nsIProxiedChannel.h"
29 #include "nsIAsyncVerifyRedirectCallback.h"
30 #include "nsIDashboardEventNotifier.h"
31 #include "nsIEventTarget.h"
32 #include "nsIHttpChannel.h"
33 #include "nsILoadGroup.h"
34 #include "nsIProtocolHandler.h"
35 #include "nsIRandomGenerator.h"
36 #include "nsISocketTransport.h"
37 #include "nsThreadUtils.h"
39 #include "nsAutoPtr.h"
40 #include "nsNetCID.h"
41 #include "nsServiceManagerUtils.h"
42 #include "nsCRT.h"
43 #include "nsThreadUtils.h"
44 #include "nsError.h"
45 #include "nsStringStream.h"
46 #include "nsAlgorithm.h"
47 #include "nsProxyRelease.h"
48 #include "nsNetUtil.h"
49 #include "mozilla/StaticMutex.h"
50 #include "mozilla/Telemetry.h"
51 #include "mozilla/TimeStamp.h"
53 #include "plbase64.h"
54 #include "prmem.h"
55 #include "prnetdb.h"
56 #include "zlib.h"
57 #include <algorithm>
59 #ifdef MOZ_WIDGET_GONK
60 #include "NetStatistics.h"
61 #endif
63 // rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
64 // dupe one constant we need from it
65 #define CLOSE_GOING_AWAY 1001
67 extern PRThread *gSocketThread;
69 using namespace mozilla;
70 using namespace mozilla::net;
72 namespace mozilla {
73 namespace net {
75 NS_IMPL_ISUPPORTS(WebSocketChannel,
76 nsIWebSocketChannel,
77 nsIHttpUpgradeListener,
78 nsIRequestObserver,
79 nsIStreamListener,
80 nsIProtocolHandler,
81 nsIInputStreamCallback,
82 nsIOutputStreamCallback,
83 nsITimerCallback,
84 nsIDNSListener,
85 nsIProtocolProxyCallback,
86 nsIInterfaceRequestor,
87 nsIChannelEventSink,
88 nsIThreadRetargetableRequest)
90 // We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
91 #define SEC_WEBSOCKET_VERSION "13"
93 /*
94 * About SSL unsigned certificates
95 *
96 * wss will not work to a host using an unsigned certificate unless there
97 * is already an exception (i.e. it cannot popup a dialog asking for
98 * a security exception). This is similar to how an inlined img will
99 * fail without a dialog if fails for the same reason. This should not
100 * be a problem in practice as it is expected the websocket javascript
101 * is served from the same host as the websocket server (or of course,
102 * a valid cert could just be provided).
103 *
104 */
106 // some helper classes
108 //-----------------------------------------------------------------------------
109 // FailDelayManager
110 //
111 // Stores entries (searchable by {host, port}) of connections that have recently
112 // failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
113 //-----------------------------------------------------------------------------
116 // Initial reconnect delay is randomly chosen between 200-400 ms.
117 // This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
118 const uint32_t kWSReconnectInitialBaseDelay = 200;
119 const uint32_t kWSReconnectInitialRandomDelay = 200;
121 // Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
122 const uint32_t kWSReconnectBaseLifeTime = 60 * 1000;
123 // Maximum reconnect delay (in ms)
124 const uint32_t kWSReconnectMaxDelay = 60 * 1000;
126 // hold record of failed connections, and calculates needed delay for reconnects
127 // to same host/port.
128 class FailDelay
129 {
130 public:
131 FailDelay(nsCString address, int32_t port)
132 : mAddress(address), mPort(port)
133 {
134 mLastFailure = TimeStamp::Now();
135 mNextDelay = kWSReconnectInitialBaseDelay +
136 (rand() % kWSReconnectInitialRandomDelay);
137 }
139 // Called to update settings when connection fails again.
140 void FailedAgain()
141 {
142 mLastFailure = TimeStamp::Now();
143 // We use a truncated exponential backoff as suggested by RFC 6455,
144 // but multiply by 1.5 instead of 2 to be more gradual.
145 mNextDelay = static_cast<uint32_t>(
146 std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
147 LOG(("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to %lu",
148 mAddress.get(), mPort, mNextDelay));
149 }
151 // returns 0 if there is no need to delay (i.e. delay interval is over)
152 uint32_t RemainingDelay(TimeStamp rightNow)
153 {
154 TimeDuration dur = rightNow - mLastFailure;
155 uint32_t sinceFail = (uint32_t) dur.ToMilliseconds();
156 if (sinceFail > mNextDelay)
157 return 0;
159 return mNextDelay - sinceFail;
160 }
162 bool IsExpired(TimeStamp rightNow)
163 {
164 return (mLastFailure +
165 TimeDuration::FromMilliseconds(kWSReconnectBaseLifeTime + mNextDelay))
166 <= rightNow;
167 }
169 nsCString mAddress; // IP address (or hostname if using proxy)
170 int32_t mPort;
172 private:
173 TimeStamp mLastFailure; // Time of last failed attempt
174 // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
175 uint32_t mNextDelay; // milliseconds
176 };
178 class FailDelayManager
179 {
180 public:
181 FailDelayManager()
182 {
183 MOZ_COUNT_CTOR(FailDelayManager);
185 mDelaysDisabled = false;
187 nsCOMPtr<nsIPrefBranch> prefService =
188 do_GetService(NS_PREFSERVICE_CONTRACTID);
189 bool boolpref = true;
190 nsresult rv;
191 rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
192 &boolpref);
193 if (NS_SUCCEEDED(rv) && !boolpref) {
194 mDelaysDisabled = true;
195 }
196 }
198 ~FailDelayManager()
199 {
200 MOZ_COUNT_DTOR(FailDelayManager);
201 for (uint32_t i = 0; i < mEntries.Length(); i++) {
202 delete mEntries[i];
203 }
204 }
206 void Add(nsCString &address, int32_t port)
207 {
208 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
210 if (mDelaysDisabled)
211 return;
213 FailDelay *record = new FailDelay(address, port);
214 mEntries.AppendElement(record);
215 }
217 // Element returned may not be valid after next main thread event: don't keep
218 // pointer to it around
219 FailDelay* Lookup(nsCString &address, int32_t port,
220 uint32_t *outIndex = nullptr)
221 {
222 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
224 if (mDelaysDisabled)
225 return nullptr;
227 FailDelay *result = nullptr;
228 TimeStamp rightNow = TimeStamp::Now();
230 // We also remove expired entries during search: iterate from end to make
231 // indexing simpler
232 for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
233 FailDelay *fail = mEntries[i];
234 if (fail->mAddress.Equals(address) && fail->mPort == port) {
235 if (outIndex)
236 *outIndex = i;
237 result = fail;
238 // break here: removing more entries would mess up *outIndex.
239 // Any remaining expired entries will be deleted next time Lookup
240 // finds nothing, which is the most common case anyway.
241 break;
242 } else if (fail->IsExpired(rightNow)) {
243 mEntries.RemoveElementAt(i);
244 delete fail;
245 }
246 }
247 return result;
248 }
250 // returns true if channel connects immediately, or false if it's delayed
251 void DelayOrBegin(WebSocketChannel *ws)
252 {
253 if (!mDelaysDisabled) {
254 uint32_t failIndex = 0;
255 FailDelay *fail = Lookup(ws->mAddress, ws->mPort, &failIndex);
257 if (fail) {
258 TimeStamp rightNow = TimeStamp::Now();
260 uint32_t remainingDelay = fail->RemainingDelay(rightNow);
261 if (remainingDelay) {
262 // reconnecting within delay interval: delay by remaining time
263 nsresult rv;
264 ws->mReconnectDelayTimer =
265 do_CreateInstance("@mozilla.org/timer;1", &rv);
266 if (NS_SUCCEEDED(rv)) {
267 rv = ws->mReconnectDelayTimer->InitWithCallback(
268 ws, remainingDelay, nsITimer::TYPE_ONE_SHOT);
269 if (NS_SUCCEEDED(rv)) {
270 LOG(("WebSocket: delaying websocket [this=%p] by %lu ms",
271 ws, (unsigned long)remainingDelay));
272 ws->mConnecting = CONNECTING_DELAYED;
273 return;
274 }
275 }
276 // if timer fails (which is very unlikely), drop down to BeginOpen call
277 } else if (fail->IsExpired(rightNow)) {
278 mEntries.RemoveElementAt(failIndex);
279 delete fail;
280 }
281 }
282 }
284 // Delays disabled, or no previous failure, or we're reconnecting after scheduled
285 // delay interval has passed: connect.
286 ws->BeginOpen();
287 }
289 // Remove() also deletes all expired entries as it iterates: better for
290 // battery life than using a periodic timer.
291 void Remove(nsCString &address, int32_t port)
292 {
293 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
295 TimeStamp rightNow = TimeStamp::Now();
297 // iterate from end, to make deletion indexing easier
298 for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
299 FailDelay *entry = mEntries[i];
300 if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
301 entry->IsExpired(rightNow)) {
302 mEntries.RemoveElementAt(i);
303 delete entry;
304 }
305 }
306 }
308 private:
309 nsTArray<FailDelay *> mEntries;
310 bool mDelaysDisabled;
311 };
313 //-----------------------------------------------------------------------------
314 // nsWSAdmissionManager
315 //
316 // 1) Ensures that only one websocket at a time is CONNECTING to a given IP
317 // address (or hostname, if using proxy), per RFC 6455 Section 4.1.
318 // 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
319 //-----------------------------------------------------------------------------
321 class nsWSAdmissionManager
322 {
323 public:
324 static void Init()
325 {
326 StaticMutexAutoLock lock(sLock);
327 if (!sManager) {
328 sManager = new nsWSAdmissionManager();
329 }
330 }
332 static void Shutdown()
333 {
334 StaticMutexAutoLock lock(sLock);
335 delete sManager;
336 sManager = nullptr;
337 }
339 // Determine if we will open connection immediately (returns true), or
340 // delay/queue the connection (returns false)
341 static void ConditionallyConnect(WebSocketChannel *ws)
342 {
343 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
344 NS_ABORT_IF_FALSE(ws->mConnecting == NOT_CONNECTING, "opening state");
346 StaticMutexAutoLock lock(sLock);
347 if (!sManager) {
348 return;
349 }
351 // If there is already another WS channel connecting to this IP address,
352 // defer BeginOpen and mark as waiting in queue.
353 bool found = (sManager->IndexOf(ws->mAddress) >= 0);
355 // Always add ourselves to queue, even if we'll connect immediately
356 nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws);
357 sManager->mQueue.AppendElement(newdata);
359 if (found) {
360 ws->mConnecting = CONNECTING_QUEUED;
361 } else {
362 sManager->mFailures.DelayOrBegin(ws);
363 }
364 }
366 static void OnConnected(WebSocketChannel *aChannel)
367 {
368 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
369 NS_ABORT_IF_FALSE(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
370 "Channel completed connect, but not connecting?");
372 StaticMutexAutoLock lock(sLock);
373 if (!sManager) {
374 return;
375 }
377 aChannel->mConnecting = NOT_CONNECTING;
379 // Remove from queue
380 sManager->RemoveFromQueue(aChannel);
382 // Connection succeeded, so stop keeping track of any previous failures
383 sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort);
385 // Check for queued connections to same host.
386 // Note: still need to check for failures, since next websocket with same
387 // host may have different port
388 sManager->ConnectNext(aChannel->mAddress);
389 }
391 // Called every time a websocket channel ends its session (including going away
392 // w/o ever successfully creating a connection)
393 static void OnStopSession(WebSocketChannel *aChannel, nsresult aReason)
394 {
395 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
397 StaticMutexAutoLock lock(sLock);
398 if (!sManager) {
399 return;
400 }
402 if (NS_FAILED(aReason)) {
403 // Have we seen this failure before?
404 FailDelay *knownFailure = sManager->mFailures.Lookup(aChannel->mAddress,
405 aChannel->mPort);
406 if (knownFailure) {
407 if (aReason == NS_ERROR_NOT_CONNECTED) {
408 // Don't count close() before connection as a network error
409 LOG(("Websocket close() before connection to %s, %d completed"
410 " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort,
411 aChannel));
412 } else {
413 // repeated failure to connect: increase delay for next connection
414 knownFailure->FailedAgain();
415 }
416 } else {
417 // new connection failure: record it.
418 LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
419 aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
420 sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
421 }
422 }
424 if (aChannel->mConnecting) {
425 // Only way a connecting channel may get here w/o failing is if it was
426 // closed with GOING_AWAY (1001) because of navigation, tab close, etc.
427 NS_ABORT_IF_FALSE(NS_FAILED(aReason) ||
428 aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
429 "websocket closed while connecting w/o failing?");
431 sManager->RemoveFromQueue(aChannel);
433 bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
434 aChannel->mConnecting = NOT_CONNECTING;
435 if (wasNotQueued) {
436 sManager->ConnectNext(aChannel->mAddress);
437 }
438 }
439 }
441 static void IncrementSessionCount()
442 {
443 StaticMutexAutoLock lock(sLock);
444 if (!sManager) {
445 return;
446 }
447 sManager->mSessionCount++;
448 }
450 static void DecrementSessionCount()
451 {
452 StaticMutexAutoLock lock(sLock);
453 if (!sManager) {
454 return;
455 }
456 sManager->mSessionCount--;
457 }
459 static void GetSessionCount(int32_t &aSessionCount)
460 {
461 StaticMutexAutoLock lock(sLock);
462 if (!sManager) {
463 return;
464 }
465 aSessionCount = sManager->mSessionCount;
466 }
468 private:
469 nsWSAdmissionManager() : mSessionCount(0)
470 {
471 MOZ_COUNT_CTOR(nsWSAdmissionManager);
472 }
474 ~nsWSAdmissionManager()
475 {
476 MOZ_COUNT_DTOR(nsWSAdmissionManager);
477 for (uint32_t i = 0; i < mQueue.Length(); i++)
478 delete mQueue[i];
479 }
481 class nsOpenConn
482 {
483 public:
484 nsOpenConn(nsCString &addr, WebSocketChannel *channel)
485 : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
486 ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); }
488 nsCString mAddress;
489 WebSocketChannel *mChannel;
490 };
492 void ConnectNext(nsCString &hostName)
493 {
494 int32_t index = IndexOf(hostName);
495 if (index >= 0) {
496 WebSocketChannel *chan = mQueue[index]->mChannel;
498 NS_ABORT_IF_FALSE(chan->mConnecting == CONNECTING_QUEUED,
499 "transaction not queued but in queue");
500 LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
502 mFailures.DelayOrBegin(chan);
503 }
504 }
506 void RemoveFromQueue(WebSocketChannel *aChannel)
507 {
508 int32_t index = IndexOf(aChannel);
509 NS_ABORT_IF_FALSE(index >= 0, "connection to remove not in queue");
510 if (index >= 0) {
511 nsOpenConn *olddata = mQueue[index];
512 mQueue.RemoveElementAt(index);
513 delete olddata;
514 }
515 }
517 int32_t IndexOf(nsCString &aStr)
518 {
519 for (uint32_t i = 0; i < mQueue.Length(); i++)
520 if (aStr == (mQueue[i])->mAddress)
521 return i;
522 return -1;
523 }
525 int32_t IndexOf(WebSocketChannel *aChannel)
526 {
527 for (uint32_t i = 0; i < mQueue.Length(); i++)
528 if (aChannel == (mQueue[i])->mChannel)
529 return i;
530 return -1;
531 }
533 // SessionCount might be decremented from the main or the socket
534 // thread, so manage it with atomic counters
535 Atomic<int32_t> mSessionCount;
537 // Queue for websockets that have not completed connecting yet.
538 // The first nsOpenConn with a given address will be either be
539 // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same
540 // hostname must be CONNECTING_QUEUED.
541 //
542 // We could hash hostnames instead of using a single big vector here, but the
543 // dataset is expected to be small.
544 nsTArray<nsOpenConn *> mQueue;
546 FailDelayManager mFailures;
548 static nsWSAdmissionManager *sManager;
549 static StaticMutex sLock;
550 };
552 nsWSAdmissionManager *nsWSAdmissionManager::sManager;
553 StaticMutex nsWSAdmissionManager::sLock;
555 //-----------------------------------------------------------------------------
556 // CallOnMessageAvailable
557 //-----------------------------------------------------------------------------
559 class CallOnMessageAvailable MOZ_FINAL : public nsIRunnable
560 {
561 public:
562 NS_DECL_THREADSAFE_ISUPPORTS
564 CallOnMessageAvailable(WebSocketChannel *aChannel,
565 nsCString &aData,
566 int32_t aLen)
567 : mChannel(aChannel),
568 mData(aData),
569 mLen(aLen) {}
571 NS_IMETHOD Run()
572 {
573 MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
575 if (mLen < 0)
576 mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData);
577 else
578 mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext, mData);
579 return NS_OK;
580 }
582 private:
583 ~CallOnMessageAvailable() {}
585 nsRefPtr<WebSocketChannel> mChannel;
586 nsCString mData;
587 int32_t mLen;
588 };
589 NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable)
591 //-----------------------------------------------------------------------------
592 // CallOnStop
593 //-----------------------------------------------------------------------------
595 class CallOnStop MOZ_FINAL : public nsIRunnable
596 {
597 public:
598 NS_DECL_THREADSAFE_ISUPPORTS
600 CallOnStop(WebSocketChannel *aChannel,
601 nsresult aReason)
602 : mChannel(aChannel),
603 mReason(aReason) {}
605 NS_IMETHOD Run()
606 {
607 MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
609 nsWSAdmissionManager::OnStopSession(mChannel, mReason);
611 if (mChannel->mListener) {
612 mChannel->mListener->OnStop(mChannel->mContext, mReason);
613 mChannel->mListener = nullptr;
614 mChannel->mContext = nullptr;
615 }
616 return NS_OK;
617 }
619 private:
620 ~CallOnStop() {}
622 nsRefPtr<WebSocketChannel> mChannel;
623 nsresult mReason;
624 };
625 NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable)
627 //-----------------------------------------------------------------------------
628 // CallOnServerClose
629 //-----------------------------------------------------------------------------
631 class CallOnServerClose MOZ_FINAL : public nsIRunnable
632 {
633 public:
634 NS_DECL_THREADSAFE_ISUPPORTS
636 CallOnServerClose(WebSocketChannel *aChannel,
637 uint16_t aCode,
638 nsCString &aReason)
639 : mChannel(aChannel),
640 mCode(aCode),
641 mReason(aReason) {}
643 NS_IMETHOD Run()
644 {
645 MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
647 mChannel->mListener->OnServerClose(mChannel->mContext, mCode, mReason);
648 return NS_OK;
649 }
651 private:
652 ~CallOnServerClose() {}
654 nsRefPtr<WebSocketChannel> mChannel;
655 uint16_t mCode;
656 nsCString mReason;
657 };
658 NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
660 //-----------------------------------------------------------------------------
661 // CallAcknowledge
662 //-----------------------------------------------------------------------------
664 class CallAcknowledge MOZ_FINAL : public nsIRunnable
665 {
666 public:
667 NS_DECL_THREADSAFE_ISUPPORTS
669 CallAcknowledge(WebSocketChannel *aChannel,
670 uint32_t aSize)
671 : mChannel(aChannel),
672 mSize(aSize) {}
674 NS_IMETHOD Run()
675 {
676 MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
678 LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
679 mChannel->mListener->OnAcknowledge(mChannel->mContext, mSize);
680 return NS_OK;
681 }
683 private:
684 ~CallAcknowledge() {}
686 nsRefPtr<WebSocketChannel> mChannel;
687 uint32_t mSize;
688 };
689 NS_IMPL_ISUPPORTS(CallAcknowledge, nsIRunnable)
691 //-----------------------------------------------------------------------------
692 // CallOnTransportAvailable
693 //-----------------------------------------------------------------------------
695 class CallOnTransportAvailable MOZ_FINAL : public nsIRunnable
696 {
697 public:
698 NS_DECL_THREADSAFE_ISUPPORTS
700 CallOnTransportAvailable(WebSocketChannel *aChannel,
701 nsISocketTransport *aTransport,
702 nsIAsyncInputStream *aSocketIn,
703 nsIAsyncOutputStream *aSocketOut)
704 : mChannel(aChannel),
705 mTransport(aTransport),
706 mSocketIn(aSocketIn),
707 mSocketOut(aSocketOut) {}
709 NS_IMETHOD Run()
710 {
711 LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
712 return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
713 }
715 private:
716 ~CallOnTransportAvailable() {}
718 nsRefPtr<WebSocketChannel> mChannel;
719 nsCOMPtr<nsISocketTransport> mTransport;
720 nsCOMPtr<nsIAsyncInputStream> mSocketIn;
721 nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
722 };
723 NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable)
725 //-----------------------------------------------------------------------------
726 // OutboundMessage
727 //-----------------------------------------------------------------------------
729 enum WsMsgType {
730 kMsgTypeString = 0,
731 kMsgTypeBinaryString,
732 kMsgTypeStream,
733 kMsgTypePing,
734 kMsgTypePong,
735 kMsgTypeFin
736 };
738 static const char* msgNames[] = {
739 "text",
740 "binaryString",
741 "binaryStream",
742 "ping",
743 "pong",
744 "close"
745 };
747 class OutboundMessage
748 {
749 public:
750 OutboundMessage(WsMsgType type, nsCString *str)
751 : mMsgType(type)
752 {
753 MOZ_COUNT_CTOR(OutboundMessage);
754 mMsg.pString = str;
755 mLength = str ? str->Length() : 0;
756 }
758 OutboundMessage(nsIInputStream *stream, uint32_t length)
759 : mMsgType(kMsgTypeStream), mLength(length)
760 {
761 MOZ_COUNT_CTOR(OutboundMessage);
762 mMsg.pStream = stream;
763 mMsg.pStream->AddRef();
764 }
766 ~OutboundMessage() {
767 MOZ_COUNT_DTOR(OutboundMessage);
768 switch (mMsgType) {
769 case kMsgTypeString:
770 case kMsgTypeBinaryString:
771 case kMsgTypePing:
772 case kMsgTypePong:
773 delete mMsg.pString;
774 break;
775 case kMsgTypeStream:
776 // for now this only gets hit if msg deleted w/o being sent
777 if (mMsg.pStream) {
778 mMsg.pStream->Close();
779 mMsg.pStream->Release();
780 }
781 break;
782 case kMsgTypeFin:
783 break; // do-nothing: avoid compiler warning
784 }
785 }
787 WsMsgType GetMsgType() const { return mMsgType; }
788 int32_t Length() const { return mLength; }
790 uint8_t* BeginWriting() {
791 NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream,
792 "Stream should have been converted to string by now");
793 return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginWriting() : nullptr);
794 }
796 uint8_t* BeginReading() {
797 NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream,
798 "Stream should have been converted to string by now");
799 return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginReading() : nullptr);
800 }
802 nsresult ConvertStreamToString()
803 {
804 NS_ABORT_IF_FALSE(mMsgType == kMsgTypeStream, "Not a stream!");
806 #ifdef DEBUG
807 // Make sure we got correct length from Blob
808 uint64_t bytes;
809 mMsg.pStream->Available(&bytes);
810 NS_ASSERTION(bytes == mLength, "Stream length != blob length!");
811 #endif
813 nsAutoPtr<nsCString> temp(new nsCString());
814 nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
816 NS_ENSURE_SUCCESS(rv, rv);
818 mMsg.pStream->Close();
819 mMsg.pStream->Release();
820 mMsg.pString = temp.forget();
821 mMsgType = kMsgTypeBinaryString;
823 return NS_OK;
824 }
826 private:
827 union {
828 nsCString *pString;
829 nsIInputStream *pStream;
830 } mMsg;
831 WsMsgType mMsgType;
832 uint32_t mLength;
833 };
835 //-----------------------------------------------------------------------------
836 // OutboundEnqueuer
837 //-----------------------------------------------------------------------------
839 class OutboundEnqueuer MOZ_FINAL : public nsIRunnable
840 {
841 public:
842 NS_DECL_THREADSAFE_ISUPPORTS
844 OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg)
845 : mChannel(aChannel), mMessage(aMsg) {}
847 NS_IMETHOD Run()
848 {
849 mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
850 return NS_OK;
851 }
853 private:
854 ~OutboundEnqueuer() {}
856 nsRefPtr<WebSocketChannel> mChannel;
857 OutboundMessage *mMessage;
858 };
859 NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable)
861 //-----------------------------------------------------------------------------
862 // nsWSCompression
863 //
864 // similar to nsDeflateConverter except for the mandatory FLUSH calls
865 // required by websocket and the absence of the deflate termination
866 // block which is appropriate because it would create data bytes after
867 // sending the websockets CLOSE message.
868 //-----------------------------------------------------------------------------
870 class nsWSCompression
871 {
872 public:
873 nsWSCompression(nsIStreamListener *aListener,
874 nsISupports *aContext)
875 : mActive(false),
876 mContext(aContext),
877 mListener(aListener)
878 {
879 MOZ_COUNT_CTOR(nsWSCompression);
881 mZlib.zalloc = allocator;
882 mZlib.zfree = destructor;
883 mZlib.opaque = Z_NULL;
885 // Initialize the compressor - these are all the normal zlib
886 // defaults except window size is set to -15 instead of +15.
887 // This is the zlib way of specifying raw RFC 1951 output instead
888 // of the zlib rfc 1950 format which has a 2 byte header and
889 // adler checksum as a trailer
891 nsresult rv;
892 mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
893 if (NS_SUCCEEDED(rv) && aContext && aListener &&
894 deflateInit2(&mZlib, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8,
895 Z_DEFAULT_STRATEGY) == Z_OK) {
896 mActive = true;
897 }
898 }
900 ~nsWSCompression()
901 {
902 MOZ_COUNT_DTOR(nsWSCompression);
904 if (mActive)
905 deflateEnd(&mZlib);
906 }
908 bool Active()
909 {
910 return mActive;
911 }
913 nsresult Deflate(uint8_t *buf1, uint32_t buf1Len,
914 uint8_t *buf2, uint32_t buf2Len)
915 {
916 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
917 "not socket thread");
918 NS_ABORT_IF_FALSE(mActive, "not active");
920 mZlib.avail_out = kBufferLen;
921 mZlib.next_out = mBuffer;
922 mZlib.avail_in = buf1Len;
923 mZlib.next_in = buf1;
925 nsresult rv;
927 while (mZlib.avail_in > 0) {
928 deflate(&mZlib, (buf2Len > 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH);
929 rv = PushData();
930 if (NS_FAILED(rv))
931 return rv;
932 mZlib.avail_out = kBufferLen;
933 mZlib.next_out = mBuffer;
934 }
936 mZlib.avail_in = buf2Len;
937 mZlib.next_in = buf2;
939 while (mZlib.avail_in > 0) {
940 deflate(&mZlib, Z_SYNC_FLUSH);
941 rv = PushData();
942 if (NS_FAILED(rv))
943 return rv;
944 mZlib.avail_out = kBufferLen;
945 mZlib.next_out = mBuffer;
946 }
948 return NS_OK;
949 }
951 private:
953 // use zlib data types
954 static void *allocator(void *opaque, uInt items, uInt size)
955 {
956 return moz_xmalloc(items * size);
957 }
959 static void destructor(void *opaque, void *addr)
960 {
961 moz_free(addr);
962 }
964 nsresult PushData()
965 {
966 uint32_t bytesToWrite = kBufferLen - mZlib.avail_out;
967 if (bytesToWrite > 0) {
968 mStream->ShareData(reinterpret_cast<char *>(mBuffer), bytesToWrite);
969 nsresult rv =
970 mListener->OnDataAvailable(nullptr, mContext, mStream, 0, bytesToWrite);
971 if (NS_FAILED(rv))
972 return rv;
973 }
974 return NS_OK;
975 }
977 bool mActive;
978 z_stream mZlib;
979 nsCOMPtr<nsIStringInputStream> mStream;
981 nsISupports *mContext; /* weak ref */
982 nsIStreamListener *mListener; /* weak ref */
984 const static int32_t kBufferLen = 4096;
985 uint8_t mBuffer[kBufferLen];
986 };
988 //-----------------------------------------------------------------------------
989 // WebSocketChannel
990 //-----------------------------------------------------------------------------
992 uint32_t WebSocketChannel::sSerialSeed = 0;
994 WebSocketChannel::WebSocketChannel() :
995 mPort(0),
996 mCloseTimeout(20000),
997 mOpenTimeout(20000),
998 mConnecting(NOT_CONNECTING),
999 mMaxConcurrentConnections(200),
1000 mGotUpgradeOK(0),
1001 mRecvdHttpUpgradeTransport(0),
1002 mRequestedClose(0),
1003 mClientClosed(0),
1004 mServerClosed(0),
1005 mStopped(0),
1006 mCalledOnStop(0),
1007 mPingOutstanding(0),
1008 mAllowCompression(1),
1009 mAutoFollowRedirects(0),
1010 mReleaseOnTransmit(0),
1011 mTCPClosed(0),
1012 mOpenedHttpChannel(0),
1013 mDataStarted(0),
1014 mIncrementedSessionCount(0),
1015 mDecrementedSessionCount(0),
1016 mMaxMessageSize(INT32_MAX),
1017 mStopOnClose(NS_OK),
1018 mServerCloseCode(CLOSE_ABNORMAL),
1019 mScriptCloseCode(0),
1020 mFragmentOpcode(kContinuation),
1021 mFragmentAccumulator(0),
1022 mBuffered(0),
1023 mBufferSize(kIncomingBufferInitialSize),
1024 mCurrentOut(nullptr),
1025 mCurrentOutSent(0),
1026 mCompressor(nullptr),
1027 mDynamicOutputSize(0),
1028 mDynamicOutput(nullptr),
1029 mPrivateBrowsing(false),
1030 mConnectionLogService(nullptr),
1031 mCountRecv(0),
1032 mCountSent(0),
1033 mAppId(NECKO_NO_APP_ID)
1034 {
1035 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
1037 LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
1039 nsWSAdmissionManager::Init();
1041 mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
1043 nsresult rv;
1044 mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
1045 if (NS_FAILED(rv))
1046 LOG(("Failed to initiate dashboard service."));
1048 mSerial = sSerialSeed++;
1049 }
1051 WebSocketChannel::~WebSocketChannel()
1052 {
1053 LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
1055 if (mWasOpened) {
1056 MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
1057 MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
1058 }
1059 MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
1060 MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
1062 moz_free(mBuffer);
1063 moz_free(mDynamicOutput);
1064 delete mCompressor;
1065 delete mCurrentOut;
1067 while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
1068 delete mCurrentOut;
1069 while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
1070 delete mCurrentOut;
1071 while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
1072 delete mCurrentOut;
1074 nsCOMPtr<nsIThread> mainThread;
1075 nsIURI *forgettable;
1076 NS_GetMainThread(getter_AddRefs(mainThread));
1078 if (mURI) {
1079 mURI.forget(&forgettable);
1080 NS_ProxyRelease(mainThread, forgettable, false);
1081 }
1083 if (mOriginalURI) {
1084 mOriginalURI.forget(&forgettable);
1085 NS_ProxyRelease(mainThread, forgettable, false);
1086 }
1088 if (mListener) {
1089 nsIWebSocketListener *forgettableListener;
1090 mListener.forget(&forgettableListener);
1091 NS_ProxyRelease(mainThread, forgettableListener, false);
1092 }
1094 if (mContext) {
1095 nsISupports *forgettableContext;
1096 mContext.forget(&forgettableContext);
1097 NS_ProxyRelease(mainThread, forgettableContext, false);
1098 }
1100 if (mLoadGroup) {
1101 nsILoadGroup *forgettableGroup;
1102 mLoadGroup.forget(&forgettableGroup);
1103 NS_ProxyRelease(mainThread, forgettableGroup, false);
1104 }
1105 }
1107 void
1108 WebSocketChannel::Shutdown()
1109 {
1110 nsWSAdmissionManager::Shutdown();
1111 }
1113 void
1114 WebSocketChannel::BeginOpen()
1115 {
1116 LOG(("WebSocketChannel::BeginOpen() %p\n", this));
1117 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
1119 nsresult rv;
1121 // Important that we set CONNECTING_IN_PROGRESS before any call to
1122 // AbortSession here: ensures that any remaining queued connection(s) are
1123 // scheduled in OnStopSession
1124 mConnecting = CONNECTING_IN_PROGRESS;
1126 if (mRedirectCallback) {
1127 LOG(("WebSocketChannel::BeginOpen: Resuming Redirect\n"));
1128 rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
1129 mRedirectCallback = nullptr;
1130 return;
1131 }
1133 nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
1134 if (NS_FAILED(rv)) {
1135 LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
1136 AbortSession(NS_ERROR_UNEXPECTED);
1137 return;
1138 }
1140 if (localChannel) {
1141 bool isInBrowser;
1142 NS_GetAppInfo(localChannel, &mAppId, &isInBrowser);
1143 }
1145 #ifdef MOZ_WIDGET_GONK
1146 if (mAppId != NECKO_NO_APP_ID) {
1147 nsCOMPtr<nsINetworkInterface> activeNetwork;
1148 GetActiveNetworkInterface(activeNetwork);
1149 mActiveNetwork =
1150 new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork);
1151 }
1152 #endif
1154 rv = localChannel->AsyncOpen(this, mHttpChannel);
1155 if (NS_FAILED(rv)) {
1156 LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
1157 AbortSession(NS_ERROR_CONNECTION_REFUSED);
1158 return;
1159 }
1160 mOpenedHttpChannel = 1;
1162 mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1163 if (NS_FAILED(rv)) {
1164 LOG(("WebSocketChannel::BeginOpen: cannot create open timer\n"));
1165 AbortSession(NS_ERROR_UNEXPECTED);
1166 return;
1167 }
1169 rv = mOpenTimer->InitWithCallback(this, mOpenTimeout,
1170 nsITimer::TYPE_ONE_SHOT);
1171 if (NS_FAILED(rv)) {
1172 LOG(("WebSocketChannel::BeginOpen: cannot initialize open timer\n"));
1173 AbortSession(NS_ERROR_UNEXPECTED);
1174 return;
1175 }
1176 }
1178 bool
1179 WebSocketChannel::IsPersistentFramePtr()
1180 {
1181 return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
1182 }
1184 // Extends the internal buffer by count and returns the total
1185 // amount of data available for read
1186 //
1187 // Accumulated fragment size is passed in instead of using the member
1188 // variable beacuse when transitioning from the stack to the persistent
1189 // read buffer we want to explicitly include them in the buffer instead
1190 // of as already existing data.
1191 bool
1192 WebSocketChannel::UpdateReadBuffer(uint8_t *buffer, uint32_t count,
1193 uint32_t accumulatedFragments,
1194 uint32_t *available)
1195 {
1196 LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n",
1197 this, buffer, count));
1199 if (!mBuffered)
1200 mFramePtr = mBuffer;
1202 NS_ABORT_IF_FALSE(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
1203 NS_ABORT_IF_FALSE(mFramePtr - accumulatedFragments >= mBuffer,
1204 "reserved FramePtr bad");
1206 if (mBuffered + count <= mBufferSize) {
1207 // append to existing buffer
1208 LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
1209 } else if (mBuffered + count -
1210 (mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) {
1211 // make room in existing buffer by shifting unused data to start
1212 mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
1213 LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
1214 ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
1215 mFramePtr = mBuffer + accumulatedFragments;
1216 } else {
1217 // existing buffer is not sufficient, extend it
1218 mBufferSize += count + 8192 + mBufferSize/3;
1219 LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
1220 uint8_t *old = mBuffer;
1221 mBuffer = (uint8_t *)moz_realloc(mBuffer, mBufferSize);
1222 if (!mBuffer) {
1223 mBuffer = old;
1224 return false;
1225 }
1226 mFramePtr = mBuffer + (mFramePtr - old);
1227 }
1229 ::memcpy(mBuffer + mBuffered, buffer, count);
1230 mBuffered += count;
1232 if (available)
1233 *available = mBuffered - (mFramePtr - mBuffer);
1235 return true;
1236 }
1238 nsresult
1239 WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
1240 {
1241 LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
1242 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
1244 // The purpose of ping/pong is to actively probe the peer so that an
1245 // unreachable peer is not mistaken for a period of idleness. This
1246 // implementation accepts any application level read activity as a sign of
1247 // life, it does not necessarily have to be a pong.
1248 ResetPingTimer();
1250 uint32_t avail;
1252 if (!mBuffered) {
1253 // Most of the time we can process right off the stack buffer without
1254 // having to accumulate anything
1255 mFramePtr = buffer;
1256 avail = count;
1257 } else {
1258 if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
1259 return NS_ERROR_FILE_TOO_BIG;
1260 }
1261 }
1263 uint8_t *payload;
1264 uint32_t totalAvail = avail;
1266 while (avail >= 2) {
1267 int64_t payloadLength64 = mFramePtr[1] & 0x7F;
1268 uint8_t finBit = mFramePtr[0] & kFinalFragBit;
1269 uint8_t rsvBits = mFramePtr[0] & 0x70;
1270 uint8_t maskBit = mFramePtr[1] & kMaskBit;
1271 uint8_t opcode = mFramePtr[0] & 0x0F;
1273 uint32_t framingLength = 2;
1274 if (maskBit)
1275 framingLength += 4;
1277 if (payloadLength64 < 126) {
1278 if (avail < framingLength)
1279 break;
1280 } else if (payloadLength64 == 126) {
1281 // 16 bit length field
1282 framingLength += 2;
1283 if (avail < framingLength)
1284 break;
1286 payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
1287 } else {
1288 // 64 bit length
1289 framingLength += 8;
1290 if (avail < framingLength)
1291 break;
1293 if (mFramePtr[2] & 0x80) {
1294 // Section 4.2 says that the most significant bit MUST be
1295 // 0. (i.e. this is really a 63 bit value)
1296 LOG(("WebSocketChannel:: high bit of 64 bit length set"));
1297 return NS_ERROR_ILLEGAL_VALUE;
1298 }
1300 // copy this in case it is unaligned
1301 payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
1302 }
1304 payload = mFramePtr + framingLength;
1305 avail -= framingLength;
1307 LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n",
1308 payloadLength64, avail));
1310 if (payloadLength64 + mFragmentAccumulator > mMaxMessageSize) {
1311 return NS_ERROR_FILE_TOO_BIG;
1312 }
1313 uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
1315 if (avail < payloadLength)
1316 break;
1318 LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
1319 opcode));
1321 if (maskBit) {
1322 // This is unexpected - the server does not generally send masked
1323 // frames to the client, but it is allowed
1324 LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
1326 uint32_t mask = NetworkEndian::readUint32(payload - 4);
1327 ApplyMask(mask, payload, payloadLength);
1328 }
1330 // Control codes are required to have the fin bit set
1331 if (!finBit && (opcode & kControlFrameMask)) {
1332 LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
1333 return NS_ERROR_ILLEGAL_VALUE;
1334 }
1336 if (rsvBits) {
1337 LOG(("WebSocketChannel:: unexpected reserved bits %x\n", rsvBits));
1338 return NS_ERROR_ILLEGAL_VALUE;
1339 }
1341 if (!finBit || opcode == kContinuation) {
1342 // This is part of a fragment response
1344 // Only the first frame has a non zero op code: Make sure we don't see a
1345 // first frame while some old fragments are open
1346 if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) {
1347 LOG(("WebSocketChannel:: nested fragments\n"));
1348 return NS_ERROR_ILLEGAL_VALUE;
1349 }
1351 LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
1353 if (opcode == kContinuation) {
1355 // Make sure this continuation fragment isn't the first fragment
1356 if (mFragmentOpcode == kContinuation) {
1357 LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
1358 return NS_ERROR_ILLEGAL_VALUE;
1359 }
1361 // For frag > 1 move the data body back on top of the headers
1362 // so we have contiguous stream of data
1363 NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload,
1364 "payload offset from frameptr wrong");
1365 ::memmove(mFramePtr, payload, avail);
1366 payload = mFramePtr;
1367 if (mBuffered)
1368 mBuffered -= framingLength;
1369 } else {
1370 mFragmentOpcode = opcode;
1371 }
1373 if (finBit) {
1374 LOG(("WebSocketChannel:: Finalizing Fragment\n"));
1375 payload -= mFragmentAccumulator;
1376 payloadLength += mFragmentAccumulator;
1377 avail += mFragmentAccumulator;
1378 mFragmentAccumulator = 0;
1379 opcode = mFragmentOpcode;
1380 // reset to detect if next message illegally starts with continuation
1381 mFragmentOpcode = kContinuation;
1382 } else {
1383 opcode = kContinuation;
1384 mFragmentAccumulator += payloadLength;
1385 }
1386 } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
1387 // This frame is not part of a fragment sequence but we
1388 // have an open fragment.. it must be a control code or else
1389 // we have a problem
1390 LOG(("WebSocketChannel:: illegal fragment sequence\n"));
1391 return NS_ERROR_ILLEGAL_VALUE;
1392 }
1394 if (mServerClosed) {
1395 LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
1396 opcode));
1397 // nop
1398 } else if (mStopped) {
1399 LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
1400 opcode));
1401 } else if (opcode == kText) {
1402 LOG(("WebSocketChannel:: text frame received\n"));
1403 if (mListener) {
1404 nsCString utf8Data;
1405 if (!utf8Data.Assign((const char *)payload, payloadLength,
1406 mozilla::fallible_t()))
1407 return NS_ERROR_OUT_OF_MEMORY;
1409 // Section 8.1 says to fail connection if invalid utf-8 in text message
1410 if (!IsUTF8(utf8Data, false)) {
1411 LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
1412 return NS_ERROR_CANNOT_CONVERT_DATA;
1413 }
1415 mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
1416 NS_DISPATCH_NORMAL);
1417 if (mConnectionLogService && !mPrivateBrowsing) {
1418 mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
1419 LOG(("Added new msg received for %s", mHost.get()));
1420 }
1421 }
1422 } else if (opcode & kControlFrameMask) {
1423 // control frames
1424 if (payloadLength > 125) {
1425 LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
1426 opcode, payloadLength));
1427 return NS_ERROR_ILLEGAL_VALUE;
1428 }
1430 if (opcode == kClose) {
1431 LOG(("WebSocketChannel:: close received\n"));
1432 mServerClosed = 1;
1434 mServerCloseCode = CLOSE_NO_STATUS;
1435 if (payloadLength >= 2) {
1436 mServerCloseCode = NetworkEndian::readUint16(payload);
1437 LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
1438 uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
1439 if (msglen > 0) {
1440 mServerCloseReason.SetLength(msglen);
1441 memcpy(mServerCloseReason.BeginWriting(),
1442 (const char *)payload + 2, msglen);
1444 // section 8.1 says to replace received non utf-8 sequences
1445 // (which are non-conformant to send) with u+fffd,
1446 // but secteam feels that silently rewriting messages is
1447 // inappropriate - so we will fail the connection instead.
1448 if (!IsUTF8(mServerCloseReason, false)) {
1449 LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
1450 return NS_ERROR_CANNOT_CONVERT_DATA;
1451 }
1453 LOG(("WebSocketChannel:: close msg %s\n",
1454 mServerCloseReason.get()));
1455 }
1456 }
1458 if (mCloseTimer) {
1459 mCloseTimer->Cancel();
1460 mCloseTimer = nullptr;
1461 }
1462 if (mListener) {
1463 mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
1464 mServerCloseReason),
1465 NS_DISPATCH_NORMAL);
1466 }
1468 if (mClientClosed)
1469 ReleaseSession();
1470 } else if (opcode == kPing) {
1471 LOG(("WebSocketChannel:: ping received\n"));
1472 GeneratePong(payload, payloadLength);
1473 } else if (opcode == kPong) {
1474 // opcode kPong: the mere act of receiving the packet is all we need
1475 // to do for the pong to trigger the activity timers
1476 LOG(("WebSocketChannel:: pong received\n"));
1477 } else {
1478 /* unknown control frame opcode */
1479 LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
1480 return NS_ERROR_ILLEGAL_VALUE;
1481 }
1483 if (mFragmentAccumulator) {
1484 // Remove the control frame from the stream so we have a contiguous
1485 // data buffer of reassembled fragments
1486 LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
1487 NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload,
1488 "payload offset from frameptr wrong");
1489 ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
1490 payload = mFramePtr;
1491 avail -= payloadLength;
1492 if (mBuffered)
1493 mBuffered -= framingLength + payloadLength;
1494 payloadLength = 0;
1495 }
1496 } else if (opcode == kBinary) {
1497 LOG(("WebSocketChannel:: binary frame received\n"));
1498 if (mListener) {
1499 nsCString binaryData((const char *)payload, payloadLength);
1500 mTargetThread->Dispatch(new CallOnMessageAvailable(this, binaryData,
1501 payloadLength),
1502 NS_DISPATCH_NORMAL);
1503 // To add the header to 'Networking Dashboard' log
1504 if (mConnectionLogService && !mPrivateBrowsing) {
1505 mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
1506 LOG(("Added new received msg for %s", mHost.get()));
1507 }
1508 }
1509 } else if (opcode != kContinuation) {
1510 /* unknown opcode */
1511 LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
1512 return NS_ERROR_ILLEGAL_VALUE;
1513 }
1515 mFramePtr = payload + payloadLength;
1516 avail -= payloadLength;
1517 totalAvail = avail;
1518 }
1520 // Adjust the stateful buffer. If we were operating off the stack and
1521 // now have a partial message then transition to the buffer, or if
1522 // we were working off the buffer but no longer have any active state
1523 // then transition to the stack
1524 if (!IsPersistentFramePtr()) {
1525 mBuffered = 0;
1527 if (mFragmentAccumulator) {
1528 LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
1530 if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
1531 totalAvail + mFragmentAccumulator, 0, nullptr)) {
1532 return NS_ERROR_FILE_TOO_BIG;
1533 }
1535 // UpdateReadBuffer will reset the frameptr to the beginning
1536 // of new saved state, so we need to skip past processed framgents
1537 mFramePtr += mFragmentAccumulator;
1538 } else if (totalAvail) {
1539 LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
1540 if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
1541 return NS_ERROR_FILE_TOO_BIG;
1542 }
1543 }
1544 } else if (!mFragmentAccumulator && !totalAvail) {
1545 // If we were working off a saved buffer state and there is no partial
1546 // frame or fragment in process, then revert to stack behavior
1547 LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
1548 mBuffered = 0;
1550 // release memory if we've been processing a large message
1551 if (mBufferSize > kIncomingBufferStableSize) {
1552 mBufferSize = kIncomingBufferStableSize;
1553 moz_free(mBuffer);
1554 mBuffer = (uint8_t *)moz_xmalloc(mBufferSize);
1555 }
1556 }
1557 return NS_OK;
1558 }
1560 void
1561 WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
1562 {
1563 if (!data || len == 0)
1564 return;
1566 // Optimally we want to apply the mask 32 bits at a time,
1567 // but the buffer might not be alligned. So we first deal with
1568 // 0 to 3 bytes of preamble individually
1570 while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
1571 *data ^= mask >> 24;
1572 mask = RotateLeft(mask, 8);
1573 data++;
1574 len--;
1575 }
1577 // perform mask on full words of data
1579 uint32_t *iData = (uint32_t *) data;
1580 uint32_t *end = iData + (len / 4);
1581 NetworkEndian::writeUint32(&mask, mask);
1582 for (; iData < end; iData++)
1583 *iData ^= mask;
1584 mask = NetworkEndian::readUint32(&mask);
1585 data = (uint8_t *)iData;
1586 len = len % 4;
1588 // There maybe up to 3 trailing bytes that need to be dealt with
1589 // individually
1591 while (len) {
1592 *data ^= mask >> 24;
1593 mask = RotateLeft(mask, 8);
1594 data++;
1595 len--;
1596 }
1597 }
1599 void
1600 WebSocketChannel::GeneratePing()
1601 {
1602 nsCString *buf = new nsCString();
1603 buf->Assign("PING");
1604 EnqueueOutgoingMessage(mOutgoingPingMessages,
1605 new OutboundMessage(kMsgTypePing, buf));
1606 }
1608 void
1609 WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len)
1610 {
1611 nsCString *buf = new nsCString();
1612 buf->SetLength(len);
1613 if (buf->Length() < len) {
1614 LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
1615 delete buf;
1616 return;
1617 }
1619 memcpy(buf->BeginWriting(), payload, len);
1620 EnqueueOutgoingMessage(mOutgoingPongMessages,
1621 new OutboundMessage(kMsgTypePong, buf));
1622 }
1624 void
1625 WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue,
1626 OutboundMessage *aMsg)
1627 {
1628 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
1630 LOG(("WebSocketChannel::EnqueueOutgoingMessage %p "
1631 "queueing msg %p [type=%s len=%d]\n",
1632 this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
1634 aQueue.Push(aMsg);
1635 OnOutputStreamReady(mSocketOut);
1636 }
1639 uint16_t
1640 WebSocketChannel::ResultToCloseCode(nsresult resultCode)
1641 {
1642 if (NS_SUCCEEDED(resultCode))
1643 return CLOSE_NORMAL;
1645 switch (resultCode) {
1646 case NS_ERROR_FILE_TOO_BIG:
1647 case NS_ERROR_OUT_OF_MEMORY:
1648 return CLOSE_TOO_LARGE;
1649 case NS_ERROR_CANNOT_CONVERT_DATA:
1650 return CLOSE_INVALID_PAYLOAD;
1651 case NS_ERROR_UNEXPECTED:
1652 return CLOSE_INTERNAL_ERROR;
1653 default:
1654 return CLOSE_PROTOCOL_ERROR;
1655 }
1656 }
1658 void
1659 WebSocketChannel::PrimeNewOutgoingMessage()
1660 {
1661 LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
1662 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
1663 NS_ABORT_IF_FALSE(!mCurrentOut, "Current message in progress");
1665 nsresult rv = NS_OK;
1667 mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront();
1668 if (mCurrentOut) {
1669 NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePong,
1670 "Not pong message!");
1671 } else {
1672 mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront();
1673 if (mCurrentOut)
1674 NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePing,
1675 "Not ping message!");
1676 else
1677 mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront();
1678 }
1680 if (!mCurrentOut)
1681 return;
1683 WsMsgType msgType = mCurrentOut->GetMsgType();
1685 LOG(("WebSocketChannel::PrimeNewOutgoingMessage "
1686 "%p found queued msg %p [type=%s len=%d]\n",
1687 this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
1689 mCurrentOutSent = 0;
1690 mHdrOut = mOutHeader;
1692 uint8_t *payload = nullptr;
1694 if (msgType == kMsgTypeFin) {
1695 // This is a demand to create a close message
1696 if (mClientClosed) {
1697 DeleteCurrentOutGoingMessage();
1698 PrimeNewOutgoingMessage();
1699 return;
1700 }
1702 mClientClosed = 1;
1703 mOutHeader[0] = kFinalFragBit | kClose;
1704 mOutHeader[1] = kMaskBit;
1706 // payload is offset 6 including 4 for the mask
1707 payload = mOutHeader + 6;
1709 // The close reason code sits in the first 2 bytes of payload
1710 // If the channel user provided a code and reason during Close()
1711 // and there isn't an internal error, use that.
1712 if (NS_SUCCEEDED(mStopOnClose)) {
1713 if (mScriptCloseCode) {
1714 NetworkEndian::writeUint16(payload, mScriptCloseCode);
1715 mOutHeader[1] += 2;
1716 mHdrOutToSend = 8;
1717 if (!mScriptCloseReason.IsEmpty()) {
1718 NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123,
1719 "Close Reason Too Long");
1720 mOutHeader[1] += mScriptCloseReason.Length();
1721 mHdrOutToSend += mScriptCloseReason.Length();
1722 memcpy (payload + 2,
1723 mScriptCloseReason.BeginReading(),
1724 mScriptCloseReason.Length());
1725 }
1726 } else {
1727 // No close code/reason, so payload length = 0. We must still send mask
1728 // even though it's not used. Keep payload offset so we write mask
1729 // below.
1730 mHdrOutToSend = 6;
1731 }
1732 } else {
1733 NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
1734 mOutHeader[1] += 2;
1735 mHdrOutToSend = 8;
1736 }
1738 if (mServerClosed) {
1739 /* bidi close complete */
1740 mReleaseOnTransmit = 1;
1741 } else if (NS_FAILED(mStopOnClose)) {
1742 /* result of abort session - give up */
1743 StopSession(mStopOnClose);
1744 } else {
1745 /* wait for reciprocal close from server */
1746 mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1747 if (NS_SUCCEEDED(rv)) {
1748 mCloseTimer->InitWithCallback(this, mCloseTimeout,
1749 nsITimer::TYPE_ONE_SHOT);
1750 } else {
1751 StopSession(rv);
1752 }
1753 }
1754 } else {
1755 switch (msgType) {
1756 case kMsgTypePong:
1757 mOutHeader[0] = kFinalFragBit | kPong;
1758 break;
1759 case kMsgTypePing:
1760 mOutHeader[0] = kFinalFragBit | kPing;
1761 break;
1762 case kMsgTypeString:
1763 mOutHeader[0] = kFinalFragBit | kText;
1764 break;
1765 case kMsgTypeStream:
1766 // HACK ALERT: read in entire stream into string.
1767 // Will block socket transport thread if file is blocking.
1768 // TODO: bug 704447: don't block socket thread!
1769 rv = mCurrentOut->ConvertStreamToString();
1770 if (NS_FAILED(rv)) {
1771 AbortSession(NS_ERROR_FILE_TOO_BIG);
1772 return;
1773 }
1774 // Now we're a binary string
1775 msgType = kMsgTypeBinaryString;
1777 // no break: fall down into binary string case
1779 case kMsgTypeBinaryString:
1780 mOutHeader[0] = kFinalFragBit | kBinary;
1781 break;
1782 case kMsgTypeFin:
1783 NS_ABORT_IF_FALSE(false, "unreachable"); // avoid compiler warning
1784 break;
1785 }
1787 if (mCurrentOut->Length() < 126) {
1788 mOutHeader[1] = mCurrentOut->Length() | kMaskBit;
1789 mHdrOutToSend = 6;
1790 } else if (mCurrentOut->Length() <= 0xffff) {
1791 mOutHeader[1] = 126 | kMaskBit;
1792 NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
1793 mCurrentOut->Length());
1794 mHdrOutToSend = 8;
1795 } else {
1796 mOutHeader[1] = 127 | kMaskBit;
1797 NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
1798 mHdrOutToSend = 14;
1799 }
1800 payload = mOutHeader + mHdrOutToSend;
1801 }
1803 NS_ABORT_IF_FALSE(payload, "payload offset not found");
1805 // Perform the sending mask. Never use a zero mask
1806 uint32_t mask;
1807 do {
1808 uint8_t *buffer;
1809 nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer);
1810 if (NS_FAILED(rv)) {
1811 LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
1812 "GenerateRandomBytes failure %x\n", rv));
1813 StopSession(rv);
1814 return;
1815 }
1816 mask = * reinterpret_cast<uint32_t *>(buffer);
1817 NS_Free(buffer);
1818 } while (!mask);
1819 NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
1821 LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
1823 // We don't mask the framing, but occasionally we stick a little payload
1824 // data in the buffer used for the framing. Close frames are the current
1825 // example. This data needs to be masked, but it is never more than a
1826 // handful of bytes and might rotate the mask, so we can just do it locally.
1827 // For real data frames we ship the bulk of the payload off to ApplyMask()
1829 while (payload < (mOutHeader + mHdrOutToSend)) {
1830 *payload ^= mask >> 24;
1831 mask = RotateLeft(mask, 8);
1832 payload++;
1833 }
1835 // Mask the real message payloads
1837 ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
1839 int32_t len = mCurrentOut->Length();
1841 // for small frames, copy it all together for a contiguous write
1842 if (len && len <= kCopyBreak) {
1843 memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
1844 mHdrOutToSend += len;
1845 mCurrentOutSent = len;
1846 }
1848 if (len && mCompressor) {
1849 // assume a 1/3 reduction in size for sizing the buffer
1850 // the buffer is used multiple times if necessary
1851 uint32_t currentHeaderSize = mHdrOutToSend;
1852 mHdrOutToSend = 0;
1854 EnsureHdrOut(32 + (currentHeaderSize + len - mCurrentOutSent) / 2 * 3);
1855 mCompressor->Deflate(mOutHeader, currentHeaderSize,
1856 mCurrentOut->BeginReading() + mCurrentOutSent,
1857 len - mCurrentOutSent);
1859 // All of the compressed data now resides in {mHdrOut, mHdrOutToSend}
1860 // so do not send the body again
1861 mCurrentOutSent = len;
1862 }
1864 // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
1865 // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
1866 // coaleseced into the former for small messages or as the result of the
1867 // compression process,
1868 }
1870 void
1871 WebSocketChannel::DeleteCurrentOutGoingMessage()
1872 {
1873 delete mCurrentOut;
1874 mCurrentOut = nullptr;
1875 mCurrentOutSent = 0;
1876 }
1878 void
1879 WebSocketChannel::EnsureHdrOut(uint32_t size)
1880 {
1881 LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
1883 if (mDynamicOutputSize < size) {
1884 mDynamicOutputSize = size;
1885 mDynamicOutput =
1886 (uint8_t *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
1887 }
1889 mHdrOut = mDynamicOutput;
1890 }
1892 void
1893 WebSocketChannel::CleanupConnection()
1894 {
1895 LOG(("WebSocketChannel::CleanupConnection() %p", this));
1897 if (mLingeringCloseTimer) {
1898 mLingeringCloseTimer->Cancel();
1899 mLingeringCloseTimer = nullptr;
1900 }
1902 if (mSocketIn) {
1903 mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
1904 mSocketIn = nullptr;
1905 }
1907 if (mSocketOut) {
1908 mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
1909 mSocketOut = nullptr;
1910 }
1912 if (mTransport) {
1913 mTransport->SetSecurityCallbacks(nullptr);
1914 mTransport->SetEventSink(nullptr, nullptr);
1915 mTransport->Close(NS_BASE_STREAM_CLOSED);
1916 mTransport = nullptr;
1917 }
1919 if (mConnectionLogService && !mPrivateBrowsing) {
1920 mConnectionLogService->RemoveHost(mHost, mSerial);
1921 }
1923 DecrementSessionCount();
1924 }
1926 void
1927 WebSocketChannel::StopSession(nsresult reason)
1928 {
1929 LOG(("WebSocketChannel::StopSession() %p [%x]\n", this, reason));
1931 // normally this should be called on socket thread, but it is ok to call it
1932 // from OnStartRequest before the socket thread machine has gotten underway
1934 mStopped = 1;
1936 if (!mOpenedHttpChannel) {
1937 // The HTTP channel information will never be used in this case
1938 mChannel = nullptr;
1939 mHttpChannel = nullptr;
1940 mLoadGroup = nullptr;
1941 mCallbacks = nullptr;
1942 }
1944 if (mCloseTimer) {
1945 mCloseTimer->Cancel();
1946 mCloseTimer = nullptr;
1947 }
1949 if (mOpenTimer) {
1950 mOpenTimer->Cancel();
1951 mOpenTimer = nullptr;
1952 }
1954 if (mReconnectDelayTimer) {
1955 mReconnectDelayTimer->Cancel();
1956 mReconnectDelayTimer = nullptr;
1957 }
1959 if (mPingTimer) {
1960 mPingTimer->Cancel();
1961 mPingTimer = nullptr;
1962 }
1964 if (mSocketIn && !mTCPClosed) {
1965 // Drain, within reason, this socket. if we leave any data
1966 // unconsumed (including the tcp fin) a RST will be generated
1967 // The right thing to do here is shutdown(SHUT_WR) and then wait
1968 // a little while to see if any data comes in.. but there is no
1969 // reason to delay things for that when the websocket handshake
1970 // is supposed to guarantee a quiet connection except for that fin.
1972 char buffer[512];
1973 uint32_t count = 0;
1974 uint32_t total = 0;
1975 nsresult rv;
1976 do {
1977 total += count;
1978 rv = mSocketIn->Read(buffer, 512, &count);
1979 if (rv != NS_BASE_STREAM_WOULD_BLOCK &&
1980 (NS_FAILED(rv) || count == 0))
1981 mTCPClosed = true;
1982 } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
1983 }
1985 int32_t sessionCount = kLingeringCloseThreshold;
1986 nsWSAdmissionManager::GetSessionCount(sessionCount);
1988 if (!mTCPClosed && mTransport && sessionCount < kLingeringCloseThreshold) {
1990 // 7.1.1 says that the client SHOULD wait for the server to close the TCP
1991 // connection. This is so we can reuse port numbers before 2 MSL expires,
1992 // which is not really as much of a concern for us as the amount of state
1993 // that might be accrued by keeping this channel object around waiting for
1994 // the server. We handle the SHOULD by waiting a short time in the common
1995 // case, but not waiting in the case of high concurrency.
1996 //
1997 // Normally this will be taken care of in AbortSession() after mTCPClosed
1998 // is set when the server close arrives without waiting for the timeout to
1999 // expire.
2001 LOG(("WebSocketChannel::StopSession: Wait for Server TCP close"));
2003 nsresult rv;
2004 mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
2005 if (NS_SUCCEEDED(rv))
2006 mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
2007 nsITimer::TYPE_ONE_SHOT);
2008 else
2009 CleanupConnection();
2010 } else {
2011 CleanupConnection();
2012 }
2014 if (mCancelable) {
2015 mCancelable->Cancel(NS_ERROR_UNEXPECTED);
2016 mCancelable = nullptr;
2017 }
2019 mInflateReader = nullptr;
2020 mInflateStream = nullptr;
2022 delete mCompressor;
2023 mCompressor = nullptr;
2025 if (!mCalledOnStop) {
2026 mCalledOnStop = 1;
2027 mTargetThread->Dispatch(new CallOnStop(this, reason),
2028 NS_DISPATCH_NORMAL);
2029 }
2031 return;
2032 }
2034 void
2035 WebSocketChannel::AbortSession(nsresult reason)
2036 {
2037 LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n",
2038 this, reason, mStopped));
2040 // normally this should be called on socket thread, but it is ok to call it
2041 // from the main thread before StartWebsocketData() has completed
2043 // When we are failing we need to close the TCP connection immediately
2044 // as per 7.1.1
2045 mTCPClosed = true;
2047 if (mLingeringCloseTimer) {
2048 NS_ABORT_IF_FALSE(mStopped, "Lingering without Stop");
2049 LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
2050 CleanupConnection();
2051 return;
2052 }
2054 if (mStopped)
2055 return;
2056 mStopped = 1;
2058 if (mTransport && reason != NS_BASE_STREAM_CLOSED &&
2059 !mRequestedClose && !mClientClosed && !mServerClosed) {
2060 mRequestedClose = 1;
2061 mStopOnClose = reason;
2062 mSocketThread->Dispatch(
2063 new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
2064 nsIEventTarget::DISPATCH_NORMAL);
2065 } else {
2066 StopSession(reason);
2067 }
2068 }
2070 // ReleaseSession is called on orderly shutdown
2071 void
2072 WebSocketChannel::ReleaseSession()
2073 {
2074 LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n",
2075 this, mStopped));
2076 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
2078 if (mStopped)
2079 return;
2080 StopSession(NS_OK);
2081 }
2083 void
2084 WebSocketChannel::IncrementSessionCount()
2085 {
2086 if (!mIncrementedSessionCount) {
2087 nsWSAdmissionManager::IncrementSessionCount();
2088 mIncrementedSessionCount = 1;
2089 }
2090 }
2092 void
2093 WebSocketChannel::DecrementSessionCount()
2094 {
2095 // Make sure we decrement session count only once, and only if we incremented it.
2096 // This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount is
2097 // atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
2098 // times when they'll never be a race condition for checking/setting them.
2099 if (mIncrementedSessionCount && !mDecrementedSessionCount) {
2100 nsWSAdmissionManager::DecrementSessionCount();
2101 mDecrementedSessionCount = 1;
2102 }
2103 }
2105 nsresult
2106 WebSocketChannel::HandleExtensions()
2107 {
2108 LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
2110 nsresult rv;
2111 nsAutoCString extensions;
2113 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2115 rv = mHttpChannel->GetResponseHeader(
2116 NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
2117 if (NS_SUCCEEDED(rv)) {
2118 if (!extensions.IsEmpty()) {
2119 if (!extensions.Equals(NS_LITERAL_CSTRING("deflate-stream"))) {
2120 LOG(("WebSocketChannel::OnStartRequest: "
2121 "HTTP Sec-WebSocket-Exensions negotiated unknown value %s\n",
2122 extensions.get()));
2123 AbortSession(NS_ERROR_ILLEGAL_VALUE);
2124 return NS_ERROR_ILLEGAL_VALUE;
2125 }
2127 if (!mAllowCompression) {
2128 LOG(("WebSocketChannel::HandleExtensions: "
2129 "Recvd Compression Extension that wasn't offered\n"));
2130 AbortSession(NS_ERROR_ILLEGAL_VALUE);
2131 return NS_ERROR_ILLEGAL_VALUE;
2132 }
2134 nsCOMPtr<nsIStreamConverterService> serv =
2135 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
2136 if (NS_FAILED(rv)) {
2137 LOG(("WebSocketChannel:: Cannot find compression service\n"));
2138 AbortSession(NS_ERROR_UNEXPECTED);
2139 return NS_ERROR_UNEXPECTED;
2140 }
2142 rv = serv->AsyncConvertData("deflate", "uncompressed", this, nullptr,
2143 getter_AddRefs(mInflateReader));
2145 if (NS_FAILED(rv)) {
2146 LOG(("WebSocketChannel:: Cannot find inflate listener\n"));
2147 AbortSession(NS_ERROR_UNEXPECTED);
2148 return NS_ERROR_UNEXPECTED;
2149 }
2151 mInflateStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
2153 if (NS_FAILED(rv)) {
2154 LOG(("WebSocketChannel:: Cannot find inflate stream\n"));
2155 AbortSession(NS_ERROR_UNEXPECTED);
2156 return NS_ERROR_UNEXPECTED;
2157 }
2159 mCompressor = new nsWSCompression(this, mSocketOut);
2160 if (!mCompressor->Active()) {
2161 LOG(("WebSocketChannel:: Cannot init deflate object\n"));
2162 delete mCompressor;
2163 mCompressor = nullptr;
2164 AbortSession(NS_ERROR_UNEXPECTED);
2165 return NS_ERROR_UNEXPECTED;
2166 }
2167 mNegotiatedExtensions = extensions;
2168 }
2169 }
2171 return NS_OK;
2172 }
2174 nsresult
2175 WebSocketChannel::SetupRequest()
2176 {
2177 LOG(("WebSocketChannel::SetupRequest() %p\n", this));
2179 nsresult rv;
2181 if (mLoadGroup) {
2182 rv = mHttpChannel->SetLoadGroup(mLoadGroup);
2183 NS_ENSURE_SUCCESS(rv, rv);
2184 }
2186 rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND |
2187 nsIRequest::INHIBIT_CACHING |
2188 nsIRequest::LOAD_BYPASS_CACHE);
2189 NS_ENSURE_SUCCESS(rv, rv);
2191 // we never let websockets be blocked by head CSS/JS loads to avoid
2192 // potential deadlock where server generation of CSS/JS requires
2193 // an XHR signal.
2194 rv = mChannel->SetLoadUnblocked(true);
2195 NS_ENSURE_SUCCESS(rv, rv);
2197 // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
2198 // in lower case, so go with that. It is technically case insensitive.
2199 rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this);
2200 NS_ENSURE_SUCCESS(rv, rv);
2202 mHttpChannel->SetRequestHeader(
2203 NS_LITERAL_CSTRING("Sec-WebSocket-Version"),
2204 NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false);
2206 if (!mOrigin.IsEmpty())
2207 mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin,
2208 false);
2210 if (!mProtocol.IsEmpty())
2211 mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
2212 mProtocol, true);
2214 if (mAllowCompression)
2215 mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
2216 NS_LITERAL_CSTRING("deflate-stream"),
2217 false);
2219 uint8_t *secKey;
2220 nsAutoCString secKeyString;
2222 rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
2223 NS_ENSURE_SUCCESS(rv, rv);
2224 char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr);
2225 NS_Free(secKey);
2226 if (!b64)
2227 return NS_ERROR_OUT_OF_MEMORY;
2228 secKeyString.Assign(b64);
2229 PR_Free(b64);
2230 mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"),
2231 secKeyString, false);
2232 LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
2234 // prepare the value we expect to see in
2235 // the sec-websocket-accept response header
2236 secKeyString.AppendLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
2237 nsCOMPtr<nsICryptoHash> hasher =
2238 do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
2239 NS_ENSURE_SUCCESS(rv, rv);
2240 rv = hasher->Init(nsICryptoHash::SHA1);
2241 NS_ENSURE_SUCCESS(rv, rv);
2242 rv = hasher->Update((const uint8_t *) secKeyString.BeginWriting(),
2243 secKeyString.Length());
2244 NS_ENSURE_SUCCESS(rv, rv);
2245 rv = hasher->Finish(true, mHashedSecret);
2246 NS_ENSURE_SUCCESS(rv, rv);
2247 LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
2248 mHashedSecret.get()));
2250 return NS_OK;
2251 }
2253 nsresult
2254 WebSocketChannel::DoAdmissionDNS()
2255 {
2256 nsresult rv;
2258 nsCString hostName;
2259 rv = mURI->GetHost(hostName);
2260 NS_ENSURE_SUCCESS(rv, rv);
2261 mAddress = hostName;
2262 rv = mURI->GetPort(&mPort);
2263 NS_ENSURE_SUCCESS(rv, rv);
2264 if (mPort == -1)
2265 mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
2266 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
2267 NS_ENSURE_SUCCESS(rv, rv);
2268 nsCOMPtr<nsIThread> mainThread;
2269 NS_GetMainThread(getter_AddRefs(mainThread));
2270 MOZ_ASSERT(!mCancelable);
2271 return dns->AsyncResolve(hostName, 0, this, mainThread, getter_AddRefs(mCancelable));
2272 }
2274 nsresult
2275 WebSocketChannel::ApplyForAdmission()
2276 {
2277 LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
2279 // Websockets has a policy of 1 session at a time being allowed in the
2280 // CONNECTING state per server IP address (not hostname)
2282 // Check to see if a proxy is being used before making DNS call
2283 nsCOMPtr<nsIProtocolProxyService> pps =
2284 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
2286 if (!pps) {
2287 // go straight to DNS
2288 // expect the callback in ::OnLookupComplete
2289 LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
2290 return DoAdmissionDNS();
2291 }
2293 MOZ_ASSERT(!mCancelable);
2295 return pps->AsyncResolve(mHttpChannel,
2296 nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
2297 nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
2298 this, getter_AddRefs(mCancelable));
2299 }
2301 // Called after both OnStartRequest and OnTransportAvailable have
2302 // executed. This essentially ends the handshake and starts the websockets
2303 // protocol state machine.
2304 nsresult
2305 WebSocketChannel::StartWebsocketData()
2306 {
2307 LOG(("WebSocketChannel::StartWebsocketData() %p", this));
2308 NS_ABORT_IF_FALSE(!mDataStarted, "StartWebsocketData twice");
2309 mDataStarted = 1;
2311 // We're now done CONNECTING, which means we can now open another,
2312 // perhaps parallel, connection to the same host if one
2313 // is pending
2314 nsWSAdmissionManager::OnConnected(this);
2316 LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p\n",
2317 mListener.get()));
2319 if (mListener)
2320 mListener->OnStart(mContext);
2322 // Start keepalive ping timer, if we're using keepalive.
2323 if (mPingInterval) {
2324 nsresult rv;
2325 mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
2326 if (NS_FAILED(rv)) {
2327 NS_WARNING("unable to create ping timer. Carrying on.");
2328 } else {
2329 LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
2330 mPingInterval));
2331 mPingTimer->SetTarget(mSocketThread);
2332 mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
2333 }
2334 }
2336 return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
2337 }
2339 void
2340 WebSocketChannel::ReportConnectionTelemetry()
2341 {
2342 // 3 bits are used. high bit is for wss, middle bit for failed,
2343 // and low bit for proxy..
2344 // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
2345 // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
2347 bool didProxy = false;
2349 nsCOMPtr<nsIProxyInfo> pi;
2350 nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
2351 if (pc)
2352 pc->GetProxyInfo(getter_AddRefs(pi));
2353 if (pi) {
2354 nsAutoCString proxyType;
2355 pi->GetType(proxyType);
2356 if (!proxyType.IsEmpty() &&
2357 !proxyType.Equals(NS_LITERAL_CSTRING("direct")))
2358 didProxy = true;
2359 }
2361 uint8_t value = (mEncrypted ? (1 << 2) : 0) |
2362 (!mGotUpgradeOK ? (1 << 1) : 0) |
2363 (didProxy ? (1 << 0) : 0);
2365 LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
2366 Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value);
2367 }
2369 // nsIDNSListener
2371 NS_IMETHODIMP
2372 WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
2373 nsIDNSRecord *aRecord,
2374 nsresult aStatus)
2375 {
2376 LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n",
2377 this, aRequest, aRecord, aStatus));
2379 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2381 if (mStopped) {
2382 LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
2383 mCancelable = nullptr;
2384 return NS_OK;
2385 }
2387 mCancelable = nullptr;
2389 // These failures are not fatal - we just use the hostname as the key
2390 if (NS_FAILED(aStatus)) {
2391 LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
2393 // set host in case we got here without calling DoAdmissionDNS()
2394 mURI->GetHost(mAddress);
2395 } else {
2396 nsresult rv = aRecord->GetNextAddrAsString(mAddress);
2397 if (NS_FAILED(rv))
2398 LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
2399 }
2401 LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
2402 nsWSAdmissionManager::ConditionallyConnect(this);
2404 return NS_OK;
2405 }
2407 // nsIProtocolProxyCallback
2408 NS_IMETHODIMP
2409 WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel,
2410 nsIProxyInfo *pi, nsresult status)
2411 {
2412 if (mStopped) {
2413 LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this));
2414 mCancelable = nullptr;
2415 return NS_OK;
2416 }
2418 MOZ_ASSERT(aRequest == mCancelable);
2419 mCancelable = nullptr;
2421 nsAutoCString type;
2422 if (NS_SUCCEEDED(status) && pi &&
2423 NS_SUCCEEDED(pi->GetType(type)) &&
2424 !type.EqualsLiteral("direct")) {
2425 LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", this));
2426 // call DNS callback directly without DNS resolver
2427 OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
2428 return NS_OK;
2429 }
2431 LOG(("WebSocketChannel::OnProxyAvailable[%] checking DNS resolution\n", this));
2432 DoAdmissionDNS();
2433 return NS_OK;
2434 }
2436 // nsIInterfaceRequestor
2438 NS_IMETHODIMP
2439 WebSocketChannel::GetInterface(const nsIID & iid, void **result)
2440 {
2441 LOG(("WebSocketChannel::GetInterface() %p\n", this));
2443 if (iid.Equals(NS_GET_IID(nsIChannelEventSink)))
2444 return QueryInterface(iid, result);
2446 if (mCallbacks)
2447 return mCallbacks->GetInterface(iid, result);
2449 return NS_ERROR_FAILURE;
2450 }
2452 // nsIChannelEventSink
2454 NS_IMETHODIMP
2455 WebSocketChannel::AsyncOnChannelRedirect(
2456 nsIChannel *oldChannel,
2457 nsIChannel *newChannel,
2458 uint32_t flags,
2459 nsIAsyncVerifyRedirectCallback *callback)
2460 {
2461 LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
2462 nsresult rv;
2464 nsCOMPtr<nsIURI> newuri;
2465 rv = newChannel->GetURI(getter_AddRefs(newuri));
2466 NS_ENSURE_SUCCESS(rv, rv);
2468 // newuri is expected to be http or https
2469 bool newuriIsHttps = false;
2470 rv = newuri->SchemeIs("https", &newuriIsHttps);
2471 NS_ENSURE_SUCCESS(rv, rv);
2473 if (!mAutoFollowRedirects) {
2474 // Even if redirects configured off, still allow them for HTTP Strict
2475 // Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO)
2477 nsCOMPtr<nsIURI> clonedNewURI;
2478 rv = newuri->Clone(getter_AddRefs(clonedNewURI));
2479 NS_ENSURE_SUCCESS(rv, rv);
2481 rv = clonedNewURI->SetScheme(NS_LITERAL_CSTRING("ws"));
2482 NS_ENSURE_SUCCESS(rv, rv);
2484 nsCOMPtr<nsIURI> currentURI;
2485 rv = GetURI(getter_AddRefs(currentURI));
2486 NS_ENSURE_SUCCESS(rv, rv);
2488 // currentURI is expected to be ws or wss
2489 bool currentIsHttps = false;
2490 rv = currentURI->SchemeIs("wss", ¤tIsHttps);
2491 NS_ENSURE_SUCCESS(rv, rv);
2493 bool uriEqual = false;
2494 rv = clonedNewURI->Equals(currentURI, &uriEqual);
2495 NS_ENSURE_SUCCESS(rv, rv);
2497 // It's only a HSTS redirect if we started with non-secure, are going to
2498 // secure, and the new URI is otherwise the same as the old one.
2499 if (!(!currentIsHttps && newuriIsHttps && uriEqual)) {
2500 nsAutoCString newSpec;
2501 rv = newuri->GetSpec(newSpec);
2502 NS_ENSURE_SUCCESS(rv, rv);
2504 LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
2505 newSpec.get()));
2506 return NS_ERROR_FAILURE;
2507 }
2508 }
2510 if (mEncrypted && !newuriIsHttps) {
2511 nsAutoCString spec;
2512 if (NS_SUCCEEDED(newuri->GetSpec(spec)))
2513 LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
2514 spec.get()));
2515 return NS_ERROR_FAILURE;
2516 }
2518 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
2519 if (NS_FAILED(rv)) {
2520 LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
2521 return rv;
2522 }
2524 nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
2525 do_QueryInterface(newChannel, &rv);
2527 if (NS_FAILED(rv)) {
2528 LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
2529 return rv;
2530 }
2532 // The redirect is likely OK
2534 newChannel->SetNotificationCallbacks(this);
2536 mEncrypted = newuriIsHttps;
2537 newuri->Clone(getter_AddRefs(mURI));
2538 if (mEncrypted)
2539 rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
2540 else
2541 rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
2543 mHttpChannel = newHttpChannel;
2544 mChannel = newUpgradeChannel;
2545 rv = SetupRequest();
2546 if (NS_FAILED(rv)) {
2547 LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
2548 return rv;
2549 }
2551 // Redirected-to URI may need to be delayed by 1-connecting-per-host and
2552 // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback
2553 // until BeginOpen, when we know it's OK to proceed with new channel.
2554 mRedirectCallback = callback;
2556 // Mark old channel as successfully connected so we'll clear any FailDelay
2557 // associated with the old URI. Note: no need to also call OnStopSession:
2558 // it's a no-op for successful, already-connected channels.
2559 nsWSAdmissionManager::OnConnected(this);
2561 // ApplyForAdmission as if we were starting from fresh...
2562 mAddress.Truncate();
2563 mOpenedHttpChannel = 0;
2564 rv = ApplyForAdmission();
2565 if (NS_FAILED(rv)) {
2566 LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
2567 mRedirectCallback = nullptr;
2568 return rv;
2569 }
2571 return NS_OK;
2572 }
2574 // nsITimerCallback
2576 NS_IMETHODIMP
2577 WebSocketChannel::Notify(nsITimer *timer)
2578 {
2579 LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
2581 if (timer == mCloseTimer) {
2582 NS_ABORT_IF_FALSE(mClientClosed, "Close Timeout without local close");
2583 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
2584 "not socket thread");
2586 mCloseTimer = nullptr;
2587 if (mStopped || mServerClosed) /* no longer relevant */
2588 return NS_OK;
2590 LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
2591 AbortSession(NS_ERROR_NET_TIMEOUT);
2592 } else if (timer == mOpenTimer) {
2593 NS_ABORT_IF_FALSE(!mGotUpgradeOK,
2594 "Open Timer after open complete");
2595 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2597 mOpenTimer = nullptr;
2598 LOG(("WebSocketChannel:: Connection Timed Out\n"));
2599 if (mStopped || mServerClosed) /* no longer relevant */
2600 return NS_OK;
2602 AbortSession(NS_ERROR_NET_TIMEOUT);
2603 } else if (timer == mReconnectDelayTimer) {
2604 NS_ABORT_IF_FALSE(mConnecting == CONNECTING_DELAYED,
2605 "woke up from delay w/o being delayed?");
2607 mReconnectDelayTimer = nullptr;
2608 LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
2609 BeginOpen();
2610 } else if (timer == mPingTimer) {
2611 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
2612 "not socket thread");
2614 if (mClientClosed || mServerClosed || mRequestedClose) {
2615 // no point in worrying about ping now
2616 mPingTimer = nullptr;
2617 return NS_OK;
2618 }
2620 if (!mPingOutstanding) {
2621 LOG(("nsWebSocketChannel:: Generating Ping\n"));
2622 mPingOutstanding = 1;
2623 GeneratePing();
2624 mPingTimer->InitWithCallback(this, mPingResponseTimeout,
2625 nsITimer::TYPE_ONE_SHOT);
2626 } else {
2627 LOG(("nsWebSocketChannel:: Timed out Ping\n"));
2628 mPingTimer = nullptr;
2629 AbortSession(NS_ERROR_NET_TIMEOUT);
2630 }
2631 } else if (timer == mLingeringCloseTimer) {
2632 LOG(("WebSocketChannel:: Lingering Close Timer"));
2633 CleanupConnection();
2634 } else {
2635 NS_ABORT_IF_FALSE(0, "Unknown Timer");
2636 }
2638 return NS_OK;
2639 }
2641 // nsIWebSocketChannel
2643 NS_IMETHODIMP
2644 WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
2645 {
2646 LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
2647 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2649 if (mTransport) {
2650 if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo)))
2651 *aSecurityInfo = nullptr;
2652 }
2653 return NS_OK;
2654 }
2657 NS_IMETHODIMP
2658 WebSocketChannel::AsyncOpen(nsIURI *aURI,
2659 const nsACString &aOrigin,
2660 nsIWebSocketListener *aListener,
2661 nsISupports *aContext)
2662 {
2663 LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
2665 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2667 if (!aURI || !aListener) {
2668 LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
2669 return NS_ERROR_UNEXPECTED;
2670 }
2672 if (mListener || mWasOpened)
2673 return NS_ERROR_ALREADY_OPENED;
2675 nsresult rv;
2677 // Ensure target thread is set.
2678 if (!mTargetThread) {
2679 mTargetThread = do_GetMainThread();
2680 }
2682 mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
2683 if (NS_FAILED(rv)) {
2684 NS_WARNING("unable to continue without socket transport service");
2685 return rv;
2686 }
2688 mRandomGenerator =
2689 do_GetService("@mozilla.org/security/random-generator;1", &rv);
2690 if (NS_FAILED(rv)) {
2691 NS_WARNING("unable to continue without random number generator");
2692 return rv;
2693 }
2695 nsCOMPtr<nsIPrefBranch> prefService;
2696 prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
2698 if (prefService) {
2699 int32_t intpref;
2700 bool boolpref;
2701 rv = prefService->GetIntPref("network.websocket.max-message-size",
2702 &intpref);
2703 if (NS_SUCCEEDED(rv)) {
2704 mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
2705 }
2706 rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
2707 if (NS_SUCCEEDED(rv)) {
2708 mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
2709 }
2710 rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
2711 if (NS_SUCCEEDED(rv)) {
2712 mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
2713 }
2714 rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
2715 &intpref);
2716 if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
2717 mPingInterval = clamped(intpref, 0, 86400) * 1000;
2718 }
2719 rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
2720 &intpref);
2721 if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
2722 mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
2723 }
2724 rv = prefService->GetBoolPref("network.websocket.extensions.stream-deflate",
2725 &boolpref);
2726 if (NS_SUCCEEDED(rv)) {
2727 mAllowCompression = boolpref ? 1 : 0;
2728 }
2729 rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects",
2730 &boolpref);
2731 if (NS_SUCCEEDED(rv)) {
2732 mAutoFollowRedirects = boolpref ? 1 : 0;
2733 }
2734 rv = prefService->GetIntPref
2735 ("network.websocket.max-connections", &intpref);
2736 if (NS_SUCCEEDED(rv)) {
2737 mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
2738 }
2739 }
2741 int32_t sessionCount = -1;
2742 nsWSAdmissionManager::GetSessionCount(sessionCount);
2743 if (sessionCount >= 0) {
2744 LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
2745 sessionCount, mMaxConcurrentConnections));
2746 }
2748 if (sessionCount >= mMaxConcurrentConnections) {
2749 LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
2750 mMaxConcurrentConnections,
2751 sessionCount));
2753 // WebSocket connections are expected to be long lived, so return
2754 // an error here instead of queueing
2755 return NS_ERROR_SOCKET_CREATE_FAILED;
2756 }
2758 mOriginalURI = aURI;
2759 mURI = mOriginalURI;
2760 mURI->GetHostPort(mHost);
2761 mOrigin = aOrigin;
2763 nsCOMPtr<nsIURI> localURI;
2764 nsCOMPtr<nsIChannel> localChannel;
2766 mURI->Clone(getter_AddRefs(localURI));
2767 if (mEncrypted)
2768 rv = localURI->SetScheme(NS_LITERAL_CSTRING("https"));
2769 else
2770 rv = localURI->SetScheme(NS_LITERAL_CSTRING("http"));
2771 NS_ENSURE_SUCCESS(rv, rv);
2773 nsCOMPtr<nsIIOService> ioService;
2774 ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
2775 if (NS_FAILED(rv)) {
2776 NS_WARNING("unable to continue without io service");
2777 return rv;
2778 }
2780 nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
2781 if (NS_FAILED(rv)) {
2782 NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
2783 return rv;
2784 }
2786 rv = io2->NewChannelFromURIWithProxyFlags(
2787 localURI,
2788 mURI,
2789 nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
2790 nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
2791 getter_AddRefs(localChannel));
2792 NS_ENSURE_SUCCESS(rv, rv);
2794 // Pass most GetInterface() requests through to our instantiator, but handle
2795 // nsIChannelEventSink in this object in order to deal with redirects
2796 localChannel->SetNotificationCallbacks(this);
2798 mChannel = do_QueryInterface(localChannel, &rv);
2799 NS_ENSURE_SUCCESS(rv, rv);
2801 mHttpChannel = do_QueryInterface(localChannel, &rv);
2802 NS_ENSURE_SUCCESS(rv, rv);
2804 rv = SetupRequest();
2805 if (NS_FAILED(rv))
2806 return rv;
2808 mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
2810 if (mConnectionLogService && !mPrivateBrowsing) {
2811 mConnectionLogService->AddHost(mHost, mSerial,
2812 BaseWebSocketChannel::mEncrypted);
2813 }
2815 rv = ApplyForAdmission();
2816 if (NS_FAILED(rv))
2817 return rv;
2819 // Only set these if the open was successful:
2820 //
2821 mWasOpened = 1;
2822 mListener = aListener;
2823 mContext = aContext;
2824 IncrementSessionCount();
2826 return rv;
2827 }
2829 NS_IMETHODIMP
2830 WebSocketChannel::Close(uint16_t code, const nsACString & reason)
2831 {
2832 LOG(("WebSocketChannel::Close() %p\n", this));
2833 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2835 // save the networkstats (bug 855949)
2836 SaveNetworkStats(true);
2838 if (mRequestedClose) {
2839 return NS_OK;
2840 }
2842 // The API requires the UTF-8 string to be 123 or less bytes
2843 if (reason.Length() > 123)
2844 return NS_ERROR_ILLEGAL_VALUE;
2846 mRequestedClose = 1;
2847 mScriptCloseReason = reason;
2848 mScriptCloseCode = code;
2850 if (!mTransport) {
2851 nsresult rv;
2852 if (code == CLOSE_GOING_AWAY) {
2853 // Not an error: for example, tab has closed or navigated away
2854 LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
2855 rv = NS_OK;
2856 } else {
2857 LOG(("WebSocketChannel::Close() without transport - error."));
2858 rv = NS_ERROR_NOT_CONNECTED;
2859 }
2860 StopSession(rv);
2861 return rv;
2862 }
2864 return mSocketThread->Dispatch(
2865 new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
2866 nsIEventTarget::DISPATCH_NORMAL);
2867 }
2869 NS_IMETHODIMP
2870 WebSocketChannel::SendMsg(const nsACString &aMsg)
2871 {
2872 LOG(("WebSocketChannel::SendMsg() %p\n", this));
2874 return SendMsgCommon(&aMsg, false, aMsg.Length());
2875 }
2877 NS_IMETHODIMP
2878 WebSocketChannel::SendBinaryMsg(const nsACString &aMsg)
2879 {
2880 LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length()));
2881 return SendMsgCommon(&aMsg, true, aMsg.Length());
2882 }
2884 NS_IMETHODIMP
2885 WebSocketChannel::SendBinaryStream(nsIInputStream *aStream, uint32_t aLength)
2886 {
2887 LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
2889 return SendMsgCommon(nullptr, true, aLength, aStream);
2890 }
2892 nsresult
2893 WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
2894 uint32_t aLength, nsIInputStream *aStream)
2895 {
2896 NS_ABORT_IF_FALSE(NS_GetCurrentThread() == mTargetThread, "not target thread");
2898 if (mRequestedClose) {
2899 LOG(("WebSocketChannel:: Error: send when closed\n"));
2900 return NS_ERROR_UNEXPECTED;
2901 }
2903 if (mStopped) {
2904 LOG(("WebSocketChannel:: Error: send when stopped\n"));
2905 return NS_ERROR_NOT_CONNECTED;
2906 }
2908 NS_ABORT_IF_FALSE(mMaxMessageSize >= 0, "max message size negative");
2909 if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
2910 LOG(("WebSocketChannel:: Error: message too big\n"));
2911 return NS_ERROR_FILE_TOO_BIG;
2912 }
2914 if (mConnectionLogService && !mPrivateBrowsing) {
2915 mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
2916 LOG(("Added new msg sent for %s", mHost.get()));
2917 }
2919 return mSocketThread->Dispatch(
2920 aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
2921 : new OutboundEnqueuer(this,
2922 new OutboundMessage(aIsBinary ? kMsgTypeBinaryString
2923 : kMsgTypeString,
2924 new nsCString(*aMsg))),
2925 nsIEventTarget::DISPATCH_NORMAL);
2926 }
2928 // nsIHttpUpgradeListener
2930 NS_IMETHODIMP
2931 WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
2932 nsIAsyncInputStream *aSocketIn,
2933 nsIAsyncOutputStream *aSocketOut)
2934 {
2935 if (!NS_IsMainThread()) {
2936 return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
2937 aTransport,
2938 aSocketIn,
2939 aSocketOut));
2940 }
2942 LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
2943 this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
2945 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2946 NS_ABORT_IF_FALSE(!mRecvdHttpUpgradeTransport, "OTA duplicated");
2947 NS_ABORT_IF_FALSE(aSocketIn, "OTA with invalid socketIn");
2949 mTransport = aTransport;
2950 mSocketIn = aSocketIn;
2951 mSocketOut = aSocketOut;
2953 nsresult rv;
2954 rv = mTransport->SetEventSink(nullptr, nullptr);
2955 if (NS_FAILED(rv)) return rv;
2956 rv = mTransport->SetSecurityCallbacks(this);
2957 if (NS_FAILED(rv)) return rv;
2959 mRecvdHttpUpgradeTransport = 1;
2960 if (mGotUpgradeOK)
2961 return StartWebsocketData();
2962 return NS_OK;
2963 }
2965 // nsIRequestObserver (from nsIStreamListener)
2967 NS_IMETHODIMP
2968 WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
2969 nsISupports *aContext)
2970 {
2971 LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
2972 this, aRequest, aContext, mRecvdHttpUpgradeTransport));
2973 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
2974 NS_ABORT_IF_FALSE(!mGotUpgradeOK, "OTA duplicated");
2976 if (mOpenTimer) {
2977 mOpenTimer->Cancel();
2978 mOpenTimer = nullptr;
2979 }
2981 if (mStopped) {
2982 LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
2983 AbortSession(NS_ERROR_CONNECTION_REFUSED);
2984 return NS_ERROR_CONNECTION_REFUSED;
2985 }
2987 nsresult rv;
2988 uint32_t status;
2989 char *val, *token;
2991 rv = mHttpChannel->GetResponseStatus(&status);
2992 if (NS_FAILED(rv)) {
2993 LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
2994 AbortSession(NS_ERROR_CONNECTION_REFUSED);
2995 return NS_ERROR_CONNECTION_REFUSED;
2996 }
2998 LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
2999 if (status != 101) {
3000 AbortSession(NS_ERROR_CONNECTION_REFUSED);
3001 return NS_ERROR_CONNECTION_REFUSED;
3002 }
3004 nsAutoCString respUpgrade;
3005 rv = mHttpChannel->GetResponseHeader(
3006 NS_LITERAL_CSTRING("Upgrade"), respUpgrade);
3008 if (NS_SUCCEEDED(rv)) {
3009 rv = NS_ERROR_ILLEGAL_VALUE;
3010 if (!respUpgrade.IsEmpty()) {
3011 val = respUpgrade.BeginWriting();
3012 while ((token = nsCRT::strtok(val, ", \t", &val))) {
3013 if (PL_strcasecmp(token, "Websocket") == 0) {
3014 rv = NS_OK;
3015 break;
3016 }
3017 }
3018 }
3019 }
3021 if (NS_FAILED(rv)) {
3022 LOG(("WebSocketChannel::OnStartRequest: "
3023 "HTTP response header Upgrade: websocket not found\n"));
3024 AbortSession(NS_ERROR_ILLEGAL_VALUE);
3025 return rv;
3026 }
3028 nsAutoCString respConnection;
3029 rv = mHttpChannel->GetResponseHeader(
3030 NS_LITERAL_CSTRING("Connection"), respConnection);
3032 if (NS_SUCCEEDED(rv)) {
3033 rv = NS_ERROR_ILLEGAL_VALUE;
3034 if (!respConnection.IsEmpty()) {
3035 val = respConnection.BeginWriting();
3036 while ((token = nsCRT::strtok(val, ", \t", &val))) {
3037 if (PL_strcasecmp(token, "Upgrade") == 0) {
3038 rv = NS_OK;
3039 break;
3040 }
3041 }
3042 }
3043 }
3045 if (NS_FAILED(rv)) {
3046 LOG(("WebSocketChannel::OnStartRequest: "
3047 "HTTP response header 'Connection: Upgrade' not found\n"));
3048 AbortSession(NS_ERROR_ILLEGAL_VALUE);
3049 return rv;
3050 }
3052 nsAutoCString respAccept;
3053 rv = mHttpChannel->GetResponseHeader(
3054 NS_LITERAL_CSTRING("Sec-WebSocket-Accept"),
3055 respAccept);
3057 if (NS_FAILED(rv) ||
3058 respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) {
3059 LOG(("WebSocketChannel::OnStartRequest: "
3060 "HTTP response header Sec-WebSocket-Accept check failed\n"));
3061 LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
3062 mHashedSecret.get(), respAccept.get()));
3063 AbortSession(NS_ERROR_ILLEGAL_VALUE);
3064 return NS_ERROR_ILLEGAL_VALUE;
3065 }
3067 // If we sent a sub protocol header, verify the response matches
3068 // If it does not, set mProtocol to "" so the protocol attribute
3069 // of the WebSocket JS object reflects that
3070 if (!mProtocol.IsEmpty()) {
3071 nsAutoCString respProtocol;
3072 rv = mHttpChannel->GetResponseHeader(
3073 NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
3074 respProtocol);
3075 if (NS_SUCCEEDED(rv)) {
3076 rv = NS_ERROR_ILLEGAL_VALUE;
3077 val = mProtocol.BeginWriting();
3078 while ((token = nsCRT::strtok(val, ", \t", &val))) {
3079 if (PL_strcasecmp(token, respProtocol.get()) == 0) {
3080 rv = NS_OK;
3081 break;
3082 }
3083 }
3085 if (NS_SUCCEEDED(rv)) {
3086 LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
3087 respProtocol.get()));
3088 mProtocol = respProtocol;
3089 } else {
3090 LOG(("WebsocketChannel::OnStartRequest: "
3091 "subprotocol [%s] not found - %s returned",
3092 mProtocol.get(), respProtocol.get()));
3093 mProtocol.Truncate();
3094 }
3095 } else {
3096 LOG(("WebsocketChannel::OnStartRequest "
3097 "subprotocol [%s] not found - none returned",
3098 mProtocol.get()));
3099 mProtocol.Truncate();
3100 }
3101 }
3103 rv = HandleExtensions();
3104 if (NS_FAILED(rv))
3105 return rv;
3107 mGotUpgradeOK = 1;
3108 if (mRecvdHttpUpgradeTransport)
3109 return StartWebsocketData();
3111 return NS_OK;
3112 }
3114 NS_IMETHODIMP
3115 WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
3116 nsISupports *aContext,
3117 nsresult aStatusCode)
3118 {
3119 LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
3120 this, aRequest, aContext, aStatusCode));
3121 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
3123 ReportConnectionTelemetry();
3125 // This is the end of the HTTP upgrade transaction, the
3126 // upgraded streams live on
3128 mChannel = nullptr;
3129 mHttpChannel = nullptr;
3130 mLoadGroup = nullptr;
3131 mCallbacks = nullptr;
3133 return NS_OK;
3134 }
3136 // nsIInputStreamCallback
3138 NS_IMETHODIMP
3139 WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream)
3140 {
3141 LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
3142 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
3144 if (!mSocketIn) // did we we clean up the socket after scheduling InputReady?
3145 return NS_OK;
3147 nsRefPtr<nsIStreamListener> deleteProtector1(mInflateReader);
3148 nsRefPtr<nsIStringInputStream> deleteProtector2(mInflateStream);
3150 // this is after the http upgrade - so we are speaking websockets
3151 char buffer[2048];
3152 uint32_t count;
3153 nsresult rv;
3155 do {
3156 rv = mSocketIn->Read((char *)buffer, 2048, &count);
3157 LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv));
3159 // accumulate received bytes
3160 CountRecvBytes(count);
3162 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
3163 mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
3164 return NS_OK;
3165 }
3167 if (NS_FAILED(rv)) {
3168 mTCPClosed = true;
3169 AbortSession(rv);
3170 return rv;
3171 }
3173 if (count == 0) {
3174 mTCPClosed = true;
3175 AbortSession(NS_BASE_STREAM_CLOSED);
3176 return NS_OK;
3177 }
3179 if (mStopped) {
3180 continue;
3181 }
3183 if (mInflateReader) {
3184 mInflateStream->ShareData(buffer, count);
3185 rv = mInflateReader->OnDataAvailable(nullptr, mSocketIn, mInflateStream,
3186 0, count);
3187 } else {
3188 rv = ProcessInput((uint8_t *)buffer, count);
3189 }
3191 if (NS_FAILED(rv)) {
3192 AbortSession(rv);
3193 return rv;
3194 }
3195 } while (NS_SUCCEEDED(rv) && mSocketIn);
3197 return NS_OK;
3198 }
3201 // nsIOutputStreamCallback
3203 NS_IMETHODIMP
3204 WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream)
3205 {
3206 LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
3207 NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
3208 nsresult rv;
3210 if (!mCurrentOut)
3211 PrimeNewOutgoingMessage();
3213 while (mCurrentOut && mSocketOut) {
3214 const char *sndBuf;
3215 uint32_t toSend;
3216 uint32_t amtSent;
3218 if (mHdrOut) {
3219 sndBuf = (const char *)mHdrOut;
3220 toSend = mHdrOutToSend;
3221 LOG(("WebSocketChannel::OnOutputStreamReady: "
3222 "Try to send %u of hdr/copybreak\n", toSend));
3223 } else {
3224 sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent;
3225 toSend = mCurrentOut->Length() - mCurrentOutSent;
3226 if (toSend > 0) {
3227 LOG(("WebSocketChannel::OnOutputStreamReady: "
3228 "Try to send %u of data\n", toSend));
3229 }
3230 }
3232 if (toSend == 0) {
3233 amtSent = 0;
3234 } else {
3235 rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
3236 LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n",
3237 amtSent, rv));
3239 // accumulate sent bytes
3240 CountSentBytes(amtSent);
3242 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
3243 mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
3244 return NS_OK;
3245 }
3247 if (NS_FAILED(rv)) {
3248 AbortSession(rv);
3249 return NS_OK;
3250 }
3251 }
3253 if (mHdrOut) {
3254 if (amtSent == toSend) {
3255 mHdrOut = nullptr;
3256 mHdrOutToSend = 0;
3257 } else {
3258 mHdrOut += amtSent;
3259 mHdrOutToSend -= amtSent;
3260 }
3261 } else {
3262 if (amtSent == toSend) {
3263 if (!mStopped) {
3264 mTargetThread->Dispatch(new CallAcknowledge(this,
3265 mCurrentOut->Length()),
3266 NS_DISPATCH_NORMAL);
3267 }
3268 DeleteCurrentOutGoingMessage();
3269 PrimeNewOutgoingMessage();
3270 } else {
3271 mCurrentOutSent += amtSent;
3272 }
3273 }
3274 }
3276 if (mReleaseOnTransmit)
3277 ReleaseSession();
3278 return NS_OK;
3279 }
3281 // nsIStreamListener
3283 NS_IMETHODIMP
3284 WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
3285 nsISupports *aContext,
3286 nsIInputStream *aInputStream,
3287 uint64_t aOffset,
3288 uint32_t aCount)
3289 {
3290 LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %u]\n",
3291 this, aRequest, aContext, aInputStream, aOffset, aCount));
3293 if (aContext == mSocketIn) {
3294 // This is the deflate decoder
3296 LOG(("WebSocketChannel::OnDataAvailable: Deflate Data %u\n",
3297 aCount));
3299 uint8_t buffer[2048];
3300 uint32_t maxRead;
3301 uint32_t count;
3302 nsresult rv = NS_OK; // aCount always > 0, so this just avoids warning
3304 while (aCount > 0) {
3305 if (mStopped)
3306 return NS_BASE_STREAM_CLOSED;
3308 maxRead = std::min(2048U, aCount);
3309 rv = aInputStream->Read((char *)buffer, maxRead, &count);
3310 LOG(("WebSocketChannel::OnDataAvailable: InflateRead read %u rv %x\n",
3311 count, rv));
3312 if (NS_FAILED(rv) || count == 0) {
3313 AbortSession(NS_ERROR_UNEXPECTED);
3314 break;
3315 }
3317 aCount -= count;
3318 rv = ProcessInput(buffer, count);
3319 if (NS_FAILED(rv)) {
3320 AbortSession(rv);
3321 break;
3322 }
3323 }
3324 return rv;
3325 }
3327 if (aContext == mSocketOut) {
3328 // This is the deflate encoder
3330 uint32_t maxRead;
3331 uint32_t count;
3332 nsresult rv;
3334 while (aCount > 0) {
3335 if (mStopped)
3336 return NS_BASE_STREAM_CLOSED;
3338 maxRead = std::min(2048U, aCount);
3339 EnsureHdrOut(mHdrOutToSend + aCount);
3340 rv = aInputStream->Read((char *)mHdrOut + mHdrOutToSend, maxRead, &count);
3341 LOG(("WebSocketChannel::OnDataAvailable: DeflateWrite read %u rv %x\n",
3342 count, rv));
3343 if (NS_FAILED(rv) || count == 0) {
3344 AbortSession(rv);
3345 break;
3346 }
3348 mHdrOutToSend += count;
3349 aCount -= count;
3350 }
3351 return NS_OK;
3352 }
3355 // Otherwise, this is the HTTP OnDataAvailable Method, which means
3356 // this is http data in response to the upgrade request and
3357 // there should be no http response body if the upgrade succeeded
3359 // This generally should be caught by a non 101 response code in
3360 // OnStartRequest().. so we can ignore the data here
3362 LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
3363 aCount));
3365 return NS_OK;
3366 }
3368 nsresult
3369 WebSocketChannel::SaveNetworkStats(bool enforce)
3370 {
3371 #ifdef MOZ_WIDGET_GONK
3372 // Check if the active network and app id are valid.
3373 if(!mActiveNetwork || mAppId == NECKO_NO_APP_ID) {
3374 return NS_OK;
3375 }
3377 if (mCountRecv <= 0 && mCountSent <= 0) {
3378 // There is no traffic, no need to save.
3379 return NS_OK;
3380 }
3382 // If |enforce| is false, the traffic amount is saved
3383 // only when the total amount exceeds the predefined
3384 // threshold.
3385 uint64_t totalBytes = mCountRecv + mCountSent;
3386 if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
3387 return NS_OK;
3388 }
3390 // Create the event to save the network statistics.
3391 // the event is then dispathed to the main thread.
3392 nsRefPtr<nsRunnable> event =
3393 new SaveNetworkStatsEvent(mAppId, mActiveNetwork,
3394 mCountRecv, mCountSent, false);
3395 NS_DispatchToMainThread(event);
3397 // Reset the counters after saving.
3398 mCountSent = 0;
3399 mCountRecv = 0;
3401 return NS_OK;
3402 #else
3403 return NS_ERROR_NOT_IMPLEMENTED;
3404 #endif
3405 }
3407 } // namespace mozilla::net
3408 } // namespace mozilla