Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/netwerk/protocol/http/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 160 kB image not shown  

Quelle  Http2Session.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"
#include "nsCOMPtr.h"
#include "nsStringFwd.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 "AltServiceChild.h"
#include "CacheControlParser.h"
#include "CachePushChecker.h"
#include "Http2Push.h"
#include "Http2Session.h"
#include "Http2Stream.h"
#include "Http2StreamBase.h"
#include "Http2StreamTunnel.h"
#include "LoadContextInfo.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/glean/NetwerkMetrics.h"
#include "mozilla/Preferences.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/Telemetry.h"
#include "nsHttp.h"
#include "nsHttpConnection.h"
#include "nsHttpHandler.h"
#include "nsIRequestContext.h"
#include "nsISupportsPriority.h"
#include "nsITLSSocketControl.h"
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "nsSocketTransportService2.h"
#include "nsStandardURL.h"
#include "nsURLHelper.h"
#include "prnetdb.h"
#include "sslerr.h"
#include "sslt.h"

namespace mozilla {
namespace net {

// Http2Session has multiple inheritance of things that implement nsISupports
NS_IMPL_ADDREF(Http2Session)
NS_IMPL_RELEASE(Http2Session)
NS_INTERFACE_MAP_BEGIN(Http2Session)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(Http2Session)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
NS_INTERFACE_MAP_END

static void RemoveStreamFromQueue(Http2StreamBase* aStream,
                                  nsTArray<WeakPtr<Http2StreamBase>>& queue) {
  for (const auto& stream : Reversed(queue)) {
    if (stream == aStream) {
      queue.RemoveElement(stream);
    }
  }
}

static void AddStreamToQueue(Http2StreamBase* aStream,
                             nsTArray<WeakPtr<Http2StreamBase>>& queue) {
  if (!queue.Contains(aStream)) {
    queue.AppendElement(aStream);
  }
}

static already_AddRefed<Http2StreamBase> GetNextStreamFromQueue(
    nsTArray<WeakPtr<Http2StreamBase>>& queue) {
  while (!queue.IsEmpty() && !queue[0]) {
    MOZ_ASSERT(false);
    queue.RemoveElementAt(0);
  }
  if (queue.IsEmpty()) {
    return nullptr;
  }

  RefPtr<Http2StreamBase> stream = queue[0].get();
  queue.RemoveElementAt(0);
  return stream.forget();
}

// "magic" refers to the string that preceeds HTTP/2 on the wire
// to help find any intermediaries speaking an older version of HTTP
const uint8_t Http2Session::kMagicHello[] = {
    0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
    0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};

Http2Session* Http2Session::CreateSession(nsISocketTransport* aSocketTransport,
                                          enum SpdyVersion version,
                                          bool attemptingEarlyData) {
  if (!gHttpHandler) {
    RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance();
    Unused << handler.get();
  }

  Http2Session* session =
      new Http2Session(aSocketTransport, version, attemptingEarlyData);
  session->SendHello();
  return session;
}

Http2Session::Http2Session(nsISocketTransport* aSocketTransport,
                           enum SpdyVersion version, bool attemptingEarlyData)
    : mSocketTransport(aSocketTransport),
      mSegmentReader(nullptr),
      mSegmentWriter(nullptr),
      mNextStreamID(3)  // 1 is reserved for Updgrade handshakes
      ,
      mLastPushedID(0),
      mConcurrentHighWater(0),
      mDownstreamState(BUFFERING_OPENING_SETTINGS),
      mInputFrameBufferSize(kDefaultBufferSize),
      mInputFrameBufferUsed(0),
      mInputFrameDataSize(0),
      mInputFrameDataRead(0),
      mInputFrameFinal(false),
      mInputFrameType(0),
      mInputFrameFlags(0),
      mInputFrameID(0),
      mPaddingLength(0),
      mInputFrameDataStream(nullptr),
      mNeedsCleanup(nullptr),
      mDownstreamRstReason(NO_HTTP_ERROR),
      mExpectedHeaderID(0),
      mExpectedPushPromiseID(0),
      mContinuedPromiseStream(0),
      mFlatHTTPResponseHeadersOut(0),
      mShouldGoAway(false),
      mClosed(false),
      mCleanShutdown(false),
      mReceivedSettings(false),
      mTLSProfileConfirmed(false),
      mGoAwayReason(NO_HTTP_ERROR),
      mClientGoAwayReason(UNASSIGNED),
      mPeerGoAwayReason(UNASSIGNED),
      mGoAwayID(0),
      mOutgoingGoAwayID(0),
      mConcurrent(0),
      mServerPushedResources(0),
      mServerInitialStreamWindow(kDefaultRwin),
      mLocalSessionWindow(kDefaultRwin),
      mServerSessionWindow(kDefaultRwin),
      mInitialRwin(ASpdySession::kInitialRwin),
      mOutputQueueSize(kDefaultQueueSize),
      mOutputQueueUsed(0),
      mOutputQueueSent(0),
      mLastReadEpoch(PR_IntervalNow()),
      mPingSentEpoch(0),
      mPreviousUsed(false),
      mAggregatedHeaderSize(0),
      mWaitingForSettingsAck(false),
      mGoAwayOnPush(false),
      mUseH2Deps(false),
      mAttemptingEarlyData(attemptingEarlyData),
      mOriginFrameActivated(false),
      mCntActivated(0),
      mTlsHandshakeFinished(false),
      mPeerFailedHandshake(false),
      mTrrStreams(0),
      mEnableWebsockets(false),
      mPeerAllowsWebsockets(false),
      mProcessedWaitingWebsockets(false) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  static uint64_t sSerial;
  mSerial = ++sSerial;

  LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n"this, mSerial));

  mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
  mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
  mDecompressBuffer.SetCapacity(kDefaultBufferSize);

  mPushAllowance = gHttpHandler->SpdyPushAllowance();
  mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
  mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
  mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();

  mLastDataReadEpoch = mLastReadEpoch;

  mPingThreshold = gHttpHandler->SpdyPingThreshold();
  mPreviousPingThreshold = mPingThreshold;
  mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId();

  mEnableWebsockets = StaticPrefs::network_http_http2_websockets();

  bool dumpHpackTables = StaticPrefs::network_http_http2_enable_hpack_dump();
  mCompressor.SetDumpTables(dumpHpackTables);
  mDecompressor.SetDumpTables(dumpHpackTables);
}

void Http2Session::Shutdown(nsresult aReason) {
  for (const auto& stream : mStreamTransactionHash.Values()) {
    ShutdownStream(stream, aReason);
  }

  for (auto& stream : mTunnelStreams) {
    ShutdownStream(stream, aReason);
  }
}

void Http2Session::ShutdownStream(Http2StreamBase* aStream, nsresult aReason) {
  // On a clean server hangup the server sets the GoAwayID to be the ID of
  // the last transaction it processed. If the ID of stream in the
  // local stream is greater than that it can safely be restarted because the
  // server guarantees it was not partially processed. Streams that have not
  // registered an ID haven't actually been sent yet so they can always be
  // restarted.
  if (mCleanShutdown &&
      (aStream->StreamID() > mGoAwayID || !aStream->HasRegisteredID())) {
    CloseStream(aStream, NS_ERROR_NET_RESET);  // can be restarted
  } else if (aStream->RecvdData()) {
    CloseStream(aStream, NS_ERROR_NET_PARTIAL_TRANSFER);
  } else if (mGoAwayReason == INADEQUATE_SECURITY) {
    CloseStream(aStream, NS_ERROR_NET_INADEQUATE_SECURITY);
  } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) {
    CloseStream(aStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
  } else if (!mCleanShutdown && PossibleZeroRTTRetryError(aReason)) {
    CloseStream(aStream, aReason);
  } else {
    CloseStream(aStream, NS_ERROR_ABORT);
  }
}

Http2Session::~Http2Session() {
  MOZ_DIAGNOSTIC_ASSERT(OnSocketThread());
  LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X"this,
        mDownstreamState));

  Shutdown(NS_OK);

  if (mTrrStreams) {
    mozilla::glean::networking::trr_request_count_per_conn.Get("h2"_ns).Add(
        static_cast<int32_t>(mTrrStreams));
  }
  Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
  Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN_3, mCntActivated);
  Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
                        mServerPushedResources);
  Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
  Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
  Telemetry::Accumulate(Telemetry::HTTP2_FAIL_BEFORE_SETTINGS,
                        mPeerFailedHandshake);
}

