Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  Http2StreamBase.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


// HttpLog.h should generally be included first
#include "HttpLog.h"

// Log on level :5, instead of default :4.
#undef LOG
#define LOG(args) LOG5(args)
#undef LOG_ENABLED
#define LOG_ENABLED() LOG5_ENABLED()

#include <algorithm>

#include "Http2Compression.h"
#include "Http2Session.h"
#include "Http2StreamBase.h"
#include "Http2Stream.h"

#include "mozilla/BasePrincipal.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Telemetry.h"
#include "nsHttp.h"
#include "nsHttpHandler.h"
#include "nsHttpRequestHead.h"
#include "nsIClassOfService.h"
#include "prnetdb.h"

namespace mozilla::net {

Http2StreamBase::Http2StreamBase(uint64_t aTransactionBrowserId,
                                 Http2Session* session, int32_t priority,
                                 uint64_t currentBrowserId)
    : mSession(
          do_GetWeakReference(static_cast<nsISupportsWeakReference*>(session))),
      mRequestHeadersDone(0),
      mOpenGenerated(0),
      mAllHeadersReceived(0),
      mQueued(0),
      mSocketTransport(session->SocketTransport()),
      mCurrentBrowserId(currentBrowserId),
      mTransactionBrowserId(aTransactionBrowserId),
      mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
      mChunkSize(session->SendingChunkSize()),
      mRequestBlockedOnRead(0),
      mRecvdFin(0),
      mReceivedData(0),
      mRecvdReset(0),
      mSentReset(0),
      mCountAsActive(0),
      mSentFin(0),
      mSentWaitingFor(0),
      mSetTCPSocketBuffer(0),
      mBypassInputBuffer(0) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  LOG1(("Http2StreamBase::Http2StreamBase %p"this));

  mServerReceiveWindow = session->GetServerInitialStreamWindow();
  mClientReceiveWindow = session->PushAllowance();

  mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);

  static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
                "Lowest Priority should be less than kNormalPriority");

  // values of priority closer to 0 are higher priority for the priority
  // argument. This value is used as a group, which maps to a
  // weight that is related to the nsISupportsPriority that we are given.
  int32_t httpPriority;
  if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
    httpPriority = kWorstPriority;
  } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
    httpPriority = kBestPriority;
  } else {
    httpPriority = kNormalPriority + priority;
  }
  MOZ_ASSERT(httpPriority >= 0);
  SetPriority(static_cast<uint32_t>(httpPriority));
}

Http2StreamBase::~Http2StreamBase() {
  MOZ_DIAGNOSTIC_ASSERT(OnSocketThread());

  mStreamID = Http2Session::kDeadStreamID;

  LOG3(("Http2StreamBase::~Http2StreamBase %p"this));
}

already_AddRefed<Http2Session> Http2StreamBase::Session() {
  RefPtr<Http2Session> session = do_QueryReferent(mSession);
  return session.forget();
}

// ReadSegments() is used to write data down the socket. Generally, HTTP
// request data is pulled from the approriate transaction and
// converted to HTTP/2 data. Sometimes control data like a window-update is
// generated instead.

