forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			3827 lines
		
	
	
	
		
			124 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3827 lines
		
	
	
	
		
			124 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set sw=2 ts=8 et tw=80 : */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| // HttpLog.h should generally be included first
 | |
| #include "HttpLog.h"
 | |
| 
 | |
| // Log on level :5, instead of default :4.
 | |
| #undef LOG
 | |
| #define LOG(args) LOG5(args)
 | |
| #undef LOG_ENABLED
 | |
| #define LOG_ENABLED() LOG5_ENABLED()
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| #include "Http2Session.h"
 | |
| #include "Http2Stream.h"
 | |
| #include "Http2Push.h"
 | |
| 
 | |
| #include "mozilla/Endian.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "nsHttp.h"
 | |
| #include "nsHttpHandler.h"
 | |
| #include "nsHttpConnection.h"
 | |
| #include "nsISchedulingContext.h"
 | |
| #include "nsISSLSocketControl.h"
 | |
| #include "nsISSLStatus.h"
 | |
| #include "nsISSLStatusProvider.h"
 | |
| #include "nsISupportsPriority.h"
 | |
| #include "nsStandardURL.h"
 | |
| #include "nsURLHelper.h"
 | |
| #include "prnetdb.h"
 | |
| #include "sslt.h"
 | |
| #include "mozilla/Snprintf.h"
 | |
| #include "nsSocketTransportService2.h"
 | |
| #include "nsNetUtil.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace net {
 | |
| 
 | |
| // Http2Session has multiple inheritance of things that implement
 | |
| // nsISupports, so this magic is taken from nsHttpPipeline that
 | |
| // implements some of the same abstract classes.
 | |
| 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
 | |
| };
 | |
| 
 | |
| #define RETURN_SESSION_ERROR(o,x)  \
 | |
| do {                             \
 | |
|   (o)->mGoAwayReason = (x);      \
 | |
|   return NS_ERROR_ILLEGAL_VALUE; \
 | |
|   } while (0)
 | |
| 
 | |
| Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version)
 | |
|   : mSocketTransport(aSocketTransport)
 | |
|   , mSegmentReader(nullptr)
 | |
|   , mSegmentWriter(nullptr)
 | |
|   , mNextStreamID(3) // 1 is reserved for Updgrade handshakes
 | |
|   , 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)
 | |
|   , 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)
 | |
|   , mWaitingForSettingsAck(false)
 | |
|   , mGoAwayOnPush(false)
 | |
|   , mUseH2Deps(false)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   static uint64_t sSerial;
 | |
|   mSerial = ++sSerial;
 | |
| 
 | |
|   LOG3(("Http2Session::Http2Session %p serial=0x%X\n", this, mSerial));
 | |
| 
 | |
|   mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
 | |
|   mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
 | |
|   mDecompressBuffer.SetCapacity(kDefaultBufferSize);
 | |
|   mDecompressor.SetCompressor(&mCompressor);
 | |
| 
 | |
|   mPushAllowance = gHttpHandler->SpdyPushAllowance();
 | |
|   mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
 | |
|   mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
 | |
|   mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
 | |
|   SendHello();
 | |
| 
 | |
|   mLastDataReadEpoch = mLastReadEpoch;
 | |
| 
 | |
|   mPingThreshold = gHttpHandler->SpdyPingThreshold();
 | |
|   mPreviousPingThreshold = mPingThreshold;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::Shutdown()
 | |
| {
 | |
|   for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
 | |
|     nsAutoPtr<Http2Stream> &stream = iter.Data();
 | |
| 
 | |
|     // 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 {
 | |
|       CloseStream(stream, NS_ERROR_ABORT);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| Http2Session::~Http2Session()
 | |
| {
 | |
|   LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
 | |
|         this, mDownstreamState));
 | |
| 
 | |
|   Shutdown();
 | |
| 
 | |
|   Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
 | |
|   Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
 | |
|   Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
 | |
|                         mServerPushedResources);
 | |
|   Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
 | |
|   Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
 | |
| }
 | |
| 
 | |
| 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
 | |
| };
 | |
| 
 | |
| bool
 | |
| Http2Session::RoomForMoreConcurrent()
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
 | |
|        this, PR_IntervalToSeconds(now - mLastReadEpoch)));
 | |
| 
 | |
|   if (!mPingThreshold)
 | |
|     return UINT32_MAX;
 | |
