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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Patrick McManus <mcmanus@ducksong.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsHttp.h"
41 : #include "SpdySession.h"
42 : #include "SpdyStream.h"
43 : #include "nsHttpConnection.h"
44 : #include "nsHttpHandler.h"
45 : #include "prnetdb.h"
46 : #include "mozilla/Telemetry.h"
47 : #include "mozilla/Preferences.h"
48 : #include "prprf.h"
49 :
50 : #ifdef DEBUG
51 : // defined by the socket transport service while active
52 : extern PRThread *gSocketThread;
53 : #endif
54 :
55 : namespace mozilla {
56 : namespace net {
57 :
58 : // SpdySession has multiple inheritance of things that implement
59 : // nsISupports, so this magic is taken from nsHttpPipeline that
60 : // implements some of the same abstract classes.
61 0 : NS_IMPL_THREADSAFE_ADDREF(SpdySession)
62 0 : NS_IMPL_THREADSAFE_RELEASE(SpdySession)
63 0 : NS_INTERFACE_MAP_BEGIN(SpdySession)
64 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
65 0 : NS_INTERFACE_MAP_END
66 :
67 0 : SpdySession::SpdySession(nsAHttpTransaction *aHttpTransaction,
68 : nsISocketTransport *aSocketTransport,
69 : PRInt32 firstPriority)
70 : : mSocketTransport(aSocketTransport),
71 : mSegmentReader(nsnull),
72 : mSegmentWriter(nsnull),
73 : mSendingChunkSize(kSendingChunkSize),
74 : mNextStreamID(1),
75 : mConcurrentHighWater(0),
76 : mDownstreamState(BUFFERING_FRAME_HEADER),
77 : mInputFrameBufferSize(kDefaultBufferSize),
78 : mInputFrameBufferUsed(0),
79 : mInputFrameDataLast(false),
80 : mInputFrameDataStream(nsnull),
81 : mNeedsCleanup(nsnull),
82 : mDecompressBufferSize(kDefaultBufferSize),
83 : mDecompressBufferUsed(0),
84 : mShouldGoAway(false),
85 : mClosed(false),
86 : mCleanShutdown(false),
87 : mGoAwayID(0),
88 : mMaxConcurrent(kDefaultMaxConcurrent),
89 : mConcurrent(0),
90 : mServerPushedResources(0),
91 : mOutputQueueSize(kDefaultQueueSize),
92 : mOutputQueueUsed(0),
93 : mOutputQueueSent(0),
94 0 : mLastReadEpoch(PR_IntervalNow()),
95 : mPingSentEpoch(0),
96 : mNextPingID(1),
97 0 : mPingThresholdExperiment(false)
98 : {
99 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
100 :
101 0 : LOG3(("SpdySession::SpdySession %p transaction 1 = %p",
102 : this, aHttpTransaction));
103 :
104 0 : mStreamIDHash.Init();
105 0 : mStreamTransactionHash.Init();
106 0 : mConnection = aHttpTransaction->Connection();
107 0 : mInputFrameBuffer = new char[mInputFrameBufferSize];
108 0 : mDecompressBuffer = new char[mDecompressBufferSize];
109 0 : mOutputQueueBuffer = new char[mOutputQueueSize];
110 0 : zlibInit();
111 :
112 0 : mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
113 0 : AddStream(aHttpTransaction, firstPriority);
114 0 : mLastDataReadEpoch = mLastReadEpoch;
115 :
116 0 : DeterminePingThreshold();
117 0 : }
118 :
119 : void
120 0 : SpdySession::DeterminePingThreshold()
121 : {
122 0 : mPingThreshold = gHttpHandler->SpdyPingThreshold();
123 :
124 0 : if (!mPingThreshold || !gHttpHandler->AllowExperiments())
125 0 : return;
126 :
127 0 : PRUint32 randomVal = gHttpHandler->Get32BitsOfPseudoRandom();
128 :
129 : // Use the lower 10 bits to select 1 in 1024 sessions for the
130 : // ping threshold experiment. Somewhat less than that will actually be
131 : // used because random values greater than the total http idle timeout
132 : // for the session are discarded.
133 0 : if ((randomVal & 0x3ff) != 1) // lottery
134 0 : return;
135 :
136 0 : randomVal = randomVal >> 10; // those bits are used up
137 :
138 : // This session has been selected - use a random ping threshold of 10 +
139 : // a random number from 0 to 255, based on the next 8 bits of the
140 : // random buffer
141 : PRIntervalTime randomThreshold =
142 0 : PR_SecondsToInterval((randomVal & 0xff) + 10);
143 0 : if (randomThreshold > gHttpHandler->IdleTimeout())
144 0 : return;
145 :
146 0 : mPingThreshold = randomThreshold;
147 0 : mPingThresholdExperiment = true;
148 0 : LOG3(("SpdySession %p Ping Threshold Experimental Selection : %dsec\n",
149 : this, PR_IntervalToSeconds(mPingThreshold)));
150 : }
151 :
152 : PLDHashOperator
153 0 : SpdySession::ShutdownEnumerator(nsAHttpTransaction *key,
154 : nsAutoPtr<SpdyStream> &stream,
155 : void *closure)
156 : {
157 0 : SpdySession *self = static_cast<SpdySession *>(closure);
158 :
159 : // On a clean server hangup the server sets the GoAwayID to be the ID of
160 : // the last transaction it processed. If the ID of stream in the
161 : // local session is greater than that it can safely be restarted because the
162 : // server guarantees it was not partially processed.
163 0 : if (self->mCleanShutdown && (stream->StreamID() > self->mGoAwayID))
164 0 : stream->Close(NS_ERROR_NET_RESET); // can be restarted
165 : else
166 0 : stream->Close(NS_ERROR_ABORT);
167 :
168 0 : return PL_DHASH_NEXT;
169 : }
170 :
171 0 : SpdySession::~SpdySession()
172 : {
173 0 : LOG3(("SpdySession::~SpdySession %p mDownstreamState=%X",
174 : this, mDownstreamState));
175 :
176 0 : inflateEnd(&mDownstreamZlib);
177 0 : deflateEnd(&mUpstreamZlib);
178 :
179 0 : mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
180 0 : Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
181 0 : Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
182 : Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
183 0 : mServerPushedResources);
184 0 : }
185 :
186 : void
187 0 : SpdySession::LogIO(SpdySession *self, SpdyStream *stream, const char *label,
188 : const char *data, PRUint32 datalen)
189 : {
190 0 : if (!LOG4_ENABLED())
191 0 : return;
192 :
193 0 : LOG4(("SpdySession::LogIO %p stream=%p id=0x%X [%s]",
194 : self, stream, stream ? stream->StreamID() : 0, label));
195 :
196 : // Max line is (16 * 3) + 10(prefix) + newline + null
197 : char linebuf[128];
198 : PRUint32 index;
199 0 : char *line = linebuf;
200 :
201 0 : linebuf[127] = 0;
202 :
203 0 : for (index = 0; index < datalen; ++index) {
204 0 : if (!(index % 16)) {
205 0 : if (index) {
206 0 : *line = 0;
207 0 : LOG4(("%s", linebuf));
208 : }
209 0 : line = linebuf;
210 0 : PR_snprintf(line, 128, "%08X: ", index);
211 0 : line += 10;
212 : }
213 : PR_snprintf(line, 128 - (line - linebuf), "%02X ",
214 0 : ((unsigned char *)data)[index]);
215 0 : line += 3;
216 : }
217 0 : if (index) {
218 0 : *line = 0;
219 0 : LOG4(("%s", linebuf));
220 : }
221 : }
222 :
223 : typedef nsresult (*Control_FX) (SpdySession *self);
224 : static Control_FX sControlFunctions[] =
225 : {
226 : nsnull,
227 : SpdySession::HandleSynStream,
228 : SpdySession::HandleSynReply,
229 : SpdySession::HandleRstStream,
230 : SpdySession::HandleSettings,
231 : SpdySession::HandleNoop,
232 : SpdySession::HandlePing,
233 : SpdySession::HandleGoAway,
234 : SpdySession::HandleHeaders,
235 : SpdySession::HandleWindowUpdate
236 : };
237 :
238 : bool
239 0 : SpdySession::RoomForMoreConcurrent()
240 : {
241 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
242 :
243 0 : return (mConcurrent < mMaxConcurrent);
244 : }
245 :
246 : bool
247 0 : SpdySession::RoomForMoreStreams()
248 : {
249 0 : if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
250 0 : return false;
251 :
252 0 : return !mShouldGoAway;
253 : }
254 :
255 : PRIntervalTime
256 0 : SpdySession::IdleTime()
257 : {
258 0 : return PR_IntervalNow() - mLastDataReadEpoch;
259 : }
260 :
261 : void
262 0 : SpdySession::ReadTimeoutTick(PRIntervalTime now)
263 : {
264 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
265 0 : NS_ABORT_IF_FALSE(mNextPingID & 1, "Ping Counter Not Odd");
266 :
267 0 : if (!mPingThreshold)
268 0 : return;
269 :
270 0 : LOG(("SpdySession::ReadTimeoutTick %p delta since last read %ds\n",
271 : this, PR_IntervalToSeconds(now - mLastReadEpoch)));
272 :
273 0 : if ((now - mLastReadEpoch) < mPingThreshold) {
274 : // recent activity means ping is not an issue
275 0 : if (mPingSentEpoch)
276 0 : ClearPing(true);
277 0 : return;
278 : }
279 :
280 0 : if (mPingSentEpoch) {
281 0 : LOG(("SpdySession::ReadTimeoutTick %p handle outstanding ping\n"));
282 0 : if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
283 0 : LOG(("SpdySession::ReadTimeoutTick %p Ping Timer Exhaustion\n",
284 : this));
285 0 : ClearPing(false);
286 0 : Close(NS_ERROR_NET_TIMEOUT);
287 : }
288 0 : return;
289 : }
290 :
291 0 : LOG(("SpdySession::ReadTimeoutTick %p generating ping 0x%x\n",
292 : this, mNextPingID));
293 :
294 0 : if (mNextPingID == 0xffffffff) {
295 0 : LOG(("SpdySession::ReadTimeoutTick %p cannot form ping - ids exhausted\n",
296 : this));
297 0 : return;
298 : }
299 :
300 0 : mPingSentEpoch = PR_IntervalNow();
301 0 : if (!mPingSentEpoch)
302 0 : mPingSentEpoch = 1; // avoid the 0 sentinel value
303 0 : GeneratePing(mNextPingID);
304 0 : mNextPingID += 2;
305 :
306 0 : if (mNextPingID == 0xffffffff) {
307 0 : LOG(("SpdySession::ReadTimeoutTick %p "
308 : "ping ids exhausted marking goaway\n", this));
309 0 : mShouldGoAway = true;
310 : }
311 : }
312 :
313 : void
314 0 : SpdySession::ClearPing(bool pingOK)
315 : {
316 0 : mPingSentEpoch = 0;
317 :
318 0 : if (mPingThresholdExperiment) {
319 0 : LOG3(("SpdySession::ClearPing %p mPingThresholdExperiment %dsec %s\n",
320 : this, PR_IntervalToSeconds(mPingThreshold),
321 : pingOK ? "pass" :"fail"));
322 :
323 0 : if (pingOK)
324 : Telemetry::Accumulate(Telemetry::SPDY_PING_EXPERIMENT_PASS,
325 0 : PR_IntervalToSeconds(mPingThreshold));
326 : else
327 : Telemetry::Accumulate(Telemetry::SPDY_PING_EXPERIMENT_FAIL,
328 0 : PR_IntervalToSeconds(mPingThreshold));
329 0 : mPingThreshold = gHttpHandler->SpdyPingThreshold();
330 0 : mPingThresholdExperiment = false;
331 : }
332 0 : }
333 :
334 : PRUint32
335 0 : SpdySession::RegisterStreamID(SpdyStream *stream)
336 : {
337 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
338 :
339 0 : LOG3(("SpdySession::RegisterStreamID session=%p stream=%p id=0x%X "
340 : "concurrent=%d",this, stream, mNextStreamID, mConcurrent));
341 :
342 0 : NS_ABORT_IF_FALSE(mNextStreamID < 0xfffffff0,
343 : "should have stopped admitting streams");
344 :
345 0 : PRUint32 result = mNextStreamID;
346 0 : mNextStreamID += 2;
347 :
348 : // We've used up plenty of ID's on this session. Start
349 : // moving to a new one before there is a crunch involving
350 : // server push streams or concurrent non-registered submits
351 0 : if (mNextStreamID >= kMaxStreamID)
352 0 : mShouldGoAway = true;
353 :
354 0 : mStreamIDHash.Put(result, stream);
355 0 : return result;
356 : }
357 :
358 : bool
359 0 : SpdySession::AddStream(nsAHttpTransaction *aHttpTransaction,
360 : PRInt32 aPriority)
361 : {
362 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
363 0 : NS_ABORT_IF_FALSE(!mStreamTransactionHash.Get(aHttpTransaction),
364 : "AddStream duplicate transaction pointer");
365 :
366 0 : aHttpTransaction->SetConnection(this);
367 : SpdyStream *stream = new SpdyStream(aHttpTransaction,
368 : this,
369 : mSocketTransport,
370 : mSendingChunkSize,
371 : &mUpstreamZlib,
372 0 : aPriority);
373 :
374 :
375 0 : LOG3(("SpdySession::AddStream session=%p stream=%p NextID=0x%X (tentative)",
376 : this, stream, mNextStreamID));
377 :
378 0 : mStreamTransactionHash.Put(aHttpTransaction, stream);
379 :
380 0 : if (RoomForMoreConcurrent()) {
381 0 : LOG3(("SpdySession::AddStream %p stream %p activated immediately.",
382 : this, stream));
383 0 : ActivateStream(stream);
384 : }
385 : else {
386 0 : LOG3(("SpdySession::AddStream %p stream %p queued.",
387 : this, stream));
388 0 : mQueuedStreams.Push(stream);
389 : }
390 :
391 0 : return true;
392 : }
393 :
394 : void
395 0 : SpdySession::ActivateStream(SpdyStream *stream)
396 : {
397 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
398 :
399 0 : mConcurrent++;
400 0 : if (mConcurrent > mConcurrentHighWater)
401 0 : mConcurrentHighWater = mConcurrent;
402 0 : LOG3(("SpdySession::AddStream %p activating stream %p Currently %d "
403 : "streams in session, high water mark is %d",
404 : this, stream, mConcurrent, mConcurrentHighWater));
405 :
406 0 : mReadyForWrite.Push(stream);
407 0 : SetWriteCallbacks();
408 :
409 : // Kick off the SYN transmit without waiting for the poll loop
410 : PRUint32 countRead;
411 0 : ReadSegments(nsnull, kDefaultBufferSize, &countRead);
412 0 : }
413 :
414 : void
415 0 : SpdySession::ProcessPending()
416 : {
417 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
418 :
419 0 : while (RoomForMoreConcurrent()) {
420 0 : SpdyStream *stream = static_cast<SpdyStream *>(mQueuedStreams.PopFront());
421 0 : if (!stream)
422 0 : return;
423 0 : LOG3(("SpdySession::ProcessPending %p stream %p activated from queue.",
424 : this, stream));
425 0 : ActivateStream(stream);
426 : }
427 : }
428 :
429 : nsresult
430 0 : SpdySession::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
431 : PRUint32 count, PRUint32 *countWritten)
432 : {
433 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
434 :
435 0 : nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
436 0 : if (NS_SUCCEEDED(rv) && *countWritten > 0)
437 0 : mLastReadEpoch = PR_IntervalNow();
438 0 : return rv;
439 : }
440 :
441 : void
442 0 : SpdySession::SetWriteCallbacks()
443 : {
444 0 : if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
445 0 : mConnection->ResumeSend();
446 0 : }
447 :
448 : void
449 0 : SpdySession::FlushOutputQueue()
450 : {
451 0 : if (!mSegmentReader || !mOutputQueueUsed)
452 0 : return;
453 :
454 : nsresult rv;
455 : PRUint32 countRead;
456 0 : PRUint32 avail = mOutputQueueUsed - mOutputQueueSent;
457 :
458 : rv = mSegmentReader->
459 0 : OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
460 0 : &countRead);
461 0 : LOG3(("SpdySession::FlushOutputQueue %p sz=%d rv=%x actual=%d",
462 : this, avail, rv, countRead));
463 :
464 : // Dont worry about errors on write, we will pick this up as a read error too
465 0 : if (NS_FAILED(rv))
466 0 : return;
467 :
468 0 : if (countRead == avail) {
469 0 : mOutputQueueUsed = 0;
470 0 : mOutputQueueSent = 0;
471 0 : return;
472 : }
473 :
474 0 : mOutputQueueSent += countRead;
475 :
476 : // If the output queue is close to filling up and we have sent out a good
477 : // chunk of data from the beginning then realign it.
478 :
479 0 : if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
480 : ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
481 0 : mOutputQueueUsed -= mOutputQueueSent;
482 0 : memmove(mOutputQueueBuffer.get(),
483 0 : mOutputQueueBuffer.get() + mOutputQueueSent,
484 0 : mOutputQueueUsed);
485 0 : mOutputQueueSent = 0;
486 : }
487 : }
488 :
489 : void
490 0 : SpdySession::DontReuse()
491 : {
492 0 : mShouldGoAway = true;
493 0 : if (!mStreamTransactionHash.Count())
494 0 : Close(NS_OK);
495 0 : }
496 :
497 : PRUint32
498 0 : SpdySession::GetWriteQueueSize()
499 : {
500 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
501 :
502 0 : return mUrgentForWrite.GetSize() + mReadyForWrite.GetSize();
503 : }
504 :
505 : void
506 0 : SpdySession::ChangeDownstreamState(enum stateType newState)
507 : {
508 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
509 :
510 0 : LOG3(("SpdyStream::ChangeDownstreamState() %p from %X to %X",
511 : this, mDownstreamState, newState));
512 0 : mDownstreamState = newState;
513 0 : }
514 :
515 : void
516 0 : SpdySession::ResetDownstreamState()
517 : {
518 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
519 :
520 0 : LOG3(("SpdyStream::ResetDownstreamState() %p", this));
521 0 : ChangeDownstreamState(BUFFERING_FRAME_HEADER);
522 :
523 0 : if (mInputFrameDataLast && mInputFrameDataStream) {
524 0 : mInputFrameDataLast = false;
525 0 : if (!mInputFrameDataStream->RecvdFin()) {
526 0 : mInputFrameDataStream->SetRecvdFin(true);
527 0 : --mConcurrent;
528 0 : ProcessPending();
529 : }
530 : }
531 0 : mInputFrameBufferUsed = 0;
532 0 : mInputFrameDataStream = nsnull;
533 0 : }
534 :
535 : void
536 0 : SpdySession::EnsureBuffer(nsAutoArrayPtr<char> &buf,
537 : PRUint32 newSize,
538 : PRUint32 preserve,
539 : PRUint32 &objSize)
540 : {
541 0 : if (objSize >= newSize)
542 0 : return;
543 :
544 : // Leave a little slop on the new allocation - add 2KB to
545 : // what we need and then round the result up to a 4KB (page)
546 : // boundary.
547 :
548 0 : objSize = (newSize + 2048 + 4095) & ~4095;
549 :
550 0 : nsAutoArrayPtr<char> tmp(new char[objSize]);
551 0 : memcpy(tmp, buf, preserve);
552 0 : buf = tmp;
553 : }
554 :
555 : void
556 0 : SpdySession::zlibInit()
557 : {
558 0 : mDownstreamZlib.zalloc = SpdyStream::zlib_allocator;
559 0 : mDownstreamZlib.zfree = SpdyStream::zlib_destructor;
560 0 : mDownstreamZlib.opaque = Z_NULL;
561 :
562 0 : inflateInit(&mDownstreamZlib);
563 :
564 0 : mUpstreamZlib.zalloc = SpdyStream::zlib_allocator;
565 0 : mUpstreamZlib.zfree = SpdyStream::zlib_destructor;
566 0 : mUpstreamZlib.opaque = Z_NULL;
567 :
568 0 : deflateInit(&mUpstreamZlib, Z_DEFAULT_COMPRESSION);
569 : deflateSetDictionary(&mUpstreamZlib,
570 : reinterpret_cast<const unsigned char *>
571 : (SpdyStream::kDictionary),
572 0 : strlen(SpdyStream::kDictionary) + 1);
573 :
574 0 : }
575 :
576 : nsresult
577 0 : SpdySession::DownstreamUncompress(char *blockStart, PRUint32 blockLen)
578 : {
579 0 : mDecompressBufferUsed = 0;
580 :
581 0 : mDownstreamZlib.avail_in = blockLen;
582 0 : mDownstreamZlib.next_in = reinterpret_cast<unsigned char *>(blockStart);
583 :
584 0 : do {
585 : mDownstreamZlib.next_out =
586 0 : reinterpret_cast<unsigned char *>(mDecompressBuffer.get()) +
587 0 : mDecompressBufferUsed;
588 0 : mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed;
589 0 : int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH);
590 :
591 0 : if (zlib_rv == Z_NEED_DICT)
592 : inflateSetDictionary(&mDownstreamZlib,
593 : reinterpret_cast<const unsigned char *>
594 : (SpdyStream::kDictionary),
595 0 : strlen(SpdyStream::kDictionary) + 1);
596 :
597 0 : if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR)
598 0 : return NS_ERROR_FAILURE;
599 :
600 : mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed -
601 0 : mDownstreamZlib.avail_out;
602 :
603 : // When there is no more output room, but input still available then
604 : // increase the output space
605 0 : if (zlib_rv == Z_OK &&
606 0 : !mDownstreamZlib.avail_out && mDownstreamZlib.avail_in) {
607 0 : LOG3(("SpdySession::DownstreamUncompress %p Large Headers - so far %d",
608 : this, mDecompressBufferSize));
609 : EnsureBuffer(mDecompressBuffer,
610 : mDecompressBufferSize + 4096,
611 : mDecompressBufferUsed,
612 0 : mDecompressBufferSize);
613 : }
614 : }
615 : while (mDownstreamZlib.avail_in);
616 0 : return NS_OK;
617 : }
618 :
619 : nsresult
620 0 : SpdySession::FindHeader(nsCString name,
621 : nsDependentCSubstring &value)
622 : {
623 : const unsigned char *nvpair = reinterpret_cast<unsigned char *>
624 0 : (mDecompressBuffer.get()) + 2;
625 : const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
626 0 : (mDecompressBuffer.get()) + mDecompressBufferUsed;
627 0 : if (lastHeaderByte < nvpair)
628 0 : return NS_ERROR_ILLEGAL_VALUE;
629 : PRUint16 numPairs =
630 0 : PR_ntohs(reinterpret_cast<PRUint16 *>(mDecompressBuffer.get())[0]);
631 0 : for (PRUint16 index = 0; index < numPairs; ++index) {
632 0 : if (lastHeaderByte < nvpair + 2)
633 0 : return NS_ERROR_ILLEGAL_VALUE;
634 0 : PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1];
635 0 : if (lastHeaderByte < nvpair + 2 + nameLen)
636 0 : return NS_ERROR_ILLEGAL_VALUE;
637 : nsDependentCSubstring nameString =
638 : Substring(reinterpret_cast<const char *>(nvpair) + 2,
639 0 : reinterpret_cast<const char *>(nvpair) + 2 + nameLen);
640 0 : if (lastHeaderByte < nvpair + 4 + nameLen)
641 0 : return NS_ERROR_ILLEGAL_VALUE;
642 0 : PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen];
643 0 : if (lastHeaderByte < nvpair + 4 + nameLen + valueLen)
644 0 : return NS_ERROR_ILLEGAL_VALUE;
645 0 : if (nameString.Equals(name)) {
646 0 : value.Assign(((char *)nvpair) + 4 + nameLen, valueLen);
647 0 : return NS_OK;
648 : }
649 0 : nvpair += 4 + nameLen + valueLen;
650 : }
651 0 : return NS_ERROR_NOT_AVAILABLE;
652 : }
653 :
654 : nsresult
655 0 : SpdySession::ConvertHeaders(nsDependentCSubstring &status,
656 : nsDependentCSubstring &version)
657 : {
658 0 : mFlatHTTPResponseHeaders.Truncate();
659 0 : mFlatHTTPResponseHeadersOut = 0;
660 0 : mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64);
661 :
662 : // Connection, Keep-Alive and chunked transfer encodings are to be
663 : // removed.
664 :
665 : // Content-Length is 'advisory'.. we will not strip it because it can
666 : // create UI feedback.
667 :
668 0 : mFlatHTTPResponseHeaders.Append(version);
669 0 : mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" "));
670 0 : mFlatHTTPResponseHeaders.Append(status);
671 0 : mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
672 :
673 : const unsigned char *nvpair = reinterpret_cast<unsigned char *>
674 0 : (mDecompressBuffer.get()) + 2;
675 : const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
676 0 : (mDecompressBuffer.get()) + mDecompressBufferUsed;
677 :
678 0 : if (lastHeaderByte < nvpair)
679 0 : return NS_ERROR_ILLEGAL_VALUE;
680 :
681 : PRUint16 numPairs =
682 0 : PR_ntohs(reinterpret_cast<PRUint16 *>(mDecompressBuffer.get())[0]);
683 :
684 0 : for (PRUint16 index = 0; index < numPairs; ++index) {
685 0 : if (lastHeaderByte < nvpair + 2)
686 0 : return NS_ERROR_ILLEGAL_VALUE;
687 :
688 0 : PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1];
689 0 : if (lastHeaderByte < nvpair + 2 + nameLen)
690 0 : return NS_ERROR_ILLEGAL_VALUE;
691 :
692 : nsDependentCSubstring nameString =
693 : Substring(reinterpret_cast<const char *>(nvpair) + 2,
694 0 : reinterpret_cast<const char *>(nvpair) + 2 + nameLen);
695 :
696 0 : if (lastHeaderByte < nvpair + 4 + nameLen)
697 0 : return NS_ERROR_ILLEGAL_VALUE;
698 :
699 : // Look for illegal characters in the nameString.
700 : // This includes upper case characters and nulls (as they will
701 : // break the fixup-nulls-in-value-string algorithm)
702 : // Look for upper case characters in the name. They are illegal.
703 0 : for (char *cPtr = nameString.BeginWriting();
704 0 : cPtr && cPtr < nameString.EndWriting();
705 : ++cPtr) {
706 0 : if (*cPtr <= 'Z' && *cPtr >= 'A') {
707 0 : nsCString toLog(nameString);
708 :
709 0 : LOG3(("SpdySession::ConvertHeaders session=%p stream=%p "
710 : "upper case response header found. [%s]\n",
711 : this, mInputFrameDataStream, toLog.get()));
712 :
713 0 : return NS_ERROR_ILLEGAL_VALUE;
714 : }
715 :
716 : // check for null characters
717 0 : if (*cPtr == '\0')
718 0 : return NS_ERROR_ILLEGAL_VALUE;
719 : }
720 :
721 : // HTTP Chunked responses are not legal over spdy. We do not need
722 : // to look for chunked specifically because it is the only HTTP
723 : // allowed default encoding and we did not negotiate further encodings
724 : // via TE
725 0 : if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) {
726 0 : LOG3(("SpdySession::ConvertHeaders session=%p stream=%p "
727 : "transfer-encoding found. Chunked is invalid and no TE sent.",
728 : this, mInputFrameDataStream));
729 :
730 0 : return NS_ERROR_ILLEGAL_VALUE;
731 : }
732 :
733 0 : PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen];
734 0 : if (lastHeaderByte < nvpair + 4 + nameLen + valueLen)
735 0 : return NS_ERROR_ILLEGAL_VALUE;
736 :
737 0 : if (!nameString.Equals(NS_LITERAL_CSTRING("version")) &&
738 0 : !nameString.Equals(NS_LITERAL_CSTRING("status")) &&
739 0 : !nameString.Equals(NS_LITERAL_CSTRING("connection")) &&
740 0 : !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) {
741 : nsDependentCSubstring valueString =
742 0 : Substring(reinterpret_cast<const char *>(nvpair) + 4 + nameLen,
743 : reinterpret_cast<const char *>(nvpair) + 4 + nameLen +
744 0 : valueLen);
745 :
746 0 : mFlatHTTPResponseHeaders.Append(nameString);
747 0 : mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": "));
748 :
749 : // expand NULL bytes in the value string
750 0 : for (char *cPtr = valueString.BeginWriting();
751 0 : cPtr && cPtr < valueString.EndWriting();
752 : ++cPtr) {
753 0 : if (*cPtr != 0) {
754 0 : mFlatHTTPResponseHeaders.Append(*cPtr);
755 0 : continue;
756 : }
757 :
758 : // NULLs are really "\r\nhdr: "
759 0 : mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
760 0 : mFlatHTTPResponseHeaders.Append(nameString);
761 0 : mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": "));
762 : }
763 :
764 0 : mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
765 : }
766 0 : nvpair += 4 + nameLen + valueLen;
767 : }
768 :
769 : mFlatHTTPResponseHeaders.Append(
770 0 : NS_LITERAL_CSTRING("X-Firefox-Spdy: 1\r\n\r\n"));
771 0 : LOG (("decoded response headers are:\n%s",
772 : mFlatHTTPResponseHeaders.get()));
773 :
774 0 : return NS_OK;
775 : }
776 :
777 : void
778 0 : SpdySession::GeneratePing(PRUint32 aID)
779 : {
780 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
781 0 : LOG3(("SpdySession::GeneratePing %p 0x%X\n", this, aID));
782 :
783 : EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12,
784 0 : mOutputQueueUsed, mOutputQueueSize);
785 0 : char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
786 0 : mOutputQueueUsed += 12;
787 :
788 0 : packet[0] = kFlag_Control;
789 0 : packet[1] = 2; /* version 2 */
790 0 : packet[2] = 0;
791 0 : packet[3] = CONTROL_TYPE_PING;
792 0 : packet[4] = 0; /* flags */
793 0 : packet[5] = 0;
794 0 : packet[6] = 0;
795 0 : packet[7] = 4; /* length */
796 :
797 0 : aID = PR_htonl(aID);
798 0 : memcpy(packet + 8, &aID, 4);
799 :
800 0 : FlushOutputQueue();
801 0 : }
802 :
803 : void
804 0 : SpdySession::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID)
805 : {
806 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
807 0 : LOG3(("SpdySession::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
808 :
809 : EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16,
810 0 : mOutputQueueUsed, mOutputQueueSize);
811 0 : char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
812 0 : mOutputQueueUsed += 16;
813 :
814 0 : packet[0] = kFlag_Control;
815 0 : packet[1] = 2; /* version 2 */
816 0 : packet[2] = 0;
817 0 : packet[3] = CONTROL_TYPE_RST_STREAM;
818 0 : packet[4] = 0; /* flags */
819 0 : packet[5] = 0;
820 0 : packet[6] = 0;
821 0 : packet[7] = 8; /* length */
822 :
823 0 : aID = PR_htonl(aID);
824 0 : memcpy(packet + 8, &aID, 4);
825 0 : aStatusCode = PR_htonl(aStatusCode);
826 0 : memcpy(packet + 12, &aStatusCode, 4);
827 :
828 0 : FlushOutputQueue();
829 0 : }
830 :
831 : void
832 0 : SpdySession::GenerateGoAway()
833 : {
834 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
835 0 : LOG3(("SpdySession::GenerateGoAway %p\n", this));
836 :
837 : EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12,
838 0 : mOutputQueueUsed, mOutputQueueSize);
839 0 : char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
840 0 : mOutputQueueUsed += 12;
841 :
842 0 : memset(packet, 0, 12);
843 0 : packet[0] = kFlag_Control;
844 0 : packet[1] = 2; /* version 2 */
845 0 : packet[3] = CONTROL_TYPE_GOAWAY;
846 0 : packet[7] = 4; /* data length */
847 :
848 : // last-good-stream-id are bytes 8-11, when we accept server push this will
849 : // need to be set non zero
850 :
851 0 : FlushOutputQueue();
852 0 : }
853 :
854 : void
855 0 : SpdySession::CleanupStream(SpdyStream *aStream, nsresult aResult,
856 : rstReason aResetCode)
857 : {
858 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
859 0 : LOG3(("SpdySession::CleanupStream %p %p 0x%x %X\n",
860 : this, aStream, aStream->StreamID(), aResult));
861 :
862 0 : if (!aStream->RecvdFin() && aStream->StreamID()) {
863 0 : LOG3(("Stream had not processed recv FIN, sending RST code %X\n",
864 : aResetCode));
865 0 : GenerateRstStream(aResetCode, aStream->StreamID());
866 0 : --mConcurrent;
867 0 : ProcessPending();
868 : }
869 :
870 : // Check if partial frame reader
871 0 : if (aStream == mInputFrameDataStream) {
872 0 : LOG3(("Stream had active partial read frame on close"));
873 0 : ChangeDownstreamState(DISCARDING_DATA_FRAME);
874 0 : mInputFrameDataStream = nsnull;
875 : }
876 :
877 : // check the streams blocked on write, this is linear but the list
878 : // should be pretty short.
879 0 : PRUint32 size = mReadyForWrite.GetSize();
880 0 : for (PRUint32 count = 0; count < size; ++count) {
881 0 : SpdyStream *stream = static_cast<SpdyStream *>(mReadyForWrite.PopFront());
882 0 : if (stream != aStream)
883 0 : mReadyForWrite.Push(stream);
884 : }
885 :
886 : // Check the streams blocked on urgent (i.e. window update) writing.
887 : // This should also be short.
888 0 : size = mUrgentForWrite.GetSize();
889 0 : for (PRUint32 count = 0; count < size; ++count) {
890 0 : SpdyStream *stream = static_cast<SpdyStream *>(mUrgentForWrite.PopFront());
891 0 : if (stream != aStream)
892 0 : mUrgentForWrite.Push(stream);
893 : }
894 :
895 : // Check the streams queued for activation. Because we normally accept a high
896 : // level of parallelization this should also be short.
897 0 : size = mQueuedStreams.GetSize();
898 0 : for (PRUint32 count = 0; count < size; ++count) {
899 0 : SpdyStream *stream = static_cast<SpdyStream *>(mQueuedStreams.PopFront());
900 0 : if (stream != aStream)
901 0 : mQueuedStreams.Push(stream);
902 : }
903 :
904 : // Remove the stream from the ID hash table. (this one isn't short, which is
905 : // why it is hashed.)
906 0 : mStreamIDHash.Remove(aStream->StreamID());
907 :
908 : // Send the stream the close() indication
909 0 : aStream->Close(aResult);
910 :
911 : // removing from the stream transaction hash will
912 : // delete the SpdyStream and drop the reference to
913 : // its transaction
914 0 : mStreamTransactionHash.Remove(aStream->Transaction());
915 :
916 0 : if (mShouldGoAway && !mStreamTransactionHash.Count())
917 0 : Close(NS_OK);
918 0 : }
919 :
920 : nsresult
921 0 : SpdySession::HandleSynStream(SpdySession *self)
922 : {
923 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM,
924 : "wrong control type");
925 :
926 0 : if (self->mInputFrameDataSize < 18) {
927 0 : LOG3(("SpdySession::HandleSynStream %p SYN_STREAM too short data=%d",
928 : self, self->mInputFrameDataSize));
929 0 : return NS_ERROR_ILLEGAL_VALUE;
930 : }
931 :
932 : PRUint32 streamID =
933 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
934 : PRUint32 associatedID =
935 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[3]);
936 :
937 0 : LOG3(("SpdySession::HandleSynStream %p recv SYN_STREAM (push) "
938 : "for ID 0x%X associated with 0x%X.",
939 : self, streamID, associatedID));
940 :
941 0 : if (streamID & 0x01) { // test for odd stream ID
942 0 : LOG3(("SpdySession::HandleSynStream %p recvd SYN_STREAM id must be even.",
943 : self));
944 0 : return NS_ERROR_ILLEGAL_VALUE;
945 : }
946 :
947 0 : ++(self->mServerPushedResources);
948 :
949 : // Anytime we start using the high bit of stream ID (either client or server)
950 : // begin to migrate to a new session.
951 0 : if (streamID >= kMaxStreamID)
952 0 : self->mShouldGoAway = true;
953 :
954 : // Need to decompress the headers even though we aren't using them yet in
955 : // order to keep the compression context consistent for other syn_reply frames
956 0 : nsresult rv = self->DownstreamUncompress(self->mInputFrameBuffer + 18,
957 0 : self->mInputFrameDataSize - 10);
958 0 : if (NS_FAILED(rv)) {
959 0 : LOG(("SpdySession::HandleSynStream uncompress failed\n"));
960 0 : return rv;
961 : }
962 :
963 : // todo populate cache. For now, just reject server push p3
964 0 : self->GenerateRstStream(RST_REFUSED_STREAM, streamID);
965 0 : self->ResetDownstreamState();
966 0 : return NS_OK;
967 : }
968 :
969 : nsresult
970 0 : SpdySession::HandleSynReply(SpdySession *self)
971 : {
972 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY,
973 : "wrong control type");
974 :
975 0 : if (self->mInputFrameDataSize < 8) {
976 0 : LOG3(("SpdySession::HandleSynReply %p SYN REPLY too short data=%d",
977 : self, self->mInputFrameDataSize));
978 : // A framing error is a session wide error that cannot be recovered
979 0 : return NS_ERROR_ILLEGAL_VALUE;
980 : }
981 :
982 : // Uncompress the headers into mDecompressBuffer, leaving them in
983 : // spdy format for the time being. Make certain to do this
984 : // step before any error handling that might abort the stream but not
985 : // the session becuase the session compression context will become
986 : // inconsistent if all of the compressed data is not processed.
987 0 : if (NS_FAILED(self->DownstreamUncompress(self->mInputFrameBuffer + 14,
988 : self->mInputFrameDataSize - 6))) {
989 0 : LOG(("SpdySession::HandleSynReply uncompress failed\n"));
990 0 : return NS_ERROR_FAILURE;
991 : }
992 :
993 : PRUint32 streamID =
994 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
995 0 : self->mInputFrameDataStream = self->mStreamIDHash.Get(streamID);
996 0 : if (!self->mInputFrameDataStream) {
997 0 : LOG3(("SpdySession::HandleSynReply %p lookup streamID in syn_reply "
998 : "0x%X failed. NextStreamID = 0x%x", self, streamID,
999 : self->mNextStreamID));
1000 0 : if (streamID >= self->mNextStreamID)
1001 0 : self->GenerateRstStream(RST_INVALID_STREAM, streamID);
1002 :
1003 0 : self->ResetDownstreamState();
1004 0 : return NS_OK;
1005 : }
1006 :
1007 0 : nsresult rv = self->HandleSynReplyForValidStream();
1008 0 : if (rv == NS_ERROR_ILLEGAL_VALUE) {
1009 0 : LOG3(("SpdySession::HandleSynReply %p PROTOCOL_ERROR detected 0x%X\n",
1010 : self, streamID));
1011 0 : self->CleanupStream(self->mInputFrameDataStream, rv, RST_PROTOCOL_ERROR);
1012 0 : self->ResetDownstreamState();
1013 0 : rv = NS_OK;
1014 : }
1015 :
1016 0 : return rv;
1017 : }
1018 :
1019 : // HandleSynReplyForValidStream() returns NS_ERROR_ILLEGAL_VALUE when the stream
1020 : // should be reset with a PROTOCOL_ERROR, NS_OK when the SYN_REPLY was
1021 : // fine, and any other error is fatal to the session.
1022 : nsresult
1023 0 : SpdySession::HandleSynReplyForValidStream()
1024 : {
1025 0 : if (mInputFrameDataStream->GetFullyOpen()) {
1026 : // "If an endpoint receives multiple SYN_REPLY frames for the same active
1027 : // stream ID, it must drop the stream, and send a RST_STREAM for the
1028 : // stream with the error PROTOCOL_ERROR."
1029 : //
1030 : // If the stream is open then just RST_STREAM and move on, otherwise
1031 : // abort the session
1032 0 : return mInputFrameDataStream->RecvdFin() ?
1033 0 : NS_ERROR_ALREADY_OPENED : NS_ERROR_ILLEGAL_VALUE;
1034 : }
1035 0 : mInputFrameDataStream->SetFullyOpen();
1036 :
1037 0 : mInputFrameDataLast = mInputFrameBuffer[4] & kFlag_Data_FIN;
1038 :
1039 0 : if (mInputFrameBuffer[4] & kFlag_Data_UNI) {
1040 0 : LOG3(("SynReply had unidirectional flag set on it - nonsensical"));
1041 0 : return NS_ERROR_ILLEGAL_VALUE;
1042 : }
1043 :
1044 0 : LOG3(("SpdySession::HandleSynReplyForValidStream %p SYN_REPLY for 0x%X "
1045 : "fin=%d",
1046 : this, mInputFrameDataStream->StreamID(), mInputFrameDataLast));
1047 :
1048 : Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE,
1049 0 : mInputFrameDataSize - 6);
1050 0 : if (mDecompressBufferUsed) {
1051 : PRUint32 ratio =
1052 0 : (mInputFrameDataSize - 6) * 100 / mDecompressBufferUsed;
1053 0 : Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
1054 : }
1055 :
1056 : // status and version are required.
1057 0 : nsDependentCSubstring status, version;
1058 0 : nsresult rv = FindHeader(NS_LITERAL_CSTRING("status"), status);
1059 0 : if (NS_FAILED(rv))
1060 0 : return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv;
1061 :
1062 0 : rv = FindHeader(NS_LITERAL_CSTRING("version"), version);
1063 0 : if (NS_FAILED(rv))
1064 0 : return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv;
1065 :
1066 : // The spdystream needs to see flattened http headers
1067 : // Uncompressed spdy format headers currently live in
1068 : // mDeccompressBuffer - convert that to HTTP format in
1069 : // mFlatHTTPResponseHeaders in ConvertHeaders()
1070 :
1071 0 : rv = ConvertHeaders(status, version);
1072 0 : if (NS_FAILED(rv))
1073 0 : return rv;
1074 :
1075 0 : mInputFrameDataStream->UpdateTransportReadEvents(mInputFrameDataSize);
1076 0 : mLastDataReadEpoch = mLastReadEpoch;
1077 0 : ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY);
1078 0 : return NS_OK;
1079 : }
1080 :
1081 : nsresult
1082 0 : SpdySession::HandleRstStream(SpdySession *self)
1083 : {
1084 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_RST_STREAM,
1085 : "wrong control type");
1086 :
1087 0 : if (self->mInputFrameDataSize != 8) {
1088 0 : LOG3(("SpdySession::HandleRstStream %p RST_STREAM wrong length data=%d",
1089 : self, self->mInputFrameDataSize));
1090 0 : return NS_ERROR_ILLEGAL_VALUE;
1091 : }
1092 :
1093 0 : PRUint8 flags = reinterpret_cast<PRUint8 *>(self->mInputFrameBuffer.get())[4];
1094 :
1095 : PRUint32 streamID =
1096 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
1097 :
1098 : self->mDownstreamRstReason =
1099 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[3]);
1100 :
1101 0 : LOG3(("SpdySession::HandleRstStream %p RST_STREAM Reason Code %u ID %x "
1102 : "flags %x", self, self->mDownstreamRstReason, streamID, flags));
1103 :
1104 0 : if (flags != 0) {
1105 0 : LOG3(("SpdySession::HandleRstStream %p RST_STREAM with flags is illegal",
1106 : self));
1107 0 : return NS_ERROR_ILLEGAL_VALUE;
1108 : }
1109 :
1110 0 : if (self->mDownstreamRstReason == RST_INVALID_STREAM ||
1111 : self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) {
1112 : // basically just ignore this
1113 0 : self->ResetDownstreamState();
1114 0 : return NS_OK;
1115 : }
1116 :
1117 0 : self->mInputFrameDataStream = self->mStreamIDHash.Get(streamID);
1118 0 : if (!self->mInputFrameDataStream) {
1119 0 : LOG3(("SpdySession::HandleRstStream %p lookup streamID for RST Frame "
1120 : "0x%X failed", self, streamID));
1121 0 : return NS_ERROR_ILLEGAL_VALUE;
1122 : }
1123 :
1124 0 : self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
1125 0 : return NS_OK;
1126 : }
1127 :
1128 : nsresult
1129 0 : SpdySession::HandleSettings(SpdySession *self)
1130 : {
1131 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SETTINGS,
1132 : "wrong control type");
1133 :
1134 0 : if (self->mInputFrameDataSize < 4) {
1135 0 : LOG3(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d",
1136 : self, self->mInputFrameDataSize));
1137 0 : return NS_ERROR_ILLEGAL_VALUE;
1138 : }
1139 :
1140 : PRUint32 numEntries =
1141 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
1142 :
1143 : // Ensure frame is large enough for supplied number of entries
1144 : // Each entry is 8 bytes, frame data is reduced by 4 to account for
1145 : // the NumEntries value.
1146 0 : if ((self->mInputFrameDataSize - 4) < (numEntries * 8)) {
1147 0 : LOG3(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d",
1148 : self, self->mInputFrameDataSize));
1149 0 : return NS_ERROR_ILLEGAL_VALUE;
1150 : }
1151 :
1152 0 : LOG3(("SpdySession::HandleSettings %p SETTINGS Control Frame with %d entries",
1153 : self, numEntries));
1154 :
1155 0 : for (PRUint32 index = 0; index < numEntries; ++index) {
1156 : // To clarify the v2 spec:
1157 : // Each entry is a 24 bits of a little endian id
1158 : // followed by 8 bits of flags
1159 : // followed by a 32 bit big endian value
1160 :
1161 : unsigned char *setting = reinterpret_cast<unsigned char *>
1162 0 : (self->mInputFrameBuffer.get()) + 12 + index * 8;
1163 :
1164 0 : PRUint32 id = (setting[2] << 16) + (setting[1] << 8) + setting[0];
1165 0 : PRUint32 flags = setting[3];
1166 0 : PRUint32 value = PR_ntohl(reinterpret_cast<PRUint32 *>(setting)[1]);
1167 :
1168 0 : LOG3(("Settings ID %d, Flags %X, Value %d", id, flags, value));
1169 :
1170 0 : switch (id)
1171 : {
1172 : case SETTINGS_TYPE_UPLOAD_BW:
1173 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value);
1174 0 : break;
1175 :
1176 : case SETTINGS_TYPE_DOWNLOAD_BW:
1177 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value);
1178 0 : break;
1179 :
1180 : case SETTINGS_TYPE_RTT:
1181 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value);
1182 0 : break;
1183 :
1184 : case SETTINGS_TYPE_MAX_CONCURRENT:
1185 0 : self->mMaxConcurrent = value;
1186 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
1187 0 : break;
1188 :
1189 : case SETTINGS_TYPE_CWND:
1190 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value);
1191 0 : break;
1192 :
1193 : case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE:
1194 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value);
1195 0 : break;
1196 :
1197 : case SETTINGS_TYPE_INITIAL_WINDOW:
1198 0 : Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
1199 0 : break;
1200 :
1201 : default:
1202 0 : break;
1203 : }
1204 :
1205 : }
1206 :
1207 0 : self->ResetDownstreamState();
1208 0 : return NS_OK;
1209 : }
1210 :
1211 : nsresult
1212 0 : SpdySession::HandleNoop(SpdySession *self)
1213 : {
1214 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_NOOP,
1215 : "wrong control type");
1216 :
1217 0 : if (self->mInputFrameDataSize != 0) {
1218 0 : LOG3(("SpdySession::HandleNoop %p NOP had data %d",
1219 : self, self->mInputFrameDataSize));
1220 0 : return NS_ERROR_ILLEGAL_VALUE;
1221 : }
1222 :
1223 0 : LOG3(("SpdySession::HandleNoop %p NOP.", self));
1224 :
1225 0 : self->ResetDownstreamState();
1226 0 : return NS_OK;
1227 : }
1228 :
1229 : nsresult
1230 0 : SpdySession::HandlePing(SpdySession *self)
1231 : {
1232 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_PING,
1233 : "wrong control type");
1234 :
1235 0 : if (self->mInputFrameDataSize != 4) {
1236 0 : LOG3(("SpdySession::HandlePing %p PING had wrong amount of data %d",
1237 : self, self->mInputFrameDataSize));
1238 0 : return NS_ERROR_ILLEGAL_VALUE;
1239 : }
1240 :
1241 : PRUint32 pingID =
1242 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
1243 :
1244 0 : LOG3(("SpdySession::HandlePing %p PING ID 0x%X.", self, pingID));
1245 :
1246 0 : if (pingID & 0x01) {
1247 : // presumably a reply to our timeout ping
1248 0 : self->ClearPing(true);
1249 : }
1250 : else {
1251 : // Servers initiate even numbered pings, go ahead and echo it back
1252 0 : self->GeneratePing(pingID);
1253 : }
1254 :
1255 0 : self->ResetDownstreamState();
1256 0 : return NS_OK;
1257 : }
1258 :
1259 : nsresult
1260 0 : SpdySession::HandleGoAway(SpdySession *self)
1261 : {
1262 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_GOAWAY,
1263 : "wrong control type");
1264 :
1265 0 : if (self->mInputFrameDataSize != 4) {
1266 0 : LOG3(("SpdySession::HandleGoAway %p GOAWAY had wrong amount of data %d",
1267 : self, self->mInputFrameDataSize));
1268 0 : return NS_ERROR_ILLEGAL_VALUE;
1269 : }
1270 :
1271 0 : self->mShouldGoAway = true;
1272 : self->mGoAwayID =
1273 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
1274 0 : self->mCleanShutdown = true;
1275 :
1276 0 : LOG3(("SpdySession::HandleGoAway %p GOAWAY Last-Good-ID 0x%X.",
1277 : self, self->mGoAwayID));
1278 0 : self->ResumeRecv();
1279 0 : self->ResetDownstreamState();
1280 0 : return NS_OK;
1281 : }
1282 :
1283 : nsresult
1284 0 : SpdySession::HandleHeaders(SpdySession *self)
1285 : {
1286 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_HEADERS,
1287 : "wrong control type");
1288 :
1289 0 : if (self->mInputFrameDataSize < 10) {
1290 0 : LOG3(("SpdySession::HandleHeaders %p HEADERS had wrong amount of data %d",
1291 : self, self->mInputFrameDataSize));
1292 0 : return NS_ERROR_ILLEGAL_VALUE;
1293 : }
1294 :
1295 : PRUint32 streamID =
1296 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(self->mInputFrameBuffer.get())[2]);
1297 :
1298 : // this is actually not legal in the HTTP mapping of SPDY. All
1299 : // headers are in the syn or syn reply. Log and ignore it.
1300 :
1301 : // in v3 this will be legal and we must remember to note
1302 : // NS_NET_STATUS_RECEIVING_FROM from it
1303 :
1304 0 : LOG3(("SpdySession::HandleHeaders %p HEADERS for Stream 0x%X. "
1305 : "They are ignored in the HTTP/SPDY mapping.",
1306 : self, streamID));
1307 0 : self->mLastDataReadEpoch = self->mLastReadEpoch;
1308 0 : self->ResetDownstreamState();
1309 0 : return NS_OK;
1310 : }
1311 :
1312 : nsresult
1313 0 : SpdySession::HandleWindowUpdate(SpdySession *self)
1314 : {
1315 0 : NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE,
1316 : "wrong control type");
1317 0 : LOG3(("SpdySession::HandleWindowUpdate %p WINDOW UPDATE was "
1318 : "received. WINDOW UPDATE is no longer defined in v2. Ignoring.",
1319 : self));
1320 :
1321 0 : self->ResetDownstreamState();
1322 0 : return NS_OK;
1323 : }
1324 :
1325 : //-----------------------------------------------------------------------------
1326 : // nsAHttpTransaction. It is expected that nsHttpConnection is the caller
1327 : // of these methods
1328 : //-----------------------------------------------------------------------------
1329 :
1330 : void
1331 0 : SpdySession::OnTransportStatus(nsITransport* aTransport,
1332 : nsresult aStatus,
1333 : PRUint64 aProgress)
1334 : {
1335 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1336 :
1337 0 : switch (aStatus) {
1338 : // These should appear only once, deliver to the first
1339 : // transaction on the session.
1340 : case NS_NET_STATUS_RESOLVING_HOST:
1341 : case NS_NET_STATUS_RESOLVED_HOST:
1342 : case NS_NET_STATUS_CONNECTING_TO:
1343 : case NS_NET_STATUS_CONNECTED_TO:
1344 : {
1345 0 : SpdyStream *target = mStreamIDHash.Get(1);
1346 0 : if (target)
1347 0 : target->Transaction()->OnTransportStatus(aTransport, aStatus, aProgress);
1348 0 : break;
1349 : }
1350 :
1351 : default:
1352 : // The other transport events are ignored here because there is no good
1353 : // way to map them to the right transaction in spdy. Instead, the events
1354 : // are generated again from the spdy code and passed directly to the
1355 : // correct transaction.
1356 :
1357 : // NS_NET_STATUS_SENDING_TO:
1358 : // This is generated by the socket transport when (part) of
1359 : // a transaction is written out
1360 : //
1361 : // There is no good way to map it to the right transaction in spdy,
1362 : // so it is ignored here and generated separately when the SYN_STREAM
1363 : // is sent from SpdyStream::TransmitFrame
1364 :
1365 : // NS_NET_STATUS_WAITING_FOR:
1366 : // Created by nsHttpConnection when the request has been totally sent.
1367 : // There is no good way to map it to the right transaction in spdy,
1368 : // so it is ignored here and generated separately when the same
1369 : // condition is complete in SpdyStream when there is no more
1370 : // request body left to be transmitted.
1371 :
1372 : // NS_NET_STATUS_RECEIVING_FROM
1373 : // Generated in spdysession whenever we read a data frame or a syn_reply
1374 : // that can be attributed to a particular stream/transaction
1375 :
1376 0 : break;
1377 : }
1378 0 : }
1379 :
1380 : // ReadSegments() is used to write data to the network. Generally, HTTP
1381 : // request data is pulled from the approriate transaction and
1382 : // converted to SPDY data. Sometimes control data like window-update are
1383 : // generated instead.
1384 :
1385 : nsresult
1386 0 : SpdySession::ReadSegments(nsAHttpSegmentReader *reader,
1387 : PRUint32 count,
1388 : PRUint32 *countRead)
1389 : {
1390 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1391 :
1392 : nsresult rv;
1393 0 : *countRead = 0;
1394 :
1395 : // First priority goes to frames that were writing to the network but were
1396 : // blocked part way through. Then to frames that have no streams (e.g ping
1397 : // reply) and then third to streams marked urgent (generally they have
1398 : // window updates), and finally to streams generally
1399 : // ready to send data frames (http requests).
1400 :
1401 0 : LOG3(("SpdySession::ReadSegments %p", this));
1402 :
1403 : SpdyStream *stream;
1404 :
1405 0 : stream = static_cast<SpdyStream *>(mUrgentForWrite.PopFront());
1406 0 : if (!stream)
1407 0 : stream = static_cast<SpdyStream *>(mReadyForWrite.PopFront());
1408 0 : if (!stream) {
1409 0 : LOG3(("SpdySession %p could not identify a stream to write; suspending.",
1410 : this));
1411 0 : FlushOutputQueue();
1412 0 : SetWriteCallbacks();
1413 0 : return NS_BASE_STREAM_WOULD_BLOCK;
1414 : }
1415 :
1416 0 : LOG3(("SpdySession %p will write from SpdyStream %p", this, stream));
1417 :
1418 0 : NS_ABORT_IF_FALSE(!mSegmentReader || !reader || (mSegmentReader == reader),
1419 : "Inconsistent Write Function Callback");
1420 :
1421 0 : if (reader)
1422 0 : mSegmentReader = reader;
1423 0 : rv = stream->ReadSegments(this, count, countRead);
1424 :
1425 : // Not every permutation of stream->ReadSegents produces data (and therefore
1426 : // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
1427 : // of that. But we might still have old data buffered that would be good
1428 : // to flush.
1429 0 : FlushOutputQueue();
1430 :
1431 0 : if (stream->RequestBlockedOnRead()) {
1432 :
1433 : // We are blocked waiting for input - either more http headers or
1434 : // any request body data. When more data from the request stream
1435 : // becomes available the httptransaction will call conn->ResumeSend().
1436 :
1437 0 : LOG3(("SpdySession::ReadSegments %p dealing with block on read", this));
1438 :
1439 : // call readsegments again if there are other streams ready
1440 : // to run in this session
1441 0 : if (GetWriteQueueSize())
1442 0 : rv = NS_OK;
1443 : else
1444 0 : rv = NS_BASE_STREAM_WOULD_BLOCK;
1445 0 : SetWriteCallbacks();
1446 0 : return rv;
1447 : }
1448 :
1449 0 : if (NS_FAILED(rv)) {
1450 0 : LOG3(("SpdySession::ReadSegments %p returning FAIL code %X",
1451 : this, rv));
1452 0 : return rv;
1453 : }
1454 :
1455 0 : if (*countRead > 0) {
1456 0 : LOG3(("SpdySession::ReadSegments %p stream=%p generated end of frame %d",
1457 : this, stream, *countRead));
1458 0 : mReadyForWrite.Push(stream);
1459 0 : SetWriteCallbacks();
1460 0 : return rv;
1461 : }
1462 :
1463 0 : LOG3(("SpdySession::ReadSegments %p stream=%p stream send complete",
1464 : this, stream));
1465 :
1466 : /* we now want to recv data */
1467 0 : ResumeRecv();
1468 :
1469 : // call readsegments again if there are other streams ready
1470 : // to go in this session
1471 0 : SetWriteCallbacks();
1472 :
1473 0 : return rv;
1474 : }
1475 :
1476 : // WriteSegments() is used to read data off the socket. Generally this is
1477 : // just the SPDY frame header and from there the appropriate SPDYStream
1478 : // is identified from the Stream-ID. The http transaction associated with
1479 : // that read then pulls in the data directly, which it will feed to
1480 : // OnWriteSegment(). That function will gateway it into http and feed
1481 : // it to the appropriate transaction.
1482 :
1483 : // we call writer->OnWriteSegment via NetworkRead() to get a spdy header..
1484 : // and decide if it is data or control.. if it is control, just deal with it.
1485 : // if it is data, identify the spdy stream
1486 : // call stream->WriteSegemnts which can call this::OnWriteSegment to get the
1487 : // data. It always gets full frames if they are part of the stream
1488 :
1489 : nsresult
1490 0 : SpdySession::WriteSegments(nsAHttpSegmentWriter *writer,
1491 : PRUint32 count,
1492 : PRUint32 *countWritten)
1493 : {
1494 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1495 :
1496 : nsresult rv;
1497 0 : *countWritten = 0;
1498 :
1499 0 : if (mClosed)
1500 0 : return NS_ERROR_FAILURE;
1501 :
1502 0 : SetWriteCallbacks();
1503 :
1504 : // We buffer all control frames and act on them in this layer.
1505 : // We buffer the first 8 bytes of data frames (the header) but
1506 : // the actual data is passed through unprocessed.
1507 :
1508 0 : if (mDownstreamState == BUFFERING_FRAME_HEADER) {
1509 : // The first 8 bytes of every frame is header information that
1510 : // we are going to want to strip before passing to http. That is
1511 : // true of both control and data packets.
1512 :
1513 0 : NS_ABORT_IF_FALSE(mInputFrameBufferUsed < 8,
1514 : "Frame Buffer Used Too Large for State");
1515 :
1516 0 : rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed,
1517 0 : 8 - mInputFrameBufferUsed, countWritten);
1518 :
1519 0 : if (NS_FAILED(rv)) {
1520 0 : LOG3(("SpdySession %p buffering frame header read failure %x\n",
1521 : this, rv));
1522 : // maybe just blocked reading from network
1523 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
1524 0 : ResumeRecv();
1525 0 : return rv;
1526 : }
1527 :
1528 : LogIO(this, nsnull, "Reading Frame Header",
1529 0 : mInputFrameBuffer + mInputFrameBufferUsed, *countWritten);
1530 :
1531 0 : mInputFrameBufferUsed += *countWritten;
1532 :
1533 0 : if (mInputFrameBufferUsed < 8)
1534 : {
1535 0 : LOG3(("SpdySession::WriteSegments %p "
1536 : "BUFFERING FRAME HEADER incomplete size=%d",
1537 : this, mInputFrameBufferUsed));
1538 0 : return rv;
1539 : }
1540 :
1541 : // For both control and data frames the second 32 bit word of the header
1542 : // is 8-flags, 24-length. (network byte order)
1543 : mInputFrameDataSize =
1544 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(mInputFrameBuffer.get())[1]);
1545 0 : mInputFrameDataSize &= 0x00ffffff;
1546 0 : mInputFrameDataRead = 0;
1547 :
1548 0 : if (mInputFrameBuffer[0] & kFlag_Control) {
1549 : EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + 8, 8,
1550 0 : mInputFrameBufferSize);
1551 0 : ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
1552 :
1553 : // The first 32 bit word of the header is
1554 : // 1 ctrl - 15 version - 16 type
1555 : PRUint16 version =
1556 0 : PR_ntohs(reinterpret_cast<PRUint16 *>(mInputFrameBuffer.get())[0]);
1557 0 : version &= 0x7fff;
1558 :
1559 : mFrameControlType =
1560 0 : PR_ntohs(reinterpret_cast<PRUint16 *>(mInputFrameBuffer.get())[1]);
1561 :
1562 0 : LOG3(("SpdySession::WriteSegments %p - Control Frame Identified "
1563 : "type %d version %d data len %d",
1564 : this, mFrameControlType, version, mInputFrameDataSize));
1565 :
1566 0 : if (mFrameControlType >= CONTROL_TYPE_LAST ||
1567 : mFrameControlType <= CONTROL_TYPE_FIRST)
1568 0 : return NS_ERROR_ILLEGAL_VALUE;
1569 :
1570 : // The protocol document says this value must be 1 even though this
1571 : // is known as version 2.. Testing interop indicates that is a typo
1572 : // in the protocol document
1573 0 : if (version != 2) {
1574 0 : return NS_ERROR_ILLEGAL_VALUE;
1575 : }
1576 : }
1577 : else {
1578 0 : ChangeDownstreamState(PROCESSING_DATA_FRAME);
1579 :
1580 : PRUint32 streamID =
1581 0 : PR_ntohl(reinterpret_cast<PRUint32 *>(mInputFrameBuffer.get())[0]);
1582 0 : mInputFrameDataStream = mStreamIDHash.Get(streamID);
1583 0 : if (!mInputFrameDataStream) {
1584 0 : LOG3(("SpdySession::WriteSegments %p lookup streamID 0x%X failed. "
1585 : "Next = 0x%x", this, streamID, mNextStreamID));
1586 0 : if (streamID >= mNextStreamID)
1587 0 : GenerateRstStream(RST_INVALID_STREAM, streamID);
1588 0 : ChangeDownstreamState(DISCARDING_DATA_FRAME);
1589 : }
1590 0 : mInputFrameDataLast = (mInputFrameBuffer[4] & kFlag_Data_FIN);
1591 : Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
1592 0 : mInputFrameDataSize >> 10);
1593 0 : LOG3(("Start Processing Data Frame. "
1594 : "Session=%p Stream ID 0x%x Stream Ptr %p Fin=%d Len=%d",
1595 : this, streamID, mInputFrameDataStream, mInputFrameDataLast,
1596 : mInputFrameDataSize));
1597 0 : mLastDataReadEpoch = mLastReadEpoch;
1598 :
1599 0 : if (mInputFrameBuffer[4] & kFlag_Data_ZLIB) {
1600 0 : LOG3(("Data flag has ZLIB flag set which is not valid >=2 spdy"));
1601 0 : return NS_ERROR_ILLEGAL_VALUE;
1602 : }
1603 : }
1604 : }
1605 :
1606 0 : if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
1607 0 : if (mDownstreamRstReason == RST_REFUSED_STREAM)
1608 0 : rv = NS_ERROR_NET_RESET; //we can retry this 100% safely
1609 0 : else if (mDownstreamRstReason == RST_CANCEL ||
1610 : mDownstreamRstReason == RST_PROTOCOL_ERROR ||
1611 : mDownstreamRstReason == RST_INTERNAL_ERROR ||
1612 : mDownstreamRstReason == RST_UNSUPPORTED_VERSION)
1613 0 : rv = NS_ERROR_NET_INTERRUPT;
1614 : else
1615 0 : rv = NS_ERROR_ILLEGAL_VALUE;
1616 :
1617 0 : if (mDownstreamRstReason != RST_REFUSED_STREAM &&
1618 : mDownstreamRstReason != RST_CANCEL)
1619 0 : mShouldGoAway = true;
1620 :
1621 : // mInputFrameDataStream is reset by ChangeDownstreamState
1622 0 : SpdyStream *stream = mInputFrameDataStream;
1623 0 : ResetDownstreamState();
1624 0 : CleanupStream(stream, rv, RST_CANCEL);
1625 0 : return NS_OK;
1626 : }
1627 :
1628 0 : if (mDownstreamState == PROCESSING_DATA_FRAME ||
1629 : mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) {
1630 :
1631 0 : mSegmentWriter = writer;
1632 0 : rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
1633 0 : mSegmentWriter = nsnull;
1634 :
1635 0 : mLastDataReadEpoch = mLastReadEpoch;
1636 :
1637 0 : if (rv == NS_BASE_STREAM_CLOSED) {
1638 : // This will happen when the transaction figures out it is EOF, generally
1639 : // due to a content-length match being made
1640 0 : SpdyStream *stream = mInputFrameDataStream;
1641 0 : if (mInputFrameDataRead == mInputFrameDataSize)
1642 0 : ResetDownstreamState();
1643 0 : CleanupStream(stream, NS_OK, RST_CANCEL);
1644 0 : NS_ABORT_IF_FALSE(!mNeedsCleanup, "double cleanup out of data frame");
1645 0 : return NS_OK;
1646 : }
1647 :
1648 0 : if (mNeedsCleanup) {
1649 0 : CleanupStream(mNeedsCleanup, NS_OK, RST_CANCEL);
1650 0 : mNeedsCleanup = nsnull;
1651 : }
1652 :
1653 : // In v3 this is where we would generate a window update
1654 :
1655 0 : return rv;
1656 : }
1657 :
1658 0 : if (mDownstreamState == DISCARDING_DATA_FRAME) {
1659 : char trash[4096];
1660 0 : PRUint32 count = NS_MIN(4096U, mInputFrameDataSize - mInputFrameDataRead);
1661 :
1662 0 : if (!count) {
1663 0 : ResetDownstreamState();
1664 0 : ResumeRecv();
1665 0 : return NS_BASE_STREAM_WOULD_BLOCK;
1666 : }
1667 :
1668 0 : rv = NetworkRead(writer, trash, count, countWritten);
1669 :
1670 0 : if (NS_FAILED(rv)) {
1671 0 : LOG3(("SpdySession %p discard frame read failure %x\n", this, rv));
1672 : // maybe just blocked reading from network
1673 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
1674 0 : ResumeRecv();
1675 0 : return rv;
1676 : }
1677 :
1678 0 : LogIO(this, nsnull, "Discarding Frame", trash, *countWritten);
1679 :
1680 0 : mInputFrameDataRead += *countWritten;
1681 :
1682 0 : if (mInputFrameDataRead == mInputFrameDataSize)
1683 0 : ResetDownstreamState();
1684 0 : return rv;
1685 : }
1686 :
1687 0 : if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
1688 : // this cannot happen
1689 0 : NS_ABORT_IF_FALSE(false, "Not in Bufering Control Frame State");
1690 0 : return NS_ERROR_UNEXPECTED;
1691 : }
1692 :
1693 0 : NS_ABORT_IF_FALSE(mInputFrameBufferUsed == 8,
1694 : "Frame Buffer Header Not Present");
1695 :
1696 0 : rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead,
1697 0 : mInputFrameDataSize - mInputFrameDataRead, countWritten);
1698 :
1699 0 : if (NS_FAILED(rv)) {
1700 0 : LOG3(("SpdySession %p buffering control frame read failure %x\n",
1701 : this, rv));
1702 : // maybe just blocked reading from network
1703 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
1704 0 : ResumeRecv();
1705 0 : return rv;
1706 : }
1707 :
1708 : LogIO(this, nsnull, "Reading Control Frame",
1709 0 : mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten);
1710 :
1711 0 : mInputFrameDataRead += *countWritten;
1712 :
1713 0 : if (mInputFrameDataRead != mInputFrameDataSize)
1714 0 : return NS_OK;
1715 :
1716 : // This check is actually redundant, the control type was previously
1717 : // checked to make sure it was in range, but we will check it again
1718 : // at time of use to make sure a regression doesn't creep in.
1719 0 : if (mFrameControlType >= CONTROL_TYPE_LAST ||
1720 : mFrameControlType <= CONTROL_TYPE_FIRST)
1721 : {
1722 0 : NS_ABORT_IF_FALSE(false, "control type out of range");
1723 0 : return NS_ERROR_ILLEGAL_VALUE;
1724 : }
1725 0 : rv = sControlFunctions[mFrameControlType](this);
1726 :
1727 0 : NS_ABORT_IF_FALSE(NS_FAILED(rv) ||
1728 : mDownstreamState != BUFFERING_CONTROL_FRAME,
1729 : "Control Handler returned OK but did not change state");
1730 :
1731 0 : if (mShouldGoAway && !mStreamTransactionHash.Count())
1732 0 : Close(NS_OK);
1733 0 : return rv;
1734 : }
1735 :
1736 : void
1737 0 : SpdySession::Close(nsresult aReason)
1738 : {
1739 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1740 :
1741 0 : if (mClosed)
1742 0 : return;
1743 :
1744 0 : LOG3(("SpdySession::Close %p %X", this, aReason));
1745 :
1746 0 : mClosed = true;
1747 0 : mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
1748 0 : if (NS_SUCCEEDED(aReason))
1749 0 : GenerateGoAway();
1750 0 : mConnection = nsnull;
1751 0 : mSegmentReader = nsnull;
1752 0 : mSegmentWriter = nsnull;
1753 : }
1754 :
1755 : void
1756 0 : SpdySession::CloseTransaction(nsAHttpTransaction *aTransaction,
1757 : nsresult aResult)
1758 : {
1759 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1760 0 : LOG3(("SpdySession::CloseTransaction %p %p %x", this, aTransaction, aResult));
1761 :
1762 : // Generally this arrives as a cancel event from the connection manager.
1763 :
1764 : // need to find the stream and call CleanupStream() on it.
1765 0 : SpdyStream *stream = mStreamTransactionHash.Get(aTransaction);
1766 0 : if (!stream) {
1767 0 : LOG3(("SpdySession::CloseTransaction %p %p %x - not found.",
1768 : this, aTransaction, aResult));
1769 0 : return;
1770 : }
1771 0 : LOG3(("SpdySession::CloseTranscation probably a cancel. "
1772 : "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
1773 : this, aTransaction, aResult, stream->StreamID(), stream));
1774 0 : CleanupStream(stream, aResult, RST_CANCEL);
1775 0 : ResumeRecv();
1776 : }
1777 :
1778 :
1779 : //-----------------------------------------------------------------------------
1780 : // nsAHttpSegmentReader
1781 : //-----------------------------------------------------------------------------
1782 :
1783 : nsresult
1784 0 : SpdySession::OnReadSegment(const char *buf,
1785 : PRUint32 count,
1786 : PRUint32 *countRead)
1787 : {
1788 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1789 :
1790 : nsresult rv;
1791 :
1792 : // If we can release old queued data then we can try and write the new
1793 : // data directly to the network without using the output queue at all
1794 0 : if (mOutputQueueUsed)
1795 0 : FlushOutputQueue();
1796 :
1797 0 : if (!mOutputQueueUsed && mSegmentReader) {
1798 : // try and write directly without output queue
1799 0 : rv = mSegmentReader->OnReadSegment(buf, count, countRead);
1800 :
1801 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
1802 0 : *countRead = 0;
1803 0 : else if (NS_FAILED(rv))
1804 0 : return rv;
1805 :
1806 0 : if (*countRead < count) {
1807 0 : PRUint32 required = count - *countRead;
1808 : // assuming a commitment() happened, this ensurebuffer is a nop
1809 : // but just in case the queuesize is too small for the required data
1810 : // call ensurebuffer().
1811 0 : EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
1812 0 : memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
1813 0 : mOutputQueueUsed = required;
1814 : }
1815 :
1816 0 : *countRead = count;
1817 0 : return NS_OK;
1818 : }
1819 :
1820 : // At this point we are going to buffer the new data in the output
1821 : // queue if it fits. By coalescing multiple small submissions into one larger
1822 : // buffer we can get larger writes out to the network later on.
1823 :
1824 : // This routine should not be allowed to fill up the output queue
1825 : // all on its own - at least kQueueReserved bytes are always left
1826 : // for other routines to use - but this is an all-or-nothing function,
1827 : // so if it will not all fit just return WOULD_BLOCK
1828 :
1829 0 : if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
1830 0 : return NS_BASE_STREAM_WOULD_BLOCK;
1831 :
1832 0 : memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
1833 0 : mOutputQueueUsed += count;
1834 0 : *countRead = count;
1835 :
1836 0 : FlushOutputQueue();
1837 :
1838 0 : return NS_OK;
1839 : }
1840 :
1841 : nsresult
1842 0 : SpdySession::CommitToSegmentSize(PRUint32 count)
1843 : {
1844 0 : if (mOutputQueueUsed)
1845 0 : FlushOutputQueue();
1846 :
1847 : // would there be enough room to buffer this if needed?
1848 0 : if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
1849 0 : return NS_OK;
1850 :
1851 : // if we are using part of our buffers already, try again later
1852 0 : if (mOutputQueueUsed)
1853 0 : return NS_BASE_STREAM_WOULD_BLOCK;
1854 :
1855 : // not enough room to buffer even with completely empty buffers.
1856 : // normal frames are max 4kb, so the only case this can really happen
1857 : // is a SYN_STREAM with technically unbounded headers. That is highly
1858 : // unlikely, but possible. Create enough room for it because the buffers
1859 : // will be necessary - SSL does not absorb writes of very large sizes
1860 : // in single sends.
1861 :
1862 0 : EnsureBuffer(mOutputQueueBuffer, count + kQueueReserved, 0, mOutputQueueSize);
1863 :
1864 0 : NS_ABORT_IF_FALSE((mOutputQueueUsed + count) <=
1865 : (mOutputQueueSize - kQueueReserved),
1866 : "buffer not as large as expected");
1867 :
1868 0 : return NS_OK;
1869 : }
1870 :
1871 : //-----------------------------------------------------------------------------
1872 : // nsAHttpSegmentWriter
1873 : //-----------------------------------------------------------------------------
1874 :
1875 : nsresult
1876 0 : SpdySession::OnWriteSegment(char *buf,
1877 : PRUint32 count,
1878 : PRUint32 *countWritten)
1879 : {
1880 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1881 : nsresult rv;
1882 :
1883 0 : if (!mSegmentWriter) {
1884 : // the only way this could happen would be if Close() were called on the
1885 : // stack with WriteSegments()
1886 0 : return NS_ERROR_FAILURE;
1887 : }
1888 :
1889 0 : if (mDownstreamState == PROCESSING_DATA_FRAME) {
1890 :
1891 0 : if (mInputFrameDataLast &&
1892 : mInputFrameDataRead == mInputFrameDataSize) {
1893 : // This will result in Close() being called
1894 0 : NS_ABORT_IF_FALSE(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
1895 0 : mNeedsCleanup = mInputFrameDataStream;
1896 :
1897 0 : LOG3(("SpdySession::OnWriteSegment %p - recorded downstream fin of "
1898 : "stream %p 0x%X", this, mInputFrameDataStream,
1899 : mInputFrameDataStream->StreamID()));
1900 0 : *countWritten = 0;
1901 0 : ResetDownstreamState();
1902 0 : return NS_BASE_STREAM_CLOSED;
1903 : }
1904 :
1905 0 : count = NS_MIN(count, mInputFrameDataSize - mInputFrameDataRead);
1906 0 : rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
1907 0 : if (NS_FAILED(rv))
1908 0 : return rv;
1909 :
1910 : LogIO(this, mInputFrameDataStream, "Reading Data Frame",
1911 0 : buf, *countWritten);
1912 :
1913 0 : mInputFrameDataRead += *countWritten;
1914 :
1915 0 : mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
1916 0 : if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameDataLast)
1917 0 : ResetDownstreamState();
1918 :
1919 0 : return rv;
1920 : }
1921 :
1922 0 : if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) {
1923 :
1924 0 : if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
1925 : mInputFrameDataLast) {
1926 0 : *countWritten = 0;
1927 0 : ResetDownstreamState();
1928 0 : return NS_BASE_STREAM_CLOSED;
1929 : }
1930 :
1931 : count = NS_MIN(count,
1932 0 : mFlatHTTPResponseHeaders.Length() -
1933 0 : mFlatHTTPResponseHeadersOut);
1934 : memcpy(buf,
1935 0 : mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
1936 0 : count);
1937 0 : mFlatHTTPResponseHeadersOut += count;
1938 0 : *countWritten = count;
1939 :
1940 0 : if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
1941 0 : !mInputFrameDataLast)
1942 0 : ResetDownstreamState();
1943 0 : return NS_OK;
1944 : }
1945 :
1946 0 : return NS_ERROR_UNEXPECTED;
1947 : }
1948 :
1949 : //-----------------------------------------------------------------------------
1950 : // Modified methods of nsAHttpConnection
1951 : //-----------------------------------------------------------------------------
1952 :
1953 : nsresult
1954 0 : SpdySession::ResumeSend()
1955 : {
1956 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1957 0 : LOG3(("SpdySession::ResumeSend %p", this));
1958 :
1959 0 : if (!mConnection)
1960 0 : return NS_ERROR_FAILURE;
1961 :
1962 0 : return mConnection->ResumeSend();
1963 : }
1964 :
1965 : void
1966 0 : SpdySession::TransactionHasDataToWrite(nsAHttpTransaction *caller)
1967 : {
1968 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1969 0 : LOG3(("SpdySession::TransactionHasDataToWrite %p trans=%p", this, caller));
1970 :
1971 : // a trapped signal from the http transaction to the connection that
1972 : // it is no longer blocked on read.
1973 :
1974 0 : SpdyStream *stream = mStreamTransactionHash.Get(caller);
1975 0 : if (!stream) {
1976 0 : LOG3(("SpdySession::TransactionHasDataToWrite %p caller %p not found",
1977 : this, caller));
1978 0 : return;
1979 : }
1980 :
1981 0 : LOG3(("SpdySession::TransactionHasDataToWrite %p ID is %x",
1982 : this, stream->StreamID()));
1983 :
1984 0 : mReadyForWrite.Push(stream);
1985 : }
1986 :
1987 : void
1988 0 : SpdySession::TransactionHasDataToWrite(SpdyStream *stream)
1989 : {
1990 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1991 0 : LOG3(("SpdySession::TransactionHasDataToWrite %p stream=%p ID=%x",
1992 : this, stream, stream->StreamID()));
1993 :
1994 0 : mReadyForWrite.Push(stream);
1995 0 : SetWriteCallbacks();
1996 0 : }
1997 :
1998 : nsresult
1999 0 : SpdySession::ResumeRecv()
2000 : {
2001 0 : if (!mConnection)
2002 0 : return NS_ERROR_FAILURE;
2003 :
2004 0 : return mConnection->ResumeRecv();
2005 : }
2006 :
2007 : bool
2008 0 : SpdySession::IsPersistent()
2009 : {
2010 0 : return true;
2011 : }
2012 :
2013 : nsresult
2014 0 : SpdySession::TakeTransport(nsISocketTransport **,
2015 : nsIAsyncInputStream **,
2016 : nsIAsyncOutputStream **)
2017 : {
2018 0 : NS_ABORT_IF_FALSE(false, "TakeTransport of SpdySession");
2019 0 : return NS_ERROR_UNEXPECTED;
2020 : }
2021 :
2022 : nsHttpConnection *
2023 0 : SpdySession::TakeHttpConnection()
2024 : {
2025 0 : NS_ABORT_IF_FALSE(false, "TakeHttpConnection of SpdySession");
2026 0 : return nsnull;
2027 : }
2028 :
2029 : nsISocketTransport *
2030 0 : SpdySession::Transport()
2031 : {
2032 0 : if (!mConnection)
2033 0 : return nsnull;
2034 0 : return mConnection->Transport();
2035 : }
2036 :
2037 : //-----------------------------------------------------------------------------
2038 : // unused methods of nsAHttpTransaction
2039 : // We can be sure of this because SpdySession is only constructed in
2040 : // nsHttpConnection and is never passed out of that object
2041 : //-----------------------------------------------------------------------------
2042 :
2043 : void
2044 0 : SpdySession::SetConnection(nsAHttpConnection *)
2045 : {
2046 : // This is unexpected
2047 0 : NS_ABORT_IF_FALSE(false, "SpdySession::SetConnection()");
2048 0 : }
2049 :
2050 : void
2051 0 : SpdySession::GetSecurityCallbacks(nsIInterfaceRequestor **,
2052 : nsIEventTarget **)
2053 : {
2054 : // This is unexpected
2055 0 : NS_ABORT_IF_FALSE(false, "SpdySession::GetSecurityCallbacks()");
2056 0 : }
2057 :
2058 : void
2059 0 : SpdySession::SetSSLConnectFailed()
2060 : {
2061 0 : NS_ABORT_IF_FALSE(false, "SpdySession::SetSSLConnectFailed()");
2062 0 : }
2063 :
2064 : bool
2065 0 : SpdySession::IsDone()
2066 : {
2067 0 : NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()");
2068 0 : return false;
2069 : }
2070 :
2071 : nsresult
2072 0 : SpdySession::Status()
2073 : {
2074 0 : NS_ABORT_IF_FALSE(false, "SpdySession::Status()");
2075 0 : return NS_ERROR_UNEXPECTED;
2076 : }
2077 :
2078 : PRUint32
2079 0 : SpdySession::Available()
2080 : {
2081 0 : NS_ABORT_IF_FALSE(false, "SpdySession::Available()");
2082 0 : return 0;
2083 : }
2084 :
2085 : nsHttpRequestHead *
2086 0 : SpdySession::RequestHead()
2087 : {
2088 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
2089 0 : NS_ABORT_IF_FALSE(false,
2090 : "SpdySession::RequestHead() "
2091 : "should not be called after SPDY is setup");
2092 0 : return NULL;
2093 : }
2094 :
2095 : PRUint32
2096 0 : SpdySession::Http1xTransactionCount()
2097 : {
2098 0 : return 0;
2099 : }
2100 :
2101 : // used as an enumerator by TakeSubTransactions()
2102 : static PLDHashOperator
2103 0 : TakeStream(nsAHttpTransaction *key,
2104 : nsAutoPtr<SpdyStream> &stream,
2105 : void *closure)
2106 : {
2107 : nsTArray<nsRefPtr<nsAHttpTransaction> > *list =
2108 0 : static_cast<nsTArray<nsRefPtr<nsAHttpTransaction> > *>(closure);
2109 :
2110 0 : list->AppendElement(key);
2111 :
2112 : // removing the stream from the hash will delete the stream
2113 : // and drop the transaction reference the hash held
2114 0 : return PL_DHASH_REMOVE;
2115 : }
2116 :
2117 : nsresult
2118 0 : SpdySession::TakeSubTransactions(
2119 : nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
2120 : {
2121 : // Generally this cannot be done with spdy as transactions are
2122 : // started right away.
2123 :
2124 0 : LOG3(("SpdySession::TakeSubTransactions %p\n", this));
2125 :
2126 0 : if (mConcurrentHighWater > 0)
2127 0 : return NS_ERROR_ALREADY_OPENED;
2128 :
2129 0 : LOG3((" taking %d\n", mStreamTransactionHash.Count()));
2130 :
2131 0 : mStreamTransactionHash.Enumerate(TakeStream, &outTransactions);
2132 0 : return NS_OK;
2133 : }
2134 :
2135 : //-----------------------------------------------------------------------------
2136 : // Pass through methods of nsAHttpConnection
2137 : //-----------------------------------------------------------------------------
2138 :
2139 : nsAHttpConnection *
2140 0 : SpdySession::Connection()
2141 : {
2142 0 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
2143 0 : return mConnection;
2144 : }
2145 :
2146 : nsresult
2147 0 : SpdySession::OnHeadersAvailable(nsAHttpTransaction *transaction,
2148 : nsHttpRequestHead *requestHead,
2149 : nsHttpResponseHead *responseHead,
2150 : bool *reset)
2151 : {
2152 0 : return mConnection->OnHeadersAvailable(transaction,
2153 : requestHead,
2154 : responseHead,
2155 0 : reset);
2156 : }
2157 :
2158 : void
2159 0 : SpdySession::GetConnectionInfo(nsHttpConnectionInfo **connInfo)
2160 : {
2161 0 : mConnection->GetConnectionInfo(connInfo);
2162 0 : }
2163 :
2164 : void
2165 0 : SpdySession::GetSecurityInfo(nsISupports **supports)
2166 : {
2167 0 : mConnection->GetSecurityInfo(supports);
2168 0 : }
2169 :
2170 : bool
2171 0 : SpdySession::IsReused()
2172 : {
2173 0 : return mConnection->IsReused();
2174 : }
2175 :
2176 : nsresult
2177 0 : SpdySession::PushBack(const char *buf, PRUint32 len)
2178 : {
2179 0 : return mConnection->PushBack(buf, len);
2180 : }
2181 :
2182 : bool
2183 0 : SpdySession::LastTransactionExpectedNoContent()
2184 : {
2185 0 : return mConnection->LastTransactionExpectedNoContent();
2186 : }
2187 :
2188 : void
2189 0 : SpdySession::SetLastTransactionExpectedNoContent(bool val)
2190 : {
2191 0 : mConnection->SetLastTransactionExpectedNoContent(val);
2192 0 : }
2193 :
2194 : } // namespace mozilla::net
2195 : } // namespace mozilla
2196 :
|