nsresult Http2StreamBase::ReadSegments(nsAHttpSegmentReader* reader,
                                       uint32_t count, uint32_t* countRead) {
  LOG3(("Http2StreamBase %p ReadSegments reader=%p count=%d state=%x"this,
        reader, count, mUpstreamState));
  RefPtr<Http2Session> session = Session();
  // Reader is nullptr when this is a push stream.
  MOZ_DIAGNOSTIC_ASSERT(!reader || (reader == session) ||
                        (IsTunnel() && NS_FAILED(Condition())));

  if (NS_FAILED(Condition())) {
    return Condition();
  }

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  nsresult rv = NS_ERROR_UNEXPECTED;
  mRequestBlockedOnRead = 0;

  if (mRecvdFin || mRecvdReset) {
    // Don't transmit any request frames if the peer cannot respond
    LOG3(
        ("Http2StreamBase %p ReadSegments request stream aborted due to"
         " response side closure\n",
         this));
    return NS_ERROR_ABORT;
  }

  // avoid runt chunks if possible by anticipating
  // full data frames
  if (count > (mChunkSize + 8)) {
    uint32_t numchunks = count / (mChunkSize + 8);
    count = numchunks * (mChunkSize + 8);
  }

  switch (mUpstreamState) {
    case GENERATING_HEADERS:
    case GENERATING_BODY:
    case SENDING_BODY:
      // Call into the HTTP Transaction to generate the HTTP request
      // stream. That stream will show up in OnReadSegment().
      mSegmentReader = reader;
      rv = CallToReadData(count, countRead);
      mSegmentReader = nullptr;

      LOG3(("Http2StreamBase::ReadSegments %p trans readsegments rv %" PRIx32
            " read=%d\n",
            thisstatic_cast<uint32_t>(rv), *countRead));

      // Check to see if the transaction's request could be written out now.
      // If not, mark the stream for callback when writing can proceed.
      if (NS_SUCCEEDED(rv) && mUpstreamState == GENERATING_HEADERS &&
          !mRequestHeadersDone) {
        session->TransactionHasDataToWrite(this);
      }

      // mTxinlineFrameUsed represents any queued un-sent frame. It might
      // be 0 if there is no such frame, which is not a gurantee that we
      // don't have more request body to send - just that any data that was
      // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
      // a queued, but complete, http/2 frame length.

      // Mark that we are blocked on read if the http transaction needs to
      // provide more of the request message body and there is nothing queued
      // for writing
      if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) {
        LOG(("Http2StreamBase %p mRequestBlockedOnRead = 1"this));
        mRequestBlockedOnRead = 1;
      }

      // A transaction that had already generated its headers before it was
      // queued at the session level (due to concurrency concerns) may not call
      // onReadSegment off the ReadSegments() stack above.

      // When mTransaction->ReadSegments returns NS_BASE_STREAM_WOULD_BLOCK it
      // means it may have already finished providing all the request data
      // necessary to generate open, calling OnReadSegment will drive sending
      // the request; this may happen after dequeue of the stream.

      if (mUpstreamState == GENERATING_HEADERS &&
          (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK)) {
        LOG3(("Http2StreamBase %p ReadSegments forcing OnReadSegment call\n",
              this));
        uint32_t wasted = 0;
        mSegmentReader = reader;
        nsresult rv2 = OnReadSegment("", 0, &wasted);
        mSegmentReader = nullptr;

        LOG3((" OnReadSegment returned 0x%08" PRIx32,
              static_cast<uint32_t>(rv2)));
        if (NS_SUCCEEDED(rv2)) {
          mRequestBlockedOnRead = 0;
        }
      }

      // If the sending flow control window is open (!mBlockedOnRwin) then
      // continue sending the request
      if (!mBlockedOnRwin && mOpenGenerated && !mTxInlineFrameUsed &&
          NS_SUCCEEDED(rv) && (!*countRead) && CloseSendStreamWhenDone()) {
        MOZ_ASSERT(!mQueued);
        MOZ_ASSERT(mRequestHeadersDone);
        LOG3(
            ("Http2StreamBase::ReadSegments %p 0x%X: Sending request data "
             "complete, "
             "mUpstreamState=%x\n",
             this, mStreamID, mUpstreamState));
        if (mSentFin) {
          ChangeState(UPSTREAM_COMPLETE);
        } else {
          GenerateDataFrameHeader(0, true);
          ChangeState(SENDING_FIN_STREAM);
          session->TransactionHasDataToWrite(this);
          rv = NS_BASE_STREAM_WOULD_BLOCK;
        }
      }
      break;

    case SENDING_FIN_STREAM:
      // We were trying to send the FIN-STREAM but were blocked from
      // sending it out - try again.
      if (!mSentFin) {
        mSegmentReader = reader;
        rv = TransmitFrame(nullptr, nullptr, false);
        mSegmentReader = nullptr;
        MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
                   "Transmit Frame should be all or nothing");
        if (NS_SUCCEEDED(rv)) ChangeState(UPSTREAM_COMPLETE);
      } else {
        rv = NS_OK;
        mTxInlineFrameUsed = 0;  // cancel fin data packet
        ChangeState(UPSTREAM_COMPLETE);
      }

      *countRead = 0;

      // don't change OK to WOULD BLOCK. we are really done sending if OK
      break;

    case UPSTREAM_COMPLETE:
      *countRead = 0;
      rv = NS_OK;
      break;

    default:
      MOZ_ASSERT(false"Http2StreamBase::ReadSegments unknown state");
      break;
  }

  return rv;
}

uint64_t Http2StreamBase::LocalUnAcked() {
  // reduce unacked by the amount of undelivered data
  // to help assert flow control
  uint64_t undelivered = mSimpleBuffer.Available();

  if (undelivered > mLocalUnacked) {
    return 0;
  }
  return mLocalUnacked - undelivered;
}

nsresult Http2StreamBase::BufferInput(uint32_t count, uint32_t* countWritten) {
  char buf[SimpleBufferPage::kSimpleBufferPageSize];
  if (SimpleBufferPage::kSimpleBufferPageSize < count) {
    count = SimpleBufferPage::kSimpleBufferPageSize;
  }

  mBypassInputBuffer = 1;
  nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
  mBypassInputBuffer = 0;

  if (NS_SUCCEEDED(rv)) {
    rv = mSimpleBuffer.Write(buf, *countWritten);
    if (NS_FAILED(rv)) {
      MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }
  return rv;
}

bool Http2StreamBase::DeferCleanup(nsresult status) {
  // do not cleanup a stream that has data buffered for the transaction
  return (NS_SUCCEEDED(status) && mSimpleBuffer.Available());
}

// WriteSegments() is used to read data off the socket. Generally this is
// just a call through to the associated nsHttpTransaction for this stream
// for the remaining data bytes indicated by the current DATA frame.

nsresult Http2StreamBase::WriteSegments(nsAHttpSegmentWriter* writer,
                                        uint32_t count,
                                        uint32_t* countWritten) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");

  LOG3(("Http2StreamBase::WriteSegments %p count=%d state=%x"this, count,
        mUpstreamState));

  mSegmentWriter = writer;
  nsresult rv = CallToWriteData(count, countWritten);

  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
    // consuming transaction won't take data. but we need to read it into a
    // buffer so that it won't block other streams. but we should not advance
    // the flow control window so that we'll eventually push back on the sender.
    rv = BufferInput(count, countWritten);
    LOG3(("Http2StreamBase::WriteSegments %p Buffered %" PRIX32 " %d\n"this,
          static_cast<uint32_t>(rv), *countWritten));
  }

  LOG3(("Http2StreamBase::WriteSegments %" PRIX32 "",
        static_cast<uint32_t>(rv)));
  mSegmentWriter = nullptr;
  return rv;
}

