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.)

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

mercurial