Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/protocol/http/Http2Stream.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
0
#define LOG_ENABLED() LOG5_ENABLED()
15
16
#include <algorithm>
17
18
#include "Http2Compression.h"
19
#include "Http2Session.h"
20
#include "Http2Stream.h"
21
#include "Http2Push.h"
22
#include "TunnelUtils.h"
23
24
#include "mozilla/BasePrincipal.h"
25
#include "mozilla/Telemetry.h"
26
#include "nsAlgorithm.h"
27
#include "nsHttp.h"
28
#include "nsHttpHandler.h"
29
#include "nsHttpRequestHead.h"
30
#include "nsIClassOfService.h"
31
#include "nsIPipe.h"
32
#include "nsISocketTransport.h"
33
#include "nsStandardURL.h"
34
#include "prnetdb.h"
35
36
namespace mozilla {
37
namespace net {
38
39
Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
40
                         Http2Session *session,
41
                         int32_t priority,
42
                         uint64_t windowId)
43
  : mStreamID(0)
44
  , mSession(session)
45
  , mSegmentReader(nullptr)
46
  , mSegmentWriter(nullptr)
47
  , mUpstreamState(GENERATING_HEADERS)
48
  , mState(IDLE)
49
  , mRequestHeadersDone(0)
50
  , mOpenGenerated(0)
51
  , mAllHeadersReceived(0)
52
  , mQueued(0)
53
  , mSocketTransport(session->SocketTransport())
54
  , mTransaction(httpTransaction)
55
  , mChunkSize(session->SendingChunkSize())
56
  , mRequestBlockedOnRead(0)
57
  , mRecvdFin(0)
58
  , mReceivedData(0)
59
  , mRecvdReset(0)
60
  , mSentReset(0)
61
  , mCountAsActive(0)
62
  , mSentFin(0)
63
  , mSentWaitingFor(0)
64
  , mSetTCPSocketBuffer(0)
65
  , mBypassInputBuffer(0)
66
  , mTxInlineFrameSize(Http2Session::kDefaultBufferSize)
67
  , mTxInlineFrameUsed(0)
68
  , mTxStreamFrameSize(0)
69
  , mRequestBodyLenRemaining(0)
70
  , mLocalUnacked(0)
71
  , mBlockedOnRwin(false)
72
  , mTotalSent(0)
73
  , mTotalRead(0)
74
  , mPushSource(nullptr)
75
  , mAttempting0RTT(false)
76
  , mCurrentForegroundTabOuterContentWindowId(windowId)
77
  , mTransactionTabId(0)
78
  , mIsTunnel(false)
79
  , mPlainTextTunnel(false)
80
0
{
81
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
82
0
83
0
  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
84
0
  LOG3(("Http2Stream::Http2Stream %p trans=%p atrans=%p", this, trans, httpTransaction));
85
0
86
0
  mServerReceiveWindow = session->GetServerInitialStreamWindow();
87
0
  mClientReceiveWindow = session->PushAllowance();
88
0
89
0
  mTxInlineFrame = MakeUnique<uint8_t[]>(mTxInlineFrameSize);
90
0
91
0
  static_assert(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority,
92
0
                "Lowest Priority should be less than kNormalPriority");
93
0
94
0
  // values of priority closer to 0 are higher priority for the priority
95
0
  // argument. This value is used as a group, which maps to a
96
0
  // weight that is related to the nsISupportsPriority that we are given.
97
0
  int32_t httpPriority;
98
0
  if (priority >= nsISupportsPriority::PRIORITY_LOWEST) {
99
0
    httpPriority = kWorstPriority;
100
0
  } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) {
101
0
    httpPriority = kBestPriority;
102
0
  } else {
103
0
    httpPriority = kNormalPriority + priority;
104
0
  }
105
0
  MOZ_ASSERT(httpPriority >= 0);
106
0
  SetPriority(static_cast<uint32_t>(httpPriority));
107
0
108
0
  if (trans) {
109
0
    mTransactionTabId = trans->TopLevelOuterContentWindowId();
110
0
  }
111
0
}
112
113
Http2Stream::~Http2Stream()
114
0
{
115
0
  ClearPushSource();
116
0
  ClearTransactionsBlockedOnTunnel();
117
0
  mStreamID = Http2Session::kDeadStreamID;
118
0
119
0
  LOG3(("Http2Stream::~Http2Stream %p", this));
120
0
}
121
122
void
123
Http2Stream::ClearPushSource()
124
0
{
125
0
  if (mPushSource) {
126
0
    mPushSource->SetConsumerStream(nullptr);
127
0
    mPushSource = nullptr;
128
0
  }
129
0
}
130
131
// ReadSegments() is used to write data down the socket. Generally, HTTP
132
// request data is pulled from the approriate transaction and
133
// converted to HTTP/2 data. Sometimes control data like a window-update is
134
// generated instead.
135
136
nsresult
137
Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
138
                          uint32_t count,
139
                          uint32_t *countRead)
140
0
{
141
0
  LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x",
142
0
        this, reader, count, mUpstreamState));
143
0
144
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
145
0
146
0
  nsresult rv = NS_ERROR_UNEXPECTED;
147
0
  mRequestBlockedOnRead = 0;
148
0
149
0
  if (mRecvdFin || mRecvdReset) {
150
0
    // Don't transmit any request frames if the peer cannot respond
151
0
    LOG3(("Http2Stream %p ReadSegments request stream aborted due to"
152
0
          " response side closure\n", this));
153
0
    return NS_ERROR_ABORT;
154
0
  }
155
0
156
0
  // avoid runt chunks if possible by anticipating
157
0
  // full data frames
158
0
  if (count > (mChunkSize + 8)) {
159
0
    uint32_t numchunks = count / (mChunkSize + 8);
160
0
    count = numchunks * (mChunkSize + 8);
161
0
  }
162
0
163
0
  switch (mUpstreamState) {
164
0
  case GENERATING_HEADERS:
165
0
  case GENERATING_BODY:
166
0
  case SENDING_BODY:
167
0
    // Call into the HTTP Transaction to generate the HTTP request
168
0
    // stream. That stream will show up in OnReadSegment().
169
0
    mSegmentReader = reader;
170
0
    rv = mTransaction->ReadSegments(this, count, countRead);
171
0
    mSegmentReader = nullptr;
172
0
173
0
    LOG3(("Http2Stream::ReadSegments %p trans readsegments rv %" PRIx32 " read=%d\n",
174
0
          this, static_cast<uint32_t>(rv), *countRead));
175
0
176
0
    // Check to see if the transaction's request could be written out now.
177
0
    // If not, mark the stream for callback when writing can proceed.
178
0
    if (NS_SUCCEEDED(rv) &&
179
0
        mUpstreamState == GENERATING_HEADERS &&
180
0
        !mRequestHeadersDone)
181
0
      mSession->TransactionHasDataToWrite(this);
182
0
183
0
    // mTxinlineFrameUsed represents any queued un-sent frame. It might
184
0
    // be 0 if there is no such frame, which is not a gurantee that we
185
0
    // don't have more request body to send - just that any data that was
186
0
    // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is
187
0
    // a queued, but complete, http/2 frame length.
188
0
189
0
    // Mark that we are blocked on read if the http transaction needs to
190
0
    // provide more of the request message body and there is nothing queued
191
0
    // for writing
192
0
    if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
193
0
      mRequestBlockedOnRead = 1;
194
0
195
0
    // A transaction that had already generated its headers before it was
196
0
    // queued at the session level (due to concurrency concerns) may not call
197
0
    // onReadSegment off the ReadSegments() stack above.
198
0
    if (mUpstreamState == GENERATING_HEADERS && NS_SUCCEEDED(rv)) {
199
0
      LOG3(("Http2Stream %p ReadSegments forcing OnReadSegment call\n", this));
200
0
      uint32_t wasted = 0;
201
0
      mSegmentReader = reader;
202
0
      Unused << OnReadSegment("", 0, &wasted);
203
0
      mSegmentReader = nullptr;
204
0
    }
205
0
206
0
    // If the sending flow control window is open (!mBlockedOnRwin) then
207
0
    // continue sending the request
208
0
    if (!mBlockedOnRwin && mOpenGenerated &&
209
0
        !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
210
0
      MOZ_ASSERT(!mQueued);
211
0
      MOZ_ASSERT(mRequestHeadersDone);
212
0
      LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
213
0
            "mUpstreamState=%x\n",this, mStreamID, mUpstreamState));