nsresult Http2StreamBase::ParseHttpRequestHeaders(const char* buf,
                                                  uint32_t avail,
                                                  uint32_t* countUsed) {
  // Returns NS_OK even if the headers are incomplete
  // set mRequestHeadersDone flag if they are complete

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
  MOZ_ASSERT(!mRequestHeadersDone);

  LOG3(("Http2StreamBase::ParseHttpRequestHeaders %p avail=%d state=%x"this,
        avail, mUpstreamState));

  mFlatHttpRequestHeaders.Append(buf, avail);

  // We can use the simple double crlf because firefox is the
  // only client we are parsing
  int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");

  if (endHeader == kNotFound) {
    // We don't have all the headers yet
    LOG3(
        ("Http2StreamBase::ParseHttpRequestHeaders %p "
         "Need more header bytes. Len = %zd",
         this, mFlatHttpRequestHeaders.Length()));
    *countUsed = avail;
    return NS_OK;
  }

  // We have recvd all the headers, trim the local
  // buffer of the final empty line, and set countUsed to reflect
  // the whole header has been consumed.
  uint32_t oldLen = mFlatHttpRequestHeaders.Length();
  mFlatHttpRequestHeaders.SetLength(endHeader + 2);
  *countUsed = avail - (oldLen - endHeader) + 4;
  mRequestHeadersDone = 1;

  Http2Stream* selfRegularStream = this->GetHttp2Stream();
  if (selfRegularStream) {
    return selfRegularStream->CheckPushCache();
  }

  return NS_OK;
}

// This is really a headers frame, but open is pretty clear from a workflow pov
nsresult Http2StreamBase::GenerateOpen() {
  // It is now OK to assign a streamID that we are assured will
  // be monotonically increasing amongst new streams on this
  // session
  RefPtr<Http2Session> session = Session();
  mStreamID = session->RegisterStreamID(this);
  MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
  MOZ_ASSERT(!mOpenGenerated);

  mOpenGenerated = 1;

  LOG3(("Http2StreamBase %p Stream ID 0x%X [session=%p]\n"this, mStreamID,
        session.get()));

  if (mStreamID >= 0x80000000) {
    // streamID must fit in 31 bits. Evading This is theoretically possible
    // because stream ID assignment is asynchronous to stream creation
    // because of the protocol requirement that the new stream ID
    // be monotonically increasing. In reality this is really not possible
    // because new streams stop being added to a session with millions of
    // IDs still available and no race condition is going to bridge that gap;
    // so we can be comfortable on just erroring out for correctness in that
    // case.
    LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
    return NS_ERROR_UNEXPECTED;
  }

  // Now we need to convert the flat http headers into a set
  // of HTTP/2 headers by writing to mTxInlineFrame{sz}

  nsCString compressedData;
  uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;

  nsresult rv = GenerateHeaders(compressedData, firstFrameFlags);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (firstFrameFlags & Http2Session::kFlag_END_STREAM) {
    SetSentFin(true);
  }

  // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it
  // exceeds the 2^14-1 limit for 1 frame. Do it by inserting header size gaps
  // in the existing frame for the new headers and for the first one a priority
  // field. There is no question this is ugly, but a 16KB HEADERS frame should
  // be a long tail event, so this is really just for correctness and a nop in
  // the base case.
  //

  MOZ_ASSERT(!mTxInlineFrameUsed);

  uint32_t dataLength = compressedData.Length();
  uint32_t maxFrameData =
      Http2Session::kMaxFrameData - 5;  // 5 bytes for priority
  uint32_t numFrames = 1;

  if (dataLength > maxFrameData) {
    numFrames +=
        ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
        Http2Session::kMaxFrameData;
    MOZ_ASSERT(numFrames > 1);
  }

  // note that we could still have 1 frame for 0 bytes of data. that's ok.

  uint32_t messageSize = dataLength;
  messageSize += Http2Session::kFrameHeaderBytes +
                 5;  // frame header + priority overhead in HEADERS frame
  messageSize += (numFrames - 1) *
                 Http2Session::kFrameHeaderBytes;  // frame header overhead in
                                                   // CONTINUATION frames

  EnsureBuffer(mTxInlineFrame, messageSize, mTxInlineFrameUsed,
               mTxInlineFrameSize);

  mTxInlineFrameUsed += messageSize;
  UpdatePriorityDependency();
  LOG1(
      ("Http2StreamBase %p Generating %d bytes of HEADERS for stream 0x%X with "
       "priority weight %u dep 0x%X frames %u\n",
       this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
       mPriorityDependency, numFrames));

  uint32_t outputOffset = 0;
  uint32_t compressedDataOffset = 0;
  for (uint32_t idx = 0; idx < numFrames; ++idx) {
    uint32_t flags, frameLen;
    bool lastFrame = (idx == numFrames - 1);

    flags = 0;
    frameLen = maxFrameData;
    if (!idx) {
      flags |= firstFrameFlags;
      // Only the first frame needs the 4-byte offset
      maxFrameData = Http2Session::kMaxFrameData;
    }
    if (lastFrame) {
      frameLen = dataLength;
      flags |= Http2Session::kFlag_END_HEADERS;
    }
    dataLength -= frameLen;

    session->CreateFrameHeader(mTxInlineFrame.get() + outputOffset,
                               frameLen + (idx ? 0 : 5),
                               (idx) ? Http2Session::FRAME_TYPE_CONTINUATION
                                     : Http2Session::FRAME_TYPE_HEADERS,
                               flags, mStreamID);
    outputOffset += Http2Session::kFrameHeaderBytes;

    if (!idx) {
      uint32_t wireDep = PR_htonl(mPriorityDependency);
      memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4);
      memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1);
      outputOffset += 5;
    }

    memcpy(mTxInlineFrame.get() + outputOffset,
           compressedData.BeginReading() + compressedDataOffset, frameLen);
    compressedDataOffset += frameLen;
    outputOffset += frameLen;
  }

  Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());

  mFlatHttpRequestHeaders.Truncate();

  return NS_OK;
}

