/src/mozilla-central/netwerk/protocol/http/Http2Stream.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 | 0 | #define LOG_ENABLED() LOG5_ENABLED() |
15 | | |
16 | | #include <algorithm> |
17 | | |
18 | | #include "Http2Compression.h" |
19 | | #include "Http2Session.h" |
20 | | #include "Http2Stream.h" |
21 | | #include "Http2Push.h" |
22 | | #include "TunnelUtils.h" |
23 | | |
24 | | #include "mozilla/BasePrincipal.h" |
25 | | #include "mozilla/Telemetry.h" |
26 | | #include "nsAlgorithm.h" |
27 | | #include "nsHttp.h" |
28 | | #include "nsHttpHandler.h" |
29 | | #include "nsHttpRequestHead.h" |
30 | | #include "nsIClassOfService.h" |
31 | | #include "nsIPipe.h" |
32 | | #include "nsISocketTransport.h" |
33 | | #include "nsStandardURL.h" |
34 | | #include "prnetdb.h" |
35 | | |
36 | | namespace mozilla { |
37 | | namespace net { |
38 | | |
39 | | Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, |
40 | | Http2Session *session, |
41 | | int32_t priority, |
42 | | uint64_t windowId) |
43 | | : mStreamID(0) |
44 | | , mSession(session) |
45 | | , mSegmentReader(nullptr) |
46 | | , mSegmentWriter(nullptr) |
47 | | , mUpstreamState(GENERATING_HEADERS) |
48 | | , mState(IDLE) |
49 | | , mRequestHeadersDone(0) |
50 | | , mOpenGenerated(0) |
51 | | , mAllHeadersReceived(0) |
52 | | , mQueued(0) |
53 | | , mSocketTransport(session->SocketTransport()) |
54 | | , mTransaction(httpTransaction) |
55 | | , mChunkSize(session->SendingChunkSize()) |
56 | | , mRequestBlockedOnRead(0) |
57 | | , mRecvdFin(0) |
58 | | , mReceivedData(0) |
59 | | , mRecvdReset(0) |
60 | | , mSentReset(0) |
61 | | , mCountAsActive(0) |
62 | | , mSentFin(0) |
63 | | , mSentWaitingFor(0) |
64 | | , mSetTCPSocketBuffer(0) |
65 | | , mBypassInputBuffer(0) |
66 | | , mTxInlineFrameSize(Http2Session::kDefaultBufferSize) |
67 | | , mTxInlineFrameUsed(0) |
68 | | , mTxStreamFrameSize(0) |
69 | | , mRequestBodyLenRemaining(0) |
70 | | , mLocalUnacked(0) |
71 | | , mBlockedOnRwin(false) |
72 | | , mTotalSent(0) |
73 | | , mTotalRead(0) |
74 | | , mPushSource(nullptr) |
75 | | , mAttempting0RTT(false) |
76 | | , mCurrentForegroundTabOuterContentWindowId(windowId) |
77 | | , mTransactionTabId(0) |
78 | | , mIsTunnel(false) |
79 | | , mPlainTextTunnel(false) |
80 | 0 | { |
81 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
82 | 0 |
|
83 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
84 | 0 | LOG3(("Http2Stream::Http2Stream %p trans=%p atrans=%p", this, trans, httpTransaction)); |
85 | 0 |
|
86 | 0 | mServerReceiveWindow = session->GetServerInitialStreamWindow(); |
87 | 0 | mClientReceiveWindow = session->PushAllowance(); |
88 | 0 |
|
89 | 0 | mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize); |
90 | 0 |
|
91 | 0 | static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority, |
92 | 0 | "Lowest Priority should be less than kNormalPriority"); |
93 | 0 |
|
94 | 0 | // values of priority closer to 0 are higher priority for the priority |
95 | 0 | // argument. This value is used as a group, which maps to a |
96 | 0 | // weight that is related to the nsISupportsPriority that we are given. |
97 | 0 | int32_t httpPriority; |
98 | 0 | if (priority >= nsISupportsPriority::PRIORITY_LOWEST) { |
99 | 0 | httpPriority = kWorstPriority; |
100 | 0 | } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) { |
101 | 0 | httpPriority = kBestPriority; |
102 | 0 | } else { |
103 | 0 | httpPriority = kNormalPriority + priority; |
104 | 0 | } |
105 | 0 | MOZ_ASSERT(httpPriority >= 0); |
106 | 0 | SetPriority(static_cast<uint32_t>(httpPriority)); |
107 | 0 |
|
108 | 0 | if (trans) { |
109 | 0 | mTransactionTabId = trans->TopLevelOuterContentWindowId(); |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | Http2Stream::~Http2Stream() |
114 | 0 | { |
115 | 0 | ClearPushSource(); |
116 | 0 | ClearTransactionsBlockedOnTunnel(); |
117 | 0 | mStreamID = Http2Session::kDeadStreamID; |
118 | 0 |
|
119 | 0 | LOG3(("Http2Stream::~Http2Stream %p", this)); |
120 | 0 | } |
121 | | |
122 | | void |
123 | | Http2Stream::ClearPushSource() |
124 | 0 | { |
125 | 0 | if (mPushSource) { |
126 | 0 | mPushSource->SetConsumerStream(nullptr); |
127 | 0 | mPushSource = nullptr; |
128 | 0 | } |
129 | 0 | } |
130 | | |
131 | | // ReadSegments() is used to write data down the socket. Generally, HTTP |
132 | | // request data is pulled from the approriate transaction and |
133 | | // converted to HTTP/2 data. Sometimes control data like a window-update is |
134 | | // generated instead. |
135 | | |
136 | | nsresult |
137 | | Http2Stream::ReadSegments(nsAHttpSegmentReader *reader, |
138 | | uint32_t count, |
139 | | uint32_t *countRead) |
140 | 0 | { |
141 | 0 | LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x", |
142 | 0 | this, reader, count, mUpstreamState)); |
143 | 0 |
|
144 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
145 | 0 |
|
146 | 0 | nsresult rv = NS_ERROR_UNEXPECTED; |
147 | 0 | mRequestBlockedOnRead = 0; |
148 | 0 |
|
149 | 0 | if (mRecvdFin || mRecvdReset) { |
150 | 0 | // Don't transmit any request frames if the peer cannot respond |
151 | 0 | LOG3(("Http2Stream %p ReadSegments request stream aborted due to" |
152 | 0 | " response side closure\n", this)); |
153 | 0 | return NS_ERROR_ABORT; |
154 | 0 | } |
155 | 0 |
|
156 | 0 | // avoid runt chunks if possible by anticipating |
157 | 0 | // full data frames |
158 | 0 | if (count > (mChunkSize + 8)) { |
159 | 0 | uint32_t numchunks = count / (mChunkSize + 8); |
160 | 0 | count = numchunks * (mChunkSize + 8); |
161 | 0 | } |
162 | 0 |
|
163 | 0 | switch (mUpstreamState) { |
164 | 0 | case GENERATING_HEADERS: |
165 | 0 | case GENERATING_BODY: |
166 | 0 | case SENDING_BODY: |
167 | 0 | // Call into the HTTP Transaction to generate the HTTP request |
168 | 0 | // stream. That stream will show up in OnReadSegment(). |
169 | 0 | mSegmentReader = reader; |
170 | 0 | rv = mTransaction->ReadSegments(this, count, countRead); |
171 | 0 | mSegmentReader = nullptr; |
172 | 0 |
|
173 | 0 | LOG3(("Http2Stream::ReadSegments %p trans readsegments rv %" PRIx32 " read=%d\n", |
174 | 0 | this, static_cast<uint32_t>(rv), *countRead)); |
175 | 0 |
|
176 | 0 | // Check to see if the transaction's request could be written out now. |
177 | 0 | // If not, mark the stream for callback when writing can proceed. |
178 | 0 | if (NS_SUCCEEDED(rv) && |
179 | 0 | mUpstreamState == GENERATING_HEADERS && |
180 | 0 | !mRequestHeadersDone) |
181 | 0 | mSession->TransactionHasDataToWrite(this); |
182 | 0 |
|
183 | 0 | // mTxinlineFrameUsed represents any queued un-sent frame. It might |
184 | 0 | // be 0 if there is no such frame, which is not a gurantee that we |
185 | 0 | // don't have more request body to send - just that any data that was |
186 | 0 | // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is |
187 | 0 | // a queued, but complete, http/2 frame length. |
188 | 0 |
|
189 | 0 | // Mark that we are blocked on read if the http transaction needs to |
190 | 0 | // provide more of the request message body and there is nothing queued |
191 | 0 | // for writing |
192 | 0 | if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) |
193 | 0 | mRequestBlockedOnRead = 1; |
194 | 0 |
|
195 | 0 | // A transaction that had already generated its headers before it was |
196 | 0 | // queued at the session level (due to concurrency concerns) may not call |
197 | 0 | // onReadSegment off the ReadSegments() stack above. |
198 | 0 | if (mUpstreamState == GENERATING_HEADERS && NS_SUCCEEDED(rv)) { |
199 | 0 | LOG3(("Http2Stream %p ReadSegments forcing OnReadSegment call\n", this)); |
200 | 0 | uint32_t wasted = 0; |
201 | 0 | mSegmentReader = reader; |
202 | 0 | Unused << OnReadSegment("", 0, &wasted); |
203 | 0 | mSegmentReader = nullptr; |
204 | 0 | } |
205 | 0 |
|
206 | 0 | // If the sending flow control window is open (!mBlockedOnRwin) then |
207 | 0 | // continue sending the request |
208 | 0 | if (!mBlockedOnRwin && mOpenGenerated && |
209 | 0 | !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { |
210 | 0 | MOZ_ASSERT(!mQueued); |
211 | 0 | MOZ_ASSERT(mRequestHeadersDone); |
212 | 0 | LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, " |
213 | 0 | "mUpstreamState=%x\n",this, mStreamID, mUpstreamState)); |
214 | 0 | if (mSentFin) { |
215 | 0 | ChangeState(UPSTREAM_COMPLETE); |
216 | 0 | } else { |
217 | 0 | GenerateDataFrameHeader(0, true); |
218 | 0 | ChangeState(SENDING_FIN_STREAM); |
219 | 0 | mSession->TransactionHasDataToWrite(this); |
220 | 0 | rv = NS_BASE_STREAM_WOULD_BLOCK; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | break; |
224 | 0 |
|
225 | 0 | case SENDING_FIN_STREAM: |
226 | 0 | // We were trying to send the FIN-STREAM but were blocked from |
227 | 0 | // sending it out - try again. |
228 | 0 | if (!mSentFin) { |
229 | 0 | mSegmentReader = reader; |
230 | 0 | rv = TransmitFrame(nullptr, nullptr, false); |
231 | 0 | mSegmentReader = nullptr; |
232 | 0 | MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, |
233 | 0 | "Transmit Frame should be all or nothing"); |
234 | 0 | if (NS_SUCCEEDED(rv)) |
235 | 0 | ChangeState(UPSTREAM_COMPLETE); |
236 | 0 | } else { |
237 | 0 | rv = NS_OK; |
238 | 0 | mTxInlineFrameUsed = 0; // cancel fin data packet |
239 | 0 | ChangeState(UPSTREAM_COMPLETE); |
240 | 0 | } |
241 | 0 |
|
242 | 0 | *countRead = 0; |
243 | 0 |
|
244 | 0 | // don't change OK to WOULD BLOCK. we are really done sending if OK |
245 | 0 | break; |
246 | 0 |
|
247 | 0 | case UPSTREAM_COMPLETE: |
248 | 0 | *countRead = 0; |
249 | 0 | rv = NS_OK; |
250 | 0 | break; |
251 | 0 |
|
252 | 0 | default: |
253 | 0 | MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state"); |
254 | 0 | break; |
255 | 0 | } |
256 | 0 |
|
257 | 0 | return rv; |
258 | 0 | } |
259 | | |
260 | | uint64_t |
261 | | Http2Stream::LocalUnAcked() |
262 | 0 | { |
263 | 0 | // reduce unacked by the amount of undelivered data |
264 | 0 | // to help assert flow control |
265 | 0 | uint64_t undelivered = mSimpleBuffer.Available(); |
266 | 0 |
|
267 | 0 | if (undelivered > mLocalUnacked) { |
268 | 0 | return 0; |
269 | 0 | } |
270 | 0 | return mLocalUnacked - undelivered; |
271 | 0 | } |
272 | | |
273 | | nsresult |
274 | | Http2Stream::BufferInput(uint32_t count, uint32_t *countWritten) |
275 | 0 | { |
276 | 0 | char buf[SimpleBufferPage::kSimpleBufferPageSize]; |
277 | 0 | if (SimpleBufferPage::kSimpleBufferPageSize < count) { |
278 | 0 | count = SimpleBufferPage::kSimpleBufferPageSize; |
279 | 0 | } |
280 | 0 |
|
281 | 0 | mBypassInputBuffer = 1; |
282 | 0 | nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten); |
283 | 0 | mBypassInputBuffer = 0; |
284 | 0 |
|
285 | 0 | if (NS_SUCCEEDED(rv)) { |
286 | 0 | rv = mSimpleBuffer.Write(buf, *countWritten); |
287 | 0 | if (NS_FAILED(rv)) { |
288 | 0 | MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY); |
289 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
290 | 0 | } |
291 | 0 | } |
292 | 0 | return rv; |
293 | 0 | } |
294 | | |
295 | | bool |
296 | | Http2Stream::DeferCleanup(nsresult status) |
297 | 0 | { |
298 | 0 | // do not cleanup a stream that has data buffered for the transaction |
299 | 0 | return (NS_SUCCEEDED(status) && mSimpleBuffer.Available()); |
300 | 0 | } |
301 | | |
302 | | // WriteSegments() is used to read data off the socket. Generally this is |
303 | | // just a call through to the associated nsHttpTransaction for this stream |
304 | | // for the remaining data bytes indicated by the current DATA frame. |
305 | | |
306 | | nsresult |
307 | | Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer, |
308 | | uint32_t count, |
309 | | uint32_t *countWritten) |
310 | 0 | { |
311 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
312 | 0 | MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); |
313 | 0 |
|
314 | 0 | LOG3(("Http2Stream::WriteSegments %p count=%d state=%x", |
315 | 0 | this, count, mUpstreamState)); |
316 | 0 |
|
317 | 0 | mSegmentWriter = writer; |
318 | 0 | nsresult rv = mTransaction->WriteSegments(this, count, countWritten); |
319 | 0 |
|
320 | 0 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) { |
321 | 0 | // consuming transaction won't take data. but we need to read it into a buffer so that it |
322 | 0 | // won't block other streams. but we should not advance the flow control window |
323 | 0 | // so that we'll eventually push back on the sender. |
324 | 0 |
|
325 | 0 | // with tunnels you need to make sure that this is an underlying connction established |
326 | 0 | // that can be meaningfully giving this signal |
327 | 0 | bool doBuffer = true; |
328 | 0 | if (mIsTunnel) { |
329 | 0 | RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction()); |
330 | 0 | if (qiTrans) { |
331 | 0 | doBuffer = qiTrans->ConnectedReadyForInput(); |
332 | 0 | } |
333 | 0 | } |
334 | 0 | // stash this data |
335 | 0 | if (doBuffer) { |
336 | 0 | rv = BufferInput(count, countWritten); |
337 | 0 | LOG3(("Http2Stream::WriteSegments %p Buffered %" PRIX32 " %d\n", this, |
338 | 0 | static_cast<uint32_t>(rv), *countWritten)); |
339 | 0 | } |
340 | 0 | } |
341 | 0 | mSegmentWriter = nullptr; |
342 | 0 | return rv; |
343 | 0 | } |
344 | | |
345 | | nsresult |
346 | | Http2Stream::MakeOriginURL(const nsACString &origin, nsCOMPtr<nsIURI> &url) |
347 | 0 | { |
348 | 0 | nsAutoCString scheme; |
349 | 0 | nsresult rv = net_ExtractURLScheme(origin, scheme); |
350 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
351 | 0 | return MakeOriginURL(scheme, origin, url); |
352 | 0 | } |
353 | | |
354 | | nsresult |
355 | | Http2Stream::MakeOriginURL(const nsACString &scheme, const nsACString &origin, |
356 | | nsCOMPtr<nsIURI> &url) |
357 | 0 | { |
358 | 0 | return NS_MutateURI(new nsStandardURL::Mutator()) |
359 | 0 | .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init, |
360 | 0 | nsIStandardURL::URLTYPE_AUTHORITY, |
361 | 0 | scheme.EqualsLiteral("http") ? NS_HTTP_DEFAULT_PORT |
362 | 0 | : NS_HTTPS_DEFAULT_PORT, |
363 | 0 | nsCString(origin), nullptr, nullptr, nullptr)) |
364 | 0 | .Finalize(url); |
365 | 0 | } |
366 | | |
367 | | void |
368 | | Http2Stream::CreatePushHashKey(const nsCString &scheme, |
369 | | const nsCString &hostHeader, |
370 | | const mozilla::OriginAttributes &originAttributes, |
371 | | uint64_t serial, |
372 | | const nsACString& pathInfo, |
373 | | nsCString &outOrigin, |
374 | | nsCString &outKey) |
375 | 0 | { |
376 | 0 | nsCString fullOrigin = scheme; |
377 | 0 | fullOrigin.AppendLiteral("://"); |
378 | 0 | fullOrigin.Append(hostHeader); |
379 | 0 |
|
380 | 0 | nsCOMPtr<nsIURI> origin; |
381 | 0 | nsresult rv = Http2Stream::MakeOriginURL(scheme, fullOrigin, origin); |
382 | 0 |
|
383 | 0 | if (NS_SUCCEEDED(rv)) { |
384 | 0 | rv = origin->GetAsciiSpec(outOrigin); |
385 | 0 | outOrigin.Trim("/", false, true, false); |
386 | 0 | } |
387 | 0 |
|
388 | 0 | if (NS_FAILED(rv)) { |
389 | 0 | // Fallback to plain text copy - this may end up behaving poorly |
390 | 0 | outOrigin = fullOrigin; |
391 | 0 | } |
392 | 0 |
|
393 | 0 | outKey = outOrigin; |
394 | 0 | outKey.AppendLiteral("/["); |
395 | 0 | nsAutoCString suffix; |
396 | 0 | originAttributes.CreateSuffix(suffix); |
397 | 0 | outKey.Append(suffix); |
398 | 0 | outKey.Append(']'); |
399 | 0 | outKey.AppendLiteral("/[http2."); |
400 | 0 | outKey.AppendInt(serial); |
401 | 0 | outKey.Append(']'); |
402 | 0 | outKey.Append(pathInfo); |
403 | 0 | } |
404 | | |
405 | | nsresult |
406 | | Http2Stream::ParseHttpRequestHeaders(const char *buf, |
407 | | uint32_t avail, |
408 | | uint32_t *countUsed) |
409 | 0 | { |
410 | 0 | // Returns NS_OK even if the headers are incomplete |
411 | 0 | // set mRequestHeadersDone flag if they are complete |
412 | 0 |
|
413 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
414 | 0 | MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS); |
415 | 0 | MOZ_ASSERT(!mRequestHeadersDone); |
416 | 0 |
|
417 | 0 | LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x", |
418 | 0 | this, avail, mUpstreamState)); |
419 | 0 |
|
420 | 0 | mFlatHttpRequestHeaders.Append(buf, avail); |
421 | 0 | nsHttpRequestHead *head = mTransaction->RequestHead(); |
422 | 0 |
|
423 | 0 | // We can use the simple double crlf because firefox is the |
424 | 0 | // only client we are parsing |
425 | 0 | int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); |
426 | 0 |
|
427 | 0 | if (endHeader == kNotFound) { |
428 | 0 | // We don't have all the headers yet |
429 | 0 | LOG3(("Http2Stream::ParseHttpRequestHeaders %p " |
430 | 0 | "Need more header bytes. Len = %d", |
431 | 0 | this, mFlatHttpRequestHeaders.Length())); |
432 | 0 | *countUsed = avail; |
433 | 0 | return NS_OK; |
434 | 0 | } |
435 | 0 |
|
436 | 0 | // We have recvd all the headers, trim the local |
437 | 0 | // buffer of the final empty line, and set countUsed to reflect |
438 | 0 | // the whole header has been consumed. |
439 | 0 | uint32_t oldLen = mFlatHttpRequestHeaders.Length(); |
440 | 0 | mFlatHttpRequestHeaders.SetLength(endHeader + 2); |
441 | 0 | *countUsed = avail - (oldLen - endHeader) + 4; |
442 | 0 | mRequestHeadersDone = 1; |
443 | 0 |
|
444 | 0 | nsAutoCString authorityHeader; |
445 | 0 | nsAutoCString hashkey; |
446 | 0 | nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader); |
447 | 0 | if (NS_FAILED(rv)) { |
448 | 0 | MOZ_ASSERT(false); |
449 | 0 | return rv; |
450 | 0 | } |
451 | 0 |
|
452 | 0 | nsAutoCString requestURI; |
453 | 0 | head->RequestURI(requestURI); |
454 | 0 |
|
455 | 0 | mozilla::OriginAttributes originAttributes; |
456 | 0 | mSocketTransport->GetOriginAttributes(&originAttributes); |
457 | 0 |
|
458 | 0 | CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"), |
459 | 0 | authorityHeader, originAttributes, mSession->Serial(), |
460 | 0 | requestURI, |
461 | 0 | mOrigin, hashkey); |
462 | 0 |
|
463 | 0 | // check the push cache for GET |
464 | 0 | if (head->IsGet()) { |
465 | 0 | // from :scheme, :authority, :path |
466 | 0 | nsIRequestContext *requestContext = mTransaction->RequestContext(); |
467 | 0 | SpdyPushCache *cache = nullptr; |
468 | 0 | if (requestContext) { |
469 | 0 | requestContext->GetSpdyPushCache(&cache); |
470 | 0 | } |
471 | 0 |
|
472 | 0 | Http2PushedStream *pushedStream = nullptr; |
473 | 0 |
|
474 | 0 | // If a push stream is attached to the transaction via onPush, match only with that |
475 | 0 | // one. This occurs when a push was made with in conjunction with a nsIHttpPushListener |
476 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
477 | 0 | if (trans && (pushedStream = trans->TakePushedStream())) { |
478 | 0 | if (pushedStream->mSession == mSession) { |
479 | 0 | LOG3(("Pushed Stream match based on OnPush correlation %p", pushedStream)); |
480 | 0 | } else { |
481 | 0 | LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64 " %" PRId64 "\n", |
482 | 0 | pushedStream, pushedStream->mSession->Serial(), mSession->Serial())); |
483 | 0 | pushedStream->OnPushFailed(); |
484 | 0 | pushedStream = nullptr; |
485 | 0 | } |
486 | 0 | } |
487 | 0 |
|
488 | 0 | // we remove the pushedstream from the push cache so that |
489 | 0 | // it will not be used for another GET. This does not destroy the |
490 | 0 | // stream itself - that is done when the transactionhash is done with it. |
491 | 0 | if (cache && !pushedStream){ |
492 | 0 | pushedStream = cache->RemovePushedStreamHttp2(hashkey); |
493 | 0 | } |
494 | 0 |
|
495 | 0 | LOG3(("Pushed Stream Lookup " |
496 | 0 | "session=%p key=%s requestcontext=%p cache=%p hit=%p\n", |
497 | 0 | mSession, hashkey.get(), requestContext, cache, pushedStream)); |
498 | 0 |
|
499 | 0 | if (pushedStream) { |
500 | 0 | LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n", |
501 | 0 | pushedStream, pushedStream->StreamID(), hashkey.get())); |
502 | 0 | pushedStream->SetConsumerStream(this); |
503 | 0 | mPushSource = pushedStream; |
504 | 0 | SetSentFin(true); |
505 | 0 | AdjustPushedPriority(); |
506 | 0 |
|
507 | 0 | // There is probably pushed data buffered so trigger a read manually |
508 | 0 | // as we can't rely on future network events to do it |
509 | 0 | mSession->ConnectPushedStream(this); |
510 | 0 | mOpenGenerated = 1; |
511 | 0 |
|
512 | 0 | // if the "mother stream" had TRR, this one is a TRR stream too! |
513 | 0 | RefPtr<nsHttpConnectionInfo> ci(Transaction()->ConnectionInfo()); |
514 | 0 | if (ci && ci->GetTrrUsed()) { |
515 | 0 | mSession->IncrementTrrCounter(); |
516 | 0 | } |
517 | 0 |
|
518 | 0 | return NS_OK; |
519 | 0 | } |
520 | 0 | } |
521 | 0 | return NS_OK; |
522 | 0 | } |
523 | | |
524 | | // This is really a headers frame, but open is pretty clear from a workflow pov |
525 | | nsresult |
526 | | Http2Stream::GenerateOpen() |
527 | 0 | { |
528 | 0 | // It is now OK to assign a streamID that we are assured will |
529 | 0 | // be monotonically increasing amongst new streams on this |
530 | 0 | // session |
531 | 0 | mStreamID = mSession->RegisterStreamID(this); |
532 | 0 | MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd"); |
533 | 0 | MOZ_ASSERT(!mOpenGenerated); |
534 | 0 |
|
535 | 0 | mOpenGenerated = 1; |
536 | 0 |
|
537 | 0 | nsHttpRequestHead *head = mTransaction->RequestHead(); |
538 | 0 | nsAutoCString requestURI; |
539 | 0 | head->RequestURI(requestURI); |
540 | 0 | LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", |
541 | 0 | this, mStreamID, mSession, requestURI.get())); |
542 | 0 |
|
543 | 0 | if (mStreamID >= 0x80000000) { |
544 | 0 | // streamID must fit in 31 bits. Evading This is theoretically possible |
545 | 0 | // because stream ID assignment is asynchronous to stream creation |
546 | 0 | // because of the protocol requirement that the new stream ID |
547 | 0 | // be monotonically increasing. In reality this is really not possible |
548 | 0 | // because new streams stop being added to a session with millions of |
549 | 0 | // IDs still available and no race condition is going to bridge that gap; |
550 | 0 | // so we can be comfortable on just erroring out for correctness in that |
551 | 0 | // case. |
552 | 0 | LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); |
553 | 0 | return NS_ERROR_UNEXPECTED; |
554 | 0 | } |
555 | 0 |
|
556 | 0 | // Now we need to convert the flat http headers into a set |
557 | 0 | // of HTTP/2 headers by writing to mTxInlineFrame{sz} |
558 | 0 |
|
559 | 0 | nsCString compressedData; |
560 | 0 | nsAutoCString authorityHeader; |
561 | 0 | nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader); |
562 | 0 | if (NS_FAILED(rv)) { |
563 | 0 | MOZ_ASSERT(false); |
564 | 0 | return rv; |
565 | 0 | } |
566 | 0 |
|
567 | 0 | nsDependentCString scheme(head->IsHTTPS() ? "https" : "http"); |
568 | 0 | if (head->IsConnect()) { |
569 | 0 | MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction()); |
570 | 0 | mIsTunnel = true; |
571 | 0 | mRequestBodyLenRemaining = 0x0fffffffffffffffULL; |
572 | 0 |
|
573 | 0 | // Our normal authority has an implicit port, best to use an |
574 | 0 | // explicit one with a tunnel |
575 | 0 | nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo(); |
576 | 0 | if (!ci) { |
577 | 0 | return NS_ERROR_UNEXPECTED; |
578 | 0 | } |
579 | 0 | |
580 | 0 | authorityHeader = ci->GetOrigin(); |
581 | 0 | authorityHeader.Append(':'); |
582 | 0 | authorityHeader.AppendInt(ci->OriginPort()); |
583 | 0 | } |
584 | 0 |
|
585 | 0 | nsAutoCString method; |
586 | 0 | nsAutoCString path; |
587 | 0 | head->Method(method); |
588 | 0 | head->Path(path); |
589 | 0 | rv = mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, |
590 | 0 | method, |
591 | 0 | path, |
592 | 0 | authorityHeader, |
593 | 0 | scheme, |
594 | 0 | head->IsConnect(), |
595 | 0 | compressedData); |
596 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
597 | 0 |
|
598 | 0 | int64_t clVal = mSession->Compressor()->GetParsedContentLength(); |
599 | 0 | if (clVal != -1) { |
600 | 0 | mRequestBodyLenRemaining = clVal; |
601 | 0 | } |
602 | 0 |
|
603 | 0 | // Determine whether to put the fin bit on the header frame or whether |
604 | 0 | // to wait for a data packet to put it on. |
605 | 0 | uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY; |
606 | 0 |
|
607 | 0 | if (head->IsGet() || |
608 | 0 | head->IsHead()) { |
609 | 0 | // for GET and HEAD place the fin bit right on the |
610 | 0 | // header packet |
611 | 0 |
|
612 | 0 | SetSentFin(true); |
613 | 0 | firstFrameFlags |= Http2Session::kFlag_END_STREAM; |
614 | 0 | } else if (head->IsPost() || |
615 | 0 | head->IsPut() || |
616 | 0 | head->IsConnect()) { |
617 | 0 | // place fin in a data frame even for 0 length messages for iterop |
618 | 0 | } else if (!mRequestBodyLenRemaining) { |
619 | 0 | // for other HTTP extension methods, rely on the content-length |
620 | 0 | // to determine whether or not to put fin on headers |
621 | 0 | SetSentFin(true); |
622 | 0 | firstFrameFlags |= Http2Session::kFlag_END_STREAM; |
623 | 0 | } |
624 | 0 |
|
625 | 0 | // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the |
626 | 0 | // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing |
627 | 0 | // frame for the new headers and for the first one a priority field. There is |
628 | 0 | // no question this is ugly, but a 16KB HEADERS frame should be a long |
629 | 0 | // tail event, so this is really just for correctness and a nop in the base case. |
630 | 0 | // |
631 | 0 |
|
632 | 0 | MOZ_ASSERT(!mTxInlineFrameUsed); |
633 | 0 |
|
634 | 0 | uint32_t dataLength = compressedData.Length(); |
635 | 0 | uint32_t maxFrameData = Http2Session::kMaxFrameData - 5; // 5 bytes for priority |
636 | 0 | uint32_t numFrames = 1; |
637 | 0 |
|
638 | 0 | if (dataLength > maxFrameData) { |
639 | 0 | numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) / |
640 | 0 | Http2Session::kMaxFrameData; |
641 | 0 | MOZ_ASSERT (numFrames > 1); |
642 | 0 | } |
643 | 0 |
|
644 | 0 | // note that we could still have 1 frame for 0 bytes of data. that's ok. |
645 | 0 |
|
646 | 0 | uint32_t messageSize = dataLength; |
647 | 0 | messageSize += Http2Session::kFrameHeaderBytes + 5; // frame header + priority overhead in HEADERS frame |
648 | 0 | messageSize += (numFrames - 1) * Http2Session::kFrameHeaderBytes; // frame header overhead in CONTINUATION frames |
649 | 0 |
|
650 | 0 | EnsureBuffer(mTxInlineFrame, messageSize, |
651 | 0 | mTxInlineFrameUsed, mTxInlineFrameSize); |
652 | 0 |
|
653 | 0 | mTxInlineFrameUsed += messageSize; |
654 | 0 | UpdatePriorityDependency(); |
655 | 0 | LOG3(("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with " |
656 | 0 | "priority weight %u dep 0x%X frames %u uri=%s\n", |
657 | 0 | this, mTxInlineFrameUsed, mStreamID, mPriorityWeight, |
658 | 0 | mPriorityDependency, numFrames, requestURI.get())); |
659 | 0 |
|
660 | 0 | uint32_t outputOffset = 0; |
661 | 0 | uint32_t compressedDataOffset = 0; |
662 | 0 | for (uint32_t idx = 0; idx < numFrames; ++idx) { |
663 | 0 | uint32_t flags, frameLen; |
664 | 0 | bool lastFrame = (idx == numFrames - 1); |
665 | 0 |
|
666 | 0 | flags = 0; |
667 | 0 | frameLen = maxFrameData; |
668 | 0 | if (!idx) { |
669 | 0 | flags |= firstFrameFlags; |
670 | 0 | // Only the first frame needs the 4-byte offset |
671 | 0 | maxFrameData = Http2Session::kMaxFrameData; |
672 | 0 | } |
673 | 0 | if (lastFrame) { |
674 | 0 | frameLen = dataLength; |
675 | 0 | flags |= Http2Session::kFlag_END_HEADERS; |
676 | 0 | } |
677 | 0 | dataLength -= frameLen; |
678 | 0 |
|
679 | 0 | mSession->CreateFrameHeader( |
680 | 0 | mTxInlineFrame.get() + outputOffset, |
681 | 0 | frameLen + (idx ? 0 : 5), |
682 | 0 | (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS, |
683 | 0 | flags, mStreamID); |
684 | 0 | outputOffset += Http2Session::kFrameHeaderBytes; |
685 | 0 |
|
686 | 0 | if (!idx) { |
687 | 0 | uint32_t wireDep = PR_htonl(mPriorityDependency); |
688 | 0 | memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4); |
689 | 0 | memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1); |
690 | 0 | outputOffset += 5; |
691 | 0 | } |
692 | 0 |
|
693 | 0 | memcpy(mTxInlineFrame.get() + outputOffset, |
694 | 0 | compressedData.BeginReading() + compressedDataOffset, frameLen); |
695 | 0 | compressedDataOffset += frameLen; |
696 | 0 | outputOffset += frameLen; |
697 | 0 | } |
698 | 0 |
|
699 | 0 | Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length()); |
700 | 0 |
|
701 | 0 | // The size of the input headers is approximate |
702 | 0 | uint32_t ratio = |
703 | 0 | compressedData.Length() * 100 / |
704 | 0 | (11 + requestURI.Length() + |
705 | 0 | mFlatHttpRequestHeaders.Length()); |
706 | 0 |
|
707 | 0 | mFlatHttpRequestHeaders.Truncate(); |
708 | 0 | Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); |
709 | 0 | return NS_OK; |
710 | 0 | } |
711 | | |
712 | | void |
713 | | Http2Stream::AdjustInitialWindow() |
714 | 0 | { |
715 | 0 | // The default initial_window is sized for pushed streams. When we |
716 | 0 | // generate a client pulled stream we want to disable flow control for |
717 | 0 | // the stream with a window update. Do the same for pushed streams |
718 | 0 | // when they connect to a pull. |
719 | 0 |
|
720 | 0 | // >0 even numbered IDs are pushed streams. |
721 | 0 | // odd numbered IDs are pulled streams. |
722 | 0 | // 0 is the sink for a pushed stream. |
723 | 0 | Http2Stream *stream = this; |
724 | 0 | if (!mStreamID) { |
725 | 0 | MOZ_ASSERT(mPushSource); |
726 | 0 | if (!mPushSource) |
727 | 0 | return; |
728 | 0 | stream = mPushSource; |
729 | 0 | MOZ_ASSERT(stream->mStreamID); |
730 | 0 | MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream |
731 | 0 |
|
732 | 0 | // If the pushed stream has recvd a FIN, there is no reason to update |
733 | 0 | // the window |
734 | 0 | if (stream->RecvdFin() || stream->RecvdReset()) |
735 | 0 | return; |
736 | 0 | } |
737 | 0 | |
738 | 0 | if (stream->mState == RESERVED_BY_REMOTE) { |
739 | 0 | // h2-14 prevents sending a window update in this state |
740 | 0 | return; |
741 | 0 | } |
742 | 0 | |
743 | 0 | // right now mClientReceiveWindow is the lower push limit |
744 | 0 | // bump it up to the pull limit set by the channel or session |
745 | 0 | // don't allow windows less than push |
746 | 0 | uint32_t bump = 0; |
747 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
748 | 0 | if (trans && trans->InitialRwin()) { |
749 | 0 | bump = (trans->InitialRwin() > mClientReceiveWindow) ? |
750 | 0 | (trans->InitialRwin() - mClientReceiveWindow) : 0; |
751 | 0 | } else { |
752 | 0 | MOZ_ASSERT(mSession->InitialRwin() >= mClientReceiveWindow); |
753 | 0 | bump = mSession->InitialRwin() - mClientReceiveWindow; |
754 | 0 | } |
755 | 0 |
|
756 | 0 | LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n", |
757 | 0 | this, stream->mStreamID, bump)); |
758 | 0 | if (!bump) { // nothing to do |
759 | 0 | return; |
760 | 0 | } |
761 | 0 | |
762 | 0 | EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4, |
763 | 0 | mTxInlineFrameUsed, mTxInlineFrameSize); |
764 | 0 | uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; |
765 | 0 | mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4; |
766 | 0 |
|
767 | 0 | mSession->CreateFrameHeader(packet, 4, |
768 | 0 | Http2Session::FRAME_TYPE_WINDOW_UPDATE, |
769 | 0 | 0, stream->mStreamID); |
770 | 0 |
|
771 | 0 | mClientReceiveWindow += bump; |
772 | 0 | bump = PR_htonl(bump); |
773 | 0 | memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4); |
774 | 0 | } |
775 | | |
776 | | void |
777 | | Http2Stream::AdjustPushedPriority() |
778 | 0 | { |
779 | 0 | // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams. |
780 | 0 | // 0 is the sink for a pushed stream. |
781 | 0 |
|
782 | 0 | if (mStreamID || !mPushSource) |
783 | 0 | return; |
784 | 0 | |
785 | 0 | MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1)); |
786 | 0 |
|
787 | 0 | // If the pushed stream has recvd a FIN, there is no reason to update |
788 | 0 | // the window |
789 | 0 | if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) |
790 | 0 | return; |
791 | 0 | |
792 | 0 | EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5, |
793 | 0 | mTxInlineFrameUsed, mTxInlineFrameSize); |
794 | 0 | uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; |
795 | 0 | mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5; |
796 | 0 |
|
797 | 0 | mSession->CreateFrameHeader(packet, 5, |
798 | 0 | Http2Session::FRAME_TYPE_PRIORITY, 0, |
799 | 0 | mPushSource->mStreamID); |
800 | 0 |
|
801 | 0 | mPushSource->SetPriority(mPriority); |
802 | 0 | memset(packet + Http2Session::kFrameHeaderBytes, 0, 4); |
803 | 0 | memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1); |
804 | 0 |
|
805 | 0 | LOG3(("AdjustPushedPriority %p id 0x%X to weight %X\n", this, mPushSource->mStreamID, |
806 | 0 | mPriorityWeight)); |
807 | 0 | } |
808 | | |
809 | | void |
810 | | Http2Stream::UpdateTransportReadEvents(uint32_t count) |
811 | 0 | { |
812 | 0 | mTotalRead += count; |
813 | 0 | if (!mSocketTransport) { |
814 | 0 | return; |
815 | 0 | } |
816 | 0 | |
817 | 0 | mTransaction->OnTransportStatus(mSocketTransport, |
818 | 0 | NS_NET_STATUS_RECEIVING_FROM, |
819 | 0 | mTotalRead); |
820 | 0 | } |
821 | | |
822 | | void |
823 | | Http2Stream::UpdateTransportSendEvents(uint32_t count) |
824 | 0 | { |
825 | 0 | mTotalSent += count; |
826 | 0 |
|
827 | 0 | // normally on non-windows platform we use TCP autotuning for |
828 | 0 | // the socket buffers, and this works well (managing enough |
829 | 0 | // buffers for BDP while conserving memory) for HTTP even when |
830 | 0 | // it creates really deep queues. However this 'buffer bloat' is |
831 | 0 | // a problem for http/2 because it ruins the low latency properties |
832 | 0 | // necessary for PING and cancel to work meaningfully. |
833 | 0 | // |
834 | 0 | // If this stream represents a large upload, disable autotuning for |
835 | 0 | // the session and cap the send buffers by default at 128KB. |
836 | 0 | // (10Mbit/sec @ 100ms) |
837 | 0 | // |
838 | 0 | uint32_t bufferSize = gHttpHandler->SpdySendBufferSize(); |
839 | 0 | if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) { |
840 | 0 | mSetTCPSocketBuffer = 1; |
841 | 0 | mSocketTransport->SetSendBufferSize(bufferSize); |
842 | 0 | } |
843 | 0 |
|
844 | 0 | if (mUpstreamState != SENDING_FIN_STREAM) |
845 | 0 | mTransaction->OnTransportStatus(mSocketTransport, |
846 | 0 | NS_NET_STATUS_SENDING_TO, |
847 | 0 | mTotalSent); |
848 | 0 |
|
849 | 0 | if (!mSentWaitingFor && !mRequestBodyLenRemaining) { |
850 | 0 | mSentWaitingFor = 1; |
851 | 0 | mTransaction->OnTransportStatus(mSocketTransport, |
852 | 0 | NS_NET_STATUS_WAITING_FOR, |
853 | 0 | 0); |
854 | 0 | } |
855 | 0 | } |
856 | | |
857 | | nsresult |
858 | | Http2Stream::TransmitFrame(const char *buf, |
859 | | uint32_t *countUsed, |
860 | | bool forceCommitment) |
861 | 0 | { |
862 | 0 | // If TransmitFrame returns SUCCESS than all the data is sent (or at least |
863 | 0 | // buffered at the session level), if it returns WOULD_BLOCK then none of |
864 | 0 | // the data is sent. |
865 | 0 |
|
866 | 0 | // You can call this function with no data and no out parameter in order to |
867 | 0 | // flush internal buffers that were previously blocked on writing. You can |
868 | 0 | // of course feed new data to it as well. |
869 | 0 |
|
870 | 0 | LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d", |
871 | 0 | this, mTxInlineFrameUsed, mTxStreamFrameSize)); |
872 | 0 | if (countUsed) |
873 | 0 | *countUsed = 0; |
874 | 0 |
|
875 | 0 | if (!mTxInlineFrameUsed) { |
876 | 0 | MOZ_ASSERT(!buf); |
877 | 0 | return NS_OK; |
878 | 0 | } |
879 | 0 |
|
880 | 0 | MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit"); |
881 | 0 | MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader"); |
882 | 0 | MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed), |
883 | 0 | "TransmitFrame arguments inconsistent"); |
884 | 0 |
|
885 | 0 | uint32_t transmittedCount; |
886 | 0 | nsresult rv; |
887 | 0 |
|
888 | 0 | // In the (relatively common) event that we have a small amount of data |
889 | 0 | // split between the inlineframe and the streamframe, then move the stream |
890 | 0 | // data into the inlineframe via copy in order to coalesce into one write. |
891 | 0 | // Given the interaction with ssl this is worth the small copy cost. |
892 | 0 | if (mTxStreamFrameSize && mTxInlineFrameUsed && |
893 | 0 | mTxStreamFrameSize < Http2Session::kDefaultBufferSize && |
894 | 0 | mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) { |
895 | 0 | LOG3(("Coalesce Transmit")); |
896 | 0 | memcpy (&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize); |
897 | 0 | if (countUsed) |
898 | 0 | *countUsed += mTxStreamFrameSize; |
899 | 0 | mTxInlineFrameUsed += mTxStreamFrameSize; |
900 | 0 | mTxStreamFrameSize = 0; |
901 | 0 | } |
902 | 0 |
|
903 | 0 | rv = |
904 | 0 | mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed, |
905 | 0 | forceCommitment); |
906 | 0 |
|
907 | 0 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) { |
908 | 0 | MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK"); |
909 | 0 | mSession->TransactionHasDataToWrite(this); |
910 | 0 | } |
911 | 0 | if (NS_FAILED(rv)) // this will include WOULD_BLOCK |
912 | 0 | return rv; |
913 | 0 | |
914 | 0 | // This function calls mSegmentReader->OnReadSegment to report the actual http/2 |
915 | 0 | // bytes through to the session object and then the HttpConnection which calls |
916 | 0 | // the socket write function. It will accept all of the inline and stream |
917 | 0 | // data because of the above 'commitment' even if it has to buffer |
918 | 0 | |
919 | 0 | rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()), |
920 | 0 | mTxInlineFrameUsed, |
921 | 0 | &transmittedCount); |
922 | 0 | LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p " |
923 | 0 | "stream=%p result %" PRIx32 " len=%d", |
924 | 0 | mSession, this, static_cast<uint32_t>(rv), transmittedCount)); |
925 | 0 |
|
926 | 0 | MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, |
927 | 0 | "inconsistent inline commitment result"); |
928 | 0 |
|
929 | 0 | if (NS_FAILED(rv)) |
930 | 0 | return rv; |
931 | 0 | |
932 | 0 | MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed, |
933 | 0 | "inconsistent inline commitment count"); |
934 | 0 |
|
935 | 0 | Http2Session::LogIO(mSession, this, "Writing from Inline Buffer", |
936 | 0 | reinterpret_cast<char*>(mTxInlineFrame.get()), |
937 | 0 | transmittedCount); |
938 | 0 |
|
939 | 0 | if (mTxStreamFrameSize) { |
940 | 0 | if (!buf) { |
941 | 0 | // this cannot happen |
942 | 0 | MOZ_ASSERT(false, "Stream transmit with null buf argument to " |
943 | 0 | "TransmitFrame()"); |
944 | 0 | LOG3(("Stream transmit with null buf argument to TransmitFrame()\n")); |
945 | 0 | return NS_ERROR_UNEXPECTED; |
946 | 0 | } |
947 | 0 |
|
948 | 0 | // If there is already data buffered, just add to that to form |
949 | 0 | // a single TLS Application Data Record - otherwise skip the memcpy |
950 | 0 | if (mSession->AmountOfOutputBuffered()) { |
951 | 0 | rv = mSession->BufferOutput(buf, mTxStreamFrameSize, |
952 | 0 | &transmittedCount); |
953 | 0 | } else { |
954 | 0 | rv = mSession->OnReadSegment(buf, mTxStreamFrameSize, |
955 | 0 | &transmittedCount); |
956 | 0 | } |
957 | 0 |
|
958 | 0 | LOG3(("Http2Stream::TransmitFrame for regular session=%p " |
959 | 0 | "stream=%p result %" PRIx32 " len=%d", |
960 | 0 | mSession, this, static_cast<uint32_t>(rv), transmittedCount)); |
961 | 0 |
|
962 | 0 | MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, |
963 | 0 | "inconsistent stream commitment result"); |
964 | 0 |
|
965 | 0 | if (NS_FAILED(rv)) |
966 | 0 | return rv; |
967 | 0 | |
968 | 0 | MOZ_ASSERT(transmittedCount == mTxStreamFrameSize, |
969 | 0 | "inconsistent stream commitment count"); |
970 | 0 |
|
971 | 0 | Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer", |
972 | 0 | buf, transmittedCount); |
973 | 0 |
|
974 | 0 | *countUsed += mTxStreamFrameSize; |
975 | 0 | } |
976 | 0 |
|
977 | 0 | if (!mAttempting0RTT) { |
978 | 0 | mSession->FlushOutputQueue(); |
979 | 0 | } |
980 | 0 |
|
981 | 0 | // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 |
982 | 0 | UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); |
983 | 0 |
|
984 | 0 | mTxInlineFrameUsed = 0; |
985 | 0 | mTxStreamFrameSize = 0; |
986 | 0 |
|
987 | 0 | return NS_OK; |
988 | 0 | } |
989 | | |
990 | | void |
991 | | Http2Stream::ChangeState(enum upstreamStateType newState) |
992 | 0 | { |
993 | 0 | LOG3(("Http2Stream::ChangeState() %p from %X to %X", |
994 | 0 | this, mUpstreamState, newState)); |
995 | 0 | mUpstreamState = newState; |
996 | 0 | } |
997 | | |
998 | | void |
999 | | Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) |
1000 | 0 | { |
1001 | 0 | LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d", |
1002 | 0 | this, dataLength, lastFrame)); |
1003 | 0 |
|
1004 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
1005 | 0 | MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty"); |
1006 | 0 | MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty"); |
1007 | 0 |
|
1008 | 0 | uint8_t frameFlags = 0; |
1009 | 0 | if (lastFrame) { |
1010 | 0 | frameFlags |= Http2Session::kFlag_END_STREAM; |
1011 | 0 | if (dataLength) |
1012 | 0 | SetSentFin(true); |
1013 | 0 | } |
1014 | 0 |
|
1015 | 0 | mSession->CreateFrameHeader(mTxInlineFrame.get(), |
1016 | 0 | dataLength, |
1017 | 0 | Http2Session::FRAME_TYPE_DATA, |
1018 | 0 | frameFlags, mStreamID); |
1019 | 0 |
|
1020 | 0 | mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes; |
1021 | 0 | mTxStreamFrameSize = dataLength; |
1022 | 0 | } |
1023 | | |
1024 | | // ConvertResponseHeaders is used to convert the response headers |
1025 | | // into HTTP/1 format and report some telemetry |
1026 | | nsresult |
1027 | | Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor, |
1028 | | nsACString &aHeadersIn, |
1029 | | nsACString &aHeadersOut, |
1030 | | int32_t &httpResponseCode) |
1031 | 0 | { |
1032 | 0 | nsresult rv = |
1033 | 0 | decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()), |
1034 | 0 | aHeadersIn.Length(), |
1035 | 0 | aHeadersOut, false); |
1036 | 0 | if (NS_FAILED(rv)) { |
1037 | 0 | LOG3(("Http2Stream::ConvertResponseHeaders %p decode Error\n", this)); |
1038 | 0 | return rv; |
1039 | 0 | } |
1040 | 0 |
|
1041 | 0 | nsAutoCString statusString; |
1042 | 0 | decompressor->GetStatus(statusString); |
1043 | 0 | if (statusString.IsEmpty()) { |
1044 | 0 | LOG3(("Http2Stream::ConvertResponseHeaders %p Error - no status\n", this)); |
1045 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
1046 | 0 | } |
1047 | 0 |
|
1048 | 0 | nsresult errcode; |
1049 | 0 | httpResponseCode = statusString.ToInteger(&errcode); |
1050 | 0 |
|
1051 | 0 | // Ensure the :status is just an HTTP status code |
1052 | 0 | // https://tools.ietf.org/html/rfc7540#section-8.1.2.4 |
1053 | 0 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1352146 |
1054 | 0 | nsAutoCString parsedStatusString; |
1055 | 0 | parsedStatusString.AppendInt(httpResponseCode); |
1056 | 0 | if (!parsedStatusString.Equals(statusString)) { |
1057 | 0 | LOG3(("Http2Stream::ConvertResposeHeaders %p status %s is not just a code", |
1058 | 0 | this, statusString.BeginReading())); |
1059 | 0 | // Results in stream reset with PROTOCOL_ERROR |
1060 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
1061 | 0 | } |
1062 | 0 |
|
1063 | 0 | LOG3(("Http2Stream::ConvertResponseHeaders %p response code %d\n", this, httpResponseCode)); |
1064 | 0 | if (mIsTunnel) { |
1065 | 0 | LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode)); |
1066 | 0 | if ((httpResponseCode / 100) != 2) { |
1067 | 0 | MapStreamToPlainText(); |
1068 | 0 | } |
1069 | 0 | MapStreamToHttpConnection(); |
1070 | 0 | ClearTransactionsBlockedOnTunnel(); |
1071 | 0 | } |
1072 | 0 |
|
1073 | 0 | if (httpResponseCode == 101) { |
1074 | 0 | // 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream |
1075 | 0 | LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this)); |
1076 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
1077 | 0 | } |
1078 | 0 |
|
1079 | 0 | if (httpResponseCode == 421) { |
1080 | 0 | // Origin Frame requires 421 to remove this origin from the origin set |
1081 | 0 | mSession->Received421(mTransaction->ConnectionInfo()); |
1082 | 0 | } |
1083 | 0 |
|
1084 | 0 | if (aHeadersIn.Length() && aHeadersOut.Length()) { |
1085 | 0 | Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length()); |
1086 | 0 | uint32_t ratio = |
1087 | 0 | aHeadersIn.Length() * 100 / aHeadersOut.Length(); |
1088 | 0 | Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); |
1089 | 0 | } |
1090 | 0 |
|
1091 | 0 | // The decoding went ok. Now we can customize and clean up. |
1092 | 0 |
|
1093 | 0 | aHeadersIn.Truncate(); |
1094 | 0 | aHeadersOut.AppendLiteral("X-Firefox-Spdy: h2"); |
1095 | 0 | aHeadersOut.AppendLiteral("\r\n\r\n"); |
1096 | 0 | LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading())); |
1097 | 0 | if (mIsTunnel && !mPlainTextTunnel) { |
1098 | 0 | aHeadersOut.Truncate(); |
1099 | 0 | LOG(("Http2Stream::ConvertHeaders %p 0x%X headers removed for tunnel\n", |
1100 | 0 | this, mStreamID)); |
1101 | 0 | } |
1102 | 0 | return NS_OK; |
1103 | 0 | } |
1104 | | |
1105 | | // ConvertPushHeaders is used to convert the pushed request headers |
1106 | | // into HTTP/1 format and report some telemetry |
1107 | | nsresult |
1108 | | Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor, |
1109 | | nsACString &aHeadersIn, |
1110 | | nsACString &aHeadersOut) |
1111 | 0 | { |
1112 | 0 | nsresult rv = |
1113 | 0 | decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()), |
1114 | 0 | aHeadersIn.Length(), |
1115 | 0 | aHeadersOut, true); |
1116 | 0 | if (NS_FAILED(rv)) { |
1117 | 0 | LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this)); |
1118 | 0 | return rv; |
1119 | 0 | } |
1120 | 0 |
|
1121 | 0 | nsCString method; |
1122 | 0 | decompressor->GetHost(mHeaderHost); |
1123 | 0 | decompressor->GetScheme(mHeaderScheme); |
1124 | 0 | decompressor->GetPath(mHeaderPath); |
1125 | 0 |
|
1126 | 0 | if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) { |
1127 | 0 | LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required " |
1128 | 0 | "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(), |
1129 | 0 | mHeaderPath.get())); |
1130 | 0 | return NS_ERROR_ILLEGAL_VALUE; |
1131 | 0 | } |
1132 | 0 |
|
1133 | 0 | decompressor->GetMethod(method); |
1134 | 0 | if (!method.EqualsLiteral("GET")) { |
1135 | 0 | LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n", |
1136 | 0 | this, method.get())); |
1137 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
1138 | 0 | } |
1139 | 0 |
|
1140 | 0 | aHeadersIn.Truncate(); |
1141 | 0 | LOG (("id 0x%X decoded push headers %s %s %s are:\n%s", mStreamID, |
1142 | 0 | mHeaderScheme.get(), mHeaderHost.get(), mHeaderPath.get(), |
1143 | 0 | aHeadersOut.BeginReading())); |
1144 | 0 | return NS_OK; |
1145 | 0 | } |
1146 | | |
1147 | | nsresult |
1148 | | Http2Stream::ConvertResponseTrailers(Http2Decompressor *decompressor, |
1149 | | nsACString &aTrailersIn) |
1150 | 0 | { |
1151 | 0 | LOG3(("Http2Stream::ConvertResponseTrailers %p", this)); |
1152 | 0 | nsAutoCString flatTrailers; |
1153 | 0 |
|
1154 | 0 | nsresult rv = |
1155 | 0 | decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aTrailersIn.BeginReading()), |
1156 | 0 | aTrailersIn.Length(), |
1157 | 0 | flatTrailers, false); |
1158 | 0 | if (NS_FAILED(rv)) { |
1159 | 0 | LOG3(("Http2Stream::ConvertResponseTrailers %p decode Error", this)); |
1160 | 0 | return rv; |
1161 | 0 | } |
1162 | 0 |
|
1163 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
1164 | 0 | if (trans) { |
1165 | 0 | trans->SetHttpTrailers(flatTrailers); |
1166 | 0 | } else { |
1167 | 0 | LOG3(("Http2Stream::ConvertResponseTrailers %p no trans", this)); |
1168 | 0 | } |
1169 | 0 |
|
1170 | 0 | return NS_OK; |
1171 | 0 | } |
1172 | | |
1173 | | void |
1174 | | Http2Stream::Close(nsresult reason) |
1175 | 0 | { |
1176 | 0 | // In case we are connected to a push, make sure the push knows we are closed, |
1177 | 0 | // so it doesn't try to give us any more DATA that comes on it after our close. |
1178 | 0 | ClearPushSource(); |
1179 | 0 |
|
1180 | 0 | mTransaction->Close(reason); |
1181 | 0 | } |
1182 | | |
1183 | | void |
1184 | | Http2Stream::SetResponseIsComplete() |
1185 | 0 | { |
1186 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
1187 | 0 | if (trans) { |
1188 | 0 | trans->SetResponseIsComplete(); |
1189 | 0 | } |
1190 | 0 | } |
1191 | | |
1192 | | void |
1193 | | Http2Stream::SetAllHeadersReceived() |
1194 | 0 | { |
1195 | 0 | if (mAllHeadersReceived) { |
1196 | 0 | return; |
1197 | 0 | } |
1198 | 0 | |
1199 | 0 | if (mState == RESERVED_BY_REMOTE) { |
1200 | 0 | // pushed streams needs to wait until headers have |
1201 | 0 | // arrived to open up their window |
1202 | 0 | LOG3(("Http2Stream::SetAllHeadersReceived %p state OPEN from reserved\n", this)); |
1203 | 0 | mState = OPEN; |
1204 | 0 | AdjustInitialWindow(); |
1205 | 0 | } |
1206 | 0 |
|
1207 | 0 | mAllHeadersReceived = 1; |
1208 | 0 | } |
1209 | | |
1210 | | bool |
1211 | | Http2Stream::AllowFlowControlledWrite() |
1212 | 0 | { |
1213 | 0 | return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0); |
1214 | 0 | } |
1215 | | |
1216 | | void |
1217 | | Http2Stream::UpdateServerReceiveWindow(int32_t delta) |
1218 | 0 | { |
1219 | 0 | mServerReceiveWindow += delta; |
1220 | 0 |
|
1221 | 0 | if (mBlockedOnRwin && AllowFlowControlledWrite()) { |
1222 | 0 | LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X " |
1223 | 0 | "Open stream window\n", this, mStreamID)); |
1224 | 0 | mSession->TransactionHasDataToWrite(this); } |
1225 | 0 | } |
1226 | | |
1227 | | void |
1228 | | Http2Stream::SetPriority(uint32_t newPriority) |
1229 | 0 | { |
1230 | 0 | int32_t httpPriority = static_cast<int32_t>(newPriority); |
1231 | 0 | if (httpPriority > kWorstPriority) { |
1232 | 0 | httpPriority = kWorstPriority; |
1233 | 0 | } else if (httpPriority < kBestPriority) { |
1234 | 0 | httpPriority = kBestPriority; |
1235 | 0 | } |
1236 | 0 | mPriority = static_cast<uint32_t>(httpPriority); |
1237 | 0 | mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) - |
1238 | 0 | (httpPriority - kNormalPriority); |
1239 | 0 |
|
1240 | 0 | mPriorityDependency = 0; // maybe adjusted later |
1241 | 0 | } |
1242 | | |
1243 | | void |
1244 | | Http2Stream::SetPriorityDependency(uint32_t newDependency, uint8_t newWeight, |
1245 | | bool exclusive) |
1246 | 0 | { |
1247 | 0 | // undefined what it means when the server sends a priority frame. ignore it. |
1248 | 0 | LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X " |
1249 | 0 | "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight, |
1250 | 0 | exclusive)); |
1251 | 0 | } |
1252 | | |
1253 | | static uint32_t |
1254 | | GetPriorityDependencyFromTransaction(nsHttpTransaction *trans) |
1255 | 0 | { |
1256 | 0 | MOZ_ASSERT(trans); |
1257 | 0 |
|
1258 | 0 | uint32_t classFlags = trans->ClassOfService(); |
1259 | 0 |
|
1260 | 0 | if (classFlags & nsIClassOfService::UrgentStart) { |
1261 | 0 | return Http2Session::kUrgentStartGroupID; |
1262 | 0 | } |
1263 | 0 | |
1264 | 0 | if (classFlags & nsIClassOfService::Leader) { |
1265 | 0 | return Http2Session::kLeaderGroupID; |
1266 | 0 | } |
1267 | 0 | |
1268 | 0 | if (classFlags & nsIClassOfService::Follower) { |
1269 | 0 | return Http2Session::kFollowerGroupID; |
1270 | 0 | } |
1271 | 0 | |
1272 | 0 | if (classFlags & nsIClassOfService::Speculative) { |
1273 | 0 | return Http2Session::kSpeculativeGroupID; |
1274 | 0 | } |
1275 | 0 | |
1276 | 0 | if (classFlags & nsIClassOfService::Background) { |
1277 | 0 | return Http2Session::kBackgroundGroupID; |
1278 | 0 | } |
1279 | 0 | |
1280 | 0 | if (classFlags & nsIClassOfService::Unblocked) { |
1281 | 0 | return Http2Session::kOtherGroupID; |
1282 | 0 | } |
1283 | 0 | |
1284 | 0 | return Http2Session::kFollowerGroupID; // unmarked followers |
1285 | 0 | } |
1286 | | |
1287 | | void |
1288 | | Http2Stream::UpdatePriorityDependency() |
1289 | 0 | { |
1290 | 0 | if (!mSession->UseH2Deps()) { |
1291 | 0 | return; |
1292 | 0 | } |
1293 | 0 | |
1294 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
1295 | 0 | if (!trans) { |
1296 | 0 | return; |
1297 | 0 | } |
1298 | 0 | |
1299 | 0 | // we create 6 fake dependency streams per session, |
1300 | 0 | // these streams are never opened with HEADERS. our first opened stream is 0xd |
1301 | 0 | // 3 depends 0, weight 200, leader class (kLeaderGroupID) |
1302 | 0 | // 5 depends 0, weight 100, other (kOtherGroupID) |
1303 | 0 | // 7 depends 0, weight 0, background (kBackgroundGroupID) |
1304 | 0 | // 9 depends 7, weight 0, speculative (kSpeculativeGroupID) |
1305 | 0 | // b depends 3, weight 0, follower class (kFollowerGroupID) |
1306 | 0 | // d depends 0, weight 240, urgent-start class (kUrgentStartGroupID) |
1307 | 0 | // |
1308 | 0 | // streams for leaders (html, js, css) depend on 3 |
1309 | 0 | // streams for folowers (images) depend on b |
1310 | 0 | // default streams (xhr, async js) depend on 5 |
1311 | 0 | // explicit bg streams (beacon, etc..) depend on 7 |
1312 | 0 | // spculative bg streams depend on 9 |
1313 | 0 | // urgent-start streams depend on d |
1314 | 0 | |
1315 | 0 | mPriorityDependency = GetPriorityDependencyFromTransaction(trans); |
1316 | 0 |
|
1317 | 0 | if (gHttpHandler->ActiveTabPriority() && |
1318 | 0 | mTransactionTabId != mCurrentForegroundTabOuterContentWindowId && |
1319 | 0 | mPriorityDependency != Http2Session::kUrgentStartGroupID) { |
1320 | 0 | LOG3(("Http2Stream::UpdatePriorityDependency %p " |
1321 | 0 | " depends on background group for trans %p\n", |
1322 | 0 | this, trans)); |
1323 | 0 | mPriorityDependency = Http2Session::kBackgroundGroupID; |
1324 | 0 |
|
1325 | 0 | nsHttp::NotifyActiveTabLoadOptimization(); |
1326 | 0 | } |
1327 | 0 |
|
1328 | 0 | LOG3(("Http2Stream::UpdatePriorityDependency %p " |
1329 | 0 | "depends on stream 0x%X\n", |
1330 | 0 | this, mPriorityDependency)); |
1331 | 0 | } |
1332 | | |
1333 | | void |
1334 | | Http2Stream::TopLevelOuterContentWindowIdChanged(uint64_t windowId) |
1335 | 0 | { |
1336 | 0 | MOZ_ASSERT(gHttpHandler->ActiveTabPriority()); |
1337 | 0 |
|
1338 | 0 | LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged " |
1339 | 0 | "%p windowId=%" PRIx64 "\n", |
1340 | 0 | this, windowId)); |
1341 | 0 |
|
1342 | 0 | mCurrentForegroundTabOuterContentWindowId = windowId; |
1343 | 0 |
|
1344 | 0 | if (!mSession->UseH2Deps()) { |
1345 | 0 | return; |
1346 | 0 | } |
1347 | 0 | |
1348 | 0 | // Urgent start takes an absolute precedence, so don't |
1349 | 0 | // change mPriorityDependency here. |
1350 | 0 | if (mPriorityDependency == Http2Session::kUrgentStartGroupID) { |
1351 | 0 | return; |
1352 | 0 | } |
1353 | 0 | |
1354 | 0 | if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) { |
1355 | 0 | mPriorityDependency = Http2Session::kBackgroundGroupID; |
1356 | 0 | LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p " |
1357 | 0 | "move into background group.\n", this)); |
1358 | 0 |
|
1359 | 0 | nsHttp::NotifyActiveTabLoadOptimization(); |
1360 | 0 | } else { |
1361 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
1362 | 0 | if (!trans) { |
1363 | 0 | return; |
1364 | 0 | } |
1365 | 0 | |
1366 | 0 | mPriorityDependency = GetPriorityDependencyFromTransaction(trans); |
1367 | 0 | LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p " |
1368 | 0 | "depends on stream 0x%X\n", this, mPriorityDependency)); |
1369 | 0 | } |
1370 | 0 |
|
1371 | 0 | if (mStreamID) { |
1372 | 0 | mSession->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight); |
1373 | 0 | } |
1374 | 0 | } |
1375 | | |
1376 | | void |
1377 | | Http2Stream::SetRecvdFin(bool aStatus) |
1378 | 0 | { |
1379 | 0 | mRecvdFin = aStatus ? 1 : 0; |
1380 | 0 | if (!aStatus) |
1381 | 0 | return; |
1382 | 0 | |
1383 | 0 | if (mState == OPEN || mState == RESERVED_BY_REMOTE) { |
1384 | 0 | mState = CLOSED_BY_REMOTE; |
1385 | 0 | } else if (mState == CLOSED_BY_LOCAL) { |
1386 | 0 | mState = CLOSED; |
1387 | 0 | } |
1388 | 0 | } |
1389 | | |
1390 | | void |
1391 | | Http2Stream::SetSentFin(bool aStatus) |
1392 | 0 | { |
1393 | 0 | mSentFin = aStatus ? 1 : 0; |
1394 | 0 | if (!aStatus) |
1395 | 0 | return; |
1396 | 0 | |
1397 | 0 | if (mState == OPEN || mState == RESERVED_BY_REMOTE) { |
1398 | 0 | mState = CLOSED_BY_LOCAL; |
1399 | 0 | } else if (mState == CLOSED_BY_REMOTE) { |
1400 | 0 | mState = CLOSED; |
1401 | 0 | } |
1402 | 0 | } |
1403 | | |
1404 | | void |
1405 | | Http2Stream::SetRecvdReset(bool aStatus) |
1406 | 0 | { |
1407 | 0 | mRecvdReset = aStatus ? 1 : 0; |
1408 | 0 | if (!aStatus) |
1409 | 0 | return; |
1410 | 0 | mState = CLOSED; |
1411 | 0 | } |
1412 | | |
1413 | | void |
1414 | | Http2Stream::SetSentReset(bool aStatus) |
1415 | 0 | { |
1416 | 0 | mSentReset = aStatus ? 1 : 0; |
1417 | 0 | if (!aStatus) |
1418 | 0 | return; |
1419 | 0 | mState = CLOSED; |
1420 | 0 | } |
1421 | | |
1422 | | //----------------------------------------------------------------------------- |
1423 | | // nsAHttpSegmentReader |
1424 | | //----------------------------------------------------------------------------- |
1425 | | |
1426 | | nsresult |
1427 | | Http2Stream::OnReadSegment(const char *buf, |
1428 | | uint32_t count, |
1429 | | uint32_t *countRead) |
1430 | 0 | { |
1431 | 0 | LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x", |
1432 | 0 | this, count, mUpstreamState)); |
1433 | 0 |
|
1434 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
1435 | 0 | MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader"); |
1436 | 0 |
|
1437 | 0 | nsresult rv = NS_ERROR_UNEXPECTED; |
1438 | 0 | uint32_t dataLength; |
1439 | 0 |
|
1440 | 0 | switch (mUpstreamState) { |
1441 | 0 | case GENERATING_HEADERS: |
1442 | 0 | // The buffer is the HTTP request stream, including at least part of the |
1443 | 0 | // HTTP request header. This state's job is to build a HEADERS frame |
1444 | 0 | // from the header information. count is the number of http bytes available |
1445 | 0 | // (which may include more than the header), and in countRead we return |
1446 | 0 | // the number of those bytes that we consume (i.e. the portion that are |
1447 | 0 | // header bytes) |
1448 | 0 |
|
1449 | 0 | if (!mRequestHeadersDone) { |
1450 | 0 | if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) { |
1451 | 0 | return rv; |
1452 | 0 | } |
1453 | 0 | } |
1454 | 0 | |
1455 | 0 | if (mRequestHeadersDone && !mOpenGenerated) { |
1456 | 0 | if (!mSession->TryToActivate(this)) { |
1457 | 0 | LOG3(("Http2Stream::OnReadSegment %p cannot activate now. queued.\n", this)); |
1458 | 0 | return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; |
1459 | 0 | } |
1460 | 0 | if (NS_FAILED(rv = GenerateOpen())) { |
1461 | 0 | return rv; |
1462 | 0 | } |
1463 | 0 | } |
1464 | 0 | |
1465 | 0 | LOG3(("ParseHttpRequestHeaders %p used %d of %d. " |
1466 | 0 | "requestheadersdone = %d mOpenGenerated = %d\n", |
1467 | 0 | this, *countRead, count, mRequestHeadersDone, mOpenGenerated)); |
1468 | 0 | if (mOpenGenerated) { |
1469 | 0 | SetHTTPState(OPEN); |
1470 | 0 | AdjustInitialWindow(); |
1471 | 0 | // This version of TransmitFrame cannot block |
1472 | 0 | rv = TransmitFrame(nullptr, nullptr, true); |
1473 | 0 | ChangeState(GENERATING_BODY); |
1474 | 0 | break; |
1475 | 0 | } |
1476 | 0 | MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data"); |
1477 | 0 | break; |
1478 | 0 |
|
1479 | 0 | case GENERATING_BODY: |
1480 | 0 | // if there is session flow control and either the stream window is active and |
1481 | 0 | // exhaused or the session window is exhausted then suspend |
1482 | 0 | if (!AllowFlowControlledWrite()) { |
1483 | 0 | *countRead = 0; |
1484 | 0 | LOG3(("Http2Stream this=%p, id 0x%X request body suspended because " |
1485 | 0 | "remote window is stream=%" PRId64 " session=%" PRId64 ".\n", this, mStreamID, |
1486 | 0 | mServerReceiveWindow, mSession->ServerSessionWindow())); |
1487 | 0 | mBlockedOnRwin = true; |
1488 | 0 | return NS_BASE_STREAM_WOULD_BLOCK; |
1489 | 0 | } |
1490 | 0 | mBlockedOnRwin = false; |
1491 | 0 |
|
1492 | 0 | // The chunk is the smallest of: availableData, configured chunkSize, |
1493 | 0 | // stream window, session window, or 14 bit framing limit. |
1494 | 0 | // Its amazing we send anything at all. |
1495 | 0 | dataLength = std::min(count, mChunkSize); |
1496 | 0 |
|
1497 | 0 | if (dataLength > Http2Session::kMaxFrameData) |
1498 | 0 | dataLength = Http2Session::kMaxFrameData; |
1499 | 0 |
|
1500 | 0 | if (dataLength > mSession->ServerSessionWindow()) |
1501 | 0 | dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow()); |
1502 | 0 |
|
1503 | 0 | if (dataLength > mServerReceiveWindow) |
1504 | 0 | dataLength = static_cast<uint32_t>(mServerReceiveWindow); |
1505 | 0 |
|
1506 | 0 | LOG3(("Http2Stream this=%p id 0x%X send calculation " |
1507 | 0 | "avail=%d chunksize=%d stream window=%" PRId64 " session window=%" PRId64 " " |
1508 | 0 | "max frame=%d USING=%u\n", this, mStreamID, |
1509 | 0 | count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(), |
1510 | 0 | Http2Session::kMaxFrameData, dataLength)); |
1511 | 0 |
|
1512 | 0 | mSession->DecrementServerSessionWindow(dataLength); |
1513 | 0 | mServerReceiveWindow -= dataLength; |
1514 | 0 |
|
1515 | 0 | LOG3(("Http2Stream %p id 0x%x request len remaining %" PRId64 ", " |
1516 | 0 | "count avail %u, chunk used %u", |
1517 | 0 | this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); |
1518 | 0 | if (!dataLength && mRequestBodyLenRemaining) { |
1519 | 0 | return NS_BASE_STREAM_WOULD_BLOCK; |
1520 | 0 | } |
1521 | 0 | if (dataLength > mRequestBodyLenRemaining) { |
1522 | 0 | return NS_ERROR_UNEXPECTED; |
1523 | 0 | } |
1524 | 0 | mRequestBodyLenRemaining -= dataLength; |
1525 | 0 | GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); |
1526 | 0 | ChangeState(SENDING_BODY); |
1527 | 0 | MOZ_FALLTHROUGH; |
1528 | 0 |
|
1529 | 0 | case SENDING_BODY: |
1530 | 0 | MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b"); |
1531 | 0 | rv = TransmitFrame(buf, countRead, false); |
1532 | 0 | MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, |
1533 | 0 | "Transmit Frame should be all or nothing"); |
1534 | 0 |
|
1535 | 0 | LOG3(("TransmitFrame() rv=%" PRIx32 " returning %d data bytes. " |
1536 | 0 | "Header is %d Body is %d.", |
1537 | 0 | static_cast<uint32_t>(rv), *countRead, mTxInlineFrameUsed, mTxStreamFrameSize)); |
1538 | 0 |
|
1539 | 0 | // normalize a partial write with a WOULD_BLOCK into just a partial write |
1540 | 0 | // as some code will take WOULD_BLOCK to mean an error with nothing |
1541 | 0 | // written (e.g. nsHttpTransaction::ReadRequestSegment() |
1542 | 0 | if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) |
1543 | 0 | rv = NS_OK; |
1544 | 0 |
|
1545 | 0 | // If that frame was all sent, look for another one |
1546 | 0 | if (!mTxInlineFrameUsed) |
1547 | 0 | ChangeState(GENERATING_BODY); |
1548 | 0 | break; |
1549 | 0 |
|
1550 | 0 | case SENDING_FIN_STREAM: |
1551 | 0 | MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment"); |
1552 | 0 | break; |
1553 | 0 |
|
1554 | 0 | case UPSTREAM_COMPLETE: |
1555 | 0 | MOZ_ASSERT(mPushSource); |
1556 | 0 | rv = TransmitFrame(nullptr, nullptr, true); |
1557 | 0 | break; |
1558 | 0 |
|
1559 | 0 | default: |
1560 | 0 | MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state"); |
1561 | 0 | break; |
1562 | 0 | } |
1563 | 0 |
|
1564 | 0 | return rv; |
1565 | 0 | } |
1566 | | |
1567 | | //----------------------------------------------------------------------------- |
1568 | | // nsAHttpSegmentWriter |
1569 | | //----------------------------------------------------------------------------- |
1570 | | |
1571 | | nsresult |
1572 | | Http2Stream::OnWriteSegment(char *buf, |
1573 | | uint32_t count, |
1574 | | uint32_t *countWritten) |
1575 | 0 | { |
1576 | 0 | LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n", |
1577 | 0 | this, count, mUpstreamState, mStreamID)); |
1578 | 0 |
|
1579 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
1580 | 0 | MOZ_ASSERT(mSegmentWriter); |
1581 | 0 |
|
1582 | 0 | if (mPushSource) { |
1583 | 0 | nsresult rv; |
1584 | 0 | rv = mPushSource->GetBufferedData(buf, count, countWritten); |
1585 | 0 | if (NS_FAILED(rv)) |
1586 | 0 | return rv; |
1587 | 0 | |
1588 | 0 | mSession->ConnectPushedStream(this); |
1589 | 0 | return NS_OK; |
1590 | 0 | } |
1591 | 0 | |
1592 | 0 | // sometimes we have read data from the network and stored it in a pipe |
1593 | 0 | // so that other streams can proceed when the gecko caller is not processing |
1594 | 0 | // data events fast enough and flow control hasn't caught up yet. This |
1595 | 0 | // gets the stored data out of that pipe |
1596 | 0 | if (!mBypassInputBuffer && mSimpleBuffer.Available()) { |
1597 | 0 | *countWritten = mSimpleBuffer.Read(buf, count); |
1598 | 0 | MOZ_ASSERT(*countWritten); |
1599 | 0 | LOG3(("Http2Stream::OnWriteSegment read from flow control buffer %p %x %d\n", |
1600 | 0 | this, mStreamID, *countWritten)); |
1601 | 0 | return NS_OK; |
1602 | 0 | } |
1603 | 0 |
|
1604 | 0 | // read from the network |
1605 | 0 | return mSegmentWriter->OnWriteSegment(buf, count, countWritten); |
1606 | 0 | } |
1607 | | |
1608 | | /// connect tunnels |
1609 | | |
1610 | | void |
1611 | | Http2Stream::ClearTransactionsBlockedOnTunnel() |
1612 | 0 | { |
1613 | 0 | MOZ_ASSERT(OnSocketThread(), "not on socket thread"); |
1614 | 0 |
|
1615 | 0 | if (!mIsTunnel) { |
1616 | 0 | return; |
1617 | 0 | } |
1618 | 0 | nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo()); |
1619 | 0 | if (NS_FAILED(rv)) { |
1620 | 0 | LOG3(("Http2Stream::ClearTransactionsBlockedOnTunnel %p\n" |
1621 | 0 | " ProcessPendingQ failed: %08x\n", |
1622 | 0 | this, static_cast<uint32_t>(rv))); |
1623 | 0 | } |
1624 | 0 | } |
1625 | | |
1626 | | void |
1627 | | Http2Stream::MapStreamToPlainText() |
1628 | 0 | { |
1629 | 0 | RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction()); |
1630 | 0 | MOZ_ASSERT(qiTrans); |
1631 | 0 | mPlainTextTunnel = true; |
1632 | 0 | qiTrans->ForcePlainText(); |
1633 | 0 | } |
1634 | | |
1635 | | void |
1636 | | Http2Stream::MapStreamToHttpConnection() |
1637 | 0 | { |
1638 | 0 | RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction()); |
1639 | 0 | MOZ_ASSERT(qiTrans); |
1640 | 0 | qiTrans->MapStreamToHttpConnection(mSocketTransport, |
1641 | 0 | mTransaction->ConnectionInfo()); |
1642 | 0 | } |
1643 | | |
1644 | | // ----------------------------------------------------------------------------- |
1645 | | // mirror nsAHttpTransaction |
1646 | | // ----------------------------------------------------------------------------- |
1647 | | |
1648 | | bool |
1649 | | Http2Stream::Do0RTT() |
1650 | 0 | { |
1651 | 0 | MOZ_ASSERT(mTransaction); |
1652 | 0 | mAttempting0RTT = mTransaction->Do0RTT(); |
1653 | 0 | return mAttempting0RTT; |
1654 | 0 | } |
1655 | | |
1656 | | nsresult |
1657 | | Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged) |
1658 | 0 | { |
1659 | 0 | MOZ_ASSERT(mTransaction); |
1660 | 0 | mAttempting0RTT = false; |
1661 | 0 | // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for |
1662 | 0 | // both arguments because as long as the alpn token stayed the same, we can |
1663 | 0 | // just reuse what we have in our buffer to send instead of having to have |
1664 | 0 | // the transaction rewind and read it all over again. We only need to rewind |
1665 | 0 | // the transaction if we're switching to a new protocol, because our buffer |
1666 | 0 | // won't get used in that case. |
1667 | 0 | // .. |
1668 | 0 | // however, we send in the aRestart value to indicate that early data failed |
1669 | 0 | // for devtools purposes |
1670 | 0 | nsresult rv = mTransaction->Finish0RTT(aAlpnChanged, aAlpnChanged); |
1671 | 0 | if (aRestart) { |
1672 | 0 | nsHttpTransaction *trans = mTransaction->QueryHttpTransaction(); |
1673 | 0 | if (trans) { |
1674 | 0 | trans->Refused0RTT(); |
1675 | 0 | } |
1676 | 0 | } |
1677 | 0 | return rv; |
1678 | 0 | } |
1679 | | |
1680 | | nsresult |
1681 | | Http2Stream::GetOriginAttributes(mozilla::OriginAttributes *oa) |
1682 | 0 | { |
1683 | 0 | if (!mSocketTransport) { |
1684 | 0 | return NS_ERROR_UNEXPECTED; |
1685 | 0 | } |
1686 | 0 | |
1687 | 0 | return mSocketTransport->GetOriginAttributes(oa); |
1688 | 0 | } |
1689 | | |
1690 | | } // namespace net |
1691 | | } // namespace mozilla |