Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
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/. */
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
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()
16 #include <algorithm>
18 #include "Http2Session.h"
19 #include "Http2Stream.h"
20 #include "Http2Push.h"
22 #include "mozilla/Telemetry.h"
23 #include "mozilla/Preferences.h"
24 #include "nsHttp.h"
25 #include "nsHttpHandler.h"
26 #include "nsHttpConnection.h"
27 #include "nsILoadGroup.h"
28 #include "nsISSLSocketControl.h"
29 #include "nsISSLStatus.h"
30 #include "nsISSLStatusProvider.h"
31 #include "prprf.h"
32 #include "prnetdb.h"
33 #include "sslt.h"
35 #ifdef DEBUG
36 // defined by the socket transport service while active
37 extern PRThread *gSocketThread;
38 #endif
40 namespace mozilla {
41 namespace net {
43 // Http2Session has multiple inheritance of things that implement
44 // nsISupports, so this magic is taken from nsHttpPipeline that
45 // implements some of the same abstract classes.
46 NS_IMPL_ADDREF(Http2Session)
47 NS_IMPL_RELEASE(Http2Session)
48 NS_INTERFACE_MAP_BEGIN(Http2Session)
49 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
50 NS_INTERFACE_MAP_END
52 // "magic" refers to the string that preceeds HTTP/2 on the wire
53 // to help find any intermediaries speaking an older version of HTTP
54 const uint8_t Http2Session::kMagicHello[] = {
55 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
56 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
57 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
58 };
60 #define RETURN_SESSION_ERROR(o,x) \
61 do { \
62 (o)->mGoAwayReason = (x); \
63 return NS_ERROR_ILLEGAL_VALUE; \
64 } while (0)
66 Http2Session::Http2Session(nsAHttpTransaction *aHttpTransaction,
67 nsISocketTransport *aSocketTransport,
68 int32_t firstPriority)
69 : mSocketTransport(aSocketTransport),
70 mSegmentReader(nullptr),
71 mSegmentWriter(nullptr),
72 mNextStreamID(3), // 1 is reserved for Updgrade handshakes
73 mConcurrentHighWater(0),
74 mDownstreamState(BUFFERING_OPENING_SETTINGS),
75 mInputFrameBufferSize(kDefaultBufferSize),
76 mInputFrameBufferUsed(0),
77 mInputFrameFinal(false),
78 mInputFrameDataStream(nullptr),
79 mNeedsCleanup(nullptr),
80 mDownstreamRstReason(NO_HTTP_ERROR),
81 mExpectedHeaderID(0),
82 mExpectedPushPromiseID(0),
83 mContinuedPromiseStream(0),
84 mShouldGoAway(false),
85 mClosed(false),
86 mCleanShutdown(false),
87 mTLSProfileConfirmed(false),
88 mGoAwayReason(NO_HTTP_ERROR),
89 mGoAwayID(0),
90 mOutgoingGoAwayID(0),
91 mMaxConcurrent(kDefaultMaxConcurrent),
92 mConcurrent(0),
93 mServerPushedResources(0),
94 mServerInitialStreamWindow(kDefaultRwin),
95 mLocalSessionWindow(kDefaultRwin),
96 mServerSessionWindow(kDefaultRwin),
97 mOutputQueueSize(kDefaultQueueSize),
98 mOutputQueueUsed(0),
99 mOutputQueueSent(0),
100 mLastReadEpoch(PR_IntervalNow()),
101 mPingSentEpoch(0)
102 {
103 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
105 static uint64_t sSerial;
106 mSerial = ++sSerial;
108 LOG3(("Http2Session::Http2Session %p transaction 1 = %p serial=0x%X\n",
109 this, aHttpTransaction, mSerial));
111 mConnection = aHttpTransaction->Connection();
112 mInputFrameBuffer = new char[mInputFrameBufferSize];
113 mOutputQueueBuffer = new char[mOutputQueueSize];
114 mDecompressBuffer.SetCapacity(kDefaultBufferSize);
115 mDecompressor.SetCompressor(&mCompressor);
117 mPushAllowance = gHttpHandler->SpdyPushAllowance();
119 mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
120 SendHello();
122 if (!aHttpTransaction->IsNullTransaction())
123 AddStream(aHttpTransaction, firstPriority);
124 mLastDataReadEpoch = mLastReadEpoch;
126 mPingThreshold = gHttpHandler->SpdyPingThreshold();
127 }
129 PLDHashOperator
130 Http2Session::ShutdownEnumerator(nsAHttpTransaction *key,
131 nsAutoPtr<Http2Stream> &stream,
132 void *closure)
133 {
134 Http2Session *self = static_cast<Http2Session *>(closure);
136 // On a clean server hangup the server sets the GoAwayID to be the ID of
137 // the last transaction it processed. If the ID of stream in the
138 // local stream is greater than that it can safely be restarted because the
139 // server guarantees it was not partially processed. Streams that have not
140 // registered an ID haven't actually been sent yet so they can always be
141 // restarted.
142 if (self->mCleanShutdown &&
143 (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID())) {
144 self->CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted
145 } else {
146 self->CloseStream(stream, NS_ERROR_ABORT);
147 }
149 return PL_DHASH_NEXT;
150 }
152 PLDHashOperator
153 Http2Session::GoAwayEnumerator(nsAHttpTransaction *key,
154 nsAutoPtr<Http2Stream> &stream,
155 void *closure)
156 {
157 Http2Session *self = static_cast<Http2Session *>(closure);
159 // these streams were not processed by the server and can be restarted.
160 // Do that after the enumerator completes to avoid the risk of
161 // a restart event re-entrantly modifying this hash. Be sure not to restart
162 // a pushed (even numbered) stream
163 if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
164 !stream->HasRegisteredID()) {
165 self->mGoAwayStreamsToRestart.Push(stream);
166 }
168 return PL_DHASH_NEXT;
169 }
171 Http2Session::~Http2Session()
172 {
173 LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
174 this, mDownstreamState));
176 mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
177 Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
178 Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
179 Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
180 mServerPushedResources);
181 }
183 void
184 Http2Session::LogIO(Http2Session *self, Http2Stream *stream,
185 const char *label,
186 const char *data, uint32_t datalen)
187 {
188 if (!LOG4_ENABLED())
189 return;
191 LOG4(("Http2Session::LogIO %p stream=%p id=0x%X [%s]",
192 self, stream, stream ? stream->StreamID() : 0, label));
194 // Max line is (16 * 3) + 10(prefix) + newline + null
195 char linebuf[128];
196 uint32_t index;
197 char *line = linebuf;
199 linebuf[127] = 0;
201 for (index = 0; index < datalen; ++index) {
202 if (!(index % 16)) {
203 if (index) {
204 *line = 0;
205 LOG4(("%s", linebuf));
206 }
207 line = linebuf;
208 PR_snprintf(line, 128, "%08X: ", index);
209 line += 10;
210 }
211 PR_snprintf(line, 128 - (line - linebuf), "%02X ",
212 (reinterpret_cast<const uint8_t *>(data))[index]);
213 line += 3;
214 }
215 if (index) {
216 *line = 0;
217 LOG4(("%s", linebuf));
218 }
219 }
221 typedef nsresult (*Http2ControlFx) (Http2Session *self);
222 static Http2ControlFx sControlFunctions[] = {
223 nullptr, // type 0 data is not a control function
224 Http2Session::RecvHeaders,
225 Http2Session::RecvPriority,
226 Http2Session::RecvRstStream,
227 Http2Session::RecvSettings,
228 Http2Session::RecvPushPromise,
229 Http2Session::RecvPing,
230 Http2Session::RecvGoAway,
231 Http2Session::RecvWindowUpdate,
232 Http2Session::RecvContinuation
233 };
235 bool
236 Http2Session::RoomForMoreConcurrent()
237 {
238 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
239 return (mConcurrent < mMaxConcurrent);
240 }
242 bool
243 Http2Session::RoomForMoreStreams()
244 {
245 if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
246 return false;
248 return !mShouldGoAway;
249 }
251 PRIntervalTime
252 Http2Session::IdleTime()
253 {
254 return PR_IntervalNow() - mLastDataReadEpoch;
255 }
257 uint32_t
258 Http2Session::ReadTimeoutTick(PRIntervalTime now)
259 {
260 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
262 LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
263 this, PR_IntervalToSeconds(now - mLastReadEpoch)));
265 if (!mPingThreshold)
266 return UINT32_MAX;
268 if ((now - mLastReadEpoch) < mPingThreshold) {
269 // recent activity means ping is not an issue
270 if (mPingSentEpoch)
271 mPingSentEpoch = 0;
273 return PR_IntervalToSeconds(mPingThreshold) -
274 PR_IntervalToSeconds(now - mLastReadEpoch);
275 }
277 if (mPingSentEpoch) {
278 LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n"));
279 if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
280 LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
281 mPingSentEpoch = 0;
282 Close(NS_ERROR_NET_TIMEOUT);
283 return UINT32_MAX;
284 }
285 return 1; // run the tick aggressively while ping is outstanding
286 }
288 LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
290 mPingSentEpoch = PR_IntervalNow();
291 if (!mPingSentEpoch)
292 mPingSentEpoch = 1; // avoid the 0 sentinel value
293 GeneratePing(false);
294 ResumeRecv(); // read the ping reply
296 // Check for orphaned push streams. This looks expensive, but generally the
297 // list is empty.
298 Http2PushedStream *deleteMe;
299 TimeStamp timestampNow;
300 do {
301 deleteMe = nullptr;
303 for (uint32_t index = mPushedStreams.Length();
304 index > 0 ; --index) {
305 Http2PushedStream *pushedStream = mPushedStreams[index - 1];
307 if (timestampNow.IsNull())
308 timestampNow = TimeStamp::Now(); // lazy initializer
310 // if stream finished, but is not connected, and its been like that for
311 // long then cleanup the stream.
312 if (pushedStream->IsOrphaned(timestampNow))
313 {
314 LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n",
315 this, pushedStream->StreamID()));
316 deleteMe = pushedStream;
317 break; // don't CleanupStream() while iterating this vector
318 }
319 }
320 if (deleteMe)
321 CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
323 } while (deleteMe);
325 return 1; // run the tick aggressively while ping is outstanding
326 }
328 uint32_t
329 Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID)
330 {
331 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
332 MOZ_ASSERT(mNextStreamID < 0xfffffff0,
333 "should have stopped admitting streams");
334 MOZ_ASSERT(!(aNewID & 1),
335 "0 for autoassign pull, otherwise explicit even push assignment");
337 if (!aNewID) {
338 // auto generate a new pull stream ID
339 aNewID = mNextStreamID;
340 MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
341 mNextStreamID += 2;
342 }
344 LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
345 "concurrent=%d",this, stream, aNewID, mConcurrent));
347 // We've used up plenty of ID's on this session. Start
348 // moving to a new one before there is a crunch involving
349 // server push streams or concurrent non-registered submits
350 if (aNewID >= kMaxStreamID)
351 mShouldGoAway = true;
353 // integrity check
354 if (mStreamIDHash.Get(aNewID)) {
355 LOG3((" New ID already present\n"));
356 MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
357 mShouldGoAway = true;
358 return kDeadStreamID;
359 }
361 mStreamIDHash.Put(aNewID, stream);
362 return aNewID;
363 }
365 bool
366 Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
367 int32_t aPriority)
368 {
369 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
371 // integrity check
372 if (mStreamTransactionHash.Get(aHttpTransaction)) {
373 LOG3((" New transaction already present\n"));
374 MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
375 return false;
376 }
378 aHttpTransaction->SetConnection(this);
379 Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
381 LOG3(("Http2Session::AddStream session=%p stream=%p NextID=0x%X (tentative)",
382 this, stream, mNextStreamID));
384 mStreamTransactionHash.Put(aHttpTransaction, stream);
386 if (RoomForMoreConcurrent()) {
387 LOG3(("Http2Session::AddStream %p stream %p activated immediately.",
388 this, stream));
389 ActivateStream(stream);
390 } else {
391 LOG3(("Http2Session::AddStream %p stream %p queued.", this, stream));
392 mQueuedStreams.Push(stream);
393 }
395 if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
396 LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
397 this, aHttpTransaction));
398 DontReuse();
399 }
401 return true;
402 }
404 void
405 Http2Session::ActivateStream(Http2Stream *stream)
406 {
407 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
408 MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
409 "Do not activate pushed streams");
411 MOZ_ASSERT(!stream->CountAsActive());
412 stream->SetCountAsActive(true);
413 ++mConcurrent;
415 if (mConcurrent > mConcurrentHighWater)
416 mConcurrentHighWater = mConcurrent;
417 LOG3(("Http2Session::AddStream %p activating stream %p Currently %d "
418 "streams in session, high water mark is %d",
419 this, stream, mConcurrent, mConcurrentHighWater));
421 mReadyForWrite.Push(stream);
422 SetWriteCallbacks();
424 // Kick off the headers transmit without waiting for the poll loop
425 // This won't work for stream id=1 because there is no segment reader
426 // yet.
427 if (mSegmentReader) {
428 uint32_t countRead;
429 ReadSegments(nullptr, kDefaultBufferSize, &countRead);
430 }
431 }
433 void
434 Http2Session::ProcessPending()
435 {
436 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
438 while (RoomForMoreConcurrent()) {
439 Http2Stream *stream = static_cast<Http2Stream *>(mQueuedStreams.PopFront());
440 if (!stream)
441 return;
442 LOG3(("Http2Session::ProcessPending %p stream %p activated from queue.",
443 this, stream));
444 ActivateStream(stream);
445 }
446 }
448 nsresult
449 Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
450 uint32_t count, uint32_t *countWritten)
451 {
452 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
454 if (!count) {
455 *countWritten = 0;
456 return NS_OK;
457 }
459 nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
460 if (NS_SUCCEEDED(rv) && *countWritten > 0)
461 mLastReadEpoch = PR_IntervalNow();
462 return rv;
463 }
465 void
466 Http2Session::SetWriteCallbacks()
467 {
468 if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
469 mConnection->ResumeSend();
470 }
472 void
473 Http2Session::RealignOutputQueue()
474 {
475 mOutputQueueUsed -= mOutputQueueSent;
476 memmove(mOutputQueueBuffer.get(),
477 mOutputQueueBuffer.get() + mOutputQueueSent,
478 mOutputQueueUsed);
479 mOutputQueueSent = 0;
480 }
482 void
483 Http2Session::FlushOutputQueue()
484 {
485 if (!mSegmentReader || !mOutputQueueUsed)
486 return;
488 nsresult rv;
489 uint32_t countRead;
490 uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
492 rv = mSegmentReader->
493 OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
494 &countRead);
495 LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d",
496 this, avail, rv, countRead));
498 // Dont worry about errors on write, we will pick this up as a read error too
499 if (NS_FAILED(rv))
500 return;
502 if (countRead == avail) {
503 mOutputQueueUsed = 0;
504 mOutputQueueSent = 0;
505 return;
506 }
508 mOutputQueueSent += countRead;
510 // If the output queue is close to filling up and we have sent out a good
511 // chunk of data from the beginning then realign it.
513 if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
514 ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
515 RealignOutputQueue();
516 }
517 }
519 void
520 Http2Session::DontReuse()
521 {
522 mShouldGoAway = true;
523 if (!mStreamTransactionHash.Count())
524 Close(NS_OK);
525 }
527 uint32_t
528 Http2Session::GetWriteQueueSize()
529 {
530 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
532 return mReadyForWrite.GetSize();
533 }
535 void
536 Http2Session::ChangeDownstreamState(enum internalStateType newState)
537 {
538 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
540 LOG3(("Http2Stream::ChangeDownstreamState() %p from %X to %X",
541 this, mDownstreamState, newState));
542 mDownstreamState = newState;
543 }
545 void
546 Http2Session::ResetDownstreamState()
547 {
548 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
550 LOG3(("Http2Stream::ResetDownstreamState() %p", this));
551 ChangeDownstreamState(BUFFERING_FRAME_HEADER);
553 if (mInputFrameFinal && mInputFrameDataStream) {
554 mInputFrameFinal = false;
555 LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
556 mInputFrameDataStream->SetRecvdFin(true);
557 MaybeDecrementConcurrent(mInputFrameDataStream);
558 }
559 mInputFrameBufferUsed = 0;
560 mInputFrameDataStream = nullptr;
561 }
563 template<typename T> void
564 Http2Session::EnsureBuffer(nsAutoArrayPtr<T> &buf, uint32_t newSize,
565 uint32_t preserve, uint32_t &objSize)
566 {
567 if (objSize >= newSize)
568 return;
570 // Leave a little slop on the new allocation - add 2KB to
571 // what we need and then round the result up to a 4KB (page)
572 // boundary.
574 objSize = (newSize + 2048 + 4095) & ~4095;
576 static_assert(sizeof(T) == 1, "sizeof(T) must be 1");
577 nsAutoArrayPtr<T> tmp(new T[objSize]);
578 memcpy(tmp, buf, preserve);
579 buf = tmp;
580 }
582 // Instantiate supported templates explicitly.
583 template void
584 Http2Session::EnsureBuffer(nsAutoArrayPtr<char> &buf, uint32_t newSize,
585 uint32_t preserve, uint32_t &objSize);
587 template void
588 Http2Session::EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf, uint32_t newSize,
589 uint32_t preserve, uint32_t &objSize);
591 // call with data length (i.e. 0 for 0 data bytes - ignore 8 byte header)
592 // dest must have 8 bytes of allocated space
593 template<typename charType> void
594 Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
595 uint8_t frameType, uint8_t frameFlags,
596 uint32_t streamID)
597 {
598 MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
599 MOZ_ASSERT(!(streamID & 0x80000000));
601 frameLength = PR_htons(frameLength);
602 streamID = PR_htonl(streamID);
604 memcpy(dest, &frameLength, 2);
605 dest[2] = frameType;
606 dest[3] = frameFlags;
607 memcpy(dest + 4, &streamID, 4);
608 }
610 char *
611 Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded)
612 {
613 // this is an infallible allocation (if an allocation is
614 // needed, which is probably isn't)
615 EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
616 mOutputQueueUsed, mOutputQueueSize);
617 return mOutputQueueBuffer.get() + mOutputQueueUsed;
618 }
620 template void
621 Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength,
622 uint8_t frameType, uint8_t frameFlags,
623 uint32_t streamID);
625 template void
626 Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength,
627 uint8_t frameType, uint8_t frameFlags,
628 uint32_t streamID);
630 void
631 Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream)
632 {
633 LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n",
634 this, aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
636 if (!aStream->CountAsActive())
637 return;
639 MOZ_ASSERT(mConcurrent);
640 aStream->SetCountAsActive(false);
641 --mConcurrent;
642 ProcessPending();
643 }
645 // Need to decompress some data in order to keep the compression
646 // context correct, but we really don't care what the result is
647 nsresult
648 Http2Session::UncompressAndDiscard()
649 {
650 nsresult rv;
651 nsAutoCString trash;
653 rv = mDecompressor.DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(mDecompressBuffer.BeginReading()),
654 mDecompressBuffer.Length(), trash);
655 mDecompressBuffer.Truncate();
656 if (NS_FAILED(rv)) {
657 LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n",
658 this));
659 mGoAwayReason = COMPRESSION_ERROR;
660 return rv;
661 }
662 return NS_OK;
663 }
665 void
666 Http2Session::GeneratePing(bool isAck)
667 {
668 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
669 LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
671 char *packet = EnsureOutputBuffer(16);
672 mOutputQueueUsed += 16;
674 if (isAck) {
675 CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
676 memcpy(packet + 8, mInputFrameBuffer.get() + 8, 8);
677 } else {
678 CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
679 memset(packet + 8, 0, 8);
680 }
682 LogIO(this, nullptr, "Generate Ping", packet, 16);
683 FlushOutputQueue();
684 }
686 void
687 Http2Session::GenerateSettingsAck()
688 {
689 // need to generate ack of this settings frame
690 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
691 LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
693 char *packet = EnsureOutputBuffer(8);
694 mOutputQueueUsed += 8;
695 CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
696 LogIO(this, nullptr, "Generate Settings ACK", packet, 8);
697 FlushOutputQueue();
698 }
700 void
701 Http2Session::GeneratePriority(uint32_t aID, uint32_t aPriority)
702 {
703 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
704 LOG3(("Http2Session::GeneratePriority %p %X %X\n",
705 this, aID, aPriority));
707 char *packet = EnsureOutputBuffer(12);
708 mOutputQueueUsed += 12;
710 CreateFrameHeader(packet, 4, FRAME_TYPE_PRIORITY, 0, aID);
711 aPriority = PR_htonl(aPriority);
712 memcpy(packet + 8, &aPriority, 4);
713 LogIO(this, nullptr, "Generate Priority", packet, 12);
714 FlushOutputQueue();
715 }
717 void
718 Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
719 {
720 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
722 // make sure we don't do this twice for the same stream (at least if we
723 // have a stream entry for it)
724 Http2Stream *stream = mStreamIDHash.Get(aID);
725 if (stream) {
726 if (stream->SentReset())
727 return;
728 stream->SetSentReset(true);
729 }
731 LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
733 char *packet = EnsureOutputBuffer(12);
734 mOutputQueueUsed += 12;
735 CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
737 aStatusCode = PR_htonl(aStatusCode);
738 memcpy(packet + 8, &aStatusCode, 4);
740 LogIO(this, nullptr, "Generate Reset", packet, 12);
741 FlushOutputQueue();
742 }
744 void
745 Http2Session::GenerateGoAway(uint32_t aStatusCode)
746 {
747 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
748 LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
750 char *packet = EnsureOutputBuffer(16);
751 mOutputQueueUsed += 16;
753 memset(packet + 8, 0, 8);
754 CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
756 // last-good-stream-id are bytes 8-11 reflecting pushes
757 uint32_t goAway = PR_htonl(mOutgoingGoAwayID);
758 memcpy (packet + 7, &goAway, 4);
760 // bytes 12-15 are the status code.
761 aStatusCode = PR_htonl(aStatusCode);
762 memcpy(packet + 12, &aStatusCode, 4);
764 LogIO(this, nullptr, "Generate GoAway", packet, 16);
765 FlushOutputQueue();
766 }
768 // The Hello is comprised of 24 octets of magic, which are designed to
769 // flush out silent but broken intermediaries, followed by a settings
770 // frame which sets a small flow control window for pushes and a
771 // window update frame which creates a large session flow control window
772 void
773 Http2Session::SendHello()
774 {
775 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
776 LOG3(("Http2Session::SendHello %p\n", this));
778 // sized for magic + 2 settings and a session window update to follow
779 // 24 magic, 23 for settings (8 header + 3 settings @5), 12 for window update
780 static const uint32_t maxSettings = 3;
781 static const uint32_t maxDataLen = 24 + 8 + maxSettings * 5 + 12;
782 char *packet = EnsureOutputBuffer(maxDataLen);
783 memcpy(packet, kMagicHello, 24);
784 mOutputQueueUsed += 24;
785 LogIO(this, nullptr, "Magic Connection Header", packet, 24);
787 packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
788 memset(packet, 0, maxDataLen - 24);
790 // frame header will be filled in after we know how long the frame is
791 uint8_t numberOfEntries = 0;
793 // entries need to be listed in order by ID
794 // 1st entry is bytes 8 to 12
795 // 2nd entry is bytes 13 to 17
796 // 3rd entry is bytes 18 to 22
798 if (!gHttpHandler->AllowPush()) {
799 // If we don't support push then set MAX_CONCURRENT to 0 and also
800 // set ENABLE_PUSH to 0
801 packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_ENABLE_PUSH;
802 // The value portion of the setting pair is already initialized to 0
803 numberOfEntries++;
805 packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT;
806 // The value portion of the setting pair is already initialized to 0
807 numberOfEntries++;
808 }
810 // Advertise the Push RWIN for the session, and on each new pull stream
811 // send a window update with END_FLOW_CONTROL
812 packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW;
813 uint32_t rwin = PR_htonl(mPushAllowance);
814 memcpy(packet + 9 + 5 * numberOfEntries, &rwin, 4);
815 numberOfEntries++;
817 MOZ_ASSERT(numberOfEntries <= maxSettings);
818 uint32_t dataLen = 5 * numberOfEntries;
819 CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
820 mOutputQueueUsed += 8 + dataLen;
822 LogIO(this, nullptr, "Generate Settings", packet, 8 + dataLen);
824 // now bump the local session window from 64KB
825 uint32_t sessionWindowBump = ASpdySession::kInitialRwin - kDefaultRwin;
826 if (kDefaultRwin >= ASpdySession::kInitialRwin)
827 goto sendHello_complete;
829 // send a window update for the session (Stream 0) for something large
830 sessionWindowBump = PR_htonl(sessionWindowBump);
831 mLocalSessionWindow = ASpdySession::kInitialRwin;
833 packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
834 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
835 mOutputQueueUsed += 12;
836 memcpy(packet + 8, &sessionWindowBump, 4);
838 LOG3(("Session Window increase at start of session %p %u\n",
839 this, PR_ntohl(sessionWindowBump)));
840 LogIO(this, nullptr, "Session Window Bump ", packet, 12);
842 sendHello_complete:
843 FlushOutputQueue();
844 }
846 // perform a bunch of integrity checks on the stream.
847 // returns true if passed, false (plus LOG and ABORT) if failed.
848 bool
849 Http2Session::VerifyStream(Http2Stream *aStream, uint32_t aOptionalID = 0)
850 {
851 // This is annoying, but at least it is O(1)
852 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
854 #ifndef DEBUG
855 // Only do the real verification in debug builds
856 return true;
857 #endif
859 if (!aStream)
860 return true;
862 uint32_t test = 0;
864 do {
865 if (aStream->StreamID() == kDeadStreamID)
866 break;
868 nsAHttpTransaction *trans = aStream->Transaction();
870 test++;
871 if (!trans)
872 break;
874 test++;
875 if (mStreamTransactionHash.Get(trans) != aStream)
876 break;
878 if (aStream->StreamID()) {
879 Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID());
881 test++;
882 if (idStream != aStream)
883 break;
885 if (aOptionalID) {
886 test++;
887 if (idStream->StreamID() != aOptionalID)
888 break;
889 }
890 }
892 // tests passed
893 return true;
894 } while (0);
896 LOG3(("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
897 "optionalID=0x%X trans=%p test=%d\n",
898 this, aStream, aStream->StreamID(),
899 aOptionalID, aStream->Transaction(), test));
901 MOZ_ASSERT(false, "VerifyStream");
902 return false;
903 }
905 void
906 Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
907 errorType aResetCode)
908 {
909 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
910 LOG3(("Http2Session::CleanupStream %p %p 0x%X %X\n",
911 this, aStream, aStream ? aStream->StreamID() : 0, aResult));
912 if (!aStream) {
913 return;
914 }
916 Http2PushedStream *pushSource = nullptr;
918 if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) {
919 LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
920 return;
921 }
923 if (!VerifyStream(aStream)) {
924 LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
925 return;
926 }
928 pushSource = aStream->PushSource();
930 if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID()) {
931 LOG3(("Stream had not processed recv FIN, sending RST code %X\n",
932 aResetCode));
933 GenerateRstStream(aResetCode, aStream->StreamID());
934 }
936 CloseStream(aStream, aResult);
938 // Remove the stream from the ID hash table and, if an even id, the pushed
939 // table too.
940 uint32_t id = aStream->StreamID();
941 if (id > 0) {
942 mStreamIDHash.Remove(id);
943 if (!(id & 1))
944 mPushedStreams.RemoveElement(aStream);
945 }
947 RemoveStreamFromQueues(aStream);
949 // removing from the stream transaction hash will
950 // delete the Http2Stream and drop the reference to
951 // its transaction
952 mStreamTransactionHash.Remove(aStream->Transaction());
954 if (mShouldGoAway && !mStreamTransactionHash.Count())
955 Close(NS_OK);
957 if (pushSource) {
958 pushSource->SetDeferCleanupOnSuccess(false);
959 CleanupStream(pushSource, aResult, aResetCode);
960 }
961 }
963 static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue)
964 {
965 uint32_t size = queue.GetSize();
966 for (uint32_t count = 0; count < size; ++count) {
967 Http2Stream *stream = static_cast<Http2Stream *>(queue.PopFront());
968 if (stream != aStream)
969 queue.Push(stream);
970 }
971 }
973 void
974 Http2Session::RemoveStreamFromQueues(Http2Stream *aStream)
975 {
976 RemoveStreamFromQueue(aStream, mReadyForWrite);
977 RemoveStreamFromQueue(aStream, mQueuedStreams);
978 RemoveStreamFromQueue(aStream, mReadyForRead);
979 }
981 void
982 Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
983 {
984 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
985 LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n",
986 this, aStream, aStream->StreamID(), aResult));
988 MaybeDecrementConcurrent(aStream);
990 // Check if partial frame reader
991 if (aStream == mInputFrameDataStream) {
992 LOG3(("Stream had active partial read frame on close"));
993 ChangeDownstreamState(DISCARDING_DATA_FRAME);
994 mInputFrameDataStream = nullptr;
995 }
997 RemoveStreamFromQueues(aStream);
999 // Send the stream the close() indication
1000 aStream->Close(aResult);
1001 }
1003 nsresult
1004 Http2Session::SetInputFrameDataStream(uint32_t streamID)
1005 {
1006 mInputFrameDataStream = mStreamIDHash.Get(streamID);
1007 if (VerifyStream(mInputFrameDataStream, streamID))
1008 return NS_OK;
1010 LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
1011 streamID));
1012 mInputFrameDataStream = nullptr;
1013 return NS_ERROR_UNEXPECTED;
1014 }
1016 nsresult
1017 Http2Session::ParsePadding(uint8_t &paddingControlBytes, uint16_t &paddingLength)
1018 {
1019 if (mInputFrameFlags & kFlag_PAD_HIGH) {
1020 uint8_t paddingHighValue = *reinterpret_cast<uint8_t *>(mInputFrameBuffer + 8);
1021 paddingLength = static_cast<uint16_t>(paddingHighValue) * 256;
1022 ++paddingControlBytes;
1023 }
1025 if (mInputFrameFlags & kFlag_PAD_LOW) {
1026 uint8_t paddingLowValue = *reinterpret_cast<uint8_t *>(mInputFrameBuffer + 8 + paddingControlBytes);
1027 paddingLength += paddingLowValue;
1028 ++paddingControlBytes;
1029 }
1031 if (paddingLength > mInputFrameDataSize) {
1032 // This is fatal to the session
1033 LOG3(("Http2Session::RecvHeaders %p stream 0x%x PROTOCOL_ERROR "
1034 "paddingLength %d > frame size %d\n",
1035 this, mInputFrameID, paddingLength, mInputFrameDataSize));
1036 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
1037 }
1039 return NS_OK;
1040 }
1042 nsresult
1043 Http2Session::RecvHeaders(Http2Session *self)
1044 {
1045 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS);
1047 // If this doesn't have END_HEADERS set on it then require the next
1048 // frame to be HEADERS of the same ID
1049 bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
1051 if (endHeadersFlag)
1052 self->mExpectedHeaderID = 0;
1053 else
1054 self->mExpectedHeaderID = self->mInputFrameID;
1056 uint32_t priorityLen = (self->mInputFrameFlags & kFlag_PRIORITY) ? 4 : 0;
1057 self->SetInputFrameDataStream(self->mInputFrameID);
1059 // Find out how much padding this frame has, so we can only extract the real
1060 // header data from the frame.
1061 uint16_t paddingLength = 0;
1062 uint8_t paddingControlBytes = 0;
1064 nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
1065 if (NS_FAILED(rv)) {
1066 return rv;
1067 }
1069 LOG3(("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
1070 "end_stream=%d end_headers=%d priority_flag=%d paddingLength=%d "
1071 "pad_high_flag=%d pad_low_flag=%d\n",
1072 self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
1073 self->mInputFrameFlags & kFlag_END_STREAM,
1074 self->mInputFrameFlags & kFlag_END_HEADERS,
1075 self->mInputFrameFlags & kFlag_PRIORITY,
1076 paddingLength,
1077 self->mInputFrameFlags & kFlag_PAD_HIGH,
1078 self->mInputFrameFlags & kFlag_PAD_LOW));
1080 if (!self->mInputFrameDataStream) {
1081 // Cannot find stream. We can continue the session, but we need to
1082 // uncompress the header block to maintain the correct compression context
1084 LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
1085 "0x%X failed. NextStreamID = 0x%X\n",
1086 self, self->mInputFrameID, self->mNextStreamID));
1088 if (self->mInputFrameID >= self->mNextStreamID)
1089 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
1091 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen,
1092 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
1094 if (self->mInputFrameFlags & kFlag_END_HEADERS) {
1095 rv = self->UncompressAndDiscard();
1096 if (NS_FAILED(rv)) {
1097 LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
1098 // this is fatal to the session
1099 self->mGoAwayReason = COMPRESSION_ERROR;
1100 return rv;
1101 }
1102 }
1104 self->ResetDownstreamState();
1105 return NS_OK;
1106 }
1108 // queue up any compression bytes
1109 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen,
1110 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
1112 self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
1113 self->mLastDataReadEpoch = self->mLastReadEpoch;
1115 if (!endHeadersFlag) { // more are coming - don't process yet
1116 self->ResetDownstreamState();
1117 return NS_OK;
1118 }
1120 rv = self->ResponseHeadersComplete();
1121 if (rv == NS_ERROR_ILLEGAL_VALUE) {
1122 LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
1123 self, self->mInputFrameID));
1124 self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
1125 self->ResetDownstreamState();
1126 rv = NS_OK;
1127 }
1128 return rv;
1129 }
1131 // ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
1132 // should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
1133 // fine, and any other error is fatal to the session.
1134 nsresult
1135 Http2Session::ResponseHeadersComplete()
1136 {
1137 LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d",
1138 this, mInputFrameDataStream->StreamID(), mInputFrameFinal));
1140 // only do this once, afterwards ignore trailers
1141 if (mInputFrameDataStream->AllHeadersReceived())
1142 return NS_OK;
1143 mInputFrameDataStream->SetAllHeadersReceived(true);
1145 // The stream needs to see flattened http headers
1146 // Uncompressed http/2 format headers currently live in
1147 // Http2Stream::mDecompressBuffer - convert that to HTTP format in
1148 // mFlatHTTPResponseHeaders via ConvertHeaders()
1150 mFlatHTTPResponseHeadersOut = 0;
1151 nsresult rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
1152 mDecompressBuffer,
1153 mFlatHTTPResponseHeaders);
1154 if (NS_FAILED(rv))
1155 return rv;
1157 ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
1158 return NS_OK;
1159 }
1161 nsresult
1162 Http2Session::RecvPriority(Http2Session *self)
1163 {
1164 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
1166 if (self->mInputFrameDataSize != 4) {
1167 LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n",
1168 self, self->mInputFrameDataSize));
1169 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1170 }
1172 if (!self->mInputFrameID) {
1173 LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
1174 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1175 }
1177 uint32_t newPriority =
1178 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
1179 newPriority &= 0x7fffffff;
1181 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
1182 if (NS_FAILED(rv))
1183 return rv;
1185 if (self->mInputFrameDataStream)
1186 self->mInputFrameDataStream->SetPriority(newPriority);
1187 self->ResetDownstreamState();
1188 return NS_OK;
1189 }
1191 nsresult
1192 Http2Session::RecvRstStream(Http2Session *self)
1193 {
1194 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
1196 if (self->mInputFrameDataSize != 4) {
1197 LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
1198 self, self->mInputFrameDataSize));
1199 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1200 }
1202 if (!self->mInputFrameID) {
1203 LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
1204 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1205 }
1207 self->mDownstreamRstReason =
1208 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
1210 LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
1211 self, self->mDownstreamRstReason, self->mInputFrameID));
1213 self->SetInputFrameDataStream(self->mInputFrameID);
1214 if (!self->mInputFrameDataStream) {
1215 // if we can't find the stream just ignore it (4.2 closed)
1216 self->ResetDownstreamState();
1217 return NS_OK;
1218 }
1220 self->mInputFrameDataStream->SetRecvdReset(true);
1221 self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
1222 self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
1223 return NS_OK;
1224 }
1226 PLDHashOperator
1227 Http2Session::UpdateServerRwinEnumerator(nsAHttpTransaction *key,
1228 nsAutoPtr<Http2Stream> &stream,
1229 void *closure)
1230 {
1231 int32_t delta = *(static_cast<int32_t *>(closure));
1232 stream->UpdateServerReceiveWindow(delta);
1233 return PL_DHASH_NEXT;
1234 }
1236 nsresult
1237 Http2Session::RecvSettings(Http2Session *self)
1238 {
1239 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
1241 if (self->mInputFrameID) {
1242 LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n",
1243 self, self->mInputFrameID));
1244 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1245 }
1247 if (self->mInputFrameDataSize % 5) {
1248 // Number of Settings is determined by dividing by each 5 byte setting
1249 // entry. So the payload must be a multiple of 5.
1250 LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d",
1251 self, self->mInputFrameDataSize));
1252 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1253 }
1255 uint32_t numEntries = self->mInputFrameDataSize / 5;
1256 LOG3(("Http2Session::RecvSettings %p SETTINGS Control Frame "
1257 "with %d entries ack=%X", self, numEntries,
1258 self->mInputFrameFlags & kFlag_ACK));
1260 if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
1261 LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n"));
1262 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1263 }
1265 for (uint32_t index = 0; index < numEntries; ++index) {
1266 uint8_t *setting = reinterpret_cast<uint8_t *>
1267 (self->mInputFrameBuffer.get()) + 8 + index * 5;
1269 uint8_t id = setting[0];
1270 uint32_t value = PR_ntohl(*reinterpret_cast<uint32_t *>(setting + 1));
1271 LOG3(("Settings ID %d, Value %d", id, value));
1273 switch (id)
1274 {
1275 case SETTINGS_TYPE_HEADER_TABLE_SIZE:
1276 LOG3(("Compression header table setting received: %d\n", value));
1277 self->mCompressor.SetMaxBufferSize(value);
1278 break;
1280 case SETTINGS_TYPE_ENABLE_PUSH:
1281 LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
1282 // nop
1283 break;
1285 case SETTINGS_TYPE_MAX_CONCURRENT:
1286 self->mMaxConcurrent = value;
1287 Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
1288 break;
1290 case SETTINGS_TYPE_INITIAL_WINDOW:
1291 {
1292 Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
1293 int32_t delta = value - self->mServerInitialStreamWindow;
1294 self->mServerInitialStreamWindow = value;
1296 // SETTINGS only adjusts stream windows. Leave the sesison window alone.
1297 // we need to add the delta to all open streams (delta can be negative)
1298 self->mStreamTransactionHash.Enumerate(UpdateServerRwinEnumerator,
1299 &delta);
1300 }
1301 break;
1303 default:
1304 break;
1305 }
1306 }
1308 self->ResetDownstreamState();
1310 if (!(self->mInputFrameFlags & kFlag_ACK))
1311 self->GenerateSettingsAck();
1313 return NS_OK;
1314 }
1316 nsresult
1317 Http2Session::RecvPushPromise(Http2Session *self)
1318 {
1319 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE);
1321 // Find out how much padding this frame has, so we can only extract the real
1322 // header data from the frame.
1323 uint16_t paddingLength = 0;
1324 uint8_t paddingControlBytes = 0;
1325 // TODO - will need to change this once PUSH_PROMISE allows padding
1326 // (post-draft10)
1327 // Right now, only CONTINUATION frames can have padding, and
1328 // mExpectedPushPromiseID being set indicates that we're actually processing a
1329 // CONTINUATION frame.
1330 if (self->mExpectedPushPromiseID) {
1331 nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
1332 if (NS_FAILED(rv)) {
1333 return rv;
1334 }
1335 }
1337 // If this doesn't have END_PUSH_PROMISE set on it then require the next
1338 // frame to be PUSH_PROMISE of the same ID
1339 uint32_t promiseLen;
1340 uint32_t promisedID;
1342 if (self->mExpectedPushPromiseID) {
1343 promiseLen = 0; // really a continuation frame
1344 promisedID = self->mContinuedPromiseStream;
1345 } else {
1346 // TODO - will need to handle padding here when getting the promisedID, once
1347 // PUSH_PROMISE allows padding (post-draft10)
1348 promiseLen = 4;
1349 promisedID =
1350 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
1351 promisedID &= 0x7fffffff;
1352 }
1354 uint32_t associatedID = self->mInputFrameID;
1356 if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
1357 self->mExpectedPushPromiseID = 0;
1358 self->mContinuedPromiseStream = 0;
1359 } else {
1360 self->mExpectedPushPromiseID = self->mInputFrameID;
1361 self->mContinuedPromiseStream = promisedID;
1362 }
1364 if (paddingLength > self->mInputFrameDataSize) {
1365 // This is fatal to the session
1366 LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
1367 "PROTOCOL_ERROR paddingLength %d > frame size %d\n",
1368 self, promisedID, associatedID, paddingLength,
1369 self->mInputFrameDataSize));
1370 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1371 }
1373 LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
1374 "paddingLength %d pad_high_flag %d pad_low_flag %d.\n",
1375 self, promisedID, associatedID, paddingLength,
1376 self->mInputFrameFlags & kFlag_PAD_HIGH,
1377 self->mInputFrameFlags & kFlag_PAD_LOW));
1379 if (!associatedID || !promisedID || (promisedID & 1)) {
1380 LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
1381 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1382 }
1384 // confirm associated-to
1385 nsresult rv = self->SetInputFrameDataStream(associatedID);
1386 if (NS_FAILED(rv))
1387 return rv;
1389 Http2Stream *associatedStream = self->mInputFrameDataStream;
1390 ++(self->mServerPushedResources);
1392 // Anytime we start using the high bit of stream ID (either client or server)
1393 // begin to migrate to a new session.
1394 if (promisedID >= kMaxStreamID)
1395 self->mShouldGoAway = true;
1397 bool resetStream = true;
1398 SpdyPushCache *cache = nullptr;
1400 if (self->mShouldGoAway) {
1401 LOG3(("Http2Session::RecvPushPromise %p push while in GoAway "
1402 "mode refused.\n", self));
1403 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1404 } else if (!gHttpHandler->AllowPush()) {
1405 // MAX_CONCURRENT_STREAMS of 0 in settings disabled push
1406 LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
1407 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1408 } else if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
1409 LOG3(("Http2Session::RecvPushPromise no support for multi frame push\n"));
1410 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1411 } else if (!associatedStream) {
1412 LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self));
1413 self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1414 } else {
1415 nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo();
1416 if (loadGroupCI) {
1417 loadGroupCI->GetSpdyPushCache(&cache);
1418 if (!cache) {
1419 cache = new SpdyPushCache();
1420 if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache(cache))) {
1421 delete cache;
1422 cache = nullptr;
1423 }
1424 }
1425 }
1426 if (!cache) {
1427 // this is unexpected, but we can handle it just by refusing the push
1428 LOG3(("Http2Session::RecvPushPromise Push Recevied without loadgroup cache\n"));
1429 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1430 } else {
1431 resetStream = false;
1432 }
1433 }
1435 if (resetStream) {
1436 // Need to decompress the headers even though we aren't using them yet in
1437 // order to keep the compression context consistent for other frames
1438 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + promiseLen,
1439 self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
1440 if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
1441 rv = self->UncompressAndDiscard();
1442 if (NS_FAILED(rv)) {
1443 LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
1444 self->mGoAwayReason = COMPRESSION_ERROR;
1445 return rv;
1446 }
1447 }
1448 self->ResetDownstreamState();
1449 return NS_OK;
1450 }
1452 // Create the buffering transaction and push stream
1453 nsRefPtr<Http2PushTransactionBuffer> transactionBuffer =
1454 new Http2PushTransactionBuffer();
1455 transactionBuffer->SetConnection(self);
1456 Http2PushedStream *pushedStream =
1457 new Http2PushedStream(transactionBuffer, self,
1458 associatedStream, promisedID);
1460 // Ownership of the pushed stream is by the transaction hash, just as it
1461 // is for a client initiated stream. Errors that aren't fatal to the
1462 // whole session must call cleanupStream() after this point in order
1463 // to remove the stream from that hash.
1464 self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
1465 self->mPushedStreams.AppendElement(pushedStream);
1467 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + promiseLen,
1468 self->mInputFrameDataSize - promiseLen);
1470 nsAutoCString requestHeaders;
1471 rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
1472 self->mDecompressBuffer, requestHeaders);
1474 if (rv == NS_ERROR_NOT_IMPLEMENTED) {
1475 LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
1476 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1477 return NS_OK;
1478 }
1480 if (NS_FAILED(rv))
1481 return rv;
1483 if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) {
1484 LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
1485 self->mGoAwayReason = INTERNAL_ERROR;
1486 return NS_ERROR_FAILURE;
1487 }
1489 if (promisedID > self->mOutgoingGoAwayID)
1490 self->mOutgoingGoAwayID = promisedID;
1492 // Fake the request side of the pushed HTTP transaction. Sets up hash
1493 // key and origin
1494 uint32_t notUsed;
1495 pushedStream->ReadSegments(nullptr, 1, ¬Used);
1497 nsAutoCString key;
1498 if (!pushedStream->GetHashKey(key)) {
1499 LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
1500 self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
1501 self->ResetDownstreamState();
1502 return NS_OK;
1503 }
1505 if (!associatedStream->Origin().Equals(pushedStream->Origin())) {
1506 LOG3(("Http2Session::RecvPushPromise pushed stream mismatched origin\n"));
1507 self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
1508 self->ResetDownstreamState();
1509 return NS_OK;
1510 }
1512 if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
1513 LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
1514 self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
1515 self->ResetDownstreamState();
1516 return NS_OK;
1517 }
1519 static_assert(Http2Stream::kWorstPriority >= 0,
1520 "kWorstPriority out of range");
1521 uint32_t unsignedPriority = static_cast<uint32_t>(Http2Stream::kWorstPriority);
1522 pushedStream->SetPriority(unsignedPriority);
1523 self->GeneratePriority(promisedID, unsignedPriority);
1524 self->ResetDownstreamState();
1525 return NS_OK;
1526 }
1528 nsresult
1529 Http2Session::RecvPing(Http2Session *self)
1530 {
1531 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
1533 LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
1534 self->mInputFrameFlags));
1536 if (self->mInputFrameDataSize != 8) {
1537 LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d",
1538 self, self->mInputFrameDataSize));
1539 RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
1540 }
1542 if (self->mInputFrameID) {
1543 LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
1544 self, self->mInputFrameID));
1545 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1546 }
1548 if (self->mInputFrameFlags & kFlag_ACK) {
1549 // presumably a reply to our timeout ping.. don't reply to it
1550 self->mPingSentEpoch = 0;
1551 } else {
1552 // reply with a ack'd ping
1553 self->GeneratePing(true);
1554 }
1556 self->ResetDownstreamState();
1557 return NS_OK;
1558 }
1560 nsresult
1561 Http2Session::RecvGoAway(Http2Session *self)
1562 {
1563 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
1565 if (self->mInputFrameDataSize < 8) {
1566 // data > 8 is an opaque token that we can't interpret. NSPR Logs will
1567 // have the hex of all packets so there is no point in separately logging.
1568 LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
1569 self, self->mInputFrameDataSize));
1570 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1571 }
1573 if (self->mInputFrameID) {
1574 LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
1575 self, self->mInputFrameID));
1576 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1577 }
1579 self->mShouldGoAway = true;
1580 self->mGoAwayID =
1581 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
1582 self->mGoAwayID &= 0x7fffffff;
1583 self->mCleanShutdown = true;
1584 uint32_t statusCode =
1585 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[3]);
1587 // Find streams greater than the last-good ID and mark them for deletion
1588 // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. The
1589 // underlying transaction can be restarted.
1590 self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self);
1592 // Process the streams marked for deletion and restart.
1593 uint32_t size = self->mGoAwayStreamsToRestart.GetSize();
1594 for (uint32_t count = 0; count < size; ++count) {
1595 Http2Stream *stream =
1596 static_cast<Http2Stream *>(self->mGoAwayStreamsToRestart.PopFront());
1598 self->CloseStream(stream, NS_ERROR_NET_RESET);
1599 if (stream->HasRegisteredID())
1600 self->mStreamIDHash.Remove(stream->StreamID());
1601 self->mStreamTransactionHash.Remove(stream->Transaction());
1602 }
1604 // Queued streams can also be deleted from this session and restarted
1605 // in another one. (they were never sent on the network so they implicitly
1606 // are not covered by the last-good id.
1607 size = self->mQueuedStreams.GetSize();
1608 for (uint32_t count = 0; count < size; ++count) {
1609 Http2Stream *stream =
1610 static_cast<Http2Stream *>(self->mQueuedStreams.PopFront());
1611 self->CloseStream(stream, NS_ERROR_NET_RESET);
1612 self->mStreamTransactionHash.Remove(stream->Transaction());
1613 }
1615 LOG3(("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
1616 "live streams=%d\n", self, self->mGoAwayID, statusCode,
1617 self->mStreamTransactionHash.Count()));
1619 self->ResetDownstreamState();
1620 return NS_OK;
1621 }
1623 PLDHashOperator
1624 Http2Session::RestartBlockedOnRwinEnumerator(nsAHttpTransaction *key,
1625 nsAutoPtr<Http2Stream> &stream,
1626 void *closure)
1627 {
1628 Http2Session *self = static_cast<Http2Session *>(closure);
1629 MOZ_ASSERT(self->mServerSessionWindow > 0);
1631 if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0)
1632 return PL_DHASH_NEXT;
1634 self->mReadyForWrite.Push(stream);
1635 self->SetWriteCallbacks();
1636 return PL_DHASH_NEXT;
1637 }
1639 nsresult
1640 Http2Session::RecvWindowUpdate(Http2Session *self)
1641 {
1642 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
1644 if (self->mInputFrameDataSize != 4) {
1645 LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
1646 self, self->mInputFrameDataSize));
1647 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1648 }
1650 uint32_t delta =
1651 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
1652 delta &= 0x7fffffff;
1654 LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n",
1655 self, delta, self->mInputFrameID));
1657 if (self->mInputFrameID) { // stream window
1658 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
1659 if (NS_FAILED(rv))
1660 return rv;
1662 if (!self->mInputFrameDataStream) {
1663 LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
1664 self, self->mInputFrameID));
1665 // only resest the session if the ID is one we haven't ever opened
1666 if (self->mInputFrameID >= self->mNextStreamID)
1667 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
1668 self->ResetDownstreamState();
1669 return NS_OK;
1670 }
1672 int64_t oldRemoteWindow = self->mInputFrameDataStream->ServerReceiveWindow();
1673 self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
1674 if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
1675 // a window cannot reach 2^31 and be in compliance. Our calculations
1676 // are 64 bit safe though.
1677 LOG3(("Http2Session::RecvWindowUpdate %p stream window "
1678 "exceeds 2^31 - 1\n", self));
1679 self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
1680 FLOW_CONTROL_ERROR);
1681 self->ResetDownstreamState();
1682 return NS_OK;
1683 }
1685 LOG3(("Http2Session::RecvWindowUpdate %p stream 0x%X window "
1686 "%d increased by %d now %d.\n", self, self->mInputFrameID,
1687 oldRemoteWindow, delta, oldRemoteWindow + delta));
1689 } else { // session window update
1690 int64_t oldRemoteWindow = self->mServerSessionWindow;
1691 self->mServerSessionWindow += delta;
1693 if (self->mServerSessionWindow >= 0x80000000) {
1694 // a window cannot reach 2^31 and be in compliance. Our calculations
1695 // are 64 bit safe though.
1696 LOG3(("Http2Session::RecvWindowUpdate %p session window "
1697 "exceeds 2^31 - 1\n", self));
1698 RETURN_SESSION_ERROR(self, FLOW_CONTROL_ERROR);
1699 }
1701 if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
1702 LOG3(("Http2Session::RecvWindowUpdate %p restart session window\n",
1703 self));
1704 self->mStreamTransactionHash.Enumerate(RestartBlockedOnRwinEnumerator, self);
1705 }
1706 LOG3(("Http2Session::RecvWindowUpdate %p session window "
1707 "%d increased by %d now %d.\n", self,
1708 oldRemoteWindow, delta, oldRemoteWindow + delta));
1709 }
1711 self->ResetDownstreamState();
1712 return NS_OK;
1713 }
1715 nsresult
1716 Http2Session::RecvContinuation(Http2Session *self)
1717 {
1718 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
1719 MOZ_ASSERT(self->mInputFrameID);
1720 MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
1721 MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
1723 LOG3(("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
1724 "promise id 0x%X header id 0x%X\n",
1725 self, self->mInputFrameFlags, self->mInputFrameID,
1726 self->mExpectedPushPromiseID, self->mExpectedHeaderID));
1728 self->SetInputFrameDataStream(self->mInputFrameID);
1730 if (!self->mInputFrameDataStream) {
1731 LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
1732 self->mInputFrameID));
1733 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1734 }
1736 // continued headers
1737 if (self->mExpectedHeaderID) {
1738 self->mInputFrameFlags &= ~kFlag_PRIORITY;
1739 return RecvHeaders(self);
1740 }
1742 // continued push promise
1743 if (self->mInputFrameFlags & kFlag_END_HEADERS) {
1744 self->mInputFrameFlags &= ~kFlag_END_HEADERS;
1745 self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
1746 }
1747 return RecvPushPromise(self);
1748 }
1750 //-----------------------------------------------------------------------------
1751 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
1752 // of these methods
1753 //-----------------------------------------------------------------------------
1755 void
1756 Http2Session::OnTransportStatus(nsITransport* aTransport,
1757 nsresult aStatus, uint64_t aProgress)
1758 {
1759 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1761 switch (aStatus) {
1762 // These should appear only once, deliver to the first
1763 // transaction on the session.
1764 case NS_NET_STATUS_RESOLVING_HOST:
1765 case NS_NET_STATUS_RESOLVED_HOST:
1766 case NS_NET_STATUS_CONNECTING_TO:
1767 case NS_NET_STATUS_CONNECTED_TO:
1768 {
1769 Http2Stream *target = mStreamIDHash.Get(1);
1770 nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
1771 if (transaction)
1772 transaction->OnTransportStatus(aTransport, aStatus, aProgress);
1773 break;
1774 }
1776 default:
1777 // The other transport events are ignored here because there is no good
1778 // way to map them to the right transaction in http/2. Instead, the events
1779 // are generated again from the http/2 code and passed directly to the
1780 // correct transaction.
1782 // NS_NET_STATUS_SENDING_TO:
1783 // This is generated by the socket transport when (part) of
1784 // a transaction is written out
1785 //
1786 // There is no good way to map it to the right transaction in http/2,
1787 // so it is ignored here and generated separately when the request
1788 // is sent from Http2Stream::TransmitFrame
1790 // NS_NET_STATUS_WAITING_FOR:
1791 // Created by nsHttpConnection when the request has been totally sent.
1792 // There is no good way to map it to the right transaction in http/2,
1793 // so it is ignored here and generated separately when the same
1794 // condition is complete in Http2Stream when there is no more
1795 // request body left to be transmitted.
1797 // NS_NET_STATUS_RECEIVING_FROM
1798 // Generated in session whenever we read a data frame or a HEADERS
1799 // that can be attributed to a particular stream/transaction
1801 break;
1802 }
1803 }
1805 // ReadSegments() is used to write data to the network. Generally, HTTP
1806 // request data is pulled from the approriate transaction and
1807 // converted to http/2 data. Sometimes control data like window-update are
1808 // generated instead.
1810 nsresult
1811 Http2Session::ReadSegments(nsAHttpSegmentReader *reader,
1812 uint32_t count, uint32_t *countRead)
1813 {
1814 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1816 MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
1817 "Inconsistent Write Function Callback");
1819 nsresult rv = ConfirmTLSProfile();
1820 if (NS_FAILED(rv))
1821 return rv;
1823 if (reader)
1824 mSegmentReader = reader;
1826 *countRead = 0;
1828 LOG3(("Http2Session::ReadSegments %p", this));
1830 Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
1831 if (!stream) {
1832 LOG3(("Http2Session %p could not identify a stream to write; suspending.",
1833 this));
1834 FlushOutputQueue();
1835 SetWriteCallbacks();
1836 return NS_BASE_STREAM_WOULD_BLOCK;
1837 }
1839 LOG3(("Http2Session %p will write from Http2Stream %p 0x%X "
1840 "block-input=%d block-output=%d\n", this, stream, stream->StreamID(),
1841 stream->RequestBlockedOnRead(), stream->BlockedOnRwin()));
1843 rv = stream->ReadSegments(this, count, countRead);
1845 // Not every permutation of stream->ReadSegents produces data (and therefore
1846 // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
1847 // of that. But we might still have old data buffered that would be good
1848 // to flush.
1849 FlushOutputQueue();
1851 // Allow new server reads - that might be data or control information
1852 // (e.g. window updates or http replies) that are responses to these writes
1853 ResumeRecv();
1855 if (stream->RequestBlockedOnRead()) {
1857 // We are blocked waiting for input - either more http headers or
1858 // any request body data. When more data from the request stream
1859 // becomes available the httptransaction will call conn->ResumeSend().
1861 LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
1863 // call readsegments again if there are other streams ready
1864 // to run in this session
1865 if (GetWriteQueueSize()) {
1866 rv = NS_OK;
1867 } else {
1868 rv = NS_BASE_STREAM_WOULD_BLOCK;
1869 }
1870 SetWriteCallbacks();
1871 return rv;
1872 }
1874 if (NS_FAILED(rv)) {
1875 LOG3(("Http2Session::ReadSegments %p returning FAIL code %X",
1876 this, rv));
1877 if (rv != NS_BASE_STREAM_WOULD_BLOCK)
1878 CleanupStream(stream, rv, CANCEL_ERROR);
1879 return rv;
1880 }
1882 if (*countRead > 0) {
1883 LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d",
1884 this, stream, *countRead));
1885 mReadyForWrite.Push(stream);
1886 SetWriteCallbacks();
1887 return rv;
1888 }
1890 if (stream->BlockedOnRwin()) {
1891 LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
1892 this, stream, stream->StreamID()));
1893 return NS_BASE_STREAM_WOULD_BLOCK;
1894 }
1896 LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete",
1897 this, stream));
1899 // call readsegments again if there are other streams ready
1900 // to go in this session
1901 SetWriteCallbacks();
1903 return rv;
1904 }
1906 nsresult
1907 Http2Session::ReadyToProcessDataFrame(enum internalStateType newState)
1908 {
1909 MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
1910 newState == DISCARDING_DATA_FRAME_PADDING);
1911 ChangeDownstreamState(newState);
1913 Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
1914 mInputFrameDataSize >> 10);
1915 mLastDataReadEpoch = mLastReadEpoch;
1917 if (!mInputFrameID) {
1918 LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
1919 this));
1920 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
1921 }
1923 nsresult rv = SetInputFrameDataStream(mInputFrameID);
1924 if (NS_FAILED(rv)) {
1925 LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
1926 "failed. probably due to verification.\n", this, mInputFrameID));
1927 return rv;
1928 }
1929 if (!mInputFrameDataStream) {
1930 LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
1931 "failed. Next = 0x%X", this, mInputFrameID, mNextStreamID));
1932 if (mInputFrameID >= mNextStreamID)
1933 GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
1934 ChangeDownstreamState(DISCARDING_DATA_FRAME);
1935 } else if (mInputFrameDataStream->RecvdFin() ||
1936 mInputFrameDataStream->RecvdReset() ||
1937 mInputFrameDataStream->SentReset()) {
1938 LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
1939 "Data arrived for already server closed stream.\n",
1940 this, mInputFrameID));
1941 if (mInputFrameDataStream->RecvdFin() || mInputFrameDataStream->RecvdReset())
1942 GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
1943 ChangeDownstreamState(DISCARDING_DATA_FRAME);
1944 }
1946 LOG3(("Start Processing Data Frame. "
1947 "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
1948 this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
1949 mInputFrameDataSize));
1950 UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
1952 return NS_OK;
1953 }
1955 // WriteSegments() is used to read data off the socket. Generally this is
1956 // just the http2 frame header and from there the appropriate *Stream
1957 // is identified from the Stream-ID. The http transaction associated with
1958 // that read then pulls in the data directly, which it will feed to
1959 // OnWriteSegment(). That function will gateway it into http and feed
1960 // it to the appropriate transaction.
1962 // we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
1963 // and decide if it is data or control.. if it is control, just deal with it.
1964 // if it is data, identify the stream
1965 // call stream->WriteSegments which can call this::OnWriteSegment to get the
1966 // data. It always gets full frames if they are part of the stream
1968 nsresult
1969 Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
1970 uint32_t count, uint32_t *countWritten)
1971 {
1972 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
1974 LOG3(("Http2Session::WriteSegments %p InternalState %X\n",
1975 this, mDownstreamState));
1977 *countWritten = 0;
1979 if (mClosed)
1980 return NS_ERROR_FAILURE;
1982 nsresult rv = ConfirmTLSProfile();
1983 if (NS_FAILED(rv))
1984 return rv;
1986 SetWriteCallbacks();
1988 // If there are http transactions attached to a push stream with filled buffers
1989 // trigger that data pump here. This only reads from buffers (not the network)
1990 // so mDownstreamState doesn't matter.
1991 Http2Stream *pushConnectedStream =
1992 static_cast<Http2Stream *>(mReadyForRead.PopFront());
1993 if (pushConnectedStream) {
1994 LOG3(("Http2Session::WriteSegments %p processing pushed stream 0x%X\n",
1995 this, pushConnectedStream->StreamID()));
1996 mSegmentWriter = writer;
1997 rv = pushConnectedStream->WriteSegments(this, count, countWritten);
1998 mSegmentWriter = nullptr;
2000 // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
2001 // so we need this check to determine the truth.
2002 if (NS_SUCCEEDED(rv) && !*countWritten &&
2003 pushConnectedStream->PushSource() &&
2004 pushConnectedStream->PushSource()->GetPushComplete()) {
2005 rv = NS_BASE_STREAM_CLOSED;
2006 }
2008 if (rv == NS_BASE_STREAM_CLOSED) {
2009 CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
2010 rv = NS_OK;
2011 }
2013 // if we return OK to nsHttpConnection it will use mSocketInCondition
2014 // to determine whether to schedule more reads, incorrectly
2015 // assuming that nsHttpConnection::OnSocketWrite() was called.
2016 if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
2017 rv = NS_BASE_STREAM_WOULD_BLOCK;
2018 ResumeRecv();
2019 }
2021 return rv;
2022 }
2024 // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER
2025 // except the only frame type it will allow is SETTINGS
2027 // The session layer buffers the leading 8 byte header of every frame.
2028 // Non-Data frames are then buffered for their full length, but data
2029 // frames (type 0) are passed through to the http stack unprocessed
2031 if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
2032 mDownstreamState == BUFFERING_FRAME_HEADER) {
2033 // The first 8 bytes of every frame is header information that
2034 // we are going to want to strip before passing to http. That is
2035 // true of both control and data packets.
2037 MOZ_ASSERT(mInputFrameBufferUsed < 8,
2038 "Frame Buffer Used Too Large for State");
2040 rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed,
2041 8 - mInputFrameBufferUsed, countWritten);
2043 if (NS_FAILED(rv)) {
2044 LOG3(("Http2Session %p buffering frame header read failure %x\n",
2045 this, rv));
2046 // maybe just blocked reading from network
2047 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
2048 rv = NS_OK;
2049 return rv;
2050 }
2052 LogIO(this, nullptr, "Reading Frame Header",
2053 mInputFrameBuffer + mInputFrameBufferUsed, *countWritten);
2055 mInputFrameBufferUsed += *countWritten;
2057 if (mInputFrameBufferUsed < 8)
2058 {
2059 LOG3(("Http2Session::WriteSegments %p "
2060 "BUFFERING FRAME HEADER incomplete size=%d",
2061 this, mInputFrameBufferUsed));
2062 return rv;
2063 }
2065 // 2 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
2066 mInputFrameDataSize =
2067 PR_ntohs(reinterpret_cast<uint16_t *>(mInputFrameBuffer.get())[0]);
2068 mInputFrameType = reinterpret_cast<uint8_t *>(mInputFrameBuffer.get())[2];
2069 mInputFrameFlags = reinterpret_cast<uint8_t *>(mInputFrameBuffer.get())[3];
2070 mInputFrameID =
2071 PR_ntohl(reinterpret_cast<uint32_t *>(mInputFrameBuffer.get())[1]);
2072 mInputFrameID &= 0x7fffffff;
2073 mInputFrameDataRead = 0;
2075 if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS) {
2076 mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
2077 } else {
2078 mInputFrameFinal = 0;
2079 }
2081 mPaddingLength = 0;
2082 if (mInputFrameType == FRAME_TYPE_DATA ||
2083 mInputFrameType == FRAME_TYPE_HEADERS ||
2084 // TODO: also mInputFrameType == FRAME_TYPE_PUSH_PROMISE after draft10
2085 mInputFrameType == FRAME_TYPE_CONTINUATION) {
2086 if ((mInputFrameFlags & kFlag_PAD_HIGH) &&
2087 !(mInputFrameFlags & kFlag_PAD_LOW)) {
2088 LOG3(("Http2Session::WriteSegments %p PROTOCOL_ERROR pad_high present "
2089 "without pad_low\n", this));
2090 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
2091 }
2092 }
2094 if (mInputFrameDataSize >= 0x4000) {
2095 // Section 9.1 HTTP frames cannot exceed 2^14 - 1 but receviers must ignore
2096 // those bits
2097 LOG3(("Http2Session::WriteSegments %p WARNING Frame Length bits past 14 are not 0 %08X\n",
2098 this, mInputFrameDataSize));
2099 mInputFrameDataSize &= 0x3fff;
2100 }
2102 LOG3(("Http2Session::WriteSegments[%p::%x] Frame Header Read "
2103 "type %X data len %u flags %x id 0x%X",
2104 this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
2105 mInputFrameID));
2107 // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION of
2108 // a HEADERS frame with a matching ID (section 6.2)
2109 if (mExpectedHeaderID &&
2110 ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
2111 (mExpectedHeaderID != mInputFrameID))) {
2112 LOG3(("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
2113 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
2114 }
2116 // if mExpectedPushPromiseID is non 0, it means this frame must be a
2117 // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
2118 if (mExpectedPushPromiseID &&
2119 ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
2120 (mExpectedPushPromiseID != mInputFrameID))) {
2121 LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
2122 mExpectedPushPromiseID));
2123 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
2124 }
2126 if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
2127 mInputFrameType != FRAME_TYPE_SETTINGS) {
2128 LOG3(("First Frame Type Must Be Settings\n"));
2129 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
2130 }
2132 if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
2133 EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + 8, 8,
2134 mInputFrameBufferSize);
2135 ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
2136 } else if (mInputFrameFlags & (kFlag_PAD_LOW | kFlag_PAD_HIGH)) {
2137 ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
2138 } else {
2139 rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
2140 if (NS_FAILED(rv)) {
2141 return rv;
2142 }
2143 }
2144 }
2146 if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
2147 uint32_t numControlBytes = 0;
2148 if (mInputFrameFlags & kFlag_PAD_LOW) {
2149 ++numControlBytes;
2150 }
2151 if (mInputFrameFlags & kFlag_PAD_HIGH) {
2152 ++numControlBytes;
2153 }
2155 MOZ_ASSERT(numControlBytes,
2156 "Processing padding control with no control bytes!");
2157 MOZ_ASSERT(mInputFrameBufferUsed < (8 + numControlBytes),
2158 "Frame buffer used too large for state");
2160 rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed,
2161 (8 + numControlBytes) - mInputFrameBufferUsed,
2162 countWritten);
2164 if (NS_FAILED(rv)) {
2165 LOG3(("Http2Session %p buffering data frame padding control read failure %x\n",
2166 this, rv));
2167 // maybe just blocked reading from network
2168 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
2169 rv = NS_OK;
2170 return rv;
2171 }
2173 LogIO(this, nullptr, "Reading Data Frame Padding Control",
2174 mInputFrameBuffer + mInputFrameBufferUsed, *countWritten);
2176 mInputFrameBufferUsed += *countWritten;
2178 if (mInputFrameBufferUsed - 8 < numControlBytes) {
2179 LOG3(("Http2Session::WriteSegments %p "
2180 "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
2181 this, mInputFrameBufferUsed - 8));
2182 return rv;
2183 }
2185 mInputFrameDataRead += numControlBytes;
2187 char *control = mInputFrameBuffer + 8;
2188 if (mInputFrameFlags & kFlag_PAD_HIGH) {
2189 mPaddingLength = static_cast<uint16_t>(*control) * 256;
2190 ++control;
2191 }
2192 mPaddingLength += static_cast<uint8_t>(*control);
2194 LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
2195 mInputFrameID, mPaddingLength));
2197 if (numControlBytes + mPaddingLength == mInputFrameDataSize) {
2198 // This frame consists entirely of padding, we can just discard it
2199 LOG3(("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
2200 this, mInputFrameID));
2201 rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
2202 if (NS_FAILED(rv)) {
2203 return rv;
2204 }
2205 } else {
2206 LOG3(("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
2207 this, mInputFrameID));
2208 rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
2209 if (NS_FAILED(rv)) {
2210 return rv;
2211 }
2212 }
2213 }
2215 if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
2216 nsresult streamCleanupCode;
2218 // There is no bounds checking on the error code.. we provide special
2219 // handling for a couple of cases and all others (including unknown) are
2220 // equivalent to cancel.
2221 if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
2222 streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely
2223 } else {
2224 streamCleanupCode = NS_ERROR_NET_INTERRUPT;
2225 }
2227 if (mDownstreamRstReason == COMPRESSION_ERROR)
2228 mShouldGoAway = true;
2230 // mInputFrameDataStream is reset by ChangeDownstreamState
2231 Http2Stream *stream = mInputFrameDataStream;
2232 ResetDownstreamState();
2233 LOG3(("Http2Session::WriteSegments cleanup stream on recv of rst "
2234 "session=%p stream=%p 0x%X\n", this, stream,
2235 stream ? stream->StreamID() : 0));
2236 CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
2237 return NS_OK;
2238 }
2240 if (mDownstreamState == PROCESSING_DATA_FRAME ||
2241 mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
2243 // The cleanup stream should only be set while stream->WriteSegments is
2244 // on the stack and then cleaned up in this code block afterwards.
2245 MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
2246 mNeedsCleanup = nullptr; /* just in case */
2248 mSegmentWriter = writer;
2249 rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
2250 mSegmentWriter = nullptr;
2252 mLastDataReadEpoch = mLastReadEpoch;
2254 if (SoftStreamError(rv)) {
2255 // This will happen when the transaction figures out it is EOF, generally
2256 // due to a content-length match being made. Return OK from this function
2257 // otherwise the whole session would be torn down.
2258 Http2Stream *stream = mInputFrameDataStream;
2260 // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
2261 // back to PROCESSING_DATA_FRAME where we came from
2262 mDownstreamState = PROCESSING_DATA_FRAME;
2264 if (mInputFrameDataRead == mInputFrameDataSize)
2265 ResetDownstreamState();
2266 LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
2267 "needscleanup=%p. cleanup stream based on "
2268 "stream->writeSegments returning code %x\n",
2269 this, stream, stream ? stream->StreamID() : 0,
2270 mNeedsCleanup, rv));
2271 CleanupStream(stream, NS_OK, CANCEL_ERROR);
2272 MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame");
2273 mNeedsCleanup = nullptr; /* just in case */
2274 return NS_OK;
2275 }
2277 if (mNeedsCleanup) {
2278 LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
2279 "cleanup stream based on mNeedsCleanup.\n",
2280 this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
2281 CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
2282 mNeedsCleanup = nullptr;
2283 }
2285 if (NS_FAILED(rv)) {
2286 LOG3(("Http2Session %p data frame read failure %x\n", this, rv));
2287 // maybe just blocked reading from network
2288 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
2289 rv = NS_OK;
2290 }
2292 return rv;
2293 }
2295 if (mDownstreamState == DISCARDING_DATA_FRAME ||
2296 mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
2297 char trash[4096];
2298 uint32_t count = std::min(4096U, mInputFrameDataSize - mInputFrameDataRead);
2299 LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of data",
2300 this, count));
2302 if (!count) {
2303 ResetDownstreamState();
2304 ResumeRecv();
2305 return NS_BASE_STREAM_WOULD_BLOCK;
2306 }
2308 rv = NetworkRead(writer, trash, count, countWritten);
2310 if (NS_FAILED(rv)) {
2311 LOG3(("Http2Session %p discard frame read failure %x\n", this, rv));
2312 // maybe just blocked reading from network
2313 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
2314 rv = NS_OK;
2315 return rv;
2316 }
2318 LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
2320 mInputFrameDataRead += *countWritten;
2322 if (mInputFrameDataRead == mInputFrameDataSize) {
2323 Http2Stream *streamToCleanup = nullptr;
2324 if (mInputFrameFinal) {
2325 streamToCleanup = mInputFrameDataStream;
2326 }
2328 ResetDownstreamState();
2330 if (streamToCleanup) {
2331 CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
2332 }
2333 }
2334 return rv;
2335 }
2337 if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
2338 MOZ_ASSERT(false); // this cannot happen
2339 return NS_ERROR_UNEXPECTED;
2340 }
2342 MOZ_ASSERT(mInputFrameBufferUsed == 8, "Frame Buffer Header Not Present");
2343 MOZ_ASSERT(mInputFrameDataSize + 8 <= mInputFrameBufferSize,
2344 "allocation for control frame insufficient");
2346 rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead,
2347 mInputFrameDataSize - mInputFrameDataRead, countWritten);
2349 if (NS_FAILED(rv)) {
2350 LOG3(("Http2Session %p buffering control frame read failure %x\n",
2351 this, rv));
2352 // maybe just blocked reading from network
2353 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
2354 rv = NS_OK;
2355 return rv;
2356 }
2358 LogIO(this, nullptr, "Reading Control Frame",
2359 mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten);
2361 mInputFrameDataRead += *countWritten;
2363 if (mInputFrameDataRead != mInputFrameDataSize)
2364 return NS_OK;
2366 MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
2367 if (mInputFrameType < FRAME_TYPE_LAST) {
2368 rv = sControlFunctions[mInputFrameType](this);
2369 } else {
2370 // Section 4.1 requires this to be ignored; though protocol_error would
2371 // be better
2372 LOG3(("Http2Session %p unknow frame type %x ignored\n",
2373 this, mInputFrameType));
2374 ResetDownstreamState();
2375 rv = NS_OK;
2376 }
2378 MOZ_ASSERT(NS_FAILED(rv) ||
2379 mDownstreamState != BUFFERING_CONTROL_FRAME,
2380 "Control Handler returned OK but did not change state");
2382 if (mShouldGoAway && !mStreamTransactionHash.Count())
2383 Close(NS_OK);
2384 return rv;
2385 }
2387 void
2388 Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes)
2389 {
2390 if (!stream) // this is ok - it means there was a data frame for a rst stream
2391 return;
2393 // If this data packet was not for a valid or live stream then there
2394 // is no reason to mess with the flow control
2395 if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
2396 mInputFrameFinal) {
2397 return;
2398 }
2400 stream->DecrementClientReceiveWindow(bytes);
2402 // Don't necessarily ack every data packet. Only do it
2403 // after a significant amount of data.
2404 uint64_t unacked = stream->LocalUnAcked();
2405 int64_t localWindow = stream->ClientReceiveWindow();
2407 LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
2408 "unacked=%llu localWindow=%lld\n",
2409 this, stream->StreamID(), bytes, unacked, localWindow));
2411 if (!unacked)
2412 return;
2414 if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
2415 return;
2417 if (!stream->HasSink()) {
2418 LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n",
2419 this, stream->StreamID()));
2420 return;
2421 }
2423 // Generate window updates directly out of session instead of the stream
2424 // in order to avoid queue delays in getting the 'ACK' out.
2425 uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
2427 LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
2428 this, stream->StreamID(), toack));
2429 stream->IncrementClientReceiveWindow(toack);
2431 // room for this packet needs to be ensured before calling this function
2432 char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
2433 mOutputQueueUsed += 12;
2434 MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
2436 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
2437 toack = PR_htonl(toack);
2438 memcpy(packet + 8, &toack, 4);
2440 LogIO(this, stream, "Stream Window Update", packet, 12);
2441 // dont flush here, this write can commonly be coalesced with a
2442 // session window update to immediately follow.
2443 }
2445 void
2446 Http2Session::UpdateLocalSessionWindow(uint32_t bytes)
2447 {
2448 if (!bytes)
2449 return;
2451 mLocalSessionWindow -= bytes;
2453 LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
2454 "localWindow=%lld\n", this, bytes, mLocalSessionWindow));
2456 // Don't necessarily ack every data packet. Only do it
2457 // after a significant amount of data.
2458 if ((mLocalSessionWindow > (ASpdySession::kInitialRwin - kMinimumToAck)) &&
2459 (mLocalSessionWindow > kEmergencyWindowThreshold))
2460 return;
2462 // Only send max bits of window updates at a time.
2463 uint64_t toack64 = ASpdySession::kInitialRwin - mLocalSessionWindow;
2464 uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
2466 LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n",
2467 this, toack));
2468 mLocalSessionWindow += toack;
2470 // room for this packet needs to be ensured before calling this function
2471 char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
2472 mOutputQueueUsed += 12;
2473 MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
2475 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
2476 toack = PR_htonl(toack);
2477 memcpy(packet + 8, &toack, 4);
2479 LogIO(this, nullptr, "Session Window Update", packet, 12);
2480 // dont flush here, this write can commonly be coalesced with others
2481 }
2483 void
2484 Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes)
2485 {
2486 // make sure there is room for 2 window updates even though
2487 // we may not generate any.
2488 EnsureOutputBuffer(16 * 2);
2490 UpdateLocalStreamWindow(stream, bytes);
2491 UpdateLocalSessionWindow(bytes);
2492 FlushOutputQueue();
2493 }
2495 void
2496 Http2Session::Close(nsresult aReason)
2497 {
2498 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2500 if (mClosed)
2501 return;
2503 LOG3(("Http2Session::Close %p %X", this, aReason));
2505 mClosed = true;
2507 mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
2508 mStreamIDHash.Clear();
2509 mStreamTransactionHash.Clear();
2511 uint32_t goAwayReason;
2512 if (mGoAwayReason != NO_HTTP_ERROR) {
2513 goAwayReason = mGoAwayReason;
2514 } else if (NS_SUCCEEDED(aReason)) {
2515 goAwayReason = NO_HTTP_ERROR;
2516 } else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
2517 goAwayReason = PROTOCOL_ERROR;
2518 } else {
2519 goAwayReason = INTERNAL_ERROR;
2520 }
2521 GenerateGoAway(goAwayReason);
2522 mConnection = nullptr;
2523 mSegmentReader = nullptr;
2524 mSegmentWriter = nullptr;
2525 }
2527 void
2528 Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction,
2529 nsresult aResult)
2530 {
2531 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2532 LOG3(("Http2Session::CloseTransaction %p %p %x", this, aTransaction, aResult));
2534 // Generally this arrives as a cancel event from the connection manager.
2536 // need to find the stream and call CleanupStream() on it.
2537 Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
2538 if (!stream) {
2539 LOG3(("Http2Session::CloseTransaction %p %p %x - not found.",
2540 this, aTransaction, aResult));
2541 return;
2542 }
2543 LOG3(("Http2Session::CloseTranscation probably a cancel. "
2544 "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
2545 this, aTransaction, aResult, stream->StreamID(), stream));
2546 CleanupStream(stream, aResult, CANCEL_ERROR);
2547 ResumeRecv();
2548 }
2550 //-----------------------------------------------------------------------------
2551 // nsAHttpSegmentReader
2552 //-----------------------------------------------------------------------------
2554 nsresult
2555 Http2Session::OnReadSegment(const char *buf,
2556 uint32_t count, uint32_t *countRead)
2557 {
2558 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2559 nsresult rv;
2561 // If we can release old queued data then we can try and write the new
2562 // data directly to the network without using the output queue at all
2563 if (mOutputQueueUsed)
2564 FlushOutputQueue();
2566 if (!mOutputQueueUsed && mSegmentReader) {
2567 // try and write directly without output queue
2568 rv = mSegmentReader->OnReadSegment(buf, count, countRead);
2570 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
2571 *countRead = 0;
2572 } else if (NS_FAILED(rv)) {
2573 return rv;
2574 }
2576 if (*countRead < count) {
2577 uint32_t required = count - *countRead;
2578 // assuming a commitment() happened, this ensurebuffer is a nop
2579 // but just in case the queuesize is too small for the required data
2580 // call ensurebuffer().
2581 EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
2582 memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
2583 mOutputQueueUsed = required;
2584 }
2586 *countRead = count;
2587 return NS_OK;
2588 }
2590 // At this point we are going to buffer the new data in the output
2591 // queue if it fits. By coalescing multiple small submissions into one larger
2592 // buffer we can get larger writes out to the network later on.
2594 // This routine should not be allowed to fill up the output queue
2595 // all on its own - at least kQueueReserved bytes are always left
2596 // for other routines to use - but this is an all-or-nothing function,
2597 // so if it will not all fit just return WOULD_BLOCK
2599 if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
2600 return NS_BASE_STREAM_WOULD_BLOCK;
2602 memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
2603 mOutputQueueUsed += count;
2604 *countRead = count;
2606 FlushOutputQueue();
2608 return NS_OK;
2609 }
2611 nsresult
2612 Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment)
2613 {
2614 if (mOutputQueueUsed)
2615 FlushOutputQueue();
2617 // would there be enough room to buffer this if needed?
2618 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
2619 return NS_OK;
2621 // if we are using part of our buffers already, try again later unless
2622 // forceCommitment is set.
2623 if (mOutputQueueUsed && !forceCommitment)
2624 return NS_BASE_STREAM_WOULD_BLOCK;
2626 if (mOutputQueueUsed) {
2627 // normally we avoid the memmove of RealignOutputQueue, but we'll try
2628 // it if forceCommitment is set before growing the buffer.
2629 RealignOutputQueue();
2631 // is there enough room now?
2632 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
2633 return NS_OK;
2634 }
2636 // resize the buffers as needed
2637 EnsureOutputBuffer(count + kQueueReserved);
2639 MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
2640 "buffer not as large as expected");
2642 return NS_OK;
2643 }
2645 //-----------------------------------------------------------------------------
2646 // nsAHttpSegmentWriter
2647 //-----------------------------------------------------------------------------
2649 nsresult
2650 Http2Session::OnWriteSegment(char *buf,
2651 uint32_t count, uint32_t *countWritten)
2652 {
2653 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2654 nsresult rv;
2656 if (!mSegmentWriter) {
2657 // the only way this could happen would be if Close() were called on the
2658 // stack with WriteSegments()
2659 return NS_ERROR_FAILURE;
2660 }
2662 if (mDownstreamState == PROCESSING_DATA_FRAME) {
2664 if (mInputFrameFinal &&
2665 mInputFrameDataRead == mInputFrameDataSize) {
2666 *countWritten = 0;
2667 SetNeedsCleanup();
2668 return NS_BASE_STREAM_CLOSED;
2669 }
2671 count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
2672 rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
2673 if (NS_FAILED(rv))
2674 return rv;
2676 LogIO(this, mInputFrameDataStream, "Reading Data Frame",
2677 buf, *countWritten);
2679 mInputFrameDataRead += *countWritten;
2680 if (mPaddingLength && (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
2681 // We are crossing from real HTTP data into the realm of padding. If
2682 // we've actually crossed the line, we need to munge countWritten for the
2683 // sake of goodness and sanity. No matter what, any future calls to
2684 // WriteSegments need to just discard data until we reach the end of this
2685 // frame.
2686 ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
2687 uint32_t paddingRead = mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
2688 LOG3(("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
2689 "crossed from HTTP data into padding (%d of %d) countWritten=%d",
2690 this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
2691 paddingRead, mPaddingLength, *countWritten));
2692 *countWritten -= paddingRead;
2693 LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
2694 this, mInputFrameID, *countWritten));
2695 }
2697 mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
2698 if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
2699 ResetDownstreamState();
2701 return rv;
2702 }
2704 if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
2706 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
2707 mInputFrameFinal) {
2708 *countWritten = 0;
2709 SetNeedsCleanup();
2710 return NS_BASE_STREAM_CLOSED;
2711 }
2713 count = std::min(count,
2714 mFlatHTTPResponseHeaders.Length() -
2715 mFlatHTTPResponseHeadersOut);
2716 memcpy(buf,
2717 mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
2718 count);
2719 mFlatHTTPResponseHeadersOut += count;
2720 *countWritten = count;
2722 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
2723 if (!mInputFrameFinal) {
2724 // If more frames are expected in this stream, then reset the state so they can be
2725 // handled. Otherwise (e.g. a 0 length response with the fin on the incoming headers)
2726 // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can
2727 // cleanup the stream.
2728 ResetDownstreamState();
2729 }
2730 }
2732 return NS_OK;
2733 }
2735 return NS_ERROR_UNEXPECTED;
2736 }
2738 void
2739 Http2Session::SetNeedsCleanup()
2740 {
2741 LOG3(("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
2742 "stream %p 0x%X", this, mInputFrameDataStream,
2743 mInputFrameDataStream->StreamID()));
2745 // This will result in Close() being called
2746 MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
2747 mNeedsCleanup = mInputFrameDataStream;
2748 ResetDownstreamState();
2749 }
2751 void
2752 Http2Session::ConnectPushedStream(Http2Stream *stream)
2753 {
2754 mReadyForRead.Push(stream);
2755 ForceRecv();
2756 }
2758 nsresult
2759 Http2Session::BufferOutput(const char *buf,
2760 uint32_t count,
2761 uint32_t *countRead)
2762 {
2763 nsAHttpSegmentReader *old = mSegmentReader;
2764 mSegmentReader = nullptr;
2765 nsresult rv = OnReadSegment(buf, count, countRead);
2766 mSegmentReader = old;
2767 return rv;
2768 }
2770 nsresult
2771 Http2Session::ConfirmTLSProfile()
2772 {
2773 if (mTLSProfileConfirmed)
2774 return NS_OK;
2776 LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
2777 this, mConnection.get()));
2779 if (!gHttpHandler->EnforceHttp2TlsProfile()) {
2780 LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this));
2781 mTLSProfileConfirmed = true;
2782 return NS_OK;
2783 }
2785 if (!mConnection)
2786 return NS_ERROR_FAILURE;
2788 nsCOMPtr<nsISupports> securityInfo;
2789 mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
2790 nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
2791 LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, ssl.get()));
2792 if (!ssl)
2793 return NS_ERROR_FAILURE;
2795 int16_t version = ssl->GetSSLVersionUsed();
2796 LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
2797 if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
2798 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", this));
2799 RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
2800 }
2802 #if 0
2803 uint16_t kea = ssl->GetKEAUsed();
2804 if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
2805 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
2806 this, kea));
2807 RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
2808 }
2809 #endif
2811 /* TODO: Enforce DHE >= 2048 || ECDHE >= 128 */
2813 /* We are required to send SNI. We do that already, so no check is done
2814 * here to make sure we did. */
2816 /* We really should check to ensure TLS compression isn't enabled on
2817 * this connection. However, we never enable TLS compression on our end,
2818 * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
2819 * for the possibility for an interface to ensure it never gets turned on. */
2821 mTLSProfileConfirmed = true;
2822 return NS_OK;
2823 }
2826 //-----------------------------------------------------------------------------
2827 // Modified methods of nsAHttpConnection
2828 //-----------------------------------------------------------------------------
2830 void
2831 Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller)
2832 {
2833 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2834 LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
2836 // a trapped signal from the http transaction to the connection that
2837 // it is no longer blocked on read.
2839 Http2Stream *stream = mStreamTransactionHash.Get(caller);
2840 if (!stream || !VerifyStream(stream)) {
2841 LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
2842 this, caller));
2843 return;
2844 }
2846 LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n",
2847 this, stream->StreamID()));
2849 mReadyForWrite.Push(stream);
2850 }
2852 void
2853 Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
2854 {
2855 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2856 LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
2857 this, stream, stream->StreamID()));
2859 mReadyForWrite.Push(stream);
2860 SetWriteCallbacks();
2861 }
2863 bool
2864 Http2Session::IsPersistent()
2865 {
2866 return true;
2867 }
2869 nsresult
2870 Http2Session::TakeTransport(nsISocketTransport **,
2871 nsIAsyncInputStream **, nsIAsyncOutputStream **)
2872 {
2873 MOZ_ASSERT(false, "TakeTransport of Http2Session");
2874 return NS_ERROR_UNEXPECTED;
2875 }
2877 nsHttpConnection *
2878 Http2Session::TakeHttpConnection()
2879 {
2880 MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
2881 return nullptr;
2882 }
2884 uint32_t
2885 Http2Session::CancelPipeline(nsresult reason)
2886 {
2887 // we don't pipeline inside http/2, so this isn't an issue
2888 return 0;
2889 }
2891 nsAHttpTransaction::Classifier
2892 Http2Session::Classification()
2893 {
2894 if (!mConnection)
2895 return nsAHttpTransaction::CLASS_GENERAL;
2896 return mConnection->Classification();
2897 }
2899 //-----------------------------------------------------------------------------
2900 // unused methods of nsAHttpTransaction
2901 // We can be sure of this because Http2Session is only constructed in
2902 // nsHttpConnection and is never passed out of that object
2903 //-----------------------------------------------------------------------------
2905 void
2906 Http2Session::SetConnection(nsAHttpConnection *)
2907 {
2908 // This is unexpected
2909 MOZ_ASSERT(false, "Http2Session::SetConnection()");
2910 }
2912 void
2913 Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **)
2914 {
2915 // This is unexpected
2916 MOZ_ASSERT(false, "Http2Session::GetSecurityCallbacks()");
2917 }
2919 void
2920 Http2Session::SetProxyConnectFailed()
2921 {
2922 MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
2923 }
2925 bool
2926 Http2Session::IsDone()
2927 {
2928 return !mStreamTransactionHash.Count();
2929 }
2931 nsresult
2932 Http2Session::Status()
2933 {
2934 MOZ_ASSERT(false, "Http2Session::Status()");
2935 return NS_ERROR_UNEXPECTED;
2936 }
2938 uint32_t
2939 Http2Session::Caps()
2940 {
2941 MOZ_ASSERT(false, "Http2Session::Caps()");
2942 return 0;
2943 }
2945 void
2946 Http2Session::SetDNSWasRefreshed()
2947 {
2948 MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
2949 }
2951 uint64_t
2952 Http2Session::Available()
2953 {
2954 MOZ_ASSERT(false, "Http2Session::Available()");
2955 return 0;
2956 }
2958 nsHttpRequestHead *
2959 Http2Session::RequestHead()
2960 {
2961 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
2962 MOZ_ASSERT(false,
2963 "Http2Session::RequestHead() "
2964 "should not be called after http/2 is setup");
2965 return NULL;
2966 }
2968 uint32_t
2969 Http2Session::Http1xTransactionCount()
2970 {
2971 return 0;
2972 }
2974 // used as an enumerator by TakeSubTransactions()
2975 static PLDHashOperator
2976 TakeStream(nsAHttpTransaction *key,
2977 nsAutoPtr<Http2Stream> &stream,
2978 void *closure)
2979 {
2980 nsTArray<nsRefPtr<nsAHttpTransaction> > *list =
2981 static_cast<nsTArray<nsRefPtr<nsAHttpTransaction> > *>(closure);
2983 list->AppendElement(key);
2985 // removing the stream from the hash will delete the stream
2986 // and drop the transaction reference the hash held
2987 return PL_DHASH_REMOVE;
2988 }
2990 nsresult
2991 Http2Session::TakeSubTransactions(
2992 nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
2993 {
2994 // Generally this cannot be done with http/2 as transactions are
2995 // started right away.
2997 LOG3(("Http2Session::TakeSubTransactions %p\n", this));
2999 if (mConcurrentHighWater > 0)
3000 return NS_ERROR_ALREADY_OPENED;
3002 LOG3((" taking %d\n", mStreamTransactionHash.Count()));
3004 mStreamTransactionHash.Enumerate(TakeStream, &outTransactions);
3005 return NS_OK;
3006 }
3008 nsresult
3009 Http2Session::AddTransaction(nsAHttpTransaction *)
3010 {
3011 // This API is meant for pipelining, Http2Session's should be
3012 // extended with AddStream()
3014 MOZ_ASSERT(false,
3015 "Http2Session::AddTransaction() should not be called");
3017 return NS_ERROR_NOT_IMPLEMENTED;
3018 }
3020 uint32_t
3021 Http2Session::PipelineDepth()
3022 {
3023 return IsDone() ? 0 : 1;
3024 }
3026 nsresult
3027 Http2Session::SetPipelinePosition(int32_t position)
3028 {
3029 // This API is meant for pipelining, Http2Session's should be
3030 // extended with AddStream()
3032 MOZ_ASSERT(false,
3033 "Http2Session::SetPipelinePosition() should not be called");
3035 return NS_ERROR_NOT_IMPLEMENTED;
3036 }
3038 int32_t
3039 Http2Session::PipelinePosition()
3040 {
3041 return 0;
3042 }
3044 //-----------------------------------------------------------------------------
3045 // Pass through methods of nsAHttpConnection
3046 //-----------------------------------------------------------------------------
3048 nsAHttpConnection *
3049 Http2Session::Connection()
3050 {
3051 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
3052 return mConnection;
3053 }
3055 nsresult
3056 Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction,
3057 nsHttpRequestHead *requestHead,
3058 nsHttpResponseHead *responseHead, bool *reset)
3059 {
3060 return mConnection->OnHeadersAvailable(transaction,
3061 requestHead,
3062 responseHead,
3063 reset);
3064 }
3066 bool
3067 Http2Session::IsReused()
3068 {
3069 return mConnection->IsReused();
3070 }
3072 nsresult
3073 Http2Session::PushBack(const char *buf, uint32_t len)
3074 {
3075 return mConnection->PushBack(buf, len);
3076 }
3078 } // namespace mozilla::net
3079 } // namespace mozilla