void Http2StreamBase::AdjustInitialWindow() {
  // The default initial_window is sized for pushed streams. When we
  // generate a client pulled stream we want to disable flow control for
  // the stream with a window update. Do the same for pushed streams
  // when they connect to a pull.

  uint32_t wireStreamId = GetWireStreamId();
  if (wireStreamId == 0) {
    return;
  }

  // right now mClientReceiveWindow is the lower push limit
  // bump it up to the pull limit set by the channel or session
  // don't allow windows less than push
  uint32_t bump = 0;
  RefPtr<Http2Session> session = Session();
  nsHttpTransaction* trans = HttpTransaction();
  if (trans && trans->InitialRwin()) {
    bump = (trans->InitialRwin() > mClientReceiveWindow)
               ? (trans->InitialRwin() - mClientReceiveWindow)
               : 0;
  } else {
    MOZ_ASSERT(session->InitialRwin() >= mClientReceiveWindow);
    bump = session->InitialRwin() - mClientReceiveWindow;
  }

  LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n"this,
        wireStreamId, bump));
  if (!bump) {  // nothing to do
    return;
  }

  EnsureBuffer(mTxInlineFrame,
               mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
               mTxInlineFrameUsed, mTxInlineFrameSize);
  uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
  mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;

  session->CreateFrameHeader(packet, 4, Http2Session::FRAME_TYPE_WINDOW_UPDATE,
                             0, wireStreamId);

  mClientReceiveWindow += bump;
  bump = PR_htonl(bump);
  memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
}

void Http2StreamBase::UpdateTransportReadEvents(uint32_t count) {
  mTotalRead += count;
  if (!mSocketTransport) {
    return;
  }

  if (Transaction()) {
    Transaction()->OnTransportStatus(mSocketTransport,
                                     NS_NET_STATUS_RECEIVING_FROM, mTotalRead);
  }
}

void Http2StreamBase::UpdateTransportSendEvents(uint32_t count) {
  mTotalSent += count;

  // Setting the TCP send buffer, introduced in
  // https://bugzilla.mozilla.org/show_bug.cgi?id=790184, which the following
  // comment refers to, is being removed once we verify no increases in error
  // rate.
  //
  // normally on non-windows platform we use TCP autotuning for
  // the socket buffers, and this works well (managing enough
  // buffers for BDP while conserving memory) for HTTP even when
  // it creates really deep queues. However this 'buffer bloat' is
  // a problem for http/2 because it ruins the low latency properties
  // necessary for PING and cancel to work meaningfully.

  // If this stream represents a large upload, disable autotuning for
  // the session and cap the send buffers by default at 128KB.
  // (10Mbit/sec @ 100ms)
  //
  uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
  if (StaticPrefs::network_http_http2_send_buffer_size() > 0 &&
      (mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
    mSetTCPSocketBuffer = 1;
    mSocketTransport->SetSendBufferSize(bufferSize);
  }

  if ((mUpstreamState != SENDING_FIN_STREAM) && Transaction()) {
    Transaction()->OnTransportStatus(mSocketTransport, NS_NET_STATUS_SENDING_TO,
                                     mTotalSent);
  }

  if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
    mSentWaitingFor = 1;
    if (Transaction()) {
      Transaction()->OnTransportStatus(mSocketTransport,
                                       NS_NET_STATUS_WAITING_FOR, 0);
    }
  }
}

nsresult Http2StreamBase::TransmitFrame(const char* buf, uint32_t* countUsed,
                                        bool forceCommitment) {
  // If TransmitFrame returns SUCCESS than all the data is sent (or at least
  // buffered at the session level), if it returns WOULD_BLOCK then none of
  // the data is sent.

  // You can call this function with no data and no out parameter in order to
  // flush internal buffers that were previously blocked on writing. You can
  // of course feed new data to it as well.

  LOG3(("Http2StreamBase::TransmitFrame %p inline=%d stream=%d"this,
        mTxInlineFrameUsed, mTxStreamFrameSize));
  if (countUsed) *countUsed = 0;

  if (!mTxInlineFrameUsed) {
    MOZ_ASSERT(!buf);
    return NS_OK;
  }

  MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
  MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
  MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
             "TransmitFrame arguments inconsistent");

  uint32_t transmittedCount;
  nsresult rv;
  RefPtr<Http2Session> session = Session();

  // In the (relatively common) event that we have a small amount of data
  // split between the inlineframe and the streamframe, then move the stream
  // data into the inlineframe via copy in order to coalesce into one write.
  // Given the interaction with ssl this is worth the small copy cost.
  if (mTxStreamFrameSize && mTxInlineFrameUsed &&
      mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
      mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
    LOG3(("Coalesce Transmit"));
    memcpy(&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize);
    if (countUsed) *countUsed += mTxStreamFrameSize;
    mTxInlineFrameUsed += mTxStreamFrameSize;
    mTxStreamFrameSize = 0;
  }

  rv = mSegmentReader->CommitToSegmentSize(
      mTxStreamFrameSize + mTxInlineFrameUsed, forceCommitment);

  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
    MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
    session->TransactionHasDataToWrite(this);
  }
  if (NS_FAILED(rv)) {  // this will include WOULD_BLOCK
    return rv;
  }

  // This function calls mSegmentReader->OnReadSegment to report the actual
  // http/2 bytes through to the session object and then the HttpConnection
  // which calls the socket write function. It will accept all of the inline and
  // stream data because of the above 'commitment' even if it has to buffer

  rv = session->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
                             mTxInlineFrameUsed, &transmittedCount);
  LOG3(
      ("Http2StreamBase::TransmitFrame for inline BufferOutput session=%p "
       "stream=%p result %" PRIx32 " len=%d",
       session.get(), thisstatic_cast<uint32_t>(rv), transmittedCount));

  MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
             "inconsistent inline commitment result");

  if (NS_FAILED(rv)) return rv;

  MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
             "inconsistent inline commitment count");

  Http2Session::LogIO(session, this"Writing from Inline Buffer",
                      reinterpret_cast<char*>(mTxInlineFrame.get()),
                      transmittedCount);

  if (mTxStreamFrameSize) {
    if (!buf) {
      // this cannot happen
      MOZ_ASSERT(false,
                 "Stream transmit with null buf argument to "
                 "TransmitFrame()");
      LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
      return NS_ERROR_UNEXPECTED;
    }

    // If there is already data buffered, just add to that to form
    // a single TLS Application Data Record - otherwise skip the memcpy
    if (session->AmountOfOutputBuffered()) {
      rv = session->BufferOutput(buf, mTxStreamFrameSize, &transmittedCount);
    } else {
      rv = session->OnReadSegment(buf, mTxStreamFrameSize, &transmittedCount);
    }

    LOG3(
        ("Http2StreamBase::TransmitFrame for regular session=%p "
         "stream=%p result %" PRIx32 " len=%d",
         session.get(), thisstatic_cast<uint32_t>(rv), transmittedCount));

    MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
               "inconsistent stream commitment result");

    if (NS_FAILED(rv)) return rv;

    MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
               "inconsistent stream commitment count");

    Http2Session::LogIO(session, this"Writing from Transaction Buffer", buf,
                        transmittedCount);

    *countUsed += mTxStreamFrameSize;
  }

  if (!mAttempting0RTT) {
    session->FlushOutputQueue();
  }

  // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
  UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);

  mTxInlineFrameUsed = 0;
  mTxStreamFrameSize = 0;

  return NS_OK;
}