214
0
      if (mSentFin) {
215
0
        ChangeState(UPSTREAM_COMPLETE);
216
0
      } else {
217
0
        GenerateDataFrameHeader(0, true);
218
0
        ChangeState(SENDING_FIN_STREAM);
219
0
        mSession->TransactionHasDataToWrite(this);
220
0
        rv = NS_BASE_STREAM_WOULD_BLOCK;
221
0
      }
222
0
    }
223
0
    break;
224
0
225
0
  case SENDING_FIN_STREAM:
226
0
    // We were trying to send the FIN-STREAM but were blocked from
227
0
    // sending it out - try again.
228
0
    if (!mSentFin) {
229
0
      mSegmentReader = reader;
230
0
      rv = TransmitFrame(nullptr, nullptr, false);
231
0
      mSegmentReader = nullptr;
232
0
      MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
233
0
                 "Transmit Frame should be all or nothing");
234
0
      if (NS_SUCCEEDED(rv))
235
0
        ChangeState(UPSTREAM_COMPLETE);
236
0
    } else {
237
0
      rv = NS_OK;
238
0
      mTxInlineFrameUsed = 0;         // cancel fin data packet
239
0
      ChangeState(UPSTREAM_COMPLETE);
240
0
    }
241
0
242
0
    *countRead = 0;
243
0
244
0
    // don't change OK to WOULD BLOCK. we are really done sending if OK
245
0
    break;
246
0
247
0
  case UPSTREAM_COMPLETE:
248
0
    *countRead = 0;
249
0
    rv = NS_OK;
250
0
    break;
251
0
252
0
  default:
253
0
    MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
254
0
    break;
255
0
  }
256
0
257
0
  return rv;
258
0
}
259
260
uint64_t
261
Http2Stream::LocalUnAcked()
262
0
{
263
0
  // reduce unacked by the amount of undelivered data
264
0
  // to help assert flow control
265
0
  uint64_t undelivered = mSimpleBuffer.Available();
266
0
267
0
  if (undelivered > mLocalUnacked) {
268
0
    return 0;
269
0
  }
270
0
  return mLocalUnacked - undelivered;
271
0
}
272
273
nsresult
274
Http2Stream::BufferInput(uint32_t count, uint32_t *countWritten)
275
0
{
276
0
  char buf[SimpleBufferPage::kSimpleBufferPageSize];
277
0
  if (SimpleBufferPage::kSimpleBufferPageSize < count) {
278
0
    count = SimpleBufferPage::kSimpleBufferPageSize;
279
0
  }
280
0
281
0
  mBypassInputBuffer = 1;
282
0
  nsresult rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten);
283
0
  mBypassInputBuffer = 0;
284
0
285
0
  if (NS_SUCCEEDED(rv)) {
286
0
    rv = mSimpleBuffer.Write(buf, *countWritten);
287
0
    if (NS_FAILED(rv)) {
288
0
      MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
289
0
      return NS_ERROR_OUT_OF_MEMORY;
290
0
    }
291
0
  }
292
0
  return rv;
293
0
}
294
295
bool
296
Http2Stream::DeferCleanup(nsresult status)
297
0
{
298
0
  // do not cleanup a stream that has data buffered for the transaction
299
0
  return (NS_SUCCEEDED(status) && mSimpleBuffer.Available());
300
0
}
301
302
// WriteSegments() is used to read data off the socket. Generally this is
303
// just a call through to the associated nsHttpTransaction for this stream
304
// for the remaining data bytes indicated by the current DATA frame.
305
306
nsresult
307
Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
308
                           uint32_t count,
309
                           uint32_t *countWritten)
310
0
{
311
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
312
0
  MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
313
0
314
0
  LOG3(("Http2Stream::WriteSegments %p count=%d state=%x",
315
0
        this, count, mUpstreamState));
316
0
317
0
  mSegmentWriter = writer;
318
0
  nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
319
0
320
0
  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
321
0
    // consuming transaction won't take data. but we need to read it into a buffer so that it
322
0
    // won't block other streams. but we should not advance the flow control window
323
0
    // so that we'll eventually push back on the sender.
324
0
325
0
    // with tunnels you need to make sure that this is an underlying connction established
326
0
    // that can be meaningfully giving this signal
327
0
    bool doBuffer = true;
328
0
    if (mIsTunnel) {
329
0
      RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
330
0
      if (qiTrans) {
331
0
        doBuffer = qiTrans->ConnectedReadyForInput();
332
0
      }
333
0
    }
334
0
    // stash this data
335
0
    if (doBuffer) {
336
0
      rv = BufferInput(count, countWritten);
337
0
      LOG3(("Http2Stream::WriteSegments %p Buffered %" PRIX32 " %d\n", this,
338
0
            static_cast<uint32_t>(rv), *countWritten));
339
0
    }
340
0
  }
341
0
  mSegmentWriter = nullptr;
342
0
  return rv;
343
0
}
344
345
nsresult
346
Http2Stream::MakeOriginURL(const nsACString &origin, nsCOMPtr<nsIURI> &url)
347
0
{
348
0
  nsAutoCString scheme;
349
0
  nsresult rv = net_ExtractURLScheme(origin, scheme);
350
0
  NS_ENSURE_SUCCESS(rv, rv);
351
0
  return MakeOriginURL(scheme, origin, url);
352
0
}
353
354
nsresult
355
Http2Stream::MakeOriginURL(const nsACString &scheme, const nsACString &origin,
356
                           nsCOMPtr<nsIURI> &url)
357
0
{
358
0
  return NS_MutateURI(new nsStandardURL::Mutator())
359
0
    .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
360
0
                            nsIStandardURL::URLTYPE_AUTHORITY,
361
0
                            scheme.EqualsLiteral("http") ? NS_HTTP_DEFAULT_PORT
362
0
                                                         : NS_HTTPS_DEFAULT_PORT,
363
0
                            nsCString(origin), nullptr, nullptr, nullptr))
364
0
    .Finalize(url);
365
0
}
366
367
void
368
Http2Stream::CreatePushHashKey(const nsCString &scheme,
369
                               const nsCString &hostHeader,
370
                               const mozilla::OriginAttributes &originAttributes,
371
                               uint64_t serial,
372
                               const nsACString& pathInfo,
373
                               nsCString &outOrigin,
374
                               nsCString &outKey)
375
0
{
376
0
  nsCString fullOrigin = scheme;
377
0
  fullOrigin.AppendLiteral("://");
378
0
  fullOrigin.Append(hostHeader);
379
0
380
0
  nsCOMPtr<nsIURI> origin;
381
0
  nsresult rv = Http2Stream::MakeOriginURL(scheme, fullOrigin, origin);
382
0
383
0
  if (NS_SUCCEEDED(rv)) {
384
0
    rv = origin->GetAsciiSpec(outOrigin);
385
0
    outOrigin.Trim("/", false, true, false);
386
0
  }
387
0
388
0
  if (NS_FAILED(rv)) {
389
0
    // Fallback to plain text copy - this may end up behaving poorly
390
0
    outOrigin = fullOrigin;
391
0
  }
392
0
393
0
  outKey = outOrigin;
394
0
  outKey.AppendLiteral("/[");
395
0
  nsAutoCString suffix;
396
0
  originAttributes.CreateSuffix(suffix);
397
0
  outKey.Append(suffix);
398
0
  outKey.Append(']');
399
0
  outKey.AppendLiteral("/[http2.");
400
0
  outKey.AppendInt(serial);
401
0
  outKey.Append(']');
402
0
  outKey.Append(pathInfo);
403
0
}
404
405
nsresult
406
Http2Stream::ParseHttpRequestHeaders(const char *buf,
407
                                     uint32_t avail,
408
                                     uint32_t *countUsed)
409
0
{
410
0
  // Returns NS_OK even if the headers are incomplete
411
0
  // set mRequestHeadersDone flag if they are complete
412
0
413
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
414
0
  MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS);
415
0
  MOZ_ASSERT(!mRequestHeadersDone);
416
0
417
0
  LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x",
418
0
        this, avail, mUpstreamState));
419
0
420
0
  mFlatHttpRequestHeaders.Append(buf, avail);
421
0
  nsHttpRequestHead *head = mTransaction->RequestHead();
422
0
423
0
  // We can use the simple double crlf because firefox is the
424
0
  // only client we are parsing
425
0
  int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
426
0
427
0
  if (endHeader == kNotFound) {
428
0
    // We don't have all the headers yet
429
0
    LOG3(("Http2Stream::ParseHttpRequestHeaders %p "
430
0
          "Need more header bytes. Len = %d",
431
0
          this, mFlatHttpRequestHeaders.Length()));
432
0
    *countUsed = avail;
433
0
    return NS_OK;
434
0
  }
435
0
436
0
  // We have recvd all the headers, trim the local
437
0
  // buffer of the final empty line, and set countUsed to reflect
438
0
  // the whole header has been consumed.
439
0
  uint32_t oldLen = mFlatHttpRequestHeaders.Length();
440
0
  mFlatHttpRequestHeaders.SetLength(endHeader + 2);
441
0
  *countUsed = avail - (oldLen - endHeader) + 4;
442
0
  mRequestHeadersDone = 1;
443
0
444
0
  nsAutoCString authorityHeader;
445
0
  nsAutoCString hashkey;
446
0
  nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
447
0
  if (NS_FAILED(rv)) {
448
0
    MOZ_ASSERT(false);
449
0
    return rv;
450
0
  }
451
0
452
0
  nsAutoCString requestURI;
453
0
  head->RequestURI(requestURI);
454
0
455
0
  mozilla::OriginAttributes originAttributes;
456
0
  mSocketTransport->GetOriginAttributes(&originAttributes);
457
0
458
0
  CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
459
0
                    authorityHeader, originAttributes, mSession->Serial(),
460
0
                    requestURI,
461
0
                    mOrigin, hashkey);
462
0
463
0
  // check the push cache for GET
464
0
  if (head->IsGet()) {
465
0
    // from :scheme, :authority, :path
466
0
    nsIRequestContext *requestContext = mTransaction->RequestContext();
467
0
    SpdyPushCache *cache = nullptr;
468
0
    if (requestContext) {
469
0
      requestContext->GetSpdyPushCache(&cache);
470
0
    }
471
0
472
0
    Http2PushedStream *pushedStream = nullptr;
473
0
474
0
    // If a push stream is attached to the transaction via onPush, match only with that
475
0
    // one. This occurs when a push was made with in conjunction with a nsIHttpPushListener
476
0
    nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
477
0
    if (trans && (pushedStream = trans->TakePushedStream())) {
478
0
      if (pushedStream->mSession == mSession) {
479
0
        LOG3(("Pushed Stream match based on OnPush correlation %p", pushedStream));
480
0
      } else {
481
0
        LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64 " %" PRId64 "\n",
482
0
              pushedStream, pushedStream->mSession->Serial(), mSession->Serial()));
483
0
        pushedStream->OnPushFailed();
484
0
        pushedStream = nullptr;
485
0
      }
486
0
    }
487
0
488
0
    // we remove the pushedstream from the push cache so that
489
0
    // it will not be used for another GET. This does not destroy the
490
0
    // stream itself - that is done when the transactionhash is done with it.
491
0
    if (cache && !pushedStream){
492
0
        pushedStream = cache->RemovePushedStreamHttp2(hashkey);
493
0
    }
494
0
495
0
    LOG3(("Pushed Stream Lookup "
496
0
          "session=%p key=%s requestcontext=%p cache=%p hit=%p\n",
497
0
          mSession, hashkey.get(), requestContext, cache, pushedStream));
498
0
499
0
    if (pushedStream) {
500
0
      LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n",
501
0
            pushedStream, pushedStream->StreamID(), hashkey.get()));
502
0
      pushedStream->SetConsumerStream(this);
503
0
      mPushSource = pushedStream;
504
0
      SetSentFin(true);
505
0
      AdjustPushedPriority();
506
0
507
0
      // There is probably pushed data buffered so trigger a read manually
508
0
      // as we can't rely on future network events to do it
509
0
      mSession->ConnectPushedStream(this);
510
0
      mOpenGenerated = 1;
511
0
512
0
      // if the "mother stream" had TRR, this one is a TRR stream too!
513
0
      RefPtr<nsHttpConnectionInfo> ci(Transaction()->ConnectionInfo());
514
0
      if (ci && ci->GetTrrUsed()) {
515
0
        mSession->IncrementTrrCounter();
516
0
      }
517
0
518
0
      return NS_OK;
519
0
    }
520
0
  }
521
0
  return NS_OK;
522
0
}
523
524
// This is really a headers frame, but open is pretty clear from a workflow pov
525
nsresult
526
Http2Stream::GenerateOpen()
527
0
{
528
0
  // It is now OK to assign a streamID that we are assured will
529
0
  // be monotonically increasing amongst new streams on this
530
0
  // session
531
0
  mStreamID = mSession->RegisterStreamID(this);
532
0
  MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
533
0
  MOZ_ASSERT(!mOpenGenerated);
534
0
535
0
  mOpenGenerated = 1;
536
0
537
0
  nsHttpRequestHead *head = mTransaction->RequestHead();
538
0
  nsAutoCString requestURI;
539
0
  head->RequestURI(requestURI);
540
0
  LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n",
541
0
        this, mStreamID, mSession, requestURI.get()));
542
0
543
0
  if (mStreamID >= 0x80000000) {
544
0
    // streamID must fit in 31 bits. Evading This is theoretically possible
545
0
    // because stream ID assignment is asynchronous to stream creation
546
0
    // because of the protocol requirement that the new stream ID
547
0
    // be monotonically increasing. In reality this is really not possible
548
0
    // because new streams stop being added to a session with millions of
549
0
    // IDs still available and no race condition is going to bridge that gap;
550
0
    // so we can be comfortable on just erroring out for correctness in that
551
0
    // case.
552
0
    LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
553
0
    return NS_ERROR_UNEXPECTED;
554
0
  }
555
0
556
0
  // Now we need to convert the flat http headers into a set
557
0
  // of HTTP/2 headers by writing to mTxInlineFrame{sz}
558
0
559
0
  nsCString compressedData;
560
0
  nsAutoCString authorityHeader;
561
0
  nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
562
0
  if (NS_FAILED(rv)) {
563
0
    MOZ_ASSERT(false);
564
0
    return rv;
565
0
  }
566
0
567
0
  nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
568
0
  if (head->IsConnect()) {
569
0
    MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
570
0
    mIsTunnel = true;
571
0
    mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
572
0
573
0
    // Our normal authority has an implicit port, best to use an
574
0
    // explicit one with a tunnel
575
0
    nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
576
0
    if (!ci) {
577
0
      return NS_ERROR_UNEXPECTED;
578
0
    }
579
0
580
0
    authorityHeader = ci->GetOrigin();
581
0
    authorityHeader.Append(':');
582
0
    authorityHeader.AppendInt(ci->OriginPort());
583
0
  }
584
0
585
0
  nsAutoCString method;
586
0
  nsAutoCString path;
587
0
  head->Method(method);
588
0
  head->Path(path);
589
0
  rv = mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
590
0
                                                 method,
591
0
                                                 path,
592
0
                                                 authorityHeader,
593
0
                                                 scheme,
594
0
                                                 head->IsConnect(),
595
0
                                                 compressedData);
596
0
  NS_ENSURE_SUCCESS(rv, rv);
597
0
598
0
  int64_t clVal = mSession->Compressor()->GetParsedContentLength();
599
0
  if (clVal != -1) {
600
0
    mRequestBodyLenRemaining = clVal;
601
0
  }
602
0
603
0
  // Determine whether to put the fin bit on the header frame or whether
604
0
  // to wait for a data packet to put it on.
605
0
  uint8_t firstFrameFlags =  Http2Session::kFlag_PRIORITY;
606
0
607
0
  if (head->IsGet() ||
608
0
      head->IsHead()) {
609
0
    // for GET and HEAD place the fin bit right on the
610
0
    // header packet
611
0
612
0
    SetSentFin(true);
613
0
    firstFrameFlags |= Http2Session::kFlag_END_STREAM;
614
0
  } else if (head->IsPost() ||
615
0
             head->IsPut() ||
616
0
             head->IsConnect()) {
617
0
    // place fin in a data frame even for 0 length messages for iterop
618
0
  } else if (!mRequestBodyLenRemaining) {
619
0
    // for other HTTP extension methods, rely on the content-length
620
0
    // to determine whether or not to put fin on headers
621
0
    SetSentFin(true);
622
0
    firstFrameFlags |= Http2Session::kFlag_END_STREAM;
623
0
  }
624
0
625
0
  // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the
626
0
  // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing
627
0
  // frame for the new headers and for the first one a priority field. There is
628
0
  // no question this is ugly, but a 16KB HEADERS frame should be a long
629
0
  // tail event, so this is really just for correctness and a nop in the base case.
630
0
  //
631
0
632
0
  MOZ_ASSERT(!mTxInlineFrameUsed);
633
0
634
0
  uint32_t dataLength = compressedData.Length();
635
0
  uint32_t maxFrameData = Http2Session::kMaxFrameData - 5; // 5 bytes for priority
636
0
  uint32_t numFrames = 1;
637
0
638
0
  if (dataLength > maxFrameData) {
639
0
    numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) /
640
0
      Http2Session::kMaxFrameData;
641
0
    MOZ_ASSERT (numFrames > 1);
642
0
  }
643
0
644
0
  // note that we could still have 1 frame for 0 bytes of data. that's ok.
645
0
646
0
  uint32_t messageSize = dataLength;
647
0
  messageSize += Http2Session::kFrameHeaderBytes + 5; // frame header + priority overhead in HEADERS frame
648
0
  messageSize += (numFrames - 1) * Http2Session::kFrameHeaderBytes; // frame header overhead in CONTINUATION frames
649
0
650
0
  EnsureBuffer(mTxInlineFrame, messageSize,
651
0
               mTxInlineFrameUsed, mTxInlineFrameSize);
652
0
653
0
  mTxInlineFrameUsed += messageSize;
654
0
  UpdatePriorityDependency();
655
0
  LOG3(("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with "
656
0
        "priority weight %u dep 0x%X frames %u uri=%s\n",
657
0
        this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
658
0
        mPriorityDependency, numFrames, requestURI.get()));
659
0
660
0
  uint32_t outputOffset = 0;
661
0
  uint32_t compressedDataOffset = 0;
662
0
  for (uint32_t idx = 0; idx < numFrames; ++idx) {
663
0
    uint32_t flags, frameLen;
664
0
    bool lastFrame = (idx == numFrames - 1);
665
0
666
0
    flags = 0;
667
0
    frameLen = maxFrameData;
668
0
    if (!idx) {
669
0
      flags |= firstFrameFlags;
670
0
      // Only the first frame needs the 4-byte offset
671
0
      maxFrameData = Http2Session::kMaxFrameData;
672
0
    }
673
0
    if (lastFrame) {
674
0
      frameLen = dataLength;
675
0
      flags |= Http2Session::kFlag_END_HEADERS;
676
0
    }
677
0
    dataLength -= frameLen;
678
0
679
0
    mSession->CreateFrameHeader(
680
0
      mTxInlineFrame.get() + outputOffset,
681
0
      frameLen + (idx ? 0 : 5),
682
0
      (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS,
683
0
      flags, mStreamID);
684
0
    outputOffset += Http2Session::kFrameHeaderBytes;
685
0
686
0
    if (!idx) {
687
0
      uint32_t wireDep = PR_htonl(mPriorityDependency);
688
0
      memcpy(mTxInlineFrame.get() + outputOffset, &wireDep, 4);
689
0
      memcpy(mTxInlineFrame.get() + outputOffset + 4, &mPriorityWeight, 1);
690
0
      outputOffset += 5;
691
0
    }
692
0
693
0
    memcpy(mTxInlineFrame.get() + outputOffset,
694
0
           compressedData.BeginReading() + compressedDataOffset, frameLen);
695
0
    compressedDataOffset += frameLen;
696
0
    outputOffset += frameLen;
697
0
  }
698
0
699
0
  Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length());
700
0
701
0
  // The size of the input headers is approximate
702
0
  uint32_t ratio =
703
0
    compressedData.Length() * 100 /
704
0
    (11 + requestURI.Length() +
705
0
     mFlatHttpRequestHeaders.Length());
706
0
707
0
  mFlatHttpRequestHeaders.Truncate();
708
0
  Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
709
0
  return NS_OK;
710
0
}
711
712
void
713
Http2Stream::AdjustInitialWindow()
714
0
{
715
0
  // The default initial_window is sized for pushed streams. When we
716
0
  // generate a client pulled stream we want to disable flow control for
717
0
  // the stream with a window update. Do the same for pushed streams
718
0
  // when they connect to a pull.
719
0
720
0
  // >0 even numbered IDs are pushed streams.
721
0
  // odd numbered IDs are pulled streams.
722
0
  // 0 is the sink for a pushed stream.
723
0
  Http2Stream *stream = this;
724
0
  if (!mStreamID) {
725
0
    MOZ_ASSERT(mPushSource);
726
0
    if (!mPushSource)
727
0
      return;
728
0
    stream = mPushSource;
729
0
    MOZ_ASSERT(stream->mStreamID);
730
0
    MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
731
0
732
0
    // If the pushed stream has recvd a FIN, there is no reason to update
733
0
    // the window
734
0
    if (stream->RecvdFin() || stream->RecvdReset())
735
0
      return;
736
0
  }
737
0
738
0
  if (stream->mState == RESERVED_BY_REMOTE) {
739
0
    // h2-14 prevents sending a window update in this state
740
0
    return;
741
0
  }
742
0
743
0
  // right now mClientReceiveWindow is the lower push limit
744
0
  // bump it up to the pull limit set by the channel or session
745
0
  // don't allow windows less than push
746
0
  uint32_t bump = 0;
747
0
  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
748
0
  if (trans && trans->InitialRwin()) {
749
0
    bump = (trans->InitialRwin() > mClientReceiveWindow) ?
750
0
      (trans->InitialRwin() - mClientReceiveWindow) : 0;
751
0
  } else {
752
0
    MOZ_ASSERT(mSession->InitialRwin() >= mClientReceiveWindow);
753
0
    bump = mSession->InitialRwin() - mClientReceiveWindow;
754
0
  }
755
0
756
0
  LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n",
757
0
        this, stream->mStreamID, bump));
758
0
  if (!bump) { // nothing to do
759
0
    return;
760
0
  }
761
0
762
0
  EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
763
0
               mTxInlineFrameUsed, mTxInlineFrameSize);
764
0
  uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
765
0
  mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 4;
766
0
767
0
  mSession->CreateFrameHeader(packet, 4,
768
0
                              Http2Session::FRAME_TYPE_WINDOW_UPDATE,
769
0
                              0, stream->mStreamID);
770
0
771
0
  mClientReceiveWindow += bump;
772
0
  bump = PR_htonl(bump);
773
0
  memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
774
0
}
775
776
void
777
Http2Stream::AdjustPushedPriority()
778
0
{
779
0
  // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams.
780
0
  // 0 is the sink for a pushed stream.
781
0
782
0
  if (mStreamID || !mPushSource)
783
0
    return;
784
0
785
0
  MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
786
0
787
0
  // If the pushed stream has recvd a FIN, there is no reason to update
788
0
  // the window
789
0
  if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
790
0
    return;
791
0
792
0
  EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
793
0
               mTxInlineFrameUsed, mTxInlineFrameSize);
794
0
  uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
795
0
  mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
796
0
797
0
  mSession->CreateFrameHeader(packet, 5,
798
0
                              Http2Session::FRAME_TYPE_PRIORITY, 0,
799
0
                              mPushSource->mStreamID);
800
0
801
0
  mPushSource->SetPriority(mPriority);
802
0
  memset(packet + Http2Session::kFrameHeaderBytes, 0, 4);
803
0
  memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
804
0
805
0
  LOG3(("AdjustPushedPriority %p id 0x%X to weight %X\n", this, mPushSource->mStreamID,
806
0
        mPriorityWeight));
807
0
}
808
809
void
810
Http2Stream::UpdateTransportReadEvents(uint32_t count)
811
0
{
812
0
  mTotalRead += count;
813
0
  if (!mSocketTransport) {
814
0
    return;
815
0
  }
816
0
817
0
  mTransaction->OnTransportStatus(mSocketTransport,
818
0
                                  NS_NET_STATUS_RECEIVING_FROM,
819
0
                                  mTotalRead);
820
0
}
821
822
void
823
Http2Stream::UpdateTransportSendEvents(uint32_t count)
824
0
{
825
0
  mTotalSent += count;
826
0
827
0
  // normally on non-windows platform we use TCP autotuning for
828
0
  // the socket buffers, and this works well (managing enough
829
0
  // buffers for BDP while conserving memory) for HTTP even when
830
0
  // it creates really deep queues. However this 'buffer bloat' is
831
0
  // a problem for http/2 because it ruins the low latency properties
832
0
  // necessary for PING and cancel to work meaningfully.
833
0
  //
834
0
  // If this stream represents a large upload, disable autotuning for
835
0
  // the session and cap the send buffers by default at 128KB.
836
0
  // (10Mbit/sec @ 100ms)
837
0
  //
838
0
  uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
839
0
  if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
840
0
    mSetTCPSocketBuffer = 1;
841
0
    mSocketTransport->SetSendBufferSize(bufferSize);
842
0
  }
