netwerk/protocol/websocket/WebSocketChannel.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,3408 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set sw=2 ts=8 et tw=80 : */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "WebSocketLog.h"
    1.11 +#include "WebSocketChannel.h"
    1.12 +
    1.13 +#include "mozilla/Atomics.h"
    1.14 +#include "mozilla/Attributes.h"
    1.15 +#include "mozilla/Endian.h"
    1.16 +#include "mozilla/MathAlgorithms.h"
    1.17 +
    1.18 +#include "nsIURI.h"
    1.19 +#include "nsIChannel.h"
    1.20 +#include "nsICryptoHash.h"
    1.21 +#include "nsIRunnable.h"
    1.22 +#include "nsIPrefBranch.h"
    1.23 +#include "nsIPrefService.h"
    1.24 +#include "nsICancelable.h"
    1.25 +#include "nsIDNSRecord.h"
    1.26 +#include "nsIDNSService.h"
    1.27 +#include "nsIStreamConverterService.h"
    1.28 +#include "nsIIOService2.h"
    1.29 +#include "nsIProtocolProxyService.h"
    1.30 +#include "nsIProxyInfo.h"
    1.31 +#include "nsIProxiedChannel.h"
    1.32 +#include "nsIAsyncVerifyRedirectCallback.h"
    1.33 +#include "nsIDashboardEventNotifier.h"
    1.34 +#include "nsIEventTarget.h"
    1.35 +#include "nsIHttpChannel.h"
    1.36 +#include "nsILoadGroup.h"
    1.37 +#include "nsIProtocolHandler.h"
    1.38 +#include "nsIRandomGenerator.h"
    1.39 +#include "nsISocketTransport.h"
    1.40 +#include "nsThreadUtils.h"
    1.41 +
    1.42 +#include "nsAutoPtr.h"
    1.43 +#include "nsNetCID.h"
    1.44 +#include "nsServiceManagerUtils.h"
    1.45 +#include "nsCRT.h"
    1.46 +#include "nsThreadUtils.h"
    1.47 +#include "nsError.h"
    1.48 +#include "nsStringStream.h"
    1.49 +#include "nsAlgorithm.h"
    1.50 +#include "nsProxyRelease.h"
    1.51 +#include "nsNetUtil.h"
    1.52 +#include "mozilla/StaticMutex.h"
    1.53 +#include "mozilla/Telemetry.h"
    1.54 +#include "mozilla/TimeStamp.h"
    1.55 +
    1.56 +#include "plbase64.h"
    1.57 +#include "prmem.h"
    1.58 +#include "prnetdb.h"
    1.59 +#include "zlib.h"
    1.60 +#include <algorithm>
    1.61 +
    1.62 +#ifdef MOZ_WIDGET_GONK
    1.63 +#include "NetStatistics.h"
    1.64 +#endif
    1.65 +
    1.66 +// rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
    1.67 +// dupe one constant we need from it
    1.68 +#define CLOSE_GOING_AWAY 1001
    1.69 +
    1.70 +extern PRThread *gSocketThread;
    1.71 +
    1.72 +using namespace mozilla;
    1.73 +using namespace mozilla::net;
    1.74 +
    1.75 +namespace mozilla {
    1.76 +namespace net {
    1.77 +
    1.78 +NS_IMPL_ISUPPORTS(WebSocketChannel,
    1.79 +                  nsIWebSocketChannel,
    1.80 +                  nsIHttpUpgradeListener,
    1.81 +                  nsIRequestObserver,
    1.82 +                  nsIStreamListener,
    1.83 +                  nsIProtocolHandler,
    1.84 +                  nsIInputStreamCallback,
    1.85 +                  nsIOutputStreamCallback,
    1.86 +                  nsITimerCallback,
    1.87 +                  nsIDNSListener,
    1.88 +                  nsIProtocolProxyCallback,
    1.89 +                  nsIInterfaceRequestor,
    1.90 +                  nsIChannelEventSink,
    1.91 +                  nsIThreadRetargetableRequest)
    1.92 +
    1.93 +// We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
    1.94 +#define SEC_WEBSOCKET_VERSION "13"
    1.95 +
    1.96 +/*
    1.97 + * About SSL unsigned certificates
    1.98 + *
    1.99 + * wss will not work to a host using an unsigned certificate unless there
   1.100 + * is already an exception (i.e. it cannot popup a dialog asking for
   1.101 + * a security exception). This is similar to how an inlined img will
   1.102 + * fail without a dialog if fails for the same reason. This should not
   1.103 + * be a problem in practice as it is expected the websocket javascript
   1.104 + * is served from the same host as the websocket server (or of course,
   1.105 + * a valid cert could just be provided).
   1.106 + *
   1.107 + */
   1.108 +
   1.109 +// some helper classes
   1.110 +
   1.111 +//-----------------------------------------------------------------------------
   1.112 +// FailDelayManager
   1.113 +//
   1.114 +// Stores entries (searchable by {host, port}) of connections that have recently
   1.115 +// failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
   1.116 +//-----------------------------------------------------------------------------
   1.117 +
   1.118 +
   1.119 +// Initial reconnect delay is randomly chosen between 200-400 ms.
   1.120 +// This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
   1.121 +const uint32_t kWSReconnectInitialBaseDelay     = 200;
   1.122 +const uint32_t kWSReconnectInitialRandomDelay   = 200;
   1.123 +
   1.124 +// Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
   1.125 +const uint32_t kWSReconnectBaseLifeTime         = 60 * 1000;
   1.126 +// Maximum reconnect delay (in ms)
   1.127 +const uint32_t kWSReconnectMaxDelay             = 60 * 1000;
   1.128 +
   1.129 +// hold record of failed connections, and calculates needed delay for reconnects
   1.130 +// to same host/port.
   1.131 +class FailDelay
   1.132 +{
   1.133 +public:
   1.134 +  FailDelay(nsCString address, int32_t port)
   1.135 +    : mAddress(address), mPort(port)
   1.136 +  {
   1.137 +    mLastFailure = TimeStamp::Now();
   1.138 +    mNextDelay = kWSReconnectInitialBaseDelay +
   1.139 +                 (rand() % kWSReconnectInitialRandomDelay);
   1.140 +  }
   1.141 +
   1.142 +  // Called to update settings when connection fails again.
   1.143 +  void FailedAgain()
   1.144 +  {
   1.145 +    mLastFailure = TimeStamp::Now();
   1.146 +    // We use a truncated exponential backoff as suggested by RFC 6455,
   1.147 +    // but multiply by 1.5 instead of 2 to be more gradual.
   1.148 +    mNextDelay = static_cast<uint32_t>(
   1.149 +      std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
   1.150 +    LOG(("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to %lu",
   1.151 +         mAddress.get(), mPort, mNextDelay));
   1.152 +  }
   1.153 +
   1.154 +  // returns 0 if there is no need to delay (i.e. delay interval is over)
   1.155 +  uint32_t RemainingDelay(TimeStamp rightNow)
   1.156 +  {
   1.157 +    TimeDuration dur = rightNow - mLastFailure;
   1.158 +    uint32_t sinceFail = (uint32_t) dur.ToMilliseconds();
   1.159 +    if (sinceFail > mNextDelay)
   1.160 +      return 0;
   1.161 +
   1.162 +    return mNextDelay - sinceFail;
   1.163 +  }
   1.164 +
   1.165 +  bool IsExpired(TimeStamp rightNow)
   1.166 +  {
   1.167 +    return (mLastFailure +
   1.168 +            TimeDuration::FromMilliseconds(kWSReconnectBaseLifeTime + mNextDelay))
   1.169 +            <= rightNow;
   1.170 +  }
   1.171 +
   1.172 +  nsCString  mAddress;     // IP address (or hostname if using proxy)
   1.173 +  int32_t    mPort;
   1.174 +
   1.175 +private:
   1.176 +  TimeStamp  mLastFailure; // Time of last failed attempt
   1.177 +  // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
   1.178 +  uint32_t   mNextDelay;   // milliseconds
   1.179 +};
   1.180 +
   1.181 +class FailDelayManager
   1.182 +{
   1.183 +public:
   1.184 +  FailDelayManager()
   1.185 +  {
   1.186 +    MOZ_COUNT_CTOR(FailDelayManager);
   1.187 +
   1.188 +    mDelaysDisabled = false;
   1.189 +
   1.190 +    nsCOMPtr<nsIPrefBranch> prefService =
   1.191 +      do_GetService(NS_PREFSERVICE_CONTRACTID);
   1.192 +    bool boolpref = true;
   1.193 +    nsresult rv;
   1.194 +    rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
   1.195 +                                  &boolpref);
   1.196 +    if (NS_SUCCEEDED(rv) && !boolpref) {
   1.197 +      mDelaysDisabled = true;
   1.198 +    }
   1.199 +  }
   1.200 +
   1.201 +  ~FailDelayManager()
   1.202 +  {
   1.203 +    MOZ_COUNT_DTOR(FailDelayManager);
   1.204 +    for (uint32_t i = 0; i < mEntries.Length(); i++) {
   1.205 +      delete mEntries[i];
   1.206 +    }
   1.207 +  }
   1.208 +
   1.209 +  void Add(nsCString &address, int32_t port)
   1.210 +  {
   1.211 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   1.212 +
   1.213 +    if (mDelaysDisabled)
   1.214 +      return;
   1.215 +
   1.216 +    FailDelay *record = new FailDelay(address, port);
   1.217 +    mEntries.AppendElement(record);
   1.218 +  }
   1.219 +
   1.220 +  // Element returned may not be valid after next main thread event: don't keep
   1.221 +  // pointer to it around
   1.222 +  FailDelay* Lookup(nsCString &address, int32_t port,
   1.223 +                    uint32_t *outIndex = nullptr)
   1.224 +  {
   1.225 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   1.226 +
   1.227 +    if (mDelaysDisabled)
   1.228 +      return nullptr;
   1.229 +
   1.230 +    FailDelay *result = nullptr;
   1.231 +    TimeStamp rightNow = TimeStamp::Now();
   1.232 +
   1.233 +    // We also remove expired entries during search: iterate from end to make
   1.234 +    // indexing simpler
   1.235 +    for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
   1.236 +      FailDelay *fail = mEntries[i];
   1.237 +      if (fail->mAddress.Equals(address) && fail->mPort == port) {
   1.238 +        if (outIndex)
   1.239 +          *outIndex = i;
   1.240 +        result = fail;
   1.241 +        // break here: removing more entries would mess up *outIndex.
   1.242 +        // Any remaining expired entries will be deleted next time Lookup
   1.243 +        // finds nothing, which is the most common case anyway.
   1.244 +        break;
   1.245 +      } else if (fail->IsExpired(rightNow)) {
   1.246 +        mEntries.RemoveElementAt(i);
   1.247 +        delete fail;
   1.248 +      }
   1.249 +    }
   1.250 +    return result;
   1.251 +  }
   1.252 +
   1.253 +  // returns true if channel connects immediately, or false if it's delayed
   1.254 +  void DelayOrBegin(WebSocketChannel *ws)
   1.255 +  {
   1.256 +    if (!mDelaysDisabled) {
   1.257 +      uint32_t failIndex = 0;
   1.258 +      FailDelay *fail = Lookup(ws->mAddress, ws->mPort, &failIndex);
   1.259 +
   1.260 +      if (fail) {
   1.261 +        TimeStamp rightNow = TimeStamp::Now();
   1.262 +
   1.263 +        uint32_t remainingDelay = fail->RemainingDelay(rightNow);
   1.264 +        if (remainingDelay) {
   1.265 +          // reconnecting within delay interval: delay by remaining time
   1.266 +          nsresult rv;
   1.267 +          ws->mReconnectDelayTimer =
   1.268 +            do_CreateInstance("@mozilla.org/timer;1", &rv);
   1.269 +          if (NS_SUCCEEDED(rv)) {
   1.270 +            rv = ws->mReconnectDelayTimer->InitWithCallback(
   1.271 +                          ws, remainingDelay, nsITimer::TYPE_ONE_SHOT);
   1.272 +            if (NS_SUCCEEDED(rv)) {
   1.273 +              LOG(("WebSocket: delaying websocket [this=%p] by %lu ms",
   1.274 +                   ws, (unsigned long)remainingDelay));
   1.275 +              ws->mConnecting = CONNECTING_DELAYED;
   1.276 +              return;
   1.277 +            }
   1.278 +          }
   1.279 +          // if timer fails (which is very unlikely), drop down to BeginOpen call
   1.280 +        } else if (fail->IsExpired(rightNow)) {
   1.281 +          mEntries.RemoveElementAt(failIndex);
   1.282 +          delete fail;
   1.283 +        }
   1.284 +      }
   1.285 +    }
   1.286 +
   1.287 +    // Delays disabled, or no previous failure, or we're reconnecting after scheduled
   1.288 +    // delay interval has passed: connect.
   1.289 +    ws->BeginOpen();
   1.290 +  }
   1.291 +
   1.292 +  // Remove() also deletes all expired entries as it iterates: better for
   1.293 +  // battery life than using a periodic timer.
   1.294 +  void Remove(nsCString &address, int32_t port)
   1.295 +  {
   1.296 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   1.297 +
   1.298 +    TimeStamp rightNow = TimeStamp::Now();
   1.299 +
   1.300 +    // iterate from end, to make deletion indexing easier
   1.301 +    for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
   1.302 +      FailDelay *entry = mEntries[i];
   1.303 +      if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
   1.304 +          entry->IsExpired(rightNow)) {
   1.305 +        mEntries.RemoveElementAt(i);
   1.306 +        delete entry;
   1.307 +      }
   1.308 +    }
   1.309 +  }
   1.310 +
   1.311 +private:
   1.312 +  nsTArray<FailDelay *> mEntries;
   1.313 +  bool                  mDelaysDisabled;
   1.314 +};
   1.315 +
   1.316 +//-----------------------------------------------------------------------------
   1.317 +// nsWSAdmissionManager
   1.318 +//
   1.319 +// 1) Ensures that only one websocket at a time is CONNECTING to a given IP
   1.320 +//    address (or hostname, if using proxy), per RFC 6455 Section 4.1.
   1.321 +// 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
   1.322 +//-----------------------------------------------------------------------------
   1.323 +
   1.324 +class nsWSAdmissionManager
   1.325 +{
   1.326 +public:
   1.327 +  static void Init()
   1.328 +  {
   1.329 +    StaticMutexAutoLock lock(sLock);
   1.330 +    if (!sManager) {
   1.331 +      sManager = new nsWSAdmissionManager();
   1.332 +    }
   1.333 +  }
   1.334 +
   1.335 +  static void Shutdown()
   1.336 +  {
   1.337 +    StaticMutexAutoLock lock(sLock);
   1.338 +    delete sManager;
   1.339 +    sManager = nullptr;
   1.340 +  }
   1.341 +
   1.342 +  // Determine if we will open connection immediately (returns true), or
   1.343 +  // delay/queue the connection (returns false)
   1.344 +  static void ConditionallyConnect(WebSocketChannel *ws)
   1.345 +  {
   1.346 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   1.347 +    NS_ABORT_IF_FALSE(ws->mConnecting == NOT_CONNECTING, "opening state");
   1.348 +
   1.349 +    StaticMutexAutoLock lock(sLock);
   1.350 +    if (!sManager) {
   1.351 +      return;
   1.352 +    }
   1.353 +
   1.354 +    // If there is already another WS channel connecting to this IP address,
   1.355 +    // defer BeginOpen and mark as waiting in queue.
   1.356 +    bool found = (sManager->IndexOf(ws->mAddress) >= 0);
   1.357 +
   1.358 +    // Always add ourselves to queue, even if we'll connect immediately
   1.359 +    nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws);
   1.360 +    sManager->mQueue.AppendElement(newdata);
   1.361 +
   1.362 +    if (found) {
   1.363 +      ws->mConnecting = CONNECTING_QUEUED;
   1.364 +    } else {
   1.365 +      sManager->mFailures.DelayOrBegin(ws);
   1.366 +    }
   1.367 +  }
   1.368 +
   1.369 +  static void OnConnected(WebSocketChannel *aChannel)
   1.370 +  {
   1.371 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   1.372 +    NS_ABORT_IF_FALSE(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
   1.373 +                      "Channel completed connect, but not connecting?");
   1.374 +
   1.375 +    StaticMutexAutoLock lock(sLock);
   1.376 +    if (!sManager) {
   1.377 +      return;
   1.378 +    }
   1.379 +
   1.380 +    aChannel->mConnecting = NOT_CONNECTING;
   1.381 +
   1.382 +    // Remove from queue
   1.383 +    sManager->RemoveFromQueue(aChannel);
   1.384 +
   1.385 +    // Connection succeeded, so stop keeping track of any previous failures
   1.386 +    sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort);
   1.387 +
   1.388 +    // Check for queued connections to same host.
   1.389 +    // Note: still need to check for failures, since next websocket with same
   1.390 +    // host may have different port
   1.391 +    sManager->ConnectNext(aChannel->mAddress);
   1.392 +  }
   1.393 +
   1.394 +  // Called every time a websocket channel ends its session (including going away
   1.395 +  // w/o ever successfully creating a connection)
   1.396 +  static void OnStopSession(WebSocketChannel *aChannel, nsresult aReason)
   1.397 +  {
   1.398 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   1.399 +
   1.400 +    StaticMutexAutoLock lock(sLock);
   1.401 +    if (!sManager) {
   1.402 +      return;
   1.403 +    }
   1.404 +
   1.405 +    if (NS_FAILED(aReason)) {
   1.406 +      // Have we seen this failure before?
   1.407 +      FailDelay *knownFailure = sManager->mFailures.Lookup(aChannel->mAddress,
   1.408 +                                                           aChannel->mPort);
   1.409 +      if (knownFailure) {
   1.410 +        if (aReason == NS_ERROR_NOT_CONNECTED) {
   1.411 +          // Don't count close() before connection as a network error
   1.412 +          LOG(("Websocket close() before connection to %s, %d completed"
   1.413 +               " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort,
   1.414 +               aChannel));
   1.415 +        } else {
   1.416 +          // repeated failure to connect: increase delay for next connection
   1.417 +          knownFailure->FailedAgain();
   1.418 +        }
   1.419 +      } else {
   1.420 +        // new connection failure: record it.
   1.421 +        LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
   1.422 +              aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
   1.423 +        sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
   1.424 +      }
   1.425 +    }
   1.426 +
   1.427 +    if (aChannel->mConnecting) {
   1.428 +      // Only way a connecting channel may get here w/o failing is if it was
   1.429 +      // closed with GOING_AWAY (1001) because of navigation, tab close, etc.
   1.430 +      NS_ABORT_IF_FALSE(NS_FAILED(aReason) ||
   1.431 +                        aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
   1.432 +                        "websocket closed while connecting w/o failing?");
   1.433 +
   1.434 +      sManager->RemoveFromQueue(aChannel);
   1.435 +
   1.436 +      bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
   1.437 +      aChannel->mConnecting = NOT_CONNECTING;
   1.438 +      if (wasNotQueued) {
   1.439 +        sManager->ConnectNext(aChannel->mAddress);
   1.440 +      }
   1.441 +    }
   1.442 +  }
   1.443 +
   1.444 +  static void IncrementSessionCount()
   1.445 +  {
   1.446 +    StaticMutexAutoLock lock(sLock);
   1.447 +    if (!sManager) {
   1.448 +      return;
   1.449 +    }
   1.450 +    sManager->mSessionCount++;
   1.451 +  }
   1.452 +
   1.453 +  static void DecrementSessionCount()
   1.454 +  {
   1.455 +    StaticMutexAutoLock lock(sLock);
   1.456 +    if (!sManager) {
   1.457 +      return;
   1.458 +    }
   1.459 +    sManager->mSessionCount--;
   1.460 +  }
   1.461 +
   1.462 +  static void GetSessionCount(int32_t &aSessionCount)
   1.463 +  {
   1.464 +    StaticMutexAutoLock lock(sLock);
   1.465 +    if (!sManager) {
   1.466 +      return;
   1.467 +    }
   1.468 +    aSessionCount = sManager->mSessionCount;
   1.469 +  }
   1.470 +
   1.471 +private:
   1.472 +  nsWSAdmissionManager() : mSessionCount(0)
   1.473 +  {
   1.474 +    MOZ_COUNT_CTOR(nsWSAdmissionManager);
   1.475 +  }
   1.476 +
   1.477 +  ~nsWSAdmissionManager()
   1.478 +  {
   1.479 +    MOZ_COUNT_DTOR(nsWSAdmissionManager);
   1.480 +    for (uint32_t i = 0; i < mQueue.Length(); i++)
   1.481 +      delete mQueue[i];
   1.482 +  }
   1.483 +
   1.484 +  class nsOpenConn
   1.485 +  {
   1.486 +  public:
   1.487 +    nsOpenConn(nsCString &addr, WebSocketChannel *channel)
   1.488 +      : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
   1.489 +    ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); }
   1.490 +
   1.491 +    nsCString mAddress;
   1.492 +    WebSocketChannel *mChannel;
   1.493 +  };
   1.494 +
   1.495 +  void ConnectNext(nsCString &hostName)
   1.496 +  {
   1.497 +    int32_t index = IndexOf(hostName);
   1.498 +    if (index >= 0) {
   1.499 +      WebSocketChannel *chan = mQueue[index]->mChannel;
   1.500 +
   1.501 +      NS_ABORT_IF_FALSE(chan->mConnecting == CONNECTING_QUEUED,
   1.502 +                        "transaction not queued but in queue");
   1.503 +      LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
   1.504 +
   1.505 +      mFailures.DelayOrBegin(chan);
   1.506 +    }
   1.507 +  }
   1.508 +
   1.509 +  void RemoveFromQueue(WebSocketChannel *aChannel)
   1.510 +  {
   1.511 +    int32_t index = IndexOf(aChannel);
   1.512 +    NS_ABORT_IF_FALSE(index >= 0, "connection to remove not in queue");
   1.513 +    if (index >= 0) {
   1.514 +      nsOpenConn *olddata = mQueue[index];
   1.515 +      mQueue.RemoveElementAt(index);
   1.516 +      delete olddata;
   1.517 +    }
   1.518 +  }
   1.519 +
   1.520 +  int32_t IndexOf(nsCString &aStr)
   1.521 +  {
   1.522 +    for (uint32_t i = 0; i < mQueue.Length(); i++)
   1.523 +      if (aStr == (mQueue[i])->mAddress)
   1.524 +        return i;
   1.525 +    return -1;
   1.526 +  }
   1.527 +
   1.528 +  int32_t IndexOf(WebSocketChannel *aChannel)
   1.529 +  {
   1.530 +    for (uint32_t i = 0; i < mQueue.Length(); i++)
   1.531 +      if (aChannel == (mQueue[i])->mChannel)
   1.532 +        return i;
   1.533 +    return -1;
   1.534 +  }
   1.535 +
   1.536 +  // SessionCount might be decremented from the main or the socket
   1.537 +  // thread, so manage it with atomic counters
   1.538 +  Atomic<int32_t>               mSessionCount;
   1.539 +
   1.540 +  // Queue for websockets that have not completed connecting yet.
   1.541 +  // The first nsOpenConn with a given address will be either be
   1.542 +  // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED.  Later ones with the same
   1.543 +  // hostname must be CONNECTING_QUEUED.
   1.544 +  //
   1.545 +  // We could hash hostnames instead of using a single big vector here, but the
   1.546 +  // dataset is expected to be small.
   1.547 +  nsTArray<nsOpenConn *> mQueue;
   1.548 +
   1.549 +  FailDelayManager       mFailures;
   1.550 +
   1.551 +  static nsWSAdmissionManager *sManager;
   1.552 +  static StaticMutex           sLock;
   1.553 +};
   1.554 +
   1.555 +nsWSAdmissionManager *nsWSAdmissionManager::sManager;
   1.556 +StaticMutex           nsWSAdmissionManager::sLock;
   1.557 +
   1.558 +//-----------------------------------------------------------------------------
   1.559 +// CallOnMessageAvailable
   1.560 +//-----------------------------------------------------------------------------
   1.561 +
   1.562 +class CallOnMessageAvailable MOZ_FINAL : public nsIRunnable
   1.563 +{
   1.564 +public:
   1.565 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.566 +
   1.567 +  CallOnMessageAvailable(WebSocketChannel *aChannel,
   1.568 +                         nsCString        &aData,
   1.569 +                         int32_t           aLen)
   1.570 +    : mChannel(aChannel),
   1.571 +      mData(aData),
   1.572 +      mLen(aLen) {}
   1.573 +
   1.574 +  NS_IMETHOD Run()
   1.575 +  {
   1.576 +    MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   1.577 +
   1.578 +    if (mLen < 0)
   1.579 +      mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData);
   1.580 +    else
   1.581 +      mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext, mData);
   1.582 +    return NS_OK;
   1.583 +  }
   1.584 +
   1.585 +private:
   1.586 +  ~CallOnMessageAvailable() {}
   1.587 +
   1.588 +  nsRefPtr<WebSocketChannel>        mChannel;
   1.589 +  nsCString                         mData;
   1.590 +  int32_t                           mLen;
   1.591 +};
   1.592 +NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable)
   1.593 +
   1.594 +//-----------------------------------------------------------------------------
   1.595 +// CallOnStop
   1.596 +//-----------------------------------------------------------------------------
   1.597 +
   1.598 +class CallOnStop MOZ_FINAL : public nsIRunnable
   1.599 +{
   1.600 +public:
   1.601 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.602 +
   1.603 +  CallOnStop(WebSocketChannel *aChannel,
   1.604 +             nsresult          aReason)
   1.605 +    : mChannel(aChannel),
   1.606 +      mReason(aReason) {}
   1.607 +
   1.608 +  NS_IMETHOD Run()
   1.609 +  {
   1.610 +    MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   1.611 +
   1.612 +    nsWSAdmissionManager::OnStopSession(mChannel, mReason);
   1.613 +
   1.614 +    if (mChannel->mListener) {
   1.615 +      mChannel->mListener->OnStop(mChannel->mContext, mReason);
   1.616 +      mChannel->mListener = nullptr;
   1.617 +      mChannel->mContext = nullptr;
   1.618 +    }
   1.619 +    return NS_OK;
   1.620 +  }
   1.621 +
   1.622 +private:
   1.623 +  ~CallOnStop() {}
   1.624 +
   1.625 +  nsRefPtr<WebSocketChannel>        mChannel;
   1.626 +  nsresult                          mReason;
   1.627 +};
   1.628 +NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable)
   1.629 +
   1.630 +//-----------------------------------------------------------------------------
   1.631 +// CallOnServerClose
   1.632 +//-----------------------------------------------------------------------------
   1.633 +
   1.634 +class CallOnServerClose MOZ_FINAL : public nsIRunnable
   1.635 +{
   1.636 +public:
   1.637 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.638 +
   1.639 +  CallOnServerClose(WebSocketChannel *aChannel,
   1.640 +                    uint16_t          aCode,
   1.641 +                    nsCString        &aReason)
   1.642 +    : mChannel(aChannel),
   1.643 +      mCode(aCode),
   1.644 +      mReason(aReason) {}
   1.645 +
   1.646 +  NS_IMETHOD Run()
   1.647 +  {
   1.648 +    MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   1.649 +
   1.650 +    mChannel->mListener->OnServerClose(mChannel->mContext, mCode, mReason);
   1.651 +    return NS_OK;
   1.652 +  }
   1.653 +
   1.654 +private:
   1.655 +  ~CallOnServerClose() {}
   1.656 +
   1.657 +  nsRefPtr<WebSocketChannel>        mChannel;
   1.658 +  uint16_t                          mCode;
   1.659 +  nsCString                         mReason;
   1.660 +};
   1.661 +NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
   1.662 +
   1.663 +//-----------------------------------------------------------------------------
   1.664 +// CallAcknowledge
   1.665 +//-----------------------------------------------------------------------------
   1.666 +
   1.667 +class CallAcknowledge MOZ_FINAL : public nsIRunnable
   1.668 +{
   1.669 +public:
   1.670 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.671 +
   1.672 +  CallAcknowledge(WebSocketChannel *aChannel,
   1.673 +                  uint32_t          aSize)
   1.674 +    : mChannel(aChannel),
   1.675 +      mSize(aSize) {}
   1.676 +
   1.677 +  NS_IMETHOD Run()
   1.678 +  {
   1.679 +    MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   1.680 +
   1.681 +    LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
   1.682 +    mChannel->mListener->OnAcknowledge(mChannel->mContext, mSize);
   1.683 +    return NS_OK;
   1.684 +  }
   1.685 +
   1.686 +private:
   1.687 +  ~CallAcknowledge() {}
   1.688 +
   1.689 +  nsRefPtr<WebSocketChannel>        mChannel;
   1.690 +  uint32_t                          mSize;
   1.691 +};
   1.692 +NS_IMPL_ISUPPORTS(CallAcknowledge, nsIRunnable)
   1.693 +
   1.694 +//-----------------------------------------------------------------------------
   1.695 +// CallOnTransportAvailable
   1.696 +//-----------------------------------------------------------------------------
   1.697 +
   1.698 +class CallOnTransportAvailable MOZ_FINAL : public nsIRunnable
   1.699 +{
   1.700 +public:
   1.701 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.702 +
   1.703 +  CallOnTransportAvailable(WebSocketChannel *aChannel,
   1.704 +                           nsISocketTransport *aTransport,
   1.705 +                           nsIAsyncInputStream *aSocketIn,
   1.706 +                           nsIAsyncOutputStream *aSocketOut)
   1.707 +    : mChannel(aChannel),
   1.708 +      mTransport(aTransport),
   1.709 +      mSocketIn(aSocketIn),
   1.710 +      mSocketOut(aSocketOut) {}
   1.711 +
   1.712 +  NS_IMETHOD Run()
   1.713 +  {
   1.714 +    LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
   1.715 +    return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
   1.716 +  }
   1.717 +
   1.718 +private:
   1.719 +  ~CallOnTransportAvailable() {}
   1.720 +
   1.721 +  nsRefPtr<WebSocketChannel>     mChannel;
   1.722 +  nsCOMPtr<nsISocketTransport>   mTransport;
   1.723 +  nsCOMPtr<nsIAsyncInputStream>  mSocketIn;
   1.724 +  nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
   1.725 +};
   1.726 +NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable)
   1.727 +
   1.728 +//-----------------------------------------------------------------------------
   1.729 +// OutboundMessage
   1.730 +//-----------------------------------------------------------------------------
   1.731 +
   1.732 +enum WsMsgType {
   1.733 +  kMsgTypeString = 0,
   1.734 +  kMsgTypeBinaryString,
   1.735 +  kMsgTypeStream,
   1.736 +  kMsgTypePing,
   1.737 +  kMsgTypePong,
   1.738 +  kMsgTypeFin
   1.739 +};
   1.740 +
   1.741 +static const char* msgNames[] = {
   1.742 +  "text",
   1.743 +  "binaryString",
   1.744 +  "binaryStream",
   1.745 +  "ping",
   1.746 +  "pong",
   1.747 +  "close"
   1.748 +};
   1.749 +
   1.750 +class OutboundMessage
   1.751 +{
   1.752 +public:
   1.753 +  OutboundMessage(WsMsgType type, nsCString *str)
   1.754 +    : mMsgType(type)
   1.755 +  {
   1.756 +    MOZ_COUNT_CTOR(OutboundMessage);
   1.757 +    mMsg.pString = str;
   1.758 +    mLength = str ? str->Length() : 0;
   1.759 +  }
   1.760 +
   1.761 +  OutboundMessage(nsIInputStream *stream, uint32_t length)
   1.762 +    : mMsgType(kMsgTypeStream), mLength(length)
   1.763 +  {
   1.764 +    MOZ_COUNT_CTOR(OutboundMessage);
   1.765 +    mMsg.pStream = stream;
   1.766 +    mMsg.pStream->AddRef();
   1.767 +  }
   1.768 +
   1.769 + ~OutboundMessage() {
   1.770 +    MOZ_COUNT_DTOR(OutboundMessage);
   1.771 +    switch (mMsgType) {
   1.772 +      case kMsgTypeString:
   1.773 +      case kMsgTypeBinaryString:
   1.774 +      case kMsgTypePing:
   1.775 +      case kMsgTypePong:
   1.776 +        delete mMsg.pString;
   1.777 +        break;
   1.778 +      case kMsgTypeStream:
   1.779 +        // for now this only gets hit if msg deleted w/o being sent
   1.780 +        if (mMsg.pStream) {
   1.781 +          mMsg.pStream->Close();
   1.782 +          mMsg.pStream->Release();
   1.783 +        }
   1.784 +        break;
   1.785 +      case kMsgTypeFin:
   1.786 +        break;    // do-nothing: avoid compiler warning
   1.787 +    }
   1.788 +  }
   1.789 +
   1.790 +  WsMsgType GetMsgType() const { return mMsgType; }
   1.791 +  int32_t Length() const { return mLength; }
   1.792 +
   1.793 +  uint8_t* BeginWriting() {
   1.794 +    NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream,
   1.795 +                      "Stream should have been converted to string by now");
   1.796 +    return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginWriting() : nullptr);
   1.797 +  }
   1.798 +
   1.799 +  uint8_t* BeginReading() {
   1.800 +    NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream,
   1.801 +                      "Stream should have been converted to string by now");
   1.802 +    return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginReading() : nullptr);
   1.803 +  }
   1.804 +
   1.805 +  nsresult ConvertStreamToString()
   1.806 +  {
   1.807 +    NS_ABORT_IF_FALSE(mMsgType == kMsgTypeStream, "Not a stream!");
   1.808 +
   1.809 +#ifdef DEBUG
   1.810 +    // Make sure we got correct length from Blob
   1.811 +    uint64_t bytes;
   1.812 +    mMsg.pStream->Available(&bytes);
   1.813 +    NS_ASSERTION(bytes == mLength, "Stream length != blob length!");
   1.814 +#endif
   1.815 +
   1.816 +    nsAutoPtr<nsCString> temp(new nsCString());
   1.817 +    nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
   1.818 +
   1.819 +    NS_ENSURE_SUCCESS(rv, rv);
   1.820 +
   1.821 +    mMsg.pStream->Close();
   1.822 +    mMsg.pStream->Release();
   1.823 +    mMsg.pString = temp.forget();
   1.824 +    mMsgType = kMsgTypeBinaryString;
   1.825 +
   1.826 +    return NS_OK;
   1.827 +  }
   1.828 +
   1.829 +private:
   1.830 +  union {
   1.831 +    nsCString      *pString;
   1.832 +    nsIInputStream *pStream;
   1.833 +  }                           mMsg;
   1.834 +  WsMsgType                   mMsgType;
   1.835 +  uint32_t                    mLength;
   1.836 +};
   1.837 +
   1.838 +//-----------------------------------------------------------------------------
   1.839 +// OutboundEnqueuer
   1.840 +//-----------------------------------------------------------------------------
   1.841 +
   1.842 +class OutboundEnqueuer MOZ_FINAL : public nsIRunnable
   1.843 +{
   1.844 +public:
   1.845 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.846 +
   1.847 +  OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg)
   1.848 +    : mChannel(aChannel), mMessage(aMsg) {}
   1.849 +
   1.850 +  NS_IMETHOD Run()
   1.851 +  {
   1.852 +    mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
   1.853 +    return NS_OK;
   1.854 +  }
   1.855 +
   1.856 +private:
   1.857 +  ~OutboundEnqueuer() {}
   1.858 +
   1.859 +  nsRefPtr<WebSocketChannel>  mChannel;
   1.860 +  OutboundMessage            *mMessage;
   1.861 +};
   1.862 +NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable)
   1.863 +
   1.864 +//-----------------------------------------------------------------------------
   1.865 +// nsWSCompression
   1.866 +//
   1.867 +// similar to nsDeflateConverter except for the mandatory FLUSH calls
   1.868 +// required by websocket and the absence of the deflate termination
   1.869 +// block which is appropriate because it would create data bytes after
   1.870 +// sending the websockets CLOSE message.
   1.871 +//-----------------------------------------------------------------------------
   1.872 +
   1.873 +class nsWSCompression
   1.874 +{
   1.875 +public:
   1.876 +  nsWSCompression(nsIStreamListener *aListener,
   1.877 +                  nsISupports *aContext)
   1.878 +    : mActive(false),
   1.879 +      mContext(aContext),
   1.880 +      mListener(aListener)
   1.881 +  {
   1.882 +    MOZ_COUNT_CTOR(nsWSCompression);
   1.883 +
   1.884 +    mZlib.zalloc = allocator;
   1.885 +    mZlib.zfree = destructor;
   1.886 +    mZlib.opaque = Z_NULL;
   1.887 +
   1.888 +    // Initialize the compressor - these are all the normal zlib
   1.889 +    // defaults except window size is set to -15 instead of +15.
   1.890 +    // This is the zlib way of specifying raw RFC 1951 output instead
   1.891 +    // of the zlib rfc 1950 format which has a 2 byte header and
   1.892 +    // adler checksum as a trailer
   1.893 +
   1.894 +    nsresult rv;
   1.895 +    mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
   1.896 +    if (NS_SUCCEEDED(rv) && aContext && aListener &&
   1.897 +      deflateInit2(&mZlib, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8,
   1.898 +                   Z_DEFAULT_STRATEGY) == Z_OK) {
   1.899 +      mActive = true;
   1.900 +    }
   1.901 +  }
   1.902 +
   1.903 +  ~nsWSCompression()
   1.904 +  {
   1.905 +    MOZ_COUNT_DTOR(nsWSCompression);
   1.906 +
   1.907 +    if (mActive)
   1.908 +      deflateEnd(&mZlib);
   1.909 +  }
   1.910 +
   1.911 +  bool Active()
   1.912 +  {
   1.913 +    return mActive;
   1.914 +  }
   1.915 +
   1.916 +  nsresult Deflate(uint8_t *buf1, uint32_t buf1Len,
   1.917 +                   uint8_t *buf2, uint32_t buf2Len)
   1.918 +  {
   1.919 +    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
   1.920 +                          "not socket thread");
   1.921 +    NS_ABORT_IF_FALSE(mActive, "not active");
   1.922 +
   1.923 +    mZlib.avail_out = kBufferLen;
   1.924 +    mZlib.next_out = mBuffer;
   1.925 +    mZlib.avail_in = buf1Len;
   1.926 +    mZlib.next_in = buf1;
   1.927 +
   1.928 +    nsresult rv;
   1.929 +
   1.930 +    while (mZlib.avail_in > 0) {
   1.931 +      deflate(&mZlib, (buf2Len > 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH);
   1.932 +      rv = PushData();
   1.933 +      if (NS_FAILED(rv))
   1.934 +        return rv;
   1.935 +      mZlib.avail_out = kBufferLen;
   1.936 +      mZlib.next_out = mBuffer;
   1.937 +    }
   1.938 +
   1.939 +    mZlib.avail_in = buf2Len;
   1.940 +    mZlib.next_in = buf2;
   1.941 +
   1.942 +    while (mZlib.avail_in > 0) {
   1.943 +      deflate(&mZlib, Z_SYNC_FLUSH);
   1.944 +      rv = PushData();
   1.945 +      if (NS_FAILED(rv))
   1.946 +        return rv;
   1.947 +      mZlib.avail_out = kBufferLen;
   1.948 +      mZlib.next_out = mBuffer;
   1.949 +    }
   1.950 +
   1.951 +    return NS_OK;
   1.952 +  }
   1.953 +
   1.954 +private:
   1.955 +
   1.956 +  // use zlib data types
   1.957 +  static void *allocator(void *opaque, uInt items, uInt size)
   1.958 +  {
   1.959 +    return moz_xmalloc(items * size);
   1.960 +  }
   1.961 +
   1.962 +  static void destructor(void *opaque, void *addr)
   1.963 +  {
   1.964 +    moz_free(addr);
   1.965 +  }
   1.966 +
   1.967 +  nsresult PushData()
   1.968 +  {
   1.969 +    uint32_t bytesToWrite = kBufferLen - mZlib.avail_out;
   1.970 +    if (bytesToWrite > 0) {
   1.971 +      mStream->ShareData(reinterpret_cast<char *>(mBuffer), bytesToWrite);
   1.972 +      nsresult rv =
   1.973 +        mListener->OnDataAvailable(nullptr, mContext, mStream, 0, bytesToWrite);
   1.974 +      if (NS_FAILED(rv))
   1.975 +        return rv;
   1.976 +    }
   1.977 +    return NS_OK;
   1.978 +  }
   1.979 +
   1.980 +  bool                            mActive;
   1.981 +  z_stream                        mZlib;
   1.982 +  nsCOMPtr<nsIStringInputStream>  mStream;
   1.983 +
   1.984 +  nsISupports                    *mContext;     /* weak ref */
   1.985 +  nsIStreamListener              *mListener;    /* weak ref */
   1.986 +
   1.987 +  const static int32_t            kBufferLen = 4096;
   1.988 +  uint8_t                         mBuffer[kBufferLen];
   1.989 +};
   1.990 +
   1.991 +//-----------------------------------------------------------------------------
   1.992 +// WebSocketChannel
   1.993 +//-----------------------------------------------------------------------------
   1.994 +
   1.995 +uint32_t WebSocketChannel::sSerialSeed = 0;
   1.996 +
   1.997 +WebSocketChannel::WebSocketChannel() :
   1.998 +  mPort(0),
   1.999 +  mCloseTimeout(20000),
  1.1000 +  mOpenTimeout(20000),
  1.1001 +  mConnecting(NOT_CONNECTING),
  1.1002 +  mMaxConcurrentConnections(200),
  1.1003 +  mGotUpgradeOK(0),
  1.1004 +  mRecvdHttpUpgradeTransport(0),
  1.1005 +  mRequestedClose(0),
  1.1006 +  mClientClosed(0),
  1.1007 +  mServerClosed(0),
  1.1008 +  mStopped(0),
  1.1009 +  mCalledOnStop(0),
  1.1010 +  mPingOutstanding(0),
  1.1011 +  mAllowCompression(1),
  1.1012 +  mAutoFollowRedirects(0),
  1.1013 +  mReleaseOnTransmit(0),
  1.1014 +  mTCPClosed(0),
  1.1015 +  mOpenedHttpChannel(0),
  1.1016 +  mDataStarted(0),
  1.1017 +  mIncrementedSessionCount(0),
  1.1018 +  mDecrementedSessionCount(0),
  1.1019 +  mMaxMessageSize(INT32_MAX),
  1.1020 +  mStopOnClose(NS_OK),
  1.1021 +  mServerCloseCode(CLOSE_ABNORMAL),
  1.1022 +  mScriptCloseCode(0),
  1.1023 +  mFragmentOpcode(kContinuation),
  1.1024 +  mFragmentAccumulator(0),
  1.1025 +  mBuffered(0),
  1.1026 +  mBufferSize(kIncomingBufferInitialSize),
  1.1027 +  mCurrentOut(nullptr),
  1.1028 +  mCurrentOutSent(0),
  1.1029 +  mCompressor(nullptr),
  1.1030 +  mDynamicOutputSize(0),
  1.1031 +  mDynamicOutput(nullptr),
  1.1032 +  mPrivateBrowsing(false),
  1.1033 +  mConnectionLogService(nullptr),
  1.1034 +  mCountRecv(0),
  1.1035 +  mCountSent(0),
  1.1036 +  mAppId(NECKO_NO_APP_ID)
  1.1037 +{
  1.1038 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.1039 +
  1.1040 +  LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
  1.1041 +
  1.1042 +  nsWSAdmissionManager::Init();
  1.1043 +
  1.1044 +  mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
  1.1045 +
  1.1046 +  nsresult rv;
  1.1047 +  mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
  1.1048 +  if (NS_FAILED(rv))
  1.1049 +    LOG(("Failed to initiate dashboard service."));
  1.1050 +
  1.1051 +  mSerial = sSerialSeed++;
  1.1052 +}
  1.1053 +
  1.1054 +WebSocketChannel::~WebSocketChannel()
  1.1055 +{
  1.1056 +  LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
  1.1057 +
  1.1058 +  if (mWasOpened) {
  1.1059 +    MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
  1.1060 +    MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
  1.1061 +  }
  1.1062 +  MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
  1.1063 +  MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
  1.1064 +
  1.1065 +  moz_free(mBuffer);
  1.1066 +  moz_free(mDynamicOutput);
  1.1067 +  delete mCompressor;
  1.1068 +  delete mCurrentOut;
  1.1069 +
  1.1070 +  while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
  1.1071 +    delete mCurrentOut;
  1.1072 +  while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
  1.1073 +    delete mCurrentOut;
  1.1074 +  while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
  1.1075 +    delete mCurrentOut;
  1.1076 +
  1.1077 +  nsCOMPtr<nsIThread> mainThread;
  1.1078 +  nsIURI *forgettable;
  1.1079 +  NS_GetMainThread(getter_AddRefs(mainThread));
  1.1080 +
  1.1081 +  if (mURI) {
  1.1082 +    mURI.forget(&forgettable);
  1.1083 +    NS_ProxyRelease(mainThread, forgettable, false);
  1.1084 +  }
  1.1085 +
  1.1086 +  if (mOriginalURI) {
  1.1087 +    mOriginalURI.forget(&forgettable);
  1.1088 +    NS_ProxyRelease(mainThread, forgettable, false);
  1.1089 +  }
  1.1090 +
  1.1091 +  if (mListener) {
  1.1092 +    nsIWebSocketListener *forgettableListener;
  1.1093 +    mListener.forget(&forgettableListener);
  1.1094 +    NS_ProxyRelease(mainThread, forgettableListener, false);
  1.1095 +  }
  1.1096 +
  1.1097 +  if (mContext) {
  1.1098 +    nsISupports *forgettableContext;
  1.1099 +    mContext.forget(&forgettableContext);
  1.1100 +    NS_ProxyRelease(mainThread, forgettableContext, false);
  1.1101 +  }
  1.1102 +
  1.1103 +  if (mLoadGroup) {
  1.1104 +    nsILoadGroup *forgettableGroup;
  1.1105 +    mLoadGroup.forget(&forgettableGroup);
  1.1106 +    NS_ProxyRelease(mainThread, forgettableGroup, false);
  1.1107 +  }
  1.1108 +}
  1.1109 +
  1.1110 +void
  1.1111 +WebSocketChannel::Shutdown()
  1.1112 +{
  1.1113 +  nsWSAdmissionManager::Shutdown();
  1.1114 +}
  1.1115 +
  1.1116 +void
  1.1117 +WebSocketChannel::BeginOpen()
  1.1118 +{
  1.1119 +  LOG(("WebSocketChannel::BeginOpen() %p\n", this));
  1.1120 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.1121 +
  1.1122 +  nsresult rv;
  1.1123 +
  1.1124 +  // Important that we set CONNECTING_IN_PROGRESS before any call to
  1.1125 +  // AbortSession here: ensures that any remaining queued connection(s) are
  1.1126 +  // scheduled in OnStopSession
  1.1127 +  mConnecting = CONNECTING_IN_PROGRESS;
  1.1128 +
  1.1129 +  if (mRedirectCallback) {
  1.1130 +    LOG(("WebSocketChannel::BeginOpen: Resuming Redirect\n"));
  1.1131 +    rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
  1.1132 +    mRedirectCallback = nullptr;
  1.1133 +    return;
  1.1134 +  }
  1.1135 +
  1.1136 +  nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
  1.1137 +  if (NS_FAILED(rv)) {
  1.1138 +    LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
  1.1139 +    AbortSession(NS_ERROR_UNEXPECTED);
  1.1140 +    return;
  1.1141 +  }
  1.1142 +
  1.1143 +  if (localChannel) {
  1.1144 +    bool isInBrowser;
  1.1145 +    NS_GetAppInfo(localChannel, &mAppId, &isInBrowser);
  1.1146 +  }
  1.1147 +
  1.1148 +#ifdef MOZ_WIDGET_GONK
  1.1149 +  if (mAppId != NECKO_NO_APP_ID) {
  1.1150 +    nsCOMPtr<nsINetworkInterface> activeNetwork;
  1.1151 +    GetActiveNetworkInterface(activeNetwork);
  1.1152 +    mActiveNetwork =
  1.1153 +      new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork);
  1.1154 +  }
  1.1155 +#endif
  1.1156 +
  1.1157 +  rv = localChannel->AsyncOpen(this, mHttpChannel);
  1.1158 +  if (NS_FAILED(rv)) {
  1.1159 +    LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
  1.1160 +    AbortSession(NS_ERROR_CONNECTION_REFUSED);
  1.1161 +    return;
  1.1162 +  }
  1.1163 +  mOpenedHttpChannel = 1;
  1.1164 +
  1.1165 +  mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1.1166 +  if (NS_FAILED(rv)) {
  1.1167 +    LOG(("WebSocketChannel::BeginOpen: cannot create open timer\n"));
  1.1168 +    AbortSession(NS_ERROR_UNEXPECTED);
  1.1169 +    return;
  1.1170 +  }
  1.1171 +
  1.1172 +  rv = mOpenTimer->InitWithCallback(this, mOpenTimeout,
  1.1173 +                                    nsITimer::TYPE_ONE_SHOT);
  1.1174 +  if (NS_FAILED(rv)) {
  1.1175 +    LOG(("WebSocketChannel::BeginOpen: cannot initialize open timer\n"));
  1.1176 +    AbortSession(NS_ERROR_UNEXPECTED);
  1.1177 +    return;
  1.1178 +  }
  1.1179 +}
  1.1180 +
  1.1181 +bool
  1.1182 +WebSocketChannel::IsPersistentFramePtr()
  1.1183 +{
  1.1184 +  return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
  1.1185 +}
  1.1186 +
  1.1187 +// Extends the internal buffer by count and returns the total
  1.1188 +// amount of data available for read
  1.1189 +//
  1.1190 +// Accumulated fragment size is passed in instead of using the member
  1.1191 +// variable beacuse when transitioning from the stack to the persistent
  1.1192 +// read buffer we want to explicitly include them in the buffer instead
  1.1193 +// of as already existing data.
  1.1194 +bool
  1.1195 +WebSocketChannel::UpdateReadBuffer(uint8_t *buffer, uint32_t count,
  1.1196 +                                   uint32_t accumulatedFragments,
  1.1197 +                                   uint32_t *available)
  1.1198 +{
  1.1199 +  LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n",
  1.1200 +         this, buffer, count));
  1.1201 +
  1.1202 +  if (!mBuffered)
  1.1203 +    mFramePtr = mBuffer;
  1.1204 +
  1.1205 +  NS_ABORT_IF_FALSE(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
  1.1206 +  NS_ABORT_IF_FALSE(mFramePtr - accumulatedFragments >= mBuffer,
  1.1207 +                    "reserved FramePtr bad");
  1.1208 +
  1.1209 +  if (mBuffered + count <= mBufferSize) {
  1.1210 +    // append to existing buffer
  1.1211 +    LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
  1.1212 +  } else if (mBuffered + count - 
  1.1213 +             (mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) {
  1.1214 +    // make room in existing buffer by shifting unused data to start
  1.1215 +    mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
  1.1216 +    LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
  1.1217 +    ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
  1.1218 +    mFramePtr = mBuffer + accumulatedFragments;
  1.1219 +  } else {
  1.1220 +    // existing buffer is not sufficient, extend it
  1.1221 +    mBufferSize += count + 8192 + mBufferSize/3;
  1.1222 +    LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
  1.1223 +    uint8_t *old = mBuffer;
  1.1224 +    mBuffer = (uint8_t *)moz_realloc(mBuffer, mBufferSize);
  1.1225 +    if (!mBuffer) {
  1.1226 +      mBuffer = old;
  1.1227 +      return false;
  1.1228 +    }
  1.1229 +    mFramePtr = mBuffer + (mFramePtr - old);
  1.1230 +  }
  1.1231 +
  1.1232 +  ::memcpy(mBuffer + mBuffered, buffer, count);
  1.1233 +  mBuffered += count;
  1.1234 +
  1.1235 +  if (available)
  1.1236 +    *available = mBuffered - (mFramePtr - mBuffer);
  1.1237 +
  1.1238 +  return true;
  1.1239 +}
  1.1240 +
  1.1241 +nsresult
  1.1242 +WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
  1.1243 +{
  1.1244 +  LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
  1.1245 +  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1.1246 +
  1.1247 +  // The purpose of ping/pong is to actively probe the peer so that an
  1.1248 +  // unreachable peer is not mistaken for a period of idleness. This
  1.1249 +  // implementation accepts any application level read activity as a sign of
  1.1250 +  // life, it does not necessarily have to be a pong.
  1.1251 +  ResetPingTimer();
  1.1252 +
  1.1253 +  uint32_t avail;
  1.1254 +
  1.1255 +  if (!mBuffered) {
  1.1256 +    // Most of the time we can process right off the stack buffer without
  1.1257 +    // having to accumulate anything
  1.1258 +    mFramePtr = buffer;
  1.1259 +    avail = count;
  1.1260 +  } else {
  1.1261 +    if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
  1.1262 +      return NS_ERROR_FILE_TOO_BIG;
  1.1263 +    }
  1.1264 +  }
  1.1265 +
  1.1266 +  uint8_t *payload;
  1.1267 +  uint32_t totalAvail = avail;
  1.1268 +
  1.1269 +  while (avail >= 2) {
  1.1270 +    int64_t payloadLength64 = mFramePtr[1] & 0x7F;
  1.1271 +    uint8_t finBit  = mFramePtr[0] & kFinalFragBit;
  1.1272 +    uint8_t rsvBits = mFramePtr[0] & 0x70;
  1.1273 +    uint8_t maskBit = mFramePtr[1] & kMaskBit;
  1.1274 +    uint8_t opcode  = mFramePtr[0] & 0x0F;
  1.1275 +
  1.1276 +    uint32_t framingLength = 2;
  1.1277 +    if (maskBit)
  1.1278 +      framingLength += 4;
  1.1279 +
  1.1280 +    if (payloadLength64 < 126) {
  1.1281 +      if (avail < framingLength)
  1.1282 +        break;
  1.1283 +    } else if (payloadLength64 == 126) {
  1.1284 +      // 16 bit length field
  1.1285 +      framingLength += 2;
  1.1286 +      if (avail < framingLength)
  1.1287 +        break;
  1.1288 +
  1.1289 +      payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
  1.1290 +    } else {
  1.1291 +      // 64 bit length
  1.1292 +      framingLength += 8;
  1.1293 +      if (avail < framingLength)
  1.1294 +        break;
  1.1295 +
  1.1296 +      if (mFramePtr[2] & 0x80) {
  1.1297 +        // Section 4.2 says that the most significant bit MUST be
  1.1298 +        // 0. (i.e. this is really a 63 bit value)
  1.1299 +        LOG(("WebSocketChannel:: high bit of 64 bit length set"));
  1.1300 +        return NS_ERROR_ILLEGAL_VALUE;
  1.1301 +      }
  1.1302 +
  1.1303 +      // copy this in case it is unaligned
  1.1304 +      payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
  1.1305 +    }
  1.1306 +
  1.1307 +    payload = mFramePtr + framingLength;
  1.1308 +    avail -= framingLength;
  1.1309 +
  1.1310 +    LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n",
  1.1311 +         payloadLength64, avail));
  1.1312 +
  1.1313 +    if (payloadLength64 + mFragmentAccumulator > mMaxMessageSize) {
  1.1314 +      return NS_ERROR_FILE_TOO_BIG;
  1.1315 +    }
  1.1316 +    uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
  1.1317 +
  1.1318 +    if (avail < payloadLength)
  1.1319 +      break;
  1.1320 +
  1.1321 +    LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
  1.1322 +         opcode));
  1.1323 +
  1.1324 +    if (maskBit) {
  1.1325 +      // This is unexpected - the server does not generally send masked
  1.1326 +      // frames to the client, but it is allowed
  1.1327 +      LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
  1.1328 +
  1.1329 +      uint32_t mask = NetworkEndian::readUint32(payload - 4);
  1.1330 +      ApplyMask(mask, payload, payloadLength);
  1.1331 +    }
  1.1332 +
  1.1333 +    // Control codes are required to have the fin bit set
  1.1334 +    if (!finBit && (opcode & kControlFrameMask)) {
  1.1335 +      LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
  1.1336 +      return NS_ERROR_ILLEGAL_VALUE;
  1.1337 +    }
  1.1338 +
  1.1339 +    if (rsvBits) {
  1.1340 +      LOG(("WebSocketChannel:: unexpected reserved bits %x\n", rsvBits));
  1.1341 +      return NS_ERROR_ILLEGAL_VALUE;
  1.1342 +    }
  1.1343 +
  1.1344 +    if (!finBit || opcode == kContinuation) {
  1.1345 +      // This is part of a fragment response
  1.1346 +
  1.1347 +      // Only the first frame has a non zero op code: Make sure we don't see a
  1.1348 +      // first frame while some old fragments are open
  1.1349 +      if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) {
  1.1350 +        LOG(("WebSocketChannel:: nested fragments\n"));
  1.1351 +        return NS_ERROR_ILLEGAL_VALUE;
  1.1352 +      }
  1.1353 +
  1.1354 +      LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
  1.1355 +
  1.1356 +      if (opcode == kContinuation) {
  1.1357 +
  1.1358 +        // Make sure this continuation fragment isn't the first fragment
  1.1359 +        if (mFragmentOpcode == kContinuation) {
  1.1360 +          LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
  1.1361 +          return NS_ERROR_ILLEGAL_VALUE;
  1.1362 +        }
  1.1363 +
  1.1364 +        // For frag > 1 move the data body back on top of the headers
  1.1365 +        // so we have contiguous stream of data
  1.1366 +        NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload,
  1.1367 +                          "payload offset from frameptr wrong");
  1.1368 +        ::memmove(mFramePtr, payload, avail);
  1.1369 +        payload = mFramePtr;
  1.1370 +        if (mBuffered)
  1.1371 +          mBuffered -= framingLength;
  1.1372 +      } else {
  1.1373 +        mFragmentOpcode = opcode;
  1.1374 +      }
  1.1375 +
  1.1376 +      if (finBit) {
  1.1377 +        LOG(("WebSocketChannel:: Finalizing Fragment\n"));
  1.1378 +        payload -= mFragmentAccumulator;
  1.1379 +        payloadLength += mFragmentAccumulator;
  1.1380 +        avail += mFragmentAccumulator;
  1.1381 +        mFragmentAccumulator = 0;
  1.1382 +        opcode = mFragmentOpcode;
  1.1383 +        // reset to detect if next message illegally starts with continuation
  1.1384 +        mFragmentOpcode = kContinuation;
  1.1385 +      } else {
  1.1386 +        opcode = kContinuation;
  1.1387 +        mFragmentAccumulator += payloadLength;
  1.1388 +      }
  1.1389 +    } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
  1.1390 +      // This frame is not part of a fragment sequence but we
  1.1391 +      // have an open fragment.. it must be a control code or else
  1.1392 +      // we have a problem
  1.1393 +      LOG(("WebSocketChannel:: illegal fragment sequence\n"));
  1.1394 +      return NS_ERROR_ILLEGAL_VALUE;
  1.1395 +    }
  1.1396 +
  1.1397 +    if (mServerClosed) {
  1.1398 +      LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
  1.1399 +                 opcode));
  1.1400 +      // nop
  1.1401 +    } else if (mStopped) {
  1.1402 +      LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
  1.1403 +           opcode));
  1.1404 +    } else if (opcode == kText) {
  1.1405 +      LOG(("WebSocketChannel:: text frame received\n"));
  1.1406 +      if (mListener) {
  1.1407 +        nsCString utf8Data;
  1.1408 +        if (!utf8Data.Assign((const char *)payload, payloadLength,
  1.1409 +                             mozilla::fallible_t()))
  1.1410 +          return NS_ERROR_OUT_OF_MEMORY;
  1.1411 +
  1.1412 +        // Section 8.1 says to fail connection if invalid utf-8 in text message
  1.1413 +        if (!IsUTF8(utf8Data, false)) {
  1.1414 +          LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
  1.1415 +          return NS_ERROR_CANNOT_CONVERT_DATA;
  1.1416 +        }
  1.1417 +
  1.1418 +        mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
  1.1419 +                                NS_DISPATCH_NORMAL);
  1.1420 +        if (mConnectionLogService && !mPrivateBrowsing) {
  1.1421 +          mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
  1.1422 +          LOG(("Added new msg received for %s", mHost.get()));
  1.1423 +        }
  1.1424 +      }
  1.1425 +    } else if (opcode & kControlFrameMask) {
  1.1426 +      // control frames
  1.1427 +      if (payloadLength > 125) {
  1.1428 +        LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
  1.1429 +             opcode, payloadLength));
  1.1430 +        return NS_ERROR_ILLEGAL_VALUE;
  1.1431 +      }
  1.1432 +
  1.1433 +      if (opcode == kClose) {
  1.1434 +        LOG(("WebSocketChannel:: close received\n"));
  1.1435 +        mServerClosed = 1;
  1.1436 +
  1.1437 +        mServerCloseCode = CLOSE_NO_STATUS;
  1.1438 +        if (payloadLength >= 2) {
  1.1439 +          mServerCloseCode = NetworkEndian::readUint16(payload);
  1.1440 +          LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
  1.1441 +          uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
  1.1442 +          if (msglen > 0) {
  1.1443 +            mServerCloseReason.SetLength(msglen);
  1.1444 +            memcpy(mServerCloseReason.BeginWriting(),
  1.1445 +                   (const char *)payload + 2, msglen);
  1.1446 +
  1.1447 +            // section 8.1 says to replace received non utf-8 sequences
  1.1448 +            // (which are non-conformant to send) with u+fffd,
  1.1449 +            // but secteam feels that silently rewriting messages is
  1.1450 +            // inappropriate - so we will fail the connection instead.
  1.1451 +            if (!IsUTF8(mServerCloseReason, false)) {
  1.1452 +              LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
  1.1453 +              return NS_ERROR_CANNOT_CONVERT_DATA;
  1.1454 +            }
  1.1455 +
  1.1456 +            LOG(("WebSocketChannel:: close msg %s\n",
  1.1457 +                 mServerCloseReason.get()));
  1.1458 +          }
  1.1459 +        }
  1.1460 +
  1.1461 +        if (mCloseTimer) {
  1.1462 +          mCloseTimer->Cancel();
  1.1463 +          mCloseTimer = nullptr;
  1.1464 +        }
  1.1465 +        if (mListener) {
  1.1466 +          mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
  1.1467 +                                                        mServerCloseReason),
  1.1468 +                                  NS_DISPATCH_NORMAL);
  1.1469 +        }
  1.1470 +
  1.1471 +        if (mClientClosed)
  1.1472 +          ReleaseSession();
  1.1473 +      } else if (opcode == kPing) {
  1.1474 +        LOG(("WebSocketChannel:: ping received\n"));
  1.1475 +        GeneratePong(payload, payloadLength);
  1.1476 +      } else if (opcode == kPong) {
  1.1477 +        // opcode kPong: the mere act of receiving the packet is all we need
  1.1478 +        // to do for the pong to trigger the activity timers
  1.1479 +        LOG(("WebSocketChannel:: pong received\n"));
  1.1480 +      } else {
  1.1481 +        /* unknown control frame opcode */
  1.1482 +        LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
  1.1483 +        return NS_ERROR_ILLEGAL_VALUE;
  1.1484 +      }
  1.1485 +
  1.1486 +      if (mFragmentAccumulator) {
  1.1487 +        // Remove the control frame from the stream so we have a contiguous
  1.1488 +        // data buffer of reassembled fragments
  1.1489 +        LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
  1.1490 +        NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload,
  1.1491 +                          "payload offset from frameptr wrong");
  1.1492 +        ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
  1.1493 +        payload = mFramePtr;
  1.1494 +        avail -= payloadLength;
  1.1495 +        if (mBuffered)
  1.1496 +          mBuffered -= framingLength + payloadLength;
  1.1497 +        payloadLength = 0;
  1.1498 +      }
  1.1499 +    } else if (opcode == kBinary) {
  1.1500 +      LOG(("WebSocketChannel:: binary frame received\n"));
  1.1501 +      if (mListener) {
  1.1502 +        nsCString binaryData((const char *)payload, payloadLength);
  1.1503 +        mTargetThread->Dispatch(new CallOnMessageAvailable(this, binaryData,
  1.1504 +                                                           payloadLength),
  1.1505 +                                NS_DISPATCH_NORMAL);
  1.1506 +        // To add the header to 'Networking Dashboard' log
  1.1507 +        if (mConnectionLogService && !mPrivateBrowsing) {
  1.1508 +          mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
  1.1509 +          LOG(("Added new received msg for %s", mHost.get()));
  1.1510 +        }
  1.1511 +      }
  1.1512 +    } else if (opcode != kContinuation) {
  1.1513 +      /* unknown opcode */
  1.1514 +      LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
  1.1515 +      return NS_ERROR_ILLEGAL_VALUE;
  1.1516 +    }
  1.1517 +
  1.1518 +    mFramePtr = payload + payloadLength;
  1.1519 +    avail -= payloadLength;
  1.1520 +    totalAvail = avail;
  1.1521 +  }
  1.1522 +
  1.1523 +  // Adjust the stateful buffer. If we were operating off the stack and
  1.1524 +  // now have a partial message then transition to the buffer, or if
  1.1525 +  // we were working off the buffer but no longer have any active state
  1.1526 +  // then transition to the stack
  1.1527 +  if (!IsPersistentFramePtr()) {
  1.1528 +    mBuffered = 0;
  1.1529 +
  1.1530 +    if (mFragmentAccumulator) {
  1.1531 +      LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
  1.1532 +
  1.1533 +      if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
  1.1534 +                            totalAvail + mFragmentAccumulator, 0, nullptr)) {
  1.1535 +        return NS_ERROR_FILE_TOO_BIG;
  1.1536 +      }
  1.1537 +
  1.1538 +      // UpdateReadBuffer will reset the frameptr to the beginning
  1.1539 +      // of new saved state, so we need to skip past processed framgents
  1.1540 +      mFramePtr += mFragmentAccumulator;
  1.1541 +    } else if (totalAvail) {
  1.1542 +      LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
  1.1543 +      if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
  1.1544 +        return NS_ERROR_FILE_TOO_BIG;
  1.1545 +      }
  1.1546 +    }
  1.1547 +  } else if (!mFragmentAccumulator && !totalAvail) {
  1.1548 +    // If we were working off a saved buffer state and there is no partial
  1.1549 +    // frame or fragment in process, then revert to stack behavior
  1.1550 +    LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
  1.1551 +    mBuffered = 0;
  1.1552 +
  1.1553 +    // release memory if we've been processing a large message
  1.1554 +    if (mBufferSize > kIncomingBufferStableSize) {
  1.1555 +      mBufferSize = kIncomingBufferStableSize;
  1.1556 +      moz_free(mBuffer);
  1.1557 +      mBuffer = (uint8_t *)moz_xmalloc(mBufferSize);
  1.1558 +    }
  1.1559 +  }
  1.1560 +  return NS_OK;
  1.1561 +}
  1.1562 +
  1.1563 +void
  1.1564 +WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
  1.1565 +{
  1.1566 +  if (!data || len == 0)
  1.1567 +    return;
  1.1568 +
  1.1569 +  // Optimally we want to apply the mask 32 bits at a time,
  1.1570 +  // but the buffer might not be alligned. So we first deal with
  1.1571 +  // 0 to 3 bytes of preamble individually
  1.1572 +
  1.1573 +  while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
  1.1574 +    *data ^= mask >> 24;
  1.1575 +    mask = RotateLeft(mask, 8);
  1.1576 +    data++;
  1.1577 +    len--;
  1.1578 +  }
  1.1579 +
  1.1580 +  // perform mask on full words of data
  1.1581 +
  1.1582 +  uint32_t *iData = (uint32_t *) data;
  1.1583 +  uint32_t *end = iData + (len / 4);
  1.1584 +  NetworkEndian::writeUint32(&mask, mask);
  1.1585 +  for (; iData < end; iData++)
  1.1586 +    *iData ^= mask;
  1.1587 +  mask = NetworkEndian::readUint32(&mask);
  1.1588 +  data = (uint8_t *)iData;
  1.1589 +  len  = len % 4;
  1.1590 +
  1.1591 +  // There maybe up to 3 trailing bytes that need to be dealt with
  1.1592 +  // individually 
  1.1593 +
  1.1594 +  while (len) {
  1.1595 +    *data ^= mask >> 24;
  1.1596 +    mask = RotateLeft(mask, 8);
  1.1597 +    data++;
  1.1598 +    len--;
  1.1599 +  }
  1.1600 +}
  1.1601 +
  1.1602 +void
  1.1603 +WebSocketChannel::GeneratePing()
  1.1604 +{
  1.1605 +  nsCString *buf = new nsCString();
  1.1606 +  buf->Assign("PING");
  1.1607 +  EnqueueOutgoingMessage(mOutgoingPingMessages,
  1.1608 +                         new OutboundMessage(kMsgTypePing, buf));
  1.1609 +}
  1.1610 +
  1.1611 +void
  1.1612 +WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len)
  1.1613 +{
  1.1614 +  nsCString *buf = new nsCString();
  1.1615 +  buf->SetLength(len);
  1.1616 +  if (buf->Length() < len) {
  1.1617 +    LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
  1.1618 +    delete buf;
  1.1619 +    return;
  1.1620 +  }
  1.1621 +
  1.1622 +  memcpy(buf->BeginWriting(), payload, len);
  1.1623 +  EnqueueOutgoingMessage(mOutgoingPongMessages,
  1.1624 +                         new OutboundMessage(kMsgTypePong, buf));
  1.1625 +}
  1.1626 +
  1.1627 +void
  1.1628 +WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue,
  1.1629 +                                         OutboundMessage *aMsg)
  1.1630 +{
  1.1631 +  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1.1632 +
  1.1633 +  LOG(("WebSocketChannel::EnqueueOutgoingMessage %p "
  1.1634 +       "queueing msg %p [type=%s len=%d]\n",
  1.1635 +       this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
  1.1636 +
  1.1637 +  aQueue.Push(aMsg);
  1.1638 +  OnOutputStreamReady(mSocketOut);
  1.1639 +}
  1.1640 +
  1.1641 +
  1.1642 +uint16_t
  1.1643 +WebSocketChannel::ResultToCloseCode(nsresult resultCode)
  1.1644 +{
  1.1645 +  if (NS_SUCCEEDED(resultCode))
  1.1646 +    return CLOSE_NORMAL;
  1.1647 +
  1.1648 +  switch (resultCode) {
  1.1649 +    case NS_ERROR_FILE_TOO_BIG:
  1.1650 +    case NS_ERROR_OUT_OF_MEMORY:
  1.1651 +      return CLOSE_TOO_LARGE;
  1.1652 +    case NS_ERROR_CANNOT_CONVERT_DATA:
  1.1653 +      return CLOSE_INVALID_PAYLOAD;
  1.1654 +    case NS_ERROR_UNEXPECTED:
  1.1655 +      return CLOSE_INTERNAL_ERROR;
  1.1656 +    default:
  1.1657 +      return CLOSE_PROTOCOL_ERROR;
  1.1658 +  }
  1.1659 +}
  1.1660 +
  1.1661 +void
  1.1662 +WebSocketChannel::PrimeNewOutgoingMessage()
  1.1663 +{
  1.1664 +  LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
  1.1665 +  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1.1666 +  NS_ABORT_IF_FALSE(!mCurrentOut, "Current message in progress");
  1.1667 +
  1.1668 +  nsresult rv = NS_OK;
  1.1669 +
  1.1670 +  mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront();
  1.1671 +  if (mCurrentOut) {
  1.1672 +    NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePong,
  1.1673 +                     "Not pong message!");
  1.1674 +  } else {
  1.1675 +    mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront();
  1.1676 +    if (mCurrentOut)
  1.1677 +      NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePing,
  1.1678 +                        "Not ping message!");
  1.1679 +    else
  1.1680 +      mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront();
  1.1681 +  }
  1.1682 +
  1.1683 +  if (!mCurrentOut)
  1.1684 +    return;
  1.1685 +
  1.1686 +  WsMsgType msgType = mCurrentOut->GetMsgType();
  1.1687 +
  1.1688 +  LOG(("WebSocketChannel::PrimeNewOutgoingMessage "
  1.1689 +       "%p found queued msg %p [type=%s len=%d]\n",
  1.1690 +       this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
  1.1691 +
  1.1692 +  mCurrentOutSent = 0;
  1.1693 +  mHdrOut = mOutHeader;
  1.1694 +
  1.1695 +  uint8_t *payload = nullptr;
  1.1696 +
  1.1697 +  if (msgType == kMsgTypeFin) {
  1.1698 +    // This is a demand to create a close message
  1.1699 +    if (mClientClosed) {
  1.1700 +      DeleteCurrentOutGoingMessage();
  1.1701 +      PrimeNewOutgoingMessage();
  1.1702 +      return;
  1.1703 +    }
  1.1704 +
  1.1705 +    mClientClosed = 1;
  1.1706 +    mOutHeader[0] = kFinalFragBit | kClose;
  1.1707 +    mOutHeader[1] = kMaskBit;
  1.1708 +
  1.1709 +    // payload is offset 6 including 4 for the mask
  1.1710 +    payload = mOutHeader + 6;
  1.1711 +
  1.1712 +    // The close reason code sits in the first 2 bytes of payload
  1.1713 +    // If the channel user provided a code and reason during Close()
  1.1714 +    // and there isn't an internal error, use that.
  1.1715 +    if (NS_SUCCEEDED(mStopOnClose)) {
  1.1716 +      if (mScriptCloseCode) {
  1.1717 +        NetworkEndian::writeUint16(payload, mScriptCloseCode);
  1.1718 +        mOutHeader[1] += 2;
  1.1719 +        mHdrOutToSend = 8;
  1.1720 +        if (!mScriptCloseReason.IsEmpty()) {
  1.1721 +          NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123,
  1.1722 +                            "Close Reason Too Long");
  1.1723 +          mOutHeader[1] += mScriptCloseReason.Length();
  1.1724 +          mHdrOutToSend += mScriptCloseReason.Length();
  1.1725 +          memcpy (payload + 2,
  1.1726 +                  mScriptCloseReason.BeginReading(),
  1.1727 +                  mScriptCloseReason.Length());
  1.1728 +        }
  1.1729 +      } else {
  1.1730 +        // No close code/reason, so payload length = 0.  We must still send mask
  1.1731 +        // even though it's not used.  Keep payload offset so we write mask
  1.1732 +        // below.
  1.1733 +        mHdrOutToSend = 6;
  1.1734 +      }
  1.1735 +    } else {
  1.1736 +      NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
  1.1737 +      mOutHeader[1] += 2;
  1.1738 +      mHdrOutToSend = 8;
  1.1739 +    }
  1.1740 +
  1.1741 +    if (mServerClosed) {
  1.1742 +      /* bidi close complete */
  1.1743 +      mReleaseOnTransmit = 1;
  1.1744 +    } else if (NS_FAILED(mStopOnClose)) {
  1.1745 +      /* result of abort session - give up */
  1.1746 +      StopSession(mStopOnClose);
  1.1747 +    } else {
  1.1748 +      /* wait for reciprocal close from server */
  1.1749 +      mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1.1750 +      if (NS_SUCCEEDED(rv)) {
  1.1751 +        mCloseTimer->InitWithCallback(this, mCloseTimeout,
  1.1752 +                                      nsITimer::TYPE_ONE_SHOT);
  1.1753 +      } else {
  1.1754 +        StopSession(rv);
  1.1755 +      }
  1.1756 +    }
  1.1757 +  } else {
  1.1758 +    switch (msgType) {
  1.1759 +    case kMsgTypePong:
  1.1760 +      mOutHeader[0] = kFinalFragBit | kPong;
  1.1761 +      break;
  1.1762 +    case kMsgTypePing:
  1.1763 +      mOutHeader[0] = kFinalFragBit | kPing;
  1.1764 +      break;
  1.1765 +    case kMsgTypeString:
  1.1766 +      mOutHeader[0] = kFinalFragBit | kText;
  1.1767 +      break;
  1.1768 +    case kMsgTypeStream:
  1.1769 +      // HACK ALERT:  read in entire stream into string.
  1.1770 +      // Will block socket transport thread if file is blocking.
  1.1771 +      // TODO: bug 704447:  don't block socket thread!
  1.1772 +      rv = mCurrentOut->ConvertStreamToString();
  1.1773 +      if (NS_FAILED(rv)) {
  1.1774 +        AbortSession(NS_ERROR_FILE_TOO_BIG);
  1.1775 +        return;
  1.1776 +      }
  1.1777 +      // Now we're a binary string
  1.1778 +      msgType = kMsgTypeBinaryString;
  1.1779 +
  1.1780 +      // no break: fall down into binary string case
  1.1781 +
  1.1782 +    case kMsgTypeBinaryString:
  1.1783 +      mOutHeader[0] = kFinalFragBit | kBinary;
  1.1784 +      break;
  1.1785 +    case kMsgTypeFin:
  1.1786 +      NS_ABORT_IF_FALSE(false, "unreachable");  // avoid compiler warning
  1.1787 +      break;
  1.1788 +    }
  1.1789 +
  1.1790 +    if (mCurrentOut->Length() < 126) {
  1.1791 +      mOutHeader[1] = mCurrentOut->Length() | kMaskBit;
  1.1792 +      mHdrOutToSend = 6;
  1.1793 +    } else if (mCurrentOut->Length() <= 0xffff) {
  1.1794 +      mOutHeader[1] = 126 | kMaskBit;
  1.1795 +      NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
  1.1796 +                                 mCurrentOut->Length());
  1.1797 +      mHdrOutToSend = 8;
  1.1798 +    } else {
  1.1799 +      mOutHeader[1] = 127 | kMaskBit;
  1.1800 +      NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
  1.1801 +      mHdrOutToSend = 14;
  1.1802 +    }
  1.1803 +    payload = mOutHeader + mHdrOutToSend;
  1.1804 +  }
  1.1805 +
  1.1806 +  NS_ABORT_IF_FALSE(payload, "payload offset not found");
  1.1807 +
  1.1808 +  // Perform the sending mask. Never use a zero mask
  1.1809 +  uint32_t mask;
  1.1810 +  do {
  1.1811 +    uint8_t *buffer;
  1.1812 +    nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer);
  1.1813 +    if (NS_FAILED(rv)) {
  1.1814 +      LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
  1.1815 +           "GenerateRandomBytes failure %x\n", rv));
  1.1816 +      StopSession(rv);
  1.1817 +      return;
  1.1818 +    }
  1.1819 +    mask = * reinterpret_cast<uint32_t *>(buffer);
  1.1820 +    NS_Free(buffer);
  1.1821 +  } while (!mask);
  1.1822 +  NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
  1.1823 +
  1.1824 +  LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
  1.1825 +
  1.1826 +  // We don't mask the framing, but occasionally we stick a little payload
  1.1827 +  // data in the buffer used for the framing. Close frames are the current
  1.1828 +  // example. This data needs to be masked, but it is never more than a
  1.1829 +  // handful of bytes and might rotate the mask, so we can just do it locally.
  1.1830 +  // For real data frames we ship the bulk of the payload off to ApplyMask()
  1.1831 +
  1.1832 +  while (payload < (mOutHeader + mHdrOutToSend)) {
  1.1833 +    *payload ^= mask >> 24;
  1.1834 +    mask = RotateLeft(mask, 8);
  1.1835 +    payload++;
  1.1836 +  }
  1.1837 +
  1.1838 +  // Mask the real message payloads
  1.1839 +
  1.1840 +  ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
  1.1841 +
  1.1842 +  int32_t len = mCurrentOut->Length();
  1.1843 +
  1.1844 +  // for small frames, copy it all together for a contiguous write
  1.1845 +  if (len && len <= kCopyBreak) {
  1.1846 +    memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
  1.1847 +    mHdrOutToSend += len;
  1.1848 +    mCurrentOutSent = len;
  1.1849 +  }
  1.1850 +
  1.1851 +  if (len && mCompressor) {
  1.1852 +    // assume a 1/3 reduction in size for sizing the buffer
  1.1853 +    // the buffer is used multiple times if necessary
  1.1854 +    uint32_t currentHeaderSize = mHdrOutToSend;
  1.1855 +    mHdrOutToSend = 0;
  1.1856 +
  1.1857 +    EnsureHdrOut(32 + (currentHeaderSize + len - mCurrentOutSent) / 2 * 3);
  1.1858 +    mCompressor->Deflate(mOutHeader, currentHeaderSize,
  1.1859 +                         mCurrentOut->BeginReading() + mCurrentOutSent,
  1.1860 +                         len - mCurrentOutSent);
  1.1861 +
  1.1862 +    // All of the compressed data now resides in {mHdrOut, mHdrOutToSend}
  1.1863 +    // so do not send the body again
  1.1864 +    mCurrentOutSent = len;
  1.1865 +  }
  1.1866 +
  1.1867 +  // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
  1.1868 +  // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
  1.1869 +  // coaleseced into the former for small messages or as the result of the
  1.1870 +  // compression process,
  1.1871 +}
  1.1872 +
  1.1873 +void
  1.1874 +WebSocketChannel::DeleteCurrentOutGoingMessage()
  1.1875 +{
  1.1876 +  delete mCurrentOut;
  1.1877 +  mCurrentOut = nullptr;
  1.1878 +  mCurrentOutSent = 0;
  1.1879 +}
  1.1880 +
  1.1881 +void
  1.1882 +WebSocketChannel::EnsureHdrOut(uint32_t size)
  1.1883 +{
  1.1884 +  LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
  1.1885 +
  1.1886 +  if (mDynamicOutputSize < size) {
  1.1887 +    mDynamicOutputSize = size;
  1.1888 +    mDynamicOutput =
  1.1889 +      (uint8_t *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
  1.1890 +  }
  1.1891 +
  1.1892 +  mHdrOut = mDynamicOutput;
  1.1893 +}
  1.1894 +
  1.1895 +void
  1.1896 +WebSocketChannel::CleanupConnection()
  1.1897 +{
  1.1898 +  LOG(("WebSocketChannel::CleanupConnection() %p", this));
  1.1899 +
  1.1900 +  if (mLingeringCloseTimer) {
  1.1901 +    mLingeringCloseTimer->Cancel();
  1.1902 +    mLingeringCloseTimer = nullptr;
  1.1903 +  }
  1.1904 +
  1.1905 +  if (mSocketIn) {
  1.1906 +    mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
  1.1907 +    mSocketIn = nullptr;
  1.1908 +  }
  1.1909 +
  1.1910 +  if (mSocketOut) {
  1.1911 +    mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
  1.1912 +    mSocketOut = nullptr;
  1.1913 +  }
  1.1914 +
  1.1915 +  if (mTransport) {
  1.1916 +    mTransport->SetSecurityCallbacks(nullptr);
  1.1917 +    mTransport->SetEventSink(nullptr, nullptr);
  1.1918 +    mTransport->Close(NS_BASE_STREAM_CLOSED);
  1.1919 +    mTransport = nullptr;
  1.1920 +  }
  1.1921 +
  1.1922 +  if (mConnectionLogService && !mPrivateBrowsing) {
  1.1923 +    mConnectionLogService->RemoveHost(mHost, mSerial);
  1.1924 +  }
  1.1925 +
  1.1926 +  DecrementSessionCount();
  1.1927 +}
  1.1928 +
  1.1929 +void
  1.1930 +WebSocketChannel::StopSession(nsresult reason)
  1.1931 +{
  1.1932 +  LOG(("WebSocketChannel::StopSession() %p [%x]\n", this, reason));
  1.1933 +
  1.1934 +  // normally this should be called on socket thread, but it is ok to call it
  1.1935 +  // from OnStartRequest before the socket thread machine has gotten underway
  1.1936 +
  1.1937 +  mStopped = 1;
  1.1938 +
  1.1939 +  if (!mOpenedHttpChannel) {
  1.1940 +    // The HTTP channel information will never be used in this case
  1.1941 +    mChannel = nullptr;
  1.1942 +    mHttpChannel = nullptr;
  1.1943 +    mLoadGroup = nullptr;
  1.1944 +    mCallbacks = nullptr;
  1.1945 +  }
  1.1946 +
  1.1947 +  if (mCloseTimer) {
  1.1948 +    mCloseTimer->Cancel();
  1.1949 +    mCloseTimer = nullptr;
  1.1950 +  }
  1.1951 +
  1.1952 +  if (mOpenTimer) {
  1.1953 +    mOpenTimer->Cancel();
  1.1954 +    mOpenTimer = nullptr;
  1.1955 +  }
  1.1956 +
  1.1957 +  if (mReconnectDelayTimer) {
  1.1958 +    mReconnectDelayTimer->Cancel();
  1.1959 +    mReconnectDelayTimer = nullptr;
  1.1960 +  }
  1.1961 +
  1.1962 +  if (mPingTimer) {
  1.1963 +    mPingTimer->Cancel();
  1.1964 +    mPingTimer = nullptr;
  1.1965 +  }
  1.1966 +
  1.1967 +  if (mSocketIn && !mTCPClosed) {
  1.1968 +    // Drain, within reason, this socket. if we leave any data
  1.1969 +    // unconsumed (including the tcp fin) a RST will be generated
  1.1970 +    // The right thing to do here is shutdown(SHUT_WR) and then wait
  1.1971 +    // a little while to see if any data comes in.. but there is no
  1.1972 +    // reason to delay things for that when the websocket handshake
  1.1973 +    // is supposed to guarantee a quiet connection except for that fin.
  1.1974 +
  1.1975 +    char     buffer[512];
  1.1976 +    uint32_t count = 0;
  1.1977 +    uint32_t total = 0;
  1.1978 +    nsresult rv;
  1.1979 +    do {
  1.1980 +      total += count;
  1.1981 +      rv = mSocketIn->Read(buffer, 512, &count);
  1.1982 +      if (rv != NS_BASE_STREAM_WOULD_BLOCK &&
  1.1983 +        (NS_FAILED(rv) || count == 0))
  1.1984 +        mTCPClosed = true;
  1.1985 +    } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
  1.1986 +  }
  1.1987 +
  1.1988 +  int32_t sessionCount = kLingeringCloseThreshold;
  1.1989 +  nsWSAdmissionManager::GetSessionCount(sessionCount);
  1.1990 +
  1.1991 +  if (!mTCPClosed && mTransport && sessionCount < kLingeringCloseThreshold) {
  1.1992 +
  1.1993 +    // 7.1.1 says that the client SHOULD wait for the server to close the TCP
  1.1994 +    // connection. This is so we can reuse port numbers before 2 MSL expires,
  1.1995 +    // which is not really as much of a concern for us as the amount of state
  1.1996 +    // that might be accrued by keeping this channel object around waiting for
  1.1997 +    // the server. We handle the SHOULD by waiting a short time in the common
  1.1998 +    // case, but not waiting in the case of high concurrency.
  1.1999 +    //
  1.2000 +    // Normally this will be taken care of in AbortSession() after mTCPClosed
  1.2001 +    // is set when the server close arrives without waiting for the timeout to
  1.2002 +    // expire.
  1.2003 +
  1.2004 +    LOG(("WebSocketChannel::StopSession: Wait for Server TCP close"));
  1.2005 +
  1.2006 +    nsresult rv;
  1.2007 +    mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1.2008 +    if (NS_SUCCEEDED(rv))
  1.2009 +      mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
  1.2010 +                                             nsITimer::TYPE_ONE_SHOT);
  1.2011 +    else
  1.2012 +      CleanupConnection();
  1.2013 +  } else {
  1.2014 +    CleanupConnection();
  1.2015 +  }
  1.2016 +
  1.2017 +  if (mCancelable) {
  1.2018 +    mCancelable->Cancel(NS_ERROR_UNEXPECTED);
  1.2019 +    mCancelable = nullptr;
  1.2020 +  }
  1.2021 +
  1.2022 +  mInflateReader = nullptr;
  1.2023 +  mInflateStream = nullptr;
  1.2024 +
  1.2025 +  delete mCompressor;
  1.2026 +  mCompressor = nullptr;
  1.2027 +
  1.2028 +  if (!mCalledOnStop) {
  1.2029 +    mCalledOnStop = 1;
  1.2030 +    mTargetThread->Dispatch(new CallOnStop(this, reason),
  1.2031 +                            NS_DISPATCH_NORMAL);
  1.2032 +  }
  1.2033 +
  1.2034 +  return;
  1.2035 +}
  1.2036 +
  1.2037 +void
  1.2038 +WebSocketChannel::AbortSession(nsresult reason)
  1.2039 +{
  1.2040 +  LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n",
  1.2041 +       this, reason, mStopped));
  1.2042 +
  1.2043 +  // normally this should be called on socket thread, but it is ok to call it
  1.2044 +  // from the main thread before StartWebsocketData() has completed
  1.2045 +
  1.2046 +  // When we are failing we need to close the TCP connection immediately
  1.2047 +  // as per 7.1.1
  1.2048 +  mTCPClosed = true;
  1.2049 +
  1.2050 +  if (mLingeringCloseTimer) {
  1.2051 +    NS_ABORT_IF_FALSE(mStopped, "Lingering without Stop");
  1.2052 +    LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
  1.2053 +    CleanupConnection();
  1.2054 +    return;
  1.2055 +  }
  1.2056 +
  1.2057 +  if (mStopped)
  1.2058 +    return;
  1.2059 +  mStopped = 1;
  1.2060 +
  1.2061 +  if (mTransport && reason != NS_BASE_STREAM_CLOSED &&
  1.2062 +      !mRequestedClose && !mClientClosed && !mServerClosed) {
  1.2063 +    mRequestedClose = 1;
  1.2064 +    mStopOnClose = reason;
  1.2065 +    mSocketThread->Dispatch(
  1.2066 +      new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
  1.2067 +                           nsIEventTarget::DISPATCH_NORMAL);
  1.2068 +  } else {
  1.2069 +    StopSession(reason);
  1.2070 +  }
  1.2071 +}
  1.2072 +
  1.2073 +// ReleaseSession is called on orderly shutdown
  1.2074 +void
  1.2075 +WebSocketChannel::ReleaseSession()
  1.2076 +{
  1.2077 +  LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n",
  1.2078 +       this, mStopped));
  1.2079 +  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1.2080 +
  1.2081 +  if (mStopped)
  1.2082 +    return;
  1.2083 +  StopSession(NS_OK);
  1.2084 +}
  1.2085 +
  1.2086 +void
  1.2087 +WebSocketChannel::IncrementSessionCount()
  1.2088 +{
  1.2089 +  if (!mIncrementedSessionCount) {
  1.2090 +    nsWSAdmissionManager::IncrementSessionCount();
  1.2091 +    mIncrementedSessionCount = 1;
  1.2092 +  }
  1.2093 +}
  1.2094 +
  1.2095 +void
  1.2096 +WebSocketChannel::DecrementSessionCount()
  1.2097 +{
  1.2098 +  // Make sure we decrement session count only once, and only if we incremented it.
  1.2099 +  // This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount is
  1.2100 +  // atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
  1.2101 +  // times when they'll never be a race condition for checking/setting them.
  1.2102 +  if (mIncrementedSessionCount && !mDecrementedSessionCount) {
  1.2103 +    nsWSAdmissionManager::DecrementSessionCount();
  1.2104 +    mDecrementedSessionCount = 1;
  1.2105 +  }
  1.2106 +}
  1.2107 +
  1.2108 +nsresult
  1.2109 +WebSocketChannel::HandleExtensions()
  1.2110 +{
  1.2111 +  LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
  1.2112 +
  1.2113 +  nsresult rv;
  1.2114 +  nsAutoCString extensions;
  1.2115 +
  1.2116 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2117 +
  1.2118 +  rv = mHttpChannel->GetResponseHeader(
  1.2119 +    NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
  1.2120 +  if (NS_SUCCEEDED(rv)) {
  1.2121 +    if (!extensions.IsEmpty()) {
  1.2122 +      if (!extensions.Equals(NS_LITERAL_CSTRING("deflate-stream"))) {
  1.2123 +        LOG(("WebSocketChannel::OnStartRequest: "
  1.2124 +             "HTTP Sec-WebSocket-Exensions negotiated unknown value %s\n",
  1.2125 +             extensions.get()));
  1.2126 +        AbortSession(NS_ERROR_ILLEGAL_VALUE);
  1.2127 +        return NS_ERROR_ILLEGAL_VALUE;
  1.2128 +      }
  1.2129 +
  1.2130 +      if (!mAllowCompression) {
  1.2131 +        LOG(("WebSocketChannel::HandleExtensions: "
  1.2132 +             "Recvd Compression Extension that wasn't offered\n"));
  1.2133 +        AbortSession(NS_ERROR_ILLEGAL_VALUE);
  1.2134 +        return NS_ERROR_ILLEGAL_VALUE;
  1.2135 +      }
  1.2136 +
  1.2137 +      nsCOMPtr<nsIStreamConverterService> serv =
  1.2138 +        do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
  1.2139 +      if (NS_FAILED(rv)) {
  1.2140 +        LOG(("WebSocketChannel:: Cannot find compression service\n"));
  1.2141 +        AbortSession(NS_ERROR_UNEXPECTED);
  1.2142 +        return NS_ERROR_UNEXPECTED;
  1.2143 +      }
  1.2144 +
  1.2145 +      rv = serv->AsyncConvertData("deflate", "uncompressed", this, nullptr,
  1.2146 +                                  getter_AddRefs(mInflateReader));
  1.2147 +
  1.2148 +      if (NS_FAILED(rv)) {
  1.2149 +        LOG(("WebSocketChannel:: Cannot find inflate listener\n"));
  1.2150 +        AbortSession(NS_ERROR_UNEXPECTED);
  1.2151 +        return NS_ERROR_UNEXPECTED;
  1.2152 +      }
  1.2153 +
  1.2154 +      mInflateStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
  1.2155 +
  1.2156 +      if (NS_FAILED(rv)) {
  1.2157 +        LOG(("WebSocketChannel:: Cannot find inflate stream\n"));
  1.2158 +        AbortSession(NS_ERROR_UNEXPECTED);
  1.2159 +        return NS_ERROR_UNEXPECTED;
  1.2160 +      }
  1.2161 +
  1.2162 +      mCompressor = new nsWSCompression(this, mSocketOut);
  1.2163 +      if (!mCompressor->Active()) {
  1.2164 +        LOG(("WebSocketChannel:: Cannot init deflate object\n"));
  1.2165 +        delete mCompressor;
  1.2166 +        mCompressor = nullptr;
  1.2167 +        AbortSession(NS_ERROR_UNEXPECTED);
  1.2168 +        return NS_ERROR_UNEXPECTED;
  1.2169 +      }
  1.2170 +      mNegotiatedExtensions = extensions;
  1.2171 +    }
  1.2172 +  }
  1.2173 +
  1.2174 +  return NS_OK;
  1.2175 +}
  1.2176 +
  1.2177 +nsresult
  1.2178 +WebSocketChannel::SetupRequest()
  1.2179 +{
  1.2180 +  LOG(("WebSocketChannel::SetupRequest() %p\n", this));
  1.2181 +
  1.2182 +  nsresult rv;
  1.2183 +
  1.2184 +  if (mLoadGroup) {
  1.2185 +    rv = mHttpChannel->SetLoadGroup(mLoadGroup);
  1.2186 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2187 +  }
  1.2188 +
  1.2189 +  rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND |
  1.2190 +                                  nsIRequest::INHIBIT_CACHING |
  1.2191 +                                  nsIRequest::LOAD_BYPASS_CACHE);
  1.2192 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2193 +
  1.2194 +  // we never let websockets be blocked by head CSS/JS loads to avoid
  1.2195 +  // potential deadlock where server generation of CSS/JS requires
  1.2196 +  // an XHR signal.
  1.2197 +  rv = mChannel->SetLoadUnblocked(true);
  1.2198 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2199 +
  1.2200 +  // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
  1.2201 +  // in lower case, so go with that. It is technically case insensitive.
  1.2202 +  rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this);
  1.2203 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2204 +
  1.2205 +  mHttpChannel->SetRequestHeader(
  1.2206 +    NS_LITERAL_CSTRING("Sec-WebSocket-Version"),
  1.2207 +    NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false);
  1.2208 +
  1.2209 +  if (!mOrigin.IsEmpty())
  1.2210 +    mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin,
  1.2211 +                                   false);
  1.2212 +
  1.2213 +  if (!mProtocol.IsEmpty())
  1.2214 +    mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
  1.2215 +                                   mProtocol, true);
  1.2216 +
  1.2217 +  if (mAllowCompression)
  1.2218 +    mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
  1.2219 +                                   NS_LITERAL_CSTRING("deflate-stream"),
  1.2220 +                                   false);
  1.2221 +
  1.2222 +  uint8_t      *secKey;
  1.2223 +  nsAutoCString secKeyString;
  1.2224 +
  1.2225 +  rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
  1.2226 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2227 +  char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr);
  1.2228 +  NS_Free(secKey);
  1.2229 +  if (!b64)
  1.2230 +    return NS_ERROR_OUT_OF_MEMORY;
  1.2231 +  secKeyString.Assign(b64);
  1.2232 +  PR_Free(b64);
  1.2233 +  mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"),
  1.2234 +                                 secKeyString, false);
  1.2235 +  LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
  1.2236 +
  1.2237 +  // prepare the value we expect to see in
  1.2238 +  // the sec-websocket-accept response header
  1.2239 +  secKeyString.AppendLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
  1.2240 +  nsCOMPtr<nsICryptoHash> hasher =
  1.2241 +    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
  1.2242 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2243 +  rv = hasher->Init(nsICryptoHash::SHA1);
  1.2244 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2245 +  rv = hasher->Update((const uint8_t *) secKeyString.BeginWriting(),
  1.2246 +                      secKeyString.Length());
  1.2247 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2248 +  rv = hasher->Finish(true, mHashedSecret);
  1.2249 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2250 +  LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
  1.2251 +       mHashedSecret.get()));
  1.2252 +
  1.2253 +  return NS_OK;
  1.2254 +}
  1.2255 +
  1.2256 +nsresult
  1.2257 +WebSocketChannel::DoAdmissionDNS()
  1.2258 +{
  1.2259 +  nsresult rv;
  1.2260 +
  1.2261 +  nsCString hostName;
  1.2262 +  rv = mURI->GetHost(hostName);
  1.2263 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2264 +  mAddress = hostName;
  1.2265 +  rv = mURI->GetPort(&mPort);
  1.2266 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2267 +  if (mPort == -1)
  1.2268 +    mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
  1.2269 +  nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
  1.2270 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2271 +  nsCOMPtr<nsIThread> mainThread;
  1.2272 +  NS_GetMainThread(getter_AddRefs(mainThread));
  1.2273 +  MOZ_ASSERT(!mCancelable);
  1.2274 +  return dns->AsyncResolve(hostName, 0, this, mainThread, getter_AddRefs(mCancelable));
  1.2275 +}
  1.2276 +
  1.2277 +nsresult
  1.2278 +WebSocketChannel::ApplyForAdmission()
  1.2279 +{
  1.2280 +  LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
  1.2281 +
  1.2282 +  // Websockets has a policy of 1 session at a time being allowed in the
  1.2283 +  // CONNECTING state per server IP address (not hostname)
  1.2284 +
  1.2285 +  // Check to see if a proxy is being used before making DNS call
  1.2286 +  nsCOMPtr<nsIProtocolProxyService> pps =
  1.2287 +    do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
  1.2288 +
  1.2289 +  if (!pps) {
  1.2290 +    // go straight to DNS
  1.2291 +    // expect the callback in ::OnLookupComplete
  1.2292 +    LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
  1.2293 +    return DoAdmissionDNS();
  1.2294 +  }
  1.2295 +
  1.2296 +  MOZ_ASSERT(!mCancelable);
  1.2297 +
  1.2298 +  return pps->AsyncResolve(mHttpChannel,
  1.2299 +                           nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
  1.2300 +                           nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
  1.2301 +                           this, getter_AddRefs(mCancelable));
  1.2302 +}
  1.2303 +
  1.2304 +// Called after both OnStartRequest and OnTransportAvailable have
  1.2305 +// executed. This essentially ends the handshake and starts the websockets
  1.2306 +// protocol state machine.
  1.2307 +nsresult
  1.2308 +WebSocketChannel::StartWebsocketData()
  1.2309 +{
  1.2310 +  LOG(("WebSocketChannel::StartWebsocketData() %p", this));
  1.2311 +  NS_ABORT_IF_FALSE(!mDataStarted, "StartWebsocketData twice");
  1.2312 +  mDataStarted = 1;
  1.2313 +
  1.2314 +  // We're now done CONNECTING, which means we can now open another,
  1.2315 +  // perhaps parallel, connection to the same host if one
  1.2316 +  // is pending
  1.2317 +  nsWSAdmissionManager::OnConnected(this);
  1.2318 +
  1.2319 +  LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p\n",
  1.2320 +       mListener.get()));
  1.2321 +
  1.2322 +  if (mListener)
  1.2323 +    mListener->OnStart(mContext);
  1.2324 +
  1.2325 +  // Start keepalive ping timer, if we're using keepalive.
  1.2326 +  if (mPingInterval) {
  1.2327 +    nsresult rv;
  1.2328 +    mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1.2329 +    if (NS_FAILED(rv)) {
  1.2330 +      NS_WARNING("unable to create ping timer. Carrying on.");
  1.2331 +    } else {
  1.2332 +      LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
  1.2333 +           mPingInterval));
  1.2334 +      mPingTimer->SetTarget(mSocketThread);
  1.2335 +      mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
  1.2336 +    }
  1.2337 +  }
  1.2338 +
  1.2339 +  return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
  1.2340 +}
  1.2341 +
  1.2342 +void
  1.2343 +WebSocketChannel::ReportConnectionTelemetry()
  1.2344 +{ 
  1.2345 +  // 3 bits are used. high bit is for wss, middle bit for failed,
  1.2346 +  // and low bit for proxy..
  1.2347 +  // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
  1.2348 +  //         wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
  1.2349 +
  1.2350 +  bool didProxy = false;
  1.2351 +
  1.2352 +  nsCOMPtr<nsIProxyInfo> pi;
  1.2353 +  nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
  1.2354 +  if (pc)
  1.2355 +    pc->GetProxyInfo(getter_AddRefs(pi));
  1.2356 +  if (pi) {
  1.2357 +    nsAutoCString proxyType;
  1.2358 +    pi->GetType(proxyType);
  1.2359 +    if (!proxyType.IsEmpty() &&
  1.2360 +        !proxyType.Equals(NS_LITERAL_CSTRING("direct")))
  1.2361 +      didProxy = true;
  1.2362 +  }
  1.2363 +
  1.2364 +  uint8_t value = (mEncrypted ? (1 << 2) : 0) | 
  1.2365 +    (!mGotUpgradeOK ? (1 << 1) : 0) |
  1.2366 +    (didProxy ? (1 << 0) : 0);
  1.2367 +
  1.2368 +  LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
  1.2369 +  Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value);
  1.2370 +}
  1.2371 +
  1.2372 +// nsIDNSListener
  1.2373 +
  1.2374 +NS_IMETHODIMP
  1.2375 +WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
  1.2376 +                                   nsIDNSRecord *aRecord,
  1.2377 +                                   nsresult aStatus)
  1.2378 +{
  1.2379 +  LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n",
  1.2380 +       this, aRequest, aRecord, aStatus));
  1.2381 +
  1.2382 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2383 +
  1.2384 +  if (mStopped) {
  1.2385 +    LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
  1.2386 +    mCancelable = nullptr;
  1.2387 +    return NS_OK;
  1.2388 +  }
  1.2389 +
  1.2390 +  mCancelable = nullptr;
  1.2391 +
  1.2392 +  // These failures are not fatal - we just use the hostname as the key
  1.2393 +  if (NS_FAILED(aStatus)) {
  1.2394 +    LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
  1.2395 +
  1.2396 +    // set host in case we got here without calling DoAdmissionDNS()
  1.2397 +    mURI->GetHost(mAddress);
  1.2398 +  } else {
  1.2399 +    nsresult rv = aRecord->GetNextAddrAsString(mAddress);
  1.2400 +    if (NS_FAILED(rv))
  1.2401 +      LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
  1.2402 +  }
  1.2403 +
  1.2404 +  LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
  1.2405 +  nsWSAdmissionManager::ConditionallyConnect(this);
  1.2406 +
  1.2407 +  return NS_OK;
  1.2408 +}
  1.2409 +
  1.2410 +// nsIProtocolProxyCallback
  1.2411 +NS_IMETHODIMP
  1.2412 +WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel,
  1.2413 +                                   nsIProxyInfo *pi, nsresult status)
  1.2414 +{
  1.2415 +  if (mStopped) {
  1.2416 +    LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this));
  1.2417 +    mCancelable = nullptr;
  1.2418 +    return NS_OK;
  1.2419 +  }
  1.2420 +
  1.2421 +  MOZ_ASSERT(aRequest == mCancelable);
  1.2422 +  mCancelable = nullptr;
  1.2423 +
  1.2424 +  nsAutoCString type;
  1.2425 +  if (NS_SUCCEEDED(status) && pi &&
  1.2426 +      NS_SUCCEEDED(pi->GetType(type)) &&
  1.2427 +      !type.EqualsLiteral("direct")) {
  1.2428 +    LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", this));
  1.2429 +    // call DNS callback directly without DNS resolver
  1.2430 +    OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
  1.2431 +    return NS_OK;
  1.2432 +  }
  1.2433 +
  1.2434 +  LOG(("WebSocketChannel::OnProxyAvailable[%] checking DNS resolution\n", this));
  1.2435 +  DoAdmissionDNS();
  1.2436 +  return NS_OK;
  1.2437 +}
  1.2438 +
  1.2439 +// nsIInterfaceRequestor
  1.2440 +
  1.2441 +NS_IMETHODIMP
  1.2442 +WebSocketChannel::GetInterface(const nsIID & iid, void **result)
  1.2443 +{
  1.2444 +  LOG(("WebSocketChannel::GetInterface() %p\n", this));
  1.2445 +
  1.2446 +  if (iid.Equals(NS_GET_IID(nsIChannelEventSink)))
  1.2447 +    return QueryInterface(iid, result);
  1.2448 +
  1.2449 +  if (mCallbacks)
  1.2450 +    return mCallbacks->GetInterface(iid, result);
  1.2451 +
  1.2452 +  return NS_ERROR_FAILURE;
  1.2453 +}
  1.2454 +
  1.2455 +// nsIChannelEventSink
  1.2456 +
  1.2457 +NS_IMETHODIMP
  1.2458 +WebSocketChannel::AsyncOnChannelRedirect(
  1.2459 +                    nsIChannel *oldChannel,
  1.2460 +                    nsIChannel *newChannel,
  1.2461 +                    uint32_t flags,
  1.2462 +                    nsIAsyncVerifyRedirectCallback *callback)
  1.2463 +{
  1.2464 +  LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
  1.2465 +  nsresult rv;
  1.2466 +
  1.2467 +  nsCOMPtr<nsIURI> newuri;
  1.2468 +  rv = newChannel->GetURI(getter_AddRefs(newuri));
  1.2469 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2470 +
  1.2471 +  // newuri is expected to be http or https
  1.2472 +  bool newuriIsHttps = false;
  1.2473 +  rv = newuri->SchemeIs("https", &newuriIsHttps);
  1.2474 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2475 +
  1.2476 +  if (!mAutoFollowRedirects) {
  1.2477 +    // Even if redirects configured off, still allow them for HTTP Strict
  1.2478 +    // Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO)
  1.2479 +
  1.2480 +    nsCOMPtr<nsIURI> clonedNewURI;
  1.2481 +    rv = newuri->Clone(getter_AddRefs(clonedNewURI));
  1.2482 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2483 +
  1.2484 +    rv = clonedNewURI->SetScheme(NS_LITERAL_CSTRING("ws"));
  1.2485 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2486 +
  1.2487 +    nsCOMPtr<nsIURI> currentURI;
  1.2488 +    rv = GetURI(getter_AddRefs(currentURI));
  1.2489 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2490 +
  1.2491 +    // currentURI is expected to be ws or wss
  1.2492 +    bool currentIsHttps = false;
  1.2493 +    rv = currentURI->SchemeIs("wss", &currentIsHttps);
  1.2494 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2495 +
  1.2496 +    bool uriEqual = false;
  1.2497 +    rv = clonedNewURI->Equals(currentURI, &uriEqual);
  1.2498 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2499 +
  1.2500 +    // It's only a HSTS redirect if we started with non-secure, are going to
  1.2501 +    // secure, and the new URI is otherwise the same as the old one.
  1.2502 +    if (!(!currentIsHttps && newuriIsHttps && uriEqual)) {
  1.2503 +      nsAutoCString newSpec;
  1.2504 +      rv = newuri->GetSpec(newSpec);
  1.2505 +      NS_ENSURE_SUCCESS(rv, rv);
  1.2506 +
  1.2507 +      LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
  1.2508 +           newSpec.get()));
  1.2509 +      return NS_ERROR_FAILURE;
  1.2510 +    }
  1.2511 +  }
  1.2512 +
  1.2513 +  if (mEncrypted && !newuriIsHttps) {
  1.2514 +    nsAutoCString spec;
  1.2515 +    if (NS_SUCCEEDED(newuri->GetSpec(spec)))
  1.2516 +      LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
  1.2517 +           spec.get()));
  1.2518 +    return NS_ERROR_FAILURE;
  1.2519 +  }
  1.2520 +
  1.2521 +  nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
  1.2522 +  if (NS_FAILED(rv)) {
  1.2523 +    LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
  1.2524 +    return rv;
  1.2525 +  }
  1.2526 +
  1.2527 +  nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
  1.2528 +    do_QueryInterface(newChannel, &rv);
  1.2529 +
  1.2530 +  if (NS_FAILED(rv)) {
  1.2531 +    LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
  1.2532 +    return rv;
  1.2533 +  }
  1.2534 +
  1.2535 +  // The redirect is likely OK
  1.2536 +
  1.2537 +  newChannel->SetNotificationCallbacks(this);
  1.2538 +
  1.2539 +  mEncrypted = newuriIsHttps;
  1.2540 +  newuri->Clone(getter_AddRefs(mURI));
  1.2541 +  if (mEncrypted)
  1.2542 +    rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
  1.2543 +  else
  1.2544 +    rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
  1.2545 +
  1.2546 +  mHttpChannel = newHttpChannel;
  1.2547 +  mChannel = newUpgradeChannel;
  1.2548 +  rv = SetupRequest();
  1.2549 +  if (NS_FAILED(rv)) {
  1.2550 +    LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
  1.2551 +    return rv;
  1.2552 +  }
  1.2553 +
  1.2554 +  // Redirected-to URI may need to be delayed by 1-connecting-per-host and
  1.2555 +  // delay-after-fail algorithms.  So hold off calling OnRedirectVerifyCallback
  1.2556 +  // until BeginOpen, when we know it's OK to proceed with new channel.
  1.2557 +  mRedirectCallback = callback;
  1.2558 +
  1.2559 +  // Mark old channel as successfully connected so we'll clear any FailDelay
  1.2560 +  // associated with the old URI.  Note: no need to also call OnStopSession:
  1.2561 +  // it's a no-op for successful, already-connected channels.
  1.2562 +  nsWSAdmissionManager::OnConnected(this);
  1.2563 +
  1.2564 +  // ApplyForAdmission as if we were starting from fresh...
  1.2565 +  mAddress.Truncate();
  1.2566 +  mOpenedHttpChannel = 0;
  1.2567 +  rv = ApplyForAdmission();
  1.2568 +  if (NS_FAILED(rv)) {
  1.2569 +    LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
  1.2570 +    mRedirectCallback = nullptr;
  1.2571 +    return rv;
  1.2572 +  }
  1.2573 +
  1.2574 +  return NS_OK;
  1.2575 +}
  1.2576 +
  1.2577 +// nsITimerCallback
  1.2578 +
  1.2579 +NS_IMETHODIMP
  1.2580 +WebSocketChannel::Notify(nsITimer *timer)
  1.2581 +{
  1.2582 +  LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
  1.2583 +
  1.2584 +  if (timer == mCloseTimer) {
  1.2585 +    NS_ABORT_IF_FALSE(mClientClosed, "Close Timeout without local close");
  1.2586 +    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
  1.2587 +                      "not socket thread");
  1.2588 +
  1.2589 +    mCloseTimer = nullptr;
  1.2590 +    if (mStopped || mServerClosed)                /* no longer relevant */
  1.2591 +      return NS_OK;
  1.2592 +
  1.2593 +    LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
  1.2594 +    AbortSession(NS_ERROR_NET_TIMEOUT);
  1.2595 +  } else if (timer == mOpenTimer) {
  1.2596 +    NS_ABORT_IF_FALSE(!mGotUpgradeOK,
  1.2597 +                      "Open Timer after open complete");
  1.2598 +    NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2599 +
  1.2600 +    mOpenTimer = nullptr;
  1.2601 +    LOG(("WebSocketChannel:: Connection Timed Out\n"));
  1.2602 +    if (mStopped || mServerClosed)                /* no longer relevant */
  1.2603 +      return NS_OK;
  1.2604 +
  1.2605 +    AbortSession(NS_ERROR_NET_TIMEOUT);
  1.2606 +  } else if (timer == mReconnectDelayTimer) {
  1.2607 +    NS_ABORT_IF_FALSE(mConnecting == CONNECTING_DELAYED,
  1.2608 +                      "woke up from delay w/o being delayed?");
  1.2609 +
  1.2610 +    mReconnectDelayTimer = nullptr;
  1.2611 +    LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
  1.2612 +    BeginOpen();
  1.2613 +  } else if (timer == mPingTimer) {
  1.2614 +    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
  1.2615 +                      "not socket thread");
  1.2616 +
  1.2617 +    if (mClientClosed || mServerClosed || mRequestedClose) {
  1.2618 +      // no point in worrying about ping now
  1.2619 +      mPingTimer = nullptr;
  1.2620 +      return NS_OK;
  1.2621 +    }
  1.2622 +
  1.2623 +    if (!mPingOutstanding) {
  1.2624 +      LOG(("nsWebSocketChannel:: Generating Ping\n"));
  1.2625 +      mPingOutstanding = 1;
  1.2626 +      GeneratePing();
  1.2627 +      mPingTimer->InitWithCallback(this, mPingResponseTimeout,
  1.2628 +                                   nsITimer::TYPE_ONE_SHOT);
  1.2629 +    } else {
  1.2630 +      LOG(("nsWebSocketChannel:: Timed out Ping\n"));
  1.2631 +      mPingTimer = nullptr;
  1.2632 +      AbortSession(NS_ERROR_NET_TIMEOUT);
  1.2633 +    }
  1.2634 +  } else if (timer == mLingeringCloseTimer) {
  1.2635 +    LOG(("WebSocketChannel:: Lingering Close Timer"));
  1.2636 +    CleanupConnection();
  1.2637 +  } else {
  1.2638 +    NS_ABORT_IF_FALSE(0, "Unknown Timer");
  1.2639 +  }
  1.2640 +
  1.2641 +  return NS_OK;
  1.2642 +}
  1.2643 +
  1.2644 +// nsIWebSocketChannel
  1.2645 +
  1.2646 +NS_IMETHODIMP
  1.2647 +WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
  1.2648 +{
  1.2649 +  LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
  1.2650 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2651 +
  1.2652 +  if (mTransport) {
  1.2653 +    if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo)))
  1.2654 +      *aSecurityInfo = nullptr;
  1.2655 +  }
  1.2656 +  return NS_OK;
  1.2657 +}
  1.2658 +
  1.2659 +
  1.2660 +NS_IMETHODIMP
  1.2661 +WebSocketChannel::AsyncOpen(nsIURI *aURI,
  1.2662 +                            const nsACString &aOrigin,
  1.2663 +                            nsIWebSocketListener *aListener,
  1.2664 +                            nsISupports *aContext)
  1.2665 +{
  1.2666 +  LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
  1.2667 +
  1.2668 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2669 +
  1.2670 +  if (!aURI || !aListener) {
  1.2671 +    LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
  1.2672 +    return NS_ERROR_UNEXPECTED;
  1.2673 +  }
  1.2674 +
  1.2675 +  if (mListener || mWasOpened)
  1.2676 +    return NS_ERROR_ALREADY_OPENED;
  1.2677 +
  1.2678 +  nsresult rv;
  1.2679 +
  1.2680 +  // Ensure target thread is set.
  1.2681 +  if (!mTargetThread) {
  1.2682 +    mTargetThread = do_GetMainThread();
  1.2683 +  }
  1.2684 +
  1.2685 +  mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
  1.2686 +  if (NS_FAILED(rv)) {
  1.2687 +    NS_WARNING("unable to continue without socket transport service");
  1.2688 +    return rv;
  1.2689 +  }
  1.2690 +
  1.2691 +  mRandomGenerator =
  1.2692 +    do_GetService("@mozilla.org/security/random-generator;1", &rv);
  1.2693 +  if (NS_FAILED(rv)) {
  1.2694 +    NS_WARNING("unable to continue without random number generator");
  1.2695 +    return rv;
  1.2696 +  }
  1.2697 +
  1.2698 +  nsCOMPtr<nsIPrefBranch> prefService;
  1.2699 +  prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
  1.2700 +
  1.2701 +  if (prefService) {
  1.2702 +    int32_t intpref;
  1.2703 +    bool boolpref;
  1.2704 +    rv = prefService->GetIntPref("network.websocket.max-message-size", 
  1.2705 +                                 &intpref);
  1.2706 +    if (NS_SUCCEEDED(rv)) {
  1.2707 +      mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
  1.2708 +    }
  1.2709 +    rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
  1.2710 +    if (NS_SUCCEEDED(rv)) {
  1.2711 +      mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
  1.2712 +    }
  1.2713 +    rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
  1.2714 +    if (NS_SUCCEEDED(rv)) {
  1.2715 +      mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
  1.2716 +    }
  1.2717 +    rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
  1.2718 +                                 &intpref);
  1.2719 +    if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
  1.2720 +      mPingInterval = clamped(intpref, 0, 86400) * 1000;
  1.2721 +    }
  1.2722 +    rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
  1.2723 +                                 &intpref);
  1.2724 +    if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
  1.2725 +      mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
  1.2726 +    }
  1.2727 +    rv = prefService->GetBoolPref("network.websocket.extensions.stream-deflate",
  1.2728 +                                  &boolpref);
  1.2729 +    if (NS_SUCCEEDED(rv)) {
  1.2730 +      mAllowCompression = boolpref ? 1 : 0;
  1.2731 +    }
  1.2732 +    rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects",
  1.2733 +                                  &boolpref);
  1.2734 +    if (NS_SUCCEEDED(rv)) {
  1.2735 +      mAutoFollowRedirects = boolpref ? 1 : 0;
  1.2736 +    }
  1.2737 +    rv = prefService->GetIntPref
  1.2738 +      ("network.websocket.max-connections", &intpref);
  1.2739 +    if (NS_SUCCEEDED(rv)) {
  1.2740 +      mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
  1.2741 +    }
  1.2742 +  }
  1.2743 +
  1.2744 +  int32_t sessionCount = -1;
  1.2745 +  nsWSAdmissionManager::GetSessionCount(sessionCount);
  1.2746 +  if (sessionCount >= 0) {
  1.2747 +    LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
  1.2748 +         sessionCount, mMaxConcurrentConnections));
  1.2749 +  }
  1.2750 +
  1.2751 +  if (sessionCount >= mMaxConcurrentConnections) {
  1.2752 +    LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
  1.2753 +         mMaxConcurrentConnections,
  1.2754 +         sessionCount));
  1.2755 +
  1.2756 +    // WebSocket connections are expected to be long lived, so return
  1.2757 +    // an error here instead of queueing
  1.2758 +    return NS_ERROR_SOCKET_CREATE_FAILED;
  1.2759 +  }
  1.2760 +
  1.2761 +  mOriginalURI = aURI;
  1.2762 +  mURI = mOriginalURI;
  1.2763 +  mURI->GetHostPort(mHost);
  1.2764 +  mOrigin = aOrigin;
  1.2765 +
  1.2766 +  nsCOMPtr<nsIURI> localURI;
  1.2767 +  nsCOMPtr<nsIChannel> localChannel;
  1.2768 +
  1.2769 +  mURI->Clone(getter_AddRefs(localURI));
  1.2770 +  if (mEncrypted)
  1.2771 +    rv = localURI->SetScheme(NS_LITERAL_CSTRING("https"));
  1.2772 +  else
  1.2773 +    rv = localURI->SetScheme(NS_LITERAL_CSTRING("http"));
  1.2774 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2775 +
  1.2776 +  nsCOMPtr<nsIIOService> ioService;
  1.2777 +  ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
  1.2778 +  if (NS_FAILED(rv)) {
  1.2779 +    NS_WARNING("unable to continue without io service");
  1.2780 +    return rv;
  1.2781 +  }
  1.2782 +
  1.2783 +  nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
  1.2784 +  if (NS_FAILED(rv)) {
  1.2785 +    NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
  1.2786 +    return rv;
  1.2787 +  }
  1.2788 +
  1.2789 +  rv = io2->NewChannelFromURIWithProxyFlags(
  1.2790 +              localURI,
  1.2791 +              mURI,
  1.2792 +              nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
  1.2793 +              nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
  1.2794 +              getter_AddRefs(localChannel));
  1.2795 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2796 +
  1.2797 +  // Pass most GetInterface() requests through to our instantiator, but handle
  1.2798 +  // nsIChannelEventSink in this object in order to deal with redirects
  1.2799 +  localChannel->SetNotificationCallbacks(this);
  1.2800 +
  1.2801 +  mChannel = do_QueryInterface(localChannel, &rv);
  1.2802 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2803 +
  1.2804 +  mHttpChannel = do_QueryInterface(localChannel, &rv);
  1.2805 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2806 +
  1.2807 +  rv = SetupRequest();
  1.2808 +  if (NS_FAILED(rv))
  1.2809 +    return rv;
  1.2810 +
  1.2811 +  mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
  1.2812 +
  1.2813 +  if (mConnectionLogService && !mPrivateBrowsing) {
  1.2814 +    mConnectionLogService->AddHost(mHost, mSerial,
  1.2815 +                                   BaseWebSocketChannel::mEncrypted);
  1.2816 +  }
  1.2817 +
  1.2818 +  rv = ApplyForAdmission();
  1.2819 +  if (NS_FAILED(rv))
  1.2820 +    return rv;
  1.2821 +
  1.2822 +  // Only set these if the open was successful:
  1.2823 +  //
  1.2824 +  mWasOpened = 1;
  1.2825 +  mListener = aListener;
  1.2826 +  mContext = aContext;
  1.2827 +  IncrementSessionCount();
  1.2828 +
  1.2829 +  return rv;
  1.2830 +}
  1.2831 +
  1.2832 +NS_IMETHODIMP
  1.2833 +WebSocketChannel::Close(uint16_t code, const nsACString & reason)
  1.2834 +{
  1.2835 +  LOG(("WebSocketChannel::Close() %p\n", this));
  1.2836 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2837 +
  1.2838 +  // save the networkstats (bug 855949)
  1.2839 +  SaveNetworkStats(true);
  1.2840 +
  1.2841 +  if (mRequestedClose) {
  1.2842 +    return NS_OK;
  1.2843 +  }
  1.2844 +
  1.2845 +  // The API requires the UTF-8 string to be 123 or less bytes
  1.2846 +  if (reason.Length() > 123)
  1.2847 +    return NS_ERROR_ILLEGAL_VALUE;
  1.2848 +
  1.2849 +  mRequestedClose = 1;
  1.2850 +  mScriptCloseReason = reason;
  1.2851 +  mScriptCloseCode = code;
  1.2852 +
  1.2853 +  if (!mTransport) {
  1.2854 +    nsresult rv;
  1.2855 +    if (code == CLOSE_GOING_AWAY) {
  1.2856 +      // Not an error: for example, tab has closed or navigated away
  1.2857 +      LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
  1.2858 +      rv = NS_OK;
  1.2859 +    } else {
  1.2860 +      LOG(("WebSocketChannel::Close() without transport - error."));
  1.2861 +      rv = NS_ERROR_NOT_CONNECTED;
  1.2862 +    }
  1.2863 +    StopSession(rv);
  1.2864 +    return rv;
  1.2865 +  }
  1.2866 +
  1.2867 +  return mSocketThread->Dispatch(
  1.2868 +      new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
  1.2869 +                           nsIEventTarget::DISPATCH_NORMAL);
  1.2870 +}
  1.2871 +
  1.2872 +NS_IMETHODIMP
  1.2873 +WebSocketChannel::SendMsg(const nsACString &aMsg)
  1.2874 +{
  1.2875 +  LOG(("WebSocketChannel::SendMsg() %p\n", this));
  1.2876 +
  1.2877 +  return SendMsgCommon(&aMsg, false, aMsg.Length());
  1.2878 +}
  1.2879 +
  1.2880 +NS_IMETHODIMP
  1.2881 +WebSocketChannel::SendBinaryMsg(const nsACString &aMsg)
  1.2882 +{
  1.2883 +  LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length()));
  1.2884 +  return SendMsgCommon(&aMsg, true, aMsg.Length());
  1.2885 +}
  1.2886 +
  1.2887 +NS_IMETHODIMP
  1.2888 +WebSocketChannel::SendBinaryStream(nsIInputStream *aStream, uint32_t aLength)
  1.2889 +{
  1.2890 +  LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
  1.2891 +
  1.2892 +  return SendMsgCommon(nullptr, true, aLength, aStream);
  1.2893 +}
  1.2894 +
  1.2895 +nsresult
  1.2896 +WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
  1.2897 +                                uint32_t aLength, nsIInputStream *aStream)
  1.2898 +{
  1.2899 +  NS_ABORT_IF_FALSE(NS_GetCurrentThread() == mTargetThread, "not target thread");
  1.2900 +
  1.2901 +  if (mRequestedClose) {
  1.2902 +    LOG(("WebSocketChannel:: Error: send when closed\n"));
  1.2903 +    return NS_ERROR_UNEXPECTED;
  1.2904 +  }
  1.2905 +
  1.2906 +  if (mStopped) {
  1.2907 +    LOG(("WebSocketChannel:: Error: send when stopped\n"));
  1.2908 +    return NS_ERROR_NOT_CONNECTED;
  1.2909 +  }
  1.2910 +
  1.2911 +  NS_ABORT_IF_FALSE(mMaxMessageSize >= 0, "max message size negative");
  1.2912 +  if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
  1.2913 +    LOG(("WebSocketChannel:: Error: message too big\n"));
  1.2914 +    return NS_ERROR_FILE_TOO_BIG;
  1.2915 +  }
  1.2916 +
  1.2917 +  if (mConnectionLogService && !mPrivateBrowsing) {
  1.2918 +    mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
  1.2919 +    LOG(("Added new msg sent for %s", mHost.get()));
  1.2920 +  }
  1.2921 +
  1.2922 +  return mSocketThread->Dispatch(
  1.2923 +    aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
  1.2924 +            : new OutboundEnqueuer(this,
  1.2925 +                     new OutboundMessage(aIsBinary ? kMsgTypeBinaryString
  1.2926 +                                                   : kMsgTypeString,
  1.2927 +                                         new nsCString(*aMsg))),
  1.2928 +    nsIEventTarget::DISPATCH_NORMAL);
  1.2929 +}
  1.2930 +
  1.2931 +// nsIHttpUpgradeListener
  1.2932 +
  1.2933 +NS_IMETHODIMP
  1.2934 +WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
  1.2935 +                                       nsIAsyncInputStream *aSocketIn,
  1.2936 +                                       nsIAsyncOutputStream *aSocketOut)
  1.2937 +{
  1.2938 +  if (!NS_IsMainThread()) {
  1.2939 +    return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
  1.2940 +                                                                aTransport,
  1.2941 +                                                                aSocketIn,
  1.2942 +                                                                aSocketOut));
  1.2943 +  }
  1.2944 +
  1.2945 +  LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
  1.2946 +       this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
  1.2947 +
  1.2948 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2949 +  NS_ABORT_IF_FALSE(!mRecvdHttpUpgradeTransport, "OTA duplicated");
  1.2950 +  NS_ABORT_IF_FALSE(aSocketIn, "OTA with invalid socketIn");
  1.2951 +
  1.2952 +  mTransport = aTransport;
  1.2953 +  mSocketIn = aSocketIn;
  1.2954 +  mSocketOut = aSocketOut;
  1.2955 +
  1.2956 +  nsresult rv;
  1.2957 +  rv = mTransport->SetEventSink(nullptr, nullptr);
  1.2958 +  if (NS_FAILED(rv)) return rv;
  1.2959 +  rv = mTransport->SetSecurityCallbacks(this);
  1.2960 +  if (NS_FAILED(rv)) return rv;
  1.2961 +
  1.2962 +  mRecvdHttpUpgradeTransport = 1;
  1.2963 +  if (mGotUpgradeOK)
  1.2964 +    return StartWebsocketData();
  1.2965 +  return NS_OK;
  1.2966 +}
  1.2967 +
  1.2968 +// nsIRequestObserver (from nsIStreamListener)
  1.2969 +
  1.2970 +NS_IMETHODIMP
  1.2971 +WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
  1.2972 +                                 nsISupports *aContext)
  1.2973 +{
  1.2974 +  LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
  1.2975 +       this, aRequest, aContext, mRecvdHttpUpgradeTransport));
  1.2976 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.2977 +  NS_ABORT_IF_FALSE(!mGotUpgradeOK, "OTA duplicated");
  1.2978 +
  1.2979 +  if (mOpenTimer) {
  1.2980 +    mOpenTimer->Cancel();
  1.2981 +    mOpenTimer = nullptr;
  1.2982 +  }
  1.2983 +
  1.2984 +  if (mStopped) {
  1.2985 +    LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
  1.2986 +    AbortSession(NS_ERROR_CONNECTION_REFUSED);
  1.2987 +    return NS_ERROR_CONNECTION_REFUSED;
  1.2988 +  }
  1.2989 +
  1.2990 +  nsresult rv;
  1.2991 +  uint32_t status;
  1.2992 +  char *val, *token;
  1.2993 +
  1.2994 +  rv = mHttpChannel->GetResponseStatus(&status);
  1.2995 +  if (NS_FAILED(rv)) {
  1.2996 +    LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
  1.2997 +    AbortSession(NS_ERROR_CONNECTION_REFUSED);
  1.2998 +    return NS_ERROR_CONNECTION_REFUSED;
  1.2999 +  }
  1.3000 +
  1.3001 +  LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
  1.3002 +  if (status != 101) {
  1.3003 +    AbortSession(NS_ERROR_CONNECTION_REFUSED);
  1.3004 +    return NS_ERROR_CONNECTION_REFUSED;
  1.3005 +  }
  1.3006 +
  1.3007 +  nsAutoCString respUpgrade;
  1.3008 +  rv = mHttpChannel->GetResponseHeader(
  1.3009 +    NS_LITERAL_CSTRING("Upgrade"), respUpgrade);
  1.3010 +
  1.3011 +  if (NS_SUCCEEDED(rv)) {
  1.3012 +    rv = NS_ERROR_ILLEGAL_VALUE;
  1.3013 +    if (!respUpgrade.IsEmpty()) {
  1.3014 +      val = respUpgrade.BeginWriting();
  1.3015 +      while ((token = nsCRT::strtok(val, ", \t", &val))) {
  1.3016 +        if (PL_strcasecmp(token, "Websocket") == 0) {
  1.3017 +          rv = NS_OK;
  1.3018 +          break;
  1.3019 +        }
  1.3020 +      }
  1.3021 +    }
  1.3022 +  }
  1.3023 +
  1.3024 +  if (NS_FAILED(rv)) {
  1.3025 +    LOG(("WebSocketChannel::OnStartRequest: "
  1.3026 +         "HTTP response header Upgrade: websocket not found\n"));
  1.3027 +    AbortSession(NS_ERROR_ILLEGAL_VALUE);
  1.3028 +    return rv;
  1.3029 +  }
  1.3030 +
  1.3031 +  nsAutoCString respConnection;
  1.3032 +  rv = mHttpChannel->GetResponseHeader(
  1.3033 +    NS_LITERAL_CSTRING("Connection"), respConnection);
  1.3034 +
  1.3035 +  if (NS_SUCCEEDED(rv)) {
  1.3036 +    rv = NS_ERROR_ILLEGAL_VALUE;
  1.3037 +    if (!respConnection.IsEmpty()) {
  1.3038 +      val = respConnection.BeginWriting();
  1.3039 +      while ((token = nsCRT::strtok(val, ", \t", &val))) {
  1.3040 +        if (PL_strcasecmp(token, "Upgrade") == 0) {
  1.3041 +          rv = NS_OK;
  1.3042 +          break;
  1.3043 +        }
  1.3044 +      }
  1.3045 +    }
  1.3046 +  }
  1.3047 +
  1.3048 +  if (NS_FAILED(rv)) {
  1.3049 +    LOG(("WebSocketChannel::OnStartRequest: "
  1.3050 +         "HTTP response header 'Connection: Upgrade' not found\n"));
  1.3051 +    AbortSession(NS_ERROR_ILLEGAL_VALUE);
  1.3052 +    return rv;
  1.3053 +  }
  1.3054 +
  1.3055 +  nsAutoCString respAccept;
  1.3056 +  rv = mHttpChannel->GetResponseHeader(
  1.3057 +                       NS_LITERAL_CSTRING("Sec-WebSocket-Accept"),
  1.3058 +                       respAccept);
  1.3059 +
  1.3060 +  if (NS_FAILED(rv) ||
  1.3061 +    respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) {
  1.3062 +    LOG(("WebSocketChannel::OnStartRequest: "
  1.3063 +         "HTTP response header Sec-WebSocket-Accept check failed\n"));
  1.3064 +    LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
  1.3065 +         mHashedSecret.get(), respAccept.get()));
  1.3066 +    AbortSession(NS_ERROR_ILLEGAL_VALUE);
  1.3067 +    return NS_ERROR_ILLEGAL_VALUE;
  1.3068 +  }
  1.3069 +
  1.3070 +  // If we sent a sub protocol header, verify the response matches
  1.3071 +  // If it does not, set mProtocol to "" so the protocol attribute
  1.3072 +  // of the WebSocket JS object reflects that
  1.3073 +  if (!mProtocol.IsEmpty()) {
  1.3074 +    nsAutoCString respProtocol;
  1.3075 +    rv = mHttpChannel->GetResponseHeader(
  1.3076 +                         NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), 
  1.3077 +                         respProtocol);
  1.3078 +    if (NS_SUCCEEDED(rv)) {
  1.3079 +      rv = NS_ERROR_ILLEGAL_VALUE;
  1.3080 +      val = mProtocol.BeginWriting();
  1.3081 +      while ((token = nsCRT::strtok(val, ", \t", &val))) {
  1.3082 +        if (PL_strcasecmp(token, respProtocol.get()) == 0) {
  1.3083 +          rv = NS_OK;
  1.3084 +          break;
  1.3085 +        }
  1.3086 +      }
  1.3087 +
  1.3088 +      if (NS_SUCCEEDED(rv)) {
  1.3089 +        LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
  1.3090 +             respProtocol.get()));
  1.3091 +        mProtocol = respProtocol;
  1.3092 +      } else {
  1.3093 +        LOG(("WebsocketChannel::OnStartRequest: "
  1.3094 +             "subprotocol [%s] not found - %s returned",
  1.3095 +             mProtocol.get(), respProtocol.get()));
  1.3096 +        mProtocol.Truncate();
  1.3097 +      }
  1.3098 +    } else {
  1.3099 +      LOG(("WebsocketChannel::OnStartRequest "
  1.3100 +                 "subprotocol [%s] not found - none returned",
  1.3101 +                 mProtocol.get()));
  1.3102 +      mProtocol.Truncate();
  1.3103 +    }
  1.3104 +  }
  1.3105 +
  1.3106 +  rv = HandleExtensions();
  1.3107 +  if (NS_FAILED(rv))
  1.3108 +    return rv;
  1.3109 +
  1.3110 +  mGotUpgradeOK = 1;
  1.3111 +  if (mRecvdHttpUpgradeTransport)
  1.3112 +    return StartWebsocketData();
  1.3113 +
  1.3114 +  return NS_OK;
  1.3115 +}
  1.3116 +
  1.3117 +NS_IMETHODIMP
  1.3118 +WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
  1.3119 +                                  nsISupports *aContext,
  1.3120 +                                  nsresult aStatusCode)
  1.3121 +{
  1.3122 +  LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
  1.3123 +       this, aRequest, aContext, aStatusCode));
  1.3124 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1.3125 +
  1.3126 +  ReportConnectionTelemetry();
  1.3127 +
  1.3128 +  // This is the end of the HTTP upgrade transaction, the
  1.3129 +  // upgraded streams live on
  1.3130 +
  1.3131 +  mChannel = nullptr;
  1.3132 +  mHttpChannel = nullptr;
  1.3133 +  mLoadGroup = nullptr;
  1.3134 +  mCallbacks = nullptr;
  1.3135 +
  1.3136 +  return NS_OK;
  1.3137 +}
  1.3138 +
  1.3139 +// nsIInputStreamCallback
  1.3140 +
  1.3141 +NS_IMETHODIMP
  1.3142 +WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream)
  1.3143 +{
  1.3144 +  LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
  1.3145 +  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1.3146 +
  1.3147 +  if (!mSocketIn) // did we we clean up the socket after scheduling InputReady?
  1.3148 +    return NS_OK;
  1.3149 +  
  1.3150 +  nsRefPtr<nsIStreamListener>    deleteProtector1(mInflateReader);
  1.3151 +  nsRefPtr<nsIStringInputStream> deleteProtector2(mInflateStream);
  1.3152 +
  1.3153 +  // this is after the  http upgrade - so we are speaking websockets
  1.3154 +  char  buffer[2048];
  1.3155 +  uint32_t count;
  1.3156 +  nsresult rv;
  1.3157 +
  1.3158 +  do {
  1.3159 +    rv = mSocketIn->Read((char *)buffer, 2048, &count);
  1.3160 +    LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv));
  1.3161 +
  1.3162 +    // accumulate received bytes
  1.3163 +    CountRecvBytes(count);
  1.3164 +
  1.3165 +    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
  1.3166 +      mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
  1.3167 +      return NS_OK;
  1.3168 +    }
  1.3169 +
  1.3170 +    if (NS_FAILED(rv)) {
  1.3171 +      mTCPClosed = true;
  1.3172 +      AbortSession(rv);
  1.3173 +      return rv;
  1.3174 +    }
  1.3175 +
  1.3176 +    if (count == 0) {
  1.3177 +      mTCPClosed = true;
  1.3178 +      AbortSession(NS_BASE_STREAM_CLOSED);
  1.3179 +      return NS_OK;
  1.3180 +    }
  1.3181 +
  1.3182 +    if (mStopped) {
  1.3183 +      continue;
  1.3184 +    }
  1.3185 +
  1.3186 +    if (mInflateReader) {
  1.3187 +      mInflateStream->ShareData(buffer, count);
  1.3188 +      rv = mInflateReader->OnDataAvailable(nullptr, mSocketIn, mInflateStream, 
  1.3189 +                                           0, count);
  1.3190 +    } else {
  1.3191 +      rv = ProcessInput((uint8_t *)buffer, count);
  1.3192 +    }
  1.3193 +
  1.3194 +    if (NS_FAILED(rv)) {
  1.3195 +      AbortSession(rv);
  1.3196 +      return rv;
  1.3197 +    }
  1.3198 +  } while (NS_SUCCEEDED(rv) && mSocketIn);
  1.3199 +
  1.3200 +  return NS_OK;
  1.3201 +}
  1.3202 +
  1.3203 +
  1.3204 +// nsIOutputStreamCallback
  1.3205 +
  1.3206 +NS_IMETHODIMP
  1.3207 +WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream)
  1.3208 +{
  1.3209 +  LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
  1.3210 +  NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1.3211 +  nsresult rv;
  1.3212 +
  1.3213 +  if (!mCurrentOut)
  1.3214 +    PrimeNewOutgoingMessage();
  1.3215 +
  1.3216 +  while (mCurrentOut && mSocketOut) {
  1.3217 +    const char *sndBuf;
  1.3218 +    uint32_t toSend;
  1.3219 +    uint32_t amtSent;
  1.3220 +
  1.3221 +    if (mHdrOut) {
  1.3222 +      sndBuf = (const char *)mHdrOut;
  1.3223 +      toSend = mHdrOutToSend;
  1.3224 +      LOG(("WebSocketChannel::OnOutputStreamReady: "
  1.3225 +           "Try to send %u of hdr/copybreak\n", toSend));
  1.3226 +    } else {
  1.3227 +      sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent;
  1.3228 +      toSend = mCurrentOut->Length() - mCurrentOutSent;
  1.3229 +      if (toSend > 0) {
  1.3230 +        LOG(("WebSocketChannel::OnOutputStreamReady: "
  1.3231 +             "Try to send %u of data\n", toSend));
  1.3232 +      }
  1.3233 +    }
  1.3234 +
  1.3235 +    if (toSend == 0) {
  1.3236 +      amtSent = 0;
  1.3237 +    } else {
  1.3238 +      rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
  1.3239 +      LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n",
  1.3240 +           amtSent, rv));
  1.3241 +
  1.3242 +      // accumulate sent bytes
  1.3243 +      CountSentBytes(amtSent);
  1.3244 +
  1.3245 +      if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
  1.3246 +        mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
  1.3247 +        return NS_OK;
  1.3248 +      }
  1.3249 +
  1.3250 +      if (NS_FAILED(rv)) {
  1.3251 +        AbortSession(rv);
  1.3252 +        return NS_OK;
  1.3253 +      }
  1.3254 +    }
  1.3255 +
  1.3256 +    if (mHdrOut) {
  1.3257 +      if (amtSent == toSend) {
  1.3258 +        mHdrOut = nullptr;
  1.3259 +        mHdrOutToSend = 0;
  1.3260 +      } else {
  1.3261 +        mHdrOut += amtSent;
  1.3262 +        mHdrOutToSend -= amtSent;
  1.3263 +      }
  1.3264 +    } else {
  1.3265 +      if (amtSent == toSend) {
  1.3266 +        if (!mStopped) {
  1.3267 +          mTargetThread->Dispatch(new CallAcknowledge(this,
  1.3268 +                                                      mCurrentOut->Length()),
  1.3269 +                                  NS_DISPATCH_NORMAL);
  1.3270 +        }
  1.3271 +        DeleteCurrentOutGoingMessage();
  1.3272 +        PrimeNewOutgoingMessage();
  1.3273 +      } else {
  1.3274 +        mCurrentOutSent += amtSent;
  1.3275 +      }
  1.3276 +    }
  1.3277 +  }
  1.3278 +
  1.3279 +  if (mReleaseOnTransmit)
  1.3280 +    ReleaseSession();
  1.3281 +  return NS_OK;
  1.3282 +}
  1.3283 +
  1.3284 +// nsIStreamListener
  1.3285 +
  1.3286 +NS_IMETHODIMP
  1.3287 +WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
  1.3288 +                                    nsISupports *aContext,
  1.3289 +                                    nsIInputStream *aInputStream,
  1.3290 +                                    uint64_t aOffset,
  1.3291 +                                    uint32_t aCount)
  1.3292 +{
  1.3293 +  LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %u]\n",
  1.3294 +         this, aRequest, aContext, aInputStream, aOffset, aCount));
  1.3295 +
  1.3296 +  if (aContext == mSocketIn) {
  1.3297 +    // This is the deflate decoder
  1.3298 +
  1.3299 +    LOG(("WebSocketChannel::OnDataAvailable: Deflate Data %u\n",
  1.3300 +             aCount));
  1.3301 +
  1.3302 +    uint8_t  buffer[2048];
  1.3303 +    uint32_t maxRead;
  1.3304 +    uint32_t count;
  1.3305 +    nsresult rv = NS_OK;  // aCount always > 0, so this just avoids warning
  1.3306 +
  1.3307 +    while (aCount > 0) {
  1.3308 +      if (mStopped)
  1.3309 +        return NS_BASE_STREAM_CLOSED;
  1.3310 +
  1.3311 +      maxRead = std::min(2048U, aCount);
  1.3312 +      rv = aInputStream->Read((char *)buffer, maxRead, &count);
  1.3313 +      LOG(("WebSocketChannel::OnDataAvailable: InflateRead read %u rv %x\n",
  1.3314 +           count, rv));
  1.3315 +      if (NS_FAILED(rv) || count == 0) {
  1.3316 +        AbortSession(NS_ERROR_UNEXPECTED);
  1.3317 +        break;
  1.3318 +      }
  1.3319 +
  1.3320 +      aCount -= count;
  1.3321 +      rv = ProcessInput(buffer, count);
  1.3322 +      if (NS_FAILED(rv)) {
  1.3323 +        AbortSession(rv);
  1.3324 +        break;
  1.3325 +      }
  1.3326 +    }
  1.3327 +    return rv;
  1.3328 +  }
  1.3329 +
  1.3330 +  if (aContext == mSocketOut) {
  1.3331 +    // This is the deflate encoder
  1.3332 +
  1.3333 +    uint32_t maxRead;
  1.3334 +    uint32_t count;
  1.3335 +    nsresult rv;
  1.3336 +
  1.3337 +    while (aCount > 0) {
  1.3338 +      if (mStopped)
  1.3339 +        return NS_BASE_STREAM_CLOSED;
  1.3340 +
  1.3341 +      maxRead = std::min(2048U, aCount);
  1.3342 +      EnsureHdrOut(mHdrOutToSend + aCount);
  1.3343 +      rv = aInputStream->Read((char *)mHdrOut + mHdrOutToSend, maxRead, &count);
  1.3344 +      LOG(("WebSocketChannel::OnDataAvailable: DeflateWrite read %u rv %x\n", 
  1.3345 +           count, rv));
  1.3346 +      if (NS_FAILED(rv) || count == 0) {
  1.3347 +        AbortSession(rv);
  1.3348 +        break;
  1.3349 +      }
  1.3350 +
  1.3351 +      mHdrOutToSend += count;
  1.3352 +      aCount -= count;
  1.3353 +    }
  1.3354 +    return NS_OK;
  1.3355 +  }
  1.3356 +
  1.3357 +
  1.3358 +  // Otherwise, this is the HTTP OnDataAvailable Method, which means
  1.3359 +  // this is http data in response to the upgrade request and
  1.3360 +  // there should be no http response body if the upgrade succeeded
  1.3361 +
  1.3362 +  // This generally should be caught by a non 101 response code in
  1.3363 +  // OnStartRequest().. so we can ignore the data here
  1.3364 +
  1.3365 +  LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
  1.3366 +         aCount));
  1.3367 +
  1.3368 +  return NS_OK;
  1.3369 +}
  1.3370 +
  1.3371 +nsresult
  1.3372 +WebSocketChannel::SaveNetworkStats(bool enforce)
  1.3373 +{
  1.3374 +#ifdef MOZ_WIDGET_GONK
  1.3375 +  // Check if the active network and app id are valid.
  1.3376 +  if(!mActiveNetwork || mAppId == NECKO_NO_APP_ID) {
  1.3377 +    return NS_OK;
  1.3378 +  }
  1.3379 +
  1.3380 +  if (mCountRecv <= 0 && mCountSent <= 0) {
  1.3381 +    // There is no traffic, no need to save.
  1.3382 +    return NS_OK;
  1.3383 +  }
  1.3384 +
  1.3385 +  // If |enforce| is false, the traffic amount is saved
  1.3386 +  // only when the total amount exceeds the predefined
  1.3387 +  // threshold.
  1.3388 +  uint64_t totalBytes = mCountRecv + mCountSent;
  1.3389 +  if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
  1.3390 +    return NS_OK;
  1.3391 +  }
  1.3392 +
  1.3393 +  // Create the event to save the network statistics.
  1.3394 +  // the event is then dispathed to the main thread.
  1.3395 +  nsRefPtr<nsRunnable> event =
  1.3396 +    new SaveNetworkStatsEvent(mAppId, mActiveNetwork,
  1.3397 +                              mCountRecv, mCountSent, false);
  1.3398 +  NS_DispatchToMainThread(event);
  1.3399 +
  1.3400 +  // Reset the counters after saving.
  1.3401 +  mCountSent = 0;
  1.3402 +  mCountRecv = 0;
  1.3403 +
  1.3404 +  return NS_OK;
  1.3405 +#else
  1.3406 +  return NS_ERROR_NOT_IMPLEMENTED;
  1.3407 +#endif
  1.3408 +}
  1.3409 +
  1.3410 +} // namespace mozilla::net
  1.3411 +} // namespace mozilla

mercurial