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