|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set sw=2 ts=8 et tw=80 : */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 // HttpLog.h should generally be included first |
|
8 #include "HttpLog.h" |
|
9 |
|
10 // Log on level :5, instead of default :4. |
|
11 #undef LOG |
|
12 #define LOG(args) LOG5(args) |
|
13 #undef LOG_ENABLED |
|
14 #define LOG_ENABLED() LOG5_ENABLED() |
|
15 |
|
16 #include <algorithm> |
|
17 |
|
18 #include "Http2Session.h" |
|
19 #include "Http2Stream.h" |
|
20 #include "Http2Push.h" |
|
21 |
|
22 #include "mozilla/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" |
|
34 |
|
35 #ifdef DEBUG |
|
36 // defined by the socket transport service while active |
|
37 extern PRThread *gSocketThread; |
|
38 #endif |
|
39 |
|
40 namespace mozilla { |
|
41 namespace net { |
|
42 |
|
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 |
|
51 |
|
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 }; |
|
59 |
|
60 #define RETURN_SESSION_ERROR(o,x) \ |
|
61 do { \ |
|
62 (o)->mGoAwayReason = (x); \ |
|
63 return NS_ERROR_ILLEGAL_VALUE; \ |
|
64 } while (0) |
|
65 |
|
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); |
|
104 |
|
105 static uint64_t sSerial; |
|
106 mSerial = ++sSerial; |
|
107 |
|
108 LOG3(("Http2Session::Http2Session %p transaction 1 = %p serial=0x%X\n", |
|
109 this, aHttpTransaction, mSerial)); |
|
110 |
|
111 mConnection = aHttpTransaction->Connection(); |
|
112 mInputFrameBuffer = new char[mInputFrameBufferSize]; |
|
113 mOutputQueueBuffer = new char[mOutputQueueSize]; |
|
114 mDecompressBuffer.SetCapacity(kDefaultBufferSize); |
|
115 mDecompressor.SetCompressor(&mCompressor); |
|
116 |
|
117 mPushAllowance = gHttpHandler->SpdyPushAllowance(); |
|
118 |
|
119 mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); |
|
120 SendHello(); |
|
121 |
|
122 if (!aHttpTransaction->IsNullTransaction()) |
|
123 AddStream(aHttpTransaction, firstPriority); |
|
124 mLastDataReadEpoch = mLastReadEpoch; |
|
125 |
|
126 mPingThreshold = gHttpHandler->SpdyPingThreshold(); |
|
127 } |
|
128 |
|
129 PLDHashOperator |
|
130 Http2Session::ShutdownEnumerator(nsAHttpTransaction *key, |
|
131 nsAutoPtr<Http2Stream> &stream, |
|
132 void *closure) |
|
133 { |
|
134 Http2Session *self = static_cast<Http2Session *>(closure); |
|
135 |
|
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 } |
|
148 |
|
149 return PL_DHASH_NEXT; |
|
150 } |
|
151 |
|
152 PLDHashOperator |
|
153 Http2Session::GoAwayEnumerator(nsAHttpTransaction *key, |
|
154 nsAutoPtr<Http2Stream> &stream, |
|
155 void *closure) |
|
156 { |
|
157 Http2Session *self = static_cast<Http2Session *>(closure); |
|
158 |
|
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 } |
|
167 |
|
168 return PL_DHASH_NEXT; |
|
169 } |
|
170 |
|
171 Http2Session::~Http2Session() |
|
172 { |
|
173 LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", |
|
174 this, mDownstreamState)); |
|
175 |
|
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 } |
|
182 |
|
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; |
|
190 |
|
191 LOG4(("Http2Session::LogIO %p stream=%p id=0x%X [%s]", |
|
192 self, stream, stream ? stream->StreamID() : 0, label)); |
|
193 |
|
194 // Max line is (16 * 3) + 10(prefix) + newline + null |
|
195 char linebuf[128]; |
|
196 uint32_t index; |
|
197 char *line = linebuf; |
|
198 |
|
199 linebuf[127] = 0; |
|
200 |
|
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 } |
|
220 |
|
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 }; |
|
234 |
|
235 bool |
|
236 Http2Session::RoomForMoreConcurrent() |
|
237 { |
|
238 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
239 return (mConcurrent < mMaxConcurrent); |
|
240 } |
|
241 |
|
242 bool |
|
243 Http2Session::RoomForMoreStreams() |
|
244 { |
|
245 if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) |
|
246 return false; |
|
247 |
|
248 return !mShouldGoAway; |
|
249 } |
|
250 |
|
251 PRIntervalTime |
|
252 Http2Session::IdleTime() |
|
253 { |
|
254 return PR_IntervalNow() - mLastDataReadEpoch; |
|
255 } |
|
256 |
|
257 uint32_t |
|
258 Http2Session::ReadTimeoutTick(PRIntervalTime now) |
|
259 { |
|
260 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
261 |
|
262 LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", |
|
263 this, PR_IntervalToSeconds(now - mLastReadEpoch))); |
|
264 |
|
265 if (!mPingThreshold) |
|
266 return UINT32_MAX; |
|
267 |
|
268 if ((now - mLastReadEpoch) < mPingThreshold) { |
|
269 // recent activity means ping is not an issue |
|
270 if (mPingSentEpoch) |
|
271 mPingSentEpoch = 0; |
|
272 |
|
273 return PR_IntervalToSeconds(mPingThreshold) - |
|
274 PR_IntervalToSeconds(now - mLastReadEpoch); |
|
275 } |
|
276 |
|
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 } |
|
287 |
|
288 LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this)); |
|
289 |
|
290 mPingSentEpoch = PR_IntervalNow(); |
|
291 if (!mPingSentEpoch) |
|
292 mPingSentEpoch = 1; // avoid the 0 sentinel value |
|
293 GeneratePing(false); |
|
294 ResumeRecv(); // read the ping reply |
|
295 |
|
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; |
|
302 |
|
303 for (uint32_t index = mPushedStreams.Length(); |
|
304 index > 0 ; --index) { |
|
305 Http2PushedStream *pushedStream = mPushedStreams[index - 1]; |
|
306 |
|
307 if (timestampNow.IsNull()) |
|
308 timestampNow = TimeStamp::Now(); // lazy initializer |
|
309 |
|
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); |
|
322 |
|
323 } while (deleteMe); |
|
324 |
|
325 return 1; // run the tick aggressively while ping is outstanding |
|
326 } |
|
327 |
|
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"); |
|
336 |
|
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 } |
|
343 |
|
344 LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X " |
|
345 "concurrent=%d",this, stream, aNewID, mConcurrent)); |
|
346 |
|
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; |
|
352 |
|
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 } |
|
360 |
|
361 mStreamIDHash.Put(aNewID, stream); |
|
362 return aNewID; |
|
363 } |
|
364 |
|
365 bool |
|
366 Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction, |
|
367 int32_t aPriority) |
|
368 { |
|
369 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
370 |
|
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 } |
|
377 |
|
378 aHttpTransaction->SetConnection(this); |
|
379 Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority); |
|
380 |
|
381 LOG3(("Http2Session::AddStream session=%p stream=%p NextID=0x%X (tentative)", |
|
382 this, stream, mNextStreamID)); |
|
383 |
|
384 mStreamTransactionHash.Put(aHttpTransaction, stream); |
|
385 |
|
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 } |
|
394 |
|
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 } |
|
400 |
|
401 return true; |
|
402 } |
|
403 |
|
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"); |
|
410 |
|
411 MOZ_ASSERT(!stream->CountAsActive()); |
|
412 stream->SetCountAsActive(true); |
|
413 ++mConcurrent; |
|
414 |
|
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)); |
|
420 |
|
421 mReadyForWrite.Push(stream); |
|
422 SetWriteCallbacks(); |
|
423 |
|
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 } |
|
432 |
|
433 void |
|
434 Http2Session::ProcessPending() |
|
435 { |
|
436 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
437 |
|
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 } |
|
447 |
|
448 nsresult |
|
449 Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf, |
|
450 uint32_t count, uint32_t *countWritten) |
|
451 { |
|
452 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
453 |
|
454 if (!count) { |
|
455 *countWritten = 0; |
|
456 return NS_OK; |
|
457 } |
|
458 |
|
459 nsresult rv = writer->OnWriteSegment(buf, count, countWritten); |
|
460 if (NS_SUCCEEDED(rv) && *countWritten > 0) |
|
461 mLastReadEpoch = PR_IntervalNow(); |
|
462 return rv; |
|
463 } |
|
464 |
|
465 void |
|
466 Http2Session::SetWriteCallbacks() |
|
467 { |
|
468 if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed)) |
|
469 mConnection->ResumeSend(); |
|
470 } |
|
471 |
|
472 void |
|
473 Http2Session::RealignOutputQueue() |
|
474 { |
|
475 mOutputQueueUsed -= mOutputQueueSent; |
|
476 memmove(mOutputQueueBuffer.get(), |
|
477 mOutputQueueBuffer.get() + mOutputQueueSent, |
|
478 mOutputQueueUsed); |
|
479 mOutputQueueSent = 0; |
|
480 } |
|
481 |
|
482 void |
|
483 Http2Session::FlushOutputQueue() |
|
484 { |
|
485 if (!mSegmentReader || !mOutputQueueUsed) |
|
486 return; |
|
487 |
|
488 nsresult rv; |
|
489 uint32_t countRead; |
|
490 uint32_t avail = mOutputQueueUsed - mOutputQueueSent; |
|
491 |
|
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)); |
|
497 |
|
498 // Dont worry about errors on write, we will pick this up as a read error too |
|
499 if (NS_FAILED(rv)) |
|
500 return; |
|
501 |
|
502 if (countRead == avail) { |
|
503 mOutputQueueUsed = 0; |
|
504 mOutputQueueSent = 0; |
|
505 return; |
|
506 } |
|
507 |
|
508 mOutputQueueSent += countRead; |
|
509 |
|
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. |
|
512 |
|
513 if ((mOutputQueueSent >= kQueueMinimumCleanup) && |
|
514 ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) { |
|
515 RealignOutputQueue(); |
|
516 } |
|
517 } |
|
518 |
|
519 void |
|
520 Http2Session::DontReuse() |
|
521 { |
|
522 mShouldGoAway = true; |
|
523 if (!mStreamTransactionHash.Count()) |
|
524 Close(NS_OK); |
|
525 } |
|
526 |
|
527 uint32_t |
|
528 Http2Session::GetWriteQueueSize() |
|
529 { |
|
530 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
531 |
|
532 return mReadyForWrite.GetSize(); |
|
533 } |
|
534 |
|
535 void |
|
536 Http2Session::ChangeDownstreamState(enum internalStateType newState) |
|
537 { |
|
538 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
539 |
|
540 LOG3(("Http2Stream::ChangeDownstreamState() %p from %X to %X", |
|
541 this, mDownstreamState, newState)); |
|
542 mDownstreamState = newState; |
|
543 } |
|
544 |
|
545 void |
|
546 Http2Session::ResetDownstreamState() |
|
547 { |
|
548 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
549 |
|
550 LOG3(("Http2Stream::ResetDownstreamState() %p", this)); |
|
551 ChangeDownstreamState(BUFFERING_FRAME_HEADER); |
|
552 |
|
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 } |
|
562 |
|
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; |
|
569 |
|
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. |
|
573 |
|
574 objSize = (newSize + 2048 + 4095) & ~4095; |
|
575 |
|
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 } |
|
581 |
|
582 // Instantiate supported templates explicitly. |
|
583 template void |
|
584 Http2Session::EnsureBuffer(nsAutoArrayPtr<char> &buf, uint32_t newSize, |
|
585 uint32_t preserve, uint32_t &objSize); |
|
586 |
|
587 template void |
|
588 Http2Session::EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf, uint32_t newSize, |
|
589 uint32_t preserve, uint32_t &objSize); |
|
590 |
|
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)); |
|
600 |
|
601 frameLength = PR_htons(frameLength); |
|
602 streamID = PR_htonl(streamID); |
|
603 |
|
604 memcpy(dest, &frameLength, 2); |
|
605 dest[2] = frameType; |
|
606 dest[3] = frameFlags; |
|
607 memcpy(dest + 4, &streamID, 4); |
|
608 } |
|
609 |
|
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 } |
|
619 |
|
620 template void |
|
621 Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength, |
|
622 uint8_t frameType, uint8_t frameFlags, |
|
623 uint32_t streamID); |
|
624 |
|
625 template void |
|
626 Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength, |
|
627 uint8_t frameType, uint8_t frameFlags, |
|
628 uint32_t streamID); |
|
629 |
|
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())); |
|
635 |
|
636 if (!aStream->CountAsActive()) |
|
637 return; |
|
638 |
|
639 MOZ_ASSERT(mConcurrent); |
|
640 aStream->SetCountAsActive(false); |
|
641 --mConcurrent; |
|
642 ProcessPending(); |
|
643 } |
|
644 |
|
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; |
|
652 |
|
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 } |
|
664 |
|
665 void |
|
666 Http2Session::GeneratePing(bool isAck) |
|
667 { |
|
668 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
669 LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck)); |
|
670 |
|
671 char *packet = EnsureOutputBuffer(16); |
|
672 mOutputQueueUsed += 16; |
|
673 |
|
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 } |
|
681 |
|
682 LogIO(this, nullptr, "Generate Ping", packet, 16); |
|
683 FlushOutputQueue(); |
|
684 } |
|
685 |
|
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)); |
|
692 |
|
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 } |
|
699 |
|
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)); |
|
706 |
|
707 char *packet = EnsureOutputBuffer(12); |
|
708 mOutputQueueUsed += 12; |
|
709 |
|
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 } |
|
716 |
|
717 void |
|
718 Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) |
|
719 { |
|
720 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
721 |
|
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 } |
|
730 |
|
731 LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); |
|
732 |
|
733 char *packet = EnsureOutputBuffer(12); |
|
734 mOutputQueueUsed += 12; |
|
735 CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID); |
|
736 |
|
737 aStatusCode = PR_htonl(aStatusCode); |
|
738 memcpy(packet + 8, &aStatusCode, 4); |
|
739 |
|
740 LogIO(this, nullptr, "Generate Reset", packet, 12); |
|
741 FlushOutputQueue(); |
|
742 } |
|
743 |
|
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)); |
|
749 |
|
750 char *packet = EnsureOutputBuffer(16); |
|
751 mOutputQueueUsed += 16; |
|
752 |
|
753 memset(packet + 8, 0, 8); |
|
754 CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0); |
|
755 |
|
756 // last-good-stream-id are bytes 8-11 reflecting pushes |
|
757 uint32_t goAway = PR_htonl(mOutgoingGoAwayID); |
|
758 memcpy (packet + 7, &goAway, 4); |
|
759 |
|
760 // bytes 12-15 are the status code. |
|
761 aStatusCode = PR_htonl(aStatusCode); |
|
762 memcpy(packet + 12, &aStatusCode, 4); |
|
763 |
|
764 LogIO(this, nullptr, "Generate GoAway", packet, 16); |
|
765 FlushOutputQueue(); |
|
766 } |
|
767 |
|
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)); |
|
777 |
|
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); |
|
786 |
|
787 packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
|
788 memset(packet, 0, maxDataLen - 24); |
|
789 |
|
790 // frame header will be filled in after we know how long the frame is |
|
791 uint8_t numberOfEntries = 0; |
|
792 |
|
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 |
|
797 |
|
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++; |
|
804 |
|
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 } |
|
809 |
|
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++; |
|
816 |
|
817 MOZ_ASSERT(numberOfEntries <= maxSettings); |
|
818 uint32_t dataLen = 5 * numberOfEntries; |
|
819 CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0); |
|
820 mOutputQueueUsed += 8 + dataLen; |
|
821 |
|
822 LogIO(this, nullptr, "Generate Settings", packet, 8 + dataLen); |
|
823 |
|
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; |
|
828 |
|
829 // send a window update for the session (Stream 0) for something large |
|
830 sessionWindowBump = PR_htonl(sessionWindowBump); |
|
831 mLocalSessionWindow = ASpdySession::kInitialRwin; |
|
832 |
|
833 packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
|
834 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0); |
|
835 mOutputQueueUsed += 12; |
|
836 memcpy(packet + 8, &sessionWindowBump, 4); |
|
837 |
|
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); |
|
841 |
|
842 sendHello_complete: |
|
843 FlushOutputQueue(); |
|
844 } |
|
845 |
|
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); |
|
853 |
|
854 #ifndef DEBUG |
|
855 // Only do the real verification in debug builds |
|
856 return true; |
|
857 #endif |
|
858 |
|
859 if (!aStream) |
|
860 return true; |
|
861 |
|
862 uint32_t test = 0; |
|
863 |
|
864 do { |
|
865 if (aStream->StreamID() == kDeadStreamID) |
|
866 break; |
|
867 |
|
868 nsAHttpTransaction *trans = aStream->Transaction(); |
|
869 |
|
870 test++; |
|
871 if (!trans) |
|
872 break; |
|
873 |
|
874 test++; |
|
875 if (mStreamTransactionHash.Get(trans) != aStream) |
|
876 break; |
|
877 |
|
878 if (aStream->StreamID()) { |
|
879 Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID()); |
|
880 |
|
881 test++; |
|
882 if (idStream != aStream) |
|
883 break; |
|
884 |
|
885 if (aOptionalID) { |
|
886 test++; |
|
887 if (idStream->StreamID() != aOptionalID) |
|
888 break; |
|
889 } |
|
890 } |
|
891 |
|
892 // tests passed |
|
893 return true; |
|
894 } while (0); |
|
895 |
|
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)); |
|
900 |
|
901 MOZ_ASSERT(false, "VerifyStream"); |
|
902 return false; |
|
903 } |
|
904 |
|
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 } |
|
915 |
|
916 Http2PushedStream *pushSource = nullptr; |
|
917 |
|
918 if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) { |
|
919 LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID())); |
|
920 return; |
|
921 } |
|
922 |
|
923 if (!VerifyStream(aStream)) { |
|
924 LOG3(("Http2Session::CleanupStream failed to verify stream\n")); |
|
925 return; |
|
926 } |
|
927 |
|
928 pushSource = aStream->PushSource(); |
|
929 |
|
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 } |
|
935 |
|
936 CloseStream(aStream, aResult); |
|
937 |
|
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 } |
|
946 |
|
947 RemoveStreamFromQueues(aStream); |
|
948 |
|
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()); |
|
953 |
|
954 if (mShouldGoAway && !mStreamTransactionHash.Count()) |
|
955 Close(NS_OK); |
|
956 |
|
957 if (pushSource) { |
|
958 pushSource->SetDeferCleanupOnSuccess(false); |
|
959 CleanupStream(pushSource, aResult, aResetCode); |
|
960 } |
|
961 } |
|
962 |
|
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 } |
|
972 |
|
973 void |
|
974 Http2Session::RemoveStreamFromQueues(Http2Stream *aStream) |
|
975 { |
|
976 RemoveStreamFromQueue(aStream, mReadyForWrite); |
|
977 RemoveStreamFromQueue(aStream, mQueuedStreams); |
|
978 RemoveStreamFromQueue(aStream, mReadyForRead); |
|
979 } |
|
980 |
|
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)); |
|
987 |
|
988 MaybeDecrementConcurrent(aStream); |
|
989 |
|
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 } |
|
996 |
|
997 RemoveStreamFromQueues(aStream); |
|
998 |
|
999 // Send the stream the close() indication |
|
1000 aStream->Close(aResult); |
|
1001 } |
|
1002 |
|
1003 nsresult |
|
1004 Http2Session::SetInputFrameDataStream(uint32_t streamID) |
|
1005 { |
|
1006 mInputFrameDataStream = mStreamIDHash.Get(streamID); |
|
1007 if (VerifyStream(mInputFrameDataStream, streamID)) |
|
1008 return NS_OK; |
|
1009 |
|
1010 LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n", |
|
1011 streamID)); |
|
1012 mInputFrameDataStream = nullptr; |
|
1013 return NS_ERROR_UNEXPECTED; |
|
1014 } |
|
1015 |
|
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 } |
|
1024 |
|
1025 if (mInputFrameFlags & kFlag_PAD_LOW) { |
|
1026 uint8_t paddingLowValue = *reinterpret_cast<uint8_t *>(mInputFrameBuffer + 8 + paddingControlBytes); |
|
1027 paddingLength += paddingLowValue; |
|
1028 ++paddingControlBytes; |
|
1029 } |
|
1030 |
|
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 } |
|
1038 |
|
1039 return NS_OK; |
|
1040 } |
|
1041 |
|
1042 nsresult |
|
1043 Http2Session::RecvHeaders(Http2Session *self) |
|
1044 { |
|
1045 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS); |
|
1046 |
|
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; |
|
1050 |
|
1051 if (endHeadersFlag) |
|
1052 self->mExpectedHeaderID = 0; |
|
1053 else |
|
1054 self->mExpectedHeaderID = self->mInputFrameID; |
|
1055 |
|
1056 uint32_t priorityLen = (self->mInputFrameFlags & kFlag_PRIORITY) ? 4 : 0; |
|
1057 self->SetInputFrameDataStream(self->mInputFrameID); |
|
1058 |
|
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; |
|
1063 |
|
1064 nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength); |
|
1065 if (NS_FAILED(rv)) { |
|
1066 return rv; |
|
1067 } |
|
1068 |
|
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)); |
|
1079 |
|
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 |
|
1083 |
|
1084 LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream " |
|
1085 "0x%X failed. NextStreamID = 0x%X\n", |
|
1086 self, self->mInputFrameID, self->mNextStreamID)); |
|
1087 |
|
1088 if (self->mInputFrameID >= self->mNextStreamID) |
|
1089 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID); |
|
1090 |
|
1091 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen, |
|
1092 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength); |
|
1093 |
|
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 } |
|
1103 |
|
1104 self->ResetDownstreamState(); |
|
1105 return NS_OK; |
|
1106 } |
|
1107 |
|
1108 // queue up any compression bytes |
|
1109 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen, |
|
1110 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength); |
|
1111 |
|
1112 self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize); |
|
1113 self->mLastDataReadEpoch = self->mLastReadEpoch; |
|
1114 |
|
1115 if (!endHeadersFlag) { // more are coming - don't process yet |
|
1116 self->ResetDownstreamState(); |
|
1117 return NS_OK; |
|
1118 } |
|
1119 |
|
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 } |
|
1130 |
|
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)); |
|
1139 |
|
1140 // only do this once, afterwards ignore trailers |
|
1141 if (mInputFrameDataStream->AllHeadersReceived()) |
|
1142 return NS_OK; |
|
1143 mInputFrameDataStream->SetAllHeadersReceived(true); |
|
1144 |
|
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() |
|
1149 |
|
1150 mFlatHTTPResponseHeadersOut = 0; |
|
1151 nsresult rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor, |
|
1152 mDecompressBuffer, |
|
1153 mFlatHTTPResponseHeaders); |
|
1154 if (NS_FAILED(rv)) |
|
1155 return rv; |
|
1156 |
|
1157 ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); |
|
1158 return NS_OK; |
|
1159 } |
|
1160 |
|
1161 nsresult |
|
1162 Http2Session::RecvPriority(Http2Session *self) |
|
1163 { |
|
1164 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY); |
|
1165 |
|
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 } |
|
1171 |
|
1172 if (!self->mInputFrameID) { |
|
1173 LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self)); |
|
1174 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); |
|
1175 } |
|
1176 |
|
1177 uint32_t newPriority = |
|
1178 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]); |
|
1179 newPriority &= 0x7fffffff; |
|
1180 |
|
1181 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); |
|
1182 if (NS_FAILED(rv)) |
|
1183 return rv; |
|
1184 |
|
1185 if (self->mInputFrameDataStream) |
|
1186 self->mInputFrameDataStream->SetPriority(newPriority); |
|
1187 self->ResetDownstreamState(); |
|
1188 return NS_OK; |
|
1189 } |
|
1190 |
|
1191 nsresult |
|
1192 Http2Session::RecvRstStream(Http2Session *self) |
|
1193 { |
|
1194 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM); |
|
1195 |
|
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 } |
|
1201 |
|
1202 if (!self->mInputFrameID) { |
|
1203 LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self)); |
|
1204 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); |
|
1205 } |
|
1206 |
|
1207 self->mDownstreamRstReason = |
|
1208 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]); |
|
1209 |
|
1210 LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n", |
|
1211 self, self->mDownstreamRstReason, self->mInputFrameID)); |
|
1212 |
|
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 } |
|
1219 |
|
1220 self->mInputFrameDataStream->SetRecvdReset(true); |
|
1221 self->MaybeDecrementConcurrent(self->mInputFrameDataStream); |
|
1222 self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); |
|
1223 return NS_OK; |
|
1224 } |
|
1225 |
|
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 } |
|
1235 |
|
1236 nsresult |
|
1237 Http2Session::RecvSettings(Http2Session *self) |
|
1238 { |
|
1239 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS); |
|
1240 |
|
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 } |
|
1246 |
|
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 } |
|
1254 |
|
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)); |
|
1259 |
|
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 } |
|
1264 |
|
1265 for (uint32_t index = 0; index < numEntries; ++index) { |
|
1266 uint8_t *setting = reinterpret_cast<uint8_t *> |
|
1267 (self->mInputFrameBuffer.get()) + 8 + index * 5; |
|
1268 |
|
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)); |
|
1272 |
|
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; |
|
1279 |
|
1280 case SETTINGS_TYPE_ENABLE_PUSH: |
|
1281 LOG3(("Client received an ENABLE Push SETTING. Odd.\n")); |
|
1282 // nop |
|
1283 break; |
|
1284 |
|
1285 case SETTINGS_TYPE_MAX_CONCURRENT: |
|
1286 self->mMaxConcurrent = value; |
|
1287 Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value); |
|
1288 break; |
|
1289 |
|
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; |
|
1295 |
|
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; |
|
1302 |
|
1303 default: |
|
1304 break; |
|
1305 } |
|
1306 } |
|
1307 |
|
1308 self->ResetDownstreamState(); |
|
1309 |
|
1310 if (!(self->mInputFrameFlags & kFlag_ACK)) |
|
1311 self->GenerateSettingsAck(); |
|
1312 |
|
1313 return NS_OK; |
|
1314 } |
|
1315 |
|
1316 nsresult |
|
1317 Http2Session::RecvPushPromise(Http2Session *self) |
|
1318 { |
|
1319 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE); |
|
1320 |
|
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 } |
|
1336 |
|
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; |
|
1341 |
|
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 } |
|
1353 |
|
1354 uint32_t associatedID = self->mInputFrameID; |
|
1355 |
|
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 } |
|
1363 |
|
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 } |
|
1372 |
|
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)); |
|
1378 |
|
1379 if (!associatedID || !promisedID || (promisedID & 1)) { |
|
1380 LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self)); |
|
1381 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR); |
|
1382 } |
|
1383 |
|
1384 // confirm associated-to |
|
1385 nsresult rv = self->SetInputFrameDataStream(associatedID); |
|
1386 if (NS_FAILED(rv)) |
|
1387 return rv; |
|
1388 |
|
1389 Http2Stream *associatedStream = self->mInputFrameDataStream; |
|
1390 ++(self->mServerPushedResources); |
|
1391 |
|
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; |
|
1396 |
|
1397 bool resetStream = true; |
|
1398 SpdyPushCache *cache = nullptr; |
|
1399 |
|
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 } |
|
1434 |
|
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 } |
|
1451 |
|
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); |
|
1459 |
|
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); |
|
1466 |
|
1467 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + promiseLen, |
|
1468 self->mInputFrameDataSize - promiseLen); |
|
1469 |
|
1470 nsAutoCString requestHeaders; |
|
1471 rv = pushedStream->ConvertPushHeaders(&self->mDecompressor, |
|
1472 self->mDecompressBuffer, requestHeaders); |
|
1473 |
|
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 } |
|
1479 |
|
1480 if (NS_FAILED(rv)) |
|
1481 return rv; |
|
1482 |
|
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 } |
|
1488 |
|
1489 if (promisedID > self->mOutgoingGoAwayID) |
|
1490 self->mOutgoingGoAwayID = promisedID; |
|
1491 |
|
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); |
|
1496 |
|
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 } |
|
1504 |
|
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 } |
|
1511 |
|
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 } |
|
1518 |
|
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 } |
|
1527 |
|
1528 nsresult |
|
1529 Http2Session::RecvPing(Http2Session *self) |
|
1530 { |
|
1531 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING); |
|
1532 |
|
1533 LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self, |
|
1534 self->mInputFrameFlags)); |
|
1535 |
|
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 } |
|
1541 |
|
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 } |
|
1547 |
|
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 } |
|
1555 |
|
1556 self->ResetDownstreamState(); |
|
1557 return NS_OK; |
|
1558 } |
|
1559 |
|
1560 nsresult |
|
1561 Http2Session::RecvGoAway(Http2Session *self) |
|
1562 { |
|
1563 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY); |
|
1564 |
|
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 } |
|
1572 |
|
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 } |
|
1578 |
|
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]); |
|
1586 |
|
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); |
|
1591 |
|
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()); |
|
1597 |
|
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 } |
|
1603 |
|
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 } |
|
1614 |
|
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())); |
|
1618 |
|
1619 self->ResetDownstreamState(); |
|
1620 return NS_OK; |
|
1621 } |
|
1622 |
|
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); |
|
1630 |
|
1631 if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) |
|
1632 return PL_DHASH_NEXT; |
|
1633 |
|
1634 self->mReadyForWrite.Push(stream); |
|
1635 self->SetWriteCallbacks(); |
|
1636 return PL_DHASH_NEXT; |
|
1637 } |
|
1638 |
|
1639 nsresult |
|
1640 Http2Session::RecvWindowUpdate(Http2Session *self) |
|
1641 { |
|
1642 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE); |
|
1643 |
|
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 } |
|
1649 |
|
1650 uint32_t delta = |
|
1651 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]); |
|
1652 delta &= 0x7fffffff; |
|
1653 |
|
1654 LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n", |
|
1655 self, delta, self->mInputFrameID)); |
|
1656 |
|
1657 if (self->mInputFrameID) { // stream window |
|
1658 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID); |
|
1659 if (NS_FAILED(rv)) |
|
1660 return rv; |
|
1661 |
|
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 } |
|
1671 |
|
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 } |
|
1684 |
|
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)); |
|
1688 |
|
1689 } else { // session window update |
|
1690 int64_t oldRemoteWindow = self->mServerSessionWindow; |
|
1691 self->mServerSessionWindow += delta; |
|
1692 |
|
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 } |
|
1700 |
|
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 } |
|
1710 |
|
1711 self->ResetDownstreamState(); |
|
1712 return NS_OK; |
|
1713 } |
|
1714 |
|
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)); |
|
1722 |
|
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)); |
|
1727 |
|
1728 self->SetInputFrameDataStream(self->mInputFrameID); |
|
1729 |
|
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 } |
|
1735 |
|
1736 // continued headers |
|
1737 if (self->mExpectedHeaderID) { |
|
1738 self->mInputFrameFlags &= ~kFlag_PRIORITY; |
|
1739 return RecvHeaders(self); |
|
1740 } |
|
1741 |
|
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 } |
|
1749 |
|
1750 //----------------------------------------------------------------------------- |
|
1751 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller |
|
1752 // of these methods |
|
1753 //----------------------------------------------------------------------------- |
|
1754 |
|
1755 void |
|
1756 Http2Session::OnTransportStatus(nsITransport* aTransport, |
|
1757 nsresult aStatus, uint64_t aProgress) |
|
1758 { |
|
1759 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
1760 |
|
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 } |
|
1775 |
|
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. |
|
1781 |
|
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 |
|
1789 |
|
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. |
|
1796 |
|
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 |
|
1800 |
|
1801 break; |
|
1802 } |
|
1803 } |
|
1804 |
|
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. |
|
1809 |
|
1810 nsresult |
|
1811 Http2Session::ReadSegments(nsAHttpSegmentReader *reader, |
|
1812 uint32_t count, uint32_t *countRead) |
|
1813 { |
|
1814 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
1815 |
|
1816 MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader), |
|
1817 "Inconsistent Write Function Callback"); |
|
1818 |
|
1819 nsresult rv = ConfirmTLSProfile(); |
|
1820 if (NS_FAILED(rv)) |
|
1821 return rv; |
|
1822 |
|
1823 if (reader) |
|
1824 mSegmentReader = reader; |
|
1825 |
|
1826 *countRead = 0; |
|
1827 |
|
1828 LOG3(("Http2Session::ReadSegments %p", this)); |
|
1829 |
|
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 } |
|
1838 |
|
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())); |
|
1842 |
|
1843 rv = stream->ReadSegments(this, count, countRead); |
|
1844 |
|
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(); |
|
1850 |
|
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(); |
|
1854 |
|
1855 if (stream->RequestBlockedOnRead()) { |
|
1856 |
|
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(). |
|
1860 |
|
1861 LOG3(("Http2Session::ReadSegments %p dealing with block on read", this)); |
|
1862 |
|
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 } |
|
1873 |
|
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 } |
|
1881 |
|
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 } |
|
1889 |
|
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 } |
|
1895 |
|
1896 LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", |
|
1897 this, stream)); |
|
1898 |
|
1899 // call readsegments again if there are other streams ready |
|
1900 // to go in this session |
|
1901 SetWriteCallbacks(); |
|
1902 |
|
1903 return rv; |
|
1904 } |
|
1905 |
|
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); |
|
1912 |
|
1913 Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, |
|
1914 mInputFrameDataSize >> 10); |
|
1915 mLastDataReadEpoch = mLastReadEpoch; |
|
1916 |
|
1917 if (!mInputFrameID) { |
|
1918 LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n", |
|
1919 this)); |
|
1920 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR); |
|
1921 } |
|
1922 |
|
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 } |
|
1945 |
|
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); |
|
1951 |
|
1952 return NS_OK; |
|
1953 } |
|
1954 |
|
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. |
|
1961 |
|
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 |
|
1967 |
|
1968 nsresult |
|
1969 Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, |
|
1970 uint32_t count, uint32_t *countWritten) |
|
1971 { |
|
1972 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
1973 |
|
1974 LOG3(("Http2Session::WriteSegments %p InternalState %X\n", |
|
1975 this, mDownstreamState)); |
|
1976 |
|
1977 *countWritten = 0; |
|
1978 |
|
1979 if (mClosed) |
|
1980 return NS_ERROR_FAILURE; |
|
1981 |
|
1982 nsresult rv = ConfirmTLSProfile(); |
|
1983 if (NS_FAILED(rv)) |
|
1984 return rv; |
|
1985 |
|
1986 SetWriteCallbacks(); |
|
1987 |
|
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; |
|
1999 |
|
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 } |
|
2007 |
|
2008 if (rv == NS_BASE_STREAM_CLOSED) { |
|
2009 CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR); |
|
2010 rv = NS_OK; |
|
2011 } |
|
2012 |
|
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 } |
|
2020 |
|
2021 return rv; |
|
2022 } |
|
2023 |
|
2024 // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER |
|
2025 // except the only frame type it will allow is SETTINGS |
|
2026 |
|
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 |
|
2030 |
|
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. |
|
2036 |
|
2037 MOZ_ASSERT(mInputFrameBufferUsed < 8, |
|
2038 "Frame Buffer Used Too Large for State"); |
|
2039 |
|
2040 rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed, |
|
2041 8 - mInputFrameBufferUsed, countWritten); |
|
2042 |
|
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 } |
|
2051 |
|
2052 LogIO(this, nullptr, "Reading Frame Header", |
|
2053 mInputFrameBuffer + mInputFrameBufferUsed, *countWritten); |
|
2054 |
|
2055 mInputFrameBufferUsed += *countWritten; |
|
2056 |
|
2057 if (mInputFrameBufferUsed < 8) |
|
2058 { |
|
2059 LOG3(("Http2Session::WriteSegments %p " |
|
2060 "BUFFERING FRAME HEADER incomplete size=%d", |
|
2061 this, mInputFrameBufferUsed)); |
|
2062 return rv; |
|
2063 } |
|
2064 |
|
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; |
|
2074 |
|
2075 if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS) { |
|
2076 mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM; |
|
2077 } else { |
|
2078 mInputFrameFinal = 0; |
|
2079 } |
|
2080 |
|
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 } |
|
2093 |
|
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 } |
|
2101 |
|
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)); |
|
2106 |
|
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 } |
|
2115 |
|
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 } |
|
2125 |
|
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 } |
|
2131 |
|
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 } |
|
2145 |
|
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 } |
|
2154 |
|
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"); |
|
2159 |
|
2160 rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed, |
|
2161 (8 + numControlBytes) - mInputFrameBufferUsed, |
|
2162 countWritten); |
|
2163 |
|
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 } |
|
2172 |
|
2173 LogIO(this, nullptr, "Reading Data Frame Padding Control", |
|
2174 mInputFrameBuffer + mInputFrameBufferUsed, *countWritten); |
|
2175 |
|
2176 mInputFrameBufferUsed += *countWritten; |
|
2177 |
|
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 } |
|
2184 |
|
2185 mInputFrameDataRead += numControlBytes; |
|
2186 |
|
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); |
|
2193 |
|
2194 LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this, |
|
2195 mInputFrameID, mPaddingLength)); |
|
2196 |
|
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 } |
|
2214 |
|
2215 if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { |
|
2216 nsresult streamCleanupCode; |
|
2217 |
|
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 } |
|
2226 |
|
2227 if (mDownstreamRstReason == COMPRESSION_ERROR) |
|
2228 mShouldGoAway = true; |
|
2229 |
|
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 } |
|
2239 |
|
2240 if (mDownstreamState == PROCESSING_DATA_FRAME || |
|
2241 mDownstreamState == PROCESSING_COMPLETE_HEADERS) { |
|
2242 |
|
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 */ |
|
2247 |
|
2248 mSegmentWriter = writer; |
|
2249 rv = mInputFrameDataStream->WriteSegments(this, count, countWritten); |
|
2250 mSegmentWriter = nullptr; |
|
2251 |
|
2252 mLastDataReadEpoch = mLastReadEpoch; |
|
2253 |
|
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; |
|
2259 |
|
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; |
|
2263 |
|
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 } |
|
2276 |
|
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 } |
|
2284 |
|
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 } |
|
2291 |
|
2292 return rv; |
|
2293 } |
|
2294 |
|
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)); |
|
2301 |
|
2302 if (!count) { |
|
2303 ResetDownstreamState(); |
|
2304 ResumeRecv(); |
|
2305 return NS_BASE_STREAM_WOULD_BLOCK; |
|
2306 } |
|
2307 |
|
2308 rv = NetworkRead(writer, trash, count, countWritten); |
|
2309 |
|
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 } |
|
2317 |
|
2318 LogIO(this, nullptr, "Discarding Frame", trash, *countWritten); |
|
2319 |
|
2320 mInputFrameDataRead += *countWritten; |
|
2321 |
|
2322 if (mInputFrameDataRead == mInputFrameDataSize) { |
|
2323 Http2Stream *streamToCleanup = nullptr; |
|
2324 if (mInputFrameFinal) { |
|
2325 streamToCleanup = mInputFrameDataStream; |
|
2326 } |
|
2327 |
|
2328 ResetDownstreamState(); |
|
2329 |
|
2330 if (streamToCleanup) { |
|
2331 CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR); |
|
2332 } |
|
2333 } |
|
2334 return rv; |
|
2335 } |
|
2336 |
|
2337 if (mDownstreamState != BUFFERING_CONTROL_FRAME) { |
|
2338 MOZ_ASSERT(false); // this cannot happen |
|
2339 return NS_ERROR_UNEXPECTED; |
|
2340 } |
|
2341 |
|
2342 MOZ_ASSERT(mInputFrameBufferUsed == 8, "Frame Buffer Header Not Present"); |
|
2343 MOZ_ASSERT(mInputFrameDataSize + 8 <= mInputFrameBufferSize, |
|
2344 "allocation for control frame insufficient"); |
|
2345 |
|
2346 rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead, |
|
2347 mInputFrameDataSize - mInputFrameDataRead, countWritten); |
|
2348 |
|
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 } |
|
2357 |
|
2358 LogIO(this, nullptr, "Reading Control Frame", |
|
2359 mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten); |
|
2360 |
|
2361 mInputFrameDataRead += *countWritten; |
|
2362 |
|
2363 if (mInputFrameDataRead != mInputFrameDataSize) |
|
2364 return NS_OK; |
|
2365 |
|
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 } |
|
2377 |
|
2378 MOZ_ASSERT(NS_FAILED(rv) || |
|
2379 mDownstreamState != BUFFERING_CONTROL_FRAME, |
|
2380 "Control Handler returned OK but did not change state"); |
|
2381 |
|
2382 if (mShouldGoAway && !mStreamTransactionHash.Count()) |
|
2383 Close(NS_OK); |
|
2384 return rv; |
|
2385 } |
|
2386 |
|
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; |
|
2392 |
|
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 } |
|
2399 |
|
2400 stream->DecrementClientReceiveWindow(bytes); |
|
2401 |
|
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(); |
|
2406 |
|
2407 LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u " |
|
2408 "unacked=%llu localWindow=%lld\n", |
|
2409 this, stream->StreamID(), bytes, unacked, localWindow)); |
|
2410 |
|
2411 if (!unacked) |
|
2412 return; |
|
2413 |
|
2414 if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) |
|
2415 return; |
|
2416 |
|
2417 if (!stream->HasSink()) { |
|
2418 LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n", |
|
2419 this, stream->StreamID())); |
|
2420 return; |
|
2421 } |
|
2422 |
|
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; |
|
2426 |
|
2427 LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n", |
|
2428 this, stream->StreamID(), toack)); |
|
2429 stream->IncrementClientReceiveWindow(toack); |
|
2430 |
|
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); |
|
2435 |
|
2436 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID()); |
|
2437 toack = PR_htonl(toack); |
|
2438 memcpy(packet + 8, &toack, 4); |
|
2439 |
|
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 } |
|
2444 |
|
2445 void |
|
2446 Http2Session::UpdateLocalSessionWindow(uint32_t bytes) |
|
2447 { |
|
2448 if (!bytes) |
|
2449 return; |
|
2450 |
|
2451 mLocalSessionWindow -= bytes; |
|
2452 |
|
2453 LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u " |
|
2454 "localWindow=%lld\n", this, bytes, mLocalSessionWindow)); |
|
2455 |
|
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; |
|
2461 |
|
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; |
|
2465 |
|
2466 LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", |
|
2467 this, toack)); |
|
2468 mLocalSessionWindow += toack; |
|
2469 |
|
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); |
|
2474 |
|
2475 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0); |
|
2476 toack = PR_htonl(toack); |
|
2477 memcpy(packet + 8, &toack, 4); |
|
2478 |
|
2479 LogIO(this, nullptr, "Session Window Update", packet, 12); |
|
2480 // dont flush here, this write can commonly be coalesced with others |
|
2481 } |
|
2482 |
|
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); |
|
2489 |
|
2490 UpdateLocalStreamWindow(stream, bytes); |
|
2491 UpdateLocalSessionWindow(bytes); |
|
2492 FlushOutputQueue(); |
|
2493 } |
|
2494 |
|
2495 void |
|
2496 Http2Session::Close(nsresult aReason) |
|
2497 { |
|
2498 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
2499 |
|
2500 if (mClosed) |
|
2501 return; |
|
2502 |
|
2503 LOG3(("Http2Session::Close %p %X", this, aReason)); |
|
2504 |
|
2505 mClosed = true; |
|
2506 |
|
2507 mStreamTransactionHash.Enumerate(ShutdownEnumerator, this); |
|
2508 mStreamIDHash.Clear(); |
|
2509 mStreamTransactionHash.Clear(); |
|
2510 |
|
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 } |
|
2526 |
|
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)); |
|
2533 |
|
2534 // Generally this arrives as a cancel event from the connection manager. |
|
2535 |
|
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 } |
|
2549 |
|
2550 //----------------------------------------------------------------------------- |
|
2551 // nsAHttpSegmentReader |
|
2552 //----------------------------------------------------------------------------- |
|
2553 |
|
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; |
|
2560 |
|
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(); |
|
2565 |
|
2566 if (!mOutputQueueUsed && mSegmentReader) { |
|
2567 // try and write directly without output queue |
|
2568 rv = mSegmentReader->OnReadSegment(buf, count, countRead); |
|
2569 |
|
2570 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { |
|
2571 *countRead = 0; |
|
2572 } else if (NS_FAILED(rv)) { |
|
2573 return rv; |
|
2574 } |
|
2575 |
|
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 } |
|
2585 |
|
2586 *countRead = count; |
|
2587 return NS_OK; |
|
2588 } |
|
2589 |
|
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. |
|
2593 |
|
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 |
|
2598 |
|
2599 if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved)) |
|
2600 return NS_BASE_STREAM_WOULD_BLOCK; |
|
2601 |
|
2602 memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count); |
|
2603 mOutputQueueUsed += count; |
|
2604 *countRead = count; |
|
2605 |
|
2606 FlushOutputQueue(); |
|
2607 |
|
2608 return NS_OK; |
|
2609 } |
|
2610 |
|
2611 nsresult |
|
2612 Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment) |
|
2613 { |
|
2614 if (mOutputQueueUsed) |
|
2615 FlushOutputQueue(); |
|
2616 |
|
2617 // would there be enough room to buffer this if needed? |
|
2618 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) |
|
2619 return NS_OK; |
|
2620 |
|
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; |
|
2625 |
|
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(); |
|
2630 |
|
2631 // is there enough room now? |
|
2632 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) |
|
2633 return NS_OK; |
|
2634 } |
|
2635 |
|
2636 // resize the buffers as needed |
|
2637 EnsureOutputBuffer(count + kQueueReserved); |
|
2638 |
|
2639 MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved), |
|
2640 "buffer not as large as expected"); |
|
2641 |
|
2642 return NS_OK; |
|
2643 } |
|
2644 |
|
2645 //----------------------------------------------------------------------------- |
|
2646 // nsAHttpSegmentWriter |
|
2647 //----------------------------------------------------------------------------- |
|
2648 |
|
2649 nsresult |
|
2650 Http2Session::OnWriteSegment(char *buf, |
|
2651 uint32_t count, uint32_t *countWritten) |
|
2652 { |
|
2653 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
2654 nsresult rv; |
|
2655 |
|
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 } |
|
2661 |
|
2662 if (mDownstreamState == PROCESSING_DATA_FRAME) { |
|
2663 |
|
2664 if (mInputFrameFinal && |
|
2665 mInputFrameDataRead == mInputFrameDataSize) { |
|
2666 *countWritten = 0; |
|
2667 SetNeedsCleanup(); |
|
2668 return NS_BASE_STREAM_CLOSED; |
|
2669 } |
|
2670 |
|
2671 count = std::min(count, mInputFrameDataSize - mInputFrameDataRead); |
|
2672 rv = NetworkRead(mSegmentWriter, buf, count, countWritten); |
|
2673 if (NS_FAILED(rv)) |
|
2674 return rv; |
|
2675 |
|
2676 LogIO(this, mInputFrameDataStream, "Reading Data Frame", |
|
2677 buf, *countWritten); |
|
2678 |
|
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 } |
|
2696 |
|
2697 mInputFrameDataStream->UpdateTransportReadEvents(*countWritten); |
|
2698 if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal) |
|
2699 ResetDownstreamState(); |
|
2700 |
|
2701 return rv; |
|
2702 } |
|
2703 |
|
2704 if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) { |
|
2705 |
|
2706 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && |
|
2707 mInputFrameFinal) { |
|
2708 *countWritten = 0; |
|
2709 SetNeedsCleanup(); |
|
2710 return NS_BASE_STREAM_CLOSED; |
|
2711 } |
|
2712 |
|
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; |
|
2721 |
|
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 } |
|
2731 |
|
2732 return NS_OK; |
|
2733 } |
|
2734 |
|
2735 return NS_ERROR_UNEXPECTED; |
|
2736 } |
|
2737 |
|
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())); |
|
2744 |
|
2745 // This will result in Close() being called |
|
2746 MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set"); |
|
2747 mNeedsCleanup = mInputFrameDataStream; |
|
2748 ResetDownstreamState(); |
|
2749 } |
|
2750 |
|
2751 void |
|
2752 Http2Session::ConnectPushedStream(Http2Stream *stream) |
|
2753 { |
|
2754 mReadyForRead.Push(stream); |
|
2755 ForceRecv(); |
|
2756 } |
|
2757 |
|
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 } |
|
2769 |
|
2770 nsresult |
|
2771 Http2Session::ConfirmTLSProfile() |
|
2772 { |
|
2773 if (mTLSProfileConfirmed) |
|
2774 return NS_OK; |
|
2775 |
|
2776 LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", |
|
2777 this, mConnection.get())); |
|
2778 |
|
2779 if (!gHttpHandler->EnforceHttp2TlsProfile()) { |
|
2780 LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this)); |
|
2781 mTLSProfileConfirmed = true; |
|
2782 return NS_OK; |
|
2783 } |
|
2784 |
|
2785 if (!mConnection) |
|
2786 return NS_ERROR_FAILURE; |
|
2787 |
|
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; |
|
2794 |
|
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 } |
|
2801 |
|
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 |
|
2810 |
|
2811 /* TODO: Enforce DHE >= 2048 || ECDHE >= 128 */ |
|
2812 |
|
2813 /* We are required to send SNI. We do that already, so no check is done |
|
2814 * here to make sure we did. */ |
|
2815 |
|
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. */ |
|
2820 |
|
2821 mTLSProfileConfirmed = true; |
|
2822 return NS_OK; |
|
2823 } |
|
2824 |
|
2825 |
|
2826 //----------------------------------------------------------------------------- |
|
2827 // Modified methods of nsAHttpConnection |
|
2828 //----------------------------------------------------------------------------- |
|
2829 |
|
2830 void |
|
2831 Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller) |
|
2832 { |
|
2833 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
2834 LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller)); |
|
2835 |
|
2836 // a trapped signal from the http transaction to the connection that |
|
2837 // it is no longer blocked on read. |
|
2838 |
|
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 } |
|
2845 |
|
2846 LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n", |
|
2847 this, stream->StreamID())); |
|
2848 |
|
2849 mReadyForWrite.Push(stream); |
|
2850 } |
|
2851 |
|
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())); |
|
2858 |
|
2859 mReadyForWrite.Push(stream); |
|
2860 SetWriteCallbacks(); |
|
2861 } |
|
2862 |
|
2863 bool |
|
2864 Http2Session::IsPersistent() |
|
2865 { |
|
2866 return true; |
|
2867 } |
|
2868 |
|
2869 nsresult |
|
2870 Http2Session::TakeTransport(nsISocketTransport **, |
|
2871 nsIAsyncInputStream **, nsIAsyncOutputStream **) |
|
2872 { |
|
2873 MOZ_ASSERT(false, "TakeTransport of Http2Session"); |
|
2874 return NS_ERROR_UNEXPECTED; |
|
2875 } |
|
2876 |
|
2877 nsHttpConnection * |
|
2878 Http2Session::TakeHttpConnection() |
|
2879 { |
|
2880 MOZ_ASSERT(false, "TakeHttpConnection of Http2Session"); |
|
2881 return nullptr; |
|
2882 } |
|
2883 |
|
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 } |
|
2890 |
|
2891 nsAHttpTransaction::Classifier |
|
2892 Http2Session::Classification() |
|
2893 { |
|
2894 if (!mConnection) |
|
2895 return nsAHttpTransaction::CLASS_GENERAL; |
|
2896 return mConnection->Classification(); |
|
2897 } |
|
2898 |
|
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 //----------------------------------------------------------------------------- |
|
2904 |
|
2905 void |
|
2906 Http2Session::SetConnection(nsAHttpConnection *) |
|
2907 { |
|
2908 // This is unexpected |
|
2909 MOZ_ASSERT(false, "Http2Session::SetConnection()"); |
|
2910 } |
|
2911 |
|
2912 void |
|
2913 Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **) |
|
2914 { |
|
2915 // This is unexpected |
|
2916 MOZ_ASSERT(false, "Http2Session::GetSecurityCallbacks()"); |
|
2917 } |
|
2918 |
|
2919 void |
|
2920 Http2Session::SetProxyConnectFailed() |
|
2921 { |
|
2922 MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()"); |
|
2923 } |
|
2924 |
|
2925 bool |
|
2926 Http2Session::IsDone() |
|
2927 { |
|
2928 return !mStreamTransactionHash.Count(); |
|
2929 } |
|
2930 |
|
2931 nsresult |
|
2932 Http2Session::Status() |
|
2933 { |
|
2934 MOZ_ASSERT(false, "Http2Session::Status()"); |
|
2935 return NS_ERROR_UNEXPECTED; |
|
2936 } |
|
2937 |
|
2938 uint32_t |
|
2939 Http2Session::Caps() |
|
2940 { |
|
2941 MOZ_ASSERT(false, "Http2Session::Caps()"); |
|
2942 return 0; |
|
2943 } |
|
2944 |
|
2945 void |
|
2946 Http2Session::SetDNSWasRefreshed() |
|
2947 { |
|
2948 MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()"); |
|
2949 } |
|
2950 |
|
2951 uint64_t |
|
2952 Http2Session::Available() |
|
2953 { |
|
2954 MOZ_ASSERT(false, "Http2Session::Available()"); |
|
2955 return 0; |
|
2956 } |
|
2957 |
|
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 } |
|
2967 |
|
2968 uint32_t |
|
2969 Http2Session::Http1xTransactionCount() |
|
2970 { |
|
2971 return 0; |
|
2972 } |
|
2973 |
|
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); |
|
2982 |
|
2983 list->AppendElement(key); |
|
2984 |
|
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 } |
|
2989 |
|
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. |
|
2996 |
|
2997 LOG3(("Http2Session::TakeSubTransactions %p\n", this)); |
|
2998 |
|
2999 if (mConcurrentHighWater > 0) |
|
3000 return NS_ERROR_ALREADY_OPENED; |
|
3001 |
|
3002 LOG3((" taking %d\n", mStreamTransactionHash.Count())); |
|
3003 |
|
3004 mStreamTransactionHash.Enumerate(TakeStream, &outTransactions); |
|
3005 return NS_OK; |
|
3006 } |
|
3007 |
|
3008 nsresult |
|
3009 Http2Session::AddTransaction(nsAHttpTransaction *) |
|
3010 { |
|
3011 // This API is meant for pipelining, Http2Session's should be |
|
3012 // extended with AddStream() |
|
3013 |
|
3014 MOZ_ASSERT(false, |
|
3015 "Http2Session::AddTransaction() should not be called"); |
|
3016 |
|
3017 return NS_ERROR_NOT_IMPLEMENTED; |
|
3018 } |
|
3019 |
|
3020 uint32_t |
|
3021 Http2Session::PipelineDepth() |
|
3022 { |
|
3023 return IsDone() ? 0 : 1; |
|
3024 } |
|
3025 |
|
3026 nsresult |
|
3027 Http2Session::SetPipelinePosition(int32_t position) |
|
3028 { |
|
3029 // This API is meant for pipelining, Http2Session's should be |
|
3030 // extended with AddStream() |
|
3031 |
|
3032 MOZ_ASSERT(false, |
|
3033 "Http2Session::SetPipelinePosition() should not be called"); |
|
3034 |
|
3035 return NS_ERROR_NOT_IMPLEMENTED; |
|
3036 } |
|
3037 |
|
3038 int32_t |
|
3039 Http2Session::PipelinePosition() |
|
3040 { |
|
3041 return 0; |
|
3042 } |
|
3043 |
|
3044 //----------------------------------------------------------------------------- |
|
3045 // Pass through methods of nsAHttpConnection |
|
3046 //----------------------------------------------------------------------------- |
|
3047 |
|
3048 nsAHttpConnection * |
|
3049 Http2Session::Connection() |
|
3050 { |
|
3051 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
3052 return mConnection; |
|
3053 } |
|
3054 |
|
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 } |
|
3065 |
|
3066 bool |
|
3067 Http2Session::IsReused() |
|
3068 { |
|
3069 return mConnection->IsReused(); |
|
3070 } |
|
3071 |
|
3072 nsresult |
|
3073 Http2Session::PushBack(const char *buf, uint32_t len) |
|
3074 { |
|
3075 return mConnection->PushBack(buf, len); |
|
3076 } |
|
3077 |
|
3078 } // namespace mozilla::net |
|
3079 } // namespace mozilla |