netwerk/protocol/http/Http2Session.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 "Http2Session.h"
michael@0 19 #include "Http2Stream.h"
michael@0 20 #include "Http2Push.h"
michael@0 21
michael@0 22 #include "mozilla/Telemetry.h"
michael@0 23 #include "mozilla/Preferences.h"
michael@0 24 #include "nsHttp.h"
michael@0 25 #include "nsHttpHandler.h"
michael@0 26 #include "nsHttpConnection.h"
michael@0 27 #include "nsILoadGroup.h"
michael@0 28 #include "nsISSLSocketControl.h"
michael@0 29 #include "nsISSLStatus.h"
michael@0 30 #include "nsISSLStatusProvider.h"
michael@0 31 #include "prprf.h"
michael@0 32 #include "prnetdb.h"
michael@0 33 #include "sslt.h"
michael@0 34
michael@0 35 #ifdef DEBUG
michael@0 36 // defined by the socket transport service while active
michael@0 37 extern PRThread *gSocketThread;
michael@0 38 #endif
michael@0 39
michael@0 40 namespace mozilla {
michael@0 41 namespace net {
michael@0 42
michael@0 43 // Http2Session has multiple inheritance of things that implement
michael@0 44 // nsISupports, so this magic is taken from nsHttpPipeline that
michael@0 45 // implements some of the same abstract classes.
michael@0 46 NS_IMPL_ADDREF(Http2Session)
michael@0 47 NS_IMPL_RELEASE(Http2Session)
michael@0 48 NS_INTERFACE_MAP_BEGIN(Http2Session)
michael@0 49 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
michael@0 50 NS_INTERFACE_MAP_END
michael@0 51
michael@0 52 // "magic" refers to the string that preceeds HTTP/2 on the wire
michael@0 53 // to help find any intermediaries speaking an older version of HTTP
michael@0 54 const uint8_t Http2Session::kMagicHello[] = {
michael@0 55 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
michael@0 56 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
michael@0 57 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
michael@0 58 };
michael@0 59
michael@0 60 #define RETURN_SESSION_ERROR(o,x) \
michael@0 61 do { \
michael@0 62 (o)->mGoAwayReason = (x); \
michael@0 63 return NS_ERROR_ILLEGAL_VALUE; \
michael@0 64 } while (0)
michael@0 65
michael@0 66 Http2Session::Http2Session(nsAHttpTransaction *aHttpTransaction,
michael@0 67 nsISocketTransport *aSocketTransport,
michael@0 68 int32_t firstPriority)
michael@0 69 : mSocketTransport(aSocketTransport),
michael@0 70 mSegmentReader(nullptr),
michael@0 71 mSegmentWriter(nullptr),
michael@0 72 mNextStreamID(3), // 1 is reserved for Updgrade handshakes
michael@0 73 mConcurrentHighWater(0),
michael@0 74 mDownstreamState(BUFFERING_OPENING_SETTINGS),
michael@0 75 mInputFrameBufferSize(kDefaultBufferSize),
michael@0 76 mInputFrameBufferUsed(0),
michael@0 77 mInputFrameFinal(false),
michael@0 78 mInputFrameDataStream(nullptr),
michael@0 79 mNeedsCleanup(nullptr),
michael@0 80 mDownstreamRstReason(NO_HTTP_ERROR),
michael@0 81 mExpectedHeaderID(0),
michael@0 82 mExpectedPushPromiseID(0),
michael@0 83 mContinuedPromiseStream(0),
michael@0 84 mShouldGoAway(false),
michael@0 85 mClosed(false),
michael@0 86 mCleanShutdown(false),
michael@0 87 mTLSProfileConfirmed(false),
michael@0 88 mGoAwayReason(NO_HTTP_ERROR),
michael@0 89 mGoAwayID(0),
michael@0 90 mOutgoingGoAwayID(0),
michael@0 91 mMaxConcurrent(kDefaultMaxConcurrent),
michael@0 92 mConcurrent(0),
michael@0 93 mServerPushedResources(0),
michael@0 94 mServerInitialStreamWindow(kDefaultRwin),
michael@0 95 mLocalSessionWindow(kDefaultRwin),
michael@0 96 mServerSessionWindow(kDefaultRwin),
michael@0 97 mOutputQueueSize(kDefaultQueueSize),
michael@0 98 mOutputQueueUsed(0),
michael@0 99 mOutputQueueSent(0),
michael@0 100 mLastReadEpoch(PR_IntervalNow()),
michael@0 101 mPingSentEpoch(0)
michael@0 102 {
michael@0 103 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 104
michael@0 105 static uint64_t sSerial;
michael@0 106 mSerial = ++sSerial;
michael@0 107
michael@0 108 LOG3(("Http2Session::Http2Session %p transaction 1 = %p serial=0x%X\n",
michael@0 109 this, aHttpTransaction, mSerial));
michael@0 110
michael@0 111 mConnection = aHttpTransaction->Connection();
michael@0 112 mInputFrameBuffer = new char[mInputFrameBufferSize];
michael@0 113 mOutputQueueBuffer = new char[mOutputQueueSize];
michael@0 114 mDecompressBuffer.SetCapacity(kDefaultBufferSize);
michael@0 115 mDecompressor.SetCompressor(&mCompressor);
michael@0 116
michael@0 117 mPushAllowance = gHttpHandler->SpdyPushAllowance();
michael@0 118
michael@0 119 mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
michael@0 120 SendHello();
michael@0 121
michael@0 122 if (!aHttpTransaction->IsNullTransaction())
michael@0 123 AddStream(aHttpTransaction, firstPriority);
michael@0 124 mLastDataReadEpoch = mLastReadEpoch;
michael@0 125
michael@0 126 mPingThreshold = gHttpHandler->SpdyPingThreshold();
michael@0 127 }
michael@0 128
michael@0 129 PLDHashOperator
michael@0 130 Http2Session::ShutdownEnumerator(nsAHttpTransaction *key,
michael@0 131 nsAutoPtr<Http2Stream> &stream,
michael@0 132 void *closure)
michael@0 133 {
michael@0 134 Http2Session *self = static_cast<Http2Session *>(closure);
michael@0 135
michael@0 136 // On a clean server hangup the server sets the GoAwayID to be the ID of
michael@0 137 // the last transaction it processed. If the ID of stream in the
michael@0 138 // local stream is greater than that it can safely be restarted because the
michael@0 139 // server guarantees it was not partially processed. Streams that have not
michael@0 140 // registered an ID haven't actually been sent yet so they can always be
michael@0 141 // restarted.
michael@0 142 if (self->mCleanShutdown &&
michael@0 143 (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID())) {
michael@0 144 self->CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted
michael@0 145 } else {
michael@0 146 self->CloseStream(stream, NS_ERROR_ABORT);
michael@0 147 }
michael@0 148
michael@0 149 return PL_DHASH_NEXT;
michael@0 150 }
michael@0 151
michael@0 152 PLDHashOperator
michael@0 153 Http2Session::GoAwayEnumerator(nsAHttpTransaction *key,
michael@0 154 nsAutoPtr<Http2Stream> &stream,
michael@0 155 void *closure)
michael@0 156 {
michael@0 157 Http2Session *self = static_cast<Http2Session *>(closure);
michael@0 158
michael@0 159 // these streams were not processed by the server and can be restarted.
michael@0 160 // Do that after the enumerator completes to avoid the risk of
michael@0 161 // a restart event re-entrantly modifying this hash. Be sure not to restart
michael@0 162 // a pushed (even numbered) stream
michael@0 163 if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
michael@0 164 !stream->HasRegisteredID()) {
michael@0 165 self->mGoAwayStreamsToRestart.Push(stream);
michael@0 166 }
michael@0 167
michael@0 168 return PL_DHASH_NEXT;
michael@0 169 }
michael@0 170
michael@0 171 Http2Session::~Http2Session()
michael@0 172 {
michael@0 173 LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
michael@0 174 this, mDownstreamState));
michael@0 175
michael@0 176 mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
michael@0 177 Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
michael@0 178 Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
michael@0 179 Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
michael@0 180 mServerPushedResources);
michael@0 181 }
michael@0 182
michael@0 183 void
michael@0 184 Http2Session::LogIO(Http2Session *self, Http2Stream *stream,
michael@0 185 const char *label,
michael@0 186 const char *data, uint32_t datalen)
michael@0 187 {
michael@0 188 if (!LOG4_ENABLED())
michael@0 189 return;
michael@0 190
michael@0 191 LOG4(("Http2Session::LogIO %p stream=%p id=0x%X [%s]",
michael@0 192 self, stream, stream ? stream->StreamID() : 0, label));
michael@0 193
michael@0 194 // Max line is (16 * 3) + 10(prefix) + newline + null
michael@0 195 char linebuf[128];
michael@0 196 uint32_t index;
michael@0 197 char *line = linebuf;
michael@0 198
michael@0 199 linebuf[127] = 0;
michael@0 200
michael@0 201 for (index = 0; index < datalen; ++index) {
michael@0 202 if (!(index % 16)) {
michael@0 203 if (index) {
michael@0 204 *line = 0;
michael@0 205 LOG4(("%s", linebuf));
michael@0 206 }
michael@0 207 line = linebuf;
michael@0 208 PR_snprintf(line, 128, "%08X: ", index);
michael@0 209 line += 10;
michael@0 210 }
michael@0 211 PR_snprintf(line, 128 - (line - linebuf), "%02X ",
michael@0 212 (reinterpret_cast<const uint8_t *>(data))[index]);
michael@0 213 line += 3;
michael@0 214 }
michael@0 215 if (index) {
michael@0 216 *line = 0;
michael@0 217 LOG4(("%s", linebuf));
michael@0 218 }
michael@0 219 }
michael@0 220
michael@0 221 typedef nsresult (*Http2ControlFx) (Http2Session *self);
michael@0 222 static Http2ControlFx sControlFunctions[] = {
michael@0 223 nullptr, // type 0 data is not a control function
michael@0 224 Http2Session::RecvHeaders,
michael@0 225 Http2Session::RecvPriority,
michael@0 226 Http2Session::RecvRstStream,
michael@0 227 Http2Session::RecvSettings,
michael@0 228 Http2Session::RecvPushPromise,
michael@0 229 Http2Session::RecvPing,
michael@0 230 Http2Session::RecvGoAway,
michael@0 231 Http2Session::RecvWindowUpdate,
michael@0 232 Http2Session::RecvContinuation
michael@0 233 };
michael@0 234
michael@0 235 bool
michael@0 236 Http2Session::RoomForMoreConcurrent()
michael@0 237 {
michael@0 238 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 239 return (mConcurrent < mMaxConcurrent);
michael@0 240 }
michael@0 241
michael@0 242 bool
michael@0 243 Http2Session::RoomForMoreStreams()
michael@0 244 {
michael@0 245 if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
michael@0 246 return false;
michael@0 247
michael@0 248 return !mShouldGoAway;
michael@0 249 }
michael@0 250
michael@0 251 PRIntervalTime
michael@0 252 Http2Session::IdleTime()
michael@0 253 {
michael@0 254 return PR_IntervalNow() - mLastDataReadEpoch;
michael@0 255 }
michael@0 256
michael@0 257 uint32_t
michael@0 258 Http2Session::ReadTimeoutTick(PRIntervalTime now)
michael@0 259 {
michael@0 260 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 261
michael@0 262 LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
michael@0 263 this, PR_IntervalToSeconds(now - mLastReadEpoch)));
michael@0 264
michael@0 265 if (!mPingThreshold)
michael@0 266 return UINT32_MAX;
michael@0 267
michael@0 268 if ((now - mLastReadEpoch) < mPingThreshold) {
michael@0 269 // recent activity means ping is not an issue
michael@0 270 if (mPingSentEpoch)
michael@0 271 mPingSentEpoch = 0;
michael@0 272
michael@0 273 return PR_IntervalToSeconds(mPingThreshold) -
michael@0 274 PR_IntervalToSeconds(now - mLastReadEpoch);
michael@0 275 }
michael@0 276
michael@0 277 if (mPingSentEpoch) {
michael@0 278 LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n"));
michael@0 279 if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
michael@0 280 LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
michael@0 281 mPingSentEpoch = 0;
michael@0 282 Close(NS_ERROR_NET_TIMEOUT);
michael@0 283 return UINT32_MAX;
michael@0 284 }
michael@0 285 return 1; // run the tick aggressively while ping is outstanding
michael@0 286 }
michael@0 287
michael@0 288 LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
michael@0 289
michael@0 290 mPingSentEpoch = PR_IntervalNow();
michael@0 291 if (!mPingSentEpoch)
michael@0 292 mPingSentEpoch = 1; // avoid the 0 sentinel value
michael@0 293 GeneratePing(false);
michael@0 294 ResumeRecv(); // read the ping reply
michael@0 295
michael@0 296 // Check for orphaned push streams. This looks expensive, but generally the
michael@0 297 // list is empty.
michael@0 298 Http2PushedStream *deleteMe;
michael@0 299 TimeStamp timestampNow;
michael@0 300 do {
michael@0 301 deleteMe = nullptr;
michael@0 302
michael@0 303 for (uint32_t index = mPushedStreams.Length();
michael@0 304 index > 0 ; --index) {
michael@0 305 Http2PushedStream *pushedStream = mPushedStreams[index - 1];
michael@0 306
michael@0 307 if (timestampNow.IsNull())
michael@0 308 timestampNow = TimeStamp::Now(); // lazy initializer
michael@0 309
michael@0 310 // if stream finished, but is not connected, and its been like that for
michael@0 311 // long then cleanup the stream.
michael@0 312 if (pushedStream->IsOrphaned(timestampNow))
michael@0 313 {
michael@0 314 LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n",
michael@0 315 this, pushedStream->StreamID()));
michael@0 316 deleteMe = pushedStream;
michael@0 317 break; // don't CleanupStream() while iterating this vector
michael@0 318 }
michael@0 319 }
michael@0 320 if (deleteMe)
michael@0 321 CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
michael@0 322
michael@0 323 } while (deleteMe);
michael@0 324
michael@0 325 return 1; // run the tick aggressively while ping is outstanding
michael@0 326 }
michael@0 327
michael@0 328 uint32_t
michael@0 329 Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID)
michael@0 330 {
michael@0 331 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 332 MOZ_ASSERT(mNextStreamID < 0xfffffff0,
michael@0 333 "should have stopped admitting streams");
michael@0 334 MOZ_ASSERT(!(aNewID & 1),
michael@0 335 "0 for autoassign pull, otherwise explicit even push assignment");
michael@0 336
michael@0 337 if (!aNewID) {
michael@0 338 // auto generate a new pull stream ID
michael@0 339 aNewID = mNextStreamID;
michael@0 340 MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
michael@0 341 mNextStreamID += 2;
michael@0 342 }
michael@0 343
michael@0 344 LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
michael@0 345 "concurrent=%d",this, stream, aNewID, mConcurrent));
michael@0 346
michael@0 347 // We've used up plenty of ID's on this session. Start
michael@0 348 // moving to a new one before there is a crunch involving
michael@0 349 // server push streams or concurrent non-registered submits
michael@0 350 if (aNewID >= kMaxStreamID)
michael@0 351 mShouldGoAway = true;
michael@0 352
michael@0 353 // integrity check
michael@0 354 if (mStreamIDHash.Get(aNewID)) {
michael@0 355 LOG3((" New ID already present\n"));
michael@0 356 MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
michael@0 357 mShouldGoAway = true;
michael@0 358 return kDeadStreamID;
michael@0 359 }
michael@0 360
michael@0 361 mStreamIDHash.Put(aNewID, stream);
michael@0 362 return aNewID;
michael@0 363 }
michael@0 364
michael@0 365 bool
michael@0 366 Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
michael@0 367 int32_t aPriority)
michael@0 368 {
michael@0 369 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 370
michael@0 371 // integrity check
michael@0 372 if (mStreamTransactionHash.Get(aHttpTransaction)) {
michael@0 373 LOG3((" New transaction already present\n"));
michael@0 374 MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
michael@0 375 return false;
michael@0 376 }
michael@0 377
michael@0 378 aHttpTransaction->SetConnection(this);
michael@0 379 Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
michael@0 380
michael@0 381 LOG3(("Http2Session::AddStream session=%p stream=%p NextID=0x%X (tentative)",
michael@0 382 this, stream, mNextStreamID));
michael@0 383
michael@0 384 mStreamTransactionHash.Put(aHttpTransaction, stream);
michael@0 385
michael@0 386 if (RoomForMoreConcurrent()) {
michael@0 387 LOG3(("Http2Session::AddStream %p stream %p activated immediately.",
michael@0 388 this, stream));
michael@0 389 ActivateStream(stream);
michael@0 390 } else {
michael@0 391 LOG3(("Http2Session::AddStream %p stream %p queued.", this, stream));
michael@0 392 mQueuedStreams.Push(stream);
michael@0 393 }
michael@0 394
michael@0 395 if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
michael@0 396 LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
michael@0 397 this, aHttpTransaction));
michael@0 398 DontReuse();
michael@0 399 }
michael@0 400
michael@0 401 return true;
michael@0 402 }
michael@0 403
michael@0 404 void
michael@0 405 Http2Session::ActivateStream(Http2Stream *stream)
michael@0 406 {
michael@0 407 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 408 MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
michael@0 409 "Do not activate pushed streams");
michael@0 410
michael@0 411 MOZ_ASSERT(!stream->CountAsActive());
michael@0 412 stream->SetCountAsActive(true);
michael@0 413 ++mConcurrent;
michael@0 414
michael@0 415 if (mConcurrent > mConcurrentHighWater)
michael@0 416 mConcurrentHighWater = mConcurrent;
michael@0 417 LOG3(("Http2Session::AddStream %p activating stream %p Currently %d "
michael@0 418 "streams in session, high water mark is %d",
michael@0 419 this, stream, mConcurrent, mConcurrentHighWater));
michael@0 420
michael@0 421 mReadyForWrite.Push(stream);
michael@0 422 SetWriteCallbacks();
michael@0 423
michael@0 424 // Kick off the headers transmit without waiting for the poll loop
michael@0 425 // This won't work for stream id=1 because there is no segment reader
michael@0 426 // yet.
michael@0 427 if (mSegmentReader) {
michael@0 428 uint32_t countRead;
michael@0 429 ReadSegments(nullptr, kDefaultBufferSize, &countRead);
michael@0 430 }
michael@0 431 }
michael@0 432
michael@0 433 void
michael@0 434 Http2Session::ProcessPending()
michael@0 435 {
michael@0 436 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 437
michael@0 438 while (RoomForMoreConcurrent()) {
michael@0 439 Http2Stream *stream = static_cast<Http2Stream *>(mQueuedStreams.PopFront());
michael@0 440 if (!stream)
michael@0 441 return;
michael@0 442 LOG3(("Http2Session::ProcessPending %p stream %p activated from queue.",
michael@0 443 this, stream));
michael@0 444 ActivateStream(stream);
michael@0 445 }
michael@0 446 }
michael@0 447
michael@0 448 nsresult
michael@0 449 Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
michael@0 450 uint32_t count, uint32_t *countWritten)
michael@0 451 {
michael@0 452 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 453
michael@0 454 if (!count) {
michael@0 455 *countWritten = 0;
michael@0 456 return NS_OK;
michael@0 457 }
michael@0 458
michael@0 459 nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
michael@0 460 if (NS_SUCCEEDED(rv) && *countWritten > 0)
michael@0 461 mLastReadEpoch = PR_IntervalNow();
michael@0 462 return rv;
michael@0 463 }
michael@0 464
michael@0 465 void
michael@0 466 Http2Session::SetWriteCallbacks()
michael@0 467 {
michael@0 468 if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
michael@0 469 mConnection->ResumeSend();
michael@0 470 }
michael@0 471
michael@0 472 void
michael@0 473 Http2Session::RealignOutputQueue()
michael@0 474 {
michael@0 475 mOutputQueueUsed -= mOutputQueueSent;
michael@0 476 memmove(mOutputQueueBuffer.get(),
michael@0 477 mOutputQueueBuffer.get() + mOutputQueueSent,
michael@0 478 mOutputQueueUsed);
michael@0 479 mOutputQueueSent = 0;
michael@0 480 }
michael@0 481
michael@0 482 void
michael@0 483 Http2Session::FlushOutputQueue()
michael@0 484 {
michael@0 485 if (!mSegmentReader || !mOutputQueueUsed)
michael@0 486 return;
michael@0 487
michael@0 488 nsresult rv;
michael@0 489 uint32_t countRead;
michael@0 490 uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
michael@0 491
michael@0 492 rv = mSegmentReader->
michael@0 493 OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
michael@0 494 &countRead);
michael@0 495 LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d",
michael@0 496 this, avail, rv, countRead));
michael@0 497
michael@0 498 // Dont worry about errors on write, we will pick this up as a read error too
michael@0 499 if (NS_FAILED(rv))
michael@0 500 return;
michael@0 501
michael@0 502 if (countRead == avail) {
michael@0 503 mOutputQueueUsed = 0;
michael@0 504 mOutputQueueSent = 0;
michael@0 505 return;
michael@0 506 }
michael@0 507
michael@0 508 mOutputQueueSent += countRead;
michael@0 509
michael@0 510 // If the output queue is close to filling up and we have sent out a good
michael@0 511 // chunk of data from the beginning then realign it.
michael@0 512
michael@0 513 if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
michael@0 514 ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
michael@0 515 RealignOutputQueue();
michael@0 516 }
michael@0 517 }
michael@0 518
michael@0 519 void
michael@0 520 Http2Session::DontReuse()
michael@0 521 {
michael@0 522 mShouldGoAway = true;
michael@0 523 if (!mStreamTransactionHash.Count())
michael@0 524 Close(NS_OK);
michael@0 525 }
michael@0 526
michael@0 527 uint32_t
michael@0 528 Http2Session::GetWriteQueueSize()
michael@0 529 {
michael@0 530 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 531
michael@0 532 return mReadyForWrite.GetSize();
michael@0 533 }
michael@0 534
michael@0 535 void
michael@0 536 Http2Session::ChangeDownstreamState(enum internalStateType newState)
michael@0 537 {
michael@0 538 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 539
michael@0 540 LOG3(("Http2Stream::ChangeDownstreamState() %p from %X to %X",
michael@0 541 this, mDownstreamState, newState));
michael@0 542 mDownstreamState = newState;
michael@0 543 }
michael@0 544
michael@0 545 void
michael@0 546 Http2Session::ResetDownstreamState()
michael@0 547 {
michael@0 548 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 549
michael@0 550 LOG3(("Http2Stream::ResetDownstreamState() %p", this));
michael@0 551 ChangeDownstreamState(BUFFERING_FRAME_HEADER);
michael@0 552
michael@0 553 if (mInputFrameFinal && mInputFrameDataStream) {
michael@0 554 mInputFrameFinal = false;
michael@0 555 LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
michael@0 556 mInputFrameDataStream->SetRecvdFin(true);
michael@0 557 MaybeDecrementConcurrent(mInputFrameDataStream);
michael@0 558 }
michael@0 559 mInputFrameBufferUsed = 0;
michael@0 560 mInputFrameDataStream = nullptr;
michael@0 561 }
michael@0 562
michael@0 563 template<typename T> void
michael@0 564 Http2Session::EnsureBuffer(nsAutoArrayPtr<T> &buf, uint32_t newSize,
michael@0 565 uint32_t preserve, uint32_t &objSize)
michael@0 566 {
michael@0 567 if (objSize >= newSize)
michael@0 568 return;
michael@0 569
michael@0 570 // Leave a little slop on the new allocation - add 2KB to
michael@0 571 // what we need and then round the result up to a 4KB (page)
michael@0 572 // boundary.
michael@0 573
michael@0 574 objSize = (newSize + 2048 + 4095) & ~4095;
michael@0 575
michael@0 576 static_assert(sizeof(T) == 1, "sizeof(T) must be 1");
michael@0 577 nsAutoArrayPtr<T> tmp(new T[objSize]);
michael@0 578 memcpy(tmp, buf, preserve);
michael@0 579 buf = tmp;
michael@0 580 }
michael@0 581
michael@0 582 // Instantiate supported templates explicitly.
michael@0 583 template void
michael@0 584 Http2Session::EnsureBuffer(nsAutoArrayPtr<char> &buf, uint32_t newSize,
michael@0 585 uint32_t preserve, uint32_t &objSize);
michael@0 586
michael@0 587 template void
michael@0 588 Http2Session::EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf, uint32_t newSize,
michael@0 589 uint32_t preserve, uint32_t &objSize);
michael@0 590
michael@0 591 // call with data length (i.e. 0 for 0 data bytes - ignore 8 byte header)
michael@0 592 // dest must have 8 bytes of allocated space
michael@0 593 template<typename charType> void
michael@0 594 Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
michael@0 595 uint8_t frameType, uint8_t frameFlags,
michael@0 596 uint32_t streamID)
michael@0 597 {
michael@0 598 MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
michael@0 599 MOZ_ASSERT(!(streamID & 0x80000000));
michael@0 600
michael@0 601 frameLength = PR_htons(frameLength);
michael@0 602 streamID = PR_htonl(streamID);
michael@0 603
michael@0 604 memcpy(dest, &frameLength, 2);
michael@0 605 dest[2] = frameType;
michael@0 606 dest[3] = frameFlags;
michael@0 607 memcpy(dest + 4, &streamID, 4);
michael@0 608 }
michael@0 609
michael@0 610 char *
michael@0 611 Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded)
michael@0 612 {
michael@0 613 // this is an infallible allocation (if an allocation is
michael@0 614 // needed, which is probably isn't)
michael@0 615 EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
michael@0 616 mOutputQueueUsed, mOutputQueueSize);
michael@0 617 return mOutputQueueBuffer.get() + mOutputQueueUsed;
michael@0 618 }
michael@0 619
michael@0 620 template void
michael@0 621 Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength,
michael@0 622 uint8_t frameType, uint8_t frameFlags,
michael@0 623 uint32_t streamID);
michael@0 624
michael@0 625 template void
michael@0 626 Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength,
michael@0 627 uint8_t frameType, uint8_t frameFlags,
michael@0 628 uint32_t streamID);
michael@0 629
michael@0 630 void
michael@0 631 Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream)
michael@0 632 {
michael@0 633 LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n",
michael@0 634 this, aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
michael@0 635
michael@0 636 if (!aStream->CountAsActive())
michael@0 637 return;
michael@0 638
michael@0 639 MOZ_ASSERT(mConcurrent);
michael@0 640 aStream->SetCountAsActive(false);
michael@0 641 --mConcurrent;
michael@0 642 ProcessPending();
michael@0 643 }
michael@0 644
michael@0 645 // Need to decompress some data in order to keep the compression
michael@0 646 // context correct, but we really don't care what the result is
michael@0 647 nsresult
michael@0 648 Http2Session::UncompressAndDiscard()
michael@0 649 {
michael@0 650 nsresult rv;
michael@0 651 nsAutoCString trash;
michael@0 652
michael@0 653 rv = mDecompressor.DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(mDecompressBuffer.BeginReading()),
michael@0 654 mDecompressBuffer.Length(), trash);
michael@0 655 mDecompressBuffer.Truncate();
michael@0 656 if (NS_FAILED(rv)) {
michael@0 657 LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n",
michael@0 658 this));
michael@0 659 mGoAwayReason = COMPRESSION_ERROR;
michael@0 660 return rv;
michael@0 661 }
michael@0 662 return NS_OK;
michael@0 663 }
michael@0 664
michael@0 665 void
michael@0 666 Http2Session::GeneratePing(bool isAck)
michael@0 667 {
michael@0 668 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 669 LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
michael@0 670
michael@0 671 char *packet = EnsureOutputBuffer(16);
michael@0 672 mOutputQueueUsed += 16;
michael@0 673
michael@0 674 if (isAck) {
michael@0 675 CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
michael@0 676 memcpy(packet + 8, mInputFrameBuffer.get() + 8, 8);
michael@0 677 } else {
michael@0 678 CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
michael@0 679 memset(packet + 8, 0, 8);
michael@0 680 }
michael@0 681
michael@0 682 LogIO(this, nullptr, "Generate Ping", packet, 16);
michael@0 683 FlushOutputQueue();
michael@0 684 }
michael@0 685
michael@0 686 void
michael@0 687 Http2Session::GenerateSettingsAck()
michael@0 688 {
michael@0 689 // need to generate ack of this settings frame
michael@0 690 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 691 LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
michael@0 692
michael@0 693 char *packet = EnsureOutputBuffer(8);
michael@0 694 mOutputQueueUsed += 8;
michael@0 695 CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
michael@0 696 LogIO(this, nullptr, "Generate Settings ACK", packet, 8);
michael@0 697 FlushOutputQueue();
michael@0 698 }
michael@0 699
michael@0 700 void
michael@0 701 Http2Session::GeneratePriority(uint32_t aID, uint32_t aPriority)
michael@0 702 {
michael@0 703 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 704 LOG3(("Http2Session::GeneratePriority %p %X %X\n",
michael@0 705 this, aID, aPriority));
michael@0 706
michael@0 707 char *packet = EnsureOutputBuffer(12);
michael@0 708 mOutputQueueUsed += 12;
michael@0 709
michael@0 710 CreateFrameHeader(packet, 4, FRAME_TYPE_PRIORITY, 0, aID);
michael@0 711 aPriority = PR_htonl(aPriority);
michael@0 712 memcpy(packet + 8, &aPriority, 4);
michael@0 713 LogIO(this, nullptr, "Generate Priority", packet, 12);
michael@0 714 FlushOutputQueue();
michael@0 715 }
michael@0 716
michael@0 717 void
michael@0 718 Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
michael@0 719 {
michael@0 720 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 721
michael@0 722 // make sure we don't do this twice for the same stream (at least if we
michael@0 723 // have a stream entry for it)
michael@0 724 Http2Stream *stream = mStreamIDHash.Get(aID);
michael@0 725 if (stream) {
michael@0 726 if (stream->SentReset())
michael@0 727 return;
michael@0 728 stream->SetSentReset(true);
michael@0 729 }
michael@0 730
michael@0 731 LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
michael@0 732
michael@0 733 char *packet = EnsureOutputBuffer(12);
michael@0 734 mOutputQueueUsed += 12;
michael@0 735 CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
michael@0 736
michael@0 737 aStatusCode = PR_htonl(aStatusCode);
michael@0 738 memcpy(packet + 8, &aStatusCode, 4);
michael@0 739
michael@0 740 LogIO(this, nullptr, "Generate Reset", packet, 12);
michael@0 741 FlushOutputQueue();
michael@0 742 }
michael@0 743
michael@0 744 void
michael@0 745 Http2Session::GenerateGoAway(uint32_t aStatusCode)
michael@0 746 {
michael@0 747 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 748 LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
michael@0 749
michael@0 750 char *packet = EnsureOutputBuffer(16);
michael@0 751 mOutputQueueUsed += 16;
michael@0 752
michael@0 753 memset(packet + 8, 0, 8);
michael@0 754 CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
michael@0 755
michael@0 756 // last-good-stream-id are bytes 8-11 reflecting pushes
michael@0 757 uint32_t goAway = PR_htonl(mOutgoingGoAwayID);
michael@0 758 memcpy (packet + 7, &goAway, 4);
michael@0 759
michael@0 760 // bytes 12-15 are the status code.
michael@0 761 aStatusCode = PR_htonl(aStatusCode);
michael@0 762 memcpy(packet + 12, &aStatusCode, 4);
michael@0 763
michael@0 764 LogIO(this, nullptr, "Generate GoAway", packet, 16);
michael@0 765 FlushOutputQueue();
michael@0 766 }
michael@0 767
michael@0 768 // The Hello is comprised of 24 octets of magic, which are designed to
michael@0 769 // flush out silent but broken intermediaries, followed by a settings
michael@0 770 // frame which sets a small flow control window for pushes and a
michael@0 771 // window update frame which creates a large session flow control window
michael@0 772 void
michael@0 773 Http2Session::SendHello()
michael@0 774 {
michael@0 775 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 776 LOG3(("Http2Session::SendHello %p\n", this));
michael@0 777
michael@0 778 // sized for magic + 2 settings and a session window update to follow
michael@0 779 // 24 magic, 23 for settings (8 header + 3 settings @5), 12 for window update
michael@0 780 static const uint32_t maxSettings = 3;
michael@0 781 static const uint32_t maxDataLen = 24 + 8 + maxSettings * 5 + 12;
michael@0 782 char *packet = EnsureOutputBuffer(maxDataLen);
michael@0 783 memcpy(packet, kMagicHello, 24);
michael@0 784 mOutputQueueUsed += 24;
michael@0 785 LogIO(this, nullptr, "Magic Connection Header", packet, 24);
michael@0 786
michael@0 787 packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
michael@0 788 memset(packet, 0, maxDataLen - 24);
michael@0 789
michael@0 790 // frame header will be filled in after we know how long the frame is
michael@0 791 uint8_t numberOfEntries = 0;
michael@0 792
michael@0 793 // entries need to be listed in order by ID
michael@0 794 // 1st entry is bytes 8 to 12
michael@0 795 // 2nd entry is bytes 13 to 17
michael@0 796 // 3rd entry is bytes 18 to 22
michael@0 797
michael@0 798 if (!gHttpHandler->AllowPush()) {
michael@0 799 // If we don't support push then set MAX_CONCURRENT to 0 and also
michael@0 800 // set ENABLE_PUSH to 0
michael@0 801 packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_ENABLE_PUSH;
michael@0 802 // The value portion of the setting pair is already initialized to 0
michael@0 803 numberOfEntries++;
michael@0 804
michael@0 805 packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT;
michael@0 806 // The value portion of the setting pair is already initialized to 0
michael@0 807 numberOfEntries++;
michael@0 808 }
michael@0 809
michael@0 810 // Advertise the Push RWIN for the session, and on each new pull stream
michael@0 811 // send a window update with END_FLOW_CONTROL
michael@0 812 packet[8 + 5 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW;
michael@0 813 uint32_t rwin = PR_htonl(mPushAllowance);
michael@0 814 memcpy(packet + 9 + 5 * numberOfEntries, &rwin, 4);
michael@0 815 numberOfEntries++;
michael@0 816
michael@0 817 MOZ_ASSERT(numberOfEntries <= maxSettings);
michael@0 818 uint32_t dataLen = 5 * numberOfEntries;
michael@0 819 CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
michael@0 820 mOutputQueueUsed += 8 + dataLen;
michael@0 821
michael@0 822 LogIO(this, nullptr, "Generate Settings", packet, 8 + dataLen);
michael@0 823
michael@0 824 // now bump the local session window from 64KB
michael@0 825 uint32_t sessionWindowBump = ASpdySession::kInitialRwin - kDefaultRwin;
michael@0 826 if (kDefaultRwin >= ASpdySession::kInitialRwin)
michael@0 827 goto sendHello_complete;
michael@0 828
michael@0 829 // send a window update for the session (Stream 0) for something large
michael@0 830 sessionWindowBump = PR_htonl(sessionWindowBump);
michael@0 831 mLocalSessionWindow = ASpdySession::kInitialRwin;
michael@0 832
michael@0 833 packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
michael@0 834 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
michael@0 835 mOutputQueueUsed += 12;
michael@0 836 memcpy(packet + 8, &sessionWindowBump, 4);
michael@0 837
michael@0 838 LOG3(("Session Window increase at start of session %p %u\n",
michael@0 839 this, PR_ntohl(sessionWindowBump)));
michael@0 840 LogIO(this, nullptr, "Session Window Bump ", packet, 12);
michael@0 841
michael@0 842 sendHello_complete:
michael@0 843 FlushOutputQueue();
michael@0 844 }
michael@0 845
michael@0 846 // perform a bunch of integrity checks on the stream.
michael@0 847 // returns true if passed, false (plus LOG and ABORT) if failed.
michael@0 848 bool
michael@0 849 Http2Session::VerifyStream(Http2Stream *aStream, uint32_t aOptionalID = 0)
michael@0 850 {
michael@0 851 // This is annoying, but at least it is O(1)
michael@0 852 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 853
michael@0 854 #ifndef DEBUG
michael@0 855 // Only do the real verification in debug builds
michael@0 856 return true;
michael@0 857 #endif
michael@0 858
michael@0 859 if (!aStream)
michael@0 860 return true;
michael@0 861
michael@0 862 uint32_t test = 0;
michael@0 863
michael@0 864 do {
michael@0 865 if (aStream->StreamID() == kDeadStreamID)
michael@0 866 break;
michael@0 867
michael@0 868 nsAHttpTransaction *trans = aStream->Transaction();
michael@0 869
michael@0 870 test++;
michael@0 871 if (!trans)
michael@0 872 break;
michael@0 873
michael@0 874 test++;
michael@0 875 if (mStreamTransactionHash.Get(trans) != aStream)
michael@0 876 break;
michael@0 877
michael@0 878 if (aStream->StreamID()) {
michael@0 879 Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID());
michael@0 880
michael@0 881 test++;
michael@0 882 if (idStream != aStream)
michael@0 883 break;
michael@0 884
michael@0 885 if (aOptionalID) {
michael@0 886 test++;
michael@0 887 if (idStream->StreamID() != aOptionalID)
michael@0 888 break;
michael@0 889 }
michael@0 890 }
michael@0 891
michael@0 892 // tests passed
michael@0 893 return true;
michael@0 894 } while (0);
michael@0 895
michael@0 896 LOG3(("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
michael@0 897 "optionalID=0x%X trans=%p test=%d\n",
michael@0 898 this, aStream, aStream->StreamID(),
michael@0 899 aOptionalID, aStream->Transaction(), test));
michael@0 900
michael@0 901 MOZ_ASSERT(false, "VerifyStream");
michael@0 902 return false;
michael@0 903 }
michael@0 904
michael@0 905 void
michael@0 906 Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
michael@0 907 errorType aResetCode)
michael@0 908 {
michael@0 909 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 910 LOG3(("Http2Session::CleanupStream %p %p 0x%X %X\n",
michael@0 911 this, aStream, aStream ? aStream->StreamID() : 0, aResult));
michael@0 912 if (!aStream) {
michael@0 913 return;
michael@0 914 }
michael@0 915
michael@0 916 Http2PushedStream *pushSource = nullptr;
michael@0 917
michael@0 918 if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) {
michael@0 919 LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
michael@0 920 return;
michael@0 921 }
michael@0 922
michael@0 923 if (!VerifyStream(aStream)) {
michael@0 924 LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
michael@0 925 return;
michael@0 926 }
michael@0 927
michael@0 928 pushSource = aStream->PushSource();
michael@0 929
michael@0 930 if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID()) {
michael@0 931 LOG3(("Stream had not processed recv FIN, sending RST code %X\n",
michael@0 932 aResetCode));
michael@0 933 GenerateRstStream(aResetCode, aStream->StreamID());
michael@0 934 }
michael@0 935
michael@0 936 CloseStream(aStream, aResult);
michael@0 937
michael@0 938 // Remove the stream from the ID hash table and, if an even id, the pushed
michael@0 939 // table too.
michael@0 940 uint32_t id = aStream->StreamID();
michael@0 941 if (id > 0) {
michael@0 942 mStreamIDHash.Remove(id);
michael@0 943 if (!(id & 1))
michael@0 944 mPushedStreams.RemoveElement(aStream);
michael@0 945 }
michael@0 946
michael@0 947 RemoveStreamFromQueues(aStream);
michael@0 948
michael@0 949 // removing from the stream transaction hash will
michael@0 950 // delete the Http2Stream and drop the reference to
michael@0 951 // its transaction
michael@0 952 mStreamTransactionHash.Remove(aStream->Transaction());
michael@0 953
michael@0 954 if (mShouldGoAway && !mStreamTransactionHash.Count())
michael@0 955 Close(NS_OK);
michael@0 956
michael@0 957 if (pushSource) {
michael@0 958 pushSource->SetDeferCleanupOnSuccess(false);
michael@0 959 CleanupStream(pushSource, aResult, aResetCode);
michael@0 960 }
michael@0 961 }
michael@0 962
michael@0 963 static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue)
michael@0 964 {
michael@0 965 uint32_t size = queue.GetSize();
michael@0 966 for (uint32_t count = 0; count < size; ++count) {
michael@0 967 Http2Stream *stream = static_cast<Http2Stream *>(queue.PopFront());
michael@0 968 if (stream != aStream)
michael@0 969 queue.Push(stream);
michael@0 970 }
michael@0 971 }
michael@0 972
michael@0 973 void
michael@0 974 Http2Session::RemoveStreamFromQueues(Http2Stream *aStream)
michael@0 975 {
michael@0 976 RemoveStreamFromQueue(aStream, mReadyForWrite);
michael@0 977 RemoveStreamFromQueue(aStream, mQueuedStreams);
michael@0 978 RemoveStreamFromQueue(aStream, mReadyForRead);
michael@0 979 }
michael@0 980
michael@0 981 void
michael@0 982 Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
michael@0 983 {
michael@0 984 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 985 LOG3(("Http2Session::CloseStream %p %p 0x%x %X\n",
michael@0 986 this, aStream, aStream->StreamID(), aResult));
michael@0 987
michael@0 988 MaybeDecrementConcurrent(aStream);
michael@0 989
michael@0 990 // Check if partial frame reader
michael@0 991 if (aStream == mInputFrameDataStream) {
michael@0 992 LOG3(("Stream had active partial read frame on close"));
michael@0 993 ChangeDownstreamState(DISCARDING_DATA_FRAME);
michael@0 994 mInputFrameDataStream = nullptr;
michael@0 995 }
michael@0 996
michael@0 997 RemoveStreamFromQueues(aStream);
michael@0 998
michael@0 999 // Send the stream the close() indication
michael@0 1000 aStream->Close(aResult);
michael@0 1001 }
michael@0 1002
michael@0 1003 nsresult
michael@0 1004 Http2Session::SetInputFrameDataStream(uint32_t streamID)
michael@0 1005 {
michael@0 1006 mInputFrameDataStream = mStreamIDHash.Get(streamID);
michael@0 1007 if (VerifyStream(mInputFrameDataStream, streamID))
michael@0 1008 return NS_OK;
michael@0 1009
michael@0 1010 LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
michael@0 1011 streamID));
michael@0 1012 mInputFrameDataStream = nullptr;
michael@0 1013 return NS_ERROR_UNEXPECTED;
michael@0 1014 }
michael@0 1015
michael@0 1016 nsresult
michael@0 1017 Http2Session::ParsePadding(uint8_t &paddingControlBytes, uint16_t &paddingLength)
michael@0 1018 {
michael@0 1019 if (mInputFrameFlags & kFlag_PAD_HIGH) {
michael@0 1020 uint8_t paddingHighValue = *reinterpret_cast<uint8_t *>(mInputFrameBuffer + 8);
michael@0 1021 paddingLength = static_cast<uint16_t>(paddingHighValue) * 256;
michael@0 1022 ++paddingControlBytes;
michael@0 1023 }
michael@0 1024
michael@0 1025 if (mInputFrameFlags & kFlag_PAD_LOW) {
michael@0 1026 uint8_t paddingLowValue = *reinterpret_cast<uint8_t *>(mInputFrameBuffer + 8 + paddingControlBytes);
michael@0 1027 paddingLength += paddingLowValue;
michael@0 1028 ++paddingControlBytes;
michael@0 1029 }
michael@0 1030
michael@0 1031 if (paddingLength > mInputFrameDataSize) {
michael@0 1032 // This is fatal to the session
michael@0 1033 LOG3(("Http2Session::RecvHeaders %p stream 0x%x PROTOCOL_ERROR "
michael@0 1034 "paddingLength %d > frame size %d\n",
michael@0 1035 this, mInputFrameID, paddingLength, mInputFrameDataSize));
michael@0 1036 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
michael@0 1037 }
michael@0 1038
michael@0 1039 return NS_OK;
michael@0 1040 }
michael@0 1041
michael@0 1042 nsresult
michael@0 1043 Http2Session::RecvHeaders(Http2Session *self)
michael@0 1044 {
michael@0 1045 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS);
michael@0 1046
michael@0 1047 // If this doesn't have END_HEADERS set on it then require the next
michael@0 1048 // frame to be HEADERS of the same ID
michael@0 1049 bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
michael@0 1050
michael@0 1051 if (endHeadersFlag)
michael@0 1052 self->mExpectedHeaderID = 0;
michael@0 1053 else
michael@0 1054 self->mExpectedHeaderID = self->mInputFrameID;
michael@0 1055
michael@0 1056 uint32_t priorityLen = (self->mInputFrameFlags & kFlag_PRIORITY) ? 4 : 0;
michael@0 1057 self->SetInputFrameDataStream(self->mInputFrameID);
michael@0 1058
michael@0 1059 // Find out how much padding this frame has, so we can only extract the real
michael@0 1060 // header data from the frame.
michael@0 1061 uint16_t paddingLength = 0;
michael@0 1062 uint8_t paddingControlBytes = 0;
michael@0 1063
michael@0 1064 nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
michael@0 1065 if (NS_FAILED(rv)) {
michael@0 1066 return rv;
michael@0 1067 }
michael@0 1068
michael@0 1069 LOG3(("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
michael@0 1070 "end_stream=%d end_headers=%d priority_flag=%d paddingLength=%d "
michael@0 1071 "pad_high_flag=%d pad_low_flag=%d\n",
michael@0 1072 self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
michael@0 1073 self->mInputFrameFlags & kFlag_END_STREAM,
michael@0 1074 self->mInputFrameFlags & kFlag_END_HEADERS,
michael@0 1075 self->mInputFrameFlags & kFlag_PRIORITY,
michael@0 1076 paddingLength,
michael@0 1077 self->mInputFrameFlags & kFlag_PAD_HIGH,
michael@0 1078 self->mInputFrameFlags & kFlag_PAD_LOW));
michael@0 1079
michael@0 1080 if (!self->mInputFrameDataStream) {
michael@0 1081 // Cannot find stream. We can continue the session, but we need to
michael@0 1082 // uncompress the header block to maintain the correct compression context
michael@0 1083
michael@0 1084 LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
michael@0 1085 "0x%X failed. NextStreamID = 0x%X\n",
michael@0 1086 self, self->mInputFrameID, self->mNextStreamID));
michael@0 1087
michael@0 1088 if (self->mInputFrameID >= self->mNextStreamID)
michael@0 1089 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
michael@0 1090
michael@0 1091 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen,
michael@0 1092 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
michael@0 1093
michael@0 1094 if (self->mInputFrameFlags & kFlag_END_HEADERS) {
michael@0 1095 rv = self->UncompressAndDiscard();
michael@0 1096 if (NS_FAILED(rv)) {
michael@0 1097 LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
michael@0 1098 // this is fatal to the session
michael@0 1099 self->mGoAwayReason = COMPRESSION_ERROR;
michael@0 1100 return rv;
michael@0 1101 }
michael@0 1102 }
michael@0 1103
michael@0 1104 self->ResetDownstreamState();
michael@0 1105 return NS_OK;
michael@0 1106 }
michael@0 1107
michael@0 1108 // queue up any compression bytes
michael@0 1109 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + priorityLen,
michael@0 1110 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
michael@0 1111
michael@0 1112 self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
michael@0 1113 self->mLastDataReadEpoch = self->mLastReadEpoch;
michael@0 1114
michael@0 1115 if (!endHeadersFlag) { // more are coming - don't process yet
michael@0 1116 self->ResetDownstreamState();
michael@0 1117 return NS_OK;
michael@0 1118 }
michael@0 1119
michael@0 1120 rv = self->ResponseHeadersComplete();
michael@0 1121 if (rv == NS_ERROR_ILLEGAL_VALUE) {
michael@0 1122 LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
michael@0 1123 self, self->mInputFrameID));
michael@0 1124 self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
michael@0 1125 self->ResetDownstreamState();
michael@0 1126 rv = NS_OK;
michael@0 1127 }
michael@0 1128 return rv;
michael@0 1129 }
michael@0 1130
michael@0 1131 // ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
michael@0 1132 // should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
michael@0 1133 // fine, and any other error is fatal to the session.
michael@0 1134 nsresult
michael@0 1135 Http2Session::ResponseHeadersComplete()
michael@0 1136 {
michael@0 1137 LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d",
michael@0 1138 this, mInputFrameDataStream->StreamID(), mInputFrameFinal));
michael@0 1139
michael@0 1140 // only do this once, afterwards ignore trailers
michael@0 1141 if (mInputFrameDataStream->AllHeadersReceived())
michael@0 1142 return NS_OK;
michael@0 1143 mInputFrameDataStream->SetAllHeadersReceived(true);
michael@0 1144
michael@0 1145 // The stream needs to see flattened http headers
michael@0 1146 // Uncompressed http/2 format headers currently live in
michael@0 1147 // Http2Stream::mDecompressBuffer - convert that to HTTP format in
michael@0 1148 // mFlatHTTPResponseHeaders via ConvertHeaders()
michael@0 1149
michael@0 1150 mFlatHTTPResponseHeadersOut = 0;
michael@0 1151 nsresult rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
michael@0 1152 mDecompressBuffer,
michael@0 1153 mFlatHTTPResponseHeaders);
michael@0 1154 if (NS_FAILED(rv))
michael@0 1155 return rv;
michael@0 1156
michael@0 1157 ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
michael@0 1158 return NS_OK;
michael@0 1159 }
michael@0 1160
michael@0 1161 nsresult
michael@0 1162 Http2Session::RecvPriority(Http2Session *self)
michael@0 1163 {
michael@0 1164 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
michael@0 1165
michael@0 1166 if (self->mInputFrameDataSize != 4) {
michael@0 1167 LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n",
michael@0 1168 self, self->mInputFrameDataSize));
michael@0 1169 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1170 }
michael@0 1171
michael@0 1172 if (!self->mInputFrameID) {
michael@0 1173 LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
michael@0 1174 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1175 }
michael@0 1176
michael@0 1177 uint32_t newPriority =
michael@0 1178 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
michael@0 1179 newPriority &= 0x7fffffff;
michael@0 1180
michael@0 1181 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
michael@0 1182 if (NS_FAILED(rv))
michael@0 1183 return rv;
michael@0 1184
michael@0 1185 if (self->mInputFrameDataStream)
michael@0 1186 self->mInputFrameDataStream->SetPriority(newPriority);
michael@0 1187 self->ResetDownstreamState();
michael@0 1188 return NS_OK;
michael@0 1189 }
michael@0 1190
michael@0 1191 nsresult
michael@0 1192 Http2Session::RecvRstStream(Http2Session *self)
michael@0 1193 {
michael@0 1194 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
michael@0 1195
michael@0 1196 if (self->mInputFrameDataSize != 4) {
michael@0 1197 LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
michael@0 1198 self, self->mInputFrameDataSize));
michael@0 1199 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1200 }
michael@0 1201
michael@0 1202 if (!self->mInputFrameID) {
michael@0 1203 LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
michael@0 1204 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1205 }
michael@0 1206
michael@0 1207 self->mDownstreamRstReason =
michael@0 1208 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
michael@0 1209
michael@0 1210 LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
michael@0 1211 self, self->mDownstreamRstReason, self->mInputFrameID));
michael@0 1212
michael@0 1213 self->SetInputFrameDataStream(self->mInputFrameID);
michael@0 1214 if (!self->mInputFrameDataStream) {
michael@0 1215 // if we can't find the stream just ignore it (4.2 closed)
michael@0 1216 self->ResetDownstreamState();
michael@0 1217 return NS_OK;
michael@0 1218 }
michael@0 1219
michael@0 1220 self->mInputFrameDataStream->SetRecvdReset(true);
michael@0 1221 self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
michael@0 1222 self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
michael@0 1223 return NS_OK;
michael@0 1224 }
michael@0 1225
michael@0 1226 PLDHashOperator
michael@0 1227 Http2Session::UpdateServerRwinEnumerator(nsAHttpTransaction *key,
michael@0 1228 nsAutoPtr<Http2Stream> &stream,
michael@0 1229 void *closure)
michael@0 1230 {
michael@0 1231 int32_t delta = *(static_cast<int32_t *>(closure));
michael@0 1232 stream->UpdateServerReceiveWindow(delta);
michael@0 1233 return PL_DHASH_NEXT;
michael@0 1234 }
michael@0 1235
michael@0 1236 nsresult
michael@0 1237 Http2Session::RecvSettings(Http2Session *self)
michael@0 1238 {
michael@0 1239 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
michael@0 1240
michael@0 1241 if (self->mInputFrameID) {
michael@0 1242 LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n",
michael@0 1243 self, self->mInputFrameID));
michael@0 1244 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1245 }
michael@0 1246
michael@0 1247 if (self->mInputFrameDataSize % 5) {
michael@0 1248 // Number of Settings is determined by dividing by each 5 byte setting
michael@0 1249 // entry. So the payload must be a multiple of 5.
michael@0 1250 LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d",
michael@0 1251 self, self->mInputFrameDataSize));
michael@0 1252 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1253 }
michael@0 1254
michael@0 1255 uint32_t numEntries = self->mInputFrameDataSize / 5;
michael@0 1256 LOG3(("Http2Session::RecvSettings %p SETTINGS Control Frame "
michael@0 1257 "with %d entries ack=%X", self, numEntries,
michael@0 1258 self->mInputFrameFlags & kFlag_ACK));
michael@0 1259
michael@0 1260 if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
michael@0 1261 LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n"));
michael@0 1262 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1263 }
michael@0 1264
michael@0 1265 for (uint32_t index = 0; index < numEntries; ++index) {
michael@0 1266 uint8_t *setting = reinterpret_cast<uint8_t *>
michael@0 1267 (self->mInputFrameBuffer.get()) + 8 + index * 5;
michael@0 1268
michael@0 1269 uint8_t id = setting[0];
michael@0 1270 uint32_t value = PR_ntohl(*reinterpret_cast<uint32_t *>(setting + 1));
michael@0 1271 LOG3(("Settings ID %d, Value %d", id, value));
michael@0 1272
michael@0 1273 switch (id)
michael@0 1274 {
michael@0 1275 case SETTINGS_TYPE_HEADER_TABLE_SIZE:
michael@0 1276 LOG3(("Compression header table setting received: %d\n", value));
michael@0 1277 self->mCompressor.SetMaxBufferSize(value);
michael@0 1278 break;
michael@0 1279
michael@0 1280 case SETTINGS_TYPE_ENABLE_PUSH:
michael@0 1281 LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
michael@0 1282 // nop
michael@0 1283 break;
michael@0 1284
michael@0 1285 case SETTINGS_TYPE_MAX_CONCURRENT:
michael@0 1286 self->mMaxConcurrent = value;
michael@0 1287 Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
michael@0 1288 break;
michael@0 1289
michael@0 1290 case SETTINGS_TYPE_INITIAL_WINDOW:
michael@0 1291 {
michael@0 1292 Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
michael@0 1293 int32_t delta = value - self->mServerInitialStreamWindow;
michael@0 1294 self->mServerInitialStreamWindow = value;
michael@0 1295
michael@0 1296 // SETTINGS only adjusts stream windows. Leave the sesison window alone.
michael@0 1297 // we need to add the delta to all open streams (delta can be negative)
michael@0 1298 self->mStreamTransactionHash.Enumerate(UpdateServerRwinEnumerator,
michael@0 1299 &delta);
michael@0 1300 }
michael@0 1301 break;
michael@0 1302
michael@0 1303 default:
michael@0 1304 break;
michael@0 1305 }
michael@0 1306 }
michael@0 1307
michael@0 1308 self->ResetDownstreamState();
michael@0 1309
michael@0 1310 if (!(self->mInputFrameFlags & kFlag_ACK))
michael@0 1311 self->GenerateSettingsAck();
michael@0 1312
michael@0 1313 return NS_OK;
michael@0 1314 }
michael@0 1315
michael@0 1316 nsresult
michael@0 1317 Http2Session::RecvPushPromise(Http2Session *self)
michael@0 1318 {
michael@0 1319 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE);
michael@0 1320
michael@0 1321 // Find out how much padding this frame has, so we can only extract the real
michael@0 1322 // header data from the frame.
michael@0 1323 uint16_t paddingLength = 0;
michael@0 1324 uint8_t paddingControlBytes = 0;
michael@0 1325 // TODO - will need to change this once PUSH_PROMISE allows padding
michael@0 1326 // (post-draft10)
michael@0 1327 // Right now, only CONTINUATION frames can have padding, and
michael@0 1328 // mExpectedPushPromiseID being set indicates that we're actually processing a
michael@0 1329 // CONTINUATION frame.
michael@0 1330 if (self->mExpectedPushPromiseID) {
michael@0 1331 nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
michael@0 1332 if (NS_FAILED(rv)) {
michael@0 1333 return rv;
michael@0 1334 }
michael@0 1335 }
michael@0 1336
michael@0 1337 // If this doesn't have END_PUSH_PROMISE set on it then require the next
michael@0 1338 // frame to be PUSH_PROMISE of the same ID
michael@0 1339 uint32_t promiseLen;
michael@0 1340 uint32_t promisedID;
michael@0 1341
michael@0 1342 if (self->mExpectedPushPromiseID) {
michael@0 1343 promiseLen = 0; // really a continuation frame
michael@0 1344 promisedID = self->mContinuedPromiseStream;
michael@0 1345 } else {
michael@0 1346 // TODO - will need to handle padding here when getting the promisedID, once
michael@0 1347 // PUSH_PROMISE allows padding (post-draft10)
michael@0 1348 promiseLen = 4;
michael@0 1349 promisedID =
michael@0 1350 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
michael@0 1351 promisedID &= 0x7fffffff;
michael@0 1352 }
michael@0 1353
michael@0 1354 uint32_t associatedID = self->mInputFrameID;
michael@0 1355
michael@0 1356 if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
michael@0 1357 self->mExpectedPushPromiseID = 0;
michael@0 1358 self->mContinuedPromiseStream = 0;
michael@0 1359 } else {
michael@0 1360 self->mExpectedPushPromiseID = self->mInputFrameID;
michael@0 1361 self->mContinuedPromiseStream = promisedID;
michael@0 1362 }
michael@0 1363
michael@0 1364 if (paddingLength > self->mInputFrameDataSize) {
michael@0 1365 // This is fatal to the session
michael@0 1366 LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
michael@0 1367 "PROTOCOL_ERROR paddingLength %d > frame size %d\n",
michael@0 1368 self, promisedID, associatedID, paddingLength,
michael@0 1369 self->mInputFrameDataSize));
michael@0 1370 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1371 }
michael@0 1372
michael@0 1373 LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
michael@0 1374 "paddingLength %d pad_high_flag %d pad_low_flag %d.\n",
michael@0 1375 self, promisedID, associatedID, paddingLength,
michael@0 1376 self->mInputFrameFlags & kFlag_PAD_HIGH,
michael@0 1377 self->mInputFrameFlags & kFlag_PAD_LOW));
michael@0 1378
michael@0 1379 if (!associatedID || !promisedID || (promisedID & 1)) {
michael@0 1380 LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
michael@0 1381 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1382 }
michael@0 1383
michael@0 1384 // confirm associated-to
michael@0 1385 nsresult rv = self->SetInputFrameDataStream(associatedID);
michael@0 1386 if (NS_FAILED(rv))
michael@0 1387 return rv;
michael@0 1388
michael@0 1389 Http2Stream *associatedStream = self->mInputFrameDataStream;
michael@0 1390 ++(self->mServerPushedResources);
michael@0 1391
michael@0 1392 // Anytime we start using the high bit of stream ID (either client or server)
michael@0 1393 // begin to migrate to a new session.
michael@0 1394 if (promisedID >= kMaxStreamID)
michael@0 1395 self->mShouldGoAway = true;
michael@0 1396
michael@0 1397 bool resetStream = true;
michael@0 1398 SpdyPushCache *cache = nullptr;
michael@0 1399
michael@0 1400 if (self->mShouldGoAway) {
michael@0 1401 LOG3(("Http2Session::RecvPushPromise %p push while in GoAway "
michael@0 1402 "mode refused.\n", self));
michael@0 1403 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
michael@0 1404 } else if (!gHttpHandler->AllowPush()) {
michael@0 1405 // MAX_CONCURRENT_STREAMS of 0 in settings disabled push
michael@0 1406 LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
michael@0 1407 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
michael@0 1408 } else if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
michael@0 1409 LOG3(("Http2Session::RecvPushPromise no support for multi frame push\n"));
michael@0 1410 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
michael@0 1411 } else if (!associatedStream) {
michael@0 1412 LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self));
michael@0 1413 self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
michael@0 1414 } else {
michael@0 1415 nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo();
michael@0 1416 if (loadGroupCI) {
michael@0 1417 loadGroupCI->GetSpdyPushCache(&cache);
michael@0 1418 if (!cache) {
michael@0 1419 cache = new SpdyPushCache();
michael@0 1420 if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache(cache))) {
michael@0 1421 delete cache;
michael@0 1422 cache = nullptr;
michael@0 1423 }
michael@0 1424 }
michael@0 1425 }
michael@0 1426 if (!cache) {
michael@0 1427 // this is unexpected, but we can handle it just by refusing the push
michael@0 1428 LOG3(("Http2Session::RecvPushPromise Push Recevied without loadgroup cache\n"));
michael@0 1429 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
michael@0 1430 } else {
michael@0 1431 resetStream = false;
michael@0 1432 }
michael@0 1433 }
michael@0 1434
michael@0 1435 if (resetStream) {
michael@0 1436 // Need to decompress the headers even though we aren't using them yet in
michael@0 1437 // order to keep the compression context consistent for other frames
michael@0 1438 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + paddingControlBytes + promiseLen,
michael@0 1439 self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
michael@0 1440 if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
michael@0 1441 rv = self->UncompressAndDiscard();
michael@0 1442 if (NS_FAILED(rv)) {
michael@0 1443 LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
michael@0 1444 self->mGoAwayReason = COMPRESSION_ERROR;
michael@0 1445 return rv;
michael@0 1446 }
michael@0 1447 }
michael@0 1448 self->ResetDownstreamState();
michael@0 1449 return NS_OK;
michael@0 1450 }
michael@0 1451
michael@0 1452 // Create the buffering transaction and push stream
michael@0 1453 nsRefPtr<Http2PushTransactionBuffer> transactionBuffer =
michael@0 1454 new Http2PushTransactionBuffer();
michael@0 1455 transactionBuffer->SetConnection(self);
michael@0 1456 Http2PushedStream *pushedStream =
michael@0 1457 new Http2PushedStream(transactionBuffer, self,
michael@0 1458 associatedStream, promisedID);
michael@0 1459
michael@0 1460 // Ownership of the pushed stream is by the transaction hash, just as it
michael@0 1461 // is for a client initiated stream. Errors that aren't fatal to the
michael@0 1462 // whole session must call cleanupStream() after this point in order
michael@0 1463 // to remove the stream from that hash.
michael@0 1464 self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
michael@0 1465 self->mPushedStreams.AppendElement(pushedStream);
michael@0 1466
michael@0 1467 self->mDecompressBuffer.Append(self->mInputFrameBuffer + 8 + promiseLen,
michael@0 1468 self->mInputFrameDataSize - promiseLen);
michael@0 1469
michael@0 1470 nsAutoCString requestHeaders;
michael@0 1471 rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
michael@0 1472 self->mDecompressBuffer, requestHeaders);
michael@0 1473
michael@0 1474 if (rv == NS_ERROR_NOT_IMPLEMENTED) {
michael@0 1475 LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
michael@0 1476 self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
michael@0 1477 return NS_OK;
michael@0 1478 }
michael@0 1479
michael@0 1480 if (NS_FAILED(rv))
michael@0 1481 return rv;
michael@0 1482
michael@0 1483 if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) {
michael@0 1484 LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
michael@0 1485 self->mGoAwayReason = INTERNAL_ERROR;
michael@0 1486 return NS_ERROR_FAILURE;
michael@0 1487 }
michael@0 1488
michael@0 1489 if (promisedID > self->mOutgoingGoAwayID)
michael@0 1490 self->mOutgoingGoAwayID = promisedID;
michael@0 1491
michael@0 1492 // Fake the request side of the pushed HTTP transaction. Sets up hash
michael@0 1493 // key and origin
michael@0 1494 uint32_t notUsed;
michael@0 1495 pushedStream->ReadSegments(nullptr, 1, &notUsed);
michael@0 1496
michael@0 1497 nsAutoCString key;
michael@0 1498 if (!pushedStream->GetHashKey(key)) {
michael@0 1499 LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
michael@0 1500 self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
michael@0 1501 self->ResetDownstreamState();
michael@0 1502 return NS_OK;
michael@0 1503 }
michael@0 1504
michael@0 1505 if (!associatedStream->Origin().Equals(pushedStream->Origin())) {
michael@0 1506 LOG3(("Http2Session::RecvPushPromise pushed stream mismatched origin\n"));
michael@0 1507 self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
michael@0 1508 self->ResetDownstreamState();
michael@0 1509 return NS_OK;
michael@0 1510 }
michael@0 1511
michael@0 1512 if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
michael@0 1513 LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
michael@0 1514 self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
michael@0 1515 self->ResetDownstreamState();
michael@0 1516 return NS_OK;
michael@0 1517 }
michael@0 1518
michael@0 1519 static_assert(Http2Stream::kWorstPriority >= 0,
michael@0 1520 "kWorstPriority out of range");
michael@0 1521 uint32_t unsignedPriority = static_cast<uint32_t>(Http2Stream::kWorstPriority);
michael@0 1522 pushedStream->SetPriority(unsignedPriority);
michael@0 1523 self->GeneratePriority(promisedID, unsignedPriority);
michael@0 1524 self->ResetDownstreamState();
michael@0 1525 return NS_OK;
michael@0 1526 }
michael@0 1527
michael@0 1528 nsresult
michael@0 1529 Http2Session::RecvPing(Http2Session *self)
michael@0 1530 {
michael@0 1531 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
michael@0 1532
michael@0 1533 LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
michael@0 1534 self->mInputFrameFlags));
michael@0 1535
michael@0 1536 if (self->mInputFrameDataSize != 8) {
michael@0 1537 LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d",
michael@0 1538 self, self->mInputFrameDataSize));
michael@0 1539 RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
michael@0 1540 }
michael@0 1541
michael@0 1542 if (self->mInputFrameID) {
michael@0 1543 LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
michael@0 1544 self, self->mInputFrameID));
michael@0 1545 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1546 }
michael@0 1547
michael@0 1548 if (self->mInputFrameFlags & kFlag_ACK) {
michael@0 1549 // presumably a reply to our timeout ping.. don't reply to it
michael@0 1550 self->mPingSentEpoch = 0;
michael@0 1551 } else {
michael@0 1552 // reply with a ack'd ping
michael@0 1553 self->GeneratePing(true);
michael@0 1554 }
michael@0 1555
michael@0 1556 self->ResetDownstreamState();
michael@0 1557 return NS_OK;
michael@0 1558 }
michael@0 1559
michael@0 1560 nsresult
michael@0 1561 Http2Session::RecvGoAway(Http2Session *self)
michael@0 1562 {
michael@0 1563 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
michael@0 1564
michael@0 1565 if (self->mInputFrameDataSize < 8) {
michael@0 1566 // data > 8 is an opaque token that we can't interpret. NSPR Logs will
michael@0 1567 // have the hex of all packets so there is no point in separately logging.
michael@0 1568 LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
michael@0 1569 self, self->mInputFrameDataSize));
michael@0 1570 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1571 }
michael@0 1572
michael@0 1573 if (self->mInputFrameID) {
michael@0 1574 LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
michael@0 1575 self, self->mInputFrameID));
michael@0 1576 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1577 }
michael@0 1578
michael@0 1579 self->mShouldGoAway = true;
michael@0 1580 self->mGoAwayID =
michael@0 1581 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
michael@0 1582 self->mGoAwayID &= 0x7fffffff;
michael@0 1583 self->mCleanShutdown = true;
michael@0 1584 uint32_t statusCode =
michael@0 1585 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[3]);
michael@0 1586
michael@0 1587 // Find streams greater than the last-good ID and mark them for deletion
michael@0 1588 // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. The
michael@0 1589 // underlying transaction can be restarted.
michael@0 1590 self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self);
michael@0 1591
michael@0 1592 // Process the streams marked for deletion and restart.
michael@0 1593 uint32_t size = self->mGoAwayStreamsToRestart.GetSize();
michael@0 1594 for (uint32_t count = 0; count < size; ++count) {
michael@0 1595 Http2Stream *stream =
michael@0 1596 static_cast<Http2Stream *>(self->mGoAwayStreamsToRestart.PopFront());
michael@0 1597
michael@0 1598 self->CloseStream(stream, NS_ERROR_NET_RESET);
michael@0 1599 if (stream->HasRegisteredID())
michael@0 1600 self->mStreamIDHash.Remove(stream->StreamID());
michael@0 1601 self->mStreamTransactionHash.Remove(stream->Transaction());
michael@0 1602 }
michael@0 1603
michael@0 1604 // Queued streams can also be deleted from this session and restarted
michael@0 1605 // in another one. (they were never sent on the network so they implicitly
michael@0 1606 // are not covered by the last-good id.
michael@0 1607 size = self->mQueuedStreams.GetSize();
michael@0 1608 for (uint32_t count = 0; count < size; ++count) {
michael@0 1609 Http2Stream *stream =
michael@0 1610 static_cast<Http2Stream *>(self->mQueuedStreams.PopFront());
michael@0 1611 self->CloseStream(stream, NS_ERROR_NET_RESET);
michael@0 1612 self->mStreamTransactionHash.Remove(stream->Transaction());
michael@0 1613 }
michael@0 1614
michael@0 1615 LOG3(("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
michael@0 1616 "live streams=%d\n", self, self->mGoAwayID, statusCode,
michael@0 1617 self->mStreamTransactionHash.Count()));
michael@0 1618
michael@0 1619 self->ResetDownstreamState();
michael@0 1620 return NS_OK;
michael@0 1621 }
michael@0 1622
michael@0 1623 PLDHashOperator
michael@0 1624 Http2Session::RestartBlockedOnRwinEnumerator(nsAHttpTransaction *key,
michael@0 1625 nsAutoPtr<Http2Stream> &stream,
michael@0 1626 void *closure)
michael@0 1627 {
michael@0 1628 Http2Session *self = static_cast<Http2Session *>(closure);
michael@0 1629 MOZ_ASSERT(self->mServerSessionWindow > 0);
michael@0 1630
michael@0 1631 if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0)
michael@0 1632 return PL_DHASH_NEXT;
michael@0 1633
michael@0 1634 self->mReadyForWrite.Push(stream);
michael@0 1635 self->SetWriteCallbacks();
michael@0 1636 return PL_DHASH_NEXT;
michael@0 1637 }
michael@0 1638
michael@0 1639 nsresult
michael@0 1640 Http2Session::RecvWindowUpdate(Http2Session *self)
michael@0 1641 {
michael@0 1642 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
michael@0 1643
michael@0 1644 if (self->mInputFrameDataSize != 4) {
michael@0 1645 LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
michael@0 1646 self, self->mInputFrameDataSize));
michael@0 1647 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1648 }
michael@0 1649
michael@0 1650 uint32_t delta =
michael@0 1651 PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
michael@0 1652 delta &= 0x7fffffff;
michael@0 1653
michael@0 1654 LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n",
michael@0 1655 self, delta, self->mInputFrameID));
michael@0 1656
michael@0 1657 if (self->mInputFrameID) { // stream window
michael@0 1658 nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
michael@0 1659 if (NS_FAILED(rv))
michael@0 1660 return rv;
michael@0 1661
michael@0 1662 if (!self->mInputFrameDataStream) {
michael@0 1663 LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
michael@0 1664 self, self->mInputFrameID));
michael@0 1665 // only resest the session if the ID is one we haven't ever opened
michael@0 1666 if (self->mInputFrameID >= self->mNextStreamID)
michael@0 1667 self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
michael@0 1668 self->ResetDownstreamState();
michael@0 1669 return NS_OK;
michael@0 1670 }
michael@0 1671
michael@0 1672 int64_t oldRemoteWindow = self->mInputFrameDataStream->ServerReceiveWindow();
michael@0 1673 self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
michael@0 1674 if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
michael@0 1675 // a window cannot reach 2^31 and be in compliance. Our calculations
michael@0 1676 // are 64 bit safe though.
michael@0 1677 LOG3(("Http2Session::RecvWindowUpdate %p stream window "
michael@0 1678 "exceeds 2^31 - 1\n", self));
michael@0 1679 self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
michael@0 1680 FLOW_CONTROL_ERROR);
michael@0 1681 self->ResetDownstreamState();
michael@0 1682 return NS_OK;
michael@0 1683 }
michael@0 1684
michael@0 1685 LOG3(("Http2Session::RecvWindowUpdate %p stream 0x%X window "
michael@0 1686 "%d increased by %d now %d.\n", self, self->mInputFrameID,
michael@0 1687 oldRemoteWindow, delta, oldRemoteWindow + delta));
michael@0 1688
michael@0 1689 } else { // session window update
michael@0 1690 int64_t oldRemoteWindow = self->mServerSessionWindow;
michael@0 1691 self->mServerSessionWindow += delta;
michael@0 1692
michael@0 1693 if (self->mServerSessionWindow >= 0x80000000) {
michael@0 1694 // a window cannot reach 2^31 and be in compliance. Our calculations
michael@0 1695 // are 64 bit safe though.
michael@0 1696 LOG3(("Http2Session::RecvWindowUpdate %p session window "
michael@0 1697 "exceeds 2^31 - 1\n", self));
michael@0 1698 RETURN_SESSION_ERROR(self, FLOW_CONTROL_ERROR);
michael@0 1699 }
michael@0 1700
michael@0 1701 if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
michael@0 1702 LOG3(("Http2Session::RecvWindowUpdate %p restart session window\n",
michael@0 1703 self));
michael@0 1704 self->mStreamTransactionHash.Enumerate(RestartBlockedOnRwinEnumerator, self);
michael@0 1705 }
michael@0 1706 LOG3(("Http2Session::RecvWindowUpdate %p session window "
michael@0 1707 "%d increased by %d now %d.\n", self,
michael@0 1708 oldRemoteWindow, delta, oldRemoteWindow + delta));
michael@0 1709 }
michael@0 1710
michael@0 1711 self->ResetDownstreamState();
michael@0 1712 return NS_OK;
michael@0 1713 }
michael@0 1714
michael@0 1715 nsresult
michael@0 1716 Http2Session::RecvContinuation(Http2Session *self)
michael@0 1717 {
michael@0 1718 MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
michael@0 1719 MOZ_ASSERT(self->mInputFrameID);
michael@0 1720 MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
michael@0 1721 MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
michael@0 1722
michael@0 1723 LOG3(("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
michael@0 1724 "promise id 0x%X header id 0x%X\n",
michael@0 1725 self, self->mInputFrameFlags, self->mInputFrameID,
michael@0 1726 self->mExpectedPushPromiseID, self->mExpectedHeaderID));
michael@0 1727
michael@0 1728 self->SetInputFrameDataStream(self->mInputFrameID);
michael@0 1729
michael@0 1730 if (!self->mInputFrameDataStream) {
michael@0 1731 LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
michael@0 1732 self->mInputFrameID));
michael@0 1733 RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
michael@0 1734 }
michael@0 1735
michael@0 1736 // continued headers
michael@0 1737 if (self->mExpectedHeaderID) {
michael@0 1738 self->mInputFrameFlags &= ~kFlag_PRIORITY;
michael@0 1739 return RecvHeaders(self);
michael@0 1740 }
michael@0 1741
michael@0 1742 // continued push promise
michael@0 1743 if (self->mInputFrameFlags & kFlag_END_HEADERS) {
michael@0 1744 self->mInputFrameFlags &= ~kFlag_END_HEADERS;
michael@0 1745 self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
michael@0 1746 }
michael@0 1747 return RecvPushPromise(self);
michael@0 1748 }
michael@0 1749
michael@0 1750 //-----------------------------------------------------------------------------
michael@0 1751 // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
michael@0 1752 // of these methods
michael@0 1753 //-----------------------------------------------------------------------------
michael@0 1754
michael@0 1755 void
michael@0 1756 Http2Session::OnTransportStatus(nsITransport* aTransport,
michael@0 1757 nsresult aStatus, uint64_t aProgress)
michael@0 1758 {
michael@0 1759 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1760
michael@0 1761 switch (aStatus) {
michael@0 1762 // These should appear only once, deliver to the first
michael@0 1763 // transaction on the session.
michael@0 1764 case NS_NET_STATUS_RESOLVING_HOST:
michael@0 1765 case NS_NET_STATUS_RESOLVED_HOST:
michael@0 1766 case NS_NET_STATUS_CONNECTING_TO:
michael@0 1767 case NS_NET_STATUS_CONNECTED_TO:
michael@0 1768 {
michael@0 1769 Http2Stream *target = mStreamIDHash.Get(1);
michael@0 1770 nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
michael@0 1771 if (transaction)
michael@0 1772 transaction->OnTransportStatus(aTransport, aStatus, aProgress);
michael@0 1773 break;
michael@0 1774 }
michael@0 1775
michael@0 1776 default:
michael@0 1777 // The other transport events are ignored here because there is no good
michael@0 1778 // way to map them to the right transaction in http/2. Instead, the events
michael@0 1779 // are generated again from the http/2 code and passed directly to the
michael@0 1780 // correct transaction.
michael@0 1781
michael@0 1782 // NS_NET_STATUS_SENDING_TO:
michael@0 1783 // This is generated by the socket transport when (part) of
michael@0 1784 // a transaction is written out
michael@0 1785 //
michael@0 1786 // There is no good way to map it to the right transaction in http/2,
michael@0 1787 // so it is ignored here and generated separately when the request
michael@0 1788 // is sent from Http2Stream::TransmitFrame
michael@0 1789
michael@0 1790 // NS_NET_STATUS_WAITING_FOR:
michael@0 1791 // Created by nsHttpConnection when the request has been totally sent.
michael@0 1792 // There is no good way to map it to the right transaction in http/2,
michael@0 1793 // so it is ignored here and generated separately when the same
michael@0 1794 // condition is complete in Http2Stream when there is no more
michael@0 1795 // request body left to be transmitted.
michael@0 1796
michael@0 1797 // NS_NET_STATUS_RECEIVING_FROM
michael@0 1798 // Generated in session whenever we read a data frame or a HEADERS
michael@0 1799 // that can be attributed to a particular stream/transaction
michael@0 1800
michael@0 1801 break;
michael@0 1802 }
michael@0 1803 }
michael@0 1804
michael@0 1805 // ReadSegments() is used to write data to the network. Generally, HTTP
michael@0 1806 // request data is pulled from the approriate transaction and
michael@0 1807 // converted to http/2 data. Sometimes control data like window-update are
michael@0 1808 // generated instead.
michael@0 1809
michael@0 1810 nsresult
michael@0 1811 Http2Session::ReadSegments(nsAHttpSegmentReader *reader,
michael@0 1812 uint32_t count, uint32_t *countRead)
michael@0 1813 {
michael@0 1814 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1815
michael@0 1816 MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
michael@0 1817 "Inconsistent Write Function Callback");
michael@0 1818
michael@0 1819 nsresult rv = ConfirmTLSProfile();
michael@0 1820 if (NS_FAILED(rv))
michael@0 1821 return rv;
michael@0 1822
michael@0 1823 if (reader)
michael@0 1824 mSegmentReader = reader;
michael@0 1825
michael@0 1826 *countRead = 0;
michael@0 1827
michael@0 1828 LOG3(("Http2Session::ReadSegments %p", this));
michael@0 1829
michael@0 1830 Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
michael@0 1831 if (!stream) {
michael@0 1832 LOG3(("Http2Session %p could not identify a stream to write; suspending.",
michael@0 1833 this));
michael@0 1834 FlushOutputQueue();
michael@0 1835 SetWriteCallbacks();
michael@0 1836 return NS_BASE_STREAM_WOULD_BLOCK;
michael@0 1837 }
michael@0 1838
michael@0 1839 LOG3(("Http2Session %p will write from Http2Stream %p 0x%X "
michael@0 1840 "block-input=%d block-output=%d\n", this, stream, stream->StreamID(),
michael@0 1841 stream->RequestBlockedOnRead(), stream->BlockedOnRwin()));
michael@0 1842
michael@0 1843 rv = stream->ReadSegments(this, count, countRead);
michael@0 1844
michael@0 1845 // Not every permutation of stream->ReadSegents produces data (and therefore
michael@0 1846 // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
michael@0 1847 // of that. But we might still have old data buffered that would be good
michael@0 1848 // to flush.
michael@0 1849 FlushOutputQueue();
michael@0 1850
michael@0 1851 // Allow new server reads - that might be data or control information
michael@0 1852 // (e.g. window updates or http replies) that are responses to these writes
michael@0 1853 ResumeRecv();
michael@0 1854
michael@0 1855 if (stream->RequestBlockedOnRead()) {
michael@0 1856
michael@0 1857 // We are blocked waiting for input - either more http headers or
michael@0 1858 // any request body data. When more data from the request stream
michael@0 1859 // becomes available the httptransaction will call conn->ResumeSend().
michael@0 1860
michael@0 1861 LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
michael@0 1862
michael@0 1863 // call readsegments again if there are other streams ready
michael@0 1864 // to run in this session
michael@0 1865 if (GetWriteQueueSize()) {
michael@0 1866 rv = NS_OK;
michael@0 1867 } else {
michael@0 1868 rv = NS_BASE_STREAM_WOULD_BLOCK;
michael@0 1869 }
michael@0 1870 SetWriteCallbacks();
michael@0 1871 return rv;
michael@0 1872 }
michael@0 1873
michael@0 1874 if (NS_FAILED(rv)) {
michael@0 1875 LOG3(("Http2Session::ReadSegments %p returning FAIL code %X",
michael@0 1876 this, rv));
michael@0 1877 if (rv != NS_BASE_STREAM_WOULD_BLOCK)
michael@0 1878 CleanupStream(stream, rv, CANCEL_ERROR);
michael@0 1879 return rv;
michael@0 1880 }
michael@0 1881
michael@0 1882 if (*countRead > 0) {
michael@0 1883 LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d",
michael@0 1884 this, stream, *countRead));
michael@0 1885 mReadyForWrite.Push(stream);
michael@0 1886 SetWriteCallbacks();
michael@0 1887 return rv;
michael@0 1888 }
michael@0 1889
michael@0 1890 if (stream->BlockedOnRwin()) {
michael@0 1891 LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
michael@0 1892 this, stream, stream->StreamID()));
michael@0 1893 return NS_BASE_STREAM_WOULD_BLOCK;
michael@0 1894 }
michael@0 1895
michael@0 1896 LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete",
michael@0 1897 this, stream));
michael@0 1898
michael@0 1899 // call readsegments again if there are other streams ready
michael@0 1900 // to go in this session
michael@0 1901 SetWriteCallbacks();
michael@0 1902
michael@0 1903 return rv;
michael@0 1904 }
michael@0 1905
michael@0 1906 nsresult
michael@0 1907 Http2Session::ReadyToProcessDataFrame(enum internalStateType newState)
michael@0 1908 {
michael@0 1909 MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
michael@0 1910 newState == DISCARDING_DATA_FRAME_PADDING);
michael@0 1911 ChangeDownstreamState(newState);
michael@0 1912
michael@0 1913 Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
michael@0 1914 mInputFrameDataSize >> 10);
michael@0 1915 mLastDataReadEpoch = mLastReadEpoch;
michael@0 1916
michael@0 1917 if (!mInputFrameID) {
michael@0 1918 LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
michael@0 1919 this));
michael@0 1920 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
michael@0 1921 }
michael@0 1922
michael@0 1923 nsresult rv = SetInputFrameDataStream(mInputFrameID);
michael@0 1924 if (NS_FAILED(rv)) {
michael@0 1925 LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
michael@0 1926 "failed. probably due to verification.\n", this, mInputFrameID));
michael@0 1927 return rv;
michael@0 1928 }
michael@0 1929 if (!mInputFrameDataStream) {
michael@0 1930 LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
michael@0 1931 "failed. Next = 0x%X", this, mInputFrameID, mNextStreamID));
michael@0 1932 if (mInputFrameID >= mNextStreamID)
michael@0 1933 GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
michael@0 1934 ChangeDownstreamState(DISCARDING_DATA_FRAME);
michael@0 1935 } else if (mInputFrameDataStream->RecvdFin() ||
michael@0 1936 mInputFrameDataStream->RecvdReset() ||
michael@0 1937 mInputFrameDataStream->SentReset()) {
michael@0 1938 LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
michael@0 1939 "Data arrived for already server closed stream.\n",
michael@0 1940 this, mInputFrameID));
michael@0 1941 if (mInputFrameDataStream->RecvdFin() || mInputFrameDataStream->RecvdReset())
michael@0 1942 GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
michael@0 1943 ChangeDownstreamState(DISCARDING_DATA_FRAME);
michael@0 1944 }
michael@0 1945
michael@0 1946 LOG3(("Start Processing Data Frame. "
michael@0 1947 "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
michael@0 1948 this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
michael@0 1949 mInputFrameDataSize));
michael@0 1950 UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
michael@0 1951
michael@0 1952 return NS_OK;
michael@0 1953 }
michael@0 1954
michael@0 1955 // WriteSegments() is used to read data off the socket. Generally this is
michael@0 1956 // just the http2 frame header and from there the appropriate *Stream
michael@0 1957 // is identified from the Stream-ID. The http transaction associated with
michael@0 1958 // that read then pulls in the data directly, which it will feed to
michael@0 1959 // OnWriteSegment(). That function will gateway it into http and feed
michael@0 1960 // it to the appropriate transaction.
michael@0 1961
michael@0 1962 // we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
michael@0 1963 // and decide if it is data or control.. if it is control, just deal with it.
michael@0 1964 // if it is data, identify the stream
michael@0 1965 // call stream->WriteSegments which can call this::OnWriteSegment to get the
michael@0 1966 // data. It always gets full frames if they are part of the stream
michael@0 1967
michael@0 1968 nsresult
michael@0 1969 Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
michael@0 1970 uint32_t count, uint32_t *countWritten)
michael@0 1971 {
michael@0 1972 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1973
michael@0 1974 LOG3(("Http2Session::WriteSegments %p InternalState %X\n",
michael@0 1975 this, mDownstreamState));
michael@0 1976
michael@0 1977 *countWritten = 0;
michael@0 1978
michael@0 1979 if (mClosed)
michael@0 1980 return NS_ERROR_FAILURE;
michael@0 1981
michael@0 1982 nsresult rv = ConfirmTLSProfile();
michael@0 1983 if (NS_FAILED(rv))
michael@0 1984 return rv;
michael@0 1985
michael@0 1986 SetWriteCallbacks();
michael@0 1987
michael@0 1988 // If there are http transactions attached to a push stream with filled buffers
michael@0 1989 // trigger that data pump here. This only reads from buffers (not the network)
michael@0 1990 // so mDownstreamState doesn't matter.
michael@0 1991 Http2Stream *pushConnectedStream =
michael@0 1992 static_cast<Http2Stream *>(mReadyForRead.PopFront());
michael@0 1993 if (pushConnectedStream) {
michael@0 1994 LOG3(("Http2Session::WriteSegments %p processing pushed stream 0x%X\n",
michael@0 1995 this, pushConnectedStream->StreamID()));
michael@0 1996 mSegmentWriter = writer;
michael@0 1997 rv = pushConnectedStream->WriteSegments(this, count, countWritten);
michael@0 1998 mSegmentWriter = nullptr;
michael@0 1999
michael@0 2000 // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
michael@0 2001 // so we need this check to determine the truth.
michael@0 2002 if (NS_SUCCEEDED(rv) && !*countWritten &&
michael@0 2003 pushConnectedStream->PushSource() &&
michael@0 2004 pushConnectedStream->PushSource()->GetPushComplete()) {
michael@0 2005 rv = NS_BASE_STREAM_CLOSED;
michael@0 2006 }
michael@0 2007
michael@0 2008 if (rv == NS_BASE_STREAM_CLOSED) {
michael@0 2009 CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
michael@0 2010 rv = NS_OK;
michael@0 2011 }
michael@0 2012
michael@0 2013 // if we return OK to nsHttpConnection it will use mSocketInCondition
michael@0 2014 // to determine whether to schedule more reads, incorrectly
michael@0 2015 // assuming that nsHttpConnection::OnSocketWrite() was called.
michael@0 2016 if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
michael@0 2017 rv = NS_BASE_STREAM_WOULD_BLOCK;
michael@0 2018 ResumeRecv();
michael@0 2019 }
michael@0 2020
michael@0 2021 return rv;
michael@0 2022 }
michael@0 2023
michael@0 2024 // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER
michael@0 2025 // except the only frame type it will allow is SETTINGS
michael@0 2026
michael@0 2027 // The session layer buffers the leading 8 byte header of every frame.
michael@0 2028 // Non-Data frames are then buffered for their full length, but data
michael@0 2029 // frames (type 0) are passed through to the http stack unprocessed
michael@0 2030
michael@0 2031 if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
michael@0 2032 mDownstreamState == BUFFERING_FRAME_HEADER) {
michael@0 2033 // The first 8 bytes of every frame is header information that
michael@0 2034 // we are going to want to strip before passing to http. That is
michael@0 2035 // true of both control and data packets.
michael@0 2036
michael@0 2037 MOZ_ASSERT(mInputFrameBufferUsed < 8,
michael@0 2038 "Frame Buffer Used Too Large for State");
michael@0 2039
michael@0 2040 rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed,
michael@0 2041 8 - mInputFrameBufferUsed, countWritten);
michael@0 2042
michael@0 2043 if (NS_FAILED(rv)) {
michael@0 2044 LOG3(("Http2Session %p buffering frame header read failure %x\n",
michael@0 2045 this, rv));
michael@0 2046 // maybe just blocked reading from network
michael@0 2047 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 2048 rv = NS_OK;
michael@0 2049 return rv;
michael@0 2050 }
michael@0 2051
michael@0 2052 LogIO(this, nullptr, "Reading Frame Header",
michael@0 2053 mInputFrameBuffer + mInputFrameBufferUsed, *countWritten);
michael@0 2054
michael@0 2055 mInputFrameBufferUsed += *countWritten;
michael@0 2056
michael@0 2057 if (mInputFrameBufferUsed < 8)
michael@0 2058 {
michael@0 2059 LOG3(("Http2Session::WriteSegments %p "
michael@0 2060 "BUFFERING FRAME HEADER incomplete size=%d",
michael@0 2061 this, mInputFrameBufferUsed));
michael@0 2062 return rv;
michael@0 2063 }
michael@0 2064
michael@0 2065 // 2 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
michael@0 2066 mInputFrameDataSize =
michael@0 2067 PR_ntohs(reinterpret_cast<uint16_t *>(mInputFrameBuffer.get())[0]);
michael@0 2068 mInputFrameType = reinterpret_cast<uint8_t *>(mInputFrameBuffer.get())[2];
michael@0 2069 mInputFrameFlags = reinterpret_cast<uint8_t *>(mInputFrameBuffer.get())[3];
michael@0 2070 mInputFrameID =
michael@0 2071 PR_ntohl(reinterpret_cast<uint32_t *>(mInputFrameBuffer.get())[1]);
michael@0 2072 mInputFrameID &= 0x7fffffff;
michael@0 2073 mInputFrameDataRead = 0;
michael@0 2074
michael@0 2075 if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS) {
michael@0 2076 mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
michael@0 2077 } else {
michael@0 2078 mInputFrameFinal = 0;
michael@0 2079 }
michael@0 2080
michael@0 2081 mPaddingLength = 0;
michael@0 2082 if (mInputFrameType == FRAME_TYPE_DATA ||
michael@0 2083 mInputFrameType == FRAME_TYPE_HEADERS ||
michael@0 2084 // TODO: also mInputFrameType == FRAME_TYPE_PUSH_PROMISE after draft10
michael@0 2085 mInputFrameType == FRAME_TYPE_CONTINUATION) {
michael@0 2086 if ((mInputFrameFlags & kFlag_PAD_HIGH) &&
michael@0 2087 !(mInputFrameFlags & kFlag_PAD_LOW)) {
michael@0 2088 LOG3(("Http2Session::WriteSegments %p PROTOCOL_ERROR pad_high present "
michael@0 2089 "without pad_low\n", this));
michael@0 2090 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
michael@0 2091 }
michael@0 2092 }
michael@0 2093
michael@0 2094 if (mInputFrameDataSize >= 0x4000) {
michael@0 2095 // Section 9.1 HTTP frames cannot exceed 2^14 - 1 but receviers must ignore
michael@0 2096 // those bits
michael@0 2097 LOG3(("Http2Session::WriteSegments %p WARNING Frame Length bits past 14 are not 0 %08X\n",
michael@0 2098 this, mInputFrameDataSize));
michael@0 2099 mInputFrameDataSize &= 0x3fff;
michael@0 2100 }
michael@0 2101
michael@0 2102 LOG3(("Http2Session::WriteSegments[%p::%x] Frame Header Read "
michael@0 2103 "type %X data len %u flags %x id 0x%X",
michael@0 2104 this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
michael@0 2105 mInputFrameID));
michael@0 2106
michael@0 2107 // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION of
michael@0 2108 // a HEADERS frame with a matching ID (section 6.2)
michael@0 2109 if (mExpectedHeaderID &&
michael@0 2110 ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
michael@0 2111 (mExpectedHeaderID != mInputFrameID))) {
michael@0 2112 LOG3(("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
michael@0 2113 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
michael@0 2114 }
michael@0 2115
michael@0 2116 // if mExpectedPushPromiseID is non 0, it means this frame must be a
michael@0 2117 // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
michael@0 2118 if (mExpectedPushPromiseID &&
michael@0 2119 ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
michael@0 2120 (mExpectedPushPromiseID != mInputFrameID))) {
michael@0 2121 LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
michael@0 2122 mExpectedPushPromiseID));
michael@0 2123 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
michael@0 2124 }
michael@0 2125
michael@0 2126 if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
michael@0 2127 mInputFrameType != FRAME_TYPE_SETTINGS) {
michael@0 2128 LOG3(("First Frame Type Must Be Settings\n"));
michael@0 2129 RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
michael@0 2130 }
michael@0 2131
michael@0 2132 if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
michael@0 2133 EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + 8, 8,
michael@0 2134 mInputFrameBufferSize);
michael@0 2135 ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
michael@0 2136 } else if (mInputFrameFlags & (kFlag_PAD_LOW | kFlag_PAD_HIGH)) {
michael@0 2137 ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
michael@0 2138 } else {
michael@0 2139 rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
michael@0 2140 if (NS_FAILED(rv)) {
michael@0 2141 return rv;
michael@0 2142 }
michael@0 2143 }
michael@0 2144 }
michael@0 2145
michael@0 2146 if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
michael@0 2147 uint32_t numControlBytes = 0;
michael@0 2148 if (mInputFrameFlags & kFlag_PAD_LOW) {
michael@0 2149 ++numControlBytes;
michael@0 2150 }
michael@0 2151 if (mInputFrameFlags & kFlag_PAD_HIGH) {
michael@0 2152 ++numControlBytes;
michael@0 2153 }
michael@0 2154
michael@0 2155 MOZ_ASSERT(numControlBytes,
michael@0 2156 "Processing padding control with no control bytes!");
michael@0 2157 MOZ_ASSERT(mInputFrameBufferUsed < (8 + numControlBytes),
michael@0 2158 "Frame buffer used too large for state");
michael@0 2159
michael@0 2160 rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed,
michael@0 2161 (8 + numControlBytes) - mInputFrameBufferUsed,
michael@0 2162 countWritten);
michael@0 2163
michael@0 2164 if (NS_FAILED(rv)) {
michael@0 2165 LOG3(("Http2Session %p buffering data frame padding control read failure %x\n",
michael@0 2166 this, rv));
michael@0 2167 // maybe just blocked reading from network
michael@0 2168 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 2169 rv = NS_OK;
michael@0 2170 return rv;
michael@0 2171 }
michael@0 2172
michael@0 2173 LogIO(this, nullptr, "Reading Data Frame Padding Control",
michael@0 2174 mInputFrameBuffer + mInputFrameBufferUsed, *countWritten);
michael@0 2175
michael@0 2176 mInputFrameBufferUsed += *countWritten;
michael@0 2177
michael@0 2178 if (mInputFrameBufferUsed - 8 < numControlBytes) {
michael@0 2179 LOG3(("Http2Session::WriteSegments %p "
michael@0 2180 "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
michael@0 2181 this, mInputFrameBufferUsed - 8));
michael@0 2182 return rv;
michael@0 2183 }
michael@0 2184
michael@0 2185 mInputFrameDataRead += numControlBytes;
michael@0 2186
michael@0 2187 char *control = mInputFrameBuffer + 8;
michael@0 2188 if (mInputFrameFlags & kFlag_PAD_HIGH) {
michael@0 2189 mPaddingLength = static_cast<uint16_t>(*control) * 256;
michael@0 2190 ++control;
michael@0 2191 }
michael@0 2192 mPaddingLength += static_cast<uint8_t>(*control);
michael@0 2193
michael@0 2194 LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
michael@0 2195 mInputFrameID, mPaddingLength));
michael@0 2196
michael@0 2197 if (numControlBytes + mPaddingLength == mInputFrameDataSize) {
michael@0 2198 // This frame consists entirely of padding, we can just discard it
michael@0 2199 LOG3(("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
michael@0 2200 this, mInputFrameID));
michael@0 2201 rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
michael@0 2202 if (NS_FAILED(rv)) {
michael@0 2203 return rv;
michael@0 2204 }
michael@0 2205 } else {
michael@0 2206 LOG3(("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
michael@0 2207 this, mInputFrameID));
michael@0 2208 rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
michael@0 2209 if (NS_FAILED(rv)) {
michael@0 2210 return rv;
michael@0 2211 }
michael@0 2212 }
michael@0 2213 }
michael@0 2214
michael@0 2215 if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
michael@0 2216 nsresult streamCleanupCode;
michael@0 2217
michael@0 2218 // There is no bounds checking on the error code.. we provide special
michael@0 2219 // handling for a couple of cases and all others (including unknown) are
michael@0 2220 // equivalent to cancel.
michael@0 2221 if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
michael@0 2222 streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely
michael@0 2223 } else {
michael@0 2224 streamCleanupCode = NS_ERROR_NET_INTERRUPT;
michael@0 2225 }
michael@0 2226
michael@0 2227 if (mDownstreamRstReason == COMPRESSION_ERROR)
michael@0 2228 mShouldGoAway = true;
michael@0 2229
michael@0 2230 // mInputFrameDataStream is reset by ChangeDownstreamState
michael@0 2231 Http2Stream *stream = mInputFrameDataStream;
michael@0 2232 ResetDownstreamState();
michael@0 2233 LOG3(("Http2Session::WriteSegments cleanup stream on recv of rst "
michael@0 2234 "session=%p stream=%p 0x%X\n", this, stream,
michael@0 2235 stream ? stream->StreamID() : 0));
michael@0 2236 CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
michael@0 2237 return NS_OK;
michael@0 2238 }
michael@0 2239
michael@0 2240 if (mDownstreamState == PROCESSING_DATA_FRAME ||
michael@0 2241 mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
michael@0 2242
michael@0 2243 // The cleanup stream should only be set while stream->WriteSegments is
michael@0 2244 // on the stack and then cleaned up in this code block afterwards.
michael@0 2245 MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
michael@0 2246 mNeedsCleanup = nullptr; /* just in case */
michael@0 2247
michael@0 2248 mSegmentWriter = writer;
michael@0 2249 rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
michael@0 2250 mSegmentWriter = nullptr;
michael@0 2251
michael@0 2252 mLastDataReadEpoch = mLastReadEpoch;
michael@0 2253
michael@0 2254 if (SoftStreamError(rv)) {
michael@0 2255 // This will happen when the transaction figures out it is EOF, generally
michael@0 2256 // due to a content-length match being made. Return OK from this function
michael@0 2257 // otherwise the whole session would be torn down.
michael@0 2258 Http2Stream *stream = mInputFrameDataStream;
michael@0 2259
michael@0 2260 // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
michael@0 2261 // back to PROCESSING_DATA_FRAME where we came from
michael@0 2262 mDownstreamState = PROCESSING_DATA_FRAME;
michael@0 2263
michael@0 2264 if (mInputFrameDataRead == mInputFrameDataSize)
michael@0 2265 ResetDownstreamState();
michael@0 2266 LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
michael@0 2267 "needscleanup=%p. cleanup stream based on "
michael@0 2268 "stream->writeSegments returning code %x\n",
michael@0 2269 this, stream, stream ? stream->StreamID() : 0,
michael@0 2270 mNeedsCleanup, rv));
michael@0 2271 CleanupStream(stream, NS_OK, CANCEL_ERROR);
michael@0 2272 MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame");
michael@0 2273 mNeedsCleanup = nullptr; /* just in case */
michael@0 2274 return NS_OK;
michael@0 2275 }
michael@0 2276
michael@0 2277 if (mNeedsCleanup) {
michael@0 2278 LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
michael@0 2279 "cleanup stream based on mNeedsCleanup.\n",
michael@0 2280 this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
michael@0 2281 CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
michael@0 2282 mNeedsCleanup = nullptr;
michael@0 2283 }
michael@0 2284
michael@0 2285 if (NS_FAILED(rv)) {
michael@0 2286 LOG3(("Http2Session %p data frame read failure %x\n", this, rv));
michael@0 2287 // maybe just blocked reading from network
michael@0 2288 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 2289 rv = NS_OK;
michael@0 2290 }
michael@0 2291
michael@0 2292 return rv;
michael@0 2293 }
michael@0 2294
michael@0 2295 if (mDownstreamState == DISCARDING_DATA_FRAME ||
michael@0 2296 mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
michael@0 2297 char trash[4096];
michael@0 2298 uint32_t count = std::min(4096U, mInputFrameDataSize - mInputFrameDataRead);
michael@0 2299 LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of data",
michael@0 2300 this, count));
michael@0 2301
michael@0 2302 if (!count) {
michael@0 2303 ResetDownstreamState();
michael@0 2304 ResumeRecv();
michael@0 2305 return NS_BASE_STREAM_WOULD_BLOCK;
michael@0 2306 }
michael@0 2307
michael@0 2308 rv = NetworkRead(writer, trash, count, countWritten);
michael@0 2309
michael@0 2310 if (NS_FAILED(rv)) {
michael@0 2311 LOG3(("Http2Session %p discard frame read failure %x\n", this, rv));
michael@0 2312 // maybe just blocked reading from network
michael@0 2313 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 2314 rv = NS_OK;
michael@0 2315 return rv;
michael@0 2316 }
michael@0 2317
michael@0 2318 LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
michael@0 2319
michael@0 2320 mInputFrameDataRead += *countWritten;
michael@0 2321
michael@0 2322 if (mInputFrameDataRead == mInputFrameDataSize) {
michael@0 2323 Http2Stream *streamToCleanup = nullptr;
michael@0 2324 if (mInputFrameFinal) {
michael@0 2325 streamToCleanup = mInputFrameDataStream;
michael@0 2326 }
michael@0 2327
michael@0 2328 ResetDownstreamState();
michael@0 2329
michael@0 2330 if (streamToCleanup) {
michael@0 2331 CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
michael@0 2332 }
michael@0 2333 }
michael@0 2334 return rv;
michael@0 2335 }
michael@0 2336
michael@0 2337 if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
michael@0 2338 MOZ_ASSERT(false); // this cannot happen
michael@0 2339 return NS_ERROR_UNEXPECTED;
michael@0 2340 }
michael@0 2341
michael@0 2342 MOZ_ASSERT(mInputFrameBufferUsed == 8, "Frame Buffer Header Not Present");
michael@0 2343 MOZ_ASSERT(mInputFrameDataSize + 8 <= mInputFrameBufferSize,
michael@0 2344 "allocation for control frame insufficient");
michael@0 2345
michael@0 2346 rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead,
michael@0 2347 mInputFrameDataSize - mInputFrameDataRead, countWritten);
michael@0 2348
michael@0 2349 if (NS_FAILED(rv)) {
michael@0 2350 LOG3(("Http2Session %p buffering control frame read failure %x\n",
michael@0 2351 this, rv));
michael@0 2352 // maybe just blocked reading from network
michael@0 2353 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 2354 rv = NS_OK;
michael@0 2355 return rv;
michael@0 2356 }
michael@0 2357
michael@0 2358 LogIO(this, nullptr, "Reading Control Frame",
michael@0 2359 mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten);
michael@0 2360
michael@0 2361 mInputFrameDataRead += *countWritten;
michael@0 2362
michael@0 2363 if (mInputFrameDataRead != mInputFrameDataSize)
michael@0 2364 return NS_OK;
michael@0 2365
michael@0 2366 MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
michael@0 2367 if (mInputFrameType < FRAME_TYPE_LAST) {
michael@0 2368 rv = sControlFunctions[mInputFrameType](this);
michael@0 2369 } else {
michael@0 2370 // Section 4.1 requires this to be ignored; though protocol_error would
michael@0 2371 // be better
michael@0 2372 LOG3(("Http2Session %p unknow frame type %x ignored\n",
michael@0 2373 this, mInputFrameType));
michael@0 2374 ResetDownstreamState();
michael@0 2375 rv = NS_OK;
michael@0 2376 }
michael@0 2377
michael@0 2378 MOZ_ASSERT(NS_FAILED(rv) ||
michael@0 2379 mDownstreamState != BUFFERING_CONTROL_FRAME,
michael@0 2380 "Control Handler returned OK but did not change state");
michael@0 2381
michael@0 2382 if (mShouldGoAway && !mStreamTransactionHash.Count())
michael@0 2383 Close(NS_OK);
michael@0 2384 return rv;
michael@0 2385 }
michael@0 2386
michael@0 2387 void
michael@0 2388 Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes)
michael@0 2389 {
michael@0 2390 if (!stream) // this is ok - it means there was a data frame for a rst stream
michael@0 2391 return;
michael@0 2392
michael@0 2393 // If this data packet was not for a valid or live stream then there
michael@0 2394 // is no reason to mess with the flow control
michael@0 2395 if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
michael@0 2396 mInputFrameFinal) {
michael@0 2397 return;
michael@0 2398 }
michael@0 2399
michael@0 2400 stream->DecrementClientReceiveWindow(bytes);
michael@0 2401
michael@0 2402 // Don't necessarily ack every data packet. Only do it
michael@0 2403 // after a significant amount of data.
michael@0 2404 uint64_t unacked = stream->LocalUnAcked();
michael@0 2405 int64_t localWindow = stream->ClientReceiveWindow();
michael@0 2406
michael@0 2407 LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
michael@0 2408 "unacked=%llu localWindow=%lld\n",
michael@0 2409 this, stream->StreamID(), bytes, unacked, localWindow));
michael@0 2410
michael@0 2411 if (!unacked)
michael@0 2412 return;
michael@0 2413
michael@0 2414 if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
michael@0 2415 return;
michael@0 2416
michael@0 2417 if (!stream->HasSink()) {
michael@0 2418 LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n",
michael@0 2419 this, stream->StreamID()));
michael@0 2420 return;
michael@0 2421 }
michael@0 2422
michael@0 2423 // Generate window updates directly out of session instead of the stream
michael@0 2424 // in order to avoid queue delays in getting the 'ACK' out.
michael@0 2425 uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
michael@0 2426
michael@0 2427 LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
michael@0 2428 this, stream->StreamID(), toack));
michael@0 2429 stream->IncrementClientReceiveWindow(toack);
michael@0 2430
michael@0 2431 // room for this packet needs to be ensured before calling this function
michael@0 2432 char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
michael@0 2433 mOutputQueueUsed += 12;
michael@0 2434 MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
michael@0 2435
michael@0 2436 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
michael@0 2437 toack = PR_htonl(toack);
michael@0 2438 memcpy(packet + 8, &toack, 4);
michael@0 2439
michael@0 2440 LogIO(this, stream, "Stream Window Update", packet, 12);
michael@0 2441 // dont flush here, this write can commonly be coalesced with a
michael@0 2442 // session window update to immediately follow.
michael@0 2443 }
michael@0 2444
michael@0 2445 void
michael@0 2446 Http2Session::UpdateLocalSessionWindow(uint32_t bytes)
michael@0 2447 {
michael@0 2448 if (!bytes)
michael@0 2449 return;
michael@0 2450
michael@0 2451 mLocalSessionWindow -= bytes;
michael@0 2452
michael@0 2453 LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
michael@0 2454 "localWindow=%lld\n", this, bytes, mLocalSessionWindow));
michael@0 2455
michael@0 2456 // Don't necessarily ack every data packet. Only do it
michael@0 2457 // after a significant amount of data.
michael@0 2458 if ((mLocalSessionWindow > (ASpdySession::kInitialRwin - kMinimumToAck)) &&
michael@0 2459 (mLocalSessionWindow > kEmergencyWindowThreshold))
michael@0 2460 return;
michael@0 2461
michael@0 2462 // Only send max bits of window updates at a time.
michael@0 2463 uint64_t toack64 = ASpdySession::kInitialRwin - mLocalSessionWindow;
michael@0 2464 uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
michael@0 2465
michael@0 2466 LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n",
michael@0 2467 this, toack));
michael@0 2468 mLocalSessionWindow += toack;
michael@0 2469
michael@0 2470 // room for this packet needs to be ensured before calling this function
michael@0 2471 char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
michael@0 2472 mOutputQueueUsed += 12;
michael@0 2473 MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
michael@0 2474
michael@0 2475 CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
michael@0 2476 toack = PR_htonl(toack);
michael@0 2477 memcpy(packet + 8, &toack, 4);
michael@0 2478
michael@0 2479 LogIO(this, nullptr, "Session Window Update", packet, 12);
michael@0 2480 // dont flush here, this write can commonly be coalesced with others
michael@0 2481 }
michael@0 2482
michael@0 2483 void
michael@0 2484 Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes)
michael@0 2485 {
michael@0 2486 // make sure there is room for 2 window updates even though
michael@0 2487 // we may not generate any.
michael@0 2488 EnsureOutputBuffer(16 * 2);
michael@0 2489
michael@0 2490 UpdateLocalStreamWindow(stream, bytes);
michael@0 2491 UpdateLocalSessionWindow(bytes);
michael@0 2492 FlushOutputQueue();
michael@0 2493 }
michael@0 2494
michael@0 2495 void
michael@0 2496 Http2Session::Close(nsresult aReason)
michael@0 2497 {
michael@0 2498 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2499
michael@0 2500 if (mClosed)
michael@0 2501 return;
michael@0 2502
michael@0 2503 LOG3(("Http2Session::Close %p %X", this, aReason));
michael@0 2504
michael@0 2505 mClosed = true;
michael@0 2506
michael@0 2507 mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
michael@0 2508 mStreamIDHash.Clear();
michael@0 2509 mStreamTransactionHash.Clear();
michael@0 2510
michael@0 2511 uint32_t goAwayReason;
michael@0 2512 if (mGoAwayReason != NO_HTTP_ERROR) {
michael@0 2513 goAwayReason = mGoAwayReason;
michael@0 2514 } else if (NS_SUCCEEDED(aReason)) {
michael@0 2515 goAwayReason = NO_HTTP_ERROR;
michael@0 2516 } else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
michael@0 2517 goAwayReason = PROTOCOL_ERROR;
michael@0 2518 } else {
michael@0 2519 goAwayReason = INTERNAL_ERROR;
michael@0 2520 }
michael@0 2521 GenerateGoAway(goAwayReason);
michael@0 2522 mConnection = nullptr;
michael@0 2523 mSegmentReader = nullptr;
michael@0 2524 mSegmentWriter = nullptr;
michael@0 2525 }
michael@0 2526
michael@0 2527 void
michael@0 2528 Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction,
michael@0 2529 nsresult aResult)
michael@0 2530 {
michael@0 2531 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2532 LOG3(("Http2Session::CloseTransaction %p %p %x", this, aTransaction, aResult));
michael@0 2533
michael@0 2534 // Generally this arrives as a cancel event from the connection manager.
michael@0 2535
michael@0 2536 // need to find the stream and call CleanupStream() on it.
michael@0 2537 Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
michael@0 2538 if (!stream) {
michael@0 2539 LOG3(("Http2Session::CloseTransaction %p %p %x - not found.",
michael@0 2540 this, aTransaction, aResult));
michael@0 2541 return;
michael@0 2542 }
michael@0 2543 LOG3(("Http2Session::CloseTranscation probably a cancel. "
michael@0 2544 "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
michael@0 2545 this, aTransaction, aResult, stream->StreamID(), stream));
michael@0 2546 CleanupStream(stream, aResult, CANCEL_ERROR);
michael@0 2547 ResumeRecv();
michael@0 2548 }
michael@0 2549
michael@0 2550 //-----------------------------------------------------------------------------
michael@0 2551 // nsAHttpSegmentReader
michael@0 2552 //-----------------------------------------------------------------------------
michael@0 2553
michael@0 2554 nsresult
michael@0 2555 Http2Session::OnReadSegment(const char *buf,
michael@0 2556 uint32_t count, uint32_t *countRead)
michael@0 2557 {
michael@0 2558 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2559 nsresult rv;
michael@0 2560
michael@0 2561 // If we can release old queued data then we can try and write the new
michael@0 2562 // data directly to the network without using the output queue at all
michael@0 2563 if (mOutputQueueUsed)
michael@0 2564 FlushOutputQueue();
michael@0 2565
michael@0 2566 if (!mOutputQueueUsed && mSegmentReader) {
michael@0 2567 // try and write directly without output queue
michael@0 2568 rv = mSegmentReader->OnReadSegment(buf, count, countRead);
michael@0 2569
michael@0 2570 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
michael@0 2571 *countRead = 0;
michael@0 2572 } else if (NS_FAILED(rv)) {
michael@0 2573 return rv;
michael@0 2574 }
michael@0 2575
michael@0 2576 if (*countRead < count) {
michael@0 2577 uint32_t required = count - *countRead;
michael@0 2578 // assuming a commitment() happened, this ensurebuffer is a nop
michael@0 2579 // but just in case the queuesize is too small for the required data
michael@0 2580 // call ensurebuffer().
michael@0 2581 EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
michael@0 2582 memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
michael@0 2583 mOutputQueueUsed = required;
michael@0 2584 }
michael@0 2585
michael@0 2586 *countRead = count;
michael@0 2587 return NS_OK;
michael@0 2588 }
michael@0 2589
michael@0 2590 // At this point we are going to buffer the new data in the output
michael@0 2591 // queue if it fits. By coalescing multiple small submissions into one larger
michael@0 2592 // buffer we can get larger writes out to the network later on.
michael@0 2593
michael@0 2594 // This routine should not be allowed to fill up the output queue
michael@0 2595 // all on its own - at least kQueueReserved bytes are always left
michael@0 2596 // for other routines to use - but this is an all-or-nothing function,
michael@0 2597 // so if it will not all fit just return WOULD_BLOCK
michael@0 2598
michael@0 2599 if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
michael@0 2600 return NS_BASE_STREAM_WOULD_BLOCK;
michael@0 2601
michael@0 2602 memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
michael@0 2603 mOutputQueueUsed += count;
michael@0 2604 *countRead = count;
michael@0 2605
michael@0 2606 FlushOutputQueue();
michael@0 2607
michael@0 2608 return NS_OK;
michael@0 2609 }
michael@0 2610
michael@0 2611 nsresult
michael@0 2612 Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment)
michael@0 2613 {
michael@0 2614 if (mOutputQueueUsed)
michael@0 2615 FlushOutputQueue();
michael@0 2616
michael@0 2617 // would there be enough room to buffer this if needed?
michael@0 2618 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
michael@0 2619 return NS_OK;
michael@0 2620
michael@0 2621 // if we are using part of our buffers already, try again later unless
michael@0 2622 // forceCommitment is set.
michael@0 2623 if (mOutputQueueUsed && !forceCommitment)
michael@0 2624 return NS_BASE_STREAM_WOULD_BLOCK;
michael@0 2625
michael@0 2626 if (mOutputQueueUsed) {
michael@0 2627 // normally we avoid the memmove of RealignOutputQueue, but we'll try
michael@0 2628 // it if forceCommitment is set before growing the buffer.
michael@0 2629 RealignOutputQueue();
michael@0 2630
michael@0 2631 // is there enough room now?
michael@0 2632 if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
michael@0 2633 return NS_OK;
michael@0 2634 }
michael@0 2635
michael@0 2636 // resize the buffers as needed
michael@0 2637 EnsureOutputBuffer(count + kQueueReserved);
michael@0 2638
michael@0 2639 MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
michael@0 2640 "buffer not as large as expected");
michael@0 2641
michael@0 2642 return NS_OK;
michael@0 2643 }
michael@0 2644
michael@0 2645 //-----------------------------------------------------------------------------
michael@0 2646 // nsAHttpSegmentWriter
michael@0 2647 //-----------------------------------------------------------------------------
michael@0 2648
michael@0 2649 nsresult
michael@0 2650 Http2Session::OnWriteSegment(char *buf,
michael@0 2651 uint32_t count, uint32_t *countWritten)
michael@0 2652 {
michael@0 2653 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2654 nsresult rv;
michael@0 2655
michael@0 2656 if (!mSegmentWriter) {
michael@0 2657 // the only way this could happen would be if Close() were called on the
michael@0 2658 // stack with WriteSegments()
michael@0 2659 return NS_ERROR_FAILURE;
michael@0 2660 }
michael@0 2661
michael@0 2662 if (mDownstreamState == PROCESSING_DATA_FRAME) {
michael@0 2663
michael@0 2664 if (mInputFrameFinal &&
michael@0 2665 mInputFrameDataRead == mInputFrameDataSize) {
michael@0 2666 *countWritten = 0;
michael@0 2667 SetNeedsCleanup();
michael@0 2668 return NS_BASE_STREAM_CLOSED;
michael@0 2669 }
michael@0 2670
michael@0 2671 count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
michael@0 2672 rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
michael@0 2673 if (NS_FAILED(rv))
michael@0 2674 return rv;
michael@0 2675
michael@0 2676 LogIO(this, mInputFrameDataStream, "Reading Data Frame",
michael@0 2677 buf, *countWritten);
michael@0 2678
michael@0 2679 mInputFrameDataRead += *countWritten;
michael@0 2680 if (mPaddingLength && (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
michael@0 2681 // We are crossing from real HTTP data into the realm of padding. If
michael@0 2682 // we've actually crossed the line, we need to munge countWritten for the
michael@0 2683 // sake of goodness and sanity. No matter what, any future calls to
michael@0 2684 // WriteSegments need to just discard data until we reach the end of this
michael@0 2685 // frame.
michael@0 2686 ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
michael@0 2687 uint32_t paddingRead = mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
michael@0 2688 LOG3(("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
michael@0 2689 "crossed from HTTP data into padding (%d of %d) countWritten=%d",
michael@0 2690 this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
michael@0 2691 paddingRead, mPaddingLength, *countWritten));
michael@0 2692 *countWritten -= paddingRead;
michael@0 2693 LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
michael@0 2694 this, mInputFrameID, *countWritten));
michael@0 2695 }
michael@0 2696
michael@0 2697 mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
michael@0 2698 if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
michael@0 2699 ResetDownstreamState();
michael@0 2700
michael@0 2701 return rv;
michael@0 2702 }
michael@0 2703
michael@0 2704 if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
michael@0 2705
michael@0 2706 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
michael@0 2707 mInputFrameFinal) {
michael@0 2708 *countWritten = 0;
michael@0 2709 SetNeedsCleanup();
michael@0 2710 return NS_BASE_STREAM_CLOSED;
michael@0 2711 }
michael@0 2712
michael@0 2713 count = std::min(count,
michael@0 2714 mFlatHTTPResponseHeaders.Length() -
michael@0 2715 mFlatHTTPResponseHeadersOut);
michael@0 2716 memcpy(buf,
michael@0 2717 mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
michael@0 2718 count);
michael@0 2719 mFlatHTTPResponseHeadersOut += count;
michael@0 2720 *countWritten = count;
michael@0 2721
michael@0 2722 if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
michael@0 2723 if (!mInputFrameFinal) {
michael@0 2724 // If more frames are expected in this stream, then reset the state so they can be
michael@0 2725 // handled. Otherwise (e.g. a 0 length response with the fin on the incoming headers)
michael@0 2726 // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can
michael@0 2727 // cleanup the stream.
michael@0 2728 ResetDownstreamState();
michael@0 2729 }
michael@0 2730 }
michael@0 2731
michael@0 2732 return NS_OK;
michael@0 2733 }
michael@0 2734
michael@0 2735 return NS_ERROR_UNEXPECTED;
michael@0 2736 }
michael@0 2737
michael@0 2738 void
michael@0 2739 Http2Session::SetNeedsCleanup()
michael@0 2740 {
michael@0 2741 LOG3(("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
michael@0 2742 "stream %p 0x%X", this, mInputFrameDataStream,
michael@0 2743 mInputFrameDataStream->StreamID()));
michael@0 2744
michael@0 2745 // This will result in Close() being called
michael@0 2746 MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
michael@0 2747 mNeedsCleanup = mInputFrameDataStream;
michael@0 2748 ResetDownstreamState();
michael@0 2749 }
michael@0 2750
michael@0 2751 void
michael@0 2752 Http2Session::ConnectPushedStream(Http2Stream *stream)
michael@0 2753 {
michael@0 2754 mReadyForRead.Push(stream);
michael@0 2755 ForceRecv();
michael@0 2756 }
michael@0 2757
michael@0 2758 nsresult
michael@0 2759 Http2Session::BufferOutput(const char *buf,
michael@0 2760 uint32_t count,
michael@0 2761 uint32_t *countRead)
michael@0 2762 {
michael@0 2763 nsAHttpSegmentReader *old = mSegmentReader;
michael@0 2764 mSegmentReader = nullptr;
michael@0 2765 nsresult rv = OnReadSegment(buf, count, countRead);
michael@0 2766 mSegmentReader = old;
michael@0 2767 return rv;
michael@0 2768 }
michael@0 2769
michael@0 2770 nsresult
michael@0 2771 Http2Session::ConfirmTLSProfile()
michael@0 2772 {
michael@0 2773 if (mTLSProfileConfirmed)
michael@0 2774 return NS_OK;
michael@0 2775
michael@0 2776 LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
michael@0 2777 this, mConnection.get()));
michael@0 2778
michael@0 2779 if (!gHttpHandler->EnforceHttp2TlsProfile()) {
michael@0 2780 LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this));
michael@0 2781 mTLSProfileConfirmed = true;
michael@0 2782 return NS_OK;
michael@0 2783 }
michael@0 2784
michael@0 2785 if (!mConnection)
michael@0 2786 return NS_ERROR_FAILURE;
michael@0 2787
michael@0 2788 nsCOMPtr<nsISupports> securityInfo;
michael@0 2789 mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
michael@0 2790 nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
michael@0 2791 LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, ssl.get()));
michael@0 2792 if (!ssl)
michael@0 2793 return NS_ERROR_FAILURE;
michael@0 2794
michael@0 2795 int16_t version = ssl->GetSSLVersionUsed();
michael@0 2796 LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
michael@0 2797 if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
michael@0 2798 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", this));
michael@0 2799 RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
michael@0 2800 }
michael@0 2801
michael@0 2802 #if 0
michael@0 2803 uint16_t kea = ssl->GetKEAUsed();
michael@0 2804 if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
michael@0 2805 LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
michael@0 2806 this, kea));
michael@0 2807 RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
michael@0 2808 }
michael@0 2809 #endif
michael@0 2810
michael@0 2811 /* TODO: Enforce DHE >= 2048 || ECDHE >= 128 */
michael@0 2812
michael@0 2813 /* We are required to send SNI. We do that already, so no check is done
michael@0 2814 * here to make sure we did. */
michael@0 2815
michael@0 2816 /* We really should check to ensure TLS compression isn't enabled on
michael@0 2817 * this connection. However, we never enable TLS compression on our end,
michael@0 2818 * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
michael@0 2819 * for the possibility for an interface to ensure it never gets turned on. */
michael@0 2820
michael@0 2821 mTLSProfileConfirmed = true;
michael@0 2822 return NS_OK;
michael@0 2823 }
michael@0 2824
michael@0 2825
michael@0 2826 //-----------------------------------------------------------------------------
michael@0 2827 // Modified methods of nsAHttpConnection
michael@0 2828 //-----------------------------------------------------------------------------
michael@0 2829
michael@0 2830 void
michael@0 2831 Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller)
michael@0 2832 {
michael@0 2833 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2834 LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
michael@0 2835
michael@0 2836 // a trapped signal from the http transaction to the connection that
michael@0 2837 // it is no longer blocked on read.
michael@0 2838
michael@0 2839 Http2Stream *stream = mStreamTransactionHash.Get(caller);
michael@0 2840 if (!stream || !VerifyStream(stream)) {
michael@0 2841 LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
michael@0 2842 this, caller));
michael@0 2843 return;
michael@0 2844 }
michael@0 2845
michael@0 2846 LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n",
michael@0 2847 this, stream->StreamID()));
michael@0 2848
michael@0 2849 mReadyForWrite.Push(stream);
michael@0 2850 }
michael@0 2851
michael@0 2852 void
michael@0 2853 Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
michael@0 2854 {
michael@0 2855 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2856 LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
michael@0 2857 this, stream, stream->StreamID()));
michael@0 2858
michael@0 2859 mReadyForWrite.Push(stream);
michael@0 2860 SetWriteCallbacks();
michael@0 2861 }
michael@0 2862
michael@0 2863 bool
michael@0 2864 Http2Session::IsPersistent()
michael@0 2865 {
michael@0 2866 return true;
michael@0 2867 }
michael@0 2868
michael@0 2869 nsresult
michael@0 2870 Http2Session::TakeTransport(nsISocketTransport **,
michael@0 2871 nsIAsyncInputStream **, nsIAsyncOutputStream **)
michael@0 2872 {
michael@0 2873 MOZ_ASSERT(false, "TakeTransport of Http2Session");
michael@0 2874 return NS_ERROR_UNEXPECTED;
michael@0 2875 }
michael@0 2876
michael@0 2877 nsHttpConnection *
michael@0 2878 Http2Session::TakeHttpConnection()
michael@0 2879 {
michael@0 2880 MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
michael@0 2881 return nullptr;
michael@0 2882 }
michael@0 2883
michael@0 2884 uint32_t
michael@0 2885 Http2Session::CancelPipeline(nsresult reason)
michael@0 2886 {
michael@0 2887 // we don't pipeline inside http/2, so this isn't an issue
michael@0 2888 return 0;
michael@0 2889 }
michael@0 2890
michael@0 2891 nsAHttpTransaction::Classifier
michael@0 2892 Http2Session::Classification()
michael@0 2893 {
michael@0 2894 if (!mConnection)
michael@0 2895 return nsAHttpTransaction::CLASS_GENERAL;
michael@0 2896 return mConnection->Classification();
michael@0 2897 }
michael@0 2898
michael@0 2899 //-----------------------------------------------------------------------------
michael@0 2900 // unused methods of nsAHttpTransaction
michael@0 2901 // We can be sure of this because Http2Session is only constructed in
michael@0 2902 // nsHttpConnection and is never passed out of that object
michael@0 2903 //-----------------------------------------------------------------------------
michael@0 2904
michael@0 2905 void
michael@0 2906 Http2Session::SetConnection(nsAHttpConnection *)
michael@0 2907 {
michael@0 2908 // This is unexpected
michael@0 2909 MOZ_ASSERT(false, "Http2Session::SetConnection()");
michael@0 2910 }
michael@0 2911
michael@0 2912 void
michael@0 2913 Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **)
michael@0 2914 {
michael@0 2915 // This is unexpected
michael@0 2916 MOZ_ASSERT(false, "Http2Session::GetSecurityCallbacks()");
michael@0 2917 }
michael@0 2918
michael@0 2919 void
michael@0 2920 Http2Session::SetProxyConnectFailed()
michael@0 2921 {
michael@0 2922 MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
michael@0 2923 }
michael@0 2924
michael@0 2925 bool
michael@0 2926 Http2Session::IsDone()
michael@0 2927 {
michael@0 2928 return !mStreamTransactionHash.Count();
michael@0 2929 }
michael@0 2930
michael@0 2931 nsresult
michael@0 2932 Http2Session::Status()
michael@0 2933 {
michael@0 2934 MOZ_ASSERT(false, "Http2Session::Status()");
michael@0 2935 return NS_ERROR_UNEXPECTED;
michael@0 2936 }
michael@0 2937
michael@0 2938 uint32_t
michael@0 2939 Http2Session::Caps()
michael@0 2940 {
michael@0 2941 MOZ_ASSERT(false, "Http2Session::Caps()");
michael@0 2942 return 0;
michael@0 2943 }
michael@0 2944
michael@0 2945 void
michael@0 2946 Http2Session::SetDNSWasRefreshed()
michael@0 2947 {
michael@0 2948 MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
michael@0 2949 }
michael@0 2950
michael@0 2951 uint64_t
michael@0 2952 Http2Session::Available()
michael@0 2953 {
michael@0 2954 MOZ_ASSERT(false, "Http2Session::Available()");
michael@0 2955 return 0;
michael@0 2956 }
michael@0 2957
michael@0 2958 nsHttpRequestHead *
michael@0 2959 Http2Session::RequestHead()
michael@0 2960 {
michael@0 2961 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 2962 MOZ_ASSERT(false,
michael@0 2963 "Http2Session::RequestHead() "
michael@0 2964 "should not be called after http/2 is setup");
michael@0 2965 return NULL;
michael@0 2966 }
michael@0 2967
michael@0 2968 uint32_t
michael@0 2969 Http2Session::Http1xTransactionCount()
michael@0 2970 {
michael@0 2971 return 0;
michael@0 2972 }
michael@0 2973
michael@0 2974 // used as an enumerator by TakeSubTransactions()
michael@0 2975 static PLDHashOperator
michael@0 2976 TakeStream(nsAHttpTransaction *key,
michael@0 2977 nsAutoPtr<Http2Stream> &stream,
michael@0 2978 void *closure)
michael@0 2979 {
michael@0 2980 nsTArray<nsRefPtr<nsAHttpTransaction> > *list =
michael@0 2981 static_cast<nsTArray<nsRefPtr<nsAHttpTransaction> > *>(closure);
michael@0 2982
michael@0 2983 list->AppendElement(key);
michael@0 2984
michael@0 2985 // removing the stream from the hash will delete the stream
michael@0 2986 // and drop the transaction reference the hash held
michael@0 2987 return PL_DHASH_REMOVE;
michael@0 2988 }
michael@0 2989
michael@0 2990 nsresult
michael@0 2991 Http2Session::TakeSubTransactions(
michael@0 2992 nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
michael@0 2993 {
michael@0 2994 // Generally this cannot be done with http/2 as transactions are
michael@0 2995 // started right away.
michael@0 2996
michael@0 2997 LOG3(("Http2Session::TakeSubTransactions %p\n", this));
michael@0 2998
michael@0 2999 if (mConcurrentHighWater > 0)
michael@0 3000 return NS_ERROR_ALREADY_OPENED;
michael@0 3001
michael@0 3002 LOG3((" taking %d\n", mStreamTransactionHash.Count()));
michael@0 3003
michael@0 3004 mStreamTransactionHash.Enumerate(TakeStream, &outTransactions);
michael@0 3005 return NS_OK;
michael@0 3006 }
michael@0 3007
michael@0 3008 nsresult
michael@0 3009 Http2Session::AddTransaction(nsAHttpTransaction *)
michael@0 3010 {
michael@0 3011 // This API is meant for pipelining, Http2Session's should be
michael@0 3012 // extended with AddStream()
michael@0 3013
michael@0 3014 MOZ_ASSERT(false,
michael@0 3015 "Http2Session::AddTransaction() should not be called");
michael@0 3016
michael@0 3017 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 3018 }
michael@0 3019
michael@0 3020 uint32_t
michael@0 3021 Http2Session::PipelineDepth()
michael@0 3022 {
michael@0 3023 return IsDone() ? 0 : 1;
michael@0 3024 }
michael@0 3025
michael@0 3026 nsresult
michael@0 3027 Http2Session::SetPipelinePosition(int32_t position)
michael@0 3028 {
michael@0 3029 // This API is meant for pipelining, Http2Session's should be
michael@0 3030 // extended with AddStream()
michael@0 3031
michael@0 3032 MOZ_ASSERT(false,
michael@0 3033 "Http2Session::SetPipelinePosition() should not be called");
michael@0 3034
michael@0 3035 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 3036 }
michael@0 3037
michael@0 3038 int32_t
michael@0 3039 Http2Session::PipelinePosition()
michael@0 3040 {
michael@0 3041 return 0;
michael@0 3042 }
michael@0 3043
michael@0 3044 //-----------------------------------------------------------------------------
michael@0 3045 // Pass through methods of nsAHttpConnection
michael@0 3046 //-----------------------------------------------------------------------------
michael@0 3047
michael@0 3048 nsAHttpConnection *
michael@0 3049 Http2Session::Connection()
michael@0 3050 {
michael@0 3051 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 3052 return mConnection;
michael@0 3053 }
michael@0 3054
michael@0 3055 nsresult
michael@0 3056 Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction,
michael@0 3057 nsHttpRequestHead *requestHead,
michael@0 3058 nsHttpResponseHead *responseHead, bool *reset)
michael@0 3059 {
michael@0 3060 return mConnection->OnHeadersAvailable(transaction,
michael@0 3061 requestHead,
michael@0 3062 responseHead,
michael@0 3063 reset);
michael@0 3064 }
michael@0 3065
michael@0 3066 bool
michael@0 3067 Http2Session::IsReused()
michael@0 3068 {
michael@0 3069 return mConnection->IsReused();
michael@0 3070 }
michael@0 3071
michael@0 3072 nsresult
michael@0 3073 Http2Session::PushBack(const char *buf, uint32_t len)
michael@0 3074 {
michael@0 3075 return mConnection->PushBack(buf, len);
michael@0 3076 }
michael@0 3077
michael@0 3078 } // namespace mozilla::net
michael@0 3079 } // namespace mozilla

mercurial