843
0
844
0
  if (mUpstreamState != SENDING_FIN_STREAM)
845
0
    mTransaction->OnTransportStatus(mSocketTransport,
846
0
                                    NS_NET_STATUS_SENDING_TO,
847
0
                                    mTotalSent);
848
0
849
0
  if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
850
0
    mSentWaitingFor = 1;
851
0
    mTransaction->OnTransportStatus(mSocketTransport,
852
0
                                    NS_NET_STATUS_WAITING_FOR,
853
0
                                    0);
854
0
  }
855
0
}
856
857
nsresult
858
Http2Stream::TransmitFrame(const char *buf,
859
                           uint32_t *countUsed,
860
                           bool forceCommitment)
861
0
{
862
0
  // If TransmitFrame returns SUCCESS than all the data is sent (or at least
863
0
  // buffered at the session level), if it returns WOULD_BLOCK then none of
864
0
  // the data is sent.
865
0
866
0
  // You can call this function with no data and no out parameter in order to
867
0
  // flush internal buffers that were previously blocked on writing. You can
868
0
  // of course feed new data to it as well.
869
0
870
0
  LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d",
871
0
        this, mTxInlineFrameUsed, mTxStreamFrameSize));
872
0
  if (countUsed)
873
0
    *countUsed = 0;
874
0
875
0
  if (!mTxInlineFrameUsed) {
876
0
    MOZ_ASSERT(!buf);
877
0
    return NS_OK;
878
0
  }
879
0
880
0
  MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
881
0
  MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
882
0
  MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
883
0
             "TransmitFrame arguments inconsistent");
884
0
885
0
  uint32_t transmittedCount;
886
0
  nsresult rv;
887
0
888
0
  // In the (relatively common) event that we have a small amount of data
889
0
  // split between the inlineframe and the streamframe, then move the stream
890
0
  // data into the inlineframe via copy in order to coalesce into one write.
891
0
  // Given the interaction with ssl this is worth the small copy cost.
892
0
  if (mTxStreamFrameSize && mTxInlineFrameUsed &&
893
0
      mTxStreamFrameSize < Http2Session::kDefaultBufferSize &&
894
0
      mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
895
0
    LOG3(("Coalesce Transmit"));
896
0
    memcpy (&mTxInlineFrame[mTxInlineFrameUsed], buf, mTxStreamFrameSize);
897
0
    if (countUsed)
898
0
      *countUsed += mTxStreamFrameSize;
899
0
    mTxInlineFrameUsed += mTxStreamFrameSize;
900
0
    mTxStreamFrameSize = 0;
901
0
  }
902
0
903
0
  rv =
904
0
    mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
905
0
                                        forceCommitment);
906
0
907
0
  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
908
0
    MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
909
0
    mSession->TransactionHasDataToWrite(this);
910
0
  }
911
0
  if (NS_FAILED(rv))     // this will include WOULD_BLOCK
912
0
    return rv;
913
0
914
0
  // This function calls mSegmentReader->OnReadSegment to report the actual http/2
915
0
  // bytes through to the session object and then the HttpConnection which calls
916
0
  // the socket write function. It will accept all of the inline and stream
917
0
  // data because of the above 'commitment' even if it has to buffer
918
0
919
0
  rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
920
0
                              mTxInlineFrameUsed,
921
0
                              &transmittedCount);
922
0
  LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
923
0
        "stream=%p result %" PRIx32 " len=%d",
924
0
        mSession, this, static_cast<uint32_t>(rv), transmittedCount));
925
0
926
0
  MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
927
0
             "inconsistent inline commitment result");
928
0
929
0
  if (NS_FAILED(rv))
930
0
    return rv;
931
0
932
0
  MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
933
0
             "inconsistent inline commitment count");
934
0
935
0
  Http2Session::LogIO(mSession, this, "Writing from Inline Buffer",
936
0
                       reinterpret_cast<char*>(mTxInlineFrame.get()),
937
0
                       transmittedCount);
938
0
939
0
  if (mTxStreamFrameSize) {
940
0
    if (!buf) {
941
0
      // this cannot happen
942
0
      MOZ_ASSERT(false, "Stream transmit with null buf argument to "
943
0
                 "TransmitFrame()");
944
0
      LOG3(("Stream transmit with null buf argument to TransmitFrame()\n"));
945
0
      return NS_ERROR_UNEXPECTED;
946
0
    }
947
0
948
0
    // If there is already data buffered, just add to that to form
949
0
    // a single TLS Application Data Record - otherwise skip the memcpy
950
0
    if (mSession->AmountOfOutputBuffered()) {
951
0
      rv = mSession->BufferOutput(buf, mTxStreamFrameSize,
952
0
                                  &transmittedCount);
953
0
    } else {
954
0
      rv = mSession->OnReadSegment(buf, mTxStreamFrameSize,
955
0
                                   &transmittedCount);
956
0
    }
957
0
958
0
    LOG3(("Http2Stream::TransmitFrame for regular session=%p "
959
0
          "stream=%p result %" PRIx32 " len=%d",
960
0
          mSession, this, static_cast<uint32_t>(rv), transmittedCount));
961
0
962
0
    MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
963
0
               "inconsistent stream commitment result");
964
0
965
0
    if (NS_FAILED(rv))
966
0
      return rv;
967
0
968
0
    MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
969
0
               "inconsistent stream commitment count");
970
0
971
0
    Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
972
0
                         buf, transmittedCount);
973
0
974
0
    *countUsed += mTxStreamFrameSize;
975
0
  }
976
0
977
0
  if (!mAttempting0RTT) {
978
0
    mSession->FlushOutputQueue();
979
0
  }
980
0
981
0
  // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
982
0
  UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
983
0
984
0
  mTxInlineFrameUsed = 0;
985
0
  mTxStreamFrameSize = 0;
986
0
987
0
  return NS_OK;
988
0
}
989
990
void
991
Http2Stream::ChangeState(enum upstreamStateType newState)
992
0
{
993
0
  LOG3(("Http2Stream::ChangeState() %p from %X to %X",
994
0
        this, mUpstreamState, newState));
995
0
  mUpstreamState = newState;
996
0
}
997
998
void
999
Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
1000
0
{
1001
0
  LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d",
1002
0
        this, dataLength, lastFrame));
1003
0
1004
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1005
0
  MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
1006
0
  MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
1007
0
1008
0
  uint8_t frameFlags = 0;
1009
0
  if (lastFrame) {
1010
0
    frameFlags |= Http2Session::kFlag_END_STREAM;
1011
0
    if (dataLength)
1012
0
      SetSentFin(true);
1013
0
  }
1014
0
1015
0
  mSession->CreateFrameHeader(mTxInlineFrame.get(),
1016
0
                              dataLength,
1017
0
                              Http2Session::FRAME_TYPE_DATA,
1018
0
                              frameFlags, mStreamID);
1019
0
1020
0
  mTxInlineFrameUsed = Http2Session::kFrameHeaderBytes;
1021
0
  mTxStreamFrameSize = dataLength;
1022
0
}
1023
1024
// ConvertResponseHeaders is used to convert the response headers
1025
// into HTTP/1 format and report some telemetry
1026
nsresult
1027
Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
1028
                                    nsACString &aHeadersIn,
1029
                                    nsACString &aHeadersOut,
1030
                                    int32_t &httpResponseCode)
1031
0
{
1032
0
  nsresult rv =
1033
0
    decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
1034
0
                                    aHeadersIn.Length(),
1035
0
                                    aHeadersOut, false);
1036
0
  if (NS_FAILED(rv)) {
1037
0
    LOG3(("Http2Stream::ConvertResponseHeaders %p decode Error\n", this));
1038
0
    return rv;
1039
0
  }
1040
0
1041
0
  nsAutoCString statusString;
1042
0
  decompressor->GetStatus(statusString);
1043
0
  if (statusString.IsEmpty()) {
1044
0
    LOG3(("Http2Stream::ConvertResponseHeaders %p Error - no status\n", this));
1045
0
    return NS_ERROR_ILLEGAL_VALUE;
1046
0
  }
1047
0
1048
0
  nsresult errcode;
1049
0
  httpResponseCode = statusString.ToInteger(&errcode);
1050
0
1051
0
  // Ensure the :status is just an HTTP status code
1052
0
  // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
1053
0
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1352146
1054
0
  nsAutoCString parsedStatusString;
1055
0
  parsedStatusString.AppendInt(httpResponseCode);
