Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 et cindent: */
/* 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
#include "ASpdySession.h" // because of SoftStreamError()
#include "Http3Session.h"
#include "Http3Stream.h"
#include "Http3StreamBase.h"
#include "Http3WebTransportSession.h"
#include "Http3WebTransportStream.h"
#include "HttpConnectionUDP.h"
#include "HttpLog.h"
#include "QuicSocketControl.h"
#include "SSLServerCertVerification.h"
#include "SSLTokensCache.h"
#include "ScopedNSSTypes.h"
#include "mozilla/RandomNum.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Telemetry.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/net/DNS.h"
#include "nsHttpHandler.h"
#include "nsIHttpActivityObserver.h"
#include "nsIOService.h"
#include "nsITLSSocketControl.h"
#include "nsNetAddr.h"
#include "nsQueryObject.h"
#include "nsSocketTransportService2.h"
#include "nsThreadUtils.h"
#include "sslerr.h"
#include "WebTransportCertificateVerifier.h"
namespace mozilla::net {
const uint64_t HTTP3_APP_ERROR_NO_ERROR = 0x100;
// const uint64_t HTTP3_APP_ERROR_GENERAL_PROTOCOL_ERROR = 0x101;
// const uint64_t HTTP3_APP_ERROR_INTERNAL_ERROR = 0x102;
// const uint64_t HTTP3_APP_ERROR_STREAM_CREATION_ERROR = 0x103;
// const uint64_t HTTP3_APP_ERROR_CLOSED_CRITICAL_STREAM = 0x104;
// const uint64_t HTTP3_APP_ERROR_FRAME_UNEXPECTED = 0x105;
// const uint64_t HTTP3_APP_ERROR_FRAME_ERROR = 0x106;
// const uint64_t HTTP3_APP_ERROR_EXCESSIVE_LOAD = 0x107;
// const uint64_t HTTP3_APP_ERROR_ID_ERROR = 0x108;
// const uint64_t HTTP3_APP_ERROR_SETTINGS_ERROR = 0x109;
// const uint64_t HTTP3_APP_ERROR_MISSING_SETTINGS = 0x10a;
const uint64_t HTTP3_APP_ERROR_REQUEST_REJECTED = 0x10b;
const uint64_t HTTP3_APP_ERROR_REQUEST_CANCELLED = 0x10c;
// const uint64_t HTTP3_APP_ERROR_REQUEST_INCOMPLETE = 0x10d;
// const uint64_t HTTP3_APP_ERROR_EARLY_RESPONSE = 0x10e;
// const uint64_t HTTP3_APP_ERROR_CONNECT_ERROR = 0x10f;
const uint64_t HTTP3_APP_ERROR_VERSION_FALLBACK = 0x110;
// const uint32_t UDP_MAX_PACKET_SIZE = 4096;
const uint32_t MAX_PTO_COUNTS = 16;
const uint32_t TRANSPORT_ERROR_STATELESS_RESET = 20;
NS_IMPL_ADDREF(Http3Session)
NS_IMPL_RELEASE(Http3Session)
NS_INTERFACE_MAP_BEGIN(Http3Session)
NS_INTERFACE_MAP_ENTRY(nsAHttpConnection)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY_CONCRETE(Http3Session)
NS_INTERFACE_MAP_END
Http3Session::Http3Session() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3Session::Http3Session [this=%p]", this));
mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId();
}
static nsresult RawBytesToNetAddr(uint16_t aFamily, const uint8_t* aRemoteAddr,
uint16_t remotePort, NetAddr* netAddr) {
if (aFamily == AF_INET) {
netAddr->inet.family = AF_INET;
netAddr->inet.port = htons(remotePort);
memcpy(&netAddr->inet.ip, aRemoteAddr, 4);
} else if (aFamily == AF_INET6) {
netAddr->inet6.family = AF_INET6;
netAddr->inet6.port = htons(remotePort);
memcpy(&netAddr->inet6.ip.u8, aRemoteAddr, 16);
} else {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo,
nsINetAddr* aSelfAddr, nsINetAddr* aPeerAddr,
HttpConnectionUDP* udpConn, uint32_t aProviderFlags,
nsIInterfaceRequestor* callbacks,
nsIUDPSocket* socket) {
LOG3(("Http3Session::Init %p", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(udpConn);
mConnInfo = aConnInfo->Clone();
mNetAddr = aPeerAddr;
bool httpsProxy =
aConnInfo->ProxyInfo() ? aConnInfo->ProxyInfo()->IsHTTPS() : false;
// Create security control and info object for quic.
mSocketControl = new QuicSocketControl(
httpsProxy ? aConnInfo->ProxyInfo()->Host() : aConnInfo->GetOrigin(),
httpsProxy ? aConnInfo->ProxyInfo()->Port() : aConnInfo->OriginPort(),
aProviderFlags, this);
NetAddr selfAddr;
MOZ_ALWAYS_SUCCEEDS(aSelfAddr->GetNetAddr(&selfAddr));
NetAddr peerAddr;
MOZ_ALWAYS_SUCCEEDS(aPeerAddr->GetNetAddr(&peerAddr));
LOG3(
("Http3Session::Init origin=%s, alpn=%s, selfAddr=%s, peerAddr=%s,"
" qpack table size=%u, max blocked streams=%u webtransport=%d "
"[this=%p]",
PromiseFlatCString(mConnInfo->GetOrigin()).get(),
PromiseFlatCString(mConnInfo->GetNPNToken()).get(),
selfAddr.ToString().get(), peerAddr.ToString().get(),
gHttpHandler->DefaultQpackTableSize(),
gHttpHandler->DefaultHttp3MaxBlockedStreams(),
mConnInfo->GetWebTransport(), this));
if (mConnInfo->GetWebTransport()) {
mWebTransportNegotiationStatus = WebTransportNegotiation::NEGOTIATING;
}
uint32_t datagramSize =
StaticPrefs::network_webtransport_datagrams_enabled()
? StaticPrefs::network_webtransport_datagram_size()
: 0;
mUseNSPRForIO = StaticPrefs::network_http_http3_use_nspr_for_io();
nsresult rv;
if (mUseNSPRForIO) {
rv = NeqoHttp3Conn::InitUseNSPRForIO(
mConnInfo->GetOrigin(), mConnInfo->GetNPNToken(), selfAddr, peerAddr,
gHttpHandler->DefaultQpackTableSize(),
gHttpHandler->DefaultHttp3MaxBlockedStreams(),
StaticPrefs::network_http_http3_max_data(),
StaticPrefs::network_http_http3_max_stream_data(),
StaticPrefs::network_http_http3_version_negotiation_enabled(),
mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(),
datagramSize, StaticPrefs::network_http_http3_max_accumlated_time_ms(),
aProviderFlags, getter_AddRefs(mHttp3Connection));
} else {
rv = NeqoHttp3Conn::Init(
mConnInfo->GetOrigin(), mConnInfo->GetNPNToken(), selfAddr, peerAddr,
gHttpHandler->DefaultQpackTableSize(),
gHttpHandler->DefaultHttp3MaxBlockedStreams(),
StaticPrefs::network_http_http3_max_data(),
StaticPrefs::network_http_http3_max_stream_data(),
StaticPrefs::network_http_http3_version_negotiation_enabled(),
mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(),
datagramSize, StaticPrefs::network_http_http3_max_accumlated_time_ms(),
aProviderFlags, socket->GetFileDescriptor(),
getter_AddRefs(mHttp3Connection));
}
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString peerId;
mSocketControl->GetPeerId(peerId);
nsTArray<uint8_t> token;
SessionCacheInfo info;
udpConn->ChangeConnectionState(ConnectionState::TLS_HANDSHAKING);
auto hasServCertHashes = [&]() -> bool {
if (!mConnInfo->GetWebTransport()) {
return false;
}
const nsTArray<RefPtr<nsIWebTransportHash>>* servCertHashes =
gHttpHandler->ConnMgr()->GetServerCertHashes(mConnInfo);
return servCertHashes && !servCertHashes->IsEmpty();
};
// In WebTransport, when servCertHashes is specified, it indicates that the
// connection to the WebTransport server should authenticate using the
// expected certificate hash. Therefore, 0RTT should be disabled in this
// context to ensure the certificate hash is checked.
if (StaticPrefs::network_http_http3_enable_0rtt() && !hasServCertHashes() &&
NS_SUCCEEDED(SSLTokensCache::Get(peerId, token, info))) {
LOG(("Found a resumption token in the cache."));
mHttp3Connection->SetResumptionToken(token);
mSocketControl->SetSessionCacheInfo(std::move(info));
if (mHttp3Connection->IsZeroRtt()) {
LOG(("Can send ZeroRtt data"));
RefPtr<Http3Session> self(this);
mState = ZERORTT;
udpConn->ChangeConnectionState(ConnectionState::ZERORTT);
mZeroRttStarted = TimeStamp::Now();
// Let the nsHttpConnectionMgr know that the connection can accept
// transactions.
// We need to dispatch the following function to this thread so that
// it is executed after the current function. At this point a
// Http3Session is still being initialized and ReportHttp3Connection
// will try to dispatch transaction on this session therefore it
// needs to be executed after the initializationg is done.
DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(
NS_NewRunnableFunction("Http3Session::ReportHttp3Connection",
[self]() { self->ReportHttp3Connection(); }));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"NS_DispatchToCurrentThread failed");
}
}
#ifndef ANDROID
if (mState != ZERORTT) {
ZeroRttTelemetry(ZeroRttOutcome::NOT_USED);
}
#endif
auto config = mConnInfo->GetEchConfig();
if (config.IsEmpty()) {
if (StaticPrefs::security_tls_ech_grease_http3() && config.IsEmpty()) {
if ((RandomUint64().valueOr(0) % 100) >=
100 - StaticPrefs::security_tls_ech_grease_probability()) {
// Setting an empty config enables GREASE mode.
mSocketControl->SetEchConfig(config);
mEchExtensionStatus = EchExtensionStatus::kGREASE;
}
}
} else if (gHttpHandler->EchConfigEnabled(true) && !config.IsEmpty()) {
mSocketControl->SetEchConfig(config);
mEchExtensionStatus = EchExtensionStatus::kReal;
HttpConnectionActivity activity(
mConnInfo->HashKey(), mConnInfo->GetOrigin(), mConnInfo->OriginPort(),
mConnInfo->EndToEndSSL(), !mConnInfo->GetEchConfig().IsEmpty(),
mConnInfo->IsHttp3());
gHttpHandler->ObserveHttpActivityWithArgs(
activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION,
NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET, PR_Now(), 0, ""_ns);
} else {
mEchExtensionStatus = EchExtensionStatus::kNotPresent;
}
// After this line, Http3Session and HttpConnectionUDP become a cycle. We put
// this line in the end of Http3Session::Init to make sure Http3Session can be
// released when Http3Session::Init early returned.
mUdpConn = udpConn;
return NS_OK;
}
void Http3Session::DoSetEchConfig(const nsACString& aEchConfig) {
LOG(("Http3Session::DoSetEchConfig %p of length %zu", this,
aEchConfig.Length()));
nsTArray<uint8_t> config;
config.AppendElements(
reinterpret_cast<const uint8_t*>(aEchConfig.BeginReading()),
aEchConfig.Length());
mHttp3Connection->SetEchConfig(config);
}
nsresult Http3Session::SendPriorityUpdateFrame(uint64_t aStreamId,
uint8_t aPriorityUrgency,
bool aPriorityIncremental) {
return mHttp3Connection->PriorityUpdate(aStreamId, aPriorityUrgency,
aPriorityIncremental);
}
// Shutdown the http3session and close all transactions.
void Http3Session::Shutdown() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mTimer) {
mTimer->Cancel();
}
mTimer = nullptr;
bool isEchRetry = mError == mozilla::psm::GetXPCOMFromNSSError(
SSL_ERROR_ECH_RETRY_WITH_ECH);
bool isNSSError = psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(mError));
bool allowToRetryWithDifferentIPFamily =
mBeforeConnectedError &&
gHttpHandler->ConnMgr()->AllowToRetryDifferentIPFamilyForHttp3(mConnInfo,
mError);
LOG(("Http3Session::Shutdown %p allowToRetryWithDifferentIPFamily=%d", this,
allowToRetryWithDifferentIPFamily));
if ((mBeforeConnectedError ||
(mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR)) &&
!isNSSError && !isEchRetry && !mConnInfo->GetWebTransport() &&
!allowToRetryWithDifferentIPFamily && !mDontExclude) {
gHttpHandler->ExcludeHttp3(mConnInfo);
if (mFirstHttpTransaction) {
mFirstHttpTransaction->DisableHttp3(false);
}
}
for (const auto& stream : mStreamTransactionHash.Values()) {
if (mBeforeConnectedError) {
// We have an error before we were connected, just restart transactions.
// The transaction restart code path will remove AltSvc mapping and the
// direct path will be used.
MOZ_ASSERT(NS_FAILED(mError));
if (isEchRetry) {
// We have to propagate this error to nsHttpTransaction, so the
// transaction will be restarted with a new echConfig.
stream->Close(mError);
} else if (isNSSError) {
stream->Close(mError);
} else {
if (allowToRetryWithDifferentIPFamily && mNetAddr) {
NetAddr addr;
mNetAddr->GetNetAddr(&addr);
gHttpHandler->ConnMgr()->SetRetryDifferentIPFamilyForHttp3(
mConnInfo, addr.raw.family);
// We want the transaction to be restarted with the same connection
// info.
stream->Transaction()->DoNotRemoveAltSvc();
// We already set the preference in SetRetryDifferentIPFamilyForHttp3,
// so we want to keep it for the next retry.
stream->Transaction()->DoNotResetIPFamilyPreference();
stream->Close(NS_ERROR_NET_RESET);
// Since Http3Session::Shutdown can be called multiple times, we set
// mDontExclude for not putting this domain into the excluded list.
mDontExclude = true;
} else {
stream->Close(NS_ERROR_NET_RESET);
}
}
} else if (!stream->HasStreamId()) {
if (NS_SUCCEEDED(mError)) {
// Connection has not been started yet. We can restart it.
stream->Transaction()->DoNotRemoveAltSvc();
}
stream->Close(NS_ERROR_NET_RESET);
} else if (stream->GetHttp3Stream() &&
stream->GetHttp3Stream()->RecvdData()) {
stream->Close(NS_ERROR_NET_PARTIAL_TRANSFER);
} else if (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR) {
stream->Close(NS_ERROR_NET_HTTP3_PROTOCOL_ERROR);
} else if (mError == NS_ERROR_NET_RESET) {
stream->Close(NS_ERROR_NET_RESET);
} else {
stream->Close(NS_ERROR_ABORT);
}
RemoveStreamFromQueues(stream);
if (stream->HasStreamId()) {
mStreamIdHash.Remove(stream->StreamId());
}
}
mStreamTransactionHash.Clear();
for (const auto& stream : mWebTransportSessions) {
stream->Close(NS_ERROR_ABORT);
RemoveStreamFromQueues(stream);
mStreamIdHash.Remove(stream->StreamId());
}
mWebTransportSessions.Clear();
for (const auto& stream : mWebTransportStreams) {
stream->Close(NS_ERROR_ABORT);
RemoveStreamFromQueues(stream);
mStreamIdHash.Remove(stream->StreamId());
}
RefPtr<Http3StreamBase> stream;
while ((stream = mQueuedStreams.PopFront())) {
LOG(("Close remaining stream in queue:%p", stream.get()));
stream->SetQueued(false);
stream->Close(NS_ERROR_ABORT);
}
mWebTransportStreams.Clear();
}
Http3Session::~Http3Session() {
LOG3(("Http3Session::~Http3Session %p", this));
#ifndef ANDROID
EchOutcomeTelemetry();
#endif
Telemetry::Accumulate(Telemetry::HTTP3_REQUEST_PER_CONN, mTransactionCount);
Telemetry::Accumulate(Telemetry::HTTP3_BLOCKED_BY_STREAM_LIMIT_PER_CONN,
mBlockedByStreamLimitCount);
Telemetry::Accumulate(Telemetry::HTTP3_TRANS_BLOCKED_BY_STREAM_LIMIT_PER_CONN,
mTransactionsBlockedByStreamLimitCount);
Telemetry::Accumulate(
Telemetry::HTTP3_TRANS_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_CONN,
mTransactionsSenderBlockedByFlowControlCount);
Shutdown();
// We only record the average interval for performance reason.
if (mTotelReadInterval) {
nsAutoCString key(mServer.EqualsLiteral("cloudflare") ? "cloudflare"_ns
: "others"_ns);
glean::network::http3_avg_read_interval.Get(key).AccumulateRawDuration(
TimeDuration::FromMilliseconds(
static_cast<double>(mTotelReadInterval / mTotelReadIntervalCount)));
}
}
// This function may return a socket error.
// It will not return an error if socket error is
// NS_BASE_STREAM_WOULD_BLOCK.
// A caller of this function will close the Http3 connection
// in case of an error.
// The only callers is Http3Session::RecvData.
nsresult Http3Session::ProcessInput(nsIUDPSocket* socket) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mUdpConn);
LOG(("Http3Session::ProcessInput writer=%p [this=%p state=%d]",
mUdpConn.get(), this, mState));
PRIntervalTime now = PR_IntervalNow();
if (!mLastReadTime) {
mLastReadTime = now;
} else {
mTotelReadInterval +=
PR_IntervalToMilliseconds(PR_IntervalNow() - mLastReadTime);
mTotelReadIntervalCount++;
mLastReadTime = now;
}
if (mUseNSPRForIO) {
while (true) {
nsTArray<uint8_t> data;
NetAddr addr{};
// RecvWithAddr actually does not return an error.
nsresult rv = socket->RecvWithAddr(&addr, data);
MOZ_ALWAYS_SUCCEEDS(rv);
if (NS_FAILED(rv) || data.IsEmpty()) {
break;
}
rv = mHttp3Connection->ProcessInputUseNSPRForIO(addr, data);
MOZ_ALWAYS_SUCCEEDS(rv);
if (NS_FAILED(rv)) {
break;
}
LOG(("Http3Session::ProcessInput received=%zu", data.Length()));
mTotalBytesRead += static_cast<int64_t>(data.Length());
}
return NS_OK;
}
// Not using NSPR.
auto rv = mHttp3Connection->ProcessInput();
// Note: WOULD_BLOCK is handled in neqo_glue.
if (NS_FAILED(rv.result)) {
mSocketError = rv.result;
// If there was an error return from here. We do not need to set a timer,
// because we will close the connection.
return rv.result;
}
mTotalBytesRead += rv.bytes_read;
socket->AddInputBytes(rv.bytes_read);
return NS_OK;
}
nsresult Http3Session::ProcessTransactionRead(uint64_t stream_id) {
RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(stream_id);
if (!stream) {
LOG(
("Http3Session::ProcessTransactionRead - stream not found "
"stream_id=0x%" PRIx64 " [this=%p].",
stream_id, this));
return NS_OK;
}
return ProcessTransactionRead(stream);
}
nsresult Http3Session::ProcessTransactionRead(Http3StreamBase* stream) {
nsresult rv = stream->WriteSegments();
if (ASpdySession::SoftStreamError(rv) || stream->Done()) {
LOG3(
("Http3Session::ProcessSingleTransactionRead session=%p stream=%p "
"0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n",
this, stream, stream->StreamId(), static_cast<uint32_t>(rv),
stream->Done()));
CloseStream(stream,
(rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK);
return NS_OK;
}
if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
return rv;
}
return NS_OK;
}
nsresult Http3Session::ProcessEvents() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3Session::ProcessEvents [this=%p]", this));
// We need an array to pick up header data or a resumption token.
nsTArray<uint8_t> data;
Http3Event event{};
event.tag = Http3Event::Tag::NoEvent;
nsresult rv = mHttp3Connection->GetEvent(&event, data);
if (NS_FAILED(rv)) {
LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
static_cast<uint32_t>(rv)));
return rv;
}
while (event.tag != Http3Event::Tag::NoEvent) {
switch (event.tag) {
case Http3Event::Tag::HeaderReady: {
MOZ_ASSERT(mState == CONNECTED);
LOG(("Http3Session::ProcessEvents - HeaderReady"));
uint64_t id = event.header_ready.stream_id;
RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
if (!stream) {
LOG(
("Http3Session::ProcessEvents - HeaderReady - stream not found "
"stream_id=0x%" PRIx64 " [this=%p].",
id, this));
break;
}
MOZ_RELEASE_ASSERT(stream->GetHttp3Stream(),
"This must be a Http3Stream");
stream->SetResponseHeaders(data, event.header_ready.fin,
event.header_ready.interim);
rv = ProcessTransactionRead(stream);
if (NS_FAILED(rv)) {
LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
static_cast<uint32_t>(rv)));
return rv;
}
mUdpConn->NotifyDataRead();
break;
}
case Http3Event::Tag::DataReadable: {
MOZ_ASSERT(mState == CONNECTED);
LOG(("Http3Session::ProcessEvents - DataReadable"));
uint64_t id = event.data_readable.stream_id;
nsresult rv = ProcessTransactionRead(id);
if (NS_FAILED(rv)) {
LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
static_cast<uint32_t>(rv)));
return rv;
}
break;
}
case Http3Event::Tag::DataWritable: {
MOZ_ASSERT(CanSendData());
LOG(("Http3Session::ProcessEvents - DataWritable"));
RefPtr<Http3StreamBase> stream =
mStreamIdHash.Get(event.data_writable.stream_id);
if (stream) {
StreamReadyToWrite(stream);
}
} break;
case Http3Event::Tag::Reset:
LOG(("Http3Session::ProcessEvents %p - Reset", this));
ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error,
RESET);
break;
case Http3Event::Tag::StopSending:
LOG(
("Http3Session::ProcessEvents %p - StopSeniding with error "
"0x%" PRIx64,
this, event.stop_sending.error));
if (event.stop_sending.error == HTTP3_APP_ERROR_NO_ERROR) {
RefPtr<Http3StreamBase> stream =
mStreamIdHash.Get(event.data_writable.stream_id);
if (stream) {
RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream();
MOZ_RELEASE_ASSERT(httpStream, "This must be a Http3Stream");
httpStream->StopSending();
}
} else {
ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error,
STOP_SENDING);
}
break;
case Http3Event::Tag::PushPromise:
LOG(("Http3Session::ProcessEvents - PushPromise"));
break;
case Http3Event::Tag::PushHeaderReady:
LOG(("Http3Session::ProcessEvents - PushHeaderReady"));
break;
case Http3Event::Tag::PushDataReadable:
LOG(("Http3Session::ProcessEvents - PushDataReadable"));
break;
case Http3Event::Tag::PushCanceled:
LOG(("Http3Session::ProcessEvents - PushCanceled"));
break;
case Http3Event::Tag::RequestsCreatable:
LOG(("Http3Session::ProcessEvents - StreamCreatable"));
ProcessPending();
break;
case Http3Event::Tag::AuthenticationNeeded:
LOG(("Http3Session::ProcessEvents - AuthenticationNeeded %d",
mAuthenticationStarted));
if (!mAuthenticationStarted) {
mAuthenticationStarted = true;
LOG(("Http3Session::ProcessEvents - AuthenticationNeeded called"));
OnTransportStatus(nullptr, NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
CallCertVerification(Nothing());
}
break;
case Http3Event::Tag::ZeroRttRejected:
LOG(("Http3Session::ProcessEvents - ZeroRttRejected"));
if (mState == ZERORTT) {
mState = INITIALIZING;
mTransactionCount = 0;
Finish0Rtt(true);
#ifndef ANDROID
ZeroRttTelemetry(ZeroRttOutcome::USED_REJECTED);
#endif
}
break;
case Http3Event::Tag::ResumptionToken: {
LOG(("Http3Session::ProcessEvents - ResumptionToken"));
if (StaticPrefs::network_http_http3_enable_0rtt() && !data.IsEmpty()) {
LOG(("Got a resumption token"));
nsAutoCString peerId;
mSocketControl->GetPeerId(peerId);
if (NS_FAILED(SSLTokensCache::Put(
peerId, data.Elements(), data.Length(), mSocketControl,
PR_Now() + event.resumption_token.expire_in))) {
LOG(("Adding resumption token failed"));
}
}
} break;
case Http3Event::Tag::ConnectionConnected: {
LOG(("Http3Session::ProcessEvents - ConnectionConnected"));
bool was0RTT = mState == ZERORTT;
mState = CONNECTED;
SetSecInfo();
mSocketControl->HandshakeCompleted();
if (was0RTT) {
Finish0Rtt(false);
#ifndef ANDROID
ZeroRttTelemetry(ZeroRttOutcome::USED_SUCCEEDED);
#endif
}
OnTransportStatus(nullptr, NS_NET_STATUS_CONNECTED_TO, 0);
// Also send the NS_NET_STATUS_TLS_HANDSHAKE_ENDED event.
OnTransportStatus(nullptr, NS_NET_STATUS_TLS_HANDSHAKE_ENDED, 0);
ReportHttp3Connection();
// Maybe call ResumeSend:
// In case ZeroRtt has been used and it has been rejected, 2 events will
// be received: ZeroRttRejected and ConnectionConnected. ZeroRttRejected
// that will put all transaction into mReadyForWrite queue and it will
// call MaybeResumeSend, but that will not have an effect because the
// connection is ont in CONNECTED state. When ConnectionConnected event
// is received call MaybeResumeSend to trigger reads for the
// zero-rtt-rejected transactions.
MaybeResumeSend();
} break;
case Http3Event::Tag::GoawayReceived:
LOG(("Http3Session::ProcessEvents - GoawayReceived"));
mUdpConn->SetCloseReason(ConnectionCloseReason::GO_AWAY);
mGoawayReceived = true;
break;
case Http3Event::Tag::ConnectionClosing:
LOG(("Http3Session::ProcessEvents - ConnectionClosing"));
if (NS_SUCCEEDED(mError) && !IsClosing()) {
mError = NS_ERROR_NET_HTTP3_PROTOCOL_ERROR;
CloseConnectionTelemetry(event.connection_closing.error, true);
auto isStatelessResetOrNoError = [](CloseError& aError) -> bool {
if (aError.tag == CloseError::Tag::TransportInternalErrorOther &&
aError.transport_internal_error_other._0 ==
TRANSPORT_ERROR_STATELESS_RESET) {
return true;
}
if (aError.tag == CloseError::Tag::TransportError &&
aError.transport_error._0 == 0) {
return true;
}
if (aError.tag == CloseError::Tag::PeerError &&
aError.peer_error._0 == 0) {
return true;
}
if (aError.tag == CloseError::Tag::AppError &&
aError.app_error._0 == HTTP3_APP_ERROR_NO_ERROR) {
return true;
}
if (aError.tag == CloseError::Tag::PeerAppError &&
aError.peer_app_error._0 == HTTP3_APP_ERROR_NO_ERROR) {
return true;
}
return false;
};
if (isStatelessResetOrNoError(event.connection_closing.error)) {
mError = NS_ERROR_NET_RESET;
}
if (event.connection_closing.error.tag == CloseError::Tag::EchRetry) {
mSocketControl->SetRetryEchConfig(Substring(
reinterpret_cast<const char*>(data.Elements()), data.Length()));
mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH);
}
}
return mError;
break;
case Http3Event::Tag::ConnectionClosed:
LOG(("Http3Session::ProcessEvents - ConnectionClosed"));
if (NS_SUCCEEDED(mError)) {
mError = NS_ERROR_NET_TIMEOUT;
mUdpConn->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT);
CloseConnectionTelemetry(event.connection_closed.error, false);
}
mIsClosedByNeqo = true;
if (event.connection_closed.error.tag == CloseError::Tag::EchRetry) {
mSocketControl->SetRetryEchConfig(Substring(
reinterpret_cast<const char*>(data.Elements()), data.Length()));
mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH);
}
LOG(("Http3Session::ProcessEvents - ConnectionClosed error=%" PRIx32,
static_cast<uint32_t>(mError)));
// We need to return here and let HttpConnectionUDP close the session.
return mError;
break;
case Http3Event::Tag::EchFallbackAuthenticationNeeded: {
nsCString echPublicName(reinterpret_cast<const char*>(data.Elements()),
data.Length());
LOG(
("Http3Session::ProcessEvents - EchFallbackAuthenticationNeeded "
"echPublicName=%s",
echPublicName.get()));
if (!mAuthenticationStarted) {
mAuthenticationStarted = true;
CallCertVerification(Some(echPublicName));
}
} break;
case Http3Event::Tag::WebTransport: {
switch (event.web_transport._0.tag) {
case WebTransportEventExternal::Tag::Negotiated:
LOG(("Http3Session::ProcessEvents - WebTransport %d",
event.web_transport._0.negotiated._0));
MOZ_ASSERT(mWebTransportNegotiationStatus ==
WebTransportNegotiation::NEGOTIATING);
mWebTransportNegotiationStatus =
event.web_transport._0.negotiated._0
? WebTransportNegotiation::SUCCEEDED
: WebTransportNegotiation::FAILED;
WebTransportNegotiationDone();
break;
case WebTransportEventExternal::Tag::Session: {
MOZ_ASSERT(mState == CONNECTED);
uint64_t id = event.web_transport._0.session._0;
LOG(
("Http3Session::ProcessEvents - WebTransport Session "
" sessionId=0x%" PRIx64,