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