netwerk/protocol/http/Http2Stream.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/protocol/http/Http2Stream.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1117 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set sw=2 ts=8 et tw=80 : */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +// HttpLog.h should generally be included first
    1.11 +#include "HttpLog.h"
    1.12 +
    1.13 +// Log on level :5, instead of default :4.
    1.14 +#undef LOG
    1.15 +#define LOG(args) LOG5(args)
    1.16 +#undef LOG_ENABLED
    1.17 +#define LOG_ENABLED() LOG5_ENABLED()
    1.18 +
    1.19 +#include <algorithm>
    1.20 +
    1.21 +#include "Http2Compression.h"
    1.22 +#include "Http2Session.h"
    1.23 +#include "Http2Stream.h"
    1.24 +#include "Http2Push.h"
    1.25 +
    1.26 +#include "mozilla/Telemetry.h"
    1.27 +#include "nsAlgorithm.h"
    1.28 +#include "nsHttp.h"
    1.29 +#include "nsHttpHandler.h"
    1.30 +#include "nsHttpRequestHead.h"
    1.31 +#include "nsISocketTransport.h"
    1.32 +#include "prnetdb.h"
    1.33 +
    1.34 +#ifdef DEBUG
    1.35 +// defined by the socket transport service while active
    1.36 +extern PRThread *gSocketThread;
    1.37 +#endif
    1.38 +
    1.39 +namespace mozilla {
    1.40 +namespace net {
    1.41 +
    1.42 +Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
    1.43 +                         Http2Session *session,
    1.44 +                         int32_t priority)
    1.45 +  : mStreamID(0),
    1.46 +    mSession(session),
    1.47 +    mUpstreamState(GENERATING_HEADERS),
    1.48 +    mState(IDLE),
    1.49 +    mAllHeadersSent(0),
    1.50 +    mAllHeadersReceived(0),
    1.51 +    mTransaction(httpTransaction),
    1.52 +    mSocketTransport(session->SocketTransport()),
    1.53 +    mSegmentReader(nullptr),
    1.54 +    mSegmentWriter(nullptr),
    1.55 +    mChunkSize(session->SendingChunkSize()),
    1.56 +    mRequestBlockedOnRead(0),
    1.57 +    mRecvdFin(0),
    1.58 +    mRecvdReset(0),
    1.59 +    mSentReset(0),
    1.60 +    mCountAsActive(0),
    1.61 +    mSentFin(0),
    1.62 +    mSentWaitingFor(0),
    1.63 +    mSetTCPSocketBuffer(0),
    1.64 +    mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
    1.65 +    mTxInlineFrameUsed(0),
    1.66 +    mTxStreamFrameSize(0),
    1.67 +    mRequestBodyLenRemaining(0),
    1.68 +    mLocalUnacked(0),
    1.69 +    mBlockedOnRwin(false),
    1.70 +    mTotalSent(0),
    1.71 +    mTotalRead(0),
    1.72 +    mPushSource(nullptr)
    1.73 +{
    1.74 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
    1.75 +
    1.76 +  LOG3(("Http2Stream::Http2Stream %p", this));
    1.77 +
    1.78 +  mServerReceiveWindow = session->GetServerInitialStreamWindow();
    1.79 +  mClientReceiveWindow = session->PushAllowance();
    1.80 +
    1.81 +  mTxInlineFrame = new uint8_t[mTxInlineFrameSize];
    1.82 +
    1.83 +  PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority);
    1.84 +
    1.85 +  // values of priority closer to 0 are higher priority for both
    1.86 +  // mPriority and the priority argument. They are relative but not
    1.87 +  // proportional.
    1.88 +  int32_t httpPriority;
    1.89 +  if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
    1.90 +    httpPriority = kWorstPriority;
    1.91 +  } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
    1.92 +    httpPriority = kBestPriority;
    1.93 +  } else {
    1.94 +    httpPriority = kNormalPriority + priority;
    1.95 +  }
    1.96 +  MOZ_ASSERT(httpPriority >= 0);
    1.97 +  mPriority = static_cast<uint32_t>(httpPriority);
    1.98 +}
    1.99 +
   1.100 +Http2Stream::~Http2Stream()
   1.101 +{
   1.102 +  mStreamID = Http2Session::kDeadStreamID;
   1.103 +}
   1.104 +
   1.105 +// ReadSegments() is used to write data down the socket. Generally, HTTP
   1.106 +// request data is pulled from the approriate transaction and
   1.107 +// converted to HTTP/2 data. Sometimes control data like a window-update is
   1.108 +// generated instead.
   1.109 +
   1.110 +nsresult
   1.111 +Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
   1.112 +                          uint32_t count,
   1.113 +                          uint32_t *countRead)
   1.114 +{
   1.115 +  LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x",
   1.116 +        this, reader, count, mUpstreamState));
   1.117 +
   1.118 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   1.119 +
   1.120 +  nsresult rv = NS_ERROR_UNEXPECTED;
   1.121 +  mRequestBlockedOnRead = 0;
   1.122 +
   1.123 +  if (mRecvdFin || mRecvdReset) {
   1.124 +    // Don't transmit any request frames if the peer cannot respond
   1.125 +    LOG3(("Http2Stream %p ReadSegments request stream aborted due to"
   1.126 +          " response side closure\n", this));
   1.127 +    return NS_ERROR_ABORT;
   1.128 +  }
   1.129 +
   1.130 +  // avoid runt chunks if possible by anticipating
   1.131 +  // full data frames
   1.132 +  if (count > (mChunkSize + 8)) {
   1.133 +    uint32_t numchunks = count / (mChunkSize + 8);
   1.134 +    count = numchunks * (mChunkSize + 8);
   1.135 +  }
   1.136 +
   1.137 +  switch (mUpstreamState) {
   1.138 +  case GENERATING_HEADERS:
   1.139 +  case GENERATING_BODY:
   1.140 +  case SENDING_BODY:
   1.141 +    // Call into the HTTP Transaction to generate the HTTP request
   1.142 +    // stream. That stream will show up in OnReadSegment().
   1.143 +    mSegmentReader = reader;
   1.144 +    rv = mTransaction->ReadSegments(this, count, countRead);
   1.145 +    mSegmentReader = nullptr;
   1.146 +
   1.147 +    // Check to see if the transaction's request could be written out now.
   1.148 +    // If not, mark the stream for callback when writing can proceed.
   1.149 +    if (NS_SUCCEEDED(rv) &&
   1.150 +        mUpstreamState == GENERATING_HEADERS &&
   1.151 +        !mAllHeadersSent)
   1.152 +      mSession->TransactionHasDataToWrite(this);
   1.153 +
   1.154 +    // mTxinlineFrameUsed represents any queued un-sent frame. It might
   1.155 +    // be 0 if there is no such frame, which is not a gurantee that we
   1.156 +    // don't have more request body to send - just that any data that was
   1.157 +    // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
   1.158 +    // a queued, but complete, http/2 frame length.
   1.159 +
   1.160 +    // Mark that we are blocked on read if the http transaction needs to
   1.161 +    // provide more of the request message body and there is nothing queued
   1.162 +    // for writing
   1.163 +    if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
   1.164 +      mRequestBlockedOnRead = 1;
   1.165 +
   1.166 +    // If the sending flow control window is open (!mBlockedOnRwin) then
   1.167 +    // continue sending the request
   1.168 +    if (!mBlockedOnRwin &&
   1.169 +        !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
   1.170 +      LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
   1.171 +            "mUpstreamState=%x",this, mStreamID, mUpstreamState));
   1.172 +      if (mSentFin) {
   1.173 +        ChangeState(UPSTREAM_COMPLETE);
   1.174 +      } else {
   1.175 +        GenerateDataFrameHeader(0, true);
   1.176 +        ChangeState(SENDING_FIN_STREAM);
   1.177 +        mSession->TransactionHasDataToWrite(this);
   1.178 +        rv = NS_BASE_STREAM_WOULD_BLOCK;
   1.179 +      }
   1.180 +    }
   1.181 +    break;
   1.182 +
   1.183 +  case SENDING_FIN_STREAM:
   1.184 +    // We were trying to send the FIN-STREAM but were blocked from
   1.185 +    // sending it out - try again.
   1.186 +    if (!mSentFin) {
   1.187 +      mSegmentReader = reader;
   1.188 +      rv = TransmitFrame(nullptr, nullptr, false);
   1.189 +      mSegmentReader = nullptr;
   1.190 +      MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
   1.191 +                 "Transmit Frame should be all or nothing");
   1.192 +      if (NS_SUCCEEDED(rv))
   1.193 +        ChangeState(UPSTREAM_COMPLETE);
   1.194 +    } else {
   1.195 +      rv = NS_OK;
   1.196 +      mTxInlineFrameUsed = 0;         // cancel fin data packet
   1.197 +      ChangeState(UPSTREAM_COMPLETE);
   1.198 +    }
   1.199 +
   1.200 +    *countRead = 0;
   1.201 +
   1.202 +    // don't change OK to WOULD BLOCK. we are really done sending if OK
   1.203 +    break;
   1.204 +
   1.205 +  case UPSTREAM_COMPLETE:
   1.206 +    *countRead = 0;
   1.207 +    rv = NS_OK;
   1.208 +    break;
   1.209 +
   1.210 +  default:
   1.211 +    MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
   1.212 +    break;
   1.213 +  }
   1.214 +
   1.215 +  return rv;
   1.216 +}
   1.217 +
   1.218 +// WriteSegments() is used to read data off the socket. Generally this is
   1.219 +// just a call through to the associate nsHttpTransaciton for this stream
   1.220 +// for the remaining data bytes indicated by the current DATA frame.
   1.221 +
   1.222 +nsresult
   1.223 +Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
   1.224 +                           uint32_t count,
   1.225 +                           uint32_t *countWritten)
   1.226 +{
   1.227 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   1.228 +  MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
   1.229 +
   1.230 +  LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
   1.231 +        this, count, mUpstreamState));
   1.232 +
   1.233 +  mSegmentWriter = writer;
   1.234 +  nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
   1.235 +  mSegmentWriter = nullptr;
   1.236 +
   1.237 +  return rv;
   1.238 +}
   1.239 +
   1.240 +void
   1.241 +Http2Stream::CreatePushHashKey(const nsCString &scheme,
   1.242 +                               const nsCString &hostHeader,
   1.243 +                               uint64_t serial,
   1.244 +                               const nsCSubstring &pathInfo,
   1.245 +                               nsCString &outOrigin,
   1.246 +                               nsCString &outKey)
   1.247 +{
   1.248 +  outOrigin = scheme;
   1.249 +  outOrigin.Append(NS_LITERAL_CSTRING("://"));
   1.250 +  outOrigin.Append(hostHeader);
   1.251 +
   1.252 +  outKey = outOrigin;
   1.253 +  outKey.Append(NS_LITERAL_CSTRING("/[http2."));
   1.254 +  outKey.AppendInt(serial);
   1.255 +  outKey.Append(NS_LITERAL_CSTRING("]"));
   1.256 +  outKey.Append(pathInfo);
   1.257 +}
   1.258 +
   1.259 +nsresult
   1.260 +Http2Stream::ParseHttpRequestHeaders(const char *buf,
   1.261 +                                     uint32_t avail,
   1.262 +                                     uint32_t *countUsed)
   1.263 +{
   1.264 +  // Returns NS_OK even if the headers are incomplete
   1.265 +  // set mAllHeadersSent flag if they are complete
   1.266 +
   1.267 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   1.268 +  MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
   1.269 +
   1.270 +  LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x",
   1.271 +        this, avail, mUpstreamState));
   1.272 +
   1.273 +  mFlatHttpRequestHeaders.Append(buf, avail);
   1.274 +
   1.275 +  // We can use the simple double crlf because firefox is the
   1.276 +  // only client we are parsing
   1.277 +  int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
   1.278 +
   1.279 +  if (endHeader == kNotFound) {
   1.280 +    // We don't have all the headers yet
   1.281 +    LOG3(("Http2Stream::ParseHttpRequestHeaders %p "
   1.282 +          "Need more header bytes. Len = %d",
   1.283 +          this, mFlatHttpRequestHeaders.Length()));
   1.284 +    *countUsed = avail;
   1.285 +    return NS_OK;
   1.286 +  }
   1.287 +
   1.288 +  // We have recvd all the headers, trim the local
   1.289 +  // buffer of the final empty line, and set countUsed to reflect
   1.290 +  // the whole header has been consumed.
   1.291 +  uint32_t oldLen = mFlatHttpRequestHeaders.Length();
   1.292 +  mFlatHttpRequestHeaders.SetLength(endHeader + 2);
   1.293 +  *countUsed = avail - (oldLen - endHeader) + 4;
   1.294 +  mAllHeadersSent = 1;
   1.295 +
   1.296 +  nsCString hostHeader;
   1.297 +  nsCString hashkey;
   1.298 +  mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
   1.299 +
   1.300 +  CreatePushHashKey(NS_LITERAL_CSTRING("https"),
   1.301 +                    hostHeader, mSession->Serial(),
   1.302 +                    mTransaction->RequestHead()->RequestURI(),
   1.303 +                    mOrigin, hashkey);
   1.304 +
   1.305 +  // check the push cache for GET
   1.306 +  if (mTransaction->RequestHead()->IsGet()) {
   1.307 +    // from :scheme, :authority, :path
   1.308 +    nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
   1.309 +    SpdyPushCache *cache = nullptr;
   1.310 +    if (loadGroupCI)
   1.311 +      loadGroupCI->GetSpdyPushCache(&cache);
   1.312 +
   1.313 +    Http2PushedStream *pushedStream = nullptr;
   1.314 +    // we remove the pushedstream from the push cache so that
   1.315 +    // it will not be used for another GET. This does not destroy the
   1.316 +    // stream itself - that is done when the transactionhash is done with it.
   1.317 +    if (cache)
   1.318 +      pushedStream = cache->RemovePushedStreamHttp2(hashkey);
   1.319 +
   1.320 +    LOG3(("Pushed Stream Lookup "
   1.321 +          "session=%p key=%s loadgroupci=%p cache=%p hit=%p\n",
   1.322 +          mSession, hashkey.get(), loadGroupCI, cache, pushedStream));
   1.323 +
   1.324 +    if (pushedStream) {
   1.325 +      LOG3(("Pushed Stream Match located id=0x%X key=%s\n",
   1.326 +            pushedStream->StreamID(), hashkey.get()));
   1.327 +      pushedStream->SetConsumerStream(this);
   1.328 +      mPushSource = pushedStream;
   1.329 +      SetSentFin(true);
   1.330 +      AdjustPushedPriority();
   1.331 +
   1.332 +      // This stream has been activated (and thus counts against the concurrency
   1.333 +      // limit intentionally), but will not be registered via
   1.334 +      // RegisterStreamID (below) because of the push match.
   1.335 +      // Release that semaphore count immediately (instead of waiting for
   1.336 +      // cleanup stream) so we can initiate more pull streams.
   1.337 +      mSession->MaybeDecrementConcurrent(this);
   1.338 +
   1.339 +      // There is probably pushed data buffered so trigger a read manually
   1.340 +      // as we can't rely on future network events to do it
   1.341 +      mSession->ConnectPushedStream(this);
   1.342 +      return NS_OK;
   1.343 +    }
   1.344 +  }
   1.345 +
   1.346 +  // It is now OK to assign a streamID that we are assured will
   1.347 +  // be monotonically increasing amongst new streams on this
   1.348 +  // session
   1.349 +  mStreamID = mSession->RegisterStreamID(this);
   1.350 +  MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
   1.351 +  LOG3(("Stream ID 0x%X [session=%p] for URI %s\n",
   1.352 +        mStreamID, mSession,
   1.353 +        nsCString(mTransaction->RequestHead()->RequestURI()).get()));
   1.354 +
   1.355 +  if (mStreamID >= 0x80000000) {
   1.356 +    // streamID must fit in 31 bits. Evading This is theoretically possible
   1.357 +    // because stream ID assignment is asynchronous to stream creation
   1.358 +    // because of the protocol requirement that the new stream ID
   1.359 +    // be monotonically increasing. In reality this is really not possible
   1.360 +    // because new streams stop being added to a session with millions of
   1.361 +    // IDs still available and no race condition is going to bridge that gap;
   1.362 +    // so we can be comfortable on just erroring out for correctness in that
   1.363 +    // case.
   1.364 +    LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
   1.365 +    return NS_ERROR_UNEXPECTED;
   1.366 +  }
   1.367 +
   1.368 +  // Now we need to convert the flat http headers into a set
   1.369 +  // of HTTP/2 headers by writing to mTxInlineFrame{sz}
   1.370 +
   1.371 +  nsCString compressedData;
   1.372 +  mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
   1.373 +                                            mTransaction->RequestHead()->Method(),
   1.374 +                                            mTransaction->RequestHead()->RequestURI(),
   1.375 +                                            hostHeader,
   1.376 +                                            NS_LITERAL_CSTRING("https"),
   1.377 +                                            compressedData);
   1.378 +
   1.379 +  // Determine whether to put the fin bit on the header frame or whether
   1.380 +  // to wait for a data packet to put it on.
   1.381 +  uint8_t firstFrameFlags =  Http2Session::kFlag_PRIORITY;
   1.382 +
   1.383 +  if (mTransaction->RequestHead()->IsGet() ||
   1.384 +      mTransaction->RequestHead()->IsConnect() ||
   1.385 +      mTransaction->RequestHead()->IsHead()) {
   1.386 +    // for GET, CONNECT, and HEAD place the fin bit right on the
   1.387 +    // header packet
   1.388 +
   1.389 +    SetSentFin(true);
   1.390 +    firstFrameFlags |= Http2Session::kFlag_END_STREAM;
   1.391 +  } else if (mTransaction->RequestHead()->IsPost() ||
   1.392 +             mTransaction->RequestHead()->IsPut() ||
   1.393 +             mTransaction->RequestHead()->IsOptions()) {
   1.394 +    // place fin in a data frame even for 0 length messages for iterop
   1.395 +  } else if (!mRequestBodyLenRemaining) {
   1.396 +    // for other HTTP extension methods, rely on the content-length
   1.397 +    // to determine whether or not to put fin on headers
   1.398 +    SetSentFin(true);
   1.399 +    firstFrameFlags |= Http2Session::kFlag_END_STREAM;
   1.400 +  }
   1.401 +
   1.402 +  // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the
   1.403 +  // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing
   1.404 +  // frame for the new headers and for the first one a priority field. There is
   1.405 +  // no question this is ugly, but a 16KB HEADERS frame should be a long
   1.406 +  // tail event, so this is really just for correctness and a nop in the base case.
   1.407 +  //
   1.408 +
   1.409 +  MOZ_ASSERT(!mTxInlineFrameUsed);
   1.410 +
   1.411 +  uint32_t dataLength = compressedData.Length();
   1.412 +  uint32_t maxFrameData = Http2Session::kMaxFrameData - 4; // 4 byes for priority
   1.413 +  uint32_t numFrames = 1;
   1.414 +
   1.415 +  if (dataLength > maxFrameData) {
   1.416 +    numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
   1.417 +      Http2Session::kMaxFrameData;
   1.418 +    MOZ_ASSERT (numFrames > 1);
   1.419 +  }
   1.420 +
   1.421 +  // note that we could still have 1 frame for 0 bytes of data. that's ok.
   1.422 +
   1.423 +  uint32_t messageSize = dataLength;
   1.424 +  messageSize += 12; // header frame overhead
   1.425 +  messageSize += (numFrames - 1) * 8; // continuation frames overhead
   1.426 +
   1.427 +  Http2Session::EnsureBuffer(mTxInlineFrame,
   1.428 +                             dataLength + messageSize,
   1.429 +                             mTxInlineFrameUsed,
   1.430 +                             mTxInlineFrameSize);
   1.431 +
   1.432 +  mTxInlineFrameUsed += messageSize;
   1.433 +  LOG3(("%p Generating %d bytes of HEADERS for stream 0x%X at priority %u frames %u\n",
   1.434 +        this, mTxInlineFrameUsed, mStreamID, mPriority, numFrames));
   1.435 +
   1.436 +  uint32_t outputOffset = 0;
   1.437 +  uint32_t compressedDataOffset = 0;
   1.438 +  for (uint32_t idx = 0; idx < numFrames; ++idx) {
   1.439 +    uint32_t flags, frameLen;
   1.440 +    bool lastFrame = (idx == numFrames - 1);
   1.441 +
   1.442 +    flags = 0;
   1.443 +    frameLen = maxFrameData;
   1.444 +    if (!idx) {
   1.445 +      flags |= firstFrameFlags;
   1.446 +      // Only the first frame needs the 4-byte offset
   1.447 +      maxFrameData = Http2Session::kMaxFrameData;
   1.448 +    }
   1.449 +    if (lastFrame) {
   1.450 +      frameLen = dataLength;
   1.451 +      flags |= Http2Session::kFlag_END_HEADERS;
   1.452 +    }
   1.453 +    dataLength -= frameLen;
   1.454 +
   1.455 +    mSession->CreateFrameHeader(
   1.456 +      mTxInlineFrame.get() + outputOffset,
   1.457 +      frameLen + (idx ? 0 : 4),
   1.458 +      (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS,
   1.459 +      flags, mStreamID);
   1.460 +    outputOffset += 8;
   1.461 +
   1.462 +    if (!idx) {
   1.463 +      uint32_t priority = PR_htonl(mPriority);
   1.464 +      memcpy (mTxInlineFrame.get() + outputOffset, &priority, 4);
   1.465 +      outputOffset += 4;
   1.466 +    }
   1.467 +
   1.468 +    memcpy(mTxInlineFrame.get() + outputOffset,
   1.469 +           compressedData.BeginReading() + compressedDataOffset, frameLen);
   1.470 +    compressedDataOffset += frameLen;
   1.471 +    outputOffset += frameLen;
   1.472 +  }
   1.473 +
   1.474 +  Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
   1.475 +
   1.476 +  // The size of the input headers is approximate
   1.477 +  uint32_t ratio =
   1.478 +    compressedData.Length() * 100 /
   1.479 +    (11 + mTransaction->RequestHead()->RequestURI().Length() +
   1.480 +     mFlatHttpRequestHeaders.Length());
   1.481 +
   1.482 +  const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
   1.483 +  int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
   1.484 +  while (true) {
   1.485 +    int32_t startIndex = crlfIndex + 2;
   1.486 +
   1.487 +    crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
   1.488 +    if (crlfIndex == -1)
   1.489 +      break;
   1.490 +
   1.491 +    int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
   1.492 +                                                      crlfIndex - startIndex);
   1.493 +    if (colonIndex == -1)
   1.494 +      break;
   1.495 +
   1.496 +    nsDependentCSubstring name = Substring(beginBuffer + startIndex,
   1.497 +                                           beginBuffer + colonIndex);
   1.498 +    // all header names are lower case in spdy
   1.499 +    ToLowerCase(name);
   1.500 +
   1.501 +    if (name.Equals("content-length")) {
   1.502 +      nsCString *val = new nsCString();
   1.503 +      int32_t valueIndex = colonIndex + 1;
   1.504 +      while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
   1.505 +        ++valueIndex;
   1.506 +
   1.507 +      nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
   1.508 +                                          beginBuffer + crlfIndex);
   1.509 +      val->Append(v);
   1.510 +
   1.511 +      int64_t len;
   1.512 +      if (nsHttp::ParseInt64(val->get(), nullptr, &len))
   1.513 +        mRequestBodyLenRemaining = len;
   1.514 +      break;
   1.515 +    }
   1.516 +  }
   1.517 +
   1.518 +  mFlatHttpRequestHeaders.Truncate();
   1.519 +  Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
   1.520 +  return NS_OK;
   1.521 +}
   1.522 +
   1.523 +void
   1.524 +Http2Stream::AdjustInitialWindow()
   1.525 +{
   1.526 +  // The default initial_window is sized for pushed streams. When we
   1.527 +  // generate a client pulled stream we want to disable flow control for
   1.528 +  // the stream with a window update. Do the same for pushed streams
   1.529 +  // when they connect to a pull.
   1.530 +
   1.531 +  // >0 even numbered IDs are pushed streams.
   1.532 +  // odd numbered IDs are pulled streams.
   1.533 +  // 0 is the sink for a pushed stream.
   1.534 +  Http2Stream *stream = this;
   1.535 +  if (!mStreamID) {
   1.536 +    MOZ_ASSERT(mPushSource);
   1.537 +    if (!mPushSource)
   1.538 +      return;
   1.539 +    stream = mPushSource;
   1.540 +    MOZ_ASSERT(stream->mStreamID);
   1.541 +    MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
   1.542 +
   1.543 +    // If the pushed stream has recvd a FIN, there is no reason to update
   1.544 +    // the window
   1.545 +    if (stream->RecvdFin() || stream->RecvdReset())
   1.546 +      return;
   1.547 +  }
   1.548 +
   1.549 +  uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
   1.550 +  Http2Session::EnsureBuffer(mTxInlineFrame,
   1.551 +                             mTxInlineFrameUsed + 12,
   1.552 +                             mTxInlineFrameUsed,
   1.553 +                             mTxInlineFrameSize);
   1.554 +  mTxInlineFrameUsed += 12;
   1.555 +
   1.556 +  mSession->CreateFrameHeader(packet, 4,
   1.557 +                              Http2Session::FRAME_TYPE_WINDOW_UPDATE,
   1.558 +                              0, stream->mStreamID);
   1.559 +
   1.560 +  MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin);
   1.561 +  uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow;
   1.562 +  mClientReceiveWindow += bump;
   1.563 +  bump = PR_htonl(bump);
   1.564 +  memcpy(packet + 8, &bump, 4);
   1.565 +  LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n",
   1.566 +        this, stream->mStreamID));
   1.567 +}
   1.568 +
   1.569 +void
   1.570 +Http2Stream::AdjustPushedPriority()
   1.571 +{
   1.572 +  // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
   1.573 +  // 0 is the sink for a pushed stream.
   1.574 +
   1.575 +  if (mStreamID || !mPushSource)
   1.576 +    return;
   1.577 +
   1.578 +  MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
   1.579 +
   1.580 +  // If the pushed stream has recvd a FIN, there is no reason to update
   1.581 +  // the window
   1.582 +  if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
   1.583 +    return;
   1.584 +
   1.585 +  uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
   1.586 +  Http2Session::EnsureBuffer(mTxInlineFrame,
   1.587 +                             mTxInlineFrameUsed + 12,
   1.588 +                             mTxInlineFrameUsed,
   1.589 +                             mTxInlineFrameSize);
   1.590 +  mTxInlineFrameUsed += 12;
   1.591 +
   1.592 +  mSession->CreateFrameHeader(packet, 4,
   1.593 +                              Http2Session::FRAME_TYPE_PRIORITY, 0,
   1.594 +                              mPushSource->mStreamID);
   1.595 +
   1.596 +  uint32_t newPriority = PR_htonl(mPriority);
   1.597 +  mPushSource->SetPriority(newPriority);
   1.598 +  memcpy(packet + 8, &newPriority, 4);
   1.599 +
   1.600 +  LOG3(("AdjustPushedPriority %p id 0x%X to %X\n", this, mPushSource->mStreamID,
   1.601 +        newPriority));
   1.602 +}
   1.603 +
   1.604 +void
   1.605 +Http2Stream::UpdateTransportReadEvents(uint32_t count)
   1.606 +{
   1.607 +  mTotalRead += count;
   1.608 +  mTransaction->OnTransportStatus(mSocketTransport,
   1.609 +                                  NS_NET_STATUS_RECEIVING_FROM,
   1.610 +                                  mTotalRead);
   1.611 +}
   1.612 +
   1.613 +void
   1.614 +Http2Stream::UpdateTransportSendEvents(uint32_t count)
   1.615 +{
   1.616 +  mTotalSent += count;
   1.617 +
   1.618 +  // normally on non-windows platform we use TCP autotuning for
   1.619 +  // the socket buffers, and this works well (managing enough
   1.620 +  // buffers for BDP while conserving memory) for HTTP even when
   1.621 +  // it creates really deep queues. However this 'buffer bloat' is
   1.622 +  // a problem for http/2 because it ruins the low latency properties
   1.623 +  // necessary for PING and cancel to work meaningfully.
   1.624 +  //
   1.625 +  // If this stream represents a large upload, disable autotuning for
   1.626 +  // the session and cap the send buffers by default at 128KB.
   1.627 +  // (10Mbit/sec @ 100ms)
   1.628 +  //
   1.629 +  uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
   1.630 +  if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
   1.631 +    mSetTCPSocketBuffer = 1;
   1.632 +    mSocketTransport->SetSendBufferSize(bufferSize);
   1.633 +  }
   1.634 +
   1.635 +  if (mUpstreamState != SENDING_FIN_STREAM)
   1.636 +    mTransaction->OnTransportStatus(mSocketTransport,
   1.637 +                                    NS_NET_STATUS_SENDING_TO,
   1.638 +                                    mTotalSent);
   1.639 +
   1.640 +  if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
   1.641 +    mSentWaitingFor = 1;
   1.642 +    mTransaction->OnTransportStatus(mSocketTransport,
   1.643 +                                    NS_NET_STATUS_WAITING_FOR,
   1.644 +                                    0);
   1.645 +  }
   1.646 +}
   1.647 +
   1.648 +nsresult
   1.649 +Http2Stream::TransmitFrame(const char *buf,
   1.650 +                           uint32_t *countUsed,
   1.651 +                           bool forceCommitment)
   1.652 +{
   1.653 +  // If TransmitFrame returns SUCCESS than all the data is sent (or at least
   1.654 +  // buffered at the session level), if it returns WOULD_BLOCK then none of
   1.655 +  // the data is sent.
   1.656 +
   1.657 +  // You can call this function with no data and no out parameter in order to
   1.658 +  // flush internal buffers that were previously blocked on writing. You can
   1.659 +  // of course feed new data to it as well.
   1.660 +
   1.661 +  LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d",
   1.662 +        this, mTxInlineFrameUsed, mTxStreamFrameSize));
   1.663 +  if (countUsed)
   1.664 +    *countUsed = 0;
   1.665 +
   1.666 +  if (!mTxInlineFrameUsed) {
   1.667 +    MOZ_ASSERT(!buf);
   1.668 +    return NS_OK;
   1.669 +  }
   1.670 +
   1.671 +  MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
   1.672 +  MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
   1.673 +  MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
   1.674 +             "TransmitFrame arguments inconsistent");
   1.675 +
   1.676 +  uint32_t transmittedCount;
   1.677 +  nsresult rv;
   1.678 +
   1.679 +  // In the (relatively common) event that we have a small amount of data
   1.680 +  // split between the inlineframe and the streamframe, then move the stream
   1.681 +  // data into the inlineframe via copy in order to coalesce into one write.
   1.682 +  // Given the interaction with ssl this is worth the small copy cost.
   1.683 +  if (mTxStreamFrameSize && mTxInlineFrameUsed &&
   1.684 +      mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
   1.685 +      mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
   1.686 +    LOG3(("Coalesce Transmit"));
   1.687 +    memcpy (mTxInlineFrame + mTxInlineFrameUsed,
   1.688 +            buf, mTxStreamFrameSize);
   1.689 +    if (countUsed)
   1.690 +      *countUsed += mTxStreamFrameSize;
   1.691 +    mTxInlineFrameUsed += mTxStreamFrameSize;
   1.692 +    mTxStreamFrameSize = 0;
   1.693 +  }
   1.694 +
   1.695 +  rv =
   1.696 +    mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
   1.697 +                                        forceCommitment);
   1.698 +
   1.699 +  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
   1.700 +    MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
   1.701 +    mSession->TransactionHasDataToWrite(this);
   1.702 +  }
   1.703 +  if (NS_FAILED(rv))     // this will include WOULD_BLOCK
   1.704 +    return rv;
   1.705 +
   1.706 +  // This function calls mSegmentReader->OnReadSegment to report the actual http/2
   1.707 +  // bytes through to the session object and then the HttpConnection which calls
   1.708 +  // the socket write function. It will accept all of the inline and stream
   1.709 +  // data because of the above 'commitment' even if it has to buffer
   1.710 +
   1.711 +  rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
   1.712 +                              mTxInlineFrameUsed,
   1.713 +                              &transmittedCount);
   1.714 +  LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
   1.715 +        "stream=%p result %x len=%d",
   1.716 +        mSession, this, rv, transmittedCount));
   1.717 +
   1.718 +  MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
   1.719 +             "inconsistent inline commitment result");
   1.720 +
   1.721 +  if (NS_FAILED(rv))
   1.722 +    return rv;
   1.723 +
   1.724 +  MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
   1.725 +             "inconsistent inline commitment count");
   1.726 +
   1.727 +  Http2Session::LogIO(mSession, this, "Writing from Inline Buffer",
   1.728 +                       reinterpret_cast<char*>(mTxInlineFrame.get()),
   1.729 +                       transmittedCount);
   1.730 +
   1.731 +  if (mTxStreamFrameSize) {
   1.732 +    if (!buf) {
   1.733 +      // this cannot happen
   1.734 +      MOZ_ASSERT(false, "Stream transmit with null buf argument to "
   1.735 +                 "TransmitFrame()");
   1.736 +      LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
   1.737 +      return NS_ERROR_UNEXPECTED;
   1.738 +    }
   1.739 +
   1.740 +    // If there is already data buffered, just add to that to form
   1.741 +    // a single TLS Application Data Record - otherwise skip the memcpy
   1.742 +    if (mSession->AmountOfOutputBuffered()) {
   1.743 +      rv = mSession->BufferOutput(buf, mTxStreamFrameSize,
   1.744 +                                  &transmittedCount);
   1.745 +    } else {
   1.746 +      rv = mSession->OnReadSegment(buf, mTxStreamFrameSize,
   1.747 +                                   &transmittedCount);
   1.748 +    }
   1.749 +
   1.750 +    LOG3(("Http2Stream::TransmitFrame for regular session=%p "
   1.751 +          "stream=%p result %x len=%d",
   1.752 +          mSession, this, rv, transmittedCount));
   1.753 +
   1.754 +    MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
   1.755 +               "inconsistent stream commitment result");
   1.756 +
   1.757 +    if (NS_FAILED(rv))
   1.758 +      return rv;
   1.759 +
   1.760 +    MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
   1.761 +               "inconsistent stream commitment count");
   1.762 +
   1.763 +    Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
   1.764 +                         buf, transmittedCount);
   1.765 +
   1.766 +    *countUsed += mTxStreamFrameSize;
   1.767 +  }
   1.768 +
   1.769 +  mSession->FlushOutputQueue();
   1.770 +
   1.771 +  // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
   1.772 +  UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
   1.773 +
   1.774 +  mTxInlineFrameUsed = 0;
   1.775 +  mTxStreamFrameSize = 0;
   1.776 +
   1.777 +  return NS_OK;
   1.778 +}
   1.779 +
   1.780 +void
   1.781 +Http2Stream::ChangeState(enum upstreamStateType newState)
   1.782 +{
   1.783 +  LOG3(("Http2Stream::ChangeState() %p from %X to %X",
   1.784 +        this, mUpstreamState, newState));
   1.785 +  mUpstreamState = newState;
   1.786 +}
   1.787 +
   1.788 +void
   1.789 +Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
   1.790 +{
   1.791 +  LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d",
   1.792 +        this, dataLength, lastFrame));
   1.793 +
   1.794 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   1.795 +  MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
   1.796 +  MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
   1.797 +
   1.798 +  uint8_t frameFlags = 0;
   1.799 +  if (lastFrame) {
   1.800 +    frameFlags |= Http2Session::kFlag_END_STREAM;
   1.801 +    if (dataLength)
   1.802 +      SetSentFin(true);
   1.803 +  }
   1.804 +
   1.805 +  mSession->CreateFrameHeader(mTxInlineFrame.get(),
   1.806 +                              dataLength,
   1.807 +                              Http2Session::FRAME_TYPE_DATA,
   1.808 +                              frameFlags, mStreamID);
   1.809 +
   1.810 +  mTxInlineFrameUsed = 8;
   1.811 +  mTxStreamFrameSize = dataLength;
   1.812 +}
   1.813 +
   1.814 +// ConvertHeaders is used to convert the response headers
   1.815 +// into HTTP/1 format and report some telemetry
   1.816 +nsresult
   1.817 +Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
   1.818 +                                    nsACString &aHeadersIn,
   1.819 +                                    nsACString &aHeadersOut)
   1.820 +{
   1.821 +  aHeadersOut.Truncate();
   1.822 +  aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
   1.823 +
   1.824 +  nsresult rv =
   1.825 +    decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
   1.826 +                                    aHeadersIn.Length(),
   1.827 +                                    aHeadersOut);
   1.828 +  if (NS_FAILED(rv)) {
   1.829 +    LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this));
   1.830 +    return NS_ERROR_ILLEGAL_VALUE;
   1.831 +  }
   1.832 +
   1.833 +  nsAutoCString status;
   1.834 +  decompressor->GetStatus(status);
   1.835 +  if (status.IsEmpty()) {
   1.836 +    LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this));
   1.837 +    return NS_ERROR_ILLEGAL_VALUE;
   1.838 +  }
   1.839 +
   1.840 +  if (aHeadersIn.Length() && aHeadersOut.Length()) {
   1.841 +    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
   1.842 +    uint32_t ratio =
   1.843 +      aHeadersIn.Length() * 100 / aHeadersOut.Length();
   1.844 +    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
   1.845 +  }
   1.846 +
   1.847 +  aHeadersIn.Truncate();
   1.848 +  aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n"));
   1.849 +  LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
   1.850 +  return NS_OK;
   1.851 +}
   1.852 +
   1.853 +// ConvertHeaders is used to convert the response headers
   1.854 +// into HTTP/1 format and report some telemetry
   1.855 +nsresult
   1.856 +Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
   1.857 +                                nsACString &aHeadersIn,
   1.858 +                                nsACString &aHeadersOut)
   1.859 +{
   1.860 +  aHeadersOut.Truncate();
   1.861 +  nsresult rv =
   1.862 +    decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
   1.863 +                                    aHeadersIn.Length(),
   1.864 +                                    aHeadersOut);
   1.865 +  if (NS_FAILED(rv)) {
   1.866 +    LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this));
   1.867 +    return NS_ERROR_ILLEGAL_VALUE;
   1.868 +  }
   1.869 +
   1.870 +  nsCString method;
   1.871 +  decompressor->GetHost(mHeaderHost);
   1.872 +  decompressor->GetScheme(mHeaderScheme);
   1.873 +  decompressor->GetPath(mHeaderPath);
   1.874 +
   1.875 +  if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) {
   1.876 +    LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required "
   1.877 +          "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(),
   1.878 +          mHeaderPath.get()));
   1.879 +    return NS_ERROR_ILLEGAL_VALUE;
   1.880 +  }
   1.881 +
   1.882 +  decompressor->GetMethod(method);
   1.883 +  if (!method.Equals(NS_LITERAL_CSTRING("GET"))) {
   1.884 +    LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n",
   1.885 +          this, method.get()));
   1.886 +    return NS_ERROR_NOT_IMPLEMENTED;
   1.887 +  }
   1.888 +
   1.889 +  aHeadersIn.Truncate();
   1.890 +  LOG (("decoded push headers are:\n%s", aHeadersOut.BeginReading()));
   1.891 +  return NS_OK;
   1.892 +}
   1.893 +
   1.894 +void
   1.895 +Http2Stream::Close(nsresult reason)
   1.896 +{
   1.897 +  mTransaction->Close(reason);
   1.898 +}
   1.899 +
   1.900 +bool
   1.901 +Http2Stream::AllowFlowControlledWrite()
   1.902 +{
   1.903 +  return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
   1.904 +}
   1.905 +
   1.906 +void
   1.907 +Http2Stream::UpdateServerReceiveWindow(int32_t delta)
   1.908 +{
   1.909 +  mServerReceiveWindow += delta;
   1.910 +
   1.911 +  if (mBlockedOnRwin && AllowFlowControlledWrite()) {
   1.912 +    LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
   1.913 +          "Open stream window\n", this, mStreamID));
   1.914 +    mSession->TransactionHasDataToWrite(this);  }
   1.915 +}
   1.916 +
   1.917 +void
   1.918 +Http2Stream::SetPriority(uint32_t newVal)
   1.919 +{
   1.920 +  mPriority = std::min(newVal, 0x7fffffffU);
   1.921 +}
   1.922 +
   1.923 +void
   1.924 +Http2Stream::SetRecvdFin(bool aStatus)
   1.925 +{
   1.926 +  mRecvdFin = aStatus ? 1 : 0;
   1.927 +  if (!aStatus)
   1.928 +    return;
   1.929 +
   1.930 +  if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
   1.931 +    mState = CLOSED_BY_REMOTE;
   1.932 +  } else if (mState == CLOSED_BY_LOCAL) {
   1.933 +    mState = CLOSED;
   1.934 +  }
   1.935 +}
   1.936 +
   1.937 +void
   1.938 +Http2Stream::SetSentFin(bool aStatus)
   1.939 +{
   1.940 +  mSentFin = aStatus ? 1 : 0;
   1.941 +  if (!aStatus)
   1.942 +    return;
   1.943 +
   1.944 +  if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
   1.945 +    mState = CLOSED_BY_LOCAL;
   1.946 +  } else if (mState == CLOSED_BY_REMOTE) {
   1.947 +    mState = CLOSED;
   1.948 +  }
   1.949 +}
   1.950 +
   1.951 +void
   1.952 +Http2Stream::SetRecvdReset(bool aStatus)
   1.953 +{
   1.954 +  mRecvdReset = aStatus ? 1 : 0;
   1.955 +  if (!aStatus)
   1.956 +    return;
   1.957 +  mState = CLOSED;
   1.958 +}
   1.959 +
   1.960 +void
   1.961 +Http2Stream::SetSentReset(bool aStatus)
   1.962 +{
   1.963 +  mSentReset = aStatus ? 1 : 0;
   1.964 +  if (!aStatus)
   1.965 +    return;
   1.966 +  mState = CLOSED;
   1.967 +}
   1.968 +
   1.969 +//-----------------------------------------------------------------------------
   1.970 +// nsAHttpSegmentReader
   1.971 +//-----------------------------------------------------------------------------
   1.972 +
   1.973 +nsresult
   1.974 +Http2Stream::OnReadSegment(const char *buf,
   1.975 +                           uint32_t count,
   1.976 +                           uint32_t *countRead)
   1.977 +{
   1.978 +  LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x",
   1.979 +        this, count, mUpstreamState));
   1.980 +
   1.981 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   1.982 +  MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
   1.983 +
   1.984 +  nsresult rv = NS_ERROR_UNEXPECTED;
   1.985 +  uint32_t dataLength;
   1.986 +
   1.987 +  switch (mUpstreamState) {
   1.988 +  case GENERATING_HEADERS:
   1.989 +    // The buffer is the HTTP request stream, including at least part of the
   1.990 +    // HTTP request header. This state's job is to build a HEADERS frame
   1.991 +    // from the header information. count is the number of http bytes available
   1.992 +    // (which may include more than the header), and in countRead we return
   1.993 +    // the number of those bytes that we consume (i.e. the portion that are
   1.994 +    // header bytes)
   1.995 +
   1.996 +    rv = ParseHttpRequestHeaders(buf, count, countRead);
   1.997 +    if (NS_FAILED(rv))
   1.998 +      return rv;
   1.999 +    LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
  1.1000 +          this, *countRead, count, mAllHeadersSent));
  1.1001 +    if (mAllHeadersSent) {
  1.1002 +      SetHTTPState(OPEN);
  1.1003 +      AdjustInitialWindow();
  1.1004 +      // This version of TransmitFrame cannot block
  1.1005 +      rv = TransmitFrame(nullptr, nullptr, true);
  1.1006 +      ChangeState(GENERATING_BODY);
  1.1007 +      break;
  1.1008 +    }
  1.1009 +    MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
  1.1010 +    break;
  1.1011 +
  1.1012 +  case GENERATING_BODY:
  1.1013 +    // if there is session flow control and either the stream window is active and
  1.1014 +    // exhaused or the session window is exhausted then suspend
  1.1015 +    if (!AllowFlowControlledWrite()) {
  1.1016 +      *countRead = 0;
  1.1017 +      LOG3(("Http2Stream this=%p, id 0x%X request body suspended because "
  1.1018 +            "remote window is stream=%ld session=%ld.\n", this, mStreamID,
  1.1019 +            mServerReceiveWindow, mSession->ServerSessionWindow()));
  1.1020 +      mBlockedOnRwin = true;
  1.1021 +      return NS_BASE_STREAM_WOULD_BLOCK;
  1.1022 +    }
  1.1023 +    mBlockedOnRwin = false;
  1.1024 +
  1.1025 +    // The chunk is the smallest of: availableData, configured chunkSize,
  1.1026 +    // stream window, session window, or 14 bit framing limit.
  1.1027 +    // Its amazing we send anything at all.
  1.1028 +    dataLength = std::min(count, mChunkSize);
  1.1029 +
  1.1030 +    if (dataLength > Http2Session::kMaxFrameData)
  1.1031 +      dataLength = Http2Session::kMaxFrameData;
  1.1032 +
  1.1033 +    if (dataLength > mSession->ServerSessionWindow())
  1.1034 +      dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
  1.1035 +
  1.1036 +    if (dataLength > mServerReceiveWindow)
  1.1037 +      dataLength = static_cast<uint32_t>(mServerReceiveWindow);
  1.1038 +
  1.1039 +    LOG3(("Http2Stream this=%p id 0x%X send calculation "
  1.1040 +          "avail=%d chunksize=%d stream window=%d session window=%d "
  1.1041 +          "max frame=%d USING=%d\n", this, mStreamID,
  1.1042 +          count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(),
  1.1043 +          Http2Session::kMaxFrameData, dataLength));
  1.1044 +
  1.1045 +    mSession->DecrementServerSessionWindow(dataLength);
  1.1046 +    mServerReceiveWindow -= dataLength;
  1.1047 +
  1.1048 +    LOG3(("Http2Stream %p id %x request len remaining %d, "
  1.1049 +          "count avail %d, chunk used %d",
  1.1050 +          this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
  1.1051 +    if (dataLength > mRequestBodyLenRemaining)
  1.1052 +      return NS_ERROR_UNEXPECTED;
  1.1053 +    mRequestBodyLenRemaining -= dataLength;
  1.1054 +    GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
  1.1055 +    ChangeState(SENDING_BODY);
  1.1056 +    // NO BREAK
  1.1057 +
  1.1058 +  case SENDING_BODY:
  1.1059 +    MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
  1.1060 +    rv = TransmitFrame(buf, countRead, false);
  1.1061 +    MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
  1.1062 +               "Transmit Frame should be all or nothing");
  1.1063 +
  1.1064 +    LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
  1.1065 +          "Header is %d Body is %d.",
  1.1066 +          rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
  1.1067 +
  1.1068 +    // normalize a partial write with a WOULD_BLOCK into just a partial write
  1.1069 +    // as some code will take WOULD_BLOCK to mean an error with nothing
  1.1070 +    // written (e.g. nsHttpTransaction::ReadRequestSegment()
  1.1071 +    if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
  1.1072 +      rv = NS_OK;
  1.1073 +
  1.1074 +    // If that frame was all sent, look for another one
  1.1075 +    if (!mTxInlineFrameUsed)
  1.1076 +      ChangeState(GENERATING_BODY);
  1.1077 +    break;
  1.1078 +
  1.1079 +  case SENDING_FIN_STREAM:
  1.1080 +    MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
  1.1081 +    break;
  1.1082 +
  1.1083 +  default:
  1.1084 +    MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
  1.1085 +    break;
  1.1086 +  }
  1.1087 +
  1.1088 +  return rv;
  1.1089 +}
  1.1090 +
  1.1091 +//-----------------------------------------------------------------------------
  1.1092 +// nsAHttpSegmentWriter
  1.1093 +//-----------------------------------------------------------------------------
  1.1094 +
  1.1095 +nsresult
  1.1096 +Http2Stream::OnWriteSegment(char *buf,
  1.1097 +                            uint32_t count,
  1.1098 +                            uint32_t *countWritten)
  1.1099 +{
  1.1100 +  LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
  1.1101 +        this, count, mUpstreamState, mStreamID));
  1.1102 +
  1.1103 +  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
  1.1104 +  MOZ_ASSERT(mSegmentWriter);
  1.1105 +
  1.1106 +  if (!mPushSource)
  1.1107 +    return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
  1.1108 +
  1.1109 +  nsresult rv;
  1.1110 +  rv = mPushSource->GetBufferedData(buf, count, countWritten);
  1.1111 +  if (NS_FAILED(rv))
  1.1112 +    return rv;
  1.1113 +
  1.1114 +  mSession->ConnectPushedStream(this);
  1.1115 +  return NS_OK;
  1.1116 +}
  1.1117 +
  1.1118 +} // namespace mozilla::net
  1.1119 +} // namespace mozilla
  1.1120 +

mercurial