netwerk/protocol/http/Http2Session.cpp
author Kershaw Chang <kershaw@mozilla.com>
Tue, 23 Jun 2020 07:51:25 +0000
changeset 600877 aecfc014ba0c7e748cc32423c0ee94e62d5acfea
parent 599241 a92c423736e5979a8cf039017fd707d41f665b9c
permissions -rw-r--r--
Bug 1644239, r=dragana, a=jcristau Differential Revision: https://phabricator.services.mozilla.com/D80365

/* -*- 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 "AltServiceChild.h"
#include "Http2Session.h"
#include "Http2Stream.h"
#include "Http2Push.h"

#include "mozilla/EndianUtils.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Preferences.h"
#include "nsHttp.h"
#include "nsHttpHandler.h"
#include "nsHttpConnection.h"
#include "nsIRequestContext.h"
#include "nsISSLSocketControl.h"
#include "nsISupportsPriority.h"
#include "nsStandardURL.h"
#include "nsURLHelper.h"
#include "prnetdb.h"
#include "sslt.h"
#include "mozilla/Sprintf.h"
#include "nsSocketTransportService2.h"
#include "nsNetUtil.h"
#include "CacheControlParser.h"
#include "CachePushChecker.h"
#include "LoadContextInfo.h"
#include "TCPFastOpenLayer.h"
#include "nsQueryObject.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_AMBIGUOUS(nsISupports, nsAHttpConnection)
NS_INTERFACE_MAP_END

// "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(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),
      mTlsHandshakeFinished(false),
      mCheckNetworkStallsWithTFO(false),
      mLastRequestBytesSentTime(0),
      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();
  SendHello();

  mLastDataReadEpoch = mLastReadEpoch;

  mPingThreshold = gHttpHandler->SpdyPingThreshold();
  mPreviousPingThreshold = mPingThreshold;
  mCurrentForegroundTabOuterContentWindowId =
      gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();

  mEnableWebsockets = gHttpHandler->IsH2WebsocketsEnabled();

  bool dumpHpackTables = gHttpHandler->DumpHpackTables();
  mCompressor.SetDumpTables(dumpHpackTables);
  mDecompressor.SetDumpTables(dumpHpackTables);
}

void Http2Session::Shutdown() {
  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
    auto stream = iter.UserData();

    // 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 &&
        (stream->StreamID() > mGoAwayID || !stream->HasRegisteredID())) {
      CloseStream(stream, NS_ERROR_NET_RESET);  // can be restarted
    } else if (stream->RecvdData()) {
      CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
    } else if (mGoAwayReason == INADEQUATE_SECURITY) {
      CloseStream(stream, NS_ERROR_NET_INADEQUATE_SECURITY);
    } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) {
      CloseStream(stream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
    } else {
      CloseStream(stream, NS_ERROR_ABORT);
    }
  }
}

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

  Shutdown();

  if (mTrrStreams) {
    Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN, mTrrStreams);
  }
  Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
  Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN_2, 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, Http2Stream* stream,
                         const char* label, const char* data,
                         uint32_t datalen) {
  if (!LOG5_ENABLED()) return;

  LOG5(("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;
        LOG5(("%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;
    LOG5(("%s", linebuf));
  }
}

typedef nsresult (*Http2ControlFx)(Http2Session* self);
static 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
};

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)));

  uint32_t nextTick = UINT32_MAX;
  if (mCheckNetworkStallsWithTFO && mLastRequestBytesSentTime) {
    PRIntervalTime initialResponseDelta = now - mLastRequestBytesSentTime;
    if (initialResponseDelta >= gHttpHandler->FastOpenStallsTimeout()) {
      gHttpHandler->IncrementFastOpenStallsCounter();
      mCheckNetworkStallsWithTFO = false;
    } else {
      nextTick = PR_IntervalToSeconds(gHttpHandler->FastOpenStallsTimeout()) -
                 PR_IntervalToSeconds(initialResponseDelta);
    }
  }
  if (!mPingThreshold) return nextTick;

  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 std::min(nextTick, PR_IntervalToSeconds(mPingThreshold) -
                                  PR_IntervalToSeconds(now - mLastReadEpoch));
  }

  if (mPingSentEpoch) {
    LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n", this));
    if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
      LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
      mPingSentEpoch = 0;
      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(Http2Stream* 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.Get(aNewID)) {
    LOG3(("   New ID already present\n"));
    MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
    mShouldGoAway = true;
    return kDeadStreamID;
  }

  mStreamIDHash.Put(aNewID, stream);

  // If TCP fast Open has been used and conection was idle for some time
  // we will be cautious and watch out for bug 1395494.
  if (!mCheckNetworkStallsWithTFO && mConnection) {
    RefPtr<HttpConnectionBase> connBase = mConnection->HttpConnection();
    RefPtr<nsHttpConnection> conn = do_QueryObject(connBase);
    if (conn && (conn->GetFastOpenStatus() == TFO_DATA_SENT) &&
        gHttpHandler
            ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() &&
        IdleTime() >=
            gHttpHandler
                ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds()) {
      // If a connection was using the TCP FastOpen and it was idle for a
      // long time we should check for stalls like bug 1395494.
      mCheckNetworkStallsWithTFO = true;
      mLastRequestBytesSentTime = PR_IntervalNow();
    }
  }

  if (aNewID & 1) {
    // don't count push streams here
    MOZ_ASSERT(stream->Transaction(), "no transation for the stream!");
    RefPtr<nsHttpConnectionInfo> ci(stream->Transaction()->ConnectionInfo());
    if (ci && ci->GetIsTrrServiceChannel()) {
      IncrementTrrCounter();
    }
  }
  return aNewID;
}

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

  // integrity check
  if (mStreamTransactionHash.Get(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();

  if (aIsWebsocket) {
    MOZ_ASSERT(!aUseTunnel, "Websocket on tunnel?!");
    nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
    MOZ_ASSERT(trans, "Websocket without transaction?!");
    if (!trans) {
      LOG3(("Http2Session::AddStream %p websocket without transaction. WAT?!",
            this));
      return true;
    }

    if (!mEnableWebsockets) {
      LOG3(
          ("Http2Session::AddStream %p Re-queuing websocket as h1 due to "
           "mEnableWebsockets=false",
           this));
      aHttpTransaction->SetConnection(nullptr);
      aHttpTransaction->DisableSpdy();
      nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
      if (NS_FAILED(rv)) {
        LOG3(
            ("Http2Session::AddStream %p failed to reinitiate websocket "
             "transaction (0x%08x).",
             this, static_cast<uint32_t>(rv)));
      }

      return true;
    }

    if (!mPeerAllowsWebsockets) {
      LOG3(("Http2Session::AddStream %p mPeerAllowsWebsockets=false", this));
      if (!mProcessedWaitingWebsockets) {
        LOG3(
            ("Http2Session::AddStream %p waiting for SETTINGS to determine "
             "fate of websocket",
             this));
        mWaitingWebsockets.AppendElement(aHttpTransaction);
        mWaitingWebsocketCallbacks.AppendElement(aCallbacks);
      } else {
        LOG3(
            ("Http2Session::AddStream %p Re-queuing websocket as h1 due to "
             "mPeerAllowsWebsockets=false",
             this));
        aHttpTransaction->SetConnection(nullptr);
        aHttpTransaction->DisableSpdy();
        if (trans) {
          nsresult rv =
              gHttpHandler->InitiateTransaction(trans, trans->Priority());
          if (NS_FAILED(rv)) {
            LOG3(
                ("Http2Session::AddStream %p failed to reinitiate websocket "
                 "transaction (%08x).\n",
                 this, static_cast<uint32_t>(rv)));
          }
        }
      }
      return true;
    }

    LOG3(("Http2Session::AddStream session=%p trans=%p websocket", this,
          aHttpTransaction));
    CreateWebsocketStream(aHttpTransaction, aCallbacks);
    return true;
  }

  if (aUseTunnel) {
    LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel", this,
          aHttpTransaction));
    DispatchOnTunnel(aHttpTransaction, aCallbacks);
    return true;
  }

  Http2Stream* stream =
      new Http2Stream(aHttpTransaction, this, aPriority,
                      mCurrentForegroundTabOuterContentWindowId);

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

  mStreamTransactionHash.Put(aHttpTransaction, stream);

  mReadyForWrite.Push(stream);
  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();
  }

  return true;
}

void Http2Session::QueueStream(Http2Stream* 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
  int32_t qsize = mQueuedStreams.GetSize();
  for (int32_t i = 0; i < qsize; i++) {
    Http2Stream* qStream =
        static_cast<Http2Stream*>(mQueuedStreams.ObjectAt(i));
    MOZ_ASSERT(qStream != stream);
    MOZ_ASSERT(qStream->Queued());
  }
#endif

  stream->SetQueued(true);
  mQueuedStreams.Push(stream);
}

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

  Http2Stream* stream;
  while (RoomForMoreConcurrent() &&
         (stream = static_cast<Http2Stream*>(mQueuedStreams.PopFront()))) {
    LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.", this,
          stream));
    MOZ_ASSERT(!stream->CountAsActive());
    MOZ_ASSERT(stream->Queued());
    stream->SetQueued(false);
    mReadyForWrite.Push(stream);
    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();
    mCheckNetworkStallsWithTFO = false;
  }
  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.GetSize();
}

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(Http2Stream* 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(Http2Stream* 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() ||
      trans->QuerySpdyConnectTransaction()) {
    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(Http2Stream* 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::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::GeneratePriority %p %X %X\n", this, aID,
        aPriorityWeight));

  char* packet = CreatePriorityFrame(aID, 0, aPriorityWeight);

  LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
  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)
  Http2Stream* 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 + 5 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 = 5;
  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 (!gHttpHandler->AllowPush()) {
    // 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++;

    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++;

  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 (gHttpHandler->UseH2Deps() &&
      gHttpHandler->CriticalRequestPrioritization()) {
    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) {
  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();
}

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(Http2Stream* 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;

    nsAHttpTransaction* trans = aStream->Transaction();

    test++;
    if (!trans) break;

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

    if (aStream->StreamID()) {
      Http2Stream* idStream = mStreamIDHash.Get(aStream->StreamID());

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

      if (aOptionalID) {
        test++;
        if (idStream->StreamID() != aOptionalID) 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
}

void Http2Session::CleanupStream(Http2Stream* 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 = aStream->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));
    aStream->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 Http2Stream and drop the reference to
  // its transaction
  mStreamTransactionHash.Remove(aStream->Transaction());

  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");
  Http2Stream* 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);
}

static void RemoveStreamFromQueue(Http2Stream* aStream, nsDeque& queue) {
  size_t size = queue.GetSize();
  for (size_t count = 0; count < size; ++count) {
    Http2Stream* stream = static_cast<Http2Stream*>(queue.PopFront());
    if (stream != aStream) queue.Push(stream);
  }
}

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

void Http2Session::CloseStream(Http2Stream* aStream, nsresult aResult) {
  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;
  }

  RemoveStreamFromQueues(aStream);

  if (aStream->IsTunnel()) {
    UnRegisterTunnel(aStream);
  }

  // Send the stream the close() indication
  aStream->Close(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);
  }

  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],
        self->mInputFrameDataSize - paddingControlBytes - priorityLen -
            paddingLength);

    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],
      self->mInputFrameDataSize - paddingControlBytes - priorityLen -
          paddingLength);

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

  if (!isContinuation) {
    self->mAggregatedHeaderSize = self->mInputFrameDataSize -
                                  paddingControlBytes - priorityLen -
                                  paddingLength;
  } else {
    self->mAggregatedHeaderSize += self->mInputFrameDataSize -
                                   paddingControlBytes - priorityLen -
                                   paddingLength;
  }

  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
  // Http2Stream::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->Transaction()->DisableSpdy();
    CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
    ResetDownstreamState();
    return NS_OK;
  } else 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 (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
             iter.Next()) {
          iter.Data()->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);
        }
      } 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->ProcessWaitingWebsockets();
  }

  return NS_OK;
}

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

  // 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 this doesn't have END_PUSH_PROMISE set on it then require the next
  // frame to be PUSH_PROMISE of the same ID
  uint32_t promiseLen;
  uint32_t promisedID;

  if (self->mExpectedPushPromiseID) {
    promiseLen = 0;  // really a continuation frame
    promisedID = self->mContinuedPromiseStream;
  } else {
    self->mDecompressBuffer.Truncate();
    nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
    if (NS_FAILED(rv)) {
      return rv;
    }
    promiseLen = 4;
    promisedID =
        NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
                                  kFrameHeaderBytes + paddingControlBytes);
    promisedID &= 0x7fffffff;
    if (promisedID <= self->mLastPushedID) {
      LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
            self, promisedID, self->mLastPushedID));
      return self->SessionError(PROTOCOL_ERROR);
    }
    self->mLastPushedID = promisedID;
  }

  uint32_t associatedID = self->mInputFrameID;

  if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
    self->mExpectedPushPromiseID = 0;
    self->mContinuedPromiseStream = 0;
  } else {
    self->mExpectedPushPromiseID = self->mInputFrameID;
    self->mContinuedPromiseStream = promisedID;
  }

  if ((paddingControlBytes + promiseLen + paddingLength) >
      self->mInputFrameDataSize) {
    // This is fatal to the session
    LOG3(
        ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
         "PROTOCOL_ERROR extra %d > frame size %d\n",
         self, promisedID, associatedID,
         (paddingControlBytes + promiseLen + paddingLength),
         self->mInputFrameDataSize));
    return self->SessionError(PROTOCOL_ERROR);
  }

  LOG3(
      ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
       "paddingLength %d padded %d\n",
       self, promisedID, associatedID, paddingLength,
       self->mInputFrameFlags & kFlag_PADDED));

  if (!associatedID || !promisedID || (promisedID & 1)) {
    LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
    return self->SessionError(PROTOCOL_ERROR);
  }

  // confirm associated-to
  nsresult rv = self->SetInputFrameDataStream(associatedID);
  if (NS_FAILED(rv)) return rv;

  Http2Stream* associatedStream = self->mInputFrameDataStream;
  ++(self->mServerPushedResources);

  // Anytime we start using the high bit of stream ID (either client or server)
  // begin to migrate to a new session.
  if (promisedID >= kMaxStreamID) self->mShouldGoAway = true;

  bool resetStream = true;
  SpdyPushCache* cache = nullptr;

  if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
    LOG3(
        ("Http2Session::RecvPushPromise %p cache push while in GoAway "
         "mode refused.\n",
         self));
    self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
  } else if (!gHttpHandler->AllowPush()) {
    // ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
    LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
    if (self->mGoAwayOnPush) {
      LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
      return self->SessionError(PROTOCOL_ERROR);
    }
    self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
  } else if (!(associatedID & 1)) {
    LOG3(
        ("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) "
         "stream not allowed\n",
         self, associatedID));
    self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
  } else if (!associatedStream) {
    LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n",
          self));
    self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
  } else if (Http2PushedStream::TestOnPush(associatedStream)) {
    LOG3(("Http2Session::RecvPushPromise %p will be handled by push listener.",
          self));
    resetStream = false;
  } else {
    nsIRequestContext* requestContext = associatedStream->RequestContext();
    if (requestContext) {
      cache = requestContext->GetSpdyPushCache();
      if (!cache) {
        cache = new SpdyPushCache();
        requestContext->SetSpdyPushCache(cache);
      }
    }
    if (!cache) {
      // this is unexpected, but we can handle it just by refusing the push
      LOG3(
          ("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
      self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
    } else {
      resetStream = false;
    }
  }

  if (resetStream) {
    // Need to decompress the headers even though we aren't using them yet in
    // order to keep the compression context consistent for other frames
    self->mDecompressBuffer.Append(
        &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
                                 promiseLen],
        self->mInputFrameDataSize - paddingControlBytes - promiseLen -
            paddingLength);
    if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
      rv = self->UncompressAndDiscard(true);
      if (NS_FAILED(rv)) {
        LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
        self->mGoAwayReason = COMPRESSION_ERROR;
        return rv;
      }
    }
    self->ResetDownstreamState();
    return NS_OK;
  }

  self->mDecompressBuffer.Append(
      &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
                               promiseLen],
      self->mInputFrameDataSize - paddingControlBytes - promiseLen -
          paddingLength);

  if (self->mInputFrameType != FRAME_TYPE_CONTINUATION) {
    self->mAggregatedHeaderSize = self->mInputFrameDataSize -
                                  paddingControlBytes - promiseLen -
                                  paddingLength;
  } else {
    self->mAggregatedHeaderSize += self->mInputFrameDataSize -
                                   paddingControlBytes - promiseLen -
                                   paddingLength;
  }

  if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
    LOG3(
        ("Http2Session::RecvPushPromise not finishing processing for "
         "multi-frame push\n"));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (self->mInputFrameType == FRAME_TYPE_CONTINUATION) {
    Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
                          self->mAggregatedHeaderSize);
  }

  // Create the buffering transaction and push stream
  RefPtr<Http2PushTransactionBuffer> transactionBuffer =
      new Http2PushTransactionBuffer();
  transactionBuffer->SetConnection(self);
  UniquePtr<Http2PushedStream> pushedStream(new Http2PushedStream(
      transactionBuffer, self, associatedStream, promisedID,
      self->mCurrentForegroundTabOuterContentWindowId));

  rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
                                        self->mDecompressBuffer,
                                        pushedStream->GetRequestString());

  if (rv == NS_ERROR_NOT_IMPLEMENTED) {
    LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
    self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (rv == NS_ERROR_ILLEGAL_VALUE) {
    // This means the decompression completed ok, but there was a problem with
    // the decoded headers. Reset the stream and go away.
    self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
    self->ResetDownstreamState();
    return NS_OK;
  } else if (NS_FAILED(rv)) {
    // This is fatal to the session.
    self->mGoAwayReason = COMPRESSION_ERROR;
    return rv;
  }

  WeakPtr<Http2Stream> pushedWeak = pushedStream.release();

  // Ownership of the pushed stream is by the transaction hash, just as it
  // is for a client initiated stream. Errors that aren't fatal to the
  // whole session must call cleanupStream() after this point in order
  // to remove the stream from that hash.
  self->mStreamTransactionHash.Put(transactionBuffer, pushedWeak);
  self->mPushedStreams.AppendElement(
      static_cast<Http2PushedStream*>(pushedWeak.get()));

  if (self->RegisterStreamID(pushedWeak, promisedID) == kDeadStreamID) {
    LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
    self->mGoAwayReason = INTERNAL_ERROR;
    return NS_ERROR_FAILURE;
  }

  if (promisedID > self->mOutgoingGoAwayID)
    self->mOutgoingGoAwayID = promisedID;

  // Fake the request side of the pushed HTTP transaction. Sets up hash
  // key and origin
  uint32_t notUsed;
  Unused << pushedWeak->ReadSegments(nullptr, 1, &notUsed);

  nsAutoCString key;
  if (!static_cast<Http2PushedStream*>(pushedWeak.get())->GetHashKey(key)) {
    LOG3(
        ("Http2Session::RecvPushPromise one of :authority :scheme :path "
         "missing from push\n"));
    self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, PROTOCOL_ERROR);
    self->ResetDownstreamState();
    return NS_OK;
  }

  // does the pushed origin belong on this connection?
  LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
        pushedWeak->Origin().get()));
  nsCOMPtr<nsIURI> pushedOrigin;
  rv = Http2Stream::MakeOriginURL(pushedWeak->Origin(), pushedOrigin);
  nsAutoCString pushedHostName;
  int32_t pushedPort = -1;
  if (NS_SUCCEEDED(rv)) {
    rv = pushedOrigin->GetHost(pushedHostName);
  }
  if (NS_SUCCEEDED(rv)) {
    rv = pushedOrigin->GetPort(&pushedPort);
    if (NS_SUCCEEDED(rv) && pushedPort == -1) {
      // Need to get the right default port, so TestJoinConnection below can
      // check things correctly. See bug 1397621.
      if (pushedOrigin->SchemeIs("http")) {
        pushedPort = NS_HTTP_DEFAULT_PORT;
      } else {
        pushedPort = NS_HTTPS_DEFAULT_PORT;
      }
    }
  }
  if (NS_FAILED(rv) || !self->TestJoinConnection(pushedHostName, pushedPort)) {
    LOG3((
        "Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
        self, pushedWeak->Origin().get()));
    self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (static_cast<Http2PushedStream*>(pushedWeak.get())->TryOnPush()) {
    LOG3(
        ("Http2Session::RecvPushPromise %p channel implements "
         "nsIHttpPushListener "
         "stream %p will not be placed into session cache.\n",
         self, pushedWeak.get()));
  } else {
    LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n",
          self));
    if (!cache->RegisterPushedStreamHttp2(
            key, static_cast<Http2PushedStream*>(pushedWeak.get()))) {
      // This only happens if they've already pushed us this item.
      LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
      self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
      self->ResetDownstreamState();
      return NS_OK;
    }

    // Kick off a lookup into the HTTP cache so we can cancel the push if it's
    // unneeded (we already have it in our local regular cache). See bug
    // 1367551.

    // Build up our full URL for the cache lookup
    nsAutoCString spec;
    spec.Assign(pushedWeak->Origin());
    spec.Append(pushedWeak->Path());
    nsCOMPtr<nsIURI> pushedURL;
    // Nifty trick: this doesn't actually do anything origin-specific, it's just
    // named that way. So by passing it the full spec here, we get a URL with
    // the full path.
    // Another nifty trick! Even though this is using nsIURIs (which are not
    // generally ok off the main thread), since we're not using the protocol
    // handler to create any URIs, this will work just fine here. Don't try this
    // at home, though, kids. I'm a trained professional.
    if (NS_SUCCEEDED(Http2Stream::MakeOriginURL(spec, pushedURL))) {
      LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry",
            self));
      mozilla::OriginAttributes oa;
      pushedWeak->GetOriginAttributes(&oa);
      RefPtr<Http2Session> session = self;
      auto cachePushCheckCallback = [session, promisedID](bool aAccepted) {
        MOZ_ASSERT(OnSocketThread());

        if (!aAccepted) {
          session->CleanupStream(promisedID, NS_ERROR_FAILURE,
                                 Http2Session::REFUSED_STREAM_ERROR);
        }
      };
      RefPtr<CachePushChecker> checker = new CachePushChecker(
          pushedURL, oa,
          static_cast<Http2PushedStream*>(pushedWeak.get())->GetRequestString(),
          std::move(cachePushCheckCallback));
      if (NS_FAILED(checker->DoCheck())) {
        LOG3(
            ("Http2Session::RecvPushPromise %p failed to open cache entry for "
             "push check",
             self));
      }
    }
  }

  if (!pushedWeak) {
    // We have an up to date entry in our cache, so the stream was closed
    // and released in the cachePushCheckCallback above.
    self->ResetDownstreamState();
    return NS_OK;
  }

  pushedWeak->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
  static_assert(Http2Stream::kWorstPriority >= 0,
                "kWorstPriority out of range");
  uint32_t priorityDependency = pushedWeak->PriorityDependency();
  uint8_t priorityWeight = pushedWeak->PriorityWeight();
  self->SendPriorityFrame(promisedID, priorityDependency, priorityWeight);
  self->ResetDownstreamState();
  return NS_OK;
}

nsresult Http2Session::RecvPing(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);

  LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
        self->mInputFrameFlags));

  if (self->mInputFrameDataSize != 8) {
    LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d", self,
          self->mInputFrameDataSize));
    return self->SessionError(FRAME_SIZE_ERROR);
  }

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

  if (self->mInputFrameFlags & kFlag_ACK) {
    // presumably a reply to our timeout ping.. don't reply to it
    self->mPingSentEpoch = 0;
  } else {
    // reply with a ack'd ping
    self->GeneratePing(true);
  }

  self->ResetDownstreamState();
  return NS_OK;
}

nsresult Http2Session::RecvGoAway(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);

  if (self->mInputFrameDataSize < 8) {
    // data > 8 is an opaque token that we can't interpret. NSPR Logs will
    // have the hex of all packets so there is no point in separately logging.
    LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
          self, self->mInputFrameDataSize));
    return self->SessionError(PROTOCOL_ERROR);
  }

  if (self->mInputFrameID) {
    LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
          self, self->mInputFrameID));
    return self->SessionError(PROTOCOL_ERROR);
  }

  self->mShouldGoAway = true;
  self->mGoAwayID = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
                                              kFrameHeaderBytes);
  self->mGoAwayID &= 0x7fffffff;
  self->mCleanShutdown = true;
  self->mPeerGoAwayReason = NetworkEndian::readUint32(
      self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);

  // Find streams greater than the last-good ID and mark them for deletion
  // in the mGoAwayStreamsToRestart queue. The underlying transaction can be
  // restarted.
  for (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
       iter.Next()) {
    // these streams were not processed by the server and can be restarted.
    // Do that after the enumerator completes to avoid the risk of
    // a restart event re-entrantly modifying this hash. Be sure not to restart
    // a pushed (even numbered) stream
    auto stream = iter.UserData();
    if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
        !stream->HasRegisteredID()) {
      self->mGoAwayStreamsToRestart.Push(stream);
    }
  }

  // Process the streams marked for deletion and restart.
  size_t size = self->mGoAwayStreamsToRestart.GetSize();
  for (size_t count = 0; count < size; ++count) {
    Http2Stream* stream =
        static_cast<Http2Stream*>(self->mGoAwayStreamsToRestart.PopFront());

    if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
      stream->Transaction()->DisableSpdy();
    }
    self->CloseStream(stream, NS_ERROR_NET_RESET);
    if (stream->HasRegisteredID())
      self->mStreamIDHash.Remove(stream->StreamID());
    self->mStreamTransactionHash.Remove(stream->Transaction());
  }

  // Queued streams can also be deleted from this session and restarted
  // in another one. (they were never sent on the network so they implicitly
  // are not covered by the last-good id.
  size = self->mQueuedStreams.GetSize();
  for (size_t count = 0; count < size; ++count) {
    Http2Stream* stream =
        static_cast<Http2Stream*>(self->mQueuedStreams.PopFront());
    MOZ_ASSERT(stream->Queued());
    stream->SetQueued(false);
    if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
      stream->Transaction()->DisableSpdy();
    }
    self->CloseStream(stream, NS_ERROR_NET_RESET);
    self->mStreamTransactionHash.Remove(stream->Transaction());
  }

  LOG3(
      ("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
       "live streams=%d\n",
       self, self->mGoAwayID, self->mPeerGoAwayReason,
       self->mStreamTransactionHash.Count()));

  self->ResetDownstreamState();
  return NS_OK;
}

nsresult Http2Session::RecvWindowUpdate(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);

  if (self->mInputFrameDataSize != 4) {
    LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
          self, self->mInputFrameDataSize));
    return self->SessionError(PROTOCOL_ERROR);
  }

  uint32_t delta = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
                                             kFrameHeaderBytes);
  delta &= 0x7fffffff;

  LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", self, delta,
        self->mInputFrameID));

  if (self->mInputFrameID) {  // stream window
    nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
    if (NS_FAILED(rv)) return rv;

    if (!self->mInputFrameDataStream) {
      LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
            self, self->mInputFrameID));
      // only resest the session if the ID is one we haven't ever opened
      if (self->mInputFrameID >= self->mNextStreamID)
        self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
      self->ResetDownstreamState();
      return NS_OK;
    }

    if (delta == 0) {
      LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
            self));
      self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
                          PROTOCOL_ERROR);
      self->ResetDownstreamState();
      return NS_OK;
    }

    int64_t oldRemoteWindow =
        self->mInputFrameDataStream->ServerReceiveWindow();
    self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
    if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
      // a window cannot reach 2^31 and be in compliance. Our calculations
      // are 64 bit safe though.
      LOG3(
          ("Http2Session::RecvWindowUpdate %p stream window "
           "exceeds 2^31 - 1\n",
           self));
      self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
                          FLOW_CONTROL_ERROR);
      self->ResetDownstreamState();
      return NS_OK;
    }

    LOG3(
        ("Http2Session::RecvWindowUpdate %p stream 0x%X window "
         "%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n",
         self, self->mInputFrameID, oldRemoteWindow, delta,
         oldRemoteWindow + delta));

  } else {  // session window update
    if (delta == 0) {
      LOG3(
          ("Http2Session::RecvWindowUpdate %p received 0 session window update",
           self));
      return self->SessionError(PROTOCOL_ERROR);
    }

    int64_t oldRemoteWindow = self->mServerSessionWindow;
    self->mServerSessionWindow += delta;

    if (self->mServerSessionWindow >= 0x80000000) {
      // a window cannot reach 2^31 and be in compliance. Our calculations
      // are 64 bit safe though.
      LOG3(
          ("Http2Session::RecvWindowUpdate %p session window "
           "exceeds 2^31 - 1\n",
           self));
      return self->SessionError(FLOW_CONTROL_ERROR);
    }

    if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
      LOG3(
          ("Http2Session::RecvWindowUpdate %p restart session window\n", self));
      for (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
           iter.Next()) {
        MOZ_ASSERT(self->mServerSessionWindow > 0);

        auto stream = iter.UserData();
        if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
          continue;
        }

        self->mReadyForWrite.Push(stream);
        self->SetWriteCallbacks();
      }
    }
    LOG3(
        ("Http2Session::RecvWindowUpdate %p session window "
         "%" PRId64 " increased by %d now %" PRId64 ".\n",
         self, oldRemoteWindow, delta, oldRemoteWindow + delta));
  }

  self->ResetDownstreamState();
  return NS_OK;
}

nsresult Http2Session::RecvContinuation(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
  MOZ_ASSERT(self->mInputFrameID);
  MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
  MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));

  LOG3(
      ("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
       "promise id 0x%X header id 0x%X\n",
       self, self->mInputFrameFlags, self->mInputFrameID,
       self->mExpectedPushPromiseID, self->mExpectedHeaderID));

  DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  if (!self->mInputFrameDataStream) {
    LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
          self->mInputFrameID));
    return self->SessionError(PROTOCOL_ERROR);
  }

  // continued headers
  if (self->mExpectedHeaderID) {
    self->mInputFrameFlags &= ~kFlag_PRIORITY;
    return RecvHeaders(self);
  }

  // continued push promise
  if (self->mInputFrameFlags & kFlag_END_HEADERS) {
    self->mInputFrameFlags &= ~kFlag_END_HEADERS;
    self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
  }
  return RecvPushPromise(self);
}

class UpdateAltSvcEvent : public Runnable {
 public:
  UpdateAltSvcEvent(const nsCString& header, const nsCString& aOrigin,
                    nsHttpConnectionInfo* aCI, nsIInterfaceRequestor* callbacks)
      : Runnable("net::UpdateAltSvcEvent"),
        mHeader(header),
        mOrigin(aOrigin),
        mCI(aCI),
        mCallbacks(callbacks) {}

  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_IsMainThread());

    nsCString originScheme;
    nsCString originHost;
    int32_t originPort = -1;

    nsCOMPtr<nsIURI> uri;
    if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
      LOG(("UpdateAltSvcEvent origin does not parse %s\n", mOrigin.get()));
      return NS_OK;
    }
    uri->GetScheme(originScheme);
    uri->GetHost(originHost);
    uri->GetPort(&originPort);

    if (XRE_IsSocketProcess()) {
      AltServiceChild::ProcessHeader(
          mHeader, originScheme, originHost, originPort, mCI->GetUsername(),
          mCI->GetTopWindowOrigin(), mCI->GetPrivate(), mCI->GetIsolated(),
          mCallbacks, mCI->ProxyInfo(), 0, mCI->GetOriginAttributes());
      return NS_OK;
    }

    AltSvcMapping::ProcessHeader(
        mHeader, originScheme, originHost, originPort, mCI->GetUsername(),
        mCI->GetTopWindowOrigin(), mCI->GetPrivate(), mCI->GetIsolated(),
        nullptr, mCI->ProxyInfo(), 0, mCI->GetOriginAttributes());
    return NS_OK;
  }

 private:
  nsCString mHeader;
  nsCString mOrigin;
  RefPtr<nsHttpConnectionInfo> mCI;
  nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
};

// defined as an http2 extension - alt-svc
// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft
// -06 sec 4 as this is an extension, never generate protocol error - just
// ignore problems
nsresult Http2Session::RecvAltSvc(Http2Session* self) {
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
  LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
        self->mInputFrameFlags, self->mInputFrameID));

  if (self->mInputFrameDataSize < 2) {
    LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  uint16_t originLen = NetworkEndian::readUint16(self->mInputFrameBuffer.get() +
                                                 kFrameHeaderBytes);
  if (originLen + 2U > self->mInputFrameDataSize) {
    LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (!gHttpHandler->AllowAltSvc()) {
    LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  uint16_t altSvcFieldValueLen =
      static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
  LOG3((
      "Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
      self, originLen, altSvcFieldValueLen));

  if (self->mInputFrameDataSize > 2000) {
    LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly",
          self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  nsAutoCString origin;
  bool impliedOrigin = true;
  if (originLen) {
    origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2,
                  originLen);
    impliedOrigin = false;
  }

  nsAutoCString altSvcFieldValue;
  if (altSvcFieldValueLen) {
    altSvcFieldValue.Assign(
        self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
        altSvcFieldValueLen);
  }

  if (altSvcFieldValue.IsEmpty() ||
      !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
    LOG(
        ("Http2Session %p Alt-Svc Response Header seems unreasonable - "
         "skipping\n",
         self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (self->mInputFrameID & 1) {
    // pulled streams apply to the origin of the pulled stream.
    // If the origin field is filled in the frame, the frame should be ignored
    if (!origin.IsEmpty()) {
      LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n",
           self));
      self->ResetDownstreamState();
      return NS_OK;
    }

    if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
        !self->mInputFrameDataStream ||
        !self->mInputFrameDataStream->Transaction() ||
        !self->mInputFrameDataStream->Transaction()->RequestHead()) {
      LOG3(
          ("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream",
           self));
      self->ResetDownstreamState();
      return NS_OK;
    }

    self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
  } else if (!self->mInputFrameID) {
    // ID 0 streams must supply their own origin
    if (origin.IsEmpty()) {
      LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
      self->ResetDownstreamState();
      return NS_OK;
    }
  } else {
    // handling of push streams is not defined. Let's ignore it
    LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n",
         self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
  if (!self->mConnection || !ci) {
    LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
          self->mInputFrameID));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (!impliedOrigin) {
    bool okToReroute = true;
    nsCOMPtr<nsISupports> securityInfo;
    self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
    nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
    if (!ssl) {
      okToReroute = false;
    }

    // a little off main thread origin parser. This is a non critical function
    // because any alternate route created has to be verified anyhow
    nsAutoCString specifiedOriginHost;
    if (origin.EqualsIgnoreCase("https://", 8)) {
      specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
    } else if (origin.EqualsIgnoreCase("http://", 7)) {
      specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
    }

    int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
    if (colonOffset != kNotFound) {
      specifiedOriginHost.Truncate(colonOffset);
    }

    if (okToReroute) {
      ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
    }

    if (!okToReroute) {
      LOG3(
          ("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin "
           "%s",
           self, origin.BeginReading()));
      self->ResetDownstreamState();
      return NS_OK;
    }
  }

  nsCOMPtr<nsISupports> callbacks;
  self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
  nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);

  RefPtr<UpdateAltSvcEvent> event =
      new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
  NS_DispatchToMainThread(event);
  self->ResetDownstreamState();
  return NS_OK;
}

void Http2Session::Received421(nsHttpConnectionInfo* ci) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
  if (!mOriginFrameActivated || !ci) {
    return;
  }

  nsAutoCString key(ci->GetOrigin());
  key.Append(':');
  key.AppendInt(ci->OriginPort());
  mOriginFrame.Remove(key);
  LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
}

nsresult Http2Session::RecvUnused(Http2Session* self) {
  LOG3(("Http2Session %p unknown frame type %x ignored\n", self,
        self->mInputFrameType));
  self->ResetDownstreamState();
  return NS_OK;
}

// defined as an http2 extension - origin
// defines receipt of frame type 0x0b..
// http://httpwg.org/http-extensions/origin-frame.html as this is an extension,
// never generate protocol error - just ignore problems
nsresult Http2Session::RecvOrigin(Http2Session* self) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
  LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
        self->mInputFrameFlags, self->mInputFrameID));

  if (self->mInputFrameFlags & 0x0F) {
    LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (self->mInputFrameID) {
    LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (self->ConnectionInfo()->UsingProxy()) {
    LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  if (!gHttpHandler->AllowOriginExtension()) {
    LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
    self->ResetDownstreamState();
    return NS_OK;
  }

  uint32_t offset = 0;
  self->mOriginFrameActivated = true;

  while (self->mInputFrameDataSize >= (offset + 2U)) {
    uint16_t originLen = NetworkEndian::readUint16(
        self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
    LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n",
          self, originLen));
    if (originLen + 2U + offset > self->mInputFrameDataSize) {
      LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
      break;
    }

    nsAutoCString originString;
    nsCOMPtr<nsIURI> originURL;
    originString.Assign(
        self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2,
        originLen);
    offset += originLen + 2;
    if (NS_FAILED(Http2Stream::MakeOriginURL(originString, originURL))) {
      LOG3(
          ("Http2Session::RecvOrigin %p origin frame string %s failed to "
           "parse\n",
           self, originString.get()));
      continue;
    }

    LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n",
          self, originString.get()));
    if (!originURL->SchemeIs("https")) {
      LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
      continue;
    }

    int32_t port = -1;
    originURL->GetPort(&port);
    if (port == -1) {
      port = 443;
    }
    // dont use ->GetHostPort because we want explicit 443
    nsAutoCString host;
    originURL->GetHost(host);
    nsAutoCString key(host);
    key.Append(':');
    key.AppendInt(port);
    if (!self->mOriginFrame.Get(key)) {
      self->mOriginFrame.Put(key, true);
      RefPtr<HttpConnectionBase> conn(self->HttpConnection());
      MOZ_ASSERT(conn.get());
      gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
    } else {
      LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n", self));
    }
  }

  self->ResetDownstreamState();
  return NS_OK;
}

//-----------------------------------------------------------------------------
// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
// of these methods
//-----------------------------------------------------------------------------

void Http2Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
                                     int64_t aProgress) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  switch (aStatus) {
      // These should appear only once, deliver to the first
      // transaction on the session.
    case NS_NET_STATUS_RESOLVING_HOST:
    case NS_NET_STATUS_RESOLVED_HOST:
    case NS_NET_STATUS_CONNECTING_TO:
    case NS_NET_STATUS_CONNECTED_TO:
    case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
    case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: {
      if (!mFirstHttpTransaction) {
        // if we still do not have a HttpTransaction store timings info in
        // a HttpConnection.
        // If some error occur it can happen that we do not have a connection.
        if (mConnection) {
          RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection();
          conn->SetEvent(aStatus);
        }
      } else {
        mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
                                                 aProgress);
      }

      if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
        mFirstHttpTransaction = nullptr;
        mTlsHandshakeFinished = true;
      }
      break;
    }

    default:
      // The other transport events are ignored here because there is no good
      // way to map them to the right transaction in http/2. Instead, the events
      // are generated again from the http/2 code and passed directly to the
      // correct transaction.

      // NS_NET_STATUS_SENDING_TO:
      // This is generated by the socket transport when (part) of
      // a transaction is written out
      //
      // There is no good way to map it to the right transaction in http/2,
      // so it is ignored here and generated separately when the request
      // is sent from Http2Stream::TransmitFrame

      // NS_NET_STATUS_WAITING_FOR:
      // Created by nsHttpConnection when the request has been totally sent.
      // There is no good way to map it to the right transaction in http/2,
      // so it is ignored here and generated separately when the same
      // condition is complete in Http2Stream when there is no more
      // request body left to be transmitted.

      // NS_NET_STATUS_RECEIVING_FROM
      // Generated in session whenever we read a data frame or a HEADERS
      // that can be attributed to a particular stream/transaction

      break;
  }
}

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

nsresult Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader,
                                         uint32_t count, uint32_t* countRead,
                                         bool* again) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
             "Inconsistent Write Function Callback");

  nsresult rv = ConfirmTLSProfile();
  if (NS_FAILED(rv)) {
    if (mGoAwayReason == INADEQUATE_SECURITY) {
      LOG3(
          ("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY "
           "%" PRIx32,
           this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY)));
      rv = NS_ERROR_NET_INADEQUATE_SECURITY;
    }
    return rv;
  }

  if (reader) mSegmentReader = reader;

  *countRead = 0;

  LOG3(("Http2Session::ReadSegments %p", this));

  Http2Stream* stream = static_cast<Http2Stream*>(mReadyForWrite.PopFront());
  if (!stream) {
    LOG3(("Http2Session %p could not identify a stream to write; suspending.",
          this));
    uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
    FlushOutputQueue();
    uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
    if (availBeforeFlush != availAfterFlush) {
      LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments",
            this));
      Unused << ResumeRecv();
    }
    SetWriteCallbacks();
    if (mAttemptingEarlyData) {
      // We can still try to send our preamble as early-data
      *countRead = mOutputQueueUsed - mOutputQueueSent;
    }
    return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
  }

  uint32_t earlyDataUsed = 0;
  if (mAttemptingEarlyData) {
    if (!stream->Do0RTT()) {
      LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X",
            this, stream, stream->StreamID()));
      FlushOutputQueue();
      SetWriteCallbacks();
      if (!mCannotDo0RTTStreams.Contains(stream)) {
        mCannotDo0RTTStreams.AppendElement(stream);
      }
      // We can still send our preamble
      *countRead = mOutputQueueUsed - mOutputQueueSent;
      return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
    }

    // Need to adjust this to only take as much as we can fit in with the
    // preamble/settings/priority stuff
    count -= (mOutputQueueUsed - mOutputQueueSent);

    // Keep track of this to add it into countRead later, as
    // stream->ReadSegments will likely change the value of mOutputQueueUsed.
    earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
  }

  LOG3(
      ("Http2Session %p will write from Http2Stream %p 0x%X "
       "block-input=%d block-output=%d\n",
       this, stream, stream->StreamID(), stream->RequestBlockedOnRead(),
       stream->BlockedOnRwin()));

  rv = stream->ReadSegments(this, count, countRead);

  if (earlyDataUsed) {
    // Do this here because countRead could get reset somewhere down the rabbit
    // hole of stream->ReadSegments, and we want to make sure we return the
    // proper value to our caller.
    *countRead += earlyDataUsed;
  }

  if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) {
    LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n",
          stream->StreamID()));
    m0RTTStreams.AppendElement(stream);
  }

  // Not every permutation of stream->ReadSegents produces data (and therefore
  // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
  // of that. But we might still have old data buffered that would be good
  // to flush.
  FlushOutputQueue();

  // Allow new server reads - that might be data or control information
  // (e.g. window updates or http replies) that are responses to these writes
  Unused << ResumeRecv();

  if (stream->RequestBlockedOnRead()) {
    // We are blocked waiting for input - either more http headers or
    // any request body data. When more data from the request stream
    // becomes available the httptransaction will call conn->ResumeSend().

    LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));

    // call readsegments again if there are other streams ready
    // to run in this session
    if (GetWriteQueueSize()) {
      rv = NS_OK;
    } else {
      rv = NS_BASE_STREAM_WOULD_BLOCK;
    }
    SetWriteCallbacks();
    return rv;
  }

  if (NS_FAILED(rv)) {
    LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32, this,
          static_cast<uint32_t>(rv)));
    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      return rv;
    }

    CleanupStream(stream, rv, CANCEL_ERROR);
    if (SoftStreamError(rv)) {
      LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
      *again = false;
      SetWriteCallbacks();
      rv = NS_OK;
    }
    return rv;
  }

  if (*countRead > 0) {
    LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d", this, stream,
          *countRead));
    mReadyForWrite.Push(stream);
    SetWriteCallbacks();
    return rv;
  }

  if (stream->BlockedOnRwin()) {
    LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
          this, stream, stream->StreamID()));
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", this,
        stream));

  // call readsegments again if there are other streams ready
  // to go in this session
  SetWriteCallbacks();

  return rv;
}

nsresult Http2Session::ReadSegments(nsAHttpSegmentReader* reader,
                                    uint32_t count, uint32_t* countRead) {
  bool again = false;
  return ReadSegmentsAgain(reader, count, countRead, &again);
}

nsresult Http2Session::ReadyToProcessDataFrame(
    enum internalStateType newState) {
  MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
             newState == DISCARDING_DATA_FRAME_PADDING);
  ChangeDownstreamState(newState);

  Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mInputFrameDataSize >> 10);
  mLastDataReadEpoch = mLastReadEpoch;

  if (!mInputFrameID) {
    LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
          this));
    return SessionError(PROTOCOL_ERROR);
  }

  nsresult rv = SetInputFrameDataStream(mInputFrameID);
  if (NS_FAILED(rv)) {
    LOG3(
        ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
         "failed. probably due to verification.\n",
         this, mInputFrameID));
    return rv;
  }
  if (!mInputFrameDataStream) {
    LOG3(
        ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
         "failed. Next = 0x%X",
         this, mInputFrameID, mNextStreamID));
    if (mInputFrameID >= mNextStreamID)
      GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
  } else if (mInputFrameDataStream->RecvdFin() ||
             mInputFrameDataStream->RecvdReset() ||
             mInputFrameDataStream->SentReset()) {
    LOG3(
        ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
         "Data arrived for already server closed stream.\n",
         this, mInputFrameID));
    if (mInputFrameDataStream->RecvdFin() ||
        mInputFrameDataStream->RecvdReset())
      GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
  } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
    // Only if non-final because the stream properly handles final frames of any
    // size, and we want the stream to be able to notice its own end flag.
    LOG3(
        ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
         "Ignoring 0-length non-terminal data frame.",
         this, mInputFrameID));
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
  }

  LOG3(
      ("Start Processing Data Frame. "
       "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
       this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
       mInputFrameDataSize));
  UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);

  if (mInputFrameDataStream) {
    mInputFrameDataStream->SetRecvdData(true);
  }

  return NS_OK;
}

// WriteSegments() is used to read data off the socket. Generally this is
// just the http2 frame header and from there the appropriate *Stream
// is identified from the Stream-ID. The http transaction associated with
// that read then pulls in the data directly, which it will feed to
// OnWriteSegment(). That function will gateway it into http and feed
// it to the appropriate transaction.

// we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
// and decide if it is data or control.. if it is control, just deal with it.
// if it is data, identify the stream
// call stream->WriteSegments which can call this::OnWriteSegment to get the
// data. It always gets full frames if they are part of the stream

nsresult Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter* writer,
                                          uint32_t count,
                                          uint32_t* countWritten, bool* again) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  LOG3(("Http2Session::WriteSegments %p InternalState %X\n", this,
        mDownstreamState));

  *countWritten = 0;

  if (mClosed) return NS_ERROR_FAILURE;

  nsresult rv = ConfirmTLSProfile();
  if (NS_FAILED(rv)) return rv;

  SetWriteCallbacks();

  // If there are http transactions attached to a push stream with filled
  // buffers trigger that data pump here. This only reads from buffers (not the
  // network) so mDownstreamState doesn't matter.
  Http2Stream* pushConnectedStream =
      static_cast<Http2Stream*>(mPushesReadyForRead.PopFront());
  if (pushConnectedStream) {
    return ProcessConnectedPush(pushConnectedStream, writer, count,
                                countWritten);
  }

  // feed gecko channels that previously stopped consuming data
  // only take data from stored buffers
  Http2Stream* slowConsumer =
      static_cast<Http2Stream*>(mSlowConsumersReadyForRead.PopFront());
  if (slowConsumer) {
    internalStateType savedState = mDownstreamState;
    mDownstreamState = NOT_USING_NETWORK;
    rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
    mDownstreamState = savedState;
    return rv;
  }

  // The BUFFERING_OPENING_SETTINGS state is just like any
  // BUFFERING_FRAME_HEADER except the only frame type it will allow is SETTINGS

  // The session layer buffers the leading 8 byte header of every frame.
  // Non-Data frames are then buffered for their full length, but data
  // frames (type 0) are passed through to the http stack unprocessed

  if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
      mDownstreamState == BUFFERING_FRAME_HEADER) {
    // The first 9 bytes of every frame is header information that
    // we are going to want to strip before passing to http. That is
    // true of both control and data packets.

    MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
               "Frame Buffer Used Too Large for State");

    rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
                     kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);

    if (NS_FAILED(rv)) {
      LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n",
            this, static_cast<uint32_t>(rv)));
      // maybe just blocked reading from network
      if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
      return rv;
    }

    LogIO(this, nullptr, "Reading Frame Header",
          &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);

    mInputFrameBufferUsed += *countWritten;

    if (mInputFrameBufferUsed < kFrameHeaderBytes) {
      LOG3(
          ("Http2Session::WriteSegments %p "
           "BUFFERING FRAME HEADER incomplete size=%d",
           this, mInputFrameBufferUsed));
      return rv;
    }

    // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
    uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
    mInputFrameDataSize =
        NetworkEndian::readUint16(mInputFrameBuffer.get() + 1);
    if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
      LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte,
            mInputFrameDataSize));
      return SessionError(PROTOCOL_ERROR);
    }
    mInputFrameType = *reinterpret_cast<uint8_t*>(mInputFrameBuffer.get() +
                                                  kFrameLengthBytes);
    mInputFrameFlags = *reinterpret_cast<uint8_t*>(
        mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
    mInputFrameID =
        NetworkEndian::readUint32(mInputFrameBuffer.get() + kFrameLengthBytes +
                                  kFrameTypeBytes + kFrameFlagBytes);
    mInputFrameID &= 0x7fffffff;
    mInputFrameDataRead = 0;

    if (mInputFrameType == FRAME_TYPE_DATA ||
        mInputFrameType == FRAME_TYPE_HEADERS) {
      mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
    } else {
      mInputFrameFinal = false;
    }

    mPaddingLength = 0;

    LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read "
          "type %X data len %u flags %x id 0x%X",
          this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
          mInputFrameID));

    // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION
    // of a HEADERS frame with a matching ID (section 6.2)
    if (mExpectedHeaderID && ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
                              (mExpectedHeaderID != mInputFrameID))) {
      LOG3(
          ("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
      return SessionError(PROTOCOL_ERROR);
    }

    // if mExpectedPushPromiseID is non 0, it means this frame must be a
    // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
    if (mExpectedPushPromiseID &&
        ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
         (mExpectedPushPromiseID != mInputFrameID))) {
      LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
            mExpectedPushPromiseID));
      return SessionError(PROTOCOL_ERROR);
    }

    if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
        mInputFrameType != FRAME_TYPE_SETTINGS) {
      LOG3(("First Frame Type Must Be Settings\n"));
      mPeerFailedHandshake = true;

      // Don't allow any more h2 connections to this host
      RefPtr<nsHttpConnectionInfo> ci = ConnectionInfo();
      if (ci) {
        gHttpHandler->BlacklistSpdy(ci);
      }

      // Go through and re-start all of our transactions with h2 disabled.
      for (auto iter = mStreamTransactionHash.Iter(); !iter.Done();
           iter.Next()) {
        auto stream = iter.UserData();
        stream->Transaction()->DisableSpdy();
        CloseStream(stream, NS_ERROR_NET_RESET);
      }
      mStreamTransactionHash.Clear();
      return SessionError(PROTOCOL_ERROR);
    }

    if (mInputFrameType != FRAME_TYPE_DATA) {  // control frame
      EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
                   kFrameHeaderBytes, mInputFrameBufferSize);
      ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
    } else if (mInputFrameFlags & kFlag_PADDED) {
      ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
    } else {
      rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
      if (NS_FAILED(rv)) {
        return rv;
      }
    }
  }

  if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
    MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
               "Processing padding control on unpadded frame");

    MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
               "Frame buffer used too large for state");

    rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
                     (kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
                     countWritten);

    if (NS_FAILED(rv)) {
      LOG3(
          ("Http2Session %p buffering data frame padding control read failure "
           "%" PRIx32 "\n",
           this, static_cast<uint32_t>(rv)));
      // maybe just blocked reading from network
      if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
      return rv;
    }

    LogIO(this, nullptr, "Reading Data Frame Padding Control",
          &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);

    mInputFrameBufferUsed += *countWritten;

    if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
      LOG3(
          ("Http2Session::WriteSegments %p "
           "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
           this, mInputFrameBufferUsed - 8));
      return rv;
    }

    ++mInputFrameDataRead;

    char* control = &mInputFrameBuffer[kFrameHeaderBytes];
    mPaddingLength = static_cast<uint8_t>(*control);

    LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
          mInputFrameID, mPaddingLength));

    if (1U + mPaddingLength > mInputFrameDataSize) {
      LOG3(
          ("Http2Session::WriteSegments %p stream 0x%X padding too large for "
           "frame",
           this, mInputFrameID));
      return SessionError(PROTOCOL_ERROR);
    } else if (1U + mPaddingLength == mInputFrameDataSize) {
      // This frame consists entirely of padding, we can just discard it
      LOG3(
          ("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
           this, mInputFrameID));
      rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
      if (NS_FAILED(rv)) {
        return rv;
      }
    } else {
      LOG3(
          ("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
           this, mInputFrameID));
      rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
      if (NS_FAILED(rv)) {
        return rv;
      }
    }
  }

  if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
    nsresult streamCleanupCode;

    // There is no bounds checking on the error code.. we provide special
    // handling for a couple of cases and all others (including unknown) are
    // equivalent to cancel.
    if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
      streamCleanupCode = NS_ERROR_NET_RESET;  // can retry this 100% safely
      mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
    } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
      streamCleanupCode = NS_ERROR_NET_RESET;
      mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
      mInputFrameDataStream->Transaction()->DisableSpdy();
      mInputFrameDataStream->Transaction()
          ->MakeNonSticky();  // actully allow restart by unsticking
    } else {
      streamCleanupCode = mInputFrameDataStream->RecvdData()
                              ? NS_ERROR_NET_PARTIAL_TRANSFER
                              : NS_ERROR_NET_INTERRUPT;
    }

    if (mDownstreamRstReason == COMPRESSION_ERROR) {
      mShouldGoAway = true;
    }

    // mInputFrameDataStream is reset by ChangeDownstreamState
    Http2Stream* stream = mInputFrameDataStream;
    ResetDownstreamState();
    LOG3(
        ("Http2Session::WriteSegments cleanup stream on recv of rst "
         "session=%p stream=%p 0x%X\n",
         this, stream, stream ? stream->StreamID() : 0));
    CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
    return NS_OK;
  }

  if (mDownstreamState == PROCESSING_DATA_FRAME ||
      mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
    // The cleanup stream should only be set while stream->WriteSegments is
    // on the stack and then cleaned up in this code block afterwards.
    MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
    mNeedsCleanup = nullptr; /* just in case */

    uint32_t streamID = mInputFrameDataStream->StreamID();
    mSegmentWriter = writer;
    rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
    mSegmentWriter = nullptr;

    mLastDataReadEpoch = mLastReadEpoch;

    if (SoftStreamError(rv)) {
      // This will happen when the transaction figures out it is EOF, generally
      // due to a content-length match being made. Return OK from this function
      // otherwise the whole session would be torn down.

      // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
      // back to PROCESSING_DATA_FRAME where we came from
      mDownstreamState = PROCESSING_DATA_FRAME;

      if (mInputFrameDataRead == mInputFrameDataSize) ResetDownstreamState();
      LOG3(
          ("Http2Session::WriteSegments session=%p id 0x%X "
           "needscleanup=%p. cleanup stream based on "
           "stream->writeSegments returning code %" PRIx32 "\n",
           this, streamID, mNeedsCleanup, static_cast<uint32_t>(rv)));
      MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
      CleanupStream(
          streamID,
          (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK,
          CANCEL_ERROR);
      mNeedsCleanup = nullptr;
      *again = false;
      rv = ResumeRecv();
      if (NS_FAILED(rv)) {
        LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv)));
      }
      return NS_OK;
    }

    if (mNeedsCleanup) {
      LOG3(
          ("Http2Session::WriteSegments session=%p stream=%p 0x%X "
           "cleanup stream based on mNeedsCleanup.\n",
           this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
      CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
      mNeedsCleanup = nullptr;
    }

    if (NS_FAILED(rv)) {
      LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this,
            static_cast<uint32_t>(rv)));
      // maybe just blocked reading from network
      if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
    }

    return rv;
  }

  if (mDownstreamState == DISCARDING_DATA_FRAME ||
      mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
    char trash[4096];
    uint32_t discardCount =
        std::min(mInputFrameDataSize - mInputFrameDataRead, 4096U);
    LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s",
          this, discardCount,
          mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding"));

    if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
      // Only do this short-cirtuit if we're not discarding a pure padding
      // frame, as we need to potentially handle the stream FIN in those cases.
      // See bug 1381016 comment 36 for more details.
      ResetDownstreamState();
      Unused << ResumeRecv();
      return NS_BASE_STREAM_WOULD_BLOCK;
    }

    rv = NetworkRead(writer, trash, discardCount, countWritten);

    if (NS_FAILED(rv)) {
      LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this,
            static_cast<uint32_t>(rv)));
      // maybe just blocked reading from network
      if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
      return rv;
    }

    LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);

    mInputFrameDataRead += *countWritten;

    if (mInputFrameDataRead == mInputFrameDataSize) {
      Http2Stream* streamToCleanup = nullptr;
      if (mInputFrameFinal) {
        streamToCleanup = mInputFrameDataStream;
      }

      bool discardedPadding =
          (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
      ResetDownstreamState();

      if (streamToCleanup) {
        if (discardedPadding && !(streamToCleanup->StreamID() & 1)) {
          // Pushed streams are special on padding-only final data frames.
          // See bug 1409570 comments 6-8 for details.
          streamToCleanup->SetPushComplete();
          Http2Stream* pushSink = streamToCleanup->GetConsumerStream();
          if (pushSink) {
            bool enqueueSink = true;
            for (auto s : mPushesReadyForRead) {
              if (s == pushSink) {
                enqueueSink = false;
                break;
              }
            }
            if (enqueueSink) {
              mPushesReadyForRead.Push(pushSink);
              // No use trying to clean up, it won't do anything, anyway
              streamToCleanup = nullptr;
            }
          }
        }
        CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
      }
    }
    return rv;
  }

  if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
    MOZ_ASSERT(false);  // this cannot happen
    return NS_ERROR_UNEXPECTED;
  }

  MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes,
             "Frame Buffer Header Not Present");
  MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
             "allocation for control frame insufficient");

  rv = NetworkRead(writer,
                   &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
                   mInputFrameDataSize - mInputFrameDataRead, countWritten);

  if (NS_FAILED(rv)) {
    LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n",
          this, static_cast<uint32_t>(rv)));
    // maybe just blocked reading from network
    if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
    return rv;
  }

  LogIO(this, nullptr, "Reading Control Frame",
        &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
        *countWritten);

  mInputFrameDataRead += *countWritten;

  if (mInputFrameDataRead != mInputFrameDataSize) return NS_OK;

  MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
  if (mInputFrameType < FRAME_TYPE_LAST) {
    rv = sControlFunctions[mInputFrameType](this);
  } else {
    // Section 4.1 requires this to be ignored; though protocol_error would
    // be better
    LOG3(("Http2Session %p unknown frame type %x ignored\n", this,
          mInputFrameType));
    ResetDownstreamState();
    rv = NS_OK;
  }

  MOZ_ASSERT(NS_FAILED(rv) || mDownstreamState != BUFFERING_CONTROL_FRAME,
             "Control Handler returned OK but did not change state");

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

nsresult Http2Session::WriteSegments(nsAHttpSegmentWriter* writer,
                                     uint32_t count, uint32_t* countWritten) {
  bool again = false;
  return WriteSegmentsAgain(writer, count, countWritten, &again);
}

nsresult Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) {
  MOZ_ASSERT(mAttemptingEarlyData);
  LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
        aRestart, aAlpnChanged));

  for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
    if (m0RTTStreams[i]) {
      m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged);
    }
  }

  if (aRestart) {
    // 0RTT failed
    if (aAlpnChanged) {
      // This is a slightly more involved case - we need to get all our streams/
      // transactions back in the queue so they can restart as http/1

      // These must be set this way to ensure we gracefully restart all streams
      mGoAwayID = 0;
      mCleanShutdown = true;

      // Close takes care of the rest of our work for us. The reason code here
      // doesn't matter, as we aren't actually going to send a GOAWAY frame, but
      // we use NS_ERROR_NET_RESET as it's closest to the truth.
      Close(NS_ERROR_NET_RESET);
    } else {
      // This is the easy case - early data failed, but we're speaking h2, so
      // we just need to rewind to the beginning of the preamble and try again.
      mOutputQueueSent = 0;

      for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
        if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
          TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
        }
      }
    }
  } else {
    // 0RTT succeeded
    for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
      if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
        TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
      }
    }
    // Make sure we look for any incoming data in repsonse to our early data.
    Unused << ResumeRecv();
  }

  mAttemptingEarlyData = false;
  m0RTTStreams.Clear();
  mCannotDo0RTTStreams.Clear();
  RealignOutputQueue();

  return NS_OK;
}