inline nsresult Http2Session::SessionError(enum errorType reason) {
  LOG3(("Http2Session::SessionError %p reason=0x%x mPeerGoAwayReason=0x%x",
        this, reason, mPeerGoAwayReason));
  mGoAwayReason = reason;

  if (reason == INADEQUATE_SECURITY) {
    // This one is special, as we have an error page just for this
    return NS_ERROR_NET_INADEQUATE_SECURITY;
  }

  // We're the one sending a generic GOAWAY
  return NS_ERROR_NET_HTTP2_SENT_GOAWAY;
}

void Http2Session::LogIO(Http2Session* self, Http2StreamBase* stream,
                         const char* label, const char* data,
                         uint32_t datalen) {
  if (!MOZ_LOG_TEST(gHttpIOLog, LogLevel::Verbose)) {
    return;
  }

  MOZ_LOG(gHttpIOLog, LogLevel::Verbose,
          ("Http2Session::LogIO %p stream=%p id=0x%X [%s]", self, stream,
           stream ? stream->StreamID() : 0, label));

  // Max line is (16 * 3) + 10(prefix) + newline + null
  char linebuf[128];
  uint32_t index;
  char* line = linebuf;

  linebuf[127] = 0;

  for (index = 0; index < datalen; ++index) {
    if (!(index % 16)) {
      if (index) {
        *line = 0;
        MOZ_LOG(gHttpIOLog, LogLevel::Verbose, ("%s", linebuf));
      }
      line = linebuf;
      snprintf(line, 128, "%08X: ", index);
      line += 10;
    }
    snprintf(line, 128 - (line - linebuf), "%02X ",
             (reinterpret_cast<const uint8_t*>(data))[index]);
    line += 3;
  }
  if (index) {
    *line = 0;
    MOZ_LOG(gHttpIOLog, LogLevel::Verbose, ("%s", linebuf));
  }
}

using Http2ControlFx = nsresult (*)(Http2Session*);
static constexpr Http2ControlFx sControlFunctions[] = {
    nullptr,  // type 0 data is not a control function
    Http2Session::RecvHeaders,
    Http2Session::RecvPriority,
    Http2Session::RecvRstStream,
    Http2Session::RecvSettings,
    Http2Session::RecvPushPromise,
    Http2Session::RecvPing,
    Http2Session::RecvGoAway,
    Http2Session::RecvWindowUpdate,
    Http2Session::RecvContinuation,
    Http2Session::RecvAltSvc,          // extension for type 0x0A
    Http2Session::RecvUnused,          // 0x0B was BLOCKED still radioactive
    Http2Session::RecvOrigin,          // extension for type 0x0C
    Http2Session::RecvUnused,          // 0x0D
    Http2Session::RecvUnused,          // 0x0E
    Http2Session::RecvUnused,          // 0x0F
    Http2Session::RecvPriorityUpdate,  // 0x10
};

static_assert(sControlFunctions[Http2Session::FRAME_TYPE_DATA] == nullptr);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_HEADERS] ==
              Http2Session::RecvHeaders);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PRIORITY] ==
              Http2Session::RecvPriority);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_RST_STREAM] ==
              Http2Session::RecvRstStream);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_SETTINGS] ==
              Http2Session::RecvSettings);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PUSH_PROMISE] ==
              Http2Session::RecvPushPromise);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PING] ==
              Http2Session::RecvPing);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_GOAWAY] ==
              Http2Session::RecvGoAway);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_WINDOW_UPDATE] ==
              Http2Session::RecvWindowUpdate);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_CONTINUATION] ==
              Http2Session::RecvContinuation);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_ALTSVC] ==
              Http2Session::RecvAltSvc);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_UNUSED] ==
              Http2Session::RecvUnused);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_ORIGIN] ==
              Http2Session::RecvOrigin);
static_assert(sControlFunctions[0x0D] == Http2Session::RecvUnused);
static_assert(sControlFunctions[0x0E] == Http2Session::RecvUnused);
static_assert(sControlFunctions[0x0F] == Http2Session::RecvUnused);
static_assert(sControlFunctions[Http2Session::FRAME_TYPE_PRIORITY_UPDATE] ==
              Http2Session::RecvPriorityUpdate);

bool Http2Session::RoomForMoreConcurrent() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  return (mConcurrent < mMaxConcurrent);
}

bool Http2Session::RoomForMoreStreams() {
  if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) {
    return false;
  }

  return !mShouldGoAway;
}

PRIntervalTime Http2Session::IdleTime() {
  return PR_IntervalNow() - mLastDataReadEpoch;
}

uint32_t Http2Session::ReadTimeoutTick(PRIntervalTime now) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n"this,
        PR_IntervalToSeconds(now - mLastReadEpoch)));

  if (!mPingThreshold) {
    return UINT32_MAX;
  }

  if ((now - mLastReadEpoch) < mPingThreshold) {
    // recent activity means ping is not an issue
    if (mPingSentEpoch) {
      mPingSentEpoch = 0;
      if (mPreviousUsed) {
        // restore the former value
        mPingThreshold = mPreviousPingThreshold;
        mPreviousUsed = false;
      }
    }

    return PR_IntervalToSeconds(mPingThreshold) -
           PR_IntervalToSeconds(now - mLastReadEpoch);
  }

  if (mPingSentEpoch) {
    bool isTrr = (mTrrStreams > 0);
    uint32_t pingTimeout = isTrr ? StaticPrefs::network_trr_ping_timeout()
                                 : gHttpHandler->SpdyPingTimeout();
    LOG3(
        ("Http2Session::ReadTimeoutTick %p handle outstanding ping, "
         "timeout=%d\n",
         this, pingTimeout));
    if ((now - mPingSentEpoch) >= pingTimeout) {
      LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n"this));
      if (mConnection) {
        mConnection->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
      }
      mPingSentEpoch = 0;
      if (isTrr) {
        // These must be set this way to ensure we gracefully restart all
        // streams
        mGoAwayID = 0;
        mCleanShutdown = true;
        // If TRR is mode 2, this Http2Session will be closed due to TRR request
        // timeout, so we won't reach this code. If we are in mode 3, the
        // request timeout is usually larger than the ping timeout. We close the
        // stream with NS_ERROR_NET_RESET, so the transactions can be restarted.
        Close(NS_ERROR_NET_RESET);
      } else {
        Close(NS_ERROR_NET_TIMEOUT);
      }
      return UINT32_MAX;
    }
    return 1;  // run the tick aggressively while ping is outstanding
  }

  LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n"this));

  mPingSentEpoch = PR_IntervalNow();
  if (!mPingSentEpoch) {
    mPingSentEpoch = 1;  // avoid the 0 sentinel value
  }
  GeneratePing(false);
  Unused << ResumeRecv();  // read the ping reply

  // Check for orphaned push streams. This looks expensive, but generally the
  // list is empty.
  Http2PushedStream* deleteMe;
  TimeStamp timestampNow;
  do {
    deleteMe = nullptr;

    for (uint32_t index = mPushedStreams.Length(); index > 0; --index) {
      Http2PushedStream* pushedStream = mPushedStreams[index - 1];

      if (timestampNow.IsNull()) {
        timestampNow = TimeStamp::Now();  // lazy initializer
      }

      // if stream finished, but is not connected, and its been like that for
      // long then cleanup the stream.
      if (pushedStream->IsOrphaned(timestampNow)) {
        LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n"this,
              pushedStream->StreamID()));
        deleteMe = pushedStream;
        break;  // don't CleanupStream() while iterating this vector
      }
    }
    if (deleteMe) CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);

  } while (deleteMe);

  return 1;  // run the tick aggressively while ping is outstanding
}

uint32_t Http2Session::RegisterStreamID(Http2StreamBase* stream,
                                        uint32_t aNewID) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(mNextStreamID < 0xfffffff0,
             "should have stopped admitting streams");
  MOZ_ASSERT(!(aNewID & 1),
             "0 for autoassign pull, otherwise explicit even push assignment");

  if (!aNewID) {
    // auto generate a new pull stream ID
    aNewID = mNextStreamID;
    MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
    mNextStreamID += 2;
  }

  LOG1(
      ("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
       "concurrent=%d",
       this, stream, aNewID, mConcurrent));

  // We've used up plenty of ID's on this session. Start
  // moving to a new one before there is a crunch involving
  // server push streams or concurrent non-registered submits
  if (aNewID >= kMaxStreamID) mShouldGoAway = true;

  // integrity check
  if (mStreamIDHash.Contains(aNewID)) {
    LOG3((" New ID already present\n"));
    MOZ_ASSERT(false"New ID already present in mStreamIDHash");
    mShouldGoAway = true;
    return kDeadStreamID;
  }

  mStreamIDHash.InsertOrUpdate(aNewID, stream);

  if (aNewID & 1) {
    // don't count push streams here
    RefPtr<nsHttpConnectionInfo> ci(stream->ConnectionInfo());
    if (ci && ci->GetIsTrrServiceChannel()) {
      IncrementTrrCounter();
    }
  }
  return aNewID;
}

bool Http2Session::AddStream(nsAHttpTransaction* aHttpTransaction,
                             int32_t aPriority,
                             nsIInterfaceRequestor* aCallbacks) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  // integrity check
  if (mStreamTransactionHash.Contains(aHttpTransaction)) {
    LOG3((" New transaction already present\n"));
    MOZ_ASSERT(false"AddStream duplicate transaction pointer");
    return false;
  }

  if (!mConnection) {
    mConnection = aHttpTransaction->Connection();
  }

  if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
    mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
    LOG3(("Http2Session::AddStream first session=%p trans=%p "this,
          mFirstHttpTransaction.get()));
  }

  if (mClosed || mShouldGoAway) {
    nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
    if (trans) {
      RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
      pushedStreamWrapper = trans->GetPushedStream();
      if (!pushedStreamWrapper || !pushedStreamWrapper->GetStream()) {
        LOG3(
            ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - "
             "resched.\n",
             this, aHttpTransaction, trans));
        aHttpTransaction->SetConnection(nullptr);
        nsresult rv =
            gHttpHandler->InitiateTransaction(trans, trans->Priority());
        if (NS_FAILED(rv)) {
          LOG3(
              ("Http2Session::AddStream %p atrans=%p trans=%p failed to "
               "initiate "
               "transaction (%08x).\n",
               this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
        }
        return true;
      }
    }
  }

  aHttpTransaction->SetConnection(this);
  aHttpTransaction->OnActivated();

  CreateStream(aHttpTransaction, aPriority, Http2StreamBaseType::Normal);
  return true;
}

void Http2Session::CreateStream(nsAHttpTransaction* aHttpTransaction,
                                int32_t aPriority,
                                Http2StreamBaseType streamType) {
  RefPtr<Http2StreamBase> refStream;
  switch (streamType) {
    case Http2StreamBaseType::Normal:
      refStream =
          new Http2Stream(aHttpTransaction, this, aPriority, mCurrentBrowserId);
      break;
    case Http2StreamBaseType::WebSocket:
    case Http2StreamBaseType::Tunnel:
    case Http2StreamBaseType::ServerPush:
      MOZ_RELEASE_ASSERT(false);
      return;
  }

  LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
        "NextID=0x%X (tentative)",
        this, refStream.get(), mSerial, mNextStreamID));

  RefPtr<Http2StreamBase> stream = refStream;
  mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, std::move(refStream));

  AddStreamToQueue(stream, mReadyForWrite);
  SetWriteCallbacks();

  // Kick off the SYN transmit without waiting for the poll loop
  // This won't work for the first stream because there is no segment reader
  // yet.
  if (mSegmentReader) {
    uint32_t countRead;
    Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
  }

  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
      !aHttpTransaction->IsNullTransaction()) {
    LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
          this, aHttpTransaction));
    DontReuse();
  }
}

already_AddRefed<nsHttpConnection> Http2Session::CreateTunnelStream(
    nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
    PRIntervalTime aRtt, bool aIsWebSocket) {
  RefPtr<Http2StreamTunnel> refStream = CreateTunnelStreamFromConnInfo(
      this, mCurrentBrowserId, aHttpTransaction->ConnectionInfo(),
      aIsWebSocket);

  RefPtr<nsHttpConnection> newConn = refStream->CreateHttpConnection(
      aHttpTransaction, aCallbacks, aRtt, aIsWebSocket);

  mTunnelStreams.AppendElement(std::move(refStream));
  return newConn.forget();
}

void Http2Session::QueueStream(Http2StreamBase* stream) {
  // will be removed via processpending or a shutdown path
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(!stream->CountAsActive());
  MOZ_ASSERT(!stream->Queued());

  LOG3(("Http2Session::QueueStream %p stream %p queued."this, stream));

#ifdef DEBUG
  for (const auto& qStream : mQueuedStreams) {
    MOZ_ASSERT(qStream != stream);
    MOZ_ASSERT(qStream->Queued());
  }
#endif

  stream->SetQueued(true);
  AddStreamToQueue(stream, mQueuedStreams);
}

void Http2Session::ProcessPending() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  RefPtr<Http2StreamBase> stream;
  while (RoomForMoreConcurrent() &&
         (stream = GetNextStreamFromQueue(mQueuedStreams))) {
    LOG3(("Http2Session::ProcessPending %p stream %p woken from queue."this,
          stream.get()));
    MOZ_ASSERT(!stream->CountAsActive());
    MOZ_ASSERT(stream->Queued());
    stream->SetQueued(false);
    AddStreamToQueue(stream, mReadyForWrite);
    SetWriteCallbacks();
  }
}

