michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: // Log on level :5, instead of default :4. michael@0: #undef LOG michael@0: #define LOG(args) LOG5(args) michael@0: #undef LOG_ENABLED michael@0: #define LOG_ENABLED() LOG5_ENABLED() michael@0: michael@0: #include michael@0: michael@0: #include "Http2Compression.h" michael@0: #include "Http2Session.h" michael@0: #include "Http2Stream.h" michael@0: #include "Http2Push.h" michael@0: michael@0: #include "mozilla/Telemetry.h" michael@0: #include "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 "prnetdb.h" michael@0: michael@0: #ifdef DEBUG michael@0: // defined by the socket transport service while active michael@0: extern PRThread *gSocketThread; michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, michael@0: Http2Session *session, michael@0: int32_t priority) michael@0: : mStreamID(0), michael@0: mSession(session), michael@0: mUpstreamState(GENERATING_HEADERS), michael@0: mState(IDLE), michael@0: mAllHeadersSent(0), michael@0: mAllHeadersReceived(0), michael@0: mTransaction(httpTransaction), michael@0: mSocketTransport(session->SocketTransport()), michael@0: mSegmentReader(nullptr), michael@0: mSegmentWriter(nullptr), michael@0: mChunkSize(session->SendingChunkSize()), michael@0: mRequestBlockedOnRead(0), michael@0: mRecvdFin(0), michael@0: mRecvdReset(0), michael@0: mSentReset(0), michael@0: mCountAsActive(0), michael@0: mSentFin(0), michael@0: mSentWaitingFor(0), michael@0: mSetTCPSocketBuffer(0), michael@0: mTxInlineFrameSize(Http2Session::kDefaultBufferSize), michael@0: mTxInlineFrameUsed(0), michael@0: mTxStreamFrameSize(0), michael@0: mRequestBodyLenRemaining(0), 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(("Http2Stream::Http2Stream %p", this)); michael@0: michael@0: mServerReceiveWindow = session->GetServerInitialStreamWindow(); michael@0: mClientReceiveWindow = session->PushAllowance(); michael@0: michael@0: mTxInlineFrame = new uint8_t[mTxInlineFrameSize]; michael@0: michael@0: PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority); michael@0: michael@0: // values of priority closer to 0 are higher priority for both michael@0: // mPriority and the priority argument. They are relative but not michael@0: // proportional. michael@0: int32_t httpPriority; michael@0: if (priority >= nsISupportsPriority::PRIORITY_LOWEST) { michael@0: httpPriority = kWorstPriority; michael@0: } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) { michael@0: httpPriority = kBestPriority; michael@0: } else { michael@0: httpPriority = kNormalPriority + priority; michael@0: } michael@0: MOZ_ASSERT(httpPriority >= 0); michael@0: mPriority = static_cast(httpPriority); michael@0: } michael@0: michael@0: Http2Stream::~Http2Stream() michael@0: { michael@0: mStreamID = Http2Session::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 HTTP/2 data. Sometimes control data like a window-update is michael@0: // generated instead. michael@0: michael@0: nsresult michael@0: Http2Stream::ReadSegments(nsAHttpSegmentReader *reader, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: LOG3(("Http2Stream %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: if (mRecvdFin || mRecvdReset) { michael@0: // Don't transmit any request frames if the peer cannot respond michael@0: LOG3(("Http2Stream %p ReadSegments request stream aborted due to" michael@0: " response side closure\n", this)); michael@0: return NS_ERROR_ABORT; michael@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_HEADERS: michael@0: case GENERATING_BODY: michael@0: case SENDING_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_HEADERS && michael@0: !mAllHeadersSent) 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 HTTP/2 frame. Likewise, a non 0 value is michael@0: // a queued, but complete, http/2 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(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, " michael@0: "mUpstreamState=%x",this, mStreamID, mUpstreamState)); michael@0: if (mSentFin) { michael@0: ChangeState(UPSTREAM_COMPLETE); 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 (!mSentFin) { 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: } 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, "Http2Stream::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 a call through to the associate nsHttpTransaciton for this stream michael@0: // for the remaining data bytes indicated by the current DATA frame. michael@0: michael@0: nsresult michael@0: Http2Stream::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(("Http2Stream::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: void michael@0: Http2Stream::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("/[http2.")); michael@0: outKey.AppendInt(serial); michael@0: outKey.Append(NS_LITERAL_CSTRING("]")); michael@0: outKey.Append(pathInfo); michael@0: } michael@0: michael@0: nsresult michael@0: Http2Stream::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 mAllHeadersSent flag if they are complete michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS); michael@0: michael@0: LOG3(("Http2Stream::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(("Http2Stream::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: mAllHeadersSent = 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, :authority, :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: Http2PushedStream *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->RemovePushedStreamHttp2(hashkey); michael@0: michael@0: LOG3(("Pushed Stream Lookup " michael@0: "session=%p key=%s loadgroupci=%p cache=%p hit=%p\n", michael@0: mSession, hashkey.get(), loadGroupCI, cache, pushedStream)); 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: SetSentFin(true); michael@0: AdjustPushedPriority(); 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. michael@0: // Release that semaphore count immediately (instead of waiting for michael@0: // cleanup stream) so we can initiate more pull streams. michael@0: mSession->MaybeDecrementConcurrent(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 new streams on this michael@0: // session michael@0: mStreamID = mSession->RegisterStreamID(this); michael@0: MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd"); michael@0: LOG3(("Stream ID 0x%X [session=%p] for URI %s\n", michael@0: mStreamID, mSession, michael@0: nsCString(mTransaction->RequestHead()->RequestURI()).get())); michael@0: michael@0: if (mStreamID >= 0x80000000) { michael@0: // streamID must fit in 31 bits. Evading This is theoretically possible michael@0: // because stream ID assignment is asynchronous to stream creation michael@0: // because of the protocol requirement that the new stream ID michael@0: // be monotonically increasing. In reality this is really not possible michael@0: // because new streams stop being added to a session with millions of 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 HTTP/2 headers by writing to mTxInlineFrame{sz} michael@0: michael@0: nsCString compressedData; michael@0: mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, michael@0: mTransaction->RequestHead()->Method(), michael@0: mTransaction->RequestHead()->RequestURI(), michael@0: hostHeader, michael@0: NS_LITERAL_CSTRING("https"), michael@0: compressedData); michael@0: michael@0: // Determine whether to put the fin bit on the header frame or whether michael@0: // to wait for a data packet to put it on. michael@0: uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY; 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: // header packet michael@0: michael@0: SetSentFin(true); michael@0: firstFrameFlags |= Http2Session::kFlag_END_STREAM; 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 for iterop 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 headers michael@0: SetSentFin(true); michael@0: firstFrameFlags |= Http2Session::kFlag_END_STREAM; michael@0: } michael@0: michael@0: // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the michael@0: // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing michael@0: // frame for the new headers and for the first one a priority field. There is michael@0: // no question this is ugly, but a 16KB HEADERS frame should be a long michael@0: // tail event, so this is really just for correctness and a nop in the base case. michael@0: // michael@0: michael@0: MOZ_ASSERT(!mTxInlineFrameUsed); michael@0: michael@0: uint32_t dataLength = compressedData.Length(); michael@0: uint32_t maxFrameData = Http2Session::kMaxFrameData - 4; // 4 byes for priority michael@0: uint32_t numFrames = 1; michael@0: michael@0: if (dataLength > maxFrameData) { michael@0: numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) / michael@0: Http2Session::kMaxFrameData; michael@0: MOZ_ASSERT (numFrames > 1); michael@0: } michael@0: michael@0: // note that we could still have 1 frame for 0 bytes of data. that's ok. michael@0: michael@0: uint32_t messageSize = dataLength; michael@0: messageSize += 12; // header frame overhead michael@0: messageSize += (numFrames - 1) * 8; // continuation frames overhead michael@0: michael@0: Http2Session::EnsureBuffer(mTxInlineFrame, michael@0: dataLength + messageSize, michael@0: mTxInlineFrameUsed, michael@0: mTxInlineFrameSize); michael@0: michael@0: mTxInlineFrameUsed += messageSize; michael@0: LOG3(("%p Generating %d bytes of HEADERS for stream 0x%X at priority %u frames %u\n", michael@0: this, mTxInlineFrameUsed, mStreamID, mPriority, numFrames)); michael@0: michael@0: uint32_t outputOffset = 0; michael@0: uint32_t compressedDataOffset = 0; michael@0: for (uint32_t idx = 0; idx < numFrames; ++idx) { michael@0: uint32_t flags, frameLen; michael@0: bool lastFrame = (idx == numFrames - 1); michael@0: michael@0: flags = 0; michael@0: frameLen = maxFrameData; michael@0: if (!idx) { michael@0: flags |= firstFrameFlags; michael@0: // Only the first frame needs the 4-byte offset michael@0: maxFrameData = Http2Session::kMaxFrameData; michael@0: } michael@0: if (lastFrame) { michael@0: frameLen = dataLength; michael@0: flags |= Http2Session::kFlag_END_HEADERS; michael@0: } michael@0: dataLength -= frameLen; michael@0: michael@0: mSession->CreateFrameHeader( michael@0: mTxInlineFrame.get() + outputOffset, michael@0: frameLen + (idx ? 0 : 4), michael@0: (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS, michael@0: flags, mStreamID); michael@0: outputOffset += 8; michael@0: michael@0: if (!idx) { michael@0: uint32_t priority = PR_htonl(mPriority); michael@0: memcpy (mTxInlineFrame.get() + outputOffset, &priority, 4); michael@0: outputOffset += 4; michael@0: } michael@0: michael@0: memcpy(mTxInlineFrame.get() + outputOffset, michael@0: compressedData.BeginReading() + compressedDataOffset, frameLen); michael@0: compressedDataOffset += frameLen; michael@0: outputOffset += frameLen; michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length()); michael@0: michael@0: // The size of the input headers is approximate michael@0: uint32_t ratio = michael@0: compressedData.Length() * 100 / michael@0: (11 + mTransaction->RequestHead()->RequestURI().Length() + michael@0: mFlatHttpRequestHeaders.Length()); michael@0: michael@0: const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); 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: if (name.Equals("content-length")) { michael@0: nsCString *val = new nsCString(); 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: val->Append(v); michael@0: michael@0: int64_t len; michael@0: if (nsHttp::ParseInt64(val->get(), nullptr, &len)) michael@0: mRequestBodyLenRemaining = len; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: mFlatHttpRequestHeaders.Truncate(); michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Http2Stream::AdjustInitialWindow() michael@0: { michael@0: // The default initial_window is sized for pushed streams. When we michael@0: // generate a client pulled stream we want to disable flow control for michael@0: // the stream with a window update. Do the same for pushed streams michael@0: // when they connect to a pull. 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: Http2Stream *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 recvd a FIN, there is no reason to update michael@0: // the window michael@0: if (stream->RecvdFin() || stream->RecvdReset()) michael@0: return; michael@0: } michael@0: michael@0: uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; michael@0: Http2Session::EnsureBuffer(mTxInlineFrame, michael@0: mTxInlineFrameUsed + 12, michael@0: mTxInlineFrameUsed, michael@0: mTxInlineFrameSize); michael@0: mTxInlineFrameUsed += 12; michael@0: michael@0: mSession->CreateFrameHeader(packet, 4, michael@0: Http2Session::FRAME_TYPE_WINDOW_UPDATE, michael@0: 0, stream->mStreamID); michael@0: michael@0: MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin); michael@0: uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow; michael@0: mClientReceiveWindow += bump; michael@0: bump = PR_htonl(bump); michael@0: memcpy(packet + 8, &bump, 4); michael@0: LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n", michael@0: this, stream->mStreamID)); michael@0: } michael@0: michael@0: void michael@0: Http2Stream::AdjustPushedPriority() michael@0: { michael@0: // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams. michael@0: // 0 is the sink for a pushed stream. michael@0: michael@0: if (mStreamID || !mPushSource) michael@0: return; michael@0: michael@0: MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1)); michael@0: michael@0: // If the pushed stream has recvd a FIN, there is no reason to update michael@0: // the window michael@0: if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) michael@0: return; michael@0: michael@0: uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; michael@0: Http2Session::EnsureBuffer(mTxInlineFrame, michael@0: mTxInlineFrameUsed + 12, michael@0: mTxInlineFrameUsed, michael@0: mTxInlineFrameSize); michael@0: mTxInlineFrameUsed += 12; michael@0: michael@0: mSession->CreateFrameHeader(packet, 4, michael@0: Http2Session::FRAME_TYPE_PRIORITY, 0, michael@0: mPushSource->mStreamID); michael@0: michael@0: uint32_t newPriority = PR_htonl(mPriority); michael@0: mPushSource->SetPriority(newPriority); michael@0: memcpy(packet + 8, &newPriority, 4); michael@0: michael@0: LOG3(("AdjustPushedPriority %p id 0x%X to %X\n", this, mPushSource->mStreamID, michael@0: newPriority)); michael@0: } michael@0: michael@0: void michael@0: Http2Stream::UpdateTransportReadEvents(uint32_t count) michael@0: { michael@0: mTotalRead += count; michael@0: mTransaction->OnTransportStatus(mSocketTransport, michael@0: NS_NET_STATUS_RECEIVING_FROM, michael@0: mTotalRead); michael@0: } michael@0: michael@0: void michael@0: Http2Stream::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 http/2 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: Http2Stream::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(("Http2Stream::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 < Http2Session::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 http/2 michael@0: // bytes through to the session object 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(("Http2Stream::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: Http2Session::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: LOG3(("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(("Http2Stream::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: Http2Session::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: Http2Stream::ChangeState(enum upstreamStateType newState) michael@0: { michael@0: LOG3(("Http2Stream::ChangeState() %p from %X to %X", michael@0: this, mUpstreamState, newState)); michael@0: mUpstreamState = newState; michael@0: } michael@0: michael@0: void michael@0: Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) michael@0: { michael@0: LOG3(("Http2Stream::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: michael@0: uint8_t frameFlags = 0; michael@0: if (lastFrame) { michael@0: frameFlags |= Http2Session::kFlag_END_STREAM; michael@0: if (dataLength) michael@0: SetSentFin(true); michael@0: } michael@0: michael@0: mSession->CreateFrameHeader(mTxInlineFrame.get(), michael@0: dataLength, michael@0: Http2Session::FRAME_TYPE_DATA, michael@0: frameFlags, mStreamID); michael@0: michael@0: mTxInlineFrameUsed = 8; michael@0: mTxStreamFrameSize = dataLength; michael@0: } michael@0: michael@0: // ConvertHeaders is used to convert the response headers michael@0: // into HTTP/1 format and report some telemetry michael@0: nsresult michael@0: Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor, michael@0: nsACString &aHeadersIn, michael@0: nsACString &aHeadersOut) michael@0: { michael@0: aHeadersOut.Truncate(); michael@0: aHeadersOut.SetCapacity(aHeadersIn.Length() + 512); michael@0: michael@0: nsresult rv = michael@0: decompressor->DecodeHeaderBlock(reinterpret_cast(aHeadersIn.BeginReading()), michael@0: aHeadersIn.Length(), michael@0: aHeadersOut); michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this)); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: nsAutoCString status; michael@0: decompressor->GetStatus(status); michael@0: if (status.IsEmpty()) { michael@0: LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this)); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: if (aHeadersIn.Length() && aHeadersOut.Length()) { michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length()); michael@0: uint32_t ratio = michael@0: aHeadersIn.Length() * 100 / aHeadersOut.Length(); michael@0: Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); michael@0: } michael@0: michael@0: aHeadersIn.Truncate(); michael@0: aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n")); michael@0: LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading())); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // ConvertHeaders is used to convert the response headers michael@0: // into HTTP/1 format and report some telemetry michael@0: nsresult michael@0: Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor, michael@0: nsACString &aHeadersIn, michael@0: nsACString &aHeadersOut) michael@0: { michael@0: aHeadersOut.Truncate(); michael@0: nsresult rv = michael@0: decompressor->DecodeHeaderBlock(reinterpret_cast(aHeadersIn.BeginReading()), michael@0: aHeadersIn.Length(), michael@0: aHeadersOut); michael@0: if (NS_FAILED(rv)) { michael@0: LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this)); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: nsCString method; michael@0: decompressor->GetHost(mHeaderHost); michael@0: decompressor->GetScheme(mHeaderScheme); michael@0: decompressor->GetPath(mHeaderPath); michael@0: michael@0: if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) { michael@0: LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required " michael@0: "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(), michael@0: mHeaderPath.get())); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: decompressor->GetMethod(method); michael@0: if (!method.Equals(NS_LITERAL_CSTRING("GET"))) { michael@0: LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n", michael@0: this, method.get())); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: aHeadersIn.Truncate(); michael@0: LOG (("decoded push headers are:\n%s", aHeadersOut.BeginReading())); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Http2Stream::Close(nsresult reason) michael@0: { michael@0: mTransaction->Close(reason); michael@0: } michael@0: michael@0: bool michael@0: Http2Stream::AllowFlowControlledWrite() michael@0: { michael@0: return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0); michael@0: } michael@0: michael@0: void michael@0: Http2Stream::UpdateServerReceiveWindow(int32_t delta) michael@0: { michael@0: mServerReceiveWindow += delta; michael@0: michael@0: if (mBlockedOnRwin && AllowFlowControlledWrite()) { michael@0: LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X " michael@0: "Open stream window\n", this, mStreamID)); michael@0: mSession->TransactionHasDataToWrite(this); } michael@0: } michael@0: michael@0: void michael@0: Http2Stream::SetPriority(uint32_t newVal) michael@0: { michael@0: mPriority = std::min(newVal, 0x7fffffffU); michael@0: } michael@0: michael@0: void michael@0: Http2Stream::SetRecvdFin(bool aStatus) michael@0: { michael@0: mRecvdFin = aStatus ? 1 : 0; michael@0: if (!aStatus) michael@0: return; michael@0: michael@0: if (mState == OPEN || mState == RESERVED_BY_REMOTE) { michael@0: mState = CLOSED_BY_REMOTE; michael@0: } else if (mState == CLOSED_BY_LOCAL) { michael@0: mState = CLOSED; michael@0: } michael@0: } michael@0: michael@0: void michael@0: Http2Stream::SetSentFin(bool aStatus) michael@0: { michael@0: mSentFin = aStatus ? 1 : 0; michael@0: if (!aStatus) michael@0: return; michael@0: michael@0: if (mState == OPEN || mState == RESERVED_BY_REMOTE) { michael@0: mState = CLOSED_BY_LOCAL; michael@0: } else if (mState == CLOSED_BY_REMOTE) { michael@0: mState = CLOSED; michael@0: } michael@0: } michael@0: michael@0: void michael@0: Http2Stream::SetRecvdReset(bool aStatus) michael@0: { michael@0: mRecvdReset = aStatus ? 1 : 0; michael@0: if (!aStatus) michael@0: return; michael@0: mState = CLOSED; michael@0: } michael@0: michael@0: void michael@0: Http2Stream::SetSentReset(bool aStatus) michael@0: { michael@0: mSentReset = aStatus ? 1 : 0; michael@0: if (!aStatus) michael@0: return; michael@0: mState = CLOSED; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAHttpSegmentReader michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: Http2Stream::OnReadSegment(const char *buf, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: LOG3(("Http2Stream::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_HEADERS: 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 HEADERS 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, mAllHeadersSent)); michael@0: if (mAllHeadersSent) { michael@0: SetHTTPState(OPEN); michael@0: AdjustInitialWindow(); michael@0: // This version of TransmitFrame cannot block michael@0: rv = TransmitFrame(nullptr, nullptr, true); michael@0: ChangeState(GENERATING_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_BODY: michael@0: // if there is session flow control and either the stream window is active and michael@0: // exhaused or the session window is exhausted then suspend michael@0: if (!AllowFlowControlledWrite()) { michael@0: *countRead = 0; michael@0: LOG3(("Http2Stream this=%p, id 0x%X request body suspended because " michael@0: "remote window is stream=%ld session=%ld.\n", this, mStreamID, michael@0: mServerReceiveWindow, mSession->ServerSessionWindow())); michael@0: mBlockedOnRwin = true; michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: mBlockedOnRwin = false; michael@0: michael@0: // The chunk is the smallest of: availableData, configured chunkSize, michael@0: // stream window, session window, or 14 bit framing limit. michael@0: // Its amazing we send anything at all. michael@0: dataLength = std::min(count, mChunkSize); michael@0: michael@0: if (dataLength > Http2Session::kMaxFrameData) michael@0: dataLength = Http2Session::kMaxFrameData; michael@0: michael@0: if (dataLength > mSession->ServerSessionWindow()) michael@0: dataLength = static_cast(mSession->ServerSessionWindow()); michael@0: michael@0: if (dataLength > mServerReceiveWindow) michael@0: dataLength = static_cast(mServerReceiveWindow); michael@0: michael@0: LOG3(("Http2Stream this=%p id 0x%X send calculation " michael@0: "avail=%d chunksize=%d stream window=%d session window=%d " michael@0: "max frame=%d USING=%d\n", this, mStreamID, michael@0: count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(), michael@0: Http2Session::kMaxFrameData, dataLength)); michael@0: michael@0: mSession->DecrementServerSessionWindow(dataLength); michael@0: mServerReceiveWindow -= dataLength; michael@0: michael@0: LOG3(("Http2Stream %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_BODY); michael@0: // NO BREAK michael@0: michael@0: case SENDING_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_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, "Http2Stream::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: Http2Stream::OnWriteSegment(char *buf, michael@0: uint32_t count, michael@0: uint32_t *countWritten) michael@0: { michael@0: LOG3(("Http2Stream::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: