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