nsresult Http2Session::NetworkRead(nsAHttpSegmentWriter* writer, char* buf,
                                   uint32_t count, uint32_t* countWritten) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  if (!count) {
    *countWritten = 0;
    return NS_OK;
  }

  nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
  if (NS_SUCCEEDED(rv) && *countWritten > 0) {
    mLastReadEpoch = PR_IntervalNow();
  }
  return rv;
}

void Http2Session::SetWriteCallbacks() {
  if (mConnection &&
      (GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) {
    Unused << mConnection->ResumeSend();
  }
}

void Http2Session::RealignOutputQueue() {
  if (mAttemptingEarlyData) {
    // We can't realign right now, because we may need what's in there if early
    // data fails.
    return;
  }

  mOutputQueueUsed -= mOutputQueueSent;
  memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent,
          mOutputQueueUsed);
  mOutputQueueSent = 0;
}

void Http2Session::FlushOutputQueue() {
  if (!mSegmentReader || !mOutputQueueUsed) return;

  nsresult rv;
  uint32_t countRead;
  uint32_t avail = mOutputQueueUsed - mOutputQueueSent;

  if (!avail && mAttemptingEarlyData) {
    // This is kind of a hack, but there are cases where we'll have already
    // written the data we want whlie doing early data, but we get called again
    // with a reader, and we need to avoid calling the reader when there's
    // nothing for it to read.
    return;
  }

  rv = mSegmentReader->OnReadSegment(
      mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead);
  LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d",
        this, avail, static_cast<uint32_t>(rv), countRead));

  // Dont worry about errors on write, we will pick this up as a read error too
  if (NS_FAILED(rv)) return;

  mOutputQueueSent += countRead;

  if (mAttemptingEarlyData) {
    return;
  }

  if (countRead == avail) {
    mOutputQueueUsed = 0;
    mOutputQueueSent = 0;
    return;
  }

  // If the output queue is close to filling up and we have sent out a good
  // chunk of data from the beginning then realign it.

  if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
      ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
    RealignOutputQueue();
  }
}

void Http2Session::DontReuse() {
  LOG3(("Http2Session::DontReuse %p\n"this));
  if (!OnSocketThread()) {
    LOG3(("Http2Session %p not on socket thread\n"this));
    nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
        "Http2Session::DontReuse"this, &Http2Session::DontReuse);
    gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
    return;
  }

  mShouldGoAway = true;
  if (!mClosed && !mStreamTransactionHash.Count()) {
    Close(NS_OK);
  }
}

enum SpdyVersion Http2Session::SpdyVersion() { return SpdyVersion::HTTP_2; }

uint32_t Http2Session::GetWriteQueueSize() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  return mReadyForWrite.Length();
}

void Http2Session::ChangeDownstreamState(enum internalStateType newState) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X"this,
        mDownstreamState, newState));
  mDownstreamState = newState;
}

void Http2Session::ResetDownstreamState() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  LOG3(("Http2Session::ResetDownstreamState() %p"this));
  ChangeDownstreamState(BUFFERING_FRAME_HEADER);

  if (mInputFrameFinal && mInputFrameDataStream) {
    mInputFrameFinal = false;
    LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
    mInputFrameDataStream->SetRecvdFin(true);
    MaybeDecrementConcurrent(mInputFrameDataStream);
  }
  mInputFrameFinal = false;
  mInputFrameBufferUsed = 0;
  mInputFrameDataStream = nullptr;
}

// return true if activated (and counted against max)
// otherwise return false and queue
bool Http2Session::TryToActivate(Http2StreamBase* aStream) {
  if (aStream->Queued()) {
    LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n"this,
          aStream));
    return false;
  }

  if (!RoomForMoreConcurrent()) {
    LOG3(
        ("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
         "streams\n",
         this, aStream));
    QueueStream(aStream);
    return false;
  }

  LOG3(("Http2Session::TryToActivate %p stream=%p\n"this, aStream));
  IncrementConcurrent(aStream);

  mCntActivated++;
  return true;
}

void Http2Session::IncrementConcurrent(Http2StreamBase* stream) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
             "Do not activate pushed streams");

  nsAHttpTransaction* trans = stream->Transaction();
  if (!trans || !trans->IsNullTransaction()) {
    MOZ_ASSERT(!stream->CountAsActive());
    stream->SetCountAsActive(true);
    ++mConcurrent;

    if (mConcurrent > mConcurrentHighWater) {
      mConcurrentHighWater = mConcurrent;
    }
    LOG3(
        ("Http2Session::IncrementCounter %p counting stream %p Currently %d "
         "streams in session, high water mark is %d\n",
         this, stream, mConcurrent, mConcurrentHighWater));
  }
}

// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
// dest must have 9 bytes of allocated space
template <typename charType>
void Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
                                     uint8_t frameType, uint8_t frameFlags,
                                     uint32_t streamID) {
  MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
  MOZ_ASSERT(!(streamID & 0x80000000));
  MOZ_ASSERT(!frameFlags || (frameType != FRAME_TYPE_PRIORITY &&
                             frameType != FRAME_TYPE_RST_STREAM &&
                             frameType != FRAME_TYPE_GOAWAY &&
                             frameType != FRAME_TYPE_WINDOW_UPDATE));

  dest[0] = 0x00;
  NetworkEndian::writeUint16(dest + 1, frameLength);
  dest[3] = frameType;
  dest[4] = frameFlags;
  NetworkEndian::writeUint32(dest + 5, streamID);
}

char* Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded) {
  // this is an infallible allocation (if an allocation is
  // needed, which is probably isn't)
  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
               mOutputQueueUsed, mOutputQueueSize);
  return mOutputQueueBuffer.get() + mOutputQueueUsed;
}

template void Http2Session::CreateFrameHeader(char* dest, uint16_t frameLength,
                                              uint8_t frameType,
                                              uint8_t frameFlags,
                                              uint32_t streamID);

template void Http2Session::CreateFrameHeader(uint8_t* dest,
                                              uint16_t frameLength,
                                              uint8_t frameType,
                                              uint8_t frameFlags,
                                              uint32_t streamID);

void Http2Session::MaybeDecrementConcurrent(Http2StreamBase* aStream) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n"this,
        aStream->StreamID(), mConcurrent, aStream->CountAsActive()));

  if (!aStream->CountAsActive()) return;

  MOZ_ASSERT(mConcurrent);
  aStream->SetCountAsActive(false);
  --mConcurrent;
  ProcessPending();
}

// Need to decompress some data in order to keep the compression
// context correct, but we really don't care what the result is
nsresult Http2Session::UncompressAndDiscard(bool isPush) {
  nsresult rv;
  nsAutoCString trash;

  rv = mDecompressor.DecodeHeaderBlock(
      reinterpret_cast<const uint8_t*>(mDecompressBuffer.BeginReading()),
      mDecompressBuffer.Length(), trash, isPush);
  mDecompressBuffer.Truncate();
  if (NS_FAILED(rv)) {
    LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n"this));
    mGoAwayReason = COMPRESSION_ERROR;
    return rv;
  }
  return NS_OK;
}

void Http2Session::GeneratePing(bool isAck) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::GeneratePing %p isAck=%d\n"this, isAck));

  char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
  mOutputQueueUsed += kFrameHeaderBytes + 8;

  if (isAck) {
    CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
    memcpy(packet + kFrameHeaderBytes,
           mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
  } else {
    CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
    memset(packet + kFrameHeaderBytes, 0, 8);
  }

  LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
  FlushOutputQueue();
}

