Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/protocol/http/Http2Session.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
// HttpLog.h should generally be included first
8
#include "HttpLog.h"
9
10
// Log on level :5, instead of default :4.
11
#undef LOG
12
0
#define LOG(args) LOG5(args)
13
#undef LOG_ENABLED
14
#define LOG_ENABLED() LOG5_ENABLED()
15
16
#include <algorithm>
17
18
#include "Http2Session.h"
19
#include "Http2Stream.h"
20
#include "Http2Push.h"
21
22
#include "mozilla/EndianUtils.h"
23
#include "mozilla/Telemetry.h"
24
#include "mozilla/Preferences.h"
25
#include "nsHttp.h"
26
#include "nsHttpHandler.h"
27
#include "nsHttpConnection.h"
28
#include "nsIRequestContext.h"
29
#include "nsISSLSocketControl.h"
30
#include "nsISupportsPriority.h"
31
#include "nsStandardURL.h"
32
#include "nsURLHelper.h"
33
#include "prnetdb.h"
34
#include "sslt.h"
35
#include "mozilla/Sprintf.h"
36
#include "nsSocketTransportService2.h"
37
#include "nsNetUtil.h"
38
#include "nsICacheEntry.h"
39
#include "nsICacheStorageService.h"
40
#include "nsICacheStorage.h"
41
#include "CacheControlParser.h"
42
#include "LoadContextInfo.h"
43
#include "TCPFastOpenLayer.h"
44
45
namespace mozilla {
46
namespace net {
47
48
// Http2Session has multiple inheritance of things that implement nsISupports
49
NS_IMPL_ADDREF(Http2Session)
50
NS_IMPL_RELEASE(Http2Session)
51
0
NS_INTERFACE_MAP_BEGIN(Http2Session)
52
0
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
53
0
NS_INTERFACE_MAP_END
54
55
// "magic" refers to the string that preceeds HTTP/2 on the wire
56
// to help find any intermediaries speaking an older version of HTTP
57
const uint8_t Http2Session::kMagicHello[] = {
58
  0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
59
  0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
60
  0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
61
};
62
63
0
#define RETURN_SESSION_ERROR(o,x)  \
64
0
do {                             \
65
0
  (o)->mGoAwayReason = (x);      \
66
0
  return NS_ERROR_ILLEGAL_VALUE; \
67
0
  } while (0)
68
69
Http2Session::Http2Session(nsISocketTransport *aSocketTransport, enum SpdyVersion version, bool attemptingEarlyData)
70
  : mSocketTransport(aSocketTransport)
71
  , mSegmentReader(nullptr)
72
  , mSegmentWriter(nullptr)
73
  , mNextStreamID(3) // 1 is reserved for Updgrade handshakes
74
  , mLastPushedID(0)
75
  , mConcurrentHighWater(0)
76
  , mDownstreamState(BUFFERING_OPENING_SETTINGS)
77
  , mInputFrameBufferSize(kDefaultBufferSize)
78
  , mInputFrameBufferUsed(0)
79
  , mInputFrameDataSize(0)
80
  , mInputFrameDataRead(0)
81
  , mInputFrameFinal(false)
82
  , mInputFrameType(0)
83
  , mInputFrameFlags(0)
84
  , mInputFrameID(0)
85
  , mPaddingLength(0)
86
  , mInputFrameDataStream(nullptr)
87
  , mNeedsCleanup(nullptr)
88
  , mDownstreamRstReason(NO_HTTP_ERROR)
89
  , mExpectedHeaderID(0)
90
  , mExpectedPushPromiseID(0)
91
  , mContinuedPromiseStream(0)
92
  , mFlatHTTPResponseHeadersOut(0)
93
  , mShouldGoAway(false)
94
  , mClosed(false)
95
  , mCleanShutdown(false)
96
  , mReceivedSettings(false)
97
  , mTLSProfileConfirmed(false)
98
  , mGoAwayReason(NO_HTTP_ERROR)
99
  , mClientGoAwayReason(UNASSIGNED)
100
  , mPeerGoAwayReason(UNASSIGNED)
101
  , mGoAwayID(0)
102
  , mOutgoingGoAwayID(0)
103
  , mConcurrent(0)
104
  , mServerPushedResources(0)
105
  , mServerInitialStreamWindow(kDefaultRwin)
106
  , mLocalSessionWindow(kDefaultRwin)
107
  , mServerSessionWindow(kDefaultRwin)
108
  , mInitialRwin(ASpdySession::kInitialRwin)
109
  , mOutputQueueSize(kDefaultQueueSize)
110
  , mOutputQueueUsed(0)
111
  , mOutputQueueSent(0)
112
  , mLastReadEpoch(PR_IntervalNow())
113
  , mPingSentEpoch(0)
114
  , mPreviousUsed(false)
115
  , mAggregatedHeaderSize(0)
116
  , mWaitingForSettingsAck(false)
117
  , mGoAwayOnPush(false)
118
  , mUseH2Deps(false)
119
  , mAttemptingEarlyData(attemptingEarlyData)
120
  , mOriginFrameActivated(false)
121
  , mTlsHandshakeFinished(false)
122
  , mCheckNetworkStallsWithTFO(false)
123
  , mLastRequestBytesSentTime(0)
124
  , mTrrStreams(0)
125
0
{
126
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
127
0
128
0
  static uint64_t sSerial;
129
0
  mSerial = ++sSerial;
130
0
131
0
  LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial));
132
0
133
0
  mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
134
0
  mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
135
0
  mDecompressBuffer.SetCapacity(kDefaultBufferSize);
136
0
137
0
  mPushAllowance = gHttpHandler->SpdyPushAllowance();
138
0
  mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
139
0
  mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
140
0
  mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
141
0
  SendHello();
142
0
143
0
  mLastDataReadEpoch = mLastReadEpoch;
144
0
145
0
  mPingThreshold = gHttpHandler->SpdyPingThreshold();
146
0
  mPreviousPingThreshold = mPingThreshold;
147
0
  mCurrentForegroundTabOuterContentWindowId =
148
0
    gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();
149
0
}
150
151
void
152
Http2Session::Shutdown()
153
0
{
154
0
  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
155
0
    nsAutoPtr<Http2Stream> &stream = iter.Data();
156
0
157
0
    // On a clean server hangup the server sets the GoAwayID to be the ID of
158
0
    // the last transaction it processed. If the ID of stream in the
159
0
    // local stream is greater than that it can safely be restarted because the
160
0
    // server guarantees it was not partially processed. Streams that have not
161
0
    // registered an ID haven't actually been sent yet so they can always be
162
0
    // restarted.
163
0
    if (mCleanShutdown &&
164
0
        (stream->StreamID() > mGoAwayID || !stream->HasRegisteredID())) {
165
0
      CloseStream(stream, NS_ERROR_NET_RESET);  // can be restarted
166
0
    } else if (stream->RecvdData()) {
167
0
      CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
168
0
    } else if (mGoAwayReason == INADEQUATE_SECURITY) {
169
0
      CloseStream(stream, NS_ERROR_NET_INADEQUATE_SECURITY);
170
0
    } else {
171
0
      CloseStream(stream, NS_ERROR_ABORT);
172
0
    }
173
0
  }
174
0
}
175
176
Http2Session::~Http2Session()
177
0
{
178
0
  LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
179
0
        this, mDownstreamState));
180
0
181
0
  Shutdown();
182
0
183
0
  if (mTrrStreams) {
184
0
    Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN, mTrrStreams);
185
0
  }
186
0
  Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
187
0
  Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
188
0
  Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
189
0
                        mServerPushedResources);
190
0
  Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
191
0
  Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
192
0
}
193
194
void
195
Http2Session::LogIO(Http2Session *self, Http2Stream *stream,
196
                    const char *label,
197
                    const char *data, uint32_t datalen)
198
0
{
199
0
  if (!LOG5_ENABLED())
200
0
    return;
201
0
202
0
  LOG5(("Http2Session::LogIO %p stream=%p id=0x%X [%s]",
203
0
        self, stream, stream ? stream->StreamID() : 0, label));
204
0
205
0
  // Max line is (16 * 3) + 10(prefix) + newline + null
206
0
  char linebuf[128];
207
0
  uint32_t index;
208
0
  char *line = linebuf;
209
0
210
0
  linebuf[127] = 0;
211
0
212
0
  for (index = 0; index < datalen; ++index) {
213
0
    if (!(index % 16)) {
214
0
      if (index) {
215
0
        *line = 0;
216
0
        LOG5(("%s", linebuf));
217
0
      }
218
0
      line = linebuf;
219
0
      snprintf(line, 128, "%08X: ", index);
220
0
      line += 10;
221
0
    }
222
0
    snprintf(line, 128 - (line - linebuf), "%02X ", (reinterpret_cast<const uint8_t *>(data))[index]);
223
0
    line += 3;
224
0
  }
225
0
  if (index) {
226
0
    *line = 0;
227
0
    LOG5(("%s", linebuf));
228
0
  }
229
0
}
230
231
typedef nsresult (*Http2ControlFx) (Http2Session *self);
232
static Http2ControlFx sControlFunctions[] = {
233
  nullptr, // type 0 data is not a control function
234
  Http2Session::RecvHeaders,
235
  Http2Session::RecvPriority,
236
  Http2Session::RecvRstStream,
237
  Http2Session::RecvSettings,
238
  Http2Session::RecvPushPromise,
239
  Http2Session::RecvPing,
240
  Http2Session::RecvGoAway,
241
  Http2Session::RecvWindowUpdate,
242
  Http2Session::RecvContinuation,
243
  Http2Session::RecvAltSvc, // extension for type 0x0A
244
  Http2Session::RecvUnused, // 0x0B was BLOCKED still radioactive
245
  Http2Session::RecvOrigin  // extension for type 0x0C
246
};
247
248
bool
249
Http2Session::RoomForMoreConcurrent()
250
0
{
251
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
252
0
  return (mConcurrent < mMaxConcurrent);
253
0
}
254
255
bool
256
Http2Session::RoomForMoreStreams()
257
0
{
258
0
  if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
259
0
    return false;
260
0
261
0
  return !mShouldGoAway;
262
0
}
263
264
PRIntervalTime
265
Http2Session::IdleTime()
266
0
{
267
0
  return PR_IntervalNow() - mLastDataReadEpoch;
268
0
}
269
270
uint32_t
271
Http2Session::ReadTimeoutTick(PRIntervalTime now)
272
0
{
273
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
274
0
275
0
  LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
276
0
       this, PR_IntervalToSeconds(now - mLastReadEpoch)));
277
0
278
0
  uint32_t nextTick = UINT32_MAX;
279
0
  if (mCheckNetworkStallsWithTFO && mLastRequestBytesSentTime) {
280
0
    PRIntervalTime initialResponseDelta = now - mLastRequestBytesSentTime;
281
0
    if (initialResponseDelta >= gHttpHandler->FastOpenStallsTimeout()) {
282
0
      gHttpHandler->IncrementFastOpenStallsCounter();
283
0
      mCheckNetworkStallsWithTFO = false;
284
0
    } else {
285
0
      nextTick = PR_IntervalToSeconds(gHttpHandler->FastOpenStallsTimeout()) -
286
0
                 PR_IntervalToSeconds(initialResponseDelta);
287
0
    }
288
0
  }
289
0
  if (!mPingThreshold)
290
0
    return nextTick;
291
0
292
0
  if ((now - mLastReadEpoch) < mPingThreshold) {
293
0
    // recent activity means ping is not an issue
294
0
    if (mPingSentEpoch) {
295
0
      mPingSentEpoch = 0;
296
0
      if (mPreviousUsed) {
297
0
        // restore the former value
298
0
        mPingThreshold = mPreviousPingThreshold;
299
0
        mPreviousUsed = false;
300
0
      }
301
0
    }
302
0
303
0
    return std::min(nextTick, PR_IntervalToSeconds(mPingThreshold) -
304
0
                              PR_IntervalToSeconds(now - mLastReadEpoch));
305
0
  }
306
0
307
0
  if (mPingSentEpoch) {
308
0
    LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n", this));
309
0
    if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
310
0
      LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
311
0
      mPingSentEpoch = 0;
312
0
      Close(NS_ERROR_NET_TIMEOUT);
313
0
      return UINT32_MAX;
314
0
    }
315
0
    return 1; // run the tick aggressively while ping is outstanding
316
0
  }
317
0
318
0
  LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
319
0
320
0
  mPingSentEpoch = PR_IntervalNow();
321
0
  if (!mPingSentEpoch) {
322
0
    mPingSentEpoch = 1; // avoid the 0 sentinel value
323
0
  }
324
0
  GeneratePing(false);
325
0
  Unused << ResumeRecv(); // read the ping reply
326
0
327
0
  // Check for orphaned push streams. This looks expensive, but generally the
328
0
  // list is empty.
329
0
  Http2PushedStream *deleteMe;
330
0
  TimeStamp timestampNow;
331
0
  do {
332
0
    deleteMe = nullptr;
333
0
334
0
    for (uint32_t index = mPushedStreams.Length();
335
0
         index > 0 ; --index) {
336
0
      Http2PushedStream *pushedStream = mPushedStreams[index - 1];
337
0
338
0
      if (timestampNow.IsNull())
339
0
        timestampNow = TimeStamp::Now(); // lazy initializer
340
0
341
0
      // if stream finished, but is not connected, and its been like that for
342
0
      // long then cleanup the stream.
343
0
      if (pushedStream->IsOrphaned(timestampNow))
344
0
      {
345
0
        LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n",
346
0
              this, pushedStream->StreamID()));
347
0
        deleteMe = pushedStream;
348
0
        break; // don't CleanupStream() while iterating this vector
349
0
      }
350
0
    }
351
0
    if (deleteMe)
352
0
      CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
353
0
354
0
  } while (deleteMe);
355
0
356
0
  return 1; // run the tick aggressively while ping is outstanding
357
0
}
358
359
uint32_t
360
Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID)
361
0
{
362
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
363
0
  MOZ_ASSERT(mNextStreamID < 0xfffffff0,
364
0
             "should have stopped admitting streams");
365
0
  MOZ_ASSERT(!(aNewID & 1),
366
0
             "0 for autoassign pull, otherwise explicit even push assignment");
367
0
368
0
  if (!aNewID) {
369
0
    // auto generate a new pull stream ID
370
0
    aNewID = mNextStreamID;
371
0
    MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
372
0
    mNextStreamID += 2;
373
0
  }
374
0
375
0
  LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
376
0
        "concurrent=%d",this, stream, aNewID, mConcurrent));
377
0
378
0
  // We've used up plenty of ID's on this session. Start
379
0
  // moving to a new one before there is a crunch involving
380
0
  // server push streams or concurrent non-registered submits
381
0
  if (aNewID >= kMaxStreamID)
382
0
    mShouldGoAway = true;
383
0
384
0
  // integrity check
385
0
  if (mStreamIDHash.Get(aNewID)) {
386
0
    LOG3(("   New ID already present\n"));
387
0
    MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
388
0
    mShouldGoAway = true;
389
0
    return kDeadStreamID;
390
0
  }
391
0
392
0
  mStreamIDHash.Put(aNewID, stream);
393
0
394
0
  // If TCP fast Open has been used and conection was idle for some time
395
0
  // we will be cautious and watch out for bug 1395494.
396
0
  if (!mCheckNetworkStallsWithTFO && mConnection) {
397
0
    RefPtr<nsHttpConnection> conn = mConnection->HttpConnection();
398
0
    if (conn && (conn->GetFastOpenStatus() == TFO_DATA_SENT) &&
399
0
        gHttpHandler->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() &&
400
0
        IdleTime() >= gHttpHandler->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds()) {
401
0
      // If a connection was using the TCP FastOpen and it was idle for a
402
0
      // long time we should check for stalls like bug 1395494.
403
0
      mCheckNetworkStallsWithTFO = true;
404
0
      mLastRequestBytesSentTime = PR_IntervalNow();
405
0
    }
406
0
  }
407
0
408
0
  if (aNewID & 1) {
409
0
    // don't count push streams here
410
0
    MOZ_ASSERT(stream->Transaction(), "no transation for the stream!");
411
0
    RefPtr<nsHttpConnectionInfo> ci(stream->Transaction()->ConnectionInfo());
412
0
    if (ci && ci->GetTrrUsed()) {
413
0
      IncrementTrrCounter();
414
0
    }
415
0
  }
416
0
  return aNewID;
417
0
}
418
419
bool
420
Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
421
                        int32_t aPriority,
422
                        bool aUseTunnel,
423
                        nsIInterfaceRequestor *aCallbacks)
424
0
{
425
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
426
0
427
0
  // integrity check
428
0
  if (mStreamTransactionHash.Get(aHttpTransaction)) {
429
0
    LOG3(("   New transaction already present\n"));
430
0
    MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
431
0
    return false;
432
0
  }
433
0
434
0
  if (!mConnection) {
435
0
    mConnection = aHttpTransaction->Connection();
436
0
  }
437
0
438
0
  if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
439
0
    mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
440
0
    LOG3(("Http2Session::AddStream first session=%p trans=%p ", this, mFirstHttpTransaction.get()));
441
0
  }
442
0
443
0
  if (mClosed || mShouldGoAway) {
444
0
    nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
445
0
    if (trans && !trans->GetPushedStream()) {
446
0
      LOG3(("Http2Session::AddStream %p atrans=%p trans=%p session unusable - resched.\n",
447
0
            this, aHttpTransaction, trans));
448
0
      aHttpTransaction->SetConnection(nullptr);
449
0
      nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
450
0
      if (NS_FAILED(rv)) {
451
0
        LOG3(("Http2Session::AddStream %p atrans=%p trans=%p failed to initiate "
452
0
              "transaction (%08x).\n", this, aHttpTransaction, trans,
453
0
              static_cast<uint32_t>(rv)));
454
0
      }
455
0
      return true;
456
0
    }
457
0
  }
458
0
459
0
  aHttpTransaction->SetConnection(this);
460
0
  aHttpTransaction->OnActivated();
461
0
462
0
  if (aUseTunnel) {
463
0
    LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
464
0
          this, aHttpTransaction));
465
0
    DispatchOnTunnel(aHttpTransaction, aCallbacks);
466
0
    return true;
467
0
  }
468
0
469
0
  Http2Stream *stream =
470
0
    new Http2Stream(aHttpTransaction,
471
0
                    this,
472
0
                    aPriority,
473
0
                    mCurrentForegroundTabOuterContentWindowId);
474
0
475
0
  LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
476
0
        "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
477
0
478
0
  mStreamTransactionHash.Put(aHttpTransaction, stream);
479
0
480
0
  mReadyForWrite.Push(stream);
481
0
  SetWriteCallbacks();
482
0
483
0
  // Kick off the SYN transmit without waiting for the poll loop
484
0
  // This won't work for the first stream because there is no segment reader
485
0
  // yet.
486
0
  if (mSegmentReader) {
487
0
    uint32_t countRead;
488
0
    Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
489
0
  }
490
0
491
0
  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
492
0
      !aHttpTransaction->IsNullTransaction()) {
493
0
    LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
494
0
          this, aHttpTransaction));
495
0
    DontReuse();
496
0
  }
497
0
498
0
  return true;
499
0
}
500
501
void
502
Http2Session::QueueStream(Http2Stream *stream)
503
0
{
504
0
  // will be removed via processpending or a shutdown path
505
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
506
0
  MOZ_ASSERT(!stream->CountAsActive());
507
0
  MOZ_ASSERT(!stream->Queued());
508
0
509
0
  LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream));
510
0
511
#ifdef DEBUG
512
  int32_t qsize = mQueuedStreams.GetSize();
513
  for (int32_t i = 0; i < qsize; i++) {
514
    Http2Stream *qStream = static_cast<Http2Stream *>(mQueuedStreams.ObjectAt(i));
515
    MOZ_ASSERT(qStream != stream);
516
    MOZ_ASSERT(qStream->Queued());
517
  }
518
#endif
519
520
0
  stream->SetQueued(true);
521
0
  mQueuedStreams.Push(stream);
522
0
}
523
524
void
525
Http2Session::ProcessPending()
526
0
{
527
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
528
0
529
0
  Http2Stream*stream;
530
0
  while (RoomForMoreConcurrent() &&
531
0
         (stream = static_cast<Http2Stream *>(mQueuedStreams.PopFront()))) {
532
0
533
0
    LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.",
534
0
          this, stream));
535
0
    MOZ_ASSERT(!stream->CountAsActive());
536
0
    MOZ_ASSERT(stream->Queued());
537
0
    stream->SetQueued(false);
538
0
    mReadyForWrite.Push(stream);
539
0
    SetWriteCallbacks();
540
0
  }
541
0
}
542
543
nsresult
544
Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
545
                          uint32_t count, uint32_t *countWritten)
546
0
{
547
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
548
0
549
0
  if (!count) {
550
0
    *countWritten = 0;
551
0
    return NS_OK;
552
0
  }
553
0
554
0
  nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
555
0
  if (NS_SUCCEEDED(rv) && *countWritten > 0) {
556
0
    mLastReadEpoch = PR_IntervalNow();
557
0
    mCheckNetworkStallsWithTFO = false;
558
0
  }
559
0
  return rv;
560
0
}
561
562
void
563
Http2Session::SetWriteCallbacks()
564
0
{
565
0
  if (mConnection &&
566
0
      (GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) {
567
0
    Unused << mConnection->ResumeSend();
568
0
  }
569
0
}
570
571
void
572
Http2Session::RealignOutputQueue()
573
0
{
574
0
  if (mAttemptingEarlyData) {
575
0
    // We can't realign right now, because we may need what's in there if early
576
0
    // data fails.
577
0
    return;
578
0
  }
579
0
580
0
  mOutputQueueUsed -= mOutputQueueSent;
581
0
  memmove(mOutputQueueBuffer.get(),
582
0
          mOutputQueueBuffer.get() + mOutputQueueSent,
583
0
          mOutputQueueUsed);
584
0
  mOutputQueueSent = 0;
585
0
}
586
587
void
588
Http2Session::FlushOutputQueue()
589
0
{
590
0
  if (!mSegmentReader || !mOutputQueueUsed)
591
0
    return;
592
0
593
0
  nsresult rv;
594
0
  uint32_t countRead;
595
0
  uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
596
0
597
0
  if (!avail && mAttemptingEarlyData) {
598
0
    // This is kind of a hack, but there are cases where we'll have already
599
0
    // written the data we want whlie doing early data, but we get called again
600
0
    // with a reader, and we need to avoid calling the reader when there's
601
0
    // nothing for it to read.
602
0
    return;
603
0
  }
604
0
605
0
  rv = mSegmentReader->
606
0
    OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
607
0
                  &countRead);
608
0
  LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d",
609
0
        this, avail, static_cast<uint32_t>(rv), countRead));
