Http2StreamBase.cpp (47904B)
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 10 // Log on level :5, instead of default :4. 11 #undef LOG 12 #define LOG(args) LOG5(args) 13 #undef LOG_ENABLED 14 #define LOG_ENABLED() LOG5_ENABLED() 15 16 #include <algorithm> 17 18 #include "Http2Compression.h" 19 #include "Http2Session.h" 20 #include "Http2StreamBase.h" 21 #include "Http2Stream.h" 22 23 #include "mozilla/BasePrincipal.h" 24 #include "mozilla/Components.h" 25 #include "mozilla/StaticPrefs_network.h" 26 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h" 27 #include "nsHttp.h" 28 #include "nsHttpHandler.h" 29 #include "nsHttpRequestHead.h" 30 #include "nsIClassOfService.h" 31 #include "prnetdb.h" 32 33 namespace mozilla::net { 34 35 NS_IMPL_ADDREF(Http2StreamBase) 36 NS_IMETHODIMP_(MozExternalRefCountType) 37 Http2StreamBase::Release() { 38 nsrefcnt count; 39 MOZ_ASSERT(0 != mRefCnt, "dup release"); 40 count = --mRefCnt; 41 NS_LOG_RELEASE(this, count, "Http2StreamBase"); 42 if (0 == count) { 43 mRefCnt = 1; /* stablize */ 44 // it is essential that the stream be destroyed on the socket thread. 45 DeleteSelfOnSocketThread(); 46 return 0; 47 } 48 return count; 49 } 50 51 NS_IMPL_QUERY_INTERFACE0(Http2StreamBase) 52 53 class DeleteHttp2StreamBase : public Runnable { 54 public: 55 explicit DeleteHttp2StreamBase(Http2StreamBase* aStream) 56 : Runnable("net::DeleteHttp2StreamBase"), mStream(aStream) {} 57 58 NS_IMETHOD Run() override { 59 delete mStream; 60 return NS_OK; 61 } 62 63 private: 64 Http2StreamBase* mStream; 65 }; 66 67 void Http2StreamBase::DeleteSelfOnSocketThread() { 68 if (OnSocketThread()) { 69 delete this; 70 return; 71 } 72 73 nsCOMPtr<nsIEventTarget> sts = 74 mozilla::components::SocketTransport::Service(); 75 nsCOMPtr<nsIRunnable> event = new DeleteHttp2StreamBase(this); 76 (void)NS_WARN_IF( 77 NS_FAILED(sts->Dispatch(event.forget(), NS_DISPATCH_NORMAL))); 78 } 79 80 Http2StreamBase::Http2StreamBase(uint64_t aTransactionBrowserId, 81 Http2Session* session, int32_t priority, 82 uint64_t currentBrowserId) 83 : mSession( 84 do_GetWeakReference(static_cast<nsISupportsWeakReference*>(session))), 85 mRequestHeadersDone(0), 86 mOpenGenerated(0), 87 mAllHeadersReceived(0), 88 mQueued(0), 89 mInWriteQueue(0), 90 mInReadQueue(0), 91 mSocketTransport(session->SocketTransport()), 92 mCurrentBrowserId(currentBrowserId), 93 mTransactionBrowserId(aTransactionBrowserId), 94 mTxInlineFrameSize(Http2Session::kDefaultBufferSize), 95 mChunkSize(session->SendingChunkSize()), 96 mRequestBlockedOnRead(0), 97 mRecvdFin(0), 98 mReceivedData(0), 99 mRecvdReset(0), 100 mSentReset(0), 101 mCountAsActive(0), 102 mSentFin(0), 103 mSentWaitingFor(0), 104 mSetTCPSocketBuffer(0), 105 mBypassInputBuffer(0) { 106 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 107 108 LOG1(("Http2StreamBase::Http2StreamBase %p", this)); 109 110 mServerReceiveWindow = session->GetServerInitialStreamWindow(); 111 mClientReceiveWindow = session->PushAllowance(); 112 113 mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize); 114 115 static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority, 116 "Lowest Priority should be less than kNormalPriority"); 117 118 // values of priority closer to 0 are higher priority for the priority 119 // argument. This value is used as a group, which maps to a 120 // weight that is related to the nsISupportsPriority that we are given. 121 int32_t httpPriority; 122 if (priority >= nsISupportsPriority::PRIORITY_LOWEST) { 123 httpPriority = kWorstPriority; 124 } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) { 125 httpPriority = kBestPriority; 126 } else { 127 httpPriority = kNormalPriority + priority; 128 } 129 MOZ_ASSERT(httpPriority >= 0); 130 SetPriority(static_cast<uint32_t>(httpPriority)); 131 } 132 133 Http2StreamBase::~Http2StreamBase() { 134 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread()); 135 136 mStreamID = Http2Session::kDeadStreamID; 137 138 LOG3(("Http2StreamBase::~Http2StreamBase %p", this)); 139 } 140 141 already_AddRefed<Http2Session> Http2StreamBase::Session() { 142 RefPtr<Http2Session> session = do_QueryReferent(mSession); 143 return session.forget(); 144 } 145 146 // ReadSegments() is used to write data down the socket. Generally, HTTP 147 // request data is pulled from the approriate transaction and 148 // converted to HTTP/2 data. Sometimes control data like a window-update is 149 // generated instead. 150 151 nsresult Http2StreamBase::ReadSegments(nsAHttpSegmentReader* reader, 152 uint32_t count, uint32_t* countRead) { 153 LOG3(("Http2StreamBase %p ReadSegments reader=%p count=%d state=%x", this, 154 reader, count, mUpstreamState)); 155 RefPtr<Http2Session> session = Session(); 156 // Reader is nullptr when this is a push stream. 157 MOZ_DIAGNOSTIC_ASSERT(!reader || (reader == session) || 158 (IsTunnel() && NS_FAILED(Condition()))); 159 160 if (NS_FAILED(Condition())) { 161 return Condition(); 162 } 163 164 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 165 166 nsresult rv = NS_ERROR_UNEXPECTED; 167 mRequestBlockedOnRead = 0; 168 169 if (mRecvdFin || mRecvdReset) { 170 // Don't transmit any request frames if the peer cannot respond 171 LOG3( 172 ("Http2StreamBase %p ReadSegments request stream aborted due to" 173 " response side closure\n", 174 this)); 175 return NS_ERROR_ABORT; 176 } 177 178 // avoid runt chunks if possible by anticipating 179 // full data frames 180 if (count > (mChunkSize + 8)) { 181 uint32_t numchunks = count / (mChunkSize + 8); 182 count = numchunks * (mChunkSize + 8); 183 } 184 185 switch (mUpstreamState) { 186 case GENERATING_HEADERS: 187 case GENERATING_BODY: 188 case SENDING_BODY: 189 // Call into the HTTP Transaction to generate the HTTP request 190 // stream. That stream will show up in OnReadSegment(). 191 mSegmentReader = reader; 192 rv = CallToReadData(count, countRead); 193 mSegmentReader = nullptr; 194 195 LOG3(("Http2StreamBase::ReadSegments %p trans readsegments rv %" PRIx32 196 " read=%d\n", 197 this, static_cast<uint32_t>(rv), *countRead)); 198 199 // Check to see if the transaction's request could be written out now. 200 // If not, mark the stream for callback when writing can proceed. 201 if (NS_SUCCEEDED(rv) && mUpstreamState == GENERATING_HEADERS && 202 !mRequestHeadersDone) { 203 session->TransactionHasDataToWrite(this); 204 } 205 206 // mTxinlineFrameUsed represents any queued un-sent frame. It might 207 // be 0 if there is no such frame, which is not a gurantee that we 208 // don't have more request body to send - just that any data that was 209 // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is 210 // a queued, but complete, http/2 frame length. 211 212 // Mark that we are blocked on read if the http transaction needs to 213 // provide more of the request message body and there is nothing queued 214 // for writing 215 if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) { 216 LOG(("Http2StreamBase %p mRequestBlockedOnRead = 1", this)); 217 mRequestBlockedOnRead = 1; 218 } 219 220 // A transaction that had already generated its headers before it was 221 // queued at the session level (due to concurrency concerns) may not call 222 // onReadSegment off the ReadSegments() stack above. 223 224 // When mTransaction->ReadSegments returns NS_BASE_STREAM_WOULD_BLOCK it 225 // means it may have already finished providing all the request data 226 // necessary to generate open, calling OnReadSegment will drive sending 227 // the request; this may happen after dequeue of the stream. 228 229 if (mUpstreamState == GENERATING_HEADERS && 230 (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK)) { 231 LOG3(("Http2StreamBase %p ReadSegments forcing OnReadSegment call\n", 232 this)); 233 uint32_t wasted = 0; 234 mSegmentReader = reader; 235 nsresult rv2 = OnReadSegment("", 0, &wasted); 236 mSegmentReader = nullptr; 237 238 LOG3((" OnReadSegment returned 0x%08" PRIx32, 239 static_cast<uint32_t>(rv2))); 240 if (NS_SUCCEEDED(rv2)) { 241 mRequestBlockedOnRead = 0; 242 } 243 } 244 245 // If the sending flow control window is open (!mBlockedOnRwin) then 246 // continue sending the request 247 if (!mBlockedOnRwin && mOpenGenerated && !mTxInlineFrameUsed && 248 NS_SUCCEEDED(rv) && (!*countRead) && CloseSendStreamWhenDone()) { 249 MOZ_ASSERT(!mQueued); 250 MOZ_ASSERT(mRequestHeadersDone); 251 LOG3( 252 ("Http2StreamBase::ReadSegments %p 0x%X: Sending request data " 253 "complete, " 254 "mUpstreamState=%x\n", 255 this, mStreamID, mUpstreamState)); 256 if (mSentFin) { 257 ChangeState(UPSTREAM_COMPLETE); 258 } else { 259 GenerateDataFrameHeader(0, true); 260 ChangeState(SENDING_FIN_STREAM); 261 session->TransactionHasDataToWrite(this); 262 rv = NS_BASE_STREAM_WOULD_BLOCK; 263 } 264 } 265 break; 266 267 case SENDING_FIN_STREAM: 268 // We were trying to send the FIN-STREAM but were blocked from 269 // sending it out - try again. 270 if (!mSentFin) { 271 mSegmentReader = reader; 272 rv = TransmitFrame(nullptr, nullptr, false); 273 mSegmentReader = nullptr; 274 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, 275 "Transmit Frame should be all or nothing"); 276 if (NS_SUCCEEDED(rv)) ChangeState(UPSTREAM_COMPLETE); 277 } else { 278 rv = NS_OK; 279 mTxInlineFrameUsed = 0; // cancel fin data packet 280 ChangeState(UPSTREAM_COMPLETE); 281 } 282 283 *countRead = 0; 284 285 // don't change OK to WOULD BLOCK. we are really done sending if OK 286 break; 287 288 case UPSTREAM_COMPLETE: 289 *countRead = 0; 290 rv = NS_OK; 291 break; 292 293 default: 294 MOZ_ASSERT(false, "Http2StreamBase::ReadSegments unknown state"); 295 break; 296 } 297 298 return rv; 299 } 300 301 uint64_t Http2StreamBase::LocalUnAcked() { 302 // reduce unacked by the amount of undelivered data 303 // to help assert flow control 304 uint64_t undelivered = mSimpleBuffer.Available(); 305 306 if (undelivered > mLocalUnacked) { 307 return 0; 308 } 309 return mLocalUnacked - undelivered; 310 } 311 312 nsresult Http2StreamBase::BufferInput(uint32_t count, uint32_t* countWritten) { 313 char buf[SimpleBufferPage::kSimpleBufferPageSize]; 314 if (SimpleBufferPage::kSimpleBufferPageSize < count) { 315 count = SimpleBufferPage::kSimpleBufferPageSize; 316 } 317 318 mBypassInputBuffer = 1; 319 nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten); 320 mBypassInputBuffer = 0; 321 322 if (NS_SUCCEEDED(rv)) { 323 rv = mSimpleBuffer.Write(buf, *countWritten); 324 if (NS_FAILED(rv)) { 325 MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY); 326 return NS_ERROR_OUT_OF_MEMORY; 327 } 328 } 329 return rv; 330 } 331 332 bool Http2StreamBase::DeferCleanup(nsresult status) { 333 // do not cleanup a stream that has data buffered for the transaction 334 return (NS_SUCCEEDED(status) && mSimpleBuffer.Available()); 335 } 336 337 // WriteSegments() is used to read data off the socket. Generally this is 338 // just a call through to the associated nsHttpTransaction for this stream 339 // for the remaining data bytes indicated by the current DATA frame. 340 341 nsresult Http2StreamBase::WriteSegments(nsAHttpSegmentWriter* writer, 342 uint32_t count, 343 uint32_t* countWritten) { 344 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 345 MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); 346 347 LOG3(("Http2StreamBase::WriteSegments %p count=%d state=%x", this, count, 348 mUpstreamState)); 349 350 mSegmentWriter = writer; 351 nsresult rv = CallToWriteData(count, countWritten); 352 353 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 354 // consuming transaction won't take data. but we need to read it into a 355 // buffer so that it won't block other streams. but we should not advance 356 // the flow control window so that we'll eventually push back on the sender. 357 rv = BufferInput(count, countWritten); 358 LOG3(("Http2StreamBase::WriteSegments %p Buffered %" PRIX32 " %d\n", this, 359 static_cast<uint32_t>(rv), *countWritten)); 360 } 361 362 LOG3(("Http2StreamBase::WriteSegments %" PRIX32 "", 363 static_cast<uint32_t>(rv))); 364 mSegmentWriter = nullptr; 365 return rv; 366 } 367 368 nsresult Http2StreamBase::ParseHttpRequestHeaders(const char* buf, 369 uint32_t avail, 370 uint32_t* countUsed) { 371 // Returns NS_OK even if the headers are incomplete 372 // set mRequestHeadersDone flag if they are complete 373 374 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 375 MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS); 376 MOZ_ASSERT(!mRequestHeadersDone); 377 378 LOG3(("Http2StreamBase::ParseHttpRequestHeaders %p avail=%d state=%x", this, 379 avail, mUpstreamState)); 380 381 mFlatHttpRequestHeaders.Append(buf, avail); 382 383 // We can use the simple double crlf because firefox is the 384 // only client we are parsing 385 int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); 386 387 if (endHeader == kNotFound) { 388 // We don't have all the headers yet 389 LOG3( 390 ("Http2StreamBase::ParseHttpRequestHeaders %p " 391 "Need more header bytes. Len = %zd", 392 this, mFlatHttpRequestHeaders.Length())); 393 *countUsed = avail; 394 return NS_OK; 395 } 396 397 // We have recvd all the headers, trim the local 398 // buffer of the final empty line, and set countUsed to reflect 399 // the whole header has been consumed. 400 uint32_t oldLen = mFlatHttpRequestHeaders.Length(); 401 mFlatHttpRequestHeaders.SetLength(endHeader + 2); 402 *countUsed = avail - (oldLen - endHeader) + 4; 403 mRequestHeadersDone = 1; 404 return NS_OK; 405 } 406 407 // This is really a headers frame, but open is pretty clear from a workflow pov 408 nsresult Http2StreamBase::GenerateOpen() { 409 // It is now OK to assign a streamID that we are assured will 410 // be monotonically increasing amongst new streams on this 411 // session 412 RefPtr<Http2Session> session = Session(); 413 mStreamID = session->RegisterStreamID(this); 414 MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd"); 415 MOZ_ASSERT(!mOpenGenerated); 416 417 mOpenGenerated = 1; 418 419 LOG3(("Http2StreamBase %p Stream ID 0x%X [session=%p]\n", this, mStreamID, 420 session.get())); 421 422 if (mStreamID >= 0x80000000) { 423 // streamID must fit in 31 bits. Evading This is theoretically possible 424 // because stream ID assignment is asynchronous to stream creation 425 // because of the protocol requirement that the new stream ID 426 // be monotonically increasing. In reality this is really not possible 427 // because new streams stop being added to a session with millions of 428 // IDs still available and no race condition is going to bridge that gap; 429 // so we can be comfortable on just erroring out for correctness in that 430 // case. 431 LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); 432 return NS_ERROR_UNEXPECTED; 433 } 434 435 // Now we need to convert the flat http headers into a set 436 // of HTTP/2 headers by writing to mTxInlineFrame{sz} 437 438 nsCString compressedData; 439 uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY; 440 441 nsresult rv = GenerateHeaders(compressedData, firstFrameFlags); 442 if (NS_FAILED(rv)) { 443 return rv; 444 } 445 446 if (firstFrameFlags & Http2Session::kFlag_END_STREAM) { 447 SetSentFin(true); 448 } 449 450 // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it 451 // exceeds the 2^14-1 limit for 1 frame. Do it by inserting header size gaps 452 // in the existing frame for the new headers and for the first one a priority 453 // field. There is no question this is ugly, but a 16KB HEADERS frame should 454 // be a long tail event, so this is really just for correctness and a nop in 455 // the base case. 456 // 457 458 MOZ_ASSERT(!mTxInlineFrameUsed); 459 460 uint32_t dataLength = compressedData.Length(); 461 uint32_t maxFrameData = 462 Http2Session::kMaxFrameData - 5; // 5 bytes for priority 463 uint32_t numFrames = 1; 464 465 if (dataLength > maxFrameData) { 466 numFrames += 467 ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) / 468 Http2Session::kMaxFrameData; 469 MOZ_ASSERT(numFrames > 1); 470 } 471 472 // note that we could still have 1 frame for 0 bytes of data. that's ok. 473 474 uint32_t messageSize = dataLength; 475 messageSize += Http2Session::kFrameHeaderBytes + 476 5; // frame header + priority overhead in HEADERS frame 477 messageSize += (numFrames - 1) * 478 Http2Session::kFrameHeaderBytes; // frame header overhead in 479 // CONTINUATION frames 480 481 EnsureBuffer(mTxInlineFrame, messageSize, mTxInlineFrameUsed, 482 mTxInlineFrameSize); 483 484 mTxInlineFrameUsed += messageSize; 485 UpdatePriorityDependency(); 486 LOG1( 487 ("Http2StreamBase %p Generating %d bytes of HEADERS for stream 0x%X with " 488 "priority weight %u dep 0x%X frames %u\n", 489 this, mTxInlineFrameUsed, mStreamID, mPriorityWeight, 490 mPriorityDependency, numFrames)); 491 492 uint32_t outputOffset = 0; 493 uint32_t compressedDataOffset = 0; 494 for (uint32_t idx = 0; idx < numFrames; ++idx) { 495 uint32_t flags, frameLen; 496 bool lastFrame = (idx == numFrames - 1); 497 498 flags = 0; 499 frameLen = maxFrameData; 500 if (!idx) { 501 flags |= firstFrameFlags; 502 // Only the first frame needs the 4-byte offset 503 maxFrameData = Http2Session::kMaxFrameData; 504 } 505 if (lastFrame) { 506 frameLen = dataLength; 507 flags |= Http2Session::kFlag_END_HEADERS; 508 } 509 dataLength -= frameLen; 510 511 session->CreateFrameHeader(mTxInlineFrame.get() + outputOffset, 512 frameLen + (idx ? 0 : 5), 513 (idx) ? Http2Session::FRAME_TYPE_CONTINUATION 514 : Http2Session::FRAME_TYPE_HEADERS, 515 flags, mStreamID); 516 outputOffset += Http2Session::kFrameHeaderBytes; 517 518 if (!idx) { 519 uint32_t wireDep = PR_htonl(mPriorityDependency); 520 memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4); 521 memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1); 522 outputOffset += 5; 523 } 524 525 memcpy(mTxInlineFrame.get() + outputOffset, 526 compressedData.BeginReading() + compressedDataOffset, frameLen); 527 compressedDataOffset += frameLen; 528 outputOffset += frameLen; 529 } 530 531 mFlatHttpRequestHeaders.Truncate(); 532 533 return NS_OK; 534 } 535 536 void Http2StreamBase::AdjustInitialWindow() { 537 // The default initial_window is sized for pushed streams. When we 538 // generate a client pulled stream we want to disable flow control for 539 // the stream with a window update. Do the same for pushed streams 540 // when they connect to a pull. 541 542 uint32_t wireStreamId = GetWireStreamId(); 543 if (wireStreamId == 0) { 544 return; 545 } 546 547 // right now mClientReceiveWindow is the lower push limit 548 // bump it up to the pull limit set by the channel or session 549 // don't allow windows less than push 550 uint32_t bump = 0; 551 RefPtr<Http2Session> session = Session(); 552 nsHttpTransaction* trans = HttpTransaction(); 553 if (trans && trans->InitialRwin()) { 554 bump = (trans->InitialRwin() > mClientReceiveWindow) 555 ? (trans->InitialRwin() - mClientReceiveWindow) 556 : 0; 557 } else { 558 MOZ_ASSERT(session->InitialRwin() >= mClientReceiveWindow); 559 bump = session->InitialRwin() - mClientReceiveWindow; 560 } 561 562 LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n", this, 563 wireStreamId, bump)); 564 if (!bump) { // nothing to do 565 return; 566 } 567 568 EnsureBuffer(mTxInlineFrame, 569 mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4, 570 mTxInlineFrameUsed, mTxInlineFrameSize); 571 uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed; 572 mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4; 573 574 session->CreateFrameHeader(packet, 4, Http2Session::FRAME_TYPE_WINDOW_UPDATE, 575 0, wireStreamId); 576 577 mClientReceiveWindow += bump; 578 bump = PR_htonl(bump); 579 memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4); 580 } 581 582 void Http2StreamBase::UpdateTransportReadEvents(uint32_t count) { 583 mTotalRead += count; 584 if (!mSocketTransport) { 585 return; 586 } 587 588 if (Transaction()) { 589 Transaction()->OnTransportStatus(mSocketTransport, 590 NS_NET_STATUS_RECEIVING_FROM, mTotalRead); 591 } 592 } 593 594 void Http2StreamBase::UpdateTransportSendEvents(uint32_t count) { 595 mTotalSent += count; 596 597 // Setting the TCP send buffer, introduced in 598 // https://bugzilla.mozilla.org/show_bug.cgi?id=790184, which the following 599 // comment refers to, is being removed once we verify no increases in error 600 // rate. 601 // 602 // normally on non-windows platform we use TCP autotuning for 603 // the socket buffers, and this works well (managing enough 604 // buffers for BDP while conserving memory) for HTTP even when 605 // it creates really deep queues. However this 'buffer bloat' is 606 // a problem for http/2 because it ruins the low latency properties 607 // necessary for PING and cancel to work meaningfully. 608 609 // If this stream represents a large upload, disable autotuning for 610 // the session and cap the send buffers by default at 128KB. 611 // (10Mbit/sec @ 100ms) 612 // 613 uint32_t bufferSize = gHttpHandler->SpdySendBufferSize(); 614 if (StaticPrefs::network_http_http2_send_buffer_size() > 0 && 615 (mTotalSent > bufferSize) && !mSetTCPSocketBuffer) { 616 mSetTCPSocketBuffer = 1; 617 mSocketTransport->SetSendBufferSize(bufferSize); 618 } 619 620 if ((mUpstreamState != SENDING_FIN_STREAM) && Transaction()) { 621 Transaction()->OnTransportStatus(mSocketTransport, NS_NET_STATUS_SENDING_TO, 622 mTotalSent); 623 } 624 625 if (!mSentWaitingFor && !mRequestBodyLenRemaining) { 626 mSentWaitingFor = 1; 627 if (Transaction()) { 628 Transaction()->OnTransportStatus(mSocketTransport, 629 NS_NET_STATUS_WAITING_FOR, 0); 630 } 631 } 632 } 633 634 nsresult Http2StreamBase::TransmitFrame(const char* buf, uint32_t* countUsed, 635 bool forceCommitment) { 636 // If TransmitFrame returns SUCCESS than all the data is sent (or at least 637 // buffered at the session level), if it returns WOULD_BLOCK then none of 638 // the data is sent. 639 640 // You can call this function with no data and no out parameter in order to 641 // flush internal buffers that were previously blocked on writing. You can 642 // of course feed new data to it as well. 643 644 LOG3(("Http2StreamBase::TransmitFrame %p inline=%d stream=%d", this, 645 mTxInlineFrameUsed, mTxStreamFrameSize)); 646 if (countUsed) *countUsed = 0; 647 648 if (!mTxInlineFrameUsed) { 649 MOZ_ASSERT(!buf); 650 return NS_OK; 651 } 652 653 MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit"); 654 MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader"); 655 MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed), 656 "TransmitFrame arguments inconsistent"); 657 658 uint32_t transmittedCount; 659 nsresult rv; 660 RefPtr<Http2Session> session = Session(); 661 662 // In the (relatively common) event that we have a small amount of data 663 // split between the inlineframe and the streamframe, then move the stream 664 // data into the inlineframe via copy in order to coalesce into one write. 665 // Given the interaction with ssl this is worth the small copy cost. 666 if (mTxStreamFrameSize && mTxInlineFrameUsed && 667 mTxStreamFrameSize < Http2Session::kDefaultBufferSize && 668 mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) { 669 LOG3(("Coalesce Transmit")); 670 memcpy(&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize); 671 if (countUsed) *countUsed += mTxStreamFrameSize; 672 mTxInlineFrameUsed += mTxStreamFrameSize; 673 mTxStreamFrameSize = 0; 674 } 675 676 rv = mSegmentReader->CommitToSegmentSize( 677 mTxStreamFrameSize + mTxInlineFrameUsed, forceCommitment); 678 679 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 680 MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK"); 681 session->TransactionHasDataToWrite(this); 682 } 683 if (NS_FAILED(rv)) { // this will include WOULD_BLOCK 684 return rv; 685 } 686 687 // This function calls mSegmentReader->OnReadSegment to report the actual 688 // http/2 bytes through to the session object and then the HttpConnection 689 // which calls the socket write function. It will accept all of the inline and 690 // stream data because of the above 'commitment' even if it has to buffer 691 692 rv = session->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()), 693 mTxInlineFrameUsed, &transmittedCount); 694 LOG3( 695 ("Http2StreamBase::TransmitFrame for inline BufferOutput session=%p " 696 "stream=%p result %" PRIx32 " len=%d", 697 session.get(), this, static_cast<uint32_t>(rv), transmittedCount)); 698 699 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, 700 "inconsistent inline commitment result"); 701 702 if (NS_FAILED(rv)) return rv; 703 704 MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed, 705 "inconsistent inline commitment count"); 706 707 Http2Session::LogIO(session, this, "Writing from Inline Buffer", 708 reinterpret_cast<char*>(mTxInlineFrame.get()), 709 transmittedCount); 710 711 if (mTxStreamFrameSize) { 712 if (!buf) { 713 // this cannot happen 714 MOZ_ASSERT(false, 715 "Stream transmit with null buf argument to " 716 "TransmitFrame()"); 717 LOG3(("Stream transmit with null buf argument to TransmitFrame()\n")); 718 return NS_ERROR_UNEXPECTED; 719 } 720 721 // If there is already data buffered, just add to that to form 722 // a single TLS Application Data Record - otherwise skip the memcpy 723 if (session->AmountOfOutputBuffered()) { 724 rv = session->BufferOutput(buf, mTxStreamFrameSize, &transmittedCount); 725 } else { 726 rv = session->OnReadSegment(buf, mTxStreamFrameSize, &transmittedCount); 727 } 728 729 LOG3( 730 ("Http2StreamBase::TransmitFrame for regular session=%p " 731 "stream=%p result %" PRIx32 " len=%d", 732 session.get(), this, static_cast<uint32_t>(rv), transmittedCount)); 733 734 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, 735 "inconsistent stream commitment result"); 736 737 if (NS_FAILED(rv)) return rv; 738 739 MOZ_ASSERT(transmittedCount == mTxStreamFrameSize, 740 "inconsistent stream commitment count"); 741 742 Http2Session::LogIO(session, this, "Writing from Transaction Buffer", buf, 743 transmittedCount); 744 745 *countUsed += mTxStreamFrameSize; 746 } 747 748 if (!mAttempting0RTT) { 749 session->FlushOutputQueue(); 750 } 751 752 // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 753 UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); 754 755 mTxInlineFrameUsed = 0; 756 mTxStreamFrameSize = 0; 757 758 return NS_OK; 759 } 760 761 void Http2StreamBase::ChangeState(enum upstreamStateType newState) { 762 LOG3(("Http2StreamBase::ChangeState() %p from %X to %X", this, mUpstreamState, 763 newState)); 764 mUpstreamState = newState; 765 } 766 767 void Http2StreamBase::GenerateDataFrameHeader(uint32_t dataLength, 768 bool lastFrame) { 769 LOG3(("Http2StreamBase::GenerateDataFrameHeader %p len=%d last=%d", this, 770 dataLength, lastFrame)); 771 772 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 773 MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty"); 774 MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty"); 775 776 uint8_t frameFlags = 0; 777 if (lastFrame) { 778 frameFlags |= Http2Session::kFlag_END_STREAM; 779 if (dataLength) SetSentFin(true); 780 } 781 782 RefPtr<Http2Session> session = Session(); 783 session->CreateFrameHeader(mTxInlineFrame.get(), dataLength, 784 Http2Session::FRAME_TYPE_DATA, frameFlags, 785 mStreamID); 786 787 mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes; 788 mTxStreamFrameSize = dataLength; 789 } 790 791 // ConvertResponseHeaders is used to convert the response headers 792 // into HTTP/1 format and report some telemetry 793 nsresult Http2StreamBase::ConvertResponseHeaders( 794 Http2Decompressor* decompressor, nsACString& aHeadersIn, 795 nsACString& aHeadersOut, int32_t& httpResponseCode) { 796 nsresult rv = decompressor->DecodeHeaderBlock( 797 reinterpret_cast<const uint8_t*>(aHeadersIn.BeginReading()), 798 aHeadersIn.Length(), aHeadersOut, false); 799 if (NS_FAILED(rv)) { 800 LOG3(("Http2StreamBase::ConvertResponseHeaders %p decode Error\n", this)); 801 return rv; 802 } 803 804 nsAutoCString statusString; 805 decompressor->GetStatus(statusString); 806 if (statusString.IsEmpty()) { 807 LOG3(("Http2StreamBase::ConvertResponseHeaders %p Error - no status\n", 808 this)); 809 return NS_ERROR_ILLEGAL_VALUE; 810 } 811 812 nsresult errcode; 813 httpResponseCode = statusString.ToInteger(&errcode); 814 815 // Ensure the :status is just an HTTP status code 816 // https://tools.ietf.org/html/rfc7540#section-8.1.2.4 817 // https://bugzilla.mozilla.org/show_bug.cgi?id=1352146 818 nsAutoCString parsedStatusString; 819 parsedStatusString.AppendInt(httpResponseCode); 820 if (!parsedStatusString.Equals(statusString)) { 821 LOG3( 822 ("Http2StreamBase::ConvertResposeHeaders %p status %s is not just a " 823 "code", 824 this, statusString.BeginReading())); 825 // Results in stream reset with PROTOCOL_ERROR 826 return NS_ERROR_ILLEGAL_VALUE; 827 } 828 829 LOG3(("Http2StreamBase::ConvertResponseHeaders %p response code %d\n", this, 830 httpResponseCode)); 831 832 if (httpResponseCode == 421) { 833 // Origin Frame requires 421 to remove this origin from the origin set 834 RefPtr<Http2Session> session = Session(); 835 session->Received421(ConnectionInfo()); 836 } 837 838 // The decoding went ok. Now we can customize and clean up. 839 840 aHeadersIn.Truncate(); 841 aHeadersOut.AppendLiteral("X-Firefox-Spdy: h2"); 842 aHeadersOut.AppendLiteral("\r\n\r\n"); 843 LOG(("decoded response headers are:\n%s", aHeadersOut.BeginReading())); 844 HandleResponseHeaders(aHeadersOut, httpResponseCode); 845 846 return NS_OK; 847 } 848 849 nsresult Http2StreamBase::ConvertResponseTrailers( 850 Http2Decompressor* decompressor, nsACString& aTrailersIn) { 851 LOG3(("Http2StreamBase::ConvertResponseTrailers %p", this)); 852 nsAutoCString flatTrailers; 853 854 nsresult rv = decompressor->DecodeHeaderBlock( 855 reinterpret_cast<const uint8_t*>(aTrailersIn.BeginReading()), 856 aTrailersIn.Length(), flatTrailers, false); 857 if (NS_FAILED(rv)) { 858 LOG3(("Http2StreamBase::ConvertResponseTrailers %p decode Error", this)); 859 return rv; 860 } 861 862 nsHttpTransaction* trans = HttpTransaction(); 863 if (trans) { 864 trans->SetHttpTrailers(flatTrailers); 865 } else { 866 LOG3(("Http2StreamBase::ConvertResponseTrailers %p no trans", this)); 867 } 868 869 return NS_OK; 870 } 871 872 void Http2StreamBase::SetResponseIsComplete() { 873 nsHttpTransaction* trans = HttpTransaction(); 874 if (trans) { 875 trans->SetResponseIsComplete(); 876 } 877 } 878 879 void Http2StreamBase::SetAllHeadersReceived() { 880 if (mAllHeadersReceived) { 881 return; 882 } 883 884 if (mState == RESERVED_BY_REMOTE) { 885 // pushed streams needs to wait until headers have 886 // arrived to open up their window 887 LOG3( 888 ("Http2StreamBase::SetAllHeadersReceived %p state OPEN from reserved\n", 889 this)); 890 mState = OPEN; 891 AdjustInitialWindow(); 892 } 893 894 mAllHeadersReceived = 1; 895 } 896 897 bool Http2StreamBase::AllowFlowControlledWrite() { 898 RefPtr<Http2Session> session = Session(); 899 return (session->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0); 900 } 901 902 void Http2StreamBase::UpdateServerReceiveWindow(int32_t delta) { 903 mServerReceiveWindow += delta; 904 905 if (mBlockedOnRwin && AllowFlowControlledWrite()) { 906 LOG3( 907 ("Http2StreamBase::UpdateServerReceived UnPause %p 0x%X " 908 "Open stream window\n", 909 this, mStreamID)); 910 RefPtr<Http2Session> session = Session(); 911 session->TransactionHasDataToWrite(this); 912 } 913 } 914 915 void Http2StreamBase::SetPriority(uint32_t newPriority) { 916 int32_t httpPriority = static_cast<int32_t>(newPriority); 917 if (httpPriority > kWorstPriority) { 918 httpPriority = kWorstPriority; 919 } else if (httpPriority < kBestPriority) { 920 httpPriority = kBestPriority; 921 } 922 mRFC7540Priority = static_cast<uint32_t>(httpPriority); 923 mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) - 924 (httpPriority - kNormalPriority); 925 926 mPriorityDependency = 0; // maybe adjusted later 927 } 928 929 void Http2StreamBase::SetPriorityDependency(uint32_t newPriority, 930 uint32_t newDependency) { 931 SetPriority(newPriority); 932 mPriorityDependency = newDependency; 933 } 934 935 static uint32_t GetPriorityDependencyFromTransaction(nsHttpTransaction* trans) { 936 MOZ_ASSERT(trans); 937 938 uint32_t classFlags = trans->GetClassOfService().Flags(); 939 940 if (classFlags & nsIClassOfService::UrgentStart) { 941 return Http2Session::kUrgentStartGroupID; 942 } 943 944 if (classFlags & nsIClassOfService::Leader) { 945 return Http2Session::kLeaderGroupID; 946 } 947 948 if (classFlags & nsIClassOfService::Follower) { 949 return Http2Session::kFollowerGroupID; 950 } 951 952 if (classFlags & nsIClassOfService::Speculative) { 953 return Http2Session::kSpeculativeGroupID; 954 } 955 956 if (classFlags & nsIClassOfService::Background) { 957 return Http2Session::kBackgroundGroupID; 958 } 959 960 if (classFlags & nsIClassOfService::Unblocked) { 961 return Http2Session::kOtherGroupID; 962 } 963 964 return Http2Session::kFollowerGroupID; // unmarked followers 965 } 966 967 void Http2StreamBase::UpdatePriorityDependency() { 968 RefPtr<Http2Session> session = Session(); 969 if (!session->UseH2Deps()) { 970 return; 971 } 972 973 nsHttpTransaction* trans = HttpTransaction(); 974 if (!trans) { 975 return; 976 } 977 978 // we create 6 fake dependency streams per session, 979 // these streams are never opened with HEADERS. our first opened stream is 0xd 980 // 3 depends 0, weight 200, leader class (kLeaderGroupID) 981 // 5 depends 0, weight 100, other (kOtherGroupID) 982 // 7 depends 0, weight 0, background (kBackgroundGroupID) 983 // 9 depends 7, weight 0, speculative (kSpeculativeGroupID) 984 // b depends 3, weight 0, follower class (kFollowerGroupID) 985 // d depends 0, weight 240, urgent-start class (kUrgentStartGroupID) 986 // 987 // streams for leaders (html, js, css) depend on 3 988 // streams for folowers (images) depend on b 989 // default streams (xhr, async js) depend on 5 990 // explicit bg streams (beacon, etc..) depend on 7 991 // spculative bg streams depend on 9 992 // urgent-start streams depend on d 993 994 mPriorityDependency = GetPriorityDependencyFromTransaction(trans); 995 996 if (StaticPrefs::network_http_active_tab_priority() && 997 mTransactionBrowserId != mCurrentBrowserId && 998 mPriorityDependency != Http2Session::kUrgentStartGroupID) { 999 LOG3( 1000 ("Http2StreamBase::UpdatePriorityDependency %p " 1001 " depends on background group for trans %p\n", 1002 this, trans)); 1003 mPriorityDependency = Http2Session::kBackgroundGroupID; 1004 1005 nsHttp::NotifyActiveTabLoadOptimization(); 1006 } 1007 1008 LOG1( 1009 ("Http2StreamBase::UpdatePriorityDependency %p " 1010 "depends on stream 0x%X\n", 1011 this, mPriorityDependency)); 1012 } 1013 1014 void Http2StreamBase::CurrentBrowserIdChanged(uint64_t id) { 1015 if (!mStreamID) { 1016 // For pushed streams, we ignore the direct call from the session and 1017 // instead let it come to the internal function from the pushed stream, so 1018 // we don't accidentally send two PRIORITY frames for the same stream. 1019 return; 1020 } 1021 1022 CurrentBrowserIdChangedInternal(id); 1023 } 1024 1025 void Http2StreamBase::CurrentBrowserIdChangedInternal(uint64_t id) { 1026 MOZ_ASSERT(StaticPrefs::network_http_active_tab_priority()); 1027 RefPtr<Http2Session> session = Session(); 1028 LOG3( 1029 ("Http2StreamBase::CurrentBrowserIdChangedInternal " 1030 "%p browserId=%" PRIx64 "\n", 1031 this, id)); 1032 1033 mCurrentBrowserId = id; 1034 1035 // Urgent start takes an absolute precedence, so don't 1036 // change mPriorityDependency here. 1037 if (mPriorityDependency == Http2Session::kUrgentStartGroupID) { 1038 return; 1039 } 1040 1041 if (session->UseH2Deps()) { 1042 UpdatePriorityRFC7540(session); 1043 } else { 1044 UpdatePriority(session); 1045 } 1046 } 1047 1048 void Http2StreamBase::UpdatePriority(Http2Session* session) { 1049 MOZ_ASSERT(!session->UseH2Deps()); 1050 bool isInBackground = mTransactionBrowserId != mCurrentBrowserId; 1051 1052 if (isInBackground) { 1053 LOG3( 1054 ("Http2StreamBase::CurrentBrowserIdChangedInternal %p " 1055 "move into background group.\n", 1056 this)); 1057 1058 nsHttp::NotifyActiveTabLoadOptimization(); 1059 } 1060 1061 if (!StaticPrefs::network_http_http2_priority_updates()) { 1062 return; 1063 } 1064 1065 nsHttpTransaction* trans = HttpTransaction(); 1066 if (!trans) { 1067 return; 1068 } 1069 1070 uint8_t urgency = nsHttpHandler::UrgencyFromCoSFlags( 1071 trans->GetClassOfService().Flags(), trans->Priority()); 1072 bool incremental = trans->GetClassOfService().Incremental(); 1073 uint32_t streamID = GetWireStreamId(); 1074 1075 // If the tab is in the background 1076 // we can probably lower the priority of this request by 1. 1077 // (to get lower priority we increase urgency). 1078 if (isInBackground && urgency < 6) { 1079 urgency++; 1080 } 1081 1082 if (streamID) { 1083 session->SendPriorityUpdateFrame(streamID, urgency, incremental); 1084 } 1085 } 1086 1087 void Http2StreamBase::UpdatePriorityRFC7540(Http2Session* session) { 1088 MOZ_ASSERT(session->UseH2Deps()); 1089 if (mTransactionBrowserId != mCurrentBrowserId) { 1090 mPriorityDependency = Http2Session::kBackgroundGroupID; 1091 LOG3( 1092 ("Http2StreamBase::CurrentBrowserIdChangedInternal %p " 1093 "move into background group.\n", 1094 this)); 1095 1096 nsHttp::NotifyActiveTabLoadOptimization(); 1097 } else { 1098 nsHttpTransaction* trans = HttpTransaction(); 1099 if (!trans) { 1100 return; 1101 } 1102 1103 mPriorityDependency = GetPriorityDependencyFromTransaction(trans); 1104 LOG3( 1105 ("Http2StreamBase::CurrentBrowserIdChangedInternal %p " 1106 "depends on stream 0x%X\n", 1107 this, mPriorityDependency)); 1108 } 1109 1110 uint32_t modifyStreamID = GetWireStreamId(); 1111 1112 if (modifyStreamID) { 1113 session->SendPriorityFrame(modifyStreamID, mPriorityDependency, 1114 mPriorityWeight); 1115 } 1116 } 1117 1118 void Http2StreamBase::SetRecvdFin(bool aStatus) { 1119 mRecvdFin = aStatus ? 1 : 0; 1120 if (!aStatus) return; 1121 1122 if (mState == OPEN || mState == RESERVED_BY_REMOTE) { 1123 mState = CLOSED_BY_REMOTE; 1124 } else if (mState == CLOSED_BY_LOCAL) { 1125 mState = CLOSED; 1126 } 1127 } 1128 1129 void Http2StreamBase::SetSentFin(bool aStatus) { 1130 mSentFin = aStatus ? 1 : 0; 1131 if (!aStatus) return; 1132 1133 if (mState == OPEN || mState == RESERVED_BY_REMOTE) { 1134 mState = CLOSED_BY_LOCAL; 1135 } else if (mState == CLOSED_BY_REMOTE) { 1136 mState = CLOSED; 1137 } 1138 } 1139 1140 void Http2StreamBase::SetRecvdReset(bool aStatus) { 1141 mRecvdReset = aStatus ? 1 : 0; 1142 if (!aStatus) return; 1143 mState = CLOSED; 1144 } 1145 1146 void Http2StreamBase::SetSentReset(bool aStatus) { 1147 mSentReset = aStatus ? 1 : 0; 1148 if (!aStatus) return; 1149 mState = CLOSED; 1150 } 1151 1152 //----------------------------------------------------------------------------- 1153 // nsAHttpSegmentReader 1154 //----------------------------------------------------------------------------- 1155 1156 nsresult Http2StreamBase::OnReadSegment(const char* buf, uint32_t count, 1157 uint32_t* countRead) { 1158 LOG3(("Http2StreamBase::OnReadSegment %p count=%d state=%x", this, count, 1159 mUpstreamState)); 1160 1161 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1162 if (!mSegmentReader) { 1163 return NS_BASE_STREAM_WOULD_BLOCK; 1164 } 1165 1166 nsresult rv = NS_ERROR_UNEXPECTED; 1167 uint32_t dataLength; 1168 RefPtr<Http2Session> session = Session(); 1169 1170 switch (mUpstreamState) { 1171 case GENERATING_HEADERS: 1172 // The buffer is the HTTP request stream, including at least part of the 1173 // HTTP request header. This state's job is to build a HEADERS frame 1174 // from the header information. count is the number of http bytes 1175 // available (which may include more than the header), and in countRead we 1176 // return the number of those bytes that we consume (i.e. the portion that 1177 // are header bytes) 1178 1179 if (!mRequestHeadersDone) { 1180 if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) { 1181 return rv; 1182 } 1183 } 1184 1185 if (mRequestHeadersDone && !mOpenGenerated) { 1186 if (!session->TryToActivate(this)) { 1187 LOG3( 1188 ("Http2StreamBase::OnReadSegment %p cannot activate now. " 1189 "queued.\n", 1190 this)); 1191 return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; 1192 } 1193 if (NS_FAILED(rv = GenerateOpen())) { 1194 return rv; 1195 } 1196 } 1197 1198 LOG3( 1199 ("ParseHttpRequestHeaders %p used %d of %d. " 1200 "requestheadersdone = %d mOpenGenerated = %d\n", 1201 this, *countRead, count, mRequestHeadersDone, mOpenGenerated)); 1202 if (mOpenGenerated) { 1203 SetHTTPState(OPEN); 1204 AdjustInitialWindow(); 1205 // This version of TransmitFrame cannot block 1206 rv = TransmitFrame(nullptr, nullptr, true); 1207 ChangeState(GENERATING_BODY); 1208 break; 1209 } 1210 MOZ_ASSERT(*countRead == count, 1211 "Header parsing not complete but unused data"); 1212 break; 1213 1214 case GENERATING_BODY: 1215 // if there is session flow control and either the stream window is active 1216 // and exhaused or the session window is exhausted then suspend 1217 if (!AllowFlowControlledWrite()) { 1218 *countRead = 0; 1219 LOG3( 1220 ("Http2StreamBase this=%p, id 0x%X request body suspended because " 1221 "remote window is stream=%" PRId64 " session=%" PRId64 ".\n", 1222 this, mStreamID, mServerReceiveWindow, 1223 session->ServerSessionWindow())); 1224 mBlockedOnRwin = true; 1225 return NS_BASE_STREAM_WOULD_BLOCK; 1226 } 1227 mBlockedOnRwin = false; 1228 1229 // The chunk is the smallest of: availableData, configured chunkSize, 1230 // stream window, session window, or 14 bit framing limit. 1231 // Its amazing we send anything at all. 1232 dataLength = std::min(count, mChunkSize); 1233 1234 if (dataLength > Http2Session::kMaxFrameData) { 1235 dataLength = Http2Session::kMaxFrameData; 1236 } 1237 1238 if (dataLength > session->ServerSessionWindow()) { 1239 dataLength = static_cast<uint32_t>(session->ServerSessionWindow()); 1240 } 1241 1242 if (dataLength > mServerReceiveWindow) { 1243 dataLength = static_cast<uint32_t>(mServerReceiveWindow); 1244 } 1245 1246 LOG3( 1247 ("Http2StreamBase this=%p id 0x%X send calculation " 1248 "avail=%d chunksize=%d stream window=%" PRId64 1249 " session window=%" PRId64 " " 1250 "max frame=%d USING=%u\n", 1251 this, mStreamID, count, mChunkSize, mServerReceiveWindow, 1252 session->ServerSessionWindow(), Http2Session::kMaxFrameData, 1253 dataLength)); 1254 1255 session->DecrementServerSessionWindow(dataLength); 1256 mServerReceiveWindow -= dataLength; 1257 1258 LOG3(("Http2StreamBase %p id 0x%x request len remaining %" PRId64 ", " 1259 "count avail %u, chunk used %u", 1260 this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); 1261 if (!dataLength && mRequestBodyLenRemaining) { 1262 return NS_BASE_STREAM_WOULD_BLOCK; 1263 } 1264 if (dataLength > mRequestBodyLenRemaining) { 1265 return NS_ERROR_UNEXPECTED; 1266 } 1267 mRequestBodyLenRemaining -= dataLength; 1268 GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); 1269 ChangeState(SENDING_BODY); 1270 [[fallthrough]]; 1271 1272 case SENDING_BODY: 1273 MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b"); 1274 rv = TransmitFrame(buf, countRead, false); 1275 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, 1276 "Transmit Frame should be all or nothing"); 1277 1278 LOG3(("TransmitFrame() rv=%" PRIx32 " returning %d data bytes. " 1279 "Header is %d Body is %d.", 1280 static_cast<uint32_t>(rv), *countRead, mTxInlineFrameUsed, 1281 mTxStreamFrameSize)); 1282 1283 // normalize a partial write with a WOULD_BLOCK into just a partial write 1284 // as some code will take WOULD_BLOCK to mean an error with nothing 1285 // written (e.g. nsHttpTransaction::ReadRequestSegment() 1286 if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) rv = NS_OK; 1287 1288 // If that frame was all sent, look for another one 1289 if (!mTxInlineFrameUsed) ChangeState(GENERATING_BODY); 1290 break; 1291 1292 case SENDING_FIN_STREAM: 1293 MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment"); 1294 break; 1295 1296 case UPSTREAM_COMPLETE: { 1297 MOZ_ASSERT(this->GetHttp2Stream()); 1298 rv = TransmitFrame(nullptr, nullptr, true); 1299 break; 1300 } 1301 default: 1302 MOZ_ASSERT(false, "Http2StreamBase::OnReadSegment non-write state"); 1303 break; 1304 } 1305 1306 return rv; 1307 } 1308 1309 //----------------------------------------------------------------------------- 1310 // nsAHttpSegmentWriter 1311 //----------------------------------------------------------------------------- 1312 1313 nsresult Http2StreamBase::OnWriteSegment(char* buf, uint32_t count, 1314 uint32_t* countWritten) { 1315 LOG3(("Http2StreamBase::OnWriteSegment %p count=%d state=%x 0x%X\n", this, 1316 count, mUpstreamState, mStreamID)); 1317 1318 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1319 if (!mSegmentWriter) { 1320 return NS_BASE_STREAM_WOULD_BLOCK; 1321 } 1322 1323 // sometimes we have read data from the network and stored it in a pipe 1324 // so that other streams can proceed when the gecko caller is not processing 1325 // data events fast enough and flow control hasn't caught up yet. This 1326 // gets the stored data out of that pipe 1327 if (!mBypassInputBuffer && mSimpleBuffer.Available()) { 1328 *countWritten = mSimpleBuffer.Read(buf, count); 1329 MOZ_ASSERT(*countWritten); 1330 LOG3( 1331 ("Http2StreamBase::OnWriteSegment read from flow control buffer %p %x " 1332 "%d\n", 1333 this, mStreamID, *countWritten)); 1334 return NS_OK; 1335 } 1336 1337 // read from the network 1338 return mSegmentWriter->OnWriteSegment(buf, count, countWritten); 1339 } 1340 1341 // ----------------------------------------------------------------------------- 1342 // mirror nsAHttpTransaction 1343 // ----------------------------------------------------------------------------- 1344 1345 bool Http2StreamBase::Do0RTT() { 1346 MOZ_ASSERT(Transaction()); 1347 mAttempting0RTT = false; 1348 nsAHttpTransaction* trans = Transaction(); 1349 if (trans) { 1350 mAttempting0RTT = trans->Do0RTT(); 1351 } 1352 return mAttempting0RTT; 1353 } 1354 1355 nsresult Http2StreamBase::Finish0RTT(bool aRestart, bool aAlpnChanged) { 1356 MOZ_ASSERT(Transaction()); 1357 mAttempting0RTT = false; 1358 // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for 1359 // both arguments because as long as the alpn token stayed the same, we can 1360 // just reuse what we have in our buffer to send instead of having to have 1361 // the transaction rewind and read it all over again. We only need to rewind 1362 // the transaction if we're switching to a new protocol, because our buffer 1363 // won't get used in that case. 1364 // .. 1365 // however, we send in the aRestart value to indicate that early data failed 1366 // for devtools purposes 1367 nsresult rv = NS_OK; 1368 nsAHttpTransaction* trans = Transaction(); 1369 if (trans) { 1370 rv = trans->Finish0RTT(aAlpnChanged, aAlpnChanged); 1371 if (aRestart) { 1372 nsHttpTransaction* hTrans = trans->QueryHttpTransaction(); 1373 if (hTrans) { 1374 hTrans->Refused0RTT(); 1375 } 1376 } 1377 } 1378 return rv; 1379 } 1380 1381 nsresult Http2StreamBase::GetOriginAttributes(mozilla::OriginAttributes* oa) { 1382 if (!mSocketTransport) { 1383 return NS_ERROR_UNEXPECTED; 1384 } 1385 1386 return mSocketTransport->GetOriginAttributes(oa); 1387 } 1388 1389 nsHttpTransaction* Http2StreamBase::HttpTransaction() { 1390 return (Transaction()) ? Transaction()->QueryHttpTransaction() : nullptr; 1391 } 1392 1393 nsHttpConnectionInfo* Http2StreamBase::ConnectionInfo() { 1394 if (Transaction()) { 1395 return Transaction()->ConnectionInfo(); 1396 } 1397 return nullptr; 1398 } 1399 1400 } // namespace mozilla::net