netwerk/protocol/websocket/WebSocketChannel.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

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

mercurial