Fri, 16 Jan 2015 18:13:44 +0100
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", ¤tIsHttps); |
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 |