1056
0
  if (!parsedStatusString.Equals(statusString)) {
1057
0
    LOG3(("Http2Stream::ConvertResposeHeaders %p status %s is not just a code",
1058
0
          this, statusString.BeginReading()));
1059
0
    // Results in stream reset with PROTOCOL_ERROR
1060
0
    return NS_ERROR_ILLEGAL_VALUE;
1061
0
  }
1062
0
1063
0
  LOG3(("Http2Stream::ConvertResponseHeaders %p response code %d\n", this, httpResponseCode));
1064
0
  if (mIsTunnel) {
1065
0
    LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode));
1066
0
    if ((httpResponseCode / 100) != 2) {
1067
0
      MapStreamToPlainText();
1068
0
    }
1069
0
    MapStreamToHttpConnection();
1070
0
    ClearTransactionsBlockedOnTunnel();
1071
0
  }
1072
0
1073
0
  if (httpResponseCode == 101) {
1074
0
    // 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream
1075
0
    LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this));
1076
0
    return NS_ERROR_ILLEGAL_VALUE;
1077
0
  }
1078
0
1079
0
  if (httpResponseCode == 421) {
1080
0
    // Origin Frame requires 421 to remove this origin from the origin set
1081
0
    mSession->Received421(mTransaction->ConnectionInfo());
1082
0
  }
1083
0
1084
0
  if (aHeadersIn.Length() && aHeadersOut.Length()) {
1085
0
    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
1086
0
    uint32_t ratio =
1087
0
      aHeadersIn.Length() * 100 / aHeadersOut.Length();
1088
0
    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
1089
0
  }
1090
0
1091
0
  // The decoding went ok. Now we can customize and clean up.
1092
0
1093
0
  aHeadersIn.Truncate();
1094
0
  aHeadersOut.AppendLiteral("X-Firefox-Spdy: h2");
1095
0
  aHeadersOut.AppendLiteral("\r\n\r\n");
1096
0
  LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
1097
0
  if (mIsTunnel && !mPlainTextTunnel) {
1098
0
    aHeadersOut.Truncate();
1099
0
    LOG(("Http2Stream::ConvertHeaders %p 0x%X headers removed for tunnel\n",
1100
0
         this, mStreamID));
1101
0
  }
1102
0
  return NS_OK;
1103
0
}
1104
1105
// ConvertPushHeaders is used to convert the pushed request headers
1106
// into HTTP/1 format and report some telemetry
1107
nsresult
1108
Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
1109
                                nsACString &aHeadersIn,
1110
                                nsACString &aHeadersOut)
1111
0
{
1112
0
  nsresult rv =
1113
0
    decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
1114
0
                                    aHeadersIn.Length(),
1115
0
                                    aHeadersOut, true);
1116
0
  if (NS_FAILED(rv)) {
1117
0
    LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this));
1118
0
    return rv;
1119
0
  }
1120
0
1121
0
  nsCString method;
1122
0
  decompressor->GetHost(mHeaderHost);
1123
0
  decompressor->GetScheme(mHeaderScheme);
1124
0
  decompressor->GetPath(mHeaderPath);
1125
0
1126
0
  if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) {
1127
0
    LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required "
1128
0
          "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(),
1129
0
          mHeaderPath.get()));
1130
0
    return NS_ERROR_ILLEGAL_VALUE;
1131
0
  }
1132
0
1133
0
  decompressor->GetMethod(method);
1134
0
  if (!method.EqualsLiteral("GET")) {
1135
0
    LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n",
1136
0
          this, method.get()));
1137
0
    return NS_ERROR_NOT_IMPLEMENTED;
1138
0
  }
1139
0
1140
0
  aHeadersIn.Truncate();
1141
0
  LOG (("id 0x%X decoded push headers %s %s %s are:\n%s", mStreamID,
1142
0
        mHeaderScheme.get(), mHeaderHost.get(), mHeaderPath.get(),
1143
0
        aHeadersOut.BeginReading()));
1144
0
  return NS_OK;
1145
0
}
1146
1147
nsresult
1148
Http2Stream::ConvertResponseTrailers(Http2Decompressor *decompressor,
1149
                                     nsACString &aTrailersIn)
1150
0
{
1151
0
  LOG3(("Http2Stream::ConvertResponseTrailers %p", this));
1152
0
  nsAutoCString flatTrailers;
1153
0
1154
0
  nsresult rv =
1155
0
    decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aTrailersIn.BeginReading()),
1156
0
                                    aTrailersIn.Length(),
1157
0
                                    flatTrailers, false);
1158
0
  if (NS_FAILED(rv)) {
1159
0
    LOG3(("Http2Stream::ConvertResponseTrailers %p decode Error", this));
1160
0
    return rv;
1161
0
  }
1162
0
1163
0
  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1164
0
  if (trans) {
1165
0
    trans->SetHttpTrailers(flatTrailers);
1166
0
  } else {
1167
0
    LOG3(("Http2Stream::ConvertResponseTrailers %p no trans", this));
1168
0
  }
1169
0
1170
0
  return NS_OK;
1171
0
}
1172
1173
void
1174
Http2Stream::Close(nsresult reason)
1175
0
{
1176
0
  // In case we are connected to a push, make sure the push knows we are closed,
1177
0
  // so it doesn't try to give us any more DATA that comes on it after our close.
1178
0
  ClearPushSource();
1179
0
1180
0
  mTransaction->Close(reason);
1181
0
}
1182
1183
void
1184
Http2Stream::SetResponseIsComplete()
1185
0
{
1186
0
  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1187
0
  if (trans) {
1188
0
    trans->SetResponseIsComplete();
1189
0
  }
1190
0
}
1191
1192
void
1193
Http2Stream::SetAllHeadersReceived()
1194
0
{
1195
0
  if (mAllHeadersReceived) {
1196
0
    return;
1197
0
  }
1198
0
1199
0
  if (mState == RESERVED_BY_REMOTE) {
1200
0
    // pushed streams needs to wait until headers have
1201
0
    // arrived to open up their window
1202
0
    LOG3(("Http2Stream::SetAllHeadersReceived %p state OPEN from reserved\n", this));
1203
0
    mState = OPEN;
1204
0
    AdjustInitialWindow();
1205
0
  }
1206
0
1207
0
  mAllHeadersReceived = 1;
1208
0
}
1209
1210
bool
1211
Http2Stream::AllowFlowControlledWrite()
1212
0
{
1213
0
  return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
1214
0
}
1215
1216
void
1217
Http2Stream::UpdateServerReceiveWindow(int32_t delta)
1218
0
{
1219
0
  mServerReceiveWindow += delta;
1220
0
1221
0
  if (mBlockedOnRwin && AllowFlowControlledWrite()) {
1222
0
    LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
1223
0
          "Open stream window\n", this, mStreamID));
1224
0
    mSession->TransactionHasDataToWrite(this);  }
1225
0
}
1226
1227
void
1228
Http2Stream::SetPriority(uint32_t newPriority)
1229
0
{
1230
0
  int32_t httpPriority = static_cast<int32_t>(newPriority);
1231
0
  if (httpPriority > kWorstPriority) {
1232
0
    httpPriority = kWorstPriority;
1233
0
  } else if (httpPriority < kBestPriority) {
1234
0
    httpPriority = kBestPriority;
1235
0
  }
1236
0
  mPriority = static_cast<uint32_t>(httpPriority);
1237
0
  mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
1238
0
    (httpPriority - kNormalPriority);
1239
0
1240
0
  mPriorityDependency = 0; // maybe adjusted later
1241
0
}
1242
1243
void
1244
Http2Stream::SetPriorityDependency(uint32_t newDependency, uint8_t newWeight,
1245
                                   bool exclusive)
1246
0
{
1247
0
  // undefined what it means when the server sends a priority frame. ignore it.
1248
0
  LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X "
1249
0
        "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight,
1250
0
        exclusive));