void Http2Session::SetFastOpenStatus(uint8_t aStatus) {
  LOG3(("Http2Session::SetFastOpenStatus %d [this=%p]", aStatus, this));

  for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
    if (m0RTTStreams[i]) {
      m0RTTStreams[i]->Transaction()->SetFastOpenStatus(aStatus);
    }
  }
}

nsresult Http2Session::ProcessConnectedPush(Http2Stream* pushConnectedStream,
                                            nsAHttpSegmentWriter* writer,
                                            uint32_t count,
                                            uint32_t* countWritten) {
  LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n", this,
        pushConnectedStream->StreamID()));
  mSegmentWriter = writer;
  nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
  mSegmentWriter = nullptr;

  // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
  // so we need this check to determine the truth.
  if (NS_SUCCEEDED(rv) && !*countWritten && pushConnectedStream->PushSource() &&
      pushConnectedStream->PushSource()->GetPushComplete()) {
    rv = NS_BASE_STREAM_CLOSED;
  }

  if (rv == NS_BASE_STREAM_CLOSED) {
    CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
    rv = NS_OK;
  }

  // if we return OK to nsHttpConnection it will use mSocketInCondition
  // to determine whether to schedule more reads, incorrectly
  // assuming that nsHttpConnection::OnSocketWrite() was called.
  if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
    rv = NS_BASE_STREAM_WOULD_BLOCK;
    Unused << ResumeRecv();
  }
  return rv;
}

