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/. */
6
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
9
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()
15
16 #include <algorithm>
17
18 #include "Http2Compression.h"
19 #include "Http2Session.h"
20 #include "Http2Stream.h"
21 #include "Http2Push.h"
22 #include "TunnelUtils.h"
23
24 #include "mozilla/Telemetry.h"
25 #include "nsAlgorithm.h"
26 #include "nsHttp.h"
27 #include "nsHttpHandler.h"
28 #include "nsHttpRequestHead.h"
29 #include "nsIClassOfService.h"
30 #include "nsIPipe.h"
31 #include "nsISocketTransport.h"
32 #include "nsStandardURL.h"
33 #include "prnetdb.h"
34
35 namespace mozilla {
36 namespace net {
37
Http2Stream(nsAHttpTransaction * httpTransaction,Http2Session * session,int32_t priority)38 Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
39 Http2Session *session,
40 int32_t priority)
41 : mStreamID(0)
42 , mSession(session)
43 , mSegmentReader(nullptr)
44 , mSegmentWriter(nullptr)
45 , mUpstreamState(GENERATING_HEADERS)
46 , mState(IDLE)
47 , mRequestHeadersDone(0)
48 , mOpenGenerated(0)
49 , mAllHeadersReceived(0)
50 , mQueued(0)
51 , mTransaction(httpTransaction)
52 , mSocketTransport(session->SocketTransport())
53 , mChunkSize(session->SendingChunkSize())
54 , mRequestBlockedOnRead(0)
55 , mRecvdFin(0)
56 , mReceivedData(0)
57 , mRecvdReset(0)
58 , mSentReset(0)
59 , mCountAsActive(0)
60 , mSentFin(0)
61 , mSentWaitingFor(0)
62 , mSetTCPSocketBuffer(0)
63 , mBypassInputBuffer(0)
64 , mTxInlineFrameSize(Http2Session::kDefaultBufferSize)
65 , mTxInlineFrameUsed(0)
66 , mTxStreamFrameSize(0)
67 , mRequestBodyLenRemaining(0)
68 , mLocalUnacked(0)
69 , mBlockedOnRwin(false)
70 , mTotalSent(0)
71 , mTotalRead(0)
72 , mPushSource(nullptr)
73 , mIsTunnel(false)
74 , mPlainTextTunnel(false)
75 {
76 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
77
78 LOG3(("Http2Stream::Http2Stream %p", this));
79
80 mServerReceiveWindow = session->GetServerInitialStreamWindow();
81 mClientReceiveWindow = session->PushAllowance();
82
83 mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);
84
85 static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
86 "Lowest Priority should be less than kNormalPriority");
87
88 // values of priority closer to 0 are higher priority for the priority
89 // argument. This value is used as a group, which maps to a
90 // weight that is related to the nsISupportsPriority that we are given.
91 int32_t httpPriority;
92 if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
93 httpPriority = kWorstPriority;
94 } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
95 httpPriority = kBestPriority;
96 } else {
97 httpPriority = kNormalPriority + priority;
98 }
99 MOZ_ASSERT(httpPriority >= 0);
100 SetPriority(static_cast<uint32_t>(httpPriority));
101 }
102
~Http2Stream()103 Http2Stream::~Http2Stream()
104 {
105 ClearPushSource();
106 ClearTransactionsBlockedOnTunnel();
107 mStreamID = Http2Session::kDeadStreamID;
108 }
109
110 void
ClearPushSource()111 Http2Stream::ClearPushSource()
112 {
113 if (mPushSource) {
114 mPushSource->SetConsumerStream(nullptr);
115 mPushSource = nullptr;
116 }
117 }
118
119 // ReadSegments() is used to write data down the socket. Generally, HTTP
120 // request data is pulled from the approriate transaction and
121 // converted to HTTP/2 data. Sometimes control data like a window-update is
122 // generated instead.
123
124 nsresult
ReadSegments(nsAHttpSegmentReader * reader,uint32_t count,uint32_t * countRead)125 Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
126 uint32_t count,
127 uint32_t *countRead)
128 {
129 LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x",
130 this, reader, count, mUpstreamState));
131
132 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
133
134 nsresult rv = NS_ERROR_UNEXPECTED;
135 mRequestBlockedOnRead = 0;
136
137 if (mRecvdFin || mRecvdReset) {
138 // Don't transmit any request frames if the peer cannot respond
139 LOG3(("Http2Stream %p ReadSegments request stream aborted due to"
140 " response side closure\n", this));
141 return NS_ERROR_ABORT;
142 }
143
144 // avoid runt chunks if possible by anticipating
145 // full data frames
146 if (count > (mChunkSize + 8)) {
147 uint32_t numchunks = count / (mChunkSize + 8);
148 count = numchunks * (mChunkSize + 8);
149 }
150
151 switch (mUpstreamState) {
152 case GENERATING_HEADERS:
153 case GENERATING_BODY:
154 case SENDING_BODY:
155 // Call into the HTTP Transaction to generate the HTTP request
156 // stream. That stream will show up in OnReadSegment().
157 mSegmentReader = reader;
158 rv = mTransaction->ReadSegments(this, count, countRead);
159 mSegmentReader = nullptr;
160
161 LOG3(("Http2Stream::ReadSegments %p trans readsegments rv %x read=%d\n",
162 this, rv, *countRead));
163
164 // Check to see if the transaction's request could be written out now.
165 // If not, mark the stream for callback when writing can proceed.
166 if (NS_SUCCEEDED(rv) &&
167 mUpstreamState == GENERATING_HEADERS &&
168 !mRequestHeadersDone)
169 mSession->TransactionHasDataToWrite(this);
170
171 // mTxinlineFrameUsed represents any queued un-sent frame. It might
172 // be 0 if there is no such frame, which is not a gurantee that we
173 // don't have more request body to send - just that any data that was
174 // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
175 // a queued, but complete, http/2 frame length.
176
177 // Mark that we are blocked on read if the http transaction needs to
178 // provide more of the request message body and there is nothing queued
179 // for writing
180 if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
181 mRequestBlockedOnRead = 1;
182
183 // A transaction that had already generated its headers before it was
184 // queued at the session level (due to concurrency concerns) may not call
185 // onReadSegment off the ReadSegments() stack above.
186 if (mUpstreamState == GENERATING_HEADERS && NS_SUCCEEDED(rv)) {
187 LOG3(("Http2Stream %p ReadSegments forcing OnReadSegment call\n", this));
188 uint32_t wasted = 0;
189 mSegmentReader = reader;
190 OnReadSegment("", 0, &wasted);
191 mSegmentReader = nullptr;
192 }
193
194 // If the sending flow control window is open (!mBlockedOnRwin) then
195 // continue sending the request
196 if (!mBlockedOnRwin && mOpenGenerated &&
197 !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
198 MOZ_ASSERT(!mQueued);
199 MOZ_ASSERT(mRequestHeadersDone);
200 LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
201 "mUpstreamState=%x\n",this, mStreamID, mUpstreamState));
202 if (mSentFin) {
203 ChangeState(UPSTREAM_COMPLETE);
204 } else {
205 GenerateDataFrameHeader(0, true);
206 ChangeState(SENDING_FIN_STREAM);
207 mSession->TransactionHasDataToWrite(this);
208 rv = NS_BASE_STREAM_WOULD_BLOCK;
209 }
210 }
211 break;
212
213 case SENDING_FIN_STREAM:
214 // We were trying to send the FIN-STREAM but were blocked from
215 // sending it out - try again.
216 if (!mSentFin) {
217 mSegmentReader = reader;
218 rv = TransmitFrame(nullptr, nullptr, false);
219 mSegmentReader = nullptr;
220 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
221 "Transmit Frame should be all or nothing");
222 if (NS_SUCCEEDED(rv))
223 ChangeState(UPSTREAM_COMPLETE);
224 } else {
225 rv = NS_OK;
226 mTxInlineFrameUsed = 0; // cancel fin data packet
227 ChangeState(UPSTREAM_COMPLETE);
228 }
229
230 *countRead = 0;
231
232 // don't change OK to WOULD BLOCK. we are really done sending if OK
233 break;
234
235 case UPSTREAM_COMPLETE:
236 *countRead = 0;
237 rv = NS_OK;
238 break;
239
240 default:
241 MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
242 break;
243 }
244
245 return rv;
246 }
247
248 uint64_t
LocalUnAcked()249 Http2Stream::LocalUnAcked()
250 {
251 // reduce unacked by the amount of undelivered data
252 // to help assert flow control
253 uint64_t undelivered = mSimpleBuffer.Available();
254
255 if (undelivered > mLocalUnacked) {
256 return 0;
257 }
258 return mLocalUnacked - undelivered;
259 }
260
261 nsresult
BufferInput(uint32_t count,uint32_t * countWritten)262 Http2Stream::BufferInput(uint32_t count, uint32_t *countWritten)
263 {
264 char buf[SimpleBufferPage::kSimpleBufferPageSize];
265 if (SimpleBufferPage::kSimpleBufferPageSize < count) {
266 count = SimpleBufferPage::kSimpleBufferPageSize;
267 }
268
269 mBypassInputBuffer = 1;
270 nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
271 mBypassInputBuffer = 0;
272
273 if (NS_SUCCEEDED(rv)) {
274 rv = mSimpleBuffer.Write(buf, *countWritten);
275 if (NS_FAILED(rv)) {
276 MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
277 return NS_ERROR_OUT_OF_MEMORY;
278 }
279 }
280 return rv;
281 }
282
283 bool
DeferCleanup(nsresult status)284 Http2Stream::DeferCleanup(nsresult status)
285 {
286 // do not cleanup a stream that has data buffered for the transaction
287 return (NS_SUCCEEDED(status) && mSimpleBuffer.Available());
288 }
289
290 // WriteSegments() is used to read data off the socket. Generally this is
291 // just a call through to the associated nsHttpTransaction for this stream
292 // for the remaining data bytes indicated by the current DATA frame.
293
294 nsresult
WriteSegments(nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)295 Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
296 uint32_t count,
297 uint32_t *countWritten)
298 {
299 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
300 MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
301
302 LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
303 this, count, mUpstreamState));
304
305 mSegmentWriter = writer;
306 nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
307
308 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
309 // consuming transaction won't take data. but we need to read it into a buffer so that it
310 // won't block other streams. but we should not advance the flow control window
311 // so that we'll eventually push back on the sender.
312
313 // with tunnels you need to make sure that this is an underlying connction established
314 // that can be meaningfully giving this signal
315 bool doBuffer = true;
316 if (mIsTunnel) {
317 RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
318 if (qiTrans) {
319 doBuffer = qiTrans->ConnectedReadyForInput();
320 }
321 }
322 // stash this data
323 if (doBuffer) {
324 rv = BufferInput(count, countWritten);
325 LOG3(("Http2Stream::WriteSegments %p Buffered %X %d\n", this, rv, *countWritten));
326 }
327 }
328 mSegmentWriter = nullptr;
329 return rv;
330 }
331
332 nsresult
MakeOriginURL(const nsACString & origin,RefPtr<nsStandardURL> & url)333 Http2Stream::MakeOriginURL(const nsACString &origin, RefPtr<nsStandardURL> &url)
334 {
335 nsAutoCString scheme;
336 nsresult rv = net_ExtractURLScheme(origin, scheme);
337 NS_ENSURE_SUCCESS(rv, rv);
338 return MakeOriginURL(scheme, origin, url);
339 }
340
341 nsresult
MakeOriginURL(const nsACString & scheme,const nsACString & origin,RefPtr<nsStandardURL> & url)342 Http2Stream::MakeOriginURL(const nsACString &scheme, const nsACString &origin,
343 RefPtr<nsStandardURL> &url)
344 {
345 url = new nsStandardURL();
346 nsresult rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY,
347 scheme.EqualsLiteral("http") ?
348 NS_HTTP_DEFAULT_PORT :
349 NS_HTTPS_DEFAULT_PORT,
350 origin, nullptr, nullptr);
351 return rv;
352 }
353
354 void
CreatePushHashKey(const nsCString & scheme,const nsCString & hostHeader,uint64_t serial,const nsCSubstring & pathInfo,nsCString & outOrigin,nsCString & outKey)355 Http2Stream::CreatePushHashKey(const nsCString &scheme,
356 const nsCString &hostHeader,
357 uint64_t serial,
358 const nsCSubstring &pathInfo,
359 nsCString &outOrigin,
360 nsCString &outKey)
361 {
362 nsCString fullOrigin = scheme;
363 fullOrigin.AppendLiteral("://");
364 fullOrigin.Append(hostHeader);
365
366 RefPtr<nsStandardURL> origin;
367 nsresult rv = Http2Stream::MakeOriginURL(scheme, fullOrigin, origin);
368
369 if (NS_SUCCEEDED(rv)) {
370 rv = origin->GetAsciiSpec(outOrigin);
371 outOrigin.Trim("/", false, true, false);
372 }
373
374 if (NS_FAILED(rv)) {
375 // Fallback to plain text copy - this may end up behaving poorly
376 outOrigin = fullOrigin;
377 }
378
379 outKey = outOrigin;
380 outKey.AppendLiteral("/[http2.");
381 outKey.AppendInt(serial);
382 outKey.Append(']');
383 outKey.Append(pathInfo);
384 }
385
386 nsresult
ParseHttpRequestHeaders(const char * buf,uint32_t avail,uint32_t * countUsed)387 Http2Stream::ParseHttpRequestHeaders(const char *buf,
388 uint32_t avail,
389 uint32_t *countUsed)
390 {
391 // Returns NS_OK even if the headers are incomplete
392 // set mRequestHeadersDone flag if they are complete
393
394 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
395 MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
396 MOZ_ASSERT(!mRequestHeadersDone);
397
398 LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x",
399 this, avail, mUpstreamState));
400
401 mFlatHttpRequestHeaders.Append(buf, avail);
402 nsHttpRequestHead *head = mTransaction->RequestHead();
403
404 // We can use the simple double crlf because firefox is the
405 // only client we are parsing
406 int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
407
408 if (endHeader == kNotFound) {
409 // We don't have all the headers yet
410 LOG3(("Http2Stream::ParseHttpRequestHeaders %p "
411 "Need more header bytes. Len = %d",
412 this, mFlatHttpRequestHeaders.Length()));
413 *countUsed = avail;
414 return NS_OK;
415 }
416
417 // We have recvd all the headers, trim the local
418 // buffer of the final empty line, and set countUsed to reflect
419 // the whole header has been consumed.
420 uint32_t oldLen = mFlatHttpRequestHeaders.Length();
421 mFlatHttpRequestHeaders.SetLength(endHeader + 2);
422 *countUsed = avail - (oldLen - endHeader) + 4;
423 mRequestHeadersDone = 1;
424
425 nsAutoCString authorityHeader;
426 nsAutoCString hashkey;
427 head->GetHeader(nsHttp::Host, authorityHeader);
428
429 nsAutoCString requestURI;
430 head->RequestURI(requestURI);
431 CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
432 authorityHeader, mSession->Serial(),
433 requestURI,
434 mOrigin, hashkey);
435
436 // check the push cache for GET
437 if (head->IsGet()) {
438 // from :scheme, :authority, :path
439 nsIRequestContext *requestContext = mTransaction->RequestContext();
440 SpdyPushCache *cache = nullptr;
441 if (requestContext) {
442 requestContext->GetSpdyPushCache(&cache);
443 }
444
445 Http2PushedStream *pushedStream = nullptr;
446
447 // If a push stream is attached to the transaction via onPush, match only with that
448 // one. This occurs when a push was made with in conjunction with a nsIHttpPushListener
449 nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
450 if (trans && (pushedStream = trans->TakePushedStream())) {
451 if (pushedStream->mSession == mSession) {
452 LOG3(("Pushed Stream match based on OnPush correlation %p", pushedStream));
453 } else {
454 LOG3(("Pushed Stream match failed due to stream mismatch %p %d %d\n", pushedStream,
455 pushedStream->mSession->Serial(), mSession->Serial()));
456 pushedStream->OnPushFailed();
457 pushedStream = nullptr;
458 }
459 }
460
461 // we remove the pushedstream from the push cache so that
462 // it will not be used for another GET. This does not destroy the
463 // stream itself - that is done when the transactionhash is done with it.
464 if (cache && !pushedStream){
465 pushedStream = cache->RemovePushedStreamHttp2(hashkey);
466 }
467
468 LOG3(("Pushed Stream Lookup "
469 "session=%p key=%s requestcontext=%p cache=%p hit=%p\n",
470 mSession, hashkey.get(), requestContext, cache, pushedStream));
471
472 if (pushedStream) {
473 LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n",
474 pushedStream, pushedStream->StreamID(), hashkey.get()));
475 pushedStream->SetConsumerStream(this);
476 mPushSource = pushedStream;
477 SetSentFin(true);
478 AdjustPushedPriority();
479
480 // There is probably pushed data buffered so trigger a read manually
481 // as we can't rely on future network events to do it
482 mSession->ConnectPushedStream(this);
483 mOpenGenerated = 1;
484 return NS_OK;
485 }
486 }
487 return NS_OK;
488 }
489
490 // This is really a headers frame, but open is pretty clear from a workflow pov
491 nsresult
GenerateOpen()492 Http2Stream::GenerateOpen()
493 {
494 // It is now OK to assign a streamID that we are assured will
495 // be monotonically increasing amongst new streams on this
496 // session
497 mStreamID = mSession->RegisterStreamID(this);
498 MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
499 MOZ_ASSERT(!mOpenGenerated);
500
501 mOpenGenerated = 1;
502
503 nsHttpRequestHead *head = mTransaction->RequestHead();
504 nsAutoCString requestURI;
505 head->RequestURI(requestURI);
506 LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n",
507 this, mStreamID, mSession, requestURI.get()));
508
509 if (mStreamID >= 0x80000000) {
510 // streamID must fit in 31 bits. Evading This is theoretically possible
511 // because stream ID assignment is asynchronous to stream creation
512 // because of the protocol requirement that the new stream ID
513 // be monotonically increasing. In reality this is really not possible
514 // because new streams stop being added to a session with millions of
515 // IDs still available and no race condition is going to bridge that gap;
516 // so we can be comfortable on just erroring out for correctness in that
517 // case.
518 LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
519 return NS_ERROR_UNEXPECTED;
520 }
521
522 // Now we need to convert the flat http headers into a set
523 // of HTTP/2 headers by writing to mTxInlineFrame{sz}
524
525 nsCString compressedData;
526 nsAutoCString authorityHeader;
527 head->GetHeader(nsHttp::Host, authorityHeader);
528
529 nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
530 if (head->IsConnect()) {
531 MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
532 mIsTunnel = true;
533 mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
534
535 // Our normal authority has an implicit port, best to use an
536 // explicit one with a tunnel
537 nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
538 if (!ci) {
539 return NS_ERROR_UNEXPECTED;
540 }
541
542 authorityHeader = ci->GetOrigin();
543 authorityHeader.Append(':');
544 authorityHeader.AppendInt(ci->OriginPort());
545 }
546
547 nsAutoCString method;
548 nsAutoCString path;
549 head->Method(method);
550 head->Path(path);
551 mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
552 method,
553 path,
554 authorityHeader,
555 scheme,
556 head->IsConnect(),
557 compressedData);
558
559 int64_t clVal = mSession->Compressor()->GetParsedContentLength();
560 if (clVal != -1) {
561 mRequestBodyLenRemaining = clVal;
562 }
563
564 // Determine whether to put the fin bit on the header frame or whether
565 // to wait for a data packet to put it on.
566 uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
567
568 if (head->IsGet() ||
569 head->IsHead()) {
570 // for GET and HEAD place the fin bit right on the
571 // header packet
572
573 SetSentFin(true);
574 firstFrameFlags |= Http2Session::kFlag_END_STREAM;
575 } else if (head->IsPost() ||
576 head->IsPut() ||
577 head->IsConnect()) {
578 // place fin in a data frame even for 0 length messages for iterop
579 } else if (!mRequestBodyLenRemaining) {
580 // for other HTTP extension methods, rely on the content-length
581 // to determine whether or not to put fin on headers
582 SetSentFin(true);
583 firstFrameFlags |= Http2Session::kFlag_END_STREAM;
584 }
585
586 // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the
587 // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing
588 // frame for the new headers and for the first one a priority field. There is
589 // no question this is ugly, but a 16KB HEADERS frame should be a long
590 // tail event, so this is really just for correctness and a nop in the base case.
591 //
592
593 MOZ_ASSERT(!mTxInlineFrameUsed);
594
595 uint32_t dataLength = compressedData.Length();
596 uint32_t maxFrameData = Http2Session::kMaxFrameData - 5; // 5 bytes for priority
597 uint32_t numFrames = 1;
598
599 if (dataLength > maxFrameData) {
600 numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
601 Http2Session::kMaxFrameData;
602 MOZ_ASSERT (numFrames > 1);
603 }
604
605 // note that we could still have 1 frame for 0 bytes of data. that's ok.
606
607 uint32_t messageSize = dataLength;
608 messageSize += Http2Session::kFrameHeaderBytes + 5; // frame header + priority overhead in HEADERS frame
609 messageSize += (numFrames - 1) * Http2Session::kFrameHeaderBytes; // frame header overhead in CONTINUATION frames
610
611 EnsureBuffer(mTxInlineFrame, messageSize,
612 mTxInlineFrameUsed, mTxInlineFrameSize);
613
614 mTxInlineFrameUsed += messageSize;
615 UpdatePriorityDependency();
616 LOG3(("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with "
617 "priority weight %u dep 0x%X frames %u uri=%s\n",
618 this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
619 mPriorityDependency, numFrames, requestURI.get()));
620
621 uint32_t outputOffset = 0;
622 uint32_t compressedDataOffset = 0;
623 for (uint32_t idx = 0; idx < numFrames; ++idx) {
624 uint32_t flags, frameLen;
625 bool lastFrame = (idx == numFrames - 1);
626
627 flags = 0;
628 frameLen = maxFrameData;
629 if (!idx) {
630 flags |= firstFrameFlags;
631 // Only the first frame needs the 4-byte offset
632 maxFrameData = Http2Session::kMaxFrameData;
633 }
634 if (lastFrame) {
635 frameLen = dataLength;
636 flags |= Http2Session::kFlag_END_HEADERS;
637 }
638 dataLength -= frameLen;
639
640 mSession->CreateFrameHeader(
641 mTxInlineFrame.get() + outputOffset,
642 frameLen + (idx ? 0 : 5),
643 (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS,
644 flags, mStreamID);
645 outputOffset += Http2Session::kFrameHeaderBytes;
646
647 if (!idx) {
648 uint32_t wireDep = PR_htonl(mPriorityDependency);
649 memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4);
650 memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1);
651 outputOffset += 5;
652 }
653
654 memcpy(mTxInlineFrame.get() + outputOffset,
655 compressedData.BeginReading() + compressedDataOffset, frameLen);
656 compressedDataOffset += frameLen;
657 outputOffset += frameLen;
658 }
659
660 Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
661
662 // The size of the input headers is approximate
663 uint32_t ratio =
664 compressedData.Length() * 100 /
665 (11 + requestURI.Length() +
666 mFlatHttpRequestHeaders.Length());
667
668 mFlatHttpRequestHeaders.Truncate();
669 Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
670 return NS_OK;
671 }
672
673 void
AdjustInitialWindow()674 Http2Stream::AdjustInitialWindow()
675 {
676 // The default initial_window is sized for pushed streams. When we
677 // generate a client pulled stream we want to disable flow control for
678 // the stream with a window update. Do the same for pushed streams
679 // when they connect to a pull.
680
681 // >0 even numbered IDs are pushed streams.
682 // odd numbered IDs are pulled streams.
683 // 0 is the sink for a pushed stream.
684 Http2Stream *stream = this;
685 if (!mStreamID) {
686 MOZ_ASSERT(mPushSource);
687 if (!mPushSource)
688 return;
689 stream = mPushSource;
690 MOZ_ASSERT(stream->mStreamID);
691 MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
692
693 // If the pushed stream has recvd a FIN, there is no reason to update
694 // the window
695 if (stream->RecvdFin() || stream->RecvdReset())
696 return;
697 }
698
699 if (stream->mState == RESERVED_BY_REMOTE) {
700 // h2-14 prevents sending a window update in this state
701 return;
702 }
703
704 // right now mClientReceiveWindow is the lower push limit
705 // bump it up to the pull limit set by the channel or session
706 // don't allow windows less than push
707 uint32_t bump = 0;
708 nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
709 if (trans && trans->InitialRwin()) {
710 bump = (trans->InitialRwin() > mClientReceiveWindow) ?
711 (trans->InitialRwin() - mClientReceiveWindow) : 0;
712 } else {
713 MOZ_ASSERT(mSession->InitialRwin() >= mClientReceiveWindow);
714 bump = mSession->InitialRwin() - mClientReceiveWindow;
715 }
716
717 LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n",
718 this, stream->mStreamID, bump));
719 if (!bump) { // nothing to do
720 return;
721 }
722
723 EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
724 mTxInlineFrameUsed, mTxInlineFrameSize);
725 uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
726 mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;
727
728 mSession->CreateFrameHeader(packet, 4,
729 Http2Session::FRAME_TYPE_WINDOW_UPDATE,
730 0, stream->mStreamID);
731
732 mClientReceiveWindow += bump;
733 bump = PR_htonl(bump);
734 memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
735 }
736
737 void
AdjustPushedPriority()738 Http2Stream::AdjustPushedPriority()
739 {
740 // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
741 // 0 is the sink for a pushed stream.
742
743 if (mStreamID || !mPushSource)
744 return;
745
746 MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
747
748 // If the pushed stream has recvd a FIN, there is no reason to update
749 // the window
750 if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
751 return;
752
753 EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
754 mTxInlineFrameUsed, mTxInlineFrameSize);
755 uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
756 mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
757
758 mSession->CreateFrameHeader(packet, 5,
759 Http2Session::FRAME_TYPE_PRIORITY, 0,
760 mPushSource->mStreamID);
761
762 mPushSource->SetPriority(mPriority);
763 memset(packet + Http2Session::kFrameHeaderBytes, 0, 4);
764 memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
765
766 LOG3(("AdjustPushedPriority %p id 0x%X to weight %X\n", this, mPushSource->mStreamID,
767 mPriorityWeight));
768 }
769
770 void
UpdateTransportReadEvents(uint32_t count)771 Http2Stream::UpdateTransportReadEvents(uint32_t count)
772 {
773 mTotalRead += count;
774 if (!mSocketTransport) {
775 return;
776 }
777
778 mTransaction->OnTransportStatus(mSocketTransport,
779 NS_NET_STATUS_RECEIVING_FROM,
780 mTotalRead);
781 }
782
783 void
UpdateTransportSendEvents(uint32_t count)784 Http2Stream::UpdateTransportSendEvents(uint32_t count)
785 {
786 mTotalSent += count;
787
788 // normally on non-windows platform we use TCP autotuning for
789 // the socket buffers, and this works well (managing enough
790 // buffers for BDP while conserving memory) for HTTP even when
791 // it creates really deep queues. However this 'buffer bloat' is
792 // a problem for http/2 because it ruins the low latency properties
793 // necessary for PING and cancel to work meaningfully.
794 //
795 // If this stream represents a large upload, disable autotuning for
796 // the session and cap the send buffers by default at 128KB.
797 // (10Mbit/sec @ 100ms)
798 //
799 uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
800 if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
801 mSetTCPSocketBuffer = 1;
802 mSocketTransport->SetSendBufferSize(bufferSize);
803 }
804
805 if (mUpstreamState != SENDING_FIN_STREAM)
806 mTransaction->OnTransportStatus(mSocketTransport,
807 NS_NET_STATUS_SENDING_TO,
808 mTotalSent);
809
810 if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
811 mSentWaitingFor = 1;
812 mTransaction->OnTransportStatus(mSocketTransport,
813 NS_NET_STATUS_WAITING_FOR,
814 0);
815 }
816 }
817
818 nsresult
TransmitFrame(const char * buf,uint32_t * countUsed,bool forceCommitment)819 Http2Stream::TransmitFrame(const char *buf,
820 uint32_t *countUsed,
821 bool forceCommitment)
822 {
823 // If TransmitFrame returns SUCCESS than all the data is sent (or at least
824 // buffered at the session level), if it returns WOULD_BLOCK then none of
825 // the data is sent.
826
827 // You can call this function with no data and no out parameter in order to
828 // flush internal buffers that were previously blocked on writing. You can
829 // of course feed new data to it as well.
830
831 LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d",
832 this, mTxInlineFrameUsed, mTxStreamFrameSize));
833 if (countUsed)
834 *countUsed = 0;
835
836 if (!mTxInlineFrameUsed) {
837 MOZ_ASSERT(!buf);
838 return NS_OK;
839 }
840
841 MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
842 MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
843 MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
844 "TransmitFrame arguments inconsistent");
845
846 uint32_t transmittedCount;
847 nsresult rv;
848
849 // In the (relatively common) event that we have a small amount of data
850 // split between the inlineframe and the streamframe, then move the stream
851 // data into the inlineframe via copy in order to coalesce into one write.
852 // Given the interaction with ssl this is worth the small copy cost.
853 if (mTxStreamFrameSize && mTxInlineFrameUsed &&
854 mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
855 mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
856 LOG3(("Coalesce Transmit"));
857 memcpy (&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize);
858 if (countUsed)
859 *countUsed += mTxStreamFrameSize;
860 mTxInlineFrameUsed += mTxStreamFrameSize;
861 mTxStreamFrameSize = 0;
862 }
863
864 rv =
865 mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
866 forceCommitment);
867
868 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
869 MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
870 mSession->TransactionHasDataToWrite(this);
871 }
872 if (NS_FAILED(rv)) // this will include WOULD_BLOCK
873 return rv;
874
875 // This function calls mSegmentReader->OnReadSegment to report the actual http/2
876 // bytes through to the session object and then the HttpConnection which calls
877 // the socket write function. It will accept all of the inline and stream
878 // data because of the above 'commitment' even if it has to buffer
879
880 rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
881 mTxInlineFrameUsed,
882 &transmittedCount);
883 LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
884 "stream=%p result %x len=%d",
885 mSession, this, rv, transmittedCount));
886
887 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
888 "inconsistent inline commitment result");
889
890 if (NS_FAILED(rv))
891 return rv;
892
893 MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
894 "inconsistent inline commitment count");
895
896 Http2Session::LogIO(mSession, this, "Writing from Inline Buffer",
897 reinterpret_cast<char*>(mTxInlineFrame.get()),
898 transmittedCount);
899
900 if (mTxStreamFrameSize) {
901 if (!buf) {
902 // this cannot happen
903 MOZ_ASSERT(false, "Stream transmit with null buf argument to "
904 "TransmitFrame()");
905 LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
906 return NS_ERROR_UNEXPECTED;
907 }
908
909 // If there is already data buffered, just add to that to form
910 // a single TLS Application Data Record - otherwise skip the memcpy
911 if (mSession->AmountOfOutputBuffered()) {
912 rv = mSession->BufferOutput(buf, mTxStreamFrameSize,
913 &transmittedCount);
914 } else {
915 rv = mSession->OnReadSegment(buf, mTxStreamFrameSize,
916 &transmittedCount);
917 }
918
919 LOG3(("Http2Stream::TransmitFrame for regular session=%p "
920 "stream=%p result %x len=%d",
921 mSession, this, rv, transmittedCount));
922
923 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
924 "inconsistent stream commitment result");
925
926 if (NS_FAILED(rv))
927 return rv;
928
929 MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
930 "inconsistent stream commitment count");
931
932 Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
933 buf, transmittedCount);
934
935 *countUsed += mTxStreamFrameSize;
936 }
937
938 mSession->FlushOutputQueue();
939
940 // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
941 UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
942
943 mTxInlineFrameUsed = 0;
944 mTxStreamFrameSize = 0;
945
946 return NS_OK;
947 }
948
949 void
ChangeState(enum upstreamStateType newState)950 Http2Stream::ChangeState(enum upstreamStateType newState)
951 {
952 LOG3(("Http2Stream::ChangeState() %p from %X to %X",
953 this, mUpstreamState, newState));
954 mUpstreamState = newState;
955 }
956
957 void
GenerateDataFrameHeader(uint32_t dataLength,bool lastFrame)958 Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
959 {
960 LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d",
961 this, dataLength, lastFrame));
962
963 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
964 MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
965 MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
966
967 uint8_t frameFlags = 0;
968 if (lastFrame) {
969 frameFlags |= Http2Session::kFlag_END_STREAM;
970 if (dataLength)
971 SetSentFin(true);
972 }
973
974 mSession->CreateFrameHeader(mTxInlineFrame.get(),
975 dataLength,
976 Http2Session::FRAME_TYPE_DATA,
977 frameFlags, mStreamID);
978
979 mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes;
980 mTxStreamFrameSize = dataLength;
981 }
982
983 // ConvertResponseHeaders is used to convert the response headers
984 // into HTTP/1 format and report some telemetry
985 nsresult
ConvertResponseHeaders(Http2Decompressor * decompressor,nsACString & aHeadersIn,nsACString & aHeadersOut,int32_t & httpResponseCode)986 Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
987 nsACString &aHeadersIn,
988 nsACString &aHeadersOut,
989 int32_t &httpResponseCode)
990 {
991 aHeadersOut.Truncate();
992 aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
993
994 nsresult rv =
995 decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
996 aHeadersIn.Length(),
997 aHeadersOut, false);
998 if (NS_FAILED(rv)) {
999 LOG3(("Http2Stream::ConvertResponseHeaders %p decode Error\n", this));
1000 return rv;
1001 }
1002
1003 nsAutoCString statusString;
1004 decompressor->GetStatus(statusString);
1005 if (statusString.IsEmpty()) {
1006 LOG3(("Http2Stream::ConvertResponseHeaders %p Error - no status\n", this));
1007 return NS_ERROR_ILLEGAL_VALUE;
1008 }
1009
1010 nsresult errcode;
1011 httpResponseCode = statusString.ToInteger(&errcode);
1012 if (mIsTunnel) {
1013 LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode));
1014 if ((httpResponseCode / 100) != 2) {
1015 MapStreamToPlainText();
1016 }
1017 }
1018
1019 if (httpResponseCode == 101) {
1020 // 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream
1021 LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this));
1022 return NS_ERROR_ILLEGAL_VALUE;
1023 }
1024
1025 if (aHeadersIn.Length() && aHeadersOut.Length()) {
1026 Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
1027 uint32_t ratio =
1028 aHeadersIn.Length() * 100 / aHeadersOut.Length();
1029 Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
1030 }
1031
1032 // The decoding went ok. Now we can customize and clean up.
1033
1034 aHeadersIn.Truncate();
1035 aHeadersOut.Append("X-Firefox-Spdy: h2");
1036 aHeadersOut.Append("\r\n\r\n");
1037 LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
1038 if (mIsTunnel && !mPlainTextTunnel) {
1039 aHeadersOut.Truncate();
1040 LOG(("Http2Stream::ConvertHeaders %p 0x%X headers removed for tunnel\n",
1041 this, mStreamID));
1042 }
1043 return NS_OK;
1044 }
1045
1046 // ConvertPushHeaders is used to convert the pushed request headers
1047 // into HTTP/1 format and report some telemetry
1048 nsresult
ConvertPushHeaders(Http2Decompressor * decompressor,nsACString & aHeadersIn,nsACString & aHeadersOut)1049 Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
1050 nsACString &aHeadersIn,
1051 nsACString &aHeadersOut)
1052 {
1053 aHeadersOut.Truncate();
1054 aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
1055 nsresult rv =
1056 decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
1057 aHeadersIn.Length(),
1058 aHeadersOut, true);
1059 if (NS_FAILED(rv)) {
1060 LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this));
1061 return rv;
1062 }
1063
1064 nsCString method;
1065 decompressor->GetHost(mHeaderHost);
1066 decompressor->GetScheme(mHeaderScheme);
1067 decompressor->GetPath(mHeaderPath);
1068
1069 if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) {
1070 LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required "
1071 "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(),
1072 mHeaderPath.get()));
1073 return NS_ERROR_ILLEGAL_VALUE;
1074 }
1075
1076 decompressor->GetMethod(method);
1077 if (!method.EqualsLiteral("GET")) {
1078 LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n",
1079 this, method.get()));
1080 return NS_ERROR_NOT_IMPLEMENTED;
1081 }
1082
1083 aHeadersIn.Truncate();
1084 LOG (("id 0x%X decoded push headers %s %s %s are:\n%s", mStreamID,
1085 mHeaderScheme.get(), mHeaderHost.get(), mHeaderPath.get(),
1086 aHeadersOut.BeginReading()));
1087 return NS_OK;
1088 }
1089
1090 void
Close(nsresult reason)1091 Http2Stream::Close(nsresult reason)
1092 {
1093 // In case we are connected to a push, make sure the push knows we are closed,
1094 // so it doesn't try to give us any more DATA that comes on it after our close.
1095 ClearPushSource();
1096
1097 mTransaction->Close(reason);
1098 }
1099
1100 void
SetResponseIsComplete()1101 Http2Stream::SetResponseIsComplete()
1102 {
1103 nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1104 if (trans) {
1105 trans->SetResponseIsComplete();
1106 }
1107 }
1108
1109 void
SetAllHeadersReceived()1110 Http2Stream::SetAllHeadersReceived()
1111 {
1112 if (mAllHeadersReceived) {
1113 return;
1114 }
1115
1116 if (mState == RESERVED_BY_REMOTE) {
1117 // pushed streams needs to wait until headers have
1118 // arrived to open up their window
1119 LOG3(("Http2Stream::SetAllHeadersReceived %p state OPEN from reserved\n", this));
1120 mState = OPEN;
1121 AdjustInitialWindow();
1122 }
1123
1124 mAllHeadersReceived = 1;
1125 if (mIsTunnel) {
1126 MapStreamToHttpConnection();
1127 ClearTransactionsBlockedOnTunnel();
1128 }
1129 return;
1130 }
1131
1132 bool
AllowFlowControlledWrite()1133 Http2Stream::AllowFlowControlledWrite()
1134 {
1135 return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
1136 }
1137
1138 void
UpdateServerReceiveWindow(int32_t delta)1139 Http2Stream::UpdateServerReceiveWindow(int32_t delta)
1140 {
1141 mServerReceiveWindow += delta;
1142
1143 if (mBlockedOnRwin && AllowFlowControlledWrite()) {
1144 LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
1145 "Open stream window\n", this, mStreamID));
1146 mSession->TransactionHasDataToWrite(this); }
1147 }
1148
1149 void
SetPriority(uint32_t newPriority)1150 Http2Stream::SetPriority(uint32_t newPriority)
1151 {
1152 int32_t httpPriority = static_cast<int32_t>(newPriority);
1153 if (httpPriority > kWorstPriority) {
1154 httpPriority = kWorstPriority;
1155 } else if (httpPriority < kBestPriority) {
1156 httpPriority = kBestPriority;
1157 }
1158 mPriority = static_cast<uint32_t>(httpPriority);
1159 mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
1160 (httpPriority - kNormalPriority);
1161
1162 mPriorityDependency = 0; // maybe adjusted later
1163 }
1164
1165 void
SetPriorityDependency(uint32_t newDependency,uint8_t newWeight,bool exclusive)1166 Http2Stream::SetPriorityDependency(uint32_t newDependency, uint8_t newWeight,
1167 bool exclusive)
1168 {
1169 // undefined what it means when the server sends a priority frame. ignore it.
1170 LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X "
1171 "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight,
1172 exclusive));
1173 }
1174
1175 void
UpdatePriorityDependency()1176 Http2Stream::UpdatePriorityDependency()
1177 {
1178 if (!mSession->UseH2Deps()) {
1179 return;
1180 }
1181
1182 nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1183 if (!trans) {
1184 return;
1185 }
1186
1187 // we create 5 fake dependency streams per session,
1188 // these streams are never opened with HEADERS. our first opened stream is 0xd
1189 // 3 depends 0, weight 200, leader class (kLeaderGroupID)
1190 // 5 depends 0, weight 100, other (kOtherGroupID)
1191 // 7 depends 0, weight 0, background (kBackgroundGroupID)
1192 // 9 depends 7, weight 0, speculative (kSpeculativeGroupID)
1193 // b depends 3, weight 0, follower class (kFollowerGroupID)
1194 //
1195 // streams for leaders (html, js, css) depend on 3
1196 // streams for folowers (images) depend on b
1197 // default streams (xhr, async js) depend on 5
1198 // explicit bg streams (beacon, etc..) depend on 7
1199 // spculative bg streams depend on 9
1200
1201 uint32_t classFlags = trans->ClassOfService();
1202
1203 if (classFlags & nsIClassOfService::Leader) {
1204 mPriorityDependency = Http2Session::kLeaderGroupID;
1205 } else if (classFlags & nsIClassOfService::Follower) {
1206 mPriorityDependency = Http2Session::kFollowerGroupID;
1207 } else if (classFlags & nsIClassOfService::Speculative) {
1208 mPriorityDependency = Http2Session::kSpeculativeGroupID;
1209 } else if (classFlags & nsIClassOfService::Background) {
1210 mPriorityDependency = Http2Session::kBackgroundGroupID;
1211 } else if (classFlags & nsIClassOfService::Unblocked) {
1212 mPriorityDependency = Http2Session::kOtherGroupID;
1213 } else {
1214 mPriorityDependency = Http2Session::kFollowerGroupID; // unmarked followers
1215 }
1216
1217 LOG3(("Http2Stream::UpdatePriorityDependency %p "
1218 "classFlags %X depends on stream 0x%X\n",
1219 this, classFlags, mPriorityDependency));
1220 }
1221
1222 void
SetRecvdFin(bool aStatus)1223 Http2Stream::SetRecvdFin(bool aStatus)
1224 {
1225 mRecvdFin = aStatus ? 1 : 0;
1226 if (!aStatus)
1227 return;
1228
1229 if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
1230 mState = CLOSED_BY_REMOTE;
1231 } else if (mState == CLOSED_BY_LOCAL) {
1232 mState = CLOSED;
1233 }
1234 }
1235
1236 void
SetSentFin(bool aStatus)1237 Http2Stream::SetSentFin(bool aStatus)
1238 {
1239 mSentFin = aStatus ? 1 : 0;
1240 if (!aStatus)
1241 return;
1242
1243 if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
1244 mState = CLOSED_BY_LOCAL;
1245 } else if (mState == CLOSED_BY_REMOTE) {
1246 mState = CLOSED;
1247 }
1248 }
1249
1250 void
SetRecvdReset(bool aStatus)1251 Http2Stream::SetRecvdReset(bool aStatus)
1252 {
1253 mRecvdReset = aStatus ? 1 : 0;
1254 if (!aStatus)
1255 return;
1256 mState = CLOSED;
1257 }
1258
1259 void
SetSentReset(bool aStatus)1260 Http2Stream::SetSentReset(bool aStatus)
1261 {
1262 mSentReset = aStatus ? 1 : 0;
1263 if (!aStatus)
1264 return;
1265 mState = CLOSED;
1266 }
1267
1268 //-----------------------------------------------------------------------------
1269 // nsAHttpSegmentReader
1270 //-----------------------------------------------------------------------------
1271
1272 nsresult
OnReadSegment(const char * buf,uint32_t count,uint32_t * countRead)1273 Http2Stream::OnReadSegment(const char *buf,
1274 uint32_t count,
1275 uint32_t *countRead)
1276 {
1277 LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x",
1278 this, count, mUpstreamState));
1279
1280 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1281 MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
1282
1283 nsresult rv = NS_ERROR_UNEXPECTED;
1284 uint32_t dataLength;
1285
1286 switch (mUpstreamState) {
1287 case GENERATING_HEADERS:
1288 // The buffer is the HTTP request stream, including at least part of the
1289 // HTTP request header. This state's job is to build a HEADERS frame
1290 // from the header information. count is the number of http bytes available
1291 // (which may include more than the header), and in countRead we return
1292 // the number of those bytes that we consume (i.e. the portion that are
1293 // header bytes)
1294
1295 if (!mRequestHeadersDone) {
1296 if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) {
1297 return rv;
1298 }
1299 }
1300
1301 if (mRequestHeadersDone && !mOpenGenerated) {
1302 if (!mSession->TryToActivate(this)) {
1303 LOG3(("Http2Stream::OnReadSegment %p cannot activate now. queued.\n", this));
1304 return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
1305 }
1306 if (NS_FAILED(rv = GenerateOpen())) {
1307 return rv;
1308 }
1309 }
1310
1311 LOG3(("ParseHttpRequestHeaders %p used %d of %d. "
1312 "requestheadersdone = %d mOpenGenerated = %d\n",
1313 this, *countRead, count, mRequestHeadersDone, mOpenGenerated));
1314 if (mOpenGenerated) {
1315 SetHTTPState(OPEN);
1316 AdjustInitialWindow();
1317 // This version of TransmitFrame cannot block
1318 rv = TransmitFrame(nullptr, nullptr, true);
1319 ChangeState(GENERATING_BODY);
1320 break;
1321 }
1322 MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
1323 break;
1324
1325 case GENERATING_BODY:
1326 // if there is session flow control and either the stream window is active and
1327 // exhaused or the session window is exhausted then suspend
1328 if (!AllowFlowControlledWrite()) {
1329 *countRead = 0;
1330 LOG3(("Http2Stream this=%p, id 0x%X request body suspended because "
1331 "remote window is stream=%ld session=%ld.\n", this, mStreamID,
1332 mServerReceiveWindow, mSession->ServerSessionWindow()));
1333 mBlockedOnRwin = true;
1334 return NS_BASE_STREAM_WOULD_BLOCK;
1335 }
1336 mBlockedOnRwin = false;
1337
1338 // The chunk is the smallest of: availableData, configured chunkSize,
1339 // stream window, session window, or 14 bit framing limit.
1340 // Its amazing we send anything at all.
1341 dataLength = std::min(count, mChunkSize);
1342
1343 if (dataLength > Http2Session::kMaxFrameData)
1344 dataLength = Http2Session::kMaxFrameData;
1345
1346 if (dataLength > mSession->ServerSessionWindow())
1347 dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
1348
1349 if (dataLength > mServerReceiveWindow)
1350 dataLength = static_cast<uint32_t>(mServerReceiveWindow);
1351
1352 LOG3(("Http2Stream this=%p id 0x%X send calculation "
1353 "avail=%d chunksize=%d stream window=%" PRId64 " session window=%" PRId64 " "
1354 "max frame=%d USING=%u\n", this, mStreamID,
1355 count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(),
1356 Http2Session::kMaxFrameData, dataLength));
1357
1358 mSession->DecrementServerSessionWindow(dataLength);
1359 mServerReceiveWindow -= dataLength;
1360
1361 LOG3(("Http2Stream %p id 0x%x request len remaining %" PRId64 ", "
1362 "count avail %u, chunk used %u",
1363 this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
1364 if (!dataLength && mRequestBodyLenRemaining) {
1365 return NS_BASE_STREAM_WOULD_BLOCK;
1366 }
1367 if (dataLength > mRequestBodyLenRemaining) {
1368 return NS_ERROR_UNEXPECTED;
1369 }
1370 mRequestBodyLenRemaining -= dataLength;
1371 GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
1372 ChangeState(SENDING_BODY);
1373 MOZ_FALLTHROUGH;
1374
1375 case SENDING_BODY:
1376 MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
1377 rv = TransmitFrame(buf, countRead, false);
1378 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
1379 "Transmit Frame should be all or nothing");
1380
1381 LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
1382 "Header is %d Body is %d.",
1383 rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
1384
1385 // normalize a partial write with a WOULD_BLOCK into just a partial write
1386 // as some code will take WOULD_BLOCK to mean an error with nothing
1387 // written (e.g. nsHttpTransaction::ReadRequestSegment()
1388 if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
1389 rv = NS_OK;
1390
1391 // If that frame was all sent, look for another one
1392 if (!mTxInlineFrameUsed)
1393 ChangeState(GENERATING_BODY);
1394 break;
1395
1396 case SENDING_FIN_STREAM:
1397 MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
1398 break;
1399
1400 case UPSTREAM_COMPLETE:
1401 MOZ_ASSERT(mPushSource);
1402 rv = TransmitFrame(nullptr, nullptr, true);
1403 break;
1404
1405 default:
1406 MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
1407 break;
1408 }
1409
1410 return rv;
1411 }
1412
1413 //-----------------------------------------------------------------------------
1414 // nsAHttpSegmentWriter
1415 //-----------------------------------------------------------------------------
1416
1417 nsresult
OnWriteSegment(char * buf,uint32_t count,uint32_t * countWritten)1418 Http2Stream::OnWriteSegment(char *buf,
1419 uint32_t count,
1420 uint32_t *countWritten)
1421 {
1422 LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
1423 this, count, mUpstreamState, mStreamID));
1424
1425 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1426 MOZ_ASSERT(mSegmentWriter);
1427
1428 if (mPushSource) {
1429 nsresult rv;
1430 rv = mPushSource->GetBufferedData(buf, count, countWritten);
1431 if (NS_FAILED(rv))
1432 return rv;
1433
1434 mSession->ConnectPushedStream(this);
1435 return NS_OK;
1436 }
1437
1438 // sometimes we have read data from the network and stored it in a pipe
1439 // so that other streams can proceed when the gecko caller is not processing
1440 // data events fast enough and flow control hasn't caught up yet. This
1441 // gets the stored data out of that pipe
1442 if (!mBypassInputBuffer && mSimpleBuffer.Available()) {
1443 *countWritten = mSimpleBuffer.Read(buf, count);
1444 MOZ_ASSERT(*countWritten);
1445 LOG3(("Http2Stream::OnWriteSegment read from flow control buffer %p %x %d\n",
1446 this, mStreamID, *countWritten));
1447 return NS_OK;
1448 }
1449
1450 // read from the network
1451 return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
1452 }
1453
1454 /// connect tunnels
1455
1456 void
ClearTransactionsBlockedOnTunnel()1457 Http2Stream::ClearTransactionsBlockedOnTunnel()
1458 {
1459 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1460
1461 if (!mIsTunnel) {
1462 return;
1463 }
1464 gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
1465 }
1466
1467 void
MapStreamToPlainText()1468 Http2Stream::MapStreamToPlainText()
1469 {
1470 RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
1471 MOZ_ASSERT(qiTrans);
1472 mPlainTextTunnel = true;
1473 qiTrans->ForcePlainText();
1474 }
1475
1476 void
MapStreamToHttpConnection()1477 Http2Stream::MapStreamToHttpConnection()
1478 {
1479 RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
1480 MOZ_ASSERT(qiTrans);
1481 qiTrans->MapStreamToHttpConnection(mSocketTransport,
1482 mTransaction->ConnectionInfo());
1483 }
1484
1485 } // namespace net
1486 } // namespace mozilla
1487