void Http2Session::GenerateSettingsAck() {
  // need to generate ack of this settings frame
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::GenerateSettingsAck %p\n"this));

  char* packet = EnsureOutputBuffer(kFrameHeaderBytes);
  mOutputQueueUsed += kFrameHeaderBytes;
  CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
  LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
  FlushOutputQueue();
}

void Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  // make sure we don't do this twice for the same stream (at least if we
  // have a stream entry for it)
  Http2StreamBase* stream = mStreamIDHash.Get(aID);
  if (stream) {
    if (stream->SentReset()) return;
    stream->SetSentReset(true);
  }

  LOG3(("Http2Session::GenerateRst %p 0x%X %d\n"this, aID, aStatusCode));

  uint32_t frameSize = kFrameHeaderBytes + 4;
  char* packet = EnsureOutputBuffer(frameSize);
  mOutputQueueUsed += frameSize;
  CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);

  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);

  LogIO(this, nullptr, "Generate Reset", packet, frameSize);
  FlushOutputQueue();
}

void Http2Session::GenerateGoAway(uint32_t aStatusCode) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::GenerateGoAway %p code=%X\n"this, aStatusCode));

  mClientGoAwayReason = aStatusCode;
  uint32_t frameSize = kFrameHeaderBytes + 8;
  char* packet = EnsureOutputBuffer(frameSize);
  mOutputQueueUsed += frameSize;

  CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);

  // last-good-stream-id are bytes 9-12 reflecting pushes
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);

  // bytes 13-16 are the status code.
  NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);

  LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
  FlushOutputQueue();
}

// The Hello is comprised of
// 1] 24 octets of magic, which are designed to
// flush out silent but broken intermediaries
// 2] a settings frame which sets a small flow control window for pushes
// 3] a window update frame which creates a large session flow control window
// 4] 6 priority frames for streams which will never be opened with headers
//    these streams (3, 5, 7, 9, b, d) build a dependency tree that all other
//    streams will be direct leaves of.
void Http2Session::SendHello() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::SendHello %p\n"this));

  // sized for magic + 6 settings and a session window update and 6 priority
  // frames 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window
  // update, 6 priority frames at 14 (9 + 5) each
  static const uint32_t maxSettings = 6;
  static const uint32_t prioritySize =
      kPriorityGroupCount * (kFrameHeaderBytes + 5);
  static const uint32_t maxDataLen =
      24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
  char* packet = EnsureOutputBuffer(maxDataLen);
  memcpy(packet, kMagicHello, 24);
  mOutputQueueUsed += 24;
  LogIO(this, nullptr, "Magic Connection Header", packet, 24);

  packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
  memset(packet, 0, maxDataLen - 24);

  // frame header will be filled in after we know how long the frame is
  uint8_t numberOfEntries = 0;

  // entries need to be listed in order by ID
  // 1st entry is bytes 9 to 14
  // 2nd entry is bytes 15 to 20
  // 3rd entry is bytes 21 to 26
  // 4th entry is bytes 27 to 32
  // 5th entry is bytes 33 to 38

  // Let the other endpoint know about our default HPACK decompress table size
  uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
  mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
  NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
                             SETTINGS_TYPE_HEADER_TABLE_SIZE);
  NetworkEndian::writeUint32(
      packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2,
      maxHpackBufferSize);
  numberOfEntries++;

  if (!StaticPrefs::network_http_http2_allow_push()) {
    // If we don't support push then set MAX_CONCURRENT to 0 and also
    // set ENABLE_PUSH to 0
    NetworkEndian::writeUint16(
        packet + kFrameHeaderBytes + (6 * numberOfEntries),
        SETTINGS_TYPE_ENABLE_PUSH);
    // The value portion of the setting pair is already initialized to 0
    numberOfEntries++;

    if (StaticPrefs::network_http_http2_send_push_max_concurrent_frame()) {
      NetworkEndian::writeUint16(
          packet + kFrameHeaderBytes + (6 * numberOfEntries),
          SETTINGS_TYPE_MAX_CONCURRENT);
      // The value portion of the setting pair is already initialized to 0
      numberOfEntries++;
    }

    mWaitingForSettingsAck = true;
  }

  // Advertise the Push RWIN for the session, and on each new pull stream
  // send a window update
  NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
                             SETTINGS_TYPE_INITIAL_WINDOW);
  NetworkEndian::writeUint32(
      packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
  numberOfEntries++;

  // Make sure the other endpoint knows that we're sticking to the default max
  // frame size
  NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
                             SETTINGS_TYPE_MAX_FRAME_SIZE);
  NetworkEndian::writeUint32(
      packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
  numberOfEntries++;

  bool disableRFC7540Priorities =
      !StaticPrefs::network_http_http2_enabled_deps() ||
      !gHttpHandler->CriticalRequestPrioritization();

  // See bug 1909666. Sending this new setting could break some websites.
  if (disableRFC7540Priorities &&
      StaticPrefs::network_http_http2_send_NO_RFC7540_PRI()) {
    NetworkEndian::writeUint16(
        packet + kFrameHeaderBytes + (6 * numberOfEntries),
        SETTINGS_NO_RFC7540_PRIORITIES);
    NetworkEndian::writeUint32(
        packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2,
        disableRFC7540Priorities ? 1 : 0);
    numberOfEntries++;
  }

  MOZ_ASSERT(numberOfEntries <= maxSettings);
  uint32_t dataLen = 6 * numberOfEntries;
  CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
  mOutputQueueUsed += kFrameHeaderBytes + dataLen;

  LogIO(this, nullptr, "Generate Settings", packet,
        kFrameHeaderBytes + dataLen);

  // now bump the local session window from 64KB
  uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
  if (kDefaultRwin < mInitialRwin) {
    // send a window update for the session (Stream 0) for something large
    mLocalSessionWindow = mInitialRwin;

    packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
    CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
    mOutputQueueUsed += kFrameHeaderBytes + 4;
    NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);

    LOG3(("Session Window increase at start of session %p %u\n"this,
          sessionWindowBump));
    LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
  }

  if (!disableRFC7540Priorities) {
    mUseH2Deps = true;
    MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
    CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
    mNextStreamID += 2;
    MOZ_ASSERT(mNextStreamID == kOtherGroupID);
    CreatePriorityNode(kOtherGroupID, 0, 100, "other");
    mNextStreamID += 2;
    MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
    CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
    mNextStreamID += 2;
    MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
    CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0,
                       "speculative");
    mNextStreamID += 2;
    MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
    CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
    mNextStreamID += 2;
    MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
    CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
    mNextStreamID += 2;
    // Hey, you! YES YOU! If you add/remove any groups here, you almost
    // certainly need to change the lookup of the stream/ID hash in
    // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
  }

  FlushOutputQueue();
}

void Http2Session::SendPriorityFrame(uint32_t streamID, uint32_t dependsOn,
                                     uint8_t weight) {
  // If mUseH2Deps is false, that means that we've sent
  // SETTINGS_NO_RFC7540_PRIORITIES = 1. Since the server must
  // ignore priority frames anyway, we can skip sending it.
  if (!UseH2Deps()) {
    return;
  }
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(
      ("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
       "weight %d\n",
       this, streamID, dependsOn, weight));

  char* packet = CreatePriorityFrame(streamID, dependsOn, weight);

  LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
  FlushOutputQueue();
}