| 
 | |
|   if ((now - mLastReadEpoch) < mPingThreshold) {
 | |
|     // recent activity means ping is not an issue
 | |
|     if (mPingSentEpoch) {
 | |
|       mPingSentEpoch = 0;
 | |
|       if (mPreviousUsed) {
 | |
|         // restore the former value
 | |
|         mPingThreshold = mPreviousPingThreshold;
 | |
|         mPreviousUsed = false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return PR_IntervalToSeconds(mPingThreshold) -
 | |
|       PR_IntervalToSeconds(now - mLastReadEpoch);
 | |
|   }
 | |
| 
 | |
|   if (mPingSentEpoch) {
 | |
|     LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n"));
 | |
|     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);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   LOG3(("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);
 | |
|   return aNewID;
 | |
| }
 | |
| 
 | |
| bool
 | |
| Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
 | |
|                         int32_t aPriority,
 | |
|                         bool aUseTunnel,
 | |
|                         nsIInterfaceRequestor *aCallbacks)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   // 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();
 | |
|   }
 | |
| 
 | |
|   aHttpTransaction->SetConnection(this);
 | |
| 
 | |
|   if (aUseTunnel) {
 | |
|     LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
 | |
|           this, aHttpTransaction));
 | |
|     DispatchOnTunnel(aHttpTransaction, aCallbacks);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
 | |
| 
 | |
|   LOG3(("Http2Session::AddStream session=%p stream=%p serial=%u "
 | |
|         "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;
 | |
|     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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   if (!count) {
 | |
|     *countWritten = 0;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
 | |
|   if (NS_SUCCEEDED(rv) && *countWritten > 0)
 | |
|     mLastReadEpoch = PR_IntervalNow();
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::SetWriteCallbacks()
 | |
| {
 | |
|   if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
 | |
|     mConnection->ResumeSend();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::RealignOutputQueue()
 | |
| {
 | |
|   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;
 | |
| 
 | |
|   rv = mSegmentReader->
 | |
|     OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
 | |
|                   &countRead);
 | |
|   LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d",
 | |
|         this, avail, rv, countRead));
 | |
| 
 | |
|   // Dont worry about errors on write, we will pick this up as a read error too
 | |
|   if (NS_FAILED(rv))
 | |
|     return;
 | |
| 
 | |
|   if (countRead == avail) {
 | |
|     mOutputQueueUsed = 0;
 | |
|     mOutputQueueSent = 0;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mOutputQueueSent += countRead;
 | |
| 
 | |
|   // 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));
 | |
|   mShouldGoAway = true;
 | |
|   if (!mStreamTransactionHash.Count())
 | |
|     Close(NS_OK);
 | |
| }
 | |
| 
 | |
| uint32_t
 | |
| Http2Session::GetWriteQueueSize()
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   return mReadyForWrite.GetSize();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::ChangeDownstreamState(enum internalStateType newState)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X",
 | |
|         this, mDownstreamState, newState));
 | |
|   mDownstreamState = newState;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::ResetDownstreamState()
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   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 %d\n", this, aStream));
 | |
|     QueueStream(aStream);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
 | |
|   IncrementConcurrent(aStream);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::IncrementConcurrent(Http2Stream *stream)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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));
 | |
| 
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   LOG3(("Http2Session::GeneratePriority %p %X %X\n",
 | |
|         this, aID, aPriorityWeight));
 | |
| 
 | |
|   uint32_t frameSize = kFrameHeaderBytes + 5;
 | |
|   char *packet = EnsureOutputBuffer(frameSize);
 | |
|   mOutputQueueUsed += frameSize;
 | |
| 
 | |
|   CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, aID);
 | |
|   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, 0);
 | |
|   memcpy(packet + frameSize - 1, &aPriorityWeight, 1);
 | |
|   LogIO(this, nullptr, "Generate Priority", packet, frameSize);
 | |
|   FlushOutputQueue();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   // 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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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] 5 priority frames for streams which will never be opened with headers
 | |
| //    these streams (3, 5, 7, 9, b) build a dependency tree that all other
 | |
| //    streams will be direct leaves of.
 | |
| void
 | |
| Http2Session::SendHello()
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   LOG3(("Http2Session::SendHello %p\n", this));
 | |
| 
 | |
|   // sized for magic + 4 settings and a session window update and 5 priority frames
 | |
|   // 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window update,
 | |