nsresult Http2Session::ProcessSlowConsumer(Http2Stream* slowConsumer,
                                           nsAHttpSegmentWriter* writer,
                                           uint32_t count,
                                           uint32_t* countWritten) {
  LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n", this,
        slowConsumer->StreamID()));
  mSegmentWriter = writer;
  nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
  mSegmentWriter = nullptr;
  LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32
        " %d\n",
        this, slowConsumer->StreamID(), static_cast<uint32_t>(rv),
        *countWritten));
  if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
    rv = NS_BASE_STREAM_CLOSED;
  }

  if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
    // There have been buffered bytes successfully fed into the
    // formerly blocked consumer. Repeat until buffer empty or
    // consumer is blocked again.
    UpdateLocalRwin(slowConsumer, 0);
    ConnectSlowConsumer(slowConsumer);
  }

  if (rv == NS_BASE_STREAM_CLOSED) {
    CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
    rv = NS_OK;
  }

  return rv;
}

void Http2Session::UpdateLocalStreamWindow(Http2Stream* stream,
                                           uint32_t bytes) {
  if (!stream)  // this is ok - it means there was a data frame for a rst stream
    return;

  // If this data packet was not for a valid or live stream then there
  // is no reason to mess with the flow control
  if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
      mInputFrameFinal) {
    return;
  }

  stream->DecrementClientReceiveWindow(bytes);

  // Don't necessarily ack every data packet. Only do it
  // after a significant amount of data.
  uint64_t unacked = stream->LocalUnAcked();
  int64_t localWindow = stream->ClientReceiveWindow();

  LOG3(
      ("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
       "unacked=%" PRIu64 " localWindow=%" PRId64 "\n",
       this, stream->StreamID(), bytes, unacked, localWindow));

  if (!unacked) return;

  if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
    return;

  if (!stream->HasSink()) {
    LOG3(
        ("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No "
         "Sink\n",
         this, stream->StreamID()));
    return;
  }

  // Generate window updates directly out of session instead of the stream
  // in order to avoid queue delays in getting the 'ACK' out.
  uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;

  LOG3(
      ("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
       this, stream->StreamID(), toack));
  stream->IncrementClientReceiveWindow(toack);
  if (toack == 0) {
    // Ensure we never send an illegal 0 window update
    return;
  }

  // room for this packet needs to be ensured before calling this function
  char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
  mOutputQueueUsed += kFrameHeaderBytes + 4;
  MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);

  CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);

  LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
  // dont flush here, this write can commonly be coalesced with a
  // session window update to immediately follow.
}

void Http2Session::UpdateLocalSessionWindow(uint32_t bytes) {
  if (!bytes) return;

  mLocalSessionWindow -= bytes;

  LOG3(
      ("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
       "localWindow=%" PRId64 "\n",
       this, bytes, mLocalSessionWindow));

  // Don't necessarily ack every data packet. Only do it
  // after a significant amount of data.
  if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
      (mLocalSessionWindow > kEmergencyWindowThreshold))
    return;

  // Only send max  bits of window updates at a time.
  uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
  uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;

  LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", this,
        toack));
  mLocalSessionWindow += toack;

  if (toack == 0) {
    // Ensure we never send an illegal 0 window update
    return;
  }

  // room for this packet needs to be ensured before calling this function
  char* packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
  mOutputQueueUsed += kFrameHeaderBytes + 4;
  MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);

  CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);

  LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
  // dont flush here, this write can commonly be coalesced with others
}

void Http2Session::UpdateLocalRwin(Http2Stream* stream, uint32_t bytes) {
  // make sure there is room for 2 window updates even though
  // we may not generate any.
  EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));

  UpdateLocalStreamWindow(stream, bytes);
  UpdateLocalSessionWindow(bytes);
  FlushOutputQueue();
}

void Http2Session::Close(nsresult aReason) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  if (mClosed) return;

  LOG3(("Http2Session::Close %p %" PRIX32, this,
        static_cast<uint32_t>(aReason)));

  mClosed = true;

  Shutdown();

  mStreamIDHash.Clear();
  mStreamTransactionHash.Clear();

  // If we have any websocket transactions waiting for settings frame,
  // reinitiate them. This can happend if we close a h2 connection before the
  // settings frame is received.
  if (mWaitingWebsockets.Length()) {
    MOZ_ASSERT(!mProcessedWaitingWebsockets);
    MOZ_ASSERT(mWaitingWebsockets.Length() ==
               mWaitingWebsocketCallbacks.Length());

    mProcessedWaitingWebsockets = true;
    for (size_t i = 0; i < mWaitingWebsockets.Length(); ++i) {
      RefPtr<nsAHttpTransaction> httpTransaction = mWaitingWebsockets[i];
      LOG3(("Http2Session::Close %p Re-queuing websocket.", this));
      httpTransaction->SetConnection(nullptr);
      nsHttpTransaction* trans = httpTransaction->QueryHttpTransaction();
      if (trans) {
        nsresult rv =
            gHttpHandler->InitiateTransaction(trans, trans->Priority());
        if (NS_FAILED(rv)) {
          LOG3(
              ("Http2Session::Close %p failed to reinitiate websocket "
               "transaction (%08x).\n",
               this, static_cast<uint32_t>(rv)));
        }
      } else {
        LOG3(("Http2Session::Close %p missing transaction?!", this));
      }
    }
    mWaitingWebsockets.Clear();
    mWaitingWebsocketCallbacks.Clear();
  }

  uint32_t goAwayReason;
  if (mGoAwayReason != NO_HTTP_ERROR) {
    goAwayReason = mGoAwayReason;
  } else if (NS_SUCCEEDED(aReason)) {
    goAwayReason = NO_HTTP_ERROR;
  } else if (aReason == NS_ERROR_NET_HTTP2_SENT_GOAWAY) {
    goAwayReason = PROTOCOL_ERROR;
  } else if (mCleanShutdown) {
    goAwayReason = NO_HTTP_ERROR;
  } else {
    goAwayReason = INTERNAL_ERROR;
  }
  if (!mAttemptingEarlyData) {
    GenerateGoAway(goAwayReason);
  }
  mConnection = nullptr;
  mSegmentReader = nullptr;
  mSegmentWriter = nullptr;
}