void Http2StreamBase::ChangeState(enum upstreamStateType newState) {
  LOG3(("Http2StreamBase::ChangeState() %p from %X to %X"this, mUpstreamState,
        newState));
  mUpstreamState = newState;
}

void Http2StreamBase::GenerateDataFrameHeader(uint32_t dataLength,
                                              bool lastFrame) {
  LOG3(("Http2StreamBase::GenerateDataFrameHeader %p len=%d last=%d"this,
        dataLength, lastFrame));

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
  MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");

  uint8_t frameFlags = 0;
  if (lastFrame) {
    frameFlags |= Http2Session::kFlag_END_STREAM;
    if (dataLength) SetSentFin(true);
  }

  RefPtr<Http2Session> session = Session();
  session->CreateFrameHeader(mTxInlineFrame.get(), dataLength,
                             Http2Session::FRAME_TYPE_DATA, frameFlags,
                             mStreamID);

  mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes;
  mTxStreamFrameSize = dataLength;
}

// ConvertResponseHeaders is used to convert the response headers
// into HTTP/1 format and report some telemetry
nsresult Http2StreamBase::ConvertResponseHeaders(
    Http2Decompressor* decompressor, nsACString& aHeadersIn,
    nsACString& aHeadersOut, int32_t& httpResponseCode) {
  nsresult rv = decompressor->DecodeHeaderBlock(
      reinterpret_cast<const uint8_t*>(aHeadersIn.BeginReading()),
      aHeadersIn.Length(), aHeadersOut, false);
  if (NS_FAILED(rv)) {
    LOG3(("Http2StreamBase::ConvertResponseHeaders %p decode Error\n"this));
    return rv;
  }

  nsAutoCString statusString;
  decompressor->GetStatus(statusString);
  if (statusString.IsEmpty()) {
    LOG3(("Http2StreamBase::ConvertResponseHeaders %p Error - no status\n",
          this));
    return NS_ERROR_ILLEGAL_VALUE;
  }

  nsresult errcode;
  httpResponseCode = statusString.ToInteger(&errcode);

  // Ensure the :status is just an HTTP status code
  // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1352146
  nsAutoCString parsedStatusString;
  parsedStatusString.AppendInt(httpResponseCode);
  if (!parsedStatusString.Equals(statusString)) {
    LOG3(
        ("Http2StreamBase::ConvertResposeHeaders %p status %s is not just a "
         "code",
         this, statusString.BeginReading()));
    // Results in stream reset with PROTOCOL_ERROR
    return NS_ERROR_ILLEGAL_VALUE;
  }

  LOG3(("Http2StreamBase::ConvertResponseHeaders %p response code %d\n"this,
        httpResponseCode));

  if (httpResponseCode == 421) {
    // Origin Frame requires 421 to remove this origin from the origin set
    RefPtr<Http2Session> session = Session();
    session->Received421(ConnectionInfo());
  }

  if (aHeadersIn.Length() && aHeadersOut.Length()) {
    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
    uint32_t ratio = aHeadersIn.Length() * 100 / aHeadersOut.Length();
    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
  }

  // The decoding went ok. Now we can customize and clean up.

  aHeadersIn.Truncate();
  aHeadersOut.AppendLiteral("X-Firefox-Spdy: h2");
  aHeadersOut.AppendLiteral("\r\n\r\n");
  LOG(("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
  HandleResponseHeaders(aHeadersOut, httpResponseCode);

  return NS_OK;
}

nsresult Http2StreamBase::ConvertResponseTrailers(
    Http2Decompressor* decompressor, nsACString& aTrailersIn) {
  LOG3(("Http2StreamBase::ConvertResponseTrailers %p"this));
  nsAutoCString flatTrailers;

  nsresult rv = decompressor->DecodeHeaderBlock(
      reinterpret_cast<const uint8_t*>(aTrailersIn.BeginReading()),
      aTrailersIn.Length(), flatTrailers, false);
  if (NS_FAILED(rv)) {
    LOG3(("Http2StreamBase::ConvertResponseTrailers %p decode Error"this));
    return rv;
  }

  nsHttpTransaction* trans = HttpTransaction();
  if (trans) {
    trans->SetHttpTrailers(flatTrailers);
  } else {
    LOG3(("Http2StreamBase::ConvertResponseTrailers %p no trans"this));
  }

  return NS_OK;
}

void Http2StreamBase::SetResponseIsComplete() {
  nsHttpTransaction* trans = HttpTransaction();
  if (trans) {
    trans->SetResponseIsComplete();
  }
}

void Http2StreamBase::SetAllHeadersReceived() {
  if (mAllHeadersReceived) {
    return;
  }

  if (mState == RESERVED_BY_REMOTE) {
    // pushed streams needs to wait until headers have
    // arrived to open up their window
    LOG3(
        ("Http2StreamBase::SetAllHeadersReceived %p state OPEN from reserved\n",
         this));
    mState = OPEN;
    AdjustInitialWindow();
  }

  mAllHeadersReceived = 1;
}

bool Http2StreamBase::AllowFlowControlledWrite() {
  RefPtr<Http2Session> session = Session();
  return (session->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
}

void Http2StreamBase::UpdateServerReceiveWindow(int32_t delta) {
  mServerReceiveWindow += delta;

  if (mBlockedOnRwin && AllowFlowControlledWrite()) {
    LOG3(
        ("Http2StreamBase::UpdateServerReceived UnPause %p 0x%X "
         "Open stream window\n",
         this, mStreamID));
    RefPtr<Http2Session> session = Session();
    session->TransactionHasDataToWrite(this);
  }
}

void Http2StreamBase::SetPriority(uint32_t newPriority) {
  int32_t httpPriority = static_cast<int32_t>(newPriority);
  if (httpPriority > kWorstPriority) {
    httpPriority = kWorstPriority;
  } else if (httpPriority < kBestPriority) {
    httpPriority = kBestPriority;
  }
  mRFC7540Priority = static_cast<uint32_t>(httpPriority);
  mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
                    (httpPriority - kNormalPriority);

  mPriorityDependency = 0;  // maybe adjusted later
}

void Http2StreamBase::SetPriorityDependency(uint32_t newPriority,
                                            uint32_t newDependency) {
  SetPriority(newPriority);
  mPriorityDependency = newDependency;
}

static uint32_t GetPriorityDependencyFromTransaction(nsHttpTransaction* trans) {
  MOZ_ASSERT(trans);

  uint32_t classFlags = trans->GetClassOfService().Flags();

  if (classFlags & nsIClassOfService::UrgentStart) {
    return Http2Session::kUrgentStartGroupID;
  }

  if (classFlags & nsIClassOfService::Leader) {
    return Http2Session::kLeaderGroupID;
  }

  if (classFlags & nsIClassOfService::Follower) {
    return Http2Session::kFollowerGroupID;
  }

  if (classFlags & nsIClassOfService::Speculative) {
    return Http2Session::kSpeculativeGroupID;
  }

  if (classFlags & nsIClassOfService::Background) {
    return Http2Session::kBackgroundGroupID;
  }

  if (classFlags & nsIClassOfService::Unblocked) {
    return Http2Session::kOtherGroupID;
  }

  return Http2Session::kFollowerGroupID;  // unmarked followers
}

void Http2StreamBase::UpdatePriorityDependency() {
  RefPtr<Http2Session> session = Session();
  if (!session->UseH2Deps()) {
    return;
  }

  nsHttpTransaction* trans = HttpTransaction();
  if (!trans) {
    return;
  }

  // we create 6 fake dependency streams per session,
  // these streams are never opened with HEADERS. our first opened stream is 0xd
  // 3 depends 0, weight 200, leader class (kLeaderGroupID)
  // 5 depends 0, weight 100, other (kOtherGroupID)
  // 7 depends 0, weight 0, background (kBackgroundGroupID)
  // 9 depends 7, weight 0, speculative (kSpeculativeGroupID)
  // b depends 3, weight 0, follower class (kFollowerGroupID)
  // d depends 0, weight 240, urgent-start class (kUrgentStartGroupID)
  //
  // streams for leaders (html, js, css) depend on 3
  // streams for folowers (images) depend on b
  // default streams (xhr, async js) depend on 5
  // explicit bg streams (beacon, etc..) depend on 7
  // spculative bg streams depend on 9
  // urgent-start streams depend on d

  mPriorityDependency = GetPriorityDependencyFromTransaction(trans);

  if (gHttpHandler->ActiveTabPriority() &&
      mTransactionBrowserId != mCurrentBrowserId &&
      mPriorityDependency != Http2Session::kUrgentStartGroupID) {
    LOG3(
        ("Http2StreamBase::UpdatePriorityDependency %p "
         " depends on background group for trans %p\n",
         this, trans));
    mPriorityDependency = Http2Session::kBackgroundGroupID;

    nsHttp::NotifyActiveTabLoadOptimization();
  }

  LOG1(
      ("Http2StreamBase::UpdatePriorityDependency %p "
       "depends on stream 0x%X\n",
       this, mPriorityDependency));
}

void Http2StreamBase::CurrentBrowserIdChanged(uint64_t id) {
  if (!mStreamID) {
    // For pushed streams, we ignore the direct call from the session and
    // instead let it come to the internal function from the pushed stream, so
    // we don't accidentally send two PRIORITY frames for the same stream.
    return;
  }

  CurrentBrowserIdChangedInternal(id);
}

void Http2StreamBase::CurrentBrowserIdChangedInternal(uint64_t id) {
  MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
  RefPtr<Http2Session> session = Session();
  LOG3(
      ("Http2StreamBase::CurrentBrowserIdChangedInternal "
       "%p browserId=%" PRIx64 "\n",
       this, id));

  mCurrentBrowserId = id;

  // Urgent start takes an absolute precedence, so don't
  // change mPriorityDependency here.
  if (mPriorityDependency == Http2Session::kUrgentStartGroupID) {
    return;
  }

  if (session->UseH2Deps()) {
    UpdatePriorityRFC7540(session);
  } else {
    UpdatePriority(session);
  }
}

void Http2StreamBase::UpdatePriority(Http2Session* session) {
  MOZ_ASSERT(!session->UseH2Deps());
  bool isInBackground = mTransactionBrowserId != mCurrentBrowserId;

  if (isInBackground) {
    LOG3(
        ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
         "move into background group.\n",
         this));

    nsHttp::NotifyActiveTabLoadOptimization();
  }

  if (!StaticPrefs::network_http_http2_priority_updates()) {
    return;
  }

  nsHttpTransaction* trans = HttpTransaction();
  if (!trans) {
    return;
  }

  uint8_t urgency = nsHttpHandler::UrgencyFromCoSFlags(
      trans->GetClassOfService().Flags(), trans->Priority());
  bool incremental = trans->GetClassOfService().Incremental();
  uint32_t streamID = GetWireStreamId();

  // If the tab is in the background
  // we can probably lower the priority of this request by 1.
  // (to get lower priority we increase urgency).
  if (isInBackground && urgency < 6) {
    urgency++;
  }

  if (streamID) {
    session->SendPriorityUpdateFrame(streamID, urgency, incremental);
  }
}

void Http2StreamBase::UpdatePriorityRFC7540(Http2Session* session) {
  MOZ_ASSERT(session->UseH2Deps());
  if (mTransactionBrowserId != mCurrentBrowserId) {
    mPriorityDependency = Http2Session::kBackgroundGroupID;
    LOG3(
        ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
         "move into background group.\n",
         this));

    nsHttp::NotifyActiveTabLoadOptimization();
  } else {
    nsHttpTransaction* trans = HttpTransaction();
    if (!trans) {
      return;
    }

    mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
    LOG3(
        ("Http2StreamBase::CurrentBrowserIdChangedInternal %p "
         "depends on stream 0x%X\n",
         this, mPriorityDependency));
  }

  uint32_t modifyStreamID = GetWireStreamId();

  if (modifyStreamID) {
    session->SendPriorityFrame(modifyStreamID, mPriorityDependency,
                               mPriorityWeight);
  }
}

void Http2StreamBase::SetRecvdFin(bool aStatus) {
  mRecvdFin = aStatus ? 1 : 0;
  if (!aStatus) return;

  if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
    mState = CLOSED_BY_REMOTE;
  } else if (mState == CLOSED_BY_LOCAL) {
    mState = CLOSED;
  }
}

