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, ¬Used);
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