michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: // Log on level :5, instead of default :4. michael@0: #undef LOG michael@0: #define LOG(args) LOG5(args) michael@0: #undef LOG_ENABLED michael@0: #define LOG_ENABLED() LOG5_ENABLED() michael@0: michael@0: #include michael@0: michael@0: #include "Http2Session.h" michael@0: #include "Http2Stream.h" michael@0: #include "Http2Push.h" michael@0: michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsHttp.h" michael@0: #include "nsHttpHandler.h" michael@0: #include "nsHttpConnection.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsISSLSocketControl.h" michael@0: #include "nsISSLStatus.h" michael@0: #include "nsISSLStatusProvider.h" michael@0: #include "prprf.h" michael@0: #include "prnetdb.h" michael@0: #include "sslt.h" michael@0: michael@0: #ifdef DEBUG michael@0: // defined by the socket transport service while active michael@0: extern PRThread *gSocketThread; michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: // Http2Session has multiple inheritance of things that implement michael@0: // nsISupports, so this magic is taken from nsHttpPipeline that michael@0: // implements some of the same abstract classes. michael@0: NS_IMPL_ADDREF(Http2Session) michael@0: NS_IMPL_RELEASE(Http2Session) michael@0: NS_INTERFACE_MAP_BEGIN(Http2Session) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: // "magic" refers to the string that preceeds HTTP/2 on the wire michael@0: // to help find any intermediaries speaking an older version of HTTP michael@0: const uint8_t Http2Session::kMagicHello[] = { michael@0: 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, michael@0: 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, michael@0: 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a michael@0: }; michael@0: michael@0: #define RETURN_SESSION_ERROR(o,x) \ michael@0: do { \ michael@0: (o)->mGoAwayReason = (x); \ michael@0: return NS_ERROR_ILLEGAL_VALUE; \ michael@0: } while (0) michael@0: michael@0: Http2Session::Http2Session(nsAHttpTransaction *aHttpTransaction, michael@0: nsISocketTransport *aSocketTransport, michael@0: int32_t firstPriority) michael@0: : mSocketTransport(aSocketTransport), michael@0: mSegmentReader(nullptr), michael@0: mSegmentWriter(nullptr), michael@0: mNextStreamID(3), // 1 is reserved for Updgrade handshakes michael@0: mConcurrentHighWater(0), michael@0: mDownstreamState(BUFFERING_OPENING_SETTINGS), michael@0: mInputFrameBufferSize(kDefaultBufferSize), michael@0: mInputFrameBufferUsed(0), michael@0: mInputFrameFinal(false), michael@0: mInputFrameDataStream(nullptr), michael@0: mNeedsCleanup(nullptr), michael@0: mDownstreamRstReason(NO_HTTP_ERROR), michael@0: mExpectedHeaderID(0), michael@0: mExpectedPushPromiseID(0), michael@0: mContinuedPromiseStream(0), michael@0: mShouldGoAway(false), michael@0: mClosed(false), michael@0: mCleanShutdown(false), michael@0: mTLSProfileConfirmed(false), michael@0: mGoAwayReason(NO_HTTP_ERROR), michael@0: mGoAwayID(0), michael@0: mOutgoingGoAwayID(0), michael@0: mMaxConcurrent(kDefaultMaxConcurrent), michael@0: mConcurrent(0), michael@0: mServerPushedResources(0), michael@0: mServerInitialStreamWindow(kDefaultRwin), michael@0: mLocalSessionWindow(kDefaultRwin), michael@0: mServerSessionWindow(kDefaultRwin), michael@0: mOutputQueueSize(kDefaultQueueSize), michael@0: mOutputQueueUsed(0), michael@0: mOutputQueueSent(0), michael@0: mLastReadEpoch(PR_IntervalNow()), michael@0: mPingSentEpoch(0) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: static uint64_t sSerial; michael@0: mSerial = ++sSerial; michael@0: michael@0: LOG3(("Http2Session::Http2Session %p transaction 1 = %p serial=0x%X\n", michael@0: this, aHttpTransaction, mSerial)); michael@0: michael@0: mConnection = aHttpTransaction->Connection(); michael@0: mInputFrameBuffer = new char[mInputFrameBufferSize]; michael@0: mOutputQueueBuffer = new char[mOutputQueueSize]; michael@0: mDecompressBuffer.SetCapacity(kDefaultBufferSize); michael@0: mDecompressor.SetCompressor(&mCompressor); michael@0: michael@0: mPushAllowance = gHttpHandler->SpdyPushAllowance(); michael@0: michael@0: mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); michael@0: SendHello(); michael@0: michael@0: if (!aHttpTransaction->IsNullTransaction()) michael@0: AddStream(aHttpTransaction, firstPriority); michael@0: mLastDataReadEpoch = mLastReadEpoch; michael@0: michael@0: mPingThreshold = gHttpHandler->SpdyPingThreshold(); michael@0: } michael@0: michael@0: PLDHashOperator michael@0: Http2Session::ShutdownEnumerator(nsAHttpTransaction *key, michael@0: nsAutoPtr &stream, michael@0: void *closure) michael@0: { michael@0: Http2Session *self = static_cast(closure); michael@0: michael@0: // On a clean server hangup the server sets the GoAwayID to be the ID of michael@0: // the last transaction it processed. If the ID of stream in the michael@0: // local stream is greater than that it can safely be restarted because the michael@0: // server guarantees it was not partially processed. Streams that have not michael@0: // registered an ID haven't actually been sent yet so they can always be michael@0: // restarted. michael@0: if (self->mCleanShutdown && michael@0: (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID())) { michael@0: self->CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted michael@0: } else { michael@0: self->CloseStream(stream, NS_ERROR_ABORT); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: Http2Session::GoAwayEnumerator(nsAHttpTransaction *key, michael@0: nsAutoPtr &stream, michael@0: void *closure) michael@0: { michael@0: Http2Session *self = static_cast(closure); michael@0: michael@0: // these streams were not processed by the server and can be restarted. michael@0: // Do that after the enumerator completes to avoid the risk of michael@0: // a restart event re-entrantly modifying this hash. Be sure not to restart michael@0: // a pushed (even numbered) stream michael@0: if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) || michael@0: !stream->HasRegisteredID()) { michael@0: self->mGoAwayStreamsToRestart.Push(stream); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: Http2Session::~Http2Session() michael@0: { michael@0: LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", michael@0: this, mDownstreamState)); michael@0: michael@0: mStreamTransactionHash.Enumerate(ShutdownEnumerator, this); michael@0: Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater); michael@0: Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2); michael@0: Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS, michael@0: mServerPushedResources); michael@0: } michael@0: michael@0: void michael@0: Http2Session::LogIO(Http2Session *self, Http2Stream *stream, michael@0: const char *label, michael@0: const char *data, uint32_t datalen) michael@0: { michael@0: if (!LOG4_ENABLED()) michael@0: return; michael@0: michael@0: LOG4(("Http2Session::LogIO %p stream=%p id=0x%X [%s]", michael@0: self, stream, stream ? stream->StreamID() : 0, label)); michael@0: michael@0: // Max line is (16 * 3) + 10(prefix) + newline + null michael@0: char linebuf[128]; michael@0: uint32_t index; michael@0: char *line = linebuf; michael@0: michael@0: linebuf[127] = 0; michael@0: michael@0: for (index = 0; index < datalen; ++index) { michael@0: if (!(index % 16)) { michael@0: if (index) { michael@0: *line = 0; michael@0: LOG4(("%s", linebuf)); michael@0: } michael@0: line = linebuf; michael@0: PR_snprintf(line, 128, "%08X: ", index); michael@0: line += 10; michael@0: } michael@0: PR_snprintf(line, 128 - (line - linebuf), "%02X ", michael@0: (reinterpret_cast(data))[index]); michael@0: line += 3; michael@0: } michael@0: if (index) { michael@0: *line = 0; michael@0: LOG4(("%s", linebuf)); michael@0: } michael@0: } michael@0: michael@0: typedef nsresult (*Http2ControlFx) (Http2Session *self); michael@0: static Http2ControlFx sControlFunctions[] = { michael@0: nullptr, // type 0 data is not a control function michael@0: Http2Session::RecvHeaders, michael@0: Http2Session::RecvPriority, michael@0: Http2Session::RecvRstStream, michael@0: Http2Session::RecvSettings, michael@0: Http2Session::RecvPushPromise, michael@0: Http2Session::RecvPing, michael@0: Http2Session::RecvGoAway, michael@0: Http2Session::RecvWindowUpdate, michael@0: Http2Session::RecvContinuation michael@0: }; michael@0: michael@0: bool michael@0: Http2Session::RoomForMoreConcurrent() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: return (mConcurrent < mMaxConcurrent); michael@0: } michael@0: michael@0: bool michael@0: Http2Session::RoomForMoreStreams() michael@0: { michael@0: if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) michael@0: return false; michael@0: michael@0: return !mShouldGoAway; michael@0: } michael@0: michael@0: PRIntervalTime michael@0: Http2Session::IdleTime() michael@0: { michael@0: return PR_IntervalNow() - mLastDataReadEpoch; michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::ReadTimeoutTick(PRIntervalTime now) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", michael@0: this, PR_IntervalToSeconds(now - mLastReadEpoch))); michael@0: michael@0: if (!mPingThreshold) michael@0: return UINT32_MAX; michael@0: michael@0: if ((now - mLastReadEpoch) < mPingThreshold) { michael@0: // recent activity means ping is not an issue michael@0: if (mPingSentEpoch) michael@0: mPingSentEpoch = 0; michael@0: michael@0: return PR_IntervalToSeconds(mPingThreshold) - michael@0: PR_IntervalToSeconds(now - mLastReadEpoch); michael@0: } michael@0: michael@0: if (mPingSentEpoch) { michael@0: LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n")); michael@0: if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) { michael@0: LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this)); michael@0: mPingSentEpoch = 0; michael@0: Close(NS_ERROR_NET_TIMEOUT); michael@0: return UINT32_MAX; michael@0: } michael@0: return 1; // run the tick aggressively while ping is outstanding michael@0: } michael@0: michael@0: LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this)); michael@0: michael@0: mPingSentEpoch = PR_IntervalNow(); michael@0: if (!mPingSentEpoch) michael@0: mPingSentEpoch = 1; // avoid the 0 sentinel value michael@0: GeneratePing(false); michael@0: ResumeRecv(); // read the ping reply michael@0: michael@0: // Check for orphaned push streams. This looks expensive, but generally the michael@0: // list is empty. michael@0: Http2PushedStream *deleteMe; michael@0: TimeStamp timestampNow; michael@0: do { michael@0: deleteMe = nullptr; michael@0: michael@0: for (uint32_t index = mPushedStreams.Length(); michael@0: index > 0 ; --index) { michael@0: Http2PushedStream *pushedStream = mPushedStreams[index - 1]; michael@0: michael@0: if (timestampNow.IsNull()) michael@0: timestampNow = TimeStamp::Now(); // lazy initializer michael@0: michael@0: // if stream finished, but is not connected, and its been like that for michael@0: // long then cleanup the stream. michael@0: if (pushedStream->IsOrphaned(timestampNow)) michael@0: { michael@0: LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n", michael@0: this, pushedStream->StreamID())); michael@0: deleteMe = pushedStream; michael@0: break; // don't CleanupStream() while iterating this vector michael@0: } michael@0: } michael@0: if (deleteMe) michael@0: CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR); michael@0: michael@0: } while (deleteMe); michael@0: michael@0: return 1; // run the tick aggressively while ping is outstanding michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(mNextStreamID < 0xfffffff0, michael@0: "should have stopped admitting streams"); michael@0: MOZ_ASSERT(!(aNewID & 1), michael@0: "0 for autoassign pull, otherwise explicit even push assignment"); michael@0: michael@0: if (!aNewID) { michael@0: // auto generate a new pull stream ID michael@0: aNewID = mNextStreamID; michael@0: MOZ_ASSERT(aNewID & 1, "pull ID must be odd."); michael@0: mNextStreamID += 2; michael@0: } michael@0: michael@0: LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X " michael@0: "concurrent=%d",this, stream, aNewID, mConcurrent)); michael@0: michael@0: // We've used up plenty of ID's on this session. Start michael@0: // moving to a new one before there is a crunch involving michael@0: // server push streams or concurrent non-registered submits michael@0: if (aNewID >= kMaxStreamID) michael@0: mShouldGoAway = true; michael@0: michael@0: // integrity check michael@0: if (mStreamIDHash.Get(aNewID)) { michael@0: LOG3((" New ID already present\n")); michael@0: MOZ_ASSERT(false, "New ID already present in mStreamIDHash"); michael@0: mShouldGoAway = true; michael@0: return kDeadStreamID; michael@0: } michael@0: michael@0: mStreamIDHash.Put(aNewID, stream); michael@0: return aNewID; michael@0: } michael@0: michael@0: bool michael@0: Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction, michael@0: int32_t aPriority) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: // integrity check michael@0: if (mStreamTransactionHash.Get(aHttpTransaction)) { michael@0: LOG3((" New transaction already present\n")); michael@0: MOZ_ASSERT(false, "AddStream duplicate transaction pointer"); michael@0: return false; michael@0: } michael@0: michael@0: aHttpTransaction->SetConnection(this); michael@0: Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority); michael@0: michael@0: LOG3(("Http2Session::AddStream session=%p stream=%p NextID=0x%X (tentative)", michael@0: this, stream, mNextStreamID)); michael@0: michael@0: mStreamTransactionHash.Put(aHttpTransaction, stream); michael@0: michael@0: if (RoomForMoreConcurrent()) { michael@0: LOG3(("Http2Session::AddStream %p stream %p activated immediately.", michael@0: this, stream)); michael@0: ActivateStream(stream); michael@0: } else { michael@0: LOG3(("Http2Session::AddStream %p stream %p queued.", this, stream)); michael@0: mQueuedStreams.Push(stream); michael@0: } michael@0: michael@0: if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) { michael@0: LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n", michael@0: this, aHttpTransaction)); michael@0: DontReuse(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Http2Session::ActivateStream(Http2Stream *stream) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1), michael@0: "Do not activate pushed streams"); michael@0: michael@0: MOZ_ASSERT(!stream->CountAsActive()); michael@0: stream->SetCountAsActive(true); michael@0: ++mConcurrent; michael@0: michael@0: if (mConcurrent > mConcurrentHighWater) michael@0: mConcurrentHighWater = mConcurrent; michael@0: LOG3(("Http2Session::AddStream %p activating stream %p Currently %d " michael@0: "streams in session, high water mark is %d", michael@0: this, stream, mConcurrent, mConcurrentHighWater)); michael@0: michael@0: mReadyForWrite.Push(stream); michael@0: SetWriteCallbacks(); michael@0: michael@0: // Kick off the headers transmit without waiting for the poll loop michael@0: // This won't work for stream id=1 because there is no segment reader michael@0: // yet. michael@0: if (mSegmentReader) { michael@0: uint32_t countRead; michael@0: ReadSegments(nullptr, kDefaultBufferSize, &countRead); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Http2Session::ProcessPending() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: while (RoomForMoreConcurrent()) { michael@0: Http2Stream *stream = static_cast(mQueuedStreams.PopFront()); michael@0: if (!stream) michael@0: return; michael@0: LOG3(("Http2Session::ProcessPending %p stream %p activated from queue.", michael@0: this, stream)); michael@0: ActivateStream(stream); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf, michael@0: uint32_t count, uint32_t *countWritten) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (!count) { michael@0: *countWritten = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = writer->OnWriteSegment(buf, count, countWritten); michael@0: if (NS_SUCCEEDED(rv) && *countWritten > 0) michael@0: mLastReadEpoch = PR_IntervalNow(); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: Http2Session::SetWriteCallbacks() michael@0: { michael@0: if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed)) michael@0: mConnection->ResumeSend(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::RealignOutputQueue() michael@0: { michael@0: mOutputQueueUsed -= mOutputQueueSent; michael@0: memmove(mOutputQueueBuffer.get(), michael@0: mOutputQueueBuffer.get() + mOutputQueueSent, michael@0: mOutputQueueUsed); michael@0: mOutputQueueSent = 0; michael@0: } michael@0: michael@0: void michael@0: Http2Session::FlushOutputQueue() michael@0: { michael@0: if (!mSegmentReader || !mOutputQueueUsed) michael@0: return; michael@0: michael@0: nsresult rv; michael@0: uint32_t countRead; michael@0: uint32_t avail = mOutputQueueUsed - mOutputQueueSent; michael@0: michael@0: rv = mSegmentReader-> michael@0: OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, michael@0: &countRead); michael@0: LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d", michael@0: this, avail, rv, countRead)); michael@0: michael@0: // Dont worry about errors on write, we will pick this up as a read error too michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: if (countRead == avail) { michael@0: mOutputQueueUsed = 0; michael@0: mOutputQueueSent = 0; michael@0: return; michael@0: } michael@0: michael@0: mOutputQueueSent += countRead; michael@0: michael@0: // If the output queue is close to filling up and we have sent out a good michael@0: // chunk of data from the beginning then realign it. michael@0: michael@0: if ((mOutputQueueSent >= kQueueMinimumCleanup) && michael@0: ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) { michael@0: RealignOutputQueue(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Http2Session::DontReuse() michael@0: { michael@0: mShouldGoAway = true; michael@0: if (!mStreamTransactionHash.Count()) michael@0: Close(NS_OK); michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::GetWriteQueueSize() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: return mReadyForWrite.GetSize(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::ChangeDownstreamState(enum internalStateType newState) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: LOG3(("Http2Stream::ChangeDownstreamState() %p from %X to %X", michael@0: this, mDownstreamState, newState)); michael@0: mDownstreamState = newState; michael@0: } michael@0: michael@0: void michael@0: Http2Session::ResetDownstreamState() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: LOG3(("Http2Stream::ResetDownstreamState() %p", this)); michael@0: ChangeDownstreamState(BUFFERING_FRAME_HEADER); michael@0: michael@0: if (mInputFrameFinal && mInputFrameDataStream) { michael@0: mInputFrameFinal = false; michael@0: LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID())); michael@0: mInputFrameDataStream->SetRecvdFin(true); michael@0: MaybeDecrementConcurrent(mInputFrameDataStream); michael@0: } michael@0: mInputFrameBufferUsed = 0; michael@0: mInputFrameDataStream = nullptr; michael@0: } michael@0: michael@0: template void michael@0: Http2Session::EnsureBuffer(nsAutoArrayPtr &buf, uint32_t newSize, michael@0: uint32_t preserve, uint32_t &objSize) michael@0: { michael@0: if (objSize >= newSize) michael@0: return; michael@0: michael@0: // Leave a little slop on the new allocation - add 2KB to michael@0: // what we need and then round the result up to a 4KB (page) michael@0: // boundary. michael@0: michael@0: objSize = (newSize + 2048 + 4095) & ~4095; michael@0: michael@0: static_assert(sizeof(T) == 1, "sizeof(T) must be 1"); michael@0: nsAutoArrayPtr tmp(new T[objSize]); michael@0: memcpy(tmp, buf, preserve); michael@0: buf = tmp; michael@0: } michael@0: michael@0: // Instantiate supported templates explicitly. michael@0: template void michael@0: Http2Session::EnsureBuffer(nsAutoArrayPtr &buf, uint32_t newSize, michael@0: uint32_t preserve, uint32_t &objSize); michael@0: michael@0: template void michael@0: Http2Session::EnsureBuffer(nsAutoArrayPtr &buf, uint32_t newSize, michael@0: uint32_t preserve, uint32_t &objSize); michael@0: michael@0: // call with data length (i.e. 0 for 0 data bytes - ignore 8 byte header) michael@0: // dest must have 8 bytes of allocated space michael@0: template void michael@0: Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength, michael@0: uint8_t frameType, uint8_t frameFlags, michael@0: uint32_t streamID) michael@0: { michael@0: MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large"); michael@0: MOZ_ASSERT(!(streamID & 0x80000000)); michael@0: michael@0: frameLength = PR_htons(frameLength); michael@0: streamID = PR_htonl(streamID); michael@0: michael@0: memcpy(dest, &frameLength, 2); michael@0: dest[2] = frameType; michael@0: dest[3] = frameFlags; michael@0: memcpy(dest + 4, &streamID, 4); michael@0: } michael@0: michael@0: char * michael@0: Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded) michael@0: { michael@0: // this is an infallible allocation (if an allocation is michael@0: // needed, which is probably isn't) michael@0: EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded, michael@0: mOutputQueueUsed, mOutputQueueSize); michael@0: return mOutputQueueBuffer.get() + mOutputQueueUsed; michael@0: } michael@0: michael@0: template void michael@0: Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength, michael@0: uint8_t frameType, uint8_t frameFlags, michael@0: uint32_t streamID); michael@0: michael@0: template void michael@0: Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength, michael@0: uint8_t frameType, uint8_t frameFlags, michael@0: uint32_t streamID); michael@0: michael@0: void michael@0: Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream) michael@0: { michael@0: LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n", michael@0: this, aStream->StreamID(), mConcurrent, aStream->CountAsActive())); michael@0: michael@0: if (!aStream->CountAsActive()) michael@0: return; michael@0: michael@0: MOZ_ASSERT(mConcurrent); michael@0: aStream->SetCountAsActive(false); michael@0: --mConcurrent; michael@0: ProcessPending(); michael@0: } michael@0: michael@0: // Need to decompress some data in order to keep the compression michael@0: // context correct, but we really don't care what the result is michael@0: nsresult michael@0: Http2Session::UncompressAndDiscard() michael@0: { michael@0: nsresult rv; michael@0: nsAutoCString trash; michael@0: michael@0: rv = mDecompressor.DecodeHeaderBlock(reinterpret_cast(mDecompressBuffer.BeginReading()), michael@0: mDecompressBuffer.Length(), trash); michael@0: mDecompressBuffer.Truncate(); michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n", michael@0: this)); michael@0: mGoAwayReason = COMPRESSION_ERROR; michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Http2Session::GeneratePing(bool isAck) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck)); michael@0: michael@0: char *packet = EnsureOutputBuffer(16); michael@0: mOutputQueueUsed += 16; michael@0: michael@0: if (isAck) { michael@0: CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0); michael@0: memcpy(packet + 8, mInputFrameBuffer.get() + 8, 8); michael@0: } else { michael@0: CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0); michael@0: memset(packet + 8, 0, 8); michael@0: } michael@0: michael@0: LogIO(this, nullptr, "Generate Ping", packet, 16); michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::GenerateSettingsAck() michael@0: { michael@0: // need to generate ack of this settings frame michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::GenerateSettingsAck %p\n", this)); michael@0: michael@0: char *packet = EnsureOutputBuffer(8); michael@0: mOutputQueueUsed += 8; michael@0: CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0); michael@0: LogIO(this, nullptr, "Generate Settings ACK", packet, 8); michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::GeneratePriority(uint32_t aID, uint32_t aPriority) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::GeneratePriority %p %X %X\n", michael@0: this, aID, aPriority)); michael@0: michael@0: char *packet = EnsureOutputBuffer(12); michael@0: mOutputQueueUsed += 12; michael@0: michael@0: CreateFrameHeader(packet, 4, FRAME_TYPE_PRIORITY, 0, aID); michael@0: aPriority = PR_htonl(aPriority); michael@0: memcpy(packet + 8, &aPriority, 4); michael@0: LogIO(this, nullptr, "Generate Priority", packet, 12); michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: // make sure we don't do this twice for the same stream (at least if we michael@0: // have a stream entry for it) michael@0: Http2Stream *stream = mStreamIDHash.Get(aID); michael@0: if (stream) { michael@0: if (stream->SentReset()) michael@0: return; michael@0: stream->SetSentReset(true); michael@0: } michael@0: michael@0: LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); michael@0: michael@0: char *packet = EnsureOutputBuffer(12); michael@0: mOutputQueueUsed += 12; michael@0: CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID); michael@0: michael@0: aStatusCode = PR_htonl(aStatusCode); michael@0: memcpy(packet + 8, &aStatusCode, 4); michael@0: michael@0: LogIO(this, nullptr, "Generate Reset", packet, 12); michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::GenerateGoAway(uint32_t aStatusCode) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode)); michael@0: michael@0: char *packet = EnsureOutputBuffer(16); michael@0: mOutputQueueUsed += 16; michael@0: michael@0: memset(packet + 8, 0, 8); michael@0: CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0); michael@0: michael@0: // last-good-stream-id are bytes 8-11 reflecting pushes michael@0: uint32_t goAway = PR_htonl(mOutgoingGoAwayID); michael@0: memcpy (packet + 7, &goAway, 4); michael@0: michael@0: // bytes 12-15 are the status code. michael@0: aStatusCode = PR_htonl(aStatusCode); michael@0: memcpy(packet + 12, &aStatusCode, 4); michael@0: michael@0: LogIO(this, nullptr, "Generate GoAway", packet, 16); michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: // The Hello is comprised of 24 octets of magic, which are designed to michael@0: // flush out silent but broken intermediaries, followed by a settings michael@0: // frame which sets a small flow control window for pushes and a michael@0: // window update frame which creates a large session flow control window michael@0: void michael@0: Http2Session::SendHello() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::SendHello %p\n", this)); michael@0: michael@0: // sized for magic + 2 settings and a session window update to follow michael@0: // 24 magic, 23 for settings (8 header + 3 settings @5), 12 for window update michael@0: static const uint32_t maxSettings = 3; michael@0: static const uint32_t maxDataLen = 24 + 8 + maxSettings * 5 + 12; michael@0: char *packet = EnsureOutputBuffer(maxDataLen); michael@0: memcpy(packet, kMagicHello, 24); michael@0: mOutputQueueUsed += 24; michael@0: LogIO(this, nullptr, "Magic Connection Header", packet, 24); michael@0: michael@0: packet = mOutputQueueBuffer.get() + mOutputQueueUsed; michael@0: memset(packet, 0, maxDataLen - 24); michael@0: michael@0: // frame header will be filled in after we know how long the frame is michael@0: uint8_t numberOfEntries = 0; michael@0: michael@0: // entries need to be listed in order by ID michael@0: // 1st entry is bytes 8 to 12 michael@0: // 2nd entry is bytes 13 to 17 michael@0: // 3rd entry is bytes 18 to 22 michael@0: michael@0: if (!gHttpHandler->AllowPush()) { michael@0: // If we don't support push then set MAX_CONCURRENT to 0 and also michael@0: // set ENABLE_PUSH to 0 michael@0: packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_ENABLE_PUSH; michael@0: // The value portion of the setting pair is already initialized to 0 michael@0: numberOfEntries++; michael@0: michael@0: packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT; michael@0: // The value portion of the setting pair is already initialized to 0 michael@0: numberOfEntries++; michael@0: } michael@0: michael@0: // Advertise the Push RWIN for the session, and on each new pull stream michael@0: // send a window update with END_FLOW_CONTROL michael@0: packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW; michael@0: uint32_t rwin = PR_htonl(mPushAllowance); michael@0: memcpy(packet + 9 + 5 * numberOfEntries, &rwin, 4); michael@0: numberOfEntries++; michael@0: michael@0: MOZ_ASSERT(numberOfEntries <= maxSettings); michael@0: uint32_t dataLen = 5 * numberOfEntries; michael@0: CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0); michael@0: mOutputQueueUsed += 8 + dataLen; michael@0: michael@0: LogIO(this, nullptr, "Generate Settings", packet, 8 + dataLen); michael@0: michael@0: // now bump the local session window from 64KB michael@0: uint32_t sessionWindowBump = ASpdySession::kInitialRwin - kDefaultRwin; michael@0: if (kDefaultRwin >= ASpdySession::kInitialRwin) michael@0: goto sendHello_complete; michael@0: michael@0: // send a window update for the session (Stream 0) for something large michael@0: sessionWindowBump = PR_htonl(sessionWindowBump); michael@0: mLocalSessionWindow = ASpdySession::kInitialRwin; michael@0: michael@0: packet = mOutputQueueBuffer.get() + mOutputQueueUsed; michael@0: CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0); michael@0: mOutputQueueUsed += 12; michael@0: memcpy(packet + 8, &sessionWindowBump, 4); michael@0: michael@0: LOG3(("Session Window increase at start of session %p %u\n", michael@0: this, PR_ntohl(sessionWindowBump))); michael@0: LogIO(this, nullptr, "Session Window Bump ", packet, 12); michael@0: michael@0: sendHello_complete: michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: // perform a bunch of integrity checks on the stream. michael@0: // returns true if passed, false (plus LOG and ABORT) if failed. michael@0: bool michael@0: Http2Session::VerifyStream(Http2Stream *aStream, uint32_t aOptionalID = 0) michael@0: { michael@0: // This is annoying, but at least it is O(1) michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: #ifndef DEBUG michael@0: // Only do the real verification in debug builds michael@0: return true; michael@0: #endif michael@0: michael@0: if (!aStream) michael@0: return true; michael@0: michael@0: uint32_t test = 0; michael@0: michael@0: do { michael@0: if (aStream->StreamID() == kDeadStreamID) michael@0: break; michael@0: michael@0: nsAHttpTransaction *trans = aStream->Transaction(); michael@0: michael@0: test++; michael@0: if (!trans) michael@0: break; michael@0: michael@0: test++; michael@0: if (mStreamTransactionHash.Get(trans) != aStream) michael@0: break; michael@0: michael@0: if (aStream->StreamID()) { michael@0: Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID()); michael@0: michael@0: test++; michael@0: if (idStream != aStream) michael@0: break; michael@0: michael@0: if (aOptionalID) { michael@0: test++; michael@0: if (idStream->StreamID() != aOptionalID) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // tests passed michael@0: return true; michael@0: } while (0); michael@0: michael@0: LOG3(("Http2Session %p VerifyStream Failure %p stream->id=0x%X " michael@0: "optionalID=0x%X trans=%p test=%d\n", michael@0: this, aStream, aStream->StreamID(), michael@0: aOptionalID, aStream->Transaction(), test)); michael@0: michael@0: MOZ_ASSERT(false, "VerifyStream"); michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult, michael@0: errorType aResetCode) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::CleanupStream %p %p 0x%X %X\n", michael@0: this, aStream, aStream ? aStream->StreamID() : 0, aResult)); michael@0: if (!aStream) { michael@0: return; michael@0: } michael@0: michael@0: Http2PushedStream *pushSource = nullptr; michael@0: michael@0: if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) { michael@0: LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID())); michael@0: return; michael@0: } michael@0: michael@0: if (!VerifyStream(aStream)) { michael@0: LOG3(("Http2Session::CleanupStream failed to verify stream\n")); michael@0: return; michael@0: } michael@0: michael@0: pushSource = aStream->PushSource(); michael@0: michael@0: if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID()) { michael@0: LOG3(("Stream had not processed recv FIN, sending RST code %X\n", michael@0: aResetCode)); michael@0: GenerateRstStream(aResetCode, aStream->StreamID()); michael@0: } michael@0: michael@0: CloseStream(aStream, aResult); michael@0: michael@0: // Remove the stream from the ID hash table and, if an even id, the pushed michael@0: // table too. michael@0: uint32_t id = aStream->StreamID(); michael@0: if (id > 0) { michael@0: mStreamIDHash.Remove(id); michael@0: if (!(id & 1)) michael@0: mPushedStreams.RemoveElement(aStream); michael@0: } michael@0: michael@0: RemoveStreamFromQueues(aStream); michael@0: michael@0: // removing from the stream transaction hash will michael@0: // delete the Http2Stream and drop the reference to michael@0: // its transaction michael@0: mStreamTransactionHash.Remove(aStream->Transaction()); michael@0: michael@0: if (mShouldGoAway && !mStreamTransactionHash.Count()) michael@0: Close(NS_OK); michael@0: michael@0: if (pushSource) { michael@0: pushSource->SetDeferCleanupOnSuccess(false); michael@0: CleanupStream(pushSource, aResult, aResetCode); michael@0: } michael@0: } michael@0: michael@0: static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue) michael@0: { michael@0: uint32_t size = queue.GetSize(); michael@0: for (uint32_t count = 0; count < size; ++count) { michael@0: Http2Stream *stream = static_cast(queue.PopFront()); michael@0: if (stream != aStream) michael@0: queue.Push(stream); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Http2Session::RemoveStreamFromQueues(Http2Stream *aStream) michael@0: { michael@0: RemoveStreamFromQueue(aStream, mReadyForWrite); michael@0: RemoveStreamFromQueue(aStream, mQueuedStreams); michael@0: RemoveStreamFromQueue(aStream, mReadyForRead); michael@0: } michael@0: michael@0: void michael@0: Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n", michael@0: this, aStream, aStream->StreamID(), aResult)); michael@0: michael@0: MaybeDecrementConcurrent(aStream); michael@0: michael@0: // Check if partial frame reader michael@0: if (aStream == mInputFrameDataStream) { michael@0: LOG3(("Stream had active partial read frame on close")); michael@0: ChangeDownstreamState(DISCARDING_DATA_FRAME); michael@0: mInputFrameDataStream = nullptr; michael@0: } michael@0: michael@0: RemoveStreamFromQueues(aStream); michael@0: michael@0: // Send the stream the close() indication michael@0: aStream->Close(aResult); michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::SetInputFrameDataStream(uint32_t streamID) michael@0: { michael@0: mInputFrameDataStream = mStreamIDHash.Get(streamID); michael@0: if (VerifyStream(mInputFrameDataStream, streamID)) michael@0: return NS_OK; michael@0: michael@0: LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n", michael@0: streamID)); michael@0: mInputFrameDataStream = nullptr; michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::ParsePadding(uint8_t &paddingControlBytes, uint16_t &paddingLength) michael@0: { michael@0: if (mInputFrameFlags & kFlag_PAD_HIGH) { michael@0: uint8_t paddingHighValue = *reinterpret_cast(mInputFrameBuffer + 8); michael@0: paddingLength = static_cast(paddingHighValue) * 256; michael@0: ++paddingControlBytes; michael@0: } michael@0: michael@0: if (mInputFrameFlags & kFlag_PAD_LOW) { michael@0: uint8_t paddingLowValue = *reinterpret_cast(mInputFrameBuffer + 8 + paddingControlBytes); michael@0: paddingLength += paddingLowValue; michael@0: ++paddingControlBytes; michael@0: } michael@0: michael@0: if (paddingLength > mInputFrameDataSize) { michael@0: // This is fatal to the session michael@0: LOG3(("Http2Session::RecvHeaders %p stream 0x%x PROTOCOL_ERROR " michael@0: "paddingLength %d > frame size %d\n", michael@0: this, mInputFrameID, paddingLength, mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvHeaders(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS); michael@0: michael@0: // If this doesn't have END_HEADERS set on it then require the next michael@0: // frame to be HEADERS of the same ID michael@0: bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS; michael@0: michael@0: if (endHeadersFlag) michael@0: self->mExpectedHeaderID = 0; michael@0: else michael@0: self->mExpectedHeaderID = self->mInputFrameID; michael@0: michael@0: uint32_t priorityLen = (self->mInputFrameFlags & kFlag_PRIORITY) ? 4 : 0; michael@0: self->SetInputFrameDataStream(self->mInputFrameID); michael@0: michael@0: // Find out how much padding this frame has, so we can only extract the real michael@0: // header data from the frame. michael@0: uint16_t paddingLength = 0; michael@0: uint8_t paddingControlBytes = 0; michael@0: michael@0: nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: LOG3(("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p " michael@0: "end_stream=%d end_headers=%d priority_flag=%d paddingLength=%d " michael@0: "pad_high_flag=%d pad_low_flag=%d\n", michael@0: self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream, michael@0: self->mInputFrameFlags & kFlag_END_STREAM, michael@0: self->mInputFrameFlags & kFlag_END_HEADERS, michael@0: self->mInputFrameFlags & kFlag_PRIORITY, michael@0: paddingLength, michael@0: self->mInputFrameFlags & kFlag_PAD_HIGH, michael@0: self->mInputFrameFlags & kFlag_PAD_LOW)); michael@0: michael@0: if (!self->mInputFrameDataStream) { michael@0: // Cannot find stream. We can continue the session, but we need to michael@0: // uncompress the header block to maintain the correct compression context michael@0: michael@0: LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream " michael@0: "0x%X failed. NextStreamID = 0x%X\n", michael@0: self, self->mInputFrameID, self->mNextStreamID)); michael@0: michael@0: if (self->mInputFrameID >= self->mNextStreamID) michael@0: self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID); michael@0: michael@0: self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen, michael@0: self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength); michael@0: michael@0: if (self->mInputFrameFlags & kFlag_END_HEADERS) { michael@0: rv = self->UncompressAndDiscard(); michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session::RecvHeaders uncompress failed\n")); michael@0: // this is fatal to the session michael@0: self->mGoAwayReason = COMPRESSION_ERROR; michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // queue up any compression bytes michael@0: self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen, michael@0: self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength); michael@0: michael@0: self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize); michael@0: self->mLastDataReadEpoch = self->mLastReadEpoch; michael@0: michael@0: if (!endHeadersFlag) { // more are coming - don't process yet michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = self->ResponseHeadersComplete(); michael@0: if (rv == NS_ERROR_ILLEGAL_VALUE) { michael@0: LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n", michael@0: self, self->mInputFrameID)); michael@0: self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR); michael@0: self->ResetDownstreamState(); michael@0: rv = NS_OK; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream michael@0: // should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were michael@0: // fine, and any other error is fatal to the session. michael@0: nsresult michael@0: Http2Session::ResponseHeadersComplete() michael@0: { michael@0: LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d", michael@0: this, mInputFrameDataStream->StreamID(), mInputFrameFinal)); michael@0: michael@0: // only do this once, afterwards ignore trailers michael@0: if (mInputFrameDataStream->AllHeadersReceived()) michael@0: return NS_OK; michael@0: mInputFrameDataStream->SetAllHeadersReceived(true); michael@0: michael@0: // The stream needs to see flattened http headers michael@0: // Uncompressed http/2 format headers currently live in michael@0: // Http2Stream::mDecompressBuffer - convert that to HTTP format in michael@0: // mFlatHTTPResponseHeaders via ConvertHeaders() michael@0: michael@0: mFlatHTTPResponseHeadersOut = 0; michael@0: nsresult rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor, michael@0: mDecompressBuffer, michael@0: mFlatHTTPResponseHeaders); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvPriority(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY); michael@0: michael@0: if (self->mInputFrameDataSize != 4) { michael@0: LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n", michael@0: self, self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (!self->mInputFrameID) { michael@0: LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: uint32_t newPriority = michael@0: PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); michael@0: newPriority &= 0x7fffffff; michael@0: michael@0: nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (self->mInputFrameDataStream) michael@0: self->mInputFrameDataStream->SetPriority(newPriority); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvRstStream(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM); michael@0: michael@0: if (self->mInputFrameDataSize != 4) { michael@0: LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d", michael@0: self, self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (!self->mInputFrameID) { michael@0: LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: self->mDownstreamRstReason = michael@0: PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); michael@0: michael@0: LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n", michael@0: self, self->mDownstreamRstReason, self->mInputFrameID)); michael@0: michael@0: self->SetInputFrameDataStream(self->mInputFrameID); michael@0: if (!self->mInputFrameDataStream) { michael@0: // if we can't find the stream just ignore it (4.2 closed) michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: self->mInputFrameDataStream->SetRecvdReset(true); michael@0: self->MaybeDecrementConcurrent(self->mInputFrameDataStream); michael@0: self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); michael@0: return NS_OK; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: Http2Session::UpdateServerRwinEnumerator(nsAHttpTransaction *key, michael@0: nsAutoPtr &stream, michael@0: void *closure) michael@0: { michael@0: int32_t delta = *(static_cast(closure)); michael@0: stream->UpdateServerReceiveWindow(delta); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvSettings(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS); michael@0: michael@0: if (self->mInputFrameID) { michael@0: LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", michael@0: self, self->mInputFrameID)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (self->mInputFrameDataSize % 5) { michael@0: // Number of Settings is determined by dividing by each 5 byte setting michael@0: // entry. So the payload must be a multiple of 5. michael@0: LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", michael@0: self, self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: uint32_t numEntries = self->mInputFrameDataSize / 5; michael@0: LOG3(("Http2Session::RecvSettings %p SETTINGS Control Frame " michael@0: "with %d entries ack=%X", self, numEntries, michael@0: self->mInputFrameFlags & kFlag_ACK)); michael@0: michael@0: if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) { michael@0: LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n")); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: for (uint32_t index = 0; index < numEntries; ++index) { michael@0: uint8_t *setting = reinterpret_cast michael@0: (self->mInputFrameBuffer.get()) + 8 + index * 5; michael@0: michael@0: uint8_t id = setting[0]; michael@0: uint32_t value = PR_ntohl(*reinterpret_cast(setting + 1)); michael@0: LOG3(("Settings ID %d, Value %d", id, value)); michael@0: michael@0: switch (id) michael@0: { michael@0: case SETTINGS_TYPE_HEADER_TABLE_SIZE: michael@0: LOG3(("Compression header table setting received: %d\n", value)); michael@0: self->mCompressor.SetMaxBufferSize(value); michael@0: break; michael@0: michael@0: case SETTINGS_TYPE_ENABLE_PUSH: michael@0: LOG3(("Client received an ENABLE Push SETTING. Odd.\n")); michael@0: // nop michael@0: break; michael@0: michael@0: case SETTINGS_TYPE_MAX_CONCURRENT: michael@0: self->mMaxConcurrent = value; michael@0: Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value); michael@0: break; michael@0: michael@0: case SETTINGS_TYPE_INITIAL_WINDOW: michael@0: { michael@0: Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10); michael@0: int32_t delta = value - self->mServerInitialStreamWindow; michael@0: self->mServerInitialStreamWindow = value; michael@0: michael@0: // SETTINGS only adjusts stream windows. Leave the sesison window alone. michael@0: // we need to add the delta to all open streams (delta can be negative) michael@0: self->mStreamTransactionHash.Enumerate(UpdateServerRwinEnumerator, michael@0: &delta); michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: self->ResetDownstreamState(); michael@0: michael@0: if (!(self->mInputFrameFlags & kFlag_ACK)) michael@0: self->GenerateSettingsAck(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvPushPromise(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE); michael@0: michael@0: // Find out how much padding this frame has, so we can only extract the real michael@0: // header data from the frame. michael@0: uint16_t paddingLength = 0; michael@0: uint8_t paddingControlBytes = 0; michael@0: // TODO - will need to change this once PUSH_PROMISE allows padding michael@0: // (post-draft10) michael@0: // Right now, only CONTINUATION frames can have padding, and michael@0: // mExpectedPushPromiseID being set indicates that we're actually processing a michael@0: // CONTINUATION frame. michael@0: if (self->mExpectedPushPromiseID) { michael@0: nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // If this doesn't have END_PUSH_PROMISE set on it then require the next michael@0: // frame to be PUSH_PROMISE of the same ID michael@0: uint32_t promiseLen; michael@0: uint32_t promisedID; michael@0: michael@0: if (self->mExpectedPushPromiseID) { michael@0: promiseLen = 0; // really a continuation frame michael@0: promisedID = self->mContinuedPromiseStream; michael@0: } else { michael@0: // TODO - will need to handle padding here when getting the promisedID, once michael@0: // PUSH_PROMISE allows padding (post-draft10) michael@0: promiseLen = 4; michael@0: promisedID = michael@0: PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); michael@0: promisedID &= 0x7fffffff; michael@0: } michael@0: michael@0: uint32_t associatedID = self->mInputFrameID; michael@0: michael@0: if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) { michael@0: self->mExpectedPushPromiseID = 0; michael@0: self->mContinuedPromiseStream = 0; michael@0: } else { michael@0: self->mExpectedPushPromiseID = self->mInputFrameID; michael@0: self->mContinuedPromiseStream = promisedID; michael@0: } michael@0: michael@0: if (paddingLength > self->mInputFrameDataSize) { michael@0: // This is fatal to the session michael@0: LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X " michael@0: "PROTOCOL_ERROR paddingLength %d > frame size %d\n", michael@0: self, promisedID, associatedID, paddingLength, michael@0: self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X " michael@0: "paddingLength %d pad_high_flag %d pad_low_flag %d.\n", michael@0: self, promisedID, associatedID, paddingLength, michael@0: self->mInputFrameFlags & kFlag_PAD_HIGH, michael@0: self->mInputFrameFlags & kFlag_PAD_LOW)); michael@0: michael@0: if (!associatedID || !promisedID || (promisedID & 1)) { michael@0: LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: // confirm associated-to michael@0: nsresult rv = self->SetInputFrameDataStream(associatedID); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: Http2Stream *associatedStream = self->mInputFrameDataStream; michael@0: ++(self->mServerPushedResources); michael@0: michael@0: // Anytime we start using the high bit of stream ID (either client or server) michael@0: // begin to migrate to a new session. michael@0: if (promisedID >= kMaxStreamID) michael@0: self->mShouldGoAway = true; michael@0: michael@0: bool resetStream = true; michael@0: SpdyPushCache *cache = nullptr; michael@0: michael@0: if (self->mShouldGoAway) { michael@0: LOG3(("Http2Session::RecvPushPromise %p push while in GoAway " michael@0: "mode refused.\n", self)); michael@0: self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID); michael@0: } else if (!gHttpHandler->AllowPush()) { michael@0: // MAX_CONCURRENT_STREAMS of 0 in settings disabled push michael@0: LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n")); michael@0: self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID); michael@0: } else if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) { michael@0: LOG3(("Http2Session::RecvPushPromise no support for multi frame push\n")); michael@0: self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID); michael@0: } else if (!associatedStream) { michael@0: LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self)); michael@0: self->GenerateRstStream(PROTOCOL_ERROR, promisedID); michael@0: } else { michael@0: nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo(); michael@0: if (loadGroupCI) { michael@0: loadGroupCI->GetSpdyPushCache(&cache); michael@0: if (!cache) { michael@0: cache = new SpdyPushCache(); michael@0: if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache(cache))) { michael@0: delete cache; michael@0: cache = nullptr; michael@0: } michael@0: } michael@0: } michael@0: if (!cache) { michael@0: // this is unexpected, but we can handle it just by refusing the push michael@0: LOG3(("Http2Session::RecvPushPromise Push Recevied without loadgroup cache\n")); michael@0: self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID); michael@0: } else { michael@0: resetStream = false; michael@0: } michael@0: } michael@0: michael@0: if (resetStream) { michael@0: // Need to decompress the headers even though we aren't using them yet in michael@0: // order to keep the compression context consistent for other frames michael@0: self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + promiseLen, michael@0: self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength); michael@0: if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) { michael@0: rv = self->UncompressAndDiscard(); michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session::RecvPushPromise uncompress failed\n")); michael@0: self->mGoAwayReason = COMPRESSION_ERROR; michael@0: return rv; michael@0: } michael@0: } michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Create the buffering transaction and push stream michael@0: nsRefPtr transactionBuffer = michael@0: new Http2PushTransactionBuffer(); michael@0: transactionBuffer->SetConnection(self); michael@0: Http2PushedStream *pushedStream = michael@0: new Http2PushedStream(transactionBuffer, self, michael@0: associatedStream, promisedID); michael@0: michael@0: // Ownership of the pushed stream is by the transaction hash, just as it michael@0: // is for a client initiated stream. Errors that aren't fatal to the michael@0: // whole session must call cleanupStream() after this point in order michael@0: // to remove the stream from that hash. michael@0: self->mStreamTransactionHash.Put(transactionBuffer, pushedStream); michael@0: self->mPushedStreams.AppendElement(pushedStream); michael@0: michael@0: self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + promiseLen, michael@0: self->mInputFrameDataSize - promiseLen); michael@0: michael@0: nsAutoCString requestHeaders; michael@0: rv = pushedStream->ConvertPushHeaders(&self->mDecompressor, michael@0: self->mDecompressBuffer, requestHeaders); michael@0: michael@0: if (rv == NS_ERROR_NOT_IMPLEMENTED) { michael@0: LOG3(("Http2Session::PushPromise Semantics not Implemented\n")); michael@0: self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) { michael@0: LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n")); michael@0: self->mGoAwayReason = INTERNAL_ERROR; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (promisedID > self->mOutgoingGoAwayID) michael@0: self->mOutgoingGoAwayID = promisedID; michael@0: michael@0: // Fake the request side of the pushed HTTP transaction. Sets up hash michael@0: // key and origin michael@0: uint32_t notUsed; michael@0: pushedStream->ReadSegments(nullptr, 1, ¬Used); michael@0: michael@0: nsAutoCString key; michael@0: if (!pushedStream->GetHashKey(key)) { michael@0: LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n")); michael@0: self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!associatedStream->Origin().Equals(pushedStream->Origin())) { michael@0: LOG3(("Http2Session::RecvPushPromise pushed stream mismatched origin\n")); michael@0: self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) { michael@0: LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n")); michael@0: self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static_assert(Http2Stream::kWorstPriority >= 0, michael@0: "kWorstPriority out of range"); michael@0: uint32_t unsignedPriority = static_cast(Http2Stream::kWorstPriority); michael@0: pushedStream->SetPriority(unsignedPriority); michael@0: self->GeneratePriority(promisedID, unsignedPriority); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvPing(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING); michael@0: michael@0: LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self, michael@0: self->mInputFrameFlags)); michael@0: michael@0: if (self->mInputFrameDataSize != 8) { michael@0: LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d", michael@0: self, self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR); michael@0: } michael@0: michael@0: if (self->mInputFrameID) { michael@0: LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n", michael@0: self, self->mInputFrameID)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (self->mInputFrameFlags & kFlag_ACK) { michael@0: // presumably a reply to our timeout ping.. don't reply to it michael@0: self->mPingSentEpoch = 0; michael@0: } else { michael@0: // reply with a ack'd ping michael@0: self->GeneratePing(true); michael@0: } michael@0: michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvGoAway(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY); michael@0: michael@0: if (self->mInputFrameDataSize < 8) { michael@0: // data > 8 is an opaque token that we can't interpret. NSPR Logs will michael@0: // have the hex of all packets so there is no point in separately logging. michael@0: LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d", michael@0: self, self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (self->mInputFrameID) { michael@0: LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n", michael@0: self, self->mInputFrameID)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: self->mShouldGoAway = true; michael@0: self->mGoAwayID = michael@0: PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); michael@0: self->mGoAwayID &= 0x7fffffff; michael@0: self->mCleanShutdown = true; michael@0: uint32_t statusCode = michael@0: PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]); michael@0: michael@0: // Find streams greater than the last-good ID and mark them for deletion michael@0: // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. The michael@0: // underlying transaction can be restarted. michael@0: self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self); michael@0: michael@0: // Process the streams marked for deletion and restart. michael@0: uint32_t size = self->mGoAwayStreamsToRestart.GetSize(); michael@0: for (uint32_t count = 0; count < size; ++count) { michael@0: Http2Stream *stream = michael@0: static_cast(self->mGoAwayStreamsToRestart.PopFront()); michael@0: michael@0: self->CloseStream(stream, NS_ERROR_NET_RESET); michael@0: if (stream->HasRegisteredID()) michael@0: self->mStreamIDHash.Remove(stream->StreamID()); michael@0: self->mStreamTransactionHash.Remove(stream->Transaction()); michael@0: } michael@0: michael@0: // Queued streams can also be deleted from this session and restarted michael@0: // in another one. (they were never sent on the network so they implicitly michael@0: // are not covered by the last-good id. michael@0: size = self->mQueuedStreams.GetSize(); michael@0: for (uint32_t count = 0; count < size; ++count) { michael@0: Http2Stream *stream = michael@0: static_cast(self->mQueuedStreams.PopFront()); michael@0: self->CloseStream(stream, NS_ERROR_NET_RESET); michael@0: self->mStreamTransactionHash.Remove(stream->Transaction()); michael@0: } michael@0: michael@0: LOG3(("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X " michael@0: "live streams=%d\n", self, self->mGoAwayID, statusCode, michael@0: self->mStreamTransactionHash.Count())); michael@0: michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: Http2Session::RestartBlockedOnRwinEnumerator(nsAHttpTransaction *key, michael@0: nsAutoPtr &stream, michael@0: void *closure) michael@0: { michael@0: Http2Session *self = static_cast(closure); michael@0: MOZ_ASSERT(self->mServerSessionWindow > 0); michael@0: michael@0: if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: self->mReadyForWrite.Push(stream); michael@0: self->SetWriteCallbacks(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvWindowUpdate(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE); michael@0: michael@0: if (self->mInputFrameDataSize != 4) { michael@0: LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n", michael@0: self, self->mInputFrameDataSize)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: uint32_t delta = michael@0: PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); michael@0: delta &= 0x7fffffff; michael@0: michael@0: LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", michael@0: self, delta, self->mInputFrameID)); michael@0: michael@0: if (self->mInputFrameID) { // stream window michael@0: nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!self->mInputFrameDataStream) { michael@0: LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n", michael@0: self, self->mInputFrameID)); michael@0: // only resest the session if the ID is one we haven't ever opened michael@0: if (self->mInputFrameID >= self->mNextStreamID) michael@0: self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int64_t oldRemoteWindow = self->mInputFrameDataStream->ServerReceiveWindow(); michael@0: self->mInputFrameDataStream->UpdateServerReceiveWindow(delta); michael@0: if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) { michael@0: // a window cannot reach 2^31 and be in compliance. Our calculations michael@0: // are 64 bit safe though. michael@0: LOG3(("Http2Session::RecvWindowUpdate %p stream window " michael@0: "exceeds 2^31 - 1\n", self)); michael@0: self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, michael@0: FLOW_CONTROL_ERROR); michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG3(("Http2Session::RecvWindowUpdate %p stream 0x%X window " michael@0: "%d increased by %d now %d.\n", self, self->mInputFrameID, michael@0: oldRemoteWindow, delta, oldRemoteWindow + delta)); michael@0: michael@0: } else { // session window update michael@0: int64_t oldRemoteWindow = self->mServerSessionWindow; michael@0: self->mServerSessionWindow += delta; michael@0: michael@0: if (self->mServerSessionWindow >= 0x80000000) { michael@0: // a window cannot reach 2^31 and be in compliance. Our calculations michael@0: // are 64 bit safe though. michael@0: LOG3(("Http2Session::RecvWindowUpdate %p session window " michael@0: "exceeds 2^31 - 1\n", self)); michael@0: RETURN_SESSION_ERROR(self, FLOW_CONTROL_ERROR); michael@0: } michael@0: michael@0: if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) { michael@0: LOG3(("Http2Session::RecvWindowUpdate %p restart session window\n", michael@0: self)); michael@0: self->mStreamTransactionHash.Enumerate(RestartBlockedOnRwinEnumerator, self); michael@0: } michael@0: LOG3(("Http2Session::RecvWindowUpdate %p session window " michael@0: "%d increased by %d now %d.\n", self, michael@0: oldRemoteWindow, delta, oldRemoteWindow + delta)); michael@0: } michael@0: michael@0: self->ResetDownstreamState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::RecvContinuation(Http2Session *self) michael@0: { michael@0: MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION); michael@0: MOZ_ASSERT(self->mInputFrameID); michael@0: MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID); michael@0: MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID)); michael@0: michael@0: LOG3(("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X " michael@0: "promise id 0x%X header id 0x%X\n", michael@0: self, self->mInputFrameFlags, self->mInputFrameID, michael@0: self->mExpectedPushPromiseID, self->mExpectedHeaderID)); michael@0: michael@0: self->SetInputFrameDataStream(self->mInputFrameID); michael@0: michael@0: if (!self->mInputFrameDataStream) { michael@0: LOG3(("Http2Session::RecvContination stream ID 0x%X not found.", michael@0: self->mInputFrameID)); michael@0: RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: // continued headers michael@0: if (self->mExpectedHeaderID) { michael@0: self->mInputFrameFlags &= ~kFlag_PRIORITY; michael@0: return RecvHeaders(self); michael@0: } michael@0: michael@0: // continued push promise michael@0: if (self->mInputFrameFlags & kFlag_END_HEADERS) { michael@0: self->mInputFrameFlags &= ~kFlag_END_HEADERS; michael@0: self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE; michael@0: } michael@0: return RecvPushPromise(self); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAHttpTransaction. It is expected that nsHttpConnection is the caller michael@0: // of these methods michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: Http2Session::OnTransportStatus(nsITransport* aTransport, michael@0: nsresult aStatus, uint64_t aProgress) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: switch (aStatus) { michael@0: // These should appear only once, deliver to the first michael@0: // transaction on the session. michael@0: case NS_NET_STATUS_RESOLVING_HOST: michael@0: case NS_NET_STATUS_RESOLVED_HOST: michael@0: case NS_NET_STATUS_CONNECTING_TO: michael@0: case NS_NET_STATUS_CONNECTED_TO: michael@0: { michael@0: Http2Stream *target = mStreamIDHash.Get(1); michael@0: nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr; michael@0: if (transaction) michael@0: transaction->OnTransportStatus(aTransport, aStatus, aProgress); michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: // The other transport events are ignored here because there is no good michael@0: // way to map them to the right transaction in http/2. Instead, the events michael@0: // are generated again from the http/2 code and passed directly to the michael@0: // correct transaction. michael@0: michael@0: // NS_NET_STATUS_SENDING_TO: michael@0: // This is generated by the socket transport when (part) of michael@0: // a transaction is written out michael@0: // michael@0: // There is no good way to map it to the right transaction in http/2, michael@0: // so it is ignored here and generated separately when the request michael@0: // is sent from Http2Stream::TransmitFrame michael@0: michael@0: // NS_NET_STATUS_WAITING_FOR: michael@0: // Created by nsHttpConnection when the request has been totally sent. michael@0: // There is no good way to map it to the right transaction in http/2, michael@0: // so it is ignored here and generated separately when the same michael@0: // condition is complete in Http2Stream when there is no more michael@0: // request body left to be transmitted. michael@0: michael@0: // NS_NET_STATUS_RECEIVING_FROM michael@0: // Generated in session whenever we read a data frame or a HEADERS michael@0: // that can be attributed to a particular stream/transaction michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // ReadSegments() is used to write data to the network. Generally, HTTP michael@0: // request data is pulled from the approriate transaction and michael@0: // converted to http/2 data. Sometimes control data like window-update are michael@0: // generated instead. michael@0: michael@0: nsresult michael@0: Http2Session::ReadSegments(nsAHttpSegmentReader *reader, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader), michael@0: "Inconsistent Write Function Callback"); michael@0: michael@0: nsresult rv = ConfirmTLSProfile(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (reader) michael@0: mSegmentReader = reader; michael@0: michael@0: *countRead = 0; michael@0: michael@0: LOG3(("Http2Session::ReadSegments %p", this)); michael@0: michael@0: Http2Stream *stream = static_cast(mReadyForWrite.PopFront()); michael@0: if (!stream) { michael@0: LOG3(("Http2Session %p could not identify a stream to write; suspending.", michael@0: this)); michael@0: FlushOutputQueue(); michael@0: SetWriteCallbacks(); michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: LOG3(("Http2Session %p will write from Http2Stream %p 0x%X " michael@0: "block-input=%d block-output=%d\n", this, stream, stream->StreamID(), michael@0: stream->RequestBlockedOnRead(), stream->BlockedOnRwin())); michael@0: michael@0: rv = stream->ReadSegments(this, count, countRead); michael@0: michael@0: // Not every permutation of stream->ReadSegents produces data (and therefore michael@0: // tries to flush the output queue) - SENDING_FIN_STREAM can be an example michael@0: // of that. But we might still have old data buffered that would be good michael@0: // to flush. michael@0: FlushOutputQueue(); michael@0: michael@0: // Allow new server reads - that might be data or control information michael@0: // (e.g. window updates or http replies) that are responses to these writes michael@0: ResumeRecv(); michael@0: michael@0: if (stream->RequestBlockedOnRead()) { michael@0: michael@0: // We are blocked waiting for input - either more http headers or michael@0: // any request body data. When more data from the request stream michael@0: // becomes available the httptransaction will call conn->ResumeSend(). michael@0: michael@0: LOG3(("Http2Session::ReadSegments %p dealing with block on read", this)); michael@0: michael@0: // call readsegments again if there are other streams ready michael@0: // to run in this session michael@0: if (GetWriteQueueSize()) { michael@0: rv = NS_OK; michael@0: } else { michael@0: rv = NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: SetWriteCallbacks(); michael@0: return rv; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session::ReadSegments %p returning FAIL code %X", michael@0: this, rv)); michael@0: if (rv != NS_BASE_STREAM_WOULD_BLOCK) michael@0: CleanupStream(stream, rv, CANCEL_ERROR); michael@0: return rv; michael@0: } michael@0: michael@0: if (*countRead > 0) { michael@0: LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d", michael@0: this, stream, *countRead)); michael@0: mReadyForWrite.Push(stream); michael@0: SetWriteCallbacks(); michael@0: return rv; michael@0: } michael@0: michael@0: if (stream->BlockedOnRwin()) { michael@0: LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n", michael@0: this, stream, stream->StreamID())); michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", michael@0: this, stream)); michael@0: michael@0: // call readsegments again if there are other streams ready michael@0: // to go in this session michael@0: SetWriteCallbacks(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::ReadyToProcessDataFrame(enum internalStateType newState) michael@0: { michael@0: MOZ_ASSERT(newState == PROCESSING_DATA_FRAME || michael@0: newState == DISCARDING_DATA_FRAME_PADDING); michael@0: ChangeDownstreamState(newState); michael@0: michael@0: Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, michael@0: mInputFrameDataSize >> 10); michael@0: mLastDataReadEpoch = mLastReadEpoch; michael@0: michael@0: if (!mInputFrameID) { michael@0: LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n", michael@0: this)); michael@0: RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: nsresult rv = SetInputFrameDataStream(mInputFrameID); michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X " michael@0: "failed. probably due to verification.\n", this, mInputFrameID)); michael@0: return rv; michael@0: } michael@0: if (!mInputFrameDataStream) { michael@0: LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X " michael@0: "failed. Next = 0x%X", this, mInputFrameID, mNextStreamID)); michael@0: if (mInputFrameID >= mNextStreamID) michael@0: GenerateRstStream(PROTOCOL_ERROR, mInputFrameID); michael@0: ChangeDownstreamState(DISCARDING_DATA_FRAME); michael@0: } else if (mInputFrameDataStream->RecvdFin() || michael@0: mInputFrameDataStream->RecvdReset() || michael@0: mInputFrameDataStream->SentReset()) { michael@0: LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X " michael@0: "Data arrived for already server closed stream.\n", michael@0: this, mInputFrameID)); michael@0: if (mInputFrameDataStream->RecvdFin() || mInputFrameDataStream->RecvdReset()) michael@0: GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID); michael@0: ChangeDownstreamState(DISCARDING_DATA_FRAME); michael@0: } michael@0: michael@0: LOG3(("Start Processing Data Frame. " michael@0: "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d", michael@0: this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal, michael@0: mInputFrameDataSize)); michael@0: UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // WriteSegments() is used to read data off the socket. Generally this is michael@0: // just the http2 frame header and from there the appropriate *Stream michael@0: // is identified from the Stream-ID. The http transaction associated with michael@0: // that read then pulls in the data directly, which it will feed to michael@0: // OnWriteSegment(). That function will gateway it into http and feed michael@0: // it to the appropriate transaction. michael@0: michael@0: // we call writer->OnWriteSegment via NetworkRead() to get a http2 header.. michael@0: // and decide if it is data or control.. if it is control, just deal with it. michael@0: // if it is data, identify the stream michael@0: // call stream->WriteSegments which can call this::OnWriteSegment to get the michael@0: // data. It always gets full frames if they are part of the stream michael@0: michael@0: nsresult michael@0: Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, michael@0: uint32_t count, uint32_t *countWritten) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: LOG3(("Http2Session::WriteSegments %p InternalState %X\n", michael@0: this, mDownstreamState)); michael@0: michael@0: *countWritten = 0; michael@0: michael@0: if (mClosed) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = ConfirmTLSProfile(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: SetWriteCallbacks(); michael@0: michael@0: // If there are http transactions attached to a push stream with filled buffers michael@0: // trigger that data pump here. This only reads from buffers (not the network) michael@0: // so mDownstreamState doesn't matter. michael@0: Http2Stream *pushConnectedStream = michael@0: static_cast(mReadyForRead.PopFront()); michael@0: if (pushConnectedStream) { michael@0: LOG3(("Http2Session::WriteSegments %p processing pushed stream 0x%X\n", michael@0: this, pushConnectedStream->StreamID())); michael@0: mSegmentWriter = writer; michael@0: rv = pushConnectedStream->WriteSegments(this, count, countWritten); michael@0: mSegmentWriter = nullptr; michael@0: michael@0: // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK michael@0: // so we need this check to determine the truth. michael@0: if (NS_SUCCEEDED(rv) && !*countWritten && michael@0: pushConnectedStream->PushSource() && michael@0: pushConnectedStream->PushSource()->GetPushComplete()) { michael@0: rv = NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: if (rv == NS_BASE_STREAM_CLOSED) { michael@0: CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR); michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: // if we return OK to nsHttpConnection it will use mSocketInCondition michael@0: // to determine whether to schedule more reads, incorrectly michael@0: // assuming that nsHttpConnection::OnSocketWrite() was called. michael@0: if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) { michael@0: rv = NS_BASE_STREAM_WOULD_BLOCK; michael@0: ResumeRecv(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER michael@0: // except the only frame type it will allow is SETTINGS michael@0: michael@0: // The session layer buffers the leading 8 byte header of every frame. michael@0: // Non-Data frames are then buffered for their full length, but data michael@0: // frames (type 0) are passed through to the http stack unprocessed michael@0: michael@0: if (mDownstreamState == BUFFERING_OPENING_SETTINGS || michael@0: mDownstreamState == BUFFERING_FRAME_HEADER) { michael@0: // The first 8 bytes of every frame is header information that michael@0: // we are going to want to strip before passing to http. That is michael@0: // true of both control and data packets. michael@0: michael@0: MOZ_ASSERT(mInputFrameBufferUsed < 8, michael@0: "Frame Buffer Used Too Large for State"); michael@0: michael@0: rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed, michael@0: 8 - mInputFrameBufferUsed, countWritten); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session %p buffering frame header read failure %x\n", michael@0: this, rv)); michael@0: // maybe just blocked reading from network michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) michael@0: rv = NS_OK; michael@0: return rv; michael@0: } michael@0: michael@0: LogIO(this, nullptr, "Reading Frame Header", michael@0: mInputFrameBuffer + mInputFrameBufferUsed, *countWritten); michael@0: michael@0: mInputFrameBufferUsed += *countWritten; michael@0: michael@0: if (mInputFrameBufferUsed < 8) michael@0: { michael@0: LOG3(("Http2Session::WriteSegments %p " michael@0: "BUFFERING FRAME HEADER incomplete size=%d", michael@0: this, mInputFrameBufferUsed)); michael@0: return rv; michael@0: } michael@0: michael@0: // 2 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID michael@0: mInputFrameDataSize = michael@0: PR_ntohs(reinterpret_cast(mInputFrameBuffer.get())[0]); michael@0: mInputFrameType = reinterpret_cast(mInputFrameBuffer.get())[2]; michael@0: mInputFrameFlags = reinterpret_cast(mInputFrameBuffer.get())[3]; michael@0: mInputFrameID = michael@0: PR_ntohl(reinterpret_cast(mInputFrameBuffer.get())[1]); michael@0: mInputFrameID &= 0x7fffffff; michael@0: mInputFrameDataRead = 0; michael@0: michael@0: if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS) { michael@0: mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM; michael@0: } else { michael@0: mInputFrameFinal = 0; michael@0: } michael@0: michael@0: mPaddingLength = 0; michael@0: if (mInputFrameType == FRAME_TYPE_DATA || michael@0: mInputFrameType == FRAME_TYPE_HEADERS || michael@0: // TODO: also mInputFrameType == FRAME_TYPE_PUSH_PROMISE after draft10 michael@0: mInputFrameType == FRAME_TYPE_CONTINUATION) { michael@0: if ((mInputFrameFlags & kFlag_PAD_HIGH) && michael@0: !(mInputFrameFlags & kFlag_PAD_LOW)) { michael@0: LOG3(("Http2Session::WriteSegments %p PROTOCOL_ERROR pad_high present " michael@0: "without pad_low\n", this)); michael@0: RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); michael@0: } michael@0: } michael@0: michael@0: if (mInputFrameDataSize >= 0x4000) { michael@0: // Section 9.1 HTTP frames cannot exceed 2^14 - 1 but receviers must ignore michael@0: // those bits michael@0: LOG3(("Http2Session::WriteSegments %p WARNING Frame Length bits past 14 are not 0 %08X\n", michael@0: this, mInputFrameDataSize)); michael@0: mInputFrameDataSize &= 0x3fff; michael@0: } michael@0: michael@0: LOG3(("Http2Session::WriteSegments[%p::%x] Frame Header Read " michael@0: "type %X data len %u flags %x id 0x%X", michael@0: this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags, michael@0: mInputFrameID)); michael@0: michael@0: // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION of michael@0: // a HEADERS frame with a matching ID (section 6.2) michael@0: if (mExpectedHeaderID && michael@0: ((mInputFrameType != FRAME_TYPE_CONTINUATION) || michael@0: (mExpectedHeaderID != mInputFrameID))) { michael@0: LOG3(("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID)); michael@0: RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: // if mExpectedPushPromiseID is non 0, it means this frame must be a michael@0: // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2) michael@0: if (mExpectedPushPromiseID && michael@0: ((mInputFrameType != FRAME_TYPE_CONTINUATION) || michael@0: (mExpectedPushPromiseID != mInputFrameID))) { michael@0: LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n", michael@0: mExpectedPushPromiseID)); michael@0: RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (mDownstreamState == BUFFERING_OPENING_SETTINGS && michael@0: mInputFrameType != FRAME_TYPE_SETTINGS) { michael@0: LOG3(("First Frame Type Must Be Settings\n")); michael@0: RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); michael@0: } michael@0: michael@0: if (mInputFrameType != FRAME_TYPE_DATA) { // control frame michael@0: EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + 8, 8, michael@0: mInputFrameBufferSize); michael@0: ChangeDownstreamState(BUFFERING_CONTROL_FRAME); michael@0: } else if (mInputFrameFlags & (kFlag_PAD_LOW | kFlag_PAD_HIGH)) { michael@0: ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL); michael@0: } else { michael@0: rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) { michael@0: uint32_t numControlBytes = 0; michael@0: if (mInputFrameFlags & kFlag_PAD_LOW) { michael@0: ++numControlBytes; michael@0: } michael@0: if (mInputFrameFlags & kFlag_PAD_HIGH) { michael@0: ++numControlBytes; michael@0: } michael@0: michael@0: MOZ_ASSERT(numControlBytes, michael@0: "Processing padding control with no control bytes!"); michael@0: MOZ_ASSERT(mInputFrameBufferUsed < (8 + numControlBytes), michael@0: "Frame buffer used too large for state"); michael@0: michael@0: rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed, michael@0: (8 + numControlBytes) - mInputFrameBufferUsed, michael@0: countWritten); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session %p buffering data frame padding control read failure %x\n", michael@0: this, rv)); michael@0: // maybe just blocked reading from network michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) michael@0: rv = NS_OK; michael@0: return rv; michael@0: } michael@0: michael@0: LogIO(this, nullptr, "Reading Data Frame Padding Control", michael@0: mInputFrameBuffer + mInputFrameBufferUsed, *countWritten); michael@0: michael@0: mInputFrameBufferUsed += *countWritten; michael@0: michael@0: if (mInputFrameBufferUsed - 8 < numControlBytes) { michael@0: LOG3(("Http2Session::WriteSegments %p " michael@0: "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d", michael@0: this, mInputFrameBufferUsed - 8)); michael@0: return rv; michael@0: } michael@0: michael@0: mInputFrameDataRead += numControlBytes; michael@0: michael@0: char *control = mInputFrameBuffer + 8; michael@0: if (mInputFrameFlags & kFlag_PAD_HIGH) { michael@0: mPaddingLength = static_cast(*control) * 256; michael@0: ++control; michael@0: } michael@0: mPaddingLength += static_cast(*control); michael@0: michael@0: LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this, michael@0: mInputFrameID, mPaddingLength)); michael@0: michael@0: if (numControlBytes + mPaddingLength == mInputFrameDataSize) { michael@0: // This frame consists entirely of padding, we can just discard it michael@0: LOG3(("Http2Session::WriteSegments %p stream 0x%X frame with only padding", michael@0: this, mInputFrameID)); michael@0: rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } else { michael@0: LOG3(("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data", michael@0: this, mInputFrameID)); michael@0: rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { michael@0: nsresult streamCleanupCode; michael@0: michael@0: // There is no bounds checking on the error code.. we provide special michael@0: // handling for a couple of cases and all others (including unknown) are michael@0: // equivalent to cancel. michael@0: if (mDownstreamRstReason == REFUSED_STREAM_ERROR) { michael@0: streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely michael@0: } else { michael@0: streamCleanupCode = NS_ERROR_NET_INTERRUPT; michael@0: } michael@0: michael@0: if (mDownstreamRstReason == COMPRESSION_ERROR) michael@0: mShouldGoAway = true; michael@0: michael@0: // mInputFrameDataStream is reset by ChangeDownstreamState michael@0: Http2Stream *stream = mInputFrameDataStream; michael@0: ResetDownstreamState(); michael@0: LOG3(("Http2Session::WriteSegments cleanup stream on recv of rst " michael@0: "session=%p stream=%p 0x%X\n", this, stream, michael@0: stream ? stream->StreamID() : 0)); michael@0: CleanupStream(stream, streamCleanupCode, CANCEL_ERROR); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mDownstreamState == PROCESSING_DATA_FRAME || michael@0: mDownstreamState == PROCESSING_COMPLETE_HEADERS) { michael@0: michael@0: // The cleanup stream should only be set while stream->WriteSegments is michael@0: // on the stack and then cleaned up in this code block afterwards. michael@0: MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly"); michael@0: mNeedsCleanup = nullptr; /* just in case */ michael@0: michael@0: mSegmentWriter = writer; michael@0: rv = mInputFrameDataStream->WriteSegments(this, count, countWritten); michael@0: mSegmentWriter = nullptr; michael@0: michael@0: mLastDataReadEpoch = mLastReadEpoch; michael@0: michael@0: if (SoftStreamError(rv)) { michael@0: // This will happen when the transaction figures out it is EOF, generally michael@0: // due to a content-length match being made. Return OK from this function michael@0: // otherwise the whole session would be torn down. michael@0: Http2Stream *stream = mInputFrameDataStream; michael@0: michael@0: // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state michael@0: // back to PROCESSING_DATA_FRAME where we came from michael@0: mDownstreamState = PROCESSING_DATA_FRAME; michael@0: michael@0: if (mInputFrameDataRead == mInputFrameDataSize) michael@0: ResetDownstreamState(); michael@0: LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X " michael@0: "needscleanup=%p. cleanup stream based on " michael@0: "stream->writeSegments returning code %x\n", michael@0: this, stream, stream ? stream->StreamID() : 0, michael@0: mNeedsCleanup, rv)); michael@0: CleanupStream(stream, NS_OK, CANCEL_ERROR); michael@0: MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame"); michael@0: mNeedsCleanup = nullptr; /* just in case */ michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mNeedsCleanup) { michael@0: LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X " michael@0: "cleanup stream based on mNeedsCleanup.\n", michael@0: this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0)); michael@0: CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR); michael@0: mNeedsCleanup = nullptr; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session %p data frame read failure %x\n", this, rv)); michael@0: // maybe just blocked reading from network michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: if (mDownstreamState == DISCARDING_DATA_FRAME || michael@0: mDownstreamState == DISCARDING_DATA_FRAME_PADDING) { michael@0: char trash[4096]; michael@0: uint32_t count = std::min(4096U, mInputFrameDataSize - mInputFrameDataRead); michael@0: LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of data", michael@0: this, count)); michael@0: michael@0: if (!count) { michael@0: ResetDownstreamState(); michael@0: ResumeRecv(); michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: rv = NetworkRead(writer, trash, count, countWritten); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session %p discard frame read failure %x\n", this, rv)); michael@0: // maybe just blocked reading from network michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) michael@0: rv = NS_OK; michael@0: return rv; michael@0: } michael@0: michael@0: LogIO(this, nullptr, "Discarding Frame", trash, *countWritten); michael@0: michael@0: mInputFrameDataRead += *countWritten; michael@0: michael@0: if (mInputFrameDataRead == mInputFrameDataSize) { michael@0: Http2Stream *streamToCleanup = nullptr; michael@0: if (mInputFrameFinal) { michael@0: streamToCleanup = mInputFrameDataStream; michael@0: } michael@0: michael@0: ResetDownstreamState(); michael@0: michael@0: if (streamToCleanup) { michael@0: CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (mDownstreamState != BUFFERING_CONTROL_FRAME) { michael@0: MOZ_ASSERT(false); // this cannot happen michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: MOZ_ASSERT(mInputFrameBufferUsed == 8, "Frame Buffer Header Not Present"); michael@0: MOZ_ASSERT(mInputFrameDataSize + 8 <= mInputFrameBufferSize, michael@0: "allocation for control frame insufficient"); michael@0: michael@0: rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead, michael@0: mInputFrameDataSize - mInputFrameDataRead, countWritten); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Session %p buffering control frame read failure %x\n", michael@0: this, rv)); michael@0: // maybe just blocked reading from network michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) michael@0: rv = NS_OK; michael@0: return rv; michael@0: } michael@0: michael@0: LogIO(this, nullptr, "Reading Control Frame", michael@0: mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten); michael@0: michael@0: mInputFrameDataRead += *countWritten; michael@0: michael@0: if (mInputFrameDataRead != mInputFrameDataSize) michael@0: return NS_OK; michael@0: michael@0: MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA); michael@0: if (mInputFrameType < FRAME_TYPE_LAST) { michael@0: rv = sControlFunctions[mInputFrameType](this); michael@0: } else { michael@0: // Section 4.1 requires this to be ignored; though protocol_error would michael@0: // be better michael@0: LOG3(("Http2Session %p unknow frame type %x ignored\n", michael@0: this, mInputFrameType)); michael@0: ResetDownstreamState(); michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(NS_FAILED(rv) || michael@0: mDownstreamState != BUFFERING_CONTROL_FRAME, michael@0: "Control Handler returned OK but did not change state"); michael@0: michael@0: if (mShouldGoAway && !mStreamTransactionHash.Count()) michael@0: Close(NS_OK); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes) michael@0: { michael@0: if (!stream) // this is ok - it means there was a data frame for a rst stream michael@0: return; michael@0: michael@0: // If this data packet was not for a valid or live stream then there michael@0: // is no reason to mess with the flow control michael@0: if (!stream || stream->RecvdFin() || stream->RecvdReset() || michael@0: mInputFrameFinal) { michael@0: return; michael@0: } michael@0: michael@0: stream->DecrementClientReceiveWindow(bytes); michael@0: michael@0: // Don't necessarily ack every data packet. Only do it michael@0: // after a significant amount of data. michael@0: uint64_t unacked = stream->LocalUnAcked(); michael@0: int64_t localWindow = stream->ClientReceiveWindow(); michael@0: michael@0: LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u " michael@0: "unacked=%llu localWindow=%lld\n", michael@0: this, stream->StreamID(), bytes, unacked, localWindow)); michael@0: michael@0: if (!unacked) michael@0: return; michael@0: michael@0: if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) michael@0: return; michael@0: michael@0: if (!stream->HasSink()) { michael@0: LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n", michael@0: this, stream->StreamID())); michael@0: return; michael@0: } michael@0: michael@0: // Generate window updates directly out of session instead of the stream michael@0: // in order to avoid queue delays in getting the 'ACK' out. michael@0: uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU; michael@0: michael@0: LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n", michael@0: this, stream->StreamID(), toack)); michael@0: stream->IncrementClientReceiveWindow(toack); michael@0: michael@0: // room for this packet needs to be ensured before calling this function michael@0: char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; michael@0: mOutputQueueUsed += 12; michael@0: MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize); michael@0: michael@0: CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID()); michael@0: toack = PR_htonl(toack); michael@0: memcpy(packet + 8, &toack, 4); michael@0: michael@0: LogIO(this, stream, "Stream Window Update", packet, 12); michael@0: // dont flush here, this write can commonly be coalesced with a michael@0: // session window update to immediately follow. michael@0: } michael@0: michael@0: void michael@0: Http2Session::UpdateLocalSessionWindow(uint32_t bytes) michael@0: { michael@0: if (!bytes) michael@0: return; michael@0: michael@0: mLocalSessionWindow -= bytes; michael@0: michael@0: LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u " michael@0: "localWindow=%lld\n", this, bytes, mLocalSessionWindow)); michael@0: michael@0: // Don't necessarily ack every data packet. Only do it michael@0: // after a significant amount of data. michael@0: if ((mLocalSessionWindow > (ASpdySession::kInitialRwin - kMinimumToAck)) && michael@0: (mLocalSessionWindow > kEmergencyWindowThreshold)) michael@0: return; michael@0: michael@0: // Only send max bits of window updates at a time. michael@0: uint64_t toack64 = ASpdySession::kInitialRwin - mLocalSessionWindow; michael@0: uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU; michael@0: michael@0: LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", michael@0: this, toack)); michael@0: mLocalSessionWindow += toack; michael@0: michael@0: // room for this packet needs to be ensured before calling this function michael@0: char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; michael@0: mOutputQueueUsed += 12; michael@0: MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize); michael@0: michael@0: CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0); michael@0: toack = PR_htonl(toack); michael@0: memcpy(packet + 8, &toack, 4); michael@0: michael@0: LogIO(this, nullptr, "Session Window Update", packet, 12); michael@0: // dont flush here, this write can commonly be coalesced with others michael@0: } michael@0: michael@0: void michael@0: Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes) michael@0: { michael@0: // make sure there is room for 2 window updates even though michael@0: // we may not generate any. michael@0: EnsureOutputBuffer(16 * 2); michael@0: michael@0: UpdateLocalStreamWindow(stream, bytes); michael@0: UpdateLocalSessionWindow(bytes); michael@0: FlushOutputQueue(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::Close(nsresult aReason) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (mClosed) michael@0: return; michael@0: michael@0: LOG3(("Http2Session::Close %p %X", this, aReason)); michael@0: michael@0: mClosed = true; michael@0: michael@0: mStreamTransactionHash.Enumerate(ShutdownEnumerator, this); michael@0: mStreamIDHash.Clear(); michael@0: mStreamTransactionHash.Clear(); michael@0: michael@0: uint32_t goAwayReason; michael@0: if (mGoAwayReason != NO_HTTP_ERROR) { michael@0: goAwayReason = mGoAwayReason; michael@0: } else if (NS_SUCCEEDED(aReason)) { michael@0: goAwayReason = NO_HTTP_ERROR; michael@0: } else if (aReason == NS_ERROR_ILLEGAL_VALUE) { michael@0: goAwayReason = PROTOCOL_ERROR; michael@0: } else { michael@0: goAwayReason = INTERNAL_ERROR; michael@0: } michael@0: GenerateGoAway(goAwayReason); michael@0: mConnection = nullptr; michael@0: mSegmentReader = nullptr; michael@0: mSegmentWriter = nullptr; michael@0: } michael@0: michael@0: void michael@0: Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction, michael@0: nsresult aResult) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::CloseTransaction %p %p %x", this, aTransaction, aResult)); michael@0: michael@0: // Generally this arrives as a cancel event from the connection manager. michael@0: michael@0: // need to find the stream and call CleanupStream() on it. michael@0: Http2Stream *stream = mStreamTransactionHash.Get(aTransaction); michael@0: if (!stream) { michael@0: LOG3(("Http2Session::CloseTransaction %p %p %x - not found.", michael@0: this, aTransaction, aResult)); michael@0: return; michael@0: } michael@0: LOG3(("Http2Session::CloseTranscation probably a cancel. " michael@0: "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p", michael@0: this, aTransaction, aResult, stream->StreamID(), stream)); michael@0: CleanupStream(stream, aResult, CANCEL_ERROR); michael@0: ResumeRecv(); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAHttpSegmentReader michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: Http2Session::OnReadSegment(const char *buf, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsresult rv; michael@0: michael@0: // If we can release old queued data then we can try and write the new michael@0: // data directly to the network without using the output queue at all michael@0: if (mOutputQueueUsed) michael@0: FlushOutputQueue(); michael@0: michael@0: if (!mOutputQueueUsed && mSegmentReader) { michael@0: // try and write directly without output queue michael@0: rv = mSegmentReader->OnReadSegment(buf, count, countRead); michael@0: michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) { michael@0: *countRead = 0; michael@0: } else if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (*countRead < count) { michael@0: uint32_t required = count - *countRead; michael@0: // assuming a commitment() happened, this ensurebuffer is a nop michael@0: // but just in case the queuesize is too small for the required data michael@0: // call ensurebuffer(). michael@0: EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize); michael@0: memcpy(mOutputQueueBuffer.get(), buf + *countRead, required); michael@0: mOutputQueueUsed = required; michael@0: } michael@0: michael@0: *countRead = count; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // At this point we are going to buffer the new data in the output michael@0: // queue if it fits. By coalescing multiple small submissions into one larger michael@0: // buffer we can get larger writes out to the network later on. michael@0: michael@0: // This routine should not be allowed to fill up the output queue michael@0: // all on its own - at least kQueueReserved bytes are always left michael@0: // for other routines to use - but this is an all-or-nothing function, michael@0: // so if it will not all fit just return WOULD_BLOCK michael@0: michael@0: if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved)) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: michael@0: memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count); michael@0: mOutputQueueUsed += count; michael@0: *countRead = count; michael@0: michael@0: FlushOutputQueue(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment) michael@0: { michael@0: if (mOutputQueueUsed) michael@0: FlushOutputQueue(); michael@0: michael@0: // would there be enough room to buffer this if needed? michael@0: if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) michael@0: return NS_OK; michael@0: michael@0: // if we are using part of our buffers already, try again later unless michael@0: // forceCommitment is set. michael@0: if (mOutputQueueUsed && !forceCommitment) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: michael@0: if (mOutputQueueUsed) { michael@0: // normally we avoid the memmove of RealignOutputQueue, but we'll try michael@0: // it if forceCommitment is set before growing the buffer. michael@0: RealignOutputQueue(); michael@0: michael@0: // is there enough room now? michael@0: if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // resize the buffers as needed michael@0: EnsureOutputBuffer(count + kQueueReserved); michael@0: michael@0: MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved), michael@0: "buffer not as large as expected"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAHttpSegmentWriter michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: Http2Session::OnWriteSegment(char *buf, michael@0: uint32_t count, uint32_t *countWritten) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: nsresult rv; michael@0: michael@0: if (!mSegmentWriter) { michael@0: // the only way this could happen would be if Close() were called on the michael@0: // stack with WriteSegments() michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (mDownstreamState == PROCESSING_DATA_FRAME) { michael@0: michael@0: if (mInputFrameFinal && michael@0: mInputFrameDataRead == mInputFrameDataSize) { michael@0: *countWritten = 0; michael@0: SetNeedsCleanup(); michael@0: return NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: count = std::min(count, mInputFrameDataSize - mInputFrameDataRead); michael@0: rv = NetworkRead(mSegmentWriter, buf, count, countWritten); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: LogIO(this, mInputFrameDataStream, "Reading Data Frame", michael@0: buf, *countWritten); michael@0: michael@0: mInputFrameDataRead += *countWritten; michael@0: if (mPaddingLength && (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) { michael@0: // We are crossing from real HTTP data into the realm of padding. If michael@0: // we've actually crossed the line, we need to munge countWritten for the michael@0: // sake of goodness and sanity. No matter what, any future calls to michael@0: // WriteSegments need to just discard data until we reach the end of this michael@0: // frame. michael@0: ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING); michael@0: uint32_t paddingRead = mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead); michael@0: LOG3(("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d " michael@0: "crossed from HTTP data into padding (%d of %d) countWritten=%d", michael@0: this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead, michael@0: paddingRead, mPaddingLength, *countWritten)); michael@0: *countWritten -= paddingRead; michael@0: LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d", michael@0: this, mInputFrameID, *countWritten)); michael@0: } michael@0: michael@0: mInputFrameDataStream->UpdateTransportReadEvents(*countWritten); michael@0: if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal) michael@0: ResetDownstreamState(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) { michael@0: michael@0: if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && michael@0: mInputFrameFinal) { michael@0: *countWritten = 0; michael@0: SetNeedsCleanup(); michael@0: return NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: count = std::min(count, michael@0: mFlatHTTPResponseHeaders.Length() - michael@0: mFlatHTTPResponseHeadersOut); michael@0: memcpy(buf, michael@0: mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut, michael@0: count); michael@0: mFlatHTTPResponseHeadersOut += count; michael@0: *countWritten = count; michael@0: michael@0: if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) { michael@0: if (!mInputFrameFinal) { michael@0: // If more frames are expected in this stream, then reset the state so they can be michael@0: // handled. Otherwise (e.g. a 0 length response with the fin on the incoming headers) michael@0: // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can michael@0: // cleanup the stream. michael@0: ResetDownstreamState(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: void michael@0: Http2Session::SetNeedsCleanup() michael@0: { michael@0: LOG3(("Http2Session::SetNeedsCleanup %p - recorded downstream fin of " michael@0: "stream %p 0x%X", this, mInputFrameDataStream, michael@0: mInputFrameDataStream->StreamID())); michael@0: michael@0: // This will result in Close() being called michael@0: MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set"); michael@0: mNeedsCleanup = mInputFrameDataStream; michael@0: ResetDownstreamState(); michael@0: } michael@0: michael@0: void michael@0: Http2Session::ConnectPushedStream(Http2Stream *stream) michael@0: { michael@0: mReadyForRead.Push(stream); michael@0: ForceRecv(); michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::BufferOutput(const char *buf, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: nsAHttpSegmentReader *old = mSegmentReader; michael@0: mSegmentReader = nullptr; michael@0: nsresult rv = OnReadSegment(buf, count, countRead); michael@0: mSegmentReader = old; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::ConfirmTLSProfile() michael@0: { michael@0: if (mTLSProfileConfirmed) michael@0: return NS_OK; michael@0: michael@0: LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", michael@0: this, mConnection.get())); michael@0: michael@0: if (!gHttpHandler->EnforceHttp2TlsProfile()) { michael@0: LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this)); michael@0: mTLSProfileConfirmed = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mConnection) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr securityInfo; michael@0: mConnection->GetSecurityInfo(getter_AddRefs(securityInfo)); michael@0: nsCOMPtr ssl = do_QueryInterface(securityInfo); michael@0: LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, ssl.get())); michael@0: if (!ssl) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: int16_t version = ssl->GetSSLVersionUsed(); michael@0: LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version)); michael@0: if (version < nsISSLSocketControl::TLS_VERSION_1_2) { michael@0: LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", this)); michael@0: RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY); michael@0: } michael@0: michael@0: #if 0 michael@0: uint16_t kea = ssl->GetKEAUsed(); michael@0: if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) { michael@0: LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n", michael@0: this, kea)); michael@0: RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY); michael@0: } michael@0: #endif michael@0: michael@0: /* TODO: Enforce DHE >= 2048 || ECDHE >= 128 */ michael@0: michael@0: /* We are required to send SNI. We do that already, so no check is done michael@0: * here to make sure we did. */ michael@0: michael@0: /* We really should check to ensure TLS compression isn't enabled on michael@0: * this connection. However, we never enable TLS compression on our end, michael@0: * anyway, so it'll never be on. All the same, see https://bugzil.la/965881 michael@0: * for the possibility for an interface to ensure it never gets turned on. */ michael@0: michael@0: mTLSProfileConfirmed = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Modified methods of nsAHttpConnection michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller)); michael@0: michael@0: // a trapped signal from the http transaction to the connection that michael@0: // it is no longer blocked on read. michael@0: michael@0: Http2Stream *stream = mStreamTransactionHash.Get(caller); michael@0: if (!stream || !VerifyStream(stream)) { michael@0: LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found", michael@0: this, caller)); michael@0: return; michael@0: } michael@0: michael@0: LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", michael@0: this, stream->StreamID())); michael@0: michael@0: mReadyForWrite.Push(stream); michael@0: } michael@0: michael@0: void michael@0: Http2Session::TransactionHasDataToWrite(Http2Stream *stream) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", michael@0: this, stream, stream->StreamID())); michael@0: michael@0: mReadyForWrite.Push(stream); michael@0: SetWriteCallbacks(); michael@0: } michael@0: michael@0: bool michael@0: Http2Session::IsPersistent() michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::TakeTransport(nsISocketTransport **, michael@0: nsIAsyncInputStream **, nsIAsyncOutputStream **) michael@0: { michael@0: MOZ_ASSERT(false, "TakeTransport of Http2Session"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsHttpConnection * michael@0: Http2Session::TakeHttpConnection() michael@0: { michael@0: MOZ_ASSERT(false, "TakeHttpConnection of Http2Session"); michael@0: return nullptr; michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::CancelPipeline(nsresult reason) michael@0: { michael@0: // we don't pipeline inside http/2, so this isn't an issue michael@0: return 0; michael@0: } michael@0: michael@0: nsAHttpTransaction::Classifier michael@0: Http2Session::Classification() michael@0: { michael@0: if (!mConnection) michael@0: return nsAHttpTransaction::CLASS_GENERAL; michael@0: return mConnection->Classification(); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // unused methods of nsAHttpTransaction michael@0: // We can be sure of this because Http2Session is only constructed in michael@0: // nsHttpConnection and is never passed out of that object michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: Http2Session::SetConnection(nsAHttpConnection *) michael@0: { michael@0: // This is unexpected michael@0: MOZ_ASSERT(false, "Http2Session::SetConnection()"); michael@0: } michael@0: michael@0: void michael@0: Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **) michael@0: { michael@0: // This is unexpected michael@0: MOZ_ASSERT(false, "Http2Session::GetSecurityCallbacks()"); michael@0: } michael@0: michael@0: void michael@0: Http2Session::SetProxyConnectFailed() michael@0: { michael@0: MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()"); michael@0: } michael@0: michael@0: bool michael@0: Http2Session::IsDone() michael@0: { michael@0: return !mStreamTransactionHash.Count(); michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::Status() michael@0: { michael@0: MOZ_ASSERT(false, "Http2Session::Status()"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::Caps() michael@0: { michael@0: MOZ_ASSERT(false, "Http2Session::Caps()"); michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: Http2Session::SetDNSWasRefreshed() michael@0: { michael@0: MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()"); michael@0: } michael@0: michael@0: uint64_t michael@0: Http2Session::Available() michael@0: { michael@0: MOZ_ASSERT(false, "Http2Session::Available()"); michael@0: return 0; michael@0: } michael@0: michael@0: nsHttpRequestHead * michael@0: Http2Session::RequestHead() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(false, michael@0: "Http2Session::RequestHead() " michael@0: "should not be called after http/2 is setup"); michael@0: return NULL; michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::Http1xTransactionCount() michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: // used as an enumerator by TakeSubTransactions() michael@0: static PLDHashOperator michael@0: TakeStream(nsAHttpTransaction *key, michael@0: nsAutoPtr &stream, michael@0: void *closure) michael@0: { michael@0: nsTArray > *list = michael@0: static_cast > *>(closure); michael@0: michael@0: list->AppendElement(key); michael@0: michael@0: // removing the stream from the hash will delete the stream michael@0: // and drop the transaction reference the hash held michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::TakeSubTransactions( michael@0: nsTArray > &outTransactions) michael@0: { michael@0: // Generally this cannot be done with http/2 as transactions are michael@0: // started right away. michael@0: michael@0: LOG3(("Http2Session::TakeSubTransactions %p\n", this)); michael@0: michael@0: if (mConcurrentHighWater > 0) michael@0: return NS_ERROR_ALREADY_OPENED; michael@0: michael@0: LOG3((" taking %d\n", mStreamTransactionHash.Count())); michael@0: michael@0: mStreamTransactionHash.Enumerate(TakeStream, &outTransactions); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::AddTransaction(nsAHttpTransaction *) michael@0: { michael@0: // This API is meant for pipelining, Http2Session's should be michael@0: // extended with AddStream() michael@0: michael@0: MOZ_ASSERT(false, michael@0: "Http2Session::AddTransaction() should not be called"); michael@0: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: uint32_t michael@0: Http2Session::PipelineDepth() michael@0: { michael@0: return IsDone() ? 0 : 1; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::SetPipelinePosition(int32_t position) michael@0: { michael@0: // This API is meant for pipelining, Http2Session's should be michael@0: // extended with AddStream() michael@0: michael@0: MOZ_ASSERT(false, michael@0: "Http2Session::SetPipelinePosition() should not be called"); michael@0: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: int32_t michael@0: Http2Session::PipelinePosition() michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Pass through methods of nsAHttpConnection michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsAHttpConnection * michael@0: Http2Session::Connection() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: return mConnection; michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction, michael@0: nsHttpRequestHead *requestHead, michael@0: nsHttpResponseHead *responseHead, bool *reset) michael@0: { michael@0: return mConnection->OnHeadersAvailable(transaction, michael@0: requestHead, michael@0: responseHead, michael@0: reset); michael@0: } michael@0: michael@0: bool michael@0: Http2Session::IsReused() michael@0: { michael@0: return mConnection->IsReused(); michael@0: } michael@0: michael@0: nsresult michael@0: Http2Session::PushBack(const char *buf, uint32_t len) michael@0: { michael@0: return mConnection->PushBack(buf, len); michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla