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 "mozilla/Endian.h" |
michael@0 | 17 | #include "mozilla/Telemetry.h" |
michael@0 | 18 | #include "nsHttp.h" |
michael@0 | 19 | #include "nsHttpHandler.h" |
michael@0 | 20 | #include "nsILoadGroup.h" |
michael@0 | 21 | #include "prprf.h" |
michael@0 | 22 | #include "SpdyPush3.h" |
michael@0 | 23 | #include "SpdySession3.h" |
michael@0 | 24 | #include "SpdyStream3.h" |
michael@0 | 25 | #include "PSpdyPush.h" |
michael@0 | 26 | #include "SpdyZlibReporter.h" |
michael@0 | 27 | |
michael@0 | 28 | #include <algorithm> |
michael@0 | 29 | |
michael@0 | 30 | #ifdef DEBUG |
michael@0 | 31 | // defined by the socket transport service while active |
michael@0 | 32 | extern PRThread *gSocketThread; |
michael@0 | 33 | #endif |
michael@0 | 34 | |
michael@0 | 35 | namespace mozilla { |
michael@0 | 36 | namespace net { |
michael@0 | 37 | |
michael@0 | 38 | // SpdySession3 has multiple inheritance of things that implement |
michael@0 | 39 | // nsISupports, so this magic is taken from nsHttpPipeline that |
michael@0 | 40 | // implements some of the same abstract classes. |
michael@0 | 41 | NS_IMPL_ADDREF(SpdySession3) |
michael@0 | 42 | NS_IMPL_RELEASE(SpdySession3) |
michael@0 | 43 | NS_INTERFACE_MAP_BEGIN(SpdySession3) |
michael@0 | 44 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) |
michael@0 | 45 | NS_INTERFACE_MAP_END |
michael@0 | 46 | |
michael@0 | 47 | SpdySession3::SpdySession3(nsAHttpTransaction *aHttpTransaction, |
michael@0 | 48 | nsISocketTransport *aSocketTransport, |
michael@0 | 49 | int32_t firstPriority) |
michael@0 | 50 | : mSocketTransport(aSocketTransport), |
michael@0 | 51 | mSegmentReader(nullptr), |
michael@0 | 52 | mSegmentWriter(nullptr), |
michael@0 | 53 | mNextStreamID(1), |
michael@0 | 54 | mConcurrentHighWater(0), |
michael@0 | 55 | mDownstreamState(BUFFERING_FRAME_HEADER), |
michael@0 | 56 | mInputFrameBufferSize(kDefaultBufferSize), |
michael@0 | 57 | mInputFrameBufferUsed(0), |
michael@0 | 58 | mInputFrameDataLast(false), |
michael@0 | 59 | mInputFrameDataStream(nullptr), |
michael@0 | 60 | mNeedsCleanup(nullptr), |
michael@0 | 61 | mShouldGoAway(false), |
michael@0 | 62 | mClosed(false), |
michael@0 | 63 | mCleanShutdown(false), |
michael@0 | 64 | mDataPending(false), |
michael@0 | 65 | mGoAwayID(0), |
michael@0 | 66 | mMaxConcurrent(kDefaultMaxConcurrent), |
michael@0 | 67 | mConcurrent(0), |
michael@0 | 68 | mServerPushedResources(0), |
michael@0 | 69 | mServerInitialWindow(kDefaultServerRwin), |
michael@0 | 70 | mOutputQueueSize(kDefaultQueueSize), |
michael@0 | 71 | mOutputQueueUsed(0), |
michael@0 | 72 | mOutputQueueSent(0), |
michael@0 | 73 | mLastReadEpoch(PR_IntervalNow()), |
michael@0 | 74 | mPingSentEpoch(0), |
michael@0 | 75 | mNextPingID(1) |
michael@0 | 76 | { |
michael@0 | 77 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 78 | |
michael@0 | 79 | static uint64_t sSerial; |
michael@0 | 80 | mSerial = ++sSerial; |
michael@0 | 81 | |
michael@0 | 82 | LOG3(("SpdySession3::SpdySession3 %p transaction 1 = %p serial=0x%X\n", |
michael@0 | 83 | this, aHttpTransaction, mSerial)); |
michael@0 | 84 | |
michael@0 | 85 | mConnection = aHttpTransaction->Connection(); |
michael@0 | 86 | mInputFrameBuffer = new char[mInputFrameBufferSize]; |
michael@0 | 87 | mOutputQueueBuffer = new char[mOutputQueueSize]; |
michael@0 | 88 | zlibInit(); |
michael@0 | 89 | |
michael@0 | 90 | mPushAllowance = gHttpHandler->SpdyPushAllowance(); |
michael@0 | 91 | mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); |
michael@0 | 92 | GenerateSettings(); |
michael@0 | 93 | |
michael@0 | 94 | if (!aHttpTransaction->IsNullTransaction()) |
michael@0 | 95 | AddStream(aHttpTransaction, firstPriority); |
michael@0 | 96 | mLastDataReadEpoch = mLastReadEpoch; |
michael@0 | 97 | |
michael@0 | 98 | mPingThreshold = gHttpHandler->SpdyPingThreshold(); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | PLDHashOperator |
michael@0 | 102 | SpdySession3::ShutdownEnumerator(nsAHttpTransaction *key, |
michael@0 | 103 | nsAutoPtr<SpdyStream3> &stream, |
michael@0 | 104 | void *closure) |
michael@0 | 105 | { |
michael@0 | 106 | SpdySession3 *self = static_cast<SpdySession3 *>(closure); |
michael@0 | 107 | |
michael@0 | 108 | // On a clean server hangup the server sets the GoAwayID to be the ID of |
michael@0 | 109 | // the last transaction it processed. If the ID of stream in the |
michael@0 | 110 | // local stream is greater than that it can safely be restarted because the |
michael@0 | 111 | // server guarantees it was not partially processed. Streams that have not |
michael@0 | 112 | // registered an ID haven't actually been sent yet so they can always be |
michael@0 | 113 | // restarted. |
michael@0 | 114 | if (self->mCleanShutdown && |
michael@0 | 115 | (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID())) |
michael@0 | 116 | self->CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted |
michael@0 | 117 | else |
michael@0 | 118 | self->CloseStream(stream, NS_ERROR_ABORT); |
michael@0 | 119 | |
michael@0 | 120 | return PL_DHASH_NEXT; |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | PLDHashOperator |
michael@0 | 124 | SpdySession3::GoAwayEnumerator(nsAHttpTransaction *key, |
michael@0 | 125 | nsAutoPtr<SpdyStream3> &stream, |
michael@0 | 126 | void *closure) |
michael@0 | 127 | { |
michael@0 | 128 | SpdySession3 *self = static_cast<SpdySession3 *>(closure); |
michael@0 | 129 | |
michael@0 | 130 | // these streams were not processed by the server and can be restarted. |
michael@0 | 131 | // Do that after the enumerator completes to avoid the risk of |
michael@0 | 132 | // a restart event re-entrantly modifying this hash. Be sure not to restart |
michael@0 | 133 | // a pushed (even numbered) stream |
michael@0 | 134 | if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) || |
michael@0 | 135 | !stream->HasRegisteredID()) { |
michael@0 | 136 | self->mGoAwayStreamsToRestart.Push(stream); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | return PL_DHASH_NEXT; |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | SpdySession3::~SpdySession3() |
michael@0 | 143 | { |
michael@0 | 144 | LOG3(("SpdySession3::~SpdySession3 %p mDownstreamState=%X", |
michael@0 | 145 | this, mDownstreamState)); |
michael@0 | 146 | |
michael@0 | 147 | inflateEnd(&mDownstreamZlib); |
michael@0 | 148 | deflateEnd(&mUpstreamZlib); |
michael@0 | 149 | |
michael@0 | 150 | mStreamTransactionHash.Enumerate(ShutdownEnumerator, this); |
michael@0 | 151 | Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater); |
michael@0 | 152 | Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2); |
michael@0 | 153 | Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS, |
michael@0 | 154 | mServerPushedResources); |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | void |
michael@0 | 158 | SpdySession3::LogIO(SpdySession3 *self, SpdyStream3 *stream, const char *label, |
michael@0 | 159 | const char *data, uint32_t datalen) |
michael@0 | 160 | { |
michael@0 | 161 | if (!LOG4_ENABLED()) |
michael@0 | 162 | return; |
michael@0 | 163 | |
michael@0 | 164 | LOG4(("SpdySession3::LogIO %p stream=%p id=0x%X [%s]", |
michael@0 | 165 | self, stream, stream ? stream->StreamID() : 0, label)); |
michael@0 | 166 | |
michael@0 | 167 | // Max line is (16 * 3) + 10(prefix) + newline + null |
michael@0 | 168 | char linebuf[128]; |
michael@0 | 169 | uint32_t index; |
michael@0 | 170 | char *line = linebuf; |
michael@0 | 171 | |
michael@0 | 172 | linebuf[127] = 0; |
michael@0 | 173 | |
michael@0 | 174 | for (index = 0; index < datalen; ++index) { |
michael@0 | 175 | if (!(index % 16)) { |
michael@0 | 176 | if (index) { |
michael@0 | 177 | *line = 0; |
michael@0 | 178 | LOG4(("%s", linebuf)); |
michael@0 | 179 | } |
michael@0 | 180 | line = linebuf; |
michael@0 | 181 | PR_snprintf(line, 128, "%08X: ", index); |
michael@0 | 182 | line += 10; |
michael@0 | 183 | } |
michael@0 | 184 | PR_snprintf(line, 128 - (line - linebuf), "%02X ", |
michael@0 | 185 | ((unsigned char *)data)[index]); |
michael@0 | 186 | line += 3; |
michael@0 | 187 | } |
michael@0 | 188 | if (index) { |
michael@0 | 189 | *line = 0; |
michael@0 | 190 | LOG4(("%s", linebuf)); |
michael@0 | 191 | } |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | bool |
michael@0 | 195 | SpdySession3::RoomForMoreConcurrent() |
michael@0 | 196 | { |
michael@0 | 197 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 198 | |
michael@0 | 199 | return (mConcurrent < mMaxConcurrent); |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | bool |
michael@0 | 203 | SpdySession3::RoomForMoreStreams() |
michael@0 | 204 | { |
michael@0 | 205 | if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) |
michael@0 | 206 | return false; |
michael@0 | 207 | |
michael@0 | 208 | return !mShouldGoAway; |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | PRIntervalTime |
michael@0 | 212 | SpdySession3::IdleTime() |
michael@0 | 213 | { |
michael@0 | 214 | return PR_IntervalNow() - mLastDataReadEpoch; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | uint32_t |
michael@0 | 218 | SpdySession3::ReadTimeoutTick(PRIntervalTime now) |
michael@0 | 219 | { |
michael@0 | 220 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 221 | MOZ_ASSERT(mNextPingID & 1, "Ping Counter Not Odd"); |
michael@0 | 222 | |
michael@0 | 223 | LOG(("SpdySession3::ReadTimeoutTick %p delta since last read %ds\n", |
michael@0 | 224 | this, PR_IntervalToSeconds(now - mLastReadEpoch))); |
michael@0 | 225 | |
michael@0 | 226 | if (!mPingThreshold) |
michael@0 | 227 | return UINT32_MAX; |
michael@0 | 228 | |
michael@0 | 229 | if ((now - mLastReadEpoch) < mPingThreshold) { |
michael@0 | 230 | // recent activity means ping is not an issue |
michael@0 | 231 | if (mPingSentEpoch) |
michael@0 | 232 | mPingSentEpoch = 0; |
michael@0 | 233 | |
michael@0 | 234 | return PR_IntervalToSeconds(mPingThreshold) - |
michael@0 | 235 | PR_IntervalToSeconds(now - mLastReadEpoch); |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | if (mPingSentEpoch) { |
michael@0 | 239 | LOG(("SpdySession3::ReadTimeoutTick %p handle outstanding ping\n")); |
michael@0 | 240 | if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) { |
michael@0 | 241 | LOG(("SpdySession3::ReadTimeoutTick %p Ping Timer Exhaustion\n", |
michael@0 | 242 | this)); |
michael@0 | 243 | mPingSentEpoch = 0; |
michael@0 | 244 | Close(NS_ERROR_NET_TIMEOUT); |
michael@0 | 245 | return UINT32_MAX; |
michael@0 | 246 | } |
michael@0 | 247 | return 1; // run the tick aggressively while ping is outstanding |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | LOG(("SpdySession3::ReadTimeoutTick %p generating ping 0x%X\n", |
michael@0 | 251 | this, mNextPingID)); |
michael@0 | 252 | |
michael@0 | 253 | if (mNextPingID == 0xffffffff) { |
michael@0 | 254 | LOG(("SpdySession3::ReadTimeoutTick %p cannot form ping - ids exhausted\n", |
michael@0 | 255 | this)); |
michael@0 | 256 | return UINT32_MAX; |
michael@0 | 257 | } |
michael@0 | 258 | |
michael@0 | 259 | mPingSentEpoch = PR_IntervalNow(); |
michael@0 | 260 | if (!mPingSentEpoch) |
michael@0 | 261 | mPingSentEpoch = 1; // avoid the 0 sentinel value |
michael@0 | 262 | GeneratePing(mNextPingID); |
michael@0 | 263 | mNextPingID += 2; |
michael@0 | 264 | ResumeRecv(); // read the ping reply |
michael@0 | 265 | |
michael@0 | 266 | // Check for orphaned push streams. This looks expensive, but generally the |
michael@0 | 267 | // list is empty. |
michael@0 | 268 | SpdyPushedStream3 *deleteMe; |
michael@0 | 269 | TimeStamp timestampNow; |
michael@0 | 270 | do { |
michael@0 | 271 | deleteMe = nullptr; |
michael@0 | 272 | |
michael@0 | 273 | for (uint32_t index = mPushedStreams.Length(); |
michael@0 | 274 | index > 0 ; --index) { |
michael@0 | 275 | SpdyPushedStream3 *pushedStream = mPushedStreams[index - 1]; |
michael@0 | 276 | |
michael@0 | 277 | if (timestampNow.IsNull()) |
michael@0 | 278 | timestampNow = TimeStamp::Now(); // lazy initializer |
michael@0 | 279 | |
michael@0 | 280 | // if spdy finished, but not connected, and its been like that for too long.. |
michael@0 | 281 | // cleanup the stream.. |
michael@0 | 282 | if (pushedStream->IsOrphaned(timestampNow)) |
michael@0 | 283 | { |
michael@0 | 284 | LOG3(("SpdySession3 Timeout Pushed Stream %p 0x%X\n", |
michael@0 | 285 | this, pushedStream->StreamID())); |
michael@0 | 286 | deleteMe = pushedStream; |
michael@0 | 287 | break; // don't CleanupStream() while iterating this vector |
michael@0 | 288 | } |
michael@0 | 289 | } |
michael@0 | 290 | if (deleteMe) |
michael@0 | 291 | CleanupStream(deleteMe, NS_ERROR_ABORT, RST_CANCEL); |
michael@0 | 292 | |
michael@0 | 293 | } while (deleteMe); |
michael@0 | 294 | |
michael@0 | 295 | if (mNextPingID == 0xffffffff) { |
michael@0 | 296 | LOG(("SpdySession3::ReadTimeoutTick %p " |
michael@0 | 297 | "ping ids exhausted marking goaway\n", this)); |
michael@0 | 298 | mShouldGoAway = true; |
michael@0 | 299 | } |
michael@0 | 300 | return 1; // run the tick aggressively while ping is outstanding |
michael@0 | 301 | } |
michael@0 | 302 | |
michael@0 | 303 | uint32_t |
michael@0 | 304 | SpdySession3::RegisterStreamID(SpdyStream3 *stream, uint32_t aNewID) |
michael@0 | 305 | { |
michael@0 | 306 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 307 | |
michael@0 | 308 | MOZ_ASSERT(mNextStreamID < 0xfffffff0, |
michael@0 | 309 | "should have stopped admitting streams"); |
michael@0 | 310 | |
michael@0 | 311 | MOZ_ASSERT(!(aNewID & 1), |
michael@0 | 312 | "0 for autoassign pull, otherwise explicit even push assignment"); |
michael@0 | 313 | if (!aNewID) { |
michael@0 | 314 | // auto generate a new pull stream ID |
michael@0 | 315 | aNewID = mNextStreamID; |
michael@0 | 316 | MOZ_ASSERT(aNewID & 1, "pull ID must be odd."); |
michael@0 | 317 | mNextStreamID += 2; |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | LOG3(("SpdySession3::RegisterStreamID session=%p stream=%p id=0x%X " |
michael@0 | 321 | "concurrent=%d",this, stream, aNewID, mConcurrent)); |
michael@0 | 322 | |
michael@0 | 323 | // We've used up plenty of ID's on this session. Start |
michael@0 | 324 | // moving to a new one before there is a crunch involving |
michael@0 | 325 | // server push streams or concurrent non-registered submits |
michael@0 | 326 | if (aNewID >= kMaxStreamID) |
michael@0 | 327 | mShouldGoAway = true; |
michael@0 | 328 | |
michael@0 | 329 | // integrity check |
michael@0 | 330 | if (mStreamIDHash.Get(aNewID)) { |
michael@0 | 331 | LOG3((" New ID already present\n")); |
michael@0 | 332 | MOZ_ASSERT(false, "New ID already present in mStreamIDHash"); |
michael@0 | 333 | mShouldGoAway = true; |
michael@0 | 334 | return kDeadStreamID; |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | mStreamIDHash.Put(aNewID, stream); |
michael@0 | 338 | return aNewID; |
michael@0 | 339 | } |
michael@0 | 340 | |
michael@0 | 341 | bool |
michael@0 | 342 | SpdySession3::AddStream(nsAHttpTransaction *aHttpTransaction, |
michael@0 | 343 | int32_t aPriority) |
michael@0 | 344 | { |
michael@0 | 345 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 346 | |
michael@0 | 347 | // integrity check |
michael@0 | 348 | if (mStreamTransactionHash.Get(aHttpTransaction)) { |
michael@0 | 349 | LOG3((" New transaction already present\n")); |
michael@0 | 350 | MOZ_ASSERT(false, "AddStream duplicate transaction pointer"); |
michael@0 | 351 | return false; |
michael@0 | 352 | } |
michael@0 | 353 | |
michael@0 | 354 | aHttpTransaction->SetConnection(this); |
michael@0 | 355 | SpdyStream3 *stream = new SpdyStream3(aHttpTransaction, this, aPriority); |
michael@0 | 356 | |
michael@0 | 357 | LOG3(("SpdySession3::AddStream session=%p stream=%p NextID=0x%X (tentative)", |
michael@0 | 358 | this, stream, mNextStreamID)); |
michael@0 | 359 | |
michael@0 | 360 | mStreamTransactionHash.Put(aHttpTransaction, stream); |
michael@0 | 361 | |
michael@0 | 362 | if (RoomForMoreConcurrent()) { |
michael@0 | 363 | LOG3(("SpdySession3::AddStream %p stream %p activated immediately.", |
michael@0 | 364 | this, stream)); |
michael@0 | 365 | ActivateStream(stream); |
michael@0 | 366 | } |
michael@0 | 367 | else { |
michael@0 | 368 | LOG3(("SpdySession3::AddStream %p stream %p queued.", this, stream)); |
michael@0 | 369 | mQueuedStreams.Push(stream); |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) { |
michael@0 | 373 | LOG3(("SpdySession3::AddStream %p transaction %p forces keep-alive off.\n", |
michael@0 | 374 | this, aHttpTransaction)); |
michael@0 | 375 | DontReuse(); |
michael@0 | 376 | } |
michael@0 | 377 | return true; |
michael@0 | 378 | } |
michael@0 | 379 | |
michael@0 | 380 | void |
michael@0 | 381 | SpdySession3::ActivateStream(SpdyStream3 *stream) |
michael@0 | 382 | { |
michael@0 | 383 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 384 | MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1), |
michael@0 | 385 | "Do not activate pushed streams"); |
michael@0 | 386 | |
michael@0 | 387 | ++mConcurrent; |
michael@0 | 388 | if (mConcurrent > mConcurrentHighWater) |
michael@0 | 389 | mConcurrentHighWater = mConcurrent; |
michael@0 | 390 | LOG3(("SpdySession3::AddStream %p activating stream %p Currently %d " |
michael@0 | 391 | "streams in session, high water mark is %d", |
michael@0 | 392 | this, stream, mConcurrent, mConcurrentHighWater)); |
michael@0 | 393 | |
michael@0 | 394 | mReadyForWrite.Push(stream); |
michael@0 | 395 | SetWriteCallbacks(); |
michael@0 | 396 | |
michael@0 | 397 | // Kick off the SYN transmit without waiting for the poll loop |
michael@0 | 398 | // This won't work for stream id=1 because there is no segment reader |
michael@0 | 399 | // yet. |
michael@0 | 400 | if (mSegmentReader) { |
michael@0 | 401 | uint32_t countRead; |
michael@0 | 402 | ReadSegments(nullptr, kDefaultBufferSize, &countRead); |
michael@0 | 403 | } |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | void |
michael@0 | 407 | SpdySession3::ProcessPending() |
michael@0 | 408 | { |
michael@0 | 409 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 410 | |
michael@0 | 411 | while (RoomForMoreConcurrent()) { |
michael@0 | 412 | SpdyStream3 *stream = static_cast<SpdyStream3 *>(mQueuedStreams.PopFront()); |
michael@0 | 413 | if (!stream) |
michael@0 | 414 | return; |
michael@0 | 415 | LOG3(("SpdySession3::ProcessPending %p stream %p activated from queue.", |
michael@0 | 416 | this, stream)); |
michael@0 | 417 | ActivateStream(stream); |
michael@0 | 418 | } |
michael@0 | 419 | } |
michael@0 | 420 | |
michael@0 | 421 | nsresult |
michael@0 | 422 | SpdySession3::NetworkRead(nsAHttpSegmentWriter *writer, char *buf, |
michael@0 | 423 | uint32_t count, uint32_t *countWritten) |
michael@0 | 424 | { |
michael@0 | 425 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 426 | |
michael@0 | 427 | if (!count) { |
michael@0 | 428 | *countWritten = 0; |
michael@0 | 429 | return NS_OK; |
michael@0 | 430 | } |
michael@0 | 431 | |
michael@0 | 432 | nsresult rv = writer->OnWriteSegment(buf, count, countWritten); |
michael@0 | 433 | if (NS_SUCCEEDED(rv) && *countWritten > 0) |
michael@0 | 434 | mLastReadEpoch = PR_IntervalNow(); |
michael@0 | 435 | return rv; |
michael@0 | 436 | } |
michael@0 | 437 | |
michael@0 | 438 | void |
michael@0 | 439 | SpdySession3::SetWriteCallbacks() |
michael@0 | 440 | { |
michael@0 | 441 | if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed)) |
michael@0 | 442 | mConnection->ResumeSend(); |
michael@0 | 443 | } |
michael@0 | 444 | |
michael@0 | 445 | void |
michael@0 | 446 | SpdySession3::RealignOutputQueue() |
michael@0 | 447 | { |
michael@0 | 448 | mOutputQueueUsed -= mOutputQueueSent; |
michael@0 | 449 | memmove(mOutputQueueBuffer.get(), |
michael@0 | 450 | mOutputQueueBuffer.get() + mOutputQueueSent, |
michael@0 | 451 | mOutputQueueUsed); |
michael@0 | 452 | mOutputQueueSent = 0; |
michael@0 | 453 | } |
michael@0 | 454 | |
michael@0 | 455 | void |
michael@0 | 456 | SpdySession3::FlushOutputQueue() |
michael@0 | 457 | { |
michael@0 | 458 | if (!mSegmentReader || !mOutputQueueUsed) |
michael@0 | 459 | return; |
michael@0 | 460 | |
michael@0 | 461 | nsresult rv; |
michael@0 | 462 | uint32_t countRead; |
michael@0 | 463 | uint32_t avail = mOutputQueueUsed - mOutputQueueSent; |
michael@0 | 464 | |
michael@0 | 465 | rv = mSegmentReader-> |
michael@0 | 466 | OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, |
michael@0 | 467 | &countRead); |
michael@0 | 468 | LOG3(("SpdySession3::FlushOutputQueue %p sz=%d rv=%x actual=%d", |
michael@0 | 469 | this, avail, rv, countRead)); |
michael@0 | 470 | |
michael@0 | 471 | // Dont worry about errors on write, we will pick this up as a read error too |
michael@0 | 472 | if (NS_FAILED(rv)) |
michael@0 | 473 | return; |
michael@0 | 474 | |
michael@0 | 475 | if (countRead == avail) { |
michael@0 | 476 | mOutputQueueUsed = 0; |
michael@0 | 477 | mOutputQueueSent = 0; |
michael@0 | 478 | return; |
michael@0 | 479 | } |
michael@0 | 480 | |
michael@0 | 481 | mOutputQueueSent += countRead; |
michael@0 | 482 | |
michael@0 | 483 | // If the output queue is close to filling up and we have sent out a good |
michael@0 | 484 | // chunk of data from the beginning then realign it. |
michael@0 | 485 | |
michael@0 | 486 | if ((mOutputQueueSent >= kQueueMinimumCleanup) && |
michael@0 | 487 | ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) { |
michael@0 | 488 | RealignOutputQueue(); |
michael@0 | 489 | } |
michael@0 | 490 | } |
michael@0 | 491 | |
michael@0 | 492 | void |
michael@0 | 493 | SpdySession3::DontReuse() |
michael@0 | 494 | { |
michael@0 | 495 | mShouldGoAway = true; |
michael@0 | 496 | if (!mStreamTransactionHash.Count()) |
michael@0 | 497 | Close(NS_OK); |
michael@0 | 498 | } |
michael@0 | 499 | |
michael@0 | 500 | uint32_t |
michael@0 | 501 | SpdySession3::GetWriteQueueSize() |
michael@0 | 502 | { |
michael@0 | 503 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 504 | |
michael@0 | 505 | return mReadyForWrite.GetSize(); |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | void |
michael@0 | 509 | SpdySession3::ChangeDownstreamState(enum stateType newState) |
michael@0 | 510 | { |
michael@0 | 511 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 512 | |
michael@0 | 513 | LOG3(("SpdyStream3::ChangeDownstreamState() %p from %X to %X", |
michael@0 | 514 | this, mDownstreamState, newState)); |
michael@0 | 515 | mDownstreamState = newState; |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | void |
michael@0 | 519 | SpdySession3::ResetDownstreamState() |
michael@0 | 520 | { |
michael@0 | 521 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 522 | |
michael@0 | 523 | LOG3(("SpdyStream3::ResetDownstreamState() %p", this)); |
michael@0 | 524 | ChangeDownstreamState(BUFFERING_FRAME_HEADER); |
michael@0 | 525 | |
michael@0 | 526 | if (mInputFrameDataLast && mInputFrameDataStream) { |
michael@0 | 527 | mInputFrameDataLast = false; |
michael@0 | 528 | if (!mInputFrameDataStream->RecvdFin()) { |
michael@0 | 529 | LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID())); |
michael@0 | 530 | mInputFrameDataStream->SetRecvdFin(true); |
michael@0 | 531 | DecrementConcurrent(mInputFrameDataStream); |
michael@0 | 532 | } |
michael@0 | 533 | } |
michael@0 | 534 | mInputFrameBufferUsed = 0; |
michael@0 | 535 | mInputFrameDataStream = nullptr; |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | template<typename T> void |
michael@0 | 539 | SpdySession3::EnsureBuffer(nsAutoArrayPtr<T> &buf, |
michael@0 | 540 | uint32_t newSize, |
michael@0 | 541 | uint32_t preserve, |
michael@0 | 542 | uint32_t &objSize) |
michael@0 | 543 | { |
michael@0 | 544 | if (objSize >= newSize) |
michael@0 | 545 | return; |
michael@0 | 546 | |
michael@0 | 547 | // Leave a little slop on the new allocation - add 2KB to |
michael@0 | 548 | // what we need and then round the result up to a 4KB (page) |
michael@0 | 549 | // boundary. |
michael@0 | 550 | |
michael@0 | 551 | objSize = (newSize + 2048 + 4095) & ~4095; |
michael@0 | 552 | |
michael@0 | 553 | static_assert(sizeof(T) == 1, "sizeof(T) must be 1"); |
michael@0 | 554 | nsAutoArrayPtr<T> tmp(new T[objSize]); |
michael@0 | 555 | memcpy(tmp, buf, preserve); |
michael@0 | 556 | buf = tmp; |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | // Instantiate supported templates explicitly. |
michael@0 | 560 | template void |
michael@0 | 561 | SpdySession3::EnsureBuffer(nsAutoArrayPtr<char> &buf, |
michael@0 | 562 | uint32_t newSize, |
michael@0 | 563 | uint32_t preserve, |
michael@0 | 564 | uint32_t &objSize); |
michael@0 | 565 | |
michael@0 | 566 | template void |
michael@0 | 567 | SpdySession3::EnsureBuffer(nsAutoArrayPtr<uint8_t> &buf, |
michael@0 | 568 | uint32_t newSize, |
michael@0 | 569 | uint32_t preserve, |
michael@0 | 570 | uint32_t &objSize); |
michael@0 | 571 | |
michael@0 | 572 | void |
michael@0 | 573 | SpdySession3::DecrementConcurrent(SpdyStream3 *aStream) |
michael@0 | 574 | { |
michael@0 | 575 | uint32_t id = aStream->StreamID(); |
michael@0 | 576 | |
michael@0 | 577 | if (id && !(id & 0x1)) |
michael@0 | 578 | return; // pushed streams aren't counted in concurrent limit |
michael@0 | 579 | |
michael@0 | 580 | MOZ_ASSERT(mConcurrent); |
michael@0 | 581 | --mConcurrent; |
michael@0 | 582 | LOG3(("DecrementConcurrent %p id=0x%X concurrent=%d\n", |
michael@0 | 583 | this, id, mConcurrent)); |
michael@0 | 584 | ProcessPending(); |
michael@0 | 585 | } |
michael@0 | 586 | |
michael@0 | 587 | void |
michael@0 | 588 | SpdySession3::zlibInit() |
michael@0 | 589 | { |
michael@0 | 590 | mDownstreamZlib.zalloc = SpdyZlibReporter::Alloc; |
michael@0 | 591 | mDownstreamZlib.zfree = SpdyZlibReporter::Free; |
michael@0 | 592 | mDownstreamZlib.opaque = Z_NULL; |
michael@0 | 593 | |
michael@0 | 594 | inflateInit(&mDownstreamZlib); |
michael@0 | 595 | |
michael@0 | 596 | mUpstreamZlib.zalloc = SpdyZlibReporter::Alloc; |
michael@0 | 597 | mUpstreamZlib.zfree = SpdyZlibReporter::Free; |
michael@0 | 598 | mUpstreamZlib.opaque = Z_NULL; |
michael@0 | 599 | |
michael@0 | 600 | // mixing carte blanche compression with tls subjects us to traffic |
michael@0 | 601 | // analysis attacks |
michael@0 | 602 | deflateInit(&mUpstreamZlib, Z_NO_COMPRESSION); |
michael@0 | 603 | deflateSetDictionary(&mUpstreamZlib, |
michael@0 | 604 | SpdyStream3::kDictionary, |
michael@0 | 605 | sizeof(SpdyStream3::kDictionary)); |
michael@0 | 606 | } |
michael@0 | 607 | |
michael@0 | 608 | // Need to decompress some data in order to keep the compression |
michael@0 | 609 | // context correct, but we really don't care what the result is |
michael@0 | 610 | nsresult |
michael@0 | 611 | SpdySession3::UncompressAndDiscard(uint32_t offset, |
michael@0 | 612 | uint32_t blockLen) |
michael@0 | 613 | { |
michael@0 | 614 | char *blockStart = mInputFrameBuffer + offset; |
michael@0 | 615 | unsigned char trash[2048]; |
michael@0 | 616 | mDownstreamZlib.avail_in = blockLen; |
michael@0 | 617 | mDownstreamZlib.next_in = reinterpret_cast<unsigned char *>(blockStart); |
michael@0 | 618 | bool triedDictionary = false; |
michael@0 | 619 | |
michael@0 | 620 | do { |
michael@0 | 621 | mDownstreamZlib.next_out = trash; |
michael@0 | 622 | mDownstreamZlib.avail_out = sizeof(trash); |
michael@0 | 623 | int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH); |
michael@0 | 624 | |
michael@0 | 625 | if (zlib_rv == Z_NEED_DICT) { |
michael@0 | 626 | if (triedDictionary) { |
michael@0 | 627 | LOG3(("SpdySession3::UncompressAndDiscard %p Dictionary Error\n", this)); |
michael@0 | 628 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 629 | } |
michael@0 | 630 | |
michael@0 | 631 | triedDictionary = true; |
michael@0 | 632 | inflateSetDictionary(&mDownstreamZlib, SpdyStream3::kDictionary, |
michael@0 | 633 | sizeof(SpdyStream3::kDictionary)); |
michael@0 | 634 | } |
michael@0 | 635 | |
michael@0 | 636 | if (zlib_rv == Z_DATA_ERROR) |
michael@0 | 637 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 638 | |
michael@0 | 639 | if (zlib_rv == Z_MEM_ERROR) |
michael@0 | 640 | return NS_ERROR_FAILURE; |
michael@0 | 641 | } |
michael@0 | 642 | while (mDownstreamZlib.avail_in); |
michael@0 | 643 | return NS_OK; |
michael@0 | 644 | } |
michael@0 | 645 | |
michael@0 | 646 | void |
michael@0 | 647 | SpdySession3::GeneratePing(uint32_t aID) |
michael@0 | 648 | { |
michael@0 | 649 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 650 | LOG3(("SpdySession3::GeneratePing %p 0x%X\n", this, aID)); |
michael@0 | 651 | |
michael@0 | 652 | EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12, |
michael@0 | 653 | mOutputQueueUsed, mOutputQueueSize); |
michael@0 | 654 | char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
michael@0 | 655 | mOutputQueueUsed += 12; |
michael@0 | 656 | |
michael@0 | 657 | packet[0] = kFlag_Control; |
michael@0 | 658 | packet[1] = kVersion; |
michael@0 | 659 | packet[2] = 0; |
michael@0 | 660 | packet[3] = CONTROL_TYPE_PING; |
michael@0 | 661 | packet[4] = 0; /* flags */ |
michael@0 | 662 | packet[5] = 0; |
michael@0 | 663 | packet[6] = 0; |
michael@0 | 664 | packet[7] = 4; /* length */ |
michael@0 | 665 | |
michael@0 | 666 | NetworkEndian::writeUint32(packet + 8, aID); |
michael@0 | 667 | |
michael@0 | 668 | LogIO(this, nullptr, "Generate Ping", packet, 12); |
michael@0 | 669 | FlushOutputQueue(); |
michael@0 | 670 | } |
michael@0 | 671 | |
michael@0 | 672 | void |
michael@0 | 673 | SpdySession3::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) |
michael@0 | 674 | { |
michael@0 | 675 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 676 | LOG3(("SpdySession3::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); |
michael@0 | 677 | |
michael@0 | 678 | EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16, |
michael@0 | 679 | mOutputQueueUsed, mOutputQueueSize); |
michael@0 | 680 | char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
michael@0 | 681 | mOutputQueueUsed += 16; |
michael@0 | 682 | |
michael@0 | 683 | packet[0] = kFlag_Control; |
michael@0 | 684 | packet[1] = kVersion; |
michael@0 | 685 | packet[2] = 0; |
michael@0 | 686 | packet[3] = CONTROL_TYPE_RST_STREAM; |
michael@0 | 687 | packet[4] = 0; /* flags */ |
michael@0 | 688 | packet[5] = 0; |
michael@0 | 689 | packet[6] = 0; |
michael@0 | 690 | packet[7] = 8; /* length */ |
michael@0 | 691 | |
michael@0 | 692 | NetworkEndian::writeUint32(packet + 8, aID); |
michael@0 | 693 | NetworkEndian::writeUint32(packet + 12, aStatusCode); |
michael@0 | 694 | |
michael@0 | 695 | LogIO(this, nullptr, "Generate Reset", packet, 16); |
michael@0 | 696 | FlushOutputQueue(); |
michael@0 | 697 | } |
michael@0 | 698 | |
michael@0 | 699 | void |
michael@0 | 700 | SpdySession3::GenerateGoAway(uint32_t aStatusCode) |
michael@0 | 701 | { |
michael@0 | 702 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 703 | LOG3(("SpdySession3::GenerateGoAway %p code=%X\n", this, aStatusCode)); |
michael@0 | 704 | |
michael@0 | 705 | EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16, |
michael@0 | 706 | mOutputQueueUsed, mOutputQueueSize); |
michael@0 | 707 | char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
michael@0 | 708 | mOutputQueueUsed += 16; |
michael@0 | 709 | |
michael@0 | 710 | memset(packet, 0, 16); |
michael@0 | 711 | packet[0] = kFlag_Control; |
michael@0 | 712 | packet[1] = kVersion; |
michael@0 | 713 | packet[3] = CONTROL_TYPE_GOAWAY; |
michael@0 | 714 | packet[7] = 8; /* data length */ |
michael@0 | 715 | |
michael@0 | 716 | // last-good-stream-id are bytes 8-11, when we accept server push this will |
michael@0 | 717 | // need to be set non zero |
michael@0 | 718 | |
michael@0 | 719 | // bytes 12-15 are the status code. |
michael@0 | 720 | NetworkEndian::writeUint32(packet + 12, aStatusCode); |
michael@0 | 721 | |
michael@0 | 722 | LogIO(this, nullptr, "Generate GoAway", packet, 16); |
michael@0 | 723 | FlushOutputQueue(); |
michael@0 | 724 | } |
michael@0 | 725 | |
michael@0 | 726 | void |
michael@0 | 727 | SpdySession3::GenerateSettings() |
michael@0 | 728 | { |
michael@0 | 729 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 730 | LOG3(("SpdySession3::GenerateSettings %p\n", this)); |
michael@0 | 731 | |
michael@0 | 732 | static const uint32_t maxDataLen = 4 + 3 * 8; // sized for 3 settings |
michael@0 | 733 | EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 8 + maxDataLen, |
michael@0 | 734 | mOutputQueueUsed, mOutputQueueSize); |
michael@0 | 735 | char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
michael@0 | 736 | |
michael@0 | 737 | memset(packet, 0, 8 + maxDataLen); |
michael@0 | 738 | packet[0] = kFlag_Control; |
michael@0 | 739 | packet[1] = kVersion; |
michael@0 | 740 | packet[3] = CONTROL_TYPE_SETTINGS; |
michael@0 | 741 | |
michael@0 | 742 | uint8_t numberOfEntries = 0; |
michael@0 | 743 | |
michael@0 | 744 | // entries need to be listed in order by ID |
michael@0 | 745 | // 1st entry is bytes 12 to 19 |
michael@0 | 746 | // 2nd entry is bytes 20 to 27 |
michael@0 | 747 | // 3rd entry is bytes 28 to 35 |
michael@0 | 748 | |
michael@0 | 749 | if (!gHttpHandler->AllowPush()) { |
michael@0 | 750 | // announcing that we accept 0 incoming streams is done to |
michael@0 | 751 | // disable server push |
michael@0 | 752 | packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT; |
michael@0 | 753 | // The value portion of the setting pair is already initialized to 0 |
michael@0 | 754 | numberOfEntries++; |
michael@0 | 755 | } |
michael@0 | 756 | |
michael@0 | 757 | nsRefPtr<nsHttpConnectionInfo> ci; |
michael@0 | 758 | uint32_t cwnd = 0; |
michael@0 | 759 | GetConnectionInfo(getter_AddRefs(ci)); |
michael@0 | 760 | if (ci) |
michael@0 | 761 | cwnd = gHttpHandler->ConnMgr()->GetSpdyCWNDSetting(ci); |
michael@0 | 762 | if (cwnd) { |
michael@0 | 763 | packet[12 + 8 * numberOfEntries] = PERSISTED_VALUE; |
michael@0 | 764 | packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_CWND; |
michael@0 | 765 | LOG(("SpdySession3::GenerateSettings %p sending CWND %u\n", this, cwnd)); |
michael@0 | 766 | NetworkEndian::writeUint32(packet + 16 + 8 * numberOfEntries, cwnd); |
michael@0 | 767 | numberOfEntries++; |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | // Advertise the Push RWIN and on each client SYN_STREAM pipeline |
michael@0 | 771 | // a window update with it in order to use larger initial windows with pulled |
michael@0 | 772 | // streams. |
michael@0 | 773 | packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW; |
michael@0 | 774 | NetworkEndian::writeUint32(packet + 16 + 8 * numberOfEntries, mPushAllowance); |
michael@0 | 775 | numberOfEntries++; |
michael@0 | 776 | |
michael@0 | 777 | uint32_t dataLen = 4 + 8 * numberOfEntries; |
michael@0 | 778 | mOutputQueueUsed += 8 + dataLen; |
michael@0 | 779 | packet[7] = dataLen; |
michael@0 | 780 | packet[11] = numberOfEntries; |
michael@0 | 781 | |
michael@0 | 782 | LogIO(this, nullptr, "Generate Settings", packet, 8 + dataLen); |
michael@0 | 783 | FlushOutputQueue(); |
michael@0 | 784 | } |
michael@0 | 785 | |
michael@0 | 786 | // perform a bunch of integrity checks on the stream. |
michael@0 | 787 | // returns true if passed, false (plus LOG and ABORT) if failed. |
michael@0 | 788 | bool |
michael@0 | 789 | SpdySession3::VerifyStream(SpdyStream3 *aStream, uint32_t aOptionalID = 0) |
michael@0 | 790 | { |
michael@0 | 791 | // This is annoying, but at least it is O(1) |
michael@0 | 792 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 793 | |
michael@0 | 794 | #ifndef DEBUG |
michael@0 | 795 | // Only do the real verification in debug builds |
michael@0 | 796 | return true; |
michael@0 | 797 | #endif |
michael@0 | 798 | |
michael@0 | 799 | if (!aStream) |
michael@0 | 800 | return true; |
michael@0 | 801 | |
michael@0 | 802 | uint32_t test = 0; |
michael@0 | 803 | |
michael@0 | 804 | do { |
michael@0 | 805 | if (aStream->StreamID() == kDeadStreamID) |
michael@0 | 806 | break; |
michael@0 | 807 | |
michael@0 | 808 | nsAHttpTransaction *trans = aStream->Transaction(); |
michael@0 | 809 | |
michael@0 | 810 | test++; |
michael@0 | 811 | if (!trans) |
michael@0 | 812 | break; |
michael@0 | 813 | |
michael@0 | 814 | test++; |
michael@0 | 815 | if (mStreamTransactionHash.Get(trans) != aStream) |
michael@0 | 816 | break; |
michael@0 | 817 | |
michael@0 | 818 | if (aStream->StreamID()) { |
michael@0 | 819 | SpdyStream3 *idStream = mStreamIDHash.Get(aStream->StreamID()); |
michael@0 | 820 | |
michael@0 | 821 | test++; |
michael@0 | 822 | if (idStream != aStream) |
michael@0 | 823 | break; |
michael@0 | 824 | |
michael@0 | 825 | if (aOptionalID) { |
michael@0 | 826 | test++; |
michael@0 | 827 | if (idStream->StreamID() != aOptionalID) |
michael@0 | 828 | break; |
michael@0 | 829 | } |
michael@0 | 830 | } |
michael@0 | 831 | |
michael@0 | 832 | // tests passed |
michael@0 | 833 | return true; |
michael@0 | 834 | } while (0); |
michael@0 | 835 | |
michael@0 | 836 | LOG(("SpdySession3 %p VerifyStream Failure %p stream->id=0x%X " |
michael@0 | 837 | "optionalID=0x%X trans=%p test=%d\n", |
michael@0 | 838 | this, aStream, aStream->StreamID(), |
michael@0 | 839 | aOptionalID, aStream->Transaction(), test)); |
michael@0 | 840 | |
michael@0 | 841 | MOZ_ASSERT(false, "VerifyStream"); |
michael@0 | 842 | return false; |
michael@0 | 843 | } |
michael@0 | 844 | |
michael@0 | 845 | void |
michael@0 | 846 | SpdySession3::CleanupStream(SpdyStream3 *aStream, nsresult aResult, |
michael@0 | 847 | rstReason aResetCode) |
michael@0 | 848 | { |
michael@0 | 849 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 850 | LOG3(("SpdySession3::CleanupStream %p %p 0x%X %X\n", |
michael@0 | 851 | this, aStream, aStream ? aStream->StreamID() : 0, aResult)); |
michael@0 | 852 | if (!aStream) { |
michael@0 | 853 | return; |
michael@0 | 854 | } |
michael@0 | 855 | |
michael@0 | 856 | SpdyPushedStream3 *pushSource = nullptr; |
michael@0 | 857 | |
michael@0 | 858 | if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) { |
michael@0 | 859 | LOG(("SpdySession3::CleanupStream 0x%X deferred\n", aStream->StreamID())); |
michael@0 | 860 | return; |
michael@0 | 861 | } |
michael@0 | 862 | |
michael@0 | 863 | if (!VerifyStream(aStream)) { |
michael@0 | 864 | LOG(("SpdySession3::CleanupStream failed to verify stream\n")); |
michael@0 | 865 | return; |
michael@0 | 866 | } |
michael@0 | 867 | |
michael@0 | 868 | pushSource = aStream->PushSource(); |
michael@0 | 869 | |
michael@0 | 870 | if (!aStream->RecvdFin() && aStream->StreamID()) { |
michael@0 | 871 | LOG3(("Stream had not processed recv FIN, sending RST code %X\n", |
michael@0 | 872 | aResetCode)); |
michael@0 | 873 | GenerateRstStream(aResetCode, aStream->StreamID()); |
michael@0 | 874 | DecrementConcurrent(aStream); |
michael@0 | 875 | } |
michael@0 | 876 | |
michael@0 | 877 | CloseStream(aStream, aResult); |
michael@0 | 878 | |
michael@0 | 879 | // Remove the stream from the ID hash table and, if an even id, the pushed |
michael@0 | 880 | // table too. |
michael@0 | 881 | uint32_t id = aStream->StreamID(); |
michael@0 | 882 | if (id > 0) { |
michael@0 | 883 | mStreamIDHash.Remove(id); |
michael@0 | 884 | if (!(id & 1)) |
michael@0 | 885 | mPushedStreams.RemoveElement(aStream); |
michael@0 | 886 | } |
michael@0 | 887 | |
michael@0 | 888 | RemoveStreamFromQueues(aStream); |
michael@0 | 889 | |
michael@0 | 890 | // removing from the stream transaction hash will |
michael@0 | 891 | // delete the SpdyStream3 and drop the reference to |
michael@0 | 892 | // its transaction |
michael@0 | 893 | mStreamTransactionHash.Remove(aStream->Transaction()); |
michael@0 | 894 | |
michael@0 | 895 | if (mShouldGoAway && !mStreamTransactionHash.Count()) |
michael@0 | 896 | Close(NS_OK); |
michael@0 | 897 | |
michael@0 | 898 | if (pushSource) { |
michael@0 | 899 | pushSource->SetDeferCleanupOnSuccess(false); |
michael@0 | 900 | CleanupStream(pushSource, aResult, aResetCode); |
michael@0 | 901 | } |
michael@0 | 902 | } |
michael@0 | 903 | |
michael@0 | 904 | static void RemoveStreamFromQueue(SpdyStream3 *aStream, nsDeque &queue) |
michael@0 | 905 | { |
michael@0 | 906 | uint32_t size = queue.GetSize(); |
michael@0 | 907 | for (uint32_t count = 0; count < size; ++count) { |
michael@0 | 908 | SpdyStream3 *stream = static_cast<SpdyStream3 *>(queue.PopFront()); |
michael@0 | 909 | if (stream != aStream) |
michael@0 | 910 | queue.Push(stream); |
michael@0 | 911 | } |
michael@0 | 912 | } |
michael@0 | 913 | |
michael@0 | 914 | void |
michael@0 | 915 | SpdySession3::RemoveStreamFromQueues(SpdyStream3 *aStream) |
michael@0 | 916 | { |
michael@0 | 917 | RemoveStreamFromQueue(aStream, mReadyForWrite); |
michael@0 | 918 | RemoveStreamFromQueue(aStream, mQueuedStreams); |
michael@0 | 919 | RemoveStreamFromQueue(aStream, mReadyForRead); |
michael@0 | 920 | } |
michael@0 | 921 | |
michael@0 | 922 | void |
michael@0 | 923 | SpdySession3::CloseStream(SpdyStream3 *aStream, nsresult aResult) |
michael@0 | 924 | { |
michael@0 | 925 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 926 | LOG3(("SpdySession3::CloseStream %p %p 0x%x %X\n", |
michael@0 | 927 | this, aStream, aStream->StreamID(), aResult)); |
michael@0 | 928 | |
michael@0 | 929 | // Check if partial frame reader |
michael@0 | 930 | if (aStream == mInputFrameDataStream) { |
michael@0 | 931 | LOG3(("Stream had active partial read frame on close")); |
michael@0 | 932 | ChangeDownstreamState(DISCARDING_DATA_FRAME); |
michael@0 | 933 | mInputFrameDataStream = nullptr; |
michael@0 | 934 | } |
michael@0 | 935 | |
michael@0 | 936 | RemoveStreamFromQueues(aStream); |
michael@0 | 937 | |
michael@0 | 938 | // Send the stream the close() indication |
michael@0 | 939 | aStream->Close(aResult); |
michael@0 | 940 | } |
michael@0 | 941 | |
michael@0 | 942 | nsresult |
michael@0 | 943 | SpdySession3::HandleSynStream(SpdySession3 *self) |
michael@0 | 944 | { |
michael@0 | 945 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM); |
michael@0 | 946 | |
michael@0 | 947 | if (self->mInputFrameDataSize < 18) { |
michael@0 | 948 | LOG3(("SpdySession3::HandleSynStream %p SYN_STREAM too short data=%d", |
michael@0 | 949 | self, self->mInputFrameDataSize)); |
michael@0 | 950 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 951 | } |
michael@0 | 952 | |
michael@0 | 953 | uint32_t streamID = |
michael@0 | 954 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 955 | uint32_t associatedID = |
michael@0 | 956 | NetworkEndian::readUint32(self->mInputFrameBuffer + 3 * sizeof(uint32_t)); |
michael@0 | 957 | uint8_t flags = reinterpret_cast<uint8_t *>(self->mInputFrameBuffer.get())[4]; |
michael@0 | 958 | |
michael@0 | 959 | LOG3(("SpdySession3::HandleSynStream %p recv SYN_STREAM (push) " |
michael@0 | 960 | "for ID 0x%X associated with 0x%X.\n", |
michael@0 | 961 | self, streamID, associatedID)); |
michael@0 | 962 | |
michael@0 | 963 | if (streamID & 0x01) { // test for odd stream ID |
michael@0 | 964 | LOG3(("SpdySession3::HandleSynStream %p recvd SYN_STREAM id must be even.", |
michael@0 | 965 | self)); |
michael@0 | 966 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 967 | } |
michael@0 | 968 | |
michael@0 | 969 | // confirm associated-to |
michael@0 | 970 | nsresult rv = self->SetInputFrameDataStream(associatedID); |
michael@0 | 971 | if (NS_FAILED(rv)) |
michael@0 | 972 | return rv; |
michael@0 | 973 | SpdyStream3 *associatedStream = self->mInputFrameDataStream; |
michael@0 | 974 | |
michael@0 | 975 | ++(self->mServerPushedResources); |
michael@0 | 976 | |
michael@0 | 977 | // Anytime we start using the high bit of stream ID (either client or server) |
michael@0 | 978 | // begin to migrate to a new session. |
michael@0 | 979 | if (streamID >= kMaxStreamID) |
michael@0 | 980 | self->mShouldGoAway = true; |
michael@0 | 981 | |
michael@0 | 982 | bool resetStream = true; |
michael@0 | 983 | SpdyPushCache *cache = nullptr; |
michael@0 | 984 | |
michael@0 | 985 | if (!(flags & kFlag_Data_UNI)) { |
michael@0 | 986 | // pushed streams require UNIDIRECTIONAL flag |
michael@0 | 987 | LOG3(("SpdySession3::HandleSynStream %p ID %0x%X associated ID 0x%X failed.\n", |
michael@0 | 988 | self, streamID, associatedID)); |
michael@0 | 989 | self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); |
michael@0 | 990 | |
michael@0 | 991 | } else if (!associatedID) { |
michael@0 | 992 | // associated stream 0 will never find a match, but the spec requires a |
michael@0 | 993 | // PROTOCOL_ERROR in this specific case |
michael@0 | 994 | LOG3(("SpdySession3::HandleSynStream %p associated ID of 0 failed.\n", self)); |
michael@0 | 995 | self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); |
michael@0 | 996 | |
michael@0 | 997 | } else if (!gHttpHandler->AllowPush()) { |
michael@0 | 998 | // MAX_CONCURRENT_STREAMS of 0 in settings should have disabled push, |
michael@0 | 999 | // but some servers are buggy about that.. or the config could have |
michael@0 | 1000 | // been updated after the settings frame was sent. In both cases just |
michael@0 | 1001 | // reject the pushed stream as refused |
michael@0 | 1002 | LOG3(("SpdySession3::HandleSynStream Push Recevied when Disabled\n")); |
michael@0 | 1003 | self->GenerateRstStream(RST_REFUSED_STREAM, streamID); |
michael@0 | 1004 | |
michael@0 | 1005 | } else if (!associatedStream) { |
michael@0 | 1006 | LOG3(("SpdySession3::HandleSynStream %p lookup associated ID failed.\n", self)); |
michael@0 | 1007 | self->GenerateRstStream(RST_INVALID_STREAM, streamID); |
michael@0 | 1008 | |
michael@0 | 1009 | } else { |
michael@0 | 1010 | nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo(); |
michael@0 | 1011 | if (loadGroupCI) { |
michael@0 | 1012 | loadGroupCI->GetSpdyPushCache(&cache); |
michael@0 | 1013 | if (!cache) { |
michael@0 | 1014 | cache = new SpdyPushCache(); |
michael@0 | 1015 | if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache(cache))) { |
michael@0 | 1016 | delete cache; |
michael@0 | 1017 | cache = nullptr; |
michael@0 | 1018 | } |
michael@0 | 1019 | } |
michael@0 | 1020 | } |
michael@0 | 1021 | if (!cache) { |
michael@0 | 1022 | // this is unexpected, but we can handle it just be refusing the push |
michael@0 | 1023 | LOG3(("SpdySession3::HandleSynStream Push Recevied without loadgroup cache\n")); |
michael@0 | 1024 | self->GenerateRstStream(RST_REFUSED_STREAM, streamID); |
michael@0 | 1025 | } |
michael@0 | 1026 | else { |
michael@0 | 1027 | resetStream = false; |
michael@0 | 1028 | } |
michael@0 | 1029 | } |
michael@0 | 1030 | |
michael@0 | 1031 | if (resetStream) { |
michael@0 | 1032 | // Need to decompress the headers even though we aren't using them yet in |
michael@0 | 1033 | // order to keep the compression context consistent for other syn_reply frames |
michael@0 | 1034 | rv = self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10); |
michael@0 | 1035 | if (NS_FAILED(rv)) { |
michael@0 | 1036 | LOG(("SpdySession3::HandleSynStream uncompress failed\n")); |
michael@0 | 1037 | return rv; |
michael@0 | 1038 | } |
michael@0 | 1039 | self->ResetDownstreamState(); |
michael@0 | 1040 | return NS_OK; |
michael@0 | 1041 | } |
michael@0 | 1042 | |
michael@0 | 1043 | // Create the buffering transaction and push stream |
michael@0 | 1044 | nsRefPtr<SpdyPush3TransactionBuffer> transactionBuffer = |
michael@0 | 1045 | new SpdyPush3TransactionBuffer(); |
michael@0 | 1046 | transactionBuffer->SetConnection(self); |
michael@0 | 1047 | SpdyPushedStream3 *pushedStream = |
michael@0 | 1048 | new SpdyPushedStream3(transactionBuffer, self, |
michael@0 | 1049 | associatedStream, streamID); |
michael@0 | 1050 | |
michael@0 | 1051 | // ownership of the pushed stream is by the transaction hash, just as it |
michael@0 | 1052 | // is for a client initiated stream. Errors that aren't fatal to the |
michael@0 | 1053 | // whole session must call cleanupStream() after this point in order |
michael@0 | 1054 | // to remove the stream from that hash. |
michael@0 | 1055 | self->mStreamTransactionHash.Put(transactionBuffer, pushedStream); |
michael@0 | 1056 | self->mPushedStreams.AppendElement(pushedStream); |
michael@0 | 1057 | |
michael@0 | 1058 | // The pushed stream is unidirectional so it is fully open immediately |
michael@0 | 1059 | pushedStream->SetFullyOpen(); |
michael@0 | 1060 | |
michael@0 | 1061 | // Uncompress the response headers into a stream specific buffer, leaving them |
michael@0 | 1062 | // in spdy format for the time being. |
michael@0 | 1063 | rv = pushedStream->Uncompress(&self->mDownstreamZlib, |
michael@0 | 1064 | self->mInputFrameBuffer + 18, |
michael@0 | 1065 | self->mInputFrameDataSize - 10); |
michael@0 | 1066 | if (NS_FAILED(rv)) { |
michael@0 | 1067 | LOG(("SpdySession3::HandleSynStream uncompress failed\n")); |
michael@0 | 1068 | return rv; |
michael@0 | 1069 | } |
michael@0 | 1070 | |
michael@0 | 1071 | if (self->RegisterStreamID(pushedStream, streamID) == kDeadStreamID) { |
michael@0 | 1072 | LOG(("SpdySession3::HandleSynStream registerstreamid failed\n")); |
michael@0 | 1073 | return NS_ERROR_FAILURE; |
michael@0 | 1074 | } |
michael@0 | 1075 | |
michael@0 | 1076 | // Fake the request side of the pushed HTTP transaction. Sets up hash |
michael@0 | 1077 | // key and origin |
michael@0 | 1078 | uint32_t notUsed; |
michael@0 | 1079 | pushedStream->ReadSegments(nullptr, 1, ¬Used); |
michael@0 | 1080 | |
michael@0 | 1081 | nsAutoCString key; |
michael@0 | 1082 | if (!pushedStream->GetHashKey(key)) { |
michael@0 | 1083 | LOG(("SpdySession3::HandleSynStream one of :host :scheme :path missing from push\n")); |
michael@0 | 1084 | self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); |
michael@0 | 1085 | self->ResetDownstreamState(); |
michael@0 | 1086 | return NS_OK; |
michael@0 | 1087 | } |
michael@0 | 1088 | |
michael@0 | 1089 | if (!associatedStream->Origin().Equals(pushedStream->Origin())) { |
michael@0 | 1090 | LOG(("SpdySession3::HandleSynStream pushed stream mismatched origin\n")); |
michael@0 | 1091 | self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); |
michael@0 | 1092 | self->ResetDownstreamState(); |
michael@0 | 1093 | return NS_OK; |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | if (!cache->RegisterPushedStreamSpdy3(key, pushedStream)) { |
michael@0 | 1097 | LOG(("SpdySession3::HandleSynStream registerPushedStream Failed\n")); |
michael@0 | 1098 | self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); |
michael@0 | 1099 | self->ResetDownstreamState(); |
michael@0 | 1100 | return NS_OK; |
michael@0 | 1101 | } |
michael@0 | 1102 | |
michael@0 | 1103 | self->ResetDownstreamState(); |
michael@0 | 1104 | return NS_OK; |
michael@0 | 1105 | } |
michael@0 | 1106 | |
michael@0 | 1107 | nsresult |
michael@0 | 1108 | SpdySession3::SetInputFrameDataStream(uint32_t streamID) |
michael@0 | 1109 | { |
michael@0 | 1110 | mInputFrameDataStream = mStreamIDHash.Get(streamID); |
michael@0 | 1111 | if (VerifyStream(mInputFrameDataStream, streamID)) |
michael@0 | 1112 | return NS_OK; |
michael@0 | 1113 | |
michael@0 | 1114 | LOG(("SpdySession3::SetInputFrameDataStream failed to verify 0x%X\n", |
michael@0 | 1115 | streamID)); |
michael@0 | 1116 | mInputFrameDataStream = nullptr; |
michael@0 | 1117 | return NS_ERROR_UNEXPECTED; |
michael@0 | 1118 | } |
michael@0 | 1119 | |
michael@0 | 1120 | nsresult |
michael@0 | 1121 | SpdySession3::HandleSynReply(SpdySession3 *self) |
michael@0 | 1122 | { |
michael@0 | 1123 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY); |
michael@0 | 1124 | |
michael@0 | 1125 | if (self->mInputFrameDataSize < 4) { |
michael@0 | 1126 | LOG3(("SpdySession3::HandleSynReply %p SYN REPLY too short data=%d", |
michael@0 | 1127 | self, self->mInputFrameDataSize)); |
michael@0 | 1128 | // A framing error is a session wide error that cannot be recovered |
michael@0 | 1129 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1130 | } |
michael@0 | 1131 | |
michael@0 | 1132 | LOG3(("SpdySession3::HandleSynReply %p lookup via streamID in syn_reply.\n", |
michael@0 | 1133 | self)); |
michael@0 | 1134 | uint32_t streamID = |
michael@0 | 1135 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1136 | nsresult rv = self->SetInputFrameDataStream(streamID); |
michael@0 | 1137 | if (NS_FAILED(rv)) |
michael@0 | 1138 | return rv; |
michael@0 | 1139 | |
michael@0 | 1140 | if (!self->mInputFrameDataStream) { |
michael@0 | 1141 | // Cannot find stream. We can continue the SPDY session, but we need to |
michael@0 | 1142 | // uncompress the header block to maintain the correct compression context |
michael@0 | 1143 | |
michael@0 | 1144 | LOG3(("SpdySession3::HandleSynReply %p lookup streamID in syn_reply " |
michael@0 | 1145 | "0x%X failed. NextStreamID = 0x%X\n", |
michael@0 | 1146 | self, streamID, self->mNextStreamID)); |
michael@0 | 1147 | |
michael@0 | 1148 | if (streamID >= self->mNextStreamID) |
michael@0 | 1149 | self->GenerateRstStream(RST_INVALID_STREAM, streamID); |
michael@0 | 1150 | |
michael@0 | 1151 | rv = self->UncompressAndDiscard(12, self->mInputFrameDataSize - 4); |
michael@0 | 1152 | if (NS_FAILED(rv)) { |
michael@0 | 1153 | LOG(("SpdySession3::HandleSynReply uncompress failed\n")); |
michael@0 | 1154 | // this is fatal to the session |
michael@0 | 1155 | return rv; |
michael@0 | 1156 | } |
michael@0 | 1157 | |
michael@0 | 1158 | self->ResetDownstreamState(); |
michael@0 | 1159 | return NS_OK; |
michael@0 | 1160 | } |
michael@0 | 1161 | |
michael@0 | 1162 | // Uncompress the headers into a stream specific buffer, leaving them in |
michael@0 | 1163 | // spdy format for the time being. Make certain to do this |
michael@0 | 1164 | // step before any error handling that might abort the stream but not |
michael@0 | 1165 | // the session becuase the session compression context will become |
michael@0 | 1166 | // inconsistent if all of the compressed data is not processed. |
michael@0 | 1167 | rv = self->mInputFrameDataStream->Uncompress(&self->mDownstreamZlib, |
michael@0 | 1168 | self->mInputFrameBuffer + 12, |
michael@0 | 1169 | self->mInputFrameDataSize - 4); |
michael@0 | 1170 | |
michael@0 | 1171 | if (NS_FAILED(rv)) { |
michael@0 | 1172 | LOG(("SpdySession3::HandleSynReply uncompress failed\n")); |
michael@0 | 1173 | return rv; |
michael@0 | 1174 | } |
michael@0 | 1175 | |
michael@0 | 1176 | if (self->mInputFrameDataStream->GetFullyOpen()) { |
michael@0 | 1177 | // "If an endpoint receives multiple SYN_REPLY frames for the same active |
michael@0 | 1178 | // stream ID, it MUST issue a stream error (Section 2.4.2) with the error |
michael@0 | 1179 | // code STREAM_IN_USE." |
michael@0 | 1180 | // |
michael@0 | 1181 | // "STREAM_ALREADY_CLOSED. The endpoint received a data or SYN_REPLY |
michael@0 | 1182 | // frame for a stream which is half closed." |
michael@0 | 1183 | // |
michael@0 | 1184 | // If the stream is open then just RST_STREAM with STREAM_IN_USE |
michael@0 | 1185 | // If the stream is half closed then RST_STREAM with STREAM_ALREADY_CLOSED |
michael@0 | 1186 | // abort the session |
michael@0 | 1187 | // |
michael@0 | 1188 | LOG3(("SpdySession3::HandleSynReply %p dup SYN_REPLY for 0x%X" |
michael@0 | 1189 | " recvdfin=%d", self, self->mInputFrameDataStream->StreamID(), |
michael@0 | 1190 | self->mInputFrameDataStream->RecvdFin())); |
michael@0 | 1191 | |
michael@0 | 1192 | self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ALREADY_OPENED, |
michael@0 | 1193 | self->mInputFrameDataStream->RecvdFin() ? |
michael@0 | 1194 | RST_STREAM_ALREADY_CLOSED : RST_STREAM_IN_USE); |
michael@0 | 1195 | self->ResetDownstreamState(); |
michael@0 | 1196 | return NS_OK; |
michael@0 | 1197 | } |
michael@0 | 1198 | self->mInputFrameDataStream->SetFullyOpen(); |
michael@0 | 1199 | |
michael@0 | 1200 | self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; |
michael@0 | 1201 | self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize); |
michael@0 | 1202 | self->mLastDataReadEpoch = self->mLastReadEpoch; |
michael@0 | 1203 | |
michael@0 | 1204 | if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) { |
michael@0 | 1205 | LOG3(("SynReply %p had undefined flag set 0x%X\n", self, streamID)); |
michael@0 | 1206 | self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, |
michael@0 | 1207 | RST_PROTOCOL_ERROR); |
michael@0 | 1208 | self->ResetDownstreamState(); |
michael@0 | 1209 | return NS_OK; |
michael@0 | 1210 | } |
michael@0 | 1211 | |
michael@0 | 1212 | if (!self->mInputFrameDataLast) { |
michael@0 | 1213 | // don't process the headers yet as there could be more coming from HEADERS |
michael@0 | 1214 | // frames |
michael@0 | 1215 | self->ResetDownstreamState(); |
michael@0 | 1216 | return NS_OK; |
michael@0 | 1217 | } |
michael@0 | 1218 | |
michael@0 | 1219 | rv = self->ResponseHeadersComplete(); |
michael@0 | 1220 | if (rv == NS_ERROR_ILLEGAL_VALUE) { |
michael@0 | 1221 | LOG3(("SpdySession3::HandleSynReply %p PROTOCOL_ERROR detected 0x%X\n", |
michael@0 | 1222 | self, streamID)); |
michael@0 | 1223 | self->CleanupStream(self->mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); |
michael@0 | 1224 | self->ResetDownstreamState(); |
michael@0 | 1225 | rv = NS_OK; |
michael@0 | 1226 | } |
michael@0 | 1227 | return rv; |
michael@0 | 1228 | } |
michael@0 | 1229 | |
michael@0 | 1230 | // ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream |
michael@0 | 1231 | // should be reset with a PROTOCOL_ERROR, NS_OK when the SYN_REPLY was |
michael@0 | 1232 | // fine, and any other error is fatal to the session. |
michael@0 | 1233 | nsresult |
michael@0 | 1234 | SpdySession3::ResponseHeadersComplete() |
michael@0 | 1235 | { |
michael@0 | 1236 | LOG3(("SpdySession3::ResponseHeadersComplete %p for 0x%X fin=%d", |
michael@0 | 1237 | this, mInputFrameDataStream->StreamID(), mInputFrameDataLast)); |
michael@0 | 1238 | |
michael@0 | 1239 | // The spdystream needs to see flattened http headers |
michael@0 | 1240 | // Uncompressed spdy format headers currently live in |
michael@0 | 1241 | // SpdyStream3::mDecompressBuffer - convert that to HTTP format in |
michael@0 | 1242 | // mFlatHTTPResponseHeaders via ConvertHeaders() |
michael@0 | 1243 | |
michael@0 | 1244 | mFlatHTTPResponseHeadersOut = 0; |
michael@0 | 1245 | nsresult rv = mInputFrameDataStream->ConvertHeaders(mFlatHTTPResponseHeaders); |
michael@0 | 1246 | if (NS_FAILED(rv)) |
michael@0 | 1247 | return rv; |
michael@0 | 1248 | |
michael@0 | 1249 | ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); |
michael@0 | 1250 | return NS_OK; |
michael@0 | 1251 | } |
michael@0 | 1252 | |
michael@0 | 1253 | nsresult |
michael@0 | 1254 | SpdySession3::HandleRstStream(SpdySession3 *self) |
michael@0 | 1255 | { |
michael@0 | 1256 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_RST_STREAM); |
michael@0 | 1257 | |
michael@0 | 1258 | if (self->mInputFrameDataSize != 8) { |
michael@0 | 1259 | LOG3(("SpdySession3::HandleRstStream %p RST_STREAM wrong length data=%d", |
michael@0 | 1260 | self, self->mInputFrameDataSize)); |
michael@0 | 1261 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1262 | } |
michael@0 | 1263 | |
michael@0 | 1264 | uint8_t flags = reinterpret_cast<uint8_t *>(self->mInputFrameBuffer.get())[4]; |
michael@0 | 1265 | |
michael@0 | 1266 | uint32_t streamID = |
michael@0 | 1267 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1268 | |
michael@0 | 1269 | self->mDownstreamRstReason = |
michael@0 | 1270 | NetworkEndian::readUint32(self->mInputFrameBuffer + 3 * sizeof(uint32_t)); |
michael@0 | 1271 | |
michael@0 | 1272 | LOG3(("SpdySession3::HandleRstStream %p RST_STREAM Reason Code %u ID %x " |
michael@0 | 1273 | "flags %x", self, self->mDownstreamRstReason, streamID, flags)); |
michael@0 | 1274 | |
michael@0 | 1275 | if (flags != 0) { |
michael@0 | 1276 | LOG3(("SpdySession3::HandleRstStream %p RST_STREAM with flags is illegal", |
michael@0 | 1277 | self)); |
michael@0 | 1278 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | if (self->mDownstreamRstReason == RST_INVALID_STREAM || |
michael@0 | 1282 | self->mDownstreamRstReason == RST_STREAM_IN_USE || |
michael@0 | 1283 | self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) { |
michael@0 | 1284 | // basically just ignore this |
michael@0 | 1285 | LOG3(("SpdySession3::HandleRstStream %p No Reset Processing Needed.\n")); |
michael@0 | 1286 | self->ResetDownstreamState(); |
michael@0 | 1287 | return NS_OK; |
michael@0 | 1288 | } |
michael@0 | 1289 | |
michael@0 | 1290 | nsresult rv = self->SetInputFrameDataStream(streamID); |
michael@0 | 1291 | |
michael@0 | 1292 | if (!self->mInputFrameDataStream) { |
michael@0 | 1293 | if (NS_FAILED(rv)) |
michael@0 | 1294 | LOG(("SpdySession3::HandleRstStream %p lookup streamID for RST Frame " |
michael@0 | 1295 | "0x%X failed reason = %d :: VerifyStream Failed\n", self, streamID, |
michael@0 | 1296 | self->mDownstreamRstReason)); |
michael@0 | 1297 | |
michael@0 | 1298 | LOG3(("SpdySession3::HandleRstStream %p lookup streamID for RST Frame " |
michael@0 | 1299 | "0x%X failed reason = %d", self, streamID, |
michael@0 | 1300 | self->mDownstreamRstReason)); |
michael@0 | 1301 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1302 | } |
michael@0 | 1303 | |
michael@0 | 1304 | self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); |
michael@0 | 1305 | return NS_OK; |
michael@0 | 1306 | } |
michael@0 | 1307 | |
michael@0 | 1308 | PLDHashOperator |
michael@0 | 1309 | SpdySession3::UpdateServerRwinEnumerator(nsAHttpTransaction *key, |
michael@0 | 1310 | nsAutoPtr<SpdyStream3> &stream, |
michael@0 | 1311 | void *closure) |
michael@0 | 1312 | { |
michael@0 | 1313 | int32_t delta = *(static_cast<int32_t *>(closure)); |
michael@0 | 1314 | stream->UpdateRemoteWindow(delta); |
michael@0 | 1315 | return PL_DHASH_NEXT; |
michael@0 | 1316 | } |
michael@0 | 1317 | |
michael@0 | 1318 | nsresult |
michael@0 | 1319 | SpdySession3::HandleSettings(SpdySession3 *self) |
michael@0 | 1320 | { |
michael@0 | 1321 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SETTINGS); |
michael@0 | 1322 | |
michael@0 | 1323 | if (self->mInputFrameDataSize < 4) { |
michael@0 | 1324 | LOG3(("SpdySession3::HandleSettings %p SETTINGS wrong length data=%d", |
michael@0 | 1325 | self, self->mInputFrameDataSize)); |
michael@0 | 1326 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1327 | } |
michael@0 | 1328 | |
michael@0 | 1329 | uint32_t numEntries = |
michael@0 | 1330 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1331 | |
michael@0 | 1332 | // Ensure frame is large enough for supplied number of entries |
michael@0 | 1333 | // Each entry is 8 bytes, frame data is reduced by 4 to account for |
michael@0 | 1334 | // the NumEntries value. |
michael@0 | 1335 | if ((self->mInputFrameDataSize - 4) < (numEntries * 8)) { |
michael@0 | 1336 | LOG3(("SpdySession3::HandleSettings %p SETTINGS wrong length data=%d", |
michael@0 | 1337 | self, self->mInputFrameDataSize)); |
michael@0 | 1338 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1339 | } |
michael@0 | 1340 | |
michael@0 | 1341 | LOG3(("SpdySession3::HandleSettings %p SETTINGS Control Frame with %d entries", |
michael@0 | 1342 | self, numEntries)); |
michael@0 | 1343 | |
michael@0 | 1344 | for (uint32_t index = 0; index < numEntries; ++index) { |
michael@0 | 1345 | unsigned char *setting = reinterpret_cast<unsigned char *> |
michael@0 | 1346 | (self->mInputFrameBuffer.get()) + 12 + index * 8; |
michael@0 | 1347 | |
michael@0 | 1348 | uint32_t flags = setting[0]; |
michael@0 | 1349 | uint32_t id = NetworkEndian::readUint32(setting) & 0xffffff; |
michael@0 | 1350 | uint32_t value = NetworkEndian::readUint32(setting + 1 * sizeof(uint32_t)); |
michael@0 | 1351 | |
michael@0 | 1352 | LOG3(("Settings ID %d, Flags %X, Value %d", id, flags, value)); |
michael@0 | 1353 | |
michael@0 | 1354 | switch (id) |
michael@0 | 1355 | { |
michael@0 | 1356 | case SETTINGS_TYPE_UPLOAD_BW: |
michael@0 | 1357 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value); |
michael@0 | 1358 | break; |
michael@0 | 1359 | |
michael@0 | 1360 | case SETTINGS_TYPE_DOWNLOAD_BW: |
michael@0 | 1361 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value); |
michael@0 | 1362 | break; |
michael@0 | 1363 | |
michael@0 | 1364 | case SETTINGS_TYPE_RTT: |
michael@0 | 1365 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value); |
michael@0 | 1366 | break; |
michael@0 | 1367 | |
michael@0 | 1368 | case SETTINGS_TYPE_MAX_CONCURRENT: |
michael@0 | 1369 | self->mMaxConcurrent = value; |
michael@0 | 1370 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value); |
michael@0 | 1371 | break; |
michael@0 | 1372 | |
michael@0 | 1373 | case SETTINGS_TYPE_CWND: |
michael@0 | 1374 | if (flags & PERSIST_VALUE) |
michael@0 | 1375 | { |
michael@0 | 1376 | nsRefPtr<nsHttpConnectionInfo> ci; |
michael@0 | 1377 | self->GetConnectionInfo(getter_AddRefs(ci)); |
michael@0 | 1378 | if (ci) |
michael@0 | 1379 | gHttpHandler->ConnMgr()->ReportSpdyCWNDSetting(ci, value); |
michael@0 | 1380 | } |
michael@0 | 1381 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value); |
michael@0 | 1382 | break; |
michael@0 | 1383 | |
michael@0 | 1384 | case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE: |
michael@0 | 1385 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value); |
michael@0 | 1386 | break; |
michael@0 | 1387 | |
michael@0 | 1388 | case SETTINGS_TYPE_INITIAL_WINDOW: |
michael@0 | 1389 | Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10); |
michael@0 | 1390 | { |
michael@0 | 1391 | int32_t delta = value - self->mServerInitialWindow; |
michael@0 | 1392 | self->mServerInitialWindow = value; |
michael@0 | 1393 | |
michael@0 | 1394 | // we need to add the delta to all open streams (delta can be negative) |
michael@0 | 1395 | self->mStreamTransactionHash.Enumerate(UpdateServerRwinEnumerator, |
michael@0 | 1396 | &delta); |
michael@0 | 1397 | } |
michael@0 | 1398 | break; |
michael@0 | 1399 | |
michael@0 | 1400 | default: |
michael@0 | 1401 | break; |
michael@0 | 1402 | } |
michael@0 | 1403 | |
michael@0 | 1404 | } |
michael@0 | 1405 | |
michael@0 | 1406 | self->ResetDownstreamState(); |
michael@0 | 1407 | return NS_OK; |
michael@0 | 1408 | } |
michael@0 | 1409 | |
michael@0 | 1410 | nsresult |
michael@0 | 1411 | SpdySession3::HandleNoop(SpdySession3 *self) |
michael@0 | 1412 | { |
michael@0 | 1413 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_NOOP); |
michael@0 | 1414 | |
michael@0 | 1415 | // Should not be receiving noop frames in spdy/3, so we'll just |
michael@0 | 1416 | // make a log and ignore it |
michael@0 | 1417 | |
michael@0 | 1418 | LOG3(("SpdySession3::HandleNoop %p NOP.", self)); |
michael@0 | 1419 | |
michael@0 | 1420 | self->ResetDownstreamState(); |
michael@0 | 1421 | return NS_OK; |
michael@0 | 1422 | } |
michael@0 | 1423 | |
michael@0 | 1424 | nsresult |
michael@0 | 1425 | SpdySession3::HandlePing(SpdySession3 *self) |
michael@0 | 1426 | { |
michael@0 | 1427 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_PING); |
michael@0 | 1428 | |
michael@0 | 1429 | if (self->mInputFrameDataSize != 4) { |
michael@0 | 1430 | LOG3(("SpdySession3::HandlePing %p PING had wrong amount of data %d", |
michael@0 | 1431 | self, self->mInputFrameDataSize)); |
michael@0 | 1432 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1433 | } |
michael@0 | 1434 | |
michael@0 | 1435 | uint32_t pingID = |
michael@0 | 1436 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1437 | |
michael@0 | 1438 | LOG3(("SpdySession3::HandlePing %p PING ID 0x%X.", self, pingID)); |
michael@0 | 1439 | |
michael@0 | 1440 | if (pingID & 0x01) { |
michael@0 | 1441 | // presumably a reply to our timeout ping |
michael@0 | 1442 | self->mPingSentEpoch = 0; |
michael@0 | 1443 | } |
michael@0 | 1444 | else { |
michael@0 | 1445 | // Servers initiate even numbered pings, go ahead and echo it back |
michael@0 | 1446 | self->GeneratePing(pingID); |
michael@0 | 1447 | } |
michael@0 | 1448 | |
michael@0 | 1449 | self->ResetDownstreamState(); |
michael@0 | 1450 | return NS_OK; |
michael@0 | 1451 | } |
michael@0 | 1452 | |
michael@0 | 1453 | nsresult |
michael@0 | 1454 | SpdySession3::HandleGoAway(SpdySession3 *self) |
michael@0 | 1455 | { |
michael@0 | 1456 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_GOAWAY); |
michael@0 | 1457 | |
michael@0 | 1458 | if (self->mInputFrameDataSize != 8) { |
michael@0 | 1459 | LOG3(("SpdySession3::HandleGoAway %p GOAWAY had wrong amount of data %d", |
michael@0 | 1460 | self, self->mInputFrameDataSize)); |
michael@0 | 1461 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1462 | } |
michael@0 | 1463 | |
michael@0 | 1464 | self->mShouldGoAway = true; |
michael@0 | 1465 | self->mGoAwayID = |
michael@0 | 1466 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1467 | self->mCleanShutdown = true; |
michael@0 | 1468 | |
michael@0 | 1469 | // Find streams greater than the last-good ID and mark them for deletion |
michael@0 | 1470 | // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. The |
michael@0 | 1471 | // underlying transaction can be restarted. |
michael@0 | 1472 | self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self); |
michael@0 | 1473 | |
michael@0 | 1474 | // Process the streams marked for deletion and restart. |
michael@0 | 1475 | uint32_t size = self->mGoAwayStreamsToRestart.GetSize(); |
michael@0 | 1476 | for (uint32_t count = 0; count < size; ++count) { |
michael@0 | 1477 | SpdyStream3 *stream = |
michael@0 | 1478 | static_cast<SpdyStream3 *>(self->mGoAwayStreamsToRestart.PopFront()); |
michael@0 | 1479 | |
michael@0 | 1480 | self->CloseStream(stream, NS_ERROR_NET_RESET); |
michael@0 | 1481 | if (stream->HasRegisteredID()) |
michael@0 | 1482 | self->mStreamIDHash.Remove(stream->StreamID()); |
michael@0 | 1483 | self->mStreamTransactionHash.Remove(stream->Transaction()); |
michael@0 | 1484 | } |
michael@0 | 1485 | |
michael@0 | 1486 | // Queued streams can also be deleted from this session and restarted |
michael@0 | 1487 | // in another one. (they were never sent on the network so they implicitly |
michael@0 | 1488 | // are not covered by the last-good id. |
michael@0 | 1489 | size = self->mQueuedStreams.GetSize(); |
michael@0 | 1490 | for (uint32_t count = 0; count < size; ++count) { |
michael@0 | 1491 | SpdyStream3 *stream = |
michael@0 | 1492 | static_cast<SpdyStream3 *>(self->mQueuedStreams.PopFront()); |
michael@0 | 1493 | self->CloseStream(stream, NS_ERROR_NET_RESET); |
michael@0 | 1494 | self->mStreamTransactionHash.Remove(stream->Transaction()); |
michael@0 | 1495 | } |
michael@0 | 1496 | |
michael@0 | 1497 | LOG3(("SpdySession3::HandleGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X " |
michael@0 | 1498 | "live streams=%d\n", self, self->mGoAwayID, |
michael@0 | 1499 | NetworkEndian::readUint32(self->mInputFrameBuffer + |
michael@0 | 1500 | 3 * sizeof(uint32_t)), |
michael@0 | 1501 | self->mStreamTransactionHash.Count())); |
michael@0 | 1502 | |
michael@0 | 1503 | self->ResetDownstreamState(); |
michael@0 | 1504 | return NS_OK; |
michael@0 | 1505 | } |
michael@0 | 1506 | |
michael@0 | 1507 | nsresult |
michael@0 | 1508 | SpdySession3::HandleHeaders(SpdySession3 *self) |
michael@0 | 1509 | { |
michael@0 | 1510 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_HEADERS); |
michael@0 | 1511 | |
michael@0 | 1512 | if (self->mInputFrameDataSize < 4) { |
michael@0 | 1513 | LOG3(("SpdySession3::HandleHeaders %p HEADERS had wrong amount of data %d", |
michael@0 | 1514 | self, self->mInputFrameDataSize)); |
michael@0 | 1515 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1516 | } |
michael@0 | 1517 | |
michael@0 | 1518 | uint32_t streamID = |
michael@0 | 1519 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1520 | LOG3(("SpdySession3::HandleHeaders %p HEADERS for Stream 0x%X.\n", |
michael@0 | 1521 | self, streamID)); |
michael@0 | 1522 | nsresult rv = self->SetInputFrameDataStream(streamID); |
michael@0 | 1523 | if (NS_FAILED(rv)) |
michael@0 | 1524 | return rv; |
michael@0 | 1525 | |
michael@0 | 1526 | if (!self->mInputFrameDataStream) { |
michael@0 | 1527 | LOG3(("SpdySession3::HandleHeaders %p lookup streamID 0x%X failed.\n", |
michael@0 | 1528 | self, streamID)); |
michael@0 | 1529 | if (streamID >= self->mNextStreamID) |
michael@0 | 1530 | self->GenerateRstStream(RST_INVALID_STREAM, streamID); |
michael@0 | 1531 | |
michael@0 | 1532 | rv = self->UncompressAndDiscard(12, self->mInputFrameDataSize - 4); |
michael@0 | 1533 | if (NS_FAILED(rv)) { |
michael@0 | 1534 | LOG(("SpdySession3::HandleHeaders uncompress failed\n")); |
michael@0 | 1535 | // this is fatal to the session |
michael@0 | 1536 | return rv; |
michael@0 | 1537 | } |
michael@0 | 1538 | self->ResetDownstreamState(); |
michael@0 | 1539 | return NS_OK; |
michael@0 | 1540 | } |
michael@0 | 1541 | |
michael@0 | 1542 | // Uncompress the headers into local buffers in the SpdyStream, leaving |
michael@0 | 1543 | // them in spdy format for the time being. Make certain to do this |
michael@0 | 1544 | // step before any error handling that might abort the stream but not |
michael@0 | 1545 | // the session becuase the session compression context will become |
michael@0 | 1546 | // inconsistent if all of the compressed data is not processed. |
michael@0 | 1547 | rv = self->mInputFrameDataStream->Uncompress(&self->mDownstreamZlib, |
michael@0 | 1548 | self->mInputFrameBuffer + 12, |
michael@0 | 1549 | self->mInputFrameDataSize - 4); |
michael@0 | 1550 | if (NS_FAILED(rv)) { |
michael@0 | 1551 | LOG(("SpdySession3::HandleHeaders uncompress failed\n")); |
michael@0 | 1552 | return rv; |
michael@0 | 1553 | } |
michael@0 | 1554 | |
michael@0 | 1555 | self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; |
michael@0 | 1556 | self->mInputFrameDataStream-> |
michael@0 | 1557 | UpdateTransportReadEvents(self->mInputFrameDataSize); |
michael@0 | 1558 | self->mLastDataReadEpoch = self->mLastReadEpoch; |
michael@0 | 1559 | |
michael@0 | 1560 | if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) { |
michael@0 | 1561 | LOG3(("Headers %p had undefined flag set 0x%X\n", self, streamID)); |
michael@0 | 1562 | self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, |
michael@0 | 1563 | RST_PROTOCOL_ERROR); |
michael@0 | 1564 | self->ResetDownstreamState(); |
michael@0 | 1565 | return NS_OK; |
michael@0 | 1566 | } |
michael@0 | 1567 | |
michael@0 | 1568 | if (!self->mInputFrameDataLast) { |
michael@0 | 1569 | // don't process the headers yet as there could be more HEADERS frames |
michael@0 | 1570 | self->ResetDownstreamState(); |
michael@0 | 1571 | return NS_OK; |
michael@0 | 1572 | } |
michael@0 | 1573 | |
michael@0 | 1574 | rv = self->ResponseHeadersComplete(); |
michael@0 | 1575 | if (rv == NS_ERROR_ILLEGAL_VALUE) { |
michael@0 | 1576 | LOG3(("SpdySession3::HanndleHeaders %p PROTOCOL_ERROR detected 0x%X\n", |
michael@0 | 1577 | self, streamID)); |
michael@0 | 1578 | self->CleanupStream(self->mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); |
michael@0 | 1579 | self->ResetDownstreamState(); |
michael@0 | 1580 | rv = NS_OK; |
michael@0 | 1581 | } |
michael@0 | 1582 | return rv; |
michael@0 | 1583 | } |
michael@0 | 1584 | |
michael@0 | 1585 | nsresult |
michael@0 | 1586 | SpdySession3::HandleWindowUpdate(SpdySession3 *self) |
michael@0 | 1587 | { |
michael@0 | 1588 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE); |
michael@0 | 1589 | |
michael@0 | 1590 | if (self->mInputFrameDataSize < 8) { |
michael@0 | 1591 | LOG3(("SpdySession3::HandleWindowUpdate %p Window Update wrong length %d\n", |
michael@0 | 1592 | self, self->mInputFrameDataSize)); |
michael@0 | 1593 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1594 | } |
michael@0 | 1595 | |
michael@0 | 1596 | uint32_t delta = |
michael@0 | 1597 | NetworkEndian::readUint32(self->mInputFrameBuffer + 3 * sizeof(uint32_t)); |
michael@0 | 1598 | delta &= 0x7fffffff; |
michael@0 | 1599 | uint32_t streamID = |
michael@0 | 1600 | NetworkEndian::readUint32(self->mInputFrameBuffer + 2 * sizeof(uint32_t)); |
michael@0 | 1601 | streamID &= 0x7fffffff; |
michael@0 | 1602 | |
michael@0 | 1603 | LOG3(("SpdySession3::HandleWindowUpdate %p len=%d for Stream 0x%X.\n", |
michael@0 | 1604 | self, delta, streamID)); |
michael@0 | 1605 | nsresult rv = self->SetInputFrameDataStream(streamID); |
michael@0 | 1606 | if (NS_FAILED(rv)) |
michael@0 | 1607 | return rv; |
michael@0 | 1608 | |
michael@0 | 1609 | if (!self->mInputFrameDataStream) { |
michael@0 | 1610 | LOG3(("SpdySession3::HandleWindowUpdate %p lookup streamID 0x%X failed.\n", |
michael@0 | 1611 | self, streamID)); |
michael@0 | 1612 | if (streamID >= self->mNextStreamID) |
michael@0 | 1613 | self->GenerateRstStream(RST_INVALID_STREAM, streamID); |
michael@0 | 1614 | self->ResetDownstreamState(); |
michael@0 | 1615 | return NS_OK; |
michael@0 | 1616 | } |
michael@0 | 1617 | |
michael@0 | 1618 | self->mInputFrameDataStream->UpdateRemoteWindow(delta); |
michael@0 | 1619 | self->ResetDownstreamState(); |
michael@0 | 1620 | return NS_OK; |
michael@0 | 1621 | } |
michael@0 | 1622 | |
michael@0 | 1623 | nsresult |
michael@0 | 1624 | SpdySession3::HandleCredential(SpdySession3 *self) |
michael@0 | 1625 | { |
michael@0 | 1626 | MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_CREDENTIAL); |
michael@0 | 1627 | |
michael@0 | 1628 | // These aren't used yet. Just ignore the frame. |
michael@0 | 1629 | |
michael@0 | 1630 | LOG3(("SpdySession3::HandleCredential %p NOP.", self)); |
michael@0 | 1631 | |
michael@0 | 1632 | self->ResetDownstreamState(); |
michael@0 | 1633 | return NS_OK; |
michael@0 | 1634 | } |
michael@0 | 1635 | |
michael@0 | 1636 | //----------------------------------------------------------------------------- |
michael@0 | 1637 | // nsAHttpTransaction. It is expected that nsHttpConnection is the caller |
michael@0 | 1638 | // of these methods |
michael@0 | 1639 | //----------------------------------------------------------------------------- |
michael@0 | 1640 | |
michael@0 | 1641 | void |
michael@0 | 1642 | SpdySession3::OnTransportStatus(nsITransport* aTransport, |
michael@0 | 1643 | nsresult aStatus, |
michael@0 | 1644 | uint64_t aProgress) |
michael@0 | 1645 | { |
michael@0 | 1646 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 1647 | |
michael@0 | 1648 | switch (aStatus) { |
michael@0 | 1649 | // These should appear only once, deliver to the first |
michael@0 | 1650 | // transaction on the session. |
michael@0 | 1651 | case NS_NET_STATUS_RESOLVING_HOST: |
michael@0 | 1652 | case NS_NET_STATUS_RESOLVED_HOST: |
michael@0 | 1653 | case NS_NET_STATUS_CONNECTING_TO: |
michael@0 | 1654 | case NS_NET_STATUS_CONNECTED_TO: |
michael@0 | 1655 | { |
michael@0 | 1656 | SpdyStream3 *target = mStreamIDHash.Get(1); |
michael@0 | 1657 | nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr; |
michael@0 | 1658 | if (transaction) |
michael@0 | 1659 | transaction->OnTransportStatus(aTransport, aStatus, aProgress); |
michael@0 | 1660 | break; |
michael@0 | 1661 | } |
michael@0 | 1662 | |
michael@0 | 1663 | default: |
michael@0 | 1664 | // The other transport events are ignored here because there is no good |
michael@0 | 1665 | // way to map them to the right transaction in spdy. Instead, the events |
michael@0 | 1666 | // are generated again from the spdy code and passed directly to the |
michael@0 | 1667 | // correct transaction. |
michael@0 | 1668 | |
michael@0 | 1669 | // NS_NET_STATUS_SENDING_TO: |
michael@0 | 1670 | // This is generated by the socket transport when (part) of |
michael@0 | 1671 | // a transaction is written out |
michael@0 | 1672 | // |
michael@0 | 1673 | // There is no good way to map it to the right transaction in spdy, |
michael@0 | 1674 | // so it is ignored here and generated separately when the SYN_STREAM |
michael@0 | 1675 | // is sent from SpdyStream3::TransmitFrame |
michael@0 | 1676 | |
michael@0 | 1677 | // NS_NET_STATUS_WAITING_FOR: |
michael@0 | 1678 | // Created by nsHttpConnection when the request has been totally sent. |
michael@0 | 1679 | // There is no good way to map it to the right transaction in spdy, |
michael@0 | 1680 | // so it is ignored here and generated separately when the same |
michael@0 | 1681 | // condition is complete in SpdyStream3 when there is no more |
michael@0 | 1682 | // request body left to be transmitted. |
michael@0 | 1683 | |
michael@0 | 1684 | // NS_NET_STATUS_RECEIVING_FROM |
michael@0 | 1685 | // Generated in spdysession whenever we read a data frame or a syn_reply |
michael@0 | 1686 | // that can be attributed to a particular stream/transaction |
michael@0 | 1687 | |
michael@0 | 1688 | break; |
michael@0 | 1689 | } |
michael@0 | 1690 | } |
michael@0 | 1691 | |
michael@0 | 1692 | // ReadSegments() is used to write data to the network. Generally, HTTP |
michael@0 | 1693 | // request data is pulled from the approriate transaction and |
michael@0 | 1694 | // converted to SPDY data. Sometimes control data like window-update are |
michael@0 | 1695 | // generated instead. |
michael@0 | 1696 | |
michael@0 | 1697 | nsresult |
michael@0 | 1698 | SpdySession3::ReadSegments(nsAHttpSegmentReader *reader, |
michael@0 | 1699 | uint32_t count, |
michael@0 | 1700 | uint32_t *countRead) |
michael@0 | 1701 | { |
michael@0 | 1702 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 1703 | |
michael@0 | 1704 | MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader), |
michael@0 | 1705 | "Inconsistent Write Function Callback"); |
michael@0 | 1706 | |
michael@0 | 1707 | if (reader) |
michael@0 | 1708 | mSegmentReader = reader; |
michael@0 | 1709 | |
michael@0 | 1710 | nsresult rv; |
michael@0 | 1711 | *countRead = 0; |
michael@0 | 1712 | |
michael@0 | 1713 | LOG3(("SpdySession3::ReadSegments %p", this)); |
michael@0 | 1714 | |
michael@0 | 1715 | SpdyStream3 *stream = static_cast<SpdyStream3 *>(mReadyForWrite.PopFront()); |
michael@0 | 1716 | if (!stream) { |
michael@0 | 1717 | LOG3(("SpdySession3 %p could not identify a stream to write; suspending.", |
michael@0 | 1718 | this)); |
michael@0 | 1719 | FlushOutputQueue(); |
michael@0 | 1720 | SetWriteCallbacks(); |
michael@0 | 1721 | return NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 1722 | } |
michael@0 | 1723 | |
michael@0 | 1724 | LOG3(("SpdySession3 %p will write from SpdyStream3 %p 0x%X " |
michael@0 | 1725 | "block-input=%d block-output=%d\n", this, stream, stream->StreamID(), |
michael@0 | 1726 | stream->RequestBlockedOnRead(), stream->BlockedOnRwin())); |
michael@0 | 1727 | |
michael@0 | 1728 | rv = stream->ReadSegments(this, count, countRead); |
michael@0 | 1729 | |
michael@0 | 1730 | // Not every permutation of stream->ReadSegents produces data (and therefore |
michael@0 | 1731 | // tries to flush the output queue) - SENDING_FIN_STREAM can be an example |
michael@0 | 1732 | // of that. But we might still have old data buffered that would be good |
michael@0 | 1733 | // to flush. |
michael@0 | 1734 | FlushOutputQueue(); |
michael@0 | 1735 | |
michael@0 | 1736 | // Allow new server reads - that might be data or control information |
michael@0 | 1737 | // (e.g. window updates or http replies) that are responses to these writes |
michael@0 | 1738 | ResumeRecv(); |
michael@0 | 1739 | |
michael@0 | 1740 | if (stream->RequestBlockedOnRead()) { |
michael@0 | 1741 | |
michael@0 | 1742 | // We are blocked waiting for input - either more http headers or |
michael@0 | 1743 | // any request body data. When more data from the request stream |
michael@0 | 1744 | // becomes available the httptransaction will call conn->ResumeSend(). |
michael@0 | 1745 | |
michael@0 | 1746 | LOG3(("SpdySession3::ReadSegments %p dealing with block on read", this)); |
michael@0 | 1747 | |
michael@0 | 1748 | // call readsegments again if there are other streams ready |
michael@0 | 1749 | // to run in this session |
michael@0 | 1750 | if (GetWriteQueueSize()) |
michael@0 | 1751 | rv = NS_OK; |
michael@0 | 1752 | else |
michael@0 | 1753 | rv = NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 1754 | SetWriteCallbacks(); |
michael@0 | 1755 | return rv; |
michael@0 | 1756 | } |
michael@0 | 1757 | |
michael@0 | 1758 | if (NS_FAILED(rv)) { |
michael@0 | 1759 | LOG3(("SpdySession3::ReadSegments %p returning FAIL code %X", |
michael@0 | 1760 | this, rv)); |
michael@0 | 1761 | if (rv != NS_BASE_STREAM_WOULD_BLOCK) |
michael@0 | 1762 | CleanupStream(stream, rv, RST_CANCEL); |
michael@0 | 1763 | return rv; |
michael@0 | 1764 | } |
michael@0 | 1765 | |
michael@0 | 1766 | if (*countRead > 0) { |
michael@0 | 1767 | LOG3(("SpdySession3::ReadSegments %p stream=%p countread=%d", |
michael@0 | 1768 | this, stream, *countRead)); |
michael@0 | 1769 | mReadyForWrite.Push(stream); |
michael@0 | 1770 | SetWriteCallbacks(); |
michael@0 | 1771 | return rv; |
michael@0 | 1772 | } |
michael@0 | 1773 | |
michael@0 | 1774 | if (stream->BlockedOnRwin()) { |
michael@0 | 1775 | LOG3(("SpdySession3 %p will stream %p 0x%X suspended for flow control\n", |
michael@0 | 1776 | this, stream, stream->StreamID())); |
michael@0 | 1777 | return NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 1778 | } |
michael@0 | 1779 | |
michael@0 | 1780 | LOG3(("SpdySession3::ReadSegments %p stream=%p stream send complete", |
michael@0 | 1781 | this, stream)); |
michael@0 | 1782 | |
michael@0 | 1783 | // call readsegments again if there are other streams ready |
michael@0 | 1784 | // to go in this session |
michael@0 | 1785 | SetWriteCallbacks(); |
michael@0 | 1786 | |
michael@0 | 1787 | return rv; |
michael@0 | 1788 | } |
michael@0 | 1789 | |
michael@0 | 1790 | // WriteSegments() is used to read data off the socket. Generally this is |
michael@0 | 1791 | // just the SPDY frame header and from there the appropriate SPDYStream |
michael@0 | 1792 | // is identified from the Stream-ID. The http transaction associated with |
michael@0 | 1793 | // that read then pulls in the data directly, which it will feed to |
michael@0 | 1794 | // OnWriteSegment(). That function will gateway it into http and feed |
michael@0 | 1795 | // it to the appropriate transaction. |
michael@0 | 1796 | |
michael@0 | 1797 | // we call writer->OnWriteSegment via NetworkRead() to get a spdy header.. |
michael@0 | 1798 | // and decide if it is data or control.. if it is control, just deal with it. |
michael@0 | 1799 | // if it is data, identify the spdy stream |
michael@0 | 1800 | // call stream->WriteSegments which can call this::OnWriteSegment to get the |
michael@0 | 1801 | // data. It always gets full frames if they are part of the stream |
michael@0 | 1802 | |
michael@0 | 1803 | nsresult |
michael@0 | 1804 | SpdySession3::WriteSegments(nsAHttpSegmentWriter *writer, |
michael@0 | 1805 | uint32_t count, |
michael@0 | 1806 | uint32_t *countWritten) |
michael@0 | 1807 | { |
michael@0 | 1808 | typedef nsresult (*Control_FX) (SpdySession3 *self); |
michael@0 | 1809 | static const Control_FX sControlFunctions[] = |
michael@0 | 1810 | { |
michael@0 | 1811 | nullptr, |
michael@0 | 1812 | SpdySession3::HandleSynStream, |
michael@0 | 1813 | SpdySession3::HandleSynReply, |
michael@0 | 1814 | SpdySession3::HandleRstStream, |
michael@0 | 1815 | SpdySession3::HandleSettings, |
michael@0 | 1816 | SpdySession3::HandleNoop, |
michael@0 | 1817 | SpdySession3::HandlePing, |
michael@0 | 1818 | SpdySession3::HandleGoAway, |
michael@0 | 1819 | SpdySession3::HandleHeaders, |
michael@0 | 1820 | SpdySession3::HandleWindowUpdate, |
michael@0 | 1821 | SpdySession3::HandleCredential |
michael@0 | 1822 | }; |
michael@0 | 1823 | |
michael@0 | 1824 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 1825 | |
michael@0 | 1826 | nsresult rv; |
michael@0 | 1827 | *countWritten = 0; |
michael@0 | 1828 | |
michael@0 | 1829 | if (mClosed) |
michael@0 | 1830 | return NS_ERROR_FAILURE; |
michael@0 | 1831 | |
michael@0 | 1832 | SetWriteCallbacks(); |
michael@0 | 1833 | |
michael@0 | 1834 | // If there are http transactions attached to a push stream with filled buffers |
michael@0 | 1835 | // trigger that data pump here. This only reads from buffers (not the network) |
michael@0 | 1836 | // so mDownstreamState doesn't matter. |
michael@0 | 1837 | SpdyStream3 *pushConnectedStream = |
michael@0 | 1838 | static_cast<SpdyStream3 *>(mReadyForRead.PopFront()); |
michael@0 | 1839 | if (pushConnectedStream) { |
michael@0 | 1840 | LOG3(("SpdySession3::WriteSegments %p processing pushed stream 0x%X\n", |
michael@0 | 1841 | this, pushConnectedStream->StreamID())); |
michael@0 | 1842 | mSegmentWriter = writer; |
michael@0 | 1843 | rv = pushConnectedStream->WriteSegments(this, count, countWritten); |
michael@0 | 1844 | mSegmentWriter = nullptr; |
michael@0 | 1845 | |
michael@0 | 1846 | // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK |
michael@0 | 1847 | // so we need this check to determine the truth. |
michael@0 | 1848 | if (NS_SUCCEEDED(rv) && !*countWritten && |
michael@0 | 1849 | pushConnectedStream->PushSource() && |
michael@0 | 1850 | pushConnectedStream->PushSource()->GetPushComplete()) { |
michael@0 | 1851 | rv = NS_BASE_STREAM_CLOSED; |
michael@0 | 1852 | } |
michael@0 | 1853 | |
michael@0 | 1854 | if (rv == NS_BASE_STREAM_CLOSED) { |
michael@0 | 1855 | CleanupStream(pushConnectedStream, NS_OK, RST_CANCEL); |
michael@0 | 1856 | rv = NS_OK; |
michael@0 | 1857 | } |
michael@0 | 1858 | |
michael@0 | 1859 | // if we return OK to nsHttpConnection it will use mSocketInCondition |
michael@0 | 1860 | // to determine whether to schedule more reads, incorrectly |
michael@0 | 1861 | // assuming that nsHttpConnection::OnSocketWrite() was called. |
michael@0 | 1862 | if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) { |
michael@0 | 1863 | rv = NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 1864 | ResumeRecv(); |
michael@0 | 1865 | } |
michael@0 | 1866 | |
michael@0 | 1867 | return rv; |
michael@0 | 1868 | } |
michael@0 | 1869 | |
michael@0 | 1870 | // We buffer all control frames and act on them in this layer. |
michael@0 | 1871 | // We buffer the first 8 bytes of data frames (the header) but |
michael@0 | 1872 | // the actual data is passed through unprocessed. |
michael@0 | 1873 | |
michael@0 | 1874 | if (mDownstreamState == BUFFERING_FRAME_HEADER) { |
michael@0 | 1875 | // The first 8 bytes of every frame is header information that |
michael@0 | 1876 | // we are going to want to strip before passing to http. That is |
michael@0 | 1877 | // true of both control and data packets. |
michael@0 | 1878 | |
michael@0 | 1879 | MOZ_ASSERT(mInputFrameBufferUsed < 8, |
michael@0 | 1880 | "Frame Buffer Used Too Large for State"); |
michael@0 | 1881 | |
michael@0 | 1882 | rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed, |
michael@0 | 1883 | 8 - mInputFrameBufferUsed, countWritten); |
michael@0 | 1884 | |
michael@0 | 1885 | if (NS_FAILED(rv)) { |
michael@0 | 1886 | LOG3(("SpdySession3 %p buffering frame header read failure %x\n", |
michael@0 | 1887 | this, rv)); |
michael@0 | 1888 | // maybe just blocked reading from network |
michael@0 | 1889 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) |
michael@0 | 1890 | rv = NS_OK; |
michael@0 | 1891 | return rv; |
michael@0 | 1892 | } |
michael@0 | 1893 | |
michael@0 | 1894 | LogIO(this, nullptr, "Reading Frame Header", |
michael@0 | 1895 | mInputFrameBuffer + mInputFrameBufferUsed, *countWritten); |
michael@0 | 1896 | |
michael@0 | 1897 | mInputFrameBufferUsed += *countWritten; |
michael@0 | 1898 | |
michael@0 | 1899 | if (mInputFrameBufferUsed < 8) |
michael@0 | 1900 | { |
michael@0 | 1901 | LOG3(("SpdySession3::WriteSegments %p " |
michael@0 | 1902 | "BUFFERING FRAME HEADER incomplete size=%d", |
michael@0 | 1903 | this, mInputFrameBufferUsed)); |
michael@0 | 1904 | return rv; |
michael@0 | 1905 | } |
michael@0 | 1906 | |
michael@0 | 1907 | // For both control and data frames the second 32 bit word of the header |
michael@0 | 1908 | // is 8-flags, 24-length. (network byte order) |
michael@0 | 1909 | mInputFrameDataSize = |
michael@0 | 1910 | NetworkEndian::readUint32(mInputFrameBuffer + 1 * sizeof(uint32_t)); |
michael@0 | 1911 | mInputFrameDataSize &= 0x00ffffff; |
michael@0 | 1912 | mInputFrameDataRead = 0; |
michael@0 | 1913 | |
michael@0 | 1914 | if (mInputFrameBuffer[0] & kFlag_Control) { |
michael@0 | 1915 | EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + 8, 8, |
michael@0 | 1916 | mInputFrameBufferSize); |
michael@0 | 1917 | ChangeDownstreamState(BUFFERING_CONTROL_FRAME); |
michael@0 | 1918 | |
michael@0 | 1919 | // The first 32 bit word of the header is |
michael@0 | 1920 | // 1 ctrl - 15 version - 16 type |
michael@0 | 1921 | uint16_t version = NetworkEndian::readUint16(mInputFrameBuffer); |
michael@0 | 1922 | version &= 0x7fff; |
michael@0 | 1923 | |
michael@0 | 1924 | mFrameControlType = |
michael@0 | 1925 | NetworkEndian::readUint16(mInputFrameBuffer + sizeof(uint16_t)); |
michael@0 | 1926 | |
michael@0 | 1927 | LOG3(("SpdySession3::WriteSegments %p - Control Frame Identified " |
michael@0 | 1928 | "type %d version %d data len %d", |
michael@0 | 1929 | this, mFrameControlType, version, mInputFrameDataSize)); |
michael@0 | 1930 | |
michael@0 | 1931 | if (mFrameControlType >= CONTROL_TYPE_LAST || |
michael@0 | 1932 | mFrameControlType <= CONTROL_TYPE_FIRST) |
michael@0 | 1933 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1934 | |
michael@0 | 1935 | if (version != kVersion) |
michael@0 | 1936 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 1937 | } |
michael@0 | 1938 | else { |
michael@0 | 1939 | ChangeDownstreamState(PROCESSING_DATA_FRAME); |
michael@0 | 1940 | |
michael@0 | 1941 | Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, |
michael@0 | 1942 | mInputFrameDataSize >> 10); |
michael@0 | 1943 | mLastDataReadEpoch = mLastReadEpoch; |
michael@0 | 1944 | |
michael@0 | 1945 | uint32_t streamID = NetworkEndian::readUint32(mInputFrameBuffer); |
michael@0 | 1946 | rv = SetInputFrameDataStream(streamID); |
michael@0 | 1947 | if (NS_FAILED(rv)) { |
michael@0 | 1948 | LOG(("SpdySession3::WriteSegments %p lookup streamID 0x%X failed. " |
michael@0 | 1949 | "probably due to verification.\n", this, streamID)); |
michael@0 | 1950 | return rv; |
michael@0 | 1951 | } |
michael@0 | 1952 | if (!mInputFrameDataStream) { |
michael@0 | 1953 | LOG3(("SpdySession3::WriteSegments %p lookup streamID 0x%X failed. " |
michael@0 | 1954 | "Next = 0x%X", this, streamID, mNextStreamID)); |
michael@0 | 1955 | if (streamID >= mNextStreamID) |
michael@0 | 1956 | GenerateRstStream(RST_INVALID_STREAM, streamID); |
michael@0 | 1957 | ChangeDownstreamState(DISCARDING_DATA_FRAME); |
michael@0 | 1958 | } |
michael@0 | 1959 | else if (mInputFrameDataStream->RecvdFin()) { |
michael@0 | 1960 | LOG3(("SpdySession3::WriteSegments %p streamID 0x%X " |
michael@0 | 1961 | "Data arrived for already server closed stream.\n", |
michael@0 | 1962 | this, streamID)); |
michael@0 | 1963 | GenerateRstStream(RST_STREAM_ALREADY_CLOSED, streamID); |
michael@0 | 1964 | ChangeDownstreamState(DISCARDING_DATA_FRAME); |
michael@0 | 1965 | } |
michael@0 | 1966 | else if (!mInputFrameDataStream->RecvdData()) { |
michael@0 | 1967 | LOG3(("SpdySession3 %p First Data Frame Flushes Headers stream 0x%X\n", |
michael@0 | 1968 | this, streamID)); |
michael@0 | 1969 | |
michael@0 | 1970 | mInputFrameDataStream->SetRecvdData(true); |
michael@0 | 1971 | rv = ResponseHeadersComplete(); |
michael@0 | 1972 | if (rv == NS_ERROR_ILLEGAL_VALUE) { |
michael@0 | 1973 | LOG3(("SpdySession3 %p PROTOCOL_ERROR detected 0x%X\n", |
michael@0 | 1974 | this, streamID)); |
michael@0 | 1975 | CleanupStream(mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); |
michael@0 | 1976 | ChangeDownstreamState(DISCARDING_DATA_FRAME); |
michael@0 | 1977 | } |
michael@0 | 1978 | else { |
michael@0 | 1979 | mDataPending = true; |
michael@0 | 1980 | } |
michael@0 | 1981 | } |
michael@0 | 1982 | |
michael@0 | 1983 | mInputFrameDataLast = (mInputFrameBuffer[4] & kFlag_Data_FIN); |
michael@0 | 1984 | LOG3(("Start Processing Data Frame. " |
michael@0 | 1985 | "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d", |
michael@0 | 1986 | this, streamID, mInputFrameDataStream, mInputFrameDataLast, |
michael@0 | 1987 | mInputFrameDataSize)); |
michael@0 | 1988 | UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize); |
michael@0 | 1989 | } |
michael@0 | 1990 | } |
michael@0 | 1991 | |
michael@0 | 1992 | if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { |
michael@0 | 1993 | if (mDownstreamRstReason == RST_REFUSED_STREAM) |
michael@0 | 1994 | rv = NS_ERROR_NET_RESET; //we can retry this 100% safely |
michael@0 | 1995 | else if (mDownstreamRstReason == RST_CANCEL || |
michael@0 | 1996 | mDownstreamRstReason == RST_PROTOCOL_ERROR || |
michael@0 | 1997 | mDownstreamRstReason == RST_INTERNAL_ERROR || |
michael@0 | 1998 | mDownstreamRstReason == RST_UNSUPPORTED_VERSION) |
michael@0 | 1999 | rv = NS_ERROR_NET_INTERRUPT; |
michael@0 | 2000 | else if (mDownstreamRstReason == RST_FRAME_TOO_LARGE) |
michael@0 | 2001 | rv = NS_ERROR_FILE_TOO_BIG; |
michael@0 | 2002 | else |
michael@0 | 2003 | rv = NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 2004 | |
michael@0 | 2005 | if (mDownstreamRstReason != RST_REFUSED_STREAM && |
michael@0 | 2006 | mDownstreamRstReason != RST_CANCEL) |
michael@0 | 2007 | mShouldGoAway = true; |
michael@0 | 2008 | |
michael@0 | 2009 | // mInputFrameDataStream is reset by ChangeDownstreamState |
michael@0 | 2010 | SpdyStream3 *stream = mInputFrameDataStream; |
michael@0 | 2011 | ResetDownstreamState(); |
michael@0 | 2012 | LOG3(("SpdySession3::WriteSegments cleanup stream on recv of rst " |
michael@0 | 2013 | "session=%p stream=%p 0x%X\n", this, stream, |
michael@0 | 2014 | stream ? stream->StreamID() : 0)); |
michael@0 | 2015 | CleanupStream(stream, rv, RST_CANCEL); |
michael@0 | 2016 | return NS_OK; |
michael@0 | 2017 | } |
michael@0 | 2018 | |
michael@0 | 2019 | if (mDownstreamState == PROCESSING_DATA_FRAME || |
michael@0 | 2020 | mDownstreamState == PROCESSING_COMPLETE_HEADERS) { |
michael@0 | 2021 | |
michael@0 | 2022 | // The cleanup stream should only be set while stream->WriteSegments is |
michael@0 | 2023 | // on the stack and then cleaned up in this code block afterwards. |
michael@0 | 2024 | MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly"); |
michael@0 | 2025 | mNeedsCleanup = nullptr; /* just in case */ |
michael@0 | 2026 | |
michael@0 | 2027 | mSegmentWriter = writer; |
michael@0 | 2028 | rv = mInputFrameDataStream->WriteSegments(this, count, countWritten); |
michael@0 | 2029 | mSegmentWriter = nullptr; |
michael@0 | 2030 | |
michael@0 | 2031 | mLastDataReadEpoch = mLastReadEpoch; |
michael@0 | 2032 | |
michael@0 | 2033 | if (SoftStreamError(rv)) { |
michael@0 | 2034 | // This will happen when the transaction figures out it is EOF, generally |
michael@0 | 2035 | // due to a content-length match being made. Return OK from this function |
michael@0 | 2036 | // otherwise the whole session would be torn down. |
michael@0 | 2037 | SpdyStream3 *stream = mInputFrameDataStream; |
michael@0 | 2038 | |
michael@0 | 2039 | // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state |
michael@0 | 2040 | // back to PROCESSING_DATA_FRAME where we came from |
michael@0 | 2041 | mDownstreamState = PROCESSING_DATA_FRAME; |
michael@0 | 2042 | |
michael@0 | 2043 | if (mInputFrameDataRead == mInputFrameDataSize) |
michael@0 | 2044 | ResetDownstreamState(); |
michael@0 | 2045 | LOG3(("SpdySession3::WriteSegments session=%p stream=%p 0x%X " |
michael@0 | 2046 | "needscleanup=%p. cleanup stream based on " |
michael@0 | 2047 | "stream->writeSegments returning code %X\n", |
michael@0 | 2048 | this, stream, stream ? stream->StreamID() : 0, |
michael@0 | 2049 | mNeedsCleanup, rv)); |
michael@0 | 2050 | CleanupStream(stream, NS_OK, RST_CANCEL); |
michael@0 | 2051 | MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame"); |
michael@0 | 2052 | mNeedsCleanup = nullptr; /* just in case */ |
michael@0 | 2053 | return NS_OK; |
michael@0 | 2054 | } |
michael@0 | 2055 | |
michael@0 | 2056 | if (mNeedsCleanup) { |
michael@0 | 2057 | LOG3(("SpdySession3::WriteSegments session=%p stream=%p 0x%X " |
michael@0 | 2058 | "cleanup stream based on mNeedsCleanup.\n", |
michael@0 | 2059 | this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0)); |
michael@0 | 2060 | CleanupStream(mNeedsCleanup, NS_OK, RST_CANCEL); |
michael@0 | 2061 | mNeedsCleanup = nullptr; |
michael@0 | 2062 | } |
michael@0 | 2063 | |
michael@0 | 2064 | if (NS_FAILED(rv)) { |
michael@0 | 2065 | LOG3(("SpdySession3 %p data frame read failure %x\n", this, rv)); |
michael@0 | 2066 | // maybe just blocked reading from network |
michael@0 | 2067 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) |
michael@0 | 2068 | rv = NS_OK; |
michael@0 | 2069 | } |
michael@0 | 2070 | |
michael@0 | 2071 | return rv; |
michael@0 | 2072 | } |
michael@0 | 2073 | |
michael@0 | 2074 | if (mDownstreamState == DISCARDING_DATA_FRAME) { |
michael@0 | 2075 | char trash[4096]; |
michael@0 | 2076 | uint32_t count = std::min(4096U, mInputFrameDataSize - mInputFrameDataRead); |
michael@0 | 2077 | |
michael@0 | 2078 | if (!count) { |
michael@0 | 2079 | ResetDownstreamState(); |
michael@0 | 2080 | ResumeRecv(); |
michael@0 | 2081 | return NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 2082 | } |
michael@0 | 2083 | |
michael@0 | 2084 | rv = NetworkRead(writer, trash, count, countWritten); |
michael@0 | 2085 | |
michael@0 | 2086 | if (NS_FAILED(rv)) { |
michael@0 | 2087 | LOG3(("SpdySession3 %p discard frame read failure %x\n", this, rv)); |
michael@0 | 2088 | // maybe just blocked reading from network |
michael@0 | 2089 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) |
michael@0 | 2090 | rv = NS_OK; |
michael@0 | 2091 | return rv; |
michael@0 | 2092 | } |
michael@0 | 2093 | |
michael@0 | 2094 | LogIO(this, nullptr, "Discarding Frame", trash, *countWritten); |
michael@0 | 2095 | |
michael@0 | 2096 | mInputFrameDataRead += *countWritten; |
michael@0 | 2097 | |
michael@0 | 2098 | if (mInputFrameDataRead == mInputFrameDataSize) |
michael@0 | 2099 | ResetDownstreamState(); |
michael@0 | 2100 | return rv; |
michael@0 | 2101 | } |
michael@0 | 2102 | |
michael@0 | 2103 | MOZ_ASSERT(mDownstreamState == BUFFERING_CONTROL_FRAME); |
michael@0 | 2104 | if (mDownstreamState != BUFFERING_CONTROL_FRAME) { |
michael@0 | 2105 | // this cannot happen |
michael@0 | 2106 | return NS_ERROR_UNEXPECTED; |
michael@0 | 2107 | } |
michael@0 | 2108 | |
michael@0 | 2109 | MOZ_ASSERT(mInputFrameBufferUsed == 8, |
michael@0 | 2110 | "Frame Buffer Header Not Present"); |
michael@0 | 2111 | |
michael@0 | 2112 | rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead, |
michael@0 | 2113 | mInputFrameDataSize - mInputFrameDataRead, countWritten); |
michael@0 | 2114 | |
michael@0 | 2115 | if (NS_FAILED(rv)) { |
michael@0 | 2116 | LOG3(("SpdySession3 %p buffering control frame read failure %x\n", |
michael@0 | 2117 | this, rv)); |
michael@0 | 2118 | // maybe just blocked reading from network |
michael@0 | 2119 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) |
michael@0 | 2120 | rv = NS_OK; |
michael@0 | 2121 | return rv; |
michael@0 | 2122 | } |
michael@0 | 2123 | |
michael@0 | 2124 | LogIO(this, nullptr, "Reading Control Frame", |
michael@0 | 2125 | mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten); |
michael@0 | 2126 | |
michael@0 | 2127 | mInputFrameDataRead += *countWritten; |
michael@0 | 2128 | |
michael@0 | 2129 | if (mInputFrameDataRead != mInputFrameDataSize) |
michael@0 | 2130 | return NS_OK; |
michael@0 | 2131 | |
michael@0 | 2132 | // This check is actually redundant, the control type was previously |
michael@0 | 2133 | // checked to make sure it was in range, but we will check it again |
michael@0 | 2134 | // at time of use to make sure a regression doesn't creep in. |
michael@0 | 2135 | if (mFrameControlType >= CONTROL_TYPE_LAST || |
michael@0 | 2136 | mFrameControlType <= CONTROL_TYPE_FIRST) |
michael@0 | 2137 | { |
michael@0 | 2138 | MOZ_ASSERT(false, "control type out of range"); |
michael@0 | 2139 | return NS_ERROR_ILLEGAL_VALUE; |
michael@0 | 2140 | } |
michael@0 | 2141 | rv = sControlFunctions[mFrameControlType](this); |
michael@0 | 2142 | |
michael@0 | 2143 | MOZ_ASSERT(NS_FAILED(rv) || |
michael@0 | 2144 | mDownstreamState != BUFFERING_CONTROL_FRAME, |
michael@0 | 2145 | "Control Handler returned OK but did not change state"); |
michael@0 | 2146 | |
michael@0 | 2147 | if (mShouldGoAway && !mStreamTransactionHash.Count()) |
michael@0 | 2148 | Close(NS_OK); |
michael@0 | 2149 | return rv; |
michael@0 | 2150 | } |
michael@0 | 2151 | |
michael@0 | 2152 | void |
michael@0 | 2153 | SpdySession3::UpdateLocalRwin(SpdyStream3 *stream, |
michael@0 | 2154 | uint32_t bytes) |
michael@0 | 2155 | { |
michael@0 | 2156 | // If this data packet was not for a valid or live stream then there |
michael@0 | 2157 | // is no reason to mess with the flow control |
michael@0 | 2158 | if (!stream || stream->RecvdFin()) |
michael@0 | 2159 | return; |
michael@0 | 2160 | |
michael@0 | 2161 | stream->DecrementLocalWindow(bytes); |
michael@0 | 2162 | |
michael@0 | 2163 | // Don't necessarily ack every data packet. Only do it |
michael@0 | 2164 | // after a significant amount of data. |
michael@0 | 2165 | uint64_t unacked = stream->LocalUnAcked(); |
michael@0 | 2166 | int64_t localWindow = stream->LocalWindow(); |
michael@0 | 2167 | |
michael@0 | 2168 | LOG3(("SpdySession3::UpdateLocalRwin this=%p id=0x%X newbytes=%u " |
michael@0 | 2169 | "unacked=%llu localWindow=%lld\n", |
michael@0 | 2170 | this, stream->StreamID(), bytes, unacked, localWindow)); |
michael@0 | 2171 | |
michael@0 | 2172 | if (!unacked) |
michael@0 | 2173 | return; |
michael@0 | 2174 | |
michael@0 | 2175 | if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) |
michael@0 | 2176 | return; |
michael@0 | 2177 | |
michael@0 | 2178 | if (!stream->HasSink()) { |
michael@0 | 2179 | LOG3(("SpdySession3::UpdateLocalRwin %p 0x%X Pushed Stream Has No Sink\n", |
michael@0 | 2180 | this, stream->StreamID())); |
michael@0 | 2181 | return; |
michael@0 | 2182 | } |
michael@0 | 2183 | |
michael@0 | 2184 | // Generate window updates directly out of spdysession instead of the stream |
michael@0 | 2185 | // in order to avoid queue delays in getting the 'ACK' out. |
michael@0 | 2186 | uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU; |
michael@0 | 2187 | |
michael@0 | 2188 | LOG3(("SpdySession3::UpdateLocalRwin Ack this=%p id=0x%X acksize=%d\n", |
michael@0 | 2189 | this, stream->StreamID(), toack)); |
michael@0 | 2190 | stream->IncrementLocalWindow(toack); |
michael@0 | 2191 | |
michael@0 | 2192 | static const uint32_t dataLen = 8; |
michael@0 | 2193 | EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 8 + dataLen, |
michael@0 | 2194 | mOutputQueueUsed, mOutputQueueSize); |
michael@0 | 2195 | char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; |
michael@0 | 2196 | mOutputQueueUsed += 8 + dataLen; |
michael@0 | 2197 | |
michael@0 | 2198 | memset(packet, 0, 8 + dataLen); |
michael@0 | 2199 | packet[0] = kFlag_Control; |
michael@0 | 2200 | packet[1] = kVersion; |
michael@0 | 2201 | packet[3] = CONTROL_TYPE_WINDOW_UPDATE; |
michael@0 | 2202 | packet[7] = dataLen; |
michael@0 | 2203 | |
michael@0 | 2204 | NetworkEndian::writeUint32(packet + 8, stream->StreamID()); |
michael@0 | 2205 | NetworkEndian::writeUint32(packet + 12, toack); |
michael@0 | 2206 | |
michael@0 | 2207 | LogIO(this, stream, "Window Update", packet, 8 + dataLen); |
michael@0 | 2208 | FlushOutputQueue(); |
michael@0 | 2209 | } |
michael@0 | 2210 | |
michael@0 | 2211 | void |
michael@0 | 2212 | SpdySession3::Close(nsresult aReason) |
michael@0 | 2213 | { |
michael@0 | 2214 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2215 | |
michael@0 | 2216 | if (mClosed) |
michael@0 | 2217 | return; |
michael@0 | 2218 | |
michael@0 | 2219 | LOG3(("SpdySession3::Close %p %X", this, aReason)); |
michael@0 | 2220 | |
michael@0 | 2221 | mClosed = true; |
michael@0 | 2222 | |
michael@0 | 2223 | mStreamTransactionHash.Enumerate(ShutdownEnumerator, this); |
michael@0 | 2224 | mStreamIDHash.Clear(); |
michael@0 | 2225 | mStreamTransactionHash.Clear(); |
michael@0 | 2226 | |
michael@0 | 2227 | uint32_t goAwayReason; |
michael@0 | 2228 | if (NS_SUCCEEDED(aReason)) { |
michael@0 | 2229 | goAwayReason = OK; |
michael@0 | 2230 | } else if (aReason == NS_ERROR_ILLEGAL_VALUE) { |
michael@0 | 2231 | goAwayReason = PROTOCOL_ERROR; |
michael@0 | 2232 | } else { |
michael@0 | 2233 | goAwayReason = INTERNAL_ERROR; |
michael@0 | 2234 | } |
michael@0 | 2235 | GenerateGoAway(goAwayReason); |
michael@0 | 2236 | mConnection = nullptr; |
michael@0 | 2237 | mSegmentReader = nullptr; |
michael@0 | 2238 | mSegmentWriter = nullptr; |
michael@0 | 2239 | } |
michael@0 | 2240 | |
michael@0 | 2241 | void |
michael@0 | 2242 | SpdySession3::CloseTransaction(nsAHttpTransaction *aTransaction, |
michael@0 | 2243 | nsresult aResult) |
michael@0 | 2244 | { |
michael@0 | 2245 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2246 | LOG3(("SpdySession3::CloseTransaction %p %p %x", this, aTransaction, aResult)); |
michael@0 | 2247 | |
michael@0 | 2248 | // Generally this arrives as a cancel event from the connection manager. |
michael@0 | 2249 | |
michael@0 | 2250 | // need to find the stream and call CleanupStream() on it. |
michael@0 | 2251 | SpdyStream3 *stream = mStreamTransactionHash.Get(aTransaction); |
michael@0 | 2252 | if (!stream) { |
michael@0 | 2253 | LOG3(("SpdySession3::CloseTransaction %p %p %x - not found.", |
michael@0 | 2254 | this, aTransaction, aResult)); |
michael@0 | 2255 | return; |
michael@0 | 2256 | } |
michael@0 | 2257 | LOG3(("SpdySession3::CloseTranscation probably a cancel. " |
michael@0 | 2258 | "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p", |
michael@0 | 2259 | this, aTransaction, aResult, stream->StreamID(), stream)); |
michael@0 | 2260 | CleanupStream(stream, aResult, RST_CANCEL); |
michael@0 | 2261 | ResumeRecv(); |
michael@0 | 2262 | } |
michael@0 | 2263 | |
michael@0 | 2264 | |
michael@0 | 2265 | //----------------------------------------------------------------------------- |
michael@0 | 2266 | // nsAHttpSegmentReader |
michael@0 | 2267 | //----------------------------------------------------------------------------- |
michael@0 | 2268 | |
michael@0 | 2269 | nsresult |
michael@0 | 2270 | SpdySession3::OnReadSegment(const char *buf, |
michael@0 | 2271 | uint32_t count, |
michael@0 | 2272 | uint32_t *countRead) |
michael@0 | 2273 | { |
michael@0 | 2274 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2275 | |
michael@0 | 2276 | nsresult rv; |
michael@0 | 2277 | |
michael@0 | 2278 | // If we can release old queued data then we can try and write the new |
michael@0 | 2279 | // data directly to the network without using the output queue at all |
michael@0 | 2280 | if (mOutputQueueUsed) |
michael@0 | 2281 | FlushOutputQueue(); |
michael@0 | 2282 | |
michael@0 | 2283 | if (!mOutputQueueUsed && mSegmentReader) { |
michael@0 | 2284 | // try and write directly without output queue |
michael@0 | 2285 | rv = mSegmentReader->OnReadSegment(buf, count, countRead); |
michael@0 | 2286 | |
michael@0 | 2287 | if (rv == NS_BASE_STREAM_WOULD_BLOCK) |
michael@0 | 2288 | *countRead = 0; |
michael@0 | 2289 | else if (NS_FAILED(rv)) |
michael@0 | 2290 | return rv; |
michael@0 | 2291 | |
michael@0 | 2292 | if (*countRead < count) { |
michael@0 | 2293 | uint32_t required = count - *countRead; |
michael@0 | 2294 | // assuming a commitment() happened, this ensurebuffer is a nop |
michael@0 | 2295 | // but just in case the queuesize is too small for the required data |
michael@0 | 2296 | // call ensurebuffer(). |
michael@0 | 2297 | EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize); |
michael@0 | 2298 | memcpy(mOutputQueueBuffer.get(), buf + *countRead, required); |
michael@0 | 2299 | mOutputQueueUsed = required; |
michael@0 | 2300 | } |
michael@0 | 2301 | |
michael@0 | 2302 | *countRead = count; |
michael@0 | 2303 | return NS_OK; |
michael@0 | 2304 | } |
michael@0 | 2305 | |
michael@0 | 2306 | // At this point we are going to buffer the new data in the output |
michael@0 | 2307 | // queue if it fits. By coalescing multiple small submissions into one larger |
michael@0 | 2308 | // buffer we can get larger writes out to the network later on. |
michael@0 | 2309 | |
michael@0 | 2310 | // This routine should not be allowed to fill up the output queue |
michael@0 | 2311 | // all on its own - at least kQueueReserved bytes are always left |
michael@0 | 2312 | // for other routines to use - but this is an all-or-nothing function, |
michael@0 | 2313 | // so if it will not all fit just return WOULD_BLOCK |
michael@0 | 2314 | |
michael@0 | 2315 | if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved)) |
michael@0 | 2316 | return NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 2317 | |
michael@0 | 2318 | memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count); |
michael@0 | 2319 | mOutputQueueUsed += count; |
michael@0 | 2320 | *countRead = count; |
michael@0 | 2321 | |
michael@0 | 2322 | FlushOutputQueue(); |
michael@0 | 2323 | |
michael@0 | 2324 | return NS_OK; |
michael@0 | 2325 | } |
michael@0 | 2326 | |
michael@0 | 2327 | nsresult |
michael@0 | 2328 | SpdySession3::CommitToSegmentSize(uint32_t count, bool forceCommitment) |
michael@0 | 2329 | { |
michael@0 | 2330 | if (mOutputQueueUsed) |
michael@0 | 2331 | FlushOutputQueue(); |
michael@0 | 2332 | |
michael@0 | 2333 | // would there be enough room to buffer this if needed? |
michael@0 | 2334 | if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) |
michael@0 | 2335 | return NS_OK; |
michael@0 | 2336 | |
michael@0 | 2337 | // if we are using part of our buffers already, try again later unless |
michael@0 | 2338 | // forceCommitment is set. |
michael@0 | 2339 | if (mOutputQueueUsed && !forceCommitment) |
michael@0 | 2340 | return NS_BASE_STREAM_WOULD_BLOCK; |
michael@0 | 2341 | |
michael@0 | 2342 | if (mOutputQueueUsed) { |
michael@0 | 2343 | // normally we avoid the memmove of RealignOutputQueue, but we'll try |
michael@0 | 2344 | // it if forceCommitment is set before growing the buffer. |
michael@0 | 2345 | RealignOutputQueue(); |
michael@0 | 2346 | |
michael@0 | 2347 | // is there enough room now? |
michael@0 | 2348 | if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved)) |
michael@0 | 2349 | return NS_OK; |
michael@0 | 2350 | } |
michael@0 | 2351 | |
michael@0 | 2352 | // resize the buffers as needed |
michael@0 | 2353 | EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + count + kQueueReserved, |
michael@0 | 2354 | mOutputQueueUsed, mOutputQueueSize); |
michael@0 | 2355 | |
michael@0 | 2356 | MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved), |
michael@0 | 2357 | "buffer not as large as expected"); |
michael@0 | 2358 | |
michael@0 | 2359 | return NS_OK; |
michael@0 | 2360 | } |
michael@0 | 2361 | |
michael@0 | 2362 | //----------------------------------------------------------------------------- |
michael@0 | 2363 | // nsAHttpSegmentWriter |
michael@0 | 2364 | //----------------------------------------------------------------------------- |
michael@0 | 2365 | |
michael@0 | 2366 | nsresult |
michael@0 | 2367 | SpdySession3::OnWriteSegment(char *buf, |
michael@0 | 2368 | uint32_t count, |
michael@0 | 2369 | uint32_t *countWritten) |
michael@0 | 2370 | { |
michael@0 | 2371 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2372 | nsresult rv; |
michael@0 | 2373 | |
michael@0 | 2374 | if (!mSegmentWriter) { |
michael@0 | 2375 | // the only way this could happen would be if Close() were called on the |
michael@0 | 2376 | // stack with WriteSegments() |
michael@0 | 2377 | return NS_ERROR_FAILURE; |
michael@0 | 2378 | } |
michael@0 | 2379 | |
michael@0 | 2380 | if (mDownstreamState == PROCESSING_DATA_FRAME) { |
michael@0 | 2381 | |
michael@0 | 2382 | if (mInputFrameDataLast && |
michael@0 | 2383 | mInputFrameDataRead == mInputFrameDataSize) { |
michael@0 | 2384 | *countWritten = 0; |
michael@0 | 2385 | SetNeedsCleanup(); |
michael@0 | 2386 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 2387 | } |
michael@0 | 2388 | |
michael@0 | 2389 | count = std::min(count, mInputFrameDataSize - mInputFrameDataRead); |
michael@0 | 2390 | rv = NetworkRead(mSegmentWriter, buf, count, countWritten); |
michael@0 | 2391 | if (NS_FAILED(rv)) |
michael@0 | 2392 | return rv; |
michael@0 | 2393 | |
michael@0 | 2394 | LogIO(this, mInputFrameDataStream, "Reading Data Frame", |
michael@0 | 2395 | buf, *countWritten); |
michael@0 | 2396 | |
michael@0 | 2397 | mInputFrameDataRead += *countWritten; |
michael@0 | 2398 | |
michael@0 | 2399 | mInputFrameDataStream->UpdateTransportReadEvents(*countWritten); |
michael@0 | 2400 | if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameDataLast) |
michael@0 | 2401 | ResetDownstreamState(); |
michael@0 | 2402 | |
michael@0 | 2403 | return rv; |
michael@0 | 2404 | } |
michael@0 | 2405 | |
michael@0 | 2406 | if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) { |
michael@0 | 2407 | |
michael@0 | 2408 | if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && |
michael@0 | 2409 | mInputFrameDataLast) { |
michael@0 | 2410 | *countWritten = 0; |
michael@0 | 2411 | SetNeedsCleanup(); |
michael@0 | 2412 | return NS_BASE_STREAM_CLOSED; |
michael@0 | 2413 | } |
michael@0 | 2414 | |
michael@0 | 2415 | count = std::min(count, |
michael@0 | 2416 | mFlatHTTPResponseHeaders.Length() - |
michael@0 | 2417 | mFlatHTTPResponseHeadersOut); |
michael@0 | 2418 | memcpy(buf, |
michael@0 | 2419 | mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut, |
michael@0 | 2420 | count); |
michael@0 | 2421 | mFlatHTTPResponseHeadersOut += count; |
michael@0 | 2422 | *countWritten = count; |
michael@0 | 2423 | |
michael@0 | 2424 | if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) { |
michael@0 | 2425 | if (mDataPending) { |
michael@0 | 2426 | // Now ready to process data frames - pop PROCESING_DATA_FRAME back onto |
michael@0 | 2427 | // the stack because receipt of that first data frame triggered the |
michael@0 | 2428 | // response header processing |
michael@0 | 2429 | mDataPending = false; |
michael@0 | 2430 | ChangeDownstreamState(PROCESSING_DATA_FRAME); |
michael@0 | 2431 | } |
michael@0 | 2432 | else if (!mInputFrameDataLast) { |
michael@0 | 2433 | // If more frames are expected in this stream, then reset the state so they can be |
michael@0 | 2434 | // handled. Otherwise (e.g. a 0 length response with the fin on the SYN_REPLY) |
michael@0 | 2435 | // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can |
michael@0 | 2436 | // cleanup the stream. |
michael@0 | 2437 | ResetDownstreamState(); |
michael@0 | 2438 | } |
michael@0 | 2439 | } |
michael@0 | 2440 | |
michael@0 | 2441 | return NS_OK; |
michael@0 | 2442 | } |
michael@0 | 2443 | |
michael@0 | 2444 | return NS_ERROR_UNEXPECTED; |
michael@0 | 2445 | } |
michael@0 | 2446 | |
michael@0 | 2447 | void |
michael@0 | 2448 | SpdySession3::SetNeedsCleanup() |
michael@0 | 2449 | { |
michael@0 | 2450 | LOG3(("SpdySession3::SetNeedsCleanup %p - recorded downstream fin of " |
michael@0 | 2451 | "stream %p 0x%X", this, mInputFrameDataStream, |
michael@0 | 2452 | mInputFrameDataStream->StreamID())); |
michael@0 | 2453 | |
michael@0 | 2454 | // This will result in Close() being called |
michael@0 | 2455 | MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set"); |
michael@0 | 2456 | mNeedsCleanup = mInputFrameDataStream; |
michael@0 | 2457 | ResetDownstreamState(); |
michael@0 | 2458 | } |
michael@0 | 2459 | |
michael@0 | 2460 | void |
michael@0 | 2461 | SpdySession3::ConnectPushedStream(SpdyStream3 *stream) |
michael@0 | 2462 | { |
michael@0 | 2463 | mReadyForRead.Push(stream); |
michael@0 | 2464 | ForceRecv(); |
michael@0 | 2465 | } |
michael@0 | 2466 | |
michael@0 | 2467 | //----------------------------------------------------------------------------- |
michael@0 | 2468 | // Modified methods of nsAHttpConnection |
michael@0 | 2469 | //----------------------------------------------------------------------------- |
michael@0 | 2470 | |
michael@0 | 2471 | void |
michael@0 | 2472 | SpdySession3::TransactionHasDataToWrite(nsAHttpTransaction *caller) |
michael@0 | 2473 | { |
michael@0 | 2474 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2475 | LOG3(("SpdySession3::TransactionHasDataToWrite %p trans=%p", this, caller)); |
michael@0 | 2476 | |
michael@0 | 2477 | // a trapped signal from the http transaction to the connection that |
michael@0 | 2478 | // it is no longer blocked on read. |
michael@0 | 2479 | |
michael@0 | 2480 | SpdyStream3 *stream = mStreamTransactionHash.Get(caller); |
michael@0 | 2481 | if (!stream || !VerifyStream(stream)) { |
michael@0 | 2482 | LOG3(("SpdySession3::TransactionHasDataToWrite %p caller %p not found", |
michael@0 | 2483 | this, caller)); |
michael@0 | 2484 | return; |
michael@0 | 2485 | } |
michael@0 | 2486 | |
michael@0 | 2487 | LOG3(("SpdySession3::TransactionHasDataToWrite %p ID is 0x%X\n", |
michael@0 | 2488 | this, stream->StreamID())); |
michael@0 | 2489 | |
michael@0 | 2490 | mReadyForWrite.Push(stream); |
michael@0 | 2491 | } |
michael@0 | 2492 | |
michael@0 | 2493 | void |
michael@0 | 2494 | SpdySession3::TransactionHasDataToWrite(SpdyStream3 *stream) |
michael@0 | 2495 | { |
michael@0 | 2496 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2497 | LOG3(("SpdySession3::TransactionHasDataToWrite %p stream=%p ID=%x", |
michael@0 | 2498 | this, stream, stream->StreamID())); |
michael@0 | 2499 | |
michael@0 | 2500 | mReadyForWrite.Push(stream); |
michael@0 | 2501 | SetWriteCallbacks(); |
michael@0 | 2502 | } |
michael@0 | 2503 | |
michael@0 | 2504 | bool |
michael@0 | 2505 | SpdySession3::IsPersistent() |
michael@0 | 2506 | { |
michael@0 | 2507 | return true; |
michael@0 | 2508 | } |
michael@0 | 2509 | |
michael@0 | 2510 | nsresult |
michael@0 | 2511 | SpdySession3::TakeTransport(nsISocketTransport **, |
michael@0 | 2512 | nsIAsyncInputStream **, |
michael@0 | 2513 | nsIAsyncOutputStream **) |
michael@0 | 2514 | { |
michael@0 | 2515 | MOZ_ASSERT(false, "TakeTransport of SpdySession3"); |
michael@0 | 2516 | return NS_ERROR_UNEXPECTED; |
michael@0 | 2517 | } |
michael@0 | 2518 | |
michael@0 | 2519 | nsHttpConnection * |
michael@0 | 2520 | SpdySession3::TakeHttpConnection() |
michael@0 | 2521 | { |
michael@0 | 2522 | MOZ_ASSERT(false, "TakeHttpConnection of SpdySession3"); |
michael@0 | 2523 | return nullptr; |
michael@0 | 2524 | } |
michael@0 | 2525 | |
michael@0 | 2526 | uint32_t |
michael@0 | 2527 | SpdySession3::CancelPipeline(nsresult reason) |
michael@0 | 2528 | { |
michael@0 | 2529 | // we don't pipeline inside spdy, so this isn't an issue |
michael@0 | 2530 | return 0; |
michael@0 | 2531 | } |
michael@0 | 2532 | |
michael@0 | 2533 | nsAHttpTransaction::Classifier |
michael@0 | 2534 | SpdySession3::Classification() |
michael@0 | 2535 | { |
michael@0 | 2536 | if (!mConnection) |
michael@0 | 2537 | return nsAHttpTransaction::CLASS_GENERAL; |
michael@0 | 2538 | return mConnection->Classification(); |
michael@0 | 2539 | } |
michael@0 | 2540 | |
michael@0 | 2541 | //----------------------------------------------------------------------------- |
michael@0 | 2542 | // unused methods of nsAHttpTransaction |
michael@0 | 2543 | // We can be sure of this because SpdySession3 is only constructed in |
michael@0 | 2544 | // nsHttpConnection and is never passed out of that object |
michael@0 | 2545 | //----------------------------------------------------------------------------- |
michael@0 | 2546 | |
michael@0 | 2547 | void |
michael@0 | 2548 | SpdySession3::SetConnection(nsAHttpConnection *) |
michael@0 | 2549 | { |
michael@0 | 2550 | // This is unexpected |
michael@0 | 2551 | MOZ_ASSERT(false, "SpdySession3::SetConnection()"); |
michael@0 | 2552 | } |
michael@0 | 2553 | |
michael@0 | 2554 | void |
michael@0 | 2555 | SpdySession3::GetSecurityCallbacks(nsIInterfaceRequestor **) |
michael@0 | 2556 | { |
michael@0 | 2557 | // This is unexpected |
michael@0 | 2558 | MOZ_ASSERT(false, "SpdySession3::GetSecurityCallbacks()"); |
michael@0 | 2559 | } |
michael@0 | 2560 | |
michael@0 | 2561 | void |
michael@0 | 2562 | SpdySession3::SetProxyConnectFailed() |
michael@0 | 2563 | { |
michael@0 | 2564 | MOZ_ASSERT(false, "SpdySession3::SetProxyConnectFailed()"); |
michael@0 | 2565 | } |
michael@0 | 2566 | |
michael@0 | 2567 | bool |
michael@0 | 2568 | SpdySession3::IsDone() |
michael@0 | 2569 | { |
michael@0 | 2570 | return !mStreamTransactionHash.Count(); |
michael@0 | 2571 | } |
michael@0 | 2572 | |
michael@0 | 2573 | nsresult |
michael@0 | 2574 | SpdySession3::Status() |
michael@0 | 2575 | { |
michael@0 | 2576 | MOZ_ASSERT(false, "SpdySession3::Status()"); |
michael@0 | 2577 | return NS_ERROR_UNEXPECTED; |
michael@0 | 2578 | } |
michael@0 | 2579 | |
michael@0 | 2580 | uint32_t |
michael@0 | 2581 | SpdySession3::Caps() |
michael@0 | 2582 | { |
michael@0 | 2583 | MOZ_ASSERT(false, "SpdySession3::Caps()"); |
michael@0 | 2584 | return 0; |
michael@0 | 2585 | } |
michael@0 | 2586 | |
michael@0 | 2587 | void |
michael@0 | 2588 | SpdySession3::SetDNSWasRefreshed() |
michael@0 | 2589 | { |
michael@0 | 2590 | } |
michael@0 | 2591 | |
michael@0 | 2592 | uint64_t |
michael@0 | 2593 | SpdySession3::Available() |
michael@0 | 2594 | { |
michael@0 | 2595 | MOZ_ASSERT(false, "SpdySession3::Available()"); |
michael@0 | 2596 | return 0; |
michael@0 | 2597 | } |
michael@0 | 2598 | |
michael@0 | 2599 | nsHttpRequestHead * |
michael@0 | 2600 | SpdySession3::RequestHead() |
michael@0 | 2601 | { |
michael@0 | 2602 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2603 | MOZ_ASSERT(false, |
michael@0 | 2604 | "SpdySession3::RequestHead() " |
michael@0 | 2605 | "should not be called after SPDY is setup"); |
michael@0 | 2606 | return nullptr; |
michael@0 | 2607 | } |
michael@0 | 2608 | |
michael@0 | 2609 | uint32_t |
michael@0 | 2610 | SpdySession3::Http1xTransactionCount() |
michael@0 | 2611 | { |
michael@0 | 2612 | return 0; |
michael@0 | 2613 | } |
michael@0 | 2614 | |
michael@0 | 2615 | // used as an enumerator by TakeSubTransactions() |
michael@0 | 2616 | static PLDHashOperator |
michael@0 | 2617 | TakeStream(nsAHttpTransaction *key, |
michael@0 | 2618 | nsAutoPtr<SpdyStream3> &stream, |
michael@0 | 2619 | void *closure) |
michael@0 | 2620 | { |
michael@0 | 2621 | nsTArray<nsRefPtr<nsAHttpTransaction> > *list = |
michael@0 | 2622 | static_cast<nsTArray<nsRefPtr<nsAHttpTransaction> > *>(closure); |
michael@0 | 2623 | |
michael@0 | 2624 | list->AppendElement(key); |
michael@0 | 2625 | |
michael@0 | 2626 | // removing the stream from the hash will delete the stream |
michael@0 | 2627 | // and drop the transaction reference the hash held |
michael@0 | 2628 | return PL_DHASH_REMOVE; |
michael@0 | 2629 | } |
michael@0 | 2630 | |
michael@0 | 2631 | nsresult |
michael@0 | 2632 | SpdySession3::TakeSubTransactions( |
michael@0 | 2633 | nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions) |
michael@0 | 2634 | { |
michael@0 | 2635 | // Generally this cannot be done with spdy as transactions are |
michael@0 | 2636 | // started right away. |
michael@0 | 2637 | |
michael@0 | 2638 | LOG3(("SpdySession3::TakeSubTransactions %p\n", this)); |
michael@0 | 2639 | |
michael@0 | 2640 | if (mConcurrentHighWater > 0) |
michael@0 | 2641 | return NS_ERROR_ALREADY_OPENED; |
michael@0 | 2642 | |
michael@0 | 2643 | LOG3((" taking %d\n", mStreamTransactionHash.Count())); |
michael@0 | 2644 | |
michael@0 | 2645 | mStreamTransactionHash.Enumerate(TakeStream, &outTransactions); |
michael@0 | 2646 | return NS_OK; |
michael@0 | 2647 | } |
michael@0 | 2648 | |
michael@0 | 2649 | nsresult |
michael@0 | 2650 | SpdySession3::AddTransaction(nsAHttpTransaction *) |
michael@0 | 2651 | { |
michael@0 | 2652 | // This API is meant for pipelining, SpdySession3's should be |
michael@0 | 2653 | // extended with AddStream() |
michael@0 | 2654 | |
michael@0 | 2655 | MOZ_ASSERT(false, |
michael@0 | 2656 | "SpdySession3::AddTransaction() should not be called"); |
michael@0 | 2657 | |
michael@0 | 2658 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 2659 | } |
michael@0 | 2660 | |
michael@0 | 2661 | uint32_t |
michael@0 | 2662 | SpdySession3::PipelineDepth() |
michael@0 | 2663 | { |
michael@0 | 2664 | return IsDone() ? 0 : 1; |
michael@0 | 2665 | } |
michael@0 | 2666 | |
michael@0 | 2667 | nsresult |
michael@0 | 2668 | SpdySession3::SetPipelinePosition(int32_t position) |
michael@0 | 2669 | { |
michael@0 | 2670 | // This API is meant for pipelining, SpdySession3's should be |
michael@0 | 2671 | // extended with AddStream() |
michael@0 | 2672 | |
michael@0 | 2673 | MOZ_ASSERT(false, |
michael@0 | 2674 | "SpdySession3::SetPipelinePosition() should not be called"); |
michael@0 | 2675 | |
michael@0 | 2676 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 2677 | } |
michael@0 | 2678 | |
michael@0 | 2679 | int32_t |
michael@0 | 2680 | SpdySession3::PipelinePosition() |
michael@0 | 2681 | { |
michael@0 | 2682 | return 0; |
michael@0 | 2683 | } |
michael@0 | 2684 | |
michael@0 | 2685 | //----------------------------------------------------------------------------- |
michael@0 | 2686 | // Pass through methods of nsAHttpConnection |
michael@0 | 2687 | //----------------------------------------------------------------------------- |
michael@0 | 2688 | |
michael@0 | 2689 | nsAHttpConnection * |
michael@0 | 2690 | SpdySession3::Connection() |
michael@0 | 2691 | { |
michael@0 | 2692 | MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
michael@0 | 2693 | return mConnection; |
michael@0 | 2694 | } |
michael@0 | 2695 | |
michael@0 | 2696 | nsresult |
michael@0 | 2697 | SpdySession3::OnHeadersAvailable(nsAHttpTransaction *transaction, |
michael@0 | 2698 | nsHttpRequestHead *requestHead, |
michael@0 | 2699 | nsHttpResponseHead *responseHead, |
michael@0 | 2700 | bool *reset) |
michael@0 | 2701 | { |
michael@0 | 2702 | return mConnection->OnHeadersAvailable(transaction, |
michael@0 | 2703 | requestHead, |
michael@0 | 2704 | responseHead, |
michael@0 | 2705 | reset); |
michael@0 | 2706 | } |
michael@0 | 2707 | |
michael@0 | 2708 | bool |
michael@0 | 2709 | SpdySession3::IsReused() |
michael@0 | 2710 | { |
michael@0 | 2711 | return mConnection->IsReused(); |
michael@0 | 2712 | } |
michael@0 | 2713 | |
michael@0 | 2714 | nsresult |
michael@0 | 2715 | SpdySession3::PushBack(const char *buf, uint32_t len) |
michael@0 | 2716 | { |
michael@0 | 2717 | return mConnection->PushBack(buf, len); |
michael@0 | 2718 | } |
michael@0 | 2719 | |
michael@0 | 2720 | } // namespace mozilla::net |
michael@0 | 2721 | } // namespace mozilla |