1251
0
}
1252
1253
static uint32_t
1254
GetPriorityDependencyFromTransaction(nsHttpTransaction *trans)
1255
0
{
1256
0
  MOZ_ASSERT(trans);
1257
0
1258
0
  uint32_t classFlags = trans->ClassOfService();
1259
0
1260
0
  if (classFlags & nsIClassOfService::UrgentStart) {
1261
0
    return Http2Session::kUrgentStartGroupID;
1262
0
  }
1263
0
1264
0
  if (classFlags & nsIClassOfService::Leader) {
1265
0
    return Http2Session::kLeaderGroupID;
1266
0
  }
1267
0
1268
0
  if (classFlags & nsIClassOfService::Follower) {
1269
0
    return Http2Session::kFollowerGroupID;
1270
0
  }
1271
0
1272
0
  if (classFlags & nsIClassOfService::Speculative) {
1273
0
    return Http2Session::kSpeculativeGroupID;
1274
0
  }
1275
0
1276
0
  if (classFlags & nsIClassOfService::Background) {
1277
0
    return Http2Session::kBackgroundGroupID;
1278
0
  }
1279
0
1280
0
  if (classFlags & nsIClassOfService::Unblocked) {
1281
0
    return Http2Session::kOtherGroupID;
1282
0
  }
1283
0
1284
0
  return Http2Session::kFollowerGroupID; // unmarked followers
1285
0
}
1286
1287
void
1288
Http2Stream::UpdatePriorityDependency()
1289
0
{
1290
0
  if (!mSession->UseH2Deps()) {
1291
0
    return;
1292
0
  }
1293
0
1294
0
  nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1295
0
  if (!trans) {
1296
0
    return;
1297
0
  }
1298
0
1299
0
  // we create 6 fake dependency streams per session,
1300
0
  // these streams are never opened with HEADERS. our first opened stream is 0xd
1301
0
  // 3 depends 0, weight 200, leader class (kLeaderGroupID)
1302
0
  // 5 depends 0, weight 100, other (kOtherGroupID)
1303
0
  // 7 depends 0, weight 0, background (kBackgroundGroupID)
1304
0
  // 9 depends 7, weight 0, speculative (kSpeculativeGroupID)
1305
0
  // b depends 3, weight 0, follower class (kFollowerGroupID)
1306
0
  // d depends 0, weight 240, urgent-start class (kUrgentStartGroupID)
1307
0
  //
1308
0
  // streams for leaders (html, js, css) depend on 3
1309
0
  // streams for folowers (images) depend on b
1310
0
  // default streams (xhr, async js) depend on 5
1311
0
  // explicit bg streams (beacon, etc..) depend on 7
1312
0
  // spculative bg streams depend on 9
1313
0
  // urgent-start streams depend on d
1314
0
1315
0
  mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
1316
0
1317
0
  if (gHttpHandler->ActiveTabPriority() &&
1318
0
      mTransactionTabId != mCurrentForegroundTabOuterContentWindowId &&
1319
0
      mPriorityDependency != Http2Session::kUrgentStartGroupID) {
1320
0
    LOG3(("Http2Stream::UpdatePriorityDependency %p "
1321
0
          " depends on background group for trans %p\n",
1322
0
          this, trans));
1323
0
    mPriorityDependency = Http2Session::kBackgroundGroupID;
1324
0
1325
0
    nsHttp::NotifyActiveTabLoadOptimization();
1326
0
  }
1327
0
1328
0
  LOG3(("Http2Stream::UpdatePriorityDependency %p "
1329
0
        "depends on stream 0x%X\n",
1330
0
        this, mPriorityDependency));
1331
0
}
1332
1333
void
1334
Http2Stream::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
1335
0
{
1336
0
  MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
1337
0
1338
0
  LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged "
1339
0
        "%p windowId=%" PRIx64 "\n",
1340
0
        this, windowId));
1341
0
1342
0
  mCurrentForegroundTabOuterContentWindowId = windowId;
1343
0
1344
0
  if (!mSession->UseH2Deps()) {
1345
0
    return;
1346
0
  }
1347
0
1348
0
  // Urgent start takes an absolute precedence, so don't
1349
0
  // change mPriorityDependency here.
1350
0
  if (mPriorityDependency == Http2Session::kUrgentStartGroupID) {
1351
0
    return;
1352
0
  }
1353
0
1354
0
  if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) {
1355
0
    mPriorityDependency = Http2Session::kBackgroundGroupID;
1356
0
    LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
1357
0
          "move into background group.\n", this));
1358
0
1359
0
    nsHttp::NotifyActiveTabLoadOptimization();
1360
0
  } else {
1361
0
    nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1362
0
    if (!trans) {
1363
0
      return;
1364
0
    }
1365
0
1366
0
    mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
1367
0
    LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
1368
0
          "depends on stream 0x%X\n", this, mPriorityDependency));
1369
0
  }
1370
0
1371
0
  if (mStreamID) {
1372
0
    mSession->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
1373
0
  }
1374
0
}
1375
1376
void
1377
Http2Stream::SetRecvdFin(bool aStatus)
1378
0
{
1379
0
  mRecvdFin = aStatus ? 1 : 0;
1380
0
  if (!aStatus)
1381
0
    return;
1382
0
1383
0
  if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
1384
0
    mState = CLOSED_BY_REMOTE;
1385
0
  } else if (mState == CLOSED_BY_LOCAL) {
1386
0
    mState = CLOSED;
1387
0
  }
1388
0
}
1389
1390
void
1391
Http2Stream::SetSentFin(bool aStatus)
1392
0
{
1393
0
  mSentFin = aStatus ? 1 : 0;
1394
0
  if (!aStatus)
1395
0
    return;
1396
0
1397
0
  if (mState == OPEN || mState == RESERVED_BY_REMOTE) {
1398
0
    mState = CLOSED_BY_LOCAL;
1399
0
  } else if (mState == CLOSED_BY_REMOTE) {
1400
0
    mState = CLOSED;
1401
0
  }
1402
0
}
1403
1404
void
1405
Http2Stream::SetRecvdReset(bool aStatus)
1406
0
{
1407
0
  mRecvdReset = aStatus ? 1 : 0;
1408
0
  if (!aStatus)
1409
0
    return;
1410
0
  mState = CLOSED;
1411
0
}
1412
1413
void
1414
Http2Stream::SetSentReset(bool aStatus)
1415
0
{
1416
0
  mSentReset = aStatus ? 1 : 0;
1417
0
  if (!aStatus)
1418
0
    return;
1419
0
  mState = CLOSED;
1420
0
}
1421
1422
//-----------------------------------------------------------------------------
1423
// nsAHttpSegmentReader
1424
//-----------------------------------------------------------------------------
1425
1426
nsresult
1427
Http2Stream::OnReadSegment(const char *buf,
1428
                           uint32_t count,
1429
                           uint32_t *countRead)
1430
0
{
1431
0
  LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x",
1432
0
        this, count, mUpstreamState));
1433
0
1434
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1435
0
  MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
1436
0
1437
0
  nsresult rv = NS_ERROR_UNEXPECTED;
1438
0
  uint32_t dataLength;
