tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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