Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
10 // Log on level :5, instead of default :4.
11 #undef LOG
12 #define LOG(args) LOG5(args)
13 #undef LOG_ENABLED
14 #define LOG_ENABLED() LOG5_ENABLED()
16 #include <algorithm>
18 #include "Http2Compression.h"
19 #include "Http2Session.h"
20 #include "Http2Stream.h"
21 #include "Http2Push.h"
23 #include "mozilla/Telemetry.h"
24 #include "nsAlgorithm.h"
25 #include "nsHttp.h"
26 #include "nsHttpHandler.h"
27 #include "nsHttpRequestHead.h"
28 #include "nsISocketTransport.h"
29 #include "prnetdb.h"
31 #ifdef DEBUG
32 // defined by the socket transport service while active
33 extern PRThread *gSocketThread;
34 #endif
36 namespace mozilla {
37 namespace net {
39 Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
40 Http2Session *session,
41 int32_t priority)
42 : mStreamID(0),
43 mSession(session),
44 mUpstreamState(GENERATING_HEADERS),
45 mState(IDLE),
46 mAllHeadersSent(0),
47 mAllHeadersReceived(0),
48 mTransaction(httpTransaction),
49 mSocketTransport(session->SocketTransport()),
50 mSegmentReader(nullptr),
51 mSegmentWriter(nullptr),
52 mChunkSize(session->SendingChunkSize()),
53 mRequestBlockedOnRead(0),
54 mRecvdFin(0),
55 mRecvdReset(0),
56 mSentReset(0),
57 mCountAsActive(0),
58 mSentFin(0),
59 mSentWaitingFor(0),
60 mSetTCPSocketBuffer(0),
61 mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
62 mTxInlineFrameUsed(0),
63 mTxStreamFrameSize(0),
64 mRequestBodyLenRemaining(0),
65 mLocalUnacked(0),
66 mBlockedOnRwin(false),
67 mTotalSent(0),
68 mTotalRead(0),
69 mPushSource(nullptr)
70 {
71 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
73 LOG3(("Http2Stream::Http2Stream %p", this));
75 mServerReceiveWindow = session->GetServerInitialStreamWindow();
76 mClientReceiveWindow = session->PushAllowance();
78 mTxInlineFrame = new uint8_t[mTxInlineFrameSize];
80 PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority);
82 // values of priority closer to 0 are higher priority for both
83 // mPriority and the priority argument. They are relative but not
84 // proportional.
85 int32_t httpPriority;
86 if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
87 httpPriority = kWorstPriority;
88 } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
89 httpPriority = kBestPriority;
90 } else {
91 httpPriority = kNormalPriority + priority;
92 }
93 MOZ_ASSERT(httpPriority >= 0);
94 mPriority = static_cast<uint32_t>(httpPriority);
95 }
97 Http2Stream::~Http2Stream()
98 {
99 mStreamID = Http2Session::kDeadStreamID;
100 }
102 // ReadSegments() is used to write data down the socket. Generally, HTTP
103 // request data is pulled from the approriate transaction and
104 // converted to HTTP/2 data. Sometimes control data like a window-update is
105 // generated instead.
107 nsresult
108 Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
109 uint32_t count,
110 uint32_t *countRead)
111 {
112 LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x",
113 this, reader, count, mUpstreamState));
115 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
117 nsresult rv = NS_ERROR_UNEXPECTED;
118 mRequestBlockedOnRead = 0;
120 if (mRecvdFin || mRecvdReset) {
121 // Don't transmit any request frames if the peer cannot respond
122 LOG3(("Http2Stream %p ReadSegments request stream aborted due to"
123 " response side closure\n", this));
124 return NS_ERROR_ABORT;
125 }
127 // avoid runt chunks if possible by anticipating
128 // full data frames
129 if (count > (mChunkSize + 8)) {
130 uint32_t numchunks = count / (mChunkSize + 8);
131 count = numchunks * (mChunkSize + 8);
132 }
134 switch (mUpstreamState) {
135 case GENERATING_HEADERS:
136 case GENERATING_BODY:
137 case SENDING_BODY:
138 // Call into the HTTP Transaction to generate the HTTP request
139 // stream. That stream will show up in OnReadSegment().
140 mSegmentReader = reader;
141 rv = mTransaction->ReadSegments(this, count, countRead);
142 mSegmentReader = nullptr;
144 // Check to see if the transaction's request could be written out now.
145 // If not, mark the stream for callback when writing can proceed.
146 if (NS_SUCCEEDED(rv) &&
147 mUpstreamState == GENERATING_HEADERS &&
148 !mAllHeadersSent)
149 mSession->TransactionHasDataToWrite(this);
151 // mTxinlineFrameUsed represents any queued un-sent frame. It might
152 // be 0 if there is no such frame, which is not a gurantee that we
153 // don't have more request body to send - just that any data that was
154 // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
155 // a queued, but complete, http/2 frame length.
157 // Mark that we are blocked on read if the http transaction needs to
158 // provide more of the request message body and there is nothing queued
159 // for writing
160 if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
161 mRequestBlockedOnRead = 1;
163 // If the sending flow control window is open (!mBlockedOnRwin) then
164 // continue sending the request
165 if (!mBlockedOnRwin &&
166 !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
167 LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
168 "mUpstreamState=%x",this, mStreamID, mUpstreamState));
169 if (mSentFin) {
170 ChangeState(UPSTREAM_COMPLETE);
171 } else {
172 GenerateDataFrameHeader(0, true);
173 ChangeState(SENDING_FIN_STREAM);
174 mSession->TransactionHasDataToWrite(this);
175 rv = NS_BASE_STREAM_WOULD_BLOCK;
176 }
177 }
178 break;
180 case SENDING_FIN_STREAM:
181 // We were trying to send the FIN-STREAM but were blocked from
182 // sending it out - try again.
183 if (!mSentFin) {
184 mSegmentReader = reader;
185 rv = TransmitFrame(nullptr, nullptr, false);
186 mSegmentReader = nullptr;
187 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
188 "Transmit Frame should be all or nothing");
189 if (NS_SUCCEEDED(rv))
190 ChangeState(UPSTREAM_COMPLETE);
191 } else {
192 rv = NS_OK;
193 mTxInlineFrameUsed = 0; // cancel fin data packet
194 ChangeState(UPSTREAM_COMPLETE);
195 }
197 *countRead = 0;
199 // don't change OK to WOULD BLOCK. we are really done sending if OK
200 break;
202 case UPSTREAM_COMPLETE:
203 *countRead = 0;
204 rv = NS_OK;
205 break;
207 default:
208 MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
209 break;
210 }
212 return rv;
213 }
215 // WriteSegments() is used to read data off the socket. Generally this is
216 // just a call through to the associate nsHttpTransaciton for this stream
217 // for the remaining data bytes indicated by the current DATA frame.
219 nsresult
220 Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
221 uint32_t count,
222 uint32_t *countWritten)
223 {
224 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
225 MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
227 LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
228 this, count, mUpstreamState));
230 mSegmentWriter = writer;
231 nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
232 mSegmentWriter = nullptr;
234 return rv;
235 }
237 void
238 Http2Stream::CreatePushHashKey(const nsCString &scheme,
239 const nsCString &hostHeader,
240 uint64_t serial,
241 const nsCSubstring &pathInfo,
242 nsCString &outOrigin,
243 nsCString &outKey)
244 {
245 outOrigin = scheme;
246 outOrigin.Append(NS_LITERAL_CSTRING("://"));
247 outOrigin.Append(hostHeader);
249 outKey = outOrigin;
250 outKey.Append(NS_LITERAL_CSTRING("/[http2."));
251 outKey.AppendInt(serial);
252 outKey.Append(NS_LITERAL_CSTRING("]"));
253 outKey.Append(pathInfo);
254 }
256 nsresult
257 Http2Stream::ParseHttpRequestHeaders(const char *buf,
258 uint32_t avail,
259 uint32_t *countUsed)
260 {
261 // Returns NS_OK even if the headers are incomplete
262 // set mAllHeadersSent flag if they are complete
264 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
265 MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
267 LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x",
268 this, avail, mUpstreamState));
270 mFlatHttpRequestHeaders.Append(buf, avail);
272 // We can use the simple double crlf because firefox is the
273 // only client we are parsing
274 int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
276 if (endHeader == kNotFound) {
277 // We don't have all the headers yet
278 LOG3(("Http2Stream::ParseHttpRequestHeaders %p "
279 "Need more header bytes. Len = %d",
280 this, mFlatHttpRequestHeaders.Length()));
281 *countUsed = avail;
282 return NS_OK;
283 }
285 // We have recvd all the headers, trim the local
286 // buffer of the final empty line, and set countUsed to reflect
287 // the whole header has been consumed.
288 uint32_t oldLen = mFlatHttpRequestHeaders.Length();
289 mFlatHttpRequestHeaders.SetLength(endHeader + 2);
290 *countUsed = avail - (oldLen - endHeader) + 4;
291 mAllHeadersSent = 1;
293 nsCString hostHeader;
294 nsCString hashkey;
295 mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
297 CreatePushHashKey(NS_LITERAL_CSTRING("https"),
298 hostHeader, mSession->Serial(),
299 mTransaction->RequestHead()->RequestURI(),
300 mOrigin, hashkey);
302 // check the push cache for GET
303 if (mTransaction->RequestHead()->IsGet()) {
304 // from :scheme, :authority, :path
305 nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
306 SpdyPushCache *cache = nullptr;
307 if (loadGroupCI)
308 loadGroupCI->GetSpdyPushCache(&cache);
310 Http2PushedStream *pushedStream = nullptr;
311 // we remove the pushedstream from the push cache so that
312 // it will not be used for another GET. This does not destroy the
313 // stream itself - that is done when the transactionhash is done with it.
314 if (cache)
315 pushedStream = cache->RemovePushedStreamHttp2(hashkey);
317 LOG3(("Pushed Stream Lookup "
318 "session=%p key=%s loadgroupci=%p cache=%p hit=%p\n",
319 mSession, hashkey.get(), loadGroupCI, cache, pushedStream));
321 if (pushedStream) {
322 LOG3(("Pushed Stream Match located id=0x%X key=%s\n",
323 pushedStream->StreamID(), hashkey.get()));
324 pushedStream->SetConsumerStream(this);
325 mPushSource = pushedStream;
326 SetSentFin(true);
327 AdjustPushedPriority();
329 // This stream has been activated (and thus counts against the concurrency
330 // limit intentionally), but will not be registered via
331 // RegisterStreamID (below) because of the push match.
332 // Release that semaphore count immediately (instead of waiting for
333 // cleanup stream) so we can initiate more pull streams.
334 mSession->MaybeDecrementConcurrent(this);
336 // There is probably pushed data buffered so trigger a read manually
337 // as we can't rely on future network events to do it
338 mSession->ConnectPushedStream(this);
339 return NS_OK;
340 }
341 }
343 // It is now OK to assign a streamID that we are assured will
344 // be monotonically increasing amongst new streams on this
345 // session
346 mStreamID = mSession->RegisterStreamID(this);
347 MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
348 LOG3(("Stream ID 0x%X [session=%p] for URI %s\n",
349 mStreamID, mSession,
350 nsCString(mTransaction->RequestHead()->RequestURI()).get()));
352 if (mStreamID >= 0x80000000) {
353 // streamID must fit in 31 bits. Evading This is theoretically possible
354 // because stream ID assignment is asynchronous to stream creation
355 // because of the protocol requirement that the new stream ID
356 // be monotonically increasing. In reality this is really not possible
357 // because new streams stop being added to a session with millions of
358 // IDs still available and no race condition is going to bridge that gap;
359 // so we can be comfortable on just erroring out for correctness in that
360 // case.
361 LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
362 return NS_ERROR_UNEXPECTED;
363 }
365 // Now we need to convert the flat http headers into a set
366 // of HTTP/2 headers by writing to mTxInlineFrame{sz}
368 nsCString compressedData;
369 mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
370 mTransaction->RequestHead()->Method(),
371 mTransaction->RequestHead()->RequestURI(),
372 hostHeader,
373 NS_LITERAL_CSTRING("https"),
374 compressedData);
376 // Determine whether to put the fin bit on the header frame or whether
377 // to wait for a data packet to put it on.
378 uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
380 if (mTransaction->RequestHead()->IsGet() ||
381 mTransaction->RequestHead()->IsConnect() ||
382 mTransaction->RequestHead()->IsHead()) {
383 // for GET, CONNECT, and HEAD place the fin bit right on the
384 // header packet
386 SetSentFin(true);
387 firstFrameFlags |= Http2Session::kFlag_END_STREAM;
388 } else if (mTransaction->RequestHead()->IsPost() ||
389 mTransaction->RequestHead()->IsPut() ||
390 mTransaction->RequestHead()->IsOptions()) {
391 // place fin in a data frame even for 0 length messages for iterop
392 } else if (!mRequestBodyLenRemaining) {
393 // for other HTTP extension methods, rely on the content-length
394 // to determine whether or not to put fin on headers
395 SetSentFin(true);
396 firstFrameFlags |= Http2Session::kFlag_END_STREAM;
397 }
399 // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the
400 // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing
401 // frame for the new headers and for the first one a priority field. There is
402 // no question this is ugly, but a 16KB HEADERS frame should be a long
403 // tail event, so this is really just for correctness and a nop in the base case.
404 //
406 MOZ_ASSERT(!mTxInlineFrameUsed);
408 uint32_t dataLength = compressedData.Length();
409 uint32_t maxFrameData = Http2Session::kMaxFrameData - 4; // 4 byes for priority
410 uint32_t numFrames = 1;
412 if (dataLength > maxFrameData) {
413 numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
414 Http2Session::kMaxFrameData;
415 MOZ_ASSERT (numFrames > 1);
416 }
418 // note that we could still have 1 frame for 0 bytes of data. that's ok.
420 uint32_t messageSize = dataLength;
421 messageSize += 12; // header frame overhead
422 messageSize += (numFrames - 1) * 8; // continuation frames overhead
424 Http2Session::EnsureBuffer(mTxInlineFrame,
425 dataLength + messageSize,
426 mTxInlineFrameUsed,
427 mTxInlineFrameSize);
429 mTxInlineFrameUsed += messageSize;
430 LOG3(("%p Generating %d bytes of HEADERS for stream 0x%X at priority %u frames %u\n",
431 this, mTxInlineFrameUsed, mStreamID, mPriority, numFrames));
433 uint32_t outputOffset = 0;
434 uint32_t compressedDataOffset = 0;
435 for (uint32_t idx = 0; idx < numFrames; ++idx) {
436 uint32_t flags, frameLen;
437 bool lastFrame = (idx == numFrames - 1);
439 flags = 0;
440 frameLen = maxFrameData;
441 if (!idx) {
442 flags |= firstFrameFlags;
443 // Only the first frame needs the 4-byte offset
444 maxFrameData = Http2Session::kMaxFrameData;
445 }
446 if (lastFrame) {
447 frameLen = dataLength;
448 flags |= Http2Session::kFlag_END_HEADERS;
449 }
450 dataLength -= frameLen;
452 mSession->CreateFrameHeader(
453 mTxInlineFrame.get() + outputOffset,
454 frameLen + (idx ? 0 : 4),
455 (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS,
456 flags, mStreamID);
457 outputOffset += 8;
459 if (!idx) {
460 uint32_t priority = PR_htonl(mPriority);
461 memcpy (mTxInlineFrame.get() + outputOffset, &priority, 4);
462 outputOffset += 4;
463 }
465 memcpy(mTxInlineFrame.get() + outputOffset,
466 compressedData.BeginReading() + compressedDataOffset, frameLen);
467 compressedDataOffset += frameLen;
468 outputOffset += frameLen;
469 }
471 Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
473 // The size of the input headers is approximate
474 uint32_t ratio =
475 compressedData.Length() * 100 /
476 (11 + mTransaction->RequestHead()->RequestURI().Length() +
477 mFlatHttpRequestHeaders.Length());
479 const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
480 int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
481 while (true) {
482 int32_t startIndex = crlfIndex + 2;
484 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
485 if (crlfIndex == -1)
486 break;
488 int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
489 crlfIndex - startIndex);
490 if (colonIndex == -1)
491 break;
493 nsDependentCSubstring name = Substring(beginBuffer + startIndex,
494 beginBuffer + colonIndex);
495 // all header names are lower case in spdy
496 ToLowerCase(name);
498 if (name.Equals("content-length")) {
499 nsCString *val = new nsCString();
500 int32_t valueIndex = colonIndex + 1;
501 while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
502 ++valueIndex;
504 nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
505 beginBuffer + crlfIndex);
506 val->Append(v);
508 int64_t len;
509 if (nsHttp::ParseInt64(val->get(), nullptr, &len))
510 mRequestBodyLenRemaining = len;
511 break;
512 }
513 }
515 mFlatHttpRequestHeaders.Truncate();
516 Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
517 return NS_OK;
518 }
520 void
521 Http2Stream::AdjustInitialWindow()
522 {
523 // The default initial_window is sized for pushed streams. When we
524 // generate a client pulled stream we want to disable flow control for
525 // the stream with a window update. Do the same for pushed streams
526 // when they connect to a pull.
528 // >0 even numbered IDs are pushed streams.
529 // odd numbered IDs are pulled streams.
530 // 0 is the sink for a pushed stream.
531 Http2Stream *stream = this;
532 if (!mStreamID) {
533 MOZ_ASSERT(mPushSource);
534 if (!mPushSource)
535 return;
536 stream = mPushSource;
537 MOZ_ASSERT(stream->mStreamID);
538 MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
540 // If the pushed stream has recvd a FIN, there is no reason to update
541 // the window
542 if (stream->RecvdFin() || stream->RecvdReset())
543 return;
544 }
546 uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
547 Http2Session::EnsureBuffer(mTxInlineFrame,
548 mTxInlineFrameUsed + 12,
549 mTxInlineFrameUsed,
550 mTxInlineFrameSize);
551 mTxInlineFrameUsed += 12;
553 mSession->CreateFrameHeader(packet, 4,
554 Http2Session::FRAME_TYPE_WINDOW_UPDATE,
555 0, stream->mStreamID);
557 MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin);
558 uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow;
559 mClientReceiveWindow += bump;
560 bump = PR_htonl(bump);
561 memcpy(packet + 8, &bump, 4);
562 LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n",
563 this, stream->mStreamID));
564 }
566 void
567 Http2Stream::AdjustPushedPriority()
568 {
569 // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
570 // 0 is the sink for a pushed stream.
572 if (mStreamID || !mPushSource)
573 return;
575 MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
577 // If the pushed stream has recvd a FIN, there is no reason to update
578 // the window
579 if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
580 return;
582 uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
583 Http2Session::EnsureBuffer(mTxInlineFrame,
584 mTxInlineFrameUsed + 12,
585 mTxInlineFrameUsed,
586 mTxInlineFrameSize);
587 mTxInlineFrameUsed += 12;
589 mSession->CreateFrameHeader(packet, 4,
590 Http2Session::FRAME_TYPE_PRIORITY, 0,
591 mPushSource->mStreamID);
593 uint32_t newPriority = PR_htonl(mPriority);
594 mPushSource->SetPriority(newPriority);
595 memcpy(packet + 8, &newPriority, 4);
597 LOG3(("AdjustPushedPriority %p id 0x%X to %X\n", this, mPushSource->mStreamID,
598 newPriority));
599 }
601 void
602 Http2Stream::UpdateTransportReadEvents(uint32_t count)
603 {
604 mTotalRead += count;
605 mTransaction->OnTransportStatus(mSocketTransport,
606 NS_NET_STATUS_RECEIVING_FROM,
607 mTotalRead);
608 }
610 void
611 Http2Stream::UpdateTransportSendEvents(uint32_t count)
612 {
613 mTotalSent += count;
615 // normally on non-windows platform we use TCP autotuning for
616 // the socket buffers, and this works well (managing enough
617 // buffers for BDP while conserving memory) for HTTP even when
618 // it creates really deep queues. However this 'buffer bloat' is
619 // a problem for http/2 because it ruins the low latency properties
620 // necessary for PING and cancel to work meaningfully.
621 //
622 // If this stream represents a large upload, disable autotuning for
623 // the session and cap the send buffers by default at 128KB.
624 // (10Mbit/sec @ 100ms)
625 //
626 uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
627 if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
628 mSetTCPSocketBuffer = 1;
629 mSocketTransport->SetSendBufferSize(bufferSize);
630 }
632 if (mUpstreamState != SENDING_FIN_STREAM)
633 mTransaction->OnTransportStatus(mSocketTransport,
634 NS_NET_STATUS_SENDING_TO,
635 mTotalSent);
637 if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
638 mSentWaitingFor = 1;
639 mTransaction->OnTransportStatus(mSocketTransport,
640 NS_NET_STATUS_WAITING_FOR,
641 0);
642 }
643 }
645 nsresult
646 Http2Stream::TransmitFrame(const char *buf,
647 uint32_t *countUsed,
648 bool forceCommitment)
649 {
650 // If TransmitFrame returns SUCCESS than all the data is sent (or at least
651 // buffered at the session level), if it returns WOULD_BLOCK then none of
652 // the data is sent.
654 // You can call this function with no data and no out parameter in order to
655 // flush internal buffers that were previously blocked on writing. You can
656 // of course feed new data to it as well.
658 LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d",
659 this, mTxInlineFrameUsed, mTxStreamFrameSize));
660 if (countUsed)
661 *countUsed = 0;
663 if (!mTxInlineFrameUsed) {
664 MOZ_ASSERT(!buf);
665 return NS_OK;
666 }
668 MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
669 MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
670 MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
671 "TransmitFrame arguments inconsistent");
673 uint32_t transmittedCount;
674 nsresult rv;
676 // In the (relatively common) event that we have a small amount of data
677 // split between the inlineframe and the streamframe, then move the stream
678 // data into the inlineframe via copy in order to coalesce into one write.
679 // Given the interaction with ssl this is worth the small copy cost.
680 if (mTxStreamFrameSize && mTxInlineFrameUsed &&
681 mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
682 mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
683 LOG3(("Coalesce Transmit"));
684 memcpy (mTxInlineFrame + mTxInlineFrameUsed,
685 buf, mTxStreamFrameSize);
686 if (countUsed)
687 *countUsed += mTxStreamFrameSize;
688 mTxInlineFrameUsed += mTxStreamFrameSize;
689 mTxStreamFrameSize = 0;
690 }
692 rv =
693 mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
694 forceCommitment);
696 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
697 MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
698 mSession->TransactionHasDataToWrite(this);
699 }
700 if (NS_FAILED(rv)) // this will include WOULD_BLOCK
701 return rv;
703 // This function calls mSegmentReader->OnReadSegment to report the actual http/2
704 // bytes through to the session object and then the HttpConnection which calls
705 // the socket write function. It will accept all of the inline and stream
706 // data because of the above 'commitment' even if it has to buffer
708 rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
709 mTxInlineFrameUsed,
710 &transmittedCount);
711 LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
712 "stream=%p result %x len=%d",
713 mSession, this, rv, transmittedCount));
715 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
716 "inconsistent inline commitment result");
718 if (NS_FAILED(rv))
719 return rv;
721 MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
722 "inconsistent inline commitment count");
724 Http2Session::LogIO(mSession, this, "Writing from Inline Buffer",
725 reinterpret_cast<char*>(mTxInlineFrame.get()),
726 transmittedCount);
728 if (mTxStreamFrameSize) {
729 if (!buf) {
730 // this cannot happen
731 MOZ_ASSERT(false, "Stream transmit with null buf argument to "
732 "TransmitFrame()");
733 LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
734 return NS_ERROR_UNEXPECTED;
735 }
737 // If there is already data buffered, just add to that to form
738 // a single TLS Application Data Record - otherwise skip the memcpy
739 if (mSession->AmountOfOutputBuffered()) {
740 rv = mSession->BufferOutput(buf, mTxStreamFrameSize,
741 &transmittedCount);
742 } else {
743 rv = mSession->OnReadSegment(buf, mTxStreamFrameSize,
744 &transmittedCount);
745 }
747 LOG3(("Http2Stream::TransmitFrame for regular session=%p "
748 "stream=%p result %x len=%d",
749 mSession, this, rv, transmittedCount));
751 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
752 "inconsistent stream commitment result");
754 if (NS_FAILED(rv))
755 return rv;
757 MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
758 "inconsistent stream commitment count");
760 Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
761 buf, transmittedCount);
763 *countUsed += mTxStreamFrameSize;
764 }
766 mSession->FlushOutputQueue();
768 // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
769 UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
771 mTxInlineFrameUsed = 0;
772 mTxStreamFrameSize = 0;
774 return NS_OK;
775 }
777 void
778 Http2Stream::ChangeState(enum upstreamStateType newState)
779 {
780 LOG3(("Http2Stream::ChangeState() %p from %X to %X",
781 this, mUpstreamState, newState));
782 mUpstreamState = newState;
783 }
785 void
786 Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
787 {
788 LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d",
789 this, dataLength, lastFrame));
791 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
792 MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
793 MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
795 uint8_t frameFlags = 0;
796 if (lastFrame) {
797 frameFlags |= Http2Session::kFlag_END_STREAM;
798 if (dataLength)
799 SetSentFin(true);
800 }
802 mSession->CreateFrameHeader(mTxInlineFrame.get(),
803 dataLength,
804 Http2Session::FRAME_TYPE_DATA,
805 frameFlags, mStreamID);
807 mTxInlineFrameUsed = 8;
808 mTxStreamFrameSize = dataLength;
809 }
811 // ConvertHeaders is used to convert the response headers
812 // into HTTP/1 format and report some telemetry
813 nsresult
814 Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
815 nsACString &aHeadersIn,
816 nsACString &aHeadersOut)
817 {
818 aHeadersOut.Truncate();
819 aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
821 nsresult rv =
822 decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
823 aHeadersIn.Length(),
824 aHeadersOut);
825 if (NS_FAILED(rv)) {
826 LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this));
827 return NS_ERROR_ILLEGAL_VALUE;
828 }
830 nsAutoCString status;
831 decompressor->GetStatus(status);
832 if (status.IsEmpty()) {
833 LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this));
834 return NS_ERROR_ILLEGAL_VALUE;
835 }
837 if (aHeadersIn.Length() && aHeadersOut.Length()) {
838 Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
839 uint32_t ratio =
840 aHeadersIn.Length() * 100 / aHeadersOut.Length();
841 Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
842 }
844 aHeadersIn.Truncate();
845 aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n"));
846 LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
847 return NS_OK;
848 }
850 // ConvertHeaders is used to convert the response headers
851 // into HTTP/1 format and report some telemetry
852 nsresult
853 Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
854 nsACString &aHeadersIn,
855 nsACString &aHeadersOut)
856 {
857 aHeadersOut.Truncate();
858 nsresult rv =
859 decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
860 aHeadersIn.Length(),
861 aHeadersOut);
862 if (NS_FAILED(rv)) {
863 LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this));
864 return NS_ERROR_ILLEGAL_VALUE;
865 }
867 nsCString method;
868 decompressor->GetHost(mHeaderHost);
869 decompressor->GetScheme(mHeaderScheme);
870 decompressor->GetPath(mHeaderPath);
872 if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) {
873 LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required "
874 "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(),
875 mHeaderPath.get()));
876 return NS_ERROR_ILLEGAL_VALUE;
877 }
879 decompressor->GetMethod(method);
880 if (!method.Equals(NS_LITERAL_CSTRING("GET"))) {
881 LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n",
882 this, method.get()));
883 return NS_ERROR_NOT_IMPLEMENTED;
884 }
886 aHeadersIn.Truncate();
887 LOG (("decoded push headers are:\n%s", aHeadersOut.BeginReading()));
888 return NS_OK;
889 }
891 void
892 Http2Stream::Close(nsresult reason)
893 {
894 mTransaction->Close(reason);
895 }
897 bool
898 Http2Stream::AllowFlowControlledWrite()
899 {
900 return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
901 }
903 void
904 Http2Stream::UpdateServerReceiveWindow(int32_t delta)
905 {
906 mServerReceiveWindow += delta;
908 if (mBlockedOnRwin && AllowFlowControlledWrite()) {
909 LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
910 "Open stream window\n", this, mStreamID));
911 mSession->TransactionHasDataToWrite(this); }
912 }
914 void
915 Http2Stream::SetPriority(uint32_t newVal)
916 {
917 mPriority = std::min(newVal, 0x7fffffffU);
918 }
920 void
921 Http2Stream::SetRecvdFin(bool aStatus)
922 {
923 mRecvdFin = aStatus ? 1 : 0;
924 if (!aStatus)
925 return;
927 if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
928 mState = CLOSED_BY_REMOTE;
929 } else if (mState == CLOSED_BY_LOCAL) {
930 mState = CLOSED;
931 }
932 }
934 void
935 Http2Stream::SetSentFin(bool aStatus)
936 {
937 mSentFin = aStatus ? 1 : 0;
938 if (!aStatus)
939 return;
941 if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
942 mState = CLOSED_BY_LOCAL;
943 } else if (mState == CLOSED_BY_REMOTE) {
944 mState = CLOSED;
945 }
946 }
948 void
949 Http2Stream::SetRecvdReset(bool aStatus)
950 {
951 mRecvdReset = aStatus ? 1 : 0;
952 if (!aStatus)
953 return;
954 mState = CLOSED;
955 }
957 void
958 Http2Stream::SetSentReset(bool aStatus)
959 {
960 mSentReset = aStatus ? 1 : 0;
961 if (!aStatus)
962 return;
963 mState = CLOSED;
964 }
966 //-----------------------------------------------------------------------------
967 // nsAHttpSegmentReader
968 //-----------------------------------------------------------------------------
970 nsresult
971 Http2Stream::OnReadSegment(const char *buf,
972 uint32_t count,
973 uint32_t *countRead)
974 {
975 LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x",
976 this, count, mUpstreamState));
978 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
979 MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
981 nsresult rv = NS_ERROR_UNEXPECTED;
982 uint32_t dataLength;
984 switch (mUpstreamState) {
985 case GENERATING_HEADERS:
986 // The buffer is the HTTP request stream, including at least part of the
987 // HTTP request header. This state's job is to build a HEADERS frame
988 // from the header information. count is the number of http bytes available
989 // (which may include more than the header), and in countRead we return
990 // the number of those bytes that we consume (i.e. the portion that are
991 // header bytes)
993 rv = ParseHttpRequestHeaders(buf, count, countRead);
994 if (NS_FAILED(rv))
995 return rv;
996 LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
997 this, *countRead, count, mAllHeadersSent));
998 if (mAllHeadersSent) {
999 SetHTTPState(OPEN);
1000 AdjustInitialWindow();
1001 // This version of TransmitFrame cannot block
1002 rv = TransmitFrame(nullptr, nullptr, true);
1003 ChangeState(GENERATING_BODY);
1004 break;
1005 }
1006 MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
1007 break;
1009 case GENERATING_BODY:
1010 // if there is session flow control and either the stream window is active and
1011 // exhaused or the session window is exhausted then suspend
1012 if (!AllowFlowControlledWrite()) {
1013 *countRead = 0;
1014 LOG3(("Http2Stream this=%p, id 0x%X request body suspended because "
1015 "remote window is stream=%ld session=%ld.\n", this, mStreamID,
1016 mServerReceiveWindow, mSession->ServerSessionWindow()));
1017 mBlockedOnRwin = true;
1018 return NS_BASE_STREAM_WOULD_BLOCK;
1019 }
1020 mBlockedOnRwin = false;
1022 // The chunk is the smallest of: availableData, configured chunkSize,
1023 // stream window, session window, or 14 bit framing limit.
1024 // Its amazing we send anything at all.
1025 dataLength = std::min(count, mChunkSize);
1027 if (dataLength > Http2Session::kMaxFrameData)
1028 dataLength = Http2Session::kMaxFrameData;
1030 if (dataLength > mSession->ServerSessionWindow())
1031 dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
1033 if (dataLength > mServerReceiveWindow)
1034 dataLength = static_cast<uint32_t>(mServerReceiveWindow);
1036 LOG3(("Http2Stream this=%p id 0x%X send calculation "
1037 "avail=%d chunksize=%d stream window=%d session window=%d "
1038 "max frame=%d USING=%d\n", this, mStreamID,
1039 count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(),
1040 Http2Session::kMaxFrameData, dataLength));
1042 mSession->DecrementServerSessionWindow(dataLength);
1043 mServerReceiveWindow -= dataLength;
1045 LOG3(("Http2Stream %p id %x request len remaining %d, "
1046 "count avail %d, chunk used %d",
1047 this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
1048 if (dataLength > mRequestBodyLenRemaining)
1049 return NS_ERROR_UNEXPECTED;
1050 mRequestBodyLenRemaining -= dataLength;
1051 GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
1052 ChangeState(SENDING_BODY);
1053 // NO BREAK
1055 case SENDING_BODY:
1056 MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
1057 rv = TransmitFrame(buf, countRead, false);
1058 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
1059 "Transmit Frame should be all or nothing");
1061 LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
1062 "Header is %d Body is %d.",
1063 rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
1065 // normalize a partial write with a WOULD_BLOCK into just a partial write
1066 // as some code will take WOULD_BLOCK to mean an error with nothing
1067 // written (e.g. nsHttpTransaction::ReadRequestSegment()
1068 if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
1069 rv = NS_OK;
1071 // If that frame was all sent, look for another one
1072 if (!mTxInlineFrameUsed)
1073 ChangeState(GENERATING_BODY);
1074 break;
1076 case SENDING_FIN_STREAM:
1077 MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
1078 break;
1080 default:
1081 MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
1082 break;
1083 }
1085 return rv;
1086 }
1088 //-----------------------------------------------------------------------------
1089 // nsAHttpSegmentWriter
1090 //-----------------------------------------------------------------------------
1092 nsresult
1093 Http2Stream::OnWriteSegment(char *buf,
1094 uint32_t count,
1095 uint32_t *countWritten)
1096 {
1097 LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
1098 this, count, mUpstreamState, mStreamID));
1100 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1101 MOZ_ASSERT(mSegmentWriter);
1103 if (!mPushSource)
1104 return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
1106 nsresult rv;
1107 rv = mPushSource->GetBufferedData(buf, count, countWritten);
1108 if (NS_FAILED(rv))
1109 return rv;
1111 mSession->ConnectPushedStream(this);
1112 return NS_OK;
1113 }
1115 } // namespace mozilla::net
1116 } // namespace mozilla