1439
0
1440
0
  switch (mUpstreamState) {
1441
0
  case GENERATING_HEADERS:
1442
0
    // The buffer is the HTTP request stream, including at least part of the
1443
0
    // HTTP request header. This state's job is to build a HEADERS frame
1444
0
    // from the header information. count is the number of http bytes available
1445
0
    // (which may include more than the header), and in countRead we return
1446
0
    // the number of those bytes that we consume (i.e. the portion that are
1447
0
    // header bytes)
1448
0
1449
0
    if (!mRequestHeadersDone) {
1450
0
      if (NS_FAILED(rv = ParseHttpRequestHeaders(buf, count, countRead))) {
1451
0
        return rv;
1452
0
      }
1453
0
    }
1454
0
1455
0
    if (mRequestHeadersDone && !mOpenGenerated) {
1456
0
      if (!mSession->TryToActivate(this)) {
1457
0
        LOG3(("Http2Stream::OnReadSegment %p cannot activate now. queued.\n", this));
1458
0
        return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
1459
0
      }
1460
0
      if (NS_FAILED(rv = GenerateOpen())) {
1461
0
        return rv;
1462
0
      }
1463
0
   }
1464
0
1465
0
    LOG3(("ParseHttpRequestHeaders %p used %d of %d. "
1466
0
          "requestheadersdone = %d mOpenGenerated = %d\n",
1467
0
          this, *countRead, count, mRequestHeadersDone, mOpenGenerated));
1468
0
    if (mOpenGenerated) {
1469
0
      SetHTTPState(OPEN);
1470
0
      AdjustInitialWindow();
1471
0
      // This version of TransmitFrame cannot block
1472
0
      rv = TransmitFrame(nullptr, nullptr, true);
1473
0
      ChangeState(GENERATING_BODY);
1474
0
      break;
1475
0
    }
1476
0
    MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
1477
0
    break;
1478
0
1479
0
  case GENERATING_BODY:
1480
0
    // if there is session flow control and either the stream window is active and
1481
0
    // exhaused or the session window is exhausted then suspend
1482
0
    if (!AllowFlowControlledWrite()) {
1483
0
      *countRead = 0;
1484
0
      LOG3(("Http2Stream this=%p, id 0x%X request body suspended because "
1485
0
            "remote window is stream=%" PRId64 " session=%" PRId64 ".\n", this, mStreamID,
1486
0
            mServerReceiveWindow, mSession->ServerSessionWindow()));
1487
0
      mBlockedOnRwin = true;
1488
0
      return NS_BASE_STREAM_WOULD_BLOCK;
1489
0
    }
1490
0
    mBlockedOnRwin = false;
1491
0
1492
0
    // The chunk is the smallest of: availableData, configured chunkSize,
1493
0
    // stream window, session window, or 14 bit framing limit.
1494
0
    // Its amazing we send anything at all.
1495
0
    dataLength = std::min(count, mChunkSize);
1496
0
1497
0
    if (dataLength > Http2Session::kMaxFrameData)
1498
0
      dataLength = Http2Session::kMaxFrameData;
1499
0
1500
0
    if (dataLength > mSession->ServerSessionWindow())
1501
0
      dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
1502
0
1503
0
    if (dataLength > mServerReceiveWindow)
1504
0
      dataLength = static_cast<uint32_t>(mServerReceiveWindow);
1505
0
1506
0
    LOG3(("Http2Stream this=%p id 0x%X send calculation "
1507
0
          "avail=%d chunksize=%d stream window=%" PRId64 " session window=%" PRId64 " "
1508
0
          "max frame=%d USING=%u\n", this, mStreamID,
1509
0
          count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(),
1510
0
          Http2Session::kMaxFrameData, dataLength));
1511
0
1512
0
    mSession->DecrementServerSessionWindow(dataLength);
1513
0
    mServerReceiveWindow -= dataLength;
1514
0
1515
0
    LOG3(("Http2Stream %p id 0x%x request len remaining %" PRId64 ", "
1516
0
          "count avail %u, chunk used %u",
1517
0
          this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
1518
0
    if (!dataLength && mRequestBodyLenRemaining) {
1519
0
      return NS_BASE_STREAM_WOULD_BLOCK;
1520
0
    }
1521
0
    if (dataLength > mRequestBodyLenRemaining) {
1522
0
      return NS_ERROR_UNEXPECTED;
1523
0
    }
1524
0
    mRequestBodyLenRemaining -= dataLength;
1525
0
    GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
1526
0
    ChangeState(SENDING_BODY);
1527
0
    MOZ_FALLTHROUGH;
1528
0
1529
0
  case SENDING_BODY:
1530
0
    MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
1531
0
    rv = TransmitFrame(buf, countRead, false);
1532
0
    MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
1533
0
               "Transmit Frame should be all or nothing");
1534
0
1535
0
    LOG3(("TransmitFrame() rv=%" PRIx32 " returning %d data bytes. "
1536
0
          "Header is %d Body is %d.",
1537
0
          static_cast<uint32_t>(rv), *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
1538
0
1539
0
    // normalize a partial write with a WOULD_BLOCK into just a partial write
1540
0
    // as some code will take WOULD_BLOCK to mean an error with nothing
1541
0
    // written (e.g. nsHttpTransaction::ReadRequestSegment()
1542
0
    if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
1543
0
      rv = NS_OK;
1544
0
1545
0
    // If that frame was all sent, look for another one
1546
0
    if (!mTxInlineFrameUsed)
1547
0
      ChangeState(GENERATING_BODY);
1548
0
    break;
1549
0
1550
0
  case SENDING_FIN_STREAM:
1551
0
    MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
1552
0
    break;
1553
0
1554
0
  case UPSTREAM_COMPLETE:
1555
0
    MOZ_ASSERT(mPushSource);
1556
0
    rv = TransmitFrame(nullptr, nullptr, true);
1557
0
    break;
1558
0
1559
0
  default:
1560
0
    MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
1561
0
    break;
1562
0
  }
1563
0
1564
0
  return rv;
1565
0
}
1566
1567
//-----------------------------------------------------------------------------
1568
// nsAHttpSegmentWriter
1569
//-----------------------------------------------------------------------------
1570
1571
nsresult
1572
Http2Stream::OnWriteSegment(char *buf,
1573
                            uint32_t count,
1574
                            uint32_t *countWritten)
1575
0
{
1576
0
  LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n",
1577
0
        this, count, mUpstreamState, mStreamID));
1578
0
1579
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1580
0
  MOZ_ASSERT(mSegmentWriter);
1581
0
1582
0
  if (mPushSource) {
1583
0
    nsresult rv;
1584
0
    rv = mPushSource->GetBufferedData(buf, count, countWritten);
1585
0
    if (NS_FAILED(rv))
1586
0
      return rv;
1587
0
1588
0
    mSession->ConnectPushedStream(this);
1589
0
    return NS_OK;
1590
0
  }
1591
0
1592
0
  // sometimes we have read data from the network and stored it in a pipe
1593
0
  // so that other streams can proceed when the gecko caller is not processing
1594
0
  // data events fast enough and flow control hasn't caught up yet. This
1595
0
  // gets the stored data out of that pipe
1596
0
  if (!mBypassInputBuffer && mSimpleBuffer.Available()) {
1597
0
    *countWritten = mSimpleBuffer.Read(buf, count);
1598
0
    MOZ_ASSERT(*countWritten);
1599
0
    LOG3(("Http2Stream::OnWriteSegment read from flow control buffer %p %x %d\n",
1600
0
          this, mStreamID, *countWritten));
1601
0
    return NS_OK;
1602
0
  }
1603
0
1604
0
  // read from the network
1605
0
  return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
1606
0
}
1607
1608
/// connect tunnels
1609
1610
void
1611
Http2Stream::ClearTransactionsBlockedOnTunnel()
1612
0
{
1613
0
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1614
0
1615
0
  if (!mIsTunnel) {
1616
0
    return;
1617
0
  }
1618
0
  nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
1619
0
  if (NS_FAILED(rv)) {
1620
0
    LOG3(("Http2Stream::ClearTransactionsBlockedOnTunnel %p\n"
1621
0
          "  ProcessPendingQ failed: %08x\n",
1622
0
          this, static_cast<uint32_t>(rv)));
1623
0
  }
1624
0
}
1625
1626
void
1627
Http2Stream::MapStreamToPlainText()
1628
0
{
1629
0
  RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
1630
0
  MOZ_ASSERT(qiTrans);
1631
0
  mPlainTextTunnel = true;
1632
0
  qiTrans->ForcePlainText();
1633
0
}
1634
1635
void
1636
Http2Stream::MapStreamToHttpConnection()
1637
0
{
1638
0
  RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
1639
0
  MOZ_ASSERT(qiTrans);
1640
0
  qiTrans->MapStreamToHttpConnection(mSocketTransport,
1641
0
                                     mTransaction->ConnectionInfo());
1642
0
}
1643
1644
// -----------------------------------------------------------------------------
1645
// mirror nsAHttpTransaction
1646
// -----------------------------------------------------------------------------
1647
1648
bool
1649
Http2Stream::Do0RTT()
1650
0
{
1651
0
  MOZ_ASSERT(mTransaction);
1652
0
  mAttempting0RTT = mTransaction->Do0RTT();
1653
0
  return mAttempting0RTT;
1654
0
}
1655
1656
nsresult
1657
Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged)
1658
0
{
1659
0
  MOZ_ASSERT(mTransaction);
1660
0
  mAttempting0RTT = false;
1661
0
  // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for
1662
0
  // both arguments because as long as the alpn token stayed the same, we can
1663
0
  // just reuse what we have in our buffer to send instead of having to have
1664
0
  // the transaction rewind and read it all over again. We only need to rewind
1665
0
  // the transaction if we're switching to a new protocol, because our buffer
1666
0
  // won't get used in that case.
1667
0
  // ..
1668
0
  // however, we send in the aRestart value to indicate that early data failed
1669
0
  // for devtools purposes
1670
0
  nsresult rv = mTransaction->Finish0RTT(aAlpnChanged, aAlpnChanged);
1671
0
  if (aRestart) {
1672
0
    nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
1673
0
    if (trans) {
1674
0
      trans->Refused0RTT();
1675
0
    }
1676
0
  }
1677
0
  return rv;
1678
0
}
1679
1680
nsresult
1681
Http2Stream::GetOriginAttributes(mozilla::OriginAttributes *oa)
1682
0
{
1683
0
  if (!mSocketTransport) {
1684
0
    return NS_ERROR_UNEXPECTED;
1685
0
  }
1686
0
1687
0
  return mSocketTransport->GetOriginAttributes(oa);
1688
0
}
1689
1690
} // namespace net
1691
} // namespace mozilla