|   // 5 priority frames at 14 (9 + 5) each
 | |
|   static const uint32_t maxSettings = 4;
 | |
|   static const uint32_t prioritySize = 5 * (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
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   FlushOutputQueue();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
 | |
|                                  const char *label)
 | |
| {
 | |
|   char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
 | |
|   CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
 | |
|   mOutputQueueUsed += kFrameHeaderBytes + 5;
 | |
|   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, dependsOn); // depends on
 | |
|   packet[kFrameHeaderBytes + 4] = weight; // 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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
| #ifndef DEBUG
 | |
|   // Only do the real verification in debug builds
 | |
|   return true;
 | |
| #endif
 | |
| 
 | |
|   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 (0);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
 | |
|                             errorType aResetCode)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   LOG3(("Http2Session::CleanupStream %p %p 0x%X %X\n",
 | |
|         this, aStream, aStream ? aStream->StreamID() : 0, aResult));
 | |
|   if (!aStream) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   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));
 | |
|     pushSource->SetConsumerStream(nullptr);
 | |
|   }
 | |
| 
 | |
|   // 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;
 | |
|       pushStream->GetHashKey(hashKey);
 | |
|       nsISchedulingContext *schedulingContext = aStream->SchedulingContext();
 | |
|       if (schedulingContext) {
 | |
|         SpdyPushCache *cache = nullptr;
 | |
|         schedulingContext->GetSpdyPushCache(&cache);
 | |
|         if (cache) {
 | |
|           Http2PushedStream *trash = cache->RemovePushedStreamHttp2(hashKey);
 | |
|           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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n",
 | |
|         this, aStream, aStream->StreamID(), 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_SESSION_ERROR(this, 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;
 | |
|   }
 | |
|   self->SetInputFrameDataStream(self->mInputFrameID);
 | |
| 
 | |
|   // 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;
 | |
|   nsresult rv;
 | |
| 
 | |
|   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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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 (!endHeadersFlag) { // more are coming - don't process yet
 | |
|     self->ResetDownstreamState();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   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));
 | |
| 
 | |
|   // only interpret headers once, afterwards ignore as trailers
 | |
|   if (mInputFrameDataStream->AllHeadersReceived()) {
 | |
|     LOG3(("Http2Session::ResponseHeadersComplete extra headers"));
 | |
|     MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
 | |
|     nsresult rv = UncompressAndDiscard(false);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       LOG3(("Http2Session::ResponseHeadersComplete extra uncompress 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_ABORT) {
 | |
|     LOG(("Http2Session::ResponseHeadersComplete ConvertResponseHeaders aborted\n"));
 | |
|     if (mInputFrameDataStream->IsTunnel()) {
 | |
|       gHttpHandler->ConnMgr()->CancelTransactions(
 | |
|         mInputFrameDataStream->Transaction()->ConnectionInfo(),
 | |
|         NS_ERROR_CONNECTION_REFUSED);
 | |
|     }
 | |
|     CleanupStream(mInputFrameDataStream, rv, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
 | |
|   }
 | |
| 
 | |
|   if (!self->mInputFrameID) {
 | |
|     LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
 | |
|     RETURN_SESSION_ERROR(self, 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);
 | |
|   if (self->mInputFrameDataStream) {
 | |
|     self->mInputFrameDataStream->SetPriorityDependency(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_SESSION_ERROR(self, PROTOCOL_ERROR);
 | |
|   }
 | |
| 
 | |
|   if (!self->mInputFrameID) {
 | |
|     LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
 | |
|     RETURN_SESSION_ERROR(self, 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));
 | |
| 
 | |
|   self->SetInputFrameDataStream(self->mInputFrameID);
 | |
|   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_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
 | |
|   }
 | |
| 
 | |
|   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"));
 | |
|     RETURN_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
 | |
|         }
 | |
|         // We stick to the default for simplicity's sake, so nothing to change
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   self->ResetDownstreamState();
 | |
| 
 | |
|   if (!(self->mInputFrameFlags & kFlag_ACK)) {
 | |
|     self->GenerateSettingsAck();
 | |
|   } else if (self->mWaitingForSettingsAck) {
 | |
|     self->mGoAwayOnPush = true;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
 | |
|     }
 | |
|     self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
 | |
|   } else if (!associatedStream) {
 | |
|     LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self));
 | |
|     self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
 | |
|   } else {
 | |
|     nsISchedulingContext *schedulingContext = associatedStream->SchedulingContext();
 | |
|     if (schedulingContext) {
 | |
|       schedulingContext->GetSpdyPushCache(&cache);
 | |
|       if (!cache) {
 | |
|         cache = new SpdyPushCache();
 | |
|         if (!cache || NS_FAILED(schedulingContext->SetSpdyPushCache(cache))) {
 | |
|           delete cache;
 | |
|           cache = nullptr;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     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->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
 | |
|     LOG3(("Http2Session::RecvPushPromise not finishing processing for multi-frame push\n"));
 | |
|     self->ResetDownstreamState();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Create the buffering transaction and push stream
 | |
|   RefPtr<Http2PushTransactionBuffer> transactionBuffer =
 | |
|     new Http2PushTransactionBuffer();
 | |
|   transactionBuffer->SetConnection(self);
 | |
|   Http2PushedStream *pushedStream =
 | |
|     new Http2PushedStream(transactionBuffer, self, associatedStream, promisedID);
 | |
| 
 | |
|   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);
 | |
|     delete pushedStream;
 | |
|     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);
 | |
|     delete pushedStream;
 | |
|     return NS_OK;
 | |
|   } else if (NS_FAILED(rv)) {
 | |
|     // This is fatal to the session.
 | |
|     self->mGoAwayReason = COMPRESSION_ERROR;
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // 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, pushedStream);
 | |
|   self->mPushedStreams.AppendElement(pushedStream);
 | |
| 
 | |
|   if (self->RegisterStreamID(pushedStream, 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;
 | |
|   pushedStream->ReadSegments(nullptr, 1, ¬Used);
 | |
| 
 | |
|   nsAutoCString key;
 | |
|   if (!pushedStream->GetHashKey(key)) {
 | |
|     LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
 | |
|     self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
 | |
|     self->ResetDownstreamState();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsStandardURL> associatedURL, pushedURL;
 | |
|   rv = Http2Stream::MakeOriginURL(associatedStream->Origin(), associatedURL);
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedURL);
 | |
|   }
 | |
|   LOG3(("Http2Session::RecvPushPromise %p checking %s == %s", self,
 | |
|         associatedStream->Origin().get(), pushedStream->Origin().get()));
 | |
|   bool match = false;
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     rv = associatedURL->Equals(pushedURL, &match);
 | |
|   }
 | |
|   if (NS_FAILED(rv)) {
 | |
|     // Fallback to string equality of origins. This won't be guaranteed to be as
 | |
|     // liberal as we want it to be, but it will at least be safe
 | |
|     match = associatedStream->Origin().Equals(pushedStream->Origin());
 | |
|   }
 | |
|   if (!match) {
 | |
|     LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin "
 | |
|           "associated origin %s .. pushed origin %s\n", self,
 | |
|           associatedStream->Origin().get(), pushedStream->Origin().get()));
 | |
|     self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
 | |
|     self->ResetDownstreamState();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (pushedStream->TryOnPush()) {
 | |
|     LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
 | |
|           "stream %p will not be placed into session cache.\n", self, pushedStream));
 | |
|   } else {
 | |
|     LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n", self));
 | |
|     if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
 | |
|       LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
 | |
|       self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
 | |
|       self->ResetDownstreamState();
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
 | |
|   static_assert(Http2Stream::kWorstPriority >= 0,
 | |
|                 "kWorstPriority out of range");
 | |
|   uint8_t priorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
 | |
|     (Http2Stream::kWorstPriority - Http2Stream::kNormalPriority);
 | |
|   pushedStream->SetPriority(Http2Stream::kWorstPriority);
 | |
|   self->GeneratePriority(promisedID, 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_SESSION_ERROR(self, FRAME_SIZE_ERROR);
 | |
|   }
 | |
| 
 | |
|   if (self->mInputFrameID) {
 | |
|     LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
 | |
|           self, self->mInputFrameID));
 | |