nsHttpConnectionInfo* Http2Session::ConnectionInfo() {
  RefPtr<nsHttpConnectionInfo> ci;
  GetConnectionInfo(getter_AddRefs(ci));
  return ci.get();
}

void Http2Session::CloseTransaction(nsAHttpTransaction* aTransaction,
                                    nsresult aResult) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction,
        static_cast<uint32_t>(aResult)));

  // Generally this arrives as a cancel event from the connection manager.

  // need to find the stream and call CleanupStream() on it.
  Http2Stream* stream = mStreamTransactionHash.Get(aTransaction);
  if (!stream) {
    LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.", this,
          aTransaction, static_cast<uint32_t>(aResult)));
    return;
  }
  LOG3(
      ("Http2Session::CloseTransaction probably a cancel. "
       "this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p",
       this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(),
       stream));
  CleanupStream(stream, aResult, CANCEL_ERROR);
  nsresult rv = ResumeRecv();
  if (NS_FAILED(rv)) {
    LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x",
          this, aTransaction, static_cast<uint32_t>(aResult),
          static_cast<uint32_t>(rv)));
  }
}

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

nsresult Http2Session::OnReadSegment(const char* buf, uint32_t count,
                                     uint32_t* countRead) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  nsresult rv;

  // If we can release old queued data then we can try and write the new
  // data directly to the network without using the output queue at all
  if (mOutputQueueUsed) FlushOutputQueue();

  if (!mOutputQueueUsed && mSegmentReader) {
    // try and write directly without output queue
    rv = mSegmentReader->OnReadSegment(buf, count, countRead);

    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      *countRead = 0;
    } else if (NS_FAILED(rv)) {
      return rv;
    }

    if (*countRead < count) {
      uint32_t required = count - *countRead;
      // assuming a commitment() happened, this ensurebuffer is a nop
      // but just in case the queuesize is too small for the required data
      // call ensurebuffer().
      EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
      memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
      mOutputQueueUsed = required;
    }

    *countRead = count;
    return NS_OK;
  }

  // At this point we are going to buffer the new data in the output
  // queue if it fits. By coalescing multiple small submissions into one larger
  // buffer we can get larger writes out to the network later on.

  // This routine should not be allowed to fill up the output queue
  // all on its own - at least kQueueReserved bytes are always left
  // for other routines to use - but this is an all-or-nothing function,
  // so if it will not all fit just return WOULD_BLOCK

  if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
    return NS_BASE_STREAM_WOULD_BLOCK;

  memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
  mOutputQueueUsed += count;
  *countRead = count;

  FlushOutputQueue();

  return NS_OK;
}