void Http2StreamBase::SetSentFin(bool aStatus) {
  mSentFin = aStatus ? 1 : 0;
  if (!aStatus) return;

  if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
    mState = CLOSED_BY_LOCAL;
  } else if (mState == CLOSED_BY_REMOTE) {
    mState = CLOSED;
  }
}

void Http2StreamBase::SetRecvdReset(bool aStatus) {
  mRecvdReset = aStatus ? 1 : 0;
  if (!aStatus) return;
  mState = CLOSED;
}

void Http2StreamBase::SetSentReset(bool aStatus) {
  mSentReset = aStatus ? 1 : 0;
  if (!aStatus) return;
  mState = CLOSED;
}

//-----------------------------------------------------------------------------
// nsAHttpSegmentReader
//-----------------------------------------------------------------------------

nsresult Http2StreamBase::OnReadSegment(const char* buf, uint32_t count,
                                        uint32_t* countRead) {
  LOG3(("Http2StreamBase::OnReadSegment %p count=%d state=%x"this, count,
        mUpstreamState));

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  if (!mSegmentReader) {
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  nsresult rv = NS_ERROR_UNEXPECTED;
  uint32_t dataLength;
  RefPtr<Http2Session> session = Session();

  switch (mUpstreamState) {
    case GENERATING_HEADERS:
      // The buffer is the HTTP request stream, including at least part of the
      // HTTP request header. This state's job is to build a HEADERS frame
      // from the header information. count is the number of http bytes
      // available (which may include more than the header), and in countRead we
      // return the number of those bytes that we consume (i.e. the portion that
      // are header bytes)

      if (!mRequestHeadersDone) {
        if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) {
          return rv;
        }
      }

      if (mRequestHeadersDone && !mOpenGenerated) {
        if (!session->TryToActivate(this)) {
          LOG3(
              ("Http2StreamBase::OnReadSegment %p cannot activate now. "
               "queued.\n",
               this));
          return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
        }
        if (NS_FAILED(rv = GenerateOpen())) {
          return rv;
        }
      }

      LOG3(
          ("ParseHttpRequestHeaders %p used %d of %d. "
           "requestheadersdone = %d mOpenGenerated = %d\n",
           this, *countRead, count, mRequestHeadersDone, mOpenGenerated));
      if (mOpenGenerated) {
        SetHTTPState(OPEN);
        AdjustInitialWindow();
        // This version of TransmitFrame cannot block
        rv = TransmitFrame(nullptr, nullptr, true);
        ChangeState(GENERATING_BODY);
        break;
      }
      MOZ_ASSERT(*countRead == count,
                 "Header parsing not complete but unused data");
      break;

    case GENERATING_BODY:
      // if there is session flow control and either the stream window is active
      // and exhaused or the session window is exhausted then suspend
      if (!AllowFlowControlledWrite()) {
        *countRead = 0;
        LOG3(
            ("Http2StreamBase this=%p, id 0x%X request body suspended because "
             "remote window is stream=%" PRId64 " session=%" PRId64 ".\n",
             this, mStreamID, mServerReceiveWindow,
             session->ServerSessionWindow()));
        mBlockedOnRwin = true;
        return NS_BASE_STREAM_WOULD_BLOCK;
      }
      mBlockedOnRwin = false;

      // The chunk is the smallest of: availableData, configured chunkSize,
      // stream window, session window, or 14 bit framing limit.
      // Its amazing we send anything at all.
      dataLength = std::min(count, mChunkSize);

      if (dataLength > Http2Session::kMaxFrameData) {
        dataLength = Http2Session::kMaxFrameData;
      }

      if (dataLength > session->ServerSessionWindow()) {
        dataLength = static_cast<uint32_t>(session->ServerSessionWindow());
      }

      if (dataLength > mServerReceiveWindow) {
        dataLength = static_cast<uint32_t>(mServerReceiveWindow);
      }

      LOG3(
          ("Http2StreamBase this=%p id 0x%X send calculation "
           "avail=%d chunksize=%d stream window=%" PRId64
           " session window=%" PRId64 " "
           "max frame=%d USING=%u\n",
           this, mStreamID, count, mChunkSize, mServerReceiveWindow,
           session->ServerSessionWindow(), Http2Session::kMaxFrameData,
           dataLength));

      session->DecrementServerSessionWindow(dataLength);
      mServerReceiveWindow -= dataLength;

      LOG3(("Http2StreamBase %p id 0x%x request len remaining %" PRId64 ", "
            "count avail %u, chunk used %u",
            this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
      if (!dataLength && mRequestBodyLenRemaining) {
        return NS_BASE_STREAM_WOULD_BLOCK;
      }
      if (dataLength > mRequestBodyLenRemaining) {
        return NS_ERROR_UNEXPECTED;
      }
      mRequestBodyLenRemaining -= dataLength;
      GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
      ChangeState(SENDING_BODY);
      [[fallthrough]];

    case SENDING_BODY:
      MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
      rv = TransmitFrame(buf, countRead, false);
      MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
                 "Transmit Frame should be all or nothing");

      LOG3(("TransmitFrame() rv=%" PRIx32 " returning %d data bytes. "
            "Header is %d Body is %d.",
            static_cast<uint32_t>(rv), *countRead, mTxInlineFrameUsed,
            mTxStreamFrameSize));

      // normalize a partial write with a WOULD_BLOCK into just a partial write
      // as some code will take WOULD_BLOCK to mean an error with nothing
      // written (e.g. nsHttpTransaction::ReadRequestSegment()
      if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) rv = NS_OK;

      // If that frame was all sent, look for another one
      if (!mTxInlineFrameUsed) ChangeState(GENERATING_BODY);
      break;

    case SENDING_FIN_STREAM:
      MOZ_ASSERT(false"resuming partial fin stream out of OnReadSegment");
      break;

    case UPSTREAM_COMPLETE: {
      MOZ_ASSERT(this->GetHttp2Stream() &&
                 this->GetHttp2Stream()->IsReadingFromPushStream());
      rv = TransmitFrame(nullptr, nullptr, true);
      break;
    }
    default:
      MOZ_ASSERT(false"Http2StreamBase::OnReadSegment non-write state");
      break;
  }

  return rv;
}