|     RETURN_SESSION_ERROR(self, 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_SESSION_ERROR(self, PROTOCOL_ERROR);
 | |
|   }
 | |
| 
 | |
|   if (self->mInputFrameID) {
 | |
|     LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
 | |
|           self, self->mInputFrameID));
 | |
|     RETURN_SESSION_ERROR(self, 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
 | |
|     nsAutoPtr<Http2Stream>& stream = iter.Data();
 | |
|     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_SESSION_ERROR(self, 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 "
 | |
|           "%d increased by %d now %d.\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_SESSION_ERROR(self, 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_SESSION_ERROR(self, 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);
 | |
| 
 | |
|         nsAutoPtr<Http2Stream>& stream = iter.Data();
 | |
|         if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         self->mReadyForWrite.Push(stream);
 | |
|         self->SetWriteCallbacks();
 | |
|       }
 | |
|     }
 | |
|     LOG3(("Http2Session::RecvWindowUpdate %p session window "
 | |
|           "%d increased by %d now %d.\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));
 | |
| 
 | |
|   self->SetInputFrameDataStream(self->mInputFrameID);
 | |
| 
 | |
|   if (!self->mInputFrameDataStream) {
 | |
|     LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
 | |
|           self->mInputFrameID));
 | |
|     RETURN_SESSION_ERROR(self, 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 nsRunnable
 | |
| {
 | |
| public:
 | |
| UpdateAltSvcEvent(const nsCString &header,
 | |
|                   const nsCString &aOrigin,
 | |
|                   nsHttpConnectionInfo *aCI,
 | |
|                   nsIInterfaceRequestor *callbacks)
 | |
|     : 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);
 | |
| 
 | |
|     AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
 | |
|                                  mCI->GetUsername(), mCI->GetPrivate(), mCallbacks,
 | |
|                                  mCI->ProxyInfo(), 0);
 | |
|     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->Transaction() ||
 | |
|         !self->mInputFrameDataStream->Transaction()->RequestHead()) {
 | |
|       LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
 | |
|       self->ResetDownstreamState();
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     origin.Assign(self->mInputFrameDataStream->Transaction()->RequestHead()->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 Stream 0 has empty origin\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);
 | |
|       if (ci->GetInsecureScheme()) {
 | |
|         // technically this is ok because it will still be confirmed before being used
 | |
|         // but let's not support it.
 | |
|         okToReroute = false;
 | |
|       }
 | |
|     } 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;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
 | |
