Http2Session.cpp (151736B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et tw=80 : */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // HttpLog.h should generally be included first 8 #include "HttpLog.h" 9 #include "nsCOMPtr.h" 10 #include "nsStringFwd.h" 11 12 // Log on level :5, instead of default :4. 13 #undef LOG 14 #define LOG(args) LOG5(args) 15 #undef LOG_ENABLED 16 #define LOG_ENABLED() LOG5_ENABLED() 17 18 #include <algorithm> 19 20 #include "AltServiceChild.h" 21 #include "CacheControlParser.h" 22 #include "Http2Session.h" 23 #include "Http2Stream.h" 24 #include "Http2StreamBase.h" 25 #include "Http2StreamTunnel.h" 26 #include "Http2WebTransportSession.h" 27 #include "LoadContextInfo.h" 28 #include "mozilla/EndianUtils.h" 29 #include "mozilla/glean/NetwerkMetrics.h" 30 #include "mozilla/Preferences.h" 31 #include "mozilla/Sprintf.h" 32 #include "mozilla/StaticPrefs_network.h" 33 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h" 34 #include "nsHttp.h" 35 #include "nsHttpConnection.h" 36 #include "nsHttpHandler.h" 37 #include "nsIRequestContext.h" 38 #include "nsISupportsPriority.h" 39 #include "nsITLSSocketControl.h" 40 #include "nsNetUtil.h" 41 #include "nsQueryObject.h" 42 #include "nsSocketTransportService2.h" 43 #include "nsStandardURL.h" 44 #include "nsURLHelper.h" 45 #include "prnetdb.h" 46 #include "sslerr.h" 47 #include "sslt.h" 48 49 namespace mozilla { 50 namespace net { 51 52 extern const nsCString& TRRProviderKey(); 53 54 Http2StreamQueueManager::StreamQueue& Http2StreamQueueManager::GetQueue( 55 Http2StreamQueueType aType) { 56 switch (aType) { 57 case Http2StreamQueueType::ReadyForWrite: 58 return mReadyForWrite; 59 case Http2StreamQueueType::QueuedStreams: 60 return mQueuedStreams; 61 case Http2StreamQueueType::SlowConsumersReadyForRead: 62 return mSlowConsumersReadyForRead; 63 default: 64 MOZ_CRASH("Invalid queue type"); 65 return mReadyForWrite; 66 } 67 } 68 69 bool Http2StreamQueueManager::GetQueueFlag(Http2StreamQueueType aType, 70 Http2StreamBase* aStream) { 71 switch (aType) { 72 case Http2StreamQueueType::ReadyForWrite: 73 return aStream->InWriteQueue(); 74 case Http2StreamQueueType::QueuedStreams: 75 return aStream->Queued(); 76 case Http2StreamQueueType::SlowConsumersReadyForRead: 77 return aStream->InReadQueue(); 78 default: 79 MOZ_CRASH("Invalid queue type"); 80 return false; 81 } 82 } 83 84 void Http2StreamQueueManager::SetQueueFlag(Http2StreamQueueType aType, 85 Http2StreamBase* aStream, 86 bool value) { 87 switch (aType) { 88 case Http2StreamQueueType::ReadyForWrite: 89 aStream->SetInWriteQueue(value); 90 break; 91 case Http2StreamQueueType::QueuedStreams: 92 aStream->SetQueued(value); 93 break; 94 case Http2StreamQueueType::SlowConsumersReadyForRead: 95 aStream->SetInReadQueue(value); 96 break; 97 default: 98 MOZ_CRASH("Invalid queue type"); 99 } 100 } 101 102 void Http2StreamQueueManager::RemoveStreamFromAllQueue( 103 Http2StreamBase* aStream) { 104 // This does not immediately remove the stream from the underlying queues. 105 // Instead, it clears the queue flags so that the stream will be skipped 106 // the next time GetNextStreamFromQueue is called. 107 aStream->SetInWriteQueue(false); 108 aStream->SetQueued(false); 109 aStream->SetInReadQueue(false); 110 } 111 112 void Http2StreamQueueManager::AddStreamToQueue(Http2StreamQueueType aType, 113 Http2StreamBase* aStream) { 114 if (GetQueueFlag(aType, aStream)) { 115 return; 116 } 117 118 GetQueue(aType).Push(aStream); 119 SetQueueFlag(aType, aStream, true); 120 } 121 122 already_AddRefed<Http2StreamBase> 123 Http2StreamQueueManager::GetNextStreamFromQueue(Http2StreamQueueType aType) { 124 StreamQueue& queue = GetQueue(aType); 125 126 while (!queue.IsEmpty()) { 127 RefPtr<Http2StreamBase> stream = queue.Pop().get(); 128 if (stream && GetQueueFlag(aType, stream)) { 129 SetQueueFlag(aType, stream, false); 130 return stream.forget(); 131 } 132 } 133 134 return nullptr; 135 } 136 137 // Http2Session has multiple inheritance of things that implement nsISupports 138 NS_IMPL_ADDREF_INHERITED(Http2Session, nsAHttpConnection) 139 NS_IMPL_RELEASE_INHERITED(Http2Session, nsAHttpConnection) 140 141 NS_INTERFACE_MAP_BEGIN(Http2Session) 142 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 143 NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2Session) 144 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) 145 NS_INTERFACE_MAP_END 146 147 // "magic" refers to the string that preceeds HTTP/2 on the wire 148 // to help find any intermediaries speaking an older version of HTTP 149 const uint8_t Http2Session::kMagicHello[] = { 150 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 151 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a}; 152 153 Http2Session* Http2Session::CreateSession(nsISocketTransport* aSocketTransport, 154 enum SpdyVersion version, 155 bool attemptingEarlyData) { 156 if (!gHttpHandler) { 157 RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance(); 158 (void)handler.get(); 159 } 160 161 Http2Session* session = 162 new Http2Session(aSocketTransport, version, attemptingEarlyData); 163 session->SendHello(); 164 return session; 165 } 166 167 Http2Session::Http2Session(nsISocketTransport* aSocketTransport, 168 enum SpdyVersion version, bool attemptingEarlyData) 169 : mSocketTransport(aSocketTransport), 170 mSegmentReader(nullptr), 171 mSegmentWriter(nullptr), 172 kMaxStreamID(StaticPrefs::network_http_http2_max_stream_id()), 173 mNextStreamID(3) // 1 is reserved for Updgrade handshakes 174 , 175 mConcurrentHighWater(0), 176 mDownstreamState(BUFFERING_OPENING_SETTINGS), 177 mInputFrameBufferSize(kDefaultBufferSize), 178 mInputFrameBufferUsed(0), 179 mInputFrameDataSize(0), 180 mInputFrameDataRead(0), 181 mInputFrameFinal(false), 182 mInputFrameType(0), 183 mInputFrameFlags(0), 184 mInputFrameID(0), 185 mPaddingLength(0), 186 mInputFrameDataStream(nullptr), 187 mNeedsCleanup(nullptr), 188 mDownstreamRstReason(NO_HTTP_ERROR), 189 mExpectedHeaderID(0), 190 mExpectedPushPromiseID(0), 191 mFlatHTTPResponseHeadersOut(0), 192 mShouldGoAway(false), 193 mClosed(false), 194 mCleanShutdown(false), 195 mReceivedSettings(false), 196 mTLSProfileConfirmed(false), 197 mGoAwayReason(NO_HTTP_ERROR), 198 mClientGoAwayReason(UNASSIGNED), 199 mPeerGoAwayReason(UNASSIGNED), 200 mGoAwayID(0), 201 mOutgoingGoAwayID(0), 202 mConcurrent(0), 203 mServerPushedResources(0), 204 mServerInitialStreamWindow(kDefaultRwin), 205 mLocalSessionWindow(kDefaultRwin), 206 mServerSessionWindow(kDefaultRwin), 207 mInitialRwin(ASpdySession::kInitialRwin), 208 mOutputQueueSize(kDefaultQueueSize), 209 mOutputQueueUsed(0), 210 mOutputQueueSent(0), 211 mLastReadEpoch(PR_IntervalNow()), 212 mPingSentEpoch(0), 213 mPreviousUsed(false), 214 mAggregatedHeaderSize(0), 215 mWaitingForSettingsAck(false), 216 mGoAwayOnPush(false), 217 mUseH2Deps(false), 218 mAttemptingEarlyData(attemptingEarlyData), 219 mOriginFrameActivated(false), 220 mCntActivated(0), 221 mTlsHandshakeFinished(false), 222 mPeerFailedHandshake(false), 223 mTrrStreams(0) { 224 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 225 226 static uint64_t sSerial; 227 mSerial = ++sSerial; 228 229 LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial)); 230 231 mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize); 232 mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize); 233 mDecompressBuffer.SetCapacity(kDefaultBufferSize); 234 235 mPushAllowance = gHttpHandler->SpdyPushAllowance(); 236 mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance); 237 mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent(); 238 mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); 239 240 mLastDataReadEpoch = mLastReadEpoch; 241 242 mPingThreshold = gHttpHandler->SpdyPingThreshold(); 243 mPreviousPingThreshold = mPingThreshold; 244 mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId(); 245 246 mEnableWebsockets = StaticPrefs::network_http_http2_websockets(); 247 248 bool dumpHpackTables = StaticPrefs::network_http_http2_enable_hpack_dump(); 249 mCompressor.SetDumpTables(dumpHpackTables); 250 mDecompressor.SetDumpTables(dumpHpackTables); 251 } 252 253 void Http2Session::Shutdown(nsresult aReason) { 254 for (const auto& stream : mStreamTransactionHash.Values()) { 255 ShutdownStream(stream, aReason); 256 } 257 258 for (auto& stream : mTunnelStreams) { 259 ShutdownStream(stream, aReason); 260 } 261 } 262 263 void Http2Session::ShutdownStream(Http2StreamBase* aStream, nsresult aReason) { 264 // On a clean server hangup the server sets the GoAwayID to be the ID of 265 // the last transaction it processed. If the ID of stream in the 266 // local stream is greater than that it can safely be restarted because the 267 // server guarantees it was not partially processed. Streams that have not 268 // registered an ID haven't actually been sent yet so they can always be 269 // restarted. 270 if (mCleanShutdown && 271 (aStream->StreamID() > mGoAwayID || !aStream->HasRegisteredID())) { 272 CloseStream(aStream, NS_ERROR_NET_RESET); // can be restarted 273 } else if (aStream->RecvdData()) { 274 CloseStream(aStream, NS_ERROR_NET_PARTIAL_TRANSFER); 275 } else if (mGoAwayReason == INADEQUATE_SECURITY) { 276 CloseStream(aStream, NS_ERROR_NET_INADEQUATE_SECURITY); 277 } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) { 278 CloseStream(aStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY); 279 } else if (!mCleanShutdown && PossibleZeroRTTRetryError(aReason)) { 280 CloseStream(aStream, aReason); 281 } else { 282 CloseStream(aStream, NS_ERROR_ABORT); 283 } 284 } 285 286 Http2Session::~Http2Session() { 287 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread()); 288 LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", this, 289 mDownstreamState)); 290 291 Shutdown(NS_OK); 292 293 if (mTrrStreams) { 294 mozilla::glean::networking::trr_request_count_per_conn 295 .Get(nsPrintfCString("%s_h2", mTrrHost.get())) 296 .Add(static_cast<int32_t>(mTrrStreams)); 297 } 298 glean::spdy::parallel_streams.AccumulateSingleSample(mConcurrentHighWater); 299 glean::spdy::request_per_conn.AccumulateSingleSample(mCntActivated); 300 glean::spdy::server_initiated_streams.AccumulateSingleSample( 301 mServerPushedResources); 302 glean::spdy::goaway_local.AccumulateSingleSample(mClientGoAwayReason); 303 glean::spdy::goaway_peer.AccumulateSingleSample(mPeerGoAwayReason); 304 glean::http::http2_fail_before_settings 305 .EnumGet(static_cast<glean::http::Http2FailBeforeSettingsLabel>( 306 mPeerFailedHandshake)) 307 .Add(); 308 } 309 310 inline nsresult Http2Session::SessionError(enum errorType reason) { 311 LOG3(("Http2Session::SessionError %p reason=0x%x mPeerGoAwayReason=0x%x", 312 this, reason, mPeerGoAwayReason)); 313 mGoAwayReason = reason; 314 315 if (reason == INADEQUATE_SECURITY) { 316 // This one is special, as we have an error page just for this 317 return NS_ERROR_NET_INADEQUATE_SECURITY; 318 } 319 320 // We're the one sending a generic GOAWAY 321 return NS_ERROR_NET_HTTP2_SENT_GOAWAY; 322 } 323 324 void Http2Session::LogIO(Http2Session* self, Http2StreamBase* stream, 325 const char* label, const char* data, 326 uint32_t datalen) { 327 if (!MOZ_LOG_TEST(gHttpIOLog, LogLevel::Verbose)) { 328 return; 329 } 330 331 MOZ_LOG(gHttpIOLog, LogLevel::Verbose, 332 ("Http2Session::LogIO %p stream=%p id=0x%X [%s]", self, stream, 333 stream ? stream->StreamID() : 0, label)); 334 335 // Max line is (16 * 3) + 10(prefix) + newline + null 336 char linebuf[128]; 337 uint32_t index; 338 char* line = linebuf; 339 340 linebuf[127] = 0; 341 342 for (index = 0; index < datalen; ++index) { 343 if (!(index % 16)) { 344 if (index) { 345 *line = 0; 346 MOZ_LOG(gHttpIOLog, LogLevel::Verbose, ("%s", linebuf)); 347 } 348 line = linebuf; 349 snprintf(line, 128, "%08X: ", index); 350 line += 10; 351 } 352 snprintf(line, 128 - (line - linebuf), "%02X ", 353 (reinterpret_cast<const uint8_t*>(data))[index]); 354 line += 3; 355 } 356 if (index) { 357 *line = 0; 358 MOZ_LOG(gHttpIOLog, LogLevel::Verbose, ("%s", linebuf)); 359 } 360 } 361 362 using Http2ControlFx = nsresult (*)(Http2Session*); 363 static constexpr Http2ControlFx sControlFunctions[] = { 364 nullptr, // type 0 data is not a control function 365 Http2Session::RecvHeaders, 366 Http2Session::RecvPriority, 367 Http2Session::RecvRstStream, 368 Http2Session::RecvSettings, 369 Http2Session::RecvPushPromise, 370 Http2Session::RecvPing, 371 Http2Session::RecvGoAway, 372 Http2Session::RecvWindowUpdate, 373 Http2Session::RecvContinuation, 374 Http2Session::RecvAltSvc, // extension for type 0x0A 375 Http2Session::RecvUnused, // 0x0B was BLOCKED still radioactive 376 Http2Session::RecvOrigin, // extension for type 0x0C 377 Http2Session::RecvUnused, // 0x0D 378 Http2Session::RecvUnused, // 0x0E 379 Http2Session::RecvUnused, // 0x0F 380 Http2Session::RecvPriorityUpdate, // 0x10 381 }; 382 383 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_DATA] == nullptr); 384 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_HEADERS] == 385 Http2Session::RecvHeaders); 386 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PRIORITY] == 387 Http2Session::RecvPriority); 388 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_RST_STREAM] == 389 Http2Session::RecvRstStream); 390 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_SETTINGS] == 391 Http2Session::RecvSettings); 392 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PUSH_PROMISE] == 393 Http2Session::RecvPushPromise); 394 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PING] == 395 Http2Session::RecvPing); 396 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_GOAWAY] == 397 Http2Session::RecvGoAway); 398 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_WINDOW_UPDATE] == 399 Http2Session::RecvWindowUpdate); 400 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_CONTINUATION] == 401 Http2Session::RecvContinuation); 402 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_ALTSVC] == 403 Http2Session::RecvAltSvc); 404 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_UNUSED] == 405 Http2Session::RecvUnused); 406 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_ORIGIN] == 407 Http2Session::RecvOrigin); 408 static_assert(sControlFunctions[0x0D] == Http2Session::RecvUnused); 409 static_assert(sControlFunctions[0x0E] == Http2Session::RecvUnused); 410 static_assert(sControlFunctions[0x0F] == Http2Session::RecvUnused); 411 static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PRIORITY_UPDATE] == 412 Http2Session::RecvPriorityUpdate); 413 414 uint32_t Http2Session::RoomForMoreConcurrent() { 415 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 416 if (mConcurrent > mMaxConcurrent) { 417 return 0; 418 } 419 return mMaxConcurrent - mConcurrent; 420 } 421 422 bool Http2Session::RoomForMoreStreams() { 423 if (mNextStreamID + mStreamTransactionHash.Count() * 2 + 424 mTunnelStreams.Length() > 425 kMaxStreamID) { 426 mShouldGoAway = true; 427 return false; 428 } 429 430 return !mShouldGoAway; 431 } 432 433 PRIntervalTime Http2Session::IdleTime() { 434 return PR_IntervalNow() - mLastDataReadEpoch; 435 } 436 437 uint32_t Http2Session::ReadTimeoutTick(PRIntervalTime now) { 438 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 439 440 LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", this, 441 PR_IntervalToSeconds(now - mLastReadEpoch))); 442 443 if (!mPingThreshold) { 444 return UINT32_MAX; 445 } 446 447 if ((now - mLastReadEpoch) < mPingThreshold) { 448 // recent activity means ping is not an issue 449 if (mPingSentEpoch) { 450 mPingSentEpoch = 0; 451 if (mPreviousUsed) { 452 // restore the former value 453 mPingThreshold = mPreviousPingThreshold; 454 mPreviousUsed = false; 455 } 456 } 457 458 return PR_IntervalToSeconds(mPingThreshold) - 459 PR_IntervalToSeconds(now - mLastReadEpoch); 460 } 461 462 if (mPingSentEpoch) { 463 bool isTrr = (mTrrStreams > 0); 464 uint32_t pingTimeout = isTrr ? StaticPrefs::network_trr_ping_timeout() 465 : gHttpHandler->SpdyPingTimeout(); 466 LOG3( 467 ("Http2Session::ReadTimeoutTick %p handle outstanding ping, " 468 "timeout=%d\n", 469 this, pingTimeout)); 470 if ((now - mPingSentEpoch) >= pingTimeout) { 471 LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this)); 472 if (mConnection) { 473 mConnection->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT); 474 } 475 mPingSentEpoch = 0; 476 if (isTrr) { 477 // These must be set this way to ensure we gracefully restart all 478 // streams 479 mGoAwayID = 0; 480 mCleanShutdown = true; 481 // If TRR is mode 2, this Http2Session will be closed due to TRR request 482 // timeout, so we won't reach this code. If we are in mode 3, the 483 // request timeout is usually larger than the ping timeout. We close the 484 // stream with NS_ERROR_NET_RESET, so the transactions can be restarted. 485 Close(NS_ERROR_NET_RESET); 486 } else { 487 Close(NS_ERROR_NET_TIMEOUT); 488 } 489 return UINT32_MAX; 490 } 491 return 1; // run the tick aggressively while ping is outstanding 492 } 493 494 LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this)); 495 496 mPingSentEpoch = PR_IntervalNow(); 497 if (!mPingSentEpoch) { 498 mPingSentEpoch = 1; // avoid the 0 sentinel value 499 } 500 GeneratePing(false); 501 (void)ResumeRecv(); // read the ping reply 502 503 return 1; // run the tick aggressively while ping is outstanding 504 } 505 506 uint32_t Http2Session::RegisterStreamID(Http2StreamBase* stream, 507 uint32_t aNewID) { 508 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 509 MOZ_ASSERT(mNextStreamID < 0xfffffff0, 510 "should have stopped admitting streams"); 511 MOZ_ASSERT(!(aNewID & 1), 512 "0 for autoassign pull, otherwise explicit even push assignment"); 513 514 if (!aNewID) { 515 // auto generate a new pull stream ID 516 aNewID = mNextStreamID; 517 MOZ_ASSERT(aNewID & 1, "pull ID must be odd."); 518 mNextStreamID += 2; 519 } 520 521 LOG1( 522 ("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X " 523 "concurrent=%d", 524 this, stream, aNewID, mConcurrent)); 525 526 // We've used up plenty of ID's on this session. Start 527 // moving to a new one before there is a crunch involving 528 // server push streams or concurrent non-registered submits 529 if (aNewID >= kMaxStreamID) { 530 mShouldGoAway = true; 531 } 532 533 // integrity check 534 if (mStreamIDHash.Contains(aNewID)) { 535 LOG3((" New ID already present\n")); 536 MOZ_ASSERT(false, "New ID already present in mStreamIDHash"); 537 mShouldGoAway = true; 538 return kDeadStreamID; 539 } 540 541 mStreamIDHash.InsertOrUpdate(aNewID, stream); 542 543 if (aNewID & 1) { 544 // don't count push streams here 545 RefPtr<nsHttpConnectionInfo> ci(stream->ConnectionInfo()); 546 if (ci && ci->GetIsTrrServiceChannel()) { 547 if (mTrrHost.IsEmpty()) { 548 mTrrHost = ci->GetOrigin(); 549 } 550 IncrementTrrCounter(); 551 } 552 } 553 return aNewID; 554 } 555 556 bool Http2Session::AddStream(nsAHttpTransaction* aHttpTransaction, 557 int32_t aPriority, 558 nsIInterfaceRequestor* aCallbacks) { 559 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 560 561 // integrity check 562 if (mStreamTransactionHash.Contains(aHttpTransaction)) { 563 LOG3((" New transaction already present\n")); 564 MOZ_ASSERT(false, "AddStream duplicate transaction pointer"); 565 return false; 566 } 567 568 if (!mConnection) { 569 mConnection = aHttpTransaction->Connection(); 570 } 571 572 if (!mFirstHttpTransaction && !mTlsHandshakeFinished) { 573 mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction(); 574 LOG3(("Http2Session::AddStream first session=%p trans=%p ", this, 575 mFirstHttpTransaction.get())); 576 } 577 578 if (mClosed || mShouldGoAway) { 579 nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction(); 580 if (trans) { 581 LOG3( 582 ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - " 583 "resched.\n", 584 this, aHttpTransaction, trans)); 585 aHttpTransaction->SetConnection(nullptr); 586 nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority()); 587 if (NS_FAILED(rv)) { 588 LOG3( 589 ("Http2Session::AddStream %p atrans=%p trans=%p failed to " 590 "initiate " 591 "transaction (%08x).\n", 592 this, aHttpTransaction, trans, static_cast<uint32_t>(rv))); 593 } 594 return true; 595 } 596 } 597 598 aHttpTransaction->SetConnection(this); 599 aHttpTransaction->OnActivated(); 600 601 CreateStream(aHttpTransaction, aPriority, Http2StreamBaseType::Normal); 602 return true; 603 } 604 605 void Http2Session::CreateStream(nsAHttpTransaction* aHttpTransaction, 606 int32_t aPriority, 607 Http2StreamBaseType streamType) { 608 RefPtr<Http2StreamBase> refStream; 609 switch (streamType) { 610 case Http2StreamBaseType::Normal: 611 refStream = 612 new Http2Stream(aHttpTransaction, this, aPriority, mCurrentBrowserId); 613 break; 614 case Http2StreamBaseType::WebSocket: 615 case Http2StreamBaseType::Tunnel: 616 case Http2StreamBaseType::ServerPush: 617 MOZ_RELEASE_ASSERT(false); 618 return; 619 } 620 621 LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " " 622 "NextID=0x%X (tentative)", 623 this, refStream.get(), mSerial, mNextStreamID)); 624 625 RefPtr<Http2StreamBase> stream = refStream; 626 mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, std::move(refStream)); 627 628 mQueueManager.AddStreamToQueue(Http2StreamQueueType::ReadyForWrite, stream); 629 SetWriteCallbacks(); 630 631 // Kick off the SYN transmit without waiting for the poll loop 632 // This won't work for the first stream because there is no segment reader 633 // yet. 634 if (mSegmentReader) { 635 uint32_t countRead; 636 (void)ReadSegments(nullptr, kDefaultBufferSize, &countRead); 637 } 638 639 if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) && 640 !aHttpTransaction->IsNullTransaction()) { 641 LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n", 642 this, aHttpTransaction)); 643 DontReuse(); 644 } 645 } 646 647 Result<already_AddRefed<nsHttpConnection>, nsresult> 648 Http2Session::CreateTunnelStream(nsAHttpTransaction* aHttpTransaction, 649 nsIInterfaceRequestor* aCallbacks, 650 PRIntervalTime aRtt, bool aIsExtendedCONNECT) { 651 bool isWebTransport = 652 aIsExtendedCONNECT && aHttpTransaction->IsForWebTransport(); 653 654 // Check if the WebTransport session limit is exceeded 655 if (isWebTransport && 656 mOngoingWebTransportSessions >= mWebTransportMaxSessions) { 657 LOG( 658 ("Http2Session::CreateTunnelStream WebTransport session limit " 659 "exceeded: Ongoing: %u, Max: %u", 660 mOngoingWebTransportSessions + 1, mWebTransportMaxSessions)); 661 aHttpTransaction->Close(NS_ERROR_WEBTRANSPORT_SESSION_LIMIT_EXCEEDED); 662 return Err(NS_ERROR_WEBTRANSPORT_SESSION_LIMIT_EXCEEDED); 663 } 664 665 RefPtr<Http2StreamTunnel> refStream = CreateTunnelStreamFromConnInfo( 666 this, mCurrentBrowserId, aHttpTransaction->ConnectionInfo(), 667 aIsExtendedCONNECT ? aHttpTransaction->IsForWebTransport() 668 ? ExtendedCONNECTType::WebTransport 669 : ExtendedCONNECTType::WebSocket 670 : ExtendedCONNECTType::Proxy); 671 672 if (isWebTransport) { 673 ++mOngoingWebTransportSessions; 674 } 675 676 RefPtr<nsHttpConnection> newConn = refStream->CreateHttpConnection( 677 aHttpTransaction, aCallbacks, aRtt, aIsExtendedCONNECT); 678 679 refStream->SetTransactionId(reinterpret_cast<uintptr_t>(aHttpTransaction)); 680 mTunnelStreams.AppendElement(std::move(refStream)); 681 return newConn.forget(); 682 } 683 684 void Http2Session::QueueStream(Http2StreamBase* stream) { 685 // will be removed via processpending or a shutdown path 686 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 687 MOZ_ASSERT(!stream->CountAsActive()); 688 MOZ_ASSERT(!stream->Queued()); 689 690 LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream)); 691 692 mQueueManager.AddStreamToQueue(Http2StreamQueueType::QueuedStreams, stream); 693 } 694 695 void Http2Session::ProcessPending() { 696 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 697 698 uint32_t available = RoomForMoreConcurrent(); 699 RefPtr<Http2StreamBase> stream; 700 while (available && (stream = mQueueManager.GetNextStreamFromQueue( 701 Http2StreamQueueType::QueuedStreams))) { 702 LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.", this, 703 stream.get())); 704 MOZ_ASSERT(!stream->CountAsActive()); 705 mQueueManager.AddStreamToQueue(Http2StreamQueueType::ReadyForWrite, stream); 706 SetWriteCallbacks(); 707 available--; 708 } 709 } 710 711 nsresult Http2Session::NetworkRead(nsAHttpSegmentWriter* writer, char* buf, 712 uint32_t count, uint32_t* countWritten) { 713 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 714 715 if (!count) { 716 *countWritten = 0; 717 return NS_OK; 718 } 719 720 nsresult rv = writer->OnWriteSegment(buf, count, countWritten); 721 if (NS_SUCCEEDED(rv) && *countWritten > 0) { 722 mLastReadEpoch = PR_IntervalNow(); 723 } 724 return rv; 725 } 726 727 void Http2Session::SetWriteCallbacks() { 728 if (mConnection && 729 (GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) { 730 (void)mConnection->ResumeSend(); 731 } 732 } 733 734 void Http2Session::RealignOutputQueue() { 735 if (mAttemptingEarlyData) { 736 // We can't realign right now, because we may need what's in there if early 737 // data fails. 738 return; 739 } 740 741 mOutputQueueUsed -= mOutputQueueSent; 742 memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent, 743 mOutputQueueUsed); 744 mOutputQueueSent = 0; 745 } 746 747 void Http2Session::FlushOutputQueue() { 748 if (!mSegmentReader || !mOutputQueueUsed) return; 749 750 nsresult rv; 751 uint32_t countRead; 752 uint32_t avail = mOutputQueueUsed - mOutputQueueSent; 753 754 if (!avail && mAttemptingEarlyData) { 755 // This is kind of a hack, but there are cases where we'll have already 756 // written the data we want whlie doing early data, but we get called again 757 // with a reader, and we need to avoid calling the reader when there's 758 // nothing for it to read. 759 return; 760 } 761 762 rv = mSegmentReader->OnReadSegment( 763 mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead); 764 LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d", 765 this, avail, static_cast<uint32_t>(rv), countRead)); 766 767 // Dont worry about errors on write, we will pick this up as a read error too 768 if (NS_FAILED(rv)) return; 769 770 mOutputQueueSent += countRead; 771 772 if (mAttemptingEarlyData) { 773 return; 774 } 775 776 if (countRead == avail) { 777 mOutputQueueUsed = 0; 778 mOutputQueueSent = 0; 779 return; 780 } 781 782 // If the output queue is close to filling up and we have sent out a good 783 // chunk of data from the beginning then realign it. 784 785 if ((mOutputQueueSent >= kQueueMinimumCleanup) && 786 ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) { 787 RealignOutputQueue(); 788 } 789 } 790 791 void Http2Session::DontReuse() { 792 LOG3(("Http2Session::DontReuse %p\n", this)); 793 if (!OnSocketThread()) { 794 LOG3(("Http2Session %p not on socket thread\n", this)); 795 nsCOMPtr<nsIRunnable> event = NewRunnableMethod( 796 "Http2Session::DontReuse", this, &Http2Session::DontReuse); 797 gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); 798 return; 799 } 800 801 mShouldGoAway = true; 802 if (!mClosed && IsDone()) { 803 Close(NS_OK); 804 } 805 } 806 807 enum SpdyVersion Http2Session::SpdyVersion() { return SpdyVersion::HTTP_2; } 808 809 uint32_t Http2Session::GetWriteQueueSize() { 810 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 811 812 return mQueueManager.GetWriteQueueSize(); 813 } 814 815 void Http2Session::ChangeDownstreamState(enum internalStateType newState) { 816 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 817 818 LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X", this, 819 mDownstreamState, newState)); 820 mDownstreamState = newState; 821 } 822 823 void Http2Session::ResetDownstreamState() { 824 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 825 826 LOG3(("Http2Session::ResetDownstreamState() %p", this)); 827 ChangeDownstreamState(BUFFERING_FRAME_HEADER); 828 829 if (mInputFrameFinal && mInputFrameDataStream) { 830 mInputFrameFinal = false; 831 LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID())); 832 mInputFrameDataStream->SetRecvdFin(true); 833 MaybeDecrementConcurrent(mInputFrameDataStream); 834 } 835 mInputFrameFinal = false; 836 mInputFrameBufferUsed = 0; 837 mInputFrameDataStream = nullptr; 838 } 839 840 // return true if activated (and counted against max) 841 // otherwise return false and queue 842 bool Http2Session::TryToActivate(Http2StreamBase* aStream) { 843 if (aStream->Queued()) { 844 LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this, 845 aStream)); 846 return false; 847 } 848 849 if (!RoomForMoreConcurrent()) { 850 LOG3( 851 ("Http2Session::TryToActivate %p stream=%p no room for more concurrent " 852 "streams\n", 853 this, aStream)); 854 QueueStream(aStream); 855 return false; 856 } 857 858 LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream)); 859 IncrementConcurrent(aStream); 860 861 mCntActivated++; 862 return true; 863 } 864 865 void Http2Session::IncrementConcurrent(Http2StreamBase* stream) { 866 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 867 MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1), 868 "Do not activate pushed streams"); 869 870 nsAHttpTransaction* trans = stream->Transaction(); 871 if (!trans || !trans->IsNullTransaction()) { 872 MOZ_ASSERT(!stream->CountAsActive()); 873 stream->SetCountAsActive(true); 874 ++mConcurrent; 875 876 if (mConcurrent > mConcurrentHighWater) { 877 mConcurrentHighWater = mConcurrent; 878 } 879 LOG3( 880 ("Http2Session::IncrementCounter %p counting stream %p Currently %d " 881 "streams in session, high water mark is %d\n", 882 this, stream, mConcurrent, mConcurrentHighWater)); 883 } 884 } 885 886 // call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header) 887 // dest must have 9 bytes of allocated space 888 template <typename charType> 889 void Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength, 890 uint8_t frameType, uint8_t frameFlags, 891 uint32_t streamID) { 892 MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large"); 893 MOZ_ASSERT(!(streamID & 0x80000000)); 894 MOZ_ASSERT(!frameFlags || (frameType != FRAME_TYPE_PRIORITY && 895 frameType != FRAME_TYPE_RST_STREAM && 896 frameType != FRAME_TYPE_GOAWAY && 897 frameType != FRAME_TYPE_WINDOW_UPDATE)); 898 899 dest[0] = 0x00; 900 NetworkEndian::writeUint16(dest + 1, frameLength); 901 dest[3] = frameType; 902 dest[4] = frameFlags; 903 NetworkEndian::writeUint32(dest + 5, streamID); 904 } 905 906 char* Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded) { 907 // this is an infallible allocation (if an allocation is 908 // needed, which is probably isn't) 909 EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded, 910 mOutputQueueUsed, mOutputQueueSize); 911 return mOutputQueueBuffer.get() + mOutputQueueUsed; 912 } 913 914 template void Http2Session::CreateFrameHeader(char* dest, uint16_t frameLength, 915 uint8_t frameType, 916 uint8_t frameFlags, 917 uint32_t streamID); 918 919 template void Http2Session::CreateFrameHeader(uint8_t* dest, 920 uint16_t frameLength, 921 uint8_t frameType, 922 uint8_t frameFlags, 923 uint32_t streamID); 924 925 void Http2Session::MaybeDecrementConcurrent(Http2StreamBase* aStream) { 926 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 927 LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n", this, 928 aStream->StreamID(), mConcurrent, aStream->CountAsActive())); 929 930 if (!aStream->CountAsActive()) return; 931 932 MOZ_ASSERT(mConcurrent); 933 aStream->SetCountAsActive(false); 934 --mConcurrent; 935 ProcessPending(); 936 } 937 938 // Need to decompress some data in order to keep the compression 939 // context correct, but we really don't care what the result is 940 nsresult Http2Session::UncompressAndDiscard(bool isPush) { 941 nsresult rv; 942 nsAutoCString trash; 943 944 rv = mDecompressor.DecodeHeaderBlock( 945 reinterpret_cast<const uint8_t*>(mDecompressBuffer.BeginReading()), 946 mDecompressBuffer.Length(), trash, isPush); 947 mDecompressBuffer.Truncate(); 948 if (NS_FAILED(rv)) { 949 LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n", this)); 950 mGoAwayReason = COMPRESSION_ERROR; 951 return rv; 952 } 953 return NS_OK; 954 } 955 956 void Http2Session::GeneratePing(bool isAck) { 957 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 958 LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck)); 959 960 char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 8); 961 mOutputQueueUsed += kFrameHeaderBytes + 8; 962 963 if (isAck) { 964 CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0); 965 memcpy(packet + kFrameHeaderBytes, 966 mInputFrameBuffer.get() + kFrameHeaderBytes, 8); 967 } else { 968 CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0); 969 memset(packet + kFrameHeaderBytes, 0, 8); 970 } 971 972 LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8); 973 FlushOutputQueue(); 974 } 975 976 void Http2Session::GenerateSettingsAck() { 977 // need to generate ack of this settings frame 978 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 979 LOG3(("Http2Session::GenerateSettingsAck %p\n", this)); 980 981 char* packet = EnsureOutputBuffer(kFrameHeaderBytes); 982 mOutputQueueUsed += kFrameHeaderBytes; 983 CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0); 984 LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes); 985 FlushOutputQueue(); 986 } 987 988 void Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) { 989 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 990 991 // make sure we don't do this twice for the same stream (at least if we 992 // have a stream entry for it) 993 Http2StreamBase* stream = mStreamIDHash.Get(aID); 994 if (stream) { 995 if (stream->SentReset()) return; 996 stream->SetSentReset(true); 997 } 998 999 LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); 1000 1001 uint32_t frameSize = kFrameHeaderBytes + 4; 1002 char* packet = EnsureOutputBuffer(frameSize); 1003 mOutputQueueUsed += frameSize; 1004 CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID); 1005 1006 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode); 1007 1008 LogIO(this, nullptr, "Generate Reset", packet, frameSize); 1009 FlushOutputQueue(); 1010 } 1011 1012 void Http2Session::GenerateGoAway(uint32_t aStatusCode) { 1013 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1014 LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode)); 1015 1016 mClientGoAwayReason = aStatusCode; 1017 uint32_t frameSize = kFrameHeaderBytes + 8; 1018 char* packet = EnsureOutputBuffer(frameSize); 1019 mOutputQueueUsed += frameSize; 1020 1021 CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0); 1022 1023 // last-good-stream-id are bytes 9-12 reflecting pushes 1024 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID); 1025 1026 // bytes 13-16 are the status code. 1027 NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode); 1028 1029 LogIO(this, nullptr, "Generate GoAway", packet, frameSize); 1030 FlushOutputQueue(); 1031 } 1032 1033 // The Hello is comprised of 1034 // 1] 24 octets of magic, which are designed to 1035 // flush out silent but broken intermediaries 1036 // 2] a settings frame which sets a small flow control window for pushes 1037 // 3] a window update frame which creates a large session flow control window 1038 // 4] 6 priority frames for streams which will never be opened with headers 1039 // these streams (3, 5, 7, 9, b, d) build a dependency tree that all other 1040 // streams will be direct leaves of. 1041 void Http2Session::SendHello() { 1042 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1043 LOG3(("Http2Session::SendHello %p\n", this)); 1044 1045 // sized for magic + 6 settings and a session window update and 6 priority 1046 // frames 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window 1047 // update, 6 priority frames at 14 (9 + 5) each 1048 static const uint32_t maxSettings = 6; 1049 static const uint32_t prioritySize = 1050 kPriorityGroupCount * (kFrameHeaderBytes + 5); 1051 static const uint32_t maxDataLen = 1052 24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize; 1053 char* packet = EnsureOutputBuffer(maxDataLen); 1054 memcpy(packet, kMagicHello, 24); 1055 mOutputQueueUsed += 24; 1056 LogIO(this, nullptr, "Magic Connection Header", packet, 24); 1057 1058 packet = mOutputQueueBuffer.get() + mOutputQueueUsed; 1059 memset(packet, 0, maxDataLen - 24); 1060 1061 // frame header will be filled in after we know how long the frame is 1062 uint8_t numberOfEntries = 0; 1063 1064 // entries need to be listed in order by ID 1065 // 1st entry is bytes 9 to 14 1066 // 2nd entry is bytes 15 to 20 1067 // 3rd entry is bytes 21 to 26 1068 // 4th entry is bytes 27 to 32 1069 // 5th entry is bytes 33 to 38 1070 1071 // Let the other endpoint know about our default HPACK decompress table size 1072 uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer(); 1073 mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize); 1074 NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), 1075 SETTINGS_TYPE_HEADER_TABLE_SIZE); 1076 NetworkEndian::writeUint32( 1077 packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, 1078 maxHpackBufferSize); 1079 numberOfEntries++; 1080 1081 // We don't support HTTP/2 Push. Set SETTINGS_TYPE_ENABLE_PUSH to 0 1082 NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), 1083 SETTINGS_TYPE_ENABLE_PUSH); 1084 // The value portion of the setting pair is already initialized to 0 1085 numberOfEntries++; 1086 1087 // We might also want to set the SETTINGS_TYPE_MAX_CONCURRENT to 0 1088 // to indicate that we don't support any incoming push streams, 1089 // but some websites panic when we do that, so we don't by default. 1090 if (StaticPrefs::network_http_http2_send_push_max_concurrent_frame()) { 1091 NetworkEndian::writeUint16( 1092 packet + kFrameHeaderBytes + (6 * numberOfEntries), 1093 SETTINGS_TYPE_MAX_CONCURRENT); 1094 // The value portion of the setting pair is already initialized to 0 1095 numberOfEntries++; 1096 } 1097 mWaitingForSettingsAck = true; 1098 1099 // Advertise the Push RWIN for the session, and on each new pull stream 1100 // send a window update 1101 NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), 1102 SETTINGS_TYPE_INITIAL_WINDOW); 1103 NetworkEndian::writeUint32( 1104 packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance); 1105 numberOfEntries++; 1106 1107 // Make sure the other endpoint knows that we're sticking to the default max 1108 // frame size 1109 NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), 1110 SETTINGS_TYPE_MAX_FRAME_SIZE); 1111 NetworkEndian::writeUint32( 1112 packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData); 1113 numberOfEntries++; 1114 1115 bool disableRFC7540Priorities = 1116 !StaticPrefs::network_http_http2_enabled_deps() || 1117 !gHttpHandler->CriticalRequestPrioritization(); 1118 1119 // See bug 1909666. Sending this new setting could break some websites. 1120 if (disableRFC7540Priorities && 1121 StaticPrefs::network_http_http2_send_NO_RFC7540_PRI()) { 1122 NetworkEndian::writeUint16( 1123 packet + kFrameHeaderBytes + (6 * numberOfEntries), 1124 SETTINGS_NO_RFC7540_PRIORITIES); 1125 NetworkEndian::writeUint32( 1126 packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, 1127 disableRFC7540Priorities ? 1 : 0); 1128 numberOfEntries++; 1129 } 1130 1131 MOZ_ASSERT(numberOfEntries <= maxSettings); 1132 uint32_t dataLen = 6 * numberOfEntries; 1133 CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0); 1134 mOutputQueueUsed += kFrameHeaderBytes + dataLen; 1135 1136 LogIO(this, nullptr, "Generate Settings", packet, 1137 kFrameHeaderBytes + dataLen); 1138 1139 // now bump the local session window from 64KB 1140 uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin; 1141 if (kDefaultRwin < mInitialRwin) { 1142 // send a window update for the session (Stream 0) for something large 1143 mLocalSessionWindow = mInitialRwin; 1144 1145 packet = mOutputQueueBuffer.get() + mOutputQueueUsed; 1146 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0); 1147 mOutputQueueUsed += kFrameHeaderBytes + 4; 1148 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump); 1149 1150 LOG3(("Session Window increase at start of session %p %u\n", this, 1151 sessionWindowBump)); 1152 LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4); 1153 } 1154 1155 if (!disableRFC7540Priorities) { 1156 mUseH2Deps = true; 1157 MOZ_ASSERT(mNextStreamID == kLeaderGroupID); 1158 CreatePriorityNode(kLeaderGroupID, 0, 200, "leader"); 1159 mNextStreamID += 2; 1160 MOZ_ASSERT(mNextStreamID == kOtherGroupID); 1161 CreatePriorityNode(kOtherGroupID, 0, 100, "other"); 1162 mNextStreamID += 2; 1163 MOZ_ASSERT(mNextStreamID == kBackgroundGroupID); 1164 CreatePriorityNode(kBackgroundGroupID, 0, 0, "background"); 1165 mNextStreamID += 2; 1166 MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID); 1167 CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0, 1168 "speculative"); 1169 mNextStreamID += 2; 1170 MOZ_ASSERT(mNextStreamID == kFollowerGroupID); 1171 CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower"); 1172 mNextStreamID += 2; 1173 MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID); 1174 CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart"); 1175 mNextStreamID += 2; 1176 // Hey, you! YES YOU! If you add/remove any groups here, you almost 1177 // certainly need to change the lookup of the stream/ID hash in 1178 // Http2Session::OnTransportStatus. Yeah, that's right. YOU! 1179 } 1180 1181 FlushOutputQueue(); 1182 } 1183 1184 void Http2Session::SendPriorityFrame(uint32_t streamID, uint32_t dependsOn, 1185 uint8_t weight) { 1186 // If mUseH2Deps is false, that means that we've sent 1187 // SETTINGS_NO_RFC7540_PRIORITIES = 1. Since the server must 1188 // ignore priority frames anyway, we can skip sending it. 1189 if (!UseH2Deps()) { 1190 return; 1191 } 1192 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1193 LOG3( 1194 ("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X " 1195 "weight %d\n", 1196 this, streamID, dependsOn, weight)); 1197 1198 char* packet = CreatePriorityFrame(streamID, dependsOn, weight); 1199 1200 LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5); 1201 FlushOutputQueue(); 1202 } 1203 1204 void Http2Session::SendPriorityUpdateFrame(uint32_t streamID, uint8_t urgency, 1205 bool incremental) { 1206 CreatePriorityUpdateFrame(streamID, urgency, incremental); 1207 FlushOutputQueue(); 1208 } 1209 1210 char* Http2Session::CreatePriorityUpdateFrame(uint32_t streamID, 1211 uint8_t urgency, 1212 bool incremental) { 1213 // https://www.rfc-editor.org/rfc/rfc9218.html#section-7.1 1214 nsPrintfCString priorityFieldValue( 1215 "%s", urgency != 3 ? nsPrintfCString("u=%d", urgency).get() : ""); 1216 size_t payloadSize = 4 + priorityFieldValue.Length(); 1217 char* packet = EnsureOutputBuffer(kFrameHeaderBytes + payloadSize); 1218 // The Stream Identifier field (see Section 5.1.1 of [HTTP/2]) in the 1219 // PRIORITY_UPDATE frame header MUST be zero 1220 CreateFrameHeader(packet, payloadSize, 1221 Http2Session::FRAME_TYPE_PRIORITY_UPDATE, 1222 0, // unused flags 1223 0); // streamID 1224 1225 // Reserved (1), 1226 // Prioritized Stream ID (31), 1227 MOZ_ASSERT(!(streamID & 0x80000000)); 1228 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, streamID & 0x7FFFFFFF); 1229 // Priority Field Value (..), 1230 for (size_t i = 0; i < priorityFieldValue.Length(); ++i) { 1231 packet[kFrameHeaderBytes + 4 + i] = priorityFieldValue[i]; 1232 } 1233 mOutputQueueUsed += kFrameHeaderBytes + payloadSize; 1234 1235 LogIO(this, nullptr, "SendPriorityUpdateFrame", packet, 1236 kFrameHeaderBytes + payloadSize); 1237 return packet; 1238 } 1239 1240 char* Http2Session::CreatePriorityFrame(uint32_t streamID, uint32_t dependsOn, 1241 uint8_t weight) { 1242 MOZ_ASSERT(streamID, "Priority on stream 0"); 1243 char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 5); 1244 CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID); 1245 mOutputQueueUsed += kFrameHeaderBytes + 5; 1246 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, 1247 dependsOn); // depends on 1248 packet[kFrameHeaderBytes + 4] = weight; // weight 1249 return packet; 1250 } 1251 1252 void Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, 1253 uint8_t weight, const char* label) { 1254 char* packet = CreatePriorityFrame(streamID, dependsOn, weight); 1255 1256 LOG3( 1257 ("Http2Session %p generate Priority Frame 0x%X depends on 0x%X " 1258 "weight %d for %s class\n", 1259 this, streamID, dependsOn, weight, label)); 1260 LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5); 1261 } 1262 1263 // perform a bunch of integrity checks on the stream. 1264 // returns true if passed, false (plus LOG and ABORT) if failed. 1265 bool Http2Session::VerifyStream(Http2StreamBase* aStream, 1266 uint32_t aOptionalID = 0) { 1267 // This is annoying, but at least it is O(1) 1268 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1269 1270 #ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1271 // Only do the real verification in early beta builds 1272 return true; 1273 #else // MOZ_DIAGNOSTIC_ASSERT_ENABLED 1274 1275 if (!aStream) return true; 1276 1277 uint32_t test = 0; 1278 1279 do { 1280 if (aStream->StreamID() == kDeadStreamID) break; 1281 1282 test++; 1283 if (aStream->StreamID()) { 1284 Http2StreamBase* idStream = mStreamIDHash.Get(aStream->StreamID()); 1285 1286 test++; 1287 if (idStream != aStream) break; 1288 1289 if (aOptionalID) { 1290 test++; 1291 if (idStream->StreamID() != aOptionalID) break; 1292 } 1293 } 1294 1295 if (aStream->IsTunnel()) { 1296 return true; 1297 } 1298 1299 nsAHttpTransaction* trans = aStream->Transaction(); 1300 1301 test++; 1302 if (!trans) break; 1303 1304 test++; 1305 if (mStreamTransactionHash.GetWeak(trans) != aStream) break; 1306 1307 // tests passed 1308 return true; 1309 } while (false); 1310 1311 LOG3( 1312 ("Http2Session %p VerifyStream Failure %p stream->id=0x%X " 1313 "optionalID=0x%X trans=%p test=%d\n", 1314 this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(), 1315 test)); 1316 1317 MOZ_DIAGNOSTIC_ASSERT(false, "VerifyStream"); 1318 return false; 1319 #endif // DEBUG 1320 } 1321 1322 // static 1323 Http2StreamTunnel* Http2Session::CreateTunnelStreamFromConnInfo( 1324 Http2Session* session, uint64_t bcId, nsHttpConnectionInfo* info, 1325 ExtendedCONNECTType aType) { 1326 MOZ_ASSERT(info); 1327 MOZ_ASSERT(session); 1328 1329 if (aType == ExtendedCONNECTType::WebTransport) { 1330 LOG(("Http2Session creating Http2WebTransportSession")); 1331 MOZ_ASSERT(session->GetExtendedCONNECTSupport() == 1332 ExtendedCONNECTSupport::SUPPORTED); 1333 Http2WebTransportInitialSettings settings; 1334 settings.mInitialMaxStreamsUni = 1335 session->mInitialWebTransportMaxStreamsUnidi; 1336 settings.mInitialMaxStreamsBidi = 1337 session->mInitialWebTransportMaxStreamsBidi; 1338 settings.mInitialMaxStreamDataUni = 1339 session->mInitialWebTransportMaxStreamDataUnidi; 1340 settings.mInitialMaxStreamDataBidi = 1341 session->mInitialWebTransportMaxStreamDataBidi; 1342 settings.mInitialMaxData = session->mInitialWebTransportMaxData; 1343 return new Http2WebTransportSession( 1344 session, nsISupportsPriority::PRIORITY_NORMAL, bcId, info, settings); 1345 } 1346 1347 if (aType == ExtendedCONNECTType::WebSocket) { 1348 LOG(("Http2Session creating Http2StreamWebSocket")); 1349 MOZ_ASSERT(session->GetExtendedCONNECTSupport() == 1350 ExtendedCONNECTSupport::SUPPORTED); 1351 return new Http2StreamWebSocket( 1352 session, nsISupportsPriority::PRIORITY_NORMAL, bcId, info); 1353 } 1354 1355 MOZ_ASSERT(info->UsingHttpProxy() && info->UsingConnect()); 1356 MOZ_ASSERT(aType == ExtendedCONNECTType::Proxy); 1357 LOG(("Http2Session creating Http2StreamTunnel")); 1358 return new Http2StreamTunnel(session, nsISupportsPriority::PRIORITY_NORMAL, 1359 bcId, info); 1360 } 1361 1362 void Http2Session::CleanupStream(Http2StreamBase* aStream, nsresult aResult, 1363 errorType aResetCode) { 1364 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1365 LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n", this, aStream, 1366 aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult))); 1367 if (!aStream) { 1368 return; 1369 } 1370 1371 if (aStream->DeferCleanup(aResult)) { 1372 LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID())); 1373 return; 1374 } 1375 1376 if (!VerifyStream(aStream)) { 1377 LOG3(("Http2Session::CleanupStream failed to verify stream\n")); 1378 return; 1379 } 1380 1381 // don't reset a stream that has recevied a fin or rst 1382 if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() && 1383 !(mInputFrameFinal && 1384 (aStream == mInputFrameDataStream))) { // !(recvdfin with mark pending) 1385 LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n", 1386 aStream->StreamID(), aResetCode)); 1387 GenerateRstStream(aResetCode, aStream->StreamID()); 1388 } 1389 1390 CloseStream(aStream, aResult); 1391 1392 RemoveStreamFromQueues(aStream); 1393 RemoveStreamFromTables(aStream); 1394 1395 mTunnelStreams.RemoveElement(aStream); 1396 1397 if (mNeedsCleanup == aStream) { 1398 mNeedsCleanup = nullptr; 1399 } 1400 1401 if (mShouldGoAway && IsDone()) { 1402 Close(NS_OK); 1403 } 1404 } 1405 1406 void Http2Session::CleanupStream(uint32_t aID, nsresult aResult, 1407 errorType aResetCode) { 1408 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1409 Http2StreamBase* stream = mStreamIDHash.Get(aID); 1410 LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n", this, aID, 1411 stream)); 1412 if (!stream) { 1413 return; 1414 } 1415 CleanupStream(stream, aResult, aResetCode); 1416 } 1417 1418 void Http2Session::RemoveStreamFromQueues(Http2StreamBase* aStream) { 1419 mQueueManager.RemoveStreamFromAllQueue(aStream); 1420 } 1421 1422 void Http2Session::RemoveStreamFromTables(Http2StreamBase* aStream) { 1423 // Remove the stream from the ID hash table 1424 if (aStream->HasRegisteredID()) { 1425 mStreamIDHash.Remove(aStream->StreamID()); 1426 } 1427 // removing from the stream transaction hash will 1428 // delete the Http2StreamBase and drop the reference to 1429 // its transaction 1430 mStreamTransactionHash.Remove(aStream->Transaction()); 1431 } 1432 1433 void Http2Session::CloseStream(Http2StreamBase* aStream, nsresult aResult, 1434 bool aRemoveFromQueue) { 1435 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1436 LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n", this, aStream, 1437 aStream->StreamID(), static_cast<uint32_t>(aResult))); 1438 1439 MaybeDecrementConcurrent(aStream); 1440 1441 // Check if partial frame reader 1442 if (aStream == mInputFrameDataStream) { 1443 LOG3(("Stream had active partial read frame on close")); 1444 ChangeDownstreamState(DISCARDING_DATA_FRAME); 1445 mInputFrameDataStream = nullptr; 1446 } 1447 1448 if (aRemoveFromQueue) { 1449 RemoveStreamFromQueues(aStream); 1450 } 1451 1452 RefPtr<nsHttpConnectionInfo> ci(aStream->ConnectionInfo()); 1453 if ((NS_SUCCEEDED(aResult) || NS_BASE_STREAM_CLOSED == aResult) && ci && 1454 ci->GetIsTrrServiceChannel()) { 1455 // save time of last successful response 1456 mLastTRRResponseTime = TimeStamp::Now(); 1457 } 1458 1459 // Send the stream the close() indication 1460 aStream->CloseStream(aResult); 1461 } 1462 1463 nsresult Http2Session::SetInputFrameDataStream(uint32_t streamID) { 1464 mInputFrameDataStream = mStreamIDHash.Get(streamID); 1465 if (VerifyStream(mInputFrameDataStream, streamID)) return NS_OK; 1466 1467 LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n", 1468 streamID)); 1469 mInputFrameDataStream = nullptr; 1470 return NS_ERROR_UNEXPECTED; 1471 } 1472 1473 nsresult Http2Session::ParsePadding(uint8_t& paddingControlBytes, 1474 uint16_t& paddingLength) { 1475 if (mInputFrameFlags & kFlag_PADDED) { 1476 paddingLength = 1477 *reinterpret_cast<uint8_t*>(&mInputFrameBuffer[kFrameHeaderBytes]); 1478 paddingControlBytes = 1; 1479 } else { 1480 paddingLength = 0; 1481 paddingControlBytes = 0; 1482 } 1483 1484 if (static_cast<uint32_t>(paddingLength + paddingControlBytes) > 1485 mInputFrameDataSize) { 1486 // This is fatal to the session 1487 LOG3( 1488 ("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR " 1489 "paddingLength %d > frame size %d\n", 1490 this, mInputFrameID, paddingLength, mInputFrameDataSize)); 1491 return SessionError(PROTOCOL_ERROR); 1492 } 1493 1494 return NS_OK; 1495 } 1496 1497 nsresult Http2Session::RecvHeaders(Http2Session* self) { 1498 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS || 1499 self->mInputFrameType == FRAME_TYPE_CONTINUATION); 1500 1501 bool isContinuation = self->mExpectedHeaderID != 0; 1502 1503 // If this doesn't have END_HEADERS set on it then require the next 1504 // frame to be HEADERS of the same ID 1505 bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS; 1506 1507 if (endHeadersFlag) { 1508 self->mExpectedHeaderID = 0; 1509 } else { 1510 self->mExpectedHeaderID = self->mInputFrameID; 1511 } 1512 1513 uint32_t priorityLen = 0; 1514 if (self->mInputFrameFlags & kFlag_PRIORITY) { 1515 priorityLen = 5; 1516 } 1517 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); 1518 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1519 1520 // Find out how much padding this frame has, so we can only extract the real 1521 // header data from the frame. 1522 uint16_t paddingLength = 0; 1523 uint8_t paddingControlBytes = 0; 1524 1525 if (!isContinuation) { 1526 self->mDecompressBuffer.Truncate(); 1527 rv = self->ParsePadding(paddingControlBytes, paddingLength); 1528 if (NS_FAILED(rv)) { 1529 return rv; 1530 } 1531 } 1532 1533 LOG3(( 1534 "Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p " 1535 "end_stream=%d end_headers=%d priority_group=%d " 1536 "paddingLength=%d padded=%d\n", 1537 self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream.get(), 1538 self->mInputFrameFlags & kFlag_END_STREAM, 1539 self->mInputFrameFlags & kFlag_END_HEADERS, 1540 self->mInputFrameFlags & kFlag_PRIORITY, paddingLength, 1541 self->mInputFrameFlags & kFlag_PADDED)); 1542 1543 if ((paddingControlBytes + priorityLen + paddingLength) > 1544 self->mInputFrameDataSize) { 1545 // This is fatal to the session 1546 return self->SessionError(PROTOCOL_ERROR); 1547 } 1548 1549 uint32_t frameSize = self->mInputFrameDataSize - paddingControlBytes - 1550 priorityLen - paddingLength; 1551 if (self->mAggregatedHeaderSize + frameSize > 1552 StaticPrefs::network_http_max_response_header_size()) { 1553 LOG(("Http2Session %p header exceeds the limit\n", self)); 1554 return self->SessionError(PROTOCOL_ERROR); 1555 } 1556 if (!self->mInputFrameDataStream) { 1557 // Cannot find stream. We can continue the session, but we need to 1558 // uncompress the header block to maintain the correct compression context 1559 1560 LOG3( 1561 ("Http2Session::RecvHeaders %p lookup mInputFrameID stream " 1562 "0x%X failed. NextStreamID = 0x%X\n", 1563 self, self->mInputFrameID, self->mNextStreamID)); 1564 1565 if (self->mInputFrameID >= self->mNextStreamID) { 1566 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID); 1567 } 1568 1569 self->mDecompressBuffer.Append( 1570 &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + 1571 priorityLen], 1572 frameSize); 1573 1574 if (self->mInputFrameFlags & kFlag_END_HEADERS) { 1575 rv = self->UncompressAndDiscard(false); 1576 if (NS_FAILED(rv)) { 1577 LOG3(("Http2Session::RecvHeaders uncompress failed\n")); 1578 // this is fatal to the session 1579 self->mGoAwayReason = COMPRESSION_ERROR; 1580 return rv; 1581 } 1582 } 1583 1584 self->ResetDownstreamState(); 1585 return NS_OK; 1586 } 1587 1588 // make sure this is either the first headers or a trailer 1589 if (self->mInputFrameDataStream->AllHeadersReceived() && 1590 !(self->mInputFrameFlags & kFlag_END_STREAM)) { 1591 // Any header block after the first that does *not* end the stream is 1592 // illegal. 1593 LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self, 1594 self->mInputFrameID)); 1595 return self->SessionError(PROTOCOL_ERROR); 1596 } 1597 1598 // queue up any compression bytes 1599 self->mDecompressBuffer.Append( 1600 &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + 1601 priorityLen], 1602 frameSize); 1603 1604 self->mInputFrameDataStream->UpdateTransportReadEvents( 1605 self->mInputFrameDataSize); 1606 self->mLastDataReadEpoch = self->mLastReadEpoch; 1607 1608 if (!isContinuation) { 1609 self->mAggregatedHeaderSize = frameSize; 1610 } else { 1611 self->mAggregatedHeaderSize += frameSize; 1612 } 1613 1614 if (!endHeadersFlag) { // more are coming - don't process yet 1615 self->ResetDownstreamState(); 1616 return NS_OK; 1617 } 1618 1619 if (isContinuation) { 1620 glean::spdy::continued_headers.Accumulate(self->mAggregatedHeaderSize); 1621 } 1622 1623 rv = self->ResponseHeadersComplete(); 1624 if (rv == NS_ERROR_ILLEGAL_VALUE) { 1625 LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n", 1626 self, self->mInputFrameID)); 1627 self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR); 1628 self->ResetDownstreamState(); 1629 rv = NS_OK; 1630 } else if (NS_FAILED(rv)) { 1631 // This is fatal to the session. 1632 self->mGoAwayReason = COMPRESSION_ERROR; 1633 } 1634 return rv; 1635 } 1636 1637 // ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream 1638 // should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were 1639 // fine, and any other error is fatal to the session. 1640 nsresult Http2Session::ResponseHeadersComplete() { 1641 if (!mInputFrameDataStream) { 1642 return NS_ERROR_UNEXPECTED; 1643 } 1644 1645 LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d", this, 1646 mInputFrameDataStream->StreamID(), mInputFrameFinal)); 1647 1648 // Anything prior to AllHeadersReceived() => true is actual headers. After 1649 // that, we need to handle them as trailers instead (which are special-cased 1650 // so we don't have to use the nasty chunked parser for all h2, just in case). 1651 if (mInputFrameDataStream->AllHeadersReceived()) { 1652 LOG3(("Http2Session::ResponseHeadersComplete processing trailers")); 1653 MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM); 1654 nsresult rv = mInputFrameDataStream->ConvertResponseTrailers( 1655 &mDecompressor, mDecompressBuffer); 1656 if (NS_FAILED(rv)) { 1657 LOG3(( 1658 "Http2Session::ResponseHeadersComplete trailer conversion failed\n")); 1659 return rv; 1660 } 1661 mFlatHTTPResponseHeadersOut = 0; 1662 mFlatHTTPResponseHeaders.Truncate(); 1663 if (mInputFrameFinal) { 1664 // need to process the fin 1665 ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); 1666 } else { 1667 ResetDownstreamState(); 1668 } 1669 1670 return NS_OK; 1671 } 1672 1673 // if this turns out to be a 1xx response code we have to 1674 // undo the headers received bit that we are setting here. 1675 bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived(); 1676 mInputFrameDataStream->SetAllHeadersReceived(); 1677 1678 // The stream needs to see flattened http headers 1679 // Uncompressed http/2 format headers currently live in 1680 // Http2StreamBase::mDecompressBuffer - convert that to HTTP format in 1681 // mFlatHTTPResponseHeaders via ConvertHeaders() 1682 1683 nsresult rv; 1684 int32_t httpResponseCode; // out param to ConvertResponseHeaders 1685 mFlatHTTPResponseHeadersOut = 0; 1686 rv = mInputFrameDataStream->ConvertResponseHeaders( 1687 &mDecompressor, mDecompressBuffer, mFlatHTTPResponseHeaders, 1688 httpResponseCode); 1689 if (rv == NS_ERROR_NET_RESET) { 1690 LOG( 1691 ("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders " 1692 "reset\n", 1693 this)); 1694 // This means the stream found connection-oriented auth. Treat this like we 1695 // got a reset with HTTP_1_1_REQUIRED. 1696 mInputFrameDataStream->DisableSpdy(); 1697 CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR); 1698 ResetDownstreamState(); 1699 return NS_OK; 1700 } 1701 if (NS_FAILED(rv)) { 1702 return rv; 1703 } 1704 1705 // allow more headers in the case of 1xx 1706 if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) { 1707 mInputFrameDataStream->UnsetAllHeadersReceived(); 1708 } 1709 1710 ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); 1711 return NS_OK; 1712 } 1713 1714 nsresult Http2Session::RecvPriority(Http2Session* self) { 1715 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY); 1716 1717 if (self->mInputFrameDataSize != 5) { 1718 LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n", self, 1719 self->mInputFrameDataSize)); 1720 return self->SessionError(PROTOCOL_ERROR); 1721 } 1722 1723 if (!self->mInputFrameID) { 1724 LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self)); 1725 return self->SessionError(PROTOCOL_ERROR); 1726 } 1727 1728 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); 1729 if (NS_FAILED(rv)) return rv; 1730 1731 uint32_t newPriorityDependency = NetworkEndian::readUint32( 1732 self->mInputFrameBuffer.get() + kFrameHeaderBytes); 1733 bool exclusive = !!(newPriorityDependency & 0x80000000); 1734 newPriorityDependency &= 0x7fffffff; 1735 uint8_t newPriorityWeight = 1736 *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4); 1737 1738 // undefined what it means when the server sends a priority frame. ignore it. 1739 LOG3( 1740 ("Http2Session::RecvPriority %p 0x%X received dependency=0x%X " 1741 "weight=%u exclusive=%d", 1742 self->mInputFrameDataStream.get(), self->mInputFrameID, 1743 newPriorityDependency, newPriorityWeight, exclusive)); 1744 1745 self->ResetDownstreamState(); 1746 return NS_OK; 1747 } 1748 1749 nsresult Http2Session::RecvRstStream(Http2Session* self) { 1750 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM); 1751 1752 if (self->mInputFrameDataSize != 4) { 1753 LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d", 1754 self, self->mInputFrameDataSize)); 1755 return self->SessionError(PROTOCOL_ERROR); 1756 } 1757 1758 if (!self->mInputFrameID) { 1759 LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self)); 1760 return self->SessionError(PROTOCOL_ERROR); 1761 } 1762 1763 self->mDownstreamRstReason = NetworkEndian::readUint32( 1764 self->mInputFrameBuffer.get() + kFrameHeaderBytes); 1765 1766 LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n", 1767 self, self->mDownstreamRstReason, self->mInputFrameID)); 1768 1769 DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID); 1770 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1771 if (!self->mInputFrameDataStream) { 1772 // if we can't find the stream just ignore it (4.2 closed) 1773 self->ResetDownstreamState(); 1774 return NS_OK; 1775 } 1776 1777 self->mInputFrameDataStream->SetRecvdReset(true); 1778 self->MaybeDecrementConcurrent(self->mInputFrameDataStream); 1779 self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); 1780 return NS_OK; 1781 } 1782 1783 nsresult Http2Session::RecvSettings(Http2Session* self) { 1784 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS); 1785 1786 if (self->mInputFrameID) { 1787 LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", self, 1788 self->mInputFrameID)); 1789 return self->SessionError(PROTOCOL_ERROR); 1790 } 1791 1792 if (self->mInputFrameDataSize % 6) { 1793 // Number of Settings is determined by dividing by each 6 byte setting 1794 // entry. So the payload must be a multiple of 6. 1795 LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", self, 1796 self->mInputFrameDataSize)); 1797 return self->SessionError(PROTOCOL_ERROR); 1798 } 1799 1800 self->mReceivedSettings = true; 1801 1802 uint32_t numEntries = self->mInputFrameDataSize / 6; 1803 LOG3( 1804 ("Http2Session::RecvSettings %p SETTINGS Control Frame " 1805 "with %d entries ack=%X", 1806 self, numEntries, self->mInputFrameFlags & kFlag_ACK)); 1807 1808 if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) { 1809 LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n", 1810 self)); 1811 return self->SessionError(PROTOCOL_ERROR); 1812 } 1813 1814 for (uint32_t index = 0; index < numEntries; ++index) { 1815 uint8_t* setting = 1816 reinterpret_cast<uint8_t*>(self->mInputFrameBuffer.get()) + 1817 kFrameHeaderBytes + index * 6; 1818 1819 uint16_t id = NetworkEndian::readUint16(setting); 1820 uint32_t value = NetworkEndian::readUint32(setting + 2); 1821 LOG3(("Settings ID %u, Value %u", id, value)); 1822 1823 switch (id) { 1824 case SETTINGS_TYPE_HEADER_TABLE_SIZE: 1825 LOG3(("Compression header table setting received: %d\n", value)); 1826 self->mCompressor.SetMaxBufferSize(value); 1827 break; 1828 1829 case SETTINGS_TYPE_ENABLE_PUSH: 1830 LOG3(("Client received an ENABLE Push SETTING. Odd.\n")); 1831 // nop 1832 break; 1833 1834 case SETTINGS_TYPE_MAX_CONCURRENT: 1835 self->mMaxConcurrent = value; 1836 glean::spdy::settings_max_streams.AccumulateSingleSample(value); 1837 self->ProcessPending(); 1838 break; 1839 1840 case SETTINGS_TYPE_INITIAL_WINDOW: { 1841 int32_t delta = value - self->mServerInitialStreamWindow; 1842 self->mServerInitialStreamWindow = value; 1843 1844 // SETTINGS only adjusts stream windows. Leave the session window alone. 1845 // We need to add the delta to all open streams (delta can be negative) 1846 for (const auto& stream : self->mStreamTransactionHash.Values()) { 1847 stream->UpdateServerReceiveWindow(delta); 1848 } 1849 } break; 1850 1851 case SETTINGS_TYPE_MAX_FRAME_SIZE: { 1852 if ((value < kMaxFrameData) || (value >= 0x01000000)) { 1853 LOG3(("Received invalid max frame size 0x%X", value)); 1854 return self->SessionError(PROTOCOL_ERROR); 1855 } 1856 // We stick to the default for simplicity's sake, so nothing to change 1857 } break; 1858 1859 case SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL: { 1860 if (value == 1) { 1861 LOG3(("Enabling extended CONNECT")); 1862 self->mPeerAllowsExtendedCONNECT = true; 1863 } else if (value > 1) { 1864 LOG3(("Peer sent invalid value for ENABLE_CONNECT_PROTOCOL %d", 1865 value)); 1866 return self->SessionError(PROTOCOL_ERROR); 1867 } else if (self->mPeerAllowsExtendedCONNECT) { 1868 LOG3(("Peer tried to re-disable extended CONNECT")); 1869 return self->SessionError(PROTOCOL_ERROR); 1870 } 1871 self->mHasTransactionWaitingForExtendedCONNECT = true; 1872 } break; 1873 1874 case SETTINGS_WEBTRANSPORT_MAX_SESSIONS: { 1875 // If the value is 0, the server doesn't want to accept webtransport 1876 // session. An error will ultimately be returned when the transaction 1877 // attempts to create a webtransport session. 1878 LOG3(("SETTINGS_WEBTRANSPORT_MAX_SESSIONS set to %u", value)); 1879 self->mWebTransportMaxSessions = value; 1880 } break; 1881 1882 case SETTINGS_WEBTRANSPORT_INITIAL_MAX_DATA: { 1883 if (!self->mPeerAllowsExtendedCONNECT) { 1884 return self->SessionError(PROTOCOL_ERROR); 1885 } 1886 self->mInitialWebTransportMaxData = value; 1887 } break; 1888 1889 case SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAM_DATA_UNI: { 1890 if (!self->mPeerAllowsExtendedCONNECT) { 1891 return self->SessionError(PROTOCOL_ERROR); 1892 } 1893 self->mInitialWebTransportMaxStreamDataUnidi = value; 1894 } break; 1895 1896 case SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAM_DATA_BIDI: { 1897 if (!self->mPeerAllowsExtendedCONNECT) { 1898 return self->SessionError(PROTOCOL_ERROR); 1899 } 1900 self->mInitialWebTransportMaxStreamDataBidi = value; 1901 } break; 1902 1903 case SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI: { 1904 if (!self->mPeerAllowsExtendedCONNECT) { 1905 return self->SessionError(PROTOCOL_ERROR); 1906 } 1907 self->mInitialWebTransportMaxStreamsUnidi = value; 1908 } break; 1909 1910 case SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI: { 1911 if (!self->mPeerAllowsExtendedCONNECT) { 1912 return self->SessionError(PROTOCOL_ERROR); 1913 } 1914 self->mInitialWebTransportMaxStreamsBidi = value; 1915 } break; 1916 1917 default: 1918 LOG3(("Received an unknown SETTING id %d. Ignoring.", id)); 1919 break; 1920 } 1921 } 1922 1923 self->ResetDownstreamState(); 1924 1925 if (!(self->mInputFrameFlags & kFlag_ACK)) { 1926 self->GenerateSettingsAck(); 1927 } else if (self->mWaitingForSettingsAck) { 1928 self->mGoAwayOnPush = true; 1929 } 1930 1931 if (self->mHasTransactionWaitingForExtendedCONNECT) { 1932 // trigger a queued websockets transaction -- enabled or not 1933 LOG3(("Http2Sesssion::RecvSettings triggering queued transactions")); 1934 RefPtr<nsHttpConnectionInfo> ci; 1935 self->GetConnectionInfo(getter_AddRefs(ci)); 1936 gHttpHandler->ConnMgr()->ProcessPendingQ(ci); 1937 self->mHasTransactionWaitingForExtendedCONNECT = false; 1938 } 1939 1940 return NS_OK; 1941 } 1942 1943 nsresult Http2Session::RecvPushPromise(Http2Session* self) { 1944 return NS_ERROR_NOT_IMPLEMENTED; 1945 } 1946 1947 nsresult Http2Session::RecvPing(Http2Session* self) { 1948 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING); 1949 1950 LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self, 1951 self->mInputFrameFlags)); 1952 1953 if (self->mInputFrameDataSize != 8) { 1954 LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d", self, 1955 self->mInputFrameDataSize)); 1956 return self->SessionError(FRAME_SIZE_ERROR); 1957 } 1958 1959 if (self->mInputFrameID) { 1960 LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n", self, 1961 self->mInputFrameID)); 1962 return self->SessionError(PROTOCOL_ERROR); 1963 } 1964 1965 if (self->mInputFrameFlags & kFlag_ACK) { 1966 // presumably a reply to our timeout ping.. don't reply to it 1967 self->mPingSentEpoch = 0; 1968 // We need to reset mPreviousUsed. If we don't, the next time 1969 // Http2Session::SendPing is called, it will have no effect. 1970 self->mPreviousUsed = false; 1971 } else { 1972 // reply with a ack'd ping 1973 self->GeneratePing(true); 1974 } 1975 1976 self->ResetDownstreamState(); 1977 return NS_OK; 1978 } 1979 1980 nsresult Http2Session::RecvGoAway(Http2Session* self) { 1981 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1982 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY); 1983 1984 if (self->mInputFrameDataSize < 8) { 1985 // data > 8 is an opaque token that we can't interpret. NSPR Logs will 1986 // have the hex of all packets so there is no point in separately logging. 1987 LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d", 1988 self, self->mInputFrameDataSize)); 1989 return self->SessionError(PROTOCOL_ERROR); 1990 } 1991 1992 if (self->mInputFrameID) { 1993 LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n", 1994 self, self->mInputFrameID)); 1995 return self->SessionError(PROTOCOL_ERROR); 1996 } 1997 1998 self->mConnection->SetCloseReason(ConnectionCloseReason::GO_AWAY); 1999 self->mShouldGoAway = true; 2000 self->mGoAwayID = NetworkEndian::readUint32(self->mInputFrameBuffer.get() + 2001 kFrameHeaderBytes); 2002 self->mGoAwayID &= 0x7fffffff; 2003 self->mCleanShutdown = true; 2004 self->mPeerGoAwayReason = NetworkEndian::readUint32( 2005 self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4); 2006 2007 // Find streams greater than the last-good ID and mark them for deletion 2008 // in the mGoAwayStreamsToRestart queue. The underlying transaction can be 2009 // restarted. 2010 for (const auto& stream : self->mStreamTransactionHash.Values()) { 2011 // these streams were not processed by the server and can be restarted. 2012 // Do that after the enumerator completes to avoid the risk of 2013 // a restart event re-entrantly modifying this hash. Be sure not to restart 2014 // a pushed (even numbered) stream 2015 if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) || 2016 !stream->HasRegisteredID()) { 2017 self->mGoAwayStreamsToRestart.Push(stream); 2018 } 2019 } 2020 2021 // Process the streams marked for deletion and restart. 2022 size_t size = self->mGoAwayStreamsToRestart.GetSize(); 2023 for (size_t count = 0; count < size; ++count) { 2024 Http2StreamBase* stream = 2025 static_cast<Http2StreamBase*>(self->mGoAwayStreamsToRestart.PopFront()); 2026 2027 if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) { 2028 stream->DisableSpdy(); 2029 } 2030 self->CloseStream(stream, NS_ERROR_NET_RESET); 2031 self->RemoveStreamFromTables(stream); 2032 } 2033 2034 // Queued streams can also be deleted from this session and restarted 2035 // in another one. (they were never sent on the network so they implicitly 2036 // are not covered by the last-good id. 2037 RefPtr<Http2StreamBase> queuedStream; 2038 while ((queuedStream = self->mQueueManager.GetNextStreamFromQueue( 2039 Http2StreamQueueType::QueuedStreams))) { 2040 if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) { 2041 queuedStream->DisableSpdy(); 2042 } 2043 self->CloseStream(queuedStream, NS_ERROR_NET_RESET, false); 2044 self->RemoveStreamFromTables(queuedStream); 2045 } 2046 2047 LOG3( 2048 ("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X " 2049 "live streams=%d\n", 2050 self, self->mGoAwayID, self->mPeerGoAwayReason, 2051 self->mStreamTransactionHash.Count())); 2052 2053 self->ResetDownstreamState(); 2054 return NS_OK; 2055 } 2056 2057 nsresult Http2Session::RecvWindowUpdate(Http2Session* self) { 2058 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2059 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE); 2060 2061 if (self->mInputFrameDataSize != 4) { 2062 LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n", 2063 self, self->mInputFrameDataSize)); 2064 return self->SessionError(PROTOCOL_ERROR); 2065 } 2066 2067 uint32_t delta = NetworkEndian::readUint32(self->mInputFrameBuffer.get() + 2068 kFrameHeaderBytes); 2069 delta &= 0x7fffffff; 2070 2071 LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", self, delta, 2072 self->mInputFrameID)); 2073 2074 if (self->mInputFrameID) { // stream window 2075 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); 2076 if (NS_FAILED(rv)) return rv; 2077 2078 RefPtr<Http2StreamBase> stream = self->mInputFrameDataStream.get(); 2079 if (!stream) { 2080 LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n", 2081 self, self->mInputFrameID)); 2082 // only reset the session if the ID is one we haven't ever opened 2083 if (self->mInputFrameID >= self->mNextStreamID) { 2084 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID); 2085 } 2086 self->ResetDownstreamState(); 2087 return NS_OK; 2088 } 2089 2090 if (delta == 0) { 2091 LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update", 2092 self)); 2093 self->CleanupStream(stream, NS_ERROR_ILLEGAL_VALUE, PROTOCOL_ERROR); 2094 self->ResetDownstreamState(); 2095 return NS_OK; 2096 } 2097 2098 int64_t oldRemoteWindow = stream->ServerReceiveWindow(); 2099 stream->UpdateServerReceiveWindow(delta); 2100 if (stream->ServerReceiveWindow() >= 0x80000000) { 2101 // a window cannot reach 2^31 and be in compliance. Our calculations 2102 // are 64 bit safe though. 2103 LOG3( 2104 ("Http2Session::RecvWindowUpdate %p stream window " 2105 "exceeds 2^31 - 1\n", 2106 self)); 2107 self->CleanupStream(stream, NS_ERROR_ILLEGAL_VALUE, FLOW_CONTROL_ERROR); 2108 self->ResetDownstreamState(); 2109 return NS_OK; 2110 } 2111 2112 LOG3( 2113 ("Http2Session::RecvWindowUpdate %p stream 0x%X window " 2114 "%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n", 2115 self, self->mInputFrameID, oldRemoteWindow, delta, 2116 oldRemoteWindow + delta)); 2117 2118 } else { // session window update 2119 if (delta == 0) { 2120 LOG3( 2121 ("Http2Session::RecvWindowUpdate %p received 0 session window update", 2122 self)); 2123 return self->SessionError(PROTOCOL_ERROR); 2124 } 2125 2126 int64_t oldRemoteWindow = self->mServerSessionWindow; 2127 self->mServerSessionWindow += delta; 2128 2129 if (self->mServerSessionWindow >= 0x80000000) { 2130 // a window cannot reach 2^31 and be in compliance. Our calculations 2131 // are 64 bit safe though. 2132 LOG3( 2133 ("Http2Session::RecvWindowUpdate %p session window " 2134 "exceeds 2^31 - 1\n", 2135 self)); 2136 return self->SessionError(FLOW_CONTROL_ERROR); 2137 } 2138 2139 if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) { 2140 LOG3( 2141 ("Http2Session::RecvWindowUpdate %p restart session window\n", self)); 2142 for (const auto& stream : self->mStreamTransactionHash.Values()) { 2143 MOZ_ASSERT(self->mServerSessionWindow > 0); 2144 2145 if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) { 2146 continue; 2147 } 2148 2149 self->mQueueManager.AddStreamToQueue( 2150 Http2StreamQueueType::ReadyForWrite, stream); 2151 self->SetWriteCallbacks(); 2152 } 2153 } 2154 LOG3( 2155 ("Http2Session::RecvWindowUpdate %p session window " 2156 "%" PRId64 " increased by %d now %" PRId64 ".\n", 2157 self, oldRemoteWindow, delta, oldRemoteWindow + delta)); 2158 } 2159 2160 self->ResetDownstreamState(); 2161 return NS_OK; 2162 } 2163 2164 nsresult Http2Session::RecvContinuation(Http2Session* self) { 2165 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION); 2166 MOZ_ASSERT(self->mInputFrameID); 2167 MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID); 2168 MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID)); 2169 2170 LOG3( 2171 ("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X " 2172 "promise id 0x%X header id 0x%X\n", 2173 self, self->mInputFrameFlags, self->mInputFrameID, 2174 self->mExpectedPushPromiseID, self->mExpectedHeaderID)); 2175 2176 DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID); 2177 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2178 2179 if (!self->mInputFrameDataStream) { 2180 LOG3(("Http2Session::RecvContination stream ID 0x%X not found.", 2181 self->mInputFrameID)); 2182 return self->SessionError(PROTOCOL_ERROR); 2183 } 2184 2185 // continued headers 2186 if (self->mExpectedHeaderID) { 2187 self->mInputFrameFlags &= ~kFlag_PRIORITY; 2188 return RecvHeaders(self); 2189 } 2190 2191 // continued push promise 2192 if (self->mInputFrameFlags & kFlag_END_HEADERS) { 2193 self->mInputFrameFlags &= ~kFlag_END_HEADERS; 2194 self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE; 2195 } 2196 return RecvPushPromise(self); 2197 } 2198 2199 class UpdateAltSvcEvent : public Runnable { 2200 public: 2201 UpdateAltSvcEvent(const nsCString& header, const nsCString& aOrigin, 2202 nsHttpConnectionInfo* aCI) 2203 : Runnable("net::UpdateAltSvcEvent"), 2204 mHeader(header), 2205 mOrigin(aOrigin), 2206 mCI(aCI) {} 2207 2208 NS_IMETHOD Run() override { 2209 MOZ_ASSERT(NS_IsMainThread()); 2210 2211 nsCString originScheme; 2212 nsCString originHost; 2213 int32_t originPort = -1; 2214 2215 nsCOMPtr<nsIURI> uri; 2216 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) { 2217 LOG(("UpdateAltSvcEvent origin does not parse %s\n", mOrigin.get())); 2218 return NS_OK; 2219 } 2220 uri->GetScheme(originScheme); 2221 uri->GetHost(originHost); 2222 uri->GetPort(&originPort); 2223 2224 if (XRE_IsSocketProcess()) { 2225 AltServiceChild::ProcessHeader( 2226 mHeader, originScheme, originHost, originPort, mCI->GetUsername(), 2227 mCI->GetPrivate(), nullptr, mCI->ProxyInfo(), 0, 2228 mCI->GetOriginAttributes(), mCI); 2229 return NS_OK; 2230 } 2231 2232 AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort, 2233 mCI->GetUsername(), mCI->GetPrivate(), nullptr, 2234 mCI->ProxyInfo(), 0, 2235 mCI->GetOriginAttributes(), mCI); 2236 return NS_OK; 2237 } 2238 2239 private: 2240 nsCString mHeader; 2241 nsCString mOrigin; 2242 RefPtr<nsHttpConnectionInfo> mCI; 2243 nsCOMPtr<nsIInterfaceRequestor> mCallbacks; 2244 }; 2245 2246 // defined as an http2 extension - alt-svc 2247 // defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft 2248 // -06 sec 4 as this is an extension, never generate protocol error - just 2249 // ignore problems 2250 nsresult Http2Session::RecvAltSvc(Http2Session* self) { 2251 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC); 2252 LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self, 2253 self->mInputFrameFlags, self->mInputFrameID)); 2254 2255 if (self->mInputFrameDataSize < 2) { 2256 LOG3(("Http2Session::RecvAltSvc %p frame too small", self)); 2257 self->ResetDownstreamState(); 2258 return NS_OK; 2259 } 2260 2261 uint16_t originLen = NetworkEndian::readUint16(self->mInputFrameBuffer.get() + 2262 kFrameHeaderBytes); 2263 if (originLen + 2U > self->mInputFrameDataSize) { 2264 LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self)); 2265 self->ResetDownstreamState(); 2266 return NS_OK; 2267 } 2268 2269 if (!gHttpHandler->AllowAltSvc()) { 2270 LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self)); 2271 self->ResetDownstreamState(); 2272 return NS_OK; 2273 } 2274 2275 uint16_t altSvcFieldValueLen = 2276 static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen; 2277 LOG3(( 2278 "Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n", 2279 self, originLen, altSvcFieldValueLen)); 2280 2281 if (self->mInputFrameDataSize > 2000) { 2282 LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly", 2283 self)); 2284 self->ResetDownstreamState(); 2285 return NS_OK; 2286 } 2287 2288 nsAutoCString origin; 2289 bool impliedOrigin = true; 2290 if (originLen) { 2291 origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2, 2292 originLen); 2293 impliedOrigin = false; 2294 } 2295 2296 nsAutoCString altSvcFieldValue; 2297 if (altSvcFieldValueLen) { 2298 altSvcFieldValue.Assign( 2299 self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen, 2300 altSvcFieldValueLen); 2301 } 2302 2303 if (altSvcFieldValue.IsEmpty() || 2304 !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) { 2305 LOG( 2306 ("Http2Session %p Alt-Svc Response Header seems unreasonable - " 2307 "skipping\n", 2308 self)); 2309 self->ResetDownstreamState(); 2310 return NS_OK; 2311 } 2312 2313 if (self->mInputFrameID & 1) { 2314 // pulled streams apply to the origin of the pulled stream. 2315 // If the origin field is filled in the frame, the frame should be ignored 2316 if (!origin.IsEmpty()) { 2317 LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n", 2318 self)); 2319 self->ResetDownstreamState(); 2320 return NS_OK; 2321 } 2322 2323 if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) || 2324 !self->mInputFrameDataStream || 2325 !self->mInputFrameDataStream->Transaction() || 2326 !self->mInputFrameDataStream->Transaction()->RequestHead()) { 2327 LOG3( 2328 ("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", 2329 self)); 2330 self->ResetDownstreamState(); 2331 return NS_OK; 2332 } 2333 2334 self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin); 2335 } else if (!self->mInputFrameID) { 2336 // ID 0 streams must supply their own origin 2337 if (origin.IsEmpty()) { 2338 LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self)); 2339 self->ResetDownstreamState(); 2340 return NS_OK; 2341 } 2342 } else { 2343 // handling of push streams is not defined. Let's ignore it 2344 LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n", 2345 self)); 2346 self->ResetDownstreamState(); 2347 return NS_OK; 2348 } 2349 2350 RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo()); 2351 if (!self->mConnection || !ci) { 2352 LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self, 2353 self->mInputFrameID)); 2354 self->ResetDownstreamState(); 2355 return NS_OK; 2356 } 2357 2358 if (!impliedOrigin) { 2359 bool okToReroute = true; 2360 nsCOMPtr<nsITLSSocketControl> ssl; 2361 self->mConnection->GetTLSSocketControl(getter_AddRefs(ssl)); 2362 if (!ssl) { 2363 okToReroute = false; 2364 } 2365 2366 // a little off main thread origin parser. This is a non critical function 2367 // because any alternate route created has to be verified anyhow 2368 nsAutoCString specifiedOriginHost; 2369 if (StringBeginsWith(origin, "https://"_ns, 2370 nsCaseInsensitiveCStringComparator)) { 2371 specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8); 2372 } else if (StringBeginsWith(origin, "http://"_ns, 2373 nsCaseInsensitiveCStringComparator)) { 2374 specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7); 2375 } 2376 2377 int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0); 2378 if (colonOffset != kNotFound) { 2379 specifiedOriginHost.Truncate(colonOffset); 2380 } 2381 2382 if (okToReroute) { 2383 ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute); 2384 } 2385 2386 if (!okToReroute) { 2387 LOG3( 2388 ("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin " 2389 "%s", 2390 self, origin.BeginReading())); 2391 self->ResetDownstreamState(); 2392 return NS_OK; 2393 } 2394 } 2395 2396 RefPtr<UpdateAltSvcEvent> event = 2397 new UpdateAltSvcEvent(altSvcFieldValue, origin, ci); 2398 NS_DispatchToMainThread(event); 2399 self->ResetDownstreamState(); 2400 return NS_OK; 2401 } 2402 2403 void Http2Session::Received421(nsHttpConnectionInfo* ci) { 2404 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2405 LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated)); 2406 if (!mOriginFrameActivated || !ci) { 2407 return; 2408 } 2409 2410 nsAutoCString key(ci->GetOrigin()); 2411 key.Append(':'); 2412 key.AppendInt(ci->OriginPort()); 2413 mOriginFrame.Remove(key); 2414 LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get())); 2415 } 2416 2417 nsresult Http2Session::RecvUnused(Http2Session* self) { 2418 LOG3(("Http2Session %p unknown frame type %x ignored\n", self, 2419 self->mInputFrameType)); 2420 self->ResetDownstreamState(); 2421 return NS_OK; 2422 } 2423 2424 // defined as an http2 extension - origin 2425 // defines receipt of frame type 0x0b.. 2426 // http://httpwg.org/http-extensions/origin-frame.html as this is an extension, 2427 // never generate protocol error - just ignore problems 2428 nsresult Http2Session::RecvOrigin(Http2Session* self) { 2429 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2430 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN); 2431 LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self, 2432 self->mInputFrameFlags, self->mInputFrameID)); 2433 2434 if (self->mInputFrameFlags & 0x0F) { 2435 LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self)); 2436 self->ResetDownstreamState(); 2437 return NS_OK; 2438 } 2439 2440 if (self->mInputFrameID) { 2441 LOG3(("Http2Session::RecvOrigin %p not stream 0", self)); 2442 self->ResetDownstreamState(); 2443 return NS_OK; 2444 } 2445 2446 if (self->ConnectionInfo()->UsingProxy()) { 2447 LOG3(("Http2Session::RecvOrigin %p must not use proxy", self)); 2448 self->ResetDownstreamState(); 2449 return NS_OK; 2450 } 2451 2452 uint32_t offset = 0; 2453 self->mOriginFrameActivated = true; 2454 2455 while (self->mInputFrameDataSize >= (offset + 2U)) { 2456 uint16_t originLen = NetworkEndian::readUint16( 2457 self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset); 2458 LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n", 2459 self, originLen)); 2460 if (originLen + 2U + offset > self->mInputFrameDataSize) { 2461 LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self)); 2462 break; 2463 } 2464 2465 nsAutoCString originString; 2466 nsCOMPtr<nsIURI> originURL; 2467 originString.Assign( 2468 self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2, 2469 originLen); 2470 offset += originLen + 2; 2471 if (NS_FAILED(MakeOriginURL(originString, originURL))) { 2472 LOG3( 2473 ("Http2Session::RecvOrigin %p origin frame string %s failed to " 2474 "parse\n", 2475 self, originString.get())); 2476 continue; 2477 } 2478 2479 LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n", 2480 self, originString.get())); 2481 if (!originURL->SchemeIs("https")) { 2482 LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self)); 2483 continue; 2484 } 2485 2486 int32_t port = -1; 2487 originURL->GetPort(&port); 2488 if (port == -1) { 2489 port = 443; 2490 } 2491 // dont use ->GetHostPort because we want explicit 443 2492 nsAutoCString host; 2493 originURL->GetHost(host); 2494 nsAutoCString key(host); 2495 key.Append(':'); 2496 key.AppendInt(port); 2497 self->mOriginFrame.WithEntryHandle(key, [&](auto&& entry) { 2498 if (!entry) { 2499 entry.Insert(true); 2500 RefPtr<HttpConnectionBase> conn(self->HttpConnection()); 2501 MOZ_ASSERT(conn.get()); 2502 gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port); 2503 } else { 2504 LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n", 2505 self)); 2506 } 2507 }); 2508 } 2509 2510 self->ResetDownstreamState(); 2511 return NS_OK; 2512 } 2513 2514 nsresult Http2Session::RecvPriorityUpdate(Http2Session* self) { 2515 // https://www.rfc-editor.org/rfc/rfc9218.html#section-7.1-9 2516 // Servers MUST NOT send PRIORITY_UPDATE frames. If a client receives a 2517 // PRIORITY_UPDATE frame, it MUST respond with a connection error of 2518 // type PROTOCOL_ERROR. 2519 return self->SessionError(PROTOCOL_ERROR); 2520 } 2521 2522 //----------------------------------------------------------------------------- 2523 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller 2524 // of these methods 2525 //----------------------------------------------------------------------------- 2526 2527 void Http2Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus, 2528 int64_t aProgress) { 2529 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2530 2531 switch (aStatus) { 2532 // These should appear only once, deliver to the first 2533 // transaction on the session. 2534 case NS_NET_STATUS_RESOLVING_HOST: 2535 case NS_NET_STATUS_RESOLVED_HOST: 2536 case NS_NET_STATUS_CONNECTING_TO: 2537 case NS_NET_STATUS_CONNECTED_TO: 2538 case NS_NET_STATUS_TLS_HANDSHAKE_STARTING: 2539 case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: { 2540 if (!mFirstHttpTransaction) { 2541 // if we still do not have a HttpTransaction store timings info in 2542 // a HttpConnection. 2543 // If some error occur it can happen that we do not have a connection. 2544 if (mConnection) { 2545 RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection(); 2546 conn->SetEvent(aStatus); 2547 } 2548 } else { 2549 mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus, 2550 aProgress); 2551 } 2552 2553 if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) { 2554 mFirstHttpTransaction = nullptr; 2555 mTlsHandshakeFinished = true; 2556 } 2557 break; 2558 } 2559 2560 default: 2561 // The other transport events are ignored here because there is no good 2562 // way to map them to the right transaction in http/2. Instead, the events 2563 // are generated again from the http/2 code and passed directly to the 2564 // correct transaction. 2565 2566 // NS_NET_STATUS_SENDING_TO: 2567 // This is generated by the socket transport when (part) of 2568 // a transaction is written out 2569 // 2570 // There is no good way to map it to the right transaction in http/2, 2571 // so it is ignored here and generated separately when the request 2572 // is sent from Http2StreamBase::TransmitFrame 2573 2574 // NS_NET_STATUS_WAITING_FOR: 2575 // Created by nsHttpConnection when the request has been totally sent. 2576 // There is no good way to map it to the right transaction in http/2, 2577 // so it is ignored here and generated separately when the same 2578 // condition is complete in Http2StreamBase when there is no more 2579 // request body left to be transmitted. 2580 2581 // NS_NET_STATUS_RECEIVING_FROM 2582 // Generated in session whenever we read a data frame or a HEADERS 2583 // that can be attributed to a particular stream/transaction 2584 2585 break; 2586 } 2587 } 2588 2589 // ReadSegments() is used to write data to the network. Generally, HTTP 2590 // request data is pulled from the approriate transaction and 2591 // converted to http/2 data. Sometimes control data like window-update are 2592 // generated instead. 2593 2594 nsresult Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader, 2595 uint32_t count, uint32_t* countRead, 2596 bool* again) { 2597 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2598 2599 MOZ_DIAGNOSTIC_ASSERT( 2600 !mSegmentReader || !reader || (mSegmentReader == reader), 2601 "Inconsistent Write Function Callback"); 2602 2603 nsresult rv = ConfirmTLSProfile(); 2604 if (NS_FAILED(rv)) { 2605 if (mGoAwayReason == INADEQUATE_SECURITY) { 2606 LOG3( 2607 ("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY " 2608 "%" PRIx32, 2609 this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY))); 2610 rv = NS_ERROR_NET_INADEQUATE_SECURITY; 2611 } 2612 return rv; 2613 } 2614 2615 if (reader) mSegmentReader = reader; 2616 2617 *countRead = 0; 2618 2619 LOG3(("Http2Session::ReadSegments %p", this)); 2620 2621 RefPtr<Http2StreamBase> stream = 2622 mQueueManager.GetNextStreamFromQueue(Http2StreamQueueType::ReadyForWrite); 2623 2624 if (!stream) { 2625 LOG3(("Http2Session %p could not identify a stream to write; suspending.", 2626 this)); 2627 uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent; 2628 FlushOutputQueue(); 2629 uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent; 2630 if (availBeforeFlush != availAfterFlush) { 2631 LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments", 2632 this)); 2633 (void)ResumeRecv(); 2634 } 2635 SetWriteCallbacks(); 2636 if (mAttemptingEarlyData) { 2637 // We can still try to send our preamble as early-data 2638 *countRead = mOutputQueueUsed - mOutputQueueSent; 2639 LOG(("Http2Session %p nothing to send because of 0RTT failed", this)); 2640 (void)ResumeRecv(); 2641 } 2642 return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; 2643 } 2644 2645 uint32_t earlyDataUsed = 0; 2646 if (mAttemptingEarlyData) { 2647 if (!stream->Do0RTT()) { 2648 LOG3( 2649 ("Http2Session %p will not get early data from Http2StreamBase %p " 2650 "0x%X", 2651 this, stream.get(), stream->StreamID())); 2652 FlushOutputQueue(); 2653 SetWriteCallbacks(); 2654 if (!mCannotDo0RTTStreams.Contains(stream)) { 2655 mCannotDo0RTTStreams.AppendElement(stream); 2656 } 2657 // We can still send our preamble 2658 *countRead = mOutputQueueUsed - mOutputQueueSent; 2659 return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; 2660 } 2661 2662 // Need to adjust this to only take as much as we can fit in with the 2663 // preamble/settings/priority stuff 2664 count -= (mOutputQueueUsed - mOutputQueueSent); 2665 2666 // Keep track of this to add it into countRead later, as 2667 // stream->ReadSegments will likely change the value of mOutputQueueUsed. 2668 earlyDataUsed = mOutputQueueUsed - mOutputQueueSent; 2669 } 2670 2671 LOG3( 2672 ("Http2Session %p will write from Http2StreamBase %p 0x%X " 2673 "block-input=%d block-output=%d\n", 2674 this, stream.get(), stream->StreamID(), stream->RequestBlockedOnRead(), 2675 stream->BlockedOnRwin())); 2676 2677 rv = stream->ReadSegments(this, count, countRead); 2678 2679 if (earlyDataUsed) { 2680 // Do this here because countRead could get reset somewhere down the rabbit 2681 // hole of stream->ReadSegments, and we want to make sure we return the 2682 // proper value to our caller. 2683 *countRead += earlyDataUsed; 2684 } 2685 2686 if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) { 2687 LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n", 2688 stream->StreamID())); 2689 m0RTTStreams.AppendElement(stream); 2690 } 2691 2692 // Not every permutation of stream->ReadSegents produces data (and therefore 2693 // tries to flush the output queue) - SENDING_FIN_STREAM can be an example 2694 // of that. But we might still have old data buffered that would be good 2695 // to flush. 2696 FlushOutputQueue(); 2697 2698 // Allow new server reads - that might be data or control information 2699 // (e.g. window updates or http replies) that are responses to these writes 2700 (void)ResumeRecv(); 2701 2702 if (stream->RequestBlockedOnRead()) { 2703 // We are blocked waiting for input - either more http headers or 2704 // any request body data. When more data from the request stream 2705 // becomes available the httptransaction will call conn->ResumeSend(). 2706 2707 LOG3(("Http2Session::ReadSegments %p dealing with block on read", this)); 2708 2709 // call readsegments again if there are other streams ready 2710 // to run in this session 2711 if (GetWriteQueueSize()) { 2712 rv = NS_OK; 2713 } else { 2714 rv = NS_BASE_STREAM_WOULD_BLOCK; 2715 } 2716 SetWriteCallbacks(); 2717 return rv; 2718 } 2719 2720 if (NS_FAILED(rv)) { 2721 LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32, this, 2722 static_cast<uint32_t>(rv))); 2723 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 2724 return rv; 2725 } 2726 2727 CleanupStream(stream, rv, CANCEL_ERROR); 2728 if (SoftStreamError(rv)) { 2729 LOG3(("Http2Session::ReadSegments %p soft error override\n", this)); 2730 *again = false; 2731 SetWriteCallbacks(); 2732 rv = NS_OK; 2733 } 2734 return rv; 2735 } 2736 2737 if (*countRead > 0) { 2738 LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d", this, 2739 stream.get(), *countRead)); 2740 mQueueManager.AddStreamToQueue(Http2StreamQueueType::ReadyForWrite, stream); 2741 SetWriteCallbacks(); 2742 return rv; 2743 } 2744 2745 if (stream->BlockedOnRwin()) { 2746 LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n", 2747 this, stream.get(), stream->StreamID())); 2748 return NS_BASE_STREAM_WOULD_BLOCK; 2749 } 2750 2751 LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", this, 2752 stream.get())); 2753 2754 // call readsegments again if there are other streams ready 2755 // to go in this session 2756 SetWriteCallbacks(); 2757 2758 return rv; 2759 } 2760 2761 nsresult Http2Session::ReadSegments(nsAHttpSegmentReader* reader, 2762 uint32_t count, uint32_t* countRead) { 2763 bool again = false; 2764 return ReadSegmentsAgain(reader, count, countRead, &again); 2765 } 2766 2767 nsresult Http2Session::ReadyToProcessDataFrame( 2768 enum internalStateType newState) { 2769 MOZ_ASSERT(newState == PROCESSING_DATA_FRAME || 2770 newState == DISCARDING_DATA_FRAME_PADDING); 2771 ChangeDownstreamState(newState); 2772 2773 mLastDataReadEpoch = mLastReadEpoch; 2774 2775 if (!mInputFrameID) { 2776 LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n", 2777 this)); 2778 return SessionError(PROTOCOL_ERROR); 2779 } 2780 2781 nsresult rv = SetInputFrameDataStream(mInputFrameID); 2782 if (NS_FAILED(rv)) { 2783 LOG3( 2784 ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X " 2785 "failed. probably due to verification.\n", 2786 this, mInputFrameID)); 2787 return rv; 2788 } 2789 if (!mInputFrameDataStream) { 2790 LOG3( 2791 ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X " 2792 "failed. Next = 0x%X", 2793 this, mInputFrameID, mNextStreamID)); 2794 if (mInputFrameID >= mNextStreamID) { 2795 GenerateRstStream(PROTOCOL_ERROR, mInputFrameID); 2796 } 2797 ChangeDownstreamState(DISCARDING_DATA_FRAME); 2798 } else if (mInputFrameDataStream->RecvdFin() || 2799 mInputFrameDataStream->RecvdReset() || 2800 mInputFrameDataStream->SentReset()) { 2801 LOG3( 2802 ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X " 2803 "Data arrived for already server closed stream.\n", 2804 this, mInputFrameID)); 2805 if (mInputFrameDataStream->RecvdFin() || 2806 mInputFrameDataStream->RecvdReset()) { 2807 GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID); 2808 } 2809 ChangeDownstreamState(DISCARDING_DATA_FRAME); 2810 } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) { 2811 // Only if non-final because the stream properly handles final frames of any 2812 // size, and we want the stream to be able to notice its own end flag. 2813 LOG3( 2814 ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X " 2815 "Ignoring 0-length non-terminal data frame.", 2816 this, mInputFrameID)); 2817 ChangeDownstreamState(DISCARDING_DATA_FRAME); 2818 } else if (newState == PROCESSING_DATA_FRAME && 2819 !mInputFrameDataStream->AllHeadersReceived()) { 2820 LOG3( 2821 ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X " 2822 "Receiving data frame without having headers.", 2823 this, mInputFrameID)); 2824 CleanupStream(mInputFrameDataStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY, 2825 PROTOCOL_ERROR); 2826 return NS_OK; 2827 } 2828 2829 LOG3( 2830 ("Start Processing Data Frame. " 2831 "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d", 2832 this, mInputFrameID, mInputFrameDataStream.get(), mInputFrameFinal, 2833 mInputFrameDataSize)); 2834 UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize); 2835 2836 if (mInputFrameDataStream) { 2837 mInputFrameDataStream->SetRecvdData(true); 2838 } 2839 2840 return NS_OK; 2841 } 2842 2843 // WriteSegments() is used to read data off the socket. Generally this is 2844 // just the http2 frame header and from there the appropriate *Stream 2845 // is identified from the Stream-ID. The http transaction associated with 2846 // that read then pulls in the data directly, which it will feed to 2847 // OnWriteSegment(). That function will gateway it into http and feed 2848 // it to the appropriate transaction. 2849 2850 // we call writer->OnWriteSegment via NetworkRead() to get a http2 header.. 2851 // and decide if it is data or control.. if it is control, just deal with it. 2852 // if it is data, identify the stream 2853 // call stream->WriteSegments which can call this::OnWriteSegment to get the 2854 // data. It always gets full frames if they are part of the stream 2855 2856 nsresult Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter* writer, 2857 uint32_t count, 2858 uint32_t* countWritten, bool* again) { 2859 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2860 2861 LOG3(("Http2Session::WriteSegments %p InternalState %X\n", this, 2862 mDownstreamState)); 2863 2864 *countWritten = 0; 2865 2866 if (mClosed) { 2867 LOG(("Http2Session::WriteSegments %p already closed", this)); 2868 // We return NS_ERROR_ABORT (a "soft" error) here, so when this error is 2869 // propagated to another Http2Session, the Http2Session will not be closed 2870 // due to this error code. 2871 return NS_ERROR_ABORT; 2872 } 2873 2874 nsresult rv = ConfirmTLSProfile(); 2875 if (NS_FAILED(rv)) return rv; 2876 2877 SetWriteCallbacks(); 2878 2879 // feed gecko channels that previously stopped consuming data 2880 // only take data from stored buffers 2881 RefPtr<Http2StreamBase> slowConsumer = mQueueManager.GetNextStreamFromQueue( 2882 Http2StreamQueueType::SlowConsumersReadyForRead); 2883 if (slowConsumer) { 2884 internalStateType savedState = mDownstreamState; 2885 mDownstreamState = NOT_USING_NETWORK; 2886 rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten); 2887 mDownstreamState = savedState; 2888 return rv; 2889 } 2890 2891 // The BUFFERING_OPENING_SETTINGS state is just like any 2892 // BUFFERING_FRAME_HEADER except the only frame type it will allow is SETTINGS 2893 2894 // The session layer buffers the leading 8 byte header of every frame. 2895 // Non-Data frames are then buffered for their full length, but data 2896 // frames (type 0) are passed through to the http stack unprocessed 2897 2898 if (mDownstreamState == BUFFERING_OPENING_SETTINGS || 2899 mDownstreamState == BUFFERING_FRAME_HEADER) { 2900 // The first 9 bytes of every frame is header information that 2901 // we are going to want to strip before passing to http. That is 2902 // true of both control and data packets. 2903 2904 MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes, 2905 "Frame Buffer Used Too Large for State"); 2906 2907 rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed], 2908 kFrameHeaderBytes - mInputFrameBufferUsed, countWritten); 2909 2910 if (NS_FAILED(rv)) { 2911 LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n", 2912 this, static_cast<uint32_t>(rv))); 2913 // maybe just blocked reading from network 2914 if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; 2915 return rv; 2916 } 2917 2918 LogIO(this, nullptr, "Reading Frame Header", 2919 &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten); 2920 2921 mInputFrameBufferUsed += *countWritten; 2922 2923 if (mInputFrameBufferUsed < kFrameHeaderBytes) { 2924 LOG3( 2925 ("Http2Session::WriteSegments %p " 2926 "BUFFERING FRAME HEADER incomplete size=%d", 2927 this, mInputFrameBufferUsed)); 2928 return rv; 2929 } 2930 2931 // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID 2932 uint8_t totallyWastedByte = mInputFrameBuffer.get()[0]; 2933 mInputFrameDataSize = 2934 NetworkEndian::readUint16(mInputFrameBuffer.get() + 1); 2935 if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) { 2936 LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte, 2937 mInputFrameDataSize)); 2938 return SessionError(PROTOCOL_ERROR); 2939 } 2940 mInputFrameType = *reinterpret_cast<uint8_t*>(mInputFrameBuffer.get() + 2941 kFrameLengthBytes); 2942 mInputFrameFlags = *reinterpret_cast<uint8_t*>( 2943 mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes); 2944 mInputFrameID = 2945 NetworkEndian::readUint32(mInputFrameBuffer.get() + kFrameLengthBytes + 2946 kFrameTypeBytes + kFrameFlagBytes); 2947 mInputFrameID &= 0x7fffffff; 2948 mInputFrameDataRead = 0; 2949 2950 if (mInputFrameType == FRAME_TYPE_DATA || 2951 mInputFrameType == FRAME_TYPE_HEADERS) { 2952 mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM; 2953 } else { 2954 mInputFrameFinal = false; 2955 } 2956 2957 mPaddingLength = 0; 2958 2959 LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read " 2960 "type %X data len %u flags %x id 0x%X", 2961 this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags, 2962 mInputFrameID)); 2963 2964 // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION 2965 // of a HEADERS frame with a matching ID (section 6.2) 2966 if (mExpectedHeaderID && ((mInputFrameType != FRAME_TYPE_CONTINUATION) || 2967 (mExpectedHeaderID != mInputFrameID))) { 2968 LOG3( 2969 ("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID)); 2970 return SessionError(PROTOCOL_ERROR); 2971 } 2972 2973 // if mExpectedPushPromiseID is non 0, it means this frame must be a 2974 // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2) 2975 if (mExpectedPushPromiseID && 2976 ((mInputFrameType != FRAME_TYPE_CONTINUATION) || 2977 (mExpectedPushPromiseID != mInputFrameID))) { 2978 LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n", 2979 mExpectedPushPromiseID)); 2980 return SessionError(PROTOCOL_ERROR); 2981 } 2982 2983 if (mDownstreamState == BUFFERING_OPENING_SETTINGS && 2984 mInputFrameType != FRAME_TYPE_SETTINGS) { 2985 LOG3(("First Frame Type Must Be Settings\n")); 2986 mPeerFailedHandshake = true; 2987 2988 // Don't allow any more h2 connections to this host 2989 RefPtr<nsHttpConnectionInfo> ci = ConnectionInfo(); 2990 if (ci) { 2991 gHttpHandler->ExcludeHttp2(ci); 2992 } 2993 2994 // Go through and re-start all of our transactions with h2 disabled. 2995 for (const auto& stream : mStreamTransactionHash.Values()) { 2996 stream->DisableSpdy(); 2997 CloseStream(stream, NS_ERROR_NET_RESET); 2998 } 2999 mStreamTransactionHash.Clear(); 3000 return SessionError(PROTOCOL_ERROR); 3001 } 3002 3003 if (mInputFrameType != FRAME_TYPE_DATA) { // control frame 3004 EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes, 3005 kFrameHeaderBytes, mInputFrameBufferSize); 3006 ChangeDownstreamState(BUFFERING_CONTROL_FRAME); 3007 } else if (mInputFrameFlags & kFlag_PADDED) { 3008 ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL); 3009 } else { 3010 rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME); 3011 if (NS_FAILED(rv)) { 3012 return rv; 3013 } 3014 } 3015 } 3016 3017 if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) { 3018 MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED, 3019 "Processing padding control on unpadded frame"); 3020 3021 MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1), 3022 "Frame buffer used too large for state"); 3023 3024 rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed], 3025 (kFrameHeaderBytes + 1) - mInputFrameBufferUsed, 3026 countWritten); 3027 3028 if (NS_FAILED(rv)) { 3029 LOG3( 3030 ("Http2Session %p buffering data frame padding control read failure " 3031 "%" PRIx32 "\n", 3032 this, static_cast<uint32_t>(rv))); 3033 // maybe just blocked reading from network 3034 if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; 3035 return rv; 3036 } 3037 3038 LogIO(this, nullptr, "Reading Data Frame Padding Control", 3039 &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten); 3040 3041 mInputFrameBufferUsed += *countWritten; 3042 3043 if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) { 3044 LOG3( 3045 ("Http2Session::WriteSegments %p " 3046 "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d", 3047 this, mInputFrameBufferUsed - 8)); 3048 return rv; 3049 } 3050 3051 ++mInputFrameDataRead; 3052 3053 char* control = &mInputFrameBuffer[kFrameHeaderBytes]; 3054 mPaddingLength = static_cast<uint8_t>(*control); 3055 3056 LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this, 3057 mInputFrameID, mPaddingLength)); 3058 3059 if (1U + mPaddingLength > mInputFrameDataSize) { 3060 LOG3( 3061 ("Http2Session::WriteSegments %p stream 0x%X padding too large for " 3062 "frame", 3063 this, mInputFrameID)); 3064 return SessionError(PROTOCOL_ERROR); 3065 } 3066 if (1U + mPaddingLength == mInputFrameDataSize) { 3067 // This frame consists entirely of padding, we can just discard it 3068 LOG3( 3069 ("Http2Session::WriteSegments %p stream 0x%X frame with only padding", 3070 this, mInputFrameID)); 3071 rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING); 3072 if (NS_FAILED(rv)) { 3073 return rv; 3074 } 3075 } else { 3076 LOG3( 3077 ("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data", 3078 this, mInputFrameID)); 3079 rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME); 3080 if (NS_FAILED(rv)) { 3081 return rv; 3082 } 3083 } 3084 } 3085 3086 if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { 3087 nsresult streamCleanupCode; 3088 3089 if (!mInputFrameDataStream) { 3090 return NS_ERROR_UNEXPECTED; 3091 } 3092 3093 // There is no bounds checking on the error code.. we provide special 3094 // handling for a couple of cases and all others (including unknown) are 3095 // equivalent to cancel. 3096 if (mDownstreamRstReason == REFUSED_STREAM_ERROR) { 3097 streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely 3098 mInputFrameDataStream->ReuseConnectionOnRestartOK(true); 3099 } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) { 3100 streamCleanupCode = NS_ERROR_NET_RESET; 3101 mInputFrameDataStream->ReuseConnectionOnRestartOK(true); 3102 mInputFrameDataStream->DisableSpdy(); 3103 // actually allow restart by unsticking 3104 mInputFrameDataStream->MakeNonSticky(); 3105 } else { 3106 streamCleanupCode = mInputFrameDataStream->RecvdData() 3107 ? NS_ERROR_NET_PARTIAL_TRANSFER 3108 : NS_ERROR_NET_INTERRUPT; 3109 } 3110 3111 if (mDownstreamRstReason == COMPRESSION_ERROR) { 3112 mShouldGoAway = true; 3113 } 3114 3115 // mInputFrameDataStream is reset by ChangeDownstreamState 3116 Http2StreamBase* stream = mInputFrameDataStream; 3117 ResetDownstreamState(); 3118 LOG3( 3119 ("Http2Session::WriteSegments cleanup stream on recv of rst " 3120 "session=%p stream=%p 0x%X\n", 3121 this, stream, stream ? stream->StreamID() : 0)); 3122 CleanupStream(stream, streamCleanupCode, CANCEL_ERROR); 3123 return NS_OK; 3124 } 3125 3126 if (mDownstreamState == PROCESSING_DATA_FRAME || 3127 mDownstreamState == PROCESSING_COMPLETE_HEADERS) { 3128 // The cleanup stream should only be set while stream->WriteSegments is 3129 // on the stack and then cleaned up in this code block afterwards. 3130 MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly"); 3131 mNeedsCleanup = nullptr; /* just in case */ 3132 3133 if (!mInputFrameDataStream) { 3134 return NS_ERROR_UNEXPECTED; 3135 } 3136 3137 RefPtr<Http2StreamBase> refStream = mInputFrameDataStream.get(); 3138 uint32_t streamID = refStream->StreamID(); 3139 mSegmentWriter = writer; 3140 rv = refStream->WriteSegments(this, count, countWritten); 3141 if (refStream->Closed() && NS_SUCCEEDED(rv)) { 3142 rv = NS_BASE_STREAM_CLOSED; 3143 } 3144 mSegmentWriter = nullptr; 3145 3146 mLastDataReadEpoch = mLastReadEpoch; 3147 3148 if (SoftStreamError(rv)) { 3149 // This will happen when the transaction figures out it is EOF, generally 3150 // due to a content-length match being made. Return OK from this function 3151 // otherwise the whole session would be torn down. 3152 3153 // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state 3154 // back to PROCESSING_DATA_FRAME where we came from 3155 mDownstreamState = PROCESSING_DATA_FRAME; 3156 3157 if (mInputFrameDataRead == mInputFrameDataSize) ResetDownstreamState(); 3158 LOG3( 3159 ("Http2Session::WriteSegments session=%p id 0x%X " 3160 "needscleanup=%p. cleanup stream based on " 3161 "stream->writeSegments returning code %" PRIx32 "\n", 3162 this, streamID, mNeedsCleanup.get(), static_cast<uint32_t>(rv))); 3163 MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID); 3164 CleanupStream( 3165 streamID, 3166 (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK, 3167 CANCEL_ERROR); 3168 mNeedsCleanup = nullptr; 3169 *again = false; 3170 rv = ResumeRecv(); 3171 if (NS_FAILED(rv)) { 3172 LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv))); 3173 } 3174 return NS_OK; 3175 } 3176 3177 if (mNeedsCleanup) { 3178 LOG3( 3179 ("Http2Session::WriteSegments session=%p stream=%p 0x%X " 3180 "cleanup stream based on mNeedsCleanup.\n", 3181 this, mNeedsCleanup.get(), 3182 mNeedsCleanup ? mNeedsCleanup->StreamID() : 0)); 3183 CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR); 3184 mNeedsCleanup = nullptr; 3185 } 3186 3187 if (NS_FAILED(rv)) { 3188 LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this, 3189 static_cast<uint32_t>(rv))); 3190 // maybe just blocked reading from network 3191 if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; 3192 } 3193 3194 return rv; 3195 } 3196 3197 if (mDownstreamState == DISCARDING_DATA_FRAME || 3198 mDownstreamState == DISCARDING_DATA_FRAME_PADDING) { 3199 char trash[4096]; 3200 uint32_t discardCount = 3201 std::min(mInputFrameDataSize - mInputFrameDataRead, 4096U); 3202 LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s", 3203 this, discardCount, 3204 mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding")); 3205 3206 if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) { 3207 // Only do this short-cirtuit if we're not discarding a pure padding 3208 // frame, as we need to potentially handle the stream FIN in those cases. 3209 // See bug 1381016 comment 36 for more details. 3210 ResetDownstreamState(); 3211 (void)ResumeRecv(); 3212 return NS_BASE_STREAM_WOULD_BLOCK; 3213 } 3214 3215 rv = NetworkRead(writer, trash, discardCount, countWritten); 3216 3217 if (NS_FAILED(rv)) { 3218 LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this, 3219 static_cast<uint32_t>(rv))); 3220 // maybe just blocked reading from network 3221 if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; 3222 return rv; 3223 } 3224 3225 LogIO(this, nullptr, "Discarding Frame", trash, *countWritten); 3226 3227 mInputFrameDataRead += *countWritten; 3228 3229 if (mInputFrameDataRead == mInputFrameDataSize) { 3230 Http2StreamBase* streamToCleanup = nullptr; 3231 if (mInputFrameFinal) { 3232 streamToCleanup = mInputFrameDataStream; 3233 } 3234 3235 ResetDownstreamState(); 3236 3237 if (streamToCleanup) { 3238 CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR); 3239 } 3240 } 3241 return rv; 3242 } 3243 3244 if (mDownstreamState != BUFFERING_CONTROL_FRAME) { 3245 MOZ_ASSERT(false); // this cannot happen 3246 return NS_ERROR_UNEXPECTED; 3247 } 3248 3249 MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes, 3250 "Frame Buffer Header Not Present"); 3251 MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize, 3252 "allocation for control frame insufficient"); 3253 3254 rv = NetworkRead(writer, 3255 &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead], 3256 mInputFrameDataSize - mInputFrameDataRead, countWritten); 3257 3258 if (NS_FAILED(rv)) { 3259 LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n", 3260 this, static_cast<uint32_t>(rv))); 3261 // maybe just blocked reading from network 3262 if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; 3263 return rv; 3264 } 3265 3266 LogIO(this, nullptr, "Reading Control Frame", 3267 &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead], 3268 *countWritten); 3269 3270 mInputFrameDataRead += *countWritten; 3271 3272 if (mInputFrameDataRead != mInputFrameDataSize) return NS_OK; 3273 3274 MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA); 3275 if (mInputFrameType < std::size(sControlFunctions)) { 3276 rv = sControlFunctions[mInputFrameType](this); 3277 } else { 3278 // Section 4.1 requires this to be ignored; though protocol_error would 3279 // be better 3280 LOG3(("Http2Session %p unknown frame type %x ignored\n", this, 3281 mInputFrameType)); 3282 ResetDownstreamState(); 3283 rv = NS_OK; 3284 } 3285 3286 MOZ_ASSERT(NS_FAILED(rv) || mDownstreamState != BUFFERING_CONTROL_FRAME, 3287 "Control Handler returned OK but did not change state"); 3288 3289 if (mShouldGoAway && IsDone()) { 3290 Close(NS_OK); 3291 } 3292 return rv; 3293 } 3294 3295 nsresult Http2Session::WriteSegments(nsAHttpSegmentWriter* writer, 3296 uint32_t count, uint32_t* countWritten) { 3297 bool again = false; 3298 return WriteSegmentsAgain(writer, count, countWritten, &again); 3299 } 3300 3301 nsresult Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) { 3302 MOZ_ASSERT(mAttemptingEarlyData); 3303 LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this, 3304 aRestart, aAlpnChanged)); 3305 3306 for (size_t i = 0; i < m0RTTStreams.Length(); ++i) { 3307 if (m0RTTStreams[i]) { 3308 m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged); 3309 } 3310 } 3311 3312 if (aRestart) { 3313 // 0RTT failed 3314 if (aAlpnChanged) { 3315 // This is a slightly more involved case - we need to get all our streams/ 3316 // transactions back in the queue so they can restart as http/1 3317 3318 // These must be set this way to ensure we gracefully restart all streams 3319 mGoAwayID = 0; 3320 mCleanShutdown = true; 3321 3322 // Close takes care of the rest of our work for us. The reason code here 3323 // doesn't matter, as we aren't actually going to send a GOAWAY frame, but 3324 // we use NS_ERROR_NET_RESET as it's closest to the truth. 3325 Close(NS_ERROR_NET_RESET); 3326 } else { 3327 // This is the easy case - early data failed, but we're speaking h2, so 3328 // we just need to rewind to the beginning of the preamble and try again. 3329 mOutputQueueSent = 0; 3330 3331 for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) { 3332 if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) { 3333 TransactionHasDataToWrite(mCannotDo0RTTStreams[i]); 3334 } 3335 } 3336 } 3337 } else { 3338 // 0RTT succeeded 3339 for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) { 3340 if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) { 3341 TransactionHasDataToWrite(mCannotDo0RTTStreams[i]); 3342 } 3343 } 3344 // Make sure we look for any incoming data in repsonse to our early data. 3345 (void)ResumeRecv(); 3346 } 3347 3348 mAttemptingEarlyData = false; 3349 m0RTTStreams.Clear(); 3350 mCannotDo0RTTStreams.Clear(); 3351 RealignOutputQueue(); 3352 3353 return NS_OK; 3354 } 3355 3356 nsresult Http2Session::ProcessConnectedPush( 3357 Http2StreamBase* pushConnectedStream, nsAHttpSegmentWriter* writer, 3358 uint32_t count, uint32_t* countWritten) { 3359 return nsresult::NS_ERROR_NOT_IMPLEMENTED; 3360 } 3361 3362 nsresult Http2Session::ProcessSlowConsumer(Http2StreamBase* slowConsumer, 3363 nsAHttpSegmentWriter* writer, 3364 uint32_t count, 3365 uint32_t* countWritten) { 3366 LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n", this, 3367 slowConsumer->StreamID())); 3368 mSegmentWriter = writer; 3369 nsresult rv = slowConsumer->WriteSegments(this, count, countWritten); 3370 mSegmentWriter = nullptr; 3371 LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32 3372 " %d\n", 3373 this, slowConsumer->StreamID(), static_cast<uint32_t>(rv), 3374 *countWritten)); 3375 if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) { 3376 rv = NS_BASE_STREAM_CLOSED; 3377 } 3378 3379 if (NS_SUCCEEDED(rv) && (*countWritten > 0)) { 3380 // There have been buffered bytes successfully fed into the 3381 // formerly blocked consumer. Repeat until buffer empty or 3382 // consumer is blocked again. 3383 UpdateLocalRwin(slowConsumer, 0); 3384 ConnectSlowConsumer(slowConsumer); 3385 } 3386 3387 if (rv == NS_BASE_STREAM_CLOSED) { 3388 CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR); 3389 rv = NS_OK; 3390 } 3391 3392 return rv; 3393 } 3394 3395 void Http2Session::UpdateLocalStreamWindow(Http2StreamBase* stream, 3396 uint32_t bytes) { 3397 if (!stream) { // this is ok - it means there was a data frame for a rst 3398 // stream 3399 return; 3400 } 3401 3402 // If this data packet was not for a valid or live stream then there 3403 // is no reason to mess with the flow control 3404 if (!stream || stream->RecvdFin() || stream->RecvdReset() || 3405 mInputFrameFinal) { 3406 return; 3407 } 3408 3409 stream->DecrementClientReceiveWindow(bytes); 3410 3411 // Don't necessarily ack every data packet. Only do it 3412 // after a significant amount of data. 3413 uint64_t unacked = stream->LocalUnAcked(); 3414 int64_t localWindow = stream->ClientReceiveWindow(); 3415 3416 LOG3( 3417 ("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u " 3418 "unacked=%" PRIu64 " localWindow=%" PRId64 "\n", 3419 this, stream->StreamID(), bytes, unacked, localWindow)); 3420 3421 if (!unacked) return; 3422 3423 if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) { 3424 return; 3425 } 3426 3427 if (!stream->HasSink()) { 3428 LOG3( 3429 ("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No " 3430 "Sink\n", 3431 this, stream->StreamID())); 3432 return; 3433 } 3434 3435 // Generate window updates directly out of session instead of the stream 3436 // in order to avoid queue delays in getting the 'ACK' out. 3437 uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU; 3438 3439 LOG3( 3440 ("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n", 3441 this, stream->StreamID(), toack)); 3442 stream->IncrementClientReceiveWindow(toack); 3443 if (toack == 0) { 3444 // Ensure we never send an illegal 0 window update 3445 return; 3446 } 3447 3448 // room for this packet needs to be ensured before calling this function 3449 char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed; 3450 mOutputQueueUsed += kFrameHeaderBytes + 4; 3451 MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize); 3452 3453 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID()); 3454 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack); 3455 3456 LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4); 3457 // dont flush here, this write can commonly be coalesced with a 3458 // session window update to immediately follow. 3459 } 3460 3461 void Http2Session::UpdateLocalSessionWindow(uint32_t bytes) { 3462 if (!bytes) return; 3463 3464 mLocalSessionWindow -= bytes; 3465 3466 LOG3( 3467 ("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u " 3468 "localWindow=%" PRId64 "\n", 3469 this, bytes, mLocalSessionWindow)); 3470 3471 // Don't necessarily ack every data packet. Only do it 3472 // after a significant amount of data. 3473 if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) && 3474 (mLocalSessionWindow > kEmergencyWindowThreshold)) { 3475 return; 3476 } 3477 3478 // Only send max bits of window updates at a time. 3479 uint64_t toack64 = mInitialRwin - mLocalSessionWindow; 3480 uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU; 3481 3482 LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", this, 3483 toack)); 3484 mLocalSessionWindow += toack; 3485 3486 if (toack == 0) { 3487 // Ensure we never send an illegal 0 window update 3488 return; 3489 } 3490 3491 // room for this packet needs to be ensured before calling this function 3492 char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed; 3493 mOutputQueueUsed += kFrameHeaderBytes + 4; 3494 MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize); 3495 3496 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0); 3497 NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack); 3498 3499 LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4); 3500 // dont flush here, this write can commonly be coalesced with others 3501 } 3502 3503 void Http2Session::UpdateLocalRwin(Http2StreamBase* stream, uint32_t bytes) { 3504 // make sure there is room for 2 window updates even though 3505 // we may not generate any. 3506 EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4)); 3507 3508 UpdateLocalStreamWindow(stream, bytes); 3509 UpdateLocalSessionWindow(bytes); 3510 FlushOutputQueue(); 3511 } 3512 3513 void Http2Session::Close(nsresult aReason) { 3514 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3515 3516 if (mClosed) return; 3517 3518 LOG3(("Http2Session::Close %p %" PRIX32, this, 3519 static_cast<uint32_t>(aReason))); 3520 3521 mClosed = true; 3522 3523 if (!mLastTRRResponseTime.IsNull()) { 3524 RefPtr<nsHttpConnectionInfo> ci; 3525 GetConnectionInfo(getter_AddRefs(ci)); 3526 if (ci && ci->GetIsTrrServiceChannel() && mCleanShutdown) { 3527 // Record telemetry keyed by TRR provider. 3528 glean::network::trr_idle_close_time_h2.Get(TRRProviderKey()) 3529 .AccumulateRawDuration(TimeStamp::Now() - mLastTRRResponseTime); 3530 } 3531 mLastTRRResponseTime = TimeStamp(); 3532 } 3533 3534 Shutdown(aReason); 3535 3536 mStreamIDHash.Clear(); 3537 mStreamTransactionHash.Clear(); 3538 mTunnelStreams.Clear(); 3539 3540 uint32_t goAwayReason; 3541 if (mGoAwayReason != NO_HTTP_ERROR) { 3542 goAwayReason = mGoAwayReason; 3543 } else if (NS_SUCCEEDED(aReason)) { 3544 goAwayReason = NO_HTTP_ERROR; 3545 } else if (aReason == NS_ERROR_NET_HTTP2_SENT_GOAWAY) { 3546 goAwayReason = PROTOCOL_ERROR; 3547 } else if (mCleanShutdown) { 3548 goAwayReason = NO_HTTP_ERROR; 3549 } else { 3550 goAwayReason = INTERNAL_ERROR; 3551 } 3552 if (!mAttemptingEarlyData) { 3553 GenerateGoAway(goAwayReason); 3554 } 3555 mConnection = nullptr; 3556 mSegmentReader = nullptr; 3557 mSegmentWriter = nullptr; 3558 } 3559 3560 nsHttpConnectionInfo* Http2Session::ConnectionInfo() { 3561 RefPtr<nsHttpConnectionInfo> ci; 3562 GetConnectionInfo(getter_AddRefs(ci)); 3563 return ci.get(); 3564 } 3565 3566 void Http2Session::CloseTransaction(nsAHttpTransaction* aTransaction, 3567 nsresult aResult) { 3568 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3569 LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction, 3570 static_cast<uint32_t>(aResult))); 3571 3572 // Generally this arrives as a cancel event from the connection manager. 3573 3574 // need to find the stream and call CleanupStream() on it. 3575 RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(aTransaction); 3576 if (!stream) { 3577 LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.", this, 3578 aTransaction, static_cast<uint32_t>(aResult))); 3579 return; 3580 } 3581 LOG3( 3582 ("Http2Session::CloseTransaction probably a cancel. " 3583 "this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p", 3584 this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(), 3585 stream.get())); 3586 CleanupStream(stream, aResult, CANCEL_ERROR); 3587 nsresult rv = ResumeRecv(); 3588 if (NS_FAILED(rv)) { 3589 LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x", 3590 this, aTransaction, static_cast<uint32_t>(aResult), 3591 static_cast<uint32_t>(rv))); 3592 } 3593 } 3594 3595 //----------------------------------------------------------------------------- 3596 // nsAHttpSegmentReader 3597 //----------------------------------------------------------------------------- 3598 3599 nsresult Http2Session::OnReadSegment(const char* buf, uint32_t count, 3600 uint32_t* countRead) { 3601 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3602 nsresult rv; 3603 3604 // If we can release old queued data then we can try and write the new 3605 // data directly to the network without using the output queue at all 3606 if (mOutputQueueUsed) FlushOutputQueue(); 3607 3608 if (!mOutputQueueUsed && mSegmentReader) { 3609 // try and write directly without output queue 3610 rv = mSegmentReader->OnReadSegment(buf, count, countRead); 3611 3612 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 3613 *countRead = 0; 3614 } else if (NS_FAILED(rv)) { 3615 return rv; 3616 } 3617 3618 if (*countRead < count) { 3619 uint32_t required = count - *countRead; 3620 // assuming a commitment() happened, this ensurebuffer is a nop 3621 // but just in case the queuesize is too small for the required data 3622 // call ensurebuffer(). 3623 EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize); 3624 memcpy(mOutputQueueBuffer.get(), buf + *countRead, required); 3625 mOutputQueueUsed = required; 3626 } 3627 3628 *countRead = count; 3629 return NS_OK; 3630 } 3631 3632 // At this point we are going to buffer the new data in the output 3633 // queue if it fits. By coalescing multiple small submissions into one larger 3634 // buffer we can get larger writes out to the network later on. 3635 3636 // This routine should not be allowed to fill up the output queue 3637 // all on its own - at least kQueueReserved bytes are always left 3638 // for other routines to use - but this is an all-or-nothing function, 3639 // so if it will not all fit just return WOULD_BLOCK 3640 3641 if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved)) { 3642 return NS_BASE_STREAM_WOULD_BLOCK; 3643 } 3644 3645 memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count); 3646 mOutputQueueUsed += count; 3647 *countRead = count; 3648 3649 FlushOutputQueue(); 3650 3651 return NS_OK; 3652 } 3653 3654 nsresult Http2Session::CommitToSegmentSize(uint32_t count, 3655 bool forceCommitment) { 3656 if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue(); 3657 3658 // would there be enough room to buffer this if needed? 3659 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) { 3660 return NS_OK; 3661 } 3662 3663 // if we are using part of our buffers already, try again later unless 3664 // forceCommitment is set. 3665 if (mOutputQueueUsed && !forceCommitment) return NS_BASE_STREAM_WOULD_BLOCK; 3666 3667 if (mOutputQueueUsed) { 3668 // normally we avoid the memmove of RealignOutputQueue, but we'll try 3669 // it if forceCommitment is set before growing the buffer. 3670 RealignOutputQueue(); 3671 3672 // is there enough room now? 3673 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) { 3674 return NS_OK; 3675 } 3676 } 3677 3678 // resize the buffers as needed 3679 EnsureOutputBuffer(count + kQueueReserved); 3680 3681 MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved), 3682 "buffer not as large as expected"); 3683 3684 return NS_OK; 3685 } 3686 3687 //----------------------------------------------------------------------------- 3688 // nsAHttpSegmentWriter 3689 //----------------------------------------------------------------------------- 3690 3691 nsresult Http2Session::OnWriteSegment(char* buf, uint32_t count, 3692 uint32_t* countWritten) { 3693 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3694 nsresult rv; 3695 3696 if (!mSegmentWriter) { 3697 // the only way this could happen would be if Close() were called on the 3698 // stack with WriteSegments() 3699 return NS_ERROR_FAILURE; 3700 } 3701 3702 if (mDownstreamState == NOT_USING_NETWORK || 3703 mDownstreamState == BUFFERING_FRAME_HEADER || 3704 mDownstreamState == DISCARDING_DATA_FRAME_PADDING) { 3705 return NS_BASE_STREAM_WOULD_BLOCK; 3706 } 3707 3708 if (mDownstreamState == PROCESSING_DATA_FRAME) { 3709 if (mInputFrameFinal && mInputFrameDataRead == mInputFrameDataSize) { 3710 *countWritten = 0; 3711 SetNeedsCleanup(); 3712 return NS_BASE_STREAM_CLOSED; 3713 } 3714 3715 count = std::min(count, mInputFrameDataSize - mInputFrameDataRead); 3716 rv = NetworkRead(mSegmentWriter, buf, count, countWritten); 3717 if (NS_FAILED(rv)) return rv; 3718 3719 LogIO(this, mInputFrameDataStream, "Reading Data Frame", buf, 3720 *countWritten); 3721 3722 mInputFrameDataRead += *countWritten; 3723 if (mPaddingLength && 3724 (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) { 3725 // We are crossing from real HTTP data into the realm of padding. If 3726 // we've actually crossed the line, we need to munge countWritten for the 3727 // sake of goodness and sanity. No matter what, any future calls to 3728 // WriteSegments need to just discard data until we reach the end of this 3729 // frame. 3730 if (mInputFrameDataSize != mInputFrameDataRead) { 3731 // Only change state if we still have padding to read. If we don't do 3732 // this, we can end up hanging on frames that combine real data, 3733 // padding, and END_STREAM (see bug 1019921) 3734 ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING); 3735 } 3736 uint32_t paddingRead = 3737 mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead); 3738 LOG3( 3739 ("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d " 3740 "crossed from HTTP data into padding (%d of %d) countWritten=%d", 3741 this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead, 3742 paddingRead, mPaddingLength, *countWritten)); 3743 *countWritten -= paddingRead; 3744 LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d", 3745 this, mInputFrameID, *countWritten)); 3746 } 3747 3748 mInputFrameDataStream->UpdateTransportReadEvents(*countWritten); 3749 if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal) { 3750 ResetDownstreamState(); 3751 } 3752 3753 return rv; 3754 } 3755 3756 if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) { 3757 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && 3758 mInputFrameFinal) { 3759 *countWritten = 0; 3760 SetNeedsCleanup(); 3761 return NS_BASE_STREAM_CLOSED; 3762 } 3763 3764 count = std::min<uint32_t>( 3765 count, mFlatHTTPResponseHeaders.Length() - mFlatHTTPResponseHeadersOut); 3766 memcpy(buf, mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut, 3767 count); 3768 mFlatHTTPResponseHeadersOut += count; 3769 *countWritten = count; 3770 3771 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) { 3772 // Since mInputFrameFinal can be reset, we need to also check RecvdFin to 3773 // see if a stream doesn’t expect more frames. 3774 if (!mInputFrameFinal && !mInputFrameDataStream->RecvdFin()) { 3775 // If more frames are expected in this stream, then reset the state so 3776 // they can be handled. Otherwise (e.g. a 0 length response with the fin 3777 // on the incoming headers) stay in PROCESSING_COMPLETE_HEADERS state so 3778 // the SetNeedsCleanup() code above can cleanup the stream. 3779 ResetDownstreamState(); 3780 } 3781 } 3782 3783 return NS_OK; 3784 } 3785 3786 MOZ_ASSERT(false); 3787 return NS_ERROR_UNEXPECTED; 3788 } 3789 3790 void Http2Session::SetNeedsCleanup() { 3791 LOG3( 3792 ("Http2Session::SetNeedsCleanup %p - recorded downstream fin of " 3793 "stream %p 0x%X", 3794 this, mInputFrameDataStream.get(), mInputFrameDataStream->StreamID())); 3795 3796 // This will result in Close() being called 3797 MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set"); 3798 mInputFrameDataStream->SetResponseIsComplete(); 3799 mNeedsCleanup = mInputFrameDataStream; 3800 ResetDownstreamState(); 3801 } 3802 3803 void Http2Session::ConnectSlowConsumer(Http2StreamBase* stream) { 3804 LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n", this, 3805 stream->StreamID())); 3806 mQueueManager.AddStreamToQueue( 3807 Http2StreamQueueType::SlowConsumersReadyForRead, stream); 3808 (void)ForceRecv(); 3809 } 3810 3811 nsresult Http2Session::BufferOutput(const char* buf, uint32_t count, 3812 uint32_t* countRead) { 3813 RefPtr<nsAHttpSegmentReader> old; 3814 mSegmentReader.swap(old); // Make mSegmentReader null 3815 nsresult rv = OnReadSegment(buf, count, countRead); 3816 mSegmentReader.swap(old); // Restore the old mSegmentReader 3817 return rv; 3818 } 3819 3820 bool // static 3821 Http2Session::ALPNCallback(nsITLSSocketControl* tlsSocketControl) { 3822 LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", tlsSocketControl)); 3823 if (tlsSocketControl) { 3824 int16_t version = tlsSocketControl->GetSSLVersionOffered(); 3825 LOG3(("Http2Session::ALPNCallback version=%x\n", version)); 3826 3827 if (version == nsITLSSocketControl::TLS_VERSION_1_2 && 3828 !gHttpHandler->IsH2MandatorySuiteEnabled()) { 3829 LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n")); 3830 return false; 3831 } 3832 3833 if (version >= nsITLSSocketControl::TLS_VERSION_1_2) { 3834 return true; 3835 } 3836 } 3837 return false; 3838 } 3839 3840 nsresult Http2Session::ConfirmTLSProfile() { 3841 if (mTLSProfileConfirmed) { 3842 return NS_OK; 3843 } 3844 3845 LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this, 3846 mConnection.get())); 3847 3848 if (mAttemptingEarlyData) { 3849 LOG3( 3850 ("Http2Session::ConfirmTLSProfile %p temporarily passing due to early " 3851 "data\n", 3852 this)); 3853 return NS_OK; 3854 } 3855 3856 if (!StaticPrefs::network_http_http2_enforce_tls_profile()) { 3857 LOG3( 3858 ("Http2Session::ConfirmTLSProfile %p passed due to configuration " 3859 "bypass\n", 3860 this)); 3861 mTLSProfileConfirmed = true; 3862 return NS_OK; 3863 } 3864 3865 if (!mConnection) return NS_ERROR_FAILURE; 3866 3867 nsCOMPtr<nsITLSSocketControl> ssl; 3868 mConnection->GetTLSSocketControl(getter_AddRefs(ssl)); 3869 LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, 3870 ssl.get())); 3871 if (!ssl) return NS_ERROR_FAILURE; 3872 3873 int16_t version = ssl->GetSSLVersionUsed(); 3874 LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version)); 3875 if (version < nsITLSSocketControl::TLS_VERSION_1_2) { 3876 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", 3877 this)); 3878 return SessionError(INADEQUATE_SECURITY); 3879 } 3880 3881 uint16_t kea = ssl->GetKEAUsed(); 3882 if (kea == ssl_kea_ecdh_hybrid && !StaticPrefs::security_tls_enable_kyber()) { 3883 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to disabled KEA %d\n", 3884 this, kea)); 3885 return SessionError(INADEQUATE_SECURITY); 3886 } 3887 3888 if (kea != ssl_kea_dh && kea != ssl_kea_ecdh && kea != ssl_kea_ecdh_hybrid) { 3889 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n", 3890 this, kea)); 3891 return SessionError(INADEQUATE_SECURITY); 3892 } 3893 3894 uint32_t keybits = ssl->GetKEAKeyBits(); 3895 if (kea == ssl_kea_dh && keybits < 2048) { 3896 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n", 3897 this, keybits)); 3898 return SessionError(INADEQUATE_SECURITY); 3899 } 3900 if (kea == ssl_kea_ecdh && keybits < 224) { // see rfc7540 9.2.1. 3901 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n", 3902 this, keybits)); 3903 return SessionError(INADEQUATE_SECURITY); 3904 } 3905 3906 int16_t macAlgorithm = ssl->GetMACAlgorithmUsed(); 3907 LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n", this, 3908 macAlgorithm)); 3909 if (macAlgorithm != nsITLSSocketControl::SSL_MAC_AEAD) { 3910 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n", 3911 this)); 3912 return SessionError(INADEQUATE_SECURITY); 3913 } 3914 3915 /* We are required to send SNI. We do that already, so no check is done 3916 * here to make sure we did. */ 3917 3918 /* We really should check to ensure TLS compression isn't enabled on 3919 * this connection. However, we never enable TLS compression on our end, 3920 * anyway, so it'll never be on. All the same, see https://bugzil.la/965881 3921 * for the possibility for an interface to ensure it never gets turned on. */ 3922 3923 mTLSProfileConfirmed = true; 3924 return NS_OK; 3925 } 3926 3927 //----------------------------------------------------------------------------- 3928 // Modified methods of nsAHttpConnection 3929 //----------------------------------------------------------------------------- 3930 3931 void Http2Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) { 3932 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3933 LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller)); 3934 3935 // a trapped signal from the http transaction to the connection that 3936 // it is no longer blocked on read. 3937 3938 RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(caller); 3939 if (!stream || !VerifyStream(stream)) { 3940 LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found", 3941 this, caller)); 3942 return; 3943 } 3944 3945 LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", this, 3946 stream->StreamID())); 3947 3948 if (!mClosed) { 3949 mQueueManager.AddStreamToQueue(Http2StreamQueueType::ReadyForWrite, stream); 3950 SetWriteCallbacks(); 3951 } else { 3952 LOG3( 3953 ("Http2Session::TransactionHasDataToWrite %p closed so not setting " 3954 "Ready4Write\n", 3955 this)); 3956 } 3957 3958 // NSPR poll will not poll the network if there are non system PR_FileDesc's 3959 // that are ready - so we can get into a deadlock waiting for the system IO 3960 // to come back here if we don't force the send loop manually. 3961 (void)ForceSend(); 3962 } 3963 3964 void Http2Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) { 3965 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3966 LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller)); 3967 3968 // a signal from the http transaction to the connection that it will consume 3969 // more 3970 RefPtr<Http2StreamBase> stream = mStreamTransactionHash.Get(caller); 3971 if (!stream || !VerifyStream(stream)) { 3972 LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found", this, 3973 caller)); 3974 return; 3975 } 3976 3977 LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n", this, 3978 stream->StreamID())); 3979 TransactionHasDataToRecv(stream); 3980 } 3981 3982 void Http2Session::TransactionHasDataToWrite(Http2StreamBase* stream) { 3983 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3984 LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", this, 3985 stream, stream->StreamID())); 3986 3987 mQueueManager.AddStreamToQueue(Http2StreamQueueType::ReadyForWrite, stream); 3988 SetWriteCallbacks(); 3989 (void)ForceSend(); 3990 } 3991 3992 void Http2Session::TransactionHasDataToRecv(Http2StreamBase* caller) { 3993 ConnectSlowConsumer(caller); 3994 } 3995 3996 bool Http2Session::IsPersistent() { return true; } 3997 3998 nsresult Http2Session::TakeTransport(nsISocketTransport**, 3999 nsIAsyncInputStream**, 4000 nsIAsyncOutputStream**) { 4001 MOZ_ASSERT(false, "TakeTransport of Http2Session"); 4002 return NS_ERROR_UNEXPECTED; 4003 } 4004 4005 WebTransportSessionBase* Http2Session::GetWebTransportSession( 4006 nsAHttpTransaction* aTransaction) { 4007 uintptr_t id = reinterpret_cast<uintptr_t>(aTransaction); 4008 RefPtr<Http2StreamBase> stream; 4009 for (auto& entry : mTunnelStreams) { 4010 if (entry->GetTransactionId() == id) { 4011 entry->SetTransactionId(0); 4012 stream = entry; 4013 break; 4014 } 4015 } 4016 4017 if (!stream || !stream->GetHttp2WebTransportSession()) { 4018 MOZ_ASSERT(false, "There must be a stream"); 4019 return nullptr; 4020 } 4021 RemoveStreamFromQueues(stream); 4022 4023 return static_cast<Http2WebTransportSession*>( 4024 stream->GetHttp2WebTransportSession()) 4025 ->GetHttp2WebTransportSessionImpl(); 4026 } 4027 4028 already_AddRefed<HttpConnectionBase> Http2Session::TakeHttpConnection() { 4029 LOG(("Http2Session::TakeHttpConnection %p", this)); 4030 return nullptr; 4031 } 4032 4033 already_AddRefed<HttpConnectionBase> Http2Session::HttpConnection() { 4034 if (mConnection) { 4035 return mConnection->HttpConnection(); 4036 } 4037 return nullptr; 4038 } 4039 4040 void Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) { 4041 *aOut = nullptr; 4042 } 4043 4044 void Http2Session::SetConnection(nsAHttpConnection* aConn) { 4045 mConnection = aConn; 4046 } 4047 4048 //----------------------------------------------------------------------------- 4049 // unused methods of nsAHttpTransaction 4050 // We can be sure of this because Http2Session is only constructed in 4051 // nsHttpConnection and is never passed out of that object or a 4052 // TLSFilterTransaction TLS tunnel 4053 //----------------------------------------------------------------------------- 4054 4055 void Http2Session::SetProxyConnectFailed() { 4056 MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()"); 4057 } 4058 4059 bool Http2Session::IsDone() { 4060 return !mStreamTransactionHash.Count() && mTunnelStreams.IsEmpty(); 4061 } 4062 4063 nsresult Http2Session::Status() { 4064 MOZ_ASSERT(false, "Http2Session::Status()"); 4065 return NS_ERROR_UNEXPECTED; 4066 } 4067 4068 uint32_t Http2Session::Caps() { 4069 MOZ_ASSERT(false, "Http2Session::Caps()"); 4070 return 0; 4071 } 4072 4073 nsHttpRequestHead* Http2Session::RequestHead() { 4074 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 4075 MOZ_ASSERT(false, 4076 "Http2Session::RequestHead() " 4077 "should not be called after http/2 is setup"); 4078 return nullptr; 4079 } 4080 4081 uint32_t Http2Session::Http1xTransactionCount() { return 0; } 4082 4083 nsresult Http2Session::TakeSubTransactions( 4084 nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) { 4085 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 4086 // Generally this cannot be done with http/2 as transactions are 4087 // started right away. 4088 4089 LOG3(("Http2Session::TakeSubTransactions %p\n", this)); 4090 4091 if (mConcurrentHighWater > 0) return NS_ERROR_ALREADY_OPENED; 4092 4093 LOG3((" taking %d\n", mStreamTransactionHash.Count())); 4094 4095 for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) { 4096 outTransactions.AppendElement(iter.Key()); 4097 4098 // Removing the stream from the hash will delete the stream and drop the 4099 // transaction reference the hash held. 4100 iter.Remove(); 4101 } 4102 return NS_OK; 4103 } 4104 4105 //----------------------------------------------------------------------------- 4106 // Pass through methods of nsAHttpConnection 4107 //----------------------------------------------------------------------------- 4108 4109 nsAHttpConnection* Http2Session::Connection() { 4110 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 4111 return mConnection; 4112 } 4113 4114 nsresult Http2Session::OnHeadersAvailable(nsAHttpTransaction* transaction, 4115 nsHttpRequestHead* requestHead, 4116 nsHttpResponseHead* responseHead, 4117 bool* reset) { 4118 return NS_OK; 4119 } 4120 4121 bool Http2Session::IsReused() { 4122 if (!mConnection) { 4123 return false; 4124 } 4125 4126 return mConnection->IsReused(); 4127 } 4128 4129 nsresult Http2Session::PushBack(const char* buf, uint32_t len) { 4130 return mConnection->PushBack(buf, len); 4131 } 4132 4133 void Http2Session::SendPing() { 4134 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 4135 LOG(("Http2Session::SendPing %p mPreviousUsed=%d", this, mPreviousUsed)); 4136 4137 if (mPreviousUsed) { 4138 // alredy in progress, get out 4139 return; 4140 } 4141 4142 mPingSentEpoch = PR_IntervalNow(); 4143 if (!mPingSentEpoch) { 4144 mPingSentEpoch = 1; // avoid the 0 sentinel value 4145 } 4146 if (!mPingThreshold || 4147 (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) { 4148 mPreviousPingThreshold = mPingThreshold; 4149 mPreviousUsed = true; 4150 mPingThreshold = gHttpHandler->NetworkChangedTimeout(); 4151 // Reset mLastReadEpoch, so we can really check when do we got pong from the 4152 // server. 4153 mLastReadEpoch = 0; 4154 } 4155 GeneratePing(false); 4156 (void)ResumeRecv(); 4157 } 4158 4159 bool Http2Session::TestOriginFrame(const nsACString& hostname, int32_t port) { 4160 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 4161 MOZ_ASSERT(mOriginFrameActivated); 4162 4163 nsAutoCString key(hostname); 4164 key.Append(':'); 4165 key.AppendInt(port); 4166 bool rv = mOriginFrame.Get(key); 4167 LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv)); 4168 if (!rv && ConnectionInfo()) { 4169 // the SNI is also implicitly in this list, so consult that too 4170 nsHttpConnectionInfo* ci = ConnectionInfo(); 4171 rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && 4172 (port == ci->OriginPort()); 4173 LOG3(("TestOriginFrame() %p sni test %d\n", this, rv)); 4174 } 4175 return rv; 4176 } 4177 4178 bool Http2Session::TestJoinConnection(const nsACString& hostname, 4179 int32_t port) { 4180 return RealJoinConnection(hostname, port, true); 4181 } 4182 4183 bool Http2Session::JoinConnection(const nsACString& hostname, int32_t port) { 4184 return RealJoinConnection(hostname, port, false); 4185 } 4186 4187 bool Http2Session::RealJoinConnection(const nsACString& hostname, int32_t port, 4188 bool justKidding) { 4189 if (!mConnection || mClosed || mShouldGoAway) { 4190 return false; 4191 } 4192 4193 nsHttpConnectionInfo* ci = ConnectionInfo(); 4194 if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && 4195 (port == ci->OriginPort())) { 4196 return true; 4197 } 4198 4199 if (!mReceivedSettings) { 4200 return false; 4201 } 4202 4203 if (mOriginFrameActivated) { 4204 bool originFrameResult = TestOriginFrame(hostname, port); 4205 if (!originFrameResult) { 4206 return false; 4207 } 4208 } else { 4209 LOG3(("JoinConnection %p no origin frame check used.\n", this)); 4210 } 4211 4212 nsAutoCString key(hostname); 4213 key.Append(':'); 4214 key.Append(justKidding ? 'k' : '.'); 4215 key.AppendInt(port); 4216 bool cachedResult; 4217 if (mJoinConnectionCache.Get(key, &cachedResult)) { 4218 LOG(("joinconnection [%p %s] %s result=%d cache\n", this, 4219 ConnectionInfo()->HashKey().get(), key.get(), cachedResult)); 4220 return cachedResult; 4221 } 4222 4223 nsresult rv; 4224 bool isJoined = false; 4225 4226 nsCOMPtr<nsITLSSocketControl> sslSocketControl; 4227 mConnection->GetTLSSocketControl(getter_AddRefs(sslSocketControl)); 4228 if (!sslSocketControl) { 4229 return false; 4230 } 4231 4232 // try all the coalescable versions we support. 4233 const SpdyInformation* info = gHttpHandler->SpdyInfo(); 4234 bool joinedReturn = false; 4235 if (StaticPrefs::network_http_http2_enabled()) { 4236 if (justKidding) { 4237 rv = sslSocketControl->TestJoinConnection(info->VersionString, hostname, 4238 port, &isJoined); 4239 } else { 4240 rv = sslSocketControl->JoinConnection(info->VersionString, hostname, port, 4241 &isJoined); 4242 } 4243 if (NS_SUCCEEDED(rv) && isJoined) { 4244 joinedReturn = true; 4245 } 4246 } 4247 4248 LOG(("joinconnection [%p %s] %s result=%d lookup\n", this, 4249 ConnectionInfo()->HashKey().get(), key.get(), joinedReturn)); 4250 mJoinConnectionCache.InsertOrUpdate(key, joinedReturn); 4251 if (!justKidding) { 4252 // cache a kidding entry too as this one is good for both 4253 nsAutoCString key2(hostname); 4254 key2.Append(':'); 4255 key2.Append('k'); 4256 key2.AppendInt(port); 4257 if (!mJoinConnectionCache.Get(key2)) { 4258 mJoinConnectionCache.InsertOrUpdate(key2, joinedReturn); 4259 } 4260 } 4261 return joinedReturn; 4262 } 4263 4264 void Http2Session::CurrentBrowserIdChanged(uint64_t id) { 4265 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 4266 4267 mCurrentBrowserId = id; 4268 4269 for (const auto& stream : mStreamTransactionHash.Values()) { 4270 stream->CurrentBrowserIdChanged(id); 4271 } 4272 } 4273 4274 void Http2Session::SetCleanShutdown(bool aCleanShutdown) { 4275 mCleanShutdown = aCleanShutdown; 4276 } 4277 4278 ExtendedCONNECTSupport Http2Session::GetExtendedCONNECTSupport() { 4279 LOG3( 4280 ("Http2Session::GetExtendedCONNECTSupport %p enable=%d peer allow=%d " 4281 "receved setting=%d", 4282 this, mEnableWebsockets, mPeerAllowsExtendedCONNECT, mReceivedSettings)); 4283 4284 if (!mEnableWebsockets || mClosed) { 4285 return ExtendedCONNECTSupport::NO_SUPPORT; 4286 } 4287 4288 if (mPeerAllowsExtendedCONNECT) { 4289 return ExtendedCONNECTSupport::SUPPORTED; 4290 } 4291 4292 if (!mReceivedSettings) { 4293 mHasTransactionWaitingForExtendedCONNECT = true; 4294 return ExtendedCONNECTSupport::UNSURE; 4295 } 4296 4297 return ExtendedCONNECTSupport::NO_SUPPORT; 4298 } 4299 4300 PRIntervalTime Http2Session::LastWriteTime() { 4301 return mConnection->LastWriteTime(); 4302 } 4303 4304 } // namespace net 4305 } // namespace mozilla