//-----------------------------------------------------------------------------
// nsAHttpSegmentWriter
//-----------------------------------------------------------------------------

nsresult Http2StreamBase::OnWriteSegment(char* buf, uint32_t count,
                                         uint32_t* countWritten) {
  LOG3(("Http2StreamBase::OnWriteSegment %p count=%d state=%x 0x%X\n"this,
        count, mUpstreamState, mStreamID));

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  if (!mSegmentWriter) {
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  // sometimes we have read data from the network and stored it in a pipe
  // so that other streams can proceed when the gecko caller is not processing
  // data events fast enough and flow control hasn't caught up yet. This
  // gets the stored data out of that pipe
  if (!mBypassInputBuffer && mSimpleBuffer.Available()) {
    *countWritten = mSimpleBuffer.Read(buf, count);
    MOZ_ASSERT(*countWritten);
    LOG3(
        ("Http2StreamBase::OnWriteSegment read from flow control buffer %p %x "
         "%d\n",
         this, mStreamID, *countWritten));
    return NS_OK;
  }

  // read from the network
  return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
}

// -----------------------------------------------------------------------------
// mirror nsAHttpTransaction
// -----------------------------------------------------------------------------

bool Http2StreamBase::Do0RTT() {
  MOZ_ASSERT(Transaction());
  mAttempting0RTT = false;
  nsAHttpTransaction* trans = Transaction();
  if (trans) {
    mAttempting0RTT = trans->Do0RTT();
  }
  return mAttempting0RTT;
}

nsresult Http2StreamBase::Finish0RTT(bool aRestart, bool aAlpnChanged) {
  MOZ_ASSERT(Transaction());
  mAttempting0RTT = false;
  // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for
  // both arguments because as long as the alpn token stayed the same, we can
  // just reuse what we have in our buffer to send instead of having to have
  // the transaction rewind and read it all over again. We only need to rewind
  // the transaction if we're switching to a new protocol, because our buffer
  // won't get used in that case.
  // ..
  // however, we send in the aRestart value to indicate that early data failed
  // for devtools purposes
  nsresult rv = NS_OK;
  nsAHttpTransaction* trans = Transaction();
  if (trans) {
    rv = trans->Finish0RTT(aAlpnChanged, aAlpnChanged);
    if (aRestart) {
      nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
      if (hTrans) {
        hTrans->Refused0RTT();
      }
    }
  }
  return rv;
}

nsresult Http2StreamBase::GetOriginAttributes(mozilla::OriginAttributes* oa) {
  if (!mSocketTransport) {
    return NS_ERROR_UNEXPECTED;
  }

  return mSocketTransport->GetOriginAttributes(oa);
}

nsHttpTransaction* Http2StreamBase::HttpTransaction() {
  return (Transaction()) ? Transaction()->QueryHttpTransaction() : nullptr;
}

nsHttpConnectionInfo* Http2StreamBase::ConnectionInfo() {
  if (Transaction()) {
    return Transaction()->ConnectionInfo();
  }
  return nullptr;
}

}  // namespace mozilla::net

95%


¤ Dauer der Verarbeitung: 0.14 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge