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