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 "mozilla/Telemetry.h" michael@0: #include "nsAlgorithm.h" michael@0: #include "nsHttp.h" michael@0: #include "nsHttpHandler.h" michael@0: #include "nsHttpRequestHead.h" michael@0: #include "nsISocketTransport.h" michael@0: #include "nsISupportsPriority.h" michael@0: #include "prnetdb.h" michael@0: #include "SpdyPush31.h" michael@0: #include "SpdySession31.h" michael@0: #include "SpdyStream31.h" michael@0: michael@0: #include 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: SpdyStream31::SpdyStream31(nsAHttpTransaction *httpTransaction, michael@0: SpdySession31 *spdySession, michael@0: int32_t priority) michael@0: : mStreamID(0), michael@0: mSession(spdySession), michael@0: mUpstreamState(GENERATING_SYN_STREAM), michael@0: mSynFrameComplete(0), michael@0: mSentFinOnData(0), michael@0: mTransaction(httpTransaction), michael@0: mSocketTransport(spdySession->SocketTransport()), michael@0: mSegmentReader(nullptr), michael@0: mSegmentWriter(nullptr), michael@0: mChunkSize(spdySession->SendingChunkSize()), michael@0: mRequestBlockedOnRead(0), michael@0: mRecvdFin(0), michael@0: mFullyOpen(0), michael@0: mSentWaitingFor(0), michael@0: mReceivedData(0), michael@0: mSetTCPSocketBuffer(0), michael@0: mTxInlineFrameSize(SpdySession31::kDefaultBufferSize), michael@0: mTxInlineFrameUsed(0), michael@0: mTxStreamFrameSize(0), michael@0: mZlib(spdySession->UpstreamZlib()), michael@0: mDecompressBufferSize(SpdySession31::kDefaultBufferSize), michael@0: mDecompressBufferUsed(0), michael@0: mDecompressedBytes(0), michael@0: mRequestBodyLenRemaining(0), michael@0: mPriority(priority), michael@0: mLocalUnacked(0), michael@0: mBlockedOnRwin(false), michael@0: mTotalSent(0), michael@0: mTotalRead(0), michael@0: mPushSource(nullptr) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: LOG3(("SpdyStream31::SpdyStream31 %p", this)); michael@0: michael@0: mRemoteWindow = spdySession->GetServerInitialStreamWindow(); michael@0: mLocalWindow = spdySession->PushAllowance(); michael@0: michael@0: mTxInlineFrame = new uint8_t[mTxInlineFrameSize]; michael@0: mDecompressBuffer = new char[mDecompressBufferSize]; michael@0: } michael@0: michael@0: SpdyStream31::~SpdyStream31() michael@0: { michael@0: mStreamID = SpdySession31::kDeadStreamID; michael@0: } michael@0: michael@0: // ReadSegments() is used to write data down the socket. Generally, HTTP michael@0: // request data is pulled from the approriate transaction and michael@0: // converted to SPDY data. Sometimes control data like a window-update is michael@0: // generated instead. michael@0: michael@0: nsresult michael@0: SpdyStream31::ReadSegments(nsAHttpSegmentReader *reader, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: LOG3(("SpdyStream31 %p ReadSegments reader=%p count=%d state=%x", michael@0: this, reader, count, mUpstreamState)); michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: nsresult rv = NS_ERROR_UNEXPECTED; michael@0: mRequestBlockedOnRead = 0; michael@0: michael@0: // avoid runt chunks if possible by anticipating michael@0: // full data frames michael@0: if (count > (mChunkSize + 8)) { michael@0: uint32_t numchunks = count / (mChunkSize + 8); michael@0: count = numchunks * (mChunkSize + 8); michael@0: } michael@0: michael@0: switch (mUpstreamState) { michael@0: case GENERATING_SYN_STREAM: michael@0: case GENERATING_REQUEST_BODY: michael@0: case SENDING_REQUEST_BODY: michael@0: // Call into the HTTP Transaction to generate the HTTP request michael@0: // stream. That stream will show up in OnReadSegment(). michael@0: mSegmentReader = reader; michael@0: rv = mTransaction->ReadSegments(this, count, countRead); michael@0: mSegmentReader = nullptr; michael@0: michael@0: // Check to see if the transaction's request could be written out now. michael@0: // If not, mark the stream for callback when writing can proceed. michael@0: if (NS_SUCCEEDED(rv) && michael@0: mUpstreamState == GENERATING_SYN_STREAM && michael@0: !mSynFrameComplete) michael@0: mSession->TransactionHasDataToWrite(this); michael@0: michael@0: // mTxinlineFrameUsed represents any queued un-sent frame. It might michael@0: // be 0 if there is no such frame, which is not a gurantee that we michael@0: // don't have more request body to send - just that any data that was michael@0: // sent comprised a complete SPDY frame. Likewise, a non 0 value is michael@0: // a queued, but complete, spdy frame length. michael@0: michael@0: // Mark that we are blocked on read if the http transaction needs to michael@0: // provide more of the request message body and there is nothing queued michael@0: // for writing michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) michael@0: mRequestBlockedOnRead = 1; michael@0: michael@0: // If the sending flow control window is open (!mBlockedOnRwin) then michael@0: // continue sending the request michael@0: if (!mBlockedOnRwin && michael@0: !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { michael@0: LOG3(("SpdyStream31::ReadSegments %p 0x%X: Sending request data complete, " michael@0: "mUpstreamState=%x",this, mStreamID, mUpstreamState)); michael@0: if (mSentFinOnData) { michael@0: ChangeState(UPSTREAM_COMPLETE); michael@0: } michael@0: else { michael@0: GenerateDataFrameHeader(0, true); michael@0: ChangeState(SENDING_FIN_STREAM); michael@0: mSession->TransactionHasDataToWrite(this); michael@0: rv = NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case SENDING_FIN_STREAM: michael@0: // We were trying to send the FIN-STREAM but were blocked from michael@0: // sending it out - try again. michael@0: if (!mSentFinOnData) { michael@0: mSegmentReader = reader; michael@0: rv = TransmitFrame(nullptr, nullptr, false); michael@0: mSegmentReader = nullptr; michael@0: MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, michael@0: "Transmit Frame should be all or nothing"); michael@0: if (NS_SUCCEEDED(rv)) michael@0: ChangeState(UPSTREAM_COMPLETE); michael@0: } michael@0: else { michael@0: rv = NS_OK; michael@0: mTxInlineFrameUsed = 0; // cancel fin data packet michael@0: ChangeState(UPSTREAM_COMPLETE); michael@0: } michael@0: michael@0: *countRead = 0; michael@0: michael@0: // don't change OK to WOULD BLOCK. we are really done sending if OK michael@0: break; michael@0: michael@0: case UPSTREAM_COMPLETE: michael@0: *countRead = 0; michael@0: rv = NS_OK; michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSERT(false, "SpdyStream31::ReadSegments unknown state"); michael@0: break; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // WriteSegments() is used to read data off the socket. Generally this is michael@0: // just the SPDY frame header and from there the appropriate SPDYStream michael@0: // is identified from the Stream-ID. The http transaction associated with michael@0: // that read then pulls in the data directly. michael@0: michael@0: nsresult michael@0: SpdyStream31::WriteSegments(nsAHttpSegmentWriter *writer, michael@0: uint32_t count, michael@0: uint32_t *countWritten) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); michael@0: michael@0: LOG3(("SpdyStream31::WriteSegments %p count=%d state=%x", michael@0: this, count, mUpstreamState)); michael@0: michael@0: mSegmentWriter = writer; michael@0: nsresult rv = mTransaction->WriteSegments(this, count, countWritten); michael@0: mSegmentWriter = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: SpdyStream31::hdrHashEnumerate(const nsACString &key, michael@0: nsAutoPtr &value, michael@0: void *closure) michael@0: { michael@0: SpdyStream31 *self = static_cast(closure); michael@0: michael@0: self->CompressToFrame(key); michael@0: self->CompressToFrame(value.get()); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::CreatePushHashKey(const nsCString &scheme, michael@0: const nsCString &hostHeader, michael@0: uint64_t serial, michael@0: const nsCSubstring &pathInfo, michael@0: nsCString &outOrigin, michael@0: nsCString &outKey) michael@0: { michael@0: outOrigin = scheme; michael@0: outOrigin.Append(NS_LITERAL_CSTRING("://")); michael@0: outOrigin.Append(hostHeader); michael@0: michael@0: outKey = outOrigin; michael@0: outKey.Append(NS_LITERAL_CSTRING("/[spdy3_1.")); michael@0: outKey.AppendInt(serial); michael@0: outKey.Append(NS_LITERAL_CSTRING("]")); michael@0: outKey.Append(pathInfo); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: SpdyStream31::ParseHttpRequestHeaders(const char *buf, michael@0: uint32_t avail, michael@0: uint32_t *countUsed) michael@0: { michael@0: // Returns NS_OK even if the headers are incomplete michael@0: // set mSynFrameComplete flag if they are complete michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(mUpstreamState == GENERATING_SYN_STREAM); michael@0: michael@0: LOG3(("SpdyStream31::ParseHttpRequestHeaders %p avail=%d state=%x", michael@0: this, avail, mUpstreamState)); michael@0: michael@0: mFlatHttpRequestHeaders.Append(buf, avail); michael@0: michael@0: // We can use the simple double crlf because firefox is the michael@0: // only client we are parsing michael@0: int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); michael@0: michael@0: if (endHeader == kNotFound) { michael@0: // We don't have all the headers yet michael@0: LOG3(("SpdyStream31::ParseHttpRequestHeaders %p " michael@0: "Need more header bytes. Len = %d", michael@0: this, mFlatHttpRequestHeaders.Length())); michael@0: *countUsed = avail; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We have recvd all the headers, trim the local michael@0: // buffer of the final empty line, and set countUsed to reflect michael@0: // the whole header has been consumed. michael@0: uint32_t oldLen = mFlatHttpRequestHeaders.Length(); michael@0: mFlatHttpRequestHeaders.SetLength(endHeader + 2); michael@0: *countUsed = avail - (oldLen - endHeader) + 4; michael@0: mSynFrameComplete = 1; michael@0: michael@0: nsCString hostHeader; michael@0: nsCString hashkey; michael@0: mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); michael@0: michael@0: CreatePushHashKey(NS_LITERAL_CSTRING("https"), michael@0: hostHeader, mSession->Serial(), michael@0: mTransaction->RequestHead()->RequestURI(), michael@0: mOrigin, hashkey); michael@0: michael@0: // check the push cache for GET michael@0: if (mTransaction->RequestHead()->IsGet()) { michael@0: // from :scheme, :host, :path michael@0: nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); michael@0: SpdyPushCache *cache = nullptr; michael@0: if (loadGroupCI) michael@0: loadGroupCI->GetSpdyPushCache(&cache); michael@0: michael@0: SpdyPushedStream31 *pushedStream = nullptr; michael@0: // we remove the pushedstream from the push cache so that michael@0: // it will not be used for another GET. This does not destroy the michael@0: // stream itself - that is done when the transactionhash is done with it. michael@0: if (cache) michael@0: pushedStream = cache->RemovePushedStreamSpdy31(hashkey); michael@0: michael@0: if (pushedStream) { michael@0: LOG3(("Pushed Stream Match located id=0x%X key=%s\n", michael@0: pushedStream->StreamID(), hashkey.get())); michael@0: pushedStream->SetConsumerStream(this); michael@0: mPushSource = pushedStream; michael@0: mSentFinOnData = 1; michael@0: michael@0: // This stream has been activated (and thus counts against the concurrency michael@0: // limit intentionally), but will not be registered via michael@0: // RegisterStreamID (below) because of the push match. Therefore the michael@0: // concurrency sempahore needs to be balanced. michael@0: mSession->DecrementConcurrent(this); michael@0: michael@0: // There is probably pushed data buffered so trigger a read manually michael@0: // as we can't rely on future network events to do it michael@0: mSession->ConnectPushedStream(this); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // It is now OK to assign a streamID that we are assured will michael@0: // be monotonically increasing amongst syn-streams on this michael@0: // session michael@0: mStreamID = mSession->RegisterStreamID(this); michael@0: MOZ_ASSERT(mStreamID & 1, "Spdy Stream Channel ID must be odd"); michael@0: michael@0: if (mStreamID >= 0x80000000) { michael@0: // streamID must fit in 31 bits. This is theoretically possible michael@0: // because stream ID assignment is asynchronous to stream creation michael@0: // because of the protocol requirement that the ID in syn-stream michael@0: // be monotonically increasing. In reality this is really not possible michael@0: // because new streams stop being added to a session with 0x10000000 / 2 michael@0: // IDs still available and no race condition is going to bridge that gap, michael@0: // so we can be comfortable on just erroring out for correctness in that michael@0: // case. michael@0: LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Now we need to convert the flat http headers into a set michael@0: // of SPDY headers.. writing to mTxInlineFrame{sz} michael@0: michael@0: mTxInlineFrame[0] = SpdySession31::kFlag_Control; michael@0: mTxInlineFrame[1] = SpdySession31::kVersion; michael@0: mTxInlineFrame[2] = 0; michael@0: mTxInlineFrame[3] = SpdySession31::CONTROL_TYPE_SYN_STREAM; michael@0: // 4 to 7 are length and flags, we'll fill that in later michael@0: michael@0: uint32_t networkOrderID = PR_htonl(mStreamID); michael@0: memcpy(mTxInlineFrame + 8, &networkOrderID, 4); michael@0: michael@0: // this is the associated-to field, which is not used sending michael@0: // from the client in the http binding michael@0: memset (mTxInlineFrame + 12, 0, 4); michael@0: michael@0: // Priority flags are the E0 mask of byte 16. michael@0: // 0 is highest priority, 7 is lowest. michael@0: // The other 5 bits of byte 16 are unused. michael@0: michael@0: if (mPriority >= nsISupportsPriority::PRIORITY_LOWEST) michael@0: mTxInlineFrame[16] = 7 << 5; michael@0: else if (mPriority <= nsISupportsPriority::PRIORITY_HIGHEST) michael@0: mTxInlineFrame[16] = 0 << 5; michael@0: else { michael@0: // The priority mapping relies on the unfiltered ranged to be michael@0: // between -20 .. +20 michael@0: PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST == 20); michael@0: PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_HIGHEST == -20); michael@0: michael@0: // Add one to the priority so that values such as -10 and -11 michael@0: // get different spdy priorities - this appears to be an important michael@0: // breaking line in the priorities content assigns to michael@0: // transactions. michael@0: uint8_t calculatedPriority = 3 + ((mPriority + 1) / 5); michael@0: MOZ_ASSERT (!(calculatedPriority & 0xf8), michael@0: "Calculated Priority Out Of Range"); michael@0: mTxInlineFrame[16] = calculatedPriority << 5; michael@0: } michael@0: michael@0: // The client cert "slot". Right now we don't send client certs michael@0: mTxInlineFrame[17] = 0; michael@0: michael@0: nsCString versionHeader; michael@0: if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1) michael@0: versionHeader = NS_LITERAL_CSTRING("HTTP/1.1"); michael@0: else michael@0: versionHeader = NS_LITERAL_CSTRING("HTTP/1.0"); michael@0: michael@0: // use mRequestHead() to get a sense of how big to make the hash, michael@0: // even though we are parsing the actual text stream because michael@0: // it is legit to append headers. michael@0: nsClassHashtable michael@0: hdrHash(1 + (mTransaction->RequestHead()->Headers().Count() * 2)); michael@0: michael@0: const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); michael@0: michael@0: // need to hash all the headers together to remove duplicates, special michael@0: // headers, etc.. michael@0: michael@0: int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); michael@0: while (true) { michael@0: int32_t startIndex = crlfIndex + 2; michael@0: michael@0: crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); michael@0: if (crlfIndex == -1) michael@0: break; michael@0: michael@0: int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, michael@0: crlfIndex - startIndex); michael@0: if (colonIndex == -1) michael@0: break; michael@0: michael@0: nsDependentCSubstring name = Substring(beginBuffer + startIndex, michael@0: beginBuffer + colonIndex); michael@0: // all header names are lower case in spdy michael@0: ToLowerCase(name); michael@0: michael@0: // exclusions.. mostly from 3.2.1 michael@0: if (name.Equals("connection") || michael@0: name.Equals("keep-alive") || michael@0: name.Equals("host") || michael@0: name.Equals("accept-encoding") || michael@0: name.Equals("te") || michael@0: name.Equals("transfer-encoding")) michael@0: continue; michael@0: michael@0: nsCString *val = hdrHash.Get(name); michael@0: if (!val) { michael@0: val = new nsCString(); michael@0: hdrHash.Put(name, val); michael@0: } michael@0: michael@0: int32_t valueIndex = colonIndex + 1; michael@0: while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') michael@0: ++valueIndex; michael@0: michael@0: nsDependentCSubstring v = Substring(beginBuffer + valueIndex, michael@0: beginBuffer + crlfIndex); michael@0: if (!val->IsEmpty()) michael@0: val->Append(static_cast(0)); michael@0: val->Append(v); michael@0: michael@0: if (name.Equals("content-length")) { michael@0: int64_t len; michael@0: if (nsHttp::ParseInt64(val->get(), nullptr, &len)) michael@0: mRequestBodyLenRemaining = len; michael@0: } michael@0: } michael@0: michael@0: mTxInlineFrameUsed = 18; michael@0: michael@0: // Do not naively log the request headers here beacuse they might michael@0: // contain auth. The http transaction already logs the sanitized request michael@0: // headers at this same level so it is not necessary to do so here. michael@0: michael@0: const char *methodHeader = mTransaction->RequestHead()->Method().get(); michael@0: michael@0: // The header block length michael@0: uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */ michael@0: CompressToFrame(count); michael@0: michael@0: // :method, :path, :version comprise a HTTP/1 request line, so send those first michael@0: // to make life easy for any gateways michael@0: CompressToFrame(NS_LITERAL_CSTRING(":method")); michael@0: CompressToFrame(methodHeader, strlen(methodHeader)); michael@0: CompressToFrame(NS_LITERAL_CSTRING(":path")); michael@0: CompressToFrame(mTransaction->RequestHead()->RequestURI()); michael@0: CompressToFrame(NS_LITERAL_CSTRING(":version")); michael@0: CompressToFrame(versionHeader); michael@0: michael@0: CompressToFrame(NS_LITERAL_CSTRING(":host")); michael@0: CompressToFrame(hostHeader); michael@0: CompressToFrame(NS_LITERAL_CSTRING(":scheme")); michael@0: CompressToFrame(NS_LITERAL_CSTRING("https")); michael@0: michael@0: hdrHash.Enumerate(hdrHashEnumerate, this); michael@0: CompressFlushFrame(); michael@0: michael@0: // 4 to 7 are length and flags, which we can now fill in michael@0: (reinterpret_cast(mTxInlineFrame.get()))[1] = michael@0: PR_htonl(mTxInlineFrameUsed - 8); michael@0: michael@0: MOZ_ASSERT(!mTxInlineFrame[4], "Size greater than 24 bits"); michael@0: michael@0: // Determine whether to put the fin bit on the syn stream frame or whether michael@0: // to wait for a data packet to put it on. michael@0: michael@0: if (mTransaction->RequestHead()->IsGet() || michael@0: mTransaction->RequestHead()->IsConnect() || michael@0: mTransaction->RequestHead()->IsHead()) { michael@0: // for GET, CONNECT, and HEAD place the fin bit right on the michael@0: // syn stream packet michael@0: michael@0: mSentFinOnData = 1; michael@0: mTxInlineFrame[4] = SpdySession31::kFlag_Data_FIN; michael@0: } michael@0: else if (mTransaction->RequestHead()->IsPost() || michael@0: mTransaction->RequestHead()->IsPut() || michael@0: mTransaction->RequestHead()->IsOptions()) { michael@0: // place fin in a data frame even for 0 length messages, I've seen michael@0: // the google gateway be unhappy with fin-on-syn for 0 length POST michael@0: } michael@0: else if (!mRequestBodyLenRemaining) { michael@0: // for other HTTP extension methods, rely on the content-length michael@0: // to determine whether or not to put fin on syn michael@0: mSentFinOnData = 1; michael@0: mTxInlineFrame[4] = SpdySession31::kFlag_Data_FIN; michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameUsed - 18); michael@0: michael@0: // The size of the input headers is approximate michael@0: uint32_t ratio = michael@0: (mTxInlineFrameUsed - 18) * 100 / michael@0: (11 + mTransaction->RequestHead()->RequestURI().Length() + michael@0: mFlatHttpRequestHeaders.Length()); michael@0: michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::AdjustInitialWindow() michael@0: { michael@0: MOZ_ASSERT(mSession->PushAllowance() <= ASpdySession::kInitialRwin); michael@0: michael@0: // The session initial_window is sized for serverpushed streams. When we michael@0: // generate a client pulled stream we want to adjust the initial window michael@0: // to a huge value in a pipeline with that SYN_STREAM. michael@0: michael@0: // >0 even numbered IDs are pushed streams. michael@0: // odd numbered IDs are pulled streams. michael@0: // 0 is the sink for a pushed stream. michael@0: SpdyStream31 *stream = this; michael@0: if (!mStreamID) { michael@0: MOZ_ASSERT(mPushSource); michael@0: if (!mPushSource) michael@0: return; michael@0: stream = mPushSource; michael@0: MOZ_ASSERT(stream->mStreamID); michael@0: MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream michael@0: michael@0: // If the pushed stream has sent a FIN, there is no reason to update michael@0: // the window michael@0: if (stream->RecvdFin()) michael@0: return; michael@0: } michael@0: michael@0: // For server pushes we also want to include in the ack any data that has been michael@0: // buffered but unacknowledged. michael@0: michael@0: // mLocalUnacked is technically 64 bits, but because it can never grow larger than michael@0: // our window size (which is closer to 29bits max) we know it fits comfortably in 32. michael@0: // However we don't really enforce that, and track it as a 64 so that broken senders michael@0: // can still interoperate. That means we have to be careful with this calculation. michael@0: uint64_t toack64 = (ASpdySession::kInitialRwin - mSession->PushAllowance()) + michael@0: stream->mLocalUnacked; michael@0: stream->mLocalUnacked = 0; michael@0: if (toack64 > 0x7fffffff) { michael@0: stream->mLocalUnacked = toack64 - 0x7fffffff; michael@0: toack64 = 0x7fffffff; michael@0: } michael@0: uint32_t toack = static_cast(toack64); michael@0: if (!toack) michael@0: return; michael@0: toack = PR_htonl(toack); michael@0: michael@0: SpdySession31::EnsureBuffer(mTxInlineFrame, michael@0: mTxInlineFrameUsed + 16, michael@0: mTxInlineFrameUsed, michael@0: mTxInlineFrameSize); michael@0: michael@0: unsigned char *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; michael@0: mTxInlineFrameUsed += 16; michael@0: michael@0: memset(packet, 0, 8); michael@0: packet[0] = SpdySession31::kFlag_Control; michael@0: packet[1] = SpdySession31::kVersion; michael@0: packet[3] = SpdySession31::CONTROL_TYPE_WINDOW_UPDATE; michael@0: packet[7] = 8; // 8 data bytes after 8 byte header michael@0: michael@0: uint32_t id = PR_htonl(stream->mStreamID); michael@0: memcpy(packet + 8, &id, 4); michael@0: memcpy(packet + 12, &toack, 4); michael@0: michael@0: stream->mLocalWindow += PR_ntohl(toack); michael@0: LOG3(("AdjustInitialwindow %p 0x%X %u\n", michael@0: this, stream->mStreamID, PR_ntohl(toack))); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::UpdateTransportReadEvents(uint32_t count) michael@0: { michael@0: mTotalRead += count; michael@0: michael@0: mTransaction->OnTransportStatus(mSocketTransport, michael@0: NS_NET_STATUS_RECEIVING_FROM, michael@0: mTotalRead); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::UpdateTransportSendEvents(uint32_t count) michael@0: { michael@0: mTotalSent += count; michael@0: michael@0: // normally on non-windows platform we use TCP autotuning for michael@0: // the socket buffers, and this works well (managing enough michael@0: // buffers for BDP while conserving memory) for HTTP even when michael@0: // it creates really deep queues. However this 'buffer bloat' is michael@0: // a problem for spdy because it ruins the low latency properties michael@0: // necessary for PING and cancel to work meaningfully. michael@0: // michael@0: // If this stream represents a large upload, disable autotuning for michael@0: // the session and cap the send buffers by default at 128KB. michael@0: // (10Mbit/sec @ 100ms) michael@0: // michael@0: uint32_t bufferSize = gHttpHandler->SpdySendBufferSize(); michael@0: if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) { michael@0: mSetTCPSocketBuffer = 1; michael@0: mSocketTransport->SetSendBufferSize(bufferSize); michael@0: } michael@0: michael@0: if (mUpstreamState != SENDING_FIN_STREAM) michael@0: mTransaction->OnTransportStatus(mSocketTransport, michael@0: NS_NET_STATUS_SENDING_TO, michael@0: mTotalSent); michael@0: michael@0: if (!mSentWaitingFor && !mRequestBodyLenRemaining) { michael@0: mSentWaitingFor = 1; michael@0: mTransaction->OnTransportStatus(mSocketTransport, michael@0: NS_NET_STATUS_WAITING_FOR, michael@0: 0); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: SpdyStream31::TransmitFrame(const char *buf, michael@0: uint32_t *countUsed, michael@0: bool forceCommitment) michael@0: { michael@0: // If TransmitFrame returns SUCCESS than all the data is sent (or at least michael@0: // buffered at the session level), if it returns WOULD_BLOCK then none of michael@0: // the data is sent. michael@0: michael@0: // You can call this function with no data and no out parameter in order to michael@0: // flush internal buffers that were previously blocked on writing. You can michael@0: // of course feed new data to it as well. michael@0: michael@0: LOG3(("SpdyStream31::TransmitFrame %p inline=%d stream=%d", michael@0: this, mTxInlineFrameUsed, mTxStreamFrameSize)); michael@0: if (countUsed) michael@0: *countUsed = 0; michael@0: michael@0: if (!mTxInlineFrameUsed) { michael@0: MOZ_ASSERT(!buf); michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit"); michael@0: MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader"); michael@0: MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed), michael@0: "TransmitFrame arguments inconsistent"); michael@0: michael@0: uint32_t transmittedCount; michael@0: nsresult rv; michael@0: michael@0: // In the (relatively common) event that we have a small amount of data michael@0: // split between the inlineframe and the streamframe, then move the stream michael@0: // data into the inlineframe via copy in order to coalesce into one write. michael@0: // Given the interaction with ssl this is worth the small copy cost. michael@0: if (mTxStreamFrameSize && mTxInlineFrameUsed && michael@0: mTxStreamFrameSize < SpdySession31::kDefaultBufferSize && michael@0: mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) { michael@0: LOG3(("Coalesce Transmit")); michael@0: memcpy (mTxInlineFrame + mTxInlineFrameUsed, michael@0: buf, mTxStreamFrameSize); michael@0: if (countUsed) michael@0: *countUsed += mTxStreamFrameSize; michael@0: mTxInlineFrameUsed += mTxStreamFrameSize; michael@0: mTxStreamFrameSize = 0; michael@0: } michael@0: michael@0: rv = michael@0: mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed, michael@0: forceCommitment); michael@0: michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) { michael@0: MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK"); michael@0: mSession->TransactionHasDataToWrite(this); michael@0: } michael@0: if (NS_FAILED(rv)) // this will include WOULD_BLOCK michael@0: return rv; michael@0: michael@0: // This function calls mSegmentReader->OnReadSegment to report the actual SPDY michael@0: // bytes through to the SpdySession31 and then the HttpConnection which calls michael@0: // the socket write function. It will accept all of the inline and stream michael@0: // data because of the above 'commitment' even if it has to buffer michael@0: michael@0: rv = mSession->BufferOutput(reinterpret_cast(mTxInlineFrame.get()), michael@0: mTxInlineFrameUsed, michael@0: &transmittedCount); michael@0: LOG3(("SpdyStream31::TransmitFrame for inline BufferOutput session=%p " michael@0: "stream=%p result %x len=%d", michael@0: mSession, this, rv, transmittedCount)); michael@0: michael@0: MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, michael@0: "inconsistent inline commitment result"); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed, michael@0: "inconsistent inline commitment count"); michael@0: michael@0: SpdySession31::LogIO(mSession, this, "Writing from Inline Buffer", michael@0: reinterpret_cast(mTxInlineFrame.get()), michael@0: transmittedCount); michael@0: michael@0: if (mTxStreamFrameSize) { michael@0: if (!buf) { michael@0: // this cannot happen michael@0: MOZ_ASSERT(false, "Stream transmit with null buf argument to " michael@0: "TransmitFrame()"); michael@0: LOG(("Stream transmit with null buf argument to TransmitFrame()\n")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // If there is already data buffered, just add to that to form michael@0: // a single TLS Application Data Record - otherwise skip the memcpy michael@0: if (mSession->AmountOfOutputBuffered()) { michael@0: rv = mSession->BufferOutput(buf, mTxStreamFrameSize, michael@0: &transmittedCount); michael@0: } else { michael@0: rv = mSession->OnReadSegment(buf, mTxStreamFrameSize, michael@0: &transmittedCount); michael@0: } michael@0: michael@0: LOG3(("SpdyStream31::TransmitFrame for regular session=%p " michael@0: "stream=%p result %x len=%d", michael@0: mSession, this, rv, transmittedCount)); michael@0: michael@0: MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, michael@0: "inconsistent stream commitment result"); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: MOZ_ASSERT(transmittedCount == mTxStreamFrameSize, michael@0: "inconsistent stream commitment count"); michael@0: michael@0: SpdySession31::LogIO(mSession, this, "Writing from Transaction Buffer", michael@0: buf, transmittedCount); michael@0: michael@0: *countUsed += mTxStreamFrameSize; michael@0: } michael@0: michael@0: mSession->FlushOutputQueue(); michael@0: michael@0: // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 michael@0: UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); michael@0: michael@0: mTxInlineFrameUsed = 0; michael@0: mTxStreamFrameSize = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::ChangeState(enum stateType newState) michael@0: { michael@0: LOG3(("SpdyStream31::ChangeState() %p from %X to %X", michael@0: this, mUpstreamState, newState)); michael@0: mUpstreamState = newState; michael@0: return; michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) michael@0: { michael@0: LOG3(("SpdyStream31::GenerateDataFrameHeader %p len=%d last=%d", michael@0: this, dataLength, lastFrame)); michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty"); michael@0: MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty"); michael@0: MOZ_ASSERT(!(dataLength & 0xff000000), "datalength > 24 bits"); michael@0: michael@0: (reinterpret_cast(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID); michael@0: (reinterpret_cast(mTxInlineFrame.get()))[1] = michael@0: PR_htonl(dataLength); michael@0: michael@0: MOZ_ASSERT(!(mTxInlineFrame[0] & 0x80), "control bit set unexpectedly"); michael@0: MOZ_ASSERT(!mTxInlineFrame[4], "flag bits set unexpectedly"); michael@0: michael@0: mTxInlineFrameUsed = 8; michael@0: mTxStreamFrameSize = dataLength; michael@0: michael@0: if (lastFrame) { michael@0: mTxInlineFrame[4] |= SpdySession31::kFlag_Data_FIN; michael@0: if (dataLength) michael@0: mSentFinOnData = 1; michael@0: } michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::CompressToFrame(const nsACString &str) michael@0: { michael@0: CompressToFrame(str.BeginReading(), str.Length()); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::CompressToFrame(const nsACString *str) michael@0: { michael@0: CompressToFrame(str->BeginReading(), str->Length()); michael@0: } michael@0: michael@0: // Dictionary taken from michael@0: // http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 michael@0: michael@0: const unsigned char SpdyStream31::kDictionary[] = { michael@0: 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i michael@0: 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h michael@0: 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p michael@0: 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p michael@0: 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e michael@0: 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - - michael@0: 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - - michael@0: 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t - michael@0: 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p michael@0: 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e michael@0: 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c michael@0: 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o michael@0: 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - - michael@0: 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l michael@0: 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e - michael@0: 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p michael@0: 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s michael@0: 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e - michael@0: 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w michael@0: 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h michael@0: 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o michael@0: 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c michael@0: 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r michael@0: 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o michael@0: 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n michael@0: 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t michael@0: 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e michael@0: 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t michael@0: 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o michael@0: 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - - michael@0: 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t - michael@0: 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e michael@0: 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t michael@0: 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g michael@0: 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o michael@0: 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o michael@0: 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - - michael@0: 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n michael@0: 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - - michael@0: 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t michael@0: 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - - michael@0: 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n michael@0: 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - - michael@0: 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - - michael@0: 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - - michael@0: 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t michael@0: 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i michael@0: 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f michael@0: 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h michael@0: 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i michael@0: 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h - michael@0: 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o michael@0: 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s michael@0: 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - - michael@0: 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e - michael@0: 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - - michael@0: 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g michael@0: 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f - michael@0: 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i michael@0: 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e michael@0: 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t michael@0: 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e michael@0: 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c michael@0: 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - - michael@0: 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r michael@0: 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - - michael@0: 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a - michael@0: 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y michael@0: 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t michael@0: 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - - michael@0: 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a michael@0: 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a michael@0: 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - - michael@0: 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - - michael@0: 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r michael@0: 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r michael@0: 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r - michael@0: 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e michael@0: 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e - michael@0: 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l michael@0: 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r michael@0: 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e michael@0: 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g - michael@0: 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a michael@0: 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s michael@0: 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t michael@0: 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y michael@0: 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a - michael@0: 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i michael@0: 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w michael@0: 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n michael@0: 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - - michael@0: 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d michael@0: 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t - michael@0: 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u michael@0: 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0 michael@0: 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v michael@0: 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - - michael@0: 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1 michael@0: 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r michael@0: 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b michael@0: 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s michael@0: 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i michael@0: 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e michael@0: 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e - michael@0: 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i michael@0: 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2 michael@0: 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5 michael@0: 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0 michael@0: 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3 michael@0: 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7 michael@0: 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0 michael@0: 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4 michael@0: 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1 michael@0: 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1 michael@0: 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4 michael@0: 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4 michael@0: 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N michael@0: 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o michael@0: 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e michael@0: 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a michael@0: 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 - michael@0: 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e michael@0: 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o michael@0: 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m michael@0: 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4 michael@0: 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R michael@0: 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0 michael@0: 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h michael@0: 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0 michael@0: 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d michael@0: 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N michael@0: 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d michael@0: 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e michael@0: 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r michael@0: 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o michael@0: 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t michael@0: 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e michael@0: 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 - michael@0: 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e - michael@0: 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a michael@0: 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F michael@0: 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A michael@0: 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J michael@0: 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A michael@0: 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t - michael@0: 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v - michael@0: 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0 michael@0: 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n michael@0: 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W michael@0: 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u - michael@0: 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a michael@0: 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - - michael@0: 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k michael@0: 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t - michael@0: 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a michael@0: 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i michael@0: 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g michael@0: 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g michael@0: 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i michael@0: 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x michael@0: 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i michael@0: 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x michael@0: 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l michael@0: 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l michael@0: 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t michael@0: 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r michael@0: 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l michael@0: 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t michael@0: 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e michael@0: 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e michael@0: 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d michael@0: 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e michael@0: 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c michael@0: 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i michael@0: 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 - michael@0: 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - - michael@0: 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 - michael@0: }; michael@0: michael@0: // This can be called N times.. 1 for syn_reply and 0->N for headers michael@0: nsresult michael@0: SpdyStream31::Uncompress(z_stream *context, michael@0: char *blockStart, michael@0: uint32_t blockLen) michael@0: { michael@0: mDecompressedBytes += blockLen; michael@0: michael@0: context->avail_in = blockLen; michael@0: context->next_in = reinterpret_cast(blockStart); michael@0: bool triedDictionary = false; michael@0: michael@0: do { michael@0: context->next_out = michael@0: reinterpret_cast(mDecompressBuffer.get()) + michael@0: mDecompressBufferUsed; michael@0: context->avail_out = mDecompressBufferSize - mDecompressBufferUsed; michael@0: int zlib_rv = inflate(context, Z_NO_FLUSH); michael@0: michael@0: if (zlib_rv == Z_NEED_DICT) { michael@0: if (triedDictionary) { michael@0: LOG3(("SpdySession31::Uncompress %p Dictionary Error\n", this)); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: triedDictionary = true; michael@0: inflateSetDictionary(context, kDictionary, sizeof(kDictionary)); michael@0: } michael@0: michael@0: if (zlib_rv == Z_DATA_ERROR) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: if (zlib_rv == Z_MEM_ERROR) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // zlib's inflate() decreases context->avail_out by the amount it places michael@0: // in the output buffer michael@0: michael@0: mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed - michael@0: context->avail_out; michael@0: michael@0: // When there is no more output room, but input still available then michael@0: // increase the output space michael@0: if (zlib_rv == Z_OK && michael@0: !context->avail_out && context->avail_in) { michael@0: LOG3(("SpdyStream31::Uncompress %p Large Headers - so far %d", michael@0: this, mDecompressBufferSize)); michael@0: SpdySession31::EnsureBuffer(mDecompressBuffer, michael@0: mDecompressBufferSize + 4096, michael@0: mDecompressBufferUsed, michael@0: mDecompressBufferSize); michael@0: } michael@0: } michael@0: while (context->avail_in); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // mDecompressBuffer contains 0 to N uncompressed Name/Value Header blocks michael@0: nsresult michael@0: SpdyStream31::FindHeader(nsCString name, michael@0: nsDependentCSubstring &value) michael@0: { michael@0: const unsigned char *nvpair = reinterpret_cast michael@0: (mDecompressBuffer.get()) + 4; michael@0: const unsigned char *lastHeaderByte = reinterpret_cast michael@0: (mDecompressBuffer.get()) + mDecompressBufferUsed; michael@0: if (lastHeaderByte < nvpair) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: do { michael@0: uint32_t numPairs = PR_ntohl(reinterpret_cast(nvpair)[-1]); michael@0: michael@0: for (uint32_t index = 0; index < numPairs; ++index) { michael@0: if (lastHeaderByte < nvpair + 4) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + michael@0: (nvpair[2] << 8) + nvpair[3]; michael@0: if (lastHeaderByte < nvpair + 4 + nameLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: nsDependentCSubstring nameString = michael@0: Substring(reinterpret_cast(nvpair) + 4, michael@0: reinterpret_cast(nvpair) + 4 + nameLen); michael@0: if (lastHeaderByte < nvpair + 8 + nameLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + michael@0: (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; michael@0: if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: if (nameString.Equals(name)) { michael@0: value.Assign(((char *)nvpair) + 8 + nameLen, valueLen); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // that pair didn't match - try the next one in this block michael@0: nvpair += 8 + nameLen + valueLen; michael@0: } michael@0: michael@0: // move to the next name/value header block (if there is one) - the michael@0: // first pair is offset 4 bytes into it michael@0: nvpair += 4; michael@0: } while (lastHeaderByte >= nvpair); michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // ConvertHeaders is used to convert the response headers michael@0: // in a syn_reply or in 0..N headers frames that follow it into michael@0: // HTTP/1 format michael@0: nsresult michael@0: SpdyStream31::ConvertHeaders(nsACString &aHeadersOut) michael@0: { michael@0: // :status and :version are required. michael@0: nsDependentCSubstring status, version; michael@0: nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"), michael@0: status); michael@0: if (NS_FAILED(rv)) michael@0: return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv; michael@0: michael@0: rv = FindHeader(NS_LITERAL_CSTRING(":version"), michael@0: version); michael@0: if (NS_FAILED(rv)) michael@0: return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv; michael@0: michael@0: if (mDecompressedBytes && mDecompressBufferUsed) { michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, mDecompressedBytes); michael@0: uint32_t ratio = michael@0: mDecompressedBytes * 100 / mDecompressBufferUsed; michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); michael@0: } michael@0: michael@0: aHeadersOut.Truncate(); michael@0: aHeadersOut.SetCapacity(mDecompressBufferUsed + 64); michael@0: michael@0: // Connection, Keep-Alive and chunked transfer encodings are to be michael@0: // removed. michael@0: michael@0: // Content-Length is 'advisory'.. we will not strip it because it can michael@0: // create UI feedback. michael@0: michael@0: aHeadersOut.Append(version); michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING(" ")); michael@0: aHeadersOut.Append(status); michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); michael@0: michael@0: const unsigned char *nvpair = reinterpret_cast michael@0: (mDecompressBuffer.get()) + 4; michael@0: const unsigned char *lastHeaderByte = reinterpret_cast michael@0: (mDecompressBuffer.get()) + mDecompressBufferUsed; michael@0: if (lastHeaderByte < nvpair) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: do { michael@0: uint32_t numPairs = PR_ntohl(reinterpret_cast(nvpair)[-1]); michael@0: michael@0: for (uint32_t index = 0; index < numPairs; ++index) { michael@0: if (lastHeaderByte < nvpair + 4) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + michael@0: (nvpair[2] << 8) + nvpair[3]; michael@0: if (lastHeaderByte < nvpair + 4 + nameLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: nsDependentCSubstring nameString = michael@0: Substring(reinterpret_cast(nvpair) + 4, michael@0: reinterpret_cast(nvpair) + 4 + nameLen); michael@0: michael@0: if (lastHeaderByte < nvpair + 8 + nameLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: // Look for illegal characters in the nameString. michael@0: // This includes upper case characters and nulls (as they will michael@0: // break the fixup-nulls-in-value-string algorithm) michael@0: // Look for upper case characters in the name. They are illegal. michael@0: for (char *cPtr = nameString.BeginWriting(); michael@0: cPtr && cPtr < nameString.EndWriting(); michael@0: ++cPtr) { michael@0: if (*cPtr <= 'Z' && *cPtr >= 'A') { michael@0: nsCString toLog(nameString); michael@0: michael@0: LOG3(("SpdyStream31::ConvertHeaders session=%p stream=%p " michael@0: "upper case response header found. [%s]\n", michael@0: mSession, this, toLog.get())); michael@0: michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: // check for null characters michael@0: if (*cPtr == '\0') michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: // HTTP Chunked responses are not legal over spdy. We do not need michael@0: // to look for chunked specifically because it is the only HTTP michael@0: // allowed default encoding and we did not negotiate further encodings michael@0: // via TE michael@0: if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) { michael@0: LOG3(("SpdyStream31::ConvertHeaders session=%p stream=%p " michael@0: "transfer-encoding found. Chunked is invalid and no TE sent.", michael@0: mSession, this)); michael@0: michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: uint32_t valueLen = michael@0: (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + michael@0: (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; michael@0: michael@0: if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: // spdy transport level headers shouldn't be gatewayed into http/1 michael@0: if (!nameString.IsEmpty() && nameString[0] != ':' && michael@0: !nameString.Equals(NS_LITERAL_CSTRING("connection")) && michael@0: !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { michael@0: nsDependentCSubstring valueString = michael@0: Substring(reinterpret_cast(nvpair) + 8 + nameLen, michael@0: reinterpret_cast(nvpair) + 8 + nameLen + michael@0: valueLen); michael@0: michael@0: aHeadersOut.Append(nameString); michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); michael@0: michael@0: // expand NULL bytes in the value string michael@0: for (char *cPtr = valueString.BeginWriting(); michael@0: cPtr && cPtr < valueString.EndWriting(); michael@0: ++cPtr) { michael@0: if (*cPtr != 0) { michael@0: aHeadersOut.Append(*cPtr); michael@0: continue; michael@0: } michael@0: michael@0: // NULLs are really "\r\nhdr: " michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); michael@0: aHeadersOut.Append(nameString); michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); michael@0: } michael@0: michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); michael@0: } michael@0: // move to the next name/value pair in this block michael@0: nvpair += 8 + nameLen + valueLen; michael@0: } michael@0: michael@0: // move to the next name/value header block (if there is one) - the michael@0: // first pair is offset 4 bytes into it michael@0: nvpair += 4; michael@0: } while (lastHeaderByte >= nvpair); michael@0: michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: 3.1\r\n\r\n")); michael@0: LOG (("decoded response headers are:\n%s", michael@0: aHeadersOut.BeginReading())); michael@0: michael@0: // The spdy formatted buffer isnt needed anymore - free it up michael@0: mDecompressBuffer = nullptr; michael@0: mDecompressBufferSize = 0; michael@0: mDecompressBufferUsed = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::ExecuteCompress(uint32_t flushMode) michael@0: { michael@0: // Expect mZlib->avail_in and mZlib->next_in to be set. michael@0: // Append the compressed version of next_in to mTxInlineFrame michael@0: michael@0: do michael@0: { michael@0: uint32_t avail = mTxInlineFrameSize - mTxInlineFrameUsed; michael@0: if (avail < 1) { michael@0: SpdySession31::EnsureBuffer(mTxInlineFrame, michael@0: mTxInlineFrameSize + 2000, michael@0: mTxInlineFrameUsed, michael@0: mTxInlineFrameSize); michael@0: avail = mTxInlineFrameSize - mTxInlineFrameUsed; michael@0: } michael@0: michael@0: mZlib->next_out = mTxInlineFrame + mTxInlineFrameUsed; michael@0: mZlib->avail_out = avail; michael@0: deflate(mZlib, flushMode); michael@0: mTxInlineFrameUsed += avail - mZlib->avail_out; michael@0: } while (mZlib->avail_in > 0 || !mZlib->avail_out); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::CompressToFrame(uint32_t data) michael@0: { michael@0: // convert the data to 4 byte network byte order and write that michael@0: // to the compressed stream michael@0: data = PR_htonl(data); michael@0: michael@0: mZlib->next_in = reinterpret_cast (&data); michael@0: mZlib->avail_in = 4; michael@0: ExecuteCompress(Z_NO_FLUSH); michael@0: } michael@0: michael@0: michael@0: void michael@0: SpdyStream31::CompressToFrame(const char *data, uint32_t len) michael@0: { michael@0: // Format calls for a network ordered 32 bit length michael@0: // followed by the utf8 string michael@0: michael@0: uint32_t networkLen = PR_htonl(len); michael@0: michael@0: // write out the length michael@0: mZlib->next_in = reinterpret_cast (&networkLen); michael@0: mZlib->avail_in = 4; michael@0: ExecuteCompress(Z_NO_FLUSH); michael@0: michael@0: // write out the data michael@0: mZlib->next_in = (unsigned char *)data; michael@0: mZlib->avail_in = len; michael@0: ExecuteCompress(Z_NO_FLUSH); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::CompressFlushFrame() michael@0: { michael@0: mZlib->next_in = (unsigned char *) ""; michael@0: mZlib->avail_in = 0; michael@0: ExecuteCompress(Z_SYNC_FLUSH); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::Close(nsresult reason) michael@0: { michael@0: mTransaction->Close(reason); michael@0: } michael@0: michael@0: void michael@0: SpdyStream31::UpdateRemoteWindow(int32_t delta) michael@0: { michael@0: mRemoteWindow += delta; michael@0: michael@0: // If the stream had a <=0 window, that has now opened michael@0: // schedule it for writing again michael@0: if (mBlockedOnRwin && mSession->RemoteSessionWindow() > 0 && michael@0: mRemoteWindow > 0) { michael@0: // the window has been opened :) michael@0: mSession->TransactionHasDataToWrite(this); michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAHttpSegmentReader michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: SpdyStream31::OnReadSegment(const char *buf, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: LOG3(("SpdyStream31::OnReadSegment %p count=%d state=%x", michael@0: this, count, mUpstreamState)); michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader"); michael@0: michael@0: nsresult rv = NS_ERROR_UNEXPECTED; michael@0: uint32_t dataLength; michael@0: michael@0: switch (mUpstreamState) { michael@0: case GENERATING_SYN_STREAM: michael@0: // The buffer is the HTTP request stream, including at least part of the michael@0: // HTTP request header. This state's job is to build a SYN_STREAM frame michael@0: // from the header information. count is the number of http bytes available michael@0: // (which may include more than the header), and in countRead we return michael@0: // the number of those bytes that we consume (i.e. the portion that are michael@0: // header bytes) michael@0: michael@0: rv = ParseHttpRequestHeaders(buf, count, countRead); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d", michael@0: this, *countRead, count, mSynFrameComplete)); michael@0: if (mSynFrameComplete) { michael@0: AdjustInitialWindow(); michael@0: rv = TransmitFrame(nullptr, nullptr, true); michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) { michael@0: // this can't happen michael@0: MOZ_ASSERT(false, "Transmit Frame SYN_FRAME must at least buffer data"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: ChangeState(GENERATING_REQUEST_BODY); michael@0: break; michael@0: } michael@0: MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data"); michael@0: break; michael@0: michael@0: case GENERATING_REQUEST_BODY: michael@0: if ((mRemoteWindow <= 0) || (mSession->RemoteSessionWindow() <= 0)) { michael@0: *countRead = 0; michael@0: LOG3(("SpdyStream31 this=%p, id 0x%X request body suspended because " michael@0: "remote window is stream=%ld session=%ld.\n", this, mStreamID, michael@0: mRemoteWindow, mSession->RemoteSessionWindow())); michael@0: mBlockedOnRwin = true; michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: mBlockedOnRwin = false; michael@0: michael@0: dataLength = std::min(count, mChunkSize); michael@0: michael@0: if (dataLength > mRemoteWindow) michael@0: dataLength = static_cast(mRemoteWindow); michael@0: michael@0: if (dataLength > mSession->RemoteSessionWindow()) michael@0: dataLength = static_cast(mSession->RemoteSessionWindow()); michael@0: michael@0: LOG3(("SpdyStream31 this=%p id 0x%X remote window is stream %ld and " michael@0: "session %ld. Chunk is %d\n", michael@0: this, mStreamID, mRemoteWindow, mSession->RemoteSessionWindow(), michael@0: dataLength)); michael@0: mRemoteWindow -= dataLength; michael@0: mSession->DecrementRemoteSessionWindow(dataLength); michael@0: michael@0: LOG3(("SpdyStream31 %p id %x request len remaining %d, " michael@0: "count avail %d, chunk used %d", michael@0: this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); michael@0: if (dataLength > mRequestBodyLenRemaining) michael@0: return NS_ERROR_UNEXPECTED; michael@0: mRequestBodyLenRemaining -= dataLength; michael@0: GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); michael@0: ChangeState(SENDING_REQUEST_BODY); michael@0: // NO BREAK michael@0: michael@0: case SENDING_REQUEST_BODY: michael@0: MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b"); michael@0: rv = TransmitFrame(buf, countRead, false); michael@0: MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, michael@0: "Transmit Frame should be all or nothing"); michael@0: michael@0: LOG3(("TransmitFrame() rv=%x returning %d data bytes. " michael@0: "Header is %d Body is %d.", michael@0: rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize)); michael@0: michael@0: // normalize a partial write with a WOULD_BLOCK into just a partial write michael@0: // as some code will take WOULD_BLOCK to mean an error with nothing michael@0: // written (e.g. nsHttpTransaction::ReadRequestSegment() michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) michael@0: rv = NS_OK; michael@0: michael@0: // If that frame was all sent, look for another one michael@0: if (!mTxInlineFrameUsed) michael@0: ChangeState(GENERATING_REQUEST_BODY); michael@0: break; michael@0: michael@0: case SENDING_FIN_STREAM: michael@0: MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment"); michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSERT(false, "SpdyStream31::OnReadSegment non-write state"); michael@0: break; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAHttpSegmentWriter michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: SpdyStream31::OnWriteSegment(char *buf, michael@0: uint32_t count, michael@0: uint32_t *countWritten) michael@0: { michael@0: LOG3(("SpdyStream31::OnWriteSegment %p count=%d state=%x 0x%X\n", michael@0: this, count, mUpstreamState, mStreamID)); michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(mSegmentWriter); michael@0: michael@0: if (!mPushSource) michael@0: return mSegmentWriter->OnWriteSegment(buf, count, countWritten); michael@0: michael@0: nsresult rv; michael@0: rv = mPushSource->GetBufferedData(buf, count, countWritten); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mSession->ConnectPushedStream(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla michael@0: