/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla. * * The Initial Developer of the Original Code is * Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Patrick McManus * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsHttp.h" #include "SpdySession.h" #include "SpdyStream.h" #include "nsAlgorithm.h" #include "prnetdb.h" #include "nsHttpRequestHead.h" #include "mozilla/Telemetry.h" #include "nsISocketTransport.h" #include "nsISupportsPriority.h" #ifdef DEBUG // defined by the socket transport service while active extern PRThread *gSocketThread; #endif namespace mozilla { namespace net { SpdyStream::SpdyStream(nsAHttpTransaction *httpTransaction, SpdySession *spdySession, nsISocketTransport *socketTransport, PRUint32 chunkSize, z_stream *compressionContext, PRInt32 priority) : mUpstreamState(GENERATING_SYN_STREAM), mTransaction(httpTransaction), mSession(spdySession), mSocketTransport(socketTransport), mSegmentReader(nsnull), mSegmentWriter(nsnull), mStreamID(0), mChunkSize(chunkSize), mSynFrameComplete(0), mBlockedOnWrite(0), mRequestBlockedOnRead(0), mSentFinOnData(0), mRecvdFin(0), mFullyOpen(0), mTxInlineFrameAllocation(SpdySession::kDefaultBufferSize), mTxInlineFrameSize(0), mTxInlineFrameSent(0), mTxStreamFrameSize(0), mTxStreamFrameSent(0), mZlib(compressionContext), mRequestBodyLen(0), mPriority(priority) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG3(("SpdyStream::SpdyStream %p", this)); mTxInlineFrame = new char[mTxInlineFrameAllocation]; } SpdyStream::~SpdyStream() { } // ReadSegments() is used to write data down the socket. Generally, HTTP // request data is pulled from the approriate transaction and // converted to SPDY data. Sometimes control data like a window-update is // generated instead. nsresult SpdyStream::ReadSegments(nsAHttpSegmentReader *reader, PRUint32 count, PRUint32 *countRead) { LOG3(("SpdyStream %p ReadSegments reader=%p count=%d state=%x", this, reader, count, mUpstreamState)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsresult rv = NS_ERROR_UNEXPECTED; mBlockedOnWrite = 0; mRequestBlockedOnRead = 0; switch (mUpstreamState) { case GENERATING_SYN_STREAM: case GENERATING_REQUEST_BODY: case SENDING_REQUEST_BODY: // Call into the HTTP Transaction to generate the HTTP request // stream. That stream will show up in OnReadSegment(). mSegmentReader = reader; rv = mTransaction->ReadSegments(this, count, countRead); mSegmentReader = nsnull; if (NS_SUCCEEDED(rv) && mUpstreamState == GENERATING_SYN_STREAM && !mSynFrameComplete) mBlockedOnWrite = 1; // Mark that we are blocked on read if we the http transaction // is going to get us going again. if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mBlockedOnWrite) mRequestBlockedOnRead = 1; if (!mBlockedOnWrite && NS_SUCCEEDED(rv) && (!*countRead)) { LOG3(("ReadSegments %p Send Request data complete from %x", this, mUpstreamState)); if (mSentFinOnData) { ChangeState(UPSTREAM_COMPLETE); } else { GenerateDataFrameHeader(0, true); ChangeState(SENDING_FIN_STREAM); mBlockedOnWrite = 1; rv = NS_BASE_STREAM_WOULD_BLOCK; } } break; case SENDING_SYN_STREAM: // We were trying to send the SYN-STREAM but only got part of it out // before being blocked. Try and send more. mSegmentReader = reader; rv = TransmitFrame(nsnull, nsnull); mSegmentReader = nsnull; *countRead = 0; if (NS_SUCCEEDED(rv)) rv = NS_BASE_STREAM_WOULD_BLOCK; if (!mTxInlineFrameSize) { if (mSentFinOnData) { ChangeState(UPSTREAM_COMPLETE); rv = NS_OK; } else { ChangeState(GENERATING_REQUEST_BODY); mBlockedOnWrite = 1; } } break; case SENDING_FIN_STREAM: // We were trying to send the SYN-STREAM but only got part of it out // before being blocked. Try and send more. if (!mSentFinOnData) { mSegmentReader = reader; rv = TransmitFrame(nsnull, nsnull); mSegmentReader = nsnull; if (!mTxInlineFrameSize) ChangeState(UPSTREAM_COMPLETE); } else { rv = NS_OK; mTxInlineFrameSize = 0; // cancel fin data packet ChangeState(UPSTREAM_COMPLETE); } *countRead = 0; // don't change OK to WOULD BLOCK. we are really done sending if OK break; default: NS_ABORT_IF_FALSE(false, "SpdyStream::ReadSegments unknown state"); break; } return rv; } // WriteSegments() is used to read data off the socket. Generally this is // just the SPDY frame header and from there the appropriate SPDYStream // is identified from the Stream-ID. The http transaction associated with // that read then pulls in the data directly. nsresult SpdyStream::WriteSegments(nsAHttpSegmentWriter *writer, PRUint32 count, PRUint32 *countWritten) { LOG3(("SpdyStream::WriteSegments %p count=%d state=%x", this, count, mUpstreamState)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress"); mSegmentWriter = writer; nsresult rv = mTransaction->WriteSegments(writer, count, countWritten); mSegmentWriter = nsnull; return rv; } PLDHashOperator SpdyStream::hdrHashEnumerate(const nsACString &key, nsAutoPtr &value, void *closure) { SpdyStream *self = static_cast(closure); self->CompressToFrame(key); self->CompressToFrame(value.get()); return PL_DHASH_NEXT; } nsresult SpdyStream::ParseHttpRequestHeaders(const char *buf, PRUint32 avail, PRUint32 *countUsed) { // Returns NS_OK even if the headers are incomplete // set mSynFrameComplete flag if they are complete NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state"); LOG3(("SpdyStream::ParseHttpRequestHeaders %p avail=%d state=%x", this, avail, mUpstreamState)); mFlatHttpRequestHeaders.Append(buf, avail); // We can use the simple double crlf because firefox is the // only client we are parsing PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); if (endHeader == -1) { // We don't have all the headers yet LOG3(("SpdyStream::ParseHttpRequestHeaders %p " "Need more header bytes. Len = %d", this, mFlatHttpRequestHeaders.Length())); *countUsed = avail; return NS_OK; } // We have recvd all the headers, trim the local // buffer of the final empty line, and set countUsed to reflect // the whole header has been consumed. PRUint32 oldLen = mFlatHttpRequestHeaders.Length(); mFlatHttpRequestHeaders.SetLength(endHeader + 2); *countUsed = avail - (oldLen - endHeader) + 4; mSynFrameComplete = 1; // It is now OK to assign a streamID that we are assured will // be monotonically increasing amongst syn-streams on this // session mStreamID = mSession->RegisterStreamID(this); NS_ABORT_IF_FALSE(mStreamID & 1, "Spdy Stream Channel ID must be odd"); if (mStreamID >= 0x80000000) { // streamID must fit in 31 bits. This is theoretically possible // because stream ID assignment is asynchronous to stream creation // because of the protocol requirement that the ID in syn-stream // be monotonically increasing. In reality this is really not possible // because new streams stop being added to a session with 0x10000000 / 2 // IDs still available and no race condition is going to bridge that gap, // so we can be comfortable on just erroring out for correctness in that // case. LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); return NS_ERROR_UNEXPECTED; } // Now we need to convert the flat http headers into a set // of SPDY headers.. writing to mTxInlineFrame{sz} mTxInlineFrame[0] = SpdySession::kFlag_Control; mTxInlineFrame[1] = 2; /* version */ mTxInlineFrame[2] = 0; mTxInlineFrame[3] = SpdySession::CONTROL_TYPE_SYN_STREAM; // 4 to 7 are length and flags, we'll fill that in later PRUint32 networkOrderID = PR_htonl(mStreamID); memcpy(mTxInlineFrame + 8, &networkOrderID, 4); // this is the associated-to field, which is not used sending // from the client in the http binding memset (mTxInlineFrame + 12, 0, 4); // Priority flags are the C0 mask of byte 16. // From low to high: 00 40 80 C0 // higher raw priority values are actually less important // // 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] = SpdySession::kPri00; else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL) mTxInlineFrame[16] = SpdySession::kPri01; else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH) mTxInlineFrame[16] = SpdySession::kPri02; else mTxInlineFrame[16] = SpdySession::kPri03; mTxInlineFrame[17] = 0; /* unused */ // nsCString methodHeader; // mTransaction->RequestHead()->Method()->ToUTF8String(methodHeader); const char *methodHeader = mTransaction->RequestHead()->Method().get(); nsCString hostHeader; mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); nsCString versionHeader; if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1) versionHeader = NS_LITERAL_CSTRING("HTTP/1.1"); else versionHeader = NS_LITERAL_CSTRING("HTTP/1.0"); nsClassHashtable hdrHash; // use mRequestHead() to get a sense of how big to make the hash, // even though we are parsing the actual text stream because // it is legit to append headers. hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2)); const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); // need to hash all the headers together to remove duplicates, special // headers, etc.. PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); while (true) { PRInt32 startIndex = crlfIndex + 2; crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); if (crlfIndex == -1) break; PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, crlfIndex - startIndex); if (colonIndex == -1) break; nsDependentCSubstring name = Substring(beginBuffer + startIndex, beginBuffer + colonIndex); // all header names are lower case in spdy ToLowerCase(name); if (name.Equals("method") || name.Equals("version") || name.Equals("scheme") || name.Equals("keep-alive") || name.Equals("accept-encoding") || name.Equals("te") || name.Equals("connection") || name.Equals("proxy-connection") || name.Equals("url")) continue; nsCString *val = hdrHash.Get(name); if (!val) { val = new nsCString(); hdrHash.Put(name, val); } PRInt32 valueIndex = colonIndex + 1; while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') ++valueIndex; nsDependentCSubstring v = Substring(beginBuffer + valueIndex, beginBuffer + crlfIndex); if (!val->IsEmpty()) val->Append(static_cast(0)); val->Append(v); if (name.Equals("content-length")) { PRInt64 len; if (nsHttp::ParseInt64(val->get(), nsnull, &len)) mRequestBodyLen = len; } } mTxInlineFrameSize = 18; LOG3(("http request headers to encode are: \n%s", mFlatHttpRequestHeaders.get())); // The header block length PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */ CompressToFrame(count); // method, scheme, url, and version headers for request line 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(mTransaction->RequestHead()->RequestURI()); CompressToFrame(NS_LITERAL_CSTRING("version")); CompressToFrame(versionHeader); hdrHash.Enumerate(hdrHashEnumerate, this); CompressFlushFrame(); // 4 to 7 are length and flags, which we can now fill in (reinterpret_cast(mTxInlineFrame.get()))[1] = PR_htonl(mTxInlineFrameSize - 8); NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "Size greater than 24 bits"); // For methods other than POST and PUT, we will set the fin bit // right on the syn stream packet. if (mTransaction->RequestHead()->Method() != nsHttp::Post && mTransaction->RequestHead()->Method() != nsHttp::Put) { mSentFinOnData = 1; mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN; } Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameSize - 18); // The size of the input headers is approximate PRUint32 ratio = (mTxInlineFrameSize - 18) * 100 / (11 + mTransaction->RequestHead()->RequestURI().Length() + mFlatHttpRequestHeaders.Length()); Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); return NS_OK; } nsresult SpdyStream::TransmitFrame(const char *buf, PRUint32 *countUsed) { NS_ABORT_IF_FALSE(mTxInlineFrameSize, "empty stream frame in transmit"); NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader"); PRUint32 transmittedCount; nsresult rv; LOG3(("SpdyStream::TransmitFrame %p inline=%d of %d stream=%d of %d", this, mTxInlineFrameSent, mTxInlineFrameSize, mTxStreamFrameSent, mTxStreamFrameSize)); if (countUsed) *countUsed = 0; mBlockedOnWrite = 0; // In the (relatively common) event that we have a small amount of data // split between the inlineframe and the streamframe, then move the stream // data into the inlineframe via copy in order to coalesce into one write. // Given the interaction with ssl this is worth the small copy cost. if (mTxStreamFrameSize && mTxInlineFrameSize && !mTxInlineFrameSent && !mTxStreamFrameSent && mTxStreamFrameSize < SpdySession::kDefaultBufferSize && mTxInlineFrameSize + mTxStreamFrameSize < mTxInlineFrameAllocation) { LOG3(("Coalesce Transmit")); memcpy (mTxInlineFrame + mTxInlineFrameSize, buf, mTxStreamFrameSize); if (countUsed) *countUsed += mTxStreamFrameSize; mTxInlineFrameSize += mTxStreamFrameSize; mTxStreamFrameSent = 0; mTxStreamFrameSize = 0; } // This function calls mSegmentReader->OnReadSegment to report the actual SPDY // bytes through to the SpdySession and then the HttpConnection which calls // the socket write function. while (mTxInlineFrameSent < mTxInlineFrameSize) { rv = mSegmentReader->OnReadSegment(mTxInlineFrame + mTxInlineFrameSent, mTxInlineFrameSize - mTxInlineFrameSent, &transmittedCount); LOG3(("SpdyStream::TransmitFrame for inline session=%p " "stream=%p result %x len=%d", mSession, this, rv, transmittedCount)); SpdySession::LogIO(mSession, this, "Writing from Inline Buffer", mTxInlineFrame + mTxInlineFrameSent, transmittedCount); if (rv == NS_BASE_STREAM_WOULD_BLOCK) mBlockedOnWrite = 1; if (NS_FAILED(rv)) // this will include WOULD_BLOCK return rv; mTxInlineFrameSent += transmittedCount; } PRUint32 offset = 0; NS_ABORT_IF_FALSE(mTxStreamFrameSize >= mTxStreamFrameSent, "negative unsent"); PRUint32 avail = mTxStreamFrameSize - mTxStreamFrameSent; while (avail) { NS_ABORT_IF_FALSE(countUsed, "null countused pointer in a stream context"); rv = mSegmentReader->OnReadSegment(buf + offset, avail, &transmittedCount); LOG3(("SpdyStream::TransmitFrame for regular session=%p " "stream=%p result %x len=%d", mSession, this, rv, transmittedCount)); SpdySession::LogIO(mSession, this, "Writing from Transaction Buffer", buf + offset, transmittedCount); if (rv == NS_BASE_STREAM_WOULD_BLOCK) mBlockedOnWrite = 1; if (NS_FAILED(rv)) // this will include WOULD_BLOCK return rv; if (mUpstreamState == SENDING_REQUEST_BODY) { mTransaction->OnTransportStatus(mSocketTransport, nsISocketTransport::STATUS_SENDING_TO, transmittedCount); } *countUsed += transmittedCount; avail -= transmittedCount; offset += transmittedCount; mTxStreamFrameSent += transmittedCount; } if (!avail) { mTxInlineFrameSent = 0; mTxInlineFrameSize = 0; mTxStreamFrameSent = 0; mTxStreamFrameSize = 0; } return NS_OK; } void SpdyStream::ChangeState(enum stateType newState) { LOG3(("SpdyStream::ChangeState() %p from %X to %X", this, mUpstreamState, newState)); mUpstreamState = newState; return; } void SpdyStream::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame) { LOG3(("SpdyStream::GenerateDataFrameHeader %p len=%d last=%d", this, dataLength, lastFrame)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(!mTxInlineFrameSize, "inline frame not empty"); NS_ABORT_IF_FALSE(!mTxInlineFrameSent, "inline partial send not 0"); NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty"); NS_ABORT_IF_FALSE(!mTxStreamFrameSent, "stream partial send not 0"); NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits"); (reinterpret_cast(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID); (reinterpret_cast(mTxInlineFrame.get()))[1] = PR_htonl(dataLength); NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80), "control bit set unexpectedly"); NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly"); mTxInlineFrameSize = 8; mTxStreamFrameSize = dataLength; if (lastFrame) { mTxInlineFrame[4] |= SpdySession::kFlag_Data_FIN; if (dataLength) mSentFinOnData = 1; } } void SpdyStream::CompressToFrame(const nsACString &str) { CompressToFrame(str.BeginReading(), str.Length()); } void SpdyStream::CompressToFrame(const nsACString *str) { CompressToFrame(str->BeginReading(), str->Length()); } // 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 const char *SpdyStream::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"; // use for zlib data types void * SpdyStream::zlib_allocator(void *opaque, uInt items, uInt size) { return moz_xmalloc(items * size); } // use for zlib data types void SpdyStream::zlib_destructor(void *opaque, void *addr) { moz_free(addr); } void SpdyStream::ExecuteCompress(PRUint32 flushMode) { // Expect mZlib->avail_in and mZlib->next_in to be set. // Append the compressed version of next_in to mTxInlineFrame do { PRUint32 avail = mTxInlineFrameAllocation - mTxInlineFrameSize; if (avail < 1) { SpdySession::EnsureBuffer(mTxInlineFrame, mTxInlineFrameAllocation + 2000, mTxInlineFrameSize, mTxInlineFrameAllocation); avail = mTxInlineFrameAllocation - mTxInlineFrameSize; } mZlib->next_out = reinterpret_cast (mTxInlineFrame.get()) + mTxInlineFrameSize; mZlib->avail_out = avail; deflate(mZlib, flushMode); mTxInlineFrameSize += avail - mZlib->avail_out; } while (mZlib->avail_in > 0 || !mZlib->avail_out); } void SpdyStream::CompressToFrame(PRUint16 data) { // convert the data to network byte order and write that // to the compressed stream data = PR_htons(data); mZlib->next_in = reinterpret_cast (&data); mZlib->avail_in = 2; ExecuteCompress(Z_NO_FLUSH); } void SpdyStream::CompressToFrame(const char *data, PRUint32 len) { // Format calls for a network ordered 16 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 = len; networkLen = PR_htons(len); // write out the length mZlib->next_in = reinterpret_cast (&networkLen); mZlib->avail_in = 2; ExecuteCompress(Z_NO_FLUSH); // write out the data mZlib->next_in = (unsigned char *)data; mZlib->avail_in = len; ExecuteCompress(Z_NO_FLUSH); } void SpdyStream::CompressFlushFrame() { mZlib->next_in = (unsigned char *) ""; mZlib->avail_in = 0; ExecuteCompress(Z_SYNC_FLUSH); } void SpdyStream::Close(nsresult reason) { mTransaction->Close(reason); } //----------------------------------------------------------------------------- // nsAHttpSegmentReader //----------------------------------------------------------------------------- nsresult SpdyStream::OnReadSegment(const char *buf, PRUint32 count, PRUint32 *countRead) { LOG3(("SpdyStream::OnReadSegment %p count=%d state=%x", this, count, mUpstreamState)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader"); nsresult rv = NS_ERROR_UNEXPECTED; PRUint32 dataLength; switch (mUpstreamState) { case GENERATING_SYN_STREAM: // The buffer is the HTTP request stream, including at least part of the // HTTP request header. This state's job is to build a SYN_STREAM frame // from the header information. count is the number of http bytes available // (which may include more than the header), and in countRead we return // the number of those bytes that we consume (i.e. the portion that are // header bytes) rv = ParseHttpRequestHeaders(buf, count, countRead); if (NS_FAILED(rv)) return rv; LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d", this, *countRead, count, mSynFrameComplete)); if (mSynFrameComplete) { NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment SynFrameComplete 0b"); rv = TransmitFrame(nsnull, nsnull); if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) rv = NS_OK; if (mTxInlineFrameSize) ChangeState(SENDING_SYN_STREAM); else ChangeState(GENERATING_REQUEST_BODY); break; } NS_ABORT_IF_FALSE(*countRead == count, "Header parsing not complete but unused data"); break; case GENERATING_REQUEST_BODY: NS_ABORT_IF_FALSE(!mTxInlineFrameSent, "OnReadSegment in generating_request_body with " "frame in progress"); dataLength = NS_MIN(count, mChunkSize); LOG3(("SpdyStream %p id %x request len remaining %d, " "count avail %d, chunk used %d", this, mStreamID, mRequestBodyLen, count, dataLength)); if (dataLength > mRequestBodyLen) return NS_ERROR_UNEXPECTED; mRequestBodyLen -= dataLength; GenerateDataFrameHeader(dataLength, !mRequestBodyLen); ChangeState(SENDING_REQUEST_BODY); // NO BREAK case SENDING_REQUEST_BODY: NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment Send Data Header 0b"); rv = TransmitFrame(buf, countRead); LOG3(("TransmitFrame() rv=%x returning %d data bytes. " "Header is %d/%d Body is %d/%d.", rv, *countRead, mTxInlineFrameSent, mTxInlineFrameSize, mTxStreamFrameSent, mTxStreamFrameSize)); if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) rv = NS_OK; // If that frame was all sent, look for another one if (!mTxInlineFrameSize) ChangeState(GENERATING_REQUEST_BODY); break; case SENDING_SYN_STREAM: rv = NS_BASE_STREAM_WOULD_BLOCK; break; case SENDING_FIN_STREAM: NS_ABORT_IF_FALSE(false, "resuming partial fin stream out of OnReadSegment"); break; default: NS_ABORT_IF_FALSE(false, "SpdyStream::OnReadSegment non-write state"); break; } return rv; } //----------------------------------------------------------------------------- // nsAHttpSegmentWriter //----------------------------------------------------------------------------- nsresult SpdyStream::OnWriteSegment(char *buf, PRUint32 count, PRUint32 *countWritten) { LOG3(("SpdyStream::OnWriteSegment %p count=%d state=%x", this, count, mUpstreamState)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); return mSegmentWriter->OnWriteSegment(buf, count, countWritten); } } // namespace mozilla::net } // namespace mozilla