2013-10-10 00:21:49 +00:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
// HttpLog.h should generally be included first
|
|
|
|
#include "HttpLog.h"
|
|
|
|
|
2014-02-07 21:15:24 +00:00
|
|
|
// Log on level :5, instead of default :4.
|
|
|
|
#undef LOG
|
|
|
|
#define LOG(args) LOG5(args)
|
|
|
|
#undef LOG_ENABLED
|
|
|
|
#define LOG_ENABLED() LOG5_ENABLED()
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include "Http2Session.h"
|
|
|
|
#include "Http2Stream.h"
|
|
|
|
#include "Http2Push.h"
|
|
|
|
|
2016-05-22 20:31:11 +00:00
|
|
|
#include "mozilla/EndianUtils.h"
|
2013-10-10 00:21:49 +00:00
|
|
|
#include "mozilla/Telemetry.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
#include "nsHttp.h"
|
|
|
|
#include "nsHttpHandler.h"
|
|
|
|
#include "nsHttpConnection.h"
|
2016-04-21 14:59:17 +00:00
|
|
|
#include "nsIRequestContext.h"
|
2013-10-10 00:21:49 +00:00
|
|
|
#include "nsISSLSocketControl.h"
|
2014-06-13 13:29:00 +00:00
|
|
|
#include "nsISupportsPriority.h"
|
2014-11-03 15:19:00 +00:00
|
|
|
#include "nsStandardURL.h"
|
|
|
|
#include "nsURLHelper.h"
|
2013-10-10 00:21:49 +00:00
|
|
|
#include "prnetdb.h"
|
2014-03-26 17:58:09 +00:00
|
|
|
#include "sslt.h"
|
2016-08-15 06:43:21 +00:00
|
|
|
#include "mozilla/Sprintf.h"
|
2015-11-06 17:00:37 +00:00
|
|
|
#include "nsSocketTransportService2.h"
|
2016-01-09 01:20:50 +00:00
|
|
|
#include "nsNetUtil.h"
|
2017-06-28 17:34:55 +00:00
|
|
|
#include "nsICacheEntry.h"
|
|
|
|
#include "nsICacheStorageService.h"
|
|
|
|
#include "nsICacheStorage.h"
|
|
|
|
#include "CacheControlParser.h"
|
|
|
|
#include "LoadContextInfo.h"
|
2017-12-21 17:32:28 +00:00
|
|
|
#include "TCPFastOpenLayer.h"
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace net {
|
|
|
|
|
2017-02-17 20:35:37 +00:00
|
|
|
// Http2Session has multiple inheritance of things that implement nsISupports
|
2013-10-10 00:21:49 +00:00
|
|
|
NS_IMPL_ADDREF(Http2Session)
|
|
|
|
NS_IMPL_RELEASE(Http2Session)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(Http2Session)
|
|
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
|
|
|
|
NS_INTERFACE_MAP_END
|
|
|
|
|
|
|
|
// "magic" refers to the string that preceeds HTTP/2 on the wire
|
|
|
|
// to help find any intermediaries speaking an older version of HTTP
|
|
|
|
const uint8_t Http2Session::kMagicHello[] = {
|
|
|
|
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
|
|
|
|
0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
|
|
|
|
0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
|
|
|
|
};
|
|
|
|
|
2018-06-01 18:32:05 +00:00
|
|
|
Http2Session::Http2Session(nsISocketTransport *aSocketTransport, enum SpdyVersion version, bool attemptingEarlyData)
|
2014-04-17 23:55:04 +00:00
|
|
|
: mSocketTransport(aSocketTransport)
|
|
|
|
, mSegmentReader(nullptr)
|
|
|
|
, mSegmentWriter(nullptr)
|
|
|
|
, mNextStreamID(3) // 1 is reserved for Updgrade handshakes
|
2016-08-03 18:36:50 +00:00
|
|
|
, mLastPushedID(0)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mConcurrentHighWater(0)
|
|
|
|
, mDownstreamState(BUFFERING_OPENING_SETTINGS)
|
|
|
|
, mInputFrameBufferSize(kDefaultBufferSize)
|
|
|
|
, mInputFrameBufferUsed(0)
|
2015-03-20 04:42:00 +00:00
|
|
|
, mInputFrameDataSize(0)
|
|
|
|
, mInputFrameDataRead(0)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mInputFrameFinal(false)
|
2015-03-20 04:42:00 +00:00
|
|
|
, mInputFrameType(0)
|
|
|
|
, mInputFrameFlags(0)
|
|
|
|
, mInputFrameID(0)
|
|
|
|
, mPaddingLength(0)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mInputFrameDataStream(nullptr)
|
|
|
|
, mNeedsCleanup(nullptr)
|
|
|
|
, mDownstreamRstReason(NO_HTTP_ERROR)
|
|
|
|
, mExpectedHeaderID(0)
|
|
|
|
, mExpectedPushPromiseID(0)
|
|
|
|
, mContinuedPromiseStream(0)
|
2015-03-20 04:42:00 +00:00
|
|
|
, mFlatHTTPResponseHeadersOut(0)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mShouldGoAway(false)
|
|
|
|
, mClosed(false)
|
|
|
|
, mCleanShutdown(false)
|
2017-04-14 21:24:53 +00:00
|
|
|
, mReceivedSettings(false)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mTLSProfileConfirmed(false)
|
|
|
|
, mGoAwayReason(NO_HTTP_ERROR)
|
2015-09-17 19:26:59 +00:00
|
|
|
, mClientGoAwayReason(UNASSIGNED)
|
2015-09-15 01:55:06 +00:00
|
|
|
, mPeerGoAwayReason(UNASSIGNED)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mGoAwayID(0)
|
|
|
|
, mOutgoingGoAwayID(0)
|
|
|
|
, mConcurrent(0)
|
|
|
|
, mServerPushedResources(0)
|
|
|
|
, mServerInitialStreamWindow(kDefaultRwin)
|
|
|
|
, mLocalSessionWindow(kDefaultRwin)
|
|
|
|
, mServerSessionWindow(kDefaultRwin)
|
2015-09-11 19:53:27 +00:00
|
|
|
, mInitialRwin(ASpdySession::kInitialRwin)
|
2014-04-17 23:55:04 +00:00
|
|
|
, mOutputQueueSize(kDefaultQueueSize)
|
|
|
|
, mOutputQueueUsed(0)
|
|
|
|
, mOutputQueueSent(0)
|
|
|
|
, mLastReadEpoch(PR_IntervalNow())
|
|
|
|
, mPingSentEpoch(0)
|
2014-09-11 10:55:00 +00:00
|
|
|
, mPreviousUsed(false)
|
2018-06-14 08:19:07 +00:00
|
|
|
, mAggregatedHeaderSize(0)
|
2014-06-27 20:13:42 +00:00
|
|
|
, mWaitingForSettingsAck(false)
|
|
|
|
, mGoAwayOnPush(false)
|
2014-12-06 19:26:50 +00:00
|
|
|
, mUseH2Deps(false)
|
2017-02-21 20:19:46 +00:00
|
|
|
, mAttemptingEarlyData(attemptingEarlyData)
|
2017-04-03 21:23:55 +00:00
|
|
|
, mOriginFrameActivated(false)
|
2017-10-17 13:28:33 +00:00
|
|
|
, mTlsHandshakeFinished(false)
|
2017-12-21 17:32:28 +00:00
|
|
|
, mCheckNetworkStallsWithTFO(false)
|
|
|
|
, mLastRequestBytesSentTime(0)
|
2018-10-25 20:46:24 +00:00
|
|
|
, mPeerFailedHandshake(false)
|
2018-08-23 18:13:34 +00:00
|
|
|
, mTrrStreams(0)
|
2014-05-09 19:25:55 +00:00
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-05-09 19:25:55 +00:00
|
|
|
|
2014-05-16 15:46:11 +00:00
|
|
|
static uint64_t sSerial;
|
|
|
|
mSerial = ++sSerial;
|
2014-05-09 19:25:55 +00:00
|
|
|
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::Http2Session %p serial=0x%" PRIX64 "\n", this, mSerial));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
mInputFrameBuffer = MakeUnique<char[]>(mInputFrameBufferSize);
|
|
|
|
mOutputQueueBuffer = MakeUnique<char[]>(mOutputQueueSize);
|
2014-05-16 15:46:11 +00:00
|
|
|
mDecompressBuffer.SetCapacity(kDefaultBufferSize);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-05-16 15:46:11 +00:00
|
|
|
mPushAllowance = gHttpHandler->SpdyPushAllowance();
|
2015-09-11 19:53:27 +00:00
|
|
|
mInitialRwin = std::max(gHttpHandler->SpdyPullAllowance(), mPushAllowance);
|
2015-01-26 21:06:44 +00:00
|
|
|
mMaxConcurrent = gHttpHandler->DefaultSpdyConcurrent();
|
2014-05-16 15:46:11 +00:00
|
|
|
mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
|
|
|
|
SendHello();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-05-16 15:46:11 +00:00
|
|
|
mLastDataReadEpoch = mLastReadEpoch;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-05-16 15:46:11 +00:00
|
|
|
mPingThreshold = gHttpHandler->SpdyPingThreshold();
|
2015-03-20 04:42:00 +00:00
|
|
|
mPreviousPingThreshold = mPingThreshold;
|
2017-08-30 06:19:00 +00:00
|
|
|
mCurrentForegroundTabOuterContentWindowId =
|
|
|
|
gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2016-01-28 02:38:45 +00:00
|
|
|
void
|
|
|
|
Http2Session::Shutdown()
|
|
|
|
{
|
|
|
|
for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
|
|
|
|
nsAutoPtr<Http2Stream> &stream = iter.Data();
|
|
|
|
|
|
|
|
// On a clean server hangup the server sets the GoAwayID to be the ID of
|
|
|
|
// the last transaction it processed. If the ID of stream in the
|
|
|
|
// local stream is greater than that it can safely be restarted because the
|
|
|
|
// server guarantees it was not partially processed. Streams that have not
|
|
|
|
// registered an ID haven't actually been sent yet so they can always be
|
|
|
|
// restarted.
|
|
|
|
if (mCleanShutdown &&
|
|
|
|
(stream->StreamID() > mGoAwayID || !stream->HasRegisteredID())) {
|
|
|
|
CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted
|
|
|
|
} else if (stream->RecvdData()) {
|
|
|
|
CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
|
2016-04-15 02:24:46 +00:00
|
|
|
} else if (mGoAwayReason == INADEQUATE_SECURITY) {
|
|
|
|
CloseStream(stream, NS_ERROR_NET_INADEQUATE_SECURITY);
|
2018-10-25 20:46:46 +00:00
|
|
|
} else if (!mCleanShutdown) {
|
|
|
|
CloseStream(stream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
|
2016-01-28 02:38:45 +00:00
|
|
|
} else {
|
|
|
|
CloseStream(stream, NS_ERROR_ABORT);
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Http2Session::~Http2Session()
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X",
|
|
|
|
this, mDownstreamState));
|
|
|
|
|
2016-01-28 02:38:45 +00:00
|
|
|
Shutdown();
|
|
|
|
|
2018-08-23 18:13:34 +00:00
|
|
|
if (mTrrStreams) {
|
|
|
|
Telemetry::Accumulate(Telemetry::DNS_TRR_REQUEST_PER_CONN, mTrrStreams);
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
|
|
|
|
mServerPushedResources);
|
2015-09-17 19:26:59 +00:00
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_LOCAL, mClientGoAwayReason);
|
2015-09-15 01:55:06 +00:00
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_GOAWAY_PEER, mPeerGoAwayReason);
|
2018-10-25 20:46:24 +00:00
|
|
|
Telemetry::Accumulate(Telemetry::HTTP2_FAIL_BEFORE_SETTINGS, mPeerFailedHandshake);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2018-10-25 20:46:01 +00:00
|
|
|
inline nsresult
|
|
|
|
Http2Session::SessionError(enum errorType reason)
|
|
|
|
{
|
2018-10-25 20:46:46 +00:00
|
|
|
LOG3(("Http2Session::SessionError %p reason=0x%x mPeerGoAwayReason=0x%x",
|
|
|
|
this, reason, mPeerGoAwayReason));
|
2018-10-25 20:46:01 +00:00
|
|
|
mGoAwayReason = reason;
|
2018-10-25 20:46:46 +00:00
|
|
|
|
|
|
|
if (reason == INADEQUATE_SECURITY) {
|
|
|
|
// This one is special, as we have an error page just for this
|
|
|
|
return NS_ERROR_NET_INADEQUATE_SECURITY;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're the one sending a generic GOAWAY
|
|
|
|
return NS_ERROR_NET_HTTP2_SENT_GOAWAY;
|
2018-10-25 20:46:01 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
void
|
|
|
|
Http2Session::LogIO(Http2Session *self, Http2Stream *stream,
|
|
|
|
const char *label,
|
|
|
|
const char *data, uint32_t datalen)
|
|
|
|
{
|
2014-11-10 15:10:41 +00:00
|
|
|
if (!LOG5_ENABLED())
|
2013-10-10 00:21:49 +00:00
|
|
|
return;
|
|
|
|
|
2014-11-10 15:10:41 +00:00
|
|
|
LOG5(("Http2Session::LogIO %p stream=%p id=0x%X [%s]",
|
2013-10-10 00:21:49 +00:00
|
|
|
self, stream, stream ? stream->StreamID() : 0, label));
|
|
|
|
|
|
|
|
// Max line is (16 * 3) + 10(prefix) + newline + null
|
|
|
|
char linebuf[128];
|
|
|
|
uint32_t index;
|
|
|
|
char *line = linebuf;
|
|
|
|
|
|
|
|
linebuf[127] = 0;
|
|
|
|
|
|
|
|
for (index = 0; index < datalen; ++index) {
|
|
|
|
if (!(index % 16)) {
|
|
|
|
if (index) {
|
|
|
|
*line = 0;
|
2014-11-10 15:10:41 +00:00
|
|
|
LOG5(("%s", linebuf));
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
line = linebuf;
|
2015-10-29 20:07:44 +00:00
|
|
|
snprintf(line, 128, "%08X: ", index);
|
2013-10-10 00:21:49 +00:00
|
|
|
line += 10;
|
|
|
|
}
|
2015-10-29 20:07:44 +00:00
|
|
|
snprintf(line, 128 - (line - linebuf), "%02X ", (reinterpret_cast<const uint8_t *>(data))[index]);
|
2013-10-10 00:21:49 +00:00
|
|
|
line += 3;
|
|
|
|
}
|
|
|
|
if (index) {
|
|
|
|
*line = 0;
|
2014-11-10 15:10:41 +00:00
|
|
|
LOG5(("%s", linebuf));
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef nsresult (*Http2ControlFx) (Http2Session *self);
|
|
|
|
static Http2ControlFx sControlFunctions[] = {
|
|
|
|
nullptr, // type 0 data is not a control function
|
|
|
|
Http2Session::RecvHeaders,
|
|
|
|
Http2Session::RecvPriority,
|
|
|
|
Http2Session::RecvRstStream,
|
|
|
|
Http2Session::RecvSettings,
|
|
|
|
Http2Session::RecvPushPromise,
|
|
|
|
Http2Session::RecvPing,
|
|
|
|
Http2Session::RecvGoAway,
|
|
|
|
Http2Session::RecvWindowUpdate,
|
2014-08-21 14:50:17 +00:00
|
|
|
Http2Session::RecvContinuation,
|
2017-04-03 21:23:55 +00:00
|
|
|
Http2Session::RecvAltSvc, // extension for type 0x0A
|
2017-04-11 01:47:07 +00:00
|
|
|
Http2Session::RecvUnused, // 0x0B was BLOCKED still radioactive
|
|
|
|
Http2Session::RecvOrigin // extension for type 0x0C
|
2013-10-10 00:21:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::RoomForMoreConcurrent()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
return (mConcurrent < mMaxConcurrent);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::RoomForMoreStreams()
|
|
|
|
{
|
|
|
|
if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return !mShouldGoAway;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRIntervalTime
|
|
|
|
Http2Session::IdleTime()
|
|
|
|
{
|
|
|
|
return PR_IntervalNow() - mLastDataReadEpoch;
|
|
|
|
}
|
|
|
|
|
2014-01-30 07:29:20 +00:00
|
|
|
uint32_t
|
2013-10-10 00:21:49 +00:00
|
|
|
Http2Session::ReadTimeoutTick(PRIntervalTime now)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n",
|
|
|
|
this, PR_IntervalToSeconds(now - mLastReadEpoch)));
|
|
|
|
|
2017-12-21 17:32:28 +00:00
|
|
|
uint32_t nextTick = UINT32_MAX;
|
|
|
|
if (mCheckNetworkStallsWithTFO && mLastRequestBytesSentTime) {
|
|
|
|
PRIntervalTime initialResponseDelta = now - mLastRequestBytesSentTime;
|
|
|
|
if (initialResponseDelta >= gHttpHandler->FastOpenStallsTimeout()) {
|
|
|
|
gHttpHandler->IncrementFastOpenStallsCounter();
|
|
|
|
mCheckNetworkStallsWithTFO = false;
|
|
|
|
} else {
|
|
|
|
nextTick = PR_IntervalToSeconds(gHttpHandler->FastOpenStallsTimeout()) -
|
|
|
|
PR_IntervalToSeconds(initialResponseDelta);
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
if (!mPingThreshold)
|
2017-12-21 17:32:28 +00:00
|
|
|
return nextTick;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if ((now - mLastReadEpoch) < mPingThreshold) {
|
|
|
|
// recent activity means ping is not an issue
|
2014-09-11 10:55:00 +00:00
|
|
|
if (mPingSentEpoch) {
|
2013-10-10 00:21:49 +00:00
|
|
|
mPingSentEpoch = 0;
|
2014-09-11 10:55:00 +00:00
|
|
|
if (mPreviousUsed) {
|
|
|
|
// restore the former value
|
|
|
|
mPingThreshold = mPreviousPingThreshold;
|
|
|
|
mPreviousUsed = false;
|
|
|
|
}
|
|
|
|
}
|
2014-01-30 07:29:20 +00:00
|
|
|
|
2017-12-21 17:32:28 +00:00
|
|
|
return std::min(nextTick, PR_IntervalToSeconds(mPingThreshold) -
|
|
|
|
PR_IntervalToSeconds(now - mLastReadEpoch));
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mPingSentEpoch) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n", this));
|
2013-10-10 00:21:49 +00:00
|
|
|
if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
|
2014-01-30 07:29:20 +00:00
|
|
|
LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
|
2013-10-10 00:21:49 +00:00
|
|
|
mPingSentEpoch = 0;
|
|
|
|
Close(NS_ERROR_NET_TIMEOUT);
|
2014-01-30 07:29:20 +00:00
|
|
|
return UINT32_MAX;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
2014-01-30 07:29:20 +00:00
|
|
|
return 1; // run the tick aggressively while ping is outstanding
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::ReadTimeoutTick %p generating ping\n", this));
|
|
|
|
|
|
|
|
mPingSentEpoch = PR_IntervalNow();
|
2014-09-11 10:55:00 +00:00
|
|
|
if (!mPingSentEpoch) {
|
2013-10-10 00:21:49 +00:00
|
|
|
mPingSentEpoch = 1; // avoid the 0 sentinel value
|
2014-09-11 10:55:00 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
GeneratePing(false);
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ResumeRecv(); // read the ping reply
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Check for orphaned push streams. This looks expensive, but generally the
|
|
|
|
// list is empty.
|
|
|
|
Http2PushedStream *deleteMe;
|
|
|
|
TimeStamp timestampNow;
|
|
|
|
do {
|
|
|
|
deleteMe = nullptr;
|
|
|
|
|
|
|
|
for (uint32_t index = mPushedStreams.Length();
|
|
|
|
index > 0 ; --index) {
|
|
|
|
Http2PushedStream *pushedStream = mPushedStreams[index - 1];
|
|
|
|
|
|
|
|
if (timestampNow.IsNull())
|
|
|
|
timestampNow = TimeStamp::Now(); // lazy initializer
|
|
|
|
|
|
|
|
// if stream finished, but is not connected, and its been like that for
|
|
|
|
// long then cleanup the stream.
|
|
|
|
if (pushedStream->IsOrphaned(timestampNow))
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session Timeout Pushed Stream %p 0x%X\n",
|
|
|
|
this, pushedStream->StreamID()));
|
|
|
|
deleteMe = pushedStream;
|
|
|
|
break; // don't CleanupStream() while iterating this vector
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (deleteMe)
|
|
|
|
CleanupStream(deleteMe, NS_ERROR_ABORT, CANCEL_ERROR);
|
|
|
|
|
|
|
|
} while (deleteMe);
|
2014-01-30 07:29:20 +00:00
|
|
|
|
|
|
|
return 1; // run the tick aggressively while ping is outstanding
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
Http2Session::RegisterStreamID(Http2Stream *stream, uint32_t aNewID)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
MOZ_ASSERT(mNextStreamID < 0xfffffff0,
|
|
|
|
"should have stopped admitting streams");
|
|
|
|
MOZ_ASSERT(!(aNewID & 1),
|
|
|
|
"0 for autoassign pull, otherwise explicit even push assignment");
|
|
|
|
|
|
|
|
if (!aNewID) {
|
|
|
|
// auto generate a new pull stream ID
|
|
|
|
aNewID = mNextStreamID;
|
|
|
|
MOZ_ASSERT(aNewID & 1, "pull ID must be odd.");
|
|
|
|
mNextStreamID += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RegisterStreamID session=%p stream=%p id=0x%X "
|
|
|
|
"concurrent=%d",this, stream, aNewID, mConcurrent));
|
|
|
|
|
|
|
|
// We've used up plenty of ID's on this session. Start
|
|
|
|
// moving to a new one before there is a crunch involving
|
|
|
|
// server push streams or concurrent non-registered submits
|
|
|
|
if (aNewID >= kMaxStreamID)
|
|
|
|
mShouldGoAway = true;
|
|
|
|
|
|
|
|
// integrity check
|
|
|
|
if (mStreamIDHash.Get(aNewID)) {
|
|
|
|
LOG3((" New ID already present\n"));
|
|
|
|
MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
|
|
|
|
mShouldGoAway = true;
|
|
|
|
return kDeadStreamID;
|
|
|
|
}
|
|
|
|
|
|
|
|
mStreamIDHash.Put(aNewID, stream);
|
2017-12-21 17:32:28 +00:00
|
|
|
|
|
|
|
// If TCP fast Open has been used and conection was idle for some time
|
|
|
|
// we will be cautious and watch out for bug 1395494.
|
|
|
|
if (!mCheckNetworkStallsWithTFO && mConnection) {
|
|
|
|
RefPtr<nsHttpConnection> conn = mConnection->HttpConnection();
|
|
|
|
if (conn && (conn->GetFastOpenStatus() == TFO_DATA_SENT) &&
|
|
|
|
gHttpHandler->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() &&
|
|
|
|
IdleTime() >= gHttpHandler->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds()) {
|
|
|
|
// If a connection was using the TCP FastOpen and it was idle for a
|
|
|
|
// long time we should check for stalls like bug 1395494.
|
|
|
|
mCheckNetworkStallsWithTFO = true;
|
|
|
|
mLastRequestBytesSentTime = PR_IntervalNow();
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 18:13:34 +00:00
|
|
|
|
2018-09-13 14:48:46 +00:00
|
|
|
if (aNewID & 1) {
|
2018-08-23 18:13:34 +00:00
|
|
|
// don't count push streams here
|
|
|
|
MOZ_ASSERT(stream->Transaction(), "no transation for the stream!");
|
|
|
|
RefPtr<nsHttpConnectionInfo> ci(stream->Transaction()->ConnectionInfo());
|
|
|
|
if (ci && ci->GetTrrUsed()) {
|
|
|
|
IncrementTrrCounter();
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return aNewID;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
|
2014-04-16 13:52:43 +00:00
|
|
|
int32_t aPriority,
|
|
|
|
bool aUseTunnel,
|
|
|
|
nsIInterfaceRequestor *aCallbacks)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// integrity check
|
|
|
|
if (mStreamTransactionHash.Get(aHttpTransaction)) {
|
|
|
|
LOG3((" New transaction already present\n"));
|
|
|
|
MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-05-16 15:46:11 +00:00
|
|
|
if (!mConnection) {
|
|
|
|
mConnection = aHttpTransaction->Connection();
|
|
|
|
}
|
2014-05-16 15:46:13 +00:00
|
|
|
|
2017-10-17 13:28:33 +00:00
|
|
|
if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
|
|
|
|
mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
|
|
|
|
LOG3(("Http2Session::AddStream first session=%p trans=%p ", this, mFirstHttpTransaction.get()));
|
|
|
|
}
|
|
|
|
|
2016-07-05 16:33:34 +00:00
|
|
|
if (mClosed || mShouldGoAway) {
|
|
|
|
nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
|
|
|
|
if (trans && !trans->GetPushedStream()) {
|
|
|
|
LOG3(("Http2Session::AddStream %p atrans=%p trans=%p session unusable - resched.\n",
|
|
|
|
this, aHttpTransaction, trans));
|
|
|
|
aHttpTransaction->SetConnection(nullptr);
|
2016-12-27 11:05:13 +00:00
|
|
|
nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::AddStream %p atrans=%p trans=%p failed to initiate "
|
|
|
|
"transaction (%08x).\n", this, aHttpTransaction, trans,
|
|
|
|
static_cast<uint32_t>(rv)));
|
|
|
|
}
|
2016-07-05 16:33:34 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
aHttpTransaction->SetConnection(this);
|
2018-01-10 21:35:02 +00:00
|
|
|
aHttpTransaction->OnActivated();
|
2014-05-16 15:46:13 +00:00
|
|
|
|
|
|
|
if (aUseTunnel) {
|
|
|
|
LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
|
|
|
|
this, aHttpTransaction));
|
|
|
|
DispatchOnTunnel(aHttpTransaction, aCallbacks);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-08-30 06:19:00 +00:00
|
|
|
Http2Stream *stream =
|
|
|
|
new Http2Stream(aHttpTransaction,
|
|
|
|
this,
|
|
|
|
aPriority,
|
|
|
|
mCurrentForegroundTabOuterContentWindowId);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
|
2014-05-16 15:46:13 +00:00
|
|
|
"NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
mStreamTransactionHash.Put(aHttpTransaction, stream);
|
|
|
|
|
2015-01-13 20:26:37 +00:00
|
|
|
mReadyForWrite.Push(stream);
|
|
|
|
SetWriteCallbacks();
|
|
|
|
|
|
|
|
// Kick off the SYN transmit without waiting for the poll loop
|
|
|
|
// This won't work for the first stream because there is no segment reader
|
|
|
|
// yet.
|
|
|
|
if (mSegmentReader) {
|
|
|
|
uint32_t countRead;
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-08-21 14:50:17 +00:00
|
|
|
if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) &&
|
|
|
|
!aHttpTransaction->IsNullTransaction()) {
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::AddStream %p transaction %p forces keep-alive off.\n",
|
|
|
|
this, aHttpTransaction));
|
|
|
|
DontReuse();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-01-13 20:26:37 +00:00
|
|
|
Http2Session::QueueStream(Http2Stream *stream)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2015-01-13 20:26:37 +00:00
|
|
|
// will be removed via processpending or a shutdown path
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
MOZ_ASSERT(!stream->CountAsActive());
|
2015-01-13 20:26:37 +00:00
|
|
|
MOZ_ASSERT(!stream->Queued());
|
2015-01-09 20:33:27 +00:00
|
|
|
|
2015-01-13 20:26:37 +00:00
|
|
|
LOG3(("Http2Session::QueueStream %p stream %p queued.", this, stream));
|
2015-01-09 20:33:27 +00:00
|
|
|
|
2015-01-13 20:26:37 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
int32_t qsize = mQueuedStreams.GetSize();
|
|
|
|
for (int32_t i = 0; i < qsize; i++) {
|
|
|
|
Http2Stream *qStream = static_cast<Http2Stream *>(mQueuedStreams.ObjectAt(i));
|
|
|
|
MOZ_ASSERT(qStream != stream);
|
|
|
|
MOZ_ASSERT(qStream->Queued());
|
2015-01-09 20:33:27 +00:00
|
|
|
}
|
2015-01-13 20:26:37 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
stream->SetQueued(true);
|
|
|
|
mQueuedStreams.Push(stream);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::ProcessPending()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2015-01-13 20:26:37 +00:00
|
|
|
Http2Stream*stream;
|
|
|
|
while (RoomForMoreConcurrent() &&
|
|
|
|
(stream = static_cast<Http2Stream *>(mQueuedStreams.PopFront()))) {
|
|
|
|
|
|
|
|
LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.",
|
2013-10-10 00:21:49 +00:00
|
|
|
this, stream));
|
2015-01-13 20:26:37 +00:00
|
|
|
MOZ_ASSERT(!stream->CountAsActive());
|
|
|
|
MOZ_ASSERT(stream->Queued());
|
|
|
|
stream->SetQueued(false);
|
|
|
|
mReadyForWrite.Push(stream);
|
|
|
|
SetWriteCallbacks();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
|
|
|
|
uint32_t count, uint32_t *countWritten)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (!count) {
|
|
|
|
*countWritten = 0;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
|
2017-12-21 17:32:28 +00:00
|
|
|
if (NS_SUCCEEDED(rv) && *countWritten > 0) {
|
2013-10-10 00:21:49 +00:00
|
|
|
mLastReadEpoch = PR_IntervalNow();
|
2017-12-21 17:32:28 +00:00
|
|
|
mCheckNetworkStallsWithTFO = false;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::SetWriteCallbacks()
|
|
|
|
{
|
2017-05-04 10:15:23 +00:00
|
|
|
if (mConnection &&
|
|
|
|
(GetWriteQueueSize() || (mOutputQueueUsed > mOutputQueueSent))) {
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << mConnection->ResumeSend();
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::RealignOutputQueue()
|
|
|
|
{
|
2017-02-21 20:19:46 +00:00
|
|
|
if (mAttemptingEarlyData) {
|
|
|
|
// We can't realign right now, because we may need what's in there if early
|
|
|
|
// data fails.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
mOutputQueueUsed -= mOutputQueueSent;
|
|
|
|
memmove(mOutputQueueBuffer.get(),
|
|
|
|
mOutputQueueBuffer.get() + mOutputQueueSent,
|
|
|
|
mOutputQueueUsed);
|
|
|
|
mOutputQueueSent = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::FlushOutputQueue()
|
|
|
|
{
|
|
|
|
if (!mSegmentReader || !mOutputQueueUsed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
nsresult rv;
|
|
|
|
uint32_t countRead;
|
|
|
|
uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
|
|
|
|
|
2017-02-21 20:19:46 +00:00
|
|
|
if (!avail && mAttemptingEarlyData) {
|
|
|
|
// This is kind of a hack, but there are cases where we'll have already
|
|
|
|
// written the data we want whlie doing early data, but we get called again
|
|
|
|
// with a reader, and we need to avoid calling the reader when there's
|
|
|
|
// nothing for it to read.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
rv = mSegmentReader->
|
|
|
|
OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
|
|
|
|
&countRead);
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%" PRIx32 " actual=%d",
|
|
|
|
this, avail, static_cast<uint32_t>(rv), countRead));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Dont worry about errors on write, we will pick this up as a read error too
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return;
|
|
|
|
|
2017-02-21 20:19:46 +00:00
|
|
|
mOutputQueueSent += countRead;
|
|
|
|
|
|
|
|
if (mAttemptingEarlyData) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
if (countRead == avail) {
|
|
|
|
mOutputQueueUsed = 0;
|
|
|
|
mOutputQueueSent = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the output queue is close to filling up and we have sent out a good
|
|
|
|
// chunk of data from the beginning then realign it.
|
|
|
|
|
|
|
|
if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
|
|
|
|
((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
|
|
|
|
RealignOutputQueue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::DontReuse()
|
|
|
|
{
|
2016-02-18 22:30:46 +00:00
|
|
|
LOG3(("Http2Session::DontReuse %p\n", this));
|
2017-10-20 17:39:49 +00:00
|
|
|
if (!OnSocketThread()) {
|
|
|
|
LOG3(("Http2Session %p not on socket thread\n", this));
|
|
|
|
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
|
|
|
|
"Http2Session::DontReuse", this, &Http2Session::DontReuse);
|
|
|
|
gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
mShouldGoAway = true;
|
2017-10-20 17:39:49 +00:00
|
|
|
if (!mClosed && !mStreamTransactionHash.Count()) {
|
2013-10-10 00:21:49 +00:00
|
|
|
Close(NS_OK);
|
2017-10-20 17:39:49 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2018-06-01 18:32:05 +00:00
|
|
|
enum SpdyVersion
|
2017-02-21 20:19:46 +00:00
|
|
|
Http2Session::SpdyVersion()
|
|
|
|
{
|
2018-06-01 18:32:05 +00:00
|
|
|
return SpdyVersion::HTTP_2;
|
2017-02-21 20:19:46 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
uint32_t
|
|
|
|
Http2Session::GetWriteQueueSize()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
return mReadyForWrite.GetSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::ChangeDownstreamState(enum internalStateType newState)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-04-17 23:55:04 +00:00
|
|
|
LOG3(("Http2Session::ChangeDownstreamState() %p from %X to %X",
|
2013-10-10 00:21:49 +00:00
|
|
|
this, mDownstreamState, newState));
|
|
|
|
mDownstreamState = newState;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::ResetDownstreamState()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-04-17 23:55:04 +00:00
|
|
|
LOG3(("Http2Session::ResetDownstreamState() %p", this));
|
2013-10-10 00:21:49 +00:00
|
|
|
ChangeDownstreamState(BUFFERING_FRAME_HEADER);
|
|
|
|
|
|
|
|
if (mInputFrameFinal && mInputFrameDataStream) {
|
|
|
|
mInputFrameFinal = false;
|
|
|
|
LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID()));
|
|
|
|
mInputFrameDataStream->SetRecvdFin(true);
|
|
|
|
MaybeDecrementConcurrent(mInputFrameDataStream);
|
|
|
|
}
|
2015-11-22 21:23:18 +00:00
|
|
|
mInputFrameFinal = false;
|
2013-10-10 00:21:49 +00:00
|
|
|
mInputFrameBufferUsed = 0;
|
|
|
|
mInputFrameDataStream = nullptr;
|
|
|
|
}
|
|
|
|
|
2015-01-13 20:26:37 +00:00
|
|
|
// return true if activated (and counted against max)
|
|
|
|
// otherwise return false and queue
|
|
|
|
bool
|
|
|
|
Http2Session::TryToActivate(Http2Stream *aStream)
|
|
|
|
{
|
|
|
|
if (aStream->Queued()) {
|
|
|
|
LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this, aStream));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!RoomForMoreConcurrent()) {
|
|
|
|
LOG3(("Http2Session::TryToActivate %p stream=%p no room for more concurrent "
|
2016-12-16 03:16:31 +00:00
|
|
|
"streams\n", this, aStream));
|
2015-01-13 20:26:37 +00:00
|
|
|
QueueStream(aStream);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::TryToActivate %p stream=%p\n", this, aStream));
|
|
|
|
IncrementConcurrent(aStream);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::IncrementConcurrent(Http2Stream *stream)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2015-01-13 20:26:37 +00:00
|
|
|
MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1),
|
|
|
|
"Do not activate pushed streams");
|
|
|
|
|
|
|
|
nsAHttpTransaction *trans = stream->Transaction();
|
|
|
|
if (!trans || !trans->IsNullTransaction() || trans->QuerySpdyConnectTransaction()) {
|
|
|
|
|
|
|
|
MOZ_ASSERT(!stream->CountAsActive());
|
|
|
|
stream->SetCountAsActive(true);
|
|
|
|
++mConcurrent;
|
|
|
|
|
|
|
|
if (mConcurrent > mConcurrentHighWater) {
|
|
|
|
mConcurrentHighWater = mConcurrent;
|
|
|
|
}
|
|
|
|
LOG3(("Http2Session::IncrementCounter %p counting stream %p Currently %d "
|
|
|
|
"streams in session, high water mark is %d\n",
|
|
|
|
this, stream, mConcurrent, mConcurrentHighWater));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header)
|
|
|
|
// dest must have 9 bytes of allocated space
|
2013-10-10 00:21:49 +00:00
|
|
|
template<typename charType> void
|
|
|
|
Http2Session::CreateFrameHeader(charType dest, uint16_t frameLength,
|
|
|
|
uint8_t frameType, uint8_t frameFlags,
|
|
|
|
uint32_t streamID)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(frameLength <= kMaxFrameData, "framelength too large");
|
|
|
|
MOZ_ASSERT(!(streamID & 0x80000000));
|
2016-03-03 21:35:38 +00:00
|
|
|
MOZ_ASSERT(!frameFlags ||
|
|
|
|
(frameType != FRAME_TYPE_PRIORITY &&
|
|
|
|
frameType != FRAME_TYPE_RST_STREAM &&
|
|
|
|
frameType != FRAME_TYPE_GOAWAY &&
|
|
|
|
frameType != FRAME_TYPE_WINDOW_UPDATE));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
dest[0] = 0x00;
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint16(dest + 1, frameLength);
|
2014-08-05 15:41:09 +00:00
|
|
|
dest[3] = frameType;
|
|
|
|
dest[4] = frameFlags;
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(dest + 5, streamID);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
Http2Session::EnsureOutputBuffer(uint32_t spaceNeeded)
|
|
|
|
{
|
|
|
|
// this is an infallible allocation (if an allocation is
|
|
|
|
// needed, which is probably isn't)
|
|
|
|
EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + spaceNeeded,
|
|
|
|
mOutputQueueUsed, mOutputQueueSize);
|
|
|
|
return mOutputQueueBuffer.get() + mOutputQueueUsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
template void
|
|
|
|
Http2Session::CreateFrameHeader(char *dest, uint16_t frameLength,
|
|
|
|
uint8_t frameType, uint8_t frameFlags,
|
|
|
|
uint32_t streamID);
|
|
|
|
|
|
|
|
template void
|
|
|
|
Http2Session::CreateFrameHeader(uint8_t *dest, uint16_t frameLength,
|
|
|
|
uint8_t frameType, uint8_t frameFlags,
|
|
|
|
uint32_t streamID);
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::MaybeDecrementConcurrent(Http2Stream *aStream)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("MaybeDecrementConcurrent %p id=0x%X concurrent=%d active=%d\n",
|
|
|
|
this, aStream->StreamID(), mConcurrent, aStream->CountAsActive()));
|
|
|
|
|
|
|
|
if (!aStream->CountAsActive())
|
|
|
|
return;
|
|
|
|
|
|
|
|
MOZ_ASSERT(mConcurrent);
|
|
|
|
aStream->SetCountAsActive(false);
|
|
|
|
--mConcurrent;
|
|
|
|
ProcessPending();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Need to decompress some data in order to keep the compression
|
|
|
|
// context correct, but we really don't care what the result is
|
|
|
|
nsresult
|
2016-01-04 20:17:39 +00:00
|
|
|
Http2Session::UncompressAndDiscard(bool isPush)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
|
|
|
nsresult rv;
|
|
|
|
nsAutoCString trash;
|
|
|
|
|
|
|
|
rv = mDecompressor.DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(mDecompressBuffer.BeginReading()),
|
2016-01-04 20:17:39 +00:00
|
|
|
mDecompressBuffer.Length(), trash, isPush);
|
2013-10-10 00:21:49 +00:00
|
|
|
mDecompressBuffer.Truncate();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::UncompressAndDiscard %p Compression Error\n",
|
|
|
|
this));
|
|
|
|
mGoAwayReason = COMPRESSION_ERROR;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::GeneratePing(bool isAck)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::GeneratePing %p isAck=%d\n", this, isAck));
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 8);
|
|
|
|
mOutputQueueUsed += kFrameHeaderBytes + 8;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (isAck) {
|
|
|
|
CreateFrameHeader(packet, 8, FRAME_TYPE_PING, kFlag_ACK, 0);
|
2014-08-05 15:41:09 +00:00
|
|
|
memcpy(packet + kFrameHeaderBytes,
|
|
|
|
mInputFrameBuffer.get() + kFrameHeaderBytes, 8);
|
2013-10-10 00:21:49 +00:00
|
|
|
} else {
|
|
|
|
CreateFrameHeader(packet, 8, FRAME_TYPE_PING, 0, 0);
|
2014-08-05 15:41:09 +00:00
|
|
|
memset(packet + kFrameHeaderBytes, 0, 8);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, nullptr, "Generate Ping", packet, kFrameHeaderBytes + 8);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::GenerateSettingsAck()
|
|
|
|
{
|
|
|
|
// need to generate ack of this settings frame
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
char *packet = EnsureOutputBuffer(kFrameHeaderBytes);
|
|
|
|
mOutputQueueUsed += kFrameHeaderBytes;
|
2013-10-10 00:21:49 +00:00
|
|
|
CreateFrameHeader(packet, 0, FRAME_TYPE_SETTINGS, kFlag_ACK, 0);
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, nullptr, "Generate Settings ACK", packet, kFrameHeaderBytes);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-04-30 01:46:03 +00:00
|
|
|
Http2Session::GeneratePriority(uint32_t aID, uint8_t aPriorityWeight)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-04-30 01:46:03 +00:00
|
|
|
LOG3(("Http2Session::GeneratePriority %p %X %X\n",
|
|
|
|
this, aID, aPriorityWeight));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2017-08-30 06:19:00 +00:00
|
|
|
char *packet = CreatePriorityFrame(aID, 0, aPriorityWeight);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2017-08-30 06:19:00 +00:00
|
|
|
LogIO(this, nullptr, "Generate Priority", packet, kFrameHeaderBytes + 5);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// make sure we don't do this twice for the same stream (at least if we
|
|
|
|
// have a stream entry for it)
|
|
|
|
Http2Stream *stream = mStreamIDHash.Get(aID);
|
|
|
|
if (stream) {
|
|
|
|
if (stream->SentReset())
|
|
|
|
return;
|
|
|
|
stream->SetSentReset(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
uint32_t frameSize = kFrameHeaderBytes + 4;
|
|
|
|
char *packet = EnsureOutputBuffer(frameSize);
|
|
|
|
mOutputQueueUsed += frameSize;
|
2013-10-10 00:21:49 +00:00
|
|
|
CreateFrameHeader(packet, 4, FRAME_TYPE_RST_STREAM, 0, aID);
|
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes, aStatusCode);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, nullptr, "Generate Reset", packet, frameSize);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::GenerateGoAway(uint32_t aStatusCode)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::GenerateGoAway %p code=%X\n", this, aStatusCode));
|
|
|
|
|
2015-09-17 19:26:59 +00:00
|
|
|
mClientGoAwayReason = aStatusCode;
|
2014-08-05 15:41:09 +00:00
|
|
|
uint32_t frameSize = kFrameHeaderBytes + 8;
|
|
|
|
char *packet = EnsureOutputBuffer(frameSize);
|
|
|
|
mOutputQueueUsed += frameSize;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
CreateFrameHeader(packet, 8, FRAME_TYPE_GOAWAY, 0, 0);
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
// last-good-stream-id are bytes 9-12 reflecting pushes
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes, mOutgoingGoAwayID);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
// bytes 13-16 are the status code.
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + frameSize - 4, aStatusCode);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, nullptr, "Generate GoAway", packet, frameSize);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-12-06 19:26:50 +00:00
|
|
|
// The Hello is comprised of
|
|
|
|
// 1] 24 octets of magic, which are designed to
|
|
|
|
// flush out silent but broken intermediaries
|
|
|
|
// 2] a settings frame which sets a small flow control window for pushes
|
|
|
|
// 3] a window update frame which creates a large session flow control window
|
2017-08-28 03:11:00 +00:00
|
|
|
// 4] 6 priority frames for streams which will never be opened with headers
|
|
|
|
// these streams (3, 5, 7, 9, b, d) build a dependency tree that all other
|
2014-12-06 19:26:50 +00:00
|
|
|
// streams will be direct leaves of.
|
2013-10-10 00:21:49 +00:00
|
|
|
void
|
|
|
|
Http2Session::SendHello()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::SendHello %p\n", this));
|
|
|
|
|
2017-08-28 03:11:00 +00:00
|
|
|
// sized for magic + 5 settings and a session window update and 6 priority frames
|
2014-12-06 19:26:50 +00:00
|
|
|
// 24 magic, 33 for settings (9 header + 4 settings @6), 13 for window update,
|
2017-08-28 03:11:00 +00:00
|
|
|
// 6 priority frames at 14 (9 + 5) each
|
2016-08-24 21:35:43 +00:00
|
|
|
static const uint32_t maxSettings = 5;
|
2017-08-28 03:11:00 +00:00
|
|
|
static const uint32_t prioritySize = kPriorityGroupCount * (kFrameHeaderBytes + 5);
|
2014-12-06 19:26:50 +00:00
|
|
|
static const uint32_t maxDataLen = 24 + kFrameHeaderBytes + maxSettings * 6 + 13 + prioritySize;
|
2013-10-10 00:21:49 +00:00
|
|
|
char *packet = EnsureOutputBuffer(maxDataLen);
|
|
|
|
memcpy(packet, kMagicHello, 24);
|
|
|
|
mOutputQueueUsed += 24;
|
|
|
|
LogIO(this, nullptr, "Magic Connection Header", packet, 24);
|
|
|
|
|
|
|
|
packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
|
|
|
|
memset(packet, 0, maxDataLen - 24);
|
|
|
|
|
|
|
|
// frame header will be filled in after we know how long the frame is
|
|
|
|
uint8_t numberOfEntries = 0;
|
|
|
|
|
|
|
|
// entries need to be listed in order by ID
|
2014-08-05 15:41:09 +00:00
|
|
|
// 1st entry is bytes 9 to 14
|
|
|
|
// 2nd entry is bytes 15 to 20
|
|
|
|
// 3rd entry is bytes 21 to 26
|
|
|
|
// 4th entry is bytes 27 to 32
|
2016-08-24 21:35:43 +00:00
|
|
|
// 5th entry is bytes 33 to 38
|
|
|
|
|
|
|
|
// Let the other endpoint know about our default HPACK decompress table size
|
|
|
|
uint32_t maxHpackBufferSize = gHttpHandler->DefaultHpackBuffer();
|
|
|
|
mDecompressor.SetInitialMaxBufferSize(maxHpackBufferSize);
|
|
|
|
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_HEADER_TABLE_SIZE);
|
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, maxHpackBufferSize);
|
|
|
|
numberOfEntries++;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (!gHttpHandler->AllowPush()) {
|
|
|
|
// If we don't support push then set MAX_CONCURRENT to 0 and also
|
|
|
|
// set ENABLE_PUSH to 0
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_ENABLE_PUSH);
|
2013-10-10 00:21:49 +00:00
|
|
|
// The value portion of the setting pair is already initialized to 0
|
|
|
|
numberOfEntries++;
|
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_MAX_CONCURRENT);
|
2013-10-10 00:21:49 +00:00
|
|
|
// The value portion of the setting pair is already initialized to 0
|
|
|
|
numberOfEntries++;
|
2014-06-27 20:13:42 +00:00
|
|
|
|
|
|
|
mWaitingForSettingsAck = true;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Advertise the Push RWIN for the session, and on each new pull stream
|
2014-10-21 18:35:41 +00:00
|
|
|
// send a window update
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_INITIAL_WINDOW);
|
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
|
2014-08-05 15:41:09 +00:00
|
|
|
numberOfEntries++;
|
|
|
|
|
|
|
|
// Make sure the other endpoint knows that we're sticking to the default max
|
|
|
|
// frame size
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_MAX_FRAME_SIZE);
|
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
|
2014-04-30 01:46:03 +00:00
|
|
|
numberOfEntries++;
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
MOZ_ASSERT(numberOfEntries <= maxSettings);
|
2014-06-26 00:33:15 +00:00
|
|
|
uint32_t dataLen = 6 * numberOfEntries;
|
2013-10-10 00:21:49 +00:00
|
|
|
CreateFrameHeader(packet, dataLen, FRAME_TYPE_SETTINGS, 0, 0);
|
2014-08-05 15:41:09 +00:00
|
|
|
mOutputQueueUsed += kFrameHeaderBytes + dataLen;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, nullptr, "Generate Settings", packet, kFrameHeaderBytes + dataLen);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// now bump the local session window from 64KB
|
2015-09-11 19:53:27 +00:00
|
|
|
uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin;
|
|
|
|
if (kDefaultRwin < mInitialRwin) {
|
2014-12-06 19:26:50 +00:00
|
|
|
// send a window update for the session (Stream 0) for something large
|
2015-09-11 19:53:27 +00:00
|
|
|
mLocalSessionWindow = mInitialRwin;
|
2014-12-06 19:26:50 +00:00
|
|
|
|
|
|
|
packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
|
|
|
|
CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
|
|
|
|
mOutputQueueUsed += kFrameHeaderBytes + 4;
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes, sessionWindowBump);
|
2014-12-06 19:26:50 +00:00
|
|
|
|
|
|
|
LOG3(("Session Window increase at start of session %p %u\n",
|
|
|
|
this, sessionWindowBump));
|
|
|
|
LogIO(this, nullptr, "Session Window Bump ", packet, kFrameHeaderBytes + 4);
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2015-05-12 17:45:15 +00:00
|
|
|
if (gHttpHandler->UseH2Deps() && gHttpHandler->CriticalRequestPrioritization()) {
|
2014-12-06 19:26:50 +00:00
|
|
|
mUseH2Deps = true;
|
|
|
|
MOZ_ASSERT(mNextStreamID == kLeaderGroupID);
|
|
|
|
CreatePriorityNode(kLeaderGroupID, 0, 200, "leader");
|
|
|
|
mNextStreamID += 2;
|
|
|
|
MOZ_ASSERT(mNextStreamID == kOtherGroupID);
|
|
|
|
CreatePriorityNode(kOtherGroupID, 0, 100, "other");
|
|
|
|
mNextStreamID += 2;
|
|
|
|
MOZ_ASSERT(mNextStreamID == kBackgroundGroupID);
|
|
|
|
CreatePriorityNode(kBackgroundGroupID, 0, 0, "background");
|
|
|
|
mNextStreamID += 2;
|
|
|
|
MOZ_ASSERT(mNextStreamID == kSpeculativeGroupID);
|
|
|
|
CreatePriorityNode(kSpeculativeGroupID, kBackgroundGroupID, 0, "speculative");
|
|
|
|
mNextStreamID += 2;
|
|
|
|
MOZ_ASSERT(mNextStreamID == kFollowerGroupID);
|
|
|
|
CreatePriorityNode(kFollowerGroupID, kLeaderGroupID, 0, "follower");
|
|
|
|
mNextStreamID += 2;
|
2017-05-18 16:50:15 +00:00
|
|
|
MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
|
|
|
|
CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
|
|
|
|
mNextStreamID += 2;
|
2017-03-16 15:45:42 +00:00
|
|
|
// Hey, you! YES YOU! If you add/remove any groups here, you almost
|
|
|
|
// certainly need to change the lookup of the stream/ID hash in
|
|
|
|
// Http2Session::OnTransportStatus. Yeah, that's right. YOU!
|
2014-12-06 19:26:50 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-12-06 19:26:50 +00:00
|
|
|
void
|
2017-08-30 06:19:00 +00:00
|
|
|
Http2Session::SendPriorityFrame(uint32_t streamID,
|
|
|
|
uint32_t dependsOn,
|
|
|
|
uint8_t weight)
|
2014-12-06 19:26:50 +00:00
|
|
|
{
|
2017-08-30 06:19:00 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
LOG3(("Http2Session::SendPriorityFrame %p Frame 0x%X depends on 0x%X "
|
|
|
|
"weight %d\n", this, streamID, dependsOn, weight));
|
|
|
|
|
|
|
|
char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
|
|
|
|
|
|
|
|
LogIO(this, nullptr, "SendPriorityFrame", packet, kFrameHeaderBytes + 5);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2017-08-30 06:19:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
Http2Session::CreatePriorityFrame(uint32_t streamID,
|
|
|
|
uint32_t dependsOn,
|
|
|
|
uint8_t weight)
|
|
|
|
{
|
2018-09-24 15:16:22 +00:00
|
|
|
MOZ_ASSERT(streamID, "Priority on stream 0");
|
2017-08-30 06:19:00 +00:00
|
|
|
char *packet = EnsureOutputBuffer(kFrameHeaderBytes + 5);
|
2014-12-06 19:26:50 +00:00
|
|
|
CreateFrameHeader(packet, 5, FRAME_TYPE_PRIORITY, 0, streamID);
|
|
|
|
mOutputQueueUsed += kFrameHeaderBytes + 5;
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes, dependsOn); // depends on
|
2014-12-06 19:26:50 +00:00
|
|
|
packet[kFrameHeaderBytes + 4] = weight; // weight
|
2017-08-30 06:19:00 +00:00
|
|
|
return packet;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::CreatePriorityNode(uint32_t streamID, uint32_t dependsOn, uint8_t weight,
|
|
|
|
const char *label)
|
|
|
|
{
|
|
|
|
char *packet = CreatePriorityFrame(streamID, dependsOn, weight);
|
2014-12-06 19:26:50 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session %p generate Priority Frame 0x%X depends on 0x%X "
|
|
|
|
"weight %d for %s class\n", this, streamID, dependsOn, weight, label));
|
|
|
|
LogIO(this, nullptr, "Priority dep node", packet, kFrameHeaderBytes + 5);
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// perform a bunch of integrity checks on the stream.
|
|
|
|
// returns true if passed, false (plus LOG and ABORT) if failed.
|
|
|
|
bool
|
|
|
|
Http2Session::VerifyStream(Http2Stream *aStream, uint32_t aOptionalID = 0)
|
|
|
|
{
|
|
|
|
// This is annoying, but at least it is O(1)
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
#ifndef DEBUG
|
|
|
|
// Only do the real verification in debug builds
|
|
|
|
return true;
|
2017-01-16 10:20:42 +00:00
|
|
|
#else //DEBUG
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (!aStream)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
uint32_t test = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (aStream->StreamID() == kDeadStreamID)
|
|
|
|
break;
|
|
|
|
|
|
|
|
nsAHttpTransaction *trans = aStream->Transaction();
|
|
|
|
|
|
|
|
test++;
|
|
|
|
if (!trans)
|
|
|
|
break;
|
|
|
|
|
|
|
|
test++;
|
|
|
|
if (mStreamTransactionHash.Get(trans) != aStream)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (aStream->StreamID()) {
|
|
|
|
Http2Stream *idStream = mStreamIDHash.Get(aStream->StreamID());
|
|
|
|
|
|
|
|
test++;
|
|
|
|
if (idStream != aStream)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (aOptionalID) {
|
|
|
|
test++;
|
|
|
|
if (idStream->StreamID() != aOptionalID)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tests passed
|
|
|
|
return true;
|
2018-04-30 16:46:04 +00:00
|
|
|
} while (false);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session %p VerifyStream Failure %p stream->id=0x%X "
|
|
|
|
"optionalID=0x%X trans=%p test=%d\n",
|
|
|
|
this, aStream, aStream->StreamID(),
|
|
|
|
aOptionalID, aStream->Transaction(), test));
|
|
|
|
|
|
|
|
MOZ_ASSERT(false, "VerifyStream");
|
|
|
|
return false;
|
2017-01-16 10:20:42 +00:00
|
|
|
#endif //DEBUG
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
|
|
|
|
errorType aResetCode)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::CleanupStream %p %p 0x%X %" PRIX32 "\n",
|
|
|
|
this, aStream, aStream ? aStream->StreamID() : 0, static_cast<uint32_t>(aResult)));
|
2014-01-13 15:10:10 +00:00
|
|
|
if (!aStream) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2018-02-02 19:03:08 +00:00
|
|
|
Http2PushedStream *pushSource = aStream->PushSource();
|
|
|
|
if (pushSource) {
|
|
|
|
// aStream is a synthetic attached to an even push
|
|
|
|
MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
|
|
|
|
MOZ_ASSERT(!aStream->StreamID());
|
|
|
|
MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
|
|
|
|
aStream->ClearPushSource();
|
|
|
|
}
|
|
|
|
|
2014-10-21 18:35:41 +00:00
|
|
|
if (aStream->DeferCleanup(aResult)) {
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!VerifyStream(aStream)) {
|
|
|
|
LOG3(("Http2Session::CleanupStream failed to verify stream\n"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-02-10 19:54:32 +00:00
|
|
|
// don't reset a stream that has recevied a fin or rst
|
|
|
|
if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID() &&
|
|
|
|
!(mInputFrameFinal && (aStream == mInputFrameDataStream))) { // !(recvdfin with mark pending)
|
|
|
|
LOG3(("Stream 0x%X had not processed recv FIN, sending RST code %X\n", aStream->StreamID(), aResetCode));
|
2013-10-10 00:21:49 +00:00
|
|
|
GenerateRstStream(aResetCode, aStream->StreamID());
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseStream(aStream, aResult);
|
|
|
|
|
|
|
|
// Remove the stream from the ID hash table and, if an even id, the pushed
|
|
|
|
// table too.
|
|
|
|
uint32_t id = aStream->StreamID();
|
|
|
|
if (id > 0) {
|
|
|
|
mStreamIDHash.Remove(id);
|
2014-06-04 19:37:00 +00:00
|
|
|
if (!(id & 1)) {
|
2013-10-10 00:21:49 +00:00
|
|
|
mPushedStreams.RemoveElement(aStream);
|
2014-06-04 19:37:00 +00:00
|
|
|
Http2PushedStream *pushStream = static_cast<Http2PushedStream *>(aStream);
|
|
|
|
nsAutoCString hashKey;
|
2017-01-12 09:48:45 +00:00
|
|
|
DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
|
|
|
|
MOZ_ASSERT(rv);
|
2016-04-21 14:59:17 +00:00
|
|
|
nsIRequestContext *requestContext = aStream->RequestContext();
|
|
|
|
if (requestContext) {
|
2014-06-04 19:37:00 +00:00
|
|
|
SpdyPushCache *cache = nullptr;
|
2016-04-21 14:59:17 +00:00
|
|
|
requestContext->GetSpdyPushCache(&cache);
|
2014-06-04 19:37:00 +00:00
|
|
|
if (cache) {
|
2017-06-28 17:29:34 +00:00
|
|
|
// Make sure the id of the stream in the push cache is the same
|
|
|
|
// as the id of the stream we're cleaning up! See bug 1368080.
|
|
|
|
Http2PushedStream *trash = cache->RemovePushedStreamHttp2ByID(hashKey, aStream->StreamID());
|
2014-06-04 19:37:00 +00:00
|
|
|
LOG3(("Http2Session::CleanupStream %p aStream=%p pushStream=%p trash=%p",
|
|
|
|
this, aStream, pushStream, trash));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RemoveStreamFromQueues(aStream);
|
|
|
|
|
|
|
|
// removing from the stream transaction hash will
|
|
|
|
// delete the Http2Stream and drop the reference to
|
|
|
|
// its transaction
|
|
|
|
mStreamTransactionHash.Remove(aStream->Transaction());
|
|
|
|
|
|
|
|
if (mShouldGoAway && !mStreamTransactionHash.Count())
|
|
|
|
Close(NS_OK);
|
|
|
|
|
|
|
|
if (pushSource) {
|
|
|
|
pushSource->SetDeferCleanupOnSuccess(false);
|
|
|
|
CleanupStream(pushSource, aResult, aResetCode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-13 15:02:08 +00:00
|
|
|
void
|
|
|
|
Http2Session::CleanupStream(uint32_t aID, nsresult aResult, errorType aResetCode)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-10-13 15:02:08 +00:00
|
|
|
Http2Stream *stream = mStreamIDHash.Get(aID);
|
|
|
|
LOG3(("Http2Session::CleanupStream %p by ID 0x%X to stream %p\n",
|
|
|
|
this, aID, stream));
|
|
|
|
if (!stream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
CleanupStream(stream, aResult, aResetCode);
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
static void RemoveStreamFromQueue(Http2Stream *aStream, nsDeque &queue)
|
|
|
|
{
|
2015-11-15 13:48:08 +00:00
|
|
|
size_t size = queue.GetSize();
|
|
|
|
for (size_t count = 0; count < size; ++count) {
|
2013-10-10 00:21:49 +00:00
|
|
|
Http2Stream *stream = static_cast<Http2Stream *>(queue.PopFront());
|
|
|
|
if (stream != aStream)
|
|
|
|
queue.Push(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::RemoveStreamFromQueues(Http2Stream *aStream)
|
|
|
|
{
|
|
|
|
RemoveStreamFromQueue(aStream, mReadyForWrite);
|
|
|
|
RemoveStreamFromQueue(aStream, mQueuedStreams);
|
2015-09-11 19:53:27 +00:00
|
|
|
RemoveStreamFromQueue(aStream, mPushesReadyForRead);
|
|
|
|
RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::CloseStream %p %p 0x%x %" PRIX32 "\n",
|
|
|
|
this, aStream, aStream->StreamID(), static_cast<uint32_t>(aResult)));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
MaybeDecrementConcurrent(aStream);
|
|
|
|
|
|
|
|
// Check if partial frame reader
|
|
|
|
if (aStream == mInputFrameDataStream) {
|
|
|
|
LOG3(("Stream had active partial read frame on close"));
|
|
|
|
ChangeDownstreamState(DISCARDING_DATA_FRAME);
|
|
|
|
mInputFrameDataStream = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoveStreamFromQueues(aStream);
|
|
|
|
|
2014-05-16 15:46:13 +00:00
|
|
|
if (aStream->IsTunnel()) {
|
|
|
|
UnRegisterTunnel(aStream);
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// Send the stream the close() indication
|
|
|
|
aStream->Close(aResult);
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::SetInputFrameDataStream(uint32_t streamID)
|
|
|
|
{
|
|
|
|
mInputFrameDataStream = mStreamIDHash.Get(streamID);
|
|
|
|
if (VerifyStream(mInputFrameDataStream, streamID))
|
|
|
|
return NS_OK;
|
|
|
|
|
|
|
|
LOG3(("Http2Session::SetInputFrameDataStream failed to verify 0x%X\n",
|
|
|
|
streamID));
|
|
|
|
mInputFrameDataStream = nullptr;
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::ParsePadding(uint8_t &paddingControlBytes, uint16_t &paddingLength)
|
|
|
|
{
|
2014-06-26 00:33:15 +00:00
|
|
|
if (mInputFrameFlags & kFlag_PADDED) {
|
2015-11-09 02:28:05 +00:00
|
|
|
paddingLength = *reinterpret_cast<uint8_t *>(&mInputFrameBuffer[kFrameHeaderBytes]);
|
2014-06-26 00:33:15 +00:00
|
|
|
paddingControlBytes = 1;
|
2015-08-18 18:57:07 +00:00
|
|
|
} else {
|
|
|
|
paddingLength = 0;
|
|
|
|
paddingControlBytes = 0;
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 18:57:07 +00:00
|
|
|
if (static_cast<uint32_t>(paddingLength + paddingControlBytes) > mInputFrameDataSize) {
|
2014-03-26 17:58:09 +00:00
|
|
|
// This is fatal to the session
|
2014-04-30 01:45:59 +00:00
|
|
|
LOG3(("Http2Session::ParsePadding %p stream 0x%x PROTOCOL_ERROR "
|
2014-03-26 17:58:09 +00:00
|
|
|
"paddingLength %d > frame size %d\n",
|
|
|
|
this, mInputFrameID, paddingLength, mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::RecvHeaders(Http2Session *self)
|
|
|
|
{
|
2015-01-30 19:34:01 +00:00
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_HEADERS ||
|
|
|
|
self->mInputFrameType == FRAME_TYPE_CONTINUATION);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-06-26 00:33:15 +00:00
|
|
|
bool isContinuation = self->mExpectedHeaderID != 0;
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// If this doesn't have END_HEADERS set on it then require the next
|
|
|
|
// frame to be HEADERS of the same ID
|
|
|
|
bool endHeadersFlag = self->mInputFrameFlags & kFlag_END_HEADERS;
|
|
|
|
|
|
|
|
if (endHeadersFlag)
|
|
|
|
self->mExpectedHeaderID = 0;
|
|
|
|
else
|
|
|
|
self->mExpectedHeaderID = self->mInputFrameID;
|
|
|
|
|
2014-04-30 01:45:59 +00:00
|
|
|
uint32_t priorityLen = 0;
|
2014-04-30 01:46:03 +00:00
|
|
|
if (self->mInputFrameFlags & kFlag_PRIORITY) {
|
2014-04-30 01:45:59 +00:00
|
|
|
priorityLen = 5;
|
|
|
|
}
|
2017-01-12 09:48:45 +00:00
|
|
|
nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
// Find out how much padding this frame has, so we can only extract the real
|
|
|
|
// header data from the frame.
|
|
|
|
uint16_t paddingLength = 0;
|
|
|
|
uint8_t paddingControlBytes = 0;
|
|
|
|
|
2014-06-26 00:33:15 +00:00
|
|
|
if (!isContinuation) {
|
2014-07-11 16:48:34 +00:00
|
|
|
self->mDecompressBuffer.Truncate();
|
2014-06-26 00:33:15 +00:00
|
|
|
rv = self->ParsePadding(paddingControlBytes, paddingLength);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return rv;
|
|
|
|
}
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::RecvHeaders %p stream 0x%X priorityLen=%d stream=%p "
|
2014-04-30 01:46:03 +00:00
|
|
|
"end_stream=%d end_headers=%d priority_group=%d "
|
2014-06-26 00:33:15 +00:00
|
|
|
"paddingLength=%d padded=%d\n",
|
2013-10-10 00:21:49 +00:00
|
|
|
self, self->mInputFrameID, priorityLen, self->mInputFrameDataStream,
|
|
|
|
self->mInputFrameFlags & kFlag_END_STREAM,
|
|
|
|
self->mInputFrameFlags & kFlag_END_HEADERS,
|
2014-04-30 01:46:03 +00:00
|
|
|
self->mInputFrameFlags & kFlag_PRIORITY,
|
2014-03-26 17:58:09 +00:00
|
|
|
paddingLength,
|
2014-06-26 00:33:15 +00:00
|
|
|
self->mInputFrameFlags & kFlag_PADDED));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2015-08-18 18:57:07 +00:00
|
|
|
if ((paddingControlBytes + priorityLen + paddingLength) > self->mInputFrameDataSize) {
|
|
|
|
// This is fatal to the session
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2015-08-18 18:57:07 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
if (!self->mInputFrameDataStream) {
|
|
|
|
// Cannot find stream. We can continue the session, but we need to
|
|
|
|
// uncompress the header block to maintain the correct compression context
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvHeaders %p lookup mInputFrameID stream "
|
|
|
|
"0x%X failed. NextStreamID = 0x%X\n",
|
|
|
|
self, self->mInputFrameID, self->mNextStreamID));
|
|
|
|
|
|
|
|
if (self->mInputFrameID >= self->mNextStreamID)
|
|
|
|
self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
|
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen],
|
2014-03-26 17:58:09 +00:00
|
|
|
self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (self->mInputFrameFlags & kFlag_END_HEADERS) {
|
2016-01-04 20:17:39 +00:00
|
|
|
rv = self->UncompressAndDiscard(false);
|
2013-10-10 00:21:49 +00:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::RecvHeaders uncompress failed\n"));
|
|
|
|
// this is fatal to the session
|
|
|
|
self->mGoAwayReason = COMPRESSION_ERROR;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2014-09-27 13:06:38 +00:00
|
|
|
// make sure this is either the first headers or a trailer
|
2014-08-05 15:41:09 +00:00
|
|
|
if (self->mInputFrameDataStream->AllHeadersReceived() &&
|
|
|
|
!(self->mInputFrameFlags & kFlag_END_STREAM)) {
|
|
|
|
// Any header block after the first that does *not* end the stream is
|
|
|
|
// illegal.
|
2014-09-27 13:06:38 +00:00
|
|
|
LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self, self->mInputFrameID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2014-08-05 15:41:09 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// queue up any compression bytes
|
2015-11-09 02:28:05 +00:00
|
|
|
self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen],
|
2014-03-26 17:58:09 +00:00
|
|
|
self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
|
|
|
|
self->mLastDataReadEpoch = self->mLastReadEpoch;
|
|
|
|
|
2016-12-20 18:54:15 +00:00
|
|
|
if (!isContinuation) {
|
|
|
|
self->mAggregatedHeaderSize = self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength;
|
|
|
|
} else {
|
|
|
|
self->mAggregatedHeaderSize += self->mInputFrameDataSize - paddingControlBytes - priorityLen - paddingLength;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
if (!endHeadersFlag) { // more are coming - don't process yet
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2016-12-20 18:54:15 +00:00
|
|
|
if (isContinuation) {
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS, self->mAggregatedHeaderSize);
|
|
|
|
}
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
rv = self->ResponseHeadersComplete();
|
2013-10-10 00:21:49 +00:00
|
|
|
if (rv == NS_ERROR_ILLEGAL_VALUE) {
|
|
|
|
LOG3(("Http2Session::RecvHeaders %p PROTOCOL_ERROR detected stream 0x%X\n",
|
|
|
|
self, self->mInputFrameID));
|
|
|
|
self->CleanupStream(self->mInputFrameDataStream, rv, PROTOCOL_ERROR);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
rv = NS_OK;
|
2015-09-23 02:58:14 +00:00
|
|
|
} else if (NS_FAILED(rv)) {
|
|
|
|
// This is fatal to the session.
|
|
|
|
self->mGoAwayReason = COMPRESSION_ERROR;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResponseHeadersComplete() returns NS_ERROR_ILLEGAL_VALUE when the stream
|
|
|
|
// should be reset with a PROTOCOL_ERROR, NS_OK when the response headers were
|
|
|
|
// fine, and any other error is fatal to the session.
|
|
|
|
nsresult
|
|
|
|
Http2Session::ResponseHeadersComplete()
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::ResponseHeadersComplete %p for 0x%X fin=%d",
|
|
|
|
this, mInputFrameDataStream->StreamID(), mInputFrameFinal));
|
|
|
|
|
2018-02-01 23:14:23 +00:00
|
|
|
// Anything prior to AllHeadersReceived() => true is actual headers. After
|
|
|
|
// that, we need to handle them as trailers instead (which are special-cased
|
|
|
|
// so we don't have to use the nasty chunked parser for all h2, just in case).
|
2014-06-20 03:42:29 +00:00
|
|
|
if (mInputFrameDataStream->AllHeadersReceived()) {
|
2018-02-01 23:14:23 +00:00
|
|
|
LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
|
2014-08-05 15:41:09 +00:00
|
|
|
MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
|
2018-02-01 23:14:23 +00:00
|
|
|
nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(&mDecompressor,
|
|
|
|
mDecompressBuffer);
|
2014-06-20 03:42:29 +00:00
|
|
|
if (NS_FAILED(rv)) {
|
2018-02-01 23:14:23 +00:00
|
|
|
LOG3(("Http2Session::ResponseHeadersComplete trailer conversion failed\n"));
|
2014-06-20 03:42:29 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
mFlatHTTPResponseHeadersOut = 0;
|
|
|
|
mFlatHTTPResponseHeaders.Truncate();
|
|
|
|
if (mInputFrameFinal) {
|
|
|
|
// need to process the fin
|
|
|
|
ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
|
|
|
|
} else {
|
|
|
|
ResetDownstreamState();
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_OK;
|
2014-06-20 03:42:29 +00:00
|
|
|
}
|
2014-09-27 13:06:38 +00:00
|
|
|
|
|
|
|
// if this turns out to be a 1xx response code we have to
|
|
|
|
// undo the headers received bit that we are setting here.
|
|
|
|
bool didFirstSetAllRecvd = !mInputFrameDataStream->AllHeadersReceived();
|
2014-05-19 19:37:35 +00:00
|
|
|
mInputFrameDataStream->SetAllHeadersReceived();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// The stream needs to see flattened http headers
|
|
|
|
// Uncompressed http/2 format headers currently live in
|
|
|
|
// Http2Stream::mDecompressBuffer - convert that to HTTP format in
|
|
|
|
// mFlatHTTPResponseHeaders via ConvertHeaders()
|
|
|
|
|
2014-05-19 19:37:35 +00:00
|
|
|
nsresult rv;
|
2014-09-27 13:06:38 +00:00
|
|
|
int32_t httpResponseCode; // out param to ConvertResponseHeaders
|
2013-10-10 00:21:49 +00:00
|
|
|
mFlatHTTPResponseHeadersOut = 0;
|
2014-05-16 15:46:13 +00:00
|
|
|
rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
|
|
|
|
mDecompressBuffer,
|
2014-09-27 13:06:38 +00:00
|
|
|
mFlatHTTPResponseHeaders,
|
|
|
|
httpResponseCode);
|
2017-05-11 21:42:05 +00:00
|
|
|
if (rv == NS_ERROR_NET_RESET) {
|
|
|
|
LOG(("Http2Session::ResponseHeadersComplete %p ConvertResponseHeaders reset\n", this));
|
|
|
|
// This means the stream found connection-oriented auth. Treat this like we
|
|
|
|
// got a reset with HTTP_1_1_REQUIRED.
|
|
|
|
mInputFrameDataStream->Transaction()->DisableSpdy();
|
|
|
|
CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
|
2014-05-16 15:46:13 +00:00
|
|
|
ResetDownstreamState();
|
|
|
|
return NS_OK;
|
2014-05-19 19:37:35 +00:00
|
|
|
} else if (NS_FAILED(rv)) {
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
2014-05-16 15:46:13 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-09-27 13:06:38 +00:00
|
|
|
// allow more headers in the case of 1xx
|
|
|
|
if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
|
|
|
|
mInputFrameDataStream->UnsetAllHeadersReceived();
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvPriority(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PRIORITY);
|
|
|
|
|
2014-04-30 01:46:03 +00:00
|
|
|
if (self->mInputFrameDataSize != 5) {
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::RecvPriority %p wrong length data=%d\n",
|
|
|
|
self, self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!self->mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::RecvPriority %p stream ID of 0.\n", self));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
uint32_t newPriorityDependency = NetworkEndian::readUint32(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes);
|
2014-04-30 01:46:03 +00:00
|
|
|
bool exclusive = !!(newPriorityDependency & 0x80000000);
|
|
|
|
newPriorityDependency &= 0x7fffffff;
|
2014-08-05 15:41:09 +00:00
|
|
|
uint8_t newPriorityWeight = *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
|
2018-10-03 09:40:23 +00:00
|
|
|
|
|
|
|
// undefined what it means when the server sends a priority frame. ignore it.
|
|
|
|
LOG3(("Http2Session::RecvPriority %p 0x%X received dependency=0x%X "
|
|
|
|
"weight=%u exclusive=%d", self->mInputFrameDataStream, self->mInputFrameID,
|
|
|
|
newPriorityDependency, newPriorityWeight, exclusive));
|
2014-04-30 01:45:59 +00:00
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvRstStream(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_RST_STREAM);
|
|
|
|
|
|
|
|
if (self->mInputFrameDataSize != 4) {
|
|
|
|
LOG3(("Http2Session::RecvRstStream %p RST_STREAM wrong length data=%d",
|
|
|
|
self, self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!self->mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::RecvRstStream %p stream ID of 0.\n", self));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
self->mDownstreamRstReason = NetworkEndian::readUint32(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvRstStream %p RST_STREAM Reason Code %u ID %x\n",
|
|
|
|
self, self->mDownstreamRstReason, self->mInputFrameID));
|
|
|
|
|
2017-01-12 09:48:45 +00:00
|
|
|
DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
2013-10-10 00:21:49 +00:00
|
|
|
if (!self->mInputFrameDataStream) {
|
|
|
|
// if we can't find the stream just ignore it (4.2 closed)
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->mInputFrameDataStream->SetRecvdReset(true);
|
|
|
|
self->MaybeDecrementConcurrent(self->mInputFrameDataStream);
|
|
|
|
self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvSettings(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_SETTINGS);
|
|
|
|
|
|
|
|
if (self->mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n",
|
|
|
|
self, self->mInputFrameID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-06-26 00:33:15 +00:00
|
|
|
if (self->mInputFrameDataSize % 6) {
|
|
|
|
// Number of Settings is determined by dividing by each 6 byte setting
|
|
|
|
// entry. So the payload must be a multiple of 6.
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d",
|
|
|
|
self, self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2017-04-14 21:24:53 +00:00
|
|
|
self->mReceivedSettings = true;
|
|
|
|
|
2014-06-26 00:33:15 +00:00
|
|
|
uint32_t numEntries = self->mInputFrameDataSize / 6;
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::RecvSettings %p SETTINGS Control Frame "
|
|
|
|
"with %d entries ack=%X", self, numEntries,
|
|
|
|
self->mInputFrameFlags & kFlag_ACK));
|
|
|
|
|
|
|
|
if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n", self));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (uint32_t index = 0; index < numEntries; ++index) {
|
|
|
|
uint8_t *setting = reinterpret_cast<uint8_t *>
|
2014-08-05 15:41:09 +00:00
|
|
|
(self->mInputFrameBuffer.get()) + kFrameHeaderBytes + index * 6;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
uint16_t id = NetworkEndian::readUint16(setting);
|
|
|
|
uint32_t value = NetworkEndian::readUint32(setting + 2);
|
2014-06-26 00:33:15 +00:00
|
|
|
LOG3(("Settings ID %u, Value %u", id, value));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
switch (id)
|
|
|
|
{
|
|
|
|
case SETTINGS_TYPE_HEADER_TABLE_SIZE:
|
|
|
|
LOG3(("Compression header table setting received: %d\n", value));
|
|
|
|
self->mCompressor.SetMaxBufferSize(value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SETTINGS_TYPE_ENABLE_PUSH:
|
|
|
|
LOG3(("Client received an ENABLE Push SETTING. Odd.\n"));
|
|
|
|
// nop
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SETTINGS_TYPE_MAX_CONCURRENT:
|
|
|
|
self->mMaxConcurrent = value;
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
|
2015-01-13 20:26:37 +00:00
|
|
|
self->ProcessPending();
|
2013-10-10 00:21:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SETTINGS_TYPE_INITIAL_WINDOW:
|
|
|
|
{
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
|
|
|
|
int32_t delta = value - self->mServerInitialStreamWindow;
|
|
|
|
self->mServerInitialStreamWindow = value;
|
|
|
|
|
2016-01-28 03:04:38 +00:00
|
|
|
// SETTINGS only adjusts stream windows. Leave the session window alone.
|
|
|
|
// We need to add the delta to all open streams (delta can be negative)
|
|
|
|
for (auto iter = self->mStreamTransactionHash.Iter();
|
|
|
|
!iter.Done();
|
|
|
|
iter.Next()) {
|
|
|
|
iter.Data()->UpdateServerReceiveWindow(delta);
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
case SETTINGS_TYPE_MAX_FRAME_SIZE:
|
|
|
|
{
|
|
|
|
if ((value < kMaxFrameData) || (value >= 0x01000000)) {
|
|
|
|
LOG3(("Received invalid max frame size 0x%X", value));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2014-08-05 15:41:09 +00:00
|
|
|
}
|
|
|
|
// We stick to the default for simplicity's sake, so nothing to change
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
|
2014-06-27 20:13:42 +00:00
|
|
|
if (!(self->mInputFrameFlags & kFlag_ACK)) {
|
2013-10-10 00:21:49 +00:00
|
|
|
self->GenerateSettingsAck();
|
2014-06-27 20:13:42 +00:00
|
|
|
} else if (self->mWaitingForSettingsAck) {
|
|
|
|
self->mGoAwayOnPush = true;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvPushPromise(Http2Session *self)
|
|
|
|
{
|
2015-01-30 19:35:20 +00:00
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PUSH_PROMISE ||
|
|
|
|
self->mInputFrameType == FRAME_TYPE_CONTINUATION);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
// Find out how much padding this frame has, so we can only extract the real
|
|
|
|
// header data from the frame.
|
|
|
|
uint16_t paddingLength = 0;
|
|
|
|
uint8_t paddingControlBytes = 0;
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// If this doesn't have END_PUSH_PROMISE set on it then require the next
|
|
|
|
// frame to be PUSH_PROMISE of the same ID
|
|
|
|
uint32_t promiseLen;
|
|
|
|
uint32_t promisedID;
|
|
|
|
|
|
|
|
if (self->mExpectedPushPromiseID) {
|
|
|
|
promiseLen = 0; // really a continuation frame
|
|
|
|
promisedID = self->mContinuedPromiseStream;
|
|
|
|
} else {
|
2014-07-11 16:48:34 +00:00
|
|
|
self->mDecompressBuffer.Truncate();
|
2014-06-26 00:33:15 +00:00
|
|
|
nsresult rv = self->ParsePadding(paddingControlBytes, paddingLength);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return rv;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
promiseLen = 4;
|
2015-04-20 18:07:16 +00:00
|
|
|
promisedID = NetworkEndian::readUint32(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes + paddingControlBytes);
|
2013-10-10 00:21:49 +00:00
|
|
|
promisedID &= 0x7fffffff;
|
2016-08-03 18:36:50 +00:00
|
|
|
if (promisedID <= self->mLastPushedID) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p ID too low %u expected > %u.\n",
|
|
|
|
self, promisedID, self->mLastPushedID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2016-08-03 18:36:50 +00:00
|
|
|
}
|
|
|
|
self->mLastPushedID = promisedID;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t associatedID = self->mInputFrameID;
|
|
|
|
|
|
|
|
if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
|
|
|
|
self->mExpectedPushPromiseID = 0;
|
|
|
|
self->mContinuedPromiseStream = 0;
|
|
|
|
} else {
|
|
|
|
self->mExpectedPushPromiseID = self->mInputFrameID;
|
|
|
|
self->mContinuedPromiseStream = promisedID;
|
|
|
|
}
|
|
|
|
|
2015-08-18 19:16:14 +00:00
|
|
|
if ((paddingControlBytes + promiseLen + paddingLength) > self->mInputFrameDataSize) {
|
2014-03-26 17:58:09 +00:00
|
|
|
// This is fatal to the session
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
|
2015-08-18 19:16:14 +00:00
|
|
|
"PROTOCOL_ERROR extra %d > frame size %d\n",
|
|
|
|
self, promisedID, associatedID, (paddingControlBytes + promiseLen + paddingLength),
|
2014-03-26 17:58:09 +00:00
|
|
|
self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p ID 0x%X assoc ID 0x%X "
|
2014-06-26 00:33:15 +00:00
|
|
|
"paddingLength %d padded %d\n",
|
2014-03-26 17:58:09 +00:00
|
|
|
self, promisedID, associatedID, paddingLength,
|
2014-06-26 00:33:15 +00:00
|
|
|
self->mInputFrameFlags & kFlag_PADDED));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (!associatedID || !promisedID || (promisedID & 1)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p ID invalid.\n", self));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// confirm associated-to
|
2014-06-26 00:33:15 +00:00
|
|
|
nsresult rv = self->SetInputFrameDataStream(associatedID);
|
2013-10-10 00:21:49 +00:00
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
Http2Stream *associatedStream = self->mInputFrameDataStream;
|
|
|
|
++(self->mServerPushedResources);
|
|
|
|
|
|
|
|
// Anytime we start using the high bit of stream ID (either client or server)
|
|
|
|
// begin to migrate to a new session.
|
|
|
|
if (promisedID >= kMaxStreamID)
|
|
|
|
self->mShouldGoAway = true;
|
|
|
|
|
|
|
|
bool resetStream = true;
|
|
|
|
SpdyPushCache *cache = nullptr;
|
|
|
|
|
2016-02-18 22:30:46 +00:00
|
|
|
if (self->mShouldGoAway && !Http2PushedStream::TestOnPush(associatedStream)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p cache push while in GoAway "
|
2013-10-10 00:21:49 +00:00
|
|
|
"mode refused.\n", self));
|
|
|
|
self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
|
|
|
|
} else if (!gHttpHandler->AllowPush()) {
|
2014-06-27 20:13:42 +00:00
|
|
|
// ENABLE_PUSH and MAX_CONCURRENT_STREAMS of 0 in settings disabled push
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::RecvPushPromise Push Recevied when Disabled\n"));
|
2014-06-27 20:13:42 +00:00
|
|
|
if (self->mGoAwayOnPush) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise sending GOAWAY"));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2014-06-27 20:13:42 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
|
2016-03-09 14:20:02 +00:00
|
|
|
} else if (!(associatedID & 1)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p assocated=0x%X on pushed (even) stream not allowed\n",
|
|
|
|
self, associatedID));
|
|
|
|
self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
|
2013-10-10 00:21:49 +00:00
|
|
|
} else if (!associatedStream) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p lookup associated ID failed.\n", self));
|
|
|
|
self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
|
2018-02-10 16:00:45 +00:00
|
|
|
} else if (Http2PushedStream::TestOnPush(associatedStream)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p will be handled by push listener.", self));
|
|
|
|
resetStream = false;
|
2013-10-10 00:21:49 +00:00
|
|
|
} else {
|
2016-04-21 14:59:17 +00:00
|
|
|
nsIRequestContext *requestContext = associatedStream->RequestContext();
|
|
|
|
if (requestContext) {
|
|
|
|
requestContext->GetSpdyPushCache(&cache);
|
2013-10-10 00:21:49 +00:00
|
|
|
if (!cache) {
|
|
|
|
cache = new SpdyPushCache();
|
2016-04-21 14:59:17 +00:00
|
|
|
if (!cache || NS_FAILED(requestContext->SetSpdyPushCache(cache))) {
|
2013-10-10 00:21:49 +00:00
|
|
|
delete cache;
|
|
|
|
cache = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!cache) {
|
|
|
|
// this is unexpected, but we can handle it just by refusing the push
|
2015-07-31 20:50:08 +00:00
|
|
|
LOG3(("Http2Session::RecvPushPromise Push Recevied without push cache\n"));
|
2013-10-10 00:21:49 +00:00
|
|
|
self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
|
|
|
|
} else {
|
|
|
|
resetStream = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resetStream) {
|
|
|
|
// Need to decompress the headers even though we aren't using them yet in
|
|
|
|
// order to keep the compression context consistent for other frames
|
2015-11-09 02:28:05 +00:00
|
|
|
self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + promiseLen],
|
2014-03-26 17:58:09 +00:00
|
|
|
self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
|
2013-10-10 00:21:49 +00:00
|
|
|
if (self->mInputFrameFlags & kFlag_END_PUSH_PROMISE) {
|
2016-01-04 20:17:39 +00:00
|
|
|
rv = self->UncompressAndDiscard(true);
|
2013-10-10 00:21:49 +00:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise uncompress failed\n"));
|
|
|
|
self->mGoAwayReason = COMPRESSION_ERROR;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
self->mDecompressBuffer.Append(&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + promiseLen],
|
2015-01-30 19:35:20 +00:00
|
|
|
self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
|
|
|
|
|
2016-12-20 18:54:15 +00:00
|
|
|
if (self->mInputFrameType != FRAME_TYPE_CONTINUATION) {
|
|
|
|
self->mAggregatedHeaderSize = self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength;
|
|
|
|
} else {
|
|
|
|
self->mAggregatedHeaderSize += self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength;
|
|
|
|
}
|
|
|
|
|
2015-01-30 19:35:20 +00:00
|
|
|
if (!(self->mInputFrameFlags & kFlag_END_PUSH_PROMISE)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise not finishing processing for multi-frame push\n"));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2016-12-20 18:54:15 +00:00
|
|
|
if (self->mInputFrameType == FRAME_TYPE_CONTINUATION) {
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS, self->mAggregatedHeaderSize);
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// Create the buffering transaction and push stream
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<Http2PushTransactionBuffer> transactionBuffer =
|
2013-10-10 00:21:49 +00:00
|
|
|
new Http2PushTransactionBuffer();
|
|
|
|
transactionBuffer->SetConnection(self);
|
|
|
|
Http2PushedStream *pushedStream =
|
2017-08-30 06:19:00 +00:00
|
|
|
new Http2PushedStream(transactionBuffer,
|
|
|
|
self,
|
|
|
|
associatedStream,
|
|
|
|
promisedID,
|
|
|
|
self->mCurrentForegroundTabOuterContentWindowId);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
|
2014-10-21 18:35:41 +00:00
|
|
|
self->mDecompressBuffer,
|
|
|
|
pushedStream->GetRequestString());
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (rv == NS_ERROR_NOT_IMPLEMENTED) {
|
|
|
|
LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
|
|
|
|
self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
|
2014-10-21 18:35:41 +00:00
|
|
|
delete pushedStream;
|
2016-03-03 16:48:30 +00:00
|
|
|
self->ResetDownstreamState();
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2015-09-23 02:58:14 +00:00
|
|
|
if (rv == NS_ERROR_ILLEGAL_VALUE) {
|
|
|
|
// This means the decompression completed ok, but there was a problem with
|
|
|
|
// the decoded headers. Reset the stream and go away.
|
|
|
|
self->GenerateRstStream(PROTOCOL_ERROR, promisedID);
|
|
|
|
delete pushedStream;
|
2016-03-03 16:48:30 +00:00
|
|
|
self->ResetDownstreamState();
|
2015-09-23 02:58:14 +00:00
|
|
|
return NS_OK;
|
|
|
|
} else if (NS_FAILED(rv)) {
|
|
|
|
// This is fatal to the session.
|
|
|
|
self->mGoAwayReason = COMPRESSION_ERROR;
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
2015-09-23 02:58:14 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-10-21 18:35:41 +00:00
|
|
|
// Ownership of the pushed stream is by the transaction hash, just as it
|
|
|
|
// is for a client initiated stream. Errors that aren't fatal to the
|
|
|
|
// whole session must call cleanupStream() after this point in order
|
|
|
|
// to remove the stream from that hash.
|
|
|
|
self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
|
|
|
|
self->mPushedStreams.AppendElement(pushedStream);
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
|
|
|
|
self->mGoAwayReason = INTERNAL_ERROR;
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (promisedID > self->mOutgoingGoAwayID)
|
|
|
|
self->mOutgoingGoAwayID = promisedID;
|
|
|
|
|
|
|
|
// Fake the request side of the pushed HTTP transaction. Sets up hash
|
|
|
|
// key and origin
|
|
|
|
uint32_t notUsed;
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << pushedStream->ReadSegments(nullptr, 1, ¬Used);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
nsAutoCString key;
|
|
|
|
if (!pushedStream->GetHashKey(key)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise one of :authority :scheme :path missing from push\n"));
|
|
|
|
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, PROTOCOL_ERROR);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
// does the pushed origin belong on this connection?
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
|
|
|
|
pushedStream->Origin().get()));
|
2018-01-24 19:33:02 +00:00
|
|
|
nsCOMPtr<nsIURI> pushedOrigin;
|
2017-06-28 17:34:55 +00:00
|
|
|
rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedOrigin);
|
2017-04-03 21:23:55 +00:00
|
|
|
nsAutoCString pushedHostName;
|
|
|
|
int32_t pushedPort = -1;
|
2014-11-03 15:19:00 +00:00
|
|
|
if (NS_SUCCEEDED(rv)) {
|
2017-06-28 17:34:55 +00:00
|
|
|
rv = pushedOrigin->GetHost(pushedHostName);
|
2014-11-03 15:19:00 +00:00
|
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
2017-06-28 17:34:55 +00:00
|
|
|
rv = pushedOrigin->GetPort(&pushedPort);
|
2017-09-18 23:09:57 +00:00
|
|
|
if (NS_SUCCEEDED(rv) && pushedPort == -1) {
|
|
|
|
// Need to get the right default port, so TestJoinConnection below can
|
|
|
|
// check things correctly. See bug 1397621.
|
|
|
|
bool isHttp = false;
|
|
|
|
if (NS_SUCCEEDED(pushedOrigin->SchemeIs("http", &isHttp)) && isHttp) {
|
|
|
|
pushedPort = NS_HTTP_DEFAULT_PORT;
|
|
|
|
} else {
|
|
|
|
pushedPort = NS_HTTPS_DEFAULT_PORT;
|
|
|
|
}
|
|
|
|
}
|
2014-11-03 15:19:00 +00:00
|
|
|
}
|
2017-04-03 21:23:55 +00:00
|
|
|
if (NS_FAILED(rv) ||
|
|
|
|
!self->TestJoinConnection(pushedHostName, pushedPort)) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin %s\n",
|
|
|
|
self, pushedStream->Origin().get()));
|
2013-10-10 00:21:49 +00:00
|
|
|
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2014-10-21 18:35:41 +00:00
|
|
|
if (pushedStream->TryOnPush()) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
|
|
|
|
"stream %p will not be placed into session cache.\n", self, pushedStream));
|
|
|
|
} else {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n", self));
|
|
|
|
if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
|
2017-06-28 17:28:54 +00:00
|
|
|
// This only happens if they've already pushed us this item.
|
2014-10-21 18:35:41 +00:00
|
|
|
LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
|
2017-06-28 17:28:54 +00:00
|
|
|
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
|
2014-10-21 18:35:41 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2017-06-28 17:34:55 +00:00
|
|
|
|
|
|
|
// Kick off a lookup into the HTTP cache so we can cancel the push if it's
|
|
|
|
// unneeded (we already have it in our local regular cache). See bug 1367551.
|
|
|
|
nsCOMPtr<nsICacheStorageService> css =
|
|
|
|
do_GetService("@mozilla.org/netwerk/cache-storage-service;1");
|
|
|
|
mozilla::OriginAttributes oa;
|
|
|
|
pushedStream->GetOriginAttributes(&oa);
|
|
|
|
RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, oa);
|
|
|
|
nsCOMPtr<nsICacheStorage> ds;
|
|
|
|
css->DiskCacheStorage(lci, false, getter_AddRefs(ds));
|
|
|
|
// Build up our full URL for the cache lookup
|
|
|
|
nsAutoCString spec;
|
|
|
|
spec.Assign(pushedStream->Origin());
|
|
|
|
spec.Append(pushedStream->Path());
|
2018-01-24 19:33:02 +00:00
|
|
|
nsCOMPtr<nsIURI> pushedURL;
|
2017-06-28 17:34:55 +00:00
|
|
|
// Nifty trick: this doesn't actually do anything origin-specific, it's just
|
|
|
|
// named that way. So by passing it the full spec here, we get a URL with
|
|
|
|
// the full path.
|
|
|
|
// Another nifty trick! Even though this is using nsIURIs (which are not
|
|
|
|
// generally ok off the main thread), since we're not using the protocol
|
|
|
|
// handler to create any URIs, this will work just fine here. Don't try this
|
|
|
|
// at home, though, kids. I'm a trained professional.
|
|
|
|
if (NS_SUCCEEDED(Http2Stream::MakeOriginURL(spec, pushedURL))) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry", self));
|
|
|
|
RefPtr<CachePushCheckCallback> cpcc = new CachePushCheckCallback(self, promisedID, pushedStream->GetRequestString());
|
|
|
|
if (NS_FAILED(ds->AsyncOpenURI(pushedURL, EmptyCString(), nsICacheStorage::OPEN_READONLY|nsICacheStorage::OPEN_SECRETLY, cpcc))) {
|
|
|
|
LOG3(("Http2Session::RecvPushPromise %p failed to open cache entry for push check", self));
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-10-21 18:35:41 +00:00
|
|
|
pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
|
2013-10-10 00:21:49 +00:00
|
|
|
static_assert(Http2Stream::kWorstPriority >= 0,
|
|
|
|
"kWorstPriority out of range");
|
2018-10-03 09:40:23 +00:00
|
|
|
uint32_t priorityDependency = pushedStream->PriorityDependency();
|
|
|
|
uint8_t priorityWeight = pushedStream->PriorityWeight();
|
|
|
|
self->SendPriorityFrame(promisedID, priorityDependency, priorityWeight);
|
2013-10-10 00:21:49 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2017-06-28 17:34:55 +00:00
|
|
|
NS_IMPL_ISUPPORTS(Http2Session::CachePushCheckCallback, nsICacheEntryOpenCallback);
|
|
|
|
|
|
|
|
Http2Session::CachePushCheckCallback::CachePushCheckCallback(Http2Session *session, uint32_t promisedID, const nsACString &requestString)
|
|
|
|
:mPromisedID(promisedID)
|
|
|
|
{
|
|
|
|
mSession = session;
|
|
|
|
mRequestHead.ParseHeaderSet(requestString.BeginReading());
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
Http2Session::CachePushCheckCallback::OnCacheEntryCheck(nsICacheEntry *entry, nsIApplicationCache *appCache, uint32_t *result)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "Not on socket thread?!");
|
|
|
|
|
|
|
|
// We never care to fully open the entry, since we won't actually use it.
|
|
|
|
// We just want to be able to do all our checks to see if a future channel can
|
|
|
|
// use this entry, or if we need to accept the push.
|
|
|
|
*result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
|
|
|
|
|
|
|
|
bool isForcedValid = false;
|
|
|
|
entry->GetIsForcedValid(&isForcedValid);
|
|
|
|
|
|
|
|
nsHttpResponseHead cachedResponseHead;
|
|
|
|
nsresult rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
// Couldn't make sense of what's in the cache entry, go ahead and accept
|
|
|
|
// the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((cachedResponseHead.Status() / 100) != 2) {
|
|
|
|
// Assume the push is sending us a success, while we don't have one in the
|
|
|
|
// cache, so we'll accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the method that was used to generate the cached response
|
2017-08-16 03:58:35 +00:00
|
|
|
nsCString buf;
|
2017-06-28 17:34:55 +00:00
|
|
|
rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
// Can't check request method, accept the push
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
nsAutoCString pushedMethod;
|
|
|
|
mRequestHead.Method(pushedMethod);
|
|
|
|
if (!buf.Equals(pushedMethod)) {
|
|
|
|
// Methods don't match, accept the push
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t size, contentLength;
|
|
|
|
rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
// Couldn't figure out if this was partial or not, accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size == int64_t(-1) || contentLength != size) {
|
|
|
|
// This is partial content in the cache, accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoCString requestedETag;
|
|
|
|
if (NS_FAILED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
|
|
|
|
// Can't check etag
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
if (!requestedETag.IsEmpty()) {
|
|
|
|
nsAutoCString cachedETag;
|
|
|
|
if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
|
|
|
|
// Can't check etag
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
if (!requestedETag.Equals(cachedETag)) {
|
|
|
|
// ETags don't match, accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoCString imsString;
|
|
|
|
Unused << mRequestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
|
|
|
|
if (!buf.IsEmpty()) {
|
|
|
|
uint32_t ims = buf.ToInteger(&rv);
|
|
|
|
uint32_t lm;
|
|
|
|
rv = cachedResponseHead.GetLastModifiedValue(&lm);
|
|
|
|
if (NS_SUCCEEDED(rv) && lm && lm < ims) {
|
|
|
|
// The push appears to be newer than what's in our cache, accept it.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoCString cacheControlRequestHeader;
|
|
|
|
Unused << mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
|
|
|
|
CacheControlParser cacheControlRequest(cacheControlRequestHeader);
|
|
|
|
if (cacheControlRequest.NoStore()) {
|
|
|
|
// Don't use a no-store cache entry, accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2017-08-16 03:58:35 +00:00
|
|
|
nsCString cachedAuth;
|
2017-06-28 17:34:55 +00:00
|
|
|
rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
uint32_t lastModifiedTime;
|
|
|
|
rv = entry->GetLastModified(&lastModifiedTime);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
if ((gHttpHandler->SessionStartTime() > lastModifiedTime) && !cachedAuth.IsEmpty()) {
|
|
|
|
// Need to revalidate this, as the auth is old. Accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cachedAuth.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization)) {
|
|
|
|
// They're pushing us something with auth, but we didn't cache anything
|
|
|
|
// with auth. Accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool weaklyFramed, isImmutable;
|
|
|
|
nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
|
|
|
|
&weaklyFramed, &isImmutable);
|
|
|
|
|
|
|
|
// We'll need this value in later computations...
|
|
|
|
uint32_t lastModifiedTime;
|
|
|
|
rv = entry->GetLastModified(&lastModifiedTime);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
// Ugh, this really sucks. OK, accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if this is the first time that this cache entry
|
|
|
|
// has been accessed during this session.
|
|
|
|
bool fromPreviousSession =
|
|
|
|
(gHttpHandler->SessionStartTime() > lastModifiedTime);
|
|
|
|
|
|
|
|
bool validationRequired = nsHttp::ValidationRequired(isForcedValid,
|
|
|
|
&cachedResponseHead, 0/*NWGH: ??? - loadFlags*/, false, isImmutable, false, mRequestHead, entry,
|
|
|
|
cacheControlRequest, fromPreviousSession);
|
|
|
|
|
|
|
|
if (validationRequired) {
|
|
|
|
// A real channel would most likely hit the net at this point, so let's
|
|
|
|
// accept the push.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here, then we would be able to use this cache entry. Cancel the
|
|
|
|
// push so as not to waste any more bandwidth.
|
|
|
|
mSession->CleanupStream(mPromisedID, NS_ERROR_FAILURE, Http2Session::REFUSED_STREAM_ERROR);
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
Http2Session::CachePushCheckCallback::OnCacheEntryAvailable(
|
|
|
|
nsICacheEntry *entry, bool isNew, nsIApplicationCache *appCache,
|
|
|
|
nsresult result)
|
|
|
|
{
|
|
|
|
// Nothing to do here, all the work is in OnCacheEntryCheck.
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::RecvPing(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_PING);
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvPing %p PING Flags 0x%X.", self,
|
|
|
|
self->mInputFrameFlags));
|
|
|
|
|
|
|
|
if (self->mInputFrameDataSize != 8) {
|
|
|
|
LOG3(("Http2Session::RecvPing %p PING had wrong amount of data %d",
|
|
|
|
self, self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(FRAME_SIZE_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (self->mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::RecvPing %p PING needs stream ID of 0. 0x%X\n",
|
|
|
|
self, self->mInputFrameID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (self->mInputFrameFlags & kFlag_ACK) {
|
|
|
|
// presumably a reply to our timeout ping.. don't reply to it
|
|
|
|
self->mPingSentEpoch = 0;
|
|
|
|
} else {
|
|
|
|
// reply with a ack'd ping
|
|
|
|
self->GeneratePing(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvGoAway(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_GOAWAY);
|
|
|
|
|
|
|
|
if (self->mInputFrameDataSize < 8) {
|
|
|
|
// data > 8 is an opaque token that we can't interpret. NSPR Logs will
|
|
|
|
// have the hex of all packets so there is no point in separately logging.
|
|
|
|
LOG3(("Http2Session::RecvGoAway %p GOAWAY had wrong amount of data %d",
|
|
|
|
self, self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (self->mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::RecvGoAway %p GOAWAY had non zero stream ID 0x%X\n",
|
|
|
|
self, self->mInputFrameID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self->mShouldGoAway = true;
|
2015-04-20 18:07:16 +00:00
|
|
|
self->mGoAwayID = NetworkEndian::readUint32(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes);
|
2013-10-10 00:21:49 +00:00
|
|
|
self->mGoAwayID &= 0x7fffffff;
|
|
|
|
self->mCleanShutdown = true;
|
2015-09-15 01:55:06 +00:00
|
|
|
self->mPeerGoAwayReason = NetworkEndian::readUint32(
|
2015-04-20 18:07:16 +00:00
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Find streams greater than the last-good ID and mark them for deletion
|
2016-01-28 03:04:38 +00:00
|
|
|
// in the mGoAwayStreamsToRestart queue. The underlying transaction can be
|
|
|
|
// restarted.
|
|
|
|
for (auto iter = self->mStreamTransactionHash.Iter();
|
|
|
|
!iter.Done();
|
|
|
|
iter.Next()) {
|
|
|
|
// these streams were not processed by the server and can be restarted.
|
|
|
|
// Do that after the enumerator completes to avoid the risk of
|
|
|
|
// a restart event re-entrantly modifying this hash. Be sure not to restart
|
|
|
|
// a pushed (even numbered) stream
|
|
|
|
nsAutoPtr<Http2Stream>& stream = iter.Data();
|
|
|
|
if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) ||
|
|
|
|
!stream->HasRegisteredID()) {
|
|
|
|
self->mGoAwayStreamsToRestart.Push(stream);
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Process the streams marked for deletion and restart.
|
2015-11-15 13:48:08 +00:00
|
|
|
size_t size = self->mGoAwayStreamsToRestart.GetSize();
|
|
|
|
for (size_t count = 0; count < size; ++count) {
|
2013-10-10 00:21:49 +00:00
|
|
|
Http2Stream *stream =
|
|
|
|
static_cast<Http2Stream *>(self->mGoAwayStreamsToRestart.PopFront());
|
|
|
|
|
2015-09-15 01:55:06 +00:00
|
|
|
if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
|
2014-11-06 14:26:00 +00:00
|
|
|
stream->Transaction()->DisableSpdy();
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
self->CloseStream(stream, NS_ERROR_NET_RESET);
|
|
|
|
if (stream->HasRegisteredID())
|
|
|
|
self->mStreamIDHash.Remove(stream->StreamID());
|
|
|
|
self->mStreamTransactionHash.Remove(stream->Transaction());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Queued streams can also be deleted from this session and restarted
|
|
|
|
// in another one. (they were never sent on the network so they implicitly
|
|
|
|
// are not covered by the last-good id.
|
|
|
|
size = self->mQueuedStreams.GetSize();
|
2015-11-15 13:48:08 +00:00
|
|
|
for (size_t count = 0; count < size; ++count) {
|
2013-10-10 00:21:49 +00:00
|
|
|
Http2Stream *stream =
|
|
|
|
static_cast<Http2Stream *>(self->mQueuedStreams.PopFront());
|
2015-01-13 20:26:37 +00:00
|
|
|
MOZ_ASSERT(stream->Queued());
|
|
|
|
stream->SetQueued(false);
|
2015-09-15 01:55:06 +00:00
|
|
|
if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
|
2014-11-06 14:26:00 +00:00
|
|
|
stream->Transaction()->DisableSpdy();
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
self->CloseStream(stream, NS_ERROR_NET_RESET);
|
|
|
|
self->mStreamTransactionHash.Remove(stream->Transaction());
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X "
|
2015-09-15 01:55:06 +00:00
|
|
|
"live streams=%d\n", self, self->mGoAwayID, self->mPeerGoAwayReason,
|
2013-10-10 00:21:49 +00:00
|
|
|
self->mStreamTransactionHash.Count()));
|
|
|
|
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvWindowUpdate(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_WINDOW_UPDATE);
|
|
|
|
|
|
|
|
if (self->mInputFrameDataSize != 4) {
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p Window Update wrong length %d\n",
|
|
|
|
self, self->mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
uint32_t delta = NetworkEndian::readUint32(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes);
|
2013-10-10 00:21:49 +00:00
|
|
|
delta &= 0x7fffffff;
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p len=%d Stream 0x%X.\n",
|
|
|
|
self, delta, self->mInputFrameID));
|
|
|
|
|
|
|
|
if (self->mInputFrameID) { // stream window
|
|
|
|
nsresult rv = self->SetInputFrameDataStream(self->mInputFrameID);
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
if (!self->mInputFrameDataStream) {
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p lookup streamID 0x%X failed.\n",
|
|
|
|
self, self->mInputFrameID));
|
|
|
|
// only resest the session if the ID is one we haven't ever opened
|
|
|
|
if (self->mInputFrameID >= self->mNextStreamID)
|
|
|
|
self->GenerateRstStream(PROTOCOL_ERROR, self->mInputFrameID);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
if (delta == 0) {
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p received 0 stream window update",
|
|
|
|
self));
|
|
|
|
self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
|
|
|
|
PROTOCOL_ERROR);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
int64_t oldRemoteWindow = self->mInputFrameDataStream->ServerReceiveWindow();
|
|
|
|
self->mInputFrameDataStream->UpdateServerReceiveWindow(delta);
|
|
|
|
if (self->mInputFrameDataStream->ServerReceiveWindow() >= 0x80000000) {
|
|
|
|
// a window cannot reach 2^31 and be in compliance. Our calculations
|
|
|
|
// are 64 bit safe though.
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p stream window "
|
|
|
|
"exceeds 2^31 - 1\n", self));
|
|
|
|
self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE,
|
|
|
|
FLOW_CONTROL_ERROR);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p stream 0x%X window "
|
2016-12-16 03:16:31 +00:00
|
|
|
"%" PRId64 " increased by %" PRIu32 " now %" PRId64 ".\n",
|
|
|
|
self, self->mInputFrameID, oldRemoteWindow, delta, oldRemoteWindow + delta));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
} else { // session window update
|
2014-08-05 15:41:09 +00:00
|
|
|
if (delta == 0) {
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p received 0 session window update",
|
|
|
|
self));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2014-08-05 15:41:09 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
int64_t oldRemoteWindow = self->mServerSessionWindow;
|
|
|
|
self->mServerSessionWindow += delta;
|
|
|
|
|
|
|
|
if (self->mServerSessionWindow >= 0x80000000) {
|
|
|
|
// a window cannot reach 2^31 and be in compliance. Our calculations
|
|
|
|
// are 64 bit safe though.
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p session window "
|
|
|
|
"exceeds 2^31 - 1\n", self));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(FLOW_CONTROL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((oldRemoteWindow <= 0) && (self->mServerSessionWindow > 0)) {
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p restart session window\n",
|
|
|
|
self));
|
2016-01-28 03:18:33 +00:00
|
|
|
for (auto iter = self->mStreamTransactionHash.Iter();
|
|
|
|
!iter.Done();
|
|
|
|
iter.Next()) {
|
|
|
|
MOZ_ASSERT(self->mServerSessionWindow > 0);
|
|
|
|
|
|
|
|
nsAutoPtr<Http2Stream>& stream = iter.Data();
|
|
|
|
if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->mReadyForWrite.Push(stream);
|
|
|
|
self->SetWriteCallbacks();
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
LOG3(("Http2Session::RecvWindowUpdate %p session window "
|
2016-12-16 03:16:31 +00:00
|
|
|
"%" PRId64 " increased by %d now %" PRId64 ".\n", self,
|
2013-10-10 00:21:49 +00:00
|
|
|
oldRemoteWindow, delta, oldRemoteWindow + delta));
|
|
|
|
}
|
|
|
|
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvContinuation(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_CONTINUATION);
|
|
|
|
MOZ_ASSERT(self->mInputFrameID);
|
|
|
|
MOZ_ASSERT(self->mExpectedPushPromiseID || self->mExpectedHeaderID);
|
|
|
|
MOZ_ASSERT(!(self->mExpectedPushPromiseID && self->mExpectedHeaderID));
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvContinuation %p Flags 0x%X id 0x%X "
|
|
|
|
"promise id 0x%X header id 0x%X\n",
|
|
|
|
self, self->mInputFrameFlags, self->mInputFrameID,
|
|
|
|
self->mExpectedPushPromiseID, self->mExpectedHeaderID));
|
|
|
|
|
2017-01-12 09:48:45 +00:00
|
|
|
DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (!self->mInputFrameDataStream) {
|
|
|
|
LOG3(("Http2Session::RecvContination stream ID 0x%X not found.",
|
|
|
|
self->mInputFrameID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return self->SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// continued headers
|
|
|
|
if (self->mExpectedHeaderID) {
|
2014-04-30 01:46:03 +00:00
|
|
|
self->mInputFrameFlags &= ~kFlag_PRIORITY;
|
2013-10-10 00:21:49 +00:00
|
|
|
return RecvHeaders(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
// continued push promise
|
|
|
|
if (self->mInputFrameFlags & kFlag_END_HEADERS) {
|
|
|
|
self->mInputFrameFlags &= ~kFlag_END_HEADERS;
|
|
|
|
self->mInputFrameFlags |= kFlag_END_PUSH_PROMISE;
|
|
|
|
}
|
|
|
|
return RecvPushPromise(self);
|
|
|
|
}
|
|
|
|
|
2016-04-26 00:23:21 +00:00
|
|
|
class UpdateAltSvcEvent : public Runnable
|
2014-08-21 14:50:17 +00:00
|
|
|
{
|
|
|
|
public:
|
2017-06-12 19:34:10 +00:00
|
|
|
UpdateAltSvcEvent(const nsCString& header,
|
|
|
|
const nsCString& aOrigin,
|
|
|
|
nsHttpConnectionInfo* aCI,
|
|
|
|
nsIInterfaceRequestor* callbacks)
|
|
|
|
: Runnable("net::UpdateAltSvcEvent")
|
|
|
|
, mHeader(header)
|
2014-08-21 14:50:17 +00:00
|
|
|
, mOrigin(aOrigin)
|
|
|
|
, mCI(aCI)
|
|
|
|
, mCallbacks(callbacks)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-03-21 16:28:04 +00:00
|
|
|
NS_IMETHOD Run() override
|
2014-08-21 14:50:17 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
nsCString originScheme;
|
|
|
|
nsCString originHost;
|
|
|
|
int32_t originPort = -1;
|
|
|
|
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
|
|
if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mOrigin))) {
|
|
|
|
LOG(("UpdateAltSvcEvent origin does not parse %s\n",
|
|
|
|
mOrigin.get()));
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
uri->GetScheme(originScheme);
|
|
|
|
uri->GetHost(originHost);
|
|
|
|
uri->GetPort(&originPort);
|
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
|
|
|
|
mCI->GetUsername(), mCI->GetPrivate(), mCallbacks,
|
2016-08-04 05:17:00 +00:00
|
|
|
mCI->ProxyInfo(), 0, mCI->GetOriginAttributes());
|
2014-08-21 14:50:17 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-02-06 22:45:05 +00:00
|
|
|
nsCString mHeader;
|
2014-08-21 14:50:17 +00:00
|
|
|
nsCString mOrigin;
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsHttpConnectionInfo> mCI;
|
2014-08-21 14:50:17 +00:00
|
|
|
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
|
|
|
|
};
|
|
|
|
|
|
|
|
// defined as an http2 extension - alt-svc
|
2015-02-06 22:45:05 +00:00
|
|
|
// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft -06 sec 4
|
|
|
|
// as this is an extension, never generate protocol error - just ignore problems
|
2014-08-21 14:50:17 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::RecvAltSvc(Http2Session *self)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ALTSVC);
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
|
|
|
|
self->mInputFrameFlags, self->mInputFrameID));
|
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
if (self->mInputFrameDataSize < 2) {
|
2014-08-21 14:50:17 +00:00
|
|
|
LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
|
2015-02-06 22:45:05 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
2014-08-21 14:50:17 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 18:07:16 +00:00
|
|
|
uint16_t originLen = NetworkEndian::readUint16(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes);
|
2015-02-06 22:45:05 +00:00
|
|
|
if (originLen + 2U > self->mInputFrameDataSize) {
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
2014-08-21 14:50:17 +00:00
|
|
|
}
|
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
if (!gHttpHandler->AllowAltSvc()) {
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
|
2014-08-21 14:50:17 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
uint16_t altSvcFieldValueLen = static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
|
|
|
|
self, originLen, altSvcFieldValueLen));
|
|
|
|
|
|
|
|
if (self->mInputFrameDataSize > 2000) {
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
2014-08-21 14:50:17 +00:00
|
|
|
}
|
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
nsAutoCString origin;
|
|
|
|
bool impliedOrigin = true;
|
|
|
|
if (originLen) {
|
|
|
|
origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2, originLen);
|
|
|
|
impliedOrigin = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoCString altSvcFieldValue;
|
|
|
|
if (altSvcFieldValueLen) {
|
|
|
|
altSvcFieldValue.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
|
|
|
|
altSvcFieldValueLen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (altSvcFieldValue.IsEmpty() || !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
|
|
|
|
LOG(("Http2Session %p Alt-Svc Response Header seems unreasonable - skipping\n", self));
|
2014-08-21 14:50:17 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
if (self->mInputFrameID & 1) {
|
|
|
|
// pulled streams apply to the origin of the pulled stream.
|
|
|
|
// If the origin field is filled in the frame, the frame should be ignored
|
|
|
|
if (!origin.IsEmpty()) {
|
|
|
|
LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2017-07-06 12:00:35 +00:00
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
|
|
|
|
!self->mInputFrameDataStream->Transaction() ||
|
|
|
|
!self->mInputFrameDataStream->Transaction()->RequestHead()) {
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2014-08-21 14:50:17 +00:00
|
|
|
|
2016-05-02 22:06:00 +00:00
|
|
|
self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin);
|
2015-02-06 22:45:05 +00:00
|
|
|
} else if (!self->mInputFrameID) {
|
|
|
|
// ID 0 streams must supply their own origin
|
|
|
|
if (origin.IsEmpty()) {
|
|
|
|
LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
|
2014-08-21 14:50:17 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2015-02-06 22:45:05 +00:00
|
|
|
} else {
|
|
|
|
// handling of push streams is not defined. Let's ignore it
|
2016-06-27 23:12:28 +00:00
|
|
|
LOG(("Http2Session %p Alt-Svc received on pushed stream - ignoring\n", self));
|
2015-02-06 22:45:05 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
|
2015-02-06 22:45:05 +00:00
|
|
|
if (!self->mConnection || !ci) {
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p no connection or conninfo for %d", self,
|
|
|
|
self->mInputFrameID));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2014-08-21 14:50:17 +00:00
|
|
|
|
2015-02-06 22:45:05 +00:00
|
|
|
if (!impliedOrigin) {
|
2014-08-21 14:50:17 +00:00
|
|
|
bool okToReroute = true;
|
|
|
|
nsCOMPtr<nsISupports> securityInfo;
|
|
|
|
self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
|
|
|
|
nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
|
|
|
|
if (!ssl) {
|
|
|
|
okToReroute = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// a little off main thread origin parser. This is a non critical function because
|
|
|
|
// any alternate route created has to be verified anyhow
|
|
|
|
nsAutoCString specifiedOriginHost;
|
2015-02-06 22:45:05 +00:00
|
|
|
if (origin.EqualsIgnoreCase("https://", 8)) {
|
|
|
|
specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
|
|
|
|
} else if (origin.EqualsIgnoreCase("http://", 7)) {
|
|
|
|
specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
|
2014-08-21 14:50:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
|
|
|
|
if (colonOffset != kNotFound) {
|
|
|
|
specifiedOriginHost.Truncate(colonOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (okToReroute) {
|
|
|
|
ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
|
|
|
|
}
|
2015-02-06 22:45:05 +00:00
|
|
|
|
2014-08-21 14:50:17 +00:00
|
|
|
if (!okToReroute) {
|
|
|
|
LOG3(("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin %s",
|
2015-02-06 22:45:05 +00:00
|
|
|
self, origin.BeginReading()));
|
2014-08-21 14:50:17 +00:00
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsISupports> callbacks;
|
|
|
|
self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
|
|
|
|
nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<UpdateAltSvcEvent> event =
|
2015-02-06 22:45:05 +00:00
|
|
|
new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
|
2014-08-21 14:50:17 +00:00
|
|
|
NS_DispatchToMainThread(event);
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
void
|
|
|
|
Http2Session::Received421(nsHttpConnectionInfo *ci)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2017-04-03 21:23:55 +00:00
|
|
|
LOG3(("Http2Session::Recevied421 %p %d\n", this, mOriginFrameActivated));
|
|
|
|
if (!mOriginFrameActivated || !ci) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoCString key(ci->GetOrigin());
|
|
|
|
key.Append(':');
|
|
|
|
key.AppendInt(ci->OriginPort());
|
|
|
|
mOriginFrame.Remove(key);
|
|
|
|
LOG3(("Http2Session::Received421 %p key %s removed\n", this, key.get()));
|
|
|
|
}
|
|
|
|
|
2017-04-11 01:47:07 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::RecvUnused(Http2Session *self)
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session %p unknown frame type %x ignored\n",
|
|
|
|
self, self->mInputFrameType));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
// defined as an http2 extension - origin
|
|
|
|
// defines receipt of frame type 0x0b.. http://httpwg.org/http-extensions/origin-frame.html
|
|
|
|
// as this is an extension, never generate protocol error - just ignore problems
|
|
|
|
nsresult
|
|
|
|
Http2Session::RecvOrigin(Http2Session *self)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2017-04-03 21:23:55 +00:00
|
|
|
MOZ_ASSERT(self->mInputFrameType == FRAME_TYPE_ORIGIN);
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p Flags 0x%X id 0x%X\n", self,
|
|
|
|
self->mInputFrameFlags, self->mInputFrameID));
|
|
|
|
|
|
|
|
if (self->mInputFrameFlags & 0x0F) {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p leading flags must be 0", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self->mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p not stream 0", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self->ConnectionInfo()->UsingProxy()) {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p must not use proxy", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!gHttpHandler->AllowOriginExtension()) {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin extension pref'd off", self));
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t offset = 0;
|
|
|
|
self->mOriginFrameActivated = true;
|
|
|
|
|
|
|
|
while (self->mInputFrameDataSize >= (offset + 2U)) {
|
|
|
|
|
|
|
|
uint16_t originLen = NetworkEndian::readUint16(
|
|
|
|
self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset);
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin extension defined as %d bytes\n", self, originLen));
|
|
|
|
if (originLen + 2U + offset > self->mInputFrameDataSize) {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin len too big for frame", self));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoCString originString;
|
2018-01-24 19:33:02 +00:00
|
|
|
nsCOMPtr<nsIURI> originURL;
|
2017-04-03 21:23:55 +00:00
|
|
|
originString.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + offset + 2, originLen);
|
|
|
|
offset += originLen + 2;
|
|
|
|
if (NS_FAILED(Http2Stream::MakeOriginURL(originString, originURL))){
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin frame string %s failed to parse\n", self, originString.get()));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin frame string %s parsed OK\n", self, originString.get()));
|
|
|
|
bool isHttps = false;
|
|
|
|
if (NS_FAILED(originURL->SchemeIs("https", &isHttps)) || !isHttps) {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin frame not https\n", self));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t port = -1;
|
|
|
|
originURL->GetPort(&port);
|
|
|
|
if (port == -1) {
|
|
|
|
port = 443;
|
|
|
|
}
|
|
|
|
// dont use ->GetHostPort because we want explicit 443
|
|
|
|
nsAutoCString host;
|
|
|
|
originURL->GetHost(host);
|
|
|
|
nsAutoCString key(host);
|
|
|
|
key.Append(':');
|
|
|
|
key.AppendInt(port);
|
|
|
|
if (!self->mOriginFrame.Get(key)) {
|
|
|
|
self->mOriginFrame.Put(key, true);
|
|
|
|
RefPtr<nsHttpConnection> conn(self->HttpConnection());
|
|
|
|
MOZ_ASSERT(conn.get());
|
|
|
|
gHttpHandler->ConnMgr()->RegisterOriginCoalescingKey(conn, host, port);
|
|
|
|
} else {
|
|
|
|
LOG3(("Http2Session::RecvOrigin %p origin frame already in set\n", self));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self->ResetDownstreamState();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
|
|
|
|
// of these methods
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::OnTransportStatus(nsITransport* aTransport,
|
2015-01-08 19:48:52 +00:00
|
|
|
nsresult aStatus, int64_t aProgress)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
switch (aStatus) {
|
|
|
|
// These should appear only once, deliver to the first
|
|
|
|
// transaction on the session.
|
|
|
|
case NS_NET_STATUS_RESOLVING_HOST:
|
|
|
|
case NS_NET_STATUS_RESOLVED_HOST:
|
|
|
|
case NS_NET_STATUS_CONNECTING_TO:
|
|
|
|
case NS_NET_STATUS_CONNECTED_TO:
|
2017-03-28 02:36:00 +00:00
|
|
|
case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
|
|
|
|
case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2017-10-17 13:28:33 +00:00
|
|
|
|
|
|
|
if (!mFirstHttpTransaction) {
|
|
|
|
// if we still do not have a HttpTransaction store timings info in
|
|
|
|
// a HttpConnection.
|
2017-10-18 14:27:32 +00:00
|
|
|
// If some error occur it can happen that we do not have a connection.
|
|
|
|
if (mConnection) {
|
|
|
|
RefPtr<nsHttpConnection> conn = mConnection->HttpConnection();
|
|
|
|
conn->SetEvent(aStatus);
|
|
|
|
}
|
2017-10-17 13:28:33 +00:00
|
|
|
} else {
|
|
|
|
mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus, aProgress);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aStatus == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
|
|
|
|
mFirstHttpTransaction = nullptr;
|
|
|
|
mTlsHandshakeFinished = true;
|
2017-07-10 19:01:35 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
// The other transport events are ignored here because there is no good
|
|
|
|
// way to map them to the right transaction in http/2. Instead, the events
|
|
|
|
// are generated again from the http/2 code and passed directly to the
|
|
|
|
// correct transaction.
|
|
|
|
|
|
|
|
// NS_NET_STATUS_SENDING_TO:
|
|
|
|
// This is generated by the socket transport when (part) of
|
|
|
|
// a transaction is written out
|
|
|
|
//
|
|
|
|
// There is no good way to map it to the right transaction in http/2,
|
|
|
|
// so it is ignored here and generated separately when the request
|
|
|
|
// is sent from Http2Stream::TransmitFrame
|
|
|
|
|
|
|
|
// NS_NET_STATUS_WAITING_FOR:
|
|
|
|
// Created by nsHttpConnection when the request has been totally sent.
|
|
|
|
// There is no good way to map it to the right transaction in http/2,
|
|
|
|
// so it is ignored here and generated separately when the same
|
|
|
|
// condition is complete in Http2Stream when there is no more
|
|
|
|
// request body left to be transmitted.
|
|
|
|
|
|
|
|
// NS_NET_STATUS_RECEIVING_FROM
|
|
|
|
// Generated in session whenever we read a data frame or a HEADERS
|
|
|
|
// that can be attributed to a particular stream/transaction
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadSegments() is used to write data to the network. Generally, HTTP
|
|
|
|
// request data is pulled from the approriate transaction and
|
|
|
|
// converted to http/2 data. Sometimes control data like window-update are
|
|
|
|
// generated instead.
|
|
|
|
|
|
|
|
nsresult
|
2016-02-14 01:54:24 +00:00
|
|
|
Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader,
|
|
|
|
uint32_t count, uint32_t *countRead, bool *again)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
|
|
|
|
"Inconsistent Write Function Callback");
|
|
|
|
|
|
|
|
nsresult rv = ConfirmTLSProfile();
|
2016-04-15 02:24:46 +00:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
if (mGoAwayReason == INADEQUATE_SECURITY) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::ReadSegments %p returning INADEQUATE_SECURITY %" PRIx32,
|
|
|
|
this, static_cast<uint32_t>(NS_ERROR_NET_INADEQUATE_SECURITY)));
|
2016-04-15 02:24:46 +00:00
|
|
|
rv = NS_ERROR_NET_INADEQUATE_SECURITY;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
2016-04-15 02:24:46 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2017-11-08 18:30:01 +00:00
|
|
|
if (reader)
|
|
|
|
mSegmentReader = reader;
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
*countRead = 0;
|
|
|
|
|
|
|
|
LOG3(("Http2Session::ReadSegments %p", this));
|
|
|
|
|
|
|
|
Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
|
|
|
|
if (!stream) {
|
|
|
|
LOG3(("Http2Session %p could not identify a stream to write; suspending.",
|
|
|
|
this));
|
2017-03-07 20:56:41 +00:00
|
|
|
uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2017-03-07 20:56:41 +00:00
|
|
|
uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
|
|
|
|
if (availBeforeFlush != availAfterFlush) {
|
|
|
|
LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments", this));
|
|
|
|
Unused << ResumeRecv();
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
SetWriteCallbacks();
|
2017-02-21 20:19:46 +00:00
|
|
|
if (mAttemptingEarlyData) {
|
|
|
|
// We can still try to send our preamble as early-data
|
|
|
|
*countRead = mOutputQueueUsed - mOutputQueueSent;
|
|
|
|
}
|
|
|
|
return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t earlyDataUsed = 0;
|
|
|
|
if (mAttemptingEarlyData) {
|
|
|
|
if (!stream->Do0RTT()) {
|
|
|
|
LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X",
|
|
|
|
this, stream, stream->StreamID()));
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2017-02-21 20:19:46 +00:00
|
|
|
SetWriteCallbacks();
|
2017-09-22 07:09:57 +00:00
|
|
|
if (!mCannotDo0RTTStreams.Contains(stream)) {
|
|
|
|
mCannotDo0RTTStreams.AppendElement(stream);
|
|
|
|
}
|
2017-02-21 20:19:46 +00:00
|
|
|
// We can still send our preamble
|
|
|
|
*countRead = mOutputQueueUsed - mOutputQueueSent;
|
|
|
|
return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Need to adjust this to only take as much as we can fit in with the
|
|
|
|
// preamble/settings/priority stuff
|
|
|
|
count -= (mOutputQueueUsed - mOutputQueueSent);
|
|
|
|
|
|
|
|
// Keep track of this to add it into countRead later, as
|
|
|
|
// stream->ReadSegments will likely change the value of mOutputQueueUsed.
|
|
|
|
earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session %p will write from Http2Stream %p 0x%X "
|
|
|
|
"block-input=%d block-output=%d\n", this, stream, stream->StreamID(),
|
|
|
|
stream->RequestBlockedOnRead(), stream->BlockedOnRwin()));
|
|
|
|
|
|
|
|
rv = stream->ReadSegments(this, count, countRead);
|
|
|
|
|
2017-02-21 20:19:46 +00:00
|
|
|
if (earlyDataUsed) {
|
|
|
|
// Do this here because countRead could get reset somewhere down the rabbit
|
|
|
|
// hole of stream->ReadSegments, and we want to make sure we return the
|
|
|
|
// proper value to our caller.
|
|
|
|
*countRead += earlyDataUsed;
|
|
|
|
}
|
|
|
|
|
2017-05-09 09:37:45 +00:00
|
|
|
if (mAttemptingEarlyData && !m0RTTStreams.Contains(stream)) {
|
2017-04-07 15:28:21 +00:00
|
|
|
LOG3(("Http2Session::ReadSegmentsAgain adding stream %d to m0RTTStreams\n",
|
|
|
|
stream->StreamID()));
|
2017-05-09 09:37:45 +00:00
|
|
|
m0RTTStreams.AppendElement(stream);
|
2017-04-07 15:28:21 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// Not every permutation of stream->ReadSegents produces data (and therefore
|
|
|
|
// tries to flush the output queue) - SENDING_FIN_STREAM can be an example
|
|
|
|
// of that. But we might still have old data buffered that would be good
|
|
|
|
// to flush.
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Allow new server reads - that might be data or control information
|
|
|
|
// (e.g. window updates or http replies) that are responses to these writes
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ResumeRecv();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (stream->RequestBlockedOnRead()) {
|
|
|
|
|
|
|
|
// We are blocked waiting for input - either more http headers or
|
|
|
|
// any request body data. When more data from the request stream
|
|
|
|
// becomes available the httptransaction will call conn->ResumeSend().
|
|
|
|
|
|
|
|
LOG3(("Http2Session::ReadSegments %p dealing with block on read", this));
|
|
|
|
|
|
|
|
// call readsegments again if there are other streams ready
|
|
|
|
// to run in this session
|
|
|
|
if (GetWriteQueueSize()) {
|
|
|
|
rv = NS_OK;
|
|
|
|
} else {
|
|
|
|
rv = NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
}
|
|
|
|
SetWriteCallbacks();
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::ReadSegments %p may return FAIL code %" PRIX32,
|
|
|
|
this, static_cast<uint32_t>(rv)));
|
2014-08-21 14:50:17 +00:00
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
CleanupStream(stream, rv, CANCEL_ERROR);
|
|
|
|
if (SoftStreamError(rv)) {
|
|
|
|
LOG3(("Http2Session::ReadSegments %p soft error override\n", this));
|
2016-02-14 01:54:24 +00:00
|
|
|
*again = false;
|
|
|
|
SetWriteCallbacks();
|
2014-08-21 14:50:17 +00:00
|
|
|
rv = NS_OK;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*countRead > 0) {
|
|
|
|
LOG3(("Http2Session::ReadSegments %p stream=%p countread=%d",
|
|
|
|
this, stream, *countRead));
|
|
|
|
mReadyForWrite.Push(stream);
|
|
|
|
SetWriteCallbacks();
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->BlockedOnRwin()) {
|
|
|
|
LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
|
|
|
|
this, stream, stream->StreamID()));
|
|
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete",
|
|
|
|
this, stream));
|
|
|
|
|
|
|
|
// call readsegments again if there are other streams ready
|
|
|
|
// to go in this session
|
|
|
|
SetWriteCallbacks();
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2016-02-14 01:54:24 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::ReadSegments(nsAHttpSegmentReader *reader,
|
|
|
|
uint32_t count, uint32_t *countRead)
|
|
|
|
{
|
|
|
|
bool again = false;
|
|
|
|
return ReadSegmentsAgain(reader, count, countRead, &again);
|
|
|
|
}
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::ReadyToProcessDataFrame(enum internalStateType newState)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(newState == PROCESSING_DATA_FRAME ||
|
|
|
|
newState == DISCARDING_DATA_FRAME_PADDING);
|
|
|
|
ChangeDownstreamState(newState);
|
|
|
|
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
|
|
|
|
mInputFrameDataSize >> 10);
|
|
|
|
mLastDataReadEpoch = mLastReadEpoch;
|
|
|
|
|
|
|
|
if (!mInputFrameID) {
|
|
|
|
LOG3(("Http2Session::ReadyToProcessDataFrame %p data frame stream 0\n",
|
|
|
|
this));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nsresult rv = SetInputFrameDataStream(mInputFrameID);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
|
|
|
|
"failed. probably due to verification.\n", this, mInputFrameID));
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
if (!mInputFrameDataStream) {
|
|
|
|
LOG3(("Http2Session::ReadyToProcessDataFrame %p lookup streamID 0x%X "
|
|
|
|
"failed. Next = 0x%X", this, mInputFrameID, mNextStreamID));
|
|
|
|
if (mInputFrameID >= mNextStreamID)
|
|
|
|
GenerateRstStream(PROTOCOL_ERROR, mInputFrameID);
|
|
|
|
ChangeDownstreamState(DISCARDING_DATA_FRAME);
|
|
|
|
} else if (mInputFrameDataStream->RecvdFin() ||
|
|
|
|
mInputFrameDataStream->RecvdReset() ||
|
|
|
|
mInputFrameDataStream->SentReset()) {
|
|
|
|
LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
|
|
|
|
"Data arrived for already server closed stream.\n",
|
|
|
|
this, mInputFrameID));
|
|
|
|
if (mInputFrameDataStream->RecvdFin() || mInputFrameDataStream->RecvdReset())
|
|
|
|
GenerateRstStream(STREAM_CLOSED_ERROR, mInputFrameID);
|
|
|
|
ChangeDownstreamState(DISCARDING_DATA_FRAME);
|
2016-03-02 00:28:39 +00:00
|
|
|
} else if (mInputFrameDataSize == 0 && !mInputFrameFinal) {
|
|
|
|
// Only if non-final because the stream properly handles final frames of any
|
|
|
|
// size, and we want the stream to be able to notice its own end flag.
|
|
|
|
LOG3(("Http2Session::ReadyToProcessDataFrame %p streamID 0x%X "
|
|
|
|
"Ignoring 0-length non-terminal data frame.", this, mInputFrameID));
|
|
|
|
ChangeDownstreamState(DISCARDING_DATA_FRAME);
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Start Processing Data Frame. "
|
|
|
|
"Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d",
|
|
|
|
this, mInputFrameID, mInputFrameDataStream, mInputFrameFinal,
|
|
|
|
mInputFrameDataSize));
|
|
|
|
UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize);
|
|
|
|
|
2014-06-19 03:15:00 +00:00
|
|
|
if (mInputFrameDataStream) {
|
|
|
|
mInputFrameDataStream->SetRecvdData(true);
|
|
|
|
}
|
2014-06-08 22:15:00 +00:00
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// WriteSegments() is used to read data off the socket. Generally this is
|
|
|
|
// just the http2 frame header and from there the appropriate *Stream
|
|
|
|
// is identified from the Stream-ID. The http transaction associated with
|
|
|
|
// that read then pulls in the data directly, which it will feed to
|
|
|
|
// OnWriteSegment(). That function will gateway it into http and feed
|
|
|
|
// it to the appropriate transaction.
|
|
|
|
|
|
|
|
// we call writer->OnWriteSegment via NetworkRead() to get a http2 header..
|
|
|
|
// and decide if it is data or control.. if it is control, just deal with it.
|
|
|
|
// if it is data, identify the stream
|
|
|
|
// call stream->WriteSegments which can call this::OnWriteSegment to get the
|
|
|
|
// data. It always gets full frames if they are part of the stream
|
|
|
|
|
|
|
|
nsresult
|
2016-02-14 01:54:24 +00:00
|
|
|
Http2Session::WriteSegmentsAgain(nsAHttpSegmentWriter *writer,
|
|
|
|
uint32_t count, uint32_t *countWritten,
|
|
|
|
bool *again)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session::WriteSegments %p InternalState %X\n",
|
|
|
|
this, mDownstreamState));
|
|
|
|
|
|
|
|
*countWritten = 0;
|
|
|
|
|
|
|
|
if (mClosed)
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
|
|
|
nsresult rv = ConfirmTLSProfile();
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
SetWriteCallbacks();
|
|
|
|
|
|
|
|
// If there are http transactions attached to a push stream with filled buffers
|
|
|
|
// trigger that data pump here. This only reads from buffers (not the network)
|
|
|
|
// so mDownstreamState doesn't matter.
|
|
|
|
Http2Stream *pushConnectedStream =
|
2015-09-11 19:53:27 +00:00
|
|
|
static_cast<Http2Stream *>(mPushesReadyForRead.PopFront());
|
2013-10-10 00:21:49 +00:00
|
|
|
if (pushConnectedStream) {
|
2015-09-11 19:53:27 +00:00
|
|
|
return ProcessConnectedPush(pushConnectedStream, writer, count, countWritten);
|
|
|
|
}
|
|
|
|
|
|
|
|
// feed gecko channels that previously stopped consuming data
|
|
|
|
// only take data from stored buffers
|
|
|
|
Http2Stream *slowConsumer =
|
|
|
|
static_cast<Http2Stream *>(mSlowConsumersReadyForRead.PopFront());
|
|
|
|
if (slowConsumer) {
|
|
|
|
internalStateType savedState = mDownstreamState;
|
|
|
|
mDownstreamState = NOT_USING_NETWORK;
|
|
|
|
rv = ProcessSlowConsumer(slowConsumer, writer, count, countWritten);
|
|
|
|
mDownstreamState = savedState;
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The BUFFERING_OPENING_SETTINGS state is just like any BUFFERING_FRAME_HEADER
|
|
|
|
// except the only frame type it will allow is SETTINGS
|
|
|
|
|
|
|
|
// The session layer buffers the leading 8 byte header of every frame.
|
|
|
|
// Non-Data frames are then buffered for their full length, but data
|
|
|
|
// frames (type 0) are passed through to the http stack unprocessed
|
|
|
|
|
|
|
|
if (mDownstreamState == BUFFERING_OPENING_SETTINGS ||
|
|
|
|
mDownstreamState == BUFFERING_FRAME_HEADER) {
|
2014-08-05 15:41:09 +00:00
|
|
|
// The first 9 bytes of every frame is header information that
|
2013-10-10 00:21:49 +00:00
|
|
|
// we are going to want to strip before passing to http. That is
|
|
|
|
// true of both control and data packets.
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
MOZ_ASSERT(mInputFrameBufferUsed < kFrameHeaderBytes,
|
2013-10-10 00:21:49 +00:00
|
|
|
"Frame Buffer Used Too Large for State");
|
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
|
2014-08-05 15:41:09 +00:00
|
|
|
kFrameHeaderBytes - mInputFrameBufferUsed, countWritten);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session %p buffering frame header read failure %" PRIx32 "\n",
|
|
|
|
this, static_cast<uint32_t>(rv)));
|
2013-10-10 00:21:49 +00:00
|
|
|
// maybe just blocked reading from network
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
|
|
|
rv = NS_OK;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
LogIO(this, nullptr, "Reading Frame Header",
|
2015-11-09 02:28:05 +00:00
|
|
|
&mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
mInputFrameBufferUsed += *countWritten;
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
if (mInputFrameBufferUsed < kFrameHeaderBytes)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
|
|
|
LOG3(("Http2Session::WriteSegments %p "
|
|
|
|
"BUFFERING FRAME HEADER incomplete size=%d",
|
|
|
|
this, mInputFrameBufferUsed));
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
// 3 bytes of length, 1 type byte, 1 flag byte, 1 unused bit, 31 bits of ID
|
|
|
|
uint8_t totallyWastedByte = mInputFrameBuffer.get()[0];
|
2015-04-20 18:07:16 +00:00
|
|
|
mInputFrameDataSize = NetworkEndian::readUint16(
|
|
|
|
mInputFrameBuffer.get() + 1);
|
2014-08-05 15:41:09 +00:00
|
|
|
if (totallyWastedByte || (mInputFrameDataSize > kMaxFrameData)) {
|
|
|
|
LOG3(("Got frame too large 0x%02X%04X", totallyWastedByte, mInputFrameDataSize));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2014-08-05 15:41:09 +00:00
|
|
|
}
|
|
|
|
mInputFrameType = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() + kFrameLengthBytes);
|
|
|
|
mInputFrameFlags = *reinterpret_cast<uint8_t *>(mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes);
|
2015-04-20 18:07:16 +00:00
|
|
|
mInputFrameID = NetworkEndian::readUint32(
|
|
|
|
mInputFrameBuffer.get() + kFrameLengthBytes + kFrameTypeBytes + kFrameFlagBytes);
|
2013-10-10 00:21:49 +00:00
|
|
|
mInputFrameID &= 0x7fffffff;
|
|
|
|
mInputFrameDataRead = 0;
|
|
|
|
|
|
|
|
if (mInputFrameType == FRAME_TYPE_DATA || mInputFrameType == FRAME_TYPE_HEADERS) {
|
|
|
|
mInputFrameFinal = mInputFrameFlags & kFlag_END_STREAM;
|
|
|
|
} else {
|
2018-04-30 16:46:04 +00:00
|
|
|
mInputFrameFinal = false;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
mPaddingLength = 0;
|
|
|
|
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::WriteSegments[%p::%" PRIu64 "] Frame Header Read "
|
2013-10-10 00:21:49 +00:00
|
|
|
"type %X data len %u flags %x id 0x%X",
|
|
|
|
this, mSerial, mInputFrameType, mInputFrameDataSize, mInputFrameFlags,
|
|
|
|
mInputFrameID));
|
|
|
|
|
|
|
|
// if mExpectedHeaderID is non 0, it means this frame must be a CONTINUATION of
|
|
|
|
// a HEADERS frame with a matching ID (section 6.2)
|
|
|
|
if (mExpectedHeaderID &&
|
|
|
|
((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
|
|
|
|
(mExpectedHeaderID != mInputFrameID))) {
|
|
|
|
LOG3(("Expected CONINUATION OF HEADERS for ID 0x%X\n", mExpectedHeaderID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if mExpectedPushPromiseID is non 0, it means this frame must be a
|
|
|
|
// CONTINUATION of a PUSH_PROMISE with a matching ID (section 6.2)
|
|
|
|
if (mExpectedPushPromiseID &&
|
|
|
|
((mInputFrameType != FRAME_TYPE_CONTINUATION) ||
|
|
|
|
(mExpectedPushPromiseID != mInputFrameID))) {
|
|
|
|
LOG3(("Expected CONTINUATION of PUSH PROMISE for ID 0x%X\n",
|
|
|
|
mExpectedPushPromiseID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mDownstreamState == BUFFERING_OPENING_SETTINGS &&
|
|
|
|
mInputFrameType != FRAME_TYPE_SETTINGS) {
|
|
|
|
LOG3(("First Frame Type Must Be Settings\n"));
|
2018-10-25 20:46:24 +00:00
|
|
|
mPeerFailedHandshake = true;
|
2018-10-25 20:52:59 +00:00
|
|
|
|
|
|
|
// Don't allow any more h2 connections to this host
|
|
|
|
RefPtr<nsHttpConnectionInfo> ci = ConnectionInfo();
|
|
|
|
if (ci) {
|
|
|
|
gHttpHandler->BlacklistSpdy(ci);
|
|
|
|
}
|
|
|
|
|
2018-10-25 20:46:24 +00:00
|
|
|
// Go through and re-start all of our transactions with h2 disabled.
|
|
|
|
for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
|
|
|
|
nsAutoPtr<Http2Stream>& stream = iter.Data();
|
|
|
|
stream->Transaction()->DisableSpdy();
|
|
|
|
CloseStream(stream, NS_ERROR_NET_RESET);
|
|
|
|
}
|
|
|
|
mStreamTransactionHash.Clear();
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mInputFrameType != FRAME_TYPE_DATA) { // control frame
|
2014-08-05 15:41:09 +00:00
|
|
|
EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + kFrameHeaderBytes,
|
|
|
|
kFrameHeaderBytes, mInputFrameBufferSize);
|
2013-10-10 00:21:49 +00:00
|
|
|
ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
|
2014-06-26 00:33:15 +00:00
|
|
|
} else if (mInputFrameFlags & kFlag_PADDED) {
|
2014-03-26 17:58:09 +00:00
|
|
|
ChangeDownstreamState(PROCESSING_DATA_FRAME_PADDING_CONTROL);
|
2013-10-10 00:21:49 +00:00
|
|
|
} else {
|
2014-03-26 17:58:09 +00:00
|
|
|
rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
if (mDownstreamState == PROCESSING_DATA_FRAME_PADDING_CONTROL) {
|
2014-06-26 00:33:15 +00:00
|
|
|
MOZ_ASSERT(mInputFrameFlags & kFlag_PADDED,
|
|
|
|
"Processing padding control on unpadded frame");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
MOZ_ASSERT(mInputFrameBufferUsed < (kFrameHeaderBytes + 1),
|
2014-03-26 17:58:09 +00:00
|
|
|
"Frame buffer used too large for state");
|
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
rv = NetworkRead(writer, &mInputFrameBuffer[mInputFrameBufferUsed],
|
2014-08-05 15:41:09 +00:00
|
|
|
(kFrameHeaderBytes + 1) - mInputFrameBufferUsed,
|
2014-03-26 17:58:09 +00:00
|
|
|
countWritten);
|
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session %p buffering data frame padding control read failure %" PRIx32 "\n",
|
|
|
|
this, static_cast<uint32_t>(rv)));
|
2014-03-26 17:58:09 +00:00
|
|
|
// maybe just blocked reading from network
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
|
|
|
rv = NS_OK;
|
|
|
|
return rv;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
LogIO(this, nullptr, "Reading Data Frame Padding Control",
|
2015-11-09 02:28:05 +00:00
|
|
|
&mInputFrameBuffer[mInputFrameBufferUsed], *countWritten);
|
2014-03-26 17:58:09 +00:00
|
|
|
|
|
|
|
mInputFrameBufferUsed += *countWritten;
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
if (mInputFrameBufferUsed - kFrameHeaderBytes < 1) {
|
2014-03-26 17:58:09 +00:00
|
|
|
LOG3(("Http2Session::WriteSegments %p "
|
|
|
|
"BUFFERING DATA FRAME CONTROL PADDING incomplete size=%d",
|
|
|
|
this, mInputFrameBufferUsed - 8));
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-06-26 00:33:15 +00:00
|
|
|
++mInputFrameDataRead;
|
2014-03-26 17:58:09 +00:00
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
char *control = &mInputFrameBuffer[kFrameHeaderBytes];
|
2014-06-26 00:33:15 +00:00
|
|
|
mPaddingLength = static_cast<uint8_t>(*control);
|
2014-03-26 17:58:09 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session::WriteSegments %p stream 0x%X mPaddingLength=%d", this,
|
|
|
|
mInputFrameID, mPaddingLength));
|
|
|
|
|
2017-03-20 22:01:31 +00:00
|
|
|
if (1U + mPaddingLength > mInputFrameDataSize) {
|
|
|
|
LOG3(("Http2Session::WriteSegments %p stream 0x%X padding too large for "
|
|
|
|
"frame", this, mInputFrameID));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(PROTOCOL_ERROR);
|
2017-03-20 22:01:31 +00:00
|
|
|
} else if (1U + mPaddingLength == mInputFrameDataSize) {
|
2014-03-26 17:58:09 +00:00
|
|
|
// This frame consists entirely of padding, we can just discard it
|
|
|
|
LOG3(("Http2Session::WriteSegments %p stream 0x%X frame with only padding",
|
|
|
|
this, mInputFrameID));
|
|
|
|
rv = ReadyToProcessDataFrame(DISCARDING_DATA_FRAME_PADDING);
|
2013-10-10 00:21:49 +00:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return rv;
|
|
|
|
}
|
2014-03-26 17:58:09 +00:00
|
|
|
} else {
|
|
|
|
LOG3(("Http2Session::WriteSegments %p stream 0x%X ready to read HTTP data",
|
|
|
|
this, mInputFrameID));
|
|
|
|
rv = ReadyToProcessDataFrame(PROCESSING_DATA_FRAME);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return rv;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
|
|
|
|
nsresult streamCleanupCode;
|
|
|
|
|
|
|
|
// There is no bounds checking on the error code.. we provide special
|
|
|
|
// handling for a couple of cases and all others (including unknown) are
|
|
|
|
// equivalent to cancel.
|
|
|
|
if (mDownstreamRstReason == REFUSED_STREAM_ERROR) {
|
|
|
|
streamCleanupCode = NS_ERROR_NET_RESET; // can retry this 100% safely
|
2014-11-06 14:47:00 +00:00
|
|
|
mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
|
2014-11-06 14:26:00 +00:00
|
|
|
} else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
|
|
|
|
streamCleanupCode = NS_ERROR_NET_RESET;
|
|
|
|
mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
|
|
|
|
mInputFrameDataStream->Transaction()->DisableSpdy();
|
2013-10-10 00:21:49 +00:00
|
|
|
} else {
|
2014-06-08 22:15:00 +00:00
|
|
|
streamCleanupCode = mInputFrameDataStream->RecvdData() ?
|
|
|
|
NS_ERROR_NET_PARTIAL_TRANSFER :
|
|
|
|
NS_ERROR_NET_INTERRUPT;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-11-06 14:47:00 +00:00
|
|
|
if (mDownstreamRstReason == COMPRESSION_ERROR) {
|
2013-10-10 00:21:49 +00:00
|
|
|
mShouldGoAway = true;
|
2014-11-06 14:47:00 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// mInputFrameDataStream is reset by ChangeDownstreamState
|
|
|
|
Http2Stream *stream = mInputFrameDataStream;
|
|
|
|
ResetDownstreamState();
|
|
|
|
LOG3(("Http2Session::WriteSegments cleanup stream on recv of rst "
|
|
|
|
"session=%p stream=%p 0x%X\n", this, stream,
|
|
|
|
stream ? stream->StreamID() : 0));
|
|
|
|
CleanupStream(stream, streamCleanupCode, CANCEL_ERROR);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mDownstreamState == PROCESSING_DATA_FRAME ||
|
|
|
|
mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
|
|
|
|
|
|
|
|
// The cleanup stream should only be set while stream->WriteSegments is
|
|
|
|
// on the stack and then cleaned up in this code block afterwards.
|
|
|
|
MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
|
|
|
|
mNeedsCleanup = nullptr; /* just in case */
|
|
|
|
|
2014-10-13 15:02:08 +00:00
|
|
|
uint32_t streamID = mInputFrameDataStream->StreamID();
|
2013-10-10 00:21:49 +00:00
|
|
|
mSegmentWriter = writer;
|
|
|
|
rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
|
|
|
|
mSegmentWriter = nullptr;
|
|
|
|
|
|
|
|
mLastDataReadEpoch = mLastReadEpoch;
|
|
|
|
|
|
|
|
if (SoftStreamError(rv)) {
|
|
|
|
// This will happen when the transaction figures out it is EOF, generally
|
|
|
|
// due to a content-length match being made. Return OK from this function
|
|
|
|
// otherwise the whole session would be torn down.
|
|
|
|
|
|
|
|
// if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
|
|
|
|
// back to PROCESSING_DATA_FRAME where we came from
|
|
|
|
mDownstreamState = PROCESSING_DATA_FRAME;
|
|
|
|
|
|
|
|
if (mInputFrameDataRead == mInputFrameDataSize)
|
|
|
|
ResetDownstreamState();
|
2014-10-13 15:02:08 +00:00
|
|
|
LOG3(("Http2Session::WriteSegments session=%p id 0x%X "
|
2013-10-10 00:21:49 +00:00
|
|
|
"needscleanup=%p. cleanup stream based on "
|
2016-12-16 03:16:31 +00:00
|
|
|
"stream->writeSegments returning code %" PRIx32 "\n",
|
|
|
|
this, streamID, mNeedsCleanup, static_cast<uint32_t>(rv)));
|
2014-10-13 15:02:08 +00:00
|
|
|
MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup->StreamID() == streamID);
|
2017-12-14 09:35:54 +00:00
|
|
|
CleanupStream(streamID,
|
|
|
|
(rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK,
|
|
|
|
CANCEL_ERROR);
|
2014-05-16 15:46:13 +00:00
|
|
|
mNeedsCleanup = nullptr;
|
2016-02-14 01:54:24 +00:00
|
|
|
*again = false;
|
2016-12-27 11:05:13 +00:00
|
|
|
rv = ResumeRecv();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("ResumeRecv returned code %x", static_cast<uint32_t>(rv)));
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mNeedsCleanup) {
|
|
|
|
LOG3(("Http2Session::WriteSegments session=%p stream=%p 0x%X "
|
|
|
|
"cleanup stream based on mNeedsCleanup.\n",
|
|
|
|
this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
|
|
|
|
CleanupStream(mNeedsCleanup, NS_OK, CANCEL_ERROR);
|
|
|
|
mNeedsCleanup = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session %p data frame read failure %" PRIx32 "\n", this,
|
|
|
|
static_cast<uint32_t>(rv)));
|
2013-10-10 00:21:49 +00:00
|
|
|
// maybe just blocked reading from network
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
|
|
|
rv = NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
if (mDownstreamState == DISCARDING_DATA_FRAME ||
|
|
|
|
mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
|
2013-10-10 00:21:49 +00:00
|
|
|
char trash[4096];
|
2016-05-11 06:57:24 +00:00
|
|
|
uint32_t discardCount = std::min(mInputFrameDataSize - mInputFrameDataRead,
|
|
|
|
4096U);
|
2017-10-18 19:34:42 +00:00
|
|
|
LOG3(("Http2Session::WriteSegments %p trying to discard %d bytes of %s",
|
|
|
|
this, discardCount,
|
|
|
|
mDownstreamState == DISCARDING_DATA_FRAME ? "data" : "padding"));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2017-08-01 22:24:44 +00:00
|
|
|
if (!discardCount && mDownstreamState == DISCARDING_DATA_FRAME) {
|
|
|
|
// Only do this short-cirtuit if we're not discarding a pure padding
|
|
|
|
// frame, as we need to potentially handle the stream FIN in those cases.
|
|
|
|
// See bug 1381016 comment 36 for more details.
|
2013-10-10 00:21:49 +00:00
|
|
|
ResetDownstreamState();
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ResumeRecv();
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
}
|
|
|
|
|
2016-05-11 06:57:24 +00:00
|
|
|
rv = NetworkRead(writer, trash, discardCount, countWritten);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session %p discard frame read failure %" PRIx32 "\n", this,
|
|
|
|
static_cast<uint32_t>(rv)));
|
2013-10-10 00:21:49 +00:00
|
|
|
// maybe just blocked reading from network
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
|
|
|
rv = NS_OK;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
|
|
|
|
|
|
|
|
mInputFrameDataRead += *countWritten;
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
if (mInputFrameDataRead == mInputFrameDataSize) {
|
|
|
|
Http2Stream *streamToCleanup = nullptr;
|
|
|
|
if (mInputFrameFinal) {
|
|
|
|
streamToCleanup = mInputFrameDataStream;
|
|
|
|
}
|
|
|
|
|
2017-10-18 19:34:42 +00:00
|
|
|
bool discardedPadding = (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
|
2013-10-10 00:21:49 +00:00
|
|
|
ResetDownstreamState();
|
2014-03-26 17:58:09 +00:00
|
|
|
|
|
|
|
if (streamToCleanup) {
|
2017-10-18 19:34:42 +00:00
|
|
|
if (discardedPadding && !(streamToCleanup->StreamID() & 1)) {
|
|
|
|
// Pushed streams are special on padding-only final data frames.
|
|
|
|
// See bug 1409570 comments 6-8 for details.
|
|
|
|
streamToCleanup->SetPushComplete();
|
2018-10-01 21:52:57 +00:00
|
|
|
Http2Stream *pushSink = streamToCleanup->GetConsumerStream();
|
|
|
|
if (pushSink) {
|
|
|
|
bool enqueueSink = true;
|
2018-10-04 08:43:54 +00:00
|
|
|
for (auto s : mPushesReadyForRead) {
|
|
|
|
if (s == pushSink) {
|
2018-10-01 21:52:57 +00:00
|
|
|
enqueueSink = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (enqueueSink) {
|
|
|
|
mPushesReadyForRead.Push(pushSink);
|
|
|
|
// No use trying to clean up, it won't do anything, anyway
|
|
|
|
streamToCleanup = nullptr;
|
|
|
|
}
|
|
|
|
}
|
2017-10-18 19:34:42 +00:00
|
|
|
}
|
2014-03-26 17:58:09 +00:00
|
|
|
CleanupStream(streamToCleanup, NS_OK, CANCEL_ERROR);
|
|
|
|
}
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
|
|
|
|
MOZ_ASSERT(false); // this cannot happen
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
MOZ_ASSERT(mInputFrameBufferUsed == kFrameHeaderBytes, "Frame Buffer Header Not Present");
|
|
|
|
MOZ_ASSERT(mInputFrameDataSize + kFrameHeaderBytes <= mInputFrameBufferSize,
|
2013-10-10 00:21:49 +00:00
|
|
|
"allocation for control frame insufficient");
|
|
|
|
|
2015-11-09 02:28:05 +00:00
|
|
|
rv = NetworkRead(writer, &mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead],
|
2013-10-10 00:21:49 +00:00
|
|
|
mInputFrameDataSize - mInputFrameDataRead, countWritten);
|
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session %p buffering control frame read failure %" PRIx32 "\n",
|
|
|
|
this, static_cast<uint32_t>(rv)));
|
2013-10-10 00:21:49 +00:00
|
|
|
// maybe just blocked reading from network
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
|
|
|
|
rv = NS_OK;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
LogIO(this, nullptr, "Reading Control Frame",
|
2015-11-09 02:28:05 +00:00
|
|
|
&mInputFrameBuffer[kFrameHeaderBytes + mInputFrameDataRead], *countWritten);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
mInputFrameDataRead += *countWritten;
|
|
|
|
|
|
|
|
if (mInputFrameDataRead != mInputFrameDataSize)
|
|
|
|
return NS_OK;
|
|
|
|
|
|
|
|
MOZ_ASSERT(mInputFrameType != FRAME_TYPE_DATA);
|
|
|
|
if (mInputFrameType < FRAME_TYPE_LAST) {
|
|
|
|
rv = sControlFunctions[mInputFrameType](this);
|
|
|
|
} else {
|
|
|
|
// Section 4.1 requires this to be ignored; though protocol_error would
|
|
|
|
// be better
|
2014-06-26 00:33:15 +00:00
|
|
|
LOG3(("Http2Session %p unknown frame type %x ignored\n",
|
2013-10-10 00:21:49 +00:00
|
|
|
this, mInputFrameType));
|
|
|
|
ResetDownstreamState();
|
|
|
|
rv = NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(NS_FAILED(rv) ||
|
|
|
|
mDownstreamState != BUFFERING_CONTROL_FRAME,
|
|
|
|
"Control Handler returned OK but did not change state");
|
|
|
|
|
|
|
|
if (mShouldGoAway && !mStreamTransactionHash.Count())
|
|
|
|
Close(NS_OK);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2016-02-14 01:54:24 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
|
|
|
|
uint32_t count, uint32_t *countWritten)
|
|
|
|
{
|
|
|
|
bool again = false;
|
|
|
|
return WriteSegmentsAgain(writer, count, countWritten, &again);
|
|
|
|
}
|
|
|
|
|
2017-02-21 20:19:46 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mAttemptingEarlyData);
|
|
|
|
LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
|
|
|
|
aRestart, aAlpnChanged));
|
|
|
|
|
|
|
|
for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
|
2017-05-09 09:37:45 +00:00
|
|
|
if (m0RTTStreams[i]) {
|
|
|
|
m0RTTStreams[i]->Finish0RTT(aRestart, aAlpnChanged);
|
2017-02-21 20:19:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aRestart) {
|
|
|
|
// 0RTT failed
|
|
|
|
if (aAlpnChanged) {
|
|
|
|
// This is a slightly more involved case - we need to get all our streams/
|
|
|
|
// transactions back in the queue so they can restart as http/1
|
|
|
|
|
|
|
|
// These must be set this way to ensure we gracefully restart all streams
|
|
|
|
mGoAwayID = 0;
|
|
|
|
mCleanShutdown = true;
|
|
|
|
|
|
|
|
// Close takes care of the rest of our work for us. The reason code here
|
|
|
|
// doesn't matter, as we aren't actually going to send a GOAWAY frame, but
|
|
|
|
// we use NS_ERROR_NET_RESET as it's closest to the truth.
|
|
|
|
Close(NS_ERROR_NET_RESET);
|
|
|
|
} else {
|
|
|
|
// This is the easy case - early data failed, but we're speaking h2, so
|
|
|
|
// we just need to rewind to the beginning of the preamble and try again.
|
|
|
|
mOutputQueueSent = 0;
|
2017-09-22 07:09:57 +00:00
|
|
|
|
|
|
|
for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
|
|
|
|
if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
|
|
|
|
TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
|
|
|
|
}
|
|
|
|
}
|
2017-02-21 20:19:46 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// 0RTT succeeded
|
2017-09-22 07:09:57 +00:00
|
|
|
for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
|
|
|
|
if (mCannotDo0RTTStreams[i] && VerifyStream(mCannotDo0RTTStreams[i])) {
|
|
|
|
TransactionHasDataToWrite(mCannotDo0RTTStreams[i]);
|
|
|
|
}
|
|
|
|
}
|
2017-02-21 20:19:46 +00:00
|
|
|
// Make sure we look for any incoming data in repsonse to our early data.
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ResumeRecv();
|
2017-02-21 20:19:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mAttemptingEarlyData = false;
|
|
|
|
m0RTTStreams.Clear();
|
2017-09-22 07:09:57 +00:00
|
|
|
mCannotDo0RTTStreams.Clear();
|
2017-02-21 20:19:46 +00:00
|
|
|
RealignOutputQueue();
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2017-05-04 10:14:54 +00:00
|
|
|
void
|
|
|
|
Http2Session::SetFastOpenStatus(uint8_t aStatus)
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::SetFastOpenStatus %d [this=%p]",
|
|
|
|
aStatus, this));
|
|
|
|
|
|
|
|
for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
|
2017-05-09 09:37:45 +00:00
|
|
|
if (m0RTTStreams[i]) {
|
|
|
|
m0RTTStreams[i]->Transaction()->SetFastOpenStatus(aStatus);
|
2017-05-04 10:14:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-11 19:53:27 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream,
|
|
|
|
nsAHttpSegmentWriter * writer,
|
|
|
|
uint32_t count, uint32_t *countWritten)
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n",
|
|
|
|
this, pushConnectedStream->StreamID()));
|
|
|
|
mSegmentWriter = writer;
|
|
|
|
nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
|
|
|
|
mSegmentWriter = nullptr;
|
|
|
|
|
|
|
|
// The pipe in nsHttpTransaction rewrites CLOSED error codes into OK
|
|
|
|
// so we need this check to determine the truth.
|
|
|
|
if (NS_SUCCEEDED(rv) && !*countWritten &&
|
|
|
|
pushConnectedStream->PushSource() &&
|
|
|
|
pushConnectedStream->PushSource()->GetPushComplete()) {
|
|
|
|
rv = NS_BASE_STREAM_CLOSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rv == NS_BASE_STREAM_CLOSED) {
|
|
|
|
CleanupStream(pushConnectedStream, NS_OK, CANCEL_ERROR);
|
|
|
|
rv = NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we return OK to nsHttpConnection it will use mSocketInCondition
|
|
|
|
// to determine whether to schedule more reads, incorrectly
|
|
|
|
// assuming that nsHttpConnection::OnSocketWrite() was called.
|
|
|
|
if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
|
|
rv = NS_BASE_STREAM_WOULD_BLOCK;
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ResumeRecv();
|
2015-09-11 19:53:27 +00:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::ProcessSlowConsumer(Http2Stream *slowConsumer,
|
|
|
|
nsAHttpSegmentWriter * writer,
|
|
|
|
uint32_t count, uint32_t *countWritten)
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::ProcessSlowConsumer %p 0x%X\n",
|
|
|
|
this, slowConsumer->StreamID()));
|
|
|
|
mSegmentWriter = writer;
|
|
|
|
nsresult rv = slowConsumer->WriteSegments(this, count, countWritten);
|
|
|
|
mSegmentWriter = nullptr;
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::ProcessSlowConsumer Writesegments %p 0x%X rv %" PRIX32 " %d\n",
|
|
|
|
this, slowConsumer->StreamID(), static_cast<uint32_t>(rv), *countWritten));
|
2015-09-11 19:53:27 +00:00
|
|
|
if (NS_SUCCEEDED(rv) && !*countWritten && slowConsumer->RecvdFin()) {
|
|
|
|
rv = NS_BASE_STREAM_CLOSED;
|
|
|
|
}
|
|
|
|
|
2015-10-07 21:45:46 +00:00
|
|
|
if (NS_SUCCEEDED(rv) && (*countWritten > 0)) {
|
|
|
|
// There have been buffered bytes successfully fed into the
|
|
|
|
// formerly blocked consumer. Repeat until buffer empty or
|
|
|
|
// consumer is blocked again.
|
2015-09-11 19:53:27 +00:00
|
|
|
UpdateLocalRwin(slowConsumer, 0);
|
|
|
|
ConnectSlowConsumer(slowConsumer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rv == NS_BASE_STREAM_CLOSED) {
|
|
|
|
CleanupStream(slowConsumer, NS_OK, CANCEL_ERROR);
|
|
|
|
rv = NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
void
|
|
|
|
Http2Session::UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes)
|
|
|
|
{
|
|
|
|
if (!stream) // this is ok - it means there was a data frame for a rst stream
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If this data packet was not for a valid or live stream then there
|
|
|
|
// is no reason to mess with the flow control
|
|
|
|
if (!stream || stream->RecvdFin() || stream->RecvdReset() ||
|
|
|
|
mInputFrameFinal) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream->DecrementClientReceiveWindow(bytes);
|
|
|
|
|
|
|
|
// Don't necessarily ack every data packet. Only do it
|
|
|
|
// after a significant amount of data.
|
|
|
|
uint64_t unacked = stream->LocalUnAcked();
|
|
|
|
int64_t localWindow = stream->ClientReceiveWindow();
|
|
|
|
|
|
|
|
LOG3(("Http2Session::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u "
|
2016-12-16 03:16:31 +00:00
|
|
|
"unacked=%" PRIu64 " localWindow=%" PRId64 "\n",
|
2013-10-10 00:21:49 +00:00
|
|
|
this, stream->StreamID(), bytes, unacked, localWindow));
|
|
|
|
|
|
|
|
if (!unacked)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!stream->HasSink()) {
|
|
|
|
LOG3(("Http2Session::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n",
|
|
|
|
this, stream->StreamID()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate window updates directly out of session instead of the stream
|
|
|
|
// in order to avoid queue delays in getting the 'ACK' out.
|
|
|
|
uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU;
|
|
|
|
|
|
|
|
LOG3(("Http2Session::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n",
|
|
|
|
this, stream->StreamID(), toack));
|
|
|
|
stream->IncrementClientReceiveWindow(toack);
|
2014-08-05 15:41:09 +00:00
|
|
|
if (toack == 0) {
|
|
|
|
// Ensure we never send an illegal 0 window update
|
|
|
|
return;
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// room for this packet needs to be ensured before calling this function
|
|
|
|
char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
|
2014-08-05 15:41:09 +00:00
|
|
|
mOutputQueueUsed += kFrameHeaderBytes + 4;
|
2013-10-10 00:21:49 +00:00
|
|
|
MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
|
|
|
|
|
|
|
|
CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, stream->StreamID());
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, stream, "Stream Window Update", packet, kFrameHeaderBytes + 4);
|
2013-10-10 00:21:49 +00:00
|
|
|
// dont flush here, this write can commonly be coalesced with a
|
|
|
|
// session window update to immediately follow.
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::UpdateLocalSessionWindow(uint32_t bytes)
|
|
|
|
{
|
|
|
|
if (!bytes)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mLocalSessionWindow -= bytes;
|
|
|
|
|
|
|
|
LOG3(("Http2Session::UpdateLocalSessionWindow this=%p newbytes=%u "
|
2016-12-16 03:16:31 +00:00
|
|
|
"localWindow=%" PRId64 "\n", this, bytes, mLocalSessionWindow));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Don't necessarily ack every data packet. Only do it
|
|
|
|
// after a significant amount of data.
|
2015-09-11 19:53:27 +00:00
|
|
|
if ((mLocalSessionWindow > (mInitialRwin - kMinimumToAck)) &&
|
2013-10-10 00:21:49 +00:00
|
|
|
(mLocalSessionWindow > kEmergencyWindowThreshold))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Only send max bits of window updates at a time.
|
2015-09-11 19:53:27 +00:00
|
|
|
uint64_t toack64 = mInitialRwin - mLocalSessionWindow;
|
2013-10-10 00:21:49 +00:00
|
|
|
uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU;
|
|
|
|
|
|
|
|
LOG3(("Http2Session::UpdateLocalSessionWindow Ack this=%p acksize=%u\n",
|
|
|
|
this, toack));
|
|
|
|
mLocalSessionWindow += toack;
|
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
if (toack == 0) {
|
|
|
|
// Ensure we never send an illegal 0 window update
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
// room for this packet needs to be ensured before calling this function
|
|
|
|
char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
|
2014-08-05 15:41:09 +00:00
|
|
|
mOutputQueueUsed += kFrameHeaderBytes + 4;
|
2013-10-10 00:21:49 +00:00
|
|
|
MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize);
|
|
|
|
|
|
|
|
CreateFrameHeader(packet, 4, FRAME_TYPE_WINDOW_UPDATE, 0, 0);
|
2015-04-20 18:07:16 +00:00
|
|
|
NetworkEndian::writeUint32(packet + kFrameHeaderBytes, toack);
|
2013-10-10 00:21:49 +00:00
|
|
|
|
2014-08-05 15:41:09 +00:00
|
|
|
LogIO(this, nullptr, "Session Window Update", packet, kFrameHeaderBytes + 4);
|
2013-10-10 00:21:49 +00:00
|
|
|
// dont flush here, this write can commonly be coalesced with others
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::UpdateLocalRwin(Http2Stream *stream, uint32_t bytes)
|
|
|
|
{
|
|
|
|
// make sure there is room for 2 window updates even though
|
|
|
|
// we may not generate any.
|
2014-08-05 15:41:09 +00:00
|
|
|
EnsureOutputBuffer(2 * (kFrameHeaderBytes + 4));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
UpdateLocalStreamWindow(stream, bytes);
|
|
|
|
UpdateLocalSessionWindow(bytes);
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::Close(nsresult aReason)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (mClosed)
|
|
|
|
return;
|
|
|
|
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::Close %p %" PRIX32, this, static_cast<uint32_t>(aReason)));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
mClosed = true;
|
|
|
|
|
2016-01-28 02:38:45 +00:00
|
|
|
Shutdown();
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
mStreamIDHash.Clear();
|
|
|
|
mStreamTransactionHash.Clear();
|
|
|
|
|
|
|
|
uint32_t goAwayReason;
|
|
|
|
if (mGoAwayReason != NO_HTTP_ERROR) {
|
|
|
|
goAwayReason = mGoAwayReason;
|
|
|
|
} else if (NS_SUCCEEDED(aReason)) {
|
|
|
|
goAwayReason = NO_HTTP_ERROR;
|
|
|
|
} else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
|
|
|
|
goAwayReason = PROTOCOL_ERROR;
|
|
|
|
} else {
|
|
|
|
goAwayReason = INTERNAL_ERROR;
|
|
|
|
}
|
2017-02-21 20:19:46 +00:00
|
|
|
if (!mAttemptingEarlyData) {
|
|
|
|
GenerateGoAway(goAwayReason);
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
mConnection = nullptr;
|
|
|
|
mSegmentReader = nullptr;
|
|
|
|
mSegmentWriter = nullptr;
|
|
|
|
}
|
|
|
|
|
2014-04-20 16:01:35 +00:00
|
|
|
nsHttpConnectionInfo *
|
|
|
|
Http2Session::ConnectionInfo()
|
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsHttpConnectionInfo> ci;
|
2014-04-20 16:01:35 +00:00
|
|
|
GetConnectionInfo(getter_AddRefs(ci));
|
|
|
|
return ci.get();
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
void
|
|
|
|
Http2Session::CloseTransaction(nsAHttpTransaction *aTransaction,
|
|
|
|
nsresult aResult)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32, this, aTransaction,
|
|
|
|
static_cast<uint32_t>(aResult)));
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// Generally this arrives as a cancel event from the connection manager.
|
|
|
|
|
|
|
|
// need to find the stream and call CleanupStream() on it.
|
|
|
|
Http2Stream *stream = mStreamTransactionHash.Get(aTransaction);
|
|
|
|
if (!stream) {
|
2016-12-16 03:16:31 +00:00
|
|
|
LOG3(("Http2Session::CloseTransaction %p %p %" PRIx32 " - not found.",
|
|
|
|
this, aTransaction, static_cast<uint32_t>(aResult)));
|
2013-10-10 00:21:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-09-18 12:13:00 +00:00
|
|
|
LOG3(("Http2Session::CloseTransaction probably a cancel. "
|
2016-12-16 03:16:31 +00:00
|
|
|
"this=%p, trans=%p, result=%" PRIx32 ", streamID=0x%X stream=%p",
|
|
|
|
this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamID(), stream));
|
2013-10-10 00:21:49 +00:00
|
|
|
CleanupStream(stream, aResult, CANCEL_ERROR);
|
2016-12-27 11:05:13 +00:00
|
|
|
nsresult rv = ResumeRecv();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::CloseTransaction %p %p %x ResumeRecv returned %x",
|
|
|
|
this, aTransaction, static_cast<uint32_t>(aResult),
|
|
|
|
static_cast<uint32_t>(rv)));
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// nsAHttpSegmentReader
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::OnReadSegment(const char *buf,
|
|
|
|
uint32_t count, uint32_t *countRead)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
nsresult rv;
|
|
|
|
|
|
|
|
// If we can release old queued data then we can try and write the new
|
|
|
|
// data directly to the network without using the output queue at all
|
|
|
|
if (mOutputQueueUsed)
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
if (!mOutputQueueUsed && mSegmentReader) {
|
|
|
|
// try and write directly without output queue
|
|
|
|
rv = mSegmentReader->OnReadSegment(buf, count, countRead);
|
|
|
|
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
|
|
*countRead = 0;
|
|
|
|
} else if (NS_FAILED(rv)) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*countRead < count) {
|
|
|
|
uint32_t required = count - *countRead;
|
|
|
|
// assuming a commitment() happened, this ensurebuffer is a nop
|
|
|
|
// but just in case the queuesize is too small for the required data
|
|
|
|
// call ensurebuffer().
|
|
|
|
EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
|
|
|
|
memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
|
|
|
|
mOutputQueueUsed = required;
|
|
|
|
}
|
|
|
|
|
|
|
|
*countRead = count;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point we are going to buffer the new data in the output
|
|
|
|
// queue if it fits. By coalescing multiple small submissions into one larger
|
|
|
|
// buffer we can get larger writes out to the network later on.
|
|
|
|
|
|
|
|
// This routine should not be allowed to fill up the output queue
|
|
|
|
// all on its own - at least kQueueReserved bytes are always left
|
|
|
|
// for other routines to use - but this is an all-or-nothing function,
|
|
|
|
// so if it will not all fit just return WOULD_BLOCK
|
|
|
|
|
|
|
|
if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
|
|
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
|
|
|
|
memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
|
|
|
|
mOutputQueueUsed += count;
|
|
|
|
*countRead = count;
|
|
|
|
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment)
|
|
|
|
{
|
2017-02-21 20:19:46 +00:00
|
|
|
if (mOutputQueueUsed && !mAttemptingEarlyData)
|
2017-11-08 18:30:01 +00:00
|
|
|
FlushOutputQueue();
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
// would there be enough room to buffer this if needed?
|
|
|
|
if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
|
|
|
|
return NS_OK;
|
|
|
|
|
|
|
|
// if we are using part of our buffers already, try again later unless
|
|
|
|
// forceCommitment is set.
|
|
|
|
if (mOutputQueueUsed && !forceCommitment)
|
|
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
|
|
|
|
if (mOutputQueueUsed) {
|
|
|
|
// normally we avoid the memmove of RealignOutputQueue, but we'll try
|
|
|
|
// it if forceCommitment is set before growing the buffer.
|
|
|
|
RealignOutputQueue();
|
|
|
|
|
|
|
|
// is there enough room now?
|
|
|
|
if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// resize the buffers as needed
|
|
|
|
EnsureOutputBuffer(count + kQueueReserved);
|
|
|
|
|
|
|
|
MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
|
|
|
|
"buffer not as large as expected");
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// nsAHttpSegmentWriter
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::OnWriteSegment(char *buf,
|
|
|
|
uint32_t count, uint32_t *countWritten)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
nsresult rv;
|
|
|
|
|
|
|
|
if (!mSegmentWriter) {
|
|
|
|
// the only way this could happen would be if Close() were called on the
|
|
|
|
// stack with WriteSegments()
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
2015-10-07 16:55:07 +00:00
|
|
|
if (mDownstreamState == NOT_USING_NETWORK ||
|
2015-10-26 19:13:53 +00:00
|
|
|
mDownstreamState == BUFFERING_FRAME_HEADER ||
|
|
|
|
mDownstreamState == DISCARDING_DATA_FRAME_PADDING) {
|
2015-09-11 19:53:27 +00:00
|
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
if (mDownstreamState == PROCESSING_DATA_FRAME) {
|
|
|
|
|
|
|
|
if (mInputFrameFinal &&
|
|
|
|
mInputFrameDataRead == mInputFrameDataSize) {
|
|
|
|
*countWritten = 0;
|
|
|
|
SetNeedsCleanup();
|
|
|
|
return NS_BASE_STREAM_CLOSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
|
|
|
|
rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
|
|
|
|
if (NS_FAILED(rv))
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
LogIO(this, mInputFrameDataStream, "Reading Data Frame",
|
|
|
|
buf, *countWritten);
|
|
|
|
|
|
|
|
mInputFrameDataRead += *countWritten;
|
2014-03-26 17:58:09 +00:00
|
|
|
if (mPaddingLength && (mInputFrameDataSize - mInputFrameDataRead <= mPaddingLength)) {
|
|
|
|
// We are crossing from real HTTP data into the realm of padding. If
|
|
|
|
// we've actually crossed the line, we need to munge countWritten for the
|
|
|
|
// sake of goodness and sanity. No matter what, any future calls to
|
|
|
|
// WriteSegments need to just discard data until we reach the end of this
|
|
|
|
// frame.
|
2014-06-04 02:41:26 +00:00
|
|
|
if (mInputFrameDataSize != mInputFrameDataRead) {
|
|
|
|
// Only change state if we still have padding to read. If we don't do
|
|
|
|
// this, we can end up hanging on frames that combine real data,
|
|
|
|
// padding, and END_STREAM (see bug 1019921)
|
|
|
|
ChangeDownstreamState(DISCARDING_DATA_FRAME_PADDING);
|
|
|
|
}
|
2014-03-26 17:58:09 +00:00
|
|
|
uint32_t paddingRead = mPaddingLength - (mInputFrameDataSize - mInputFrameDataRead);
|
|
|
|
LOG3(("Http2Session::OnWriteSegment %p stream 0x%X len=%d read=%d "
|
|
|
|
"crossed from HTTP data into padding (%d of %d) countWritten=%d",
|
|
|
|
this, mInputFrameID, mInputFrameDataSize, mInputFrameDataRead,
|
|
|
|
paddingRead, mPaddingLength, *countWritten));
|
|
|
|
*countWritten -= paddingRead;
|
|
|
|
LOG3(("Http2Session::OnWriteSegment %p stream 0x%X new countWritten=%d",
|
|
|
|
this, mInputFrameID, *countWritten));
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
|
|
|
|
if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameFinal)
|
|
|
|
ResetDownstreamState();
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mDownstreamState == PROCESSING_COMPLETE_HEADERS) {
|
|
|
|
|
|
|
|
if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
|
|
|
|
mInputFrameFinal) {
|
|
|
|
*countWritten = 0;
|
|
|
|
SetNeedsCleanup();
|
|
|
|
return NS_BASE_STREAM_CLOSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
count = std::min(count,
|
|
|
|
mFlatHTTPResponseHeaders.Length() -
|
|
|
|
mFlatHTTPResponseHeadersOut);
|
|
|
|
memcpy(buf,
|
|
|
|
mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
|
|
|
|
count);
|
|
|
|
mFlatHTTPResponseHeadersOut += count;
|
|
|
|
*countWritten = count;
|
|
|
|
|
|
|
|
if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
|
|
|
|
if (!mInputFrameFinal) {
|
|
|
|
// If more frames are expected in this stream, then reset the state so they can be
|
|
|
|
// handled. Otherwise (e.g. a 0 length response with the fin on the incoming headers)
|
|
|
|
// stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can
|
|
|
|
// cleanup the stream.
|
|
|
|
ResetDownstreamState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2015-10-22 19:48:57 +00:00
|
|
|
MOZ_ASSERT(false);
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::SetNeedsCleanup()
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::SetNeedsCleanup %p - recorded downstream fin of "
|
|
|
|
"stream %p 0x%X", this, mInputFrameDataStream,
|
|
|
|
mInputFrameDataStream->StreamID()));
|
|
|
|
|
|
|
|
// This will result in Close() being called
|
|
|
|
MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
|
2016-05-04 21:03:59 +00:00
|
|
|
mInputFrameDataStream->SetResponseIsComplete();
|
2013-10-10 00:21:49 +00:00
|
|
|
mNeedsCleanup = mInputFrameDataStream;
|
|
|
|
ResetDownstreamState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::ConnectPushedStream(Http2Stream *stream)
|
|
|
|
{
|
2015-09-11 19:53:27 +00:00
|
|
|
mPushesReadyForRead.Push(stream);
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ForceRecv();
|
2015-09-11 19:53:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::ConnectSlowConsumer(Http2Stream *stream)
|
|
|
|
{
|
|
|
|
LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n",
|
|
|
|
this, stream->StreamID()));
|
|
|
|
mSlowConsumersReadyForRead.Push(stream);
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ForceRecv();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 15:46:13 +00:00
|
|
|
uint32_t
|
|
|
|
Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-05-16 15:46:13 +00:00
|
|
|
uint32_t rv = 0;
|
|
|
|
mTunnelHash.Get(aConnInfo->HashKey(), &rv);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::RegisterTunnel(Http2Stream *aTunnel)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-05-16 15:46:13 +00:00
|
|
|
nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
|
|
|
|
uint32_t newcount = FindTunnelCount(ci) + 1;
|
|
|
|
mTunnelHash.Remove(ci->HashKey());
|
|
|
|
mTunnelHash.Put(ci->HashKey(), newcount);
|
|
|
|
LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]",
|
|
|
|
this, aTunnel, newcount, ci->HashKey().get()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::UnRegisterTunnel(Http2Stream *aTunnel)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-05-16 15:46:13 +00:00
|
|
|
nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
|
|
|
|
MOZ_ASSERT(FindTunnelCount(ci));
|
|
|
|
uint32_t newcount = FindTunnelCount(ci) - 1;
|
|
|
|
mTunnelHash.Remove(ci->HashKey());
|
|
|
|
if (newcount) {
|
|
|
|
mTunnelHash.Put(ci->HashKey(), newcount);
|
|
|
|
}
|
|
|
|
LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
|
|
|
|
this, aTunnel, newcount, ci->HashKey().get()));
|
|
|
|
}
|
|
|
|
|
2015-02-19 18:50:15 +00:00
|
|
|
void
|
|
|
|
Http2Session::CreateTunnel(nsHttpTransaction *trans,
|
|
|
|
nsHttpConnectionInfo *ci,
|
|
|
|
nsIInterfaceRequestor *aCallbacks)
|
|
|
|
{
|
|
|
|
LOG(("Http2Session::CreateTunnel %p %p make new tunnel\n", this, trans));
|
|
|
|
// The connect transaction will hold onto the underlying http
|
|
|
|
// transaction so that an auth created by the connect can be mappped
|
|
|
|
// to the correct security callbacks
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<SpdyConnectTransaction> connectTrans =
|
2015-02-19 18:50:15 +00:00
|
|
|
new SpdyConnectTransaction(ci, aCallbacks, trans->Caps(), trans, this);
|
2017-01-12 09:48:45 +00:00
|
|
|
DebugOnly<bool> rv = AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false, nullptr);
|
|
|
|
MOZ_ASSERT(rv);
|
2015-02-19 18:50:15 +00:00
|
|
|
Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
|
|
|
|
MOZ_ASSERT(tunnel);
|
|
|
|
RegisterTunnel(tunnel);
|
|
|
|
}
|
|
|
|
|
2014-05-16 15:46:13 +00:00
|
|
|
void
|
|
|
|
Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
|
|
|
|
nsIInterfaceRequestor *aCallbacks)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-05-16 15:46:13 +00:00
|
|
|
nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
|
|
|
|
nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
|
|
|
|
MOZ_ASSERT(trans);
|
|
|
|
|
|
|
|
LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
|
|
|
|
|
|
|
|
aHttpTransaction->SetConnection(nullptr);
|
|
|
|
|
|
|
|
// this transaction has done its work of setting up a tunnel, let
|
|
|
|
// the connection manager queue it if necessary
|
2015-02-19 18:50:15 +00:00
|
|
|
trans->SetTunnelProvider(this);
|
2014-07-07 15:43:11 +00:00
|
|
|
trans->EnableKeepAlive();
|
2014-05-16 15:46:13 +00:00
|
|
|
|
|
|
|
if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
|
|
|
|
LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s",
|
|
|
|
this, ci->HashKey().get()));
|
2015-02-19 18:50:15 +00:00
|
|
|
CreateTunnel(trans, ci, aCallbacks);
|
2014-07-07 15:43:11 +00:00
|
|
|
} else {
|
|
|
|
// requeue it. The connection manager is responsible for actually putting
|
2015-02-19 18:50:15 +00:00
|
|
|
// this on the tunnel connection with the specific ci. If that can't
|
|
|
|
// happen the cmgr checks with us via MaybeReTunnel() to see if it should
|
|
|
|
// make a new tunnel or just wait longer.
|
2014-07-07 15:43:11 +00:00
|
|
|
LOG3(("Http2Session::DispatchOnTunnel %p trans=%p queue in connection manager",
|
|
|
|
this, trans));
|
2016-12-27 11:05:13 +00:00
|
|
|
nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::DispatchOnTunnel %p trans=%p failed to initiate "
|
|
|
|
"transaction (%08x)", this, trans, static_cast<uint32_t>(rv)));
|
|
|
|
}
|
2014-05-16 15:46:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-19 18:50:15 +00:00
|
|
|
// From ASpdySession
|
|
|
|
bool
|
|
|
|
Http2Session::MaybeReTunnel(nsAHttpTransaction *aHttpTransaction)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2015-02-19 18:50:15 +00:00
|
|
|
nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
|
|
|
|
LOG(("Http2Session::MaybeReTunnel %p trans=%p\n", this, trans));
|
|
|
|
if (!trans || trans->TunnelProvider() != this) {
|
|
|
|
// this isn't really one of our transactions.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mClosed || mShouldGoAway) {
|
|
|
|
LOG(("Http2Session::MaybeReTunnel %p %p session closed - requeue\n", this, trans));
|
|
|
|
trans->SetTunnelProvider(nullptr);
|
2016-12-27 11:05:13 +00:00
|
|
|
nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
LOG3(("Http2Session::MaybeReTunnel %p trans=%p failed to initiate "
|
|
|
|
"transaction (%08x)", this, trans, static_cast<uint32_t>(rv)));
|
|
|
|
}
|
2015-02-19 18:50:15 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
|
|
|
|
LOG(("Http2Session:MaybeReTunnel %p %p count=%d limit %d\n",
|
|
|
|
this, trans, FindTunnelCount(ci), gHttpHandler->MaxConnectionsPerOrigin()));
|
|
|
|
if (FindTunnelCount(ci) >= gHttpHandler->MaxConnectionsPerOrigin()) {
|
|
|
|
// patience - a tunnel will open up.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG(("Http2Session::MaybeReTunnel %p %p make new tunnel\n", this, trans));
|
|
|
|
CreateTunnel(trans, ci, trans->SecurityCallbacks());
|
|
|
|
return true;
|
|
|
|
}
|
2014-05-16 15:46:13 +00:00
|
|
|
|
2014-02-24 21:27:27 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::BufferOutput(const char *buf,
|
|
|
|
uint32_t count,
|
|
|
|
uint32_t *countRead)
|
|
|
|
{
|
|
|
|
nsAHttpSegmentReader *old = mSegmentReader;
|
|
|
|
mSegmentReader = nullptr;
|
|
|
|
nsresult rv = OnReadSegment(buf, count, countRead);
|
|
|
|
mSegmentReader = old;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-08-15 13:39:53 +00:00
|
|
|
bool // static
|
|
|
|
Http2Session::ALPNCallback(nsISupports *securityInfo)
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
|
|
|
|
LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
|
|
|
|
if (ssl) {
|
|
|
|
int16_t version = ssl->GetSSLVersionOffered();
|
|
|
|
LOG3(("Http2Session::ALPNCallback version=%x\n", version));
|
2018-08-27 18:56:44 +00:00
|
|
|
|
|
|
|
if (version == nsISSLSocketControl::TLS_VERSION_1_2 &&
|
|
|
|
!gHttpHandler->IsH2MandatorySuiteEnabled()) {
|
|
|
|
LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-15 13:39:53 +00:00
|
|
|
if (version >= nsISSLSocketControl::TLS_VERSION_1_2) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
nsresult
|
|
|
|
Http2Session::ConfirmTLSProfile()
|
|
|
|
{
|
2017-02-21 20:19:46 +00:00
|
|
|
if (mTLSProfileConfirmed) {
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_OK;
|
2017-02-21 20:19:46 +00:00
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
|
|
|
|
this, mConnection.get()));
|
|
|
|
|
2017-02-21 20:19:46 +00:00
|
|
|
if (mAttemptingEarlyData) {
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p temporarily passing due to early data\n", this));
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
if (!gHttpHandler->EnforceHttp2TlsProfile()) {
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this));
|
|
|
|
mTLSProfileConfirmed = true;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mConnection)
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
|
|
|
nsCOMPtr<nsISupports> securityInfo;
|
|
|
|
mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
|
|
|
|
nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p sslsocketcontrol=%p\n", this, ssl.get()));
|
|
|
|
if (!ssl)
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
|
|
|
int16_t version = ssl->GetSSLVersionUsed();
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p version=%x\n", this, version));
|
2014-03-26 17:58:09 +00:00
|
|
|
if (version < nsISSLSocketControl::TLS_VERSION_1_2) {
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of TLS1.2\n", this));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(INADEQUATE_SECURITY);
|
2014-03-26 17:58:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t kea = ssl->GetKEAUsed();
|
|
|
|
if (kea != ssl_kea_dh && kea != ssl_kea_ecdh) {
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to invalid KEA %d\n",
|
|
|
|
this, kea));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(INADEQUATE_SECURITY);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
2014-03-26 17:58:09 +00:00
|
|
|
|
2014-05-14 17:37:05 +00:00
|
|
|
uint32_t keybits = ssl->GetKEAKeyBits();
|
|
|
|
if (kea == ssl_kea_dh && keybits < 2048) {
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to DH %d < 2048\n",
|
|
|
|
this, keybits));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(INADEQUATE_SECURITY);
|
2016-07-28 14:48:00 +00:00
|
|
|
} else if (kea == ssl_kea_ecdh && keybits < 224) { // see rfc7540 9.2.1.
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to ECDH %d < 224\n",
|
2014-05-14 17:37:05 +00:00
|
|
|
this, keybits));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(INADEQUATE_SECURITY);
|
2014-05-14 17:37:05 +00:00
|
|
|
}
|
2014-03-26 17:58:09 +00:00
|
|
|
|
2014-08-04 20:54:10 +00:00
|
|
|
int16_t macAlgorithm = ssl->GetMACAlgorithmUsed();
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p MAC Algortihm (aead==6) %d\n",
|
|
|
|
this, macAlgorithm));
|
|
|
|
if (macAlgorithm != nsISSLSocketControl::SSL_MAC_AEAD) {
|
|
|
|
LOG3(("Http2Session::ConfirmTLSProfile %p FAILED due to lack of AEAD\n", this));
|
2018-10-25 20:46:01 +00:00
|
|
|
return SessionError(INADEQUATE_SECURITY);
|
2014-08-04 20:54:10 +00:00
|
|
|
}
|
|
|
|
|
2014-03-26 17:58:09 +00:00
|
|
|
/* We are required to send SNI. We do that already, so no check is done
|
|
|
|
* here to make sure we did. */
|
|
|
|
|
|
|
|
/* We really should check to ensure TLS compression isn't enabled on
|
|
|
|
* this connection. However, we never enable TLS compression on our end,
|
|
|
|
* anyway, so it'll never be on. All the same, see https://bugzil.la/965881
|
|
|
|
* for the possibility for an interface to ensure it never gets turned on. */
|
2013-10-10 00:21:49 +00:00
|
|
|
|
|
|
|
mTLSProfileConfirmed = true;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Modified methods of nsAHttpConnection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::TransactionHasDataToWrite %p trans=%p", this, caller));
|
|
|
|
|
|
|
|
// a trapped signal from the http transaction to the connection that
|
|
|
|
// it is no longer blocked on read.
|
|
|
|
|
|
|
|
Http2Stream *stream = mStreamTransactionHash.Get(caller);
|
|
|
|
if (!stream || !VerifyStream(stream)) {
|
|
|
|
LOG3(("Http2Session::TransactionHasDataToWrite %p caller %p not found",
|
|
|
|
this, caller));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::TransactionHasDataToWrite %p ID is 0x%X\n",
|
|
|
|
this, stream->StreamID()));
|
|
|
|
|
2016-07-05 21:43:21 +00:00
|
|
|
if (!mClosed) {
|
|
|
|
mReadyForWrite.Push(stream);
|
|
|
|
SetWriteCallbacks();
|
|
|
|
} else {
|
|
|
|
LOG3(("Http2Session::TransactionHasDataToWrite %p closed so not setting Ready4Write\n",
|
|
|
|
this));
|
|
|
|
}
|
2014-05-16 15:46:13 +00:00
|
|
|
|
|
|
|
// NSPR poll will not poll the network if there are non system PR_FileDesc's
|
|
|
|
// that are ready - so we can get into a deadlock waiting for the system IO
|
|
|
|
// to come back here if we don't force the send loop manually.
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ForceSend();
|
2015-09-11 19:53:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::TransactionHasDataToRecv(nsAHttpTransaction *caller)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2015-09-11 19:53:27 +00:00
|
|
|
LOG3(("Http2Session::TransactionHasDataToRecv %p trans=%p", this, caller));
|
2014-05-16 15:46:13 +00:00
|
|
|
|
2015-09-11 19:53:27 +00:00
|
|
|
// a signal from the http transaction to the connection that it will consume more
|
|
|
|
Http2Stream *stream = mStreamTransactionHash.Get(caller);
|
|
|
|
if (!stream || !VerifyStream(stream)) {
|
|
|
|
LOG3(("Http2Session::TransactionHasDataToRecv %p caller %p not found",
|
|
|
|
this, caller));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG3(("Http2Session::TransactionHasDataToRecv %p ID is 0x%X\n",
|
|
|
|
this, stream->StreamID()));
|
|
|
|
ConnectSlowConsumer(stream);
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x",
|
|
|
|
this, stream, stream->StreamID()));
|
|
|
|
|
|
|
|
mReadyForWrite.Push(stream);
|
|
|
|
SetWriteCallbacks();
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ForceSend();
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::IsPersistent()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::TakeTransport(nsISocketTransport **,
|
|
|
|
nsIAsyncInputStream **, nsIAsyncOutputStream **)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(false, "TakeTransport of Http2Session");
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
2016-03-29 19:35:56 +00:00
|
|
|
already_AddRefed<nsHttpConnection>
|
2013-10-10 00:21:49 +00:00
|
|
|
Http2Session::TakeHttpConnection()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
already_AddRefed<nsHttpConnection>
|
|
|
|
Http2Session::HttpConnection()
|
|
|
|
{
|
|
|
|
if (mConnection) {
|
|
|
|
return mConnection->HttpConnection();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2014-04-16 13:52:43 +00:00
|
|
|
void
|
|
|
|
Http2Session::GetSecurityCallbacks(nsIInterfaceRequestor **aOut)
|
|
|
|
{
|
|
|
|
*aOut = nullptr;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:21:49 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// unused methods of nsAHttpTransaction
|
|
|
|
// We can be sure of this because Http2Session is only constructed in
|
2014-04-16 13:52:43 +00:00
|
|
|
// nsHttpConnection and is never passed out of that object or a TLSFilterTransaction
|
|
|
|
// TLS tunnel
|
2013-10-10 00:21:49 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::SetConnection(nsAHttpConnection *)
|
|
|
|
{
|
|
|
|
// This is unexpected
|
|
|
|
MOZ_ASSERT(false, "Http2Session::SetConnection()");
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::SetProxyConnectFailed()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(false, "Http2Session::SetProxyConnectFailed()");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::IsDone()
|
|
|
|
{
|
|
|
|
return !mStreamTransactionHash.Count();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::Status()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(false, "Http2Session::Status()");
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
Http2Session::Caps()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(false, "Http2Session::Caps()");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Http2Session::SetDNSWasRefreshed()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(false, "Http2Session::SetDNSWasRefreshed()");
|
|
|
|
}
|
|
|
|
|
|
|
|
nsHttpRequestHead *
|
|
|
|
Http2Session::RequestHead()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
MOZ_ASSERT(false,
|
|
|
|
"Http2Session::RequestHead() "
|
|
|
|
"should not be called after http/2 is setup");
|
2018-04-30 16:46:04 +00:00
|
|
|
return nullptr;
|
2013-10-10 00:21:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
Http2Session::Http1xTransactionCount()
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::TakeSubTransactions(
|
2015-10-18 05:24:48 +00:00
|
|
|
nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions)
|
2013-10-10 00:21:49 +00:00
|
|
|
{
|
|
|
|
// Generally this cannot be done with http/2 as transactions are
|
|
|
|
// started right away.
|
|
|
|
|
|
|
|
LOG3(("Http2Session::TakeSubTransactions %p\n", this));
|
|
|
|
|
|
|
|
if (mConcurrentHighWater > 0)
|
|
|
|
return NS_ERROR_ALREADY_OPENED;
|
|
|
|
|
|
|
|
LOG3((" taking %d\n", mStreamTransactionHash.Count()));
|
|
|
|
|
2016-01-28 03:18:33 +00:00
|
|
|
for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
|
|
|
|
outTransactions.AppendElement(iter.Key());
|
|
|
|
|
|
|
|
// Removing the stream from the hash will delete the stream and drop the
|
|
|
|
// transaction reference the hash held.
|
|
|
|
iter.Remove();
|
|
|
|
}
|
2013-10-10 00:21:49 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Pass through methods of nsAHttpConnection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
nsAHttpConnection *
|
|
|
|
Http2Session::Connection()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2013-10-10 00:21:49 +00:00
|
|
|
return mConnection;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::OnHeadersAvailable(nsAHttpTransaction *transaction,
|
|
|
|
nsHttpRequestHead *requestHead,
|
|
|
|
nsHttpResponseHead *responseHead, bool *reset)
|
|
|
|
{
|
|
|
|
return mConnection->OnHeadersAvailable(transaction,
|
|
|
|
requestHead,
|
|
|
|
responseHead,
|
|
|
|
reset);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::IsReused()
|
|
|
|
{
|
|
|
|
return mConnection->IsReused();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
Http2Session::PushBack(const char *buf, uint32_t len)
|
|
|
|
{
|
|
|
|
return mConnection->PushBack(buf, len);
|
|
|
|
}
|
|
|
|
|
2014-09-11 10:55:00 +00:00
|
|
|
void
|
|
|
|
Http2Session::SendPing()
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2014-09-11 10:55:00 +00:00
|
|
|
|
|
|
|
if (mPreviousUsed) {
|
|
|
|
// alredy in progress, get out
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mPingSentEpoch = PR_IntervalNow();
|
|
|
|
if (!mPingSentEpoch) {
|
|
|
|
mPingSentEpoch = 1; // avoid the 0 sentinel value
|
|
|
|
}
|
|
|
|
if (!mPingThreshold ||
|
|
|
|
(mPingThreshold > gHttpHandler->NetworkChangedTimeout())) {
|
|
|
|
mPreviousPingThreshold = mPingThreshold;
|
|
|
|
mPreviousUsed = true;
|
|
|
|
mPingThreshold = gHttpHandler->NetworkChangedTimeout();
|
|
|
|
}
|
|
|
|
GeneratePing(false);
|
2016-12-27 11:05:13 +00:00
|
|
|
Unused << ResumeRecv();
|
2014-09-11 10:55:00 +00:00
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
bool
|
|
|
|
Http2Session::TestOriginFrame(const nsACString &hostname, int32_t port)
|
|
|
|
{
|
2017-04-27 18:34:42 +00:00
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
2017-04-03 21:23:55 +00:00
|
|
|
MOZ_ASSERT(mOriginFrameActivated);
|
|
|
|
|
|
|
|
nsAutoCString key(hostname);
|
|
|
|
key.Append (':');
|
|
|
|
key.AppendInt(port);
|
|
|
|
bool rv = mOriginFrame.Get(key);
|
2017-04-04 18:51:32 +00:00
|
|
|
LOG3(("TestOriginFrame() hash.get %p %s %d\n", this, key.get(), rv));
|
|
|
|
if (!rv && ConnectionInfo()) {
|
|
|
|
// the SNI is also implicitly in this list, so consult that too
|
|
|
|
nsHttpConnectionInfo *ci = ConnectionInfo();
|
|
|
|
rv = nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && (port == ci->OriginPort());
|
|
|
|
LOG3(("TestOriginFrame() %p sni test %d\n", this, rv));
|
|
|
|
}
|
2017-04-03 21:23:55 +00:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::TestJoinConnection(const nsACString &hostname, int32_t port)
|
|
|
|
{
|
2017-04-14 21:24:53 +00:00
|
|
|
return RealJoinConnection(hostname, port, true);
|
2017-04-14 21:24:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2017-04-15 07:32:29 +00:00
|
|
|
Http2Session::JoinConnection(const nsACString &hostname, int32_t port)
|
2017-04-14 21:24:53 +00:00
|
|
|
{
|
|
|
|
return RealJoinConnection(hostname, port, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Http2Session::RealJoinConnection(const nsACString &hostname, int32_t port,
|
|
|
|
bool justKidding)
|
2017-04-14 21:24:53 +00:00
|
|
|
{
|
2017-04-15 07:32:29 +00:00
|
|
|
if (!mConnection || mClosed || mShouldGoAway) {
|
2017-04-03 21:23:55 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-04-14 21:24:53 +00:00
|
|
|
|
|
|
|
nsHttpConnectionInfo *ci = ConnectionInfo();
|
|
|
|
if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && (port == ci->OriginPort())) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mReceivedSettings) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
if (mOriginFrameActivated) {
|
|
|
|
bool originFrameResult = TestOriginFrame(hostname, port);
|
|
|
|
if (!originFrameResult) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOG3(("JoinConnection %p no origin frame check used.\n", this));
|
|
|
|
}
|
|
|
|
|
2017-04-14 21:24:53 +00:00
|
|
|
nsAutoCString key(hostname);
|
|
|
|
key.Append(':');
|
|
|
|
key.Append(justKidding ? 'k' : '.');
|
|
|
|
key.AppendInt(port);
|
|
|
|
bool cachedResult;
|
|
|
|
if (mJoinConnectionCache.Get(key, &cachedResult)) {
|
|
|
|
LOG(("joinconnection [%p %s] %s result=%d cache\n",
|
|
|
|
this, ConnectionInfo()->HashKey().get(), key.get(),
|
|
|
|
cachedResult));
|
|
|
|
return cachedResult;
|
|
|
|
}
|
|
|
|
|
2017-04-03 21:23:55 +00:00
|
|
|
nsresult rv;
|
|
|
|
bool isJoined = false;
|
|
|
|
|
|
|
|
nsCOMPtr<nsISupports> securityInfo;
|
|
|
|
nsCOMPtr<nsISSLSocketControl> sslSocketControl;
|
|
|
|
|
|
|
|
mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
|
|
|
|
sslSocketControl = do_QueryInterface(securityInfo, &rv);
|
|
|
|
if (NS_FAILED(rv) || !sslSocketControl) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// try all the coalescable versions we support.
|
|
|
|
const SpdyInformation *info = gHttpHandler->SpdyInfo();
|
2017-04-14 21:24:53 +00:00
|
|
|
static_assert(SpdyInformation::kCount == 1, "assume 1 alpn version");
|
|
|
|
bool joinedReturn = false;
|
|
|
|
if (info->ProtocolEnabled(0)) {
|
|
|
|
if (justKidding) {
|
|
|
|
rv = sslSocketControl->TestJoinConnection(info->VersionString[0],
|
|
|
|
hostname, port, &isJoined);
|
|
|
|
} else {
|
|
|
|
rv = sslSocketControl->JoinConnection(info->VersionString[0],
|
2017-04-03 21:23:55 +00:00
|
|
|
hostname, port, &isJoined);
|
2017-04-14 21:24:53 +00:00
|
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv) && isJoined) {
|
|
|
|
joinedReturn = true;
|
2017-04-14 21:24:53 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-14 21:24:53 +00:00
|
|
|
|
|
|
|
LOG(("joinconnection [%p %s] %s result=%d lookup\n",
|
|
|
|
this, ConnectionInfo()->HashKey().get(), key.get(), joinedReturn));
|
|
|
|
mJoinConnectionCache.Put(key, joinedReturn);
|
|
|
|
if (!justKidding) {
|
|
|
|
// cache a kidding entry too as this one is good for both
|
|
|
|
nsAutoCString key2(hostname);
|
|
|
|
key2.Append(':');
|
|
|
|
key2.Append('k');
|
|
|
|
key2.AppendInt(port);
|
|
|
|
if (!mJoinConnectionCache.Get(key2)) {
|
|
|
|
mJoinConnectionCache.Put(key2, joinedReturn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return joinedReturn;
|
2017-04-03 21:23:55 +00:00
|
|
|
}
|
|
|
|
|
2017-08-30 06:19:00 +00:00
|
|
|
void
|
|
|
|
Http2Session::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
|
|
|
|
mCurrentForegroundTabOuterContentWindowId = windowId;
|
|
|
|
|
|
|
|
for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
|
|
|
|
iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 15:25:42 +00:00
|
|
|
} // namespace net
|
2013-10-10 00:21:49 +00:00
|
|
|
} // namespace mozilla
|