| // of these methods
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| void
 | |
| Http2Session::OnTransportStatus(nsITransport* aTransport,
 | |
|                                 nsresult aStatus, int64_t aProgress)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   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:
 | |
|   {
 | |
|     Http2Stream *target = mStreamIDHash.Get(1);
 | |
|     nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
 | |
|     if (transaction)
 | |
|       transaction->OnTransportStatus(aTransport, aStatus, aProgress);
 | |
|     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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
 | |
|              "Inconsistent Write Function Callback");
 | |
| 
 | |
|   nsresult rv = ConfirmTLSProfile();
 | |
|   if (NS_FAILED(rv))
 | |
|     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));
 | |
|     FlushOutputQueue();
 | |
|     SetWriteCallbacks();
 | |
|     return NS_BASE_STREAM_WOULD_BLOCK;
 | |
|   }
 | |
| 
 | |
|   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);
 | |
| 
 | |
|   // 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
 | |
|   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 %X",
 | |
|           this, 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_SESSION_ERROR(this, 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);
 | |
|   }
 | |
| 
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   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 %x\n",
 | |
|             this, 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_SESSION_ERROR(this, 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 = 0;
 | |
|     }
 | |
| 
 | |
|     mPaddingLength = 0;
 | |
| 
 | |
|     LOG3(("Http2Session::WriteSegments[%p::%x] 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, PROTOCOL_ERROR);
 | |
|     }
 | |
| 
 | |
|     if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
 | |
|         mInputFrameType != FRAME_TYPE_SETTINGS) {
 | |
|       LOG3(("First Frame Type Must Be Settings\n"));
 | |
|       RETURN_SESSION_ERROR(this, 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 %x\n",
 | |
|             this, 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) {
 | |
|       // 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();
 | |
|     } 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 %x\n",
 | |
|             this, streamID, mNeedsCleanup, rv));
 | |
|       MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
 | |
|       CleanupStream(streamID, NS_OK, CANCEL_ERROR);
 | |
|       mNeedsCleanup = nullptr;
 | |
|       *again = false;
 | |
|       ResumeRecv();
 | |
|       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 %x\n", this, 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 count = std::min(4096U, mInputFrameDataSize - mInputFrameDataRead);
 | |
|     LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of data",
 | |
|           this, count));
 | |
| 
 | |
|     if (!count) {
 | |
|       ResetDownstreamState();
 | |
|       ResumeRecv();
 | |
|       return NS_BASE_STREAM_WOULD_BLOCK;
 | |
|     }
 | |
| 
 | |
|     rv = NetworkRead(writer, trash, count, countWritten);
 | |
| 
 | |
|     if (NS_FAILED(rv)) {
 | |
|       LOG3(("Http2Session %p discard frame read failure %x\n", this, 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;
 | |
|       }
 | |
| 
 | |
|       ResetDownstreamState();
 | |
| 
 | |
|       if (streamToCleanup) {
 | |
|         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 %x\n",
 | |
|           this, 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::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;
 | |
|     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 %X %d\n",
 | |
|         this, slowConsumer->StreamID(), 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=%llu localWindow=%lld\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=%lld\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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   if (mClosed)
 | |
|     return;
 | |
| 
 | |
|   LOG3(("Http2Session::Close %p %X", this, aReason));
 | |
| 
 | |
|   mClosed = true;
 | |
| 
 | |
|   Shutdown();
 | |
| 
 | |
|   mStreamIDHash.Clear();
 | |
|   mStreamTransactionHash.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_ILLEGAL_VALUE) {
 | |
|     goAwayReason = PROTOCOL_ERROR;
 | |
|   } else {
 | |
|     goAwayReason = INTERNAL_ERROR;
 | |
|   }
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   LOG3(("Http2Session::CloseTransaction %p %p %x", this, aTransaction, 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 %x - not found.",
 | |
|           this, aTransaction, aResult));
 | |
|     return;
 | |
|   }
 | |
|   LOG3(("Http2Session::CloseTransaction probably a cancel. "
 | |
|         "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
 | |
|         this, aTransaction, aResult, stream->StreamID(), stream));
 | |
|   CleanupStream(stream, aResult, CANCEL_ERROR);
 | |
|   ResumeRecv();
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsAHttpSegmentReader
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| nsresult
 | |
| Http2Session::OnReadSegment(const char *buf,
 | |
|                             uint32_t count, uint32_t *countRead)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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)
 | |
|     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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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");
 | |
|   mNeedsCleanup = mInputFrameDataStream;
 | |
|   ResetDownstreamState();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::ConnectPushedStream(Http2Stream *stream)
 | |
| {
 | |
|   mPushesReadyForRead.Push(stream);
 | |
|   ForceRecv();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::ConnectSlowConsumer(Http2Stream *stream)
 | |
| {
 | |
|   LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n",
 | |
|         this, stream->StreamID()));
 | |
|   mSlowConsumersReadyForRead.Push(stream);
 | |
|   ForceRecv();
 | |
| }
 | |
| 
 | |
| uint32_t
 | |
| Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   uint32_t rv = 0;
 | |
|   mTunnelHash.Get(aConnInfo->HashKey(), &rv);
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::RegisterTunnel(Http2Stream *aTunnel)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
 | |
|   uint32_t newcount = FindTunnelCount(ci) + 1;
 | |
|   mTunnelHash.Remove(ci->HashKey());
 | |
|   mTunnelHash.Put(ci->HashKey(), newcount);
 | |
|   LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]",
 | |
|         this, aTunnel, newcount, ci->HashKey().get()));
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::UnRegisterTunnel(Http2Stream *aTunnel)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
 | |
|   MOZ_ASSERT(FindTunnelCount(ci));
 | |
|   uint32_t newcount = FindTunnelCount(ci) - 1;
 | |
|   mTunnelHash.Remove(ci->HashKey());
 | |
|   if (newcount) {
 | |
|     mTunnelHash.Put(ci->HashKey(), newcount);
 | |
|   }
 | |
|   LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
 | |
|         this, aTunnel, newcount, ci->HashKey().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);
 | |
|   AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false, nullptr);
 | |
|   Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
 | |
|   MOZ_ASSERT(tunnel);
 | |
|   RegisterTunnel(tunnel);
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
 | |
|                                nsIInterfaceRequestor *aCallbacks)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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));
 | |
|     gHttpHandler->InitiateTransaction(trans, trans->Priority());
 | |
|   }
 | |
| }
 | |
| 
 | |
| // From ASpdySession
 | |
| bool
 | |
| Http2Session::MaybeReTunnel(nsAHttpTransaction *aHttpTransaction)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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);
 | |
