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 "Http2Session.h"
19 #include "Http2Stream.h"
20 #include "Http2Push.h"
21 
22 #include "mozilla/EndianUtils.h"
23 #include "mozilla/Telemetry.h"
24 #include "mozilla/Preferences.h"
25 #include "nsHttp.h"
26 #include "nsHttpHandler.h"
27 #include "nsHttpConnection.h"
28 #include "nsIRequestContext.h"
29 #include "nsISSLSocketControl.h"
30 #include "nsISSLStatus.h"
31 #include "nsISSLStatusProvider.h"
32 #include "nsISupportsPriority.h"
33 #include "nsStandardURL.h"
34 #include "nsURLHelper.h"
35 #include "prnetdb.h"
36 #include "sslt.h"
37 #include "mozilla/Sprintf.h"
38 #include "nsSocketTransportService2.h"
39 #include "nsNetUtil.h"
40 #include "nsICacheEntry.h"
41 #include "nsICacheStorageService.h"
42 #include "nsICacheStorage.h"
43 #include "CacheControlParser.h"
44 #include "LoadContextInfo.h"
45 #include "TCPFastOpenLayer.h"
46 
47 namespace mozilla {
48 namespace net {
49 
50 // Http2Session has multiple inheritance of things that implement nsISupports
51 NS_IMPL_ADDREF(Http2Session)
52 NS_IMPL_RELEASE(Http2Session)
53 NS_INTERFACE_MAP_BEGIN(Http2Session)
54   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
55 NS_INTERFACE_MAP_END
56 
57 // "magic" refers to the string that preceeds HTTP/2 on the wire
58 // to help find any intermediaries speaking an older version of HTTP
59 const uint8_t Http2Session::kMagicHello[] = {
60     0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,
61     0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
62 
63 #define RETURN_SESSION_ERROR(o, x) \
64   do {                             \
65     (o)->mGoAwayReason = (x);      \
66     return NS_ERROR_ILLEGAL_VALUE; \
67   } while (0)
68 
Http2Session(nsISocketTransport * aSocketTransport,uint32_t version,bool attemptingEarlyData)69 Http2Session::Http2Session(nsISocketTransport *aSocketTransport,
70                            uint32_t version, bool attemptingEarlyData)
71     : mSocketTransport(aSocketTransport),
72       mSegmentReader(nullptr),
73       mSegmentWriter(nullptr),
74       mNextStreamID(3)  // 1 is reserved for Updgrade handshakes
75       ,
76       mLastPushedID(0),
77       mConcurrentHighWater(0),
78       mDownstreamState(BUFFERING_OPENING_SETTINGS),
79       mInputFrameBufferSize(kDefaultBufferSize),
80       mInputFrameBufferUsed(0),
81       mInputFrameDataSize(0),
82       mInputFrameDataRead(0),
83       mInputFrameFinal(false),
84       mInputFrameType(0),
85       mInputFrameFlags(0),
86       mInputFrameID(0),
87       mPaddingLength(0),
88       mInputFrameDataStream(nullptr),
89       mNeedsCleanup(nullptr),
90       mDownstreamRstReason(NO_HTTP_ERROR),
91       mExpectedHeaderID(0),
92       mExpectedPushPromiseID(0),
93       mContinuedPromiseStream(0),
94       mFlatHTTPResponseHeadersOut(0),
95       mShouldGoAway(false),
96       mClosed(false),
97       mCleanShutdown(false),
98       mReceivedSettings(false),
99       mTLSProfileConfirmed(false),
100       mGoAwayReason(NO_HTTP_ERROR),
101       mClientGoAwayReason(UNASSIGNED),
102       mPeerGoAwayReason(UNASSIGNED),
103       mGoAwayID(0),
104       mOutgoingGoAwayID(0),
105       mConcurrent(0),
106       mServerPushedResources(0),
107       mServerInitialStreamWindow(kDefaultRwin),
108       mLocalSessionWindow(kDefaultRwin),
109       mServerSessionWindow(kDefaultRwin),
110       mInitialRwin(ASpdySession::kInitialRwin),
111       mOutputQueueSize(kDefaultQueueSize),
112       mOutputQueueUsed(0),
113       mOutputQueueSent(0),
114       mLastReadEpoch(PR_IntervalNow()),
115       mPingSentEpoch(0),
116       mPreviousUsed(false),
117       mWaitingForSettingsAck(false),
118       mGoAwayOnPush(false),
119       mUseH2Deps(false),
120       mAttemptingEarlyData(attemptingEarlyData),
121       mOriginFrameActivated(false),
122       mTlsHandshakeFinished(false),
123       mCheckNetworkStallsWithTFO(false),
124       mLastRequestBytesSentTime(0) {
125   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
126 
127   static uint64_t sSerial;
128   mSerial = ++sSerial;
129 
130   LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial));
131 
132   mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
133   mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
134   mDecompressBuffer.SetCapacity(kDefaultBufferSize);
135 
136   mPushAllowance = gHttpHandler->SpdyPushAllowance();
137   mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
138   mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
139   mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
140   SendHello();
141 
142   mLastDataReadEpoch = mLastReadEpoch;
143 
144   mPingThreshold = gHttpHandler->SpdyPingThreshold();
145   mPreviousPingThreshold = mPingThreshold;
146   mCurrentForegroundTabOuterContentWindowId =
147       gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();
148 }
149 
Shutdown()150 void Http2Session::Shutdown() {
151   for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
152     nsAutoPtr<Http2Stream> &stream = iter.Data();
153 
154     // On a clean server hangup the server sets the GoAwayID to be the ID of
155     // the last transaction it processed. If the ID of stream in the
156     // local stream is greater than that it can safely be restarted because the
157     // server guarantees it was not partially processed. Streams that have not
158     // registered an ID haven't actually been sent yet so they can always be
159     // restarted.
160     if (mCleanShutdown &&
161         (stream->StreamID() > mGoAwayID || !stream->HasRegisteredID())) {
162       CloseStream(stream, NS_ERROR_NET_RESET);  // can be restarted
163     } else if (stream->RecvdData()) {
164       CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
165     } else if (mGoAwayReason == INADEQUATE_SECURITY) {
166       CloseStream(stream, NS_ERROR_NET_INADEQUATE_SECURITY);
167     } else {
168       CloseStream(stream, NS_ERROR_ABORT);
169     }
170   }
171 }
172 
~Http2Session()173 Http2Session::~Http2Session() {
174   LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", this,
175         mDownstreamState));
176 
177   Shutdown();
178 
179   Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
180   Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN,
181                         (mNextStreamID - 1) / 2);
182   Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
183                         mServerPushedResources);
184   Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
185   Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
186 }
187 
LogIO(Http2Session * self,Http2Stream * stream,const char * label,const char * data,uint32_t datalen)188 void Http2Session::LogIO(Http2Session *self, Http2Stream *stream,
189                          const char *label, const char *data,
190                          uint32_t datalen) {
191   if (!LOG5_ENABLED()) return;
192 
193   LOG5(("Http2Session::LogIO %p stream=%p id=0x%X [%s]", self, stream,
194         stream ? stream->StreamID() : 0, label));
195 
196   // Max line is (16 * 3) + 10(prefix) + newline + null
197   char linebuf[128];
198   uint32_t index;
199   char *line = linebuf;
200 
201   linebuf[127] = 0;
202 
203   for (index = 0; index < datalen; ++index) {
204     if (!(index % 16)) {
205       if (index) {
206         *line = 0;
207         LOG5(("%s", linebuf));
208       }
209       line = linebuf;
210       snprintf(line, 128, "%08X: ", index);
211       line += 10;
212     }
213     snprintf(line, 128 - (line - linebuf), "%02X ",
214              (reinterpret_cast<const uint8_t *>(data))[index]);
215     line += 3;
216   }
217   if (index) {
218     *line = 0;
219     LOG5(("%s", linebuf));
220   }
221 }
222 
223 typedef nsresult (*Http2ControlFx)(Http2Session *self);
224 static Http2ControlFx sControlFunctions[] = {
225     nullptr,  // type 0 data is not a control function
226     Http2Session::RecvHeaders,
227     Http2Session::RecvPriority,
228     Http2Session::RecvRstStream,
229     Http2Session::RecvSettings,
230     Http2Session::RecvPushPromise,
231     Http2Session::RecvPing,
232     Http2Session::RecvGoAway,
233     Http2Session::RecvWindowUpdate,
234     Http2Session::RecvContinuation,
235     Http2Session::RecvAltSvc,  // extension for type 0x0A
236     Http2Session::RecvUnused,  // 0x0B was BLOCKED still radioactive
237     Http2Session::RecvOrigin   // extension for type 0x0C
238 };
239 
RoomForMoreConcurrent()240 bool Http2Session::RoomForMoreConcurrent() {
241   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
242   return (mConcurrent < mMaxConcurrent);
243 }
244 
RoomForMoreStreams()245 bool Http2Session::RoomForMoreStreams() {
246   if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
247     return false;
248 
249   return !mShouldGoAway;
250 }
251 
IdleTime()252 PRIntervalTime Http2Session::IdleTime() {
253   return PR_IntervalNow() - mLastDataReadEpoch;
254 }
255 
ReadTimeoutTick(PRIntervalTime now)256 uint32_t Http2Session::ReadTimeoutTick(PRIntervalTime now) {
257   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
258 
259   LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", this,
260         PR_IntervalToSeconds(now - mLastReadEpoch)));
261 
262   uint32_t nextTick = UINT32_MAX;
263   if (mCheckNetworkStallsWithTFO && mLastRequestBytesSentTime) {
264     PRIntervalTime initialResponseDelta = now - mLastRequestBytesSentTime;
265     if (initialResponseDelta >= gHttpHandler->FastOpenStallsTimeout()) {
266       gHttpHandler->IncrementFastOpenStallsCounter();
267       mCheckNetworkStallsWithTFO = false;
268     } else {
269       nextTick = PR_IntervalToSeconds(gHttpHandler->FastOpenStallsTimeout()) -
270                  PR_IntervalToSeconds(initialResponseDelta);
271     }
272   }
273   if (!mPingThreshold) return nextTick;
274 
275   if ((now - mLastReadEpoch) < mPingThreshold) {
276     // recent activity means ping is not an issue
277     if (mPingSentEpoch) {
278       mPingSentEpoch = 0;
279       if (mPreviousUsed) {
280         // restore the former value
281         mPingThreshold = mPreviousPingThreshold;
282         mPreviousUsed = false;
283       }
284     }
285 
286     return std::min(nextTick, PR_IntervalToSeconds(mPingThreshold) -
287                                   PR_IntervalToSeconds(now - mLastReadEpoch));
288   }
289 
290   if (mPingSentEpoch) {
291     LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n", this));
292     if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
293       LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
294       mPingSentEpoch = 0;
295       Close(NS_ERROR_NET_TIMEOUT);
296       return UINT32_MAX;
297     }
298     return 1;  // run the tick aggressively while ping is outstanding
299   }
300 
301   LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
302 
303   mPingSentEpoch = PR_IntervalNow();
304   if (!mPingSentEpoch) {
305     mPingSentEpoch = 1;  // avoid the 0 sentinel value
306   }
307   GeneratePing(false);
308   Unused << ResumeRecv();  // read the ping reply
309 
310   // Check for orphaned push streams. This looks expensive, but generally the
311   // list is empty.
312   Http2PushedStream *deleteMe;
313   TimeStamp timestampNow;
314   do {
315     deleteMe = nullptr;
316 
317     for (uint32_t index = mPushedStreams.Length(); index > 0; --index) {
318       Http2PushedStream *pushedStream = mPushedStreams[index - 1];
319 
320       if (timestampNow.IsNull())
321         timestampNow = TimeStamp::Now();  // lazy initializer
322 
323       // if stream finished, but is not connected, and its been like that for
324       // long then cleanup the stream.
325       if (pushedStream->IsOrphaned(timestampNow)) {
326         LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n", this,
327               pushedStream->StreamID()));
328         deleteMe = pushedStream;
329         break;  // don't CleanupStream() while iterating this vector
330       }
331     }
332     if (deleteMe) CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
333 
334   } while (deleteMe);
335 
336   return 1;  // run the tick aggressively while ping is outstanding
337 }
338 
RegisterStreamID(Http2Stream * stream,uint32_t aNewID)339 uint32_t Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID) {
340   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
341   MOZ_ASSERT(mNextStreamID < 0xfffffff0,
342              "should have stopped admitting streams");
343   MOZ_ASSERT(!(aNewID & 1),
344              "0 for autoassign pull, otherwise explicit even push assignment");
345 
346   if (!aNewID) {
347     // auto generate a new pull stream ID
348     aNewID = mNextStreamID;
349     MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
350     mNextStreamID += 2;
351   }
352 
353   LOG3(
354       ("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
355        "concurrent=%d",
356        this, stream, aNewID, mConcurrent));
357 
358   // We've used up plenty of ID's on this session. Start
359   // moving to a new one before there is a crunch involving
360   // server push streams or concurrent non-registered submits
361   if (aNewID >= kMaxStreamID) mShouldGoAway = true;
362 
363   // integrity check
364   if (mStreamIDHash.Get(aNewID)) {
365     LOG3(("   New ID already present\n"));
366     MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
367     mShouldGoAway = true;
368     return kDeadStreamID;
369   }
370 
371   mStreamIDHash.Put(aNewID, stream);
372 
373   // If TCP fast Open has been used and conection was idle for some time
374   // we will be cautious and watch out for bug 1395494.
375   if (!mCheckNetworkStallsWithTFO && mConnection) {
376     RefPtr<nsHttpConnection> conn = mConnection->HttpConnection();
377     if (conn && (conn->GetFastOpenStatus() == TFO_DATA_SENT) &&
378         gHttpHandler
379             ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() &&
380         IdleTime() >=
381             gHttpHandler
382                 ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds()) {
383       // If a connection was using the TCP FastOpen and it was idle for a
384       // long time we should check for stalls like bug 1395494.
385       mCheckNetworkStallsWithTFO = true;
386       mLastRequestBytesSentTime = PR_IntervalNow();
387     }
388   }
389   return aNewID;
390 }
391 
AddStream(nsAHttpTransaction * aHttpTransaction,int32_t aPriority,bool aUseTunnel,nsIInterfaceRequestor * aCallbacks)392 bool Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
393                              int32_t aPriority, bool aUseTunnel,
394                              nsIInterfaceRequestor *aCallbacks) {
395   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
396 
397   // integrity check
398   if (mStreamTransactionHash.Get(aHttpTransaction)) {
399     LOG3(("   New transaction already present\n"));
400     MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
401     return false;
402   }
403 
404   if (!mConnection) {
405     mConnection = aHttpTransaction->Connection();
406   }
407 
408   if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
409     mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
410     LOG3(("Http2Session::AddStream first session=%p trans=%p ", this,
411           mFirstHttpTransaction.get()));
412   }
413 
414   if (mClosed || mShouldGoAway) {
415     nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
416     if (trans) {
417       RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
418       pushedStreamWrapper = trans->GetPushedStream();
419       if (!pushedStreamWrapper || !pushedStreamWrapper->GetStream()) {
420         LOG3(
421             ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - "
422              "resched.\n", this, aHttpTransaction, trans));
423         aHttpTransaction->SetConnection(nullptr);
424         nsresult rv =
425             gHttpHandler->InitiateTransaction(trans, trans->Priority());
426         if (NS_FAILED(rv)) {
427           LOG3(
428               ("Http2Session::AddStream %p atrans=%p trans=%p failed to "
429                "initiate transaction (%08x).\n",
430                this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
431         }
432         return true;
433       }
434     }
435   }
436 
437   aHttpTransaction->SetConnection(this);
438   aHttpTransaction->OnActivated();
439 
440   if (aUseTunnel) {
441     LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel", this,
442           aHttpTransaction));
443     DispatchOnTunnel(aHttpTransaction, aCallbacks);
444     return true;
445   }
446 
447   Http2Stream *stream =
448       new Http2Stream(aHttpTransaction, this, aPriority,
449                       mCurrentForegroundTabOuterContentWindowId);
450 
451   LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
452         "NextID=0x%X (tentative)",
453         this, stream, mSerial, mNextStreamID));
454 
455   mStreamTransactionHash.Put(aHttpTransaction, stream);
456 
457   mReadyForWrite.Push(stream);
458   SetWriteCallbacks();
459 
460   // Kick off the SYN transmit without waiting for the poll loop
461   // This won't work for the first stream because there is no segment reader
462   // yet.
463   if (mSegmentReader) {
464     uint32_t countRead;
465     Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
466   }
467 
468   if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
469       !aHttpTransaction->IsNullTransaction()) {
470     LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
471           this, aHttpTransaction));
472     DontReuse();
473   }
474 
475   return true;
476 }
477 
QueueStream(Http2Stream * stream)478 void Http2Session::QueueStream(Http2Stream *stream) {
479   // will be removed via processpending or a shutdown path
480   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
481   MOZ_ASSERT(!stream->CountAsActive());
482   MOZ_ASSERT(!stream->Queued());
483 
484   LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream));
485 
486 #ifdef DEBUG
487   int32_t qsize = mQueuedStreams.GetSize();
488   for (int32_t i = 0; i < qsize; i++) {
489     Http2Stream *qStream =
490         static_cast<Http2Stream *>(mQueuedStreams.ObjectAt(i));
491     MOZ_ASSERT(qStream != stream);
492     MOZ_ASSERT(qStream->Queued());
493   }
494 #endif
495 
496   stream->SetQueued(true);
497   mQueuedStreams.Push(stream);
498 }
499 
ProcessPending()500 void Http2Session::ProcessPending() {
501   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
502 
503   Http2Stream *stream;
504   while (RoomForMoreConcurrent() &&
505          (stream = static_cast<Http2Stream *>(mQueuedStreams.PopFront()))) {
506     LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.", this,
507           stream));
508     MOZ_ASSERT(!stream->CountAsActive());
509     MOZ_ASSERT(stream->Queued());
510     stream->SetQueued(false);
511     mReadyForWrite.Push(stream);
512     SetWriteCallbacks();
513   }
514 }
515 
NetworkRead(nsAHttpSegmentWriter * writer,char * buf,uint32_t count,uint32_t * countWritten)516 nsresult Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
517                                    uint32_t count, uint32_t *countWritten) {
518   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
519 
520   if (!count) {
521     *countWritten = 0;
522     return NS_OK;
523   }
524 
525   nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
526   if (NS_SUCCEEDED(rv) && *countWritten > 0) {
527     mLastReadEpoch = PR_IntervalNow();
528     mCheckNetworkStallsWithTFO = false;
529   }
530   return rv;
531 }
532 
SetWriteCallbacks()533 void Http2Session::SetWriteCallbacks() {
534   if (mConnection &&
535       (GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) {
536     Unused << mConnection->ResumeSend();
537   }
538 }
539 
RealignOutputQueue()540 void Http2Session::RealignOutputQueue() {
541   if (mAttemptingEarlyData) {
542     // We can't realign right now, because we may need what's in there if early
543     // data fails.
544     return;
545   }
546 
547   mOutputQueueUsed -= mOutputQueueSent;
548   memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent,
549           mOutputQueueUsed);
550   mOutputQueueSent = 0;
551 }
552 
FlushOutputQueue()553 void Http2Session::FlushOutputQueue() {
554   if (!mSegmentReader || !mOutputQueueUsed) return;
555 
556   nsresult rv;
557   uint32_t countRead;
558   uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
559 
560   if (!avail && mAttemptingEarlyData) {
561     // This is kind of a hack, but there are cases where we'll have already
562     // written the data we want whlie doing early data, but we get called again
563     // with a reader, and we need to avoid calling the reader when there's
564     // nothing for it to read.
565     return;
566   }
567 
568   rv = mSegmentReader->OnReadSegment(
569       mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead);
570   LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d",
571         this, avail, static_cast<uint32_t>(rv), countRead));
572 
573   // Dont worry about errors on write, we will pick this up as a read error too
574   if (NS_FAILED(rv)) return;
575 
576   mOutputQueueSent += countRead;
577 
578   if (mAttemptingEarlyData) {
579     return;
580   }
581 
582   if (countRead == avail) {
583     mOutputQueueUsed = 0;
584     mOutputQueueSent = 0;
585     return;
586   }
587 
588   // If the output queue is close to filling up and we have sent out a good
589   // chunk of data from the beginning then realign it.
590 
591   if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
592       ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
593     RealignOutputQueue();
594   }
595 }
596 
DontReuse()597 void Http2Session::DontReuse() {
598   LOG3(("Http2Session::DontReuse %p\n", this));
599   if (!OnSocketThread()) {
600     LOG3(("Http2Session %p not on socket thread\n", this));
601     nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
602         "Http2Session::DontReuse", this, &Http2Session::DontReuse);
603     gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
604     return;
605   }
606 
607   mShouldGoAway = true;
608   if (!mClosed && !mStreamTransactionHash.Count()) {
609     Close(NS_OK);
610   }
611 }
612 
SpdyVersion()613 uint32_t Http2Session::SpdyVersion() { return HTTP_VERSION_2; }
614 
GetWriteQueueSize()615 uint32_t Http2Session::GetWriteQueueSize() {
616   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
617 
618   return mReadyForWrite.GetSize();
619 }
620 
ChangeDownstreamState(enum internalStateType newState)621 void Http2Session::ChangeDownstreamState(enum internalStateType newState) {
622   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
623 
624   LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X", this,
625         mDownstreamState, newState));
626   mDownstreamState = newState;
627 }
628 
ResetDownstreamState()629 void Http2Session::ResetDownstreamState() {
630   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
631 
632   LOG3(("Http2Session::ResetDownstreamState() %p", this));
633   ChangeDownstreamState(BUFFERING_FRAME_HEADER);
634 
635   if (mInputFrameFinal && mInputFrameDataStream) {
636     mInputFrameFinal = false;
637     LOG3(("  SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
638     mInputFrameDataStream->SetRecvdFin(true);
639     MaybeDecrementConcurrent(mInputFrameDataStream);
640   }
641   mInputFrameFinal = false;
642   mInputFrameBufferUsed = 0;
643   mInputFrameDataStream = nullptr;
644 }
645 
646 // return true if activated (and counted against max)
647 // otherwise return false and queue
TryToActivate(Http2Stream * aStream)648 bool Http2Session::TryToActivate(Http2Stream *aStream) {
649   if (aStream->Queued()) {
650     LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this,
651           aStream));
652     return false;
653   }
654 
655   if (!RoomForMoreConcurrent()) {
656     LOG3(
657         ("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
658          "streams\n",
659          this, aStream));
660     QueueStream(aStream);
661     return false;
662   }
663 
664   LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
665   IncrementConcurrent(aStream);
666   return true;
667 }
668 
IncrementConcurrent(Http2Stream * stream)669 void Http2Session::IncrementConcurrent(Http2Stream *stream) {
670   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
671   MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
672              "Do not activate pushed streams");
673 
674   nsAHttpTransaction *trans = stream->Transaction();
675   if (!trans || !trans->IsNullTransaction() ||
676       trans->QuerySpdyConnectTransaction()) {
677     MOZ_ASSERT(!stream->CountAsActive());
678     stream->SetCountAsActive(true);
679     ++mConcurrent;
680 
681     if (mConcurrent > mConcurrentHighWater) {
682       mConcurrentHighWater = mConcurrent;
683     }
684     LOG3(
685         ("Http2Session::IncrementCounter %p counting stream %p Currently %d "
686          "streams in session, high water mark is %d\n",
687          this, stream, mConcurrent, mConcurrentHighWater));
688   }
689 }
690 
691 // call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
692 // dest must have 9 bytes of allocated space
693 template <typename charType>
CreateFrameHeader(charType dest,uint16_t frameLength,uint8_t frameType,uint8_t frameFlags,uint32_t streamID)694 void Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
695                                      uint8_t frameType, uint8_t frameFlags,
696                                      uint32_t streamID) {
697   MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
698   MOZ_ASSERT(!(streamID & 0x80000000));
699   MOZ_ASSERT(!frameFlags || (frameType != FRAME_TYPE_PRIORITY &&
700                              frameType != FRAME_TYPE_RST_STREAM &&
701                              frameType != FRAME_TYPE_GOAWAY &&
702                              frameType != FRAME_TYPE_WINDOW_UPDATE));
703 
704   dest[0] = 0x00;
705   NetworkEndian::writeUint16(dest + 1, frameLength);
706   dest[3] = frameType;
707   dest[4] = frameFlags;
708   NetworkEndian::writeUint32(dest + 5, streamID);
709 }
710 
EnsureOutputBuffer(uint32_t spaceNeeded)711 char *Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded) {
712   // this is an infallible allocation (if an allocation is
713   // needed, which is probably isn't)
714   EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
715                mOutputQueueUsed, mOutputQueueSize);
716   return mOutputQueueBuffer.get() + mOutputQueueUsed;
717 }
718 
719 template void Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength,
720                                               uint8_t frameType,
721                                               uint8_t frameFlags,
722                                               uint32_t streamID);
723 
724 template void Http2Session::CreateFrameHeader(uint8_t *dest,
725                                               uint16_t frameLength,
726                                               uint8_t frameType,
727                                               uint8_t frameFlags,
728                                               uint32_t streamID);
729 
MaybeDecrementConcurrent(Http2Stream * aStream)730 void Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream) {
731   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
732   LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n", this,
733         aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
734 
735   if (!aStream->CountAsActive()) return;
736 
737   MOZ_ASSERT(mConcurrent);
738   aStream->SetCountAsActive(false);
739   --mConcurrent;
740   ProcessPending();
741 }
742 
743 // Need to decompress some data in order to keep the compression
744 // context correct, but we really don't care what the result is
UncompressAndDiscard(bool isPush)745 nsresult Http2Session::UncompressAndDiscard(bool isPush) {
746   nsresult rv;
747   nsAutoCString trash;
748 
749   rv = mDecompressor.DecodeHeaderBlock(
750       reinterpret_cast<const uint8_t *>(mDecompressBuffer.BeginReading()),
751       mDecompressBuffer.Length(), trash, isPush);
752   mDecompressBuffer.Truncate();
753   if (NS_FAILED(rv)) {
754     LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n", this));
755     mGoAwayReason = COMPRESSION_ERROR;
756     return rv;
757   }
758   return NS_OK;
759 }
760 
GeneratePing(bool isAck)761 void Http2Session::GeneratePing(bool isAck) {
762   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
763   LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
764 
765   char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
766   mOutputQueueUsed += kFrameHeaderBytes + 8;
767 
768   if (isAck) {
769     CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
770     memcpy(packet + kFrameHeaderBytes,
771            mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
772   } else {
773     CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
774     memset(packet + kFrameHeaderBytes, 0, 8);
775   }
776 
777   LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
778   FlushOutputQueue();
779 }
780 
GenerateSettingsAck()781 void Http2Session::GenerateSettingsAck() {
782   // need to generate ack of this settings frame
783   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
784   LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
785 
786   char *packet = EnsureOutputBuffer(kFrameHeaderBytes);
787   mOutputQueueUsed += kFrameHeaderBytes;
788   CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
789   LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
790   FlushOutputQueue();
791 }
792 
GeneratePriority(uint32_t aID,uint8_t aPriorityWeight)793 void Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight) {
794   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
795   LOG3(("Http2Session::GeneratePriority %p %X %X\n", this, aID,
796         aPriorityWeight));
797 
798   char *packet = CreatePriorityFrame(aID, 0, aPriorityWeight);
799 
800   LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
801   FlushOutputQueue();
802 }
803 
GenerateRstStream(uint32_t aStatusCode,uint32_t aID)804 void Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) {
805   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
806 
807   // make sure we don't do this twice for the same stream (at least if we
808   // have a stream entry for it)
809   Http2Stream *stream = mStreamIDHash.Get(aID);
810   if (stream) {
811     if (stream->SentReset()) return;
812     stream->SetSentReset(true);
813   }
814 
815   LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
816 
817   uint32_t frameSize = kFrameHeaderBytes + 4;
818   char *packet = EnsureOutputBuffer(frameSize);
819   mOutputQueueUsed += frameSize;
820   CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
821 
822   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);
823 
824   LogIO(this, nullptr, "Generate Reset", packet, frameSize);
825   FlushOutputQueue();
826 }
827 
GenerateGoAway(uint32_t aStatusCode)828 void Http2Session::GenerateGoAway(uint32_t aStatusCode) {
829   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
830   LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
831 
832   mClientGoAwayReason = aStatusCode;
833   uint32_t frameSize = kFrameHeaderBytes + 8;
834   char *packet = EnsureOutputBuffer(frameSize);
835   mOutputQueueUsed += frameSize;
836 
837   CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
838 
839   // last-good-stream-id are bytes 9-12 reflecting pushes
840   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);
841 
842   // bytes 13-16 are the status code.
843   NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);
844 
845   LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
846   FlushOutputQueue();
847 }
848 
849 // The Hello is comprised of
850 // 1] 24 octets of magic, which are designed to
851 // flush out silent but broken intermediaries
852 // 2] a settings frame which sets a small flow control window for pushes
853 // 3] a window update frame which creates a large session flow control window
854 // 4] 6 priority frames for streams which will never be opened with headers
855 //    these streams (3, 5, 7, 9, b, d) build a dependency tree that all other
856 //    streams will be direct leaves of.
SendHello()857 void Http2Session::SendHello() {
858   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
859   LOG3(("Http2Session::SendHello %p\n", this));
860 
861   // sized for magic + 5 settings and a session window update and 6 priority
862   // frames 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window
863   // update, 6 priority frames at 14 (9 + 5) each
864   static const uint32_t maxSettings = 5;
865   static const uint32_t prioritySize =
866       kPriorityGroupCount * (kFrameHeaderBytes + 5);
867   static const uint32_t maxDataLen =
868       24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
869   char *packet = EnsureOutputBuffer(maxDataLen);
870   memcpy(packet, kMagicHello, 24);
871   mOutputQueueUsed += 24;
872   LogIO(this, nullptr, "Magic Connection Header", packet, 24);
873 
874   packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
875   memset(packet, 0, maxDataLen - 24);
876 
877   // frame header will be filled in after we know how long the frame is
878   uint8_t numberOfEntries = 0;
879 
880   // entries need to be listed in order by ID
881   // 1st entry is bytes 9 to 14
882   // 2nd entry is bytes 15 to 20
883   // 3rd entry is bytes 21 to 26
884   // 4th entry is bytes 27 to 32
885   // 5th entry is bytes 33 to 38
886 
887   // Let the other endpoint know about our default HPACK decompress table size
888   uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
889   mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
890   NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
891                              SETTINGS_TYPE_HEADER_TABLE_SIZE);
892   NetworkEndian::writeUint32(
893       packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2,
894       maxHpackBufferSize);
895   numberOfEntries++;
896 
897   if (!gHttpHandler->AllowPush()) {
898     // If we don't support push then set MAX_CONCURRENT to 0 and also
899     // set ENABLE_PUSH to 0
900     NetworkEndian::writeUint16(
901         packet + kFrameHeaderBytes + (6 * numberOfEntries),
902         SETTINGS_TYPE_ENABLE_PUSH);
903     // The value portion of the setting pair is already initialized to 0
904     numberOfEntries++;
905 
906     NetworkEndian::writeUint16(
907         packet + kFrameHeaderBytes + (6 * numberOfEntries),
908         SETTINGS_TYPE_MAX_CONCURRENT);
909     // The value portion of the setting pair is already initialized to 0
910     numberOfEntries++;
911 
912     mWaitingForSettingsAck = true;
913   }
914 
915   // Advertise the Push RWIN for the session, and on each new pull stream
916   // send a window update
917   NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
918                              SETTINGS_TYPE_INITIAL_WINDOW);
919   NetworkEndian::writeUint32(
920       packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
921   numberOfEntries++;
922 
923   // Make sure the other endpoint knows that we're sticking to the default max
924   // frame size
925   NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
926                              SETTINGS_TYPE_MAX_FRAME_SIZE);
927   NetworkEndian::writeUint32(
928       packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
929   numberOfEntries++;
930 
931   MOZ_ASSERT(numberOfEntries <= maxSettings);
932   uint32_t dataLen = 6 * numberOfEntries;
933   CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
934   mOutputQueueUsed += kFrameHeaderBytes + dataLen;
935 
936   LogIO(this, nullptr, "Generate Settings", packet,
937         kFrameHeaderBytes + dataLen);
938 
939   // now bump the local session window from 64KB
940   uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
941   if (kDefaultRwin < mInitialRwin) {
942     // send a window update for the session (Stream 0) for something large
943     mLocalSessionWindow = mInitialRwin;
944 
945     packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
946     CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
947     mOutputQueueUsed += kFrameHeaderBytes + 4;
948     NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
949 
950     LOG3(("Session Window increase at start of session %p %u\n", this,
951           sessionWindowBump));
952     LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
953   }
954 
955   if (gHttpHandler->UseH2Deps() &&
956       gHttpHandler->CriticalRequestPrioritization()) {
957     mUseH2Deps = true;
958     MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
959     CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
960     mNextStreamID += 2;
961     MOZ_ASSERT(mNextStreamID == kOtherGroupID);
962     CreatePriorityNode(kOtherGroupID, 0, 100, "other");
963     mNextStreamID += 2;
964     MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
965     CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
966     mNextStreamID += 2;
967     MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
968     CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0,
969                        "speculative");
970     mNextStreamID += 2;
971     MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
972     CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
973     mNextStreamID += 2;
974     MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
975     CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
976     mNextStreamID += 2;
977     // Hey, you! YES YOU! If you add/remove any groups here, you almost
978     // certainly need to change the lookup of the stream/ID hash in
979     // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
980   }
981 
982   FlushOutputQueue();
983 }
984 
SendPriorityFrame(uint32_t streamID,uint32_t dependsOn,uint8_t weight)985 void Http2Session::SendPriorityFrame(uint32_t streamID, uint32_t dependsOn,
986                                      uint8_t weight) {
987   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
988   LOG3(
989       ("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
990        "weight %d\n",
991        this, streamID, dependsOn, weight));
992 
993   char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
994 
995   LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
996   FlushOutputQueue();
997 }
998 
CreatePriorityFrame(uint32_t streamID,uint32_t dependsOn,uint8_t weight)999 char *Http2Session::CreatePriorityFrame(uint32_t streamID, uint32_t dependsOn,
1000                                         uint8_t weight) {
1001   MOZ_ASSERT(streamID, "Priority on stream 0");
1002   char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
1003   CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
1004   mOutputQueueUsed += kFrameHeaderBytes + 5;
1005   NetworkEndian::writeUint32(packet + kFrameHeaderBytes,
1006                              dependsOn);   // depends on
1007   packet[kFrameHeaderBytes + 4] = weight;  // weight
1008   return packet;
1009 }
1010 
CreatePriorityNode(uint32_t streamID,uint32_t dependsOn,uint8_t weight,const char * label)1011 void Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn,
1012                                       uint8_t weight, const char *label) {
1013   char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
1014 
1015   LOG3(
1016       ("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
1017        "weight %d for %s class\n",
1018        this, streamID, dependsOn, weight, label));
1019   LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
1020 }
1021 
1022 // perform a bunch of integrity checks on the stream.
1023 // returns true if passed, false (plus LOG and ABORT) if failed.
VerifyStream(Http2Stream * aStream,uint32_t aOptionalID=0)1024 bool Http2Session::VerifyStream(Http2Stream *aStream,
1025                                 uint32_t aOptionalID = 0) {
1026   // This is annoying, but at least it is O(1)
1027   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1028 
1029 #ifndef DEBUG
1030   // Only do the real verification in debug builds
1031   return true;
1032 #else   // DEBUG
1033 
1034   if (!aStream) return true;
1035 
1036   uint32_t test = 0;
1037 
1038   do {
1039     if (aStream->StreamID() == kDeadStreamID) break;
1040 
1041     nsAHttpTransaction *trans = aStream->Transaction();
1042 
1043     test++;
1044     if (!trans) break;
1045 
1046     test++;
1047     if (mStreamTransactionHash.Get(trans) != aStream) break;
1048 
1049     if (aStream->StreamID()) {
1050       Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID());
1051 
1052       test++;
1053       if (idStream != aStream) break;
1054 
1055       if (aOptionalID) {
1056         test++;
1057         if (idStream->StreamID() != aOptionalID) break;
1058       }
1059     }
1060 
1061     // tests passed
1062     return true;
1063   } while (0);
1064 
1065   LOG3(
1066       ("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
1067        "optionalID=0x%X trans=%p test=%d\n",
1068        this, aStream, aStream->StreamID(), aOptionalID, aStream->Transaction(),
1069        test));
1070 
1071   MOZ_ASSERT(false, "VerifyStream");
1072   return false;
1073 #endif  // DEBUG
1074 }
1075 
CleanupStream(Http2Stream * aStream,nsresult aResult,errorType aResetCode)1076 void Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
1077                                  errorType aResetCode) {
1078   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1079   LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n", this, aStream,
1080         aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult)));
1081   if (!aStream) {
1082     return;
1083   }
1084 
1085   Http2PushedStream *pushSource = aStream->PushSource();
1086   if (pushSource) {
1087     // aStream is a synthetic  attached to an even push
1088     MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
1089     MOZ_ASSERT(!aStream->StreamID());
1090     MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
1091     aStream->ClearPushSource();
1092   }
1093 
1094   if (aStream->DeferCleanup(aResult)) {
1095     LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
1096     return;
1097   }
1098 
1099   if (!VerifyStream(aStream)) {
1100     LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
1101     return;
1102   }
1103 
1104   // don't reset a stream that has recevied a fin or rst
1105   if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
1106       !(mInputFrameFinal &&
1107         (aStream == mInputFrameDataStream))) {  // !(recvdfin with mark pending)
1108     LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n",
1109           aStream->StreamID(), aResetCode));
1110     GenerateRstStream(aResetCode, aStream->StreamID());
1111   }
1112 
1113   CloseStream(aStream, aResult);
1114 
1115   // Remove the stream from the ID hash table and, if an even id, the pushed
1116   // table too.
1117   uint32_t id = aStream->StreamID();
1118   if (id > 0) {
1119     mStreamIDHash.Remove(id);
1120     if (!(id & 1)) {
1121       mPushedStreams.RemoveElement(aStream);
1122       Http2PushedStream *pushStream = static_cast<Http2PushedStream *>(aStream);
1123       nsAutoCString hashKey;
1124       DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
1125       MOZ_ASSERT(rv);
1126       nsIRequestContext *requestContext = aStream->RequestContext();
1127       if (requestContext) {
1128         SpdyPushCache *cache = nullptr;
1129         requestContext->GetSpdyPushCache(&cache);
1130         if (cache) {
1131           // Make sure the id of the stream in the push cache is the same
1132           // as the id of the stream we're cleaning up! See bug 1368080.
1133           Http2PushedStream *trash =
1134               cache->RemovePushedStreamHttp2ByID(hashKey, aStream->StreamID());
1135           LOG3(
1136               ("Http2Session::CleanupStream %p aStream=%p pushStream=%p "
1137                "trash=%p",
1138                this, aStream, pushStream, trash));
1139         }
1140       }
1141     }
1142   }
1143 
1144   RemoveStreamFromQueues(aStream);
1145 
1146   // removing from the stream transaction hash will
1147   // delete the Http2Stream and drop the reference to
1148   // its transaction
1149   mStreamTransactionHash.Remove(aStream->Transaction());
1150 
1151   if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
1152 
1153   if (pushSource) {
1154     pushSource->SetDeferCleanupOnSuccess(false);
1155     CleanupStream(pushSource, aResult, aResetCode);
1156   }
1157 }
1158 
CleanupStream(uint32_t aID,nsresult aResult,errorType aResetCode)1159 void Http2Session::CleanupStream(uint32_t aID, nsresult aResult,
1160                                  errorType aResetCode) {
1161   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1162   Http2Stream *stream = mStreamIDHash.Get(aID);
1163   LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n", this, aID,
1164         stream));
1165   if (!stream) {
1166     return;
1167   }
1168   CleanupStream(stream, aResult, aResetCode);
1169 }
1170 
RemoveStreamFromQueue(Http2Stream * aStream,nsDeque & queue)1171 static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue) {
1172   size_t size = queue.GetSize();
1173   for (size_t count = 0; count < size; ++count) {
1174     Http2Stream *stream = static_cast<Http2Stream *>(queue.PopFront());
1175     if (stream != aStream) queue.Push(stream);
1176   }
1177 }
1178 
RemoveStreamFromQueues(Http2Stream * aStream)1179 void Http2Session::RemoveStreamFromQueues(Http2Stream *aStream) {
1180   RemoveStreamFromQueue(aStream, mReadyForWrite);
1181   RemoveStreamFromQueue(aStream, mQueuedStreams);
1182   RemoveStreamFromQueue(aStream, mPushesReadyForRead);
1183   RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
1184 }
1185 
CloseStream(Http2Stream * aStream,nsresult aResult)1186 void Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult) {
1187   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1188   LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n", this, aStream,
1189         aStream->StreamID(), static_cast<uint32_t>(aResult)));
1190 
1191   MaybeDecrementConcurrent(aStream);
1192 
1193   // Check if partial frame reader
1194   if (aStream == mInputFrameDataStream) {
1195     LOG3(("Stream had active partial read frame on close"));
1196     ChangeDownstreamState(DISCARDING_DATA_FRAME);
1197     mInputFrameDataStream = nullptr;
1198   }
1199 
1200   RemoveStreamFromQueues(aStream);
1201 
1202   if (aStream->IsTunnel()) {
1203     UnRegisterTunnel(aStream);
1204   }
1205 
1206   // Send the stream the close() indication
1207   aStream->Close(aResult);
1208 }
1209 
SetInputFrameDataStream(uint32_t streamID)1210 nsresult Http2Session::SetInputFrameDataStream(uint32_t streamID) {
1211   mInputFrameDataStream = mStreamIDHash.Get(streamID);
1212   if (VerifyStream(mInputFrameDataStream, streamID)) return NS_OK;
1213 
1214   LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
1215         streamID));
1216   mInputFrameDataStream = nullptr;
1217   return NS_ERROR_UNEXPECTED;
1218 }
1219 
ParsePadding(uint8_t & paddingControlBytes,uint16_t & paddingLength)1220 nsresult Http2Session::ParsePadding(uint8_t &paddingControlBytes,
1221                                     uint16_t &paddingLength) {
1222   if (mInputFrameFlags & kFlag_PADDED) {
1223     paddingLength =
1224         *reinterpret_cast<uint8_t *>(&mInputFrameBuffer[kFrameHeaderBytes]);
1225     paddingControlBytes = 1;
1226   } else {
1227     paddingLength = 0;
1228     paddingControlBytes = 0;
1229   }
1230 
1231   if (static_cast<uint32_t>(paddingLength + paddingControlBytes) >
1232       mInputFrameDataSize) {
1233     // This is fatal to the session
1234     LOG3(
1235         ("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
1236          "paddingLength %d > frame size %d\n",
1237          this, mInputFrameID, paddingLength, mInputFrameDataSize));
1238     RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
1239   }
1240 
1241   return NS_OK;
1242 }
1243 
RecvHeaders(Http2Session * self)1244 nsresult Http2Session::RecvHeaders(Http2Session *self) {
1245   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
1246              self->mInputFrameType == FRAME_TYPE_CONTINUATION);
1247 
1248   bool isContinuation = self->mExpectedHeaderID != 0;
1249 
1250   // If this doesn't have END_HEADERS set on it then require the next
1251   // frame to be HEADERS of the same ID
1252   bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
1253 
1254   if (endHeadersFlag)
1255     self->mExpectedHeaderID = 0;
1256   else
1257     self->mExpectedHeaderID = self->mInputFrameID;
1258 
1259   uint32_t priorityLen = 0;
1260   if (self->mInputFrameFlags & kFlag_PRIORITY) {
1261     priorityLen = 5;
1262   }
1263   nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
1264   MOZ_ASSERT(NS_SUCCEEDED(rv));
1265 
1266   // Find out how much padding this frame has, so we can only extract the real
1267   // header data from the frame.
1268   uint16_t paddingLength = 0;
1269   uint8_t paddingControlBytes = 0;
1270 
1271   if (!isContinuation) {
1272     self->mDecompressBuffer.Truncate();
1273     rv = self->ParsePadding(paddingControlBytes, paddingLength);
1274     if (NS_FAILED(rv)) {
1275       return rv;
1276     }
1277   }
1278 
1279   LOG3(
1280       ("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
1281        "end_stream=%d end_headers=%d priority_group=%d "
1282        "paddingLength=%d padded=%d\n",
1283        self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
1284        self->mInputFrameFlags & kFlag_END_STREAM,
1285        self->mInputFrameFlags & kFlag_END_HEADERS,
1286        self->mInputFrameFlags & kFlag_PRIORITY, paddingLength,
1287        self->mInputFrameFlags & kFlag_PADDED));
1288 
1289   if ((paddingControlBytes + priorityLen + paddingLength) >
1290       self->mInputFrameDataSize) {
1291     // This is fatal to the session
1292     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1293   }
1294 
1295   if (!self->mInputFrameDataStream) {
1296     // Cannot find stream. We can continue the session, but we need to
1297     // uncompress the header block to maintain the correct compression context
1298 
1299     LOG3(
1300         ("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
1301          "0x%X failed. NextStreamID = 0x%X\n",
1302          self, self->mInputFrameID, self->mNextStreamID));
1303 
1304     if (self->mInputFrameID >= self->mNextStreamID)
1305       self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
1306 
1307     self->mDecompressBuffer.Append(
1308         &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
1309                                  priorityLen],
1310         self->mInputFrameDataSize - paddingControlBytes - priorityLen -
1311             paddingLength);
1312 
1313     if (self->mInputFrameFlags & kFlag_END_HEADERS) {
1314       rv = self->UncompressAndDiscard(false);
1315       if (NS_FAILED(rv)) {
1316         LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
1317         // this is fatal to the session
1318         self->mGoAwayReason = COMPRESSION_ERROR;
1319         return rv;
1320       }
1321     }
1322 
1323     self->ResetDownstreamState();
1324     return NS_OK;
1325   }
1326 
1327   // make sure this is either the first headers or a trailer
1328   if (self->mInputFrameDataStream->AllHeadersReceived() &&
1329       !(self->mInputFrameFlags & kFlag_END_STREAM)) {
1330     // Any header block after the first that does *not* end the stream is
1331     // illegal.
1332     LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self,
1333           self->mInputFrameID));
1334     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1335   }
1336 
1337   // queue up any compression bytes
1338   self->mDecompressBuffer.Append(
1339       &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
1340                                priorityLen],
1341       self->mInputFrameDataSize - paddingControlBytes - priorityLen -
1342           paddingLength);
1343 
1344   self->mInputFrameDataStream->UpdateTransportReadEvents(
1345       self->mInputFrameDataSize);
1346   self->mLastDataReadEpoch = self->mLastReadEpoch;
1347 
1348   if (!isContinuation) {
1349     self->mAggregatedHeaderSize = self->mInputFrameDataSize -
1350                                   paddingControlBytes - priorityLen -
1351                                   paddingLength;
1352   } else {
1353     self->mAggregatedHeaderSize += self->mInputFrameDataSize -
1354                                    paddingControlBytes - priorityLen -
1355                                    paddingLength;
1356   }
1357 
1358   if (!endHeadersFlag) {  // more are coming - don't process yet
1359     self->ResetDownstreamState();
1360     return NS_OK;
1361   }
1362 
1363   if (isContinuation) {
1364     Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
1365                           self->mAggregatedHeaderSize);
1366   }
1367 
1368   rv = self->ResponseHeadersComplete();
1369   if (rv == NS_ERROR_ILLEGAL_VALUE) {
1370     LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
1371           self, self->mInputFrameID));
1372     self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
1373     self->ResetDownstreamState();
1374     rv = NS_OK;
1375   } else if (NS_FAILED(rv)) {
1376     // This is fatal to the session.
1377     self->mGoAwayReason = COMPRESSION_ERROR;
1378   }
1379   return rv;
1380 }
1381 
1382 // ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
1383 // should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
1384 // fine, and any other error is fatal to the session.
ResponseHeadersComplete()1385 nsresult Http2Session::ResponseHeadersComplete() {
1386   LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d", this,
1387         mInputFrameDataStream->StreamID(), mInputFrameFinal));
1388 
1389   // Anything prior to AllHeadersReceived() => true is actual headers. After
1390   // that, we need to handle them as trailers instead (which are special-cased
1391   // so we don't have to use the nasty chunked parser for all h2, just in case).
1392   if (mInputFrameDataStream->AllHeadersReceived()) {
1393     LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
1394     MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
1395     nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(
1396         &mDecompressor, mDecompressBuffer);
1397     if (NS_FAILED(rv)) {
1398       LOG3((
1399           "Http2Session::ResponseHeadersComplete trailer conversion failed\n"));
1400       return rv;
1401     }
1402     mFlatHTTPResponseHeadersOut = 0;
1403     mFlatHTTPResponseHeaders.Truncate();
1404     if (mInputFrameFinal) {
1405       // need to process the fin
1406       ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
1407     } else {
1408       ResetDownstreamState();
1409     }
1410 
1411     return NS_OK;
1412   }
1413 
1414   // if this turns out to be a 1xx response code we have to
1415   // undo the headers received bit that we are setting here.
1416   bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
1417   mInputFrameDataStream->SetAllHeadersReceived();
1418 
1419   // The stream needs to see flattened http headers
1420   // Uncompressed http/2 format headers currently live in
1421   // Http2Stream::mDecompressBuffer - convert that to HTTP format in
1422   // mFlatHTTPResponseHeaders via ConvertHeaders()
1423 
1424   nsresult rv;
1425   int32_t httpResponseCode;  // out param to ConvertResponseHeaders
1426   mFlatHTTPResponseHeadersOut = 0;
1427   rv = mInputFrameDataStream->ConvertResponseHeaders(
1428       &mDecompressor, mDecompressBuffer, mFlatHTTPResponseHeaders,
1429       httpResponseCode);
1430   if (rv == NS_ERROR_NET_RESET) {
1431     LOG(
1432         ("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders "
1433          "reset\n",
1434          this));
1435     // This means the stream found connection-oriented auth. Treat this like we
1436     // got a reset with HTTP_1_1_REQUIRED.
1437     mInputFrameDataStream->Transaction()->DisableSpdy();
1438     CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
1439     ResetDownstreamState();
1440     return NS_OK;
1441   } else if (NS_FAILED(rv)) {
1442     return rv;
1443   }
1444 
1445   // allow more headers in the case of 1xx
1446   if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
1447     mInputFrameDataStream->UnsetAllHeadersReceived();
1448   }
1449 
1450   ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
1451   return NS_OK;
1452 }
1453 
RecvPriority(Http2Session * self)1454 nsresult Http2Session::RecvPriority(Http2Session *self) {
1455   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
1456 
1457   if (self->mInputFrameDataSize != 5) {
1458     LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n", self,
1459           self->mInputFrameDataSize));
1460     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1461   }
1462 
1463   if (!self->mInputFrameID) {
1464     LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
1465     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1466   }
1467 
1468   nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
1469   if (NS_FAILED(rv)) return rv;
1470 
1471   uint32_t newPriorityDependency = NetworkEndian::readUint32(
1472       self->mInputFrameBuffer.get() + kFrameHeaderBytes);
1473   bool exclusive = !!(newPriorityDependency & 0x80000000);
1474   newPriorityDependency &= 0x7fffffff;
1475   uint8_t newPriorityWeight =
1476       *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
1477   if (self->mInputFrameDataStream) {
1478     self->mInputFrameDataStream->SetPriorityDependency(
1479         newPriorityDependency, newPriorityWeight, exclusive);
1480   }
1481 
1482   self->ResetDownstreamState();
1483   return NS_OK;
1484 }
1485 
RecvRstStream(Http2Session * self)1486 nsresult Http2Session::RecvRstStream(Http2Session *self) {
1487   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
1488 
1489   if (self->mInputFrameDataSize != 4) {
1490     LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
1491           self, self->mInputFrameDataSize));
1492     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1493   }
1494 
1495   if (!self->mInputFrameID) {
1496     LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
1497     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1498   }
1499 
1500   self->mDownstreamRstReason = NetworkEndian::readUint32(
1501       self->mInputFrameBuffer.get() + kFrameHeaderBytes);
1502 
1503   LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
1504         self, self->mDownstreamRstReason, self->mInputFrameID));
1505 
1506   DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
1507   MOZ_ASSERT(NS_SUCCEEDED(rv));
1508   if (!self->mInputFrameDataStream) {
1509     // if we can't find the stream just ignore it (4.2 closed)
1510     self->ResetDownstreamState();
1511     return NS_OK;
1512   }
1513 
1514   self->mInputFrameDataStream->SetRecvdReset(true);
1515   self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
1516   self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
1517   return NS_OK;
1518 }
1519 
RecvSettings(Http2Session * self)1520 nsresult Http2Session::RecvSettings(Http2Session *self) {
1521   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
1522 
1523   if (self->mInputFrameID) {
1524     LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", self,
1525           self->mInputFrameID));
1526     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1527   }
1528 
1529   if (self->mInputFrameDataSize % 6) {
1530     // Number of Settings is determined by dividing by each 6 byte setting
1531     // entry. So the payload must be a multiple of 6.
1532     LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", self,
1533           self->mInputFrameDataSize));
1534     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1535   }
1536 
1537   self->mReceivedSettings = true;
1538 
1539   uint32_t numEntries = self->mInputFrameDataSize / 6;
1540   LOG3(
1541       ("Http2Session::RecvSettings %p SETTINGS Control Frame "
1542        "with %d entries ack=%X",
1543        self, numEntries, self->mInputFrameFlags & kFlag_ACK));
1544 
1545   if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
1546     LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n",
1547           self));
1548     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1549   }
1550 
1551   for (uint32_t index = 0; index < numEntries; ++index) {
1552     uint8_t *setting =
1553         reinterpret_cast<uint8_t *>(self->mInputFrameBuffer.get()) +
1554         kFrameHeaderBytes + index * 6;
1555 
1556     uint16_t id = NetworkEndian::readUint16(setting);
1557     uint32_t value = NetworkEndian::readUint32(setting + 2);
1558     LOG3(("Settings ID %u, Value %u", id, value));
1559 
1560     switch (id) {
1561       case SETTINGS_TYPE_HEADER_TABLE_SIZE:
1562         LOG3(("Compression header table setting received: %d\n", value));
1563         self->mCompressor.SetMaxBufferSize(value);
1564         break;
1565 
1566       case SETTINGS_TYPE_ENABLE_PUSH:
1567         LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
1568         // nop
1569         break;
1570 
1571       case SETTINGS_TYPE_MAX_CONCURRENT:
1572         self->mMaxConcurrent = value;
1573         Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
1574         self->ProcessPending();
1575         break;
1576 
1577       case SETTINGS_TYPE_INITIAL_WINDOW: {
1578         Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
1579         int32_t delta = value - self->mServerInitialStreamWindow;
1580         self->mServerInitialStreamWindow = value;
1581 
1582         // SETTINGS only adjusts stream windows. Leave the session window alone.
1583         // We need to add the delta to all open streams (delta can be negative)
1584         for (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
1585              iter.Next()) {
1586           iter.Data()->UpdateServerReceiveWindow(delta);
1587         }
1588       } break;
1589 
1590       case SETTINGS_TYPE_MAX_FRAME_SIZE: {
1591         if ((value < kMaxFrameData) || (value >= 0x01000000)) {
1592           LOG3(("Received invalid max frame size 0x%X", value));
1593           RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1594         }
1595         // We stick to the default for simplicity's sake, so nothing to change
1596       } break;
1597 
1598       default:
1599         break;
1600     }
1601   }
1602 
1603   self->ResetDownstreamState();
1604 
1605   if (!(self->mInputFrameFlags & kFlag_ACK)) {
1606     self->GenerateSettingsAck();
1607   } else if (self->mWaitingForSettingsAck) {
1608     self->mGoAwayOnPush = true;
1609   }
1610 
1611   return NS_OK;
1612 }
1613 
RecvPushPromise(Http2Session * self)1614 nsresult Http2Session::RecvPushPromise(Http2Session *self) {
1615   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE ||
1616              self->mInputFrameType == FRAME_TYPE_CONTINUATION);
1617 
1618   // Find out how much padding this frame has, so we can only extract the real
1619   // header data from the frame.
1620   uint16_t paddingLength = 0;
1621   uint8_t paddingControlBytes = 0;
1622 
1623   // If this doesn't have END_PUSH_PROMISE set on it then require the next
1624   // frame to be PUSH_PROMISE of the same ID
1625   uint32_t promiseLen;
1626   uint32_t promisedID;
1627 
1628   if (self->mExpectedPushPromiseID) {
1629     promiseLen = 0;  // really a continuation frame
1630     promisedID = self->mContinuedPromiseStream;
1631   } else {
1632     self->mDecompressBuffer.Truncate();
1633     nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
1634     if (NS_FAILED(rv)) {
1635       return rv;
1636     }
1637     promiseLen = 4;
1638     promisedID =
1639         NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
1640                                   kFrameHeaderBytes + paddingControlBytes);
1641     promisedID &= 0x7fffffff;
1642     if (promisedID <= self->mLastPushedID) {
1643       LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
1644             self, promisedID, self->mLastPushedID));
1645       RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1646     }
1647     self->mLastPushedID = promisedID;
1648   }
1649 
1650   uint32_t associatedID = self->mInputFrameID;
1651 
1652   if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
1653     self->mExpectedPushPromiseID = 0;
1654     self->mContinuedPromiseStream = 0;
1655   } else {
1656     self->mExpectedPushPromiseID = self->mInputFrameID;
1657     self->mContinuedPromiseStream = promisedID;
1658   }
1659 
1660   if ((paddingControlBytes + promiseLen + paddingLength) >
1661       self->mInputFrameDataSize) {
1662     // This is fatal to the session
1663     LOG3(
1664         ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
1665          "PROTOCOL_ERROR extra %d > frame size %d\n",
1666          self, promisedID, associatedID,
1667          (paddingControlBytes + promiseLen + paddingLength),
1668          self->mInputFrameDataSize));
1669     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1670   }
1671 
1672   LOG3(
1673       ("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
1674        "paddingLength %d padded %d\n",
1675        self, promisedID, associatedID, paddingLength,
1676        self->mInputFrameFlags & kFlag_PADDED));
1677 
1678   if (!associatedID || !promisedID || (promisedID & 1)) {
1679     LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
1680     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1681   }
1682 
1683   // confirm associated-to
1684   nsresult rv = self->SetInputFrameDataStream(associatedID);
1685   if (NS_FAILED(rv)) return rv;
1686 
1687   Http2Stream *associatedStream = self->mInputFrameDataStream;
1688   ++(self->mServerPushedResources);
1689 
1690   // Anytime we start using the high bit of stream ID (either client or server)
1691   // begin to migrate to a new session.
1692   if (promisedID >= kMaxStreamID) self->mShouldGoAway = true;
1693 
1694   bool resetStream = true;
1695   SpdyPushCache *cache = nullptr;
1696 
1697   if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
1698     LOG3(
1699         ("Http2Session::RecvPushPromise %p cache push while in GoAway "
1700          "mode refused.\n",
1701          self));
1702     self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1703   } else if (!gHttpHandler->AllowPush()) {
1704     // ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
1705     LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
1706     if (self->mGoAwayOnPush) {
1707       LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
1708       RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1709     }
1710     self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1711   } else if (!(associatedID & 1)) {
1712     LOG3(
1713         ("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) "
1714          "stream not allowed\n",
1715          self, associatedID));
1716     self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1717   } else if (!associatedStream) {
1718     LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n",
1719           self));
1720     self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1721   } else if (Http2PushedStream::TestOnPush(associatedStream)) {
1722     LOG3(("Http2Session::RecvPushPromise %p will be handled by push listener.",
1723           self));
1724     resetStream = false;
1725   } else {
1726     nsIRequestContext *requestContext = associatedStream->RequestContext();
1727     if (requestContext) {
1728       requestContext->GetSpdyPushCache(&cache);
1729       if (!cache) {
1730         cache = new SpdyPushCache();
1731         if (!cache || NS_FAILED(requestContext->SetSpdyPushCache(cache))) {
1732           delete cache;
1733           cache = nullptr;
1734         }
1735       }
1736     }
1737     if (!cache) {
1738       // this is unexpected, but we can handle it just by refusing the push
1739       LOG3(
1740           ("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
1741       self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1742     } else {
1743       resetStream = false;
1744     }
1745   }
1746 
1747   if (resetStream) {
1748     // Need to decompress the headers even though we aren't using them yet in
1749     // order to keep the compression context consistent for other frames
1750     self->mDecompressBuffer.Append(
1751         &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
1752                                  promiseLen],
1753         self->mInputFrameDataSize - paddingControlBytes - promiseLen -
1754             paddingLength);
1755     if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
1756       rv = self->UncompressAndDiscard(true);
1757       if (NS_FAILED(rv)) {
1758         LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
1759         self->mGoAwayReason = COMPRESSION_ERROR;
1760         return rv;
1761       }
1762     }
1763     self->ResetDownstreamState();
1764     return NS_OK;
1765   }
1766 
1767   self->mDecompressBuffer.Append(
1768       &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
1769                                promiseLen],
1770       self->mInputFrameDataSize - paddingControlBytes - promiseLen -
1771           paddingLength);
1772 
1773   if (self->mInputFrameType != FRAME_TYPE_CONTINUATION) {
1774     self->mAggregatedHeaderSize = self->mInputFrameDataSize -
1775                                   paddingControlBytes - promiseLen -
1776                                   paddingLength;
1777   } else {
1778     self->mAggregatedHeaderSize += self->mInputFrameDataSize -
1779                                    paddingControlBytes - promiseLen -
1780                                    paddingLength;
1781   }
1782 
1783   if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
1784     LOG3(
1785         ("Http2Session::RecvPushPromise not finishing processing for "
1786          "multi-frame push\n"));
1787     self->ResetDownstreamState();
1788     return NS_OK;
1789   }
1790 
1791   if (self->mInputFrameType == FRAME_TYPE_CONTINUATION) {
1792     Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
1793                           self->mAggregatedHeaderSize);
1794   }
1795 
1796   // Create the buffering transaction and push stream
1797   RefPtr<Http2PushTransactionBuffer> transactionBuffer =
1798       new Http2PushTransactionBuffer();
1799   transactionBuffer->SetConnection(self);
1800   nsAutoPtr<Http2PushedStream> pushedStream(new Http2PushedStream(
1801       transactionBuffer, self, associatedStream, promisedID,
1802       self->mCurrentForegroundTabOuterContentWindowId));
1803 
1804   rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
1805                                         self->mDecompressBuffer,
1806                                         pushedStream->GetRequestString());
1807 
1808   if (rv == NS_ERROR_NOT_IMPLEMENTED) {
1809     LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
1810     self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1811     self->ResetDownstreamState();
1812     return NS_OK;
1813   }
1814 
1815   if (rv == NS_ERROR_ILLEGAL_VALUE) {
1816     // This means the decompression completed ok, but there was a problem with
1817     // the decoded headers. Reset the stream and go away.
1818     self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1819     self->ResetDownstreamState();
1820     return NS_OK;
1821   } else if (NS_FAILED(rv)) {
1822     // This is fatal to the session.
1823     self->mGoAwayReason = COMPRESSION_ERROR;
1824     return rv;
1825   }
1826 
1827   WeakPtr<Http2Stream> pushedWeak = pushedStream.forget();
1828 
1829   // Ownership of the pushed stream is by the transaction hash, just as it
1830   // is for a client initiated stream. Errors that aren't fatal to the
1831   // whole session must call cleanupStream() after this point in order
1832   // to remove the stream from that hash.
1833   self->mStreamTransactionHash.Put(transactionBuffer, pushedWeak);
1834   self->mPushedStreams.AppendElement(
1835       static_cast<Http2PushedStream*>(pushedWeak.get()));
1836 
1837   if (self->RegisterStreamID(pushedWeak, promisedID) == kDeadStreamID) {
1838     LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
1839     self->mGoAwayReason = INTERNAL_ERROR;
1840     return NS_ERROR_FAILURE;
1841   }
1842 
1843   if (promisedID > self->mOutgoingGoAwayID)
1844     self->mOutgoingGoAwayID = promisedID;
1845 
1846   // Fake the request side of the pushed HTTP transaction. Sets up hash
1847   // key and origin
1848   uint32_t notUsed;
1849   Unused << pushedWeak->ReadSegments(nullptr, 1, &notUsed);
1850 
1851   nsAutoCString key;
1852   if (!static_cast<Http2PushedStream*>(pushedWeak.get())->GetHashKey(key)) {
1853     LOG3(
1854         ("Http2Session::RecvPushPromise one of :authority :scheme :path "
1855          "missing from push\n"));
1856     self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, PROTOCOL_ERROR);
1857     self->ResetDownstreamState();
1858     return NS_OK;
1859   }
1860 
1861   // does the pushed origin belong on this connection?
1862   LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
1863         pushedWeak->Origin().get()));
1864   nsCOMPtr<nsIURI> pushedOrigin;
1865   rv = Http2Stream::MakeOriginURL(pushedWeak->Origin(), pushedOrigin);
1866   nsAutoCString pushedHostName;
1867   int32_t pushedPort = -1;
1868   if (NS_SUCCEEDED(rv)) {
1869     rv = pushedOrigin->GetHost(pushedHostName);
1870   }
1871   if (NS_SUCCEEDED(rv)) {
1872     rv = pushedOrigin->GetPort(&pushedPort);
1873     if (NS_SUCCEEDED(rv) && pushedPort == -1) {
1874       // Need to get the right default port, so TestJoinConnection below can
1875       // check things correctly. See bug 1397621.
1876       bool isHttp = false;
1877       if (NS_SUCCEEDED(pushedOrigin->SchemeIs("http", &isHttp)) && isHttp) {
1878         pushedPort = NS_HTTP_DEFAULT_PORT;
1879       } else {
1880         pushedPort = NS_HTTPS_DEFAULT_PORT;
1881       }
1882     }
1883   }
1884   if (NS_FAILED(rv) || !self->TestJoinConnection(pushedHostName, pushedPort)) {
1885     LOG3((
1886         "Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
1887         self, pushedWeak->Origin().get()));
1888     self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
1889     self->ResetDownstreamState();
1890     return NS_OK;
1891   }
1892 
1893   if (static_cast<Http2PushedStream*>(pushedWeak.get())->TryOnPush()) {
1894     LOG3(
1895         ("Http2Session::RecvPushPromise %p channel implements "
1896          "nsIHttpPushListener "
1897          "stream %p will not be placed into session cache.\n",
1898          self, pushedWeak.get()));
1899   } else {
1900     LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n",
1901           self));
1902     if (!cache->RegisterPushedStreamHttp2(
1903             key, static_cast<Http2PushedStream*>(pushedWeak.get()))) {
1904       // This only happens if they've already pushed us this item.
1905       LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
1906       self->CleanupStream(pushedWeak, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
1907       self->ResetDownstreamState();
1908       return NS_OK;
1909     }
1910 
1911     // Kick off a lookup into the HTTP cache so we can cancel the push if it's
1912     // unneeded (we already have it in our local regular cache). See bug
1913     // 1367551.
1914     nsCOMPtr<nsICacheStorageService> css =
1915         do_GetService("@mozilla.org/netwerk/cache-storage-service;1");
1916     mozilla::OriginAttributes oa;
1917     pushedWeak->GetOriginAttributes(&oa);
1918     RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, oa);
1919     nsCOMPtr<nsICacheStorage> ds;
1920     css->DiskCacheStorage(lci, false, getter_AddRefs(ds));
1921     // Build up our full URL for the cache lookup
1922     nsAutoCString spec;
1923     spec.Assign(pushedWeak->Origin());
1924     spec.Append(pushedWeak->Path());
1925     nsCOMPtr<nsIURI> pushedURL;
1926     // Nifty trick: this doesn't actually do anything origin-specific, it's just
1927     // named that way. So by passing it the full spec here, we get a URL with
1928     // the full path.
1929     // Another nifty trick! Even though this is using nsIURIs (which are not
1930     // generally ok off the main thread), since we're not using the protocol
1931     // handler to create any URIs, this will work just fine here. Don't try this
1932     // at home, though, kids. I'm a trained professional.
1933     if (NS_SUCCEEDED(Http2Stream::MakeOriginURL(spec, pushedURL))) {
1934       LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry",
1935             self));
1936       RefPtr<CachePushCheckCallback> cpcc = new CachePushCheckCallback(
1937           self, promisedID,
1938           static_cast<Http2PushedStream*>(pushedWeak.get())
1939               ->GetRequestString());
1940       if (NS_FAILED(ds->AsyncOpenURI(
1941               pushedURL, EmptyCString(),
1942               nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY,
1943               cpcc))) {
1944         LOG3(
1945             ("Http2Session::RecvPushPromise %p failed to open cache entry for "
1946              "push check",
1947              self));
1948       } else if (!pushedWeak) {
1949         // We have an up to date entry in our cache, so the stream was closed
1950         // and released in CachePushCheckCallback::OnCacheEntryCheck().
1951         return NS_OK;
1952       }
1953     }
1954   }
1955 
1956   pushedWeak->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
1957   static_assert(Http2Stream::kWorstPriority >= 0,
1958                 "kWorstPriority out of range");
1959   uint8_t priorityWeight =
1960       (nsISupportsPriority::PRIORITY_LOWEST + 1) -
1961       (Http2Stream::kWorstPriority - Http2Stream::kNormalPriority);
1962   pushedWeak->SetPriority(Http2Stream::kWorstPriority);
1963   self->GeneratePriority(promisedID, priorityWeight);
1964   self->ResetDownstreamState();
1965   return NS_OK;
1966 }
1967 
1968 NS_IMPL_ISUPPORTS(Http2Session::CachePushCheckCallback,
1969                   nsICacheEntryOpenCallback);
1970 
CachePushCheckCallback(Http2Session * session,uint32_t promisedID,const nsACString & requestString)1971 Http2Session::CachePushCheckCallback::CachePushCheckCallback(
1972     Http2Session *session, uint32_t promisedID, const nsACString &requestString)
1973     : mPromisedID(promisedID) {
1974   mSession = session;
1975   mRequestHead.ParseHeaderSet(requestString.BeginReading());
1976 }
1977 
1978 NS_IMETHODIMP
OnCacheEntryCheck(nsICacheEntry * entry,nsIApplicationCache * appCache,uint32_t * result)1979 Http2Session::CachePushCheckCallback::OnCacheEntryCheck(
1980     nsICacheEntry *entry, nsIApplicationCache *appCache, uint32_t *result) {
1981   MOZ_ASSERT(OnSocketThread(), "Not on socket thread?!");
1982 
1983   // We never care to fully open the entry, since we won't actually use it.
1984   // We just want to be able to do all our checks to see if a future channel can
1985   // use this entry, or if we need to accept the push.
1986   *result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
1987 
1988   bool isForcedValid = false;
1989   entry->GetIsForcedValid(&isForcedValid);
1990 
1991   nsHttpResponseHead cachedResponseHead;
1992   nsresult rv =
1993       nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
1994   if (NS_FAILED(rv)) {
1995     // Couldn't make sense of what's in the cache entry, go ahead and accept
1996     // the push.
1997     return NS_OK;
1998   }
1999 
2000   if ((cachedResponseHead.Status() / 100) != 2) {
2001     // Assume the push is sending us a success, while we don't have one in the
2002     // cache, so we'll accept the push.
2003     return NS_OK;
2004   }
2005 
2006   // Get the method that was used to generate the cached response
2007   nsCString buf;
2008   rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
2009   if (NS_FAILED(rv)) {
2010     // Can't check request method, accept the push
2011     return NS_OK;
2012   }
2013   nsAutoCString pushedMethod;
2014   mRequestHead.Method(pushedMethod);
2015   if (!buf.Equals(pushedMethod)) {
2016     // Methods don't match, accept the push
2017     return NS_OK;
2018   }
2019 
2020   int64_t size, contentLength;
2021   rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
2022   if (NS_FAILED(rv)) {
2023     // Couldn't figure out if this was partial or not, accept the push.
2024     return NS_OK;
2025   }
2026 
2027   if (size == int64_t(-1) || contentLength != size) {
2028     // This is partial content in the cache, accept the push.
2029     return NS_OK;
2030   }
2031 
2032   nsAutoCString requestedETag;
2033   if (NS_FAILED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
2034     // Can't check etag
2035     return NS_OK;
2036   }
2037   if (!requestedETag.IsEmpty()) {
2038     nsAutoCString cachedETag;
2039     if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
2040       // Can't check etag
2041       return NS_OK;
2042     }
2043     if (!requestedETag.Equals(cachedETag)) {
2044       // ETags don't match, accept the push.
2045       return NS_OK;
2046     }
2047   }
2048 
2049   nsAutoCString imsString;
2050   Unused << mRequestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
2051   if (!buf.IsEmpty()) {
2052     uint32_t ims = buf.ToInteger(&rv);
2053     uint32_t lm;
2054     rv = cachedResponseHead.GetLastModifiedValue(&lm);
2055     if (NS_SUCCEEDED(rv) && lm && lm < ims) {
2056       // The push appears to be newer than what's in our cache, accept it.
2057       return NS_OK;
2058     }
2059   }
2060 
2061   nsAutoCString cacheControlRequestHeader;
2062   Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
2063                                    cacheControlRequestHeader);
2064   CacheControlParser cacheControlRequest(cacheControlRequestHeader);
2065   if (cacheControlRequest.NoStore()) {
2066     // Don't use a no-store cache entry, accept the push.
2067     return NS_OK;
2068   }
2069 
2070   nsCString cachedAuth;
2071   rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
2072   if (NS_SUCCEEDED(rv)) {
2073     uint32_t lastModifiedTime;
2074     rv = entry->GetLastModified(&lastModifiedTime);
2075     if (NS_SUCCEEDED(rv)) {
2076       if ((gHttpHandler->SessionStartTime() > lastModifiedTime) &&
2077           !cachedAuth.IsEmpty()) {
2078         // Need to revalidate this, as the auth is old. Accept the push.
2079         return NS_OK;
2080       }
2081 
2082       if (cachedAuth.IsEmpty() &&
2083           mRequestHead.HasHeader(nsHttp::Authorization)) {
2084         // They're pushing us something with auth, but we didn't cache anything
2085         // with auth. Accept the push.
2086         return NS_OK;
2087       }
2088     }
2089   }
2090 
2091   bool weaklyFramed, isImmutable;
2092   nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
2093                                           &weaklyFramed, &isImmutable);
2094 
2095   // We'll need this value in later computations...
2096   uint32_t lastModifiedTime;
2097   rv = entry->GetLastModified(&lastModifiedTime);
2098   if (NS_FAILED(rv)) {
2099     // Ugh, this really sucks. OK, accept the push.
2100     return NS_OK;
2101   }
2102 
2103   // Determine if this is the first time that this cache entry
2104   // has been accessed during this session.
2105   bool fromPreviousSession =
2106       (gHttpHandler->SessionStartTime() > lastModifiedTime);
2107 
2108   bool validationRequired = nsHttp::ValidationRequired(
2109       isForcedValid, &cachedResponseHead, 0 /*NWGH: ??? - loadFlags*/, false,
2110       isImmutable, false, mRequestHead, entry, cacheControlRequest,
2111       fromPreviousSession);
2112 
2113   if (validationRequired) {
2114     // A real channel would most likely hit the net at this point, so let's
2115     // accept the push.
2116     return NS_OK;
2117   }
2118 
2119   // If we get here, then we would be able to use this cache entry. Cancel the
2120   // push so as not to waste any more bandwidth.
2121   mSession->CleanupStream(mPromisedID, NS_ERROR_FAILURE,
2122                           Http2Session::REFUSED_STREAM_ERROR);
2123 
2124   return NS_OK;
2125 }
2126 
2127 NS_IMETHODIMP
OnCacheEntryAvailable(nsICacheEntry * entry,bool isNew,nsIApplicationCache * appCache,nsresult result)2128 Http2Session::CachePushCheckCallback::OnCacheEntryAvailable(
2129     nsICacheEntry *entry, bool isNew, nsIApplicationCache *appCache,
2130     nsresult result) {
2131   // Nothing to do here, all the work is in OnCacheEntryCheck.
2132   return NS_OK;
2133 }
2134 
RecvPing(Http2Session * self)2135 nsresult Http2Session::RecvPing(Http2Session *self) {
2136   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
2137 
2138   LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
2139         self->mInputFrameFlags));
2140 
2141   if (self->mInputFrameDataSize != 8) {
2142     LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d", self,
2143           self->mInputFrameDataSize));
2144     RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
2145   }
2146 
2147   if (self->mInputFrameID) {
2148     LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n", self,
2149           self->mInputFrameID));
2150     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2151   }
2152 
2153   if (self->mInputFrameFlags & kFlag_ACK) {
2154     // presumably a reply to our timeout ping.. don't reply to it
2155     self->mPingSentEpoch = 0;
2156   } else {
2157     // reply with a ack'd ping
2158     self->GeneratePing(true);
2159   }
2160 
2161   self->ResetDownstreamState();
2162   return NS_OK;
2163 }
2164 
RecvGoAway(Http2Session * self)2165 nsresult Http2Session::RecvGoAway(Http2Session *self) {
2166   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
2167 
2168   if (self->mInputFrameDataSize < 8) {
2169     // data > 8 is an opaque token that we can't interpret. NSPR Logs will
2170     // have the hex of all packets so there is no point in separately logging.
2171     LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
2172           self, self->mInputFrameDataSize));
2173     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2174   }
2175 
2176   if (self->mInputFrameID) {
2177     LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
2178           self, self->mInputFrameID));
2179     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2180   }
2181 
2182   self->mShouldGoAway = true;
2183   self->mGoAwayID = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
2184                                               kFrameHeaderBytes);
2185   self->mGoAwayID &= 0x7fffffff;
2186   self->mCleanShutdown = true;
2187   self->mPeerGoAwayReason = NetworkEndian::readUint32(
2188       self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
2189 
2190   // Find streams greater than the last-good ID and mark them for deletion
2191   // in the mGoAwayStreamsToRestart queue. The underlying transaction can be
2192   // restarted.
2193   for (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
2194        iter.Next()) {
2195     // these streams were not processed by the server and can be restarted.
2196     // Do that after the enumerator completes to avoid the risk of
2197     // a restart event re-entrantly modifying this hash. Be sure not to restart
2198     // a pushed (even numbered) stream
2199     nsAutoPtr<Http2Stream> &stream = iter.Data();
2200     if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
2201         !stream->HasRegisteredID()) {
2202       self->mGoAwayStreamsToRestart.Push(stream);
2203     }
2204   }
2205 
2206   // Process the streams marked for deletion and restart.
2207   size_t size = self->mGoAwayStreamsToRestart.GetSize();
2208   for (size_t count = 0; count < size; ++count) {
2209     Http2Stream *stream =
2210         static_cast<Http2Stream *>(self->mGoAwayStreamsToRestart.PopFront());
2211 
2212     if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
2213       stream->Transaction()->DisableSpdy();
2214     }
2215     self->CloseStream(stream, NS_ERROR_NET_RESET);
2216     if (stream->HasRegisteredID())
2217       self->mStreamIDHash.Remove(stream->StreamID());
2218     self->mStreamTransactionHash.Remove(stream->Transaction());
2219   }
2220 
2221   // Queued streams can also be deleted from this session and restarted
2222   // in another one. (they were never sent on the network so they implicitly
2223   // are not covered by the last-good id.
2224   size = self->mQueuedStreams.GetSize();
2225   for (size_t count = 0; count < size; ++count) {
2226     Http2Stream *stream =
2227         static_cast<Http2Stream *>(self->mQueuedStreams.PopFront());
2228     MOZ_ASSERT(stream->Queued());
2229     stream->SetQueued(false);
2230     if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
2231       stream->Transaction()->DisableSpdy();
2232     }
2233     self->CloseStream(stream, NS_ERROR_NET_RESET);
2234     self->mStreamTransactionHash.Remove(stream->Transaction());
2235   }
2236 
2237   LOG3(
2238       ("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
2239        "live streams=%d\n",
2240        self, self->mGoAwayID, self->mPeerGoAwayReason,
2241        self->mStreamTransactionHash.Count()));
2242 
2243   self->ResetDownstreamState();
2244   return NS_OK;
2245 }
2246 
RecvWindowUpdate(Http2Session * self)2247 nsresult Http2Session::RecvWindowUpdate(Http2Session *self) {
2248   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
2249 
2250   if (self->mInputFrameDataSize != 4) {
2251     LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
2252           self, self->mInputFrameDataSize));
2253     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2254   }
2255 
2256   uint32_t delta = NetworkEndian::readUint32(self->mInputFrameBuffer.get() +
2257                                              kFrameHeaderBytes);
2258   delta &= 0x7fffffff;
2259 
2260   LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", self, delta,
2261         self->mInputFrameID));
2262 
2263   if (self->mInputFrameID) {  // stream window
2264     nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
2265     if (NS_FAILED(rv)) return rv;
2266 
2267     if (!self->mInputFrameDataStream) {
2268       LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
2269             self, self->mInputFrameID));
2270       // only resest the session if the ID is one we haven't ever opened
2271       if (self->mInputFrameID >= self->mNextStreamID)
2272         self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
2273       self->ResetDownstreamState();
2274       return NS_OK;
2275     }
2276 
2277     if (delta == 0) {
2278       LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
2279             self));
2280       self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
2281                           PROTOCOL_ERROR);
2282       self->ResetDownstreamState();
2283       return NS_OK;
2284     }
2285 
2286     int64_t oldRemoteWindow =
2287         self->mInputFrameDataStream->ServerReceiveWindow();
2288     self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
2289     if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
2290       // a window cannot reach 2^31 and be in compliance. Our calculations
2291       // are 64 bit safe though.
2292       LOG3(
2293           ("Http2Session::RecvWindowUpdate %p stream window "
2294            "exceeds 2^31 - 1\n",
2295            self));
2296       self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
2297                           FLOW_CONTROL_ERROR);
2298       self->ResetDownstreamState();
2299       return NS_OK;
2300     }
2301 
2302     LOG3(
2303         ("Http2Session::RecvWindowUpdate %p stream 0x%X window "
2304          "%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n",
2305          self, self->mInputFrameID, oldRemoteWindow, delta,
2306          oldRemoteWindow + delta));
2307 
2308   } else {  // session window update
2309     if (delta == 0) {
2310       LOG3(
2311           ("Http2Session::RecvWindowUpdate %p received 0 session window update",
2312            self));
2313       RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2314     }
2315 
2316     int64_t oldRemoteWindow = self->mServerSessionWindow;
2317     self->mServerSessionWindow += delta;
2318 
2319     if (self->mServerSessionWindow >= 0x80000000) {
2320       // a window cannot reach 2^31 and be in compliance. Our calculations
2321       // are 64 bit safe though.
2322       LOG3(
2323           ("Http2Session::RecvWindowUpdate %p session window "
2324            "exceeds 2^31 - 1\n",
2325            self));
2326       RETURN_SESSION_ERROR(self, FLOW_CONTROL_ERROR);
2327     }
2328 
2329     if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
2330       LOG3(
2331           ("Http2Session::RecvWindowUpdate %p restart session window\n", self));
2332       for (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
2333            iter.Next()) {
2334         MOZ_ASSERT(self->mServerSessionWindow > 0);
2335 
2336         nsAutoPtr<Http2Stream> &stream = iter.Data();
2337         if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
2338           continue;
2339         }
2340 
2341         self->mReadyForWrite.Push(stream);
2342         self->SetWriteCallbacks();
2343       }
2344     }
2345     LOG3(
2346         ("Http2Session::RecvWindowUpdate %p session window "
2347          "%" PRId64 " increased by %d now %" PRId64 ".\n",
2348          self, oldRemoteWindow, delta, oldRemoteWindow + delta));
2349   }
2350 
2351   self->ResetDownstreamState();
2352   return NS_OK;
2353 }
2354 
RecvContinuation(Http2Session * self)2355 nsresult Http2Session::RecvContinuation(Http2Session *self) {
2356   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
2357   MOZ_ASSERT(self->mInputFrameID);
2358   MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
2359   MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
2360 
2361   LOG3(
2362       ("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
2363        "promise id 0x%X header id 0x%X\n",
2364        self, self->mInputFrameFlags, self->mInputFrameID,
2365        self->mExpectedPushPromiseID, self->mExpectedHeaderID));
2366 
2367   DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
2368   MOZ_ASSERT(NS_SUCCEEDED(rv));
2369 
2370   if (!self->mInputFrameDataStream) {
2371     LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
2372           self->mInputFrameID));
2373     RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2374   }
2375 
2376   // continued headers
2377   if (self->mExpectedHeaderID) {
2378     self->mInputFrameFlags &= ~kFlag_PRIORITY;
2379     return RecvHeaders(self);
2380   }
2381 
2382   // continued push promise
2383   if (self->mInputFrameFlags & kFlag_END_HEADERS) {
2384     self->mInputFrameFlags &= ~kFlag_END_HEADERS;
2385     self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
2386   }
2387   return RecvPushPromise(self);
2388 }
2389 
2390 class UpdateAltSvcEvent : public Runnable {
2391  public:
UpdateAltSvcEvent(const nsCString & header,const nsCString & aOrigin,nsHttpConnectionInfo * aCI,nsIInterfaceRequestor * callbacks)2392   UpdateAltSvcEvent(const nsCString &header, const nsCString &aOrigin,
2393                     nsHttpConnectionInfo *aCI, nsIInterfaceRequestor *callbacks)
2394       : Runnable("net::UpdateAltSvcEvent"),
2395         mHeader(header),
2396         mOrigin(aOrigin),
2397         mCI(aCI),
2398         mCallbacks(callbacks) {}
2399 
Run()2400   NS_IMETHOD Run() override {
2401     MOZ_ASSERT(NS_IsMainThread());
2402 
2403     nsCString originScheme;
2404     nsCString originHost;
2405     int32_t originPort = -1;
2406 
2407     nsCOMPtr<nsIURI> uri;
2408     if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
2409       LOG(("UpdateAltSvcEvent origin does not parse %s\n", mOrigin.get()));
2410       return NS_OK;
2411     }
2412     uri->GetScheme(originScheme);
2413     uri->GetHost(originHost);
2414     uri->GetPort(&originPort);
2415 
2416     AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
2417                                  mCI->GetUsername(), mCI->GetPrivate(),
2418                                  mCallbacks, mCI->ProxyInfo(), 0,
2419                                  mCI->GetOriginAttributes());
2420     return NS_OK;
2421   }
2422 
2423  private:
2424   nsCString mHeader;
2425   nsCString mOrigin;
2426   RefPtr<nsHttpConnectionInfo> mCI;
2427   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
2428 };
2429 
2430 // defined as an http2 extension - alt-svc
2431 // defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft
2432 // -06 sec 4 as this is an extension, never generate protocol error - just
2433 // ignore problems
RecvAltSvc(Http2Session * self)2434 nsresult Http2Session::RecvAltSvc(Http2Session *self) {
2435   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
2436   LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
2437         self->mInputFrameFlags, self->mInputFrameID));
2438 
2439   if (self->mInputFrameDataSize < 2) {
2440     LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
2441     self->ResetDownstreamState();
2442     return NS_OK;
2443   }
2444 
2445   uint16_t originLen = NetworkEndian::readUint16(self->mInputFrameBuffer.get() +
2446                                                  kFrameHeaderBytes);
2447   if (originLen + 2U > self->mInputFrameDataSize) {
2448     LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
2449     self->ResetDownstreamState();
2450     return NS_OK;
2451   }
2452 
2453   if (!gHttpHandler->AllowAltSvc()) {
2454     LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
2455     self->ResetDownstreamState();
2456     return NS_OK;
2457   }
2458 
2459   uint16_t altSvcFieldValueLen =
2460       static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
2461   LOG3((
2462       "Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
2463       self, originLen, altSvcFieldValueLen));
2464 
2465   if (self->mInputFrameDataSize > 2000) {
2466     LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly",
2467           self));
2468     self->ResetDownstreamState();
2469     return NS_OK;
2470   }
2471 
2472   nsAutoCString origin;
2473   bool impliedOrigin = true;
2474   if (originLen) {
2475     origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2,
2476                   originLen);
2477     impliedOrigin = false;
2478   }
2479 
2480   nsAutoCString altSvcFieldValue;
2481   if (altSvcFieldValueLen) {
2482     altSvcFieldValue.Assign(
2483         self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
2484         altSvcFieldValueLen);
2485   }
2486 
2487   if (altSvcFieldValue.IsEmpty() ||
2488       !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
2489     LOG(
2490         ("Http2Session %p Alt-Svc Response Header seems unreasonable - "
2491          "skipping\n",
2492          self));
2493     self->ResetDownstreamState();
2494     return NS_OK;
2495   }
2496 
2497   if (self->mInputFrameID & 1) {
2498     // pulled streams apply to the origin of the pulled stream.
2499     // If the origin field is filled in the frame, the frame should be ignored
2500     if (!origin.IsEmpty()) {
2501       LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n",
2502            self));
2503       self->ResetDownstreamState();
2504       return NS_OK;
2505     }
2506 
2507     if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
2508         !self->mInputFrameDataStream->Transaction() ||
2509         !self->mInputFrameDataStream->Transaction()->RequestHead()) {
2510       LOG3(
2511           ("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream",
2512            self));
2513       self->ResetDownstreamState();
2514       return NS_OK;
2515     }
2516 
2517     self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
2518   } else if (!self->mInputFrameID) {
2519     // ID 0 streams must supply their own origin
2520     if (origin.IsEmpty()) {
2521       LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
2522       self->ResetDownstreamState();
2523       return NS_OK;
2524     }
2525   } else {
2526     // handling of push streams is not defined. Let's ignore it
2527     LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n",
2528          self));
2529     self->ResetDownstreamState();
2530     return NS_OK;
2531   }
2532 
2533   RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
2534   if (!self->mConnection || !ci) {
2535     LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
2536           self->mInputFrameID));
2537     self->ResetDownstreamState();
2538     return NS_OK;
2539   }
2540 
2541   if (!impliedOrigin) {
2542     bool okToReroute = true;
2543     nsCOMPtr<nsISupports> securityInfo;
2544     self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
2545     nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
2546     if (!ssl) {
2547       okToReroute = false;
2548     }
2549 
2550     // a little off main thread origin parser. This is a non critical function
2551     // because any alternate route created has to be verified anyhow
2552     nsAutoCString specifiedOriginHost;
2553     if (origin.EqualsIgnoreCase("https://", 8)) {
2554       specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
2555     } else if (origin.EqualsIgnoreCase("http://", 7)) {
2556       specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
2557     }
2558 
2559     int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
2560     if (colonOffset != kNotFound) {
2561       specifiedOriginHost.Truncate(colonOffset);
2562     }
2563 
2564     if (okToReroute) {
2565       ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
2566     }
2567 
2568     if (!okToReroute) {
2569       LOG3(
2570           ("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin "
2571            "%s",
2572            self, origin.BeginReading()));
2573       self->ResetDownstreamState();
2574       return NS_OK;
2575     }
2576   }
2577 
2578   nsCOMPtr<nsISupports> callbacks;
2579   self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
2580   nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
2581 
2582   RefPtr<UpdateAltSvcEvent> event =
2583       new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
2584   NS_DispatchToMainThread(event);
2585   self->ResetDownstreamState();
2586   return NS_OK;
2587 }
2588 
Received421(nsHttpConnectionInfo * ci)2589 void Http2Session::Received421(nsHttpConnectionInfo *ci) {
2590   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2591   LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
2592   if (!mOriginFrameActivated || !ci) {
2593     return;
2594   }
2595 
2596   nsAutoCString key(ci->GetOrigin());
2597   key.Append(':');
2598   key.AppendInt(ci->OriginPort());
2599   mOriginFrame.Remove(key);
2600   LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
2601 }
2602 
RecvUnused(Http2Session * self)2603 nsresult Http2Session::RecvUnused(Http2Session *self) {
2604   LOG3(("Http2Session %p unknown frame type %x ignored\n", self,
2605         self->mInputFrameType));
2606   self->ResetDownstreamState();
2607   return NS_OK;
2608 }
2609 
2610 // defined as an http2 extension - origin
2611 // defines receipt of frame type 0x0b..
2612 // http://httpwg.org/http-extensions/origin-frame.html as this is an extension,
2613 // never generate protocol error - just ignore problems
RecvOrigin(Http2Session * self)2614 nsresult Http2Session::RecvOrigin(Http2Session *self) {
2615   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2616   MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
2617   LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
2618         self->mInputFrameFlags, self->mInputFrameID));
2619 
2620   if (self->mInputFrameFlags & 0x0F) {
2621     LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
2622     self->ResetDownstreamState();
2623     return NS_OK;
2624   }
2625 
2626   if (self->mInputFrameID) {
2627     LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
2628     self->ResetDownstreamState();
2629     return NS_OK;
2630   }
2631 
2632   if (self->ConnectionInfo()->UsingProxy()) {
2633     LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
2634     self->ResetDownstreamState();
2635     return NS_OK;
2636   }
2637 
2638   if (!gHttpHandler->AllowOriginExtension()) {
2639     LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
2640     self->ResetDownstreamState();
2641     return NS_OK;
2642   }
2643 
2644   uint32_t offset = 0;
2645   self->mOriginFrameActivated = true;
2646 
2647   while (self->mInputFrameDataSize >= (offset + 2U)) {
2648     uint16_t originLen = NetworkEndian::readUint16(
2649         self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
2650     LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n",
2651           self, originLen));
2652     if (originLen + 2U + offset > self->mInputFrameDataSize) {
2653       LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
2654       break;
2655     }
2656 
2657     nsAutoCString originString;
2658     nsCOMPtr<nsIURI> originURL;
2659     originString.Assign(
2660         self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2,
2661         originLen);
2662     offset += originLen + 2;
2663     if (NS_FAILED(Http2Stream::MakeOriginURL(originString, originURL))) {
2664       LOG3(
2665           ("Http2Session::RecvOrigin %p origin frame string %s failed to "
2666            "parse\n",
2667            self, originString.get()));
2668       continue;
2669     }
2670 
2671     LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n",
2672           self, originString.get()));
2673     bool isHttps = false;
2674     if (NS_FAILED(originURL->SchemeIs("https", &isHttps)) || !isHttps) {
2675       LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
2676       continue;
2677     }
2678 
2679     int32_t port = -1;
2680     originURL->GetPort(&port);
2681     if (port == -1) {
2682       port = 443;
2683     }
2684     // dont use ->GetHostPort because we want explicit 443
2685     nsAutoCString host;
2686     originURL->GetHost(host);
2687     nsAutoCString key(host);
2688     key.Append(':');
2689     key.AppendInt(port);
2690     if (!self->mOriginFrame.Get(key)) {
2691       self->mOriginFrame.Put(key, true);
2692       RefPtr<nsHttpConnection> conn(self->HttpConnection());
2693       MOZ_ASSERT(conn.get());
2694       gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
2695     } else {
2696       LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n", self));
2697     }
2698   }
2699 
2700   self->ResetDownstreamState();
2701   return NS_OK;
2702 }
2703 
2704 //-----------------------------------------------------------------------------
2705 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
2706 // of these methods
2707 //-----------------------------------------------------------------------------
2708 
OnTransportStatus(nsITransport * aTransport,nsresult aStatus,int64_t aProgress)2709 void Http2Session::OnTransportStatus(nsITransport *aTransport, nsresult aStatus,
2710                                      int64_t aProgress) {
2711   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2712 
2713   switch (aStatus) {
2714       // These should appear only once, deliver to the first
2715       // transaction on the session.
2716     case NS_NET_STATUS_RESOLVING_HOST:
2717     case NS_NET_STATUS_RESOLVED_HOST:
2718     case NS_NET_STATUS_CONNECTING_TO:
2719     case NS_NET_STATUS_CONNECTED_TO:
2720     case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
2721     case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: {
2722       if (!mFirstHttpTransaction) {
2723         // if we still do not have a HttpTransaction store timings info in
2724         // a HttpConnection.
2725         // If some error occur it can happen that we do not have a connection.
2726         if (mConnection) {
2727           RefPtr<nsHttpConnection> conn = mConnection->HttpConnection();
2728           conn->SetEvent(aStatus);
2729         }
2730       } else {
2731         mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
2732                                                  aProgress);
2733       }
2734 
2735       if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
2736         mFirstHttpTransaction = nullptr;
2737         mTlsHandshakeFinished = true;
2738       }
2739       break;
2740     }
2741 
2742     default:
2743       // The other transport events are ignored here because there is no good
2744       // way to map them to the right transaction in http/2. Instead, the events
2745       // are generated again from the http/2 code and passed directly to the
2746       // correct transaction.
2747 
2748       // NS_NET_STATUS_SENDING_TO:
2749       // This is generated by the socket transport when (part) of
2750       // a transaction is written out
2751       //
2752       // There is no good way to map it to the right transaction in http/2,
2753       // so it is ignored here and generated separately when the request
2754       // is sent from Http2Stream::TransmitFrame
2755 
2756       // NS_NET_STATUS_WAITING_FOR:
2757       // Created by nsHttpConnection when the request has been totally sent.
2758       // There is no good way to map it to the right transaction in http/2,
2759       // so it is ignored here and generated separately when the same
2760       // condition is complete in Http2Stream when there is no more
2761       // request body left to be transmitted.
2762 
2763       // NS_NET_STATUS_RECEIVING_FROM
2764       // Generated in session whenever we read a data frame or a HEADERS
2765       // that can be attributed to a particular stream/transaction
2766 
2767       break;
2768   }
2769 }
2770 
2771 // ReadSegments() is used to write data to the network. Generally, HTTP
2772 // request data is pulled from the approriate transaction and
2773 // converted to http/2 data. Sometimes control data like window-update are
2774 // generated instead.
2775 
ReadSegmentsAgain(nsAHttpSegmentReader * reader,uint32_t count,uint32_t * countRead,bool * again)2776 nsresult Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader,
2777                                          uint32_t count, uint32_t *countRead,
2778                                          bool *again) {
2779   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2780 
2781   MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
2782              "Inconsistent Write Function Callback");
2783 
2784   nsresult rv = ConfirmTLSProfile();
2785   if (NS_FAILED(rv)) {
2786     if (mGoAwayReason == INADEQUATE_SECURITY) {
2787       LOG3(
2788           ("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY "
2789            "%" PRIx32,
2790            this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY)));
2791       rv = NS_ERROR_NET_INADEQUATE_SECURITY;
2792     }
2793     return rv;
2794   }
2795 
2796   if (reader) mSegmentReader = reader;
2797 
2798   *countRead = 0;
2799 
2800   LOG3(("Http2Session::ReadSegments %p", this));
2801 
2802   Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
2803   if (!stream) {
2804     LOG3(("Http2Session %p could not identify a stream to write; suspending.",
2805           this));
2806     uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
2807     FlushOutputQueue();
2808     uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
2809     if (availBeforeFlush != availAfterFlush) {
2810       LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments",
2811             this));
2812       Unused << ResumeRecv();
2813     }
2814     SetWriteCallbacks();
2815     if (mAttemptingEarlyData) {
2816       // We can still try to send our preamble as early-data
2817       *countRead = mOutputQueueUsed - mOutputQueueSent;
2818     }
2819     return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
2820   }
2821 
2822   uint32_t earlyDataUsed = 0;
2823   if (mAttemptingEarlyData) {
2824     if (!stream->Do0RTT()) {
2825       LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X",
2826             this, stream, stream->StreamID()));
2827       FlushOutputQueue();
2828       SetWriteCallbacks();
2829       if (!mCannotDo0RTTStreams.Contains(stream)) {
2830         mCannotDo0RTTStreams.AppendElement(stream);
2831       }
2832       // We can still send our preamble
2833       *countRead = mOutputQueueUsed - mOutputQueueSent;
2834       return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
2835     }
2836 
2837     // Need to adjust this to only take as much as we can fit in with the
2838     // preamble/settings/priority stuff
2839     count -= (mOutputQueueUsed - mOutputQueueSent);
2840 
2841     // Keep track of this to add it into countRead later, as
2842     // stream->ReadSegments will likely change the value of mOutputQueueUsed.
2843     earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
2844   }
2845 
2846   LOG3(
2847       ("Http2Session %p will write from Http2Stream %p 0x%X "
2848        "block-input=%d block-output=%d\n",
2849        this, stream, stream->StreamID(), stream->RequestBlockedOnRead(),
2850        stream->BlockedOnRwin()));
2851 
2852   rv = stream->ReadSegments(this, count, countRead);
2853 
2854   if (earlyDataUsed) {
2855     // Do this here because countRead could get reset somewhere down the rabbit
2856     // hole of stream->ReadSegments, and we want to make sure we return the
2857     // proper value to our caller.
2858     *countRead += earlyDataUsed;
2859   }
2860 
2861   if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) {
2862     LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n",
2863           stream->StreamID()));
2864     m0RTTStreams.AppendElement(stream);
2865   }
2866 
2867   // Not every permutation of stream->ReadSegents produces data (and therefore
2868   // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
2869   // of that. But we might still have old data buffered that would be good
2870   // to flush.
2871   FlushOutputQueue();
2872 
2873   // Allow new server reads - that might be data or control information
2874   // (e.g. window updates or http replies) that are responses to these writes
2875   Unused << ResumeRecv();
2876 
2877   if (stream->RequestBlockedOnRead()) {
2878     // We are blocked waiting for input - either more http headers or
2879     // any request body data. When more data from the request stream
2880     // becomes available the httptransaction will call conn->ResumeSend().
2881 
2882     LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
2883 
2884     // call readsegments again if there are other streams ready
2885     // to run in this session
2886     if (GetWriteQueueSize()) {
2887       rv = NS_OK;
2888     } else {
2889       rv = NS_BASE_STREAM_WOULD_BLOCK;
2890     }
2891     SetWriteCallbacks();
2892     return rv;
2893   }
2894 
2895   if (NS_FAILED(rv)) {
2896     LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32, this,
2897           static_cast<uint32_t>(rv)));
2898     if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
2899       return rv;
2900     }
2901 
2902     CleanupStream(stream, rv, CANCEL_ERROR);
2903     if (SoftStreamError(rv)) {
2904       LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
2905       *again = false;
2906       SetWriteCallbacks();
2907       rv = NS_OK;
2908     }
2909     return rv;
2910   }
2911 
2912   if (*countRead > 0) {
2913     LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d", this, stream,
2914           *countRead));
2915     mReadyForWrite.Push(stream);
2916     SetWriteCallbacks();
2917     return rv;
2918   }
2919 
2920   if (stream->BlockedOnRwin()) {
2921     LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
2922           this, stream, stream->StreamID()));
2923     return NS_BASE_STREAM_WOULD_BLOCK;
2924   }
2925 
2926   LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", this,
2927         stream));
2928 
2929   // call readsegments again if there are other streams ready
2930   // to go in this session
2931   SetWriteCallbacks();
2932 
2933   return rv;
2934 }
2935 
ReadSegments(nsAHttpSegmentReader * reader,uint32_t count,uint32_t * countRead)2936 nsresult Http2Session::ReadSegments(nsAHttpSegmentReader *reader,
2937                                     uint32_t count, uint32_t *countRead) {
2938   bool again = false;
2939   return ReadSegmentsAgain(reader, count, countRead, &again);
2940 }
2941 
ReadyToProcessDataFrame(enum internalStateType newState)2942 nsresult Http2Session::ReadyToProcessDataFrame(
2943     enum internalStateType newState) {
2944   MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
2945              newState == DISCARDING_DATA_FRAME_PADDING);
2946   ChangeDownstreamState(newState);
2947 
2948   Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mInputFrameDataSize >> 10);
2949   mLastDataReadEpoch = mLastReadEpoch;
2950 
2951   if (!mInputFrameID) {
2952     LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
2953           this));
2954     RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
2955   }
2956 
2957   nsresult rv = SetInputFrameDataStream(mInputFrameID);
2958   if (NS_FAILED(rv)) {
2959     LOG3(
2960         ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
2961          "failed. probably due to verification.\n",
2962          this, mInputFrameID));
2963     return rv;
2964   }
2965   if (!mInputFrameDataStream) {
2966     LOG3(
2967         ("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
2968          "failed. Next = 0x%X",
2969          this, mInputFrameID, mNextStreamID));
2970     if (mInputFrameID >= mNextStreamID)
2971       GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
2972     ChangeDownstreamState(DISCARDING_DATA_FRAME);
2973   } else if (mInputFrameDataStream->RecvdFin() ||
2974              mInputFrameDataStream->RecvdReset() ||
2975              mInputFrameDataStream->SentReset()) {
2976     LOG3(
2977         ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
2978          "Data arrived for already server closed stream.\n",
2979          this, mInputFrameID));
2980     if (mInputFrameDataStream->RecvdFin() ||
2981         mInputFrameDataStream->RecvdReset())
2982       GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
2983     ChangeDownstreamState(DISCARDING_DATA_FRAME);
2984   } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
2985     // Only if non-final because the stream properly handles final frames of any
2986     // size, and we want the stream to be able to notice its own end flag.
2987     LOG3(
2988         ("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
2989          "Ignoring 0-length non-terminal data frame.",
2990          this, mInputFrameID));
2991     ChangeDownstreamState(DISCARDING_DATA_FRAME);
2992   }
2993 
2994   LOG3(
2995       ("Start Processing Data Frame. "
2996        "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
2997        this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
2998        mInputFrameDataSize));
2999   UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
3000 
3001   if (mInputFrameDataStream) {
3002     mInputFrameDataStream->SetRecvdData(true);
3003   }
3004 
3005   return NS_OK;
3006 }
3007 
3008 // WriteSegments() is used to read data off the socket. Generally this is
3009 // just the http2 frame header and from there the appropriate *Stream
3010 // is identified from the Stream-ID. The http transaction associated with
3011 // that read then pulls in the data directly, which it will feed to
3012 // OnWriteSegment(). That function will gateway it into http and feed
3013 // it to the appropriate transaction.
3014 
3015 // we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
3016 // and decide if it is data or control.. if it is control, just deal with it.
3017 // if it is data, identify the stream
3018 // call stream->WriteSegments which can call this::OnWriteSegment to get the
3019 // data. It always gets full frames if they are part of the stream
3020 
WriteSegmentsAgain(nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten,bool * again)3021 nsresult Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter *writer,
3022                                           uint32_t count,
3023                                           uint32_t *countWritten, bool *again) {
3024   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3025 
3026   LOG3(("Http2Session::WriteSegments %p InternalState %X\n", this,
3027         mDownstreamState));
3028 
3029   *countWritten = 0;
3030 
3031   if (mClosed) return NS_ERROR_FAILURE;
3032 
3033   nsresult rv = ConfirmTLSProfile();
3034   if (NS_FAILED(rv)) return rv;
3035 
3036   SetWriteCallbacks();
3037 
3038   // If there are http transactions attached to a push stream with filled
3039   // buffers trigger that data pump here. This only reads from buffers (not the
3040   // network) so mDownstreamState doesn't matter.
3041   Http2Stream *pushConnectedStream =
3042       static_cast<Http2Stream *>(mPushesReadyForRead.PopFront());
3043   if (pushConnectedStream) {
3044     return ProcessConnectedPush(pushConnectedStream, writer, count,
3045                                 countWritten);
3046   }
3047 
3048   // feed gecko channels that previously stopped consuming data
3049   // only take data from stored buffers
3050   Http2Stream *slowConsumer =
3051       static_cast<Http2Stream *>(mSlowConsumersReadyForRead.PopFront());
3052   if (slowConsumer) {
3053     internalStateType savedState = mDownstreamState;
3054     mDownstreamState = NOT_USING_NETWORK;
3055     rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
3056     mDownstreamState = savedState;
3057     return rv;
3058   }
3059 
3060   // The BUFFERING_OPENING_SETTINGS state is just like any
3061   // BUFFERING_FRAME_HEADER except the only frame type it will allow is SETTINGS
3062 
3063   // The session layer buffers the leading 8 byte header of every frame.
3064   // Non-Data frames are then buffered for their full length, but data
3065   // frames (type 0) are passed through to the http stack unprocessed
3066 
3067   if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
3068       mDownstreamState == BUFFERING_FRAME_HEADER) {
3069     // The first 9 bytes of every frame is header information that
3070     // we are going to want to strip before passing to http. That is
3071     // true of both control and data packets.
3072 
3073     MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
3074                "Frame Buffer Used Too Large for State");
3075 
3076     rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
3077                      kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);
3078 
3079     if (NS_FAILED(rv)) {
3080       LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n",
3081             this, static_cast<uint32_t>(rv)));
3082       // maybe just blocked reading from network
3083       if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
3084       return rv;
3085     }
3086 
3087     LogIO(this, nullptr, "Reading Frame Header",
3088           &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
3089 
3090     mInputFrameBufferUsed += *countWritten;
3091 
3092     if (mInputFrameBufferUsed < kFrameHeaderBytes) {
3093       LOG3(
3094           ("Http2Session::WriteSegments %p "
3095            "BUFFERING FRAME HEADER incomplete size=%d",
3096            this, mInputFrameBufferUsed));
3097       return rv;
3098     }
3099 
3100     // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
3101     uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
3102     mInputFrameDataSize =
3103         NetworkEndian::readUint16(mInputFrameBuffer.get() + 1);
3104     if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
3105       LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte,
3106             mInputFrameDataSize));
3107       RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3108     }
3109     mInputFrameType = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() +
3110                                                    kFrameLengthBytes);
3111     mInputFrameFlags = *reinterpret_cast<uint8_t *>(
3112         mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
3113     mInputFrameID =
3114         NetworkEndian::readUint32(mInputFrameBuffer.get() + kFrameLengthBytes +
3115                                   kFrameTypeBytes + kFrameFlagBytes);
3116     mInputFrameID &= 0x7fffffff;
3117     mInputFrameDataRead = 0;
3118 
3119     if (mInputFrameType == FRAME_TYPE_DATA ||
3120         mInputFrameType == FRAME_TYPE_HEADERS) {
3121       mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
3122     } else {
3123       mInputFrameFinal = 0;
3124     }
3125 
3126     mPaddingLength = 0;
3127 
3128     LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read "
3129           "type %X data len %u flags %x id 0x%X",
3130           this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
3131           mInputFrameID));
3132 
3133     // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION
3134     // of a HEADERS frame with a matching ID (section 6.2)
3135     if (mExpectedHeaderID && ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
3136                               (mExpectedHeaderID != mInputFrameID))) {
3137       LOG3(
3138           ("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
3139       RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3140     }
3141 
3142     // if mExpectedPushPromiseID is non 0, it means this frame must be a
3143     // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
3144     if (mExpectedPushPromiseID &&
3145         ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
3146          (mExpectedPushPromiseID != mInputFrameID))) {
3147       LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
3148             mExpectedPushPromiseID));
3149       RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3150     }
3151 
3152     if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
3153         mInputFrameType != FRAME_TYPE_SETTINGS) {
3154       LOG3(("First Frame Type Must Be Settings\n"));
3155       RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3156     }
3157 
3158     if (mInputFrameType != FRAME_TYPE_DATA) {  // control frame
3159       EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
3160                    kFrameHeaderBytes, mInputFrameBufferSize);
3161       ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
3162     } else if (mInputFrameFlags & kFlag_PADDED) {
3163       ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
3164     } else {
3165       rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
3166       if (NS_FAILED(rv)) {
3167         return rv;
3168       }
3169     }
3170   }
3171 
3172   if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
3173     MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
3174                "Processing padding control on unpadded frame");
3175 
3176     MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
3177                "Frame buffer used too large for state");
3178 
3179     rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
3180                      (kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
3181                      countWritten);
3182 
3183     if (NS_FAILED(rv)) {
3184       LOG3(
3185           ("Http2Session %p buffering data frame padding control read failure "
3186            "%" PRIx32 "\n",
3187            this, static_cast<uint32_t>(rv)));
3188       // maybe just blocked reading from network
3189       if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
3190       return rv;
3191     }
3192 
3193     LogIO(this, nullptr, "Reading Data Frame Padding Control",
3194           &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
3195 
3196     mInputFrameBufferUsed += *countWritten;
3197 
3198     if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
3199       LOG3(
3200           ("Http2Session::WriteSegments %p "
3201            "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
3202            this, mInputFrameBufferUsed - 8));
3203       return rv;
3204     }
3205 
3206     ++mInputFrameDataRead;
3207 
3208     char *control = &mInputFrameBuffer[kFrameHeaderBytes];
3209     mPaddingLength = static_cast<uint8_t>(*control);
3210 
3211     LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
3212           mInputFrameID, mPaddingLength));
3213 
3214     if (1U + mPaddingLength > mInputFrameDataSize) {
3215       LOG3(
3216           ("Http2Session::WriteSegments %p stream 0x%X padding too large for "
3217            "frame",
3218            this, mInputFrameID));
3219       RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3220     } else if (1U + mPaddingLength == mInputFrameDataSize) {
3221       // This frame consists entirely of padding, we can just discard it
3222       LOG3(
3223           ("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
3224            this, mInputFrameID));
3225       rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
3226       if (NS_FAILED(rv)) {
3227         return rv;
3228       }
3229     } else {
3230       LOG3(
3231           ("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
3232            this, mInputFrameID));
3233       rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
3234       if (NS_FAILED(rv)) {
3235         return rv;
3236       }
3237     }
3238   }
3239 
3240   if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
3241     nsresult streamCleanupCode;
3242 
3243     // There is no bounds checking on the error code.. we provide special
3244     // handling for a couple of cases and all others (including unknown) are
3245     // equivalent to cancel.
3246     if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
3247       streamCleanupCode = NS_ERROR_NET_RESET;  // can retry this 100% safely
3248       mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
3249     } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
3250       streamCleanupCode = NS_ERROR_NET_RESET;
3251       mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
3252       mInputFrameDataStream->Transaction()->DisableSpdy();
3253     } else {
3254       streamCleanupCode = mInputFrameDataStream->RecvdData()
3255                               ? NS_ERROR_NET_PARTIAL_TRANSFER
3256                               : NS_ERROR_NET_INTERRUPT;
3257     }
3258 
3259     if (mDownstreamRstReason == COMPRESSION_ERROR) {
3260       mShouldGoAway = true;
3261     }
3262 
3263     // mInputFrameDataStream is reset by ChangeDownstreamState
3264     Http2Stream *stream = mInputFrameDataStream;
3265     ResetDownstreamState();
3266     LOG3(
3267         ("Http2Session::WriteSegments cleanup stream on recv of rst "
3268          "session=%p stream=%p 0x%X\n",
3269          this, stream, stream ? stream->StreamID() : 0));
3270     CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
3271     return NS_OK;
3272   }
3273 
3274   if (mDownstreamState == PROCESSING_DATA_FRAME ||
3275       mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
3276     // The cleanup stream should only be set while stream->WriteSegments is
3277     // on the stack and then cleaned up in this code block afterwards.
3278     MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
3279     mNeedsCleanup = nullptr; /* just in case */
3280 
3281     uint32_t streamID = mInputFrameDataStream->StreamID();
3282     mSegmentWriter = writer;
3283     rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
3284     mSegmentWriter = nullptr;
3285 
3286     mLastDataReadEpoch = mLastReadEpoch;
3287 
3288     if (SoftStreamError(rv)) {
3289       // This will happen when the transaction figures out it is EOF, generally
3290       // due to a content-length match being made. Return OK from this function
3291       // otherwise the whole session would be torn down.
3292 
3293       // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
3294       // back to PROCESSING_DATA_FRAME where we came from
3295       mDownstreamState = PROCESSING_DATA_FRAME;
3296 
3297       if (mInputFrameDataRead == mInputFrameDataSize) ResetDownstreamState();
3298       LOG3(
3299           ("Http2Session::WriteSegments session=%p id 0x%X "
3300            "needscleanup=%p. cleanup stream based on "
3301            "stream->writeSegments returning code %" PRIx32 "\n",
3302            this, streamID, mNeedsCleanup, static_cast<uint32_t>(rv)));
3303       MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
3304       CleanupStream(
3305           streamID,
3306           (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK,
3307           CANCEL_ERROR);
3308       mNeedsCleanup = nullptr;
3309       *again = false;
3310       rv = ResumeRecv();
3311       if (NS_FAILED(rv)) {
3312         LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv)));
3313       }
3314       return NS_OK;
3315     }
3316 
3317     if (mNeedsCleanup) {
3318       LOG3(
3319           ("Http2Session::WriteSegments session=%p stream=%p 0x%X "
3320            "cleanup stream based on mNeedsCleanup.\n",
3321            this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
3322       CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
3323       mNeedsCleanup = nullptr;
3324     }
3325 
3326     if (NS_FAILED(rv)) {
3327       LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this,
3328             static_cast<uint32_t>(rv)));
3329       // maybe just blocked reading from network
3330       if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
3331     }
3332 
3333     return rv;
3334   }
3335 
3336   if (mDownstreamState == DISCARDING_DATA_FRAME ||
3337       mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
3338     char trash[4096];
3339     uint32_t discardCount =
3340         std::min(mInputFrameDataSize - mInputFrameDataRead, 4096U);
3341     LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s",
3342           this, discardCount,
3343           mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding"));
3344 
3345     if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
3346       // Only do this short-cirtuit if we're not discarding a pure padding
3347       // frame, as we need to potentially handle the stream FIN in those cases.
3348       // See bug 1381016 comment 36 for more details.
3349       ResetDownstreamState();
3350       Unused << ResumeRecv();
3351       return NS_BASE_STREAM_WOULD_BLOCK;
3352     }
3353 
3354     rv = NetworkRead(writer, trash, discardCount, countWritten);
3355 
3356     if (NS_FAILED(rv)) {
3357       LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this,
3358             static_cast<uint32_t>(rv)));
3359       // maybe just blocked reading from network
3360       if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
3361       return rv;
3362     }
3363 
3364     LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
3365 
3366     mInputFrameDataRead += *countWritten;
3367 
3368     if (mInputFrameDataRead == mInputFrameDataSize) {
3369       Http2Stream *streamToCleanup = nullptr;
3370       if (mInputFrameFinal) {
3371         streamToCleanup = mInputFrameDataStream;
3372       }
3373 
3374       bool discardedPadding =
3375           (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
3376       ResetDownstreamState();
3377 
3378       if (streamToCleanup) {
3379         if (discardedPadding && !(streamToCleanup->StreamID() & 1)) {
3380           // Pushed streams are special on padding-only final data frames.
3381           // See bug 1409570 comments 6-8 for details.
3382           streamToCleanup->SetPushComplete();
3383           Http2Stream *pushSink = streamToCleanup->GetConsumerStream();
3384           if (pushSink) {
3385             bool enqueueSink = true;
3386             for (auto iter = mPushesReadyForRead.begin();
3387                  iter != mPushesReadyForRead.end(); ++iter) {
3388               if (*iter == pushSink) {
3389                 enqueueSink = false;
3390                 break;
3391               }
3392             }
3393             if (enqueueSink) {
3394               mPushesReadyForRead.Push(pushSink);
3395               // No use trying to clean up, it won't do anything, anyway
3396               streamToCleanup = nullptr;
3397             }
3398           }
3399         }
3400         CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
3401       }
3402     }
3403     return rv;
3404   }
3405 
3406   if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
3407     MOZ_ASSERT(false);  // this cannot happen
3408     return NS_ERROR_UNEXPECTED;
3409   }
3410 
3411   MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes,
3412              "Frame Buffer Header Not Present");
3413   MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
3414              "allocation for control frame insufficient");
3415 
3416   rv = NetworkRead(writer,
3417                    &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
3418                    mInputFrameDataSize - mInputFrameDataRead, countWritten);
3419 
3420   if (NS_FAILED(rv)) {
3421     LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n",
3422           this, static_cast<uint32_t>(rv)));
3423     // maybe just blocked reading from network
3424     if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
3425     return rv;
3426   }
3427 
3428   LogIO(this, nullptr, "Reading Control Frame",
3429         &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
3430         *countWritten);
3431 
3432   mInputFrameDataRead += *countWritten;
3433 
3434   if (mInputFrameDataRead != mInputFrameDataSize) return NS_OK;
3435 
3436   MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
3437   if (mInputFrameType < FRAME_TYPE_LAST) {
3438     rv = sControlFunctions[mInputFrameType](this);
3439   } else {
3440     // Section 4.1 requires this to be ignored; though protocol_error would
3441     // be better
3442     LOG3(("Http2Session %p unknown frame type %x ignored\n", this,
3443           mInputFrameType));
3444     ResetDownstreamState();
3445     rv = NS_OK;
3446   }
3447 
3448   MOZ_ASSERT(NS_FAILED(rv) || mDownstreamState != BUFFERING_CONTROL_FRAME,
3449              "Control Handler returned OK but did not change state");
3450 
3451   if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
3452   return rv;
3453 }
3454 
WriteSegments(nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)3455 nsresult Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
3456                                      uint32_t count, uint32_t *countWritten) {
3457   bool again = false;
3458   return WriteSegmentsAgain(writer, count, countWritten, &again);
3459 }
3460 
Finish0RTT(bool aRestart,bool aAlpnChanged)3461 nsresult Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) {
3462   MOZ_ASSERT(mAttemptingEarlyData);
3463   LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
3464         aRestart, aAlpnChanged));
3465 
3466   for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
3467     if (m0RTTStreams[i]) {
3468       m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged);
3469     }
3470   }
3471 
3472   if (aRestart) {
3473     // 0RTT failed
3474     if (aAlpnChanged) {
3475       // This is a slightly more involved case - we need to get all our streams/
3476       // transactions back in the queue so they can restart as http/1
3477 
3478       // These must be set this way to ensure we gracefully restart all streams
3479       mGoAwayID = 0;
3480       mCleanShutdown = true;
3481 
3482       // Close takes care of the rest of our work for us. The reason code here
3483       // doesn't matter, as we aren't actually going to send a GOAWAY frame, but
3484       // we use NS_ERROR_NET_RESET as it's closest to the truth.
3485       Close(NS_ERROR_NET_RESET);
3486     } else {
3487       // This is the easy case - early data failed, but we're speaking h2, so
3488       // we just need to rewind to the beginning of the preamble and try again.
3489       mOutputQueueSent = 0;
3490 
3491       for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
3492         if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
3493           TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
3494         }
3495       }
3496     }
3497   } else {
3498     // 0RTT succeeded
3499     for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
3500       if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
3501         TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
3502       }
3503     }
3504     // Make sure we look for any incoming data in repsonse to our early data.
3505     Unused << ResumeRecv();
3506   }
3507 
3508   mAttemptingEarlyData = false;
3509   m0RTTStreams.Clear();
3510   mCannotDo0RTTStreams.Clear();
3511   RealignOutputQueue();
3512 
3513   return NS_OK;
3514 }
3515 
SetFastOpenStatus(uint8_t aStatus)3516 void Http2Session::SetFastOpenStatus(uint8_t aStatus) {
3517   LOG3(("Http2Session::SetFastOpenStatus %d [this=%p]", aStatus, this));
3518 
3519   for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
3520     if (m0RTTStreams[i]) {
3521       m0RTTStreams[i]->Transaction()->SetFastOpenStatus(aStatus);
3522     }
3523   }
3524 }
3525 
ProcessConnectedPush(Http2Stream * pushConnectedStream,nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)3526 nsresult Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream,
3527                                             nsAHttpSegmentWriter *writer,
3528                                             uint32_t count,
3529                                             uint32_t *countWritten) {
3530   LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n", this,
3531         pushConnectedStream->StreamID()));
3532   mSegmentWriter = writer;
3533   nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
3534   mSegmentWriter = nullptr;
3535 
3536   // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
3537   // so we need this check to determine the truth.
3538   if (NS_SUCCEEDED(rv) && !*countWritten && pushConnectedStream->PushSource() &&
3539       pushConnectedStream->PushSource()->GetPushComplete()) {
3540     rv = NS_BASE_STREAM_CLOSED;
3541   }
3542 
3543   if (rv == NS_BASE_STREAM_CLOSED) {
3544     CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
3545     rv = NS_OK;
3546   }
3547 
3548   // if we return OK to nsHttpConnection it will use mSocketInCondition
3549   // to determine whether to schedule more reads, incorrectly
3550   // assuming that nsHttpConnection::OnSocketWrite() was called.
3551   if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
3552     rv = NS_BASE_STREAM_WOULD_BLOCK;
3553     Unused << ResumeRecv();
3554   }
3555   return rv;
3556 }
3557 
ProcessSlowConsumer(Http2Stream * slowConsumer,nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)3558 nsresult Http2Session::ProcessSlowConsumer(Http2Stream *slowConsumer,
3559                                            nsAHttpSegmentWriter *writer,
3560                                            uint32_t count,
3561                                            uint32_t *countWritten) {
3562   LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n", this,
3563         slowConsumer->StreamID()));
3564   mSegmentWriter = writer;
3565   nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
3566   mSegmentWriter = nullptr;
3567   LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32
3568         " %d\n",
3569         this, slowConsumer->StreamID(), static_cast<uint32_t>(rv),
3570         *countWritten));
3571   if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
3572     rv = NS_BASE_STREAM_CLOSED;
3573   }
3574 
3575   if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
3576     // There have been buffered bytes successfully fed into the
3577     // formerly blocked consumer. Repeat until buffer empty or
3578     // consumer is blocked again.
3579     UpdateLocalRwin(slowConsumer, 0);
3580     ConnectSlowConsumer(slowConsumer);
3581   }
3582 
3583   if (rv == NS_BASE_STREAM_CLOSED) {
3584     CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
3585     rv = NS_OK;
3586   }
3587 
3588   return rv;
3589 }
3590 
UpdateLocalStreamWindow(Http2Stream * stream,uint32_t bytes)3591 void Http2Session::UpdateLocalStreamWindow(Http2Stream *stream,
3592                                            uint32_t bytes) {
3593   if (!stream)  // this is ok - it means there was a data frame for a rst stream
3594     return;
3595 
3596   // If this data packet was not for a valid or live stream then there
3597   // is no reason to mess with the flow control
3598   if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
3599       mInputFrameFinal) {
3600     return;
3601   }
3602 
3603   stream->DecrementClientReceiveWindow(bytes);
3604 
3605   // Don't necessarily ack every data packet. Only do it
3606   // after a significant amount of data.
3607   uint64_t unacked = stream->LocalUnAcked();
3608   int64_t localWindow = stream->ClientReceiveWindow();
3609 
3610   LOG3(
3611       ("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
3612        "unacked=%" PRIu64 " localWindow=%" PRId64 "\n",
3613        this, stream->StreamID(), bytes, unacked, localWindow));
3614 
3615   if (!unacked) return;
3616 
3617   if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
3618     return;
3619 
3620   if (!stream->HasSink()) {
3621     LOG3(
3622         ("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No "
3623          "Sink\n",
3624          this, stream->StreamID()));
3625     return;
3626   }
3627 
3628   // Generate window updates directly out of session instead of the stream
3629   // in order to avoid queue delays in getting the 'ACK' out.
3630   uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
3631 
3632   LOG3(
3633       ("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
3634        this, stream->StreamID(), toack));
3635   stream->IncrementClientReceiveWindow(toack);
3636   if (toack == 0) {
3637     // Ensure we never send an illegal 0 window update
3638     return;
3639   }
3640 
3641   // room for this packet needs to be ensured before calling this function
3642   char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
3643   mOutputQueueUsed += kFrameHeaderBytes + 4;
3644   MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
3645 
3646   CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
3647   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
3648 
3649   LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
3650   // dont flush here, this write can commonly be coalesced with a
3651   // session window update to immediately follow.
3652 }
3653 
UpdateLocalSessionWindow(uint32_t bytes)3654 void Http2Session::UpdateLocalSessionWindow(uint32_t bytes) {
3655   if (!bytes) return;
3656 
3657   mLocalSessionWindow -= bytes;
3658 
3659   LOG3(
3660       ("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
3661        "localWindow=%" PRId64 "\n",
3662        this, bytes, mLocalSessionWindow));
3663 
3664   // Don't necessarily ack every data packet. Only do it
3665   // after a significant amount of data.
3666   if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
3667       (mLocalSessionWindow > kEmergencyWindowThreshold))
3668     return;
3669 
3670   // Only send max  bits of window updates at a time.
3671   uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
3672   uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
3673 
3674   LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", this,
3675         toack));
3676   mLocalSessionWindow += toack;
3677 
3678   if (toack == 0) {
3679     // Ensure we never send an illegal 0 window update
3680     return;
3681   }
3682 
3683   // room for this packet needs to be ensured before calling this function
3684   char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
3685   mOutputQueueUsed += kFrameHeaderBytes + 4;
3686   MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
3687 
3688   CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
3689   NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
3690 
3691   LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
3692   // dont flush here, this write can commonly be coalesced with others
3693 }
3694 
UpdateLocalRwin(Http2Stream * stream,uint32_t bytes)3695 void Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes) {
3696   // make sure there is room for 2 window updates even though
3697   // we may not generate any.
3698   EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));
3699 
3700   UpdateLocalStreamWindow(stream, bytes);
3701   UpdateLocalSessionWindow(bytes);
3702   FlushOutputQueue();
3703 }
3704 
Close(nsresult aReason)3705 void Http2Session::Close(nsresult aReason) {
3706   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3707 
3708   if (mClosed) return;
3709 
3710   LOG3(("Http2Session::Close %p %" PRIX32, this,
3711         static_cast<uint32_t>(aReason)));
3712 
3713   mClosed = true;
3714 
3715   Shutdown();
3716 
3717   mStreamIDHash.Clear();
3718   mStreamTransactionHash.Clear();
3719 
3720   uint32_t goAwayReason;
3721   if (mGoAwayReason != NO_HTTP_ERROR) {
3722     goAwayReason = mGoAwayReason;
3723   } else if (NS_SUCCEEDED(aReason)) {
3724     goAwayReason = NO_HTTP_ERROR;
3725   } else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
3726     goAwayReason = PROTOCOL_ERROR;
3727   } else {
3728     goAwayReason = INTERNAL_ERROR;
3729   }
3730   if (!mAttemptingEarlyData) {
3731     GenerateGoAway(goAwayReason);
3732   }
3733   mConnection = nullptr;
3734   mSegmentReader = nullptr;
3735   mSegmentWriter = nullptr;
3736 }
3737 
ConnectionInfo()3738 nsHttpConnectionInfo *Http2Session::ConnectionInfo() {
3739   RefPtr<nsHttpConnectionInfo> ci;
3740   GetConnectionInfo(getter_AddRefs(ci));
3741   return ci.get();
3742 }
3743 
CloseTransaction(nsAHttpTransaction * aTransaction,nsresult aResult)3744 void Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction,
3745                                     nsresult aResult) {
3746   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3747   LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction,
3748         static_cast<uint32_t>(aResult)));
3749 
3750   // Generally this arrives as a cancel event from the connection manager.
3751 
3752   // need to find the stream and call CleanupStream() on it.
3753   Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
3754   if (!stream) {
3755     LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.", this,
3756           aTransaction, static_cast<uint32_t>(aResult)));
3757     return;
3758   }
3759   LOG3(
3760       ("Http2Session::CloseTransaction probably a cancel. "
3761        "this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p",
3762        this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(),
3763        stream));
3764   CleanupStream(stream, aResult, CANCEL_ERROR);
3765   nsresult rv = ResumeRecv();
3766   if (NS_FAILED(rv)) {
3767     LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x",
3768           this, aTransaction, static_cast<uint32_t>(aResult),
3769           static_cast<uint32_t>(rv)));
3770   }
3771 }
3772 
3773 //-----------------------------------------------------------------------------
3774 // nsAHttpSegmentReader
3775 //-----------------------------------------------------------------------------
3776 
OnReadSegment(const char * buf,uint32_t count,uint32_t * countRead)3777 nsresult Http2Session::OnReadSegment(const char *buf, uint32_t count,
3778                                      uint32_t *countRead) {
3779   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3780   nsresult rv;
3781 
3782   // If we can release old queued data then we can try and write the new
3783   // data directly to the network without using the output queue at all
3784   if (mOutputQueueUsed) FlushOutputQueue();
3785 
3786   if (!mOutputQueueUsed && mSegmentReader) {
3787     // try and write directly without output queue
3788     rv = mSegmentReader->OnReadSegment(buf, count, countRead);
3789 
3790     if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
3791       *countRead = 0;
3792     } else if (NS_FAILED(rv)) {
3793       return rv;
3794     }
3795 
3796     if (*countRead < count) {
3797       uint32_t required = count - *countRead;
3798       // assuming a commitment() happened, this ensurebuffer is a nop
3799       // but just in case the queuesize is too small for the required data
3800       // call ensurebuffer().
3801       EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
3802       memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
3803       mOutputQueueUsed = required;
3804     }
3805 
3806     *countRead = count;
3807     return NS_OK;
3808   }
3809 
3810   // At this point we are going to buffer the new data in the output
3811   // queue if it fits. By coalescing multiple small submissions into one larger
3812   // buffer we can get larger writes out to the network later on.
3813 
3814   // This routine should not be allowed to fill up the output queue
3815   // all on its own - at least kQueueReserved bytes are always left
3816   // for other routines to use - but this is an all-or-nothing function,
3817   // so if it will not all fit just return WOULD_BLOCK
3818 
3819   if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
3820     return NS_BASE_STREAM_WOULD_BLOCK;
3821 
3822   memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
3823   mOutputQueueUsed += count;
3824   *countRead = count;
3825 
3826   FlushOutputQueue();
3827 
3828   return NS_OK;
3829 }
3830 
CommitToSegmentSize(uint32_t count,bool forceCommitment)3831 nsresult Http2Session::CommitToSegmentSize(uint32_t count,
3832                                            bool forceCommitment) {
3833   if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue();
3834 
3835   // would there be enough room to buffer this if needed?
3836   if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
3837     return NS_OK;
3838 
3839   // if we are using part of our buffers already, try again later unless
3840   // forceCommitment is set.
3841   if (mOutputQueueUsed && !forceCommitment) return NS_BASE_STREAM_WOULD_BLOCK;
3842 
3843   if (mOutputQueueUsed) {
3844     // normally we avoid the memmove of RealignOutputQueue, but we'll try
3845     // it if forceCommitment is set before growing the buffer.
3846     RealignOutputQueue();
3847 
3848     // is there enough room now?
3849     if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
3850       return NS_OK;
3851   }
3852 
3853   // resize the buffers as needed
3854   EnsureOutputBuffer(count + kQueueReserved);
3855 
3856   MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
3857              "buffer not as large as expected");
3858 
3859   return NS_OK;
3860 }
3861 
3862 //-----------------------------------------------------------------------------
3863 // nsAHttpSegmentWriter
3864 //-----------------------------------------------------------------------------
3865 
OnWriteSegment(char * buf,uint32_t count,uint32_t * countWritten)3866 nsresult Http2Session::OnWriteSegment(char *buf, uint32_t count,
3867                                       uint32_t *countWritten) {
3868   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3869   nsresult rv;
3870 
3871   if (!mSegmentWriter) {
3872     // the only way this could happen would be if Close() were called on the
3873     // stack with WriteSegments()
3874     return NS_ERROR_FAILURE;
3875   }
3876 
3877   if (mDownstreamState == NOT_USING_NETWORK ||
3878       mDownstreamState == BUFFERING_FRAME_HEADER ||
3879       mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
3880     return NS_BASE_STREAM_WOULD_BLOCK;
3881   }
3882 
3883   if (mDownstreamState == PROCESSING_DATA_FRAME) {
3884     if (mInputFrameFinal && mInputFrameDataRead == mInputFrameDataSize) {
3885       *countWritten = 0;
3886       SetNeedsCleanup();
3887       return NS_BASE_STREAM_CLOSED;
3888     }
3889 
3890     count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
3891     rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
3892     if (NS_FAILED(rv)) return rv;
3893 
3894     LogIO(this, mInputFrameDataStream, "Reading Data Frame", buf,
3895           *countWritten);
3896 
3897     mInputFrameDataRead += *countWritten;
3898     if (mPaddingLength &&
3899         (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
3900       // We are crossing from real HTTP data into the realm of padding. If
3901       // we've actually crossed the line, we need to munge countWritten for the
3902       // sake of goodness and sanity. No matter what, any future calls to
3903       // WriteSegments need to just discard data until we reach the end of this
3904       // frame.
3905       if (mInputFrameDataSize != mInputFrameDataRead) {
3906         // Only change state if we still have padding to read. If we don't do
3907         // this, we can end up hanging on frames that combine real data,
3908         // padding, and END_STREAM (see bug 1019921)
3909         ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
3910       }
3911       uint32_t paddingRead =
3912           mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
3913       LOG3(
3914           ("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
3915            "crossed from HTTP data into padding (%d of %d) countWritten=%d",
3916            this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
3917            paddingRead, mPaddingLength, *countWritten));
3918       *countWritten -= paddingRead;
3919       LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
3920             this, mInputFrameID, *countWritten));
3921     }
3922 
3923     mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
3924     if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
3925       ResetDownstreamState();
3926 
3927     return rv;
3928   }
3929 
3930   if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
3931     if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
3932         mInputFrameFinal) {
3933       *countWritten = 0;
3934       SetNeedsCleanup();
3935       return NS_BASE_STREAM_CLOSED;
3936     }
3937 
3938     count = std::min(
3939         count, mFlatHTTPResponseHeaders.Length() - mFlatHTTPResponseHeadersOut);
3940     memcpy(buf, mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
3941            count);
3942     mFlatHTTPResponseHeadersOut += count;
3943     *countWritten = count;
3944 
3945     if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
3946       if (!mInputFrameFinal) {
3947         // If more frames are expected in this stream, then reset the state so
3948         // they can be handled. Otherwise (e.g. a 0 length response with the fin
3949         // on the incoming headers) stay in PROCESSING_COMPLETE_HEADERS state so
3950         // the SetNeedsCleanup() code above can cleanup the stream.
3951         ResetDownstreamState();
3952       }
3953     }
3954 
3955     return NS_OK;
3956   }
3957 
3958   MOZ_ASSERT(false);
3959   return NS_ERROR_UNEXPECTED;
3960 }
3961 
SetNeedsCleanup()3962 void Http2Session::SetNeedsCleanup() {
3963   LOG3(
3964       ("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
3965        "stream %p 0x%X",
3966        this, mInputFrameDataStream, mInputFrameDataStream->StreamID()));
3967 
3968   // This will result in Close() being called
3969   MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
3970   mInputFrameDataStream->SetResponseIsComplete();
3971   mNeedsCleanup = mInputFrameDataStream;
3972   ResetDownstreamState();
3973 }
3974 
ConnectPushedStream(Http2Stream * stream)3975 void Http2Session::ConnectPushedStream(Http2Stream *stream) {
3976   mPushesReadyForRead.Push(stream);
3977   Unused << ForceRecv();
3978 }
3979 
ConnectSlowConsumer(Http2Stream * stream)3980 void Http2Session::ConnectSlowConsumer(Http2Stream *stream) {
3981   LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n", this,
3982         stream->StreamID()));
3983   mSlowConsumersReadyForRead.Push(stream);
3984   Unused << ForceRecv();
3985 }
3986 
FindTunnelCount(nsHttpConnectionInfo * aConnInfo)3987 uint32_t Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo) {
3988   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3989   uint32_t rv = 0;
3990   mTunnelHash.Get(aConnInfo->HashKey(), &rv);
3991   return rv;
3992 }
3993 
RegisterTunnel(Http2Stream * aTunnel)3994 void Http2Session::RegisterTunnel(Http2Stream *aTunnel) {
3995   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3996   nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
3997   uint32_t newcount = FindTunnelCount(ci) + 1;
3998   mTunnelHash.Remove(ci->HashKey());
3999   mTunnelHash.Put(ci->HashKey(), newcount);
4000   LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]", this,
4001         aTunnel, newcount, ci->HashKey().get()));
4002 }
4003 
UnRegisterTunnel(Http2Stream * aTunnel)4004 void Http2Session::UnRegisterTunnel(Http2Stream *aTunnel) {
4005   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4006   nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
4007   MOZ_ASSERT(FindTunnelCount(ci));
4008   uint32_t newcount = FindTunnelCount(ci) - 1;
4009   mTunnelHash.Remove(ci->HashKey());
4010   if (newcount) {
4011     mTunnelHash.Put(ci->HashKey(), newcount);
4012   }
4013   LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]", this,
4014         aTunnel, newcount, ci->HashKey().get()));
4015 }
4016 
CreateTunnel(nsHttpTransaction * trans,nsHttpConnectionInfo * ci,nsIInterfaceRequestor * aCallbacks)4017 void Http2Session::CreateTunnel(nsHttpTransaction *trans,
4018                                 nsHttpConnectionInfo *ci,
4019                                 nsIInterfaceRequestor *aCallbacks) {
4020   LOG(("Http2Session::CreateTunnel %p %p make new tunnel\n", this, trans));
4021   // The connect transaction will hold onto the underlying http
4022   // transaction so that an auth created by the connect can be mappped
4023   // to the correct security callbacks
4024 
4025   RefPtr<SpdyConnectTransaction> connectTrans =
4026       new SpdyConnectTransaction(ci, aCallbacks, trans->Caps(), trans, this);
4027   DebugOnly<bool> rv = AddStream(
4028       connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false, nullptr);
4029   MOZ_ASSERT(rv);
4030   Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
4031   MOZ_ASSERT(tunnel);
4032   RegisterTunnel(tunnel);
4033 }
4034 
DispatchOnTunnel(nsAHttpTransaction * aHttpTransaction,nsIInterfaceRequestor * aCallbacks)4035 void Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
4036                                     nsIInterfaceRequestor *aCallbacks) {
4037   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4038   nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
4039   nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
4040   MOZ_ASSERT(trans);
4041 
4042   LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
4043 
4044   aHttpTransaction->SetConnection(nullptr);
4045 
4046   // this transaction has done its work of setting up a tunnel, let
4047   // the connection manager queue it if necessary
4048   trans->SetTunnelProvider(this);
4049   trans->EnableKeepAlive();
4050 
4051   if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
4052     LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s", this,
4053           ci->HashKey().get()));
4054     CreateTunnel(trans, ci, aCallbacks);
4055   } else {
4056     // requeue it. The connection manager is responsible for actually putting
4057     // this on the tunnel connection with the specific ci. If that can't
4058     // happen the cmgr checks with us via MaybeReTunnel() to see if it should
4059     // make a new tunnel or just wait longer.
4060     LOG3(
4061         ("Http2Session::DispatchOnTunnel %p trans=%p queue in connection "
4062          "manager",
4063          this, trans));
4064     nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
4065     if (NS_FAILED(rv)) {
4066       LOG3(
4067           ("Http2Session::DispatchOnTunnel %p trans=%p failed to initiate "
4068            "transaction (%08x)",
4069            this, trans, static_cast<uint32_t>(rv)));
4070     }
4071   }
4072 }
4073 
4074 // From ASpdySession
MaybeReTunnel(nsAHttpTransaction * aHttpTransaction)4075 bool Http2Session::MaybeReTunnel(nsAHttpTransaction *aHttpTransaction) {
4076   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4077   nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
4078   LOG(("Http2Session::MaybeReTunnel %p trans=%p\n", this, trans));
4079   if (!trans || trans->TunnelProvider() != this) {
4080     // this isn't really one of our transactions.
4081     return false;
4082   }
4083 
4084   if (mClosed || mShouldGoAway) {
4085     LOG(("Http2Session::MaybeReTunnel %p %p session closed - requeue\n", this,
4086          trans));
4087     trans->SetTunnelProvider(nullptr);
4088     nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
4089     if (NS_FAILED(rv)) {
4090       LOG3(
4091           ("Http2Session::MaybeReTunnel %p trans=%p failed to initiate "
4092            "transaction (%08x)",
4093            this, trans, static_cast<uint32_t>(rv)));
4094     }
4095     return true;
4096   }
4097 
4098   nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
4099   LOG(("Http2Session:MaybeReTunnel %p %p count=%d limit %d\n", this, trans,
4100        FindTunnelCount(ci), gHttpHandler->MaxConnectionsPerOrigin()));
4101   if (FindTunnelCount(ci) >= gHttpHandler->MaxConnectionsPerOrigin()) {
4102     // patience - a tunnel will open up.
4103     return false;
4104   }
4105 
4106   LOG(("Http2Session::MaybeReTunnel %p %p make new tunnel\n", this, trans));
4107   CreateTunnel(trans, ci, trans->SecurityCallbacks());
4108   return true;
4109 }
4110 
BufferOutput(const char * buf,uint32_t count,uint32_t * countRead)4111 nsresult Http2Session::BufferOutput(const char *buf, uint32_t count,
4112                                     uint32_t *countRead) {
4113   nsAHttpSegmentReader *old = mSegmentReader;
4114   mSegmentReader = nullptr;
4115   nsresult rv = OnReadSegment(buf, count, countRead);
4116   mSegmentReader = old;
4117   return rv;
4118 }
4119 
4120 bool  // static
ALPNCallback(nsISupports * securityInfo)4121 Http2Session::ALPNCallback(nsISupports *securityInfo) {
4122   if (!gHttpHandler->IsH2MandatorySuiteEnabled()) {
4123     LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
4124     return false;
4125   }
4126 
4127   nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
4128   LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
4129   if (ssl) {
4130     int16_t version = ssl->GetSSLVersionOffered();
4131     LOG3(("Http2Session::ALPNCallback version=%x\n", version));
4132     if (version >= nsISSLSocketControl::TLS_VERSION_1_2) {
4133       return true;
4134     }
4135   }
4136   return false;
4137 }
4138 
ConfirmTLSProfile()4139 nsresult Http2Session::ConfirmTLSProfile() {
4140   if (mTLSProfileConfirmed) {
4141     return NS_OK;
4142   }
4143 
4144   LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this,
4145         mConnection.get()));
4146 
4147   if (mAttemptingEarlyData) {
4148     LOG3(
4149         ("Http2Session::ConfirmTLSProfile %p temporarily passing due to early "
4150          "data\n",
4151          this));
4152     return NS_OK;
4153   }
4154 
4155   if (!gHttpHandler->EnforceHttp2TlsProfile()) {
4156     LOG3(
4157         ("Http2Session::ConfirmTLSProfile %p passed due to configuration "
4158          "bypass\n",
4159          this));
4160     mTLSProfileConfirmed = true;
4161     return NS_OK;
4162   }
4163 
4164   if (!mConnection) return NS_ERROR_FAILURE;
4165 
4166   nsCOMPtr<nsISupports> securityInfo;
4167   mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
4168   nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
4169   LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this,
4170         ssl.get()));
4171   if (!ssl) return NS_ERROR_FAILURE;
4172 
4173   int16_t version = ssl->GetSSLVersionUsed();
4174   LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
4175   if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
4176     LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n",
4177           this));
4178     RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4179   }
4180 
4181   uint16_t kea = ssl->GetKEAUsed();
4182   if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
4183     LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
4184           this, kea));
4185     RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4186   }
4187 
4188   uint32_t keybits = ssl->GetKEAKeyBits();
4189   if (kea == ssl_kea_dh && keybits < 2048) {
4190     LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
4191           this, keybits));
4192     RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4193   } else if (kea == ssl_kea_ecdh && keybits < 224) {  // see rfc7540 9.2.1.
4194     LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
4195           this, keybits));
4196     RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4197   }
4198 
4199   int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
4200   LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n", this,
4201         macAlgorithm));
4202   if (macAlgorithm != nsISSLSocketControl::SSL_MAC_AEAD) {
4203     LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n",
4204           this));
4205     RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4206   }
4207 
4208   /* We are required to send SNI. We do that already, so no check is done
4209    * here to make sure we did. */
4210 
4211   /* We really should check to ensure TLS compression isn't enabled on
4212    * this connection. However, we never enable TLS compression on our end,
4213    * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
4214    * for the possibility for an interface to ensure it never gets turned on. */
4215 
4216   mTLSProfileConfirmed = true;
4217   return NS_OK;
4218 }
4219 
4220 //-----------------------------------------------------------------------------
4221 // Modified methods of nsAHttpConnection
4222 //-----------------------------------------------------------------------------
4223 
TransactionHasDataToWrite(nsAHttpTransaction * caller)4224 void Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller) {
4225   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4226   LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
4227 
4228   // a trapped signal from the http transaction to the connection that
4229   // it is no longer blocked on read.
4230 
4231   Http2Stream *stream = mStreamTransactionHash.Get(caller);
4232   if (!stream || !VerifyStream(stream)) {
4233     LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
4234           this, caller));
4235     return;
4236   }
4237 
4238   LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", this,
4239         stream->StreamID()));
4240 
4241   if (!mClosed) {
4242     mReadyForWrite.Push(stream);
4243     SetWriteCallbacks();
4244   } else {
4245     LOG3(
4246         ("Http2Session::TransactionHasDataToWrite %p closed so not setting "
4247          "Ready4Write\n",
4248          this));
4249   }
4250 
4251   // NSPR poll will not poll the network if there are non system PR_FileDesc's
4252   // that are ready - so we can get into a deadlock waiting for the system IO
4253   // to come back here if we don't force the send loop manually.
4254   Unused << ForceSend();
4255 }
4256 
TransactionHasDataToRecv(nsAHttpTransaction * caller)4257 void Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller) {
4258   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4259   LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
4260 
4261   // a signal from the http transaction to the connection that it will consume
4262   // more
4263   Http2Stream *stream = mStreamTransactionHash.Get(caller);
4264   if (!stream || !VerifyStream(stream)) {
4265     LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found", this,
4266           caller));
4267     return;
4268   }
4269 
4270   LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n", this,
4271         stream->StreamID()));
4272   ConnectSlowConsumer(stream);
4273 }
4274 
TransactionHasDataToWrite(Http2Stream * stream)4275 void Http2Session::TransactionHasDataToWrite(Http2Stream *stream) {
4276   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4277   LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", this,
4278         stream, stream->StreamID()));
4279 
4280   mReadyForWrite.Push(stream);
4281   SetWriteCallbacks();
4282   Unused << ForceSend();
4283 }
4284 
IsPersistent()4285 bool Http2Session::IsPersistent() { return true; }
4286 
TakeTransport(nsISocketTransport **,nsIAsyncInputStream **,nsIAsyncOutputStream **)4287 nsresult Http2Session::TakeTransport(nsISocketTransport **,
4288                                      nsIAsyncInputStream **,
4289                                      nsIAsyncOutputStream **) {
4290   MOZ_ASSERT(false, "TakeTransport of Http2Session");
4291   return NS_ERROR_UNEXPECTED;
4292 }
4293 
TakeHttpConnection()4294 already_AddRefed<nsHttpConnection> Http2Session::TakeHttpConnection() {
4295   MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
4296   return nullptr;
4297 }
4298 
HttpConnection()4299 already_AddRefed<nsHttpConnection> Http2Session::HttpConnection() {
4300   if (mConnection) {
4301     return mConnection->HttpConnection();
4302   }
4303   return nullptr;
4304 }
4305 
GetSecurityCallbacks(nsIInterfaceRequestor ** aOut)4306 void Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut) {
4307   *aOut = nullptr;
4308 }
4309 
4310 //-----------------------------------------------------------------------------
4311 // unused methods of nsAHttpTransaction
4312 // We can be sure of this because Http2Session is only constructed in
4313 // nsHttpConnection and is never passed out of that object or a
4314 // TLSFilterTransaction TLS tunnel
4315 //-----------------------------------------------------------------------------
4316 
SetConnection(nsAHttpConnection *)4317 void Http2Session::SetConnection(nsAHttpConnection *) {
4318   // This is unexpected
4319   MOZ_ASSERT(false, "Http2Session::SetConnection()");
4320 }
4321 
SetProxyConnectFailed()4322 void Http2Session::SetProxyConnectFailed() {
4323   MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
4324 }
4325 
IsDone()4326 bool Http2Session::IsDone() { return !mStreamTransactionHash.Count(); }
4327 
Status()4328 nsresult Http2Session::Status() {
4329   MOZ_ASSERT(false, "Http2Session::Status()");
4330   return NS_ERROR_UNEXPECTED;
4331 }
4332 
Caps()4333 uint32_t Http2Session::Caps() {
4334   MOZ_ASSERT(false, "Http2Session::Caps()");
4335   return 0;
4336 }
4337 
SetDNSWasRefreshed()4338 void Http2Session::SetDNSWasRefreshed() {
4339   MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
4340 }
4341 
RequestHead()4342 nsHttpRequestHead *Http2Session::RequestHead() {
4343   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4344   MOZ_ASSERT(false,
4345              "Http2Session::RequestHead() "
4346              "should not be called after http/2 is setup");
4347   return NULL;
4348 }
4349 
Http1xTransactionCount()4350 uint32_t Http2Session::Http1xTransactionCount() { return 0; }
4351 
TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction>> & outTransactions)4352 nsresult Http2Session::TakeSubTransactions(
4353     nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) {
4354   // Generally this cannot be done with http/2 as transactions are
4355   // started right away.
4356 
4357   LOG3(("Http2Session::TakeSubTransactions %p\n", this));
4358 
4359   if (mConcurrentHighWater > 0) return NS_ERROR_ALREADY_OPENED;
4360 
4361   LOG3(("   taking %d\n", mStreamTransactionHash.Count()));
4362 
4363   for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
4364     outTransactions.AppendElement(iter.Key());
4365 
4366     // Removing the stream from the hash will delete the stream and drop the
4367     // transaction reference the hash held.
4368     iter.Remove();
4369   }
4370   return NS_OK;
4371 }
4372 
4373 //-----------------------------------------------------------------------------
4374 // Pass through methods of nsAHttpConnection
4375 //-----------------------------------------------------------------------------
4376 
Connection()4377 nsAHttpConnection *Http2Session::Connection() {
4378   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4379   return mConnection;
4380 }
4381 
OnHeadersAvailable(nsAHttpTransaction * transaction,nsHttpRequestHead * requestHead,nsHttpResponseHead * responseHead,bool * reset)4382 nsresult Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction,
4383                                           nsHttpRequestHead *requestHead,
4384                                           nsHttpResponseHead *responseHead,
4385                                           bool *reset) {
4386   return mConnection->OnHeadersAvailable(transaction, requestHead, responseHead,
4387                                          reset);
4388 }
4389 
IsReused()4390 bool Http2Session::IsReused() { return mConnection->IsReused(); }
4391 
PushBack(const char * buf,uint32_t len)4392 nsresult Http2Session::PushBack(const char *buf, uint32_t len) {
4393   return mConnection->PushBack(buf, len);
4394 }
4395 
SendPing()4396 void Http2Session::SendPing() {
4397   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4398 
4399   if (mPreviousUsed) {
4400     // alredy in progress, get out
4401     return;
4402   }
4403 
4404   mPingSentEpoch = PR_IntervalNow();
4405   if (!mPingSentEpoch) {
4406     mPingSentEpoch = 1;  // avoid the 0 sentinel value
4407   }
4408   if (!mPingThreshold ||
4409       (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
4410     mPreviousPingThreshold = mPingThreshold;
4411     mPreviousUsed = true;
4412     mPingThreshold = gHttpHandler->NetworkChangedTimeout();
4413   }
4414   GeneratePing(false);
4415   Unused << ResumeRecv();
4416 }
4417 
TestOriginFrame(const nsACString & hostname,int32_t port)4418 bool Http2Session::TestOriginFrame(const nsACString &hostname, int32_t port) {
4419   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4420   MOZ_ASSERT(mOriginFrameActivated);
4421 
4422   nsAutoCString key(hostname);
4423   key.Append(':');
4424   key.AppendInt(port);
4425   bool rv = mOriginFrame.Get(key);
4426   LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv));
4427   if (!rv && ConnectionInfo()) {
4428     // the SNI is also implicitly in this list, so consult that too
4429     nsHttpConnectionInfo *ci = ConnectionInfo();
4430     rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
4431          (port == ci->OriginPort());
4432     LOG3(("TestOriginFrame() %p sni test %d\n", this, rv));
4433   }
4434   return rv;
4435 }
4436 
TestJoinConnection(const nsACString & hostname,int32_t port)4437 bool Http2Session::TestJoinConnection(const nsACString &hostname,
4438                                       int32_t port) {
4439   return RealJoinConnection(hostname, port, true);
4440 }
4441 
JoinConnection(const nsACString & hostname,int32_t port)4442 bool Http2Session::JoinConnection(const nsACString &hostname, int32_t port) {
4443   return RealJoinConnection(hostname, port, false);
4444 }
4445 
RealJoinConnection(const nsACString & hostname,int32_t port,bool justKidding)4446 bool Http2Session::RealJoinConnection(const nsACString &hostname, int32_t port,
4447                                       bool justKidding) {
4448   if (!mConnection || mClosed || mShouldGoAway) {
4449     return false;
4450   }
4451 
4452   nsHttpConnectionInfo *ci = ConnectionInfo();
4453   if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) &&
4454       (port == ci->OriginPort())) {
4455     return true;
4456   }
4457 
4458   if (!mReceivedSettings) {
4459     return false;
4460   }
4461 
4462   if (mOriginFrameActivated) {
4463     bool originFrameResult = TestOriginFrame(hostname, port);
4464     if (!originFrameResult) {
4465       return false;
4466     }
4467   } else {
4468     LOG3(("JoinConnection %p no origin frame check used.\n", this));
4469   }
4470 
4471   nsAutoCString key(hostname);
4472   key.Append(':');
4473   key.Append(justKidding ? 'k' : '.');
4474   key.AppendInt(port);
4475   bool cachedResult;
4476   if (mJoinConnectionCache.Get(key, &cachedResult)) {
4477     LOG(("joinconnection [%p %s] %s result=%d cache\n", this,
4478          ConnectionInfo()->HashKey().get(), key.get(), cachedResult));
4479     return cachedResult;
4480   }
4481 
4482   nsresult rv;
4483   bool isJoined = false;
4484 
4485   nsCOMPtr<nsISupports> securityInfo;
4486   nsCOMPtr<nsISSLSocketControl> sslSocketControl;
4487 
4488   mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
4489   sslSocketControl = do_QueryInterface(securityInfo, &rv);
4490   if (NS_FAILED(rv) || !sslSocketControl) {
4491     return false;
4492   }
4493 
4494   // try all the coalescable versions we support.
4495   const SpdyInformation *info = gHttpHandler->SpdyInfo();
4496   static_assert(SpdyInformation::kCount == 1, "assume 1 alpn version");
4497   bool joinedReturn = false;
4498   if (info->ProtocolEnabled(0)) {
4499     if (justKidding) {
4500       rv = sslSocketControl->TestJoinConnection(info->VersionString[0],
4501                                                 hostname, port, &isJoined);
4502     } else {
4503       rv = sslSocketControl->JoinConnection(info->VersionString[0], hostname,
4504                                             port, &isJoined);
4505     }
4506     if (NS_SUCCEEDED(rv) && isJoined) {
4507       joinedReturn = true;
4508     }
4509   }
4510 
4511   LOG(("joinconnection [%p %s] %s result=%d lookup\n", this,
4512        ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
4513   mJoinConnectionCache.Put(key, joinedReturn);
4514   if (!justKidding) {
4515     // cache a kidding entry too as this one is good for both
4516     nsAutoCString key2(hostname);
4517     key2.Append(':');
4518     key2.Append('k');
4519     key2.AppendInt(port);
4520     if (!mJoinConnectionCache.Get(key2)) {
4521       mJoinConnectionCache.Put(key2, joinedReturn);
4522     }
4523   }
4524   return joinedReturn;
4525 }
4526 
TopLevelOuterContentWindowIdChanged(uint64_t windowId)4527 void Http2Session::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
4528   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4529 
4530   mCurrentForegroundTabOuterContentWindowId = windowId;
4531 
4532   for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
4533     iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
4534   }
4535 }
4536 
4537 }  // namespace net
4538 }  // namespace mozilla
4539