/* -*- 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" #include"nsCOMPtr.h" #include"nsStringFwd.h"
// Log on level :5, instead of default :4. #undef LOG #define LOG(args) LOG5(args) #undef LOG_ENABLED #define LOG_ENABLED() LOG5_ENABLED()
// Http2Session has multiple inheritance of things that implement nsISupports
NS_IMPL_ADDREF(Http2Session)
NS_IMPL_RELEASE(Http2Session)
NS_INTERFACE_MAP_BEGIN(Http2Session)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY_CONCRETE(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};
for (auto& stream : mTunnelStreams) {
ShutdownStream(stream, aReason);
}
}
void Http2Session::ShutdownStream(Http2StreamBase* aStream, nsresult aReason) { // 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 &&
(aStream->StreamID() > mGoAwayID || !aStream->HasRegisteredID())) {
CloseStream(aStream, NS_ERROR_NET_RESET); // can be restarted
} elseif (aStream->RecvdData()) {
CloseStream(aStream, NS_ERROR_NET_PARTIAL_TRANSFER);
} elseif (mGoAwayReason == INADEQUATE_SECURITY) {
CloseStream(aStream, NS_ERROR_NET_INADEQUATE_SECURITY);
} elseif (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) {
CloseStream(aStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
} elseif (!mCleanShutdown && PossibleZeroRTTRetryError(aReason)) {
CloseStream(aStream, aReason);
} else {
CloseStream(aStream, NS_ERROR_ABORT);
}
}
// 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;
MOZ_LOG(gHttpIOLog, LogLevel::Verbose, ("%s", linebuf));
}
line = linebuf;
snprintf(line, 128, "%08X: ", index);
line += 10;
}
snprintf(line, 128 - (line - linebuf), "%02X ",
(reinterpret_cast<const uint8_t*>(data))[index]);
line += 3;
} if (index) {
*line = 0;
MOZ_LOG(gHttpIOLog, LogLevel::Verbose, ("%s", linebuf));
}
}
using Http2ControlFx = nsresult (*)(Http2Session*); static constexpr 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,
Http2Session::RecvContinuation,
Http2Session::RecvAltSvc, // extension for type 0x0A
Http2Session::RecvUnused, // 0x0B was BLOCKED still radioactive
Http2Session::RecvOrigin, // extension for type 0x0C
Http2Session::RecvUnused, // 0x0D
Http2Session::RecvUnused, // 0x0E
Http2Session::RecvUnused, // 0x0F
Http2Session::RecvPriorityUpdate, // 0x10
};
uint32_t Http2Session::ReadTimeoutTick(PRIntervalTime now) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG3(("Http2Session::ReadTimeoutTick %p delta since last read %ds\n", this,
PR_IntervalToSeconds(now - mLastReadEpoch)));
if (!mPingThreshold) { return UINT32_MAX;
}
if ((now - mLastReadEpoch) < mPingThreshold) { // recent activity means ping is not an issue if (mPingSentEpoch) {
mPingSentEpoch = 0; if (mPreviousUsed) { // restore the former value
mPingThreshold = mPreviousPingThreshold;
mPreviousUsed = false;
}
}
if (mPingSentEpoch) { bool isTrr = (mTrrStreams > 0);
uint32_t pingTimeout = isTrr ? StaticPrefs::network_trr_ping_timeout()
: gHttpHandler->SpdyPingTimeout();
LOG3(
("Http2Session::ReadTimeoutTick %p handle outstanding ping, " "timeout=%d\n", this, pingTimeout)); if ((now - mPingSentEpoch) >= pingTimeout) {
LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this)); if (mConnection) {
mConnection->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
}
mPingSentEpoch = 0; if (isTrr) { // These must be set this way to ensure we gracefully restart all // streams
mGoAwayID = 0;
mCleanShutdown = true; // If TRR is mode 2, this Http2Session will be closed due to TRR request // timeout, so we won't reach this code. If we are in mode 3, the // request timeout is usually larger than the ping timeout. We close the // stream with NS_ERROR_NET_RESET, so the transactions can be restarted.
Close(NS_ERROR_NET_RESET);
} else {
Close(NS_ERROR_NET_TIMEOUT);
} return UINT32_MAX;
} return 1; // run the tick aggressively while ping is outstanding
}
mPingSentEpoch = PR_IntervalNow(); if (!mPingSentEpoch) {
mPingSentEpoch = 1; // avoid the 0 sentinel value
}
GeneratePing(false);
Unused << ResumeRecv(); // read the ping reply
// 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);
return 1; // run the tick aggressively while ping is outstanding
}
uint32_t Http2Session::RegisterStreamID(Http2StreamBase* stream,
uint32_t aNewID) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
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;
}
// 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.Contains(aNewID)) {
LOG3((" New ID already present\n"));
MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
mShouldGoAway = true; return kDeadStreamID;
}
mStreamIDHash.InsertOrUpdate(aNewID, stream);
if (aNewID & 1) { // don't count push streams here
RefPtr<nsHttpConnectionInfo> ci(stream->ConnectionInfo()); if (ci && ci->GetIsTrrServiceChannel()) {
IncrementTrrCounter();
}
} return aNewID;
}
// 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;
Unused << ReadSegments(nullptr, kDefaultBufferSize, &countRead);
}
void Http2Session::QueueStream(Http2StreamBase* stream) { // will be removed via processpending or a shutdown path
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(!stream->CountAsActive());
MOZ_ASSERT(!stream->Queued());
void Http2Session::RealignOutputQueue() { if (mAttemptingEarlyData) { // We can't realign right now, because we may need what's in there if early // data fails. return;
}
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;
}
// return true if activated (and counted against max) // otherwise return false and queue bool Http2Session::TryToActivate(Http2StreamBase* aStream) { if (aStream->Queued()) {
LOG3(("Http2Session::TryToActivate %p stream=%p already queued.\n", this,
aStream)); returnfalse;
}
if (!RoomForMoreConcurrent()) {
LOG3(
("Http2Session::TryToActivate %p stream=%p no room for more concurrent " "streams\n", this, aStream));
QueueStream(aStream); returnfalse;
}
void Http2Session::IncrementConcurrent(Http2StreamBase* stream) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1), "Do not activate pushed streams");
nsAHttpTransaction* trans = stream->Transaction(); if (!trans || !trans->IsNullTransaction()) {
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));
}
}
// call with data length (i.e. 0 for 0 data bytes - ignore 9 byte header) // dest must have 9 bytes of allocated space 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));
MOZ_ASSERT(!frameFlags || (frameType != FRAME_TYPE_PRIORITY &&
frameType != FRAME_TYPE_RST_STREAM &&
frameType != FRAME_TYPE_GOAWAY &&
frameType != FRAME_TYPE_WINDOW_UPDATE));
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;
}
// Need to decompress some data in order to keep the compression // context correct, but we really don't care what the result is
nsresult Http2Session::UncompressAndDiscard(bool isPush) {
nsresult rv;
nsAutoCString trash;
void Http2Session::GenerateSettingsAck() { // need to generate ack of this settings frame
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG3(("Http2Session::GenerateSettingsAck %p\n", this));
// make sure we don't do this twice for the same stream (at least if we // have a stream entry for it)
Http2StreamBase* stream = mStreamIDHash.Get(aID); if (stream) { if (stream->SentReset()) return;
stream->SetSentReset(true);
}
// 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 // 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 // streams will be direct leaves of. void Http2Session::SendHello() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG3(("Http2Session::SendHello %p\n", this));
// 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 // 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 // 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++;
if (!StaticPrefs::network_http_http2_allow_push()) { // If we don't support push then set MAX_CONCURRENT to 0 and also // set ENABLE_PUSH to 0
NetworkEndian::writeUint16(
packet + kFrameHeaderBytes + (6 * numberOfEntries),
SETTINGS_TYPE_ENABLE_PUSH); // The value portion of the setting pair is already initialized to 0
numberOfEntries++;
if (StaticPrefs::network_http_http2_send_push_max_concurrent_frame()) {
NetworkEndian::writeUint16(
packet + kFrameHeaderBytes + (6 * numberOfEntries),
SETTINGS_TYPE_MAX_CONCURRENT); // The value portion of the setting pair is already initialized to 0
numberOfEntries++;
}
mWaitingForSettingsAck = true;
}
// Advertise the Push RWIN for the session, and on each new pull stream // send a window update
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
SETTINGS_TYPE_INITIAL_WINDOW);
NetworkEndian::writeUint32(
packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
numberOfEntries++;
// Make sure the other endpoint knows that we're sticking to the default max // frame size
NetworkEndian::writeUint16(packet + kFrameHeaderBytes + (6 * numberOfEntries),
SETTINGS_TYPE_MAX_FRAME_SIZE);
NetworkEndian::writeUint32(
packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, kMaxFrameData);
numberOfEntries++;
// now bump the local session window from 64KB
uint32_t sessionWindowBump = mInitialRwin - kDefaultRwin; if (kDefaultRwin < mInitialRwin) { // send a window update for the session (Stream 0) for something large
mLocalSessionWindow = mInitialRwin;
if (!disableRFC7540Priorities) {
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;
MOZ_ASSERT(mNextStreamID == kUrgentStartGroupID);
CreatePriorityNode(kUrgentStartGroupID, 0, 240, "urgentStart");
mNextStreamID += 2; // 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!
}
FlushOutputQueue();
}
void Http2Session::SendPriorityFrame(uint32_t streamID, uint32_t dependsOn,
uint8_t weight) { // If mUseH2Deps is false, that means that we've sent // SETTINGS_NO_RFC7540_PRIORITIES = 1. Since the server must // ignore priority frames anyway, we can skip sending it. if (!UseH2Deps()) { return;
}
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));
// perform a bunch of integrity checks on the stream. // returns true if passed, false (plus LOG and ABORT) if failed. bool Http2Session::VerifyStream(Http2StreamBase* aStream,
uint32_t aOptionalID = 0) { // This is annoying, but at least it is O(1)
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
#ifndef DEBUG // Only do the real verification in debug builds returntrue; #else// DEBUG
if (!aStream) returntrue;
uint32_t test = 0;
do { if (aStream->StreamID() == kDeadStreamID) break;
test++; if (aStream->StreamID()) {
Http2StreamBase* idStream = mStreamIDHash.Get(aStream->StreamID());
test++; if (idStream != aStream) break;
if (aOptionalID) {
test++; if (idStream->StreamID() != aOptionalID) break;
}
}
if (aStream->IsTunnel()) { returntrue;
}
nsAHttpTransaction* trans = aStream->Transaction();
test++; if (!trans) break;
test++; if (mStreamTransactionHash.GetWeak(trans) != aStream) break;
Http2PushedStream* pushSource = nullptr;
Http2Stream* h2Stream = aStream->GetHttp2Stream(); if (h2Stream) {
pushSource = h2Stream->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));
h2Stream->ClearPushSource();
}
}
if (aStream->DeferCleanup(aResult)) {
LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID())); return;
}
if (!VerifyStream(aStream)) {
LOG3(("Http2Session::CleanupStream failed to verify stream\n")); return;
}
// 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));
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); if (!(id & 1)) {
mPushedStreams.RemoveElement(aStream);
Http2PushedStream* pushStream = static_cast<Http2PushedStream*>(aStream);
nsAutoCString hashKey;
DebugOnly<bool> rv = pushStream->GetHashKey(hashKey);
MOZ_ASSERT(rv);
nsIRequestContext* requestContext = aStream->RequestContext(); if (requestContext) {
SpdyPushCache* cache = requestContext->GetSpdyPushCache(); if (cache) { // 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());
LOG3(
("Http2Session::CleanupStream %p aStream=%p pushStream=%p " "trash=%p", this, aStream, pushStream, trash));
}
}
}
}
RemoveStreamFromQueues(aStream);
// removing from the stream transaction hash will // delete the Http2StreamBase and drop the reference to // its transaction
mStreamTransactionHash.Remove(aStream->Transaction());
mTunnelStreams.RemoveElement(aStream);
if (mShouldGoAway && !mStreamTransactionHash.Count()) Close(NS_OK);
if (pushSource) {
pushSource->SetDeferCleanupOnSuccess(false);
CleanupStream(pushSource, aResult, aResetCode);
}
}
void Http2Session::CleanupStream(uint32_t aID, nsresult aResult,
errorType aResetCode) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
Http2StreamBase* 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);
}
// Check if partial frame reader if (aStream == mInputFrameDataStream) {
LOG3(("Stream had active partial read frame on close"));
ChangeDownstreamState(DISCARDING_DATA_FRAME);
mInputFrameDataStream = nullptr;
}
if (aRemoveFromQueue) {
RemoveStreamFromQueues(aStream);
}
// Send the stream the close() indication
aStream->CloseStream(aResult);
}
// 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;
// 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;
if (!isContinuation) {
self->mDecompressBuffer.Truncate();
rv = self->ParsePadding(paddingControlBytes, paddingLength); if (NS_FAILED(rv)) { return rv;
}
}
if ((paddingControlBytes + priorityLen + paddingLength) >
self->mInputFrameDataSize) { // This is fatal to the session return self->SessionError(PROTOCOL_ERROR);
}
uint32_t frameSize = self->mInputFrameDataSize - paddingControlBytes -
priorityLen - paddingLength; if (self->mAggregatedHeaderSize + frameSize >
StaticPrefs::network_http_max_response_header_size()) {
LOG(("Http2Session %p header exceeds the limit\n", self)); return self->SessionError(PROTOCOL_ERROR);
} 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
if (self->mInputFrameFlags & kFlag_END_HEADERS) {
rv = self->UncompressAndDiscard(false); 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;
}
// make sure this is either the first headers or a trailer if (self->mInputFrameDataStream->AllHeadersReceived() &&
!(self->mInputFrameFlags & kFlag_END_STREAM)) { // Any header block after the first that does *not* end the stream is // illegal.
LOG3(("Http2Session::Illegal Extra HeaderBlock %p 0x%X\n", self,
self->mInputFrameID)); return self->SessionError(PROTOCOL_ERROR);
}
// queue up any compression bytes
self->mDecompressBuffer.Append(
&self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes +
priorityLen],
frameSize);
if (!endHeadersFlag) { // more are coming - don't process yet
self->ResetDownstreamState(); return NS_OK;
}
if (isContinuation) {
Telemetry::Accumulate(Telemetry::SPDY_CONTINUED_HEADERS,
self->mAggregatedHeaderSize);
}
rv = self->ResponseHeadersComplete(); 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;
} elseif (NS_FAILED(rv)) { // This is fatal to the session.
self->mGoAwayReason = COMPRESSION_ERROR;
} 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));
// 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). if (mInputFrameDataStream->AllHeadersReceived()) {
LOG3(("Http2Session::ResponseHeadersComplete processing trailers"));
MOZ_ASSERT(mInputFrameFlags & kFlag_END_STREAM);
nsresult rv = mInputFrameDataStream->ConvertResponseTrailers(
&mDecompressor, mDecompressBuffer); if (NS_FAILED(rv)) {
LOG3(( "Http2Session::ResponseHeadersComplete trailer conversion failed\n")); return rv;
}
mFlatHTTPResponseHeadersOut = 0;
mFlatHTTPResponseHeaders.Truncate(); if (mInputFrameFinal) { // need to process the fin
ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
} else {
ResetDownstreamState();
}
return NS_OK;
}
// 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();
mInputFrameDataStream->SetAllHeadersReceived();
// The stream needs to see flattened http headers // Uncompressed http/2 format headers currently live in // Http2StreamBase::mDecompressBuffer - convert that to HTTP format in // mFlatHTTPResponseHeaders via ConvertHeaders()
nsresult rv;
int32_t httpResponseCode; // out param to ConvertResponseHeaders
mFlatHTTPResponseHeadersOut = 0;
rv = mInputFrameDataStream->ConvertResponseHeaders(
&mDecompressor, mDecompressBuffer, mFlatHTTPResponseHeaders,
httpResponseCode); 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->DisableSpdy();
CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
ResetDownstreamState(); return NS_OK;
} if (NS_FAILED(rv)) { return rv;
}
// allow more headers in the case of 1xx if (((httpResponseCode / 100) == 1) && didFirstSetAllRecvd) {
mInputFrameDataStream->UnsetAllHeadersReceived();
}
// 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));
DebugOnly<nsresult> rv = self->SetInputFrameDataStream(self->mInputFrameID);
MOZ_ASSERT(NS_SUCCEEDED(rv)); if (!self->mInputFrameDataStream) { // if we can't find the stream just ignore it (4.2 closed)
self->ResetDownstreamState(); return NS_OK;
}
if (self->mInputFrameID) {
LOG3(("Http2Session::RecvSettings %p needs stream ID of 0. 0x%X\n", self,
self->mInputFrameID)); return self->SessionError(PROTOCOL_ERROR);
}
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.
LOG3(("Http2Session::RecvSettings %p SETTINGS wrong length data=%d", self,
self->mInputFrameDataSize)); return self->SessionError(PROTOCOL_ERROR);
}
if ((self->mInputFrameFlags & kFlag_ACK) && self->mInputFrameDataSize) {
LOG3(("Http2Session::RecvSettings %p ACK with non zero payload is err\n",
self)); return self->SessionError(PROTOCOL_ERROR);
}
for (uint32_t index = 0; index < numEntries; ++index) {
uint8_t* setting = reinterpret_cast<uint8_t*>(self->mInputFrameBuffer.get()) +
kFrameHeaderBytes + index * 6;
uint16_t id = NetworkEndian::readUint16(setting);
uint32_t value = NetworkEndian::readUint32(setting + 2);
LOG3(("Settings ID %u, Value %u", id, value));
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);
self->ProcessPending(); break;
case SETTINGS_TYPE_INITIAL_WINDOW: {
Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
int32_t delta = value - self->mServerInitialStreamWindow;
self->mServerInitialStreamWindow = value;
// 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 (constauto& stream : self->mStreamTransactionHash.Values()) {
stream->UpdateServerReceiveWindow(delta);
}
} break;
case SETTINGS_TYPE_MAX_FRAME_SIZE: { if ((value < kMaxFrameData) || (value >= 0x01000000)) {
LOG3(("Received invalid max frame size 0x%X", value)); return self->SessionError(PROTOCOL_ERROR);
} // We stick to the default for simplicity's sake, so nothing to change
} break;
case SETTINGS_TYPE_ENABLE_CONNECT_PROTOCOL: { if (value == 1) {
LOG3(("Enabling extended CONNECT"));
self->mPeerAllowsWebsockets = true;
} elseif (value > 1) {
LOG3(("Peer sent invalid value for ENABLE_CONNECT_PROTOCOL %d",
value)); return self->SessionError(PROTOCOL_ERROR);
} elseif (self->mPeerAllowsWebsockets) {
LOG3(("Peer tried to re-disable extended CONNECT")); return self->SessionError(PROTOCOL_ERROR);
}
self->mHasTransactionWaitingForWebsockets = true;
} break;
default:
LOG3(("Received an unknown SETTING id %d. Ignoring.", id)); break;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.