/* -*- 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// 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);
// 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
// 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;
}
for (constauto& 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);
} elseif (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);
}
}
} elseif (!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);
} elseif (stream->GetHttp3Stream() &&
stream->GetHttp3Stream()->RecvdData()) {
stream->Close(NS_ERROR_NET_PARTIAL_TRANSFER);
} elseif (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR) {
stream->Close(NS_ERROR_NET_HTTP3_PROTOCOL_ERROR);
} elseif (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();
// 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);
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;
}
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) { returntrue;
} if (aError.tag == CloseError::Tag::TransportError &&
aError.transport_error._0 == 0) { returntrue;
} if (aError.tag == CloseError::Tag::PeerError &&
aError.peer_error._0 == 0) { returntrue;
} if (aError.tag == CloseError::Tag::AppError &&
aError.app_error._0 == HTTP3_APP_ERROR_NO_ERROR) { returntrue;
} if (aError.tag == CloseError::Tag::PeerAppError &&
aError.peer_app_error._0 == HTTP3_APP_ERROR_NO_ERROR) { returntrue;
} returnfalse;
}; 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<constchar*>(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<constchar*>(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<constchar*>(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,
id));
RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id); if (!stream) {
LOG(
("Http3Session::ProcessEvents - WebTransport Session - " "stream not found " "stream_id=0x%" PRIx64 " [this=%p].",
id, this)); break;
}
MOZ_RELEASE_ASSERT(stream->GetHttp3WebTransportSession(), "It must be a WebTransport session");
stream->SetResponseHeaders(data, false, false);
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.get(), stream->StreamId(), static_cast<uint32_t>(rv), stream->Done())); // We need to keep the transaction, so we can use it to remove the // stream from mStreamTransactionHash.
nsAHttpTransaction* trans = stream->Transaction(); if (mStreamTransactionHash.Contains(trans)) {
CloseStream(stream, (rv == NS_BINDING_RETARGETED)
? NS_BINDING_RETARGETED
: NS_OK);
mStreamTransactionHash.Remove(trans);
} else {
stream->GetHttp3WebTransportSession()->TransactionIsDone(
(rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED
: NS_OK);
} break;
}
RefPtr<Http3WebTransportSession> wt =
stream->GetHttp3WebTransportSession();
MOZ_RELEASE_ASSERT(wt, "It must be a WebTransport session");
bool cleanly = false;
// TODO we do not handle the case when a WebTransport session stream // is closed before headers are sent.
SessionCloseReasonExternal& reasonExternal =
event.web_transport._0.session_closed.reason;
uint32_t status = 0;
nsCString reason = ""_ns; if (reasonExternal.tag == SessionCloseReasonExternal::Tag::Error) {
status = reasonExternal.error._0;
} elseif (reasonExternal.tag ==
SessionCloseReasonExternal::Tag::Status) {
status = reasonExternal.status._0;
cleanly = true;
} else {
status = reasonExternal.clean._0;
reason.Assign(reinterpret_cast<constchar*>(data.Elements()),
data.Length());
cleanly = true;
}
LOG(("reason.tag=%u err=%u data=%s\n", static_cast<uint32_t>(reasonExternal.tag), status,
reason.get()));
wt->OnSessionClosed(cleanly, status, reason);
// 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 // if this function returns an error. // Callers are: // 1) HttpConnectionUDP::OnQuicTimeoutExpired // 2) HttpConnectionUDP::SendData -> // Http3Session::SendData
nsresult Http3Session::ProcessOutput(nsIUDPSocket* socket) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mUdpConn);
LOG(("Http3Session::ProcessOutput sending packet rv=%d osError=%d", static_cast<int32_t>(rv), NS_FAILED(rv) ? PR_GetOSError() : 0)); if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
self->mSocketError = rv; // If there was an error that is not NS_BASE_STREAM_WOULD_BLOCK // return from here. We do not need to set a timer, because we // will close the connection. return rv;
}
self->mTotalBytesWritten += aLength;
self->mLastWriteTime = PR_IntervalNow(); return NS_OK;
},
[](void* aContext, uint64_t timeout) {
Http3Session* self = (Http3Session*)aContext;
self->SetupTimer(timeout);
});
mSocket = nullptr; return rv;
}
// Not using NSPR.
auto rv = mHttp3Connection->ProcessOutputAndSend( this, [](void* aContext, uint64_t timeout) {
Http3Session* self = (Http3Session*)aContext;
self->SetupTimer(timeout);
}); // 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;
} if (rv.bytes_written != 0) {
mTotalBytesWritten += rv.bytes_written;
mLastWriteTime = PR_IntervalNow();
socket->AddOutputBytes(rv.bytes_written);
}
return NS_OK;
}
// This is only called when timer expires. // It is called by HttpConnectionUDP::OnQuicTimeout. // If tihs function returns an error OnQuicTimeout will handle the error // properly and close the connection.
nsresult Http3Session::ProcessOutputAndEvents(nsIUDPSocket* socket) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); // ProcessOutput could fire another timer. Need to unset the flag before that.
mTimerActive = false;
MOZ_ASSERT(mTimerShouldTrigger);
auto now = TimeStamp::Now(); if (mTimerShouldTrigger > now) { // See bug 1935459
Telemetry::Accumulate(Telemetry::HTTP3_TIMER_DELAYED, 0);
} else {
Telemetry::AccumulateTimeDelta(Telemetry::HTTP3_TIMER_DELAYED,
mTimerShouldTrigger, now);
}
void Http3Session::SetupTimer(uint64_t aTimeout) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); // UINT64_MAX indicated a no-op from neqo, which only happens when a // connection is in or going to be Closed state. if (aTimeout == UINT64_MAX) { return;
}
LOG3(
("Http3Session::SetupTimer to %" PRIu64 "ms [this=%p].", aTimeout, this));
// Remember the time when the timer should trigger.
mTimerShouldTrigger =
TimeStamp::Now() + TimeDuration::FromMilliseconds(aTimeout);
if (mTimerActive && mTimer) {
LOG(
(" -- Previous timer has not fired. Update the delay instead of " "re-initializing the timer"));
mTimer->SetDelay(aTimeout); return;
}
nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
bool firstStream = false; if (!mConnection) { // Get the connection from the first transaction.
mConnection = aHttpTransaction->Connection();
firstStream = true;
}
// Make sure we report the connectStart auto reportConnectStart = MakeScopeExit([&] { if (firstStream) {
OnTransportStatus(nullptr, NS_NET_STATUS_CONNECTING_TO, 0);
}
});
aHttpTransaction->SetConnection(this);
aHttpTransaction->OnActivated(); // reset the read timers to wash away any idle time
mLastWriteTime = PR_IntervalNow();
ClassOfService cos; if (trans) {
cos = trans->GetClassOfService();
}
Http3StreamBase* stream = nullptr;
if (trans && trans->IsForWebTransport()) {
LOG3(("Http3Session::AddStream new WeTransport session %p atrans=%p.\n", this, aHttpTransaction));
stream = new Http3WebTransportSession(aHttpTransaction, this);
mHasWebTransportSession = true;
} else {
LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction));
stream = new Http3Stream(aHttpTransaction, this, cos, mCurrentBrowserId);
}
if (mState == ZERORTT) { if (!stream->Do0RTT()) {
LOG(("Http3Session %p will not get early data from Http3Stream %p", this,
stream)); if (!mCannotDo0RTTStreams.Contains(stream)) {
mCannotDo0RTTStreams.AppendElement(stream);
} if ((mWebTransportNegotiationStatus ==
WebTransportNegotiation::NEGOTIATING) &&
(trans && trans->IsForWebTransport())) {
LOG(("waiting for negotiation"));
mWaitingForWebTransportNegotiation.AppendElement(stream);
} returntrue;
}
m0RTTStreams.AppendElement(stream);
}
if ((mWebTransportNegotiationStatus ==
WebTransportNegotiation::NEGOTIATING) &&
(trans && trans->IsForWebTransport())) {
LOG(("waiting for negotiation"));
mWaitingForWebTransportNegotiation.AppendElement(stream); returntrue;
}
if (!mFirstHttpTransaction && !IsConnected()) {
mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
LOG3(("Http3Session::AddStream first session=%p trans=%p ", this,
mFirstHttpTransaction.get()));
}
StreamReadyToWrite(stream);
returntrue;
}
bool Http3Session::CanReuse() { // TODO: we assume "pooling" is disabled here, so we don't allow this session // to be reused. "pooling" will be implemented in bug 1815735. return CanSendData() && !(mGoawayReceived || mShouldClose) &&
!mHasWebTransportSession;
}
void Http3Session::QueueStream(Http3StreamBase* stream) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(!stream->Queued());
// This is called by Http3Stream::OnReadSegment. // ProcessOutput will be called in Http3Session::ReadSegment that // calls Http3Stream::OnReadSegment.
nsresult Http3Session::TryActivating( const nsACString& aMethod, const nsACString& aScheme, const nsACString& aAuthorityHeader, const nsACString& aPath, const nsACString& aHeaders, uint64_t* aStreamId, Http3StreamBase* aStream) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(*aStreamId == UINT64_MAX);
if (mState == ZERORTT) { if (!aStream->Do0RTT()) {
MOZ_ASSERT(!mCannotDo0RTTStreams.Contains(aStream)); return NS_BASE_STREAM_WOULD_BLOCK;
}
}
nsresult rv = NS_OK;
RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream(); if (httpStream) {
rv = mHttp3Connection->Fetch(
aMethod, aScheme, aAuthorityHeader, aPath, aHeaders, aStreamId,
httpStream->PriorityUrgency(), httpStream->PriorityIncremental());
} else {
MOZ_RELEASE_ASSERT(aStream->GetHttp3WebTransportSession(), "It must be a WebTransport session"); // Don't call CreateWebTransport if we are still waiting for the negotiation // result. if (mWebTransportNegotiationStatus ==
WebTransportNegotiation::NEGOTIATING) { if (!mWaitingForWebTransportNegotiation.Contains(aStream)) {
mWaitingForWebTransportNegotiation.AppendElement(aStream);
} return NS_BASE_STREAM_WOULD_BLOCK;
}
rv = mHttp3Connection->CreateWebTransport(aAuthorityHeader, aPath, aHeaders,
aStreamId);
}
if (NS_FAILED(rv)) {
LOG(("Http3Session::TryActivating returns error=0x%" PRIx32 "[stream=%p, " "this=%p]", static_cast<uint32_t>(rv), aStream, this)); if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
LOG3(
("Http3Session::TryActivating %p stream=%p no room for more " "concurrent streams\n", this, aStream));
mTransactionsBlockedByStreamLimitCount++; if (mQueuedStreams.GetSize() == 0) {
mBlockedByStreamLimitCount++;
}
QueueStream(aStream); return rv;
} // Ignore this error. This may happen if some events are not handled yet. // TODO we may try to add an assertion here. return NS_OK;
}
LOG(("Http3Session::TryActivating streamId=0x%" PRIx64 " for stream=%p [this=%p].",
*aStreamId, aStream, this));
MOZ_ASSERT(*aStreamId != UINT64_MAX);
if (mTransactionCount > 0 && mStreamIdHash.IsEmpty()) {
MOZ_ASSERT(mConnectionIdleStart);
MOZ_ASSERT(mFirstStreamIdReuseIdleConnection.isNothing());
// This is called by Http3WebTransportStream::OnReadSegment. // TODO: this function is almost the same as TryActivating(). // We should try to reduce the duplicate code.
nsresult Http3Session::TryActivatingWebTransportStream(
uint64_t* aStreamId, Http3StreamBase* aStream) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(*aStreamId == UINT64_MAX);
// WebTransportStream is managed by Http3Session now.
mWebTransportStreams.AppendElement(wtStream);
mWebTransportStreamToSessionMap.InsertOrUpdate(*aStreamId,
session->StreamId());
mStreamIdHash.InsertOrUpdate(*aStreamId, std::move(wtStream)); return NS_OK;
}
// This is only called by Http3Stream::OnReadSegment. // ProcessOutput will be called in Http3Session::ReadSegment that // calls Http3Stream::OnReadSegment. void Http3Session::CloseSendingSide(uint64_t aStreamId) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mHttp3Connection->CloseStream(aStreamId);
}
// This is only called by Http3Stream::OnReadSegment. // ProcessOutput will be called in Http3Session::ReadSegment that // calls Http3Stream::OnReadSegment.
nsresult Http3Session::SendRequestBody(uint64_t aStreamId, constchar* buf,
uint32_t count, uint32_t* countRead) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsresult rv = mHttp3Connection->SendRequestBody(
aStreamId, (const uint8_t*)buf, count, countRead); if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
mTransactionsSenderBlockedByFlowControlCount++;
} elseif (NS_FAILED(rv)) { // Ignore this error. This may happen if some events are not handled yet. // TODO we may try to add an assertion here. // We will pretend that sender is blocked, that will cause the caller to // stop trying.
*countRead = 0;
rv = NS_BASE_STREAM_WOULD_BLOCK;
}
RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId); if (!stream) { return;
}
RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream(); if (!httpStream) { return;
}
// We only handle some of Http3 error as epecial, the rest are just equivalent // to cancel. if (aError == HTTP3_APP_ERROR_VERSION_FALLBACK) { // We will restart the request and the alt-svc will be removed // automatically. // Also disable http3 we want http1.1.
httpStream->Transaction()->DisableHttp3(false);
httpStream->Transaction()->DisableSpdy();
CloseStream(stream, NS_ERROR_NET_RESET);
} elseif (aError == HTTP3_APP_ERROR_REQUEST_REJECTED) { // This request was rejected because server is probably busy or going away. // We can restart the request using alt-svc. Without calling // DoNotRemoveAltSvc the alt-svc route will be removed.
httpStream->Transaction()->DoNotRemoveAltSvc();
CloseStream(stream, NS_ERROR_NET_RESET);
} else { if (httpStream->RecvdData()) {
CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
} else {
CloseStream(stream, NS_ERROR_NET_INTERRUPT);
}
}
}
// TODO void Http3Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
int64_t aProgress) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
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: case NS_NET_STATUS_TLS_HANDSHAKE_STARTING: case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: { if (!mFirstHttpTransaction) { // if we still do not have a HttpTransaction store timings info in // a HttpConnection. // If some error occur it can happen that we do not have a connection. if (mConnection) {
RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection();
conn->SetEvent(aStatus);
}
} else {
mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus,
aProgress);
}
default: // The other transport events are ignored here because there is no good // way to map them to the right transaction in HTTP3. Instead, the events // are generated again from the HTTP3 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 HTTP3, // so it is ignored here and generated separately when the request // is sent from Http3Stream.
// 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 HTTP3, // so it is ignored here and generated separately when the same // condition is complete in Http3Stream when there is no more // request body left to be transmitted.
// NS_NET_STATUS_RECEIVING_FROM // Generated in Http3Stream whenever the stream reads data.
nsresult Http3Session::SendData(nsIUDPSocket* socket) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3Session::SendData [this=%p]", this));
// 1) go through all streams/transactions that are ready to write and // write their data into quic streams (no network write yet). // 2) call ProcessOutput that will loop until all available packets are // written to a socket or the socket returns an error code. // 3) if we still have streams ready to write call ResumeSend()(we may // still have such streams because on an stream error we return earlier // to let the error be handled). // 4)
// on stream error we return earlier to let the error be handled. if (NS_FAILED(rv)) {
LOG3(("Http3Session::SendData %p returns error code 0x%" PRIx32, this, static_cast<uint32_t>(rv)));
MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK); if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // Just in case!
rv = NS_OK;
} elseif (ASpdySession::SoftStreamError(rv)) {
CloseStream(stream, rv);
LOG3(("Http3Session::SendData %p soft error override\n", this));
rv = NS_OK;
} else { break;
}
}
}
if (NS_SUCCEEDED(rv)) { // Step 2: // Call actual network write.
rv = ProcessOutput(socket);
}
// Step 3:
MaybeResumeSend();
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
rv = NS_OK;
}
if (NS_FAILED(rv)) { return rv;
}
rv = ProcessEvents();
// Let the connection know we sent some app data successfully. if (stream && NS_SUCCEEDED(rv)) {
mUdpConn->NotifyDataWrite();
}
return rv;
}
void Http3Session::StreamReadyToWrite(Http3StreamBase* aStream) {
MOZ_ASSERT(aStream); // Http3Session::StreamReadyToWrite can be called multiple times when we get // duplicate DataWrite events from neqo at the same time. In this case, we // only want to insert the stream in `mReadyForWrite` once. if (aStream->IsInTxQueue()) { return;
}
rv = ProcessInput(socket); if (NS_FAILED(rv)) { return rv;
}
rv = ProcessEvents(); if (NS_FAILED(rv)) { return rv;
}
rv = SendData(socket); if (NS_FAILED(rv)) { return rv;
}
return NS_OK;
}
const uint32_t HTTP3_TELEMETRY_APP_NECKO = 42;
void Http3Session::Close(nsresult aReason) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3Session::Close [this=%p]", this));
if (NS_FAILED(mError)) {
CloseInternal(false);
} else {
mError = aReason; // If necko closes connection, this will map to the "closing" key and the // value HTTP3_TELEMETRY_APP_NECKO.
Telemetry::Accumulate(Telemetry::HTTP3_CONNECTION_CLOSE_CODE_3, "app_closing"_ns, HTTP3_TELEMETRY_APP_NECKO);
CloseInternal(true);
}
if (mCleanShutdown || mIsClosedByNeqo || NS_FAILED(mSocketError)) { // It is network-tear-down, a socker error or neqo is state CLOSED // (it does not need to send any more packets or wait for new packets). // We need to remove all references, so that // Http3Session will be destroyed. if (mTimer) {
mTimer->Cancel();
}
mTimer = nullptr;
mConnection = nullptr;
mUdpConn = nullptr;
mState = CLOSED;
} if (mConnection) { // resume sending to send CLOSE_CONNECTION frame.
Unused << mConnection->ResumeSend();
}
}
void Http3Session::CloseInternal(bool aCallNeqoClose) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (IsClosing()) { return;
}
LOG(("Http3Session::Closing [this=%p]", this));
if (mState != CONNECTED) {
mBeforeConnectedError = true;
}
nsHttpRequestHead* Http3Session::RequestHead() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(false, "Http3Session::RequestHead() " "should not be called after http/3 is setup"); return nullptr;
}
//----------------------------------------------------------------------------- // Pass through methods of nsAHttpConnection //-----------------------------------------------------------------------------
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.