1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/protocol/http/Http2Stream.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1117 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set sw=2 ts=8 et tw=80 : */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +// HttpLog.h should generally be included first 1.11 +#include "HttpLog.h" 1.12 + 1.13 +// Log on level :5, instead of default :4. 1.14 +#undef LOG 1.15 +#define LOG(args) LOG5(args) 1.16 +#undef LOG_ENABLED 1.17 +#define LOG_ENABLED() LOG5_ENABLED() 1.18 + 1.19 +#include <algorithm> 1.20 + 1.21 +#include "Http2Compression.h" 1.22 +#include "Http2Session.h" 1.23 +#include "Http2Stream.h" 1.24 +#include "Http2Push.h" 1.25 + 1.26 +#include "mozilla/Telemetry.h" 1.27 +#include "nsAlgorithm.h" 1.28 +#include "nsHttp.h" 1.29 +#include "nsHttpHandler.h" 1.30 +#include "nsHttpRequestHead.h" 1.31 +#include "nsISocketTransport.h" 1.32 +#include "prnetdb.h" 1.33 + 1.34 +#ifdef DEBUG 1.35 +// defined by the socket transport service while active 1.36 +extern PRThread *gSocketThread; 1.37 +#endif 1.38 + 1.39 +namespace mozilla { 1.40 +namespace net { 1.41 + 1.42 +Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, 1.43 + Http2Session *session, 1.44 + int32_t priority) 1.45 + : mStreamID(0), 1.46 + mSession(session), 1.47 + mUpstreamState(GENERATING_HEADERS), 1.48 + mState(IDLE), 1.49 + mAllHeadersSent(0), 1.50 + mAllHeadersReceived(0), 1.51 + mTransaction(httpTransaction), 1.52 + mSocketTransport(session->SocketTransport()), 1.53 + mSegmentReader(nullptr), 1.54 + mSegmentWriter(nullptr), 1.55 + mChunkSize(session->SendingChunkSize()), 1.56 + mRequestBlockedOnRead(0), 1.57 + mRecvdFin(0), 1.58 + mRecvdReset(0), 1.59 + mSentReset(0), 1.60 + mCountAsActive(0), 1.61 + mSentFin(0), 1.62 + mSentWaitingFor(0), 1.63 + mSetTCPSocketBuffer(0), 1.64 + mTxInlineFrameSize(Http2Session::kDefaultBufferSize), 1.65 + mTxInlineFrameUsed(0), 1.66 + mTxStreamFrameSize(0), 1.67 + mRequestBodyLenRemaining(0), 1.68 + mLocalUnacked(0), 1.69 + mBlockedOnRwin(false), 1.70 + mTotalSent(0), 1.71 + mTotalRead(0), 1.72 + mPushSource(nullptr) 1.73 +{ 1.74 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.75 + 1.76 + LOG3(("Http2Stream::Http2Stream %p", this)); 1.77 + 1.78 + mServerReceiveWindow = session->GetServerInitialStreamWindow(); 1.79 + mClientReceiveWindow = session->PushAllowance(); 1.80 + 1.81 + mTxInlineFrame = new uint8_t[mTxInlineFrameSize]; 1.82 + 1.83 + PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority); 1.84 + 1.85 + // values of priority closer to 0 are higher priority for both 1.86 + // mPriority and the priority argument. They are relative but not 1.87 + // proportional. 1.88 + int32_t httpPriority; 1.89 + if (priority >= nsISupportsPriority::PRIORITY_LOWEST) { 1.90 + httpPriority = kWorstPriority; 1.91 + } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) { 1.92 + httpPriority = kBestPriority; 1.93 + } else { 1.94 + httpPriority = kNormalPriority + priority; 1.95 + } 1.96 + MOZ_ASSERT(httpPriority >= 0); 1.97 + mPriority = static_cast<uint32_t>(httpPriority); 1.98 +} 1.99 + 1.100 +Http2Stream::~Http2Stream() 1.101 +{ 1.102 + mStreamID = Http2Session::kDeadStreamID; 1.103 +} 1.104 + 1.105 +// ReadSegments() is used to write data down the socket. Generally, HTTP 1.106 +// request data is pulled from the approriate transaction and 1.107 +// converted to HTTP/2 data. Sometimes control data like a window-update is 1.108 +// generated instead. 1.109 + 1.110 +nsresult 1.111 +Http2Stream::ReadSegments(nsAHttpSegmentReader *reader, 1.112 + uint32_t count, 1.113 + uint32_t *countRead) 1.114 +{ 1.115 + LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x", 1.116 + this, reader, count, mUpstreamState)); 1.117 + 1.118 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.119 + 1.120 + nsresult rv = NS_ERROR_UNEXPECTED; 1.121 + mRequestBlockedOnRead = 0; 1.122 + 1.123 + if (mRecvdFin || mRecvdReset) { 1.124 + // Don't transmit any request frames if the peer cannot respond 1.125 + LOG3(("Http2Stream %p ReadSegments request stream aborted due to" 1.126 + " response side closure\n", this)); 1.127 + return NS_ERROR_ABORT; 1.128 + } 1.129 + 1.130 + // avoid runt chunks if possible by anticipating 1.131 + // full data frames 1.132 + if (count > (mChunkSize + 8)) { 1.133 + uint32_t numchunks = count / (mChunkSize + 8); 1.134 + count = numchunks * (mChunkSize + 8); 1.135 + } 1.136 + 1.137 + switch (mUpstreamState) { 1.138 + case GENERATING_HEADERS: 1.139 + case GENERATING_BODY: 1.140 + case SENDING_BODY: 1.141 + // Call into the HTTP Transaction to generate the HTTP request 1.142 + // stream. That stream will show up in OnReadSegment(). 1.143 + mSegmentReader = reader; 1.144 + rv = mTransaction->ReadSegments(this, count, countRead); 1.145 + mSegmentReader = nullptr; 1.146 + 1.147 + // Check to see if the transaction's request could be written out now. 1.148 + // If not, mark the stream for callback when writing can proceed. 1.149 + if (NS_SUCCEEDED(rv) && 1.150 + mUpstreamState == GENERATING_HEADERS && 1.151 + !mAllHeadersSent) 1.152 + mSession->TransactionHasDataToWrite(this); 1.153 + 1.154 + // mTxinlineFrameUsed represents any queued un-sent frame. It might 1.155 + // be 0 if there is no such frame, which is not a gurantee that we 1.156 + // don't have more request body to send - just that any data that was 1.157 + // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is 1.158 + // a queued, but complete, http/2 frame length. 1.159 + 1.160 + // Mark that we are blocked on read if the http transaction needs to 1.161 + // provide more of the request message body and there is nothing queued 1.162 + // for writing 1.163 + if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) 1.164 + mRequestBlockedOnRead = 1; 1.165 + 1.166 + // If the sending flow control window is open (!mBlockedOnRwin) then 1.167 + // continue sending the request 1.168 + if (!mBlockedOnRwin && 1.169 + !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { 1.170 + LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, " 1.171 + "mUpstreamState=%x",this, mStreamID, mUpstreamState)); 1.172 + if (mSentFin) { 1.173 + ChangeState(UPSTREAM_COMPLETE); 1.174 + } else { 1.175 + GenerateDataFrameHeader(0, true); 1.176 + ChangeState(SENDING_FIN_STREAM); 1.177 + mSession->TransactionHasDataToWrite(this); 1.178 + rv = NS_BASE_STREAM_WOULD_BLOCK; 1.179 + } 1.180 + } 1.181 + break; 1.182 + 1.183 + case SENDING_FIN_STREAM: 1.184 + // We were trying to send the FIN-STREAM but were blocked from 1.185 + // sending it out - try again. 1.186 + if (!mSentFin) { 1.187 + mSegmentReader = reader; 1.188 + rv = TransmitFrame(nullptr, nullptr, false); 1.189 + mSegmentReader = nullptr; 1.190 + MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, 1.191 + "Transmit Frame should be all or nothing"); 1.192 + if (NS_SUCCEEDED(rv)) 1.193 + ChangeState(UPSTREAM_COMPLETE); 1.194 + } else { 1.195 + rv = NS_OK; 1.196 + mTxInlineFrameUsed = 0; // cancel fin data packet 1.197 + ChangeState(UPSTREAM_COMPLETE); 1.198 + } 1.199 + 1.200 + *countRead = 0; 1.201 + 1.202 + // don't change OK to WOULD BLOCK. we are really done sending if OK 1.203 + break; 1.204 + 1.205 + case UPSTREAM_COMPLETE: 1.206 + *countRead = 0; 1.207 + rv = NS_OK; 1.208 + break; 1.209 + 1.210 + default: 1.211 + MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state"); 1.212 + break; 1.213 + } 1.214 + 1.215 + return rv; 1.216 +} 1.217 + 1.218 +// WriteSegments() is used to read data off the socket. Generally this is 1.219 +// just a call through to the associate nsHttpTransaciton for this stream 1.220 +// for the remaining data bytes indicated by the current DATA frame. 1.221 + 1.222 +nsresult 1.223 +Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer, 1.224 + uint32_t count, 1.225 + uint32_t *countWritten) 1.226 +{ 1.227 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.228 + MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); 1.229 + 1.230 + LOG3(("Http2Stream::WriteSegments %p count=%d state=%x", 1.231 + this, count, mUpstreamState)); 1.232 + 1.233 + mSegmentWriter = writer; 1.234 + nsresult rv = mTransaction->WriteSegments(this, count, countWritten); 1.235 + mSegmentWriter = nullptr; 1.236 + 1.237 + return rv; 1.238 +} 1.239 + 1.240 +void 1.241 +Http2Stream::CreatePushHashKey(const nsCString &scheme, 1.242 + const nsCString &hostHeader, 1.243 + uint64_t serial, 1.244 + const nsCSubstring &pathInfo, 1.245 + nsCString &outOrigin, 1.246 + nsCString &outKey) 1.247 +{ 1.248 + outOrigin = scheme; 1.249 + outOrigin.Append(NS_LITERAL_CSTRING("://")); 1.250 + outOrigin.Append(hostHeader); 1.251 + 1.252 + outKey = outOrigin; 1.253 + outKey.Append(NS_LITERAL_CSTRING("/[http2.")); 1.254 + outKey.AppendInt(serial); 1.255 + outKey.Append(NS_LITERAL_CSTRING("]")); 1.256 + outKey.Append(pathInfo); 1.257 +} 1.258 + 1.259 +nsresult 1.260 +Http2Stream::ParseHttpRequestHeaders(const char *buf, 1.261 + uint32_t avail, 1.262 + uint32_t *countUsed) 1.263 +{ 1.264 + // Returns NS_OK even if the headers are incomplete 1.265 + // set mAllHeadersSent flag if they are complete 1.266 + 1.267 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.268 + MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS); 1.269 + 1.270 + LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x", 1.271 + this, avail, mUpstreamState)); 1.272 + 1.273 + mFlatHttpRequestHeaders.Append(buf, avail); 1.274 + 1.275 + // We can use the simple double crlf because firefox is the 1.276 + // only client we are parsing 1.277 + int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); 1.278 + 1.279 + if (endHeader == kNotFound) { 1.280 + // We don't have all the headers yet 1.281 + LOG3(("Http2Stream::ParseHttpRequestHeaders %p " 1.282 + "Need more header bytes. Len = %d", 1.283 + this, mFlatHttpRequestHeaders.Length())); 1.284 + *countUsed = avail; 1.285 + return NS_OK; 1.286 + } 1.287 + 1.288 + // We have recvd all the headers, trim the local 1.289 + // buffer of the final empty line, and set countUsed to reflect 1.290 + // the whole header has been consumed. 1.291 + uint32_t oldLen = mFlatHttpRequestHeaders.Length(); 1.292 + mFlatHttpRequestHeaders.SetLength(endHeader + 2); 1.293 + *countUsed = avail - (oldLen - endHeader) + 4; 1.294 + mAllHeadersSent = 1; 1.295 + 1.296 + nsCString hostHeader; 1.297 + nsCString hashkey; 1.298 + mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); 1.299 + 1.300 + CreatePushHashKey(NS_LITERAL_CSTRING("https"), 1.301 + hostHeader, mSession->Serial(), 1.302 + mTransaction->RequestHead()->RequestURI(), 1.303 + mOrigin, hashkey); 1.304 + 1.305 + // check the push cache for GET 1.306 + if (mTransaction->RequestHead()->IsGet()) { 1.307 + // from :scheme, :authority, :path 1.308 + nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); 1.309 + SpdyPushCache *cache = nullptr; 1.310 + if (loadGroupCI) 1.311 + loadGroupCI->GetSpdyPushCache(&cache); 1.312 + 1.313 + Http2PushedStream *pushedStream = nullptr; 1.314 + // we remove the pushedstream from the push cache so that 1.315 + // it will not be used for another GET. This does not destroy the 1.316 + // stream itself - that is done when the transactionhash is done with it. 1.317 + if (cache) 1.318 + pushedStream = cache->RemovePushedStreamHttp2(hashkey); 1.319 + 1.320 + LOG3(("Pushed Stream Lookup " 1.321 + "session=%p key=%s loadgroupci=%p cache=%p hit=%p\n", 1.322 + mSession, hashkey.get(), loadGroupCI, cache, pushedStream)); 1.323 + 1.324 + if (pushedStream) { 1.325 + LOG3(("Pushed Stream Match located id=0x%X key=%s\n", 1.326 + pushedStream->StreamID(), hashkey.get())); 1.327 + pushedStream->SetConsumerStream(this); 1.328 + mPushSource = pushedStream; 1.329 + SetSentFin(true); 1.330 + AdjustPushedPriority(); 1.331 + 1.332 + // This stream has been activated (and thus counts against the concurrency 1.333 + // limit intentionally), but will not be registered via 1.334 + // RegisterStreamID (below) because of the push match. 1.335 + // Release that semaphore count immediately (instead of waiting for 1.336 + // cleanup stream) so we can initiate more pull streams. 1.337 + mSession->MaybeDecrementConcurrent(this); 1.338 + 1.339 + // There is probably pushed data buffered so trigger a read manually 1.340 + // as we can't rely on future network events to do it 1.341 + mSession->ConnectPushedStream(this); 1.342 + return NS_OK; 1.343 + } 1.344 + } 1.345 + 1.346 + // It is now OK to assign a streamID that we are assured will 1.347 + // be monotonically increasing amongst new streams on this 1.348 + // session 1.349 + mStreamID = mSession->RegisterStreamID(this); 1.350 + MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd"); 1.351 + LOG3(("Stream ID 0x%X [session=%p] for URI %s\n", 1.352 + mStreamID, mSession, 1.353 + nsCString(mTransaction->RequestHead()->RequestURI()).get())); 1.354 + 1.355 + if (mStreamID >= 0x80000000) { 1.356 + // streamID must fit in 31 bits. Evading This is theoretically possible 1.357 + // because stream ID assignment is asynchronous to stream creation 1.358 + // because of the protocol requirement that the new stream ID 1.359 + // be monotonically increasing. In reality this is really not possible 1.360 + // because new streams stop being added to a session with millions of 1.361 + // IDs still available and no race condition is going to bridge that gap; 1.362 + // so we can be comfortable on just erroring out for correctness in that 1.363 + // case. 1.364 + LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); 1.365 + return NS_ERROR_UNEXPECTED; 1.366 + } 1.367 + 1.368 + // Now we need to convert the flat http headers into a set 1.369 + // of HTTP/2 headers by writing to mTxInlineFrame{sz} 1.370 + 1.371 + nsCString compressedData; 1.372 + mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, 1.373 + mTransaction->RequestHead()->Method(), 1.374 + mTransaction->RequestHead()->RequestURI(), 1.375 + hostHeader, 1.376 + NS_LITERAL_CSTRING("https"), 1.377 + compressedData); 1.378 + 1.379 + // Determine whether to put the fin bit on the header frame or whether 1.380 + // to wait for a data packet to put it on. 1.381 + uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY; 1.382 + 1.383 + if (mTransaction->RequestHead()->IsGet() || 1.384 + mTransaction->RequestHead()->IsConnect() || 1.385 + mTransaction->RequestHead()->IsHead()) { 1.386 + // for GET, CONNECT, and HEAD place the fin bit right on the 1.387 + // header packet 1.388 + 1.389 + SetSentFin(true); 1.390 + firstFrameFlags |= Http2Session::kFlag_END_STREAM; 1.391 + } else if (mTransaction->RequestHead()->IsPost() || 1.392 + mTransaction->RequestHead()->IsPut() || 1.393 + mTransaction->RequestHead()->IsOptions()) { 1.394 + // place fin in a data frame even for 0 length messages for iterop 1.395 + } else if (!mRequestBodyLenRemaining) { 1.396 + // for other HTTP extension methods, rely on the content-length 1.397 + // to determine whether or not to put fin on headers 1.398 + SetSentFin(true); 1.399 + firstFrameFlags |= Http2Session::kFlag_END_STREAM; 1.400 + } 1.401 + 1.402 + // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the 1.403 + // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing 1.404 + // frame for the new headers and for the first one a priority field. There is 1.405 + // no question this is ugly, but a 16KB HEADERS frame should be a long 1.406 + // tail event, so this is really just for correctness and a nop in the base case. 1.407 + // 1.408 + 1.409 + MOZ_ASSERT(!mTxInlineFrameUsed); 1.410 + 1.411 + uint32_t dataLength = compressedData.Length(); 1.412 + uint32_t maxFrameData = Http2Session::kMaxFrameData - 4; // 4 byes for priority 1.413 + uint32_t numFrames = 1; 1.414 + 1.415 + if (dataLength > maxFrameData) { 1.416 + numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) / 1.417 + Http2Session::kMaxFrameData; 1.418 + MOZ_ASSERT (numFrames > 1); 1.419 + } 1.420 + 1.421 + // note that we could still have 1 frame for 0 bytes of data. that's ok. 1.422 + 1.423 + uint32_t messageSize = dataLength; 1.424 + messageSize += 12; // header frame overhead 1.425 + messageSize += (numFrames - 1) * 8; // continuation frames overhead 1.426 + 1.427 + Http2Session::EnsureBuffer(mTxInlineFrame, 1.428 + dataLength + messageSize, 1.429 + mTxInlineFrameUsed, 1.430 + mTxInlineFrameSize); 1.431 + 1.432 + mTxInlineFrameUsed += messageSize; 1.433 + LOG3(("%p Generating %d bytes of HEADERS for stream 0x%X at priority %u frames %u\n", 1.434 + this, mTxInlineFrameUsed, mStreamID, mPriority, numFrames)); 1.435 + 1.436 + uint32_t outputOffset = 0; 1.437 + uint32_t compressedDataOffset = 0; 1.438 + for (uint32_t idx = 0; idx < numFrames; ++idx) { 1.439 + uint32_t flags, frameLen; 1.440 + bool lastFrame = (idx == numFrames - 1); 1.441 + 1.442 + flags = 0; 1.443 + frameLen = maxFrameData; 1.444 + if (!idx) { 1.445 + flags |= firstFrameFlags; 1.446 + // Only the first frame needs the 4-byte offset 1.447 + maxFrameData = Http2Session::kMaxFrameData; 1.448 + } 1.449 + if (lastFrame) { 1.450 + frameLen = dataLength; 1.451 + flags |= Http2Session::kFlag_END_HEADERS; 1.452 + } 1.453 + dataLength -= frameLen; 1.454 + 1.455 + mSession->CreateFrameHeader( 1.456 + mTxInlineFrame.get() + outputOffset, 1.457 + frameLen + (idx ? 0 : 4), 1.458 + (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS, 1.459 + flags, mStreamID); 1.460 + outputOffset += 8; 1.461 + 1.462 + if (!idx) { 1.463 + uint32_t priority = PR_htonl(mPriority); 1.464 + memcpy (mTxInlineFrame.get() + outputOffset, &priority, 4); 1.465 + outputOffset += 4; 1.466 + } 1.467 + 1.468 + memcpy(mTxInlineFrame.get() + outputOffset, 1.469 + compressedData.BeginReading() + compressedDataOffset, frameLen); 1.470 + compressedDataOffset += frameLen; 1.471 + outputOffset += frameLen; 1.472 + } 1.473 + 1.474 + Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length()); 1.475 + 1.476 + // The size of the input headers is approximate 1.477 + uint32_t ratio = 1.478 + compressedData.Length() * 100 / 1.479 + (11 + mTransaction->RequestHead()->RequestURI().Length() + 1.480 + mFlatHttpRequestHeaders.Length()); 1.481 + 1.482 + const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); 1.483 + int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); 1.484 + while (true) { 1.485 + int32_t startIndex = crlfIndex + 2; 1.486 + 1.487 + crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); 1.488 + if (crlfIndex == -1) 1.489 + break; 1.490 + 1.491 + int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, 1.492 + crlfIndex - startIndex); 1.493 + if (colonIndex == -1) 1.494 + break; 1.495 + 1.496 + nsDependentCSubstring name = Substring(beginBuffer + startIndex, 1.497 + beginBuffer + colonIndex); 1.498 + // all header names are lower case in spdy 1.499 + ToLowerCase(name); 1.500 + 1.501 + if (name.Equals("content-length")) { 1.502 + nsCString *val = new nsCString(); 1.503 + int32_t valueIndex = colonIndex + 1; 1.504 + while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') 1.505 + ++valueIndex; 1.506 + 1.507 + nsDependentCSubstring v = Substring(beginBuffer + valueIndex, 1.508 + beginBuffer + crlfIndex); 1.509 + val->Append(v); 1.510 + 1.511 + int64_t len; 1.512 + if (nsHttp::ParseInt64(val->get(), nullptr, &len)) 1.513 + mRequestBodyLenRemaining = len; 1.514 + break; 1.515 + } 1.516 + } 1.517 + 1.518 + mFlatHttpRequestHeaders.Truncate(); 1.519 + Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); 1.520 + return NS_OK; 1.521 +} 1.522 + 1.523 +void 1.524 +Http2Stream::AdjustInitialWindow() 1.525 +{ 1.526 + // The default initial_window is sized for pushed streams. When we 1.527 + // generate a client pulled stream we want to disable flow control for 1.528 + // the stream with a window update. Do the same for pushed streams 1.529 + // when they connect to a pull. 1.530 + 1.531 + // >0 even numbered IDs are pushed streams. 1.532 + // odd numbered IDs are pulled streams. 1.533 + // 0 is the sink for a pushed stream. 1.534 + Http2Stream *stream = this; 1.535 + if (!mStreamID) { 1.536 + MOZ_ASSERT(mPushSource); 1.537 + if (!mPushSource) 1.538 + return; 1.539 + stream = mPushSource; 1.540 + MOZ_ASSERT(stream->mStreamID); 1.541 + MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream 1.542 + 1.543 + // If the pushed stream has recvd a FIN, there is no reason to update 1.544 + // the window 1.545 + if (stream->RecvdFin() || stream->RecvdReset()) 1.546 + return; 1.547 + } 1.548 + 1.549 + uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; 1.550 + Http2Session::EnsureBuffer(mTxInlineFrame, 1.551 + mTxInlineFrameUsed + 12, 1.552 + mTxInlineFrameUsed, 1.553 + mTxInlineFrameSize); 1.554 + mTxInlineFrameUsed += 12; 1.555 + 1.556 + mSession->CreateFrameHeader(packet, 4, 1.557 + Http2Session::FRAME_TYPE_WINDOW_UPDATE, 1.558 + 0, stream->mStreamID); 1.559 + 1.560 + MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin); 1.561 + uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow; 1.562 + mClientReceiveWindow += bump; 1.563 + bump = PR_htonl(bump); 1.564 + memcpy(packet + 8, &bump, 4); 1.565 + LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n", 1.566 + this, stream->mStreamID)); 1.567 +} 1.568 + 1.569 +void 1.570 +Http2Stream::AdjustPushedPriority() 1.571 +{ 1.572 + // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams. 1.573 + // 0 is the sink for a pushed stream. 1.574 + 1.575 + if (mStreamID || !mPushSource) 1.576 + return; 1.577 + 1.578 + MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1)); 1.579 + 1.580 + // If the pushed stream has recvd a FIN, there is no reason to update 1.581 + // the window 1.582 + if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) 1.583 + return; 1.584 + 1.585 + uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; 1.586 + Http2Session::EnsureBuffer(mTxInlineFrame, 1.587 + mTxInlineFrameUsed + 12, 1.588 + mTxInlineFrameUsed, 1.589 + mTxInlineFrameSize); 1.590 + mTxInlineFrameUsed += 12; 1.591 + 1.592 + mSession->CreateFrameHeader(packet, 4, 1.593 + Http2Session::FRAME_TYPE_PRIORITY, 0, 1.594 + mPushSource->mStreamID); 1.595 + 1.596 + uint32_t newPriority = PR_htonl(mPriority); 1.597 + mPushSource->SetPriority(newPriority); 1.598 + memcpy(packet + 8, &newPriority, 4); 1.599 + 1.600 + LOG3(("AdjustPushedPriority %p id 0x%X to %X\n", this, mPushSource->mStreamID, 1.601 + newPriority)); 1.602 +} 1.603 + 1.604 +void 1.605 +Http2Stream::UpdateTransportReadEvents(uint32_t count) 1.606 +{ 1.607 + mTotalRead += count; 1.608 + mTransaction->OnTransportStatus(mSocketTransport, 1.609 + NS_NET_STATUS_RECEIVING_FROM, 1.610 + mTotalRead); 1.611 +} 1.612 + 1.613 +void 1.614 +Http2Stream::UpdateTransportSendEvents(uint32_t count) 1.615 +{ 1.616 + mTotalSent += count; 1.617 + 1.618 + // normally on non-windows platform we use TCP autotuning for 1.619 + // the socket buffers, and this works well (managing enough 1.620 + // buffers for BDP while conserving memory) for HTTP even when 1.621 + // it creates really deep queues. However this 'buffer bloat' is 1.622 + // a problem for http/2 because it ruins the low latency properties 1.623 + // necessary for PING and cancel to work meaningfully. 1.624 + // 1.625 + // If this stream represents a large upload, disable autotuning for 1.626 + // the session and cap the send buffers by default at 128KB. 1.627 + // (10Mbit/sec @ 100ms) 1.628 + // 1.629 + uint32_t bufferSize = gHttpHandler->SpdySendBufferSize(); 1.630 + if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) { 1.631 + mSetTCPSocketBuffer = 1; 1.632 + mSocketTransport->SetSendBufferSize(bufferSize); 1.633 + } 1.634 + 1.635 + if (mUpstreamState != SENDING_FIN_STREAM) 1.636 + mTransaction->OnTransportStatus(mSocketTransport, 1.637 + NS_NET_STATUS_SENDING_TO, 1.638 + mTotalSent); 1.639 + 1.640 + if (!mSentWaitingFor && !mRequestBodyLenRemaining) { 1.641 + mSentWaitingFor = 1; 1.642 + mTransaction->OnTransportStatus(mSocketTransport, 1.643 + NS_NET_STATUS_WAITING_FOR, 1.644 + 0); 1.645 + } 1.646 +} 1.647 + 1.648 +nsresult 1.649 +Http2Stream::TransmitFrame(const char *buf, 1.650 + uint32_t *countUsed, 1.651 + bool forceCommitment) 1.652 +{ 1.653 + // If TransmitFrame returns SUCCESS than all the data is sent (or at least 1.654 + // buffered at the session level), if it returns WOULD_BLOCK then none of 1.655 + // the data is sent. 1.656 + 1.657 + // You can call this function with no data and no out parameter in order to 1.658 + // flush internal buffers that were previously blocked on writing. You can 1.659 + // of course feed new data to it as well. 1.660 + 1.661 + LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d", 1.662 + this, mTxInlineFrameUsed, mTxStreamFrameSize)); 1.663 + if (countUsed) 1.664 + *countUsed = 0; 1.665 + 1.666 + if (!mTxInlineFrameUsed) { 1.667 + MOZ_ASSERT(!buf); 1.668 + return NS_OK; 1.669 + } 1.670 + 1.671 + MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit"); 1.672 + MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader"); 1.673 + MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed), 1.674 + "TransmitFrame arguments inconsistent"); 1.675 + 1.676 + uint32_t transmittedCount; 1.677 + nsresult rv; 1.678 + 1.679 + // In the (relatively common) event that we have a small amount of data 1.680 + // split between the inlineframe and the streamframe, then move the stream 1.681 + // data into the inlineframe via copy in order to coalesce into one write. 1.682 + // Given the interaction with ssl this is worth the small copy cost. 1.683 + if (mTxStreamFrameSize && mTxInlineFrameUsed && 1.684 + mTxStreamFrameSize < Http2Session::kDefaultBufferSize && 1.685 + mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) { 1.686 + LOG3(("Coalesce Transmit")); 1.687 + memcpy (mTxInlineFrame + mTxInlineFrameUsed, 1.688 + buf, mTxStreamFrameSize); 1.689 + if (countUsed) 1.690 + *countUsed += mTxStreamFrameSize; 1.691 + mTxInlineFrameUsed += mTxStreamFrameSize; 1.692 + mTxStreamFrameSize = 0; 1.693 + } 1.694 + 1.695 + rv = 1.696 + mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed, 1.697 + forceCommitment); 1.698 + 1.699 + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1.700 + MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK"); 1.701 + mSession->TransactionHasDataToWrite(this); 1.702 + } 1.703 + if (NS_FAILED(rv)) // this will include WOULD_BLOCK 1.704 + return rv; 1.705 + 1.706 + // This function calls mSegmentReader->OnReadSegment to report the actual http/2 1.707 + // bytes through to the session object and then the HttpConnection which calls 1.708 + // the socket write function. It will accept all of the inline and stream 1.709 + // data because of the above 'commitment' even if it has to buffer 1.710 + 1.711 + rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()), 1.712 + mTxInlineFrameUsed, 1.713 + &transmittedCount); 1.714 + LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p " 1.715 + "stream=%p result %x len=%d", 1.716 + mSession, this, rv, transmittedCount)); 1.717 + 1.718 + MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, 1.719 + "inconsistent inline commitment result"); 1.720 + 1.721 + if (NS_FAILED(rv)) 1.722 + return rv; 1.723 + 1.724 + MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed, 1.725 + "inconsistent inline commitment count"); 1.726 + 1.727 + Http2Session::LogIO(mSession, this, "Writing from Inline Buffer", 1.728 + reinterpret_cast<char*>(mTxInlineFrame.get()), 1.729 + transmittedCount); 1.730 + 1.731 + if (mTxStreamFrameSize) { 1.732 + if (!buf) { 1.733 + // this cannot happen 1.734 + MOZ_ASSERT(false, "Stream transmit with null buf argument to " 1.735 + "TransmitFrame()"); 1.736 + LOG3(("Stream transmit with null buf argument to TransmitFrame()\n")); 1.737 + return NS_ERROR_UNEXPECTED; 1.738 + } 1.739 + 1.740 + // If there is already data buffered, just add to that to form 1.741 + // a single TLS Application Data Record - otherwise skip the memcpy 1.742 + if (mSession->AmountOfOutputBuffered()) { 1.743 + rv = mSession->BufferOutput(buf, mTxStreamFrameSize, 1.744 + &transmittedCount); 1.745 + } else { 1.746 + rv = mSession->OnReadSegment(buf, mTxStreamFrameSize, 1.747 + &transmittedCount); 1.748 + } 1.749 + 1.750 + LOG3(("Http2Stream::TransmitFrame for regular session=%p " 1.751 + "stream=%p result %x len=%d", 1.752 + mSession, this, rv, transmittedCount)); 1.753 + 1.754 + MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, 1.755 + "inconsistent stream commitment result"); 1.756 + 1.757 + if (NS_FAILED(rv)) 1.758 + return rv; 1.759 + 1.760 + MOZ_ASSERT(transmittedCount == mTxStreamFrameSize, 1.761 + "inconsistent stream commitment count"); 1.762 + 1.763 + Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer", 1.764 + buf, transmittedCount); 1.765 + 1.766 + *countUsed += mTxStreamFrameSize; 1.767 + } 1.768 + 1.769 + mSession->FlushOutputQueue(); 1.770 + 1.771 + // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 1.772 + UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); 1.773 + 1.774 + mTxInlineFrameUsed = 0; 1.775 + mTxStreamFrameSize = 0; 1.776 + 1.777 + return NS_OK; 1.778 +} 1.779 + 1.780 +void 1.781 +Http2Stream::ChangeState(enum upstreamStateType newState) 1.782 +{ 1.783 + LOG3(("Http2Stream::ChangeState() %p from %X to %X", 1.784 + this, mUpstreamState, newState)); 1.785 + mUpstreamState = newState; 1.786 +} 1.787 + 1.788 +void 1.789 +Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) 1.790 +{ 1.791 + LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d", 1.792 + this, dataLength, lastFrame)); 1.793 + 1.794 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.795 + MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty"); 1.796 + MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty"); 1.797 + 1.798 + uint8_t frameFlags = 0; 1.799 + if (lastFrame) { 1.800 + frameFlags |= Http2Session::kFlag_END_STREAM; 1.801 + if (dataLength) 1.802 + SetSentFin(true); 1.803 + } 1.804 + 1.805 + mSession->CreateFrameHeader(mTxInlineFrame.get(), 1.806 + dataLength, 1.807 + Http2Session::FRAME_TYPE_DATA, 1.808 + frameFlags, mStreamID); 1.809 + 1.810 + mTxInlineFrameUsed = 8; 1.811 + mTxStreamFrameSize = dataLength; 1.812 +} 1.813 + 1.814 +// ConvertHeaders is used to convert the response headers 1.815 +// into HTTP/1 format and report some telemetry 1.816 +nsresult 1.817 +Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor, 1.818 + nsACString &aHeadersIn, 1.819 + nsACString &aHeadersOut) 1.820 +{ 1.821 + aHeadersOut.Truncate(); 1.822 + aHeadersOut.SetCapacity(aHeadersIn.Length() + 512); 1.823 + 1.824 + nsresult rv = 1.825 + decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()), 1.826 + aHeadersIn.Length(), 1.827 + aHeadersOut); 1.828 + if (NS_FAILED(rv)) { 1.829 + LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this)); 1.830 + return NS_ERROR_ILLEGAL_VALUE; 1.831 + } 1.832 + 1.833 + nsAutoCString status; 1.834 + decompressor->GetStatus(status); 1.835 + if (status.IsEmpty()) { 1.836 + LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this)); 1.837 + return NS_ERROR_ILLEGAL_VALUE; 1.838 + } 1.839 + 1.840 + if (aHeadersIn.Length() && aHeadersOut.Length()) { 1.841 + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length()); 1.842 + uint32_t ratio = 1.843 + aHeadersIn.Length() * 100 / aHeadersOut.Length(); 1.844 + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); 1.845 + } 1.846 + 1.847 + aHeadersIn.Truncate(); 1.848 + aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n")); 1.849 + LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading())); 1.850 + return NS_OK; 1.851 +} 1.852 + 1.853 +// ConvertHeaders is used to convert the response headers 1.854 +// into HTTP/1 format and report some telemetry 1.855 +nsresult 1.856 +Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor, 1.857 + nsACString &aHeadersIn, 1.858 + nsACString &aHeadersOut) 1.859 +{ 1.860 + aHeadersOut.Truncate(); 1.861 + nsresult rv = 1.862 + decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()), 1.863 + aHeadersIn.Length(), 1.864 + aHeadersOut); 1.865 + if (NS_FAILED(rv)) { 1.866 + LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this)); 1.867 + return NS_ERROR_ILLEGAL_VALUE; 1.868 + } 1.869 + 1.870 + nsCString method; 1.871 + decompressor->GetHost(mHeaderHost); 1.872 + decompressor->GetScheme(mHeaderScheme); 1.873 + decompressor->GetPath(mHeaderPath); 1.874 + 1.875 + if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) { 1.876 + LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required " 1.877 + "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(), 1.878 + mHeaderPath.get())); 1.879 + return NS_ERROR_ILLEGAL_VALUE; 1.880 + } 1.881 + 1.882 + decompressor->GetMethod(method); 1.883 + if (!method.Equals(NS_LITERAL_CSTRING("GET"))) { 1.884 + LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n", 1.885 + this, method.get())); 1.886 + return NS_ERROR_NOT_IMPLEMENTED; 1.887 + } 1.888 + 1.889 + aHeadersIn.Truncate(); 1.890 + LOG (("decoded push headers are:\n%s", aHeadersOut.BeginReading())); 1.891 + return NS_OK; 1.892 +} 1.893 + 1.894 +void 1.895 +Http2Stream::Close(nsresult reason) 1.896 +{ 1.897 + mTransaction->Close(reason); 1.898 +} 1.899 + 1.900 +bool 1.901 +Http2Stream::AllowFlowControlledWrite() 1.902 +{ 1.903 + return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0); 1.904 +} 1.905 + 1.906 +void 1.907 +Http2Stream::UpdateServerReceiveWindow(int32_t delta) 1.908 +{ 1.909 + mServerReceiveWindow += delta; 1.910 + 1.911 + if (mBlockedOnRwin && AllowFlowControlledWrite()) { 1.912 + LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X " 1.913 + "Open stream window\n", this, mStreamID)); 1.914 + mSession->TransactionHasDataToWrite(this); } 1.915 +} 1.916 + 1.917 +void 1.918 +Http2Stream::SetPriority(uint32_t newVal) 1.919 +{ 1.920 + mPriority = std::min(newVal, 0x7fffffffU); 1.921 +} 1.922 + 1.923 +void 1.924 +Http2Stream::SetRecvdFin(bool aStatus) 1.925 +{ 1.926 + mRecvdFin = aStatus ? 1 : 0; 1.927 + if (!aStatus) 1.928 + return; 1.929 + 1.930 + if (mState == OPEN || mState == RESERVED_BY_REMOTE) { 1.931 + mState = CLOSED_BY_REMOTE; 1.932 + } else if (mState == CLOSED_BY_LOCAL) { 1.933 + mState = CLOSED; 1.934 + } 1.935 +} 1.936 + 1.937 +void 1.938 +Http2Stream::SetSentFin(bool aStatus) 1.939 +{ 1.940 + mSentFin = aStatus ? 1 : 0; 1.941 + if (!aStatus) 1.942 + return; 1.943 + 1.944 + if (mState == OPEN || mState == RESERVED_BY_REMOTE) { 1.945 + mState = CLOSED_BY_LOCAL; 1.946 + } else if (mState == CLOSED_BY_REMOTE) { 1.947 + mState = CLOSED; 1.948 + } 1.949 +} 1.950 + 1.951 +void 1.952 +Http2Stream::SetRecvdReset(bool aStatus) 1.953 +{ 1.954 + mRecvdReset = aStatus ? 1 : 0; 1.955 + if (!aStatus) 1.956 + return; 1.957 + mState = CLOSED; 1.958 +} 1.959 + 1.960 +void 1.961 +Http2Stream::SetSentReset(bool aStatus) 1.962 +{ 1.963 + mSentReset = aStatus ? 1 : 0; 1.964 + if (!aStatus) 1.965 + return; 1.966 + mState = CLOSED; 1.967 +} 1.968 + 1.969 +//----------------------------------------------------------------------------- 1.970 +// nsAHttpSegmentReader 1.971 +//----------------------------------------------------------------------------- 1.972 + 1.973 +nsresult 1.974 +Http2Stream::OnReadSegment(const char *buf, 1.975 + uint32_t count, 1.976 + uint32_t *countRead) 1.977 +{ 1.978 + LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x", 1.979 + this, count, mUpstreamState)); 1.980 + 1.981 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.982 + MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader"); 1.983 + 1.984 + nsresult rv = NS_ERROR_UNEXPECTED; 1.985 + uint32_t dataLength; 1.986 + 1.987 + switch (mUpstreamState) { 1.988 + case GENERATING_HEADERS: 1.989 + // The buffer is the HTTP request stream, including at least part of the 1.990 + // HTTP request header. This state's job is to build a HEADERS frame 1.991 + // from the header information. count is the number of http bytes available 1.992 + // (which may include more than the header), and in countRead we return 1.993 + // the number of those bytes that we consume (i.e. the portion that are 1.994 + // header bytes) 1.995 + 1.996 + rv = ParseHttpRequestHeaders(buf, count, countRead); 1.997 + if (NS_FAILED(rv)) 1.998 + return rv; 1.999 + LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d", 1.1000 + this, *countRead, count, mAllHeadersSent)); 1.1001 + if (mAllHeadersSent) { 1.1002 + SetHTTPState(OPEN); 1.1003 + AdjustInitialWindow(); 1.1004 + // This version of TransmitFrame cannot block 1.1005 + rv = TransmitFrame(nullptr, nullptr, true); 1.1006 + ChangeState(GENERATING_BODY); 1.1007 + break; 1.1008 + } 1.1009 + MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data"); 1.1010 + break; 1.1011 + 1.1012 + case GENERATING_BODY: 1.1013 + // if there is session flow control and either the stream window is active and 1.1014 + // exhaused or the session window is exhausted then suspend 1.1015 + if (!AllowFlowControlledWrite()) { 1.1016 + *countRead = 0; 1.1017 + LOG3(("Http2Stream this=%p, id 0x%X request body suspended because " 1.1018 + "remote window is stream=%ld session=%ld.\n", this, mStreamID, 1.1019 + mServerReceiveWindow, mSession->ServerSessionWindow())); 1.1020 + mBlockedOnRwin = true; 1.1021 + return NS_BASE_STREAM_WOULD_BLOCK; 1.1022 + } 1.1023 + mBlockedOnRwin = false; 1.1024 + 1.1025 + // The chunk is the smallest of: availableData, configured chunkSize, 1.1026 + // stream window, session window, or 14 bit framing limit. 1.1027 + // Its amazing we send anything at all. 1.1028 + dataLength = std::min(count, mChunkSize); 1.1029 + 1.1030 + if (dataLength > Http2Session::kMaxFrameData) 1.1031 + dataLength = Http2Session::kMaxFrameData; 1.1032 + 1.1033 + if (dataLength > mSession->ServerSessionWindow()) 1.1034 + dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow()); 1.1035 + 1.1036 + if (dataLength > mServerReceiveWindow) 1.1037 + dataLength = static_cast<uint32_t>(mServerReceiveWindow); 1.1038 + 1.1039 + LOG3(("Http2Stream this=%p id 0x%X send calculation " 1.1040 + "avail=%d chunksize=%d stream window=%d session window=%d " 1.1041 + "max frame=%d USING=%d\n", this, mStreamID, 1.1042 + count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(), 1.1043 + Http2Session::kMaxFrameData, dataLength)); 1.1044 + 1.1045 + mSession->DecrementServerSessionWindow(dataLength); 1.1046 + mServerReceiveWindow -= dataLength; 1.1047 + 1.1048 + LOG3(("Http2Stream %p id %x request len remaining %d, " 1.1049 + "count avail %d, chunk used %d", 1.1050 + this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); 1.1051 + if (dataLength > mRequestBodyLenRemaining) 1.1052 + return NS_ERROR_UNEXPECTED; 1.1053 + mRequestBodyLenRemaining -= dataLength; 1.1054 + GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); 1.1055 + ChangeState(SENDING_BODY); 1.1056 + // NO BREAK 1.1057 + 1.1058 + case SENDING_BODY: 1.1059 + MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b"); 1.1060 + rv = TransmitFrame(buf, countRead, false); 1.1061 + MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, 1.1062 + "Transmit Frame should be all or nothing"); 1.1063 + 1.1064 + LOG3(("TransmitFrame() rv=%x returning %d data bytes. " 1.1065 + "Header is %d Body is %d.", 1.1066 + rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize)); 1.1067 + 1.1068 + // normalize a partial write with a WOULD_BLOCK into just a partial write 1.1069 + // as some code will take WOULD_BLOCK to mean an error with nothing 1.1070 + // written (e.g. nsHttpTransaction::ReadRequestSegment() 1.1071 + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) 1.1072 + rv = NS_OK; 1.1073 + 1.1074 + // If that frame was all sent, look for another one 1.1075 + if (!mTxInlineFrameUsed) 1.1076 + ChangeState(GENERATING_BODY); 1.1077 + break; 1.1078 + 1.1079 + case SENDING_FIN_STREAM: 1.1080 + MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment"); 1.1081 + break; 1.1082 + 1.1083 + default: 1.1084 + MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state"); 1.1085 + break; 1.1086 + } 1.1087 + 1.1088 + return rv; 1.1089 +} 1.1090 + 1.1091 +//----------------------------------------------------------------------------- 1.1092 +// nsAHttpSegmentWriter 1.1093 +//----------------------------------------------------------------------------- 1.1094 + 1.1095 +nsresult 1.1096 +Http2Stream::OnWriteSegment(char *buf, 1.1097 + uint32_t count, 1.1098 + uint32_t *countWritten) 1.1099 +{ 1.1100 + LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n", 1.1101 + this, count, mUpstreamState, mStreamID)); 1.1102 + 1.1103 + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); 1.1104 + MOZ_ASSERT(mSegmentWriter); 1.1105 + 1.1106 + if (!mPushSource) 1.1107 + return mSegmentWriter->OnWriteSegment(buf, count, countWritten); 1.1108 + 1.1109 + nsresult rv; 1.1110 + rv = mPushSource->GetBufferedData(buf, count, countWritten); 1.1111 + if (NS_FAILED(rv)) 1.1112 + return rv; 1.1113 + 1.1114 + mSession->ConnectPushedStream(this); 1.1115 + return NS_OK; 1.1116 +} 1.1117 + 1.1118 +} // namespace mozilla::net 1.1119 +} // namespace mozilla 1.1120 +