diff --git a/netwerk/protocol/http/SpdySession3.cpp b/netwerk/protocol/http/SpdySession3.cpp index 2d4aa30d3ac9..2f6d0dafa21a 100644 --- a/netwerk/protocol/http/SpdySession3.cpp +++ b/netwerk/protocol/http/SpdySession3.cpp @@ -79,15 +79,15 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction, mInputFrameDataLast(false), mInputFrameDataStream(nsnull), mNeedsCleanup(nsnull), - mDecompressBufferSize(kDefaultBufferSize), - mDecompressBufferUsed(0), mShouldGoAway(false), mClosed(false), mCleanShutdown(false), + mDataPending(false), mGoAwayID(0), mMaxConcurrent(kDefaultMaxConcurrent), mConcurrent(0), mServerPushedResources(0), + mServerInitialWindow(kDefaultServerRwin), mOutputQueueSize(kDefaultQueueSize), mOutputQueueUsed(0), mOutputQueueSent(0), @@ -105,11 +105,12 @@ SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction, mStreamTransactionHash.Init(); mConnection = aHttpTransaction->Connection(); mInputFrameBuffer = new char[mInputFrameBufferSize]; - mDecompressBuffer = new char[mDecompressBufferSize]; mOutputQueueBuffer = new char[mOutputQueueSize]; zlibInit(); mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); + GenerateSettings(); + AddStream(aHttpTransaction, firstPriority); mLastDataReadEpoch = mLastReadEpoch; @@ -288,7 +289,7 @@ SpdySession3::ReadTimeoutTick(PRIntervalTime now) return; } - LOG(("SpdySession3::ReadTimeoutTick %p generating ping 0x%x\n", + LOG(("SpdySession3::ReadTimeoutTick %p generating ping 0x%X\n", this, mNextPingID)); if (mNextPingID == 0xffffffff) { @@ -518,7 +519,7 @@ SpdySession3::GetWriteQueueSize() { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - return mUrgentForWrite.GetSize() + mReadyForWrite.GetSize(); + return mReadyForWrite.GetSize(); } void @@ -586,213 +587,37 @@ SpdySession3::zlibInit() deflateInit(&mUpstreamZlib, Z_DEFAULT_COMPRESSION); deflateSetDictionary(&mUpstreamZlib, - reinterpret_cast - (SpdyStream3::kDictionary), - strlen(SpdyStream3::kDictionary) + 1); - + SpdyStream3::kDictionary, + sizeof(SpdyStream3::kDictionary)); } +// Need to decompress some data in order to keep the compression +// context correct, but we really don't care what the result is nsresult -SpdySession3::DownstreamUncompress(char *blockStart, PRUint32 blockLen) +SpdySession3::UncompressAndDiscard(PRUint32 offset, + PRUint32 blockLen) { - mDecompressBufferUsed = 0; - + char *blockStart = mInputFrameBuffer + offset; + unsigned char trash[2048]; mDownstreamZlib.avail_in = blockLen; mDownstreamZlib.next_in = reinterpret_cast(blockStart); do { - mDownstreamZlib.next_out = - reinterpret_cast(mDecompressBuffer.get()) + - mDecompressBufferUsed; - mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed; + mDownstreamZlib.next_out = trash; + mDownstreamZlib.avail_out = sizeof(trash); int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH); if (zlib_rv == Z_NEED_DICT) - inflateSetDictionary(&mDownstreamZlib, - reinterpret_cast - (SpdyStream3::kDictionary), - strlen(SpdyStream3::kDictionary) + 1); - + inflateSetDictionary(&mDownstreamZlib, SpdyStream3::kDictionary, + sizeof(SpdyStream3::kDictionary)); + if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR) return NS_ERROR_FAILURE; - - mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed - - mDownstreamZlib.avail_out; - - // When there is no more output room, but input still available then - // increase the output space - if (zlib_rv == Z_OK && - !mDownstreamZlib.avail_out && mDownstreamZlib.avail_in) { - LOG3(("SpdySession3::DownstreamUncompress %p Large Headers - so far %d", - this, mDecompressBufferSize)); - EnsureBuffer(mDecompressBuffer, - mDecompressBufferSize + 4096, - mDecompressBufferUsed, - mDecompressBufferSize); - } } while (mDownstreamZlib.avail_in); return NS_OK; } -nsresult -SpdySession3::FindHeader(nsCString name, - nsDependentCSubstring &value) -{ - const unsigned char *nvpair = reinterpret_cast - (mDecompressBuffer.get()) + 2; - const unsigned char *lastHeaderByte = reinterpret_cast - (mDecompressBuffer.get()) + mDecompressBufferUsed; - if (lastHeaderByte < nvpair) - return NS_ERROR_ILLEGAL_VALUE; - PRUint16 numPairs = - PR_ntohs(reinterpret_cast(mDecompressBuffer.get())[0]); - for (PRUint16 index = 0; index < numPairs; ++index) { - if (lastHeaderByte < nvpair + 2) - return NS_ERROR_ILLEGAL_VALUE; - PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; - if (lastHeaderByte < nvpair + 2 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - nsDependentCSubstring nameString = - Substring(reinterpret_cast(nvpair) + 2, - reinterpret_cast(nvpair) + 2 + nameLen); - if (lastHeaderByte < nvpair + 4 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; - if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) - return NS_ERROR_ILLEGAL_VALUE; - if (nameString.Equals(name)) { - value.Assign(((char *)nvpair) + 4 + nameLen, valueLen); - return NS_OK; - } - nvpair += 4 + nameLen + valueLen; - } - return NS_ERROR_NOT_AVAILABLE; -} - -nsresult -SpdySession3::ConvertHeaders(nsDependentCSubstring &status, - nsDependentCSubstring &version) -{ - mFlatHTTPResponseHeaders.Truncate(); - mFlatHTTPResponseHeadersOut = 0; - mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64); - - // Connection, Keep-Alive and chunked transfer encodings are to be - // removed. - - // Content-Length is 'advisory'.. we will not strip it because it can - // create UI feedback. - - mFlatHTTPResponseHeaders.Append(version); - mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" ")); - mFlatHTTPResponseHeaders.Append(status); - mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); - - const unsigned char *nvpair = reinterpret_cast - (mDecompressBuffer.get()) + 2; - const unsigned char *lastHeaderByte = reinterpret_cast - (mDecompressBuffer.get()) + mDecompressBufferUsed; - - if (lastHeaderByte < nvpair) - return NS_ERROR_ILLEGAL_VALUE; - - PRUint16 numPairs = - PR_ntohs(reinterpret_cast(mDecompressBuffer.get())[0]); - - for (PRUint16 index = 0; index < numPairs; ++index) { - if (lastHeaderByte < nvpair + 2) - return NS_ERROR_ILLEGAL_VALUE; - - PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; - if (lastHeaderByte < nvpair + 2 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - - nsDependentCSubstring nameString = - Substring(reinterpret_cast(nvpair) + 2, - reinterpret_cast(nvpair) + 2 + nameLen); - - if (lastHeaderByte < nvpair + 4 + nameLen) - return NS_ERROR_ILLEGAL_VALUE; - - // Look for illegal characters in the nameString. - // This includes upper case characters and nulls (as they will - // break the fixup-nulls-in-value-string algorithm) - // Look for upper case characters in the name. They are illegal. - for (char *cPtr = nameString.BeginWriting(); - cPtr && cPtr < nameString.EndWriting(); - ++cPtr) { - if (*cPtr <= 'Z' && *cPtr >= 'A') { - nsCString toLog(nameString); - - LOG3(("SpdySession3::ConvertHeaders session=%p stream=%p " - "upper case response header found. [%s]\n", - this, mInputFrameDataStream, toLog.get())); - - return NS_ERROR_ILLEGAL_VALUE; - } - - // check for null characters - if (*cPtr == '\0') - return NS_ERROR_ILLEGAL_VALUE; - } - - // HTTP Chunked responses are not legal over spdy. We do not need - // to look for chunked specifically because it is the only HTTP - // allowed default encoding and we did not negotiate further encodings - // via TE - if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { - LOG3(("SpdySession3::ConvertHeaders session=%p stream=%p " - "transfer-encoding found. Chunked is invalid and no TE sent.", - this, mInputFrameDataStream)); - - return NS_ERROR_ILLEGAL_VALUE; - } - - PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; - if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) - return NS_ERROR_ILLEGAL_VALUE; - - if (!nameString.Equals(NS_LITERAL_CSTRING("version")) && - !nameString.Equals(NS_LITERAL_CSTRING("status")) && - !nameString.Equals(NS_LITERAL_CSTRING("connection")) && - !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { - nsDependentCSubstring valueString = - Substring(reinterpret_cast(nvpair) + 4 + nameLen, - reinterpret_cast(nvpair) + 4 + nameLen + - valueLen); - - mFlatHTTPResponseHeaders.Append(nameString); - mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": ")); - - // expand NULL bytes in the value string - for (char *cPtr = valueString.BeginWriting(); - cPtr && cPtr < valueString.EndWriting(); - ++cPtr) { - if (*cPtr != 0) { - mFlatHTTPResponseHeaders.Append(*cPtr); - continue; - } - - // NULLs are really "\r\nhdr: " - mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); - mFlatHTTPResponseHeaders.Append(nameString); - mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": ")); - } - - mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); - } - nvpair += 4 + nameLen + valueLen; - } - - mFlatHTTPResponseHeaders.Append( - NS_LITERAL_CSTRING("X-Firefox-Spdy: 3\r\n\r\n")); - LOG (("decoded response headers are:\n%s", - mFlatHTTPResponseHeaders.get())); - - return NS_OK; -} - void SpdySession3::GeneratePing(PRUint32 aID) { @@ -805,7 +630,7 @@ SpdySession3::GeneratePing(PRUint32 aID) mOutputQueueUsed += 12; packet[0] = kFlag_Control; - packet[1] = 2; /* version 2 */ + packet[1] = kVersion; packet[2] = 0; packet[3] = CONTROL_TYPE_PING; packet[4] = 0; /* flags */ @@ -816,6 +641,7 @@ SpdySession3::GeneratePing(PRUint32 aID) aID = PR_htonl(aID); memcpy(packet + 8, &aID, 4); + LogIO(this, nsnull, "Generate Ping", packet, 12); FlushOutputQueue(); } @@ -831,7 +657,7 @@ SpdySession3::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID) mOutputQueueUsed += 16; packet[0] = kFlag_Control; - packet[1] = 2; /* version 2 */ + packet[1] = kVersion; packet[2] = 0; packet[3] = CONTROL_TYPE_RST_STREAM; packet[4] = 0; /* flags */ @@ -844,6 +670,7 @@ SpdySession3::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID) aStatusCode = PR_htonl(aStatusCode); memcpy(packet + 12, &aStatusCode, 4); + LogIO(this, nsnull, "Generate Reset", packet, 16); FlushOutputQueue(); } @@ -860,13 +687,41 @@ SpdySession3::GenerateGoAway() memset(packet, 0, 12); packet[0] = kFlag_Control; - packet[1] = 2; /* version 2 */ + packet[1] = kVersion; packet[3] = CONTROL_TYPE_GOAWAY; packet[7] = 4; /* data length */ // last-good-stream-id are bytes 8-11, when we accept server push this will // need to be set non zero + LogIO(this, nsnull, "Generate GoAway", packet, 12); + FlushOutputQueue(); +} + +void +SpdySession3::GenerateSettings() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG3(("SpdySession3::GenerateSettings %p\n", this)); + + static const PRUint32 dataLen = 12; + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 8 + dataLen, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 8 + dataLen; + + memset(packet, 0, 8 + dataLen); + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_SETTINGS; + packet[7] = dataLen; + + packet[11] = 1; /* 1 setting */ + packet[15] = SETTINGS_TYPE_INITIAL_WINDOW; + PRUint32 rwin = PR_htonl(kInitialRwin); + memcpy(packet + 16, &rwin, 4); + + LogIO(this, nsnull, "Generate Settings", packet, 8 + dataLen); FlushOutputQueue(); } @@ -915,8 +770,8 @@ SpdySession3::VerifyStream(SpdyStream3 *aStream, PRUint32 aOptionalID = 0) return true; } while (0); - LOG(("SpdySession3 %p VerifyStream Failure %p stream->id=0x%x " - "optionalID=0x%x trans=%p test=%d\n", + LOG(("SpdySession3 %p VerifyStream Failure %p stream->id=0x%X " + "optionalID=0x%X trans=%p test=%d\n", this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(), test)); NS_ABORT_IF_FALSE(false, "VerifyStream"); @@ -928,7 +783,7 @@ SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult, rstReason aResetCode) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - LOG3(("SpdySession3::CleanupStream %p %p 0x%x %X\n", + LOG3(("SpdySession3::CleanupStream %p %p 0x%X %X\n", this, aStream, aStream->StreamID(), aResult)); if (!VerifyStream(aStream)) { @@ -960,15 +815,6 @@ SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult, mReadyForWrite.Push(stream); } - // Check the streams blocked on urgent (i.e. window update) writing. - // This should also be short. - size = mUrgentForWrite.GetSize(); - for (PRUint32 count = 0; count < size; ++count) { - SpdyStream3 *stream = static_cast(mUrgentForWrite.PopFront()); - if (stream != aStream) - mUrgentForWrite.Push(stream); - } - // Check the streams queued for activation. Because we normally accept a high // level of parallelization this should also be short. size = mQueuedStreams.GetSize(); @@ -1030,8 +876,8 @@ SpdySession3::HandleSynStream(SpdySession3 *self) // Need to decompress the headers even though we aren't using them yet in // order to keep the compression context consistent for other syn_reply frames - nsresult rv = self->DownstreamUncompress(self->mInputFrameBuffer + 18, - self->mInputFrameDataSize - 10); + nsresult rv = + self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10); if (NS_FAILED(rv)) { LOG(("SpdySession3::HandleSynStream uncompress failed\n")); return rv; @@ -1062,24 +908,13 @@ SpdySession3::HandleSynReply(SpdySession3 *self) NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY, "wrong control type"); - if (self->mInputFrameDataSize < 8) { + if (self->mInputFrameDataSize < 4) { LOG3(("SpdySession3::HandleSynReply %p SYN REPLY too short data=%d", self, self->mInputFrameDataSize)); // A framing error is a session wide error that cannot be recovered return NS_ERROR_ILLEGAL_VALUE; } - // Uncompress the headers into mDecompressBuffer, leaving them in - // spdy format for the time being. Make certain to do this - // step before any error handling that might abort the stream but not - // the session becuase the session compression context will become - // inconsistent if all of the compressed data is not processed. - if (NS_FAILED(self->DownstreamUncompress(self->mInputFrameBuffer + 14, - self->mInputFrameDataSize - 6))) { - LOG(("SpdySession3::HandleSynReply uncompress failed\n")); - return NS_ERROR_FAILURE; - } - LOG3(("SpdySession3::HandleSynReply %p lookup via streamID in syn_reply.\n", self)); PRUint32 streamID = @@ -1089,17 +924,85 @@ SpdySession3::HandleSynReply(SpdySession3 *self) return rv; if (!self->mInputFrameDataStream) { + // Cannot find stream. We can continue the SPDY session, but we need to + // uncompress the header block to maintain the correct compression context + LOG3(("SpdySession3::HandleSynReply %p lookup streamID in syn_reply " - "0x%X failed. NextStreamID = 0x%x", self, streamID, - self->mNextStreamID)); + "0x%X failed. NextStreamID = 0x%X\n", + self, streamID, self->mNextStreamID)); + if (streamID >= self->mNextStreamID) self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + if (NS_FAILED(self->UncompressAndDiscard(12, + self->mInputFrameDataSize - 4))) { + LOG(("SpdySession3::HandleSynReply uncompress failed\n")); + // this is fatal to the session + return NS_ERROR_FAILURE; + } self->ResetDownstreamState(); return NS_OK; } - rv = self->HandleSynReplyForValidStream(); + // Uncompress the headers into a stream specific buffer, leaving them in + // spdy format for the time being. Make certain to do this + // step before any error handling that might abort the stream but not + // the session becuase the session compression context will become + // inconsistent if all of the compressed data is not processed. + rv = self->mInputFrameDataStream->Uncompress(&self->mDownstreamZlib, + self->mInputFrameBuffer + 12, + self->mInputFrameDataSize - 4); + + if (NS_FAILED(rv)) { + LOG(("SpdySession3::HandleSynReply uncompress failed\n")); + return NS_ERROR_FAILURE; + } + + if (self->mInputFrameDataStream->GetFullyOpen()) { + // "If an endpoint receives multiple SYN_REPLY frames for the same active + // stream ID, it MUST issue a stream error (Section 2.4.2) with the error + // code STREAM_IN_USE." + // + // "STREAM_ALREADY_CLOSED. The endpoint received a data or SYN_REPLY + // frame for a stream which is half closed." + // + // If the stream is open then just RST_STREAM with STREAM_IN_USE + // If the stream is half closed then RST_STREAM with STREAM_ALREADY_CLOSED + // abort the session + // + LOG3(("SpdySession3::HandleSynReply %p dup SYN_REPLY for 0x%X" + " recvdfin=%d", self, self->mInputFrameDataStream->StreamID(), + self->mInputFrameDataStream->RecvdFin())); + + self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ALREADY_OPENED, + self->mInputFrameDataStream->RecvdFin() ? + RST_STREAM_ALREADY_CLOSED : RST_STREAM_IN_USE); + self->ResetDownstreamState(); + return NS_OK; + } + self->mInputFrameDataStream->SetFullyOpen(); + + self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; + self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize); + self->mLastDataReadEpoch = self->mLastReadEpoch; + + if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) { + LOG3(("SynReply %p had undefined flag set 0x%X\n", self, streamID)); + self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, + RST_PROTOCOL_ERROR); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!self->mInputFrameDataLast) { + // don't process the headers yet as there could be more coming from HEADERS + // frames + self->ResetDownstreamState(); + return NS_OK; + } + + rv = self->ResponseHeadersComplete(); if (rv == NS_ERROR_ILLEGAL_VALUE) { LOG3(("SpdySession3::HandleSynReply %p PROTOCOL_ERROR detected 0x%X\n", self, streamID)); @@ -1107,69 +1010,29 @@ SpdySession3::HandleSynReply(SpdySession3 *self) self->ResetDownstreamState(); rv = NS_OK; } - return rv; } -// HandleSynReplyForValidStream() returns NS_ERROR_ILLEGAL_VALUE when the stream +// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream // should be reset with a PROTOCOL_ERROR, NS_OK when the SYN_REPLY was // fine, and any other error is fatal to the session. nsresult -SpdySession3::HandleSynReplyForValidStream() +SpdySession3::ResponseHeadersComplete() { - if (mInputFrameDataStream->GetFullyOpen()) { - // "If an endpoint receives multiple SYN_REPLY frames for the same active - // stream ID, it must drop the stream, and send a RST_STREAM for the - // stream with the error PROTOCOL_ERROR." - // - // If the stream is open then just RST_STREAM and move on, otherwise - // abort the session - return mInputFrameDataStream->RecvdFin() ? - NS_ERROR_ALREADY_OPENED : NS_ERROR_ILLEGAL_VALUE; - } - mInputFrameDataStream->SetFullyOpen(); - - mInputFrameDataLast = mInputFrameBuffer[4] & kFlag_Data_FIN; - - if (mInputFrameBuffer[4] & kFlag_Data_UNI) { - LOG3(("SynReply had unidirectional flag set on it - nonsensical")); - return NS_ERROR_ILLEGAL_VALUE; - } - - LOG3(("SpdySession3::HandleSynReplyForValidStream %p SYN_REPLY for 0x%X " - "fin=%d", + LOG3(("SpdySession3::ResponseHeadersComplete %p for 0x%X fin=%d", this, mInputFrameDataStream->StreamID(), mInputFrameDataLast)); - - Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, - mInputFrameDataSize - 6); - if (mDecompressBufferUsed) { - PRUint32 ratio = - (mInputFrameDataSize - 6) * 100 / mDecompressBufferUsed; - Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); - } - - // status and version are required. - nsDependentCSubstring status, version; - nsresult rv = FindHeader(NS_LITERAL_CSTRING("status"), status); - if (NS_FAILED(rv)) - return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv; - - rv = FindHeader(NS_LITERAL_CSTRING("version"), version); - if (NS_FAILED(rv)) - return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv; // The spdystream needs to see flattened http headers // Uncompressed spdy format headers currently live in - // mDeccompressBuffer - convert that to HTTP format in - // mFlatHTTPResponseHeaders in ConvertHeaders() + // SpdyStream3::mDecompressBuffer - convert that to HTTP format in + // mFlatHTTPResponseHeaders via ConvertHeaders() - rv = ConvertHeaders(status, version); + mFlatHTTPResponseHeadersOut = 0; + nsresult rv = mInputFrameDataStream->ConvertHeaders(mFlatHTTPResponseHeaders); if (NS_FAILED(rv)) return rv; - mInputFrameDataStream->UpdateTransportReadEvents(mInputFrameDataSize); - mLastDataReadEpoch = mLastReadEpoch; - ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY); + ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); return NS_OK; } @@ -1203,8 +1066,10 @@ SpdySession3::HandleRstStream(SpdySession3 *self) } if (self->mDownstreamRstReason == RST_INVALID_STREAM || + self->mDownstreamRstReason == RST_STREAM_IN_USE || self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) { // basically just ignore this + LOG3(("SpdySession3::HandleRstStream %p No Reset Processing Needed.\n")); self->ResetDownstreamState(); return NS_OK; } @@ -1227,6 +1092,16 @@ SpdySession3::HandleRstStream(SpdySession3 *self) return NS_OK; } +PLDHashOperator +SpdySession3::UpdateServerRwinEnumerator(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + PRInt32 delta = *(static_cast(closure)); + stream->UpdateRemoteWindow(delta); + return PL_DHASH_NEXT; +} + nsresult SpdySession3::HandleSettings(SpdySession3 *self) { @@ -1255,16 +1130,11 @@ SpdySession3::HandleSettings(SpdySession3 *self) self, numEntries)); for (PRUint32 index = 0; index < numEntries; ++index) { - // To clarify the v2 spec: - // Each entry is a 24 bits of a little endian id - // followed by 8 bits of flags - // followed by a 32 bit big endian value - unsigned char *setting = reinterpret_cast (self->mInputFrameBuffer.get()) + 12 + index * 8; - PRUint32 id = (setting[2] << 16) + (setting[1] << 8) + setting[0]; - PRUint32 flags = setting[3]; + PRUint32 flags = setting[0]; + PRUint32 id = PR_ntohl(reinterpret_cast(setting)[0]) & 0xffffff; PRUint32 value = PR_ntohl(reinterpret_cast(setting)[1]); LOG3(("Settings ID %d, Flags %X, Value %d", id, flags, value)); @@ -1298,6 +1168,14 @@ SpdySession3::HandleSettings(SpdySession3 *self) case SETTINGS_TYPE_INITIAL_WINDOW: Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10); + { + PRInt32 delta = value - self->mServerInitialWindow; + self->mServerInitialWindow = value; + + // we need to add the delta to all open streams (delta can be negative) + self->mStreamTransactionHash.Enumerate(UpdateServerRwinEnumerator, + &delta); + } break; default: @@ -1316,11 +1194,8 @@ SpdySession3::HandleNoop(SpdySession3 *self) NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_NOOP, "wrong control type"); - if (self->mInputFrameDataSize != 0) { - LOG3(("SpdySession3::HandleNoop %p NOP had data %d", - self, self->mInputFrameDataSize)); - return NS_ERROR_ILLEGAL_VALUE; - } + // Should not be receiving noop frames in spdy/3, so we'll just + // make a log and ignore it LOG3(("SpdySession3::HandleNoop %p NOP.", self)); @@ -1388,7 +1263,7 @@ SpdySession3::HandleHeaders(SpdySession3 *self) NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_HEADERS, "wrong control type"); - if (self->mInputFrameDataSize < 10) { + if (self->mInputFrameDataSize < 4) { LOG3(("SpdySession3::HandleHeaders %p HEADERS had wrong amount of data %d", self, self->mInputFrameDataSize)); return NS_ERROR_ILLEGAL_VALUE; @@ -1396,19 +1271,69 @@ SpdySession3::HandleHeaders(SpdySession3 *self) PRUint32 streamID = PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); - - // this is actually not legal in the HTTP mapping of SPDY. All - // headers are in the syn or syn reply. Log and ignore it. - - // in v3 this will be legal and we must remember to note - // NS_NET_STATUS_RECEIVING_FROM from it - - LOG3(("SpdySession3::HandleHeaders %p HEADERS for Stream 0x%X. " - "They are ignored in the HTTP/SPDY mapping.", + LOG3(("SpdySession3::HandleHeaders %p HEADERS for Stream 0x%X.\n", self, streamID)); + nsresult rv = self->SetInputFrameDataStream(streamID); + if (NS_FAILED(rv)) + return rv; + + if (!self->mInputFrameDataStream) { + LOG3(("SpdySession3::HandleHeaders %p lookup streamID 0x%X failed.\n", + self, streamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + if (NS_FAILED(self->UncompressAndDiscard(12, + self->mInputFrameDataSize - 4))) { + LOG(("SpdySession3::HandleSynReply uncompress failed\n")); + // this is fatal to the session + return NS_ERROR_FAILURE; + } + self->ResetDownstreamState(); + return NS_OK; + } + + // Uncompress the headers into local buffers in the SpdyStream, leaving + // them in spdy format for the time being. Make certain to do this + // step before any error handling that might abort the stream but not + // the session becuase the session compression context will become + // inconsistent if all of the compressed data is not processed. + rv = self->mInputFrameDataStream->Uncompress(&self->mDownstreamZlib, + self->mInputFrameBuffer + 12, + self->mInputFrameDataSize - 4); + if (NS_FAILED(rv)) { + LOG(("SpdySession3::HandleHeaders uncompress failed\n")); + return NS_ERROR_FAILURE; + } + + self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; + self->mInputFrameDataStream-> + UpdateTransportReadEvents(self->mInputFrameDataSize); self->mLastDataReadEpoch = self->mLastReadEpoch; - self->ResetDownstreamState(); - return NS_OK; + + if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) { + LOG3(("Headers %p had undefined flag set 0x%X\n", self, streamID)); + self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, + RST_PROTOCOL_ERROR); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!self->mInputFrameDataLast) { + // don't process the headers yet as there could be more HEADERS frames + self->ResetDownstreamState(); + return NS_OK; + } + + rv = self->ResponseHeadersComplete(); + if (rv == NS_ERROR_ILLEGAL_VALUE) { + LOG3(("SpdySession3::HanndleHeaders %p PROTOCOL_ERROR detected 0x%X\n", + self, streamID)); + self->CleanupStream(self->mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); + self->ResetDownstreamState(); + rv = NS_OK; + } + return rv; } nsresult @@ -1416,9 +1341,48 @@ SpdySession3::HandleWindowUpdate(SpdySession3 *self) { NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE, "wrong control type"); - LOG3(("SpdySession3::HandleWindowUpdate %p WINDOW UPDATE was " - "received. WINDOW UPDATE is no longer defined in v2. Ignoring.", - self)); + + if (self->mInputFrameDataSize < 8) { + LOG3(("SpdySession3::HandleWindowUpdate %p Window Update wrong length %d\n", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 delta = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]); + delta &= 0x7fffffff; + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + streamID &= 0x7fffffff; + + LOG3(("SpdySession3::HandleWindowUpdate %p len=%d for Stream 0x%X.\n", + self, delta, streamID)); + nsresult rv = self->SetInputFrameDataStream(streamID); + if (NS_FAILED(rv)) + return rv; + + if (!self->mInputFrameDataStream) { + LOG3(("SpdySession3::HandleWindowUpdate %p lookup streamID 0x%X failed.\n", + self, streamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + self->ResetDownstreamState(); + return NS_OK; + } + + PRInt64 oldRemoteWindow = self->mInputFrameDataStream->RemoteWindow(); + self->mInputFrameDataStream->UpdateRemoteWindow(delta); + + LOG3(("SpdySession3::HandleWindowUpdate %p stream 0x%X window " + "%d increased by %d.\n", self, streamID, oldRemoteWindow, delta)); + + // If the stream had a <=0 window, that has now opened + // schedule it for writing again + if (oldRemoteWindow <= 0 && + self->mInputFrameDataStream->RemoteWindow() > 0) { + self->mReadyForWrite.Push(self->mInputFrameDataStream); + self->SetWriteCallbacks(); + } self->ResetDownstreamState(); return NS_OK; @@ -1500,19 +1464,15 @@ SpdySession3::ReadSegments(nsAHttpSegmentReader *reader, nsresult rv; *countRead = 0; - // First priority goes to frames that were writing to the network but were - // blocked part way through. Then to frames that have no streams (e.g ping - // reply) and then third to streams marked urgent (generally they have - // window updates), and finally to streams generally - // ready to send data frames (http requests). - LOG3(("SpdySession3::ReadSegments %p", this)); - SpdyStream3 *stream; - - stream = static_cast(mUrgentForWrite.PopFront()); - if (!stream) - stream = static_cast(mReadyForWrite.PopFront()); + NS_ABORT_IF_FALSE(!mSegmentReader || !reader || (mSegmentReader == reader), + "Inconsistent Write Function Callback"); + + if (reader) + mSegmentReader = reader; + + SpdyStream3 *stream = static_cast(mReadyForWrite.PopFront()); if (!stream) { LOG3(("SpdySession3 %p could not identify a stream to write; suspending.", this)); @@ -1521,7 +1481,9 @@ SpdySession3::ReadSegments(nsAHttpSegmentReader *reader, return NS_BASE_STREAM_WOULD_BLOCK; } - LOG3(("SpdySession3 %p will write from SpdyStream3 %p", this, stream)); + LOG3(("SpdySession3 %p will write from SpdyStream3 %p 0x%X " + "block-input=%d block-output=%d\n", this, stream, stream->StreamID(), + stream->RequestBlockedOnRead(), stream->BlockedOnRwin())); rv = stream->ReadSegments(this, count, countRead); @@ -1558,12 +1520,18 @@ SpdySession3::ReadSegments(nsAHttpSegmentReader *reader, } if (*countRead > 0) { - LOG3(("SpdySession3::ReadSegments %p stream=%p generated end of frame %d", + LOG3(("SpdySession3::ReadSegments %p stream=%p countread=%d", this, stream, *countRead)); mReadyForWrite.Push(stream); SetWriteCallbacks(); return rv; } + + if (stream->BlockedOnRwin()) { + LOG3(("SpdySession3 %p will stream %p 0x%X suspended for flow control\n", + this, stream, stream->StreamID())); + return NS_BASE_STREAM_WOULD_BLOCK; + } LOG3(("SpdySession3::ReadSegments %p stream=%p stream send complete", this, stream)); @@ -1672,16 +1640,16 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, mFrameControlType <= CONTROL_TYPE_FIRST) return NS_ERROR_ILLEGAL_VALUE; - // The protocol document says this value must be 1 even though this - // is known as version 2.. Testing interop indicates that is a typo - // in the protocol document - if (version != 2) { + if (version != kVersion) return NS_ERROR_ILLEGAL_VALUE; - } } else { ChangeDownstreamState(PROCESSING_DATA_FRAME); + Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, + mInputFrameDataSize >> 10); + mLastDataReadEpoch = mLastReadEpoch; + PRUint32 streamID = PR_ntohl(reinterpret_cast(mInputFrameBuffer.get())[0]); rv = SetInputFrameDataStream(streamID); @@ -1692,24 +1660,41 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, } if (!mInputFrameDataStream) { LOG3(("SpdySession3::WriteSegments %p lookup streamID 0x%X failed. " - "Next = 0x%x", this, streamID, mNextStreamID)); + "Next = 0x%X", this, streamID, mNextStreamID)); if (streamID >= mNextStreamID) GenerateRstStream(RST_INVALID_STREAM, streamID); ChangeDownstreamState(DISCARDING_DATA_FRAME); } + else if (mInputFrameDataStream->RecvdFin()) { + LOG3(("SpdySession3::WriteSegments %p streamID 0x%X " + "Data arrived for already server closed stream.\n", + this, streamID)); + GenerateRstStream(RST_STREAM_ALREADY_CLOSED, streamID); + ChangeDownstreamState(DISCARDING_DATA_FRAME); + } + else if (!mInputFrameDataStream->RecvdData()) { + LOG3(("SpdySession3 %p First Data Frame Flushes Headers stream 0x%X\n", + this, streamID)); + + mInputFrameDataStream->SetRecvdData(true); + rv = ResponseHeadersComplete(); + if (rv == NS_ERROR_ILLEGAL_VALUE) { + LOG3(("SpdySession3 %p PROTOCOL_ERROR detected 0x%X\n", + this, streamID)); + CleanupStream(mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); + ChangeDownstreamState(DISCARDING_DATA_FRAME); + } + else { + mDataPending = true; + } + } + mInputFrameDataLast = (mInputFrameBuffer[4] & kFlag_Data_FIN); - Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, - mInputFrameDataSize >> 10); LOG3(("Start Processing Data Frame. " - "Session=%p Stream ID 0x%x Stream Ptr %p Fin=%d Len=%d", + "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d", this, streamID, mInputFrameDataStream, mInputFrameDataLast, mInputFrameDataSize)); - mLastDataReadEpoch = mLastReadEpoch; - - if (mInputFrameBuffer[4] & kFlag_Data_ZLIB) { - LOG3(("Data flag has ZLIB flag set which is not valid >=2 spdy")); - return NS_ERROR_ILLEGAL_VALUE; - } + UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize); } } @@ -1721,6 +1706,8 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, mDownstreamRstReason == RST_INTERNAL_ERROR || mDownstreamRstReason == RST_UNSUPPORTED_VERSION) rv = NS_ERROR_NET_INTERRUPT; + else if (mDownstreamRstReason == RST_FRAME_TOO_LARGE) + rv = NS_ERROR_FILE_TOO_BIG; else rv = NS_ERROR_ILLEGAL_VALUE; @@ -1739,7 +1726,7 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, } if (mDownstreamState == PROCESSING_DATA_FRAME || - mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + mDownstreamState == PROCESSING_COMPLETE_HEADERS) { // The cleanup stream should only be set while stream->WriteSegments is // on the stack and then cleaned up in this code block afterwards. @@ -1756,6 +1743,11 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, // This will happen when the transaction figures out it is EOF, generally // due to a content-length match being made SpdyStream3 *stream = mInputFrameDataStream; + + // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state + // back to PROCESSING_DATA_FRAME where we came from + mDownstreamState = PROCESSING_DATA_FRAME; + if (mInputFrameDataRead == mInputFrameDataSize) ResetDownstreamState(); LOG3(("SpdySession3::WriteSegments session=%p stream=%p 0x%X " @@ -1777,8 +1769,6 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, mNeedsCleanup = nsnull; } - // In v3 this is where we would generate a window update - return rv; } @@ -1860,6 +1850,60 @@ SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, return rv; } +void +SpdySession3::UpdateLocalRwin(SpdyStream3 *stream, + PRUint32 bytes) +{ + // If this data packet was not for a valid or live stream then there + // is no reason to mess with the flow control + if (!stream || stream->RecvdFin()) + return; + + LOG3(("SpdySession3::UpdateLocalRwin %p 0x%X %d\n", + this, stream->StreamID(), bytes)); + stream->DecrementLocalWindow(bytes); + + // Don't necessarily ack every data packet. Only do it + // after a significant amount of data. + PRUint64 unacked = stream->LocalUnAcked(); + + if (unacked < kMinimumToAck) { + // Sanity check to make sure this won't let the window drop below 1MB + PR_STATIC_ASSERT(kMinimumToAck < kInitialRwin); + PR_STATIC_ASSERT((kInitialRwin - kMinimumToAck) > 1024 * 1024); + + return; + } + + // Generate window updates directly out of spdysession instead of the stream + // in order to avoid queue delays in getting the ACK out. + PRUint32 toack = unacked & 0x7fffffff; + + LOG3(("SpdySession3::UpdateLocalRwin Ack %p 0x%X %d\n", + this, stream->StreamID(), toack)); + stream->IncrementLocalWindow(toack); + + static const PRUint32 dataLen = 8; + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 8 + dataLen, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 8 + dataLen; + + memset(packet, 0, 8 + dataLen); + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_WINDOW_UPDATE; + packet[7] = dataLen; + + PRUint32 id = PR_htonl(stream->StreamID()); + memcpy(packet + 8, &id, 4); + toack = PR_htonl(toack); + memcpy(packet + 12, &toack, 4); + + LogIO(this, stream, "Window Update", packet, 8 + dataLen); + FlushOutputQueue(); +} + void SpdySession3::Close(nsresult aReason) { @@ -2039,7 +2083,7 @@ SpdySession3::OnWriteSegment(char *buf, return rv; } - if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) { if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && mInputFrameDataLast) { @@ -2057,9 +2101,17 @@ SpdySession3::OnWriteSegment(char *buf, mFlatHTTPResponseHeadersOut += count; *countWritten = count; - if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && - !mInputFrameDataLast) - ResetDownstreamState(); + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) { + // Now ready to process data frames. + if (mDataPending) { + mDataPending = false; + ChangeDownstreamState(PROCESSING_DATA_FRAME); + } + else { + ResetDownstreamState(); + } + } + return NS_OK; } @@ -2111,7 +2163,7 @@ SpdySession3::TransactionHasDataToWrite(nsAHttpTransaction *caller) return; } - LOG3(("SpdySession3::TransactionHasDataToWrite %p ID is %x", + LOG3(("SpdySession3::TransactionHasDataToWrite %p ID is 0x%X\n", this, stream->StreamID())); mReadyForWrite.Push(stream); @@ -2399,4 +2451,3 @@ SpdySession3::SetLastTransactionExpectedNoContent(bool val) } // namespace mozilla::net } // namespace mozilla - diff --git a/netwerk/protocol/http/SpdySession3.h b/netwerk/protocol/http/SpdySession3.h index 50ae171db780..67ff21f049ce 100644 --- a/netwerk/protocol/http/SpdySession3.h +++ b/netwerk/protocol/http/SpdySession3.h @@ -41,7 +41,7 @@ #define mozilla_net_SpdySession3_h // SPDY as defined by -// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 #include "ASpdySession.h" #include "nsClassHashtable.h" @@ -84,26 +84,13 @@ public: PRUint32 RegisterStreamID(SpdyStream3 *); + const static PRUint8 kVersion = 3; + const static PRUint8 kFlag_Control = 0x80; const static PRUint8 kFlag_Data_FIN = 0x01; const static PRUint8 kFlag_Data_UNI = 0x02; - const static PRUint8 kFlag_Data_ZLIB = 0x02; - // The protocol document for v2 specifies that the - // highest value (3) is the highest priority, but in - // reality 0 is the highest priority. - // - // Draft 3 notes here https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/ - // are the best guide to the mistake. Also see - // GetLowestPriority() and GetHighestPriority() in spdy_framer.h of - // chromium source. - - const static PRUint8 kPri00 = 0 << 6; // highest - const static PRUint8 kPri01 = 1 << 6; - const static PRUint8 kPri02 = 2 << 6; - const static PRUint8 kPri03 = 3 << 6; // lowest - enum { CONTROL_TYPE_FIRST = 0, @@ -111,12 +98,13 @@ public: CONTROL_TYPE_SYN_REPLY = 2, CONTROL_TYPE_RST_STREAM = 3, CONTROL_TYPE_SETTINGS = 4, - CONTROL_TYPE_NOOP = 5, + CONTROL_TYPE_NOOP = 5, /* deprecated */ CONTROL_TYPE_PING = 6, CONTROL_TYPE_GOAWAY = 7, CONTROL_TYPE_HEADERS = 8, - CONTROL_TYPE_WINDOW_UPDATE = 9, /* no longer in v2 */ - CONTROL_TYPE_LAST = 10 + CONTROL_TYPE_WINDOW_UPDATE = 9, + CONTROL_TYPE_CREDENTIAL = 10, + CONTROL_TYPE_LAST = 11 }; enum rstReason @@ -128,7 +116,10 @@ public: RST_CANCEL = 5, RST_INTERNAL_ERROR = 6, RST_FLOW_CONTROL_ERROR = 7, - RST_BAD_ASSOC_STREAM = 8 + RST_STREAM_IN_USE = 8, + RST_STREAM_ALREADY_CLOSED = 9, + RST_INVALID_CREDENTIALS = 10, + RST_FRAME_TOO_LARGE = 11 }; enum @@ -139,7 +130,8 @@ public: SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams SETTINGS_TYPE_CWND = 5, // packets SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage - SETTINGS_TYPE_INITIAL_WINDOW = 7 // bytes. Not used in v2. + SETTINGS_TYPE_INITIAL_WINDOW = 7, // bytes + SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8 }; // This should be big enough to hold all of your control packets, @@ -161,6 +153,16 @@ public: // 31 bit stream ID. const static PRUint32 kDeadStreamID = 0xffffdead; + // until we have an API that can push back on receiving data (right now + // WriteSegments is obligated to accept data and buffer) there is no + // reason to throttle with the rwin other than in server push + // scenarios. + const static PRUint32 kInitialRwin = 256 * 1024 * 1024; + const static PRUint32 kMinimumToAck = 64 * 1024; + + // The default peer rwin is 64KB unless updated by a settings frame + const static PRUint32 kDefaultServerRwin = 64 * 1024; + static nsresult HandleSynStream(SpdySession3 *); static nsresult HandleSynReply(SpdySession3 *); static nsresult HandleRstStream(SpdySession3 *); @@ -187,6 +189,8 @@ public: // an overload of nsAHttpSegementReader virtual nsresult CommitToSegmentSize(PRUint32 size); + PRUint32 GetServerInitialWindow() { return mServerInitialWindow; } + private: enum stateType { @@ -194,25 +198,23 @@ private: BUFFERING_CONTROL_FRAME, PROCESSING_DATA_FRAME, DISCARDING_DATA_FRAME, - PROCESSING_CONTROL_SYN_REPLY, + PROCESSING_COMPLETE_HEADERS, PROCESSING_CONTROL_RST_STREAM }; void DeterminePingThreshold(); - nsresult HandleSynReplyForValidStream(); + nsresult ResponseHeadersComplete(); PRUint32 GetWriteQueueSize(); void ChangeDownstreamState(enum stateType); void ResetDownstreamState(); - nsresult DownstreamUncompress(char *, PRUint32); + nsresult UncompressAndDiscard(PRUint32, PRUint32); void zlibInit(); - nsresult FindHeader(nsCString, nsDependentCSubstring &); - nsresult ConvertHeaders(nsDependentCSubstring &, - nsDependentCSubstring &); void GeneratePing(PRUint32); void ClearPing(bool); void GenerateRstStream(PRUint32, PRUint32); void GenerateGoAway(); void CleanupStream(SpdyStream3 *, nsresult, rstReason); + void GenerateSettings(); void SetWriteCallbacks(); void FlushOutputQueue(); @@ -224,6 +226,8 @@ private: bool VerifyStream(SpdyStream3 *, PRUint32); void SetNeedsCleanup(); + void UpdateLocalRwin(SpdyStream3 *stream, PRUint32 bytes); + // a wrapper for all calls to the nshttpconnection level segment writer. Used // to track network I/O for timeout purposes nsresult NetworkRead(nsAHttpSegmentWriter *, char *, PRUint32, PRUint32 *); @@ -232,6 +236,10 @@ private: nsAutoPtr &, void *); + static PLDHashOperator UpdateServerRwinEnumerator(nsAHttpTransaction *, + nsAutoPtr &, + void *); + // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken // from the first transaction on this session. That object contains the // pointer to the real network-level nsHttpConnection object. @@ -252,28 +260,23 @@ private: stateType mDownstreamState; /* in frame, between frames, etc.. */ - // Maintain 5 indexes - one by stream ID, one by transaction ptr, + // Maintain 4 indexes - one by stream ID, one by transaction ptr, // one list of streams ready to write, one list of streams that are queued - // due to max parallelism settings, and one list of streams - // that must be given priority to write for window updates. The objects + // due to max parallelism settings. The objects // are not ref counted - they get destroyed // by the nsClassHashtable implementation when they are removed from - // there. + // the transaction hash. nsDataHashtable mStreamIDHash; nsClassHashtable, SpdyStream3> mStreamTransactionHash; nsDeque mReadyForWrite; nsDeque mQueuedStreams; - // UrgentForWrite is meant to carry window updates. They were defined in - // the v2 spec but apparently never implemented so are now scheduled to - // be removed. But they will be reintroduced for v3, so we will leave - // this queue in place to ease that transition. - nsDeque mUrgentForWrite; - // Compression contexts for header transport using deflate. // SPDY compresses only HTTP headers and does not reset zlib in between - // frames. + // frames. Even data that is not associated with a stream (e.g invalid + // stream ID) is passed through these contexts to keep the compression + // context correct. z_stream mDownstreamZlib; z_stream mUpstreamZlib; @@ -308,15 +311,8 @@ private: // This reason code in the last processed RESET frame PRUint32 mDownstreamRstReason; - // These are used for decompressing downstream spdy response headers - // This is done at the session level because sometimes the stream - // has already been canceled but the decompression still must happen - // to keep the zlib state correct for the next state of headers. - PRUint32 mDecompressBufferSize; - PRUint32 mDecompressBufferUsed; - nsAutoArrayPtr mDecompressBuffer; - // for the conversion of downstream http headers into spdy formatted headers + // The data here does not persist between frames nsCString mFlatHTTPResponseHeaders; PRUint32 mFlatHTTPResponseHeadersOut; @@ -333,6 +329,11 @@ private: // the session received a GoAway frame with a valid GoAwayID bool mCleanShutdown; + // indicates PROCESSING_COMPLETE_HEADERS state was pushed onto the stack + // over an active PROCESSING_DATA_FRAME, which should be restored when + // the processed headers are written to the stream + bool mDataPending; + // If a GoAway message was received this is the ID of the last valid // stream. 0 otherwise. (0 is never a valid stream id.) PRUint32 mGoAwayID; @@ -350,6 +351,9 @@ private: // The number of server initiated SYN-STREAMS, tracked for telemetry PRUint32 mServerPushedResources; + // The server rwin for new streams as determined from a SETTINGS frame + PRUint32 mServerInitialWindow; + // This is a output queue of bytes ready to be written to the SSL stream. // When that streams returns WOULD_BLOCK on direct write the bytes get // coalesced together here. This results in larger writes to the SSL layer. diff --git a/netwerk/protocol/http/SpdyStream2.cpp b/netwerk/protocol/http/SpdyStream2.cpp index 82e76194308c..19fa238664a3 100644 --- a/netwerk/protocol/http/SpdyStream2.cpp +++ b/netwerk/protocol/http/SpdyStream2.cpp @@ -295,7 +295,7 @@ SpdyStream2::ParseHttpRequestHeaders(const char *buf, // Priority flags are the C0 mask of byte 16. // // The other 6 bits of 16 are unused. Spdy/3 will expand - // priority to 4 bits. + // priority to 3 bits. // // When Spdy/3 implements WINDOW_UPDATE the lowest priority // streams over a threshold (32?) should be given tiny diff --git a/netwerk/protocol/http/SpdyStream3.cpp b/netwerk/protocol/http/SpdyStream3.cpp index 6882330735a4..04634c424d90 100644 --- a/netwerk/protocol/http/SpdyStream3.cpp +++ b/netwerk/protocol/http/SpdyStream3.cpp @@ -75,12 +75,19 @@ SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction, mRecvdFin(0), mFullyOpen(0), mSentWaitingFor(0), + mReceivedData(0), mTxInlineFrameSize(SpdySession3::kDefaultBufferSize), mTxInlineFrameUsed(0), mTxStreamFrameSize(0), mZlib(compressionContext), + mDecompressBufferSize(SpdySession3::kDefaultBufferSize), + mDecompressBufferUsed(0), + mDecompressedBytes(0), mRequestBodyLenRemaining(0), mPriority(priority), + mLocalWindow(SpdySession3::kInitialRwin), + mLocalUnacked(0), + mBlockedOnRwin(false), mTotalSent(0), mTotalRead(0) { @@ -88,7 +95,9 @@ SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction, LOG3(("SpdyStream3::SpdyStream3 %p", this)); + mRemoteWindow = spdySession->GetServerInitialWindow(); mTxInlineFrame = new char[mTxInlineFrameSize]; + mDecompressBuffer = new char[mDecompressBufferSize]; } SpdyStream3::~SpdyStream3() @@ -143,9 +152,12 @@ SpdyStream3::ReadSegments(nsAHttpSegmentReader *reader, if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) mRequestBlockedOnRead = 1; - if (!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { - LOG3(("ReadSegments %p: Sending request data complete, mUpstreamState=%x", - this, mUpstreamState)); + // If the sending flow control window is open (!mBlockedOnRwin) then + // continue sending the request + if (!mBlockedOnRwin && + !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { + LOG3(("SpdyStream3::ReadSegments %p 0x%X: Sending request data complete, " + "mUpstreamState=%x",this, mStreamID, mUpstreamState)); if (mSentFinOnData) { ChangeState(UPSTREAM_COMPLETE); } @@ -156,7 +168,6 @@ SpdyStream3::ReadSegments(nsAHttpSegmentReader *reader, rv = NS_BASE_STREAM_WOULD_BLOCK; } } - break; case SENDING_SYN_STREAM: @@ -313,7 +324,7 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, // of SPDY headers.. writing to mTxInlineFrame{sz} mTxInlineFrame[0] = SpdySession3::kFlag_Control; - mTxInlineFrame[1] = 2; /* version */ + mTxInlineFrame[1] = SpdySession3::kVersion; mTxInlineFrame[2] = 0; mTxInlineFrame[3] = SpdySession3::CONTROL_TYPE_SYN_STREAM; // 4 to 7 are length and flags, we'll fill that in later @@ -325,25 +336,32 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, // from the client in the http binding memset (mTxInlineFrame + 12, 0, 4); - // Priority flags are the C0 mask of byte 16. - // - // The other 6 bits of 16 are unused. Spdy/3 will expand - // priority to 4 bits. - // - // When Spdy/3 implements WINDOW_UPDATE the lowest priority - // streams over a threshold (32?) should be given tiny - // receive windows, separate from their spdy priority - // - if (mPriority >= nsISupportsPriority::PRIORITY_LOW) - mTxInlineFrame[16] = SpdySession3::kPri03; - else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL) - mTxInlineFrame[16] = SpdySession3::kPri02; - else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH) - mTxInlineFrame[16] = SpdySession3::kPri01; - else - mTxInlineFrame[16] = SpdySession3::kPri00; + // Priority flags are the E0 mask of byte 16. + // 0 is highest priority, 7 is lowest. + // The other 5 bits of byte 16 are unused. + + if (mPriority >= nsISupportsPriority::PRIORITY_LOWEST) + mTxInlineFrame[16] = 7 << 5; + else if (mPriority <= nsISupportsPriority::PRIORITY_HIGHEST) + mTxInlineFrame[16] = 0 << 5; + else { + // The priority mapping relies on the unfiltered ranged to be + // between -20 .. +20 + PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST == 20); + PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_HIGHEST == -20); - mTxInlineFrame[17] = 0; /* unused */ + // Add one to the priority so that values such as -10 and -11 + // get different spdy priorities - this appears to be an important + // breaking line in the priorities content assigns to + // transactions. + PRUint8 calculatedPriority = 3 + ((mPriority + 1) / 5); + NS_ABORT_IF_FALSE (!(calculatedPriority & 0xf8), + "Calculated Priority Out Of Range"); + mTxInlineFrame[16] = calculatedPriority << 5; + } + + // The client cert "slot". Right now we don't send client certs + mTxInlineFrame[17] = 0; const char *methodHeader = mTransaction->RequestHead()->Method().get(); @@ -386,17 +404,16 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, // all header names are lower case in spdy ToLowerCase(name); - if (name.Equals("method") || - name.Equals("version") || - name.Equals("scheme") || + // exclusions.. mostly from 3.2.1 + if (name.Equals("connection") || name.Equals("keep-alive") || + name.Equals("host") || + name.Equals("proxy-connection") || name.Equals("accept-encoding") || name.Equals("te") || - name.Equals("connection") || - name.Equals("proxy-connection") || - name.Equals("url")) + name.Equals("transfer-encoding")) continue; - + nsCString *val = hdrHash.Get(name); if (!val) { val = new nsCString(); @@ -427,20 +444,23 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, // headers at this same level so it is not necessary to do so here. // The header block length - PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */ + PRUint16 count = hdrHash.Count() + 5; /* method, path, version, host, scheme */ CompressToFrame(count); - // method, scheme, url, and version headers for request line - - CompressToFrame(NS_LITERAL_CSTRING("method")); + // :method, :path, :version comprise a HTTP/1 request line, so send those first + // to make life easy for any gateways + CompressToFrame(NS_LITERAL_CSTRING(":method")); CompressToFrame(methodHeader, strlen(methodHeader)); - CompressToFrame(NS_LITERAL_CSTRING("scheme")); - CompressToFrame(NS_LITERAL_CSTRING("https")); - CompressToFrame(NS_LITERAL_CSTRING("url")); + CompressToFrame(NS_LITERAL_CSTRING(":path")); CompressToFrame(mTransaction->RequestHead()->RequestURI()); - CompressToFrame(NS_LITERAL_CSTRING("version")); + CompressToFrame(NS_LITERAL_CSTRING(":version")); CompressToFrame(versionHeader); - + + CompressToFrame(NS_LITERAL_CSTRING(":host")); + CompressToFrame(hostHeader); + CompressToFrame(NS_LITERAL_CSTRING(":scheme")); + CompressToFrame(NS_LITERAL_CSTRING("https")); + hdrHash.Enumerate(hdrHashEnumerate, this); CompressFlushFrame(); @@ -678,26 +698,188 @@ SpdyStream3::CompressToFrame(const nsACString *str) } // Dictionary taken from -// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 -// Name/Value Header Block Format -// spec indicates that the compression dictionary is not null terminated -// but in reality it is. see: -// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 -const char *SpdyStream3::kDictionary = - "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" - "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" - "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" - "-agent10010120020120220320420520630030130230330430530630740040140240340440" - "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" - "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" - "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" - "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" - "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" - "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" - "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" - "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" - ".1statusversionurl"; +const unsigned char SpdyStream3::kDictionary[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - - + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - - + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t - + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - - + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e - + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e - + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - - + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t - + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - - + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - - + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - - + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - - + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - - + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - - + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h - + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - - + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e - + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - - + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f - + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - - + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - - + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a - + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - - + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - - + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - - + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r - + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e - + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g - + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a - + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - - + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t - + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - - + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e - + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 - + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 - + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e - + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t - + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v - + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u - + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - - + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t - + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 - + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - - + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 - +}; // use for zlib data types void * @@ -713,6 +895,239 @@ SpdyStream3::zlib_destructor(void *opaque, void *addr) moz_free(addr); } +nsresult +SpdyStream3::Uncompress(z_stream *context, + char *blockStart, + PRUint32 blockLen) +{ + mDecompressedBytes += blockLen; + + context->avail_in = blockLen; + context->next_in = reinterpret_cast(blockStart); + + do { + context->next_out = + reinterpret_cast(mDecompressBuffer.get()) + + mDecompressBufferUsed; + context->avail_out = mDecompressBufferSize - mDecompressBufferUsed; + int zlib_rv = inflate(context, Z_NO_FLUSH); + + if (zlib_rv == Z_NEED_DICT) + inflateSetDictionary(context, kDictionary, sizeof(kDictionary)); + + if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR) + return NS_ERROR_FAILURE; + + // zlib's inflate() decreases context->avail_out by the amount it places + // in the output buffer + + mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed - + context->avail_out; + + // When there is no more output room, but input still available then + // increase the output space + if (zlib_rv == Z_OK && + !context->avail_out && context->avail_in) { + LOG3(("SpdyStream3::Uncompress %p Large Headers - so far %d", + this, mDecompressBufferSize)); + SpdySession3::EnsureBuffer(mDecompressBuffer, + mDecompressBufferSize + 4096, + mDecompressBufferUsed, + mDecompressBufferSize); + } + } + while (context->avail_in); + return NS_OK; +} + +nsresult +SpdyStream3::FindHeader(nsCString name, + nsDependentCSubstring &value) +{ + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 4; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + PRUint32 numPairs = + PR_ntohl(reinterpret_cast(mDecompressBuffer.get())[0]); + for (PRUint32 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 4) + return NS_ERROR_ILLEGAL_VALUE; + PRUint32 nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + + (nvpair[2] << 8) + nvpair[3]; + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + nsDependentCSubstring nameString = + Substring(reinterpret_cast(nvpair) + 4, + reinterpret_cast(nvpair) + 4 + nameLen); + if (lastHeaderByte < nvpair + 8 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint32 valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + + (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; + if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + if (nameString.Equals(name)) { + value.Assign(((char *)nvpair) + 8 + nameLen, valueLen); + return NS_OK; + } + nvpair += 8 + nameLen + valueLen; + } + return NS_ERROR_NOT_AVAILABLE; +} + +// ConvertHeaders is used to convert the response headers +// in a syn_reply or in 0..N headers frames that follow it into +// HTTP/1 format +nsresult +SpdyStream3::ConvertHeaders(nsACString &aHeadersOut) +{ + // :status and :version are required. + nsDependentCSubstring status, version; + nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"), + status); + if (NS_FAILED(rv)) + return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv; + + rv = FindHeader(NS_LITERAL_CSTRING(":version"), + version); + if (NS_FAILED(rv)) + return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv; + + if (mDecompressedBytes && mDecompressBufferUsed) { + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, mDecompressedBytes); + PRUint32 ratio = + mDecompressedBytes * 100 / mDecompressBufferUsed; + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); + } + + aHeadersOut.Truncate(); + aHeadersOut.SetCapacity(mDecompressBufferUsed + 64); + + // Connection, Keep-Alive and chunked transfer encodings are to be + // removed. + + // Content-Length is 'advisory'.. we will not strip it because it can + // create UI feedback. + + aHeadersOut.Append(version); + aHeadersOut.Append(NS_LITERAL_CSTRING(" ")); + aHeadersOut.Append(status); + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 4; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint32 numPairs = + PR_ntohl(reinterpret_cast(mDecompressBuffer.get())[0]); + + for (PRUint32 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 4) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint32 nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + + (nvpair[2] << 8) + nvpair[3]; + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + + nsDependentCSubstring nameString = + Substring(reinterpret_cast(nvpair) + 4, + reinterpret_cast(nvpair) + 4 + nameLen); + + if (lastHeaderByte < nvpair + 8 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + + // Look for illegal characters in the nameString. + // This includes upper case characters and nulls (as they will + // break the fixup-nulls-in-value-string algorithm) + // Look for upper case characters in the name. They are illegal. + for (char *cPtr = nameString.BeginWriting(); + cPtr && cPtr < nameString.EndWriting(); + ++cPtr) { + if (*cPtr <= 'Z' && *cPtr >= 'A') { + nsCString toLog(nameString); + + LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p " + "upper case response header found. [%s]\n", + mSession, this, toLog.get())); + + return NS_ERROR_ILLEGAL_VALUE; + } + + // check for null characters + if (*cPtr == '\0') + return NS_ERROR_ILLEGAL_VALUE; + } + + // HTTP Chunked responses are not legal over spdy. We do not need + // to look for chunked specifically because it is the only HTTP + // allowed default encoding and we did not negotiate further encodings + // via TE + if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { + LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p " + "transfer-encoding found. Chunked is invalid and no TE sent.", + mSession, this)); + + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 valueLen = + (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + + (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; + + if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + + if (!nameString.Equals(NS_LITERAL_CSTRING(":version")) && + !nameString.Equals(NS_LITERAL_CSTRING(":status")) && + !nameString.Equals(NS_LITERAL_CSTRING("connection")) && + !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { + nsDependentCSubstring valueString = + Substring(reinterpret_cast(nvpair) + 8 + nameLen, + reinterpret_cast(nvpair) + 8 + nameLen + + valueLen); + + aHeadersOut.Append(nameString); + aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + + // expand NULL bytes in the value string + for (char *cPtr = valueString.BeginWriting(); + cPtr && cPtr < valueString.EndWriting(); + ++cPtr) { + if (*cPtr != 0) { + aHeadersOut.Append(*cPtr); + continue; + } + + // NULLs are really "\r\nhdr: " + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + aHeadersOut.Append(nameString); + aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + } + + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + } + nvpair += 8 + nameLen + valueLen; + } + + aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: 3\r\n\r\n")); + LOG (("decoded response headers are:\n%s", + aHeadersOut.BeginReading())); + + // The spdy formatted buffer isnt needed anymore - free it up + mDecompressBuffer = nsnull; + mDecompressBufferSize = 0; + mDecompressBufferUsed = 0; + + return NS_OK; +} + void SpdyStream3::ExecuteCompress(PRUint32 flushMode) { @@ -739,15 +1154,14 @@ SpdyStream3::ExecuteCompress(PRUint32 flushMode) } void -SpdyStream3::CompressToFrame(PRUint16 data) +SpdyStream3::CompressToFrame(PRUint32 data) { - // convert the data to network byte order and write that + // convert the data to 4 byte network byte order and write that // to the compressed stream - - data = PR_htons(data); + data = PR_htonl(data); mZlib->next_in = reinterpret_cast (&data); - mZlib->avail_in = 2; + mZlib->avail_in = 4; ExecuteCompress(Z_NO_FLUSH); } @@ -755,19 +1169,14 @@ SpdyStream3::CompressToFrame(PRUint16 data) void SpdyStream3::CompressToFrame(const char *data, PRUint32 len) { - // Format calls for a network ordered 16 bit length + // Format calls for a network ordered 32 bit length // followed by the utf8 string - // for now, silently truncate headers greater than 64KB. Spdy/3 will - // fix this by making the len a 32 bit quantity - if (len > 0xffff) - len = 0xffff; - - PRUint16 networkLen = PR_htons(len); + PRUint32 networkLen = PR_htonl(len); // write out the length mZlib->next_in = reinterpret_cast (&networkLen); - mZlib->avail_in = 2; + mZlib->avail_in = 4; ExecuteCompress(Z_NO_FLUSH); // write out the data @@ -854,7 +1263,24 @@ SpdyStream3::OnReadSegment(const char *buf, break; case GENERATING_REQUEST_BODY: + if (mRemoteWindow <= 0) { + *countRead = 0; + LOG3(("SpdyStream3 this=%p, id 0x%X request body suspended because " + "remote window is %d.\n", this, mStreamID, mRemoteWindow)); + mBlockedOnRwin = true; + return NS_BASE_STREAM_WOULD_BLOCK; + } + mBlockedOnRwin = false; + dataLength = NS_MIN(count, mChunkSize); + + if (dataLength > mRemoteWindow) + dataLength = mRemoteWindow; + + LOG3(("SpdyStream3 this=%p id 0x%X remote window is %d. Chunk is %d\n", + this, mStreamID, mRemoteWindow, dataLength)); + mRemoteWindow -= dataLength; + LOG3(("SpdyStream3 %p id %x request len remaining %d, " "count avail %d, chunk used %d", this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); diff --git a/netwerk/protocol/http/SpdyStream3.h b/netwerk/protocol/http/SpdyStream3.h index 5de9c573acda..1dc3a5d76ba9 100644 --- a/netwerk/protocol/http/SpdyStream3.h +++ b/netwerk/protocol/http/SpdyStream3.h @@ -83,15 +83,37 @@ public: void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; } bool RecvdFin() { return mRecvdFin; } + void SetRecvdData(bool aStatus) { mReceivedData = aStatus ? 1 : 0; } + bool RecvdData() { return mReceivedData; } + void UpdateTransportSendEvents(PRUint32 count); void UpdateTransportReadEvents(PRUint32 count); // The zlib header compression dictionary defined by SPDY, // and hooks to the mozilla allocator for zlib to use. - static const char *kDictionary; + static const unsigned char kDictionary[1423]; static void *zlib_allocator(void *, uInt, uInt); static void zlib_destructor(void *, void *); + nsresult Uncompress(z_stream *, char *, PRUint32); + nsresult ConvertHeaders(nsACString &); + + void UpdateRemoteWindow(PRInt32 delta) { mRemoteWindow += delta; } + PRInt64 RemoteWindow() { return mRemoteWindow; } + + void DecrementLocalWindow(PRUint32 delta) { + mLocalWindow -= delta; + mLocalUnacked += delta; + } + + void IncrementLocalWindow(PRUint32 delta) { + mLocalWindow += delta; + mLocalUnacked -= delta; + } + + PRUint64 LocalUnAcked() { return mLocalUnacked; } + bool BlockedOnRwin() { return mBlockedOnRwin; } + private: // a SpdyStream3 object is only destroyed by being removed from the @@ -121,9 +143,10 @@ private: void CompressToFrame(const nsACString &); void CompressToFrame(const nsACString *); void CompressToFrame(const char *, PRUint32); - void CompressToFrame(PRUint16); + void CompressToFrame(PRUint32); void CompressFlushFrame(); void ExecuteCompress(PRUint32); + nsresult FindHeader(nsCString, nsDependentCSubstring &); // Each stream goes from syn_stream to upstream_complete, perhaps // looping on multiple instances of generating_request_body and @@ -175,6 +198,10 @@ private: // Flag is set after the WAITING_FOR Transport event has been generated PRUint32 mSentWaitingFor : 1; + // Flag is set after 1st DATA frame has been passed to stream, after + // which additional HEADERS data is invalid + PRUint32 mReceivedData : 1; + // The InlineFrame and associated data is used for composing control // frames and data frame headers. nsAutoArrayPtr mTxInlineFrame; @@ -192,6 +219,12 @@ private: z_stream *mZlib; nsCString mFlatHttpRequestHeaders; + // These are used for decompressing downstream spdy response headers + PRUint32 mDecompressBufferSize; + PRUint32 mDecompressBufferUsed; + PRUint32 mDecompressedBytes; + nsAutoArrayPtr mDecompressBuffer; + // Track the content-length of a request body so that we can // place the fin flag on the last data packet instead of waiting // for a stream closed indication. Relying on stream close results @@ -202,6 +235,27 @@ private: // based on nsISupportsPriority definitions PRInt32 mPriority; + // mLocalWindow, mRemoteWindow, and mLocalUnacked are for flow control. + // *window are signed because they race conditions in asynchronous SETTINGS + // messages can force them temporarily negative. + + // LocalWindow is how much data the server will send without getting a + // window update + PRInt64 mLocalWindow; + + // RemoteWindow is how much data the client is allowed to send without + // getting a window update + PRInt64 mRemoteWindow; + + // LocalUnacked is the number of bytes received by the client but not + // yet reflected in a window update. Sending that update will increment + // LocalWindow + PRUint64 mLocalUnacked; + + // True when sending is suspended becuase the remote flow control window is + // <= 0 + bool mBlockedOnRwin; + // For Progress Events PRUint64 mTotalSent; PRUint64 mTotalRead;