Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/netwerk/protocol/http/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 92 kB image not shown  

Quelle  Http3Session.cpp   Sprache: C

 
/* -*- 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/. */


#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/NetwerkProtocolHttpMetrics.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();
}

// 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));

  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,
                 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, falsefalse);

            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;
            }

            if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
              LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
                   static_cast<uint32_t>(rv)));
              return rv;
            }
          } break;
          case WebTransportEventExternal::Tag::SessionClosed: {
            uint64_t id = event.web_transport._0.session_closed.stream_id;
            LOG(
                ("Http3Session::ProcessEvents - WebTransport SessionClosed "
                 " sessionId=0x%" PRIx64,
                 id));
            RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
            if (!stream) {
              LOG(
                  ("Http3Session::ProcessEvents - WebTransport SessionClosed - "
                   "stream not found "
                   "stream_id=0x%" PRIx64 " [this=%p].",
                   id, this));
              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;
            } else if (reasonExternal.tag ==
                       SessionCloseReasonExternal::Tag::Status) {
              status = reasonExternal.status._0;
              cleanly = true;
            } else {
              status = reasonExternal.clean._0;
              reason.Assign(reinterpret_cast<const char*>(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);

          } break;
          case WebTransportEventExternal::Tag::NewStream: {
            LOG(
                ("Http3Session::ProcessEvents - WebTransport NewStream "
                 "streamId=0x%" PRIx64 " sessionId=0x%" PRIx64,
                 event.web_transport._0.new_stream.stream_id,
                 event.web_transport._0.new_stream.session_id));
            uint64_t sessionId = event.web_transport._0.new_stream.session_id;
            RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId);
            if (!stream) {
              LOG(
                  ("Http3Session::ProcessEvents - WebTransport NewStream - "
                   "session not found "
                   "sessionId=0x%" PRIx64 " [this=%p].",
                   sessionId, this));
              break;
            }

            RefPtr<Http3WebTransportSession> wt =
                stream->GetHttp3WebTransportSession();
            if (!wt) {
              break;
            }

            RefPtr<Http3WebTransportStream> wtStream =
                wt->OnIncomingWebTransportStream(
                    event.web_transport._0.new_stream.stream_type,
                    event.web_transport._0.new_stream.stream_id);
            if (!wtStream) {
              break;
            }

            // WebTransportStream is managed by Http3Session now.
            mWebTransportStreams.AppendElement(wtStream);
            mWebTransportStreamToSessionMap.InsertOrUpdate(wtStream->StreamId(),
                                                           wt->StreamId());
            mStreamIdHash.InsertOrUpdate(wtStream->StreamId(),
                                         std::move(wtStream));
          } break;
          case WebTransportEventExternal::Tag::Datagram:
            LOG(
                ("Http3Session::ProcessEvents - "
                 "WebTransportEventExternal::Tag::Datagram [this=%p]",
                 this));
            uint64_t sessionId = event.web_transport._0.datagram.session_id;
            RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId);
            if (!stream) {
              LOG(
                  ("Http3Session::ProcessEvents - WebTransport Datagram - "
                   "session not found "
                   "sessionId=0x%" PRIx64 " [this=%p].",
                   sessionId, this));
              break;
            }

            RefPtr<Http3WebTransportSession> wt =
                stream->GetHttp3WebTransportSession();
            if (!wt) {
              break;
            }

            wt->OnDatagramReceived(std::move(data));
            break;
        }
      } break;
      default:
        break;
    }
    // Delete previous content of data
    data.TruncateLength(0);
    rv = mHttp3Connection->GetEvent(&event, data);
    if (NS_FAILED(rv)) {
      LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
           static_cast<uint32_t>(rv)));
      return rv;
    }
  }

  return NS_OK;
}  // namespace net

// 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 reader=%p, [this=%p]", mUdpConn.get(),
       this));

  if (mUseNSPRForIO) {
    mSocket = socket;
    nsresult rv = mHttp3Connection->ProcessOutputAndSendUseNSPRForIO(
        this,
        [](void* aContext, uint16_t aFamily, const uint8_t* aAddr,
           uint16_t aPort, const uint8_t* aData, uint32_t aLength) {
          Http3Session* self = (Http3Session*)aContext;

          uint32_t written = 0;
          NetAddr addr;
          if (NS_FAILED(RawBytesToNetAddr(aFamily, aAddr, aPort, &addr))) {
            return NS_OK;
          }

          LOG3(
              ("Http3Session::ProcessOutput sending packet with %u bytes to %s "
               "port=%d [this=%p].",
               aLength, addr.ToString().get(), aPort, self));

          nsresult rv =
              self->mSocket->SendWithAddress(&addr, aData, aLength, &written);

          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);
  }

  mTimerShouldTrigger = TimeStamp();

  nsresult rv = SendData(socket);
  if (NS_FAILED(rv)) {
    return rv;
  }
  return NS_OK;
}

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;
  }

  nsresult rv = NS_NewTimerWithCallback(
      getter_AddRefs(mTimer),
      [conn = RefPtr{mUdpConn}](nsITimer*) { conn->OnQuicTimeoutExpired(); },
      aTimeout, nsITimer::TYPE_ONE_SHOT,
      "net::HttpConnectionUDP::OnQuicTimeout");

  mTimerActive = true;

  if (NS_FAILED(rv)) {
    NS_DispatchToCurrentThread(
        NewRunnableMethod("net::HttpConnectionUDP::OnQuicTimeoutExpired",
                          mUdpConn, &HttpConnectionUDP::OnQuicTimeoutExpired));
  }
}

bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction,
                             int32_t aPriority,
                             nsIInterfaceRequestor* aCallbacks) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  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);
    }
  });

  if (IsClosing()) {
    LOG3(
        ("Http3Session::AddStream %p atrans=%p trans=%p session unusable - "
         "resched.\n",
         this, aHttpTransaction, trans));
    aHttpTransaction->SetConnection(nullptr);
    nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
    if (NS_FAILED(rv)) {
      LOG3(
          ("Http3Session::AddStream %p atrans=%p trans=%p failed to initiate "
           "transaction (0x%" PRIx32 ").\n",
           this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
    }
    return true;
  }

  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);
  }

  mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream});

  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);
      }
      return true;
    }
    m0RTTStreams.AppendElement(stream);
  }

  if ((mWebTransportNegotiationStatus ==
       WebTransportNegotiation::NEGOTIATING) &&
      (trans && trans->IsForWebTransport())) {
    LOG(("waiting for negotiation"));
    mWaitingForWebTransportNegotiation.AppendElement(stream);
    return true;
  }

  if (!mFirstHttpTransaction && !IsConnected()) {
    mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
    LOG3(("Http3Session::AddStream first session=%p trans=%p "this,
          mFirstHttpTransaction.get()));
  }
  StreamReadyToWrite(stream);

  return true;
}

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());

  LOG3(("Http3Session::QueueStream %p stream %p queued."this, stream));

  stream->SetQueued(true);
  mQueuedStreams.Push(stream);
}

void Http3Session::ProcessPending() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  RefPtr<Http3StreamBase> stream;
  while ((stream = mQueuedStreams.PopFront())) {
    LOG3(("Http3Session::ProcessPending %p stream %p woken from queue."this,
          stream.get()));
    MOZ_ASSERT(stream->Queued());
    stream->SetQueued(false);
    mReadyForWrite.Push(stream);
  }
  MaybeResumeSend();
}

static void RemoveStreamFromQueue(Http3StreamBase* aStream,
                                  nsRefPtrDeque<Http3StreamBase>& queue) {
  size_t size = queue.GetSize();
  for (size_t count = 0; count < size; ++count) {
    RefPtr<Http3StreamBase> stream = queue.PopFront();
    if (stream != aStream) {
      queue.Push(stream);
    }
  }
}

void Http3Session::RemoveStreamFromQueues(Http3StreamBase* aStream) {
  RemoveStreamFromQueue(aStream, mReadyForWrite);
  RemoveStreamFromQueue(aStream, mQueuedStreams);
  mSlowConsumersReadyForRead.RemoveElement(aStream);
}

// 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);

  LOG(("Http3Session::TryActivating [stream=%p, this=%p state=%d]", aStream,
       this, mState));

  if (IsClosing()) {
    if (NS_FAILED(mError)) {
      return mError;
    }
    return NS_ERROR_FAILURE;
  }

  if (aStream->Queued()) {
    LOG3(("Http3Session::TryActivating %p stream=%p already queued.\n"this,
          aStream));
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  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());

    mConnectionIdleEnd = TimeStamp::Now();
    mFirstStreamIdReuseIdleConnection = Some(*aStreamId);
  }
  mStreamIdHash.InsertOrUpdate(*aStreamId, RefPtr{aStream});
  mTransactionCount++;

  return NS_OK;
}

// 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);

  LOG(
      ("Http3Session::TryActivatingWebTransportStream [stream=%p, this=%p "
       "state=%d]",
       aStream, this, mState));

  if (IsClosing()) {
    if (NS_FAILED(mError)) {
      return mError;
    }
    return NS_ERROR_FAILURE;
  }

  if (aStream->Queued()) {
    LOG3(
        ("Http3Session::TryActivatingWebTransportStream %p stream=%p already "
         "queued.\n",
         this, aStream));
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  nsresult rv = NS_OK;
  RefPtr<Http3WebTransportStream> wtStream =
      aStream->GetHttp3WebTransportStream();
  MOZ_RELEASE_ASSERT(wtStream, "It must be a WebTransport stream");
  rv = mHttp3Connection->CreateWebTransportStream(
      wtStream->SessionId(), wtStream->StreamType(), aStreamId);

  if (NS_FAILED(rv)) {
    LOG((
        "Http3Session::TryActivatingWebTransportStream returns error=0x%" PRIx32
        "[stream=%p, "
        "this=%p]",
        static_cast<uint32_t>(rv), aStream, this));
    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      LOG3(
          ("Http3Session::TryActivatingWebTransportStream %p stream=%p no room "
           "for more "
           "concurrent streams\n",
           this, aStream));
      QueueStream(aStream);
      return rv;
    }

    return rv;
  }

  LOG(("Http3Session::TryActivatingWebTransportStream streamId=0x%" PRIx64
       " for stream=%p [this=%p].",
       *aStreamId, aStream, this));

  MOZ_ASSERT(*aStreamId != UINT64_MAX);

  RefPtr<Http3StreamBase> session = mStreamIdHash.Get(wtStream->SessionId());
  MOZ_ASSERT(session);
  Http3WebTransportSession* wtSession = session->GetHttp3WebTransportSession();
  MOZ_ASSERT(wtSession);

  wtSession->RemoveWebTransportStream(wtStream);

  // 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, const char* 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++;
  } else if (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;
  }

  MOZ_ASSERT((*countRead != 0) || NS_FAILED(rv));
  return rv;
}

void Http3Session::ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError,
                                           ResetType aType) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  uint64_t sessionId = 0;
  if (mWebTransportStreamToSessionMap.Get(aStreamId, &sessionId)) {
    uint8_t wtError = Http3ErrorToWebTransportError(aError);
    nsresult rv = GetNSResultFromWebTransportError(wtError);

    RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId);
    if (stream) {
      if (aType == RESET) {
        stream->SetRecvdReset();
      }
      RefPtr<Http3WebTransportStream> wtStream =
          stream->GetHttp3WebTransportStream();
      if (wtStream) {
        CloseWebTransportStream(wtStream, rv);
      }
    }

    RefPtr<Http3StreamBase> session = mStreamIdHash.Get(sessionId);
    if (session) {
      Http3WebTransportSession* wtSession =
          session->GetHttp3WebTransportSession();
      MOZ_ASSERT(wtSession);
      if (wtSession) {
        if (aType == RESET) {
          wtSession->OnStreamReset(aStreamId, rv);
        } else {
          wtSession->OnStreamStopSending(aStreamId, rv);
        }
      }
    }
    return;
  }

  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);
  } else if (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);
    }
  }
}

void Http3Session::SetConnection(nsAHttpConnection* aConn) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  mConnection = aConn;
}

void Http3Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) {
  *aOut = nullptr;
}

// 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);
      }

      if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
        mFirstHttpTransaction = nullptr;
      }
      break;
    }

    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.

      break;
  }
}

bool Http3Session::IsDone() { return mState == CLOSED; }

nsresult Http3Session::Status() {
  MOZ_ASSERT(false"Http3Session::Status()");
  return NS_ERROR_UNEXPECTED;
}

uint32_t Http3Session::Caps() {
  MOZ_ASSERT(false"Http3Session::Caps()");
  return 0;
}

nsresult Http3Session::ReadSegments(nsAHttpSegmentReader* reader,
                                    uint32_t count, uint32_t* countRead) {
  MOZ_ASSERT(false"Http3Session::ReadSegments()");
  return NS_ERROR_UNEXPECTED;
}

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)

  nsresult rv = NS_OK;
  RefPtr<Http3StreamBase> stream;

  // Step 1)
  while (CanSendData() && (stream = mReadyForWrite.PopFront())) {
    LOG(("Http3Session::SendData call ReadSegments from stream=%p [this=%p]",
         stream.get(), this));
    stream->SetInTxQueue(false);
    rv = stream->ReadSegments();

    // 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;
      } else if (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;
  }

  mReadyForWrite.Push(aStream);
  aStream->SetInTxQueue(true);
  if (CanSendData() && mConnection) {
    Unused << mConnection->ResumeSend();
  }
}

void Http3Session::MaybeResumeSend() {
  if ((mReadyForWrite.GetSize() > 0) && CanSendData() && mConnection) {
    Unused << mConnection->ResumeSend();
  }
}

nsresult Http3Session::ProcessSlowConsumers() {
  if (mSlowConsumersReadyForRead.IsEmpty()) {
    return NS_OK;
  }

  RefPtr<Http3StreamBase> slowConsumer =
      mSlowConsumersReadyForRead.ElementAt(0);
  mSlowConsumersReadyForRead.RemoveElementAt(0);

  nsresult rv = ProcessTransactionRead(slowConsumer);

  return rv;
}

nsresult Http3Session::WriteSegments(nsAHttpSegmentWriter* writer,
                                     uint32_t count, uint32_t* countWritten) {
  MOZ_ASSERT(false"Http3Session::WriteSegments()");
  return NS_ERROR_UNEXPECTED;
}

nsresult Http3Session::RecvData(nsIUDPSocket* socket) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  // Process slow consumers.
  nsresult rv = ProcessSlowConsumers();
  if (NS_FAILED(rv)) {
    LOG3(("Http3Session %p ProcessSlowConsumers returns 0x%" PRIx32 "\n"this,
          static_cast<uint32_t>(rv)));
    return rv;
  }

  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;
  }

#ifndef ANDROID
  if (mState == ZERORTT) {
    ZeroRttTelemetry(aCallNeqoClose ? ZeroRttOutcome::USED_CONN_CLOSED_BY_NECKO
                                    : ZeroRttOutcome::USED_CONN_ERROR);
  }
#endif

  mState = CLOSING;
  Shutdown();

  if (aCallNeqoClose) {
    mHttp3Connection->Close(HTTP3_APP_ERROR_NO_ERROR);
  }

  mStreamIdHash.Clear();
  mStreamTransactionHash.Clear();
}

nsHttpConnectionInfo* Http3Session::ConnectionInfo() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  RefPtr<nsHttpConnectionInfo> ci;
  GetConnectionInfo(getter_AddRefs(ci));
  return ci.get();
}

void Http3Session::SetProxyConnectFailed() {
  MOZ_ASSERT(false"Http3Session::SetProxyConnectFailed()");
}

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;
}

uint32_t Http3Session::Http1xTransactionCount() { return 0; }

nsresult Http3Session::TakeSubTransactions(
    nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
  return NS_OK;
}

//-----------------------------------------------------------------------------
// Pass through methods of nsAHttpConnection
//-----------------------------------------------------------------------------

nsAHttpConnection* Http3Session::Connection() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  return mConnection;
}

nsresult Http3Session::OnHeadersAvailable(nsAHttpTransaction* transaction,
                                          nsHttpRequestHead* requestHead,
                                          nsHttpResponseHead* responseHead,
                                          bool* reset) {
  MOZ_ASSERT(mConnection);
  if (mConnection) {
    return mConnection->OnHeadersAvailable(transaction, requestHead,
                                           responseHead, reset);
  }
  return NS_OK;
}

bool Http3Session::IsReused() {
  if (mConnection) {
    return mConnection->IsReused();
  }
  return true;
}

nsresult Http3Session::PushBack(const char* buf, uint32_t len) {
  return NS_ERROR_UNEXPECTED;
}

--> --------------------

--> maximum size reached

--> --------------------

97%


¤ Dauer der Verarbeitung: 0.40 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.