void Http2Session::SendPriorityUpdateFrame(uint32_t streamID, uint8_t urgency,
                                           bool incremental) {
  CreatePriorityUpdateFrame(streamID, urgency, incremental);
  FlushOutputQueue();
}

char* Http2Session::CreatePriorityUpdateFrame(uint32_t streamID,
                                              uint8_t urgency,
                                              bool incremental) {
  // https://www.rfc-editor.org/rfc/rfc9218.html#section-7.1
  nsPrintfCString priorityFieldValue(
      "%s", urgency != 3 ? nsPrintfCString("u=%d", urgency).get() : "");
  size_t payloadSize = 4 + priorityFieldValue.Length();
  char* packet = EnsureOutputBuffer(kFrameHeaderBytes + payloadSize);
  // The Stream Identifier field (see Section 5.1.1 of [HTTP/2]) in the
  // PRIORITY_UPDATE frame header MUST be zero
  CreateFrameHeader(packet, payloadSize,
                    Http2Session::FRAME_TYPE_PRIORITY_UPDATE,
                    0,   // unused flags
                    0);  // streamID

  // Reserved (1),
  // Prioritized Stream ID (31),
  MOZ_ASSERT(!(streamID & 0x80000000));
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, streamID & 0x7FFFFFFF);
  // Priority Field Value (..),
  for (size_t i = 0; i < priorityFieldValue.Length(); ++i) {
    packet[kFrameHeaderBytes + 4 + i] = priorityFieldValue[i];
  }
  mOutputQueueUsed += kFrameHeaderBytes + payloadSize;

  LogIO(this, nullptr, "SendPriorityUpdateFrame", packet,
        kFrameHeaderBytes + payloadSize);
  return packet;
}

char* Http2Session::CreatePriorityFrame(uint32_t streamID, uint32_t dependsOn,
                                        uint8_t weight) {
  MOZ_ASSERT(streamID, "Priority on stream 0");
  char* packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
  CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
  mOutputQueueUsed += kFrameHeaderBytes + 5;
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes,
                             dependsOn);   // depends on
  packet[kFrameHeaderBytes + 4] = weight;  // weight
  return packet;
}

void Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn,
                                      uint8_t weight, const char* label) {
  char* packet = CreatePriorityFrame(streamID, dependsOn, weight);

  LOG3(
      ("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
       "weight %d for %s class\n",
       this, streamID, dependsOn, weight, label));
  LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
}

// perform a bunch of integrity checks on the stream.
// returns true if passed, false (plus LOG and ABORT) if failed.
bool Http2Session::VerifyStream(Http2StreamBase* aStream,
                                uint32_t aOptionalID = 0) {
  // This is annoying, but at least it is O(1)
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

#ifndef DEBUG
  // Only do the real verification in debug builds
  return true;
#else   // DEBUG

  if (!aStream) return true;

  uint32_t test = 0;

  do {
    if (aStream->StreamID() == kDeadStreamID) break;

    test++;
    if (aStream->StreamID()) {
      Http2StreamBase* idStream = mStreamIDHash.Get(aStream->StreamID());

      test++;
      if (idStream != aStream) break;

      if (aOptionalID) {
        test++;
        if (idStream->StreamID() != aOptionalID) break;
      }
    }

    if (aStream->IsTunnel()) {
      return true;
    }

    nsAHttpTransaction* trans = aStream->Transaction();

    test++;
    if (!trans) break;

    test++;
    if (mStreamTransactionHash.GetWeak(trans) != aStream) break;

    // tests passed
    return true;
  } while (false);

  LOG3(
      ("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
       "optionalID=0x%X trans=%p test=%d\n",
       this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(),
       test));

  MOZ_ASSERT(false"VerifyStream");
  return false;
#endif  // DEBUG
}

// static
Http2StreamTunnel* Http2Session::CreateTunnelStreamFromConnInfo(
    Http2Session* session, uint64_t bcId, nsHttpConnectionInfo* info,
    bool isWebSocket) {
  MOZ_ASSERT(info);
  MOZ_ASSERT(session);

  if (isWebSocket) {
    LOG(("Http2Session creating Http2StreamWebSocket"));
    MOZ_ASSERT(session->GetWebSocketSupport() == WebSocketSupport::SUPPORTED);
    return new Http2StreamWebSocket(
        session, nsISupportsPriority::PRIORITY_NORMAL, bcId, info);
  }

  MOZ_ASSERT(info->UsingHttpProxy() && info->UsingConnect());
  LOG(("Http2Session creating Http2StreamTunnel"));
  return new Http2StreamTunnel(session, nsISupportsPriority::PRIORITY_NORMAL,
                               bcId, info);
}

void Http2Session::CleanupStream(Http2StreamBase* aStream, nsresult aResult,
                                 errorType aResetCode) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n"this, aStream,
        aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult)));
  if (!aStream) {
    return;
  }

  Http2PushedStream* pushSource = nullptr;
  Http2Stream* h2Stream = aStream->GetHttp2Stream();
  if (h2Stream) {
    pushSource = h2Stream->PushSource();
    if (pushSource) {
      // aStream is a synthetic  attached to an even push
      MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
      MOZ_ASSERT(!aStream->StreamID());
      MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
      h2Stream->ClearPushSource();
    }
  }

  if (aStream->DeferCleanup(aResult)) {
    LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
    return;
  }

  if (!VerifyStream(aStream)) {
    LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
    return;
  }

  // don't reset a stream that has recevied a fin or rst
  if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
      !(mInputFrameFinal &&
        (aStream == mInputFrameDataStream))) {  // !(recvdfin with mark pending)
    LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n",
          aStream->StreamID(), aResetCode));
    GenerateRstStream(aResetCode, aStream->StreamID());
  }

  CloseStream(aStream, aResult);

  // Remove the stream from the ID hash table and, if an even id, the pushed
  // table too.
  uint32_t id = aStream->StreamID();
  if (id > 0) {
    mStreamIDHash.Remove(id);
    if (!(id & 1)) {
      mPushedStreams.RemoveElement(aStream);
      Http2PushedStream* pushStream = static_cast<Http2PushedStream*>(aStream);
      nsAutoCString hashKey;
      DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
      MOZ_ASSERT(rv);
      nsIRequestContext* requestContext = aStream->RequestContext();
      if (requestContext) {
        SpdyPushCache* cache = requestContext->GetSpdyPushCache();
        if (cache) {
          // Make sure the id of the stream in the push cache is the same
          // as the id of the stream we're cleaning up! See bug 1368080.
          Http2PushedStream* trash =
              cache->RemovePushedStreamHttp2ByID(hashKey, aStream->StreamID());
          LOG3(
              ("Http2Session::CleanupStream %p aStream=%p pushStream=%p "
               "trash=%p",
               this, aStream, pushStream, trash));
        }
      }
    }
  }

  RemoveStreamFromQueues(aStream);

  // removing from the stream transaction hash will
  // delete the Http2StreamBase and drop the reference to
  // its transaction
  mStreamTransactionHash.Remove(aStream->Transaction());
  mTunnelStreams.RemoveElement(aStream);

  if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);

  if (pushSource) {
    pushSource->SetDeferCleanupOnSuccess(false);
    CleanupStream(pushSource, aResult, aResetCode);
  }
}

