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