610
0
611
0
  // Dont worry about errors on write, we will pick this up as a read error too
612
0
  if (NS_FAILED(rv))
613
0
    return;
614
0
615
0
  mOutputQueueSent += countRead;
616
0
617
0
  if (mAttemptingEarlyData) {
618
0
    return;
619
0
  }
620
0
621
0
  if (countRead == avail) {
622
0
    mOutputQueueUsed = 0;
623
0
    mOutputQueueSent = 0;
624
0
    return;
625
0
  }
626
0
627
0
  // If the output queue is close to filling up and we have sent out a good
628
0
  // chunk of data from the beginning then realign it.
629
0
630
0
  if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
631
0
      ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
632
0
    RealignOutputQueue();
633
0
  }
634
0
}
635
636
void
637
Http2Session::DontReuse()
638
0
{
639
0
  LOG3(("Http2Session::DontReuse %p\n", this));
640
0
  if (!OnSocketThread()) {
641
0
    LOG3(("Http2Session %p not on socket thread\n", this));
642
0
    nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
643
0
      "Http2Session::DontReuse", this, &Http2Session::DontReuse);
644
0
    gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
645
0
    return;
646
0
  }
647
0
648
0
  mShouldGoAway = true;
649
0
  if (!mClosed && !mStreamTransactionHash.Count()) {
650
0
    Close(NS_OK);
651
0
  }
652
0
}
653
654
enum SpdyVersion
655
Http2Session::SpdyVersion()
656
0
{
657
0
  return SpdyVersion::HTTP_2;
658
0
}
659
660
uint32_t
661
Http2Session::GetWriteQueueSize()
662
0
{
663
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
664
0
665
0
  return mReadyForWrite.GetSize();
666
0
}
667
668
void
669
Http2Session::ChangeDownstreamState(enum internalStateType newState)
670
0
{
671
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
672
0
673
0
  LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X",
674
0
        this, mDownstreamState, newState));
675
0
  mDownstreamState = newState;
676
0
}
677
678
void
679
Http2Session::ResetDownstreamState()
680
0
{
681
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
682
0
683
0
  LOG3(("Http2Session::ResetDownstreamState() %p", this));
684
0
  ChangeDownstreamState(BUFFERING_FRAME_HEADER);
685
0
686
0
  if (mInputFrameFinal && mInputFrameDataStream) {
687
0
    mInputFrameFinal = false;
688
0
    LOG3(("  SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
689
0
    mInputFrameDataStream->SetRecvdFin(true);
690
0
    MaybeDecrementConcurrent(mInputFrameDataStream);
691
0
  }
692
0
  mInputFrameFinal = false;
693
0
  mInputFrameBufferUsed = 0;
694
0
  mInputFrameDataStream = nullptr;
695
0
}
696
697
// return true if activated (and counted against max)
698
// otherwise return false and queue
699
bool
700
Http2Session::TryToActivate(Http2Stream *aStream)
701
0
{
702
0
  if (aStream->Queued()) {
703
0
    LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this, aStream));
704
0
    return false;
705
0
  }
706
0
707
0
  if (!RoomForMoreConcurrent()) {
708
0
    LOG3(("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
709
0
          "streams\n", this, aStream));
710
0
    QueueStream(aStream);
711
0
    return false;
712
0
  }
713
0
714
0
  LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
715
0
  IncrementConcurrent(aStream);
716
0
  return true;
717
0
}
718
719
void
720
Http2Session::IncrementConcurrent(Http2Stream *stream)
721
0
{
722
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
723
0
  MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
724
0
             "Do not activate pushed streams");
725
0
726
0
  nsAHttpTransaction *trans = stream->Transaction();
727
0
  if (!trans || !trans->IsNullTransaction() || trans->QuerySpdyConnectTransaction()) {
728
0
729
0
    MOZ_ASSERT(!stream->CountAsActive());
730
0
    stream->SetCountAsActive(true);
731
0
    ++mConcurrent;
732
0
733
0
    if (mConcurrent > mConcurrentHighWater) {
734
0
      mConcurrentHighWater = mConcurrent;
735
0
    }
736
0
    LOG3(("Http2Session::IncrementCounter %p counting stream %p Currently %d "
737
0
          "streams in session, high water mark is %d\n",
738
0
          this, stream, mConcurrent, mConcurrentHighWater));
739
0
  }
740
0
}
741
742
// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
743
// dest must have 9 bytes of allocated space
744
template<typename charType> void
745
Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
746
                                uint8_t frameType, uint8_t frameFlags,
747
                                uint32_t streamID)
748
0
{
749
0
  MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
750
0
  MOZ_ASSERT(!(streamID & 0x80000000));
751
0
  MOZ_ASSERT(!frameFlags ||
752
0
             (frameType != FRAME_TYPE_PRIORITY &&
753
0
              frameType != FRAME_TYPE_RST_STREAM &&
754
0
              frameType != FRAME_TYPE_GOAWAY &&
755
0
              frameType != FRAME_TYPE_WINDOW_UPDATE));
756
0
757
0
  dest[0] = 0x00;
758
0
  NetworkEndian::writeUint16(dest + 1, frameLength);
759
0
  dest[3] = frameType;
760
0
  dest[4] = frameFlags;
761
0
  NetworkEndian::writeUint32(dest + 5, streamID);
762
0
}
Unexecuted instantiation: void mozilla::net::Http2Session::CreateFrameHeader<char*>(char*, unsigned short, unsigned char, unsigned char, unsigned int)
Unexecuted instantiation: void mozilla::net::Http2Session::CreateFrameHeader<unsigned char*>(unsigned char*, unsigned short, unsigned char, unsigned char, unsigned int)
763
764
char *
765
Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded)
766
0
{
767
0
  // this is an infallible allocation (if an allocation is
768
0
  // needed, which is probably isn't)
769
0
  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
770
0
               mOutputQueueUsed, mOutputQueueSize);
771
0
  return mOutputQueueBuffer.get() + mOutputQueueUsed;
772
0
}
773
774
template void
775
Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength,
776
                                uint8_t frameType, uint8_t frameFlags,
777
                                uint32_t streamID);
778
779
template void
780
Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength,
781
                                uint8_t frameType, uint8_t frameFlags,
782
                                uint32_t streamID);
783
784
void
785
Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream)
786
0
{
787
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
788
0
  LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n",
789
0
        this, aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
790
0
791
0
  if (!aStream->CountAsActive())
792
0
    return;
793
0
794
0
  MOZ_ASSERT(mConcurrent);
795
0
  aStream->SetCountAsActive(false);
796
0
  --mConcurrent;
797
0
  ProcessPending();
798
0
}
799
800
// Need to decompress some data in order to keep the compression
801
// context correct, but we really don't care what the result is
802
nsresult
803
Http2Session::UncompressAndDiscard(bool isPush)
804
0
{
805
0
  nsresult rv;
806
0
  nsAutoCString trash;
807
0
808
0
  rv = mDecompressor.DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(mDecompressBuffer.BeginReading()),
809
0
                                       mDecompressBuffer.Length(), trash, isPush);
810
0
  mDecompressBuffer.Truncate();
811
0
  if (NS_FAILED(rv)) {
812
0
    LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n",
813
0
          this));
814
0
    mGoAwayReason = COMPRESSION_ERROR;
815
0
    return rv;
816
0
  }
817
0
  return NS_OK;
818
0
}
819
820
void
821
Http2Session::GeneratePing(bool isAck)
822
0
{
823
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
824
0
  LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
825
0
826
0
  char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
827
0
  mOutputQueueUsed += kFrameHeaderBytes + 8;
828
0
829
0
  if (isAck) {
830
0
    CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
831
0
    memcpy(packet + kFrameHeaderBytes,
832
0
           mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
833
0
  } else {
834
0
    CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
835
0
    memset(packet + kFrameHeaderBytes, 0, 8);
836
0
  }
837
0
838
0
  LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
839
0
  FlushOutputQueue();
840
0
}
841
842
void
843
Http2Session::GenerateSettingsAck()
844
0
{
845
0
  // need to generate ack of this settings frame
846
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
847
0
  LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
848
0
849
0
  char *packet = EnsureOutputBuffer(kFrameHeaderBytes);
850
0
  mOutputQueueUsed += kFrameHeaderBytes;
851
0
  CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
852
0
  LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
853
0
  FlushOutputQueue();
854
0
}
855
856
void
857
Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight)
858
0
{
859
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
860
0
  LOG3(("Http2Session::GeneratePriority %p %X %X\n",
861
0
        this, aID, aPriorityWeight));
862
0
863
0
  char *packet = CreatePriorityFrame(aID, 0, aPriorityWeight);
864
0
865
0
  LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
866
0
  FlushOutputQueue();
867
0
}
868
869
void
870
Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
871
0
{
872
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
873
0
874
0
  // make sure we don't do this twice for the same stream (at least if we
875
0
  // have a stream entry for it)
876
0
  Http2Stream *stream = mStreamIDHash.Get(aID);
877
0
  if (stream) {
878
0
    if (stream->SentReset())
879
0
      return;
880
0
    stream->SetSentReset(true);
881
0
  }
882
0
883
0
  LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
884
0
885
0
  uint32_t frameSize = kFrameHeaderBytes + 4;
886
0
  char *packet = EnsureOutputBuffer(frameSize);
887
0
  mOutputQueueUsed += frameSize;
888
0
  CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
889
0
890
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);
891
0
892
0
  LogIO(this, nullptr, "Generate Reset", packet, frameSize);
893
0
  FlushOutputQueue();
894
0
}
895
896
void
897
Http2Session::GenerateGoAway(uint32_t aStatusCode)
898
0
{
899
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
900
0
  LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
901
0
902
0
  mClientGoAwayReason = aStatusCode;
903
0
  uint32_t frameSize = kFrameHeaderBytes + 8;
904
0
  char *packet = EnsureOutputBuffer(frameSize);
905
0
  mOutputQueueUsed += frameSize;
906
0
907
0
  CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
908
0
909
0
  // last-good-stream-id are bytes 9-12 reflecting pushes
910
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);
911
0
912
0
  // bytes 13-16 are the status code.
913
0
  NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);
914
0
915
0
  LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
916
0
  FlushOutputQueue();
917
0
}
918
919
// The Hello is comprised of
920
// 1] 24 octets of magic, which are designed to
921
// flush out silent but broken intermediaries
922
// 2] a settings frame which sets a small flow control window for pushes
923
// 3] a window update frame which creates a large session flow control window
924
// 4] 6 priority frames for streams which will never be opened with headers
925
//    these streams (3, 5, 7, 9, b, d) build a dependency tree that all other
926
//    streams will be direct leaves of.
927
void
928
Http2Session::SendHello()
929
0
{
930
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
931
0
  LOG3(("Http2Session::SendHello %p\n", this));
932
0
933
0
  // sized for magic + 5 settings and a session window update and 6 priority frames
934
0
  // 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window update,
935
0
  // 6 priority frames at 14 (9 + 5) each
936
0
  static const uint32_t maxSettings = 5;
937
0
  static const uint32_t prioritySize = kPriorityGroupCount * (kFrameHeaderBytes + 5);
938
0
  static const uint32_t maxDataLen = 24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
939
0
  char *packet = EnsureOutputBuffer(maxDataLen);
940
0
  memcpy(packet, kMagicHello, 24);
941
0
  mOutputQueueUsed += 24;
942
0
  LogIO(this, nullptr, "Magic Connection Header", packet, 24);
943
0
944
0
  packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
945
0
  memset(packet, 0, maxDataLen - 24);
946
0
947
0
  // frame header will be filled in after we know how long the frame is
948
0
  uint8_t numberOfEntries = 0;
949
0
950
0
  // entries need to be listed in order by ID
951
0
  // 1st entry is bytes 9 to 14
952
0
  // 2nd entry is bytes 15 to 20
953
0
  // 3rd entry is bytes 21 to 26
954
0
  // 4th entry is bytes 27 to 32
955
0
  // 5th entry is bytes 33 to 38
956
0
957
0
  // Let the other endpoint know about our default HPACK decompress table size
958
0
  uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
959
0
  mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
960
0
  NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_HEADER_TABLE_SIZE);
961
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, maxHpackBufferSize);
962
0
  numberOfEntries++;
963
0
964
0
  if (!gHttpHandler->AllowPush()) {
965
0
    // If we don't support push then set MAX_CONCURRENT to 0 and also
966
0
    // set ENABLE_PUSH to 0
967
0
    NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_ENABLE_PUSH);
968
0
    // The value portion of the setting pair is already initialized to 0
969
0
    numberOfEntries++;
970
0
971
0
    NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_MAX_CONCURRENT);
972
0
    // The value portion of the setting pair is already initialized to 0
973
0
    numberOfEntries++;
974
0
975
0
    mWaitingForSettingsAck = true;
976
0
  }
977
0
978
0
  // Advertise the Push RWIN for the session, and on each new pull stream
979
0
  // send a window update
980
0
  NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_INITIAL_WINDOW);
981
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
982
0
  numberOfEntries++;
983
0
984
0
  // Make sure the other endpoint knows that we're sticking to the default max
985
0
  // frame size
986
0
  NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_MAX_FRAME_SIZE);
987
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
988
0
  numberOfEntries++;
989
0
990
0
  MOZ_ASSERT(numberOfEntries <= maxSettings);
991
0
  uint32_t dataLen = 6 * numberOfEntries;
992
0
  CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
993
0
  mOutputQueueUsed += kFrameHeaderBytes + dataLen;
994
0
995
0
  LogIO(this, nullptr, "Generate Settings", packet, kFrameHeaderBytes + dataLen);
996
0
997
0
  // now bump the local session window from 64KB
998
0
  uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
999
0
  if (kDefaultRwin < mInitialRwin) {
1000
0
    // send a window update for the session (Stream 0) for something large
1001
0
    mLocalSessionWindow = mInitialRwin;
1002
0
1003
0
    packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
1004
0
    CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
1005
0
    mOutputQueueUsed += kFrameHeaderBytes + 4;
1006
0
    NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
1007
0
1008
0
    LOG3(("Session Window increase at start of session %p %u\n",
1009
0
          this, sessionWindowBump));
1010
0
    LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
1011
0
  }
1012
0
1013
0
  if (gHttpHandler->UseH2Deps() && gHttpHandler->CriticalRequestPrioritization()) {
1014
0
    mUseH2Deps = true;
1015
0
    MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
1016
0
    CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
1017
0
    mNextStreamID += 2;
1018
0
    MOZ_ASSERT(mNextStreamID == kOtherGroupID);
1019
0
    CreatePriorityNode(kOtherGroupID, 0, 100, "other");
1020
0
    mNextStreamID += 2;
1021
0
    MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
1022
0
    CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
1023
0
    mNextStreamID += 2;
1024
0
    MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
1025
0
    CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0, "speculative");
1026
0
    mNextStreamID += 2;
1027
0
    MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
1028
0
    CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
1029
0
    mNextStreamID += 2;
1030
0
    MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
1031
0
    CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
1032
0
    mNextStreamID += 2;
1033
0
    // Hey, you! YES YOU! If you add/remove any groups here, you almost
1034
0
    // certainly need to change the lookup of the stream/ID hash in
1035
0
    // Http2Session::OnTransportStatus. Yeah, that's right. YOU!
1036
0
  }
1037
0
1038
0
  FlushOutputQueue();
1039
0
}
1040
1041
void
1042
Http2Session::SendPriorityFrame(uint32_t streamID,
1043
                                uint32_t dependsOn,
1044
                                uint8_t weight)
1045
0
{
1046
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1047
0
  LOG3(("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
1048
0
        "weight %d\n", this, streamID, dependsOn, weight));
1049
0
1050
0
  char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
1051
0
1052
0
  LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
1053
0
  FlushOutputQueue();
1054
0
}
1055
1056
char *
1057
Http2Session::CreatePriorityFrame(uint32_t streamID,
1058
                                  uint32_t dependsOn,
1059
                                  uint8_t weight)
1060
0
{
1061
0
  MOZ_ASSERT(streamID, "Priority on stream 0");
1062
0
  char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
1063
0
  CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
1064
0
  mOutputQueueUsed += kFrameHeaderBytes + 5;
1065
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, dependsOn); // depends on
1066
0
  packet[kFrameHeaderBytes + 4] = weight; // weight
1067
0
  return packet;
1068
0
}
1069
1070
void
1071
Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
1072
                                 const char *label)
1073
0
{
1074
0
  char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
1075
0
1076
0
  LOG3(("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
1077
0
        "weight %d for %s class\n", this, streamID, dependsOn, weight, label));
1078
0
  LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
1079
0
}
1080
1081
// perform a bunch of integrity checks on the stream.
1082
// returns true if passed, false (plus LOG and ABORT) if failed.
1083
bool
1084
Http2Session::VerifyStream(Http2Stream *aStream, uint32_t aOptionalID = 0)
1085
0
{
1086
0
  // This is annoying, but at least it is O(1)
1087
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1088
0
1089
0
#ifndef DEBUG
1090
0
  // Only do the real verification in debug builds
1091
0
  return true;
1092
#else //DEBUG
1093
1094
  if (!aStream)
1095
    return true;
1096
1097
  uint32_t test = 0;
1098
1099
  do {
1100
    if (aStream->StreamID() == kDeadStreamID)
1101
      break;
1102
1103
    nsAHttpTransaction *trans = aStream->Transaction();
1104
1105
    test++;
1106
    if (!trans)
1107
      break;
1108
1109
    test++;
1110
    if (mStreamTransactionHash.Get(trans) != aStream)
1111
      break;
1112
1113
    if (aStream->StreamID()) {
1114
      Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID());
1115
1116
      test++;
1117
      if (idStream != aStream)
1118
        break;
1119
1120
      if (aOptionalID) {
1121
        test++;
1122
        if (idStream->StreamID() != aOptionalID)
1123
          break;
1124
      }
1125
    }
1126
1127
    // tests passed
1128
    return true;
1129
  } while (false);
1130
1131
  LOG3(("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
1132
       "optionalID=0x%X trans=%p test=%d\n",
1133
       this, aStream, aStream->StreamID(),
1134
       aOptionalID, aStream->Transaction(), test));
1135
1136
  MOZ_ASSERT(false, "VerifyStream");
1137
  return false;
1138
#endif //DEBUG
1139
}
1140
1141
void
1142
Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
1143
                            errorType aResetCode)
1144
0
{
1145
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1146
0
  LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n",
1147
0
        this, aStream, aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult)));
1148
0
  if (!aStream) {
1149
0
    return;
1150
0
  }
1151
0
1152
0
  Http2PushedStream *pushSource = aStream->PushSource();
1153
0
  if (pushSource) {
1154
0
    // aStream is a synthetic  attached to an even push
1155
0
    MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
1156
0
    MOZ_ASSERT(!aStream->StreamID());
1157
0
    MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
1158
0
    aStream->ClearPushSource();
1159
0
  }
1160
0
1161
0
  if (aStream->DeferCleanup(aResult)) {
1162
0
    LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
1163
0
    return;
1164
0
  }
1165
0
1166
0
  if (!VerifyStream(aStream)) {
1167
0
    LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
1168
0
    return;
1169
0
  }
1170
0
1171
0
  // don't reset a stream that has recevied a fin or rst
1172
0
  if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
1173
0
      !(mInputFrameFinal && (aStream == mInputFrameDataStream))) { // !(recvdfin with mark pending)
1174
0
    LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n", aStream->StreamID(), aResetCode));
1175
0
    GenerateRstStream(aResetCode, aStream->StreamID());
1176
0
  }
1177
0
1178
0
  CloseStream(aStream, aResult);
1179
0
1180
0
  // Remove the stream from the ID hash table and, if an even id, the pushed
1181
0
  // table too.
1182
0
  uint32_t id = aStream->StreamID();
1183
0
  if (id > 0) {
1184
0
    mStreamIDHash.Remove(id);
1185
0
    if (!(id & 1)) {
1186
0
      mPushedStreams.RemoveElement(aStream);
1187
0
      Http2PushedStream *pushStream = static_cast<Http2PushedStream *>(aStream);
1188
0
      nsAutoCString hashKey;
1189
0
      DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
1190
0
      MOZ_ASSERT(rv);
1191
0
      nsIRequestContext *requestContext = aStream->RequestContext();
1192
0
      if (requestContext) {
1193
0
        SpdyPushCache *cache = nullptr;
1194
0
        requestContext->GetSpdyPushCache(&cache);
1195
0
        if (cache) {
1196
0
          // Make sure the id of the stream in the push cache is the same
1197
0
          // as the id of the stream we're cleaning up! See bug 1368080.
1198
0
          Http2PushedStream *trash = cache->RemovePushedStreamHttp2ByID(hashKey, aStream->StreamID());
1199
0
          LOG3(("Http2Session::CleanupStream %p aStream=%p pushStream=%p trash=%p",
1200
0
                this, aStream, pushStream, trash));
1201
0
        }
1202
0
      }
1203
0
    }
1204
0
  }
1205
0
1206
0
  RemoveStreamFromQueues(aStream);
1207
0
1208
0
  // removing from the stream transaction hash will
1209
0
  // delete the Http2Stream and drop the reference to
1210
0
  // its transaction
1211
0
  mStreamTransactionHash.Remove(aStream->Transaction());
1212
0
1213
0
  if (mShouldGoAway && !mStreamTransactionHash.Count())
1214
0
    Close(NS_OK);
1215
0
1216
0
  if (pushSource) {
1217
0
    pushSource->SetDeferCleanupOnSuccess(false);
1218
0
    CleanupStream(pushSource, aResult, aResetCode);
1219
0
  }