nsresult Http2Session::CommitToSegmentSize(uint32_t count,
                                           bool forceCommitment) {
  if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue();

  // would there be enough room to buffer this if needed?
  if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
    return NS_OK;

  // if we are using part of our buffers already, try again later unless
  // forceCommitment is set.
  if (mOutputQueueUsed && !forceCommitment) return NS_BASE_STREAM_WOULD_BLOCK;

  if (mOutputQueueUsed) {
    // normally we avoid the memmove of RealignOutputQueue, but we'll try
    // it if forceCommitment is set before growing the buffer.
    RealignOutputQueue();

    // is there enough room now?
    if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
      return NS_OK;
  }

  // resize the buffers as needed
  EnsureOutputBuffer(count + kQueueReserved);

  MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
             "buffer not as large as expected");

  return NS_OK;
}

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

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

  if (!mSegmentWriter) {
    // the only way this could happen would be if Close() were called on the
    // stack with WriteSegments()
    return NS_ERROR_FAILURE;
  }

  if (mDownstreamState == NOT_USING_NETWORK ||
      mDownstreamState == BUFFERING_FRAME_HEADER ||
      mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  if (mDownstreamState == PROCESSING_DATA_FRAME) {
    if (mInputFrameFinal && mInputFrameDataRead == mInputFrameDataSize) {
      *countWritten = 0;
      SetNeedsCleanup();
      return NS_BASE_STREAM_CLOSED;
    }

    count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
    rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
    if (NS_FAILED(rv)) return rv;

    LogIO(this, mInputFrameDataStream, "Reading Data Frame", buf,
          *countWritten);

    mInputFrameDataRead += *countWritten;
    if (mPaddingLength &&
        (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
      // We are crossing from real HTTP data into the realm of padding. If
      // we've actually crossed the line, we need to munge countWritten for the
      // sake of goodness and sanity. No matter what, any future calls to
      // WriteSegments need to just discard data until we reach the end of this
      // frame.
      if (mInputFrameDataSize != mInputFrameDataRead) {
        // Only change state if we still have padding to read. If we don't do
        // this, we can end up hanging on frames that combine real data,
        // padding, and END_STREAM (see bug 1019921)
        ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
      }
      uint32_t paddingRead =
          mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
      LOG3(
          ("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
           "crossed from HTTP data into padding (%d of %d) countWritten=%d",
           this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
           paddingRead, mPaddingLength, *countWritten));
      *countWritten -= paddingRead;
      LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
            this, mInputFrameID, *countWritten));
    }

    mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
    if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
      ResetDownstreamState();

    return rv;
  }

  if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
        mInputFrameFinal) {
      *countWritten = 0;
      SetNeedsCleanup();
      return NS_BASE_STREAM_CLOSED;
    }

    count = std::min(
        count, mFlatHTTPResponseHeaders.Length() - mFlatHTTPResponseHeadersOut);
    memcpy(buf, mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
           count);
    mFlatHTTPResponseHeadersOut += count;
    *countWritten = count;

    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
      if (!mInputFrameFinal) {
        // If more frames are expected in this stream, then reset the state so
        // they can be handled. Otherwise (e.g. a 0 length response with the fin
        // on the incoming headers) stay in PROCESSING_COMPLETE_HEADERS state so
        // the SetNeedsCleanup() code above can cleanup the stream.
        ResetDownstreamState();
      }
    }

    return NS_OK;
  }

  MOZ_ASSERT(false);
  return NS_ERROR_UNEXPECTED;
}

void Http2Session::SetNeedsCleanup() {
  LOG3(
      ("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
       "stream %p 0x%X",
       this, mInputFrameDataStream, mInputFrameDataStream->StreamID()));

  // This will result in Close() being called
  MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
  mInputFrameDataStream->SetResponseIsComplete();
  mNeedsCleanup = mInputFrameDataStream;
  ResetDownstreamState();
}

void Http2Session::ConnectPushedStream(Http2Stream* stream) {
  mPushesReadyForRead.Push(stream);
  Unused << ForceRecv();
}

void Http2Session::ConnectSlowConsumer(Http2Stream* stream) {
  LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n", this,
        stream->StreamID()));
  mSlowConsumersReadyForRead.Push(stream);
  Unused << ForceRecv();
}

uint32_t Http2Session::FindTunnelCount(nsHttpConnectionInfo* aConnInfo) {
  return FindTunnelCount(aConnInfo->HashKey());
}
uint32_t Http2Session::FindTunnelCount(nsCString const& aHashKey) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  uint32_t rv = 0;
  mTunnelHash.Get(aHashKey, &rv);
  return rv;
}

void Http2Session::RegisterTunnel(Http2Stream* aTunnel) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  nsCString const& regKey = aTunnel->RegistrationKey();
  uint32_t newcount = FindTunnelCount(regKey) + 1;
  mTunnelHash.Remove(regKey);
  mTunnelHash.Put(regKey, newcount);
  LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]", this,
        aTunnel, newcount, regKey.get()));
}

void Http2Session::UnRegisterTunnel(Http2Stream* aTunnel) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  nsCString const& regKey = aTunnel->RegistrationKey();
  MOZ_ASSERT(FindTunnelCount(regKey));
  uint32_t newcount = FindTunnelCount(regKey) - 1;
  mTunnelHash.Remove(regKey);
  if (newcount) {
    mTunnelHash.Put(regKey, newcount);
  }
  LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]", this,
        aTunnel, newcount, regKey.get()));
}

void Http2Session::CreateTunnel(nsHttpTransaction* trans,
                                nsHttpConnectionInfo* ci,
                                nsIInterfaceRequestor* aCallbacks) {
  LOG(("Http2Session::CreateTunnel %p %p make new tunnel\n", this, trans));
  // The connect transaction will hold onto the underlying http
  // transaction so that an auth created by the connect can be mappped
  // to the correct security callbacks

  RefPtr<SpdyConnectTransaction> connectTrans = new SpdyConnectTransaction(
      ci, aCallbacks, trans->Caps(), trans, this, false);
  DebugOnly<bool> rv =
      AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false,
                false, nullptr);
  MOZ_ASSERT(rv);
  Http2Stream* tunnel = mStreamTransactionHash.Get(connectTrans);
  MOZ_ASSERT(tunnel);
  RegisterTunnel(tunnel);
}

void Http2Session::DispatchOnTunnel(nsAHttpTransaction* aHttpTransaction,
                                    nsIInterfaceRequestor* aCallbacks) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
  nsHttpConnectionInfo* ci = aHttpTransaction->ConnectionInfo();
  MOZ_ASSERT(trans);

  LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));

  aHttpTransaction->SetConnection(nullptr);

  // this transaction has done its work of setting up a tunnel, let
  // the connection manager queue it if necessary
  trans->SetTunnelProvider(this);
  trans->EnableKeepAlive();

  if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
    LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s", this,
          ci->HashKey().get()));
    CreateTunnel(trans, ci, aCallbacks);
  } else {
    // requeue it. The connection manager is responsible for actually putting
    // this on the tunnel connection with the specific ci. If that can't
    // happen the cmgr checks with us via MaybeReTunnel() to see if it should
    // make a new tunnel or just wait longer.
    LOG3(
        ("Http2Session::DispatchOnTunnel %p trans=%p queue in connection "
         "manager",
         this, trans));
    nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
    if (NS_FAILED(rv)) {
      LOG3(
          ("Http2Session::DispatchOnTunnel %p trans=%p failed to initiate "
           "transaction (%08x)",
           this, trans, static_cast<uint32_t>(rv)));
    }
  }
}

// From ASpdySession
bool Http2Session::MaybeReTunnel(nsAHttpTransaction* aHttpTransaction) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
  LOG(("Http2Session::MaybeReTunnel %p trans=%p\n", this, trans));
  if (!trans || trans->TunnelProvider() != this) {
    // this isn't really one of our transactions.
    return false;
  }

  if (mClosed || mShouldGoAway) {
    LOG(("Http2Session::MaybeReTunnel %p %p session closed - requeue\n", this,
         trans));
    trans->SetTunnelProvider(nullptr);
    nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
    if (NS_FAILED(rv)) {
      LOG3(
          ("Http2Session::MaybeReTunnel %p trans=%p failed to initiate "
           "transaction (%08x)",
           this, trans, static_cast<uint32_t>(rv)));
    }
    return true;
  }

  nsHttpConnectionInfo* ci = aHttpTransaction->ConnectionInfo();
  LOG(("Http2Session:MaybeReTunnel %p %p count=%d limit %d\n", this, trans,
       FindTunnelCount(ci), gHttpHandler->MaxConnectionsPerOrigin()));
  if (FindTunnelCount(ci) >= gHttpHandler->MaxConnectionsPerOrigin()) {
    // patience - a tunnel will open up.
    return false;
  }

  LOG(("Http2Session::MaybeReTunnel %p %p make new tunnel\n", this, trans));
  CreateTunnel(trans, ci, trans->SecurityCallbacks());
  return true;
}

nsresult Http2Session::BufferOutput(const char* buf, uint32_t count,
                                    uint32_t* countRead) {
  nsAHttpSegmentReader* old = mSegmentReader;
  mSegmentReader = nullptr;
  nsresult rv = OnReadSegment(buf, count, countRead);
  mSegmentReader = old;
  return rv;
}

bool  // static
Http2Session::ALPNCallback(nsISupports* securityInfo) {
  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
  LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
  if (ssl) {
    int16_t version = ssl->GetSSLVersionOffered();
    LOG3(("Http2Session::ALPNCallback version=%x\n", version));

    if (version == nsISSLSocketControl::TLS_VERSION_1_2 &&
        !gHttpHandler->IsH2MandatorySuiteEnabled()) {
      LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
      return false;
    }

    if (version >= nsISSLSocketControl::TLS_VERSION_1_2) {
      return true;
    }
  }
  return false;
}

nsresult Http2Session::ConfirmTLSProfile() {
  if (mTLSProfileConfirmed) {
    return NS_OK;
  }

  LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this,
        mConnection.get()));

  if (mAttemptingEarlyData) {
    LOG3(
        ("Http2Session::ConfirmTLSProfile %p temporarily passing due to early "
         "data\n",
         this));
    return NS_OK;
  }

  if (!gHttpHandler->EnforceHttp2TlsProfile()) {
    LOG3(
        ("Http2Session::ConfirmTLSProfile %p passed due to configuration "
         "bypass\n",
         this));
    mTLSProfileConfirmed = true;
    return NS_OK;
  }

  if (!mConnection) return NS_ERROR_FAILURE;

  nsCOMPtr<nsISupports> securityInfo;
  mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
  LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this,
        ssl.get()));
  if (!ssl) return NS_ERROR_FAILURE;

  int16_t version = ssl->GetSSLVersionUsed();
  LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
  if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n",
          this));
    return SessionError(INADEQUATE_SECURITY);
  }

  uint16_t kea = ssl->GetKEAUsed();
  if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
          this, kea));
    return SessionError(INADEQUATE_SECURITY);
  }

  uint32_t keybits = ssl->GetKEAKeyBits();
  if (kea == ssl_kea_dh && keybits < 2048) {
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
          this, keybits));
    return SessionError(INADEQUATE_SECURITY);
  } else if (kea == ssl_kea_ecdh && keybits < 224) {  // see rfc7540 9.2.1.
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
          this, keybits));
    return SessionError(INADEQUATE_SECURITY);
  }

  int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
  LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n", this,
        macAlgorithm));
  if (macAlgorithm != nsISSLSocketControl::SSL_MAC_AEAD) {
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n",
          this));
    return SessionError(INADEQUATE_SECURITY);
  }

  /* We are required to send SNI. We do that already, so no check is done
   * here to make sure we did. */

  /* We really should check to ensure TLS compression isn't enabled on
   * this connection. However, we never enable TLS compression on our end,
   * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
   * for the possibility for an interface to ensure it never gets turned on. */

  mTLSProfileConfirmed = true;
  return NS_OK;
}