void Http2Session::CleanupStream(uint32_t aID, nsresult aResult,
                                 errorType aResetCode) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  Http2StreamBase* stream = mStreamIDHash.Get(aID);
  LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n"this, aID,
        stream));
  if (!stream) {
    return;
  }
  CleanupStream(stream, aResult, aResetCode);
}

void Http2Session::RemoveStreamFromQueues(Http2StreamBase* aStream) {
  RemoveStreamFromQueue(aStream, mReadyForWrite);
  RemoveStreamFromQueue(aStream, mQueuedStreams);
  RemoveStreamFromQueue(aStream, mPushesReadyForRead);
  RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
}

void Http2Session::CloseStream(Http2StreamBase* aStream, nsresult aResult,
                               bool aRemoveFromQueue) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n"this, aStream,
        aStream->StreamID(), static_cast<uint32_t>(aResult)));

  MaybeDecrementConcurrent(aStream);

  // Check if partial frame reader
  if (aStream == mInputFrameDataStream) {
    LOG3(("Stream had active partial read frame on close"));
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
    mInputFrameDataStream = nullptr;
  }

  if (aRemoveFromQueue) {
    RemoveStreamFromQueues(aStream);
  }

  // Send the stream the close() indication
  aStream->CloseStream(aResult);
}

nsresult Http2Session::SetInputFrameDataStream(uint32_t streamID) {
  mInputFrameDataStream = mStreamIDHash.Get(streamID);
  if (VerifyStream(mInputFrameDataStream, streamID)) return NS_OK;

  LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
        streamID));
  mInputFrameDataStream = nullptr;
  return NS_ERROR_UNEXPECTED;
}

nsresult Http2Session::ParsePadding(uint8_t& paddingControlBytes,
                                    uint16_t& paddingLength) {
  if (mInputFrameFlags & kFlag_PADDED) {
    paddingLength =
        *reinterpret_cast<uint8_t*>(&mInputFrameBuffer[kFrameHeaderBytes]);
    paddingControlBytes = 1;
  } else {
    paddingLength = 0;
    paddingControlBytes = 0;
  }

  if (static_cast<uint32_t>(paddingLength + paddingControlBytes) >
      mInputFrameDataSize) {
    // This is fatal to the session
    LOG3(
        ("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
         "paddingLength %d > frame size %d\n",
         this, mInputFrameID, paddingLength, mInputFrameDataSize));
    return SessionError(PROTOCOL_ERROR);
  }

  return NS_OK;
}

nsresult Http2Session::RecvHeaders(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
             self->mInputFrameType == FRAME_TYPE_CONTINUATION);

  bool isContinuation = self->mExpectedHeaderID != 0;

  // If this doesn't have END_HEADERS set on it then require the next
  // frame to be HEADERS of the same ID
  bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;

  if (endHeadersFlag) {
    self->mExpectedHeaderID = 0;
  } else {
    self->mExpectedHeaderID = self->mInputFrameID;
  }

  uint32_t priorityLen = 0;
  if (self->mInputFrameFlags & kFlag_PRIORITY) {
    priorityLen = 5;
  }
  nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  // Find out how much padding this frame has, so we can only extract the real
  // header data from the frame.
  uint16_t paddingLength = 0;
  uint8_t paddingControlBytes = 0;

  if (!isContinuation) {
    self->mDecompressBuffer.Truncate();
    rv = self->ParsePadding(paddingControlBytes, paddingLength);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  LOG3(
      ("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
       "end_stream=%d end_headers=%d priority_group=%d "
       "paddingLength=%d padded=%d\n",
       self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
       self->mInputFrameFlags & kFlag_END_STREAM,
       self->mInputFrameFlags & kFlag_END_HEADERS,
       self->mInputFrameFlags & kFlag_PRIORITY, paddingLength,
       self->mInputFrameFlags & kFlag_PADDED));

  if ((paddingControlBytes + priorityLen + paddingLength) >
      self->mInputFrameDataSize) {
    // This is fatal to the session
    return self->SessionError(PROTOCOL_ERROR);
  }

  uint32_t frameSize = self->mInputFrameDataSize - paddingControlBytes -
                       priorityLen - paddingLength;
  if (self->mAggregatedHeaderSize + frameSize >
      StaticPrefs::network_http_max_response_header_size()) {
    LOG(("Http2Session %p header exceeds the limit\n", self));
    return self->SessionError(PROTOCOL_ERROR);
  }
  if (!self->mInputFrameDataStream) {
    // Cannot find stream. We can continue the session, but we need to
    // uncompress the header block to maintain the correct compression context

    LOG3(
        ("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
         "0x%X failed. NextStreamID = 0x%X\n",
         self, self->mInputFrameID, self->mNextStreamID));

    if (self->mInputFrameID >= self->mNextStreamID) {
      self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
    }

    self->mDecompressBuffer.Append(
        &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
                                 priorityLen],
        frameSize);

    if (self->mInputFrameFlags & kFlag_END_HEADERS) {
      rv = self->UncompressAndDiscard(false);
      if (NS_FAILED(rv)) {
        LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
        // this is fatal to the session
        self->mGoAwayReason = COMPRESSION_ERROR;
        return rv;
      }
    }

    self->ResetDownstreamState();
    return NS_OK;
  }

  // make sure this is either the first headers or a trailer
  if (self->mInputFrameDataStream->AllHeadersReceived() &&
      !(self->mInputFrameFlags & kFlag_END_STREAM)) {
    // Any header block after the first that does *not* end the stream is
    // illegal.
    LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self,
          self->mInputFrameID));
    return self->SessionError(PROTOCOL_ERROR);
  }

  // queue up any compression bytes
  self->mDecompressBuffer.Append(
      &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
                               priorityLen],
      frameSize);

  self->mInputFrameDataStream->UpdateTransportReadEvents(
      self->mInputFrameDataSize);
  self->mLastDataReadEpoch = self->mLastReadEpoch;

  if (!isContinuation) {
    self->mAggregatedHeaderSize = frameSize;
  } else {
    self->mAggregatedHeaderSize += frameSize;
  }

  if (!endHeadersFlag) {  // more are coming - don't process yet
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (isContinuation) {
    Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
                          self->mAggregatedHeaderSize);
  }

  rv = self->ResponseHeadersComplete();
  if (rv == NS_ERROR_ILLEGAL_VALUE) {
    LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
          self, self->mInputFrameID));
    self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
    self->ResetDownstreamState();
    rv = NS_OK;
  } else if (NS_FAILED(rv)) {
    // This is fatal to the session.
    self->mGoAwayReason = COMPRESSION_ERROR;
  }
  return rv;
}

// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
// should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
// fine, and any other error is fatal to the session.
nsresult Http2Session::ResponseHeadersComplete() {
  LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d"this,
        mInputFrameDataStream->StreamID(), mInputFrameFinal));

  // Anything prior to AllHeadersReceived() => true is actual headers. After
  // that, we need to handle them as trailers instead (which are special-cased
  // so we don't have to use the nasty chunked parser for all h2, just in case).
  if (mInputFrameDataStream->AllHeadersReceived()) {
    LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
    MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
    nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(
        &mDecompressor, mDecompressBuffer);
    if (NS_FAILED(rv)) {
      LOG3((
          "Http2Session::ResponseHeadersComplete trailer conversion failed\n"));
      return rv;
    }
    mFlatHTTPResponseHeadersOut = 0;
    mFlatHTTPResponseHeaders.Truncate();
    if (mInputFrameFinal) {
      // need to process the fin
      ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
    } else {
      ResetDownstreamState();
    }

    return NS_OK;
  }

  // if this turns out to be a 1xx response code we have to
  // undo the headers received bit that we are setting here.
  bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
  mInputFrameDataStream->SetAllHeadersReceived();

  // The stream needs to see flattened http headers
  // Uncompressed http/2 format headers currently live in
  // Http2StreamBase::mDecompressBuffer - convert that to HTTP format in
  // mFlatHTTPResponseHeaders via ConvertHeaders()

  nsresult rv;
  int32_t httpResponseCode;  // out param to ConvertResponseHeaders
  mFlatHTTPResponseHeadersOut = 0;
  rv = mInputFrameDataStream->ConvertResponseHeaders(
      &mDecompressor, mDecompressBuffer, mFlatHTTPResponseHeaders,
      httpResponseCode);
  if (rv == NS_ERROR_NET_RESET) {
    LOG(
        ("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders "
         "reset\n",
         this));
    // This means the stream found connection-oriented auth. Treat this like we
    // got a reset with HTTP_1_1_REQUIRED.
    mInputFrameDataStream->DisableSpdy();
    CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
    ResetDownstreamState();
    return NS_OK;
  }
  if (NS_FAILED(rv)) {
    return rv;
  }

  // allow more headers in the case of 1xx
  if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
    mInputFrameDataStream->UnsetAllHeadersReceived();
  }

  ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
  return NS_OK;
}

nsresult Http2Session::RecvPriority(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);

  if (self->mInputFrameDataSize != 5) {
    LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n", self,
          self->mInputFrameDataSize));
    return self->SessionError(PROTOCOL_ERROR);
  }

  if (!self->mInputFrameID) {
    LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
    return self->SessionError(PROTOCOL_ERROR);
  }

  nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
  if (NS_FAILED(rv)) return rv;

  uint32_t newPriorityDependency = NetworkEndian::readUint32(
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);
  bool exclusive = !!(newPriorityDependency & 0x80000000);
  newPriorityDependency &= 0x7fffffff;
  uint8_t newPriorityWeight =
      *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);

  // undefined what it means when the server sends a priority frame. ignore it.
  LOG3(
      ("Http2Session::RecvPriority %p 0x%X received dependency=0x%X "
       "weight=%u exclusive=%d",
       self->mInputFrameDataStream, self->mInputFrameID, newPriorityDependency,
       newPriorityWeight, exclusive));

  self->ResetDownstreamState();
  return NS_OK;
}

nsresult Http2Session::RecvRstStream(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);

  if (self->mInputFrameDataSize != 4) {
    LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
          self, self->mInputFrameDataSize));
    return self->SessionError(PROTOCOL_ERROR);
  }

  if (!self->mInputFrameID) {
    LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
    return self->SessionError(PROTOCOL_ERROR);
  }

  self->mDownstreamRstReason = NetworkEndian::readUint32(
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);

  LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
        self, self->mDownstreamRstReason, self->mInputFrameID));

  DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  if (!self->mInputFrameDataStream) {
    // if we can't find the stream just ignore it (4.2 closed)
    self->ResetDownstreamState();
    return NS_OK;
  }

  self->mInputFrameDataStream->SetRecvdReset(true);
  self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
  self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
  return NS_OK;
}

nsresult Http2Session::RecvSettings(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);

  if (self->mInputFrameID) {
    LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", self,
          self->mInputFrameID));
    return self->SessionError(PROTOCOL_ERROR);
  }

  if (self->mInputFrameDataSize % 6) {
    // Number of Settings is determined by dividing by each 6 byte setting
    // entry. So the payload must be a multiple of 6.
    LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", self,
          self->mInputFrameDataSize));
    return self->SessionError(PROTOCOL_ERROR);
  }

  self->mReceivedSettings = true;

  uint32_t numEntries = self->mInputFrameDataSize / 6;
  LOG3(
      ("Http2Session::RecvSettings %p SETTINGS Control Frame "
       "with %d entries ack=%X",
       self, numEntries, self->mInputFrameFlags & kFlag_ACK));

  if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
    LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n",
          self));
    return self->SessionError(PROTOCOL_ERROR);
  }

  for (uint32_t index = 0; index < numEntries; ++index) {
    uint8_t* setting =
        reinterpret_cast<uint8_t*>(self->mInputFrameBuffer.get()) +
        kFrameHeaderBytes + index * 6;

    uint16_t id = NetworkEndian::readUint16(setting);
    uint32_t value = NetworkEndian::readUint32(setting + 2);
    LOG3(("Settings ID %u, Value %u", id, value));

    switch (id) {
      case SETTINGS_TYPE_HEADER_TABLE_SIZE:
        LOG3(("Compression header table setting received: %d\n", value));
        self->mCompressor.SetMaxBufferSize(value);
        break;

      case SETTINGS_TYPE_ENABLE_PUSH:
        LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
        // nop
        break;

      case SETTINGS_TYPE_MAX_CONCURRENT:
        self->mMaxConcurrent = value;
        Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
        self->ProcessPending();
        break;

      case SETTINGS_TYPE_INITIAL_WINDOW: {
        Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
        int32_t delta = value - self->mServerInitialStreamWindow;
        self->mServerInitialStreamWindow = value;

        // SETTINGS only adjusts stream windows. Leave the session window alone.
        // We need to add the delta to all open streams (delta can be negative)
        for (const auto& stream : self->mStreamTransactionHash.Values()) {
          stream->UpdateServerReceiveWindow(delta);
        }
      } break;

      case SETTINGS_TYPE_MAX_FRAME_SIZE: {
        if ((value < kMaxFrameData) || (value >= 0x01000000)) {
          LOG3(("Received invalid max frame size 0x%X", value));
          return self->SessionError(PROTOCOL_ERROR);
        }
        // We stick to the default for simplicity's sake, so nothing to change
      } break;

      case SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL: {
        if (value == 1) {
          LOG3(("Enabling extended CONNECT"));
          self->mPeerAllowsWebsockets = true;
        } else if (value > 1) {
          LOG3(("Peer sent invalid value for ENABLE_CONNECT_PROTOCOL %d",
                value));
          return self->SessionError(PROTOCOL_ERROR);
        } else if (self->mPeerAllowsWebsockets) {
          LOG3(("Peer tried to re-disable extended CONNECT"));
          return self->SessionError(PROTOCOL_ERROR);
        }
        self->mHasTransactionWaitingForWebsockets = true;
      } break;

      default:
        LOG3(("Received an unknown SETTING id %d. Ignoring.", id));
        break;
    }
  }

  self->ResetDownstreamState();

  if (!(self->mInputFrameFlags & kFlag_ACK)) {
    self->GenerateSettingsAck();
  } else if (self->mWaitingForSettingsAck) {
    self->mGoAwayOnPush = true;
  }

  if (!self->mProcessedWaitingWebsockets) {
    self->mProcessedWaitingWebsockets = true;
  }

  if (self->mHasTransactionWaitingForWebsockets) {
    // trigger a queued websockets transaction -- enabled or not
    LOG3(("Http2Sesssion::RecvSettings triggering queued websocket"));
    RefPtr<nsHttpConnectionInfo> ci;
--> --------------------

--> maximum size reached

--> --------------------

95%


¤ Dauer der Verarbeitung: 0.37 Sekunden  (vorverarbeitet)  ¤

*© 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.