1220
0
}
1221
1222
void
1223
Http2Session::CleanupStream(uint32_t aID, nsresult aResult, errorType aResetCode)
1224
0
{
1225
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1226
0
  Http2Stream *stream = mStreamIDHash.Get(aID);
1227
0
  LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n",
1228
0
        this, aID, stream));
1229
0
  if (!stream) {
1230
0
    return;
1231
0
  }
1232
0
  CleanupStream(stream, aResult, aResetCode);
1233
0
}
1234
1235
static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue)
1236
0
{
1237
0
  size_t size = queue.GetSize();
1238
0
  for (size_t count = 0; count < size; ++count) {
1239
0
    Http2Stream *stream = static_cast<Http2Stream *>(queue.PopFront());
1240
0
    if (stream != aStream)
1241
0
      queue.Push(stream);
1242
0
  }
1243
0
}
1244
1245
void
1246
Http2Session::RemoveStreamFromQueues(Http2Stream *aStream)
1247
0
{
1248
0
  RemoveStreamFromQueue(aStream, mReadyForWrite);
1249
0
  RemoveStreamFromQueue(aStream, mQueuedStreams);
1250
0
  RemoveStreamFromQueue(aStream, mPushesReadyForRead);
1251
0
  RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
1252
0
}
1253
1254
void
1255
Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
1256
0
{
1257
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1258
0
  LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n",
1259
0
        this, aStream, aStream->StreamID(), static_cast<uint32_t>(aResult)));
1260
0
1261
0
  MaybeDecrementConcurrent(aStream);
1262
0
1263
0
  // Check if partial frame reader
1264
0
  if (aStream == mInputFrameDataStream) {
1265
0
    LOG3(("Stream had active partial read frame on close"));
1266
0
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
1267
0
    mInputFrameDataStream = nullptr;
1268
0
  }
1269
0
1270
0
  RemoveStreamFromQueues(aStream);
1271
0
1272
0
  if (aStream->IsTunnel()) {
1273
0
    UnRegisterTunnel(aStream);
1274
0
  }
1275
0
1276
0
  // Send the stream the close() indication
1277
0
  aStream->Close(aResult);
1278
0
}
1279
1280
nsresult
1281
Http2Session::SetInputFrameDataStream(uint32_t streamID)
1282
0
{
1283
0
  mInputFrameDataStream = mStreamIDHash.Get(streamID);
1284
0
  if (VerifyStream(mInputFrameDataStream, streamID))
1285
0
    return NS_OK;
1286
0
1287
0
  LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
1288
0
       streamID));
1289
0
  mInputFrameDataStream = nullptr;
1290
0
  return NS_ERROR_UNEXPECTED;
1291
0
}
1292
1293
nsresult
1294
Http2Session::ParsePadding(uint8_t &paddingControlBytes, uint16_t &paddingLength)
1295
0
{
1296
0
  if (mInputFrameFlags & kFlag_PADDED) {
1297
0
    paddingLength = *reinterpret_cast<uint8_t *>(&mInputFrameBuffer[kFrameHeaderBytes]);
1298
0
    paddingControlBytes = 1;
1299
0
  } else {
1300
0
    paddingLength = 0;
1301
0
    paddingControlBytes = 0;
1302
0
  }
1303
0
1304
0
  if (static_cast<uint32_t>(paddingLength + paddingControlBytes) > mInputFrameDataSize) {
1305
0
    // This is fatal to the session
1306
0
    LOG3(("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
1307
0
          "paddingLength %d > frame size %d\n",
1308
0
          this, mInputFrameID, paddingLength, mInputFrameDataSize));
1309
0
    RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
1310
0
  }
1311
0
1312
0
  return NS_OK;
1313
0
}
1314
1315
nsresult
1316
Http2Session::RecvHeaders(Http2Session *self)
1317
0
{
1318
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
1319
0
             self->mInputFrameType == FRAME_TYPE_CONTINUATION);
1320
0
1321
0
  bool isContinuation = self->mExpectedHeaderID != 0;
1322
0
1323
0
  // If this doesn't have END_HEADERS set on it then require the next
1324
0
  // frame to be HEADERS of the same ID
1325
0
  bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
1326
0
1327
0
  if (endHeadersFlag)
1328
0
    self->mExpectedHeaderID = 0;
1329
0
  else
1330
0
    self->mExpectedHeaderID = self->mInputFrameID;
1331
0
1332
0
  uint32_t priorityLen = 0;
1333
0
  if (self->mInputFrameFlags & kFlag_PRIORITY) {
1334
0
    priorityLen = 5;
1335
0
  }
1336
0
  nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
1337
0
  MOZ_ASSERT(NS_SUCCEEDED(rv));
1338
0
1339
0
  // Find out how much padding this frame has, so we can only extract the real
1340
0
  // header data from the frame.
1341
0
  uint16_t paddingLength = 0;
1342
0
  uint8_t paddingControlBytes = 0;
1343
0
1344
0
  if (!isContinuation) {
1345
0
    self->mDecompressBuffer.Truncate();
1346
0
    rv = self->ParsePadding(paddingControlBytes, paddingLength);
1347
0
    if (NS_FAILED(rv)) {
1348
0
      return rv;
1349
0
    }
1350
0
  }
1351
0
1352
0
  LOG3(("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
1353
0
        "end_stream=%d end_headers=%d priority_group=%d "
1354
0
        "paddingLength=%d padded=%d\n",
1355
0
        self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
1356
0
        self->mInputFrameFlags & kFlag_END_STREAM,
1357
0
        self->mInputFrameFlags & kFlag_END_HEADERS,
1358
0
        self->mInputFrameFlags & kFlag_PRIORITY,
1359
0
        paddingLength,
1360
0
        self->mInputFrameFlags & kFlag_PADDED));
1361
0
1362
0
  if ((paddingControlBytes + priorityLen + paddingLength) > self->mInputFrameDataSize) {
1363
0
    // This is fatal to the session
1364
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1365
0
  }
1366
0
1367
0
  if (!self->mInputFrameDataStream) {
1368
0
    // Cannot find stream. We can continue the session, but we need to
1369
0
    // uncompress the header block to maintain the correct compression context
1370
0
1371
0
    LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
1372
0
          "0x%X failed. NextStreamID = 0x%X\n",
1373
0
          self, self->mInputFrameID, self->mNextStreamID));
1374
0
1375
0
    if (self->mInputFrameID >= self->mNextStreamID)
1376
0
      self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
1377
0
1378
0
    self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen],
1379
0
                                   self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
1380
0
1381
0
    if (self->mInputFrameFlags & kFlag_END_HEADERS) {
1382
0
      rv = self->UncompressAndDiscard(false);
1383
0
      if (NS_FAILED(rv)) {
1384
0
        LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
1385
0
        // this is fatal to the session
1386
0
        self->mGoAwayReason = COMPRESSION_ERROR;
1387
0
        return rv;
1388
0
      }
1389
0
    }
1390
0
1391
0
    self->ResetDownstreamState();
1392
0
    return NS_OK;
1393
0
  }
1394
0
1395
0
  // make sure this is either the first headers or a trailer
1396
0
  if (self->mInputFrameDataStream->AllHeadersReceived() &&
1397
0
      !(self->mInputFrameFlags & kFlag_END_STREAM)) {
1398
0
    // Any header block after the first that does *not* end the stream is
1399
0
    // illegal.
1400
0
    LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self, self->mInputFrameID));
1401
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1402
0
  }
1403
0
1404
0
  // queue up any compression bytes
1405
0
  self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen],
1406
0
                                 self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
1407
0
1408
0
  self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
1409
0
  self->mLastDataReadEpoch = self->mLastReadEpoch;
1410
0
1411
0
  if (!isContinuation) {
1412
0
    self->mAggregatedHeaderSize = self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength;
1413
0
  } else {
1414
0
    self->mAggregatedHeaderSize += self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength;
1415
0
  }
1416
0
1417
0
  if (!endHeadersFlag) { // more are coming - don't process yet
1418
0
    self->ResetDownstreamState();
1419
0
    return NS_OK;
1420
0
  }
1421
0
1422
0
  if (isContinuation) {
1423
0
    Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS, self->mAggregatedHeaderSize);
1424
0
  }
1425
0
1426
0
  rv = self->ResponseHeadersComplete();
1427
0
  if (rv == NS_ERROR_ILLEGAL_VALUE) {
1428
0
    LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
1429
0
          self, self->mInputFrameID));
1430
0
    self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
1431
0
    self->ResetDownstreamState();
1432
0
    rv = NS_OK;
1433
0
  } else if (NS_FAILED(rv)) {
1434
0
    // This is fatal to the session.
1435
0
    self->mGoAwayReason = COMPRESSION_ERROR;
1436
0
  }
1437
0
  return rv;
1438
0
}
1439
1440
// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
1441
// should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
1442
// fine, and any other error is fatal to the session.
1443
nsresult
1444
Http2Session::ResponseHeadersComplete()
1445
0
{
1446
0
  LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d",
1447
0
        this, mInputFrameDataStream->StreamID(), mInputFrameFinal));
1448
0
1449
0
  // Anything prior to AllHeadersReceived() => true is actual headers. After
1450
0
  // that, we need to handle them as trailers instead (which are special-cased
1451
0
  // so we don't have to use the nasty chunked parser for all h2, just in case).
1452
0
  if (mInputFrameDataStream->AllHeadersReceived()) {
1453
0
    LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
1454
0
    MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
1455
0
    nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(&mDecompressor,
1456
0
        mDecompressBuffer);
1457
0
    if (NS_FAILED(rv)) {
1458
0
      LOG3(("Http2Session::ResponseHeadersComplete trailer conversion failed\n"));
1459
0
      return rv;
1460
0
    }
1461
0
    mFlatHTTPResponseHeadersOut = 0;
1462
0
    mFlatHTTPResponseHeaders.Truncate();
1463
0
    if (mInputFrameFinal) {
1464
0
      // need to process the fin
1465
0
      ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
1466
0
    } else {
1467
0
      ResetDownstreamState();
1468
0
    }
1469
0
1470
0
    return NS_OK;
1471
0
  }
1472
0
1473
0
  // if this turns out to be a 1xx response code we have to
1474
0
  // undo the headers received bit that we are setting here.
1475
0
  bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
1476
0
  mInputFrameDataStream->SetAllHeadersReceived();
1477
0
1478
0
  // The stream needs to see flattened http headers
1479
0
  // Uncompressed http/2 format headers currently live in
1480
0
  // Http2Stream::mDecompressBuffer - convert that to HTTP format in
1481
0
  // mFlatHTTPResponseHeaders via ConvertHeaders()
1482
0
1483
0
  nsresult rv;
1484
0
  int32_t httpResponseCode; // out param to ConvertResponseHeaders
1485
0
  mFlatHTTPResponseHeadersOut = 0;
1486
0
  rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
1487
0
                                                     mDecompressBuffer,
1488
0
                                                     mFlatHTTPResponseHeaders,
1489
0
                                                     httpResponseCode);
1490
0
  if (rv == NS_ERROR_NET_RESET) {
1491
0
    LOG(("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders reset\n", this));
1492
0
    // This means the stream found connection-oriented auth. Treat this like we
1493
0
    // got a reset with HTTP_1_1_REQUIRED.
1494
0
    mInputFrameDataStream->Transaction()->DisableSpdy();
1495
0
    CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
1496
0
    ResetDownstreamState();
1497
0
    return NS_OK;
1498
0
  } else if (NS_FAILED(rv)) {
1499
0
    return rv;
1500
0
  }
1501
0
1502
0
  // allow more headers in the case of 1xx
1503
0
  if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
1504
0
    mInputFrameDataStream->UnsetAllHeadersReceived();
1505
0
  }
1506
0
1507
0
  ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
1508
0
  return NS_OK;
1509
0
}
1510
1511
nsresult
1512
Http2Session::RecvPriority(Http2Session *self)
1513
0
{
1514
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
1515
0
1516
0
  if (self->mInputFrameDataSize != 5) {
1517
0
    LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n",
1518
0
          self, self->mInputFrameDataSize));
1519
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1520
0
  }
1521
0
1522
0
  if (!self->mInputFrameID) {
1523
0
    LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
1524
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1525
0
  }
1526
0
1527
0
  nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
1528
0
  if (NS_FAILED(rv))
1529
0
    return rv;
1530
0
1531
0
  uint32_t newPriorityDependency = NetworkEndian::readUint32(
1532
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);
1533
0
  bool exclusive = !!(newPriorityDependency & 0x80000000);
1534
0
  newPriorityDependency &= 0x7fffffff;
1535
0
  uint8_t newPriorityWeight = *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
1536
0
  if (self->mInputFrameDataStream) {
1537
0
    self->mInputFrameDataStream->SetPriorityDependency(newPriorityDependency,
1538
0
                                                       newPriorityWeight,
1539
0
                                                       exclusive);
1540
0
  }
1541
0
1542
0
  self->ResetDownstreamState();
1543
0
  return NS_OK;
1544
0
}
1545
1546
nsresult
1547
Http2Session::RecvRstStream(Http2Session *self)
1548
0
{
1549
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
1550
0
1551
0
  if (self->mInputFrameDataSize != 4) {
1552
0
    LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
1553
0
          self, self->mInputFrameDataSize));
1554
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1555
0
  }
1556
0
1557
0
  if (!self->mInputFrameID) {
1558
0
    LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
1559
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1560
0
  }
1561
0
1562
0
  self->mDownstreamRstReason = NetworkEndian::readUint32(
1563
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);
1564
0
1565
0
  LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
1566
0
        self, self->mDownstreamRstReason, self->mInputFrameID));
1567
0
1568
0
  DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
1569
0
  MOZ_ASSERT(NS_SUCCEEDED(rv));
1570
0
  if (!self->mInputFrameDataStream) {
1571
0
    // if we can't find the stream just ignore it (4.2 closed)
1572
0
    self->ResetDownstreamState();
1573
0
    return NS_OK;
1574
0
  }
1575
0
1576
0
  self->mInputFrameDataStream->SetRecvdReset(true);
1577
0
  self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
1578
0
  self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
1579
0
  return NS_OK;
1580
0
}
1581
1582
nsresult
1583
Http2Session::RecvSettings(Http2Session *self)
1584
0
{
1585
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
1586
0
1587
0
  if (self->mInputFrameID) {
1588
0
    LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n",
1589
0
          self, self->mInputFrameID));
1590
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1591
0
  }
1592
0
1593
0
  if (self->mInputFrameDataSize % 6) {
1594
0
    // Number of Settings is determined by dividing by each 6 byte setting
1595
0
    // entry. So the payload must be a multiple of 6.
1596
0
    LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d",
1597
0
          self, self->mInputFrameDataSize));
1598
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1599
0
  }
1600
0
1601
0
  self->mReceivedSettings = true;
1602
0
1603
0
  uint32_t numEntries = self->mInputFrameDataSize / 6;
1604
0
  LOG3(("Http2Session::RecvSettings %p SETTINGS Control Frame "
1605
0
        "with %d entries ack=%X", self, numEntries,
1606
0
        self->mInputFrameFlags & kFlag_ACK));
1607
0
1608
0
  if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
1609
0
    LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n", self));
1610
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1611
0
  }
1612
0
1613
0
  for (uint32_t index = 0; index < numEntries; ++index) {
1614
0
    uint8_t *setting = reinterpret_cast<uint8_t *>
1615
0
      (self->mInputFrameBuffer.get()) + kFrameHeaderBytes + index * 6;
1616
0
1617
0
    uint16_t id = NetworkEndian::readUint16(setting);
1618
0
    uint32_t value = NetworkEndian::readUint32(setting + 2);
1619
0
    LOG3(("Settings ID %u, Value %u", id, value));
1620
0
1621
0
    switch (id)
1622
0
    {
1623
0
    case SETTINGS_TYPE_HEADER_TABLE_SIZE:
1624
0
      LOG3(("Compression header table setting received: %d\n", value));
1625
0
      self->mCompressor.SetMaxBufferSize(value);
1626
0
      break;
1627
0
1628
0
    case SETTINGS_TYPE_ENABLE_PUSH:
1629
0
      LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
1630
0
      // nop
1631
0
      break;
1632
0
1633
0
    case SETTINGS_TYPE_MAX_CONCURRENT:
1634
0
      self->mMaxConcurrent = value;
1635
0
      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
1636
0
      self->ProcessPending();
1637
0
      break;
1638
0
1639
0
    case SETTINGS_TYPE_INITIAL_WINDOW:
1640
0
      {
1641
0
        Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
1642
0
        int32_t delta = value - self->mServerInitialStreamWindow;
1643
0
        self->mServerInitialStreamWindow = value;
1644
0
1645
0
        // SETTINGS only adjusts stream windows. Leave the session window alone.
1646
0
        // We need to add the delta to all open streams (delta can be negative)
1647
0
        for (auto iter = self->mStreamTransactionHash.Iter();
1648
0
             !iter.Done();
1649
0
             iter.Next()) {
1650
0
          iter.Data()->UpdateServerReceiveWindow(delta);
1651
0
        }
1652
0
      }
1653
0
      break;
1654
0
1655
0
    case SETTINGS_TYPE_MAX_FRAME_SIZE:
1656
0
      {
1657
0
        if ((value < kMaxFrameData) || (value >= 0x01000000)) {
1658
0
          LOG3(("Received invalid max frame size 0x%X", value));
1659
0
          RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1660
0
        }
1661
0
        // We stick to the default for simplicity's sake, so nothing to change
1662
0
      }
1663
0
      break;
1664
0
1665
0
    default:
1666
0
      break;
1667
0
    }
1668
0
  }
1669
0
1670
0
  self->ResetDownstreamState();
1671
0
1672
0
  if (!(self->mInputFrameFlags & kFlag_ACK)) {
1673
0
    self->GenerateSettingsAck();
1674
0
  } else if (self->mWaitingForSettingsAck) {
1675
0
    self->mGoAwayOnPush = true;
1676
0
  }
1677
0
1678
0
  return NS_OK;
1679
0
}
1680
1681
nsresult
1682
Http2Session::RecvPushPromise(Http2Session *self)
1683
0
{
1684
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE ||
1685
0
             self->mInputFrameType == FRAME_TYPE_CONTINUATION);
1686
0
1687
0
  // Find out how much padding this frame has, so we can only extract the real
1688
0
  // header data from the frame.
1689
0
  uint16_t paddingLength = 0;
1690
0
  uint8_t paddingControlBytes = 0;
1691
0
1692
0
  // If this doesn't have END_PUSH_PROMISE set on it then require the next
1693
0
  // frame to be PUSH_PROMISE of the same ID
1694
0
  uint32_t promiseLen;
1695
0
  uint32_t promisedID;
1696
0
1697
0
  if (self->mExpectedPushPromiseID) {
1698
0
    promiseLen = 0; // really a continuation frame
1699
0
    promisedID = self->mContinuedPromiseStream;
1700
0
  } else {
1701
0
    self->mDecompressBuffer.Truncate();
1702
0
    nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
1703
0
    if (NS_FAILED(rv)) {
1704
0
      return rv;
1705
0
    }
1706
0
    promiseLen = 4;
1707
0
    promisedID = NetworkEndian::readUint32(
1708
0
        self->mInputFrameBuffer.get() + kFrameHeaderBytes + paddingControlBytes);
1709
0
    promisedID &= 0x7fffffff;
1710
0
    if (promisedID <= self->mLastPushedID) {
1711
0
      LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
1712
0
            self, promisedID, self->mLastPushedID));
1713
0
      RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1714
0
    }
1715
0
    self->mLastPushedID = promisedID;
1716
0
  }
1717
0
1718
0
  uint32_t associatedID = self->mInputFrameID;
1719
0
1720
0
  if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
1721
0
    self->mExpectedPushPromiseID = 0;
1722
0
    self->mContinuedPromiseStream = 0;
1723
0
  } else {
1724
0
    self->mExpectedPushPromiseID = self->mInputFrameID;
1725
0
    self->mContinuedPromiseStream = promisedID;
1726
0
  }
1727
0
1728
0
  if ((paddingControlBytes + promiseLen + paddingLength) > self->mInputFrameDataSize) {
1729
0
    // This is fatal to the session
1730
0
    LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
1731
0
          "PROTOCOL_ERROR extra %d > frame size %d\n",
1732
0
          self, promisedID, associatedID, (paddingControlBytes + promiseLen + paddingLength),
1733
0
          self->mInputFrameDataSize));
1734
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1735
0
  }
1736
0
1737
0
  LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
1738
0
        "paddingLength %d padded %d\n",
1739
0
        self, promisedID, associatedID, paddingLength,
1740
0
        self->mInputFrameFlags & kFlag_PADDED));
1741
0
1742
0
  if (!associatedID || !promisedID || (promisedID & 1)) {
1743
0
    LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
1744
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1745
0
  }
1746
0
1747
0
  // confirm associated-to
1748
0
  nsresult rv = self->SetInputFrameDataStream(associatedID);
1749
0
  if (NS_FAILED(rv))
1750
0
    return rv;
1751
0
1752
0
  Http2Stream *associatedStream = self->mInputFrameDataStream;
1753
0
  ++(self->mServerPushedResources);
1754
0
1755
0
  // Anytime we start using the high bit of stream ID (either client or server)
1756
0
  // begin to migrate to a new session.
1757
0
  if (promisedID >= kMaxStreamID)
1758
0
    self->mShouldGoAway = true;
1759
0
1760
0
  bool resetStream = true;
1761
0
  SpdyPushCache *cache = nullptr;
1762
0
1763
0
  if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
1764
0
    LOG3(("Http2Session::RecvPushPromise %p cache push while in GoAway "
1765
0
          "mode refused.\n", self));
1766
0
    self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1767
0
  } else if (!gHttpHandler->AllowPush()) {
1768
0
    // ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
1769
0
    LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
1770
0
    if (self->mGoAwayOnPush) {
1771
0
      LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
1772
0
      RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
1773
0
    }
1774
0
    self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1775
0
  } else if (!(associatedID & 1)) {
1776
0
    LOG3(("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) stream not allowed\n",
1777
0
          self, associatedID));
1778
0
    self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1779
0
  } else if (!associatedStream) {
1780
0
    LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self));
1781
0
    self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1782
0
  } else if (Http2PushedStream::TestOnPush(associatedStream)) {
1783
0
    LOG3(("Http2Session::RecvPushPromise %p will be handled by push listener.", self));
1784
0
    resetStream = false;
1785
0
  } else {
1786
0
    nsIRequestContext *requestContext = associatedStream->RequestContext();
1787
0
    if (requestContext) {
1788
0
      requestContext->GetSpdyPushCache(&cache);
1789
0
      if (!cache) {
1790
0
        cache = new SpdyPushCache();
1791
0
        if (!cache || NS_FAILED(requestContext->SetSpdyPushCache(cache))) {
1792
0
          delete cache;
1793
0
          cache = nullptr;
1794
0
        }
1795
0
      }
1796
0
    }
1797
0
    if (!cache) {
1798
0
      // this is unexpected, but we can handle it just by refusing the push
1799
0
      LOG3(("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
1800
0
      self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1801
0
    } else {
1802
0
      resetStream = false;
1803
0
    }
1804
0
  }
1805
0
1806
0
  if (resetStream) {
1807
0
    // Need to decompress the headers even though we aren't using them yet in
1808
0
    // order to keep the compression context consistent for other frames
1809
0
    self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + promiseLen],
1810
0
                                   self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
1811
0
    if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
1812
0
      rv = self->UncompressAndDiscard(true);
1813
0
      if (NS_FAILED(rv)) {
1814
0
        LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
1815
0
        self->mGoAwayReason = COMPRESSION_ERROR;
1816
0
        return rv;
1817
0
      }
1818
0
    }
1819
0
    self->ResetDownstreamState();
1820
0
    return NS_OK;
1821
0
  }
1822
0
1823
0
  self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + promiseLen],
1824
0
                                 self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
1825
0
1826
0
  if (self->mInputFrameType != FRAME_TYPE_CONTINUATION) {
1827
0
    self->mAggregatedHeaderSize = self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength;
1828
0
  } else {
1829
0
    self->mAggregatedHeaderSize += self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength;
1830
0
  }
1831
0
1832
0
  if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
1833
0
    LOG3(("Http2Session::RecvPushPromise not finishing processing for multi-frame push\n"));
1834
0
    self->ResetDownstreamState();
1835
0
    return NS_OK;
1836
0
  }
1837
0
1838
0
  if (self->mInputFrameType == FRAME_TYPE_CONTINUATION) {
1839
0
    Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS, self->mAggregatedHeaderSize);
1840
0
  }
1841
0
1842
0
  // Create the buffering transaction and push stream
1843
0
  RefPtr<Http2PushTransactionBuffer> transactionBuffer =
1844
0
    new Http2PushTransactionBuffer();
1845
0
  transactionBuffer->SetConnection(self);
1846
0
  Http2PushedStream *pushedStream =
1847
0
    new Http2PushedStream(transactionBuffer,
1848
0
                          self,
1849
0
                          associatedStream,
1850
0
                          promisedID,
1851
0
                          self->mCurrentForegroundTabOuterContentWindowId);
1852
0
1853
0
  rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
1854
0
                                        self->mDecompressBuffer,
1855
0
                                        pushedStream->GetRequestString());
1856
0
1857
0
  if (rv == NS_ERROR_NOT_IMPLEMENTED) {
1858
0
    LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
1859
0
    self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
1860
0
    delete pushedStream;
1861
0
    self->ResetDownstreamState();
1862
0
    return NS_OK;
1863
0
  }
1864
0
1865
0
  if (rv == NS_ERROR_ILLEGAL_VALUE) {
1866
0
    // This means the decompression completed ok, but there was a problem with
1867
0
    // the decoded headers. Reset the stream and go away.
1868
0
    self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
1869
0
    delete pushedStream;
1870
0
    self->ResetDownstreamState();
1871
0
    return NS_OK;
1872
0
  } else if (NS_FAILED(rv)) {
1873
0
    // This is fatal to the session.
1874
0
    self->mGoAwayReason = COMPRESSION_ERROR;
1875
0
    return rv;
1876
0
  }
1877
0
1878
0
  // Ownership of the pushed stream is by the transaction hash, just as it
1879
0
  // is for a client initiated stream. Errors that aren't fatal to the
1880
0
  // whole session must call cleanupStream() after this point in order
1881
0
  // to remove the stream from that hash.
1882
0
  self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
1883
0
  self->mPushedStreams.AppendElement(pushedStream);
1884
0
1885
0
  if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) {
1886
0
    LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
1887
0
    self->mGoAwayReason = INTERNAL_ERROR;
1888
0
    return NS_ERROR_FAILURE;
1889
0
  }
1890
0
1891
0
  if (promisedID > self->mOutgoingGoAwayID)
1892
0
    self->mOutgoingGoAwayID = promisedID;
1893
0
1894
0
  // Fake the request side of the pushed HTTP transaction. Sets up hash
1895
0
  // key and origin
1896
0
  uint32_t notUsed;
1897
0
  Unused << pushedStream->ReadSegments(nullptr, 1, &notUsed);
1898
0
1899
0
  nsAutoCString key;
1900
0
  if (!pushedStream->GetHashKey(key)) {
1901
0
    LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
1902
0
    self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
1903
0
    self->ResetDownstreamState();
1904
0
    return NS_OK;
1905
0
  }
1906
0
1907
0
  // does the pushed origin belong on this connection?
1908
0
  LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
1909
0
        pushedStream->Origin().get()));
1910
0
  nsCOMPtr<nsIURI> pushedOrigin;
1911
0
  rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedOrigin);
1912
0
  nsAutoCString pushedHostName;
1913
0
  int32_t pushedPort = -1;
1914
0
  if (NS_SUCCEEDED(rv)) {
1915
0
    rv = pushedOrigin->GetHost(pushedHostName);
1916
0
  }
1917
0
  if (NS_SUCCEEDED(rv)) {
1918
0
    rv = pushedOrigin->GetPort(&pushedPort);
1919
0
    if (NS_SUCCEEDED(rv) && pushedPort == -1) {
1920
0
      // Need to get the right default port, so TestJoinConnection below can
1921
0
      // check things correctly. See bug 1397621.
1922
0
      bool isHttp = false;
1923
0
      if (NS_SUCCEEDED(pushedOrigin->SchemeIs("http", &isHttp)) && isHttp) {
1924
0
        pushedPort = NS_HTTP_DEFAULT_PORT;
1925
0
      } else {
1926
0
        pushedPort = NS_HTTPS_DEFAULT_PORT;
1927
0
      }
1928
0
    }
1929
0
  }
1930
0
  if (NS_FAILED(rv) ||
1931
0
      !self->TestJoinConnection(pushedHostName, pushedPort)) {
1932
0
    LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
1933
0
          self, pushedStream->Origin().get()));
1934
0
    self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
1935
0
    self->ResetDownstreamState();
1936
0
    return NS_OK;
1937
0
  }
1938
0
1939
0
  if (pushedStream->TryOnPush()) {
1940
0
    LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
1941
0
          "stream %p will not be placed into session cache.\n", self, pushedStream));
1942
0
  } else {
1943
0
    LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n", self));
1944
0
    if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
1945
0
      // This only happens if they've already pushed us this item.
1946
0
      LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
1947
0
      self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
1948
0
      self->ResetDownstreamState();
1949
0
      return NS_OK;
1950
0
    }
1951
0
1952
0
    // Kick off a lookup into the HTTP cache so we can cancel the push if it's
1953
0
    // unneeded (we already have it in our local regular cache). See bug 1367551.
1954
0
    nsCOMPtr<nsICacheStorageService> css =
1955
0
      do_GetService("@mozilla.org/netwerk/cache-storage-service;1");
1956
0
    mozilla::OriginAttributes oa;
1957
0
    pushedStream->GetOriginAttributes(&oa);
1958
0
    RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, oa);
1959
0
    nsCOMPtr<nsICacheStorage> ds;
1960
0
    css->DiskCacheStorage(lci, false, getter_AddRefs(ds));
1961
0
    // Build up our full URL for the cache lookup
1962
0
    nsAutoCString spec;
1963
0
    spec.Assign(pushedStream->Origin());
1964
0
    spec.Append(pushedStream->Path());
1965
0
    nsCOMPtr<nsIURI> pushedURL;
1966
0
    // Nifty trick: this doesn't actually do anything origin-specific, it's just
1967
0
    // named that way. So by passing it the full spec here, we get a URL with
1968
0
    // the full path.
1969
0
    // Another nifty trick! Even though this is using nsIURIs (which are not
1970
0
    // generally ok off the main thread), since we're not using the protocol
1971
0
    // handler to create any URIs, this will work just fine here. Don't try this
1972
0
    // at home, though, kids. I'm a trained professional.
1973
0
    if (NS_SUCCEEDED(Http2Stream::MakeOriginURL(spec, pushedURL))) {
1974
0
      LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry", self));
1975
0
      RefPtr<CachePushCheckCallback> cpcc = new CachePushCheckCallback(self, promisedID, pushedStream->GetRequestString());
1976
0
      if (NS_FAILED(ds->AsyncOpenURI(pushedURL, EmptyCString(), nsICacheStorage::OPEN_READONLY|nsICacheStorage::OPEN_SECRETLY, cpcc))) {
1977
0
        LOG3(("Http2Session::RecvPushPromise %p failed to open cache entry for push check", self));
1978
0
      }
1979
0
    }
1980
0
  }
1981
0
1982
0
  pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
1983
0
  static_assert(Http2Stream::kWorstPriority >= 0,
1984
0
                "kWorstPriority out of range");
1985
0
  uint8_t priorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
1986
0
    (Http2Stream::kWorstPriority - Http2Stream::kNormalPriority);
1987
0
  pushedStream->SetPriority(Http2Stream::kWorstPriority);
1988
0
  self->GeneratePriority(promisedID, priorityWeight);
1989
0
  self->ResetDownstreamState();
1990
0
  return NS_OK;
1991
0
}
1992
1993
NS_IMPL_ISUPPORTS(Http2Session::CachePushCheckCallback, nsICacheEntryOpenCallback);
1994
1995
Http2Session::CachePushCheckCallback::CachePushCheckCallback(Http2Session *session, uint32_t promisedID, const nsACString &requestString)
1996
  :mPromisedID(promisedID)
1997
0
{
1998
0
  mSession = session;
1999
0
  mRequestHead.ParseHeaderSet(requestString.BeginReading());
2000
0
}
2001
2002
NS_IMETHODIMP
2003
Http2Session::CachePushCheckCallback::OnCacheEntryCheck(nsICacheEntry *entry, nsIApplicationCache *appCache, uint32_t *result)
2004
0
{
2005
0
  MOZ_ASSERT(OnSocketThread(), "Not on socket thread?!");
2006
0
2007
0
  // We never care to fully open the entry, since we won't actually use it.
2008
0
  // We just want to be able to do all our checks to see if a future channel can
2009
0
  // use this entry, or if we need to accept the push.
2010
0
  *result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
2011
0
2012
0
  bool isForcedValid = false;
2013
0
  entry->GetIsForcedValid(&isForcedValid);
2014
0
2015
0
  nsHttpResponseHead cachedResponseHead;
2016
0
  nsresult rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
2017
0
  if (NS_FAILED(rv)) {
2018
0
    // Couldn't make sense of what's in the cache entry, go ahead and accept
2019
0
    // the push.
2020
0
    return NS_OK;
2021
0
  }
2022
0
2023
0
  if ((cachedResponseHead.Status() / 100) != 2) {
2024
0
    // Assume the push is sending us a success, while we don't have one in the
2025
0
    // cache, so we'll accept the push.
2026
0
    return NS_OK;
2027
0
  }
2028
0
2029
0
  // Get the method that was used to generate the cached response
2030
0
  nsCString buf;
2031
0
  rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
2032
0
  if (NS_FAILED(rv)) {
2033
0
    // Can't check request method, accept the push
2034
0
    return NS_OK;
2035
0
  }
2036
0
  nsAutoCString pushedMethod;
2037
0
  mRequestHead.Method(pushedMethod);
2038
0
  if (!buf.Equals(pushedMethod)) {
2039
0
    // Methods don't match, accept the push
2040
0
    return NS_OK;
2041
0
  }
2042
0
2043
0
  int64_t size, contentLength;
2044
0
  rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
2045
0
  if (NS_FAILED(rv)) {
2046
0
    // Couldn't figure out if this was partial or not, accept the push.
2047
0
    return NS_OK;
2048
0
  }
2049
0
2050
0
  if (size == int64_t(-1) || contentLength != size) {
2051
0
    // This is partial content in the cache, accept the push.
2052
0
    return NS_OK;
2053
0
  }
2054
0
2055
0
  nsAutoCString requestedETag;
2056
0
  if (NS_FAILED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
2057
0
    // Can't check etag
2058
0
    return NS_OK;
2059
0
  }
2060
0
  if (!requestedETag.IsEmpty()) {
2061
0
    nsAutoCString cachedETag;
2062
0
    if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
2063
0
      // Can't check etag
2064
0
      return NS_OK;
2065
0
    }
2066
0
    if (!requestedETag.Equals(cachedETag)) {
2067
0
      // ETags don't match, accept the push.
2068
0
      return NS_OK;
2069
0
    }
2070
0
  }
2071
0
2072
0
  nsAutoCString imsString;
2073
0
  Unused << mRequestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
2074
0
  if (!buf.IsEmpty()) {
2075
0
    uint32_t ims = buf.ToInteger(&rv);
2076
0
    uint32_t lm;
2077
0
    rv = cachedResponseHead.GetLastModifiedValue(&lm);
2078
0
    if (NS_SUCCEEDED(rv) && lm && lm < ims) {
2079
0
      // The push appears to be newer than what's in our cache, accept it.
2080
0
      return NS_OK;
2081
0
    }
2082
0
  }
2083
0
2084
0
  nsAutoCString cacheControlRequestHeader;
2085
0
  Unused << mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
2086
0
  CacheControlParser cacheControlRequest(cacheControlRequestHeader);
2087
0
  if (cacheControlRequest.NoStore()) {
2088
0
    // Don't use a no-store cache entry, accept the push.
2089
0
    return NS_OK;
2090
0
  }
2091
0
2092
0
  nsCString cachedAuth;
2093
0
  rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
2094
0
  if (NS_SUCCEEDED(rv)) {
2095
0
    uint32_t lastModifiedTime;
2096
0
    rv = entry->GetLastModified(&lastModifiedTime);
2097
0
    if (NS_SUCCEEDED(rv)) {
2098
0
      if ((gHttpHandler->SessionStartTime() > lastModifiedTime) && !cachedAuth.IsEmpty()) {
2099
0
        // Need to revalidate this, as the auth is old. Accept the push.
2100
0
        return NS_OK;
2101
0
      }
2102
0
2103
0
      if (cachedAuth.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization)) {
2104
0
        // They're pushing us something with auth, but we didn't cache anything
2105
0
        // with auth. Accept the push.
2106
0
        return NS_OK;
2107
0
      }
2108
0
    }
2109
0
  }
2110
0
2111
0
  bool weaklyFramed, isImmutable;
2112
0
  nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
2113
0
                                          &weaklyFramed, &isImmutable);
2114
0
2115
0
  // We'll need this value in later computations...
2116
0
  uint32_t lastModifiedTime;
2117
0
  rv = entry->GetLastModified(&lastModifiedTime);
2118
0
  if (NS_FAILED(rv)) {
2119
0
    // Ugh, this really sucks. OK, accept the push.
2120
0
    return NS_OK;
2121
0
  }
2122
0
2123
0
  // Determine if this is the first time that this cache entry
2124
0
  // has been accessed during this session.
2125
0
  bool fromPreviousSession =
2126
0
          (gHttpHandler->SessionStartTime() > lastModifiedTime);
2127
0
2128
0
  bool validationRequired = nsHttp::ValidationRequired(isForcedValid,
2129
0
    &cachedResponseHead, 0/*NWGH: ??? - loadFlags*/, false, isImmutable, false, mRequestHead, entry,
2130
0
    cacheControlRequest, fromPreviousSession);
2131
0
2132
0
  if (validationRequired) {
2133
0
    // A real channel would most likely hit the net at this point, so let's
2134
0
    // accept the push.
2135
0
    return NS_OK;
2136
0
  }
2137
0
2138
0
  // If we get here, then we would be able to use this cache entry. Cancel the
2139
0
  // push so as not to waste any more bandwidth.
2140
0
  mSession->CleanupStream(mPromisedID, NS_ERROR_FAILURE, Http2Session::REFUSED_STREAM_ERROR);
2141
0
2142
0
  return NS_OK;
2143
0
}
2144
2145
NS_IMETHODIMP
2146
Http2Session::CachePushCheckCallback::OnCacheEntryAvailable(
2147
    nsICacheEntry *entry, bool isNew, nsIApplicationCache *appCache,
2148
    nsresult result)
2149
0
{
2150
0
  // Nothing to do here, all the work is in OnCacheEntryCheck.
2151
0
  return NS_OK;
2152
0
}
2153
2154
nsresult
2155
Http2Session::RecvPing(Http2Session *self)
2156
0
{
2157
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
2158
0
2159
0
  LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
2160
0
        self->mInputFrameFlags));
2161
0
2162
0
  if (self->mInputFrameDataSize != 8) {
2163
0
    LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d",
2164
0
          self, self->mInputFrameDataSize));
2165
0
    RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
2166
0
  }
2167
0
2168
0
  if (self->mInputFrameID) {
2169
0
    LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
2170
0
          self, self->mInputFrameID));
2171
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2172
0
  }
2173
0
2174
0
  if (self->mInputFrameFlags & kFlag_ACK) {
2175
0
    // presumably a reply to our timeout ping.. don't reply to it
2176
0
    self->mPingSentEpoch = 0;
2177
0
  } else {
2178
0
    // reply with a ack'd ping
2179
0
    self->GeneratePing(true);
2180
0
  }
2181
0
2182
0
  self->ResetDownstreamState();
2183
0
  return NS_OK;
2184
0
}
2185
2186
nsresult
2187
Http2Session::RecvGoAway(Http2Session *self)
2188
0
{
2189
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
2190
0
2191
0
  if (self->mInputFrameDataSize < 8) {
2192
0
    // data > 8 is an opaque token that we can't interpret. NSPR Logs will
2193
0
    // have the hex of all packets so there is no point in separately logging.
2194
0
    LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
2195
0
          self, self->mInputFrameDataSize));
2196
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2197
0
  }
2198
0
2199
0
  if (self->mInputFrameID) {
2200
0
    LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
2201
0
          self, self->mInputFrameID));
2202
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2203
0
  }
2204
0
2205
0
  self->mShouldGoAway = true;
2206
0
  self->mGoAwayID = NetworkEndian::readUint32(
2207
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);
2208
0
  self->mGoAwayID &= 0x7fffffff;
2209
0
  self->mCleanShutdown = true;
2210
0
  self->mPeerGoAwayReason = NetworkEndian::readUint32(
2211
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
2212
0
2213
0
  // Find streams greater than the last-good ID and mark them for deletion
2214
0
  // in the mGoAwayStreamsToRestart queue. The underlying transaction can be
2215
0
  // restarted.
2216
0
  for (auto iter = self->mStreamTransactionHash.Iter();
2217
0
       !iter.Done();
2218
0
       iter.Next()) {
2219
0
    // these streams were not processed by the server and can be restarted.
2220
0
    // Do that after the enumerator completes to avoid the risk of
2221
0
    // a restart event re-entrantly modifying this hash. Be sure not to restart
2222
0
    // a pushed (even numbered) stream
2223
0
    nsAutoPtr<Http2Stream>& stream = iter.Data();
2224
0
    if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
2225
0
        !stream->HasRegisteredID()) {
2226
0
      self->mGoAwayStreamsToRestart.Push(stream);
2227
0
    }
2228
0
  }
2229
0
2230
0
  // Process the streams marked for deletion and restart.
2231
0
  size_t size = self->mGoAwayStreamsToRestart.GetSize();
2232
0
  for (size_t count = 0; count < size; ++count) {
2233
0
    Http2Stream *stream =
2234
0
      static_cast<Http2Stream *>(self->mGoAwayStreamsToRestart.PopFront());
2235
0
2236
0
    if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
2237
0
      stream->Transaction()->DisableSpdy();
2238
0
    }
2239
0
    self->CloseStream(stream, NS_ERROR_NET_RESET);
2240
0
    if (stream->HasRegisteredID())
2241
0
      self->mStreamIDHash.Remove(stream->StreamID());
2242
0
    self->mStreamTransactionHash.Remove(stream->Transaction());
2243
0
  }
2244
0
2245
0
  // Queued streams can also be deleted from this session and restarted
2246
0
  // in another one. (they were never sent on the network so they implicitly
2247
0
  // are not covered by the last-good id.
2248
0
  size = self->mQueuedStreams.GetSize();
2249
0
  for (size_t count = 0; count < size; ++count) {
2250
0
    Http2Stream *stream =
2251
0
      static_cast<Http2Stream *>(self->mQueuedStreams.PopFront());
2252
0
    MOZ_ASSERT(stream->Queued());
2253
0
    stream->SetQueued(false);
2254
0
    if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
2255
0
      stream->Transaction()->DisableSpdy();
2256
0
    }
2257
0
    self->CloseStream(stream, NS_ERROR_NET_RESET);
2258
0
    self->mStreamTransactionHash.Remove(stream->Transaction());
2259
0
  }
2260
0
2261
0
  LOG3(("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
2262
0
        "live streams=%d\n", self, self->mGoAwayID, self->mPeerGoAwayReason,
2263
0
        self->mStreamTransactionHash.Count()));
2264
0
2265
0
  self->ResetDownstreamState();
2266
0
  return NS_OK;
2267
0
}
2268
2269
nsresult
2270
Http2Session::RecvWindowUpdate(Http2Session *self)
2271
0
{
2272
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
2273
0
2274
0
  if (self->mInputFrameDataSize != 4) {
2275
0
    LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
2276
0
          self, self->mInputFrameDataSize));
2277
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2278
0
  }
2279
0
2280
0
  uint32_t delta = NetworkEndian::readUint32(
2281
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);
2282
0
  delta &= 0x7fffffff;
2283
0
2284
0
  LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n",
2285
0
        self, delta, self->mInputFrameID));
2286
0
2287
0
  if (self->mInputFrameID) { // stream window
2288
0
    nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
2289
0
    if (NS_FAILED(rv))
2290
0
      return rv;
2291
0
2292
0
    if (!self->mInputFrameDataStream) {
2293
0
      LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
2294
0
            self, self->mInputFrameID));
2295
0
      // only resest the session if the ID is one we haven't ever opened
2296
0
      if (self->mInputFrameID >= self->mNextStreamID)
2297
0
        self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
2298
0
      self->ResetDownstreamState();
2299
0
      return NS_OK;
2300
0
    }
2301
0
2302
0
    if (delta == 0) {
2303
0
      LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
2304
0
            self));
2305
0
      self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
2306
0
                          PROTOCOL_ERROR);
2307
0
      self->ResetDownstreamState();
2308
0
      return NS_OK;
2309
0
    }
2310
0
2311
0
    int64_t oldRemoteWindow = self->mInputFrameDataStream->ServerReceiveWindow();
2312
0
    self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
2313
0
    if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
2314
0
      // a window cannot reach 2^31 and be in compliance. Our calculations
2315
0
      // are 64 bit safe though.
2316
0
      LOG3(("Http2Session::RecvWindowUpdate %p stream window "
2317
0
            "exceeds 2^31 - 1\n", self));
2318
0
      self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
2319
0
                          FLOW_CONTROL_ERROR);
2320
0
      self->ResetDownstreamState();
2321
0
      return NS_OK;
2322
0
    }
2323
0
2324
0
    LOG3(("Http2Session::RecvWindowUpdate %p stream 0x%X window "
2325
0
          "%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n",
2326
0
          self, self->mInputFrameID, oldRemoteWindow, delta, oldRemoteWindow + delta));
2327
0
2328
0
  } else { // session window update
2329
0
    if (delta == 0) {
2330
0
      LOG3(("Http2Session::RecvWindowUpdate %p received 0 session window update",
2331
0
            self));
2332
0
      RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2333
0
    }
2334
0
2335
0
    int64_t oldRemoteWindow = self->mServerSessionWindow;
2336
0
    self->mServerSessionWindow += delta;
2337
0
2338
0
    if (self->mServerSessionWindow >= 0x80000000) {
2339
0
      // a window cannot reach 2^31 and be in compliance. Our calculations
2340
0
      // are 64 bit safe though.
2341
0
      LOG3(("Http2Session::RecvWindowUpdate %p session window "
2342
0
            "exceeds 2^31 - 1\n", self));
2343
0
      RETURN_SESSION_ERROR(self, FLOW_CONTROL_ERROR);
2344
0
    }
2345
0
2346
0
    if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
2347
0
      LOG3(("Http2Session::RecvWindowUpdate %p restart session window\n",
2348
0
            self));
2349
0
      for (auto iter = self->mStreamTransactionHash.Iter();
2350
0
           !iter.Done();
2351
0
           iter.Next()) {
2352
0
        MOZ_ASSERT(self->mServerSessionWindow > 0);
2353
0
2354
0
        nsAutoPtr<Http2Stream>& stream = iter.Data();
2355
0
        if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
2356
0
          continue;
2357
0
        }
2358
0
2359
0
        self->mReadyForWrite.Push(stream);
2360
0
        self->SetWriteCallbacks();
2361
0
      }
2362
0
    }
2363
0
    LOG3(("Http2Session::RecvWindowUpdate %p session window "
2364
0
          "%" PRId64 " increased by %d now %" PRId64 ".\n", self,
2365
0
          oldRemoteWindow, delta, oldRemoteWindow + delta));
2366
0
  }
2367
0
2368
0
  self->ResetDownstreamState();
2369
0
  return NS_OK;
2370
0
}
2371
2372
nsresult
2373
Http2Session::RecvContinuation(Http2Session *self)
2374
0
{
2375
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
2376
0
  MOZ_ASSERT(self->mInputFrameID);
2377
0
  MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
2378
0
  MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
2379
0
2380
0
  LOG3(("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
2381
0
        "promise id 0x%X header id 0x%X\n",
2382
0
        self, self->mInputFrameFlags, self->mInputFrameID,
2383
0
        self->mExpectedPushPromiseID, self->mExpectedHeaderID));
2384
0
2385
0
  DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
2386
0
  MOZ_ASSERT(NS_SUCCEEDED(rv));
2387
0
2388
0
  if (!self->mInputFrameDataStream) {
2389
0
    LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
2390
0
          self->mInputFrameID));
2391
0
    RETURN_SESSION_ERROR(self, PROTOCOL_ERROR);
2392
0
  }
2393
0
2394
0
  // continued headers
2395
0
  if (self->mExpectedHeaderID) {
2396
0
    self->mInputFrameFlags &= ~kFlag_PRIORITY;
2397
0
    return RecvHeaders(self);
2398
0
  }
2399
0
2400
0
  // continued push promise
2401
0
  if (self->mInputFrameFlags & kFlag_END_HEADERS) {
2402
0
    self->mInputFrameFlags &= ~kFlag_END_HEADERS;
2403
0
    self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
2404
0
  }
2405
0
  return RecvPushPromise(self);
2406
0
}
2407
2408
class UpdateAltSvcEvent : public Runnable
2409
{
2410
public:
2411
  UpdateAltSvcEvent(const nsCString& header,
2412
                    const nsCString& aOrigin,
2413
                    nsHttpConnectionInfo* aCI,
2414
                    nsIInterfaceRequestor* callbacks)
2415
    : Runnable("net::UpdateAltSvcEvent")
2416
    , mHeader(header)
2417
    , mOrigin(aOrigin)
2418
    , mCI(aCI)
2419
    , mCallbacks(callbacks)
2420
0
  {
2421
0
  }
2422
2423
  NS_IMETHOD Run() override
2424
0
  {
2425
0
    MOZ_ASSERT(NS_IsMainThread());
2426
0
2427
0
    nsCString originScheme;
2428
0
    nsCString originHost;
2429
0
    int32_t originPort = -1;
2430
0
2431
0
    nsCOMPtr<nsIURI> uri;
2432
0
    if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
2433
0
      LOG(("UpdateAltSvcEvent origin does not parse %s\n",
2434
0
           mOrigin.get()));
2435
0
      return NS_OK;
2436
0
    }
2437
0
    uri->GetScheme(originScheme);
2438
0
    uri->GetHost(originHost);
2439
0
    uri->GetPort(&originPort);
2440
0
2441
0
    AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
2442
0
                                 mCI->GetUsername(), mCI->GetPrivate(), mCallbacks,
2443
0
                                 mCI->ProxyInfo(), 0, mCI->GetOriginAttributes());
2444
0
    return NS_OK;
2445
0
  }
2446
2447
private:
2448
  nsCString mHeader;
2449
  nsCString mOrigin;
2450
  RefPtr<nsHttpConnectionInfo> mCI;
2451
  nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
2452
};
2453
2454
// defined as an http2 extension - alt-svc
2455
// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft -06 sec 4
2456
// as this is an extension, never generate protocol error - just ignore problems
2457
nsresult
2458
Http2Session::RecvAltSvc(Http2Session *self)
2459
0
{
2460
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
2461
0
  LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
2462
0
        self->mInputFrameFlags, self->mInputFrameID));
2463
0
2464
0
  if (self->mInputFrameDataSize < 2) {
2465
0
    LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
2466
0
    self->ResetDownstreamState();
2467
0
    return NS_OK;
2468
0
  }
2469
0
2470
0
  uint16_t originLen = NetworkEndian::readUint16(
2471
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes);
2472
0
  if (originLen + 2U > self->mInputFrameDataSize) {
2473
0
    LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
2474
0
    self->ResetDownstreamState();
2475
0
    return NS_OK;
2476
0
  }
2477
0
2478
0
  if (!gHttpHandler->AllowAltSvc()) {
2479
0
    LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
2480
0
    self->ResetDownstreamState();
2481
0
    return NS_OK;
2482
0
  }
2483
0
2484
0
  uint16_t altSvcFieldValueLen = static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
2485
0
  LOG3(("Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
2486
0
        self, originLen, altSvcFieldValueLen));
2487
0
2488
0
  if (self->mInputFrameDataSize > 2000) {
2489
0
    LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly", self));
2490
0
    self->ResetDownstreamState();
2491
0
    return NS_OK;
2492
0
  }
2493
0
2494
0
  nsAutoCString origin;
2495
0
  bool impliedOrigin = true;
2496
0
  if (originLen) {
2497
0
    origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2, originLen);
2498
0
    impliedOrigin = false;
2499
0
  }
2500
0
2501
0
  nsAutoCString altSvcFieldValue;
2502
0
  if (altSvcFieldValueLen) {
2503
0
    altSvcFieldValue.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
2504
0
                            altSvcFieldValueLen);
2505
0
  }
2506
0
2507
0
  if (altSvcFieldValue.IsEmpty() || !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
2508
0
    LOG(("Http2Session %p Alt-Svc Response Header seems unreasonable - skipping\n", self));
2509
0
    self->ResetDownstreamState();
2510
0
    return NS_OK;
2511
0
  }
2512
0
2513
0
  if (self->mInputFrameID & 1) {
2514
0
    // pulled streams apply to the origin of the pulled stream.
2515
0
    // If the origin field is filled in the frame, the frame should be ignored
2516
0
    if (!origin.IsEmpty()) {
2517
0
      LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n", self));
2518
0
      self->ResetDownstreamState();
2519
0
      return NS_OK;
2520
0
    }
2521
0
2522
0
    if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
2523
0
        !self->mInputFrameDataStream->Transaction() ||
2524
0
        !self->mInputFrameDataStream->Transaction()->RequestHead()) {
2525
0
      LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
2526
0
      self->ResetDownstreamState();
2527
0
      return NS_OK;
2528
0
    }
2529
0
2530
0
    self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
2531
0
  } else if (!self->mInputFrameID) {
2532
0
    // ID 0 streams must supply their own origin
2533
0
    if (origin.IsEmpty()) {
2534
0
      LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
2535
0
      self->ResetDownstreamState();
2536
0
      return NS_OK;
2537
0
    }
2538
0
  } else {
2539
0
    // handling of push streams is not defined. Let's ignore it
2540
0
    LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n", self));
2541
0
    self->ResetDownstreamState();
2542
0
    return NS_OK;
2543
0
  }
2544
0
2545
0
  RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
2546
0
  if (!self->mConnection || !ci) {
2547
0
    LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
2548
0
          self->mInputFrameID));
2549
0
    self->ResetDownstreamState();
2550
0
    return NS_OK;
2551
0
  }
2552
0
2553
0
  if (!impliedOrigin) {
2554
0
    bool okToReroute = true;
2555
0
    nsCOMPtr<nsISupports> securityInfo;
2556
0
    self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
2557
0
    nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
2558
0
    if (!ssl) {
2559
0
      okToReroute = false;
2560
0
    }
2561
0
2562
0
    // a little off main thread origin parser. This is a non critical function because
2563
0
    // any alternate route created has to be verified anyhow
2564
0
    nsAutoCString specifiedOriginHost;
2565
0
    if (origin.EqualsIgnoreCase("https://", 8)) {
2566
0
      specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
2567
0
    } else if (origin.EqualsIgnoreCase("http://", 7)) {
2568
0
      specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
2569
0
    }
2570
0
2571
0
    int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
2572
0
    if (colonOffset != kNotFound) {
2573
0
      specifiedOriginHost.Truncate(colonOffset);
2574
0
    }
2575
0
2576
0
    if (okToReroute) {
2577
0
      ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
2578
0
    }
2579
0
2580
0
    if (!okToReroute) {
2581
0
      LOG3(("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin %s",
2582
0
            self, origin.BeginReading()));
2583
0
      self->ResetDownstreamState();
2584
0
      return NS_OK;
2585
0
    }
2586
0
  }
2587
0
2588
0
  nsCOMPtr<nsISupports> callbacks;
2589
0
  self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
2590
0
  nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
2591
0
2592
0
  RefPtr<UpdateAltSvcEvent> event =
2593
0
    new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
2594
0
  NS_DispatchToMainThread(event);
2595
0
  self->ResetDownstreamState();
2596
0
  return NS_OK;
2597
0
}
2598
2599
void
2600
Http2Session::Received421(nsHttpConnectionInfo *ci)
2601
0
{
2602
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2603
0
  LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
2604
0
  if (!mOriginFrameActivated || !ci) {
2605
0
    return;
2606
0
  }
2607
0
2608
0
  nsAutoCString key(ci->GetOrigin());
2609
0
  key.Append(':');
2610
0
  key.AppendInt(ci->OriginPort());
2611
0
  mOriginFrame.Remove(key);
2612
0
  LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
2613
0
}
2614
2615
nsresult
2616
Http2Session::RecvUnused(Http2Session *self)
2617
0
{
2618
0
  LOG3(("Http2Session %p unknown frame type %x ignored\n",
2619
0
        self, self->mInputFrameType));
2620
0
  self->ResetDownstreamState();
2621
0
  return NS_OK;
2622
0
}
2623
2624
// defined as an http2 extension - origin
2625
// defines receipt of frame type 0x0b.. http://httpwg.org/http-extensions/origin-frame.html
2626
// as this is an extension, never generate protocol error - just ignore problems
2627
nsresult
2628
Http2Session::RecvOrigin(Http2Session *self)
2629
0
{
2630
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2631
0
  MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
2632
0
  LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
2633
0
        self->mInputFrameFlags, self->mInputFrameID));
2634
0
2635
0
  if (self->mInputFrameFlags & 0x0F) {
2636
0
    LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
2637
0
    self->ResetDownstreamState();
2638
0
    return NS_OK;
2639
0
  }
2640
0
2641
0
  if (self->mInputFrameID) {
2642
0
    LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
2643
0
    self->ResetDownstreamState();
2644
0
    return NS_OK;
2645
0
  }
2646
0
2647
0
  if (self->ConnectionInfo()->UsingProxy()) {
2648
0
    LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
2649
0
    self->ResetDownstreamState();
2650
0
    return NS_OK;
2651
0
  }
2652
0
2653
0
  if (!gHttpHandler->AllowOriginExtension()) {
2654
0
    LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
2655
0
    self->ResetDownstreamState();
2656
0
    return NS_OK;
2657
0
  }
2658
0
2659
0
  uint32_t offset = 0;
2660
0
  self->mOriginFrameActivated = true;
2661
0
2662
0
  while (self->mInputFrameDataSize >= (offset + 2U)) {
2663
0
2664
0
    uint16_t originLen = NetworkEndian::readUint16(
2665
0
      self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
2666
0
    LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n", self, originLen));
2667
0
    if (originLen + 2U + offset > self->mInputFrameDataSize) {
2668
0
      LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
2669
0
      break;
2670
0
    }
2671
0
2672
0
    nsAutoCString originString;
2673
0
    nsCOMPtr<nsIURI> originURL;
2674
0
    originString.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2, originLen);
2675
0
    offset += originLen + 2;
2676
0
    if (NS_FAILED(Http2Stream::MakeOriginURL(originString, originURL))){
2677
0
      LOG3(("Http2Session::RecvOrigin %p origin frame string %s failed to parse\n", self, originString.get()));
2678
0
      continue;
2679
0
    }
2680
0
2681
0
    LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n", self, originString.get()));
2682
0
    bool isHttps = false;
2683
0
    if (NS_FAILED(originURL->SchemeIs("https", &isHttps)) || !isHttps) {
2684
0
      LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
2685
0
      continue;
2686
0
    }
2687
0
2688
0
    int32_t port = -1;
2689
0
    originURL->GetPort(&port);
2690
0
    if (port == -1) {
2691
0
      port = 443;
2692
0
    }
2693
0
    // dont use ->GetHostPort because we want explicit 443
2694
0
    nsAutoCString host;
2695
0
    originURL->GetHost(host);
2696
0
    nsAutoCString key(host);
2697
0
    key.Append(':');
2698
0
    key.AppendInt(port);
2699
0
    if (!self->mOriginFrame.Get(key)) {
2700
0
      self->mOriginFrame.Put(key, true);
2701
0
      RefPtr<nsHttpConnection> conn(self->HttpConnection());
2702
0
      MOZ_ASSERT(conn.get());
2703
0
      gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
2704
0
    } else {
2705
0
      LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n", self));
2706
0
    }
2707
0
  }
2708
0
2709
0
  self->ResetDownstreamState();
2710
0
  return NS_OK;
2711
0
}
2712
2713
//-----------------------------------------------------------------------------
2714
// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
2715
// of these methods
2716
//-----------------------------------------------------------------------------
2717
2718
void
2719
Http2Session::OnTransportStatus(nsITransport* aTransport,
2720
                                nsresult aStatus, int64_t aProgress)
2721
0
{
2722
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2723
0
2724
0
  switch (aStatus) {
2725
0
    // These should appear only once, deliver to the first
2726
0
    // transaction on the session.
2727
0
  case NS_NET_STATUS_RESOLVING_HOST:
2728
0
  case NS_NET_STATUS_RESOLVED_HOST:
2729
0
  case NS_NET_STATUS_CONNECTING_TO:
2730
0
  case NS_NET_STATUS_CONNECTED_TO:
2731
0
  case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
2732
0
  case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
2733
0
  {
2734
0
2735
0
    if (!mFirstHttpTransaction) {
2736
0
      // if we still do not have a HttpTransaction store timings info in
2737
0
      // a HttpConnection.
2738
0
      // If some error occur it can happen that we do not have a connection.
2739
0
      if (mConnection) {
2740
0
        RefPtr<nsHttpConnection> conn = mConnection->HttpConnection();
2741
0
        conn->SetEvent(aStatus);
2742
0
      }
2743
0
    } else {
2744
0
      mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus, aProgress);
2745
0
    }
2746
0
2747
0
    if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
2748
0
      mFirstHttpTransaction = nullptr;
2749
0
      mTlsHandshakeFinished = true;
2750
0
    }
2751
0
    break;
2752
0
  }
2753
0
2754
0
  default:
2755
0
    // The other transport events are ignored here because there is no good
2756
0
    // way to map them to the right transaction in http/2. Instead, the events
2757
0
    // are generated again from the http/2 code and passed directly to the
2758
0
    // correct transaction.
2759
0
2760
0
    // NS_NET_STATUS_SENDING_TO:
2761
0
    // This is generated by the socket transport when (part) of
2762
0
    // a transaction is written out
2763
0
    //
2764
0
    // There is no good way to map it to the right transaction in http/2,
2765
0
    // so it is ignored here and generated separately when the request
2766
0
    // is sent from Http2Stream::TransmitFrame
2767
0
2768
0
    // NS_NET_STATUS_WAITING_FOR:
2769
0
    // Created by nsHttpConnection when the request has been totally sent.
2770
0
    // There is no good way to map it to the right transaction in http/2,
2771
0
    // so it is ignored here and generated separately when the same
2772
0
    // condition is complete in Http2Stream when there is no more
2773
0
    // request body left to be transmitted.
2774
0
2775
0
    // NS_NET_STATUS_RECEIVING_FROM
2776
0
    // Generated in session whenever we read a data frame or a HEADERS
2777
0
    // that can be attributed to a particular stream/transaction
2778
0
2779
0
    break;
2780
0
  }
2781
0
}
2782
2783
// ReadSegments() is used to write data to the network. Generally, HTTP
2784
// request data is pulled from the approriate transaction and
2785
// converted to http/2 data. Sometimes control data like window-update are
2786
// generated instead.
2787
2788
nsresult
2789
Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader,
2790
                                uint32_t count, uint32_t *countRead, bool *again)
2791
0
{
2792
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2793
0
2794
0
  MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
2795
0
             "Inconsistent Write Function Callback");
2796
0
2797
0
  nsresult rv = ConfirmTLSProfile();
2798
0
  if (NS_FAILED(rv)) {
2799
0
    if (mGoAwayReason == INADEQUATE_SECURITY) {
2800
0
      LOG3(("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY %" PRIx32,
2801
0
            this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY)));
2802
0
      rv = NS_ERROR_NET_INADEQUATE_SECURITY;
2803
0
    }
2804
0
    return rv;
2805
0
  }
2806
0
2807
0
  if (reader)
2808
0
    mSegmentReader = reader;
2809
0
2810
0
  *countRead = 0;
2811
0
2812
0
  LOG3(("Http2Session::ReadSegments %p", this));
2813
0
2814
0
  Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
2815
0
  if (!stream) {
2816
0
    LOG3(("Http2Session %p could not identify a stream to write; suspending.",
2817
0
          this));
2818
0
    uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
2819
0
    FlushOutputQueue();
2820
0
    uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
2821
0
    if (availBeforeFlush != availAfterFlush) {
2822
0
      LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments", this));
2823
0
      Unused << ResumeRecv();
2824
0
    }
2825
0
    SetWriteCallbacks();
2826
0
    if (mAttemptingEarlyData) {
2827
0
      // We can still try to send our preamble as early-data
2828
0
      *countRead = mOutputQueueUsed - mOutputQueueSent;
2829
0
    }
2830
0
    return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
2831
0
  }
2832
0
2833
0
  uint32_t earlyDataUsed = 0;
2834
0
  if (mAttemptingEarlyData) {
2835
0
    if (!stream->Do0RTT()) {
2836
0
      LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X",
2837
0
            this, stream, stream->StreamID()));
2838
0
      FlushOutputQueue();
2839
0
      SetWriteCallbacks();
2840
0
      if (!mCannotDo0RTTStreams.Contains(stream)) {
2841
0
        mCannotDo0RTTStreams.AppendElement(stream);
2842
0
      }
2843
0
      // We can still send our preamble
2844
0
      *countRead = mOutputQueueUsed - mOutputQueueSent;
2845
0
      return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
2846
0
    }
2847
0
2848
0
    // Need to adjust this to only take as much as we can fit in with the
2849
0
    // preamble/settings/priority stuff
2850
0
    count -= (mOutputQueueUsed - mOutputQueueSent);
2851
0
2852
0
    // Keep track of this to add it into countRead later, as
2853
0
    // stream->ReadSegments will likely change the value of mOutputQueueUsed.
2854
0
    earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
2855
0
  }
2856
0
2857
0
  LOG3(("Http2Session %p will write from Http2Stream %p 0x%X "
2858
0
        "block-input=%d block-output=%d\n", this, stream, stream->StreamID(),
2859
0
        stream->RequestBlockedOnRead(), stream->BlockedOnRwin()));
2860
0
2861
0
  rv = stream->ReadSegments(this, count, countRead);
2862
0
2863
0
  if (earlyDataUsed) {
2864
0
    // Do this here because countRead could get reset somewhere down the rabbit
2865
0
    // hole of stream->ReadSegments, and we want to make sure we return the
2866
0
    // proper value to our caller.
2867
0
    *countRead += earlyDataUsed;
2868
0
  }
2869
0
2870
0
  if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) {
2871
0
    LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n",
2872
0
          stream->StreamID()));
2873
0
    m0RTTStreams.AppendElement(stream);
2874
0
  }
2875
0
2876
0
  // Not every permutation of stream->ReadSegents produces data (and therefore
2877
0
  // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
2878
0
  // of that. But we might still have old data buffered that would be good
2879
0
  // to flush.
2880
0
  FlushOutputQueue();
2881
0
2882
0
  // Allow new server reads - that might be data or control information
2883
0
  // (e.g. window updates or http replies) that are responses to these writes
2884
0
  Unused << ResumeRecv();
2885
0
2886
0
  if (stream->RequestBlockedOnRead()) {
2887
0
2888
0
    // We are blocked waiting for input - either more http headers or
2889
0
    // any request body data. When more data from the request stream
2890
0
    // becomes available the httptransaction will call conn->ResumeSend().
2891
0
2892
0
    LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
2893
0
2894
0
    // call readsegments again if there are other streams ready
2895
0
    // to run in this session
2896
0
    if (GetWriteQueueSize()) {
2897
0
      rv = NS_OK;
2898
0
    } else {
2899
0
      rv = NS_BASE_STREAM_WOULD_BLOCK;
2900
0
    }
2901
0
    SetWriteCallbacks();
2902
0
    return rv;
2903
0
  }
2904
0
2905
0
  if (NS_FAILED(rv)) {
2906
0
    LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32,
2907
0
          this, static_cast<uint32_t>(rv)));
2908
0
    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
2909
0
      return rv;
2910
0
    }
2911
0
2912
0
    CleanupStream(stream, rv, CANCEL_ERROR);
2913
0
    if (SoftStreamError(rv)) {
2914
0
      LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
2915
0
      *again = false;
2916
0
      SetWriteCallbacks();
2917
0
      rv = NS_OK;
2918
0
    }
2919
0
    return rv;
2920
0
  }
2921
0
2922
0
  if (*countRead > 0) {
2923
0
    LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d",
2924
0
          this, stream, *countRead));
2925
0
    mReadyForWrite.Push(stream);
2926
0
    SetWriteCallbacks();
2927
0
    return rv;
2928
0
  }
2929
0
2930
0
  if (stream->BlockedOnRwin()) {
2931
0
    LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
2932
0
          this, stream, stream->StreamID()));
2933
0
    return NS_BASE_STREAM_WOULD_BLOCK;
2934
0
  }
2935
0
2936
0
  LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete",
2937
0
        this, stream));
2938
0
2939
0
  // call readsegments again if there are other streams ready
2940
0
  // to go in this session
2941
0
  SetWriteCallbacks();
2942
0
2943
0
  return rv;
2944
0
}
2945
2946
nsresult
2947
Http2Session::ReadSegments(nsAHttpSegmentReader *reader,
2948
                           uint32_t count, uint32_t *countRead)
2949
0
{
2950
0
  bool again = false;
2951
0
  return ReadSegmentsAgain(reader, count, countRead, &again);
2952
0
}
2953
2954
nsresult
2955
Http2Session::ReadyToProcessDataFrame(enum internalStateType newState)
2956
0
{
2957
0
  MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
2958
0
             newState == DISCARDING_DATA_FRAME_PADDING);
2959
0
  ChangeDownstreamState(newState);
2960
0
2961
0
  Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
2962
0
                        mInputFrameDataSize >> 10);
2963
0
  mLastDataReadEpoch = mLastReadEpoch;
2964
0
2965
0
  if (!mInputFrameID) {
2966
0
    LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
2967
0
          this));
2968
0
    RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
2969
0
  }
2970
0
2971
0
  nsresult rv = SetInputFrameDataStream(mInputFrameID);
2972
0
  if (NS_FAILED(rv)) {
2973
0
    LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
2974
0
          "failed. probably due to verification.\n", this, mInputFrameID));
2975
0
    return rv;
2976
0
  }
2977
0
  if (!mInputFrameDataStream) {
2978
0
    LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
2979
0
          "failed. Next = 0x%X", this, mInputFrameID, mNextStreamID));
2980
0
    if (mInputFrameID >= mNextStreamID)
2981
0
      GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
2982
0
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
2983
0
  } else if (mInputFrameDataStream->RecvdFin() ||
2984
0
            mInputFrameDataStream->RecvdReset() ||
2985
0
            mInputFrameDataStream->SentReset()) {
2986
0
    LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
2987
0
          "Data arrived for already server closed stream.\n",
2988
0
          this, mInputFrameID));
2989
0
    if (mInputFrameDataStream->RecvdFin() || mInputFrameDataStream->RecvdReset())
2990
0
      GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
2991
0
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
2992
0
  } else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
2993
0
    // Only if non-final because the stream properly handles final frames of any
2994
0
    // size, and we want the stream to be able to notice its own end flag.
2995
0
    LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
2996
0
          "Ignoring 0-length non-terminal data frame.", this, mInputFrameID));
2997
0
    ChangeDownstreamState(DISCARDING_DATA_FRAME);
2998
0
  }
2999
0
3000
0
  LOG3(("Start Processing Data Frame. "
3001
0
        "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
3002
0
        this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
3003
0
        mInputFrameDataSize));
3004
0
  UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
3005
0
3006
0
  if (mInputFrameDataStream) {
3007
0
    mInputFrameDataStream->SetRecvdData(true);
3008
0
  }
3009
0
3010
0
  return NS_OK;
3011
0
}
3012
3013
// WriteSegments() is used to read data off the socket. Generally this is
3014
// just the http2 frame header and from there the appropriate *Stream
3015
// is identified from the Stream-ID. The http transaction associated with
3016
// that read then pulls in the data directly, which it will feed to
3017
// OnWriteSegment(). That function will gateway it into http and feed
3018
// it to the appropriate transaction.
3019
3020
// we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
3021
// and decide if it is data or control.. if it is control, just deal with it.
3022
// if it is data, identify the stream
3023
// call stream->WriteSegments which can call this::OnWriteSegment to get the
3024
// data. It always gets full frames if they are part of the stream
3025
3026
nsresult
3027
Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter *writer,
3028
                                 uint32_t count, uint32_t *countWritten,
3029
                                 bool *again)
3030
0
{
3031
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3032
0
3033
0
  LOG3(("Http2Session::WriteSegments %p InternalState %X\n",
3034
0
        this, mDownstreamState));
3035
0
3036
0
  *countWritten = 0;
3037
0
3038
0
  if (mClosed)
3039
0
    return NS_ERROR_FAILURE;
3040
0
3041
0
  nsresult rv = ConfirmTLSProfile();
3042
0
  if (NS_FAILED(rv))
3043
0
    return rv;
3044
0
3045
0
  SetWriteCallbacks();
3046
0
3047
0
  // If there are http transactions attached to a push stream with filled buffers
3048
0
  // trigger that data pump here. This only reads from buffers (not the network)
3049
0
  // so mDownstreamState doesn't matter.
3050
0
  Http2Stream *pushConnectedStream =
3051
0
    static_cast<Http2Stream *>(mPushesReadyForRead.PopFront());
3052
0
  if (pushConnectedStream) {
3053
0
    return ProcessConnectedPush(pushConnectedStream, writer, count, countWritten);
3054
0
  }
3055
0
3056
0
  // feed gecko channels that previously stopped consuming data
3057
0
  // only take data from stored buffers
3058
0
  Http2Stream *slowConsumer =
3059
0
    static_cast<Http2Stream *>(mSlowConsumersReadyForRead.PopFront());
3060
0
  if (slowConsumer) {
3061
0
    internalStateType savedState = mDownstreamState;
3062
0
    mDownstreamState = NOT_USING_NETWORK;
3063
0
    rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
3064
0
    mDownstreamState = savedState;
3065
0
    return rv;
3066
0
  }
3067
0
3068
0
  // The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER
3069
0
  // except the only frame type it will allow is SETTINGS
3070
0
3071
0
  // The session layer buffers the leading 8 byte header of every frame.
3072
0
  // Non-Data frames are then buffered for their full length, but data
3073
0
  // frames (type 0) are passed through to the http stack unprocessed
3074
0
3075
0
  if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
3076
0
      mDownstreamState == BUFFERING_FRAME_HEADER) {
3077
0
    // The first 9 bytes of every frame is header information that
3078
0
    // we are going to want to strip before passing to http. That is
3079
0
    // true of both control and data packets.
3080
0
3081
0
    MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
3082
0
               "Frame Buffer Used Too Large for State");
3083
0
3084
0
    rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
3085
0
                     kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);
3086
0
3087
0
    if (NS_FAILED(rv)) {
3088
0
      LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n",
3089
0
            this, static_cast<uint32_t>(rv)));
3090
0
      // maybe just blocked reading from network
3091
0
      if (rv == NS_BASE_STREAM_WOULD_BLOCK)
3092
0
        rv = NS_OK;
3093
0
      return rv;
3094
0
    }
3095
0
3096
0
    LogIO(this, nullptr, "Reading Frame Header",
3097
0
          &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
3098
0
3099
0
    mInputFrameBufferUsed += *countWritten;
3100
0
3101
0
    if (mInputFrameBufferUsed < kFrameHeaderBytes)
3102
0
    {
3103
0
      LOG3(("Http2Session::WriteSegments %p "
3104
0
            "BUFFERING FRAME HEADER incomplete size=%d",
3105
0
            this, mInputFrameBufferUsed));
3106
0
      return rv;
3107
0
    }
3108
0
3109
0
    // 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
3110
0
    uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
3111
0
    mInputFrameDataSize = NetworkEndian::readUint16(
3112
0
        mInputFrameBuffer.get() + 1);
3113
0
    if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
3114
0
      LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte, mInputFrameDataSize));
3115
0
      RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3116
0
    }
3117
0
    mInputFrameType = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() + kFrameLengthBytes);
3118
0
    mInputFrameFlags = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
3119
0
    mInputFrameID = NetworkEndian::readUint32(
3120
0
        mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes + kFrameFlagBytes);
3121
0
    mInputFrameID &= 0x7fffffff;
3122
0
    mInputFrameDataRead = 0;
3123
0
3124
0
    if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS)  {
3125
0
      mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
3126
0
    } else {
3127
0
      mInputFrameFinal = false;
3128
0
    }
3129
0
3130
0
    mPaddingLength = 0;
3131
0
3132
0
    LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read "
3133
0
          "type %X data len %u flags %x id 0x%X",
3134
0
          this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
3135
0
          mInputFrameID));
3136
0
3137
0
    // if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION of
3138
0
    // a HEADERS frame with a matching ID (section 6.2)
3139
0
    if (mExpectedHeaderID &&
3140
0
        ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
3141
0
         (mExpectedHeaderID != mInputFrameID))) {
3142
0
      LOG3(("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
3143
0
      RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3144
0
    }
3145
0
3146
0
    // if mExpectedPushPromiseID is non 0, it means this frame must be a
3147
0
    // CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
3148
0
    if (mExpectedPushPromiseID &&
3149
0
        ((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
3150
0
         (mExpectedPushPromiseID != mInputFrameID))) {
3151
0
      LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
3152
0
            mExpectedPushPromiseID));
3153
0
      RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3154
0
    }
3155
0
3156
0
    if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
3157
0
        mInputFrameType != FRAME_TYPE_SETTINGS) {
3158
0
      LOG3(("First Frame Type Must Be Settings\n"));
3159
0
      RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3160
0
    }
3161
0
3162
0
    if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
3163
0
      EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
3164
0
                   kFrameHeaderBytes, mInputFrameBufferSize);
3165
0
      ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
3166
0
    } else if (mInputFrameFlags & kFlag_PADDED) {
3167
0
      ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
3168
0
    } else {
3169
0
      rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
3170
0
      if (NS_FAILED(rv)) {
3171
0
        return rv;
3172
0
      }
3173
0
    }
3174
0
  }
3175
0
3176
0
  if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
3177
0
    MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
3178
0
               "Processing padding control on unpadded frame");
3179
0
3180
0
    MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
3181
0
               "Frame buffer used too large for state");
3182
0
3183
0
    rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
3184
0
                     (kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
3185
0
                     countWritten);
3186
0
3187
0
    if (NS_FAILED(rv)) {
3188
0
      LOG3(("Http2Session %p buffering data frame padding control read failure %" PRIx32 "\n",
3189
0
            this, static_cast<uint32_t>(rv)));
3190
0
      // maybe just blocked reading from network
3191
0
      if (rv == NS_BASE_STREAM_WOULD_BLOCK)
3192
0
        rv = NS_OK;
3193
0
      return rv;
3194
0
    }
3195
0
3196
0
    LogIO(this, nullptr, "Reading Data Frame Padding Control",
3197
0
          &mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
3198
0
3199
0
    mInputFrameBufferUsed += *countWritten;
3200
0
3201
0
    if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
3202
0
      LOG3(("Http2Session::WriteSegments %p "
3203
0
            "BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
3204
0
            this, mInputFrameBufferUsed - 8));
3205
0
      return rv;
3206
0
    }
3207
0
3208
0
    ++mInputFrameDataRead;
3209
0
3210
0
    char *control = &mInputFrameBuffer[kFrameHeaderBytes];
3211
0
    mPaddingLength = static_cast<uint8_t>(*control);
3212
0
3213
0
    LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
3214
0
          mInputFrameID, mPaddingLength));
3215
0
3216
0
    if (1U + mPaddingLength > mInputFrameDataSize) {
3217
0
      LOG3(("Http2Session::WriteSegments %p stream 0x%X padding too large for "
3218
0
            "frame", this, mInputFrameID));
3219
0
      RETURN_SESSION_ERROR(this, PROTOCOL_ERROR);
3220
0
    } else if (1U + mPaddingLength == mInputFrameDataSize) {
3221
0
      // This frame consists entirely of padding, we can just discard it
3222
0
      LOG3(("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
3223
0
            this, mInputFrameID));
3224
0
      rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
3225
0
      if (NS_FAILED(rv)) {
3226
0
        return rv;
3227
0
      }
3228
0
    } else {
3229
0
      LOG3(("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
3230
0
            this, mInputFrameID));
3231
0
      rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
3232
0
      if (NS_FAILED(rv)) {
3233
0
        return rv;
3234
0
      }
3235
0
    }
3236
0
  }
3237
0
3238
0
  if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
3239
0
    nsresult streamCleanupCode;
3240
0
3241
0
    // There is no bounds checking on the error code.. we provide special
3242
0
    // handling for a couple of cases and all others (including unknown) are
3243
0
    // equivalent to cancel.
3244
0
    if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
3245
0
      streamCleanupCode = NS_ERROR_NET_RESET;      // can retry this 100% safely
3246
0
      mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
3247
0
    } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
3248
0
      streamCleanupCode = NS_ERROR_NET_RESET;
3249
0
      mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
3250
0
      mInputFrameDataStream->Transaction()->DisableSpdy();
3251
0
    } else {
3252
0
      streamCleanupCode = mInputFrameDataStream->RecvdData() ?
3253
0
        NS_ERROR_NET_PARTIAL_TRANSFER :
3254
0
        NS_ERROR_NET_INTERRUPT;
3255
0
    }
3256
0
3257
0
    if (mDownstreamRstReason == COMPRESSION_ERROR) {
3258
0
      mShouldGoAway = true;
3259
0
    }
3260
0
3261
0
    // mInputFrameDataStream is reset by ChangeDownstreamState
3262
0
    Http2Stream *stream = mInputFrameDataStream;
3263
0
    ResetDownstreamState();
3264
0
    LOG3(("Http2Session::WriteSegments cleanup stream on recv of rst "
3265
0
          "session=%p stream=%p 0x%X\n", this, stream,
3266
0
          stream ? stream->StreamID() : 0));
3267
0
    CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
3268
0
    return NS_OK;
3269
0
  }
3270
0
3271
0
  if (mDownstreamState == PROCESSING_DATA_FRAME ||
3272
0
      mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
3273
0
3274
0
    // The cleanup stream should only be set while stream->WriteSegments is
3275
0
    // on the stack and then cleaned up in this code block afterwards.
3276
0
    MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
3277
0
    mNeedsCleanup = nullptr;                     /* just in case */
3278
0
3279
0
    uint32_t streamID = mInputFrameDataStream->StreamID();
3280
0
    mSegmentWriter = writer;
3281
0
    rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
3282
0
    mSegmentWriter = nullptr;
3283
0
3284
0
    mLastDataReadEpoch = mLastReadEpoch;
3285
0
3286
0
    if (SoftStreamError(rv)) {
3287
0
      // This will happen when the transaction figures out it is EOF, generally
3288
0
      // due to a content-length match being made. Return OK from this function
3289
0
      // otherwise the whole session would be torn down.
3290
0
3291
0
      // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
3292
0
      // back to PROCESSING_DATA_FRAME where we came from
3293
0
      mDownstreamState = PROCESSING_DATA_FRAME;
3294
0
3295
0
      if (mInputFrameDataRead == mInputFrameDataSize)
3296
0
        ResetDownstreamState();
3297
0
      LOG3(("Http2Session::WriteSegments session=%p id 0x%X "
3298
0
            "needscleanup=%p. cleanup stream based on "
3299
0
            "stream->writeSegments returning code %" PRIx32 "\n",
3300
0
            this, streamID, mNeedsCleanup, static_cast<uint32_t>(rv)));
3301
0
      MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
3302
0
      CleanupStream(streamID,
3303
0
                    (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK,
3304
0
                    CANCEL_ERROR);
3305
0
      mNeedsCleanup = nullptr;
3306
0
      *again = false;
3307
0
      rv = ResumeRecv();
3308
0
      if (NS_FAILED(rv)) {
3309
0
        LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv)));
3310
0
      }
3311
0
      return NS_OK;
3312
0
    }
3313
0
3314
0
    if (mNeedsCleanup) {
3315
0
      LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
3316
0
            "cleanup stream based on mNeedsCleanup.\n",
3317
0
            this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
3318
0
      CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
3319
0
      mNeedsCleanup = nullptr;
3320
0
    }
3321
0
3322
0
    if (NS_FAILED(rv)) {
3323
0
      LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this,
3324
0
            static_cast<uint32_t>(rv)));
3325
0
      // maybe just blocked reading from network
3326
0
      if (rv == NS_BASE_STREAM_WOULD_BLOCK)
3327
0
        rv = NS_OK;
3328
0
    }
3329
0
3330
0
    return rv;
3331
0
  }
3332
0
3333
0
  if (mDownstreamState == DISCARDING_DATA_FRAME ||
3334
0
      mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
3335
0
    char trash[4096];
3336
0
    uint32_t discardCount = std::min(mInputFrameDataSize - mInputFrameDataRead,
3337
0
                                     4096U);
3338
0
    LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s",
3339
0
          this, discardCount,
3340
0
          mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding"));
3341
0
3342
0
    if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
3343
0
      // Only do this short-cirtuit if we're not discarding a pure padding
3344
0
      // frame, as we need to potentially handle the stream FIN in those cases.
3345
0
      // See bug 1381016 comment 36 for more details.
3346
0
      ResetDownstreamState();
3347
0
      Unused << ResumeRecv();
3348
0
      return NS_BASE_STREAM_WOULD_BLOCK;
3349
0
    }
3350
0
3351
0
    rv = NetworkRead(writer, trash, discardCount, countWritten);
3352
0
3353
0
    if (NS_FAILED(rv)) {
3354
0
      LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this,
3355
0
            static_cast<uint32_t>(rv)));
3356
0
      // maybe just blocked reading from network
3357
0
      if (rv == NS_BASE_STREAM_WOULD_BLOCK)
3358
0
        rv = NS_OK;
3359
0
      return rv;
3360
0
    }
3361
0
3362
0
    LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
3363
0
3364
0
    mInputFrameDataRead += *countWritten;
3365
0
3366
0
    if (mInputFrameDataRead == mInputFrameDataSize) {
3367
0
      Http2Stream *streamToCleanup = nullptr;
3368
0
      if (mInputFrameFinal) {
3369
0
        streamToCleanup = mInputFrameDataStream;
3370
0
      }
3371
0
3372
0
      bool discardedPadding = (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
3373
0
      ResetDownstreamState();
3374
0
3375
0
      if (streamToCleanup) {
3376
0
        if (discardedPadding && !(streamToCleanup->StreamID() & 1)) {
3377
0
          // Pushed streams are special on padding-only final data frames.
3378
0
          // See bug 1409570 comments 6-8 for details.
3379
0
          streamToCleanup->SetPushComplete();
3380
0
        }
3381
0
        CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
3382
0
      }
3383
0
    }
3384
0
    return rv;
3385
0
  }
3386
0
3387
0
  if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
3388
0
    MOZ_ASSERT(false); // this cannot happen
3389
0
    return NS_ERROR_UNEXPECTED;
3390
0
  }
3391
0
3392
0
  MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes, "Frame Buffer Header Not Present");
3393
0
  MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
3394
0
             "allocation for control frame insufficient");
3395
0
3396
0
  rv = NetworkRead(writer, &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
3397
0
                   mInputFrameDataSize - mInputFrameDataRead, countWritten);
3398
0
3399
0
  if (NS_FAILED(rv)) {
3400
0
    LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n",
3401
0
          this, static_cast<uint32_t>(rv)));
3402
0
    // maybe just blocked reading from network
3403
0
    if (rv == NS_BASE_STREAM_WOULD_BLOCK)
3404
0
      rv = NS_OK;
3405
0
    return rv;
3406
0
  }
3407
0
3408
0
  LogIO(this, nullptr, "Reading Control Frame",
3409
0
        &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead], *countWritten);
3410
0
3411
0
  mInputFrameDataRead += *countWritten;
3412
0
3413
0
  if (mInputFrameDataRead != mInputFrameDataSize)
3414
0
    return NS_OK;
3415
0
3416
0
  MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
3417
0
  if (mInputFrameType < FRAME_TYPE_LAST) {
3418
0
    rv = sControlFunctions[mInputFrameType](this);
3419
0
  } else {
3420
0
    // Section 4.1 requires this to be ignored; though protocol_error would
3421
0
    // be better
3422
0
    LOG3(("Http2Session %p unknown frame type %x ignored\n",
3423
0
          this, mInputFrameType));
3424
0
    ResetDownstreamState();
3425
0
    rv = NS_OK;
3426
0
  }
3427
0
3428
0
  MOZ_ASSERT(NS_FAILED(rv) ||
3429
0
             mDownstreamState != BUFFERING_CONTROL_FRAME,
3430
0
             "Control Handler returned OK but did not change state");
3431
0
3432
0
  if (mShouldGoAway && !mStreamTransactionHash.Count())
3433
0
    Close(NS_OK);
3434
0
  return rv;
3435
0
}
3436
3437
nsresult
3438
Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
3439
                            uint32_t count, uint32_t *countWritten)
3440
0
{
3441
0
  bool again = false;
3442
0
  return WriteSegmentsAgain(writer, count, countWritten, &again);
3443
0
}
3444
3445
nsresult
3446
Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged)
3447
0
{
3448
0
  MOZ_ASSERT(mAttemptingEarlyData);
3449
0
  LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
3450
0
        aRestart, aAlpnChanged));
3451
0
3452
0
  for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
3453
0
    if (m0RTTStreams[i]) {
3454
0
      m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged);
3455
0
    }
3456
0
  }
3457
0
3458
0
  if (aRestart) {
3459
0
    // 0RTT failed
3460
0
    if (aAlpnChanged) {
3461
0
      // This is a slightly more involved case - we need to get all our streams/
3462
0
      // transactions back in the queue so they can restart as http/1
3463
0
3464
0
      // These must be set this way to ensure we gracefully restart all streams
3465
0
      mGoAwayID = 0;
3466
0
      mCleanShutdown = true;
3467
0
3468
0
      // Close takes care of the rest of our work for us. The reason code here
3469
0
      // doesn't matter, as we aren't actually going to send a GOAWAY frame, but
3470
0
      // we use NS_ERROR_NET_RESET as it's closest to the truth.
3471
0
      Close(NS_ERROR_NET_RESET);
3472
0
    } else {
3473
0
      // This is the easy case - early data failed, but we're speaking h2, so
3474
0
      // we just need to rewind to the beginning of the preamble and try again.
3475
0
      mOutputQueueSent = 0;
3476
0
3477
0
      for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
3478
0
        if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
3479
0
          TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
3480
0
        }
3481
0
      }
3482
0
    }
3483
0
  } else {
3484
0
    // 0RTT succeeded
3485
0
    for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
3486
0
      if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
3487
0
        TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
3488
0
      }
3489
0
    }
3490
0
    // Make sure we look for any incoming data in repsonse to our early data.
3491
0
    Unused << ResumeRecv();
3492
0
  }
3493
0
3494
0
  mAttemptingEarlyData = false;
3495
0
  m0RTTStreams.Clear();
3496
0
  mCannotDo0RTTStreams.Clear();
3497
0
  RealignOutputQueue();
3498
0
3499
0
  return NS_OK;
3500
0
}
3501
3502
void
3503
Http2Session::SetFastOpenStatus(uint8_t aStatus)
3504
0
{
3505
0
  LOG3(("Http2Session::SetFastOpenStatus %d [this=%p]",
3506
0
        aStatus, this));
3507
0
3508
0
  for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
3509
0
    if (m0RTTStreams[i]) {
3510
0
      m0RTTStreams[i]->Transaction()->SetFastOpenStatus(aStatus);
3511
0
    }
3512
0
  }
3513
0
}
3514
3515
nsresult
3516
Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream,
3517
                                   nsAHttpSegmentWriter * writer,
3518
                                   uint32_t count, uint32_t *countWritten)
3519
0
{
3520
0
  LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n",
3521
0
        this, pushConnectedStream->StreamID()));
3522
0
  mSegmentWriter = writer;
3523
0
  nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
3524
0
  mSegmentWriter = nullptr;
3525
0
3526
0
  // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
3527
0
  // so we need this check to determine the truth.
3528
0
  if (NS_SUCCEEDED(rv) && !*countWritten &&
3529
0
      pushConnectedStream->PushSource() &&
3530
0
      pushConnectedStream->PushSource()->GetPushComplete()) {
3531
0
    rv = NS_BASE_STREAM_CLOSED;
3532
0
  }
3533
0
3534
0
  if (rv == NS_BASE_STREAM_CLOSED) {
3535
0
    CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
3536
0
    rv = NS_OK;
3537
0
  }
3538
0
3539
0
  // if we return OK to nsHttpConnection it will use mSocketInCondition
3540
0
  // to determine whether to schedule more reads, incorrectly
3541
0
  // assuming that nsHttpConnection::OnSocketWrite() was called.
3542
0
  if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
3543
0
    rv = NS_BASE_STREAM_WOULD_BLOCK;
3544
0
    Unused << ResumeRecv();
3545
0
  }
3546
0
  return rv;
3547
0
}
3548
3549
nsresult
3550
Http2Session::ProcessSlowConsumer(Http2Stream *slowConsumer,
3551
                                  nsAHttpSegmentWriter * writer,
3552
                                  uint32_t count, uint32_t *countWritten)
3553
0
{
3554
0
  LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n",
3555
0
        this, slowConsumer->StreamID()));
3556
0
  mSegmentWriter = writer;
3557
0
  nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
3558
0
  mSegmentWriter = nullptr;
3559
0
  LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32 " %d\n",
3560
0
        this, slowConsumer->StreamID(), static_cast<uint32_t>(rv), *countWritten));
3561
0
  if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
3562
0
    rv = NS_BASE_STREAM_CLOSED;
3563
0
  }
3564
0
3565
0
  if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
3566
0
    // There have been buffered bytes successfully fed into the
3567
0
    // formerly blocked consumer. Repeat until buffer empty or
3568
0
    // consumer is blocked again.
3569
0
    UpdateLocalRwin(slowConsumer, 0);
3570
0
    ConnectSlowConsumer(slowConsumer);
3571
0
  }
3572
0
3573
0
  if (rv == NS_BASE_STREAM_CLOSED) {
3574
0
    CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
3575
0
    rv = NS_OK;
3576
0
  }
3577
0
3578
0
  return rv;
3579
0
}
3580
3581
void
3582
Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes)
3583
0
{
3584
0
  if (!stream) // this is ok - it means there was a data frame for a rst stream
3585
0
    return;
3586
0
3587
0
  // If this data packet was not for a valid or live stream then there
3588
0
  // is no reason to mess with the flow control
3589
0
  if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
3590
0
      mInputFrameFinal) {
3591
0
    return;
3592
0
  }
3593
0
3594
0
  stream->DecrementClientReceiveWindow(bytes);
3595
0
3596
0
  // Don't necessarily ack every data packet. Only do it
3597
0
  // after a significant amount of data.
3598
0
  uint64_t unacked = stream->LocalUnAcked();
3599
0
  int64_t  localWindow = stream->ClientReceiveWindow();
3600
0
3601
0
  LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
3602
0
        "unacked=%" PRIu64 " localWindow=%" PRId64 "\n",
3603
0
        this, stream->StreamID(), bytes, unacked, localWindow));
3604
0
3605
0
  if (!unacked)
3606
0
    return;
3607
0
3608
0
  if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
3609
0
    return;
3610
0
3611
0
  if (!stream->HasSink()) {
3612
0
    LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n",
3613
0
          this, stream->StreamID()));
3614
0
    return;
3615
0
  }
3616
0
3617
0
  // Generate window updates directly out of session instead of the stream
3618
0
  // in order to avoid queue delays in getting the 'ACK' out.
3619
0
  uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
3620
0
3621
0
  LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
3622
0
        this, stream->StreamID(), toack));
3623
0
  stream->IncrementClientReceiveWindow(toack);
3624
0
  if (toack == 0) {
3625
0
    // Ensure we never send an illegal 0 window update
3626
0
    return;
3627
0
  }
3628
0
3629
0
  // room for this packet needs to be ensured before calling this function
3630
0
  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
3631
0
  mOutputQueueUsed += kFrameHeaderBytes + 4;
3632
0
  MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
3633
0
3634
0
  CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
3635
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
3636
0
3637
0
  LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
3638
0
  // dont flush here, this write can commonly be coalesced with a
3639
0
  // session window update to immediately follow.
3640
0
}
3641
3642
void
3643
Http2Session::UpdateLocalSessionWindow(uint32_t bytes)
3644
0
{
3645
0
  if (!bytes)
3646
0
    return;
3647
0
3648
0
  mLocalSessionWindow -= bytes;
3649
0
3650
0
  LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
3651
0
        "localWindow=%" PRId64 "\n", this, bytes, mLocalSessionWindow));
3652
0
3653
0
  // Don't necessarily ack every data packet. Only do it
3654
0
  // after a significant amount of data.
3655
0
  if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
3656
0
      (mLocalSessionWindow > kEmergencyWindowThreshold))
3657
0
    return;
3658
0
3659
0
  // Only send max  bits of window updates at a time.
3660
0
  uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
3661
0
  uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
3662
0
3663
0
  LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n",
3664
0
        this, toack));
3665
0
  mLocalSessionWindow += toack;
3666
0
3667
0
  if (toack == 0) {
3668
0
    // Ensure we never send an illegal 0 window update
3669
0
    return;
3670
0
  }
3671
0
3672
0
  // room for this packet needs to be ensured before calling this function
3673
0
  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
3674
0
  mOutputQueueUsed += kFrameHeaderBytes + 4;
3675
0
  MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
3676
0
3677
0
  CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
3678
0
  NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
3679
0
3680
0
  LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
3681
0
  // dont flush here, this write can commonly be coalesced with others
3682
0
}
3683
3684
void
3685
Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes)
3686
0
{
3687
0
  // make sure there is room for 2 window updates even though
3688
0
  // we may not generate any.
3689
0
  EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));
3690
0
3691
0
  UpdateLocalStreamWindow(stream, bytes);
3692
0
  UpdateLocalSessionWindow(bytes);
3693
0
  FlushOutputQueue();
3694
0
}
3695
3696
void
3697
Http2Session::Close(nsresult aReason)
3698
0
{
3699
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3700
0
3701
0
  if (mClosed)
3702
0
    return;
3703
0
3704
0
  LOG3(("Http2Session::Close %p %" PRIX32, this, static_cast<uint32_t>(aReason)));
3705
0
3706
0
  mClosed = true;
3707
0
3708
0
  Shutdown();
3709
0
3710
0
  mStreamIDHash.Clear();
3711
0
  mStreamTransactionHash.Clear();
3712
0
3713
0
  uint32_t goAwayReason;
3714
0
  if (mGoAwayReason != NO_HTTP_ERROR) {
3715
0
    goAwayReason = mGoAwayReason;
3716
0
  } else if (NS_SUCCEEDED(aReason)) {
3717
0
    goAwayReason = NO_HTTP_ERROR;
3718
0
  } else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
3719
0
    goAwayReason = PROTOCOL_ERROR;
3720
0
  } else {
3721
0
    goAwayReason = INTERNAL_ERROR;
3722
0
  }
3723
0
  if (!mAttemptingEarlyData) {
3724
0
    GenerateGoAway(goAwayReason);
3725
0
  }
3726
0
  mConnection = nullptr;
3727
0
  mSegmentReader = nullptr;
3728
0
  mSegmentWriter = nullptr;
3729
0
}
3730
3731
nsHttpConnectionInfo *
3732
Http2Session::ConnectionInfo()
3733
0
{
3734
0
  RefPtr<nsHttpConnectionInfo> ci;
3735
0
  GetConnectionInfo(getter_AddRefs(ci));
3736
0
  return ci.get();
3737
0
}
3738
3739
void
3740
Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction,
3741
                               nsresult aResult)
3742
0
{
3743
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3744
0
  LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction,
3745
0
        static_cast<uint32_t>(aResult)));
3746
0
3747
0
  // Generally this arrives as a cancel event from the connection manager.
3748
0
3749
0
  // need to find the stream and call CleanupStream() on it.
3750
0
  Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
3751
0
  if (!stream) {
3752
0
    LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.",
3753
0
          this, aTransaction, static_cast<uint32_t>(aResult)));
3754
0
    return;
3755
0
  }
3756
0
  LOG3(("Http2Session::CloseTransaction probably a cancel. "
3757
0
        "this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p",
3758
0
        this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(), stream));
3759
0
  CleanupStream(stream, aResult, CANCEL_ERROR);
3760
0
  nsresult rv = ResumeRecv();
3761
0
  if (NS_FAILED(rv)) {
3762
0
    LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x",
3763
0
          this, aTransaction, static_cast<uint32_t>(aResult),
3764
0
          static_cast<uint32_t>(rv)));
3765
0
  }
3766
0
}
3767
3768
//-----------------------------------------------------------------------------
3769
// nsAHttpSegmentReader
3770
//-----------------------------------------------------------------------------
3771
3772
nsresult
3773
Http2Session::OnReadSegment(const char *buf,
3774
                            uint32_t count, uint32_t *countRead)
3775
0
{
3776
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3777
0
  nsresult rv;
3778
0
3779
0
  // If we can release old queued data then we can try and write the new
3780
0
  // data directly to the network without using the output queue at all
3781
0
  if (mOutputQueueUsed)
3782
0
    FlushOutputQueue();
3783
0
3784
0
  if (!mOutputQueueUsed && mSegmentReader) {
3785
0
    // try and write directly without output queue
3786
0
    rv = mSegmentReader->OnReadSegment(buf, count, countRead);
3787
0
3788
0
    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
3789
0
      *countRead = 0;
3790
0
    } else if (NS_FAILED(rv)) {
3791
0
      return rv;
3792
0
    }
3793
0
3794
0
    if (*countRead < count) {
3795
0
      uint32_t required = count - *countRead;
3796
0
      // assuming a commitment() happened, this ensurebuffer is a nop
3797
0
      // but just in case the queuesize is too small for the required data
3798
0
      // call ensurebuffer().
3799
0
      EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
3800
0
      memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
3801
0
      mOutputQueueUsed = required;
3802
0
    }
3803
0
3804
0
    *countRead = count;
3805
0
    return NS_OK;
3806
0
  }
3807
0
3808
0
  // At this point we are going to buffer the new data in the output
3809
0
  // queue if it fits. By coalescing multiple small submissions into one larger
3810
0
  // buffer we can get larger writes out to the network later on.
3811
0
3812
0
  // This routine should not be allowed to fill up the output queue
3813
0
  // all on its own - at least kQueueReserved bytes are always left
3814
0
  // for other routines to use - but this is an all-or-nothing function,
3815
0
  // so if it will not all fit just return WOULD_BLOCK
3816
0
3817
0
  if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
3818
0
    return NS_BASE_STREAM_WOULD_BLOCK;
3819
0
3820
0
  memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
3821
0
  mOutputQueueUsed += count;
3822
0
  *countRead = count;
3823
0
3824
0
  FlushOutputQueue();
3825
0
3826
0
  return NS_OK;
3827
0
}
3828
3829
nsresult
3830
Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment)
3831
0
{
3832
0
  if (mOutputQueueUsed && !mAttemptingEarlyData)
3833
0
    FlushOutputQueue();
3834
0
3835
0
  // would there be enough room to buffer this if needed?
3836
0
  if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
3837
0
    return NS_OK;
3838
0
3839
0
  // if we are using part of our buffers already, try again later unless
3840
0
  // forceCommitment is set.
3841
0
  if (mOutputQueueUsed && !forceCommitment)
3842
0
    return NS_BASE_STREAM_WOULD_BLOCK;
3843
0
3844
0
  if (mOutputQueueUsed) {
3845
0
    // normally we avoid the memmove of RealignOutputQueue, but we'll try
3846
0
    // it if forceCommitment is set before growing the buffer.
3847
0
    RealignOutputQueue();
3848
0
3849
0
    // is there enough room now?
3850
0
    if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
3851
0
      return NS_OK;
3852
0
  }
3853
0
3854
0
  // resize the buffers as needed
3855
0
  EnsureOutputBuffer(count + kQueueReserved);
3856
0
3857
0
  MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
3858
0
             "buffer not as large as expected");
3859
0
3860
0
  return NS_OK;
3861
0
}
3862
3863
//-----------------------------------------------------------------------------
3864
// nsAHttpSegmentWriter
3865
//-----------------------------------------------------------------------------
3866
3867
nsresult
3868
Http2Session::OnWriteSegment(char *buf,
3869
                             uint32_t count, uint32_t *countWritten)
3870
0
{
3871
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3872
0
  nsresult rv;
3873
0
3874
0
  if (!mSegmentWriter) {
3875
0
    // the only way this could happen would be if Close() were called on the
3876
0
    // stack with WriteSegments()
3877
0
    return NS_ERROR_FAILURE;
3878
0
  }
3879
0
3880
0
  if (mDownstreamState == NOT_USING_NETWORK ||
3881
0
      mDownstreamState == BUFFERING_FRAME_HEADER ||
3882
0
      mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
3883
0
    return NS_BASE_STREAM_WOULD_BLOCK;
3884
0
  }
3885
0
3886
0
  if (mDownstreamState == PROCESSING_DATA_FRAME) {
3887
0
3888
0
    if (mInputFrameFinal &&
3889
0
        mInputFrameDataRead == mInputFrameDataSize) {
3890
0
      *countWritten = 0;
3891
0
      SetNeedsCleanup();
3892
0
      return NS_BASE_STREAM_CLOSED;
3893
0
    }
3894
0
3895
0
    count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
3896
0
    rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
3897
0
    if (NS_FAILED(rv))
3898
0
      return rv;
3899
0
3900
0
    LogIO(this, mInputFrameDataStream, "Reading Data Frame",
3901
0
          buf, *countWritten);
3902
0
3903
0
    mInputFrameDataRead += *countWritten;
3904
0
    if (mPaddingLength && (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
3905
0
      // We are crossing from real HTTP data into the realm of padding. If
3906
0
      // we've actually crossed the line, we need to munge countWritten for the
3907
0
      // sake of goodness and sanity. No matter what, any future calls to
3908
0
      // WriteSegments need to just discard data until we reach the end of this
3909
0
      // frame.
3910
0
      if (mInputFrameDataSize != mInputFrameDataRead) {
3911
0
        // Only change state if we still have padding to read. If we don't do
3912
0
        // this, we can end up hanging on frames that combine real data,
3913
0
        // padding, and END_STREAM (see bug 1019921)
3914
0
        ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
3915
0
      }
3916
0
      uint32_t paddingRead = mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
3917
0
      LOG3(("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
3918
0
            "crossed from HTTP data into padding (%d of %d) countWritten=%d",
3919
0
            this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
3920
0
            paddingRead, mPaddingLength, *countWritten));
3921
0
      *countWritten -= paddingRead;
3922
0
      LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
3923
0
            this, mInputFrameID, *countWritten));
3924
0
    }
3925
0
3926
0
    mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
3927
0
    if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
3928
0
      ResetDownstreamState();
3929
0
3930
0
    return rv;
3931
0
  }
3932
0
3933
0
  if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
3934
0
3935
0
    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
3936
0
        mInputFrameFinal) {
3937
0
      *countWritten = 0;
3938
0
      SetNeedsCleanup();
3939
0
      return NS_BASE_STREAM_CLOSED;
3940
0
    }
3941
0
3942
0
    count = std::min(count,
3943
0
                     mFlatHTTPResponseHeaders.Length() -
3944
0
                     mFlatHTTPResponseHeadersOut);
3945
0
    memcpy(buf,
3946
0
           mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
3947
0
           count);
3948
0
    mFlatHTTPResponseHeadersOut += count;
3949
0
    *countWritten = count;
3950
0
3951
0
    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
3952
0
      if (!mInputFrameFinal) {
3953
0
        // If more frames are expected in this stream, then reset the state so they can be
3954
0
        // handled. Otherwise (e.g. a 0 length response with the fin on the incoming headers)
3955
0
        // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can
3956
0
        // cleanup the stream.
3957
0
        ResetDownstreamState();
3958
0
      }
3959
0
    }
3960
0
3961
0
    return NS_OK;
3962
0
  }
3963
0
3964
0
  MOZ_ASSERT(false);
3965
0
  return NS_ERROR_UNEXPECTED;
3966
0
}
3967
3968
void
3969
Http2Session::SetNeedsCleanup()
3970
0
{
3971
0
  LOG3(("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
3972
0
        "stream %p 0x%X", this, mInputFrameDataStream,
3973
0
        mInputFrameDataStream->StreamID()));
3974
0
3975
0
  // This will result in Close() being called
3976
0
  MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
3977
0
  mInputFrameDataStream->SetResponseIsComplete();
3978
0
  mNeedsCleanup = mInputFrameDataStream;
3979
0
  ResetDownstreamState();
3980
0
}
3981
3982
void
3983
Http2Session::ConnectPushedStream(Http2Stream *stream)
3984
0
{
3985
0
  mPushesReadyForRead.Push(stream);
3986
0
  Unused << ForceRecv();
3987
0
}
3988
3989
void
3990
Http2Session::ConnectSlowConsumer(Http2Stream *stream)
3991
0
{
3992
0
  LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n",
3993
0
        this, stream->StreamID()));
3994
0
  mSlowConsumersReadyForRead.Push(stream);
3995
0
  Unused << ForceRecv();
3996
0
}
3997
3998
uint32_t
3999
Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
4000
0
{
4001
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4002
0
  uint32_t rv = 0;
4003
0
  mTunnelHash.Get(aConnInfo->HashKey(), &rv);
4004
0
  return rv;
4005
0
}
4006
4007
void
4008
Http2Session::RegisterTunnel(Http2Stream *aTunnel)
4009
0
{
4010
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4011
0
  nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
4012
0
  uint32_t newcount = FindTunnelCount(ci) + 1;
4013
0
  mTunnelHash.Remove(ci->HashKey());
4014
0
  mTunnelHash.Put(ci->HashKey(), newcount);
4015
0
  LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]",
