netwerk/protocol/http/Http2Stream.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     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/. */
     7 // HttpLog.h should generally be included first
     8 #include "HttpLog.h"
    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()
    16 #include <algorithm>
    18 #include "Http2Compression.h"
    19 #include "Http2Session.h"
    20 #include "Http2Stream.h"
    21 #include "Http2Push.h"
    23 #include "mozilla/Telemetry.h"
    24 #include "nsAlgorithm.h"
    25 #include "nsHttp.h"
    26 #include "nsHttpHandler.h"
    27 #include "nsHttpRequestHead.h"
    28 #include "nsISocketTransport.h"
    29 #include "prnetdb.h"
    31 #ifdef DEBUG
    32 // defined by the socket transport service while active
    33 extern PRThread *gSocketThread;
    34 #endif
    36 namespace mozilla {
    37 namespace net {
    39 Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
    40                          Http2Session *session,
    41                          int32_t priority)
    42   : mStreamID(0),
    43     mSession(session),
    44     mUpstreamState(GENERATING_HEADERS),
    45     mState(IDLE),
    46     mAllHeadersSent(0),
    47     mAllHeadersReceived(0),
    48     mTransaction(httpTransaction),
    49     mSocketTransport(session->SocketTransport()),
    50     mSegmentReader(nullptr),
    51     mSegmentWriter(nullptr),
    52     mChunkSize(session->SendingChunkSize()),
    53     mRequestBlockedOnRead(0),
    54     mRecvdFin(0),
    55     mRecvdReset(0),
    56     mSentReset(0),
    57     mCountAsActive(0),
    58     mSentFin(0),
    59     mSentWaitingFor(0),
    60     mSetTCPSocketBuffer(0),
    61     mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
    62     mTxInlineFrameUsed(0),
    63     mTxStreamFrameSize(0),
    64     mRequestBodyLenRemaining(0),
    65     mLocalUnacked(0),
    66     mBlockedOnRwin(false),
    67     mTotalSent(0),
    68     mTotalRead(0),
    69     mPushSource(nullptr)
    70 {
    71   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
    73   LOG3(("Http2Stream::Http2Stream %p", this));
    75   mServerReceiveWindow = session->GetServerInitialStreamWindow();
    76   mClientReceiveWindow = session->PushAllowance();
    78   mTxInlineFrame = new uint8_t[mTxInlineFrameSize];
    80   PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority);
    82   // values of priority closer to 0 are higher priority for both
    83   // mPriority and the priority argument. They are relative but not
    84   // proportional.
    85   int32_t httpPriority;
    86   if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
    87     httpPriority = kWorstPriority;
    88   } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
    89     httpPriority = kBestPriority;
    90   } else {
    91     httpPriority = kNormalPriority + priority;
    92   }
    93   MOZ_ASSERT(httpPriority >= 0);
    94   mPriority = static_cast<uint32_t>(httpPriority);
    95 }
    97 Http2Stream::~Http2Stream()
    98 {
    99   mStreamID = Http2Session::kDeadStreamID;
   100 }
   102 // ReadSegments() is used to write data down the socket. Generally, HTTP
   103 // request data is pulled from the approriate transaction and
   104 // converted to HTTP/2 data. Sometimes control data like a window-update is
   105 // generated instead.
   107 nsresult
   108 Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
   109                           uint32_t count,
   110                           uint32_t *countRead)
   111 {
   112   LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x",
   113         this, reader, count, mUpstreamState));
   115   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   117   nsresult rv = NS_ERROR_UNEXPECTED;
   118   mRequestBlockedOnRead = 0;
   120   if (mRecvdFin || mRecvdReset) {
   121     // Don't transmit any request frames if the peer cannot respond
   122     LOG3(("Http2Stream %p ReadSegments request stream aborted due to"
   123           " response side closure\n", this));
   124     return NS_ERROR_ABORT;
   125   }
   127   // avoid runt chunks if possible by anticipating
   128   // full data frames
   129   if (count > (mChunkSize + 8)) {
   130     uint32_t numchunks = count / (mChunkSize + 8);
   131     count = numchunks * (mChunkSize + 8);
   132   }
   134   switch (mUpstreamState) {
   135   case GENERATING_HEADERS:
   136   case GENERATING_BODY:
   137   case SENDING_BODY:
   138     // Call into the HTTP Transaction to generate the HTTP request
   139     // stream. That stream will show up in OnReadSegment().
   140     mSegmentReader = reader;
   141     rv = mTransaction->ReadSegments(this, count, countRead);
   142     mSegmentReader = nullptr;
   144     // Check to see if the transaction's request could be written out now.
   145     // If not, mark the stream for callback when writing can proceed.
   146     if (NS_SUCCEEDED(rv) &&
   147         mUpstreamState == GENERATING_HEADERS &&
   148         !mAllHeadersSent)
   149       mSession->TransactionHasDataToWrite(this);
   151     // mTxinlineFrameUsed represents any queued un-sent frame. It might
   152     // be 0 if there is no such frame, which is not a gurantee that we
   153     // don't have more request body to send - just that any data that was
   154     // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
   155     // a queued, but complete, http/2 frame length.
   157     // Mark that we are blocked on read if the http transaction needs to
   158     // provide more of the request message body and there is nothing queued
   159     // for writing
   160     if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
   161       mRequestBlockedOnRead = 1;
   163     // If the sending flow control window is open (!mBlockedOnRwin) then
   164     // continue sending the request
   165     if (!mBlockedOnRwin &&
   166         !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
   167       LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
   168             "mUpstreamState=%x",this, mStreamID, mUpstreamState));
   169       if (mSentFin) {
   170         ChangeState(UPSTREAM_COMPLETE);
   171       } else {
   172         GenerateDataFrameHeader(0, true);
   173         ChangeState(SENDING_FIN_STREAM);
   174         mSession->TransactionHasDataToWrite(this);
   175         rv = NS_BASE_STREAM_WOULD_BLOCK;
   176       }
   177     }
   178     break;
   180   case SENDING_FIN_STREAM:
   181     // We were trying to send the FIN-STREAM but were blocked from
   182     // sending it out - try again.
   183     if (!mSentFin) {
   184       mSegmentReader = reader;
   185       rv = TransmitFrame(nullptr, nullptr, false);
   186       mSegmentReader = nullptr;
   187       MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
   188                  "Transmit Frame should be all or nothing");
   189       if (NS_SUCCEEDED(rv))
   190         ChangeState(UPSTREAM_COMPLETE);
   191     } else {
   192       rv = NS_OK;
   193       mTxInlineFrameUsed = 0;         // cancel fin data packet
   194       ChangeState(UPSTREAM_COMPLETE);
   195     }
   197     *countRead = 0;
   199     // don't change OK to WOULD BLOCK. we are really done sending if OK
   200     break;
   202   case UPSTREAM_COMPLETE:
   203     *countRead = 0;
   204     rv = NS_OK;
   205     break;
   207   default:
   208     MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
   209     break;
   210   }
   212   return rv;
   213 }
   215 // WriteSegments() is used to read data off the socket. Generally this is
   216 // just a call through to the associate nsHttpTransaciton for this stream
   217 // for the remaining data bytes indicated by the current DATA frame.
   219 nsresult
   220 Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
   221                            uint32_t count,
   222                            uint32_t *countWritten)
   223 {
   224   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   225   MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
   227   LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
   228         this, count, mUpstreamState));
   230   mSegmentWriter = writer;
   231   nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
   232   mSegmentWriter = nullptr;
   234   return rv;
   235 }
   237 void
   238 Http2Stream::CreatePushHashKey(const nsCString &scheme,
   239                                const nsCString &hostHeader,
   240                                uint64_t serial,
   241                                const nsCSubstring &pathInfo,
   242                                nsCString &outOrigin,
   243                                nsCString &outKey)
   244 {
   245   outOrigin = scheme;
   246   outOrigin.Append(NS_LITERAL_CSTRING("://"));
   247   outOrigin.Append(hostHeader);
   249   outKey = outOrigin;
   250   outKey.Append(NS_LITERAL_CSTRING("/[http2."));
   251   outKey.AppendInt(serial);
   252   outKey.Append(NS_LITERAL_CSTRING("]"));
   253   outKey.Append(pathInfo);
   254 }
   256 nsresult
   257 Http2Stream::ParseHttpRequestHeaders(const char *buf,
   258                                      uint32_t avail,
   259                                      uint32_t *countUsed)
   260 {
   261   // Returns NS_OK even if the headers are incomplete
   262   // set mAllHeadersSent flag if they are complete
   264   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   265   MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
   267   LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x",
   268         this, avail, mUpstreamState));
   270   mFlatHttpRequestHeaders.Append(buf, avail);
   272   // We can use the simple double crlf because firefox is the
   273   // only client we are parsing
   274   int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
   276   if (endHeader == kNotFound) {
   277     // We don't have all the headers yet
   278     LOG3(("Http2Stream::ParseHttpRequestHeaders %p "
   279           "Need more header bytes. Len = %d",
   280           this, mFlatHttpRequestHeaders.Length()));
   281     *countUsed = avail;
   282     return NS_OK;
   283   }
   285   // We have recvd all the headers, trim the local
   286   // buffer of the final empty line, and set countUsed to reflect
   287   // the whole header has been consumed.
   288   uint32_t oldLen = mFlatHttpRequestHeaders.Length();
   289   mFlatHttpRequestHeaders.SetLength(endHeader + 2);
   290   *countUsed = avail - (oldLen - endHeader) + 4;
   291   mAllHeadersSent = 1;
   293   nsCString hostHeader;
   294   nsCString hashkey;
   295   mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
   297   CreatePushHashKey(NS_LITERAL_CSTRING("https"),
   298                     hostHeader, mSession->Serial(),
   299                     mTransaction->RequestHead()->RequestURI(),
   300                     mOrigin, hashkey);
   302   // check the push cache for GET
   303   if (mTransaction->RequestHead()->IsGet()) {
   304     // from :scheme, :authority, :path
   305     nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
   306     SpdyPushCache *cache = nullptr;
   307     if (loadGroupCI)
   308       loadGroupCI->GetSpdyPushCache(&cache);
   310     Http2PushedStream *pushedStream = nullptr;
   311     // we remove the pushedstream from the push cache so that
   312     // it will not be used for another GET. This does not destroy the
   313     // stream itself - that is done when the transactionhash is done with it.
   314     if (cache)
   315       pushedStream = cache->RemovePushedStreamHttp2(hashkey);
   317     LOG3(("Pushed Stream Lookup "
   318           "session=%p key=%s loadgroupci=%p cache=%p hit=%p\n",
   319           mSession, hashkey.get(), loadGroupCI, cache, pushedStream));
   321     if (pushedStream) {
   322       LOG3(("Pushed Stream Match located id=0x%X key=%s\n",
   323             pushedStream->StreamID(), hashkey.get()));
   324       pushedStream->SetConsumerStream(this);
   325       mPushSource = pushedStream;
   326       SetSentFin(true);
   327       AdjustPushedPriority();
   329       // This stream has been activated (and thus counts against the concurrency
   330       // limit intentionally), but will not be registered via
   331       // RegisterStreamID (below) because of the push match.
   332       // Release that semaphore count immediately (instead of waiting for
   333       // cleanup stream) so we can initiate more pull streams.
   334       mSession->MaybeDecrementConcurrent(this);
   336       // There is probably pushed data buffered so trigger a read manually
   337       // as we can't rely on future network events to do it
   338       mSession->ConnectPushedStream(this);
   339       return NS_OK;
   340     }
   341   }
   343   // It is now OK to assign a streamID that we are assured will
   344   // be monotonically increasing amongst new streams on this
   345   // session
   346   mStreamID = mSession->RegisterStreamID(this);
   347   MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
   348   LOG3(("Stream ID 0x%X [session=%p] for URI %s\n",
   349         mStreamID, mSession,
   350         nsCString(mTransaction->RequestHead()->RequestURI()).get()));
   352   if (mStreamID >= 0x80000000) {
   353     // streamID must fit in 31 bits. Evading This is theoretically possible
   354     // because stream ID assignment is asynchronous to stream creation
   355     // because of the protocol requirement that the new stream ID
   356     // be monotonically increasing. In reality this is really not possible
   357     // because new streams stop being added to a session with millions of
   358     // IDs still available and no race condition is going to bridge that gap;
   359     // so we can be comfortable on just erroring out for correctness in that
   360     // case.
   361     LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
   362     return NS_ERROR_UNEXPECTED;
   363   }
   365   // Now we need to convert the flat http headers into a set
   366   // of HTTP/2 headers by writing to mTxInlineFrame{sz}
   368   nsCString compressedData;
   369   mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
   370                                             mTransaction->RequestHead()->Method(),
   371                                             mTransaction->RequestHead()->RequestURI(),
   372                                             hostHeader,
   373                                             NS_LITERAL_CSTRING("https"),
   374                                             compressedData);
   376   // Determine whether to put the fin bit on the header frame or whether
   377   // to wait for a data packet to put it on.
   378   uint8_t firstFrameFlags =  Http2Session::kFlag_PRIORITY;
   380   if (mTransaction->RequestHead()->IsGet() ||
   381       mTransaction->RequestHead()->IsConnect() ||
   382       mTransaction->RequestHead()->IsHead()) {
   383     // for GET, CONNECT, and HEAD place the fin bit right on the
   384     // header packet
   386     SetSentFin(true);
   387     firstFrameFlags |= Http2Session::kFlag_END_STREAM;
   388   } else if (mTransaction->RequestHead()->IsPost() ||
   389              mTransaction->RequestHead()->IsPut() ||
   390              mTransaction->RequestHead()->IsOptions()) {
   391     // place fin in a data frame even for 0 length messages for iterop
   392   } else if (!mRequestBodyLenRemaining) {
   393     // for other HTTP extension methods, rely on the content-length
   394     // to determine whether or not to put fin on headers
   395     SetSentFin(true);
   396     firstFrameFlags |= Http2Session::kFlag_END_STREAM;
   397   }
   399   // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the
   400   // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing
   401   // frame for the new headers and for the first one a priority field. There is
   402   // no question this is ugly, but a 16KB HEADERS frame should be a long
   403   // tail event, so this is really just for correctness and a nop in the base case.
   404   //
   406   MOZ_ASSERT(!mTxInlineFrameUsed);
   408   uint32_t dataLength = compressedData.Length();
   409   uint32_t maxFrameData = Http2Session::kMaxFrameData - 4; // 4 byes for priority
   410   uint32_t numFrames = 1;
   412   if (dataLength > maxFrameData) {
   413     numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
   414       Http2Session::kMaxFrameData;
   415     MOZ_ASSERT (numFrames > 1);
   416   }
   418   // note that we could still have 1 frame for 0 bytes of data. that's ok.
   420   uint32_t messageSize = dataLength;
   421   messageSize += 12; // header frame overhead
   422   messageSize += (numFrames - 1) * 8; // continuation frames overhead
   424   Http2Session::EnsureBuffer(mTxInlineFrame,
   425                              dataLength + messageSize,
   426                              mTxInlineFrameUsed,
   427                              mTxInlineFrameSize);
   429   mTxInlineFrameUsed += messageSize;
   430   LOG3(("%p Generating %d bytes of HEADERS for stream 0x%X at priority %u frames %u\n",
   431         this, mTxInlineFrameUsed, mStreamID, mPriority, numFrames));
   433   uint32_t outputOffset = 0;
   434   uint32_t compressedDataOffset = 0;
   435   for (uint32_t idx = 0; idx < numFrames; ++idx) {
   436     uint32_t flags, frameLen;
   437     bool lastFrame = (idx == numFrames - 1);
   439     flags = 0;
   440     frameLen = maxFrameData;
   441     if (!idx) {
   442       flags |= firstFrameFlags;
   443       // Only the first frame needs the 4-byte offset
   444       maxFrameData = Http2Session::kMaxFrameData;
   445     }
   446     if (lastFrame) {
   447       frameLen = dataLength;
   448       flags |= Http2Session::kFlag_END_HEADERS;
   449     }
   450     dataLength -= frameLen;
   452     mSession->CreateFrameHeader(
   453       mTxInlineFrame.get() + outputOffset,
   454       frameLen + (idx ? 0 : 4),
   455       (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS,
   456       flags, mStreamID);
   457     outputOffset += 8;
   459     if (!idx) {
   460       uint32_t priority = PR_htonl(mPriority);
   461       memcpy (mTxInlineFrame.get() + outputOffset, &priority, 4);
   462       outputOffset += 4;
   463     }
   465     memcpy(mTxInlineFrame.get() + outputOffset,
   466            compressedData.BeginReading() + compressedDataOffset, frameLen);
   467     compressedDataOffset += frameLen;
   468     outputOffset += frameLen;
   469   }
   471   Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
   473   // The size of the input headers is approximate
   474   uint32_t ratio =
   475     compressedData.Length() * 100 /
   476     (11 + mTransaction->RequestHead()->RequestURI().Length() +
   477      mFlatHttpRequestHeaders.Length());
   479   const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
   480   int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
   481   while (true) {
   482     int32_t startIndex = crlfIndex + 2;
   484     crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
   485     if (crlfIndex == -1)
   486       break;
   488     int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
   489                                                       crlfIndex - startIndex);
   490     if (colonIndex == -1)
   491       break;
   493     nsDependentCSubstring name = Substring(beginBuffer + startIndex,
   494                                            beginBuffer + colonIndex);
   495     // all header names are lower case in spdy
   496     ToLowerCase(name);
   498     if (name.Equals("content-length")) {
   499       nsCString *val = new nsCString();
   500       int32_t valueIndex = colonIndex + 1;
   501       while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
   502         ++valueIndex;
   504       nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
   505                                           beginBuffer + crlfIndex);
   506       val->Append(v);
   508       int64_t len;
   509       if (nsHttp::ParseInt64(val->get(), nullptr, &len))
   510         mRequestBodyLenRemaining = len;
   511       break;
   512     }
   513   }
   515   mFlatHttpRequestHeaders.Truncate();
   516   Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
   517   return NS_OK;
   518 }
   520 void
   521 Http2Stream::AdjustInitialWindow()
   522 {
   523   // The default initial_window is sized for pushed streams. When we
   524   // generate a client pulled stream we want to disable flow control for
   525   // the stream with a window update. Do the same for pushed streams
   526   // when they connect to a pull.
   528   // >0 even numbered IDs are pushed streams.
   529   // odd numbered IDs are pulled streams.
   530   // 0 is the sink for a pushed stream.
   531   Http2Stream *stream = this;
   532   if (!mStreamID) {
   533     MOZ_ASSERT(mPushSource);
   534     if (!mPushSource)
   535       return;
   536     stream = mPushSource;
   537     MOZ_ASSERT(stream->mStreamID);
   538     MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
   540     // If the pushed stream has recvd a FIN, there is no reason to update
   541     // the window
   542     if (stream->RecvdFin() || stream->RecvdReset())
   543       return;
   544   }
   546   uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
   547   Http2Session::EnsureBuffer(mTxInlineFrame,
   548                              mTxInlineFrameUsed + 12,
   549                              mTxInlineFrameUsed,
   550                              mTxInlineFrameSize);
   551   mTxInlineFrameUsed += 12;
   553   mSession->CreateFrameHeader(packet, 4,
   554                               Http2Session::FRAME_TYPE_WINDOW_UPDATE,
   555                               0, stream->mStreamID);
   557   MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin);
   558   uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow;
   559   mClientReceiveWindow += bump;
   560   bump = PR_htonl(bump);
   561   memcpy(packet + 8, &bump, 4);
   562   LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n",
   563         this, stream->mStreamID));
   564 }
   566 void
   567 Http2Stream::AdjustPushedPriority()
   568 {
   569   // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
   570   // 0 is the sink for a pushed stream.
   572   if (mStreamID || !mPushSource)
   573     return;
   575   MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
   577   // If the pushed stream has recvd a FIN, there is no reason to update
   578   // the window
   579   if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
   580     return;
   582   uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
   583   Http2Session::EnsureBuffer(mTxInlineFrame,
   584                              mTxInlineFrameUsed + 12,
   585                              mTxInlineFrameUsed,
   586                              mTxInlineFrameSize);
   587   mTxInlineFrameUsed += 12;
   589   mSession->CreateFrameHeader(packet, 4,
   590                               Http2Session::FRAME_TYPE_PRIORITY, 0,
   591                               mPushSource->mStreamID);
   593   uint32_t newPriority = PR_htonl(mPriority);
   594   mPushSource->SetPriority(newPriority);
   595   memcpy(packet + 8, &newPriority, 4);
   597   LOG3(("AdjustPushedPriority %p id 0x%X to %X\n", this, mPushSource->mStreamID,
   598         newPriority));
   599 }
   601 void
   602 Http2Stream::UpdateTransportReadEvents(uint32_t count)
   603 {
   604   mTotalRead += count;
   605   mTransaction->OnTransportStatus(mSocketTransport,
   606                                   NS_NET_STATUS_RECEIVING_FROM,
   607                                   mTotalRead);
   608 }
   610 void
   611 Http2Stream::UpdateTransportSendEvents(uint32_t count)
   612 {
   613   mTotalSent += count;
   615   // normally on non-windows platform we use TCP autotuning for
   616   // the socket buffers, and this works well (managing enough
   617   // buffers for BDP while conserving memory) for HTTP even when
   618   // it creates really deep queues. However this 'buffer bloat' is
   619   // a problem for http/2 because it ruins the low latency properties
   620   // necessary for PING and cancel to work meaningfully.
   621   //
   622   // If this stream represents a large upload, disable autotuning for
   623   // the session and cap the send buffers by default at 128KB.
   624   // (10Mbit/sec @ 100ms)
   625   //
   626   uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
   627   if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
   628     mSetTCPSocketBuffer = 1;
   629     mSocketTransport->SetSendBufferSize(bufferSize);
   630   }
   632   if (mUpstreamState != SENDING_FIN_STREAM)
   633     mTransaction->OnTransportStatus(mSocketTransport,
   634                                     NS_NET_STATUS_SENDING_TO,
   635                                     mTotalSent);
   637   if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
   638     mSentWaitingFor = 1;
   639     mTransaction->OnTransportStatus(mSocketTransport,
   640                                     NS_NET_STATUS_WAITING_FOR,
   641                                     0);
   642   }
   643 }
   645 nsresult
   646 Http2Stream::TransmitFrame(const char *buf,
   647                            uint32_t *countUsed,
   648                            bool forceCommitment)
   649 {
   650   // If TransmitFrame returns SUCCESS than all the data is sent (or at least
   651   // buffered at the session level), if it returns WOULD_BLOCK then none of
   652   // the data is sent.
   654   // You can call this function with no data and no out parameter in order to
   655   // flush internal buffers that were previously blocked on writing. You can
   656   // of course feed new data to it as well.
   658   LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d",
   659         this, mTxInlineFrameUsed, mTxStreamFrameSize));
   660   if (countUsed)
   661     *countUsed = 0;
   663   if (!mTxInlineFrameUsed) {
   664     MOZ_ASSERT(!buf);
   665     return NS_OK;
   666   }
   668   MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
   669   MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
   670   MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
   671              "TransmitFrame arguments inconsistent");
   673   uint32_t transmittedCount;
   674   nsresult rv;
   676   // In the (relatively common) event that we have a small amount of data
   677   // split between the inlineframe and the streamframe, then move the stream
   678   // data into the inlineframe via copy in order to coalesce into one write.
   679   // Given the interaction with ssl this is worth the small copy cost.
   680   if (mTxStreamFrameSize && mTxInlineFrameUsed &&
   681       mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
   682       mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
   683     LOG3(("Coalesce Transmit"));
   684     memcpy (mTxInlineFrame + mTxInlineFrameUsed,
   685             buf, mTxStreamFrameSize);
   686     if (countUsed)
   687       *countUsed += mTxStreamFrameSize;
   688     mTxInlineFrameUsed += mTxStreamFrameSize;
   689     mTxStreamFrameSize = 0;
   690   }
   692   rv =
   693     mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
   694                                         forceCommitment);
   696   if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
   697     MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
   698     mSession->TransactionHasDataToWrite(this);
   699   }
   700   if (NS_FAILED(rv))     // this will include WOULD_BLOCK
   701     return rv;
   703   // This function calls mSegmentReader->OnReadSegment to report the actual http/2
   704   // bytes through to the session object and then the HttpConnection which calls
   705   // the socket write function. It will accept all of the inline and stream
   706   // data because of the above 'commitment' even if it has to buffer
   708   rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
   709                               mTxInlineFrameUsed,
   710                               &transmittedCount);
   711   LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
   712         "stream=%p result %x len=%d",
   713         mSession, this, rv, transmittedCount));
   715   MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
   716              "inconsistent inline commitment result");
   718   if (NS_FAILED(rv))
   719     return rv;
   721   MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
   722              "inconsistent inline commitment count");
   724   Http2Session::LogIO(mSession, this, "Writing from Inline Buffer",
   725                        reinterpret_cast<char*>(mTxInlineFrame.get()),
   726                        transmittedCount);
   728   if (mTxStreamFrameSize) {
   729     if (!buf) {
   730       // this cannot happen
   731       MOZ_ASSERT(false, "Stream transmit with null buf argument to "
   732                  "TransmitFrame()");
   733       LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
   734       return NS_ERROR_UNEXPECTED;
   735     }
   737     // If there is already data buffered, just add to that to form
   738     // a single TLS Application Data Record - otherwise skip the memcpy
   739     if (mSession->AmountOfOutputBuffered()) {
   740       rv = mSession->BufferOutput(buf, mTxStreamFrameSize,
   741                                   &transmittedCount);
   742     } else {
   743       rv = mSession->OnReadSegment(buf, mTxStreamFrameSize,
   744                                    &transmittedCount);
   745     }
   747     LOG3(("Http2Stream::TransmitFrame for regular session=%p "
   748           "stream=%p result %x len=%d",
   749           mSession, this, rv, transmittedCount));
   751     MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
   752                "inconsistent stream commitment result");
   754     if (NS_FAILED(rv))
   755       return rv;
   757     MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
   758                "inconsistent stream commitment count");
   760     Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
   761                          buf, transmittedCount);
   763     *countUsed += mTxStreamFrameSize;
   764   }
   766   mSession->FlushOutputQueue();
   768   // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
   769   UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
   771   mTxInlineFrameUsed = 0;
   772   mTxStreamFrameSize = 0;
   774   return NS_OK;
   775 }
   777 void
   778 Http2Stream::ChangeState(enum upstreamStateType newState)
   779 {
   780   LOG3(("Http2Stream::ChangeState() %p from %X to %X",
   781         this, mUpstreamState, newState));
   782   mUpstreamState = newState;
   783 }
   785 void
   786 Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
   787 {
   788   LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d",
   789         this, dataLength, lastFrame));
   791   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   792   MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
   793   MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
   795   uint8_t frameFlags = 0;
   796   if (lastFrame) {
   797     frameFlags |= Http2Session::kFlag_END_STREAM;
   798     if (dataLength)
   799       SetSentFin(true);
   800   }
   802   mSession->CreateFrameHeader(mTxInlineFrame.get(),
   803                               dataLength,
   804                               Http2Session::FRAME_TYPE_DATA,
   805                               frameFlags, mStreamID);
   807   mTxInlineFrameUsed = 8;
   808   mTxStreamFrameSize = dataLength;
   809 }
   811 // ConvertHeaders is used to convert the response headers
   812 // into HTTP/1 format and report some telemetry
   813 nsresult
   814 Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
   815                                     nsACString &aHeadersIn,
   816                                     nsACString &aHeadersOut)
   817 {
   818   aHeadersOut.Truncate();
   819   aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
   821   nsresult rv =
   822     decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
   823                                     aHeadersIn.Length(),
   824                                     aHeadersOut);
   825   if (NS_FAILED(rv)) {
   826     LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this));
   827     return NS_ERROR_ILLEGAL_VALUE;
   828   }
   830   nsAutoCString status;
   831   decompressor->GetStatus(status);
   832   if (status.IsEmpty()) {
   833     LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this));
   834     return NS_ERROR_ILLEGAL_VALUE;
   835   }
   837   if (aHeadersIn.Length() && aHeadersOut.Length()) {
   838     Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
   839     uint32_t ratio =
   840       aHeadersIn.Length() * 100 / aHeadersOut.Length();
   841     Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
   842   }
   844   aHeadersIn.Truncate();
   845   aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n"));
   846   LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
   847   return NS_OK;
   848 }
   850 // ConvertHeaders is used to convert the response headers
   851 // into HTTP/1 format and report some telemetry
   852 nsresult
   853 Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
   854                                 nsACString &aHeadersIn,
   855                                 nsACString &aHeadersOut)
   856 {
   857   aHeadersOut.Truncate();
   858   nsresult rv =
   859     decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
   860                                     aHeadersIn.Length(),
   861                                     aHeadersOut);
   862   if (NS_FAILED(rv)) {
   863     LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this));
   864     return NS_ERROR_ILLEGAL_VALUE;
   865   }
   867   nsCString method;
   868   decompressor->GetHost(mHeaderHost);
   869   decompressor->GetScheme(mHeaderScheme);
   870   decompressor->GetPath(mHeaderPath);
   872   if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) {
   873     LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required "
   874           "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(),
   875           mHeaderPath.get()));
   876     return NS_ERROR_ILLEGAL_VALUE;
   877   }
   879   decompressor->GetMethod(method);
   880   if (!method.Equals(NS_LITERAL_CSTRING("GET"))) {
   881     LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n",
   882           this, method.get()));
   883     return NS_ERROR_NOT_IMPLEMENTED;
   884   }
   886   aHeadersIn.Truncate();
   887   LOG (("decoded push headers are:\n%s", aHeadersOut.BeginReading()));
   888   return NS_OK;
   889 }
   891 void
   892 Http2Stream::Close(nsresult reason)
   893 {
   894   mTransaction->Close(reason);
   895 }
   897 bool
   898 Http2Stream::AllowFlowControlledWrite()
   899 {
   900   return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
   901 }
   903 void
   904 Http2Stream::UpdateServerReceiveWindow(int32_t delta)
   905 {
   906   mServerReceiveWindow += delta;
   908   if (mBlockedOnRwin && AllowFlowControlledWrite()) {
   909     LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
   910           "Open stream window\n", this, mStreamID));
   911     mSession->TransactionHasDataToWrite(this);  }
   912 }
   914 void
   915 Http2Stream::SetPriority(uint32_t newVal)
   916 {
   917   mPriority = std::min(newVal, 0x7fffffffU);
   918 }
   920 void
   921 Http2Stream::SetRecvdFin(bool aStatus)
   922 {
   923   mRecvdFin = aStatus ? 1 : 0;
   924   if (!aStatus)
   925     return;
   927   if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
   928     mState = CLOSED_BY_REMOTE;
   929   } else if (mState == CLOSED_BY_LOCAL) {
   930     mState = CLOSED;
   931   }
   932 }
   934 void
   935 Http2Stream::SetSentFin(bool aStatus)
   936 {
   937   mSentFin = aStatus ? 1 : 0;
   938   if (!aStatus)
   939     return;
   941   if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
   942     mState = CLOSED_BY_LOCAL;
   943   } else if (mState == CLOSED_BY_REMOTE) {
   944     mState = CLOSED;
   945   }
   946 }
   948 void
   949 Http2Stream::SetRecvdReset(bool aStatus)
   950 {
   951   mRecvdReset = aStatus ? 1 : 0;
   952   if (!aStatus)
   953     return;
   954   mState = CLOSED;
   955 }
   957 void
   958 Http2Stream::SetSentReset(bool aStatus)
   959 {
   960   mSentReset = aStatus ? 1 : 0;
   961   if (!aStatus)
   962     return;
   963   mState = CLOSED;
   964 }
   966 //-----------------------------------------------------------------------------
   967 // nsAHttpSegmentReader
   968 //-----------------------------------------------------------------------------
   970 nsresult
   971 Http2Stream::OnReadSegment(const char *buf,
   972                            uint32_t count,
   973                            uint32_t *countRead)
   974 {
   975   LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x",
   976         this, count, mUpstreamState));
   978   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   979   MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
   981   nsresult rv = NS_ERROR_UNEXPECTED;
   982   uint32_t dataLength;
   984   switch (mUpstreamState) {
   985   case GENERATING_HEADERS:
   986     // The buffer is the HTTP request stream, including at least part of the
   987     // HTTP request header. This state's job is to build a HEADERS frame
   988     // from the header information. count is the number of http bytes available
   989     // (which may include more than the header), and in countRead we return
   990     // the number of those bytes that we consume (i.e. the portion that are
   991     // header bytes)
   993     rv = ParseHttpRequestHeaders(buf, count, countRead);
   994     if (NS_FAILED(rv))
   995       return rv;
   996     LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
   997           this, *countRead, count, mAllHeadersSent));
   998     if (mAllHeadersSent) {
   999       SetHTTPState(OPEN);
  1000       AdjustInitialWindow();
  1001       // This version of TransmitFrame cannot block
  1002       rv = TransmitFrame(nullptr, nullptr, true);
  1003       ChangeState(GENERATING_BODY);
  1004       break;
  1006     MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
  1007     break;
  1009   case GENERATING_BODY:
  1010     // if there is session flow control and either the stream window is active and
  1011     // exhaused or the session window is exhausted then suspend
  1012     if (!AllowFlowControlledWrite()) {
  1013       *countRead = 0;
  1014       LOG3(("Http2Stream this=%p, id 0x%X request body suspended because "
  1015             "remote window is stream=%ld session=%ld.\n", this, mStreamID,
  1016             mServerReceiveWindow, mSession->ServerSessionWindow()));
  1017       mBlockedOnRwin = true;
  1018       return NS_BASE_STREAM_WOULD_BLOCK;
  1020     mBlockedOnRwin = false;
  1022     // The chunk is the smallest of: availableData, configured chunkSize,
  1023     // stream window, session window, or 14 bit framing limit.
  1024     // Its amazing we send anything at all.
  1025     dataLength = std::min(count, mChunkSize);
  1027     if (dataLength > Http2Session::kMaxFrameData)
  1028       dataLength = Http2Session::kMaxFrameData;
  1030     if (dataLength > mSession->ServerSessionWindow())
  1031       dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
  1033     if (dataLength > mServerReceiveWindow)
  1034       dataLength = static_cast<uint32_t>(mServerReceiveWindow);
  1036     LOG3(("Http2Stream this=%p id 0x%X send calculation "
  1037           "avail=%d chunksize=%d stream window=%d session window=%d "
  1038           "max frame=%d USING=%d\n", this, mStreamID,
  1039           count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(),
  1040           Http2Session::kMaxFrameData, dataLength));
  1042     mSession->DecrementServerSessionWindow(dataLength);
  1043     mServerReceiveWindow -= dataLength;
  1045     LOG3(("Http2Stream %p id %x request len remaining %d, "
  1046           "count avail %d, chunk used %d",
  1047           this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
  1048     if (dataLength > mRequestBodyLenRemaining)
  1049       return NS_ERROR_UNEXPECTED;
  1050     mRequestBodyLenRemaining -= dataLength;
  1051     GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
  1052     ChangeState(SENDING_BODY);
  1053     // NO BREAK
  1055   case SENDING_BODY:
  1056     MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
  1057     rv = TransmitFrame(buf, countRead, false);
  1058     MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
  1059                "Transmit Frame should be all or nothing");
  1061     LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
  1062           "Header is %d Body is %d.",
  1063           rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
  1065     // normalize a partial write with a WOULD_BLOCK into just a partial write
  1066     // as some code will take WOULD_BLOCK to mean an error with nothing
  1067     // written (e.g. nsHttpTransaction::ReadRequestSegment()
  1068     if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
  1069       rv = NS_OK;
  1071     // If that frame was all sent, look for another one
  1072     if (!mTxInlineFrameUsed)
  1073       ChangeState(GENERATING_BODY);
  1074     break;
  1076   case SENDING_FIN_STREAM:
  1077     MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
  1078     break;
  1080   default:
  1081     MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
  1082     break;
  1085   return rv;
  1088 //-----------------------------------------------------------------------------
  1089 // nsAHttpSegmentWriter
  1090 //-----------------------------------------------------------------------------
  1092 nsresult
  1093 Http2Stream::OnWriteSegment(char *buf,
  1094                             uint32_t count,
  1095                             uint32_t *countWritten)
  1097   LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
  1098         this, count, mUpstreamState, mStreamID));
  1100   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
  1101   MOZ_ASSERT(mSegmentWriter);
  1103   if (!mPushSource)
  1104     return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
  1106   nsresult rv;
  1107   rv = mPushSource->GetBufferedData(buf, count, countWritten);
  1108   if (NS_FAILED(rv))
  1109     return rv;
  1111   mSession->ConnectPushedStream(this);
  1112   return NS_OK;
  1115 } // namespace mozilla::net
  1116 } // namespace mozilla

mercurial