|     gHttpHandler->InitiateTransaction(trans, trans->Priority());
 | |
|     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)
 | |
| {
 | |
|   if (!gHttpHandler->IsH2MandatorySuiteEnabled()) {
 | |
|     LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   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) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Http2Session::ConfirmTLSProfile()
 | |
| {
 | |
|   if (mTLSProfileConfirmed)
 | |
|     return NS_OK;
 | |
| 
 | |
|   LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
 | |
|         this, mConnection.get()));
 | |
| 
 | |
|   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_SESSION_ERROR(this, 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_SESSION_ERROR(this, 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_SESSION_ERROR(this, INADEQUATE_SECURITY);
 | |
|   } else if (kea == ssl_kea_ecdh && keybits < 256) { // 256 bits is "security level" of 128
 | |
|     LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 256\n",
 | |
|           this, keybits));
 | |
|     RETURN_SESSION_ERROR(this, 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_SESSION_ERROR(this, 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(PR_GetCurrentThread() == gSocketThread);
 | |
|   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()));
 | |
| 
 | |
|   mReadyForWrite.Push(stream);
 | |
|   SetWriteCallbacks();
 | |
| 
 | |
|   // 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.
 | |
|   ForceSend();
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller)
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
|   LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
 | |
|         this, stream, stream->StreamID()));
 | |
| 
 | |
|   mReadyForWrite.Push(stream);
 | |
|   SetWriteCallbacks();
 | |
|   ForceSend();
 | |
| }
 | |
| 
 | |
| bool
 | |
| Http2Session::IsPersistent()
 | |
| {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Http2Session::TakeTransport(nsISocketTransport **,
 | |
|                             nsIAsyncInputStream **, nsIAsyncOutputStream **)
 | |
| {
 | |
|   MOZ_ASSERT(false, "TakeTransport of Http2Session");
 | |
|   return NS_ERROR_UNEXPECTED;
 | |
| }
 | |
| 
 | |
| nsHttpConnection *
 | |
| Http2Session::TakeHttpConnection()
 | |
| {
 | |
|   MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| uint32_t
 | |
| Http2Session::CancelPipeline(nsresult reason)
 | |
| {
 | |
|   // we don't pipeline inside http/2, so this isn't an issue
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| nsAHttpTransaction::Classifier
 | |
| Http2Session::Classification()
 | |
| {
 | |
|   if (!mConnection)
 | |
|     return nsAHttpTransaction::CLASS_GENERAL;
 | |
|   return mConnection->Classification();
 | |
| }
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| void
 | |
| Http2Session::SetDNSWasRefreshed()
 | |
| {
 | |
|   MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
 | |
| }
 | |
| 
 | |
| uint64_t
 | |
| Http2Session::Available()
 | |
| {
 | |
|   MOZ_ASSERT(false, "Http2Session::Available()");
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| nsHttpRequestHead *
 | |
| Http2Session::RequestHead()
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   MOZ_ASSERT(false,
 | |
|              "Http2Session::RequestHead() "
 | |
|              "should not be called after http/2 is setup");
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Http2Session::AddTransaction(nsAHttpTransaction *)
 | |
| {
 | |
|   // This API is meant for pipelining, Http2Session's should be
 | |
|   // extended with AddStream()
 | |
| 
 | |
|   MOZ_ASSERT(false,
 | |
|              "Http2Session::AddTransaction() should not be called");
 | |
| 
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| uint32_t
 | |
| Http2Session::PipelineDepth()
 | |
| {
 | |
|   return IsDone() ? 0 : 1;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| Http2Session::SetPipelinePosition(int32_t position)
 | |
| {
 | |
|   // This API is meant for pipelining, Http2Session's should be
 | |
|   // extended with AddStream()
 | |
| 
 | |
|   MOZ_ASSERT(false,
 | |
|              "Http2Session::SetPipelinePosition() should not be called");
 | |
| 
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| int32_t
 | |
| Http2Session::PipelinePosition()
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // Pass through methods of nsAHttpConnection
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| nsAHttpConnection *
 | |
| Http2Session::Connection()
 | |
| {
 | |
|   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 | |
|   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(PR_GetCurrentThread() == gSocketThread);
 | |
| 
 | |
|   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);
 | |
|   ResumeRecv();
 | |
| }
 | |
| 
 | |
| } // namespace net
 | |
| } // namespace mozilla
 | 
