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