//-----------------------------------------------------------------------------
// Modified methods of nsAHttpConnection
//-----------------------------------------------------------------------------

void Http2Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));

  // a trapped signal from the http transaction to the connection that
  // it is no longer blocked on read.

  Http2Stream* stream = mStreamTransactionHash.Get(caller);
  if (!stream || !VerifyStream(stream)) {
    LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
          this, caller));
    return;
  }

  LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", this,
        stream->StreamID()));

  if (!mClosed) {
    mReadyForWrite.Push(stream);
    SetWriteCallbacks();
  } else {
    LOG3(
        ("Http2Session::TransactionHasDataToWrite %p closed so not setting "
         "Ready4Write\n",
         this));
  }

  // NSPR poll will not poll the network if there are non system PR_FileDesc's
  // that are ready - so we can get into a deadlock waiting for the system IO
  // to come back here if we don't force the send loop manually.
  Unused << ForceSend();
}

void Http2Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));

  // a signal from the http transaction to the connection that it will consume
  // more
  Http2Stream* stream = mStreamTransactionHash.Get(caller);
  if (!stream || !VerifyStream(stream)) {
    LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found", this,
          caller));
    return;
  }

  LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n", this,
        stream->StreamID()));
  ConnectSlowConsumer(stream);
}

void Http2Session::TransactionHasDataToWrite(Http2Stream* stream) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", this,
        stream, stream->StreamID()));

  mReadyForWrite.Push(stream);
  SetWriteCallbacks();
  Unused << ForceSend();
}

bool Http2Session::IsPersistent() { return true; }

nsresult Http2Session::TakeTransport(nsISocketTransport**,
                                     nsIAsyncInputStream**,
                                     nsIAsyncOutputStream**) {
  MOZ_ASSERT(false, "TakeTransport of Http2Session");
  return NS_ERROR_UNEXPECTED;
}

already_AddRefed<HttpConnectionBase> Http2Session::TakeHttpConnection() {
  MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
  return nullptr;
}

already_AddRefed<HttpConnectionBase> Http2Session::HttpConnection() {
  if (mConnection) {
    return mConnection->HttpConnection();
  }
  return nullptr;
}

void Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) {
  *aOut = nullptr;
}

//-----------------------------------------------------------------------------
// unused methods of nsAHttpTransaction
// We can be sure of this because Http2Session is only constructed in
// nsHttpConnection and is never passed out of that object or a
// TLSFilterTransaction TLS tunnel
//-----------------------------------------------------------------------------

void Http2Session::SetConnection(nsAHttpConnection*) {
  // This is unexpected
  MOZ_ASSERT(false, "Http2Session::SetConnection()");
}

void Http2Session::SetProxyConnectFailed() {
  MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
}

bool Http2Session::IsDone() { return !mStreamTransactionHash.Count(); }

nsresult Http2Session::Status() {
  MOZ_ASSERT(false, "Http2Session::Status()");
  return NS_ERROR_UNEXPECTED;
}

uint32_t Http2Session::Caps() {
  MOZ_ASSERT(false, "Http2Session::Caps()");
  return 0;
}

nsHttpRequestHead* Http2Session::RequestHead() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(false,
             "Http2Session::RequestHead() "
             "should not be called after http/2 is setup");
  return nullptr;
}

uint32_t Http2Session::Http1xTransactionCount() { return 0; }

nsresult Http2Session::TakeSubTransactions(
    nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
  // Generally this cannot be done with http/2 as transactions are
  // started right away.

  LOG3(("Http2Session::TakeSubTransactions %p\n", this));

  if (mConcurrentHighWater > 0) return NS_ERROR_ALREADY_OPENED;

  LOG3(("   taking %d\n", mStreamTransactionHash.Count()));

  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
    outTransactions.AppendElement(iter.Key());

    // Removing the stream from the hash will delete the stream and drop the
    // transaction reference the hash held.
    iter.Remove();
  }
  return NS_OK;
}

//-----------------------------------------------------------------------------
// Pass through methods of nsAHttpConnection
//-----------------------------------------------------------------------------

nsAHttpConnection* Http2Session::Connection() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  return mConnection;
}

nsresult Http2Session::OnHeadersAvailable(nsAHttpTransaction* transaction,
                                          nsHttpRequestHead* requestHead,
                                          nsHttpResponseHead* responseHead,
                                          bool* reset) {
  return mConnection->OnHeadersAvailable(transaction, requestHead, responseHead,
                                         reset);
}

bool Http2Session::IsReused() { return mConnection->IsReused(); }

nsresult Http2Session::PushBack(const char* buf, uint32_t len) {
  return mConnection->PushBack(buf, len);
}

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

  if (mPreviousUsed) {
    // alredy in progress, get out
    return;
  }

  mPingSentEpoch = PR_IntervalNow();
  if (!mPingSentEpoch) {
    mPingSentEpoch = 1;  // avoid the 0 sentinel value
  }
  if (!mPingThreshold ||
      (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
    mPreviousPingThreshold = mPingThreshold;
    mPreviousUsed = true;
    mPingThreshold = gHttpHandler->NetworkChangedTimeout();
  }
  GeneratePing(false);
  Unused << ResumeRecv();
}

bool Http2Session::TestOriginFrame(const nsACString& hostname, int32_t port) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(mOriginFrameActivated);

  nsAutoCString key(hostname);
  key.Append(':');
  key.AppendInt(port);
  bool rv = mOriginFrame.Get(key);
  LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv));
  if (!rv && ConnectionInfo()) {
    // the SNI is also implicitly in this list, so consult that too
    nsHttpConnectionInfo* ci = ConnectionInfo();
    rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
         (port == ci->OriginPort());
    LOG3(("TestOriginFrame() %p sni test %d\n", this, rv));
  }
  return rv;
}

bool Http2Session::TestJoinConnection(const nsACString& hostname,
                                      int32_t port) {
  return RealJoinConnection(hostname, port, true);
}

bool Http2Session::JoinConnection(const nsACString& hostname, int32_t port) {
  return RealJoinConnection(hostname, port, false);
}

bool Http2Session::RealJoinConnection(const nsACString& hostname, int32_t port,
                                      bool justKidding) {
  if (!mConnection || mClosed || mShouldGoAway) {
    return false;
  }

  nsHttpConnectionInfo* ci = ConnectionInfo();
  if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
      (port == ci->OriginPort())) {
    return true;
  }

  if (!mReceivedSettings) {
    return false;
  }

  if (mOriginFrameActivated) {
    bool originFrameResult = TestOriginFrame(hostname, port);
    if (!originFrameResult) {
      return false;
    }
  } else {
    LOG3(("JoinConnection %p no origin frame check used.\n", this));
  }

  nsAutoCString key(hostname);
  key.Append(':');
  key.Append(justKidding ? 'k' : '.');
  key.AppendInt(port);
  bool cachedResult;
  if (mJoinConnectionCache.Get(key, &cachedResult)) {
    LOG(("joinconnection [%p %s] %s result=%d cache\n", this,
         ConnectionInfo()->HashKey().get(), key.get(), cachedResult));
    return cachedResult;
  }

  nsresult rv;
  bool isJoined = false;

  nsCOMPtr<nsISupports> securityInfo;
  nsCOMPtr<nsISSLSocketControl> sslSocketControl;

  mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
  sslSocketControl = do_QueryInterface(securityInfo, &rv);
  if (NS_FAILED(rv) || !sslSocketControl) {
    return false;
  }

  // try all the coalescable versions we support.
  const SpdyInformation* info = gHttpHandler->SpdyInfo();
  static_assert(SpdyInformation::kCount == 1, "assume 1 alpn version");
  bool joinedReturn = false;
  if (info->ProtocolEnabled(0)) {
    if (justKidding) {
      rv = sslSocketControl->TestJoinConnection(info->VersionString[0],
                                                hostname, port, &isJoined);
    } else {
      rv = sslSocketControl->JoinConnection(info->VersionString[0], hostname,
                                            port, &isJoined);
    }
    if (NS_SUCCEEDED(rv) && isJoined) {
      joinedReturn = true;
    }
  }

  LOG(("joinconnection [%p %s] %s result=%d lookup\n", this,
       ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
  mJoinConnectionCache.Put(key, joinedReturn);
  if (!justKidding) {
    // cache a kidding entry too as this one is good for both
    nsAutoCString key2(hostname);
    key2.Append(':');
    key2.Append('k');
    key2.AppendInt(port);
    if (!mJoinConnectionCache.Get(key2)) {
      mJoinConnectionCache.Put(key2, joinedReturn);
    }
  }
  return joinedReturn;
}

void Http2Session::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  mCurrentForegroundTabOuterContentWindowId = windowId;

  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
    iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
  }
}

void Http2Session::SetCleanShutdown(bool aCleanShutdown) {
  mCleanShutdown = aCleanShutdown;
}

void Http2Session::CreateWebsocketStream(
    nsAHttpTransaction* aOriginalTransaction,
    nsIInterfaceRequestor* aCallbacks) {
  LOG(("Http2Session::CreateWebsocketStream %p %p\n", this,
       aOriginalTransaction));

  nsHttpTransaction* trans = aOriginalTransaction->QueryHttpTransaction();
  MOZ_ASSERT(trans);

  nsHttpConnectionInfo* ci = aOriginalTransaction->ConnectionInfo();
  MOZ_ASSERT(ci);

  RefPtr<SpdyConnectTransaction> connectTrans = new SpdyConnectTransaction(
      ci, aCallbacks, trans->Caps(), trans, this, true);
  DebugOnly<bool> rv =
      AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false,
                false, nullptr);
  MOZ_ASSERT(rv);
}

void Http2Session::ProcessWaitingWebsockets() {
  MOZ_ASSERT(!mProcessedWaitingWebsockets);
  MOZ_ASSERT(mWaitingWebsockets.Length() ==
             mWaitingWebsocketCallbacks.Length());

  mProcessedWaitingWebsockets = true;

  if (!mWaitingWebsockets.Length()) {
    // Nothing to do here
    LOG3(("Http2Session::ProcessWaitingWebsockets %p nothing to do", this));
    return;
  }

  for (size_t i = 0; i < mWaitingWebsockets.Length(); ++i) {
    RefPtr<nsAHttpTransaction> httpTransaction = mWaitingWebsockets[i];
    nsCOMPtr<nsIInterfaceRequestor> callbacks = mWaitingWebsocketCallbacks[i];

    if (mPeerAllowsWebsockets) {
      LOG3(
          ("Http2Session::ProcessWaitingWebsockets session=%p trans=%p "
           "websocket",
           this, httpTransaction.get()));
      CreateWebsocketStream(httpTransaction, callbacks);
    } else {
      LOG3(
          ("Http2Session::ProcessWaitingWebsockets %p Re-queuing websocket as "
           "h1 due to mPeerAllowsWebsockets=false",
           this));
      httpTransaction->SetConnection(nullptr);
      httpTransaction->DisableSpdy();
      nsHttpTransaction* trans = httpTransaction->QueryHttpTransaction();
      if (trans) {
        nsresult rv =
            gHttpHandler->InitiateTransaction(trans, trans->Priority());
        if (NS_FAILED(rv)) {
          LOG3(
              ("Http2Session::ProcessWaitingWebsockets %p failed to reinitiate "
               "websocket transaction (%08x).\n",
               this, static_cast<uint32_t>(rv)));
        }
      } else {
        LOG3(("Http2Session::ProcessWaitingWebsockets %p missing transaction?!",
              this));
      }
    }
  }

  mWaitingWebsockets.Clear();
  mWaitingWebsocketCallbacks.Clear();
}

bool Http2Session::CanAcceptWebsocket() {
  LOG3(("Http2Session::CanAcceptWebsocket %p enable=%d allow=%d processed=%d",
        this, mEnableWebsockets, mPeerAllowsWebsockets,
        mProcessedWaitingWebsockets));
  if (mEnableWebsockets &&
      (mPeerAllowsWebsockets || !mProcessedWaitingWebsockets)) {
    return true;
  }

  return false;
}

}  // namespace net
}  // namespace mozilla