4016
0
        this, aTunnel, newcount, ci->HashKey().get()));
4017
0
}
4018
4019
void
4020
Http2Session::UnRegisterTunnel(Http2Stream *aTunnel)
4021
0
{
4022
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4023
0
  nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
4024
0
  MOZ_ASSERT(FindTunnelCount(ci));
4025
0
  uint32_t newcount = FindTunnelCount(ci) - 1;
4026
0
  mTunnelHash.Remove(ci->HashKey());
4027
0
  if (newcount) {
4028
0
    mTunnelHash.Put(ci->HashKey(), newcount);
4029
0
  }
4030
0
  LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
4031
0
        this, aTunnel, newcount, ci->HashKey().get()));
4032
0
}
4033
4034
void
4035
Http2Session::CreateTunnel(nsHttpTransaction *trans,
4036
                           nsHttpConnectionInfo *ci,
4037
                           nsIInterfaceRequestor *aCallbacks)
4038
0
{
4039
0
  LOG(("Http2Session::CreateTunnel %p %p make new tunnel\n", this, trans));
4040
0
  // The connect transaction will hold onto the underlying http
4041
0
  // transaction so that an auth created by the connect can be mappped
4042
0
  // to the correct security callbacks
4043
0
4044
0
  RefPtr<SpdyConnectTransaction> connectTrans =
4045
0
    new SpdyConnectTransaction(ci, aCallbacks, trans->Caps(), trans, this);
4046
0
  DebugOnly<bool> rv = AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false, nullptr);
4047
0
  MOZ_ASSERT(rv);
4048
0
  Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
4049
0
  MOZ_ASSERT(tunnel);
4050
0
  RegisterTunnel(tunnel);
4051
0
}
4052
4053
void
4054
Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
4055
                               nsIInterfaceRequestor *aCallbacks)
4056
0
{
4057
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4058
0
  nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
4059
0
  nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
4060
0
  MOZ_ASSERT(trans);
4061
0
4062
0
  LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
4063
0
4064
0
  aHttpTransaction->SetConnection(nullptr);
4065
0
4066
0
  // this transaction has done its work of setting up a tunnel, let
4067
0
  // the connection manager queue it if necessary
4068
0
  trans->SetTunnelProvider(this);
4069
0
  trans->EnableKeepAlive();
4070
0
4071
0
  if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
4072
0
    LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s",
4073
0
          this, ci->HashKey().get()));
4074
0
    CreateTunnel(trans, ci, aCallbacks);
4075
0
  } else {
4076
0
    // requeue it. The connection manager is responsible for actually putting
4077
0
    // this on the tunnel connection with the specific ci. If that can't
4078
0
    // happen the cmgr checks with us via MaybeReTunnel() to see if it should
4079
0
    // make a new tunnel or just wait longer.
4080
0
    LOG3(("Http2Session::DispatchOnTunnel %p trans=%p queue in connection manager",
4081
0
          this, trans));
4082
0
    nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
4083
0
    if (NS_FAILED(rv)) {
4084
0
      LOG3(("Http2Session::DispatchOnTunnel %p trans=%p failed to initiate "
4085
0
            "transaction (%08x)", this, trans, static_cast<uint32_t>(rv)));
4086
0
    }
4087
0
  }
4088
0
}
4089
4090
// From ASpdySession
4091
bool
4092
Http2Session::MaybeReTunnel(nsAHttpTransaction *aHttpTransaction)
4093
0
{
4094
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4095
0
  nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
4096
0
  LOG(("Http2Session::MaybeReTunnel %p trans=%p\n", this, trans));
4097
0
  if (!trans || trans->TunnelProvider() != this) {
4098
0
    // this isn't really one of our transactions.
4099
0
    return false;
4100
0
  }
4101
0
4102
0
  if (mClosed || mShouldGoAway) {
4103
0
    LOG(("Http2Session::MaybeReTunnel %p %p session closed - requeue\n", this, trans));
4104
0
    trans->SetTunnelProvider(nullptr);
4105
0
    nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
4106
0
    if (NS_FAILED(rv)) {
4107
0
      LOG3(("Http2Session::MaybeReTunnel %p trans=%p failed to initiate "
4108
0
            "transaction (%08x)", this, trans, static_cast<uint32_t>(rv)));
4109
0
    }
4110
0
    return true;
4111
0
  }
4112
0
4113
0
  nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
4114
0
  LOG(("Http2Session:MaybeReTunnel %p %p count=%d limit %d\n",
4115
0
       this, trans, FindTunnelCount(ci), gHttpHandler->MaxConnectionsPerOrigin()));
4116
0
  if (FindTunnelCount(ci) >= gHttpHandler->MaxConnectionsPerOrigin()) {
4117
0
    // patience - a tunnel will open up.
4118
0
    return false;
4119
0
  }
4120
0
4121
0
  LOG(("Http2Session::MaybeReTunnel %p %p make new tunnel\n", this, trans));
4122
0
  CreateTunnel(trans, ci, trans->SecurityCallbacks());
4123
0
  return true;
4124
0
}
4125
4126
nsresult
4127
Http2Session::BufferOutput(const char *buf,
4128
                           uint32_t count,
4129
                           uint32_t *countRead)
4130
0
{
4131
0
  nsAHttpSegmentReader *old = mSegmentReader;
4132
0
  mSegmentReader = nullptr;
4133
0
  nsresult rv = OnReadSegment(buf, count, countRead);
4134
0
  mSegmentReader = old;
4135
0
  return rv;
4136
0
}
4137
4138
bool // static
4139
Http2Session::ALPNCallback(nsISupports *securityInfo)
4140
0
{
4141
0
  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
4142
0
  LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
4143
0
  if (ssl) {
4144
0
    int16_t version = ssl->GetSSLVersionOffered();
4145
0
    LOG3(("Http2Session::ALPNCallback version=%x\n", version));
4146
0
4147
0
    if (version == nsISSLSocketControl::TLS_VERSION_1_2 &&
4148
0
        !gHttpHandler->IsH2MandatorySuiteEnabled()) {
4149
0
      LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
4150
0
      return false;
4151
0
    }
4152
0
4153
0
    if (version >= nsISSLSocketControl::TLS_VERSION_1_2) {
4154
0
      return true;
4155
0
    }
4156
0
  }
4157
0
  return false;
4158
0
}
4159
4160
nsresult
4161
Http2Session::ConfirmTLSProfile()
4162
0
{
4163
0
  if (mTLSProfileConfirmed) {
4164
0
    return NS_OK;
4165
0
  }
4166
0
4167
0
  LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
4168
0
        this, mConnection.get()));
4169
0
4170
0
  if (mAttemptingEarlyData) {
4171
0
    LOG3(("Http2Session::ConfirmTLSProfile %p temporarily passing due to early data\n", this));
4172
0
    return NS_OK;
4173
0
  }
4174
0
4175
0
  if (!gHttpHandler->EnforceHttp2TlsProfile()) {
4176
0
    LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this));
4177
0
    mTLSProfileConfirmed = true;
4178
0
    return NS_OK;
4179
0
  }
4180
0
4181
0
  if (!mConnection)
4182
0
    return NS_ERROR_FAILURE;
4183
0
4184
0
  nsCOMPtr<nsISupports> securityInfo;
4185
0
  mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
4186
0
  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
4187
0
  LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, ssl.get()));
4188
0
  if (!ssl)
4189
0
    return NS_ERROR_FAILURE;
4190
0
4191
0
  int16_t version = ssl->GetSSLVersionUsed();
4192
0
  LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
4193
0
  if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
4194
0
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", this));
4195
0
    RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4196
0
  }
4197
0
4198
0
  uint16_t kea = ssl->GetKEAUsed();
4199
0
  if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
4200
0
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
4201
0
          this, kea));
4202
0
    RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4203
0
  }
4204
0
4205
0
  uint32_t keybits = ssl->GetKEAKeyBits();
4206
0
  if (kea == ssl_kea_dh && keybits < 2048) {
4207
0
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
4208
0
          this, keybits));
4209
0
    RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4210
0
  } else if (kea == ssl_kea_ecdh && keybits < 224) { // see rfc7540 9.2.1.
4211
0
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
4212
0
          this, keybits));
4213
0
    RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4214
0
  }
4215
0
4216
0
  int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
4217
0
  LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n",
4218
0
        this, macAlgorithm));
4219
0
  if (macAlgorithm != nsISSLSocketControl::SSL_MAC_AEAD) {
4220
0
    LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n", this));
4221
0
    RETURN_SESSION_ERROR(this, INADEQUATE_SECURITY);
4222
0
  }
4223
0
4224
0
  /* We are required to send SNI. We do that already, so no check is done
4225
0
   * here to make sure we did. */
4226
0
4227
0
  /* We really should check to ensure TLS compression isn't enabled on
4228
0
   * this connection. However, we never enable TLS compression on our end,
4229
0
   * anyway, so it'll never be on. All the same, see https://bugzil.la/965881
4230
0
   * for the possibility for an interface to ensure it never gets turned on. */
4231
0
4232
0
  mTLSProfileConfirmed = true;
4233
0
  return NS_OK;
4234
0
}
4235
4236
4237
//-----------------------------------------------------------------------------
4238
// Modified methods of nsAHttpConnection
4239
//-----------------------------------------------------------------------------
4240
4241
void
4242
Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller)
4243
0
{
4244
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4245
0
  LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
4246
0
4247
0
  // a trapped signal from the http transaction to the connection that
4248
0
  // it is no longer blocked on read.
4249
0
4250
0
  Http2Stream *stream = mStreamTransactionHash.Get(caller);
4251
0
  if (!stream || !VerifyStream(stream)) {
4252
0
    LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
4253
0
          this, caller));
4254
0
    return;
4255
0
  }
4256
0
4257
0
  LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n",
4258
0
        this, stream->StreamID()));
4259
0
4260
0
  if (!mClosed) {
4261
0
    mReadyForWrite.Push(stream);
4262
0
    SetWriteCallbacks();
4263
0
  } else {
4264
0
    LOG3(("Http2Session::TransactionHasDataToWrite %p closed so not setting Ready4Write\n",
4265
0
          this));
4266
0
  }
4267
0
4268
0
  // NSPR poll will not poll the network if there are non system PR_FileDesc's
4269
0
  // that are ready - so we can get into a deadlock waiting for the system IO
4270
0
  // to come back here if we don't force the send loop manually.
4271
0
  Unused << ForceSend();
4272
0
}
4273
4274
void
4275
Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller)
4276
0
{
4277
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4278
0
  LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
4279
0
4280
0
  // a signal from the http transaction to the connection that it will consume more
4281
0
  Http2Stream *stream = mStreamTransactionHash.Get(caller);
4282
0
  if (!stream || !VerifyStream(stream)) {
4283
0
    LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found",
4284
0
          this, caller));
4285
0
    return;
4286
0
  }
4287
0
4288
0
  LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n",
4289
0
        this, stream->StreamID()));
4290
0
  ConnectSlowConsumer(stream);
4291
0
}
4292
4293
void
4294
Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
4295
0
{
4296
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4297
0
  LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
4298
0
        this, stream, stream->StreamID()));
4299
0
4300
0
  mReadyForWrite.Push(stream);
4301
0
  SetWriteCallbacks();
4302
0
  Unused << ForceSend();
4303
0
}
4304
4305
bool
4306
Http2Session::IsPersistent()
4307
0
{
4308
0
  return true;
4309
0
}
4310
4311
nsresult
4312
Http2Session::TakeTransport(nsISocketTransport **,
4313
                            nsIAsyncInputStream **, nsIAsyncOutputStream **)
4314
0
{
4315
0
  MOZ_ASSERT(false, "TakeTransport of Http2Session");
4316
0
  return NS_ERROR_UNEXPECTED;
4317
0
}
4318
4319
already_AddRefed<nsHttpConnection>
4320
Http2Session::TakeHttpConnection()
4321
0
{
4322
0
  MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
4323
0
  return nullptr;
4324
0
}
4325
4326
already_AddRefed<nsHttpConnection>
4327
Http2Session::HttpConnection()
4328
0
{
4329
0
  if (mConnection) {
4330
0
    return mConnection->HttpConnection();
4331
0
  }
4332
0
  return nullptr;
4333
0
}
4334
4335
void
4336
Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
4337
0
{
4338
0
  *aOut = nullptr;
4339
0
}
4340
4341
//-----------------------------------------------------------------------------
4342
// unused methods of nsAHttpTransaction
4343
// We can be sure of this because Http2Session is only constructed in
4344
// nsHttpConnection and is never passed out of that object or a TLSFilterTransaction
4345
// TLS tunnel
4346
//-----------------------------------------------------------------------------
4347
4348
void
4349
Http2Session::SetConnection(nsAHttpConnection *)
4350
0
{
4351
0
  // This is unexpected
4352
0
  MOZ_ASSERT(false, "Http2Session::SetConnection()");
4353
0
}
4354
4355
void
4356
Http2Session::SetProxyConnectFailed()
4357
0
{
4358
0
  MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
4359
0
}
4360
4361
bool
4362
Http2Session::IsDone()
4363
0
{
4364
0
  return !mStreamTransactionHash.Count();
4365
0
}
4366
4367
nsresult
4368
Http2Session::Status()
4369
0
{
4370
0
  MOZ_ASSERT(false, "Http2Session::Status()");
4371
0
  return NS_ERROR_UNEXPECTED;
4372
0
}
4373
4374
uint32_t
4375
Http2Session::Caps()
4376
0
{
4377
0
  MOZ_ASSERT(false, "Http2Session::Caps()");
4378
0
  return 0;
4379
0
}
4380
4381
void
4382
Http2Session::SetDNSWasRefreshed()
4383
0
{
4384
0
  MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
4385
0
}
4386
4387
nsHttpRequestHead *
4388
Http2Session::RequestHead()
4389
0
{
4390
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4391
0
  MOZ_ASSERT(false,
4392
0
             "Http2Session::RequestHead() "
4393
0
             "should not be called after http/2 is setup");
4394
0
  return nullptr;
4395
0
}
4396
4397
uint32_t
4398
Http2Session::Http1xTransactionCount()
4399
0
{
4400
0
  return 0;
4401
0
}
4402
4403
nsresult
4404
Http2Session::TakeSubTransactions(
4405
  nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
4406
0
{
4407
0
  // Generally this cannot be done with http/2 as transactions are
4408
0
  // started right away.
4409
0
4410
0
  LOG3(("Http2Session::TakeSubTransactions %p\n", this));
4411
0
4412
0
  if (mConcurrentHighWater > 0)
4413
0
    return NS_ERROR_ALREADY_OPENED;
4414
0
4415
0
  LOG3(("   taking %d\n", mStreamTransactionHash.Count()));
4416
0
4417
0
  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
4418
0
    outTransactions.AppendElement(iter.Key());
4419
0
4420
0
    // Removing the stream from the hash will delete the stream and drop the
4421
0
    // transaction reference the hash held.
4422
0
    iter.Remove();
4423
0
  }
4424
0
  return NS_OK;
4425
0
}
4426
4427
//-----------------------------------------------------------------------------
4428
// Pass through methods of nsAHttpConnection
4429
//-----------------------------------------------------------------------------
4430
4431
nsAHttpConnection *
4432
Http2Session::Connection()
4433
0
{
4434
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4435
0
  return mConnection;
4436
0
}
4437
4438
nsresult
4439
Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction,
4440
                                 nsHttpRequestHead *requestHead,
4441
                                 nsHttpResponseHead *responseHead, bool *reset)
4442
0
{
4443
0
  return mConnection->OnHeadersAvailable(transaction,
4444
0
                                         requestHead,
4445
0
                                         responseHead,
4446
0
                                         reset);
4447
0
}
4448
4449
bool
4450
Http2Session::IsReused()
4451
0
{
4452
0
  return mConnection->IsReused();
4453
0
}
4454
4455
nsresult
4456
Http2Session::PushBack(const char *buf, uint32_t len)
4457
0
{
4458
0
  return mConnection->PushBack(buf, len);
4459
0
}
4460
4461
void
4462
Http2Session::SendPing()
4463
0
{
4464
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4465
0
4466
0
  if (mPreviousUsed) {
4467
0
    // alredy in progress, get out
4468
0
    return;
4469
0
  }
4470
0
4471
0
  mPingSentEpoch = PR_IntervalNow();
4472
0
  if (!mPingSentEpoch) {
4473
0
    mPingSentEpoch = 1; // avoid the 0 sentinel value
4474
0
  }
4475
0
  if (!mPingThreshold ||
4476
0
      (mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
4477
0
    mPreviousPingThreshold = mPingThreshold;
4478
0
    mPreviousUsed = true;
4479
0
    mPingThreshold = gHttpHandler->NetworkChangedTimeout();
4480
0
  }
4481
0
  GeneratePing(false);
4482
0
  Unused << ResumeRecv();
4483
0
}
4484
4485
bool
4486
Http2Session::TestOriginFrame(const nsACString &hostname, int32_t port)
4487
0
{
4488
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4489
0
  MOZ_ASSERT(mOriginFrameActivated);
4490
0
4491
0
  nsAutoCString key(hostname);
4492
0
  key.Append (':');
4493
0
  key.AppendInt(port);
4494
0
  bool rv = mOriginFrame.Get(key);
4495
0
  LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv));
4496
0
  if (!rv && ConnectionInfo()) {
4497
0
    // the SNI is also implicitly in this list, so consult that too
4498
0
    nsHttpConnectionInfo *ci = ConnectionInfo();
4499
0
    rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && (port == ci->OriginPort());
4500
0
    LOG3(("TestOriginFrame() %p sni test %d\n", this, rv));
4501
0
  }
4502
0
  return rv;
4503
0
}
4504
4505
bool
4506
Http2Session::TestJoinConnection(const nsACString &hostname, int32_t port)
4507
0
{
4508
0
  return RealJoinConnection(hostname, port, true);
4509
0
}
4510
4511
bool
4512
Http2Session::JoinConnection(const nsACString &hostname, int32_t port)
4513
0
{
4514
0
  return RealJoinConnection(hostname, port, false);
4515
0
}
4516
4517
bool
4518
Http2Session::RealJoinConnection(const nsACString &hostname, int32_t port,
4519
                                 bool justKidding)
4520
0
{
4521
0
  if (!mConnection || mClosed || mShouldGoAway) {
4522
0
    return false;
4523
0
  }
4524
0
4525
0
  nsHttpConnectionInfo *ci = ConnectionInfo();
4526
0
  if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && (port == ci->OriginPort())) {
4527
0
    return true;
4528
0
 }
4529
0
4530
0
  if (!mReceivedSettings) {
4531
0
    return false;
4532
0
  }
4533
0
4534
0
  if (mOriginFrameActivated) {
4535
0
    bool originFrameResult = TestOriginFrame(hostname, port);
4536
0
    if (!originFrameResult) {
4537
0
      return false;
4538
0
    }
4539
0
  } else {
4540
0
    LOG3(("JoinConnection %p no origin frame check used.\n", this));
4541
0
  }
4542
0
4543
0
  nsAutoCString key(hostname);
4544
0
  key.Append(':');
4545
0
  key.Append(justKidding ? 'k' : '.');
4546
0
  key.AppendInt(port);
4547
0
  bool cachedResult;
4548
0
  if (mJoinConnectionCache.Get(key, &cachedResult)) {
4549
0
    LOG(("joinconnection [%p %s] %s result=%d cache\n",
4550
0
         this, ConnectionInfo()->HashKey().get(), key.get(),
4551
0
         cachedResult));
4552
0
    return cachedResult;
4553
0
  }
4554
0
4555
0
  nsresult rv;
4556
0
  bool isJoined = false;
4557
0
4558
0
  nsCOMPtr<nsISupports> securityInfo;
4559
0
  nsCOMPtr<nsISSLSocketControl> sslSocketControl;
4560
0
4561
0
  mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
4562
0
  sslSocketControl = do_QueryInterface(securityInfo, &rv);
4563
0
  if (NS_FAILED(rv) || !sslSocketControl) {
4564
0
    return false;
4565
0
  }
4566
0
4567
0
  // try all the coalescable versions we support.
4568
0
  const SpdyInformation *info = gHttpHandler->SpdyInfo();
4569
0
  static_assert(SpdyInformation::kCount == 1, "assume 1 alpn version");
4570
0
  bool joinedReturn = false;
4571
0
  if (info->ProtocolEnabled(0)) {
4572
0
    if (justKidding) {
4573
0
      rv = sslSocketControl->TestJoinConnection(info->VersionString[0],
4574
0
                                                hostname, port, &isJoined);
4575
0
    } else {
4576
0
      rv = sslSocketControl->JoinConnection(info->VersionString[0],
4577
0
                                            hostname, port, &isJoined);
4578
0
    }
4579
0
    if (NS_SUCCEEDED(rv) && isJoined) {
4580
0
      joinedReturn = true;
4581
0
    }
4582
0
  }
4583
0
4584
0
  LOG(("joinconnection [%p %s] %s result=%d lookup\n",
4585
0
       this, ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
4586
0
  mJoinConnectionCache.Put(key, joinedReturn);
4587
0
  if (!justKidding) {
4588
0
    // cache a kidding entry too as this one is good for both
4589
0
    nsAutoCString key2(hostname);
4590
0
    key2.Append(':');
4591
0
    key2.Append('k');
4592
0
    key2.AppendInt(port);
4593
0
    if (!mJoinConnectionCache.Get(key2)) {
4594
0
      mJoinConnectionCache.Put(key2, joinedReturn);
4595
0
    }
4596
0
  }
4597
0
  return joinedReturn;
4598
0
}
4599
4600
void
4601
Http2Session::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
4602
0
{
4603
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
4604
0
4605
0
  mCurrentForegroundTabOuterContentWindowId = windowId;
4606
0
4607
0
  for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
4608
0
    iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
4609
0
  }
4610
0
}
4611
4612
} // namespace net
4613
} // namespace mozilla