Thu, 15 Jan 2015 21:03:48 +0100
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, ¬Used); |
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 |