tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

lib.rs (95159B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 #![expect(clippy::missing_panics_doc, reason = "OK here")]
      6 
      7 #[cfg(feature = "fuzzing")]
      8 use std::time::Duration;
      9 use std::{
     10    borrow::Cow,
     11    cell::RefCell,
     12    cmp::min,
     13    ffi::c_void,
     14    io,
     15    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
     16    path::PathBuf,
     17    ptr,
     18    rc::Rc,
     19    slice, str,
     20    time::{Duration, Instant},
     21 };
     22 
     23 use firefox_on_glean::{
     24    metrics::networking,
     25    private::{LocalCustomDistribution, LocalMemoryDistribution},
     26 };
     27 #[cfg(not(windows))]
     28 use libc::{c_int, AF_INET, AF_INET6};
     29 use libc::{c_uchar, size_t};
     30 use neqo_common::{
     31    event::Provider as _, qdebug, qerror, qlog::Qlog, qwarn, Datagram, DatagramBatch, Decoder,
     32    Encoder, Header, Role, Tos,
     33 };
     34 use neqo_crypto::{agent::CertificateCompressor, init, PRErrorCode};
     35 use neqo_http3::{
     36    features::extended_connect::session, ConnectUdpEvent, Error as Http3Error, Http3Client,
     37    Http3ClientEvent, Http3Parameters, Http3State, Priority, WebTransportEvent,
     38 };
     39 use neqo_transport::{
     40    stream_id::StreamType, CongestionControlAlgorithm, Connection, ConnectionParameters,
     41    Error as TransportError, Output, OutputBatch, RandomConnectionIdGenerator, StreamId, Version,
     42 };
     43 use nserror::{
     44    nsresult, NS_BASE_STREAM_WOULD_BLOCK, NS_ERROR_CONNECTION_REFUSED,
     45    NS_ERROR_DOM_INVALID_HEADER_NAME, NS_ERROR_FILE_ALREADY_EXISTS, NS_ERROR_ILLEGAL_VALUE,
     46    NS_ERROR_INVALID_ARG, NS_ERROR_NET_HTTP3_PROTOCOL_ERROR, NS_ERROR_NET_INTERRUPT,
     47    NS_ERROR_NET_RESET, NS_ERROR_NET_TIMEOUT, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_CONNECTED,
     48    NS_ERROR_OUT_OF_MEMORY, NS_ERROR_SOCKET_ADDRESS_IN_USE, NS_ERROR_UNEXPECTED, NS_OK,
     49 };
     50 use nsstring::{nsACString, nsCString};
     51 use thin_vec::ThinVec;
     52 use uuid::Uuid;
     53 #[cfg(windows)]
     54 use winapi::{
     55    ctypes::c_int,
     56    shared::ws2def::{AF_INET, AF_INET6},
     57 };
     58 use xpcom::{interfaces::nsISocketProvider, AtomicRefcnt, RefCounted, RefPtr};
     59 use zlib_rs::inflate::{uncompress_slice, InflateConfig};
     60 use zlib_rs::ReturnCode;
     61 
     62 std::thread_local! {
     63    static RECV_BUF: RefCell<neqo_udp::RecvBuf> = RefCell::new(neqo_udp::RecvBuf::default());
     64 }
     65 
     66 #[allow(clippy::cast_possible_truncation, reason = "see check below")]
     67 const AF_INET_U16: u16 = AF_INET as u16;
     68 static_assertions::const_assert_eq!(AF_INET_U16 as c_int, AF_INET);
     69 
     70 #[allow(clippy::cast_possible_truncation, reason = "see check below")]
     71 const AF_INET6_U16: u16 = AF_INET6 as u16;
     72 static_assertions::const_assert_eq!(AF_INET6_U16 as c_int, AF_INET6);
     73 
     74 #[repr(C)]
     75 pub struct WouldBlockCounter {
     76    rx: usize,
     77    tx: usize,
     78 }
     79 
     80 impl WouldBlockCounter {
     81    pub fn new() -> Self {
     82        Self { rx: 0, tx: 0 }
     83    }
     84 
     85    pub fn increment_rx(&mut self) {
     86        self.rx += 1;
     87    }
     88 
     89    pub fn increment_tx(&mut self) {
     90        self.tx += 1;
     91    }
     92 
     93    pub fn rx_count(&self) -> usize {
     94        self.rx
     95    }
     96 
     97    pub fn tx_count(&self) -> usize {
     98        self.tx
     99    }
    100 }
    101 
    102 #[repr(C)]
    103 pub struct NeqoHttp3Conn {
    104    conn: Http3Client,
    105    local_addr: SocketAddr,
    106    refcnt: AtomicRefcnt,
    107    /// Socket to use for IO.
    108    ///
    109    /// When [`None`], NSPR is used for IO.
    110    //
    111    // Use a `BorrowedSocket` instead of e.g. `std::net::UdpSocket`. The latter
    112    // would close the file descriptor on `Drop`. The lifetime of the underlying
    113    // OS socket is managed not by `neqo_glue` but `NSPR`.
    114    socket: Option<neqo_udp::Socket<BorrowedSocket>>,
    115    /// Buffered outbound datagram from previous send that failed with
    116    /// WouldBlock. To be sent once UDP socket has write-availability again.
    117    buffered_outbound_datagram: Option<DatagramBatch>,
    118 
    119    datagram_segment_size_sent: LocalMemoryDistribution<'static>,
    120    datagram_segment_size_received: LocalMemoryDistribution<'static>,
    121    datagram_size_sent: LocalMemoryDistribution<'static>,
    122    datagram_size_received: LocalMemoryDistribution<'static>,
    123    datagram_segments_sent: LocalCustomDistribution<'static>,
    124    datagram_segments_received: LocalCustomDistribution<'static>,
    125    would_block_counter: WouldBlockCounter,
    126 }
    127 
    128 impl Drop for NeqoHttp3Conn {
    129    fn drop(&mut self) {
    130        self.record_stats_in_glean();
    131    }
    132 }
    133 
    134 // Opaque interface to mozilla::net::NetAddr defined in DNS.h
    135 #[repr(C)]
    136 pub union NetAddr {
    137    private: [u8; 0],
    138 }
    139 
    140 extern "C" {
    141    pub fn moz_netaddr_get_family(arg: *const NetAddr) -> u16;
    142    pub fn moz_netaddr_get_network_order_ip(arg: *const NetAddr) -> u32;
    143    pub fn moz_netaddr_get_ipv6(arg: *const NetAddr) -> *const u8;
    144    pub fn moz_netaddr_get_network_order_port(arg: *const NetAddr) -> u16;
    145 }
    146 
    147 fn netaddr_to_socket_addr(arg: *const NetAddr) -> Result<SocketAddr, nsresult> {
    148    if arg.is_null() {
    149        return Err(NS_ERROR_INVALID_ARG);
    150    }
    151 
    152    unsafe {
    153        let family = i32::from(moz_netaddr_get_family(arg));
    154        if family == AF_INET {
    155            let port = u16::from_be(moz_netaddr_get_network_order_port(arg));
    156            let ipv4 = Ipv4Addr::from(u32::from_be(moz_netaddr_get_network_order_ip(arg)));
    157            return Ok(SocketAddr::new(IpAddr::V4(ipv4), port));
    158        }
    159 
    160        if family == AF_INET6 {
    161            let port = u16::from_be(moz_netaddr_get_network_order_port(arg));
    162            let ipv6_slice: [u8; 16] = slice::from_raw_parts(moz_netaddr_get_ipv6(arg), 16)
    163                .try_into()
    164                .expect("slice with incorrect length");
    165            let ipv6 = Ipv6Addr::from(ipv6_slice);
    166            return Ok(SocketAddr::new(IpAddr::V6(ipv6), port));
    167        }
    168    }
    169 
    170    Err(NS_ERROR_UNEXPECTED)
    171 }
    172 
    173 fn enable_zlib_decoder(c: &mut Connection) -> neqo_transport::Res<()> {
    174    struct ZlibCertDecoder {}
    175 
    176    impl CertificateCompressor for ZlibCertDecoder {
    177        // RFC 8879
    178        const ID: u16 = 0x1;
    179        const NAME: &std::ffi::CStr = c"zlib";
    180 
    181        fn decode(input: &[u8], output: &mut [u8]) -> neqo_crypto::Res<()> {
    182            let (output_slice, error) = uncompress_slice(output, &input, InflateConfig::default());
    183            if error != ReturnCode::Ok {
    184                return Err(neqo_crypto::Error::CertificateDecoding);
    185            }
    186            if output_slice.len() != output.len() {
    187                return Err(neqo_crypto::Error::CertificateDecoding);
    188            }
    189 
    190            Ok(())
    191        }
    192    }
    193 
    194    c.set_certificate_compression::<ZlibCertDecoder>()
    195 }
    196 
    197 extern "C" {
    198    pub fn ZSTD_decompress(
    199        dst: *mut ::core::ffi::c_void,
    200        dstCapacity: usize,
    201        src: *const ::core::ffi::c_void,
    202        compressedSize: usize,
    203    ) -> usize;
    204 }
    205 
    206 extern "C" {
    207    pub fn ZSTD_isError(result: usize) -> ::core::ffi::c_uint;
    208 }
    209 
    210 fn enable_zstd_decoder(c: &mut Connection) -> neqo_transport::Res<()> {
    211    struct ZstdCertDecoder {}
    212 
    213    impl CertificateCompressor for ZstdCertDecoder {
    214        // RFC 8879
    215        const ID: u16 = 0x3;
    216        const NAME: &std::ffi::CStr = c"zstd";
    217 
    218        fn decode(input: &[u8], output: &mut [u8]) -> neqo_crypto::Res<()> {
    219            if input.is_empty() {
    220                return Err(neqo_crypto::Error::CertificateDecoding);
    221            }
    222            if output.is_empty() {
    223                return Err(neqo_crypto::Error::CertificateDecoding);
    224            }
    225 
    226            let output_len = unsafe {
    227                ZSTD_decompress(
    228                    output.as_mut_ptr() as *mut c_void,
    229                    output.len(),
    230                    input.as_ptr() as *const c_void,
    231                    input.len(),
    232                )
    233            };
    234 
    235            // ZSTD_isError return 1 if error, 0 otherwise
    236            if unsafe { ZSTD_isError(output_len) != 0 } {
    237                qdebug!("zstd compression failed with {output_len}");
    238                return Err(neqo_crypto::Error::CertificateDecoding);
    239            }
    240 
    241            if output.len() != output_len {
    242                qdebug!("zstd compression `output_len` {output_len} doesn't match expected `output.len()` {}", output.len());
    243                return Err(neqo_crypto::Error::CertificateDecoding);
    244            }
    245 
    246            Ok(())
    247        }
    248    }
    249 
    250    c.set_certificate_compression::<ZstdCertDecoder>()
    251 }
    252 
    253 #[repr(C)]
    254 #[derive(Debug, PartialEq)]
    255 pub enum BrotliDecoderResult {
    256    Error = 0,
    257    Success = 1,
    258    NeedsMoreInput = 2,
    259    NeedsMoreOutput = 3,
    260 }
    261 
    262 extern "C" {
    263    pub fn BrotliDecoderDecompress(
    264        encoded_size: size_t,
    265        encoded_buffer: *const c_uchar,
    266        decoded_size: *mut size_t,
    267        decoded_buffer: *mut c_uchar,
    268    ) -> BrotliDecoderResult;
    269 }
    270 
    271 fn enable_brotli_decoder(c: &mut Connection) -> neqo_transport::Res<()> {
    272    struct BrotliCertDecoder {}
    273 
    274    impl CertificateCompressor for BrotliCertDecoder {
    275        // RFC 8879
    276        const ID: u16 = 0x2;
    277        const NAME: &std::ffi::CStr = c"brotli";
    278 
    279        fn decode(input: &[u8], output: &mut [u8]) -> neqo_crypto::Res<()> {
    280            if input.is_empty() {
    281                return Err(neqo_crypto::Error::CertificateDecoding);
    282            }
    283            if output.is_empty() {
    284                return Err(neqo_crypto::Error::CertificateDecoding);
    285            }
    286 
    287            let mut uncompressed_size = output.len();
    288            let result = unsafe {
    289                BrotliDecoderDecompress(
    290                    input.len(),
    291                    input.as_ptr(),
    292                    &mut uncompressed_size as *mut usize,
    293                    output.as_mut_ptr(),
    294                )
    295            };
    296 
    297            if result != BrotliDecoderResult::Success {
    298                return Err(neqo_crypto::Error::CertificateDecoding);
    299            }
    300 
    301            if uncompressed_size != output.len() {
    302                return Err(neqo_crypto::Error::CertificateDecoding);
    303            }
    304 
    305            Ok(())
    306        }
    307    }
    308 
    309    c.set_certificate_compression::<BrotliCertDecoder>()
    310 }
    311 
    312 type SendFunc = extern "C" fn(
    313    context: *mut c_void,
    314    addr_family: u16,
    315    addr: *const u8,
    316    port: u16,
    317    data: *const u8,
    318    size: u32,
    319 ) -> nsresult;
    320 
    321 type SetTimerFunc = extern "C" fn(context: *mut c_void, timeout: u64);
    322 
    323 #[cfg(unix)]
    324 type BorrowedSocket = std::os::fd::BorrowedFd<'static>;
    325 #[cfg(windows)]
    326 type BorrowedSocket = std::os::windows::io::BorrowedSocket<'static>;
    327 
    328 impl NeqoHttp3Conn {
    329    /// Create a new [`NeqoHttp3Conn`].
    330    ///
    331    /// Note that [`NeqoHttp3Conn`] works under the assumption that the UDP
    332    /// socket of the connection, i.e. the one provided to
    333    /// [`NeqoHttp3Conn::new`], does not change throughout the lifetime of
    334    /// [`NeqoHttp3Conn`].
    335    #[expect(
    336        clippy::too_many_arguments,
    337        clippy::too_many_lines,
    338        reason = "Nothing to be done about it."
    339    )]
    340    fn new(
    341        origin: &nsACString,
    342        alpn: &nsACString,
    343        local_addr: *const NetAddr,
    344        remote_addr: *const NetAddr,
    345        max_table_size: u64,
    346        max_blocked_streams: u16,
    347        max_data: u64,
    348        max_stream_data: u64,
    349        version_negotiation: bool,
    350        webtransport: bool,
    351        qlog_dir: &nsACString,
    352        provider_flags: u32,
    353        idle_timeout: u32,
    354        pmtud_enabled: bool,
    355        socket: Option<i64>,
    356    ) -> Result<RefPtr<Self>, nsresult> {
    357        // Nss init.
    358        init().map_err(|_| NS_ERROR_UNEXPECTED)?;
    359 
    360        let socket = socket
    361            .map(|socket| {
    362                #[cfg(unix)]
    363                let borrowed = {
    364                    use std::os::fd::{BorrowedFd, RawFd};
    365                    if socket == -1 {
    366                        qerror!("got invalid socked {}", socket);
    367                        return Err(NS_ERROR_INVALID_ARG);
    368                    }
    369                    let raw: RawFd = socket.try_into().map_err(|e| {
    370                        qerror!("got invalid socked {}: {}", socket, e);
    371                        NS_ERROR_INVALID_ARG
    372                    })?;
    373                    unsafe { BorrowedFd::borrow_raw(raw) }
    374                };
    375                #[cfg(windows)]
    376                let borrowed = {
    377                    use std::os::windows::io::{BorrowedSocket, RawSocket};
    378                    if socket as usize == winapi::um::winsock2::INVALID_SOCKET {
    379                        qerror!("got invalid socked {}", socket);
    380                        return Err(NS_ERROR_INVALID_ARG);
    381                    }
    382                    let raw: RawSocket = socket.try_into().map_err(|e| {
    383                        qerror!("got invalid socked {}: {}", socket, e);
    384                        NS_ERROR_INVALID_ARG
    385                    })?;
    386                    unsafe { BorrowedSocket::borrow_raw(raw) }
    387                };
    388                neqo_udp::Socket::new(borrowed).map_err(|e| {
    389                    qerror!("failed to initialize socket {}: {}", socket, e);
    390                    into_nsresult(&e)
    391                })
    392            })
    393            .transpose()?;
    394 
    395        let origin_conv = str::from_utf8(origin).map_err(|_| NS_ERROR_INVALID_ARG)?;
    396 
    397        let alpn_conv = str::from_utf8(alpn).map_err(|_| NS_ERROR_INVALID_ARG)?;
    398 
    399        let local: SocketAddr = netaddr_to_socket_addr(local_addr)?;
    400 
    401        let remote: SocketAddr = netaddr_to_socket_addr(remote_addr)?;
    402 
    403        let quic_version = match alpn_conv {
    404            "h3" => Version::Version1,
    405            _ => return Err(NS_ERROR_INVALID_ARG),
    406        };
    407 
    408        let version_list = if version_negotiation {
    409            Version::all()
    410        } else {
    411            vec![quic_version]
    412        };
    413 
    414        let cc_algorithm = match static_prefs::pref!("network.http.http3.cc_algorithm") {
    415            0 => CongestionControlAlgorithm::NewReno,
    416            1 => CongestionControlAlgorithm::Cubic,
    417            _ => {
    418                // Unknown preferences; default to Cubic
    419                CongestionControlAlgorithm::Cubic
    420            }
    421        };
    422 
    423        let pmtud_enabled =
    424            // Check if PMTUD is explicitly enabled,
    425            pmtud_enabled
    426            // or enabled via pref,
    427            || static_prefs::pref!("network.http.http3.pmtud")
    428            // but disable PMTUD if NSPR is used (socket == None) or
    429            // transmitted UDP datagrams might get fragmented by the IP layer.
    430            && socket.as_ref().map_or(false, |s| !s.may_fragment());
    431 
    432        let params = ConnectionParameters::default()
    433            .versions(quic_version, version_list)
    434            .cc_algorithm(cc_algorithm)
    435            .max_data(max_data)
    436            .max_stream_data(StreamType::BiDi, false, max_stream_data)
    437            .grease(static_prefs::pref!("security.tls.grease_http3_enable"))
    438            .sni_slicing(static_prefs::pref!("network.http.http3.sni-slicing"))
    439            .idle_timeout(Duration::from_secs(idle_timeout.into()))
    440            // Disabled on OpenBSD. See <https://bugzilla.mozilla.org/show_bug.cgi?id=1952304>.
    441            .pmtud_iface_mtu(cfg!(not(target_os = "openbsd")))
    442            // MLKEM support is configured further below. By default, disable it.
    443            .mlkem(false)
    444            .pmtud(pmtud_enabled);
    445 
    446        // Set a short timeout when fuzzing.
    447        #[cfg(feature = "fuzzing")]
    448        if static_prefs::pref!("fuzzing.necko.http3") {
    449            params = params.idle_timeout(Duration::from_millis(10));
    450        }
    451 
    452        let http3_settings = Http3Parameters::default()
    453            .max_table_size_encoder(max_table_size)
    454            .max_table_size_decoder(max_table_size)
    455            .max_blocked_streams(max_blocked_streams)
    456            .max_concurrent_push_streams(0)
    457            .connection_parameters(params)
    458            .webtransport(webtransport)
    459            .connect(true)
    460            .http3_datagram(true);
    461 
    462        let Ok(mut conn) = Connection::new_client(
    463            origin_conv,
    464            &[alpn_conv],
    465            Rc::new(RefCell::new(RandomConnectionIdGenerator::new(3))),
    466            local,
    467            remote,
    468            http3_settings.get_connection_parameters().clone(),
    469            Instant::now(),
    470        ) else {
    471            return Err(NS_ERROR_INVALID_ARG);
    472        };
    473 
    474        let mut additional_shares = usize::from(static_prefs::pref!(
    475            "security.tls.client_hello.send_p256_keyshare"
    476        ));
    477        if static_prefs::pref!("security.tls.enable_kyber")
    478            && static_prefs::pref!("network.http.http3.enable_kyber")
    479            && (provider_flags & nsISocketProvider::IS_RETRY) == 0
    480            && (provider_flags & nsISocketProvider::BE_CONSERVATIVE) == 0
    481        {
    482            // These operations are infallible when conn.state == State::Init.
    483            conn.set_groups(&[
    484                neqo_crypto::TLS_GRP_KEM_MLKEM768X25519,
    485                neqo_crypto::TLS_GRP_EC_X25519,
    486                neqo_crypto::TLS_GRP_EC_SECP256R1,
    487                neqo_crypto::TLS_GRP_EC_SECP384R1,
    488                neqo_crypto::TLS_GRP_EC_SECP521R1,
    489            ])
    490            .map_err(|_| NS_ERROR_UNEXPECTED)?;
    491            additional_shares += 1;
    492        }
    493        // If additional_shares == 2, send mlkem768x25519, x25519, and p256.
    494        // If additional_shares == 1, send {mlkem768x25519, x25519} or {x25519, p256}.
    495        // If additional_shares == 0, send x25519.
    496        conn.send_additional_key_shares(additional_shares)
    497            .map_err(|_| NS_ERROR_UNEXPECTED)?;
    498 
    499        if static_prefs::pref!("security.tls.enable_certificate_compression_zlib")
    500            && static_prefs::pref!("network.http.http3.enable_certificate_compression_zlib")
    501        {
    502            enable_zlib_decoder(&mut conn).map_err(|_| NS_ERROR_UNEXPECTED)?;
    503        }
    504 
    505        if static_prefs::pref!("security.tls.enable_certificate_compression_zstd")
    506            && static_prefs::pref!("network.http.http3.enable_certificate_compression_zstd")
    507        {
    508            enable_zstd_decoder(&mut conn).map_err(|_| NS_ERROR_UNEXPECTED)?;
    509        }
    510 
    511        if static_prefs::pref!("security.tls.enable_certificate_compression_brotli")
    512            && static_prefs::pref!("network.http.http3.enable_certificate_compression_brotli")
    513        {
    514            enable_brotli_decoder(&mut conn).map_err(|_| NS_ERROR_UNEXPECTED)?;
    515        }
    516 
    517        let mut conn = Http3Client::new_with_conn(conn, http3_settings);
    518 
    519        if !qlog_dir.is_empty() {
    520            let qlog_dir_conv = str::from_utf8(qlog_dir).map_err(|_| NS_ERROR_INVALID_ARG)?;
    521            let qlog_path = PathBuf::from(qlog_dir_conv);
    522 
    523            match Qlog::enabled_with_file(
    524                qlog_path.clone(),
    525                Role::Client,
    526                Some("Firefox Client qlog".to_string()),
    527                Some("Firefox Client qlog".to_string()),
    528                format!("{}_{}.qlog", origin, Uuid::new_v4()),
    529                Instant::now(),
    530            ) {
    531                Ok(qlog) => conn.set_qlog(qlog),
    532                Err(e) => {
    533                    // Emit warnings but to not return an error if qlog initialization
    534                    // fails.
    535                    qwarn!("failed to create Qlog at {}: {}", qlog_path.display(), e);
    536                }
    537            }
    538        }
    539 
    540        let conn = Box::into_raw(Box::new(Self {
    541            conn,
    542            local_addr: local,
    543            refcnt: unsafe { AtomicRefcnt::new() },
    544            socket,
    545            datagram_segment_size_sent: networking::http_3_udp_datagram_segment_size_sent
    546                .start_buffer(),
    547            datagram_segment_size_received: networking::http_3_udp_datagram_segment_size_received
    548                .start_buffer(),
    549            datagram_size_sent: networking::http_3_udp_datagram_size_sent.start_buffer(),
    550            datagram_size_received: networking::http_3_udp_datagram_size_received.start_buffer(),
    551            datagram_segments_sent: networking::http_3_udp_datagram_segments_sent.start_buffer(),
    552            datagram_segments_received: networking::http_3_udp_datagram_segments_received
    553                .start_buffer(),
    554            buffered_outbound_datagram: None,
    555            would_block_counter: WouldBlockCounter::new(),
    556        }));
    557        unsafe { RefPtr::from_raw(conn).ok_or(NS_ERROR_NOT_CONNECTED) }
    558    }
    559 
    560    fn record_stats_in_glean(&self) {
    561        use firefox_on_glean::metrics::networking as glean;
    562        use neqo_common::Ecn;
    563        use neqo_transport::{ecn, CongestionEvent};
    564 
    565        // Metric values must be recorded as integers. Glean does not support
    566        // floating point distributions. In order to represent values <1, they
    567        // are multiplied by `PRECISION_FACTOR`. A `PRECISION_FACTOR` of
    568        // `10_000` allows one to represent fractions down to 0.0001.
    569        const PRECISION_FACTOR: u64 = 10_000;
    570        #[allow(clippy::cast_possible_truncation, reason = "see check below")]
    571        const PRECISION_FACTOR_USIZE: usize = PRECISION_FACTOR as usize;
    572        static_assertions::const_assert_eq!(PRECISION_FACTOR_USIZE as u64, PRECISION_FACTOR);
    573 
    574        let stats = self.conn.transport_stats();
    575 
    576        if stats.packets_tx == 0 {
    577            return;
    578        }
    579 
    580        for (s, postfix) in [(&stats.frame_tx, "_tx"), (&stats.frame_rx, "_rx")] {
    581            let add = |label: &str, value: usize| {
    582                glean::http_3_quic_frame_count
    583                    .get(&(label.to_string() + postfix))
    584                    .add(value.try_into().unwrap_or(i32::MAX));
    585            };
    586 
    587            add("ack", s.ack);
    588            add("crypto", s.crypto);
    589            add("stream", s.stream);
    590            add("reset_stream", s.reset_stream);
    591            add("stop_sending", s.stop_sending);
    592            add("ping", s.ping);
    593            add("padding", s.padding);
    594            add("max_streams", s.max_streams);
    595            add("streams_blocked", s.streams_blocked);
    596            add("max_data", s.max_data);
    597            add("data_blocked", s.data_blocked);
    598            add("max_stream_data", s.max_stream_data);
    599            add("stream_data_blocked", s.stream_data_blocked);
    600            add("new_connection_id", s.new_connection_id);
    601            add("retire_connection_id", s.retire_connection_id);
    602            add("path_challenge", s.path_challenge);
    603            add("path_response", s.path_response);
    604            add("connection_close", s.connection_close);
    605            add("handshake_done", s.handshake_done);
    606            add("new_token", s.new_token);
    607            add("ack_frequency", s.ack_frequency);
    608            add("datagram", s.datagram);
    609        }
    610 
    611        if !static_prefs::pref!("network.http.http3.use_nspr_for_io")
    612            && static_prefs::pref!("network.http.http3.ecn_report")
    613            && stats.frame_rx.handshake_done != 0
    614        {
    615            let rx_ect0_sum: u64 = stats.ecn_rx.into_values().map(|v| v[Ecn::Ect0]).sum();
    616            let rx_ce_sum: u64 = stats.ecn_rx.into_values().map(|v| v[Ecn::Ce]).sum();
    617            if rx_ect0_sum > 0 {
    618                if let Ok(ratio) = i64::try_from((rx_ce_sum * PRECISION_FACTOR) / rx_ect0_sum) {
    619                    glean::http_3_ecn_ce_ect0_ratio_received.accumulate_single_sample_signed(ratio);
    620                } else {
    621                    let msg = "Failed to convert ratio to i64 for use with glean";
    622                    qwarn!("{msg}");
    623                    debug_assert!(false, "{msg}");
    624                }
    625            }
    626        }
    627 
    628        if !static_prefs::pref!("network.http.http3.use_nspr_for_io")
    629            && static_prefs::pref!("network.http.http3.ecn_mark")
    630            && stats.frame_rx.handshake_done != 0
    631        {
    632            let tx_ect0_sum: u64 = stats.ecn_tx_acked.into_values().map(|v| v[Ecn::Ect0]).sum();
    633            let tx_ce_sum: u64 = stats.ecn_tx_acked.into_values().map(|v| v[Ecn::Ce]).sum();
    634            if tx_ect0_sum > 0 {
    635                if let Ok(ratio) = i64::try_from((tx_ce_sum * PRECISION_FACTOR) / tx_ect0_sum) {
    636                    glean::http_3_ecn_ce_ect0_ratio_sent.accumulate_single_sample_signed(ratio);
    637                } else {
    638                    let msg = "Failed to convert ratio to i64 for use with glean";
    639                    qwarn!("{msg}");
    640                    debug_assert!(false, "{msg}");
    641                }
    642            }
    643            for (outcome, value) in stats.ecn_path_validation.into_iter() {
    644                let Ok(value) = i32::try_from(value) else {
    645                    let msg = format!("Failed to convert {value} to i32 for use with glean");
    646                    qwarn!("{msg}");
    647                    debug_assert!(false, "{msg}");
    648                    continue;
    649                };
    650                match outcome {
    651                    ecn::ValidationOutcome::Capable => {
    652                        glean::http_3_ecn_path_capability.get("capable").add(value);
    653                    }
    654                    ecn::ValidationOutcome::NotCapable(ecn::ValidationError::BlackHole) => {
    655                        glean::http_3_ecn_path_capability
    656                            .get("black-hole")
    657                            .add(value);
    658                    }
    659                    ecn::ValidationOutcome::NotCapable(ecn::ValidationError::Bleaching) => {
    660                        glean::http_3_ecn_path_capability
    661                            .get("bleaching")
    662                            .add(value);
    663                    }
    664                    ecn::ValidationOutcome::NotCapable(
    665                        ecn::ValidationError::ReceivedUnsentECT1,
    666                    ) => {
    667                        glean::http_3_ecn_path_capability
    668                            .get("received-unsent-ect-1")
    669                            .add(value);
    670                    }
    671                }
    672            }
    673        }
    674 
    675        // Ignore connections into the void for metrics where it makes sense.
    676        if stats.packets_rx != 0 {
    677            // Calculate and collect packet loss ratio.
    678            if let Ok(loss) =
    679                i64::try_from((stats.lost * PRECISION_FACTOR_USIZE) / stats.packets_tx)
    680            {
    681                glean::http_3_loss_ratio.accumulate_single_sample_signed(loss);
    682            } else {
    683                let msg = "Failed to convert ratio to i64 for use with glean";
    684                qwarn!("{msg}");
    685                debug_assert!(false, "{msg}");
    686            }
    687 
    688            // Count whether the connection exited slow start.
    689            if stats.cc.slow_start_exited {
    690                glean::http_3_slow_start_exited.get("exited").add(1);
    691            } else {
    692                glean::http_3_slow_start_exited.get("not_exited").add(1);
    693            }
    694        }
    695 
    696        // Ignore connections that never had loss induced congestion events (and prevent dividing by zero).
    697        if stats.cc.congestion_events[CongestionEvent::Loss] != 0 {
    698            if let Ok(spurious) = i64::try_from(
    699                (stats.cc.congestion_events[CongestionEvent::Spurious] * PRECISION_FACTOR_USIZE)
    700                    / stats.cc.congestion_events[CongestionEvent::Loss],
    701            ) {
    702                glean::http_3_spurious_congestion_event_ratio
    703                    .accumulate_single_sample_signed(spurious);
    704            } else {
    705                let msg = "Failed to convert ratio to i64 for use with glean";
    706                qwarn!("{msg}");
    707                debug_assert!(false, "{msg}");
    708            }
    709        }
    710 
    711        // Collect congestion event reason metric
    712        if let Ok(ce_loss) = i32::try_from(stats.cc.congestion_events[CongestionEvent::Loss]) {
    713            glean::http_3_congestion_event_reason
    714                .get("loss")
    715                .add(ce_loss);
    716        } else {
    717            let msg = "Failed to convert to i32 for use with glean";
    718            qwarn!("{msg}");
    719            debug_assert!(false, "{msg}");
    720        }
    721        if let Ok(ce_ecn) = i32::try_from(stats.cc.congestion_events[CongestionEvent::Ecn]) {
    722            glean::http_3_congestion_event_reason
    723                .get("ecn-ce")
    724                .add(ce_ecn);
    725        } else {
    726            let msg = "Failed to convert to i32 for use with glean";
    727            qwarn!("{msg}");
    728            debug_assert!(false, "{msg}");
    729        }
    730    }
    731 
    732    fn increment_would_block_rx(&mut self) {
    733        self.would_block_counter.increment_rx();
    734    }
    735 
    736    fn would_block_rx_count(&self) -> usize {
    737        self.would_block_counter.rx_count()
    738    }
    739 
    740    fn increment_would_block_tx(&mut self) {
    741        self.would_block_counter.increment_tx();
    742    }
    743 
    744    fn would_block_tx_count(&self) -> usize {
    745        self.would_block_counter.tx_count()
    746    }
    747 }
    748 
    749 /// # Safety
    750 ///
    751 /// See [`AtomicRefcnt::inc`].
    752 #[no_mangle]
    753 pub unsafe extern "C" fn neqo_http3conn_addref(conn: &NeqoHttp3Conn) {
    754    conn.refcnt.inc();
    755 }
    756 
    757 /// # Safety
    758 ///
    759 /// Manually drops a pointer without consuming pointee. The caller needs to
    760 /// ensure no other referenecs remain. In addition safety conditions of
    761 /// [`AtomicRefcnt::dec`] apply.
    762 #[no_mangle]
    763 pub unsafe extern "C" fn neqo_http3conn_release(conn: &NeqoHttp3Conn) {
    764    let rc = conn.refcnt.dec();
    765    if rc == 0 {
    766        drop(Box::from_raw(ptr::from_ref(conn).cast_mut()));
    767    }
    768 }
    769 
    770 // xpcom::RefPtr support
    771 unsafe impl RefCounted for NeqoHttp3Conn {
    772    unsafe fn addref(&self) {
    773        neqo_http3conn_addref(self);
    774    }
    775    unsafe fn release(&self) {
    776        neqo_http3conn_release(self);
    777    }
    778 }
    779 
    780 // Allocate a new NeqoHttp3Conn object.
    781 #[no_mangle]
    782 pub extern "C" fn neqo_http3conn_new(
    783    origin: &nsACString,
    784    alpn: &nsACString,
    785    local_addr: *const NetAddr,
    786    remote_addr: *const NetAddr,
    787    max_table_size: u64,
    788    max_blocked_streams: u16,
    789    max_data: u64,
    790    max_stream_data: u64,
    791    version_negotiation: bool,
    792    webtransport: bool,
    793    qlog_dir: &nsACString,
    794    provider_flags: u32,
    795    idle_timeout: u32,
    796    socket: i64,
    797    pmtud_enabled: bool,
    798    result: &mut *const NeqoHttp3Conn,
    799 ) -> nsresult {
    800    *result = ptr::null_mut();
    801 
    802    match NeqoHttp3Conn::new(
    803        origin,
    804        alpn,
    805        local_addr,
    806        remote_addr,
    807        max_table_size,
    808        max_blocked_streams,
    809        max_data,
    810        max_stream_data,
    811        version_negotiation,
    812        webtransport,
    813        qlog_dir,
    814        provider_flags,
    815        idle_timeout,
    816        pmtud_enabled,
    817        Some(socket),
    818    ) {
    819        Ok(http3_conn) => {
    820            http3_conn.forget(result);
    821            NS_OK
    822        }
    823        Err(e) => e,
    824    }
    825 }
    826 
    827 // Allocate a new NeqoHttp3Conn object using NSPR for IO.
    828 #[no_mangle]
    829 pub extern "C" fn neqo_http3conn_new_use_nspr_for_io(
    830    origin: &nsACString,
    831    alpn: &nsACString,
    832    local_addr: *const NetAddr,
    833    remote_addr: *const NetAddr,
    834    max_table_size: u64,
    835    max_blocked_streams: u16,
    836    max_data: u64,
    837    max_stream_data: u64,
    838    version_negotiation: bool,
    839    webtransport: bool,
    840    qlog_dir: &nsACString,
    841    provider_flags: u32,
    842    idle_timeout: u32,
    843    result: &mut *const NeqoHttp3Conn,
    844 ) -> nsresult {
    845    *result = ptr::null_mut();
    846 
    847    match NeqoHttp3Conn::new(
    848        origin,
    849        alpn,
    850        local_addr,
    851        remote_addr,
    852        max_table_size,
    853        max_blocked_streams,
    854        max_data,
    855        max_stream_data,
    856        version_negotiation,
    857        webtransport,
    858        qlog_dir,
    859        provider_flags,
    860        idle_timeout,
    861        false,
    862        None,
    863    ) {
    864        Ok(http3_conn) => {
    865            http3_conn.forget(result);
    866            NS_OK
    867        }
    868        Err(e) => e,
    869    }
    870 }
    871 
    872 /// Process a packet.
    873 /// packet holds packet data.
    874 ///
    875 /// # Safety
    876 ///
    877 /// Use of raw (i.e. unsafe) pointers as arguments.
    878 #[no_mangle]
    879 pub unsafe extern "C" fn neqo_http3conn_process_input_use_nspr_for_io(
    880    conn: &mut NeqoHttp3Conn,
    881    remote_addr: *const NetAddr,
    882    packet: *const ThinVec<u8>,
    883 ) -> nsresult {
    884    assert!(conn.socket.is_none(), "NSPR IO path");
    885 
    886    let remote = match netaddr_to_socket_addr(remote_addr) {
    887        Ok(addr) => addr,
    888        Err(result) => return result,
    889    };
    890    let d = Datagram::new(
    891        remote,
    892        conn.local_addr,
    893        Tos::default(),
    894        (*packet).as_slice(),
    895    );
    896    conn.conn.process_input(d, Instant::now());
    897    NS_OK
    898 }
    899 
    900 #[repr(C)]
    901 pub struct ProcessInputResult {
    902    pub result: nsresult,
    903    pub bytes_read: u32,
    904 }
    905 
    906 /// Process input, reading incoming datagrams from the socket and passing them
    907 /// to the Neqo state machine.
    908 ///
    909 /// # Safety
    910 ///
    911 /// Marked as unsafe given exposition via FFI i.e. `extern "C"`.
    912 #[no_mangle]
    913 pub unsafe extern "C" fn neqo_http3conn_process_input(
    914    conn: &mut NeqoHttp3Conn,
    915 ) -> ProcessInputResult {
    916    let mut bytes_read = 0;
    917 
    918    RECV_BUF.with_borrow_mut(|recv_buf| {
    919        loop {
    920            let dgrams = match conn
    921                .socket
    922                .as_mut()
    923                .expect("non NSPR IO")
    924                .recv(conn.local_addr, recv_buf)
    925            {
    926                Ok(dgrams) => dgrams,
    927                Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
    928                    conn.increment_would_block_rx();
    929                    break;
    930                }
    931                Err(e) => {
    932                    qwarn!("failed to receive datagrams: {}", e);
    933                    return ProcessInputResult {
    934                        result: into_nsresult(&e),
    935                        bytes_read: 0,
    936                    };
    937                }
    938            };
    939 
    940            // Attach metric instrumentation to `dgrams` iterator.
    941            let mut sum = 0;
    942            let mut segment_count = 0;
    943            let datagram_segment_size_received = &mut conn.datagram_segment_size_received;
    944            let dgrams = dgrams.inspect(|d| {
    945                datagram_segment_size_received.accumulate(d.len() as u64);
    946                sum += d.len();
    947                segment_count += 1;
    948            });
    949 
    950            // Override `dgrams` ECN marks according to prefs.
    951            let ecn_enabled = static_prefs::pref!("network.http.http3.ecn_report");
    952            let dgrams = dgrams.map(|mut d| {
    953                if !ecn_enabled {
    954                    d.set_tos(Tos::default());
    955                }
    956                d
    957            });
    958 
    959            conn.conn.process_multiple_input(dgrams, Instant::now());
    960 
    961            conn.datagram_size_received.accumulate(sum as u64);
    962            conn.datagram_segments_received.accumulate(segment_count);
    963            bytes_read += sum;
    964        }
    965 
    966        ProcessInputResult {
    967            result: NS_OK,
    968            bytes_read: bytes_read.try_into().unwrap_or(u32::MAX),
    969        }
    970    })
    971 }
    972 
    973 #[no_mangle]
    974 pub extern "C" fn neqo_http3conn_process_output_and_send_use_nspr_for_io(
    975    conn: &mut NeqoHttp3Conn,
    976    context: *mut c_void,
    977    send_func: SendFunc,
    978    set_timer_func: SetTimerFunc,
    979 ) -> nsresult {
    980    assert!(conn.socket.is_none(), "NSPR IO path");
    981 
    982    loop {
    983        match conn.conn.process_output(Instant::now()) {
    984            Output::Datagram(dg) => {
    985                let Ok(len) = u32::try_from(dg.len()) else {
    986                    return NS_ERROR_UNEXPECTED;
    987                };
    988                let rv = match dg.destination().ip() {
    989                    IpAddr::V4(v4) => send_func(
    990                        context,
    991                        AF_INET_U16,
    992                        v4.octets().as_ptr(),
    993                        dg.destination().port(),
    994                        dg.as_ptr(),
    995                        len,
    996                    ),
    997                    IpAddr::V6(v6) => send_func(
    998                        context,
    999                        AF_INET6_U16,
   1000                        v6.octets().as_ptr(),
   1001                        dg.destination().port(),
   1002                        dg.as_ptr(),
   1003                        len,
   1004                    ),
   1005                };
   1006                if rv != NS_OK {
   1007                    return rv;
   1008                }
   1009            }
   1010            Output::Callback(to) => {
   1011                let timeout = if to.is_zero() {
   1012                    Duration::from_millis(1)
   1013                } else {
   1014                    to
   1015                };
   1016                let Ok(timeout) = u64::try_from(timeout.as_millis()) else {
   1017                    return NS_ERROR_UNEXPECTED;
   1018                };
   1019                set_timer_func(context, timeout);
   1020                break;
   1021            }
   1022            Output::None => {
   1023                set_timer_func(context, u64::MAX);
   1024                break;
   1025            }
   1026        }
   1027    }
   1028    NS_OK
   1029 }
   1030 
   1031 #[repr(C)]
   1032 pub struct ProcessOutputAndSendResult {
   1033    pub result: nsresult,
   1034    pub bytes_written: u32,
   1035 }
   1036 
   1037 /// Process output, retrieving outgoing datagrams from the Neqo state machine
   1038 /// and writing them to the socket.
   1039 #[no_mangle]
   1040 pub extern "C" fn neqo_http3conn_process_output_and_send(
   1041    conn: &mut NeqoHttp3Conn,
   1042    context: *mut c_void,
   1043    set_timer_func: SetTimerFunc,
   1044 ) -> ProcessOutputAndSendResult {
   1045    let mut bytes_written: usize = 0;
   1046    loop {
   1047        let Ok(max_gso_segments) = min(
   1048            static_prefs::pref!("network.http.http3.max_gso_segments")
   1049                .try_into()
   1050                .expect("u32 fit usize"),
   1051            conn.socket
   1052                .as_mut()
   1053                .expect("non NSPR IO")
   1054                .max_gso_segments(),
   1055        )
   1056        .try_into() else {
   1057            qerror!("Socket return GSO size of 0");
   1058            return ProcessOutputAndSendResult {
   1059                result: NS_ERROR_UNEXPECTED,
   1060                bytes_written: 0,
   1061            };
   1062        };
   1063 
   1064        let output = conn
   1065            .buffered_outbound_datagram
   1066            .take()
   1067            .map(OutputBatch::DatagramBatch)
   1068            .unwrap_or_else(|| {
   1069                conn.conn
   1070                    .process_multiple_output(Instant::now(), max_gso_segments)
   1071            });
   1072        match output {
   1073            OutputBatch::DatagramBatch(mut dg) => {
   1074                if !static_prefs::pref!("network.http.http3.ecn_mark") {
   1075                    dg.set_tos(Tos::default());
   1076                }
   1077 
   1078                if static_prefs::pref!("network.http.http3.block_loopback_ipv6_addr")
   1079                    && matches!(dg.destination(), SocketAddr::V6(addr) if addr.ip().is_loopback())
   1080                {
   1081                    qdebug!("network.http.http3.block_loopback_ipv6_addr is set, returning NS_ERROR_CONNECTION_REFUSED for localhost IPv6");
   1082                    return ProcessOutputAndSendResult {
   1083                        result: NS_ERROR_CONNECTION_REFUSED,
   1084                        bytes_written: 0,
   1085                    };
   1086                }
   1087 
   1088                match conn.socket.as_mut().expect("non NSPR IO").send(&dg) {
   1089                    Ok(()) => {}
   1090                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
   1091                        conn.increment_would_block_tx();
   1092                        if static_prefs::pref!("network.http.http3.pr_poll_write") {
   1093                            qdebug!("Buffer outbound datagram to be sent once UDP socket has write-availability.");
   1094                            conn.buffered_outbound_datagram = Some(dg);
   1095                            return ProcessOutputAndSendResult {
   1096                                // Propagate WouldBlock error, thus indicating that
   1097                                // the UDP socket should be polled for
   1098                                // write-availability.
   1099                                result: NS_BASE_STREAM_WOULD_BLOCK,
   1100                                bytes_written: bytes_written.try_into().unwrap_or(u32::MAX),
   1101                            };
   1102                        } else {
   1103                            qwarn!("dropping datagram as socket would block");
   1104                            break;
   1105                        }
   1106                    }
   1107                    Err(e) if e.raw_os_error() == Some(libc::EIO) && dg.num_datagrams() > 1 => {
   1108                        // See following resources for details:
   1109                        // - <https://github.com/quinn-rs/quinn/blob/93b6d01605147b9763ee1b1b381a6feb9fcd454e/quinn-udp/src/unix.rs#L345-L349>
   1110                        // - <https://bugzilla.mozilla.org/show_bug.cgi?id=1989895>
   1111                        //
   1112                        // Ideally one would retry at the quinn-udp layer, see <https://github.com/quinn-rs/quinn/issues/2399>.
   1113                        qdebug!("Failed to send datagram batch size {} with error {e}. Missing GSO support? Socket will set max_gso_segments to 1. QUIC layer will retry.", dg.num_datagrams());
   1114                    }
   1115                    Err(e) => {
   1116                        qwarn!("failed to send datagram: {}", e);
   1117                        return ProcessOutputAndSendResult {
   1118                            result: into_nsresult(&e),
   1119                            bytes_written: 0,
   1120                        };
   1121                    }
   1122                }
   1123                bytes_written += dg.data().len();
   1124 
   1125                // Glean metrics
   1126                conn.datagram_size_sent.accumulate(dg.data().len() as u64);
   1127                conn.datagram_segments_sent
   1128                    .accumulate(dg.num_datagrams() as u64);
   1129                for _ in 0..(dg.data().len() / dg.datagram_size()) {
   1130                    conn.datagram_segment_size_sent
   1131                        .accumulate(dg.datagram_size().get() as u64);
   1132                }
   1133                conn.datagram_segment_size_sent.accumulate(
   1134                    dg.data()
   1135                        .len()
   1136                        .checked_rem(dg.datagram_size().get())
   1137                        .expect("datagram_size is a NonZeroUsize") as u64,
   1138                );
   1139            }
   1140            OutputBatch::Callback(to) => {
   1141                let timeout = if to.is_zero() {
   1142                    Duration::from_millis(1)
   1143                } else {
   1144                    to
   1145                };
   1146                let Ok(timeout) = u64::try_from(timeout.as_millis()) else {
   1147                    return ProcessOutputAndSendResult {
   1148                        result: NS_ERROR_UNEXPECTED,
   1149                        bytes_written: 0,
   1150                    };
   1151                };
   1152                set_timer_func(context, timeout);
   1153                break;
   1154            }
   1155            OutputBatch::None => {
   1156                set_timer_func(context, u64::MAX);
   1157                break;
   1158            }
   1159        }
   1160    }
   1161 
   1162    ProcessOutputAndSendResult {
   1163        result: NS_OK,
   1164        bytes_written: bytes_written.try_into().unwrap_or(u32::MAX),
   1165    }
   1166 }
   1167 
   1168 #[no_mangle]
   1169 pub extern "C" fn neqo_http3conn_close(conn: &mut NeqoHttp3Conn, error: u64) {
   1170    conn.conn.close(Instant::now(), error, "");
   1171 }
   1172 
   1173 fn is_excluded_header(name: &str) -> bool {
   1174    matches!(
   1175        name,
   1176        "connection"
   1177            | "host"
   1178            | "keep-alive"
   1179            | "proxy-connection"
   1180            | "te"
   1181            | "transfer-encoding"
   1182            | "upgrade"
   1183            | "sec-websocket-key"
   1184    )
   1185 }
   1186 
   1187 fn parse_headers(headers: &nsACString) -> Result<Vec<Header>, nsresult> {
   1188    let mut hdrs = Vec::new();
   1189    // this is only used for headers built by Firefox.
   1190    // Firefox supplies all headers already prepared for sending over http1.
   1191    // They need to be split into (name, value) pairs where name is a String
   1192    // and value is a Vec<u8>.
   1193 
   1194    let headers_bytes: &[u8] = headers;
   1195 
   1196    // Split on either \r or \n. When splitting "\r\n" sequences, this produces
   1197    // an empty element between them which is filtered out by the is_empty check.
   1198    // This also handles malformed inputs with bare \r or \n.
   1199    for elem in headers_bytes.split(|&b| b == b'\r' || b == b'\n').skip(1) {
   1200        if elem.is_empty() {
   1201            continue;
   1202        }
   1203        if elem.starts_with(b":") {
   1204            // colon headers are for http/2 and 3 and this is http/1
   1205            // input, so that is probably a smuggling attack of some
   1206            // kind.
   1207            continue;
   1208        }
   1209 
   1210        let colon_pos = match elem.iter().position(|&b| b == b':') {
   1211            Some(pos) => pos,
   1212            None => continue, // No colon, skip this line
   1213        };
   1214 
   1215        let name_bytes = &elem[..colon_pos];
   1216        // Safe: if colon is at the end, this yields an empty slice
   1217        let value_bytes = &elem[colon_pos + 1..];
   1218 
   1219        // Header names must be valid UTF-8
   1220        let name = match str::from_utf8(name_bytes) {
   1221            Ok(n) => n.trim().to_lowercase(),
   1222            Err(_) => return Err(NS_ERROR_DOM_INVALID_HEADER_NAME),
   1223        };
   1224 
   1225        if is_excluded_header(&name) {
   1226            continue;
   1227        }
   1228 
   1229        // Trim leading and trailing optional whitespace (OWS) from value.
   1230        // Per RFC 9110, OWS is defined as *( SP / HTAB ), i.e., space and tab only.
   1231        let value = value_bytes
   1232            .iter()
   1233            .position(|&b| b != b' ' && b != b'\t')
   1234            .map_or(&value_bytes[0..0], |start| {
   1235                let end = value_bytes
   1236                    .iter()
   1237                    .rposition(|&b| b != b' ' && b != b'\t')
   1238                    .map_or(value_bytes.len(), |pos| pos + 1);
   1239                &value_bytes[start..end]
   1240            })
   1241            .to_vec();
   1242 
   1243        hdrs.push(Header::new(name, value));
   1244    }
   1245    Ok(hdrs)
   1246 }
   1247 
   1248 #[no_mangle]
   1249 pub extern "C" fn neqo_http3conn_fetch(
   1250    conn: &mut NeqoHttp3Conn,
   1251    method: &nsACString,
   1252    scheme: &nsACString,
   1253    host: &nsACString,
   1254    path: &nsACString,
   1255    headers: &nsACString,
   1256    stream_id: &mut u64,
   1257    urgency: u8,
   1258    incremental: bool,
   1259 ) -> nsresult {
   1260    let hdrs = match parse_headers(headers) {
   1261        Err(e) => {
   1262            return e;
   1263        }
   1264        Ok(h) => h,
   1265    };
   1266    let Ok(method_tmp) = str::from_utf8(method) else {
   1267        return NS_ERROR_INVALID_ARG;
   1268    };
   1269    let Ok(scheme_tmp) = str::from_utf8(scheme) else {
   1270        return NS_ERROR_INVALID_ARG;
   1271    };
   1272    let Ok(host_tmp) = str::from_utf8(host) else {
   1273        return NS_ERROR_INVALID_ARG;
   1274    };
   1275    let Ok(path_tmp) = str::from_utf8(path) else {
   1276        return NS_ERROR_INVALID_ARG;
   1277    };
   1278    if urgency >= 8 {
   1279        return NS_ERROR_INVALID_ARG;
   1280    }
   1281    let priority = Priority::new(urgency, incremental);
   1282    match conn.conn.fetch(
   1283        Instant::now(),
   1284        method_tmp,
   1285        (scheme_tmp, host_tmp, path_tmp),
   1286        &hdrs,
   1287        priority,
   1288    ) {
   1289        Ok(id) => {
   1290            *stream_id = id.as_u64();
   1291            NS_OK
   1292        }
   1293        Err(Http3Error::StreamLimit) => NS_BASE_STREAM_WOULD_BLOCK,
   1294        Err(_) => NS_ERROR_UNEXPECTED,
   1295    }
   1296 }
   1297 
   1298 #[no_mangle]
   1299 pub extern "C" fn neqo_http3conn_connect(
   1300    conn: &mut NeqoHttp3Conn,
   1301    host: &nsACString,
   1302    headers: &nsACString,
   1303    stream_id: &mut u64,
   1304    urgency: u8,
   1305    incremental: bool,
   1306 ) -> nsresult {
   1307    let hdrs = match parse_headers(headers) {
   1308        Err(e) => {
   1309            return e;
   1310        }
   1311        Ok(h) => h,
   1312    };
   1313    let Ok(host_tmp) = str::from_utf8(host) else {
   1314        return NS_ERROR_INVALID_ARG;
   1315    };
   1316    if urgency >= 8 {
   1317        return NS_ERROR_INVALID_ARG;
   1318    }
   1319    let priority = Priority::new(urgency, incremental);
   1320    match conn.conn.connect(Instant::now(), host_tmp, &hdrs, priority) {
   1321        Ok(id) => {
   1322            *stream_id = id.as_u64();
   1323            NS_OK
   1324        }
   1325        Err(Http3Error::StreamLimit) => NS_BASE_STREAM_WOULD_BLOCK,
   1326        Err(_) => NS_ERROR_UNEXPECTED,
   1327    }
   1328 }
   1329 
   1330 #[no_mangle]
   1331 pub extern "C" fn neqo_http3conn_priority_update(
   1332    conn: &mut NeqoHttp3Conn,
   1333    stream_id: u64,
   1334    urgency: u8,
   1335    incremental: bool,
   1336 ) -> nsresult {
   1337    if urgency >= 8 {
   1338        return NS_ERROR_INVALID_ARG;
   1339    }
   1340    let priority = Priority::new(urgency, incremental);
   1341    match conn
   1342        .conn
   1343        .priority_update(StreamId::from(stream_id), priority)
   1344    {
   1345        Ok(_) => NS_OK,
   1346        Err(_) => NS_ERROR_UNEXPECTED,
   1347    }
   1348 }
   1349 
   1350 /// # Safety
   1351 ///
   1352 /// Use of raw (i.e. unsafe) pointers as arguments.
   1353 #[no_mangle]
   1354 pub unsafe extern "C" fn neqo_htttp3conn_send_request_body(
   1355    conn: &mut NeqoHttp3Conn,
   1356    stream_id: u64,
   1357    buf: *const u8,
   1358    len: u32,
   1359    read: &mut u32,
   1360 ) -> nsresult {
   1361    let array = slice::from_raw_parts(buf, len as usize);
   1362    conn.conn
   1363        .send_data(StreamId::from(stream_id), array, Instant::now())
   1364        .map_or(NS_ERROR_UNEXPECTED, |amount| {
   1365            let Ok(amount) = u32::try_from(amount) else {
   1366                return NS_ERROR_UNEXPECTED;
   1367            };
   1368            *read = amount;
   1369            if amount == 0 {
   1370                NS_BASE_STREAM_WOULD_BLOCK
   1371            } else {
   1372                NS_OK
   1373            }
   1374        })
   1375 }
   1376 
   1377 const fn crypto_error_code(err: &neqo_crypto::Error) -> u64 {
   1378    match err {
   1379        // Removed in https://github.com/mozilla/neqo/pull/2912. Don't reuse
   1380        // code point.
   1381        //
   1382        // neqo_crypto::Error::Aead => 1,
   1383        neqo_crypto::Error::CertificateLoading => 2,
   1384        neqo_crypto::Error::CreateSslSocket => 3,
   1385        neqo_crypto::Error::Hkdf => 4,
   1386        neqo_crypto::Error::Internal => 5,
   1387        neqo_crypto::Error::IntegerOverflow => 6,
   1388        neqo_crypto::Error::InvalidEpoch => 7,
   1389        neqo_crypto::Error::MixedHandshakeMethod => 8,
   1390        neqo_crypto::Error::NoDataAvailable => 9,
   1391        neqo_crypto::Error::Nss { .. } => 10,
   1392        // Removed in https://github.com/mozilla/neqo/pull/2912. Don't reuse
   1393        // code point.
   1394        //
   1395        // neqo_crypto::Error::Overrun => 11,
   1396        neqo_crypto::Error::SelfEncrypt => 12,
   1397        neqo_crypto::Error::TimeTravel => 13,
   1398        neqo_crypto::Error::UnsupportedCipher => 14,
   1399        neqo_crypto::Error::UnsupportedVersion => 15,
   1400        neqo_crypto::Error::String => 16,
   1401        neqo_crypto::Error::EchRetry(_) => 17,
   1402        neqo_crypto::Error::CipherInit => 18,
   1403        neqo_crypto::Error::CertificateDecoding => 19,
   1404        neqo_crypto::Error::CertificateEncoding => 20,
   1405        neqo_crypto::Error::InvalidCertificateCompressionID => 21,
   1406        neqo_crypto::Error::InvalidAlpn => 22,
   1407    }
   1408 }
   1409 
   1410 // This is only used for telemetry. Therefore we only return error code
   1411 // numbers and do not label them. Recording telemetry is easier with a
   1412 // number.
   1413 #[repr(C)]
   1414 pub enum CloseError {
   1415    TransportInternalError,
   1416    TransportInternalErrorOther(u16),
   1417    TransportError(u64),
   1418    CryptoError(u64),
   1419    CryptoAlert(u8),
   1420    PeerAppError(u64),
   1421    PeerError(u64),
   1422    AppError(u64),
   1423    EchRetry,
   1424 }
   1425 
   1426 impl From<TransportError> for CloseError {
   1427    fn from(error: TransportError) -> Self {
   1428        #[expect(clippy::match_same_arms, reason = "It's cleaner this way.")]
   1429        match error {
   1430            TransportError::Internal => Self::TransportInternalError,
   1431            TransportError::Crypto(neqo_crypto::Error::EchRetry(_)) => Self::EchRetry,
   1432            TransportError::Crypto(c) => Self::CryptoError(crypto_error_code(&c)),
   1433            TransportError::CryptoAlert(c) => Self::CryptoAlert(c),
   1434            TransportError::PeerApplication(c) => Self::PeerAppError(c),
   1435            TransportError::Peer(c) => Self::PeerError(c),
   1436            TransportError::None
   1437            | TransportError::IdleTimeout
   1438            | TransportError::ConnectionRefused
   1439            | TransportError::FlowControl
   1440            | TransportError::StreamLimit
   1441            | TransportError::StreamState
   1442            | TransportError::FinalSize
   1443            | TransportError::FrameEncoding
   1444            | TransportError::TransportParameter
   1445            | TransportError::ProtocolViolation
   1446            | TransportError::InvalidToken
   1447            | TransportError::KeysExhausted
   1448            | TransportError::Application
   1449            | TransportError::NoAvailablePath
   1450            | TransportError::CryptoBufferExceeded => Self::TransportError(error.code()),
   1451            TransportError::EchRetry(_) => Self::EchRetry,
   1452            TransportError::AckedUnsentPacket => Self::TransportInternalErrorOther(0),
   1453            TransportError::ConnectionIdLimitExceeded => Self::TransportInternalErrorOther(1),
   1454            TransportError::ConnectionIdsExhausted => Self::TransportInternalErrorOther(2),
   1455            TransportError::ConnectionState => Self::TransportInternalErrorOther(3),
   1456            TransportError::Decrypt => Self::TransportInternalErrorOther(5),
   1457            TransportError::IntegerOverflow => Self::TransportInternalErrorOther(7),
   1458            TransportError::InvalidInput => Self::TransportInternalErrorOther(8),
   1459            TransportError::InvalidMigration => Self::TransportInternalErrorOther(9),
   1460            TransportError::InvalidPacket => Self::TransportInternalErrorOther(10),
   1461            TransportError::InvalidResumptionToken => Self::TransportInternalErrorOther(11),
   1462            TransportError::InvalidRetry => Self::TransportInternalErrorOther(12),
   1463            TransportError::InvalidStreamId => Self::TransportInternalErrorOther(13),
   1464            TransportError::KeysDiscarded(_) => Self::TransportInternalErrorOther(14),
   1465            TransportError::KeysPending(_) => Self::TransportInternalErrorOther(15),
   1466            TransportError::KeyUpdateBlocked => Self::TransportInternalErrorOther(16),
   1467            TransportError::NoMoreData => Self::TransportInternalErrorOther(17),
   1468            TransportError::NotConnected => Self::TransportInternalErrorOther(18),
   1469            TransportError::PacketNumberOverlap => Self::TransportInternalErrorOther(19),
   1470            TransportError::StatelessReset => Self::TransportInternalErrorOther(20),
   1471            TransportError::TooMuchData => Self::TransportInternalErrorOther(21),
   1472            TransportError::UnexpectedMessage => Self::TransportInternalErrorOther(22),
   1473            TransportError::UnknownConnectionId => Self::TransportInternalErrorOther(23),
   1474            TransportError::UnknownFrameType => Self::TransportInternalErrorOther(24),
   1475            TransportError::VersionNegotiation => Self::TransportInternalErrorOther(25),
   1476            TransportError::WrongRole => Self::TransportInternalErrorOther(26),
   1477            TransportError::Qlog => Self::TransportInternalErrorOther(27),
   1478            TransportError::NotAvailable => Self::TransportInternalErrorOther(28),
   1479            TransportError::DisabledVersion => Self::TransportInternalErrorOther(29),
   1480            TransportError::UnknownTransportParameter => Self::TransportInternalErrorOther(30),
   1481        }
   1482    }
   1483 }
   1484 
   1485 // Keep in sync with `netwerk/metrics.yaml` `http_3_connection_close_reason` metric labels.
   1486 #[cfg(not(target_os = "android"))]
   1487 const fn transport_error_to_glean_label(error: &TransportError) -> &'static str {
   1488    match error {
   1489        TransportError::None => "NoError",
   1490        TransportError::Internal => "InternalError",
   1491        TransportError::ConnectionRefused => "ConnectionRefused",
   1492        TransportError::FlowControl => "FlowControlError",
   1493        TransportError::StreamLimit => "StreamLimitError",
   1494        TransportError::StreamState => "StreamStateError",
   1495        TransportError::FinalSize => "FinalSizeError",
   1496        TransportError::FrameEncoding => "FrameEncodingError",
   1497        TransportError::TransportParameter => "TransportParameterError",
   1498        TransportError::ProtocolViolation => "ProtocolViolation",
   1499        TransportError::InvalidToken => "InvalidToken",
   1500        TransportError::Application => "ApplicationError",
   1501        TransportError::CryptoBufferExceeded => "CryptoBufferExceeded",
   1502        TransportError::Crypto(_) => "CryptoError",
   1503        TransportError::Qlog => "QlogError",
   1504        TransportError::CryptoAlert(_) => "CryptoAlert",
   1505        TransportError::EchRetry(_) => "EchRetry",
   1506        TransportError::AckedUnsentPacket => "AckedUnsentPacket",
   1507        TransportError::ConnectionIdLimitExceeded => "ConnectionIdLimitExceeded",
   1508        TransportError::ConnectionIdsExhausted => "ConnectionIdsExhausted",
   1509        TransportError::ConnectionState => "ConnectionState",
   1510        TransportError::Decrypt => "DecryptError",
   1511        TransportError::DisabledVersion => "DisabledVersion",
   1512        TransportError::IdleTimeout => "IdleTimeout",
   1513        TransportError::IntegerOverflow => "IntegerOverflow",
   1514        TransportError::InvalidInput => "InvalidInput",
   1515        TransportError::InvalidMigration => "InvalidMigration",
   1516        TransportError::InvalidPacket => "InvalidPacket",
   1517        TransportError::InvalidResumptionToken => "InvalidResumptionToken",
   1518        TransportError::InvalidRetry => "InvalidRetry",
   1519        TransportError::InvalidStreamId => "InvalidStreamId",
   1520        TransportError::KeysDiscarded(_) => "KeysDiscarded",
   1521        TransportError::KeysExhausted => "KeysExhausted",
   1522        TransportError::KeysPending(_) => "KeysPending",
   1523        TransportError::KeyUpdateBlocked => "KeyUpdateBlocked",
   1524        TransportError::NoAvailablePath => "NoAvailablePath",
   1525        TransportError::NoMoreData => "NoMoreData",
   1526        TransportError::NotAvailable => "NotAvailable",
   1527        TransportError::NotConnected => "NotConnected",
   1528        TransportError::PacketNumberOverlap => "PacketNumberOverlap",
   1529        TransportError::PeerApplication(_) => "PeerApplicationError",
   1530        TransportError::Peer(_) => "PeerError",
   1531        TransportError::StatelessReset => "StatelessReset",
   1532        TransportError::TooMuchData => "TooMuchData",
   1533        TransportError::UnexpectedMessage => "UnexpectedMessage",
   1534        TransportError::UnknownConnectionId => "UnknownConnectionId",
   1535        TransportError::UnknownFrameType => "UnknownFrameType",
   1536        TransportError::VersionNegotiation => "VersionNegotiation",
   1537        TransportError::WrongRole => "WrongRole",
   1538        TransportError::UnknownTransportParameter => "UnknownTransportParameter",
   1539    }
   1540 }
   1541 
   1542 impl From<neqo_transport::CloseReason> for CloseError {
   1543    fn from(error: neqo_transport::CloseReason) -> Self {
   1544        match error {
   1545            neqo_transport::CloseReason::Transport(c) => c.into(),
   1546            neqo_transport::CloseReason::Application(c) => Self::AppError(c),
   1547        }
   1548    }
   1549 }
   1550 
   1551 // Reset a stream with streamId.
   1552 #[no_mangle]
   1553 pub extern "C" fn neqo_http3conn_cancel_fetch(
   1554    conn: &mut NeqoHttp3Conn,
   1555    stream_id: u64,
   1556    error: u64,
   1557 ) -> nsresult {
   1558    match conn.conn.cancel_fetch(StreamId::from(stream_id), error) {
   1559        Ok(()) => NS_OK,
   1560        Err(_) => NS_ERROR_INVALID_ARG,
   1561    }
   1562 }
   1563 
   1564 // Reset a stream with streamId.
   1565 #[no_mangle]
   1566 pub extern "C" fn neqo_http3conn_reset_stream(
   1567    conn: &mut NeqoHttp3Conn,
   1568    stream_id: u64,
   1569    error: u64,
   1570 ) -> nsresult {
   1571    match conn
   1572        .conn
   1573        .stream_reset_send(StreamId::from(stream_id), error)
   1574    {
   1575        Ok(()) => NS_OK,
   1576        Err(_) => NS_ERROR_INVALID_ARG,
   1577    }
   1578 }
   1579 
   1580 #[no_mangle]
   1581 pub extern "C" fn neqo_http3conn_stream_stop_sending(
   1582    conn: &mut NeqoHttp3Conn,
   1583    stream_id: u64,
   1584    error: u64,
   1585 ) -> nsresult {
   1586    match conn
   1587        .conn
   1588        .stream_stop_sending(StreamId::from(stream_id), error)
   1589    {
   1590        Ok(()) => NS_OK,
   1591        Err(_) => NS_ERROR_INVALID_ARG,
   1592    }
   1593 }
   1594 
   1595 // Close sending side of a stream with stream_id
   1596 #[no_mangle]
   1597 pub extern "C" fn neqo_http3conn_close_stream(
   1598    conn: &mut NeqoHttp3Conn,
   1599    stream_id: u64,
   1600 ) -> nsresult {
   1601    match conn
   1602        .conn
   1603        .stream_close_send(StreamId::from(stream_id), Instant::now())
   1604    {
   1605        Ok(()) => NS_OK,
   1606        Err(_) => NS_ERROR_INVALID_ARG,
   1607    }
   1608 }
   1609 
   1610 // WebTransport streams can be unidirectional and bidirectional.
   1611 // It is mapped to and from neqo's StreamType enum.
   1612 #[repr(C)]
   1613 pub enum WebTransportStreamType {
   1614    BiDi,
   1615    UniDi,
   1616 }
   1617 
   1618 impl From<StreamType> for WebTransportStreamType {
   1619    fn from(t: StreamType) -> Self {
   1620        match t {
   1621            StreamType::BiDi => Self::BiDi,
   1622            StreamType::UniDi => Self::UniDi,
   1623        }
   1624    }
   1625 }
   1626 
   1627 impl From<WebTransportStreamType> for StreamType {
   1628    fn from(t: WebTransportStreamType) -> Self {
   1629        match t {
   1630            WebTransportStreamType::BiDi => Self::BiDi,
   1631            WebTransportStreamType::UniDi => Self::UniDi,
   1632        }
   1633    }
   1634 }
   1635 
   1636 #[repr(C)]
   1637 pub enum SessionCloseReasonExternal {
   1638    Error(u64),
   1639    Status(u16),
   1640    Clean(u32),
   1641 }
   1642 
   1643 impl SessionCloseReasonExternal {
   1644    fn new(reason: session::CloseReason, data: &mut ThinVec<u8>) -> Self {
   1645        match reason {
   1646            session::CloseReason::Error(e) => Self::Error(e),
   1647            session::CloseReason::Status(s) => Self::Status(s),
   1648            session::CloseReason::Clean { error, message } => {
   1649                data.extend_from_slice(message.as_ref());
   1650                Self::Clean(error)
   1651            }
   1652        }
   1653    }
   1654 }
   1655 
   1656 #[repr(C)]
   1657 pub enum WebTransportEventExternal {
   1658    Negotiated(bool),
   1659    Session(u64),
   1660    SessionClosed {
   1661        stream_id: u64,
   1662        reason: SessionCloseReasonExternal,
   1663    },
   1664    NewStream {
   1665        stream_id: u64,
   1666        stream_type: WebTransportStreamType,
   1667        session_id: u64,
   1668    },
   1669    Datagram {
   1670        session_id: u64,
   1671    },
   1672 }
   1673 #[repr(C)]
   1674 pub enum ConnectUdpEventExternal {
   1675    Negotiated(bool),
   1676    Session(u64),
   1677    SessionClosed {
   1678        stream_id: u64,
   1679        reason: SessionCloseReasonExternal,
   1680    },
   1681    Datagram {
   1682        session_id: u64,
   1683    },
   1684 }
   1685 
   1686 impl WebTransportEventExternal {
   1687    fn new(event: WebTransportEvent, data: &mut ThinVec<u8>) -> Self {
   1688        match event {
   1689            WebTransportEvent::Negotiated(n) => Self::Negotiated(n),
   1690            WebTransportEvent::NewSession {
   1691                stream_id, status, ..
   1692            } => {
   1693                data.extend_from_slice(b"HTTP/3 ");
   1694                data.extend_from_slice(status.to_string().as_bytes());
   1695                data.extend_from_slice(b"\r\n\r\n");
   1696                Self::Session(stream_id.as_u64())
   1697            }
   1698            WebTransportEvent::SessionClosed {
   1699                stream_id, reason, ..
   1700            } => match reason {
   1701                session::CloseReason::Status(status) => {
   1702                    data.extend_from_slice(b"HTTP/3 ");
   1703                    data.extend_from_slice(status.to_string().as_bytes());
   1704                    data.extend_from_slice(b"\r\n\r\n");
   1705                    Self::Session(stream_id.as_u64())
   1706                }
   1707                _ => Self::SessionClosed {
   1708                    stream_id: stream_id.as_u64(),
   1709                    reason: SessionCloseReasonExternal::new(reason, data),
   1710                },
   1711            },
   1712            WebTransportEvent::NewStream {
   1713                stream_id,
   1714                session_id,
   1715            } => Self::NewStream {
   1716                stream_id: stream_id.as_u64(),
   1717                stream_type: stream_id.stream_type().into(),
   1718                session_id: session_id.as_u64(),
   1719            },
   1720            WebTransportEvent::Datagram {
   1721                session_id,
   1722                datagram,
   1723            } => {
   1724                data.extend_from_slice(datagram.as_ref());
   1725                Self::Datagram {
   1726                    session_id: session_id.as_u64(),
   1727                }
   1728            }
   1729        }
   1730    }
   1731 }
   1732 impl ConnectUdpEventExternal {
   1733    fn new(event: ConnectUdpEvent, data: &mut ThinVec<u8>) -> Self {
   1734        match event {
   1735            ConnectUdpEvent::Negotiated(n) => Self::Negotiated(n),
   1736            ConnectUdpEvent::NewSession {
   1737                stream_id, status, ..
   1738            } => {
   1739                data.extend_from_slice(b"HTTP/3 ");
   1740                data.extend_from_slice(status.to_string().as_bytes());
   1741                data.extend_from_slice(b"\r\n\r\n");
   1742                Self::Session(stream_id.as_u64())
   1743            }
   1744            ConnectUdpEvent::SessionClosed {
   1745                stream_id, reason, ..
   1746            } => match reason {
   1747                session::CloseReason::Status(status) => {
   1748                    data.extend_from_slice(b"HTTP/3 ");
   1749                    data.extend_from_slice(status.to_string().as_bytes());
   1750                    data.extend_from_slice(b"\r\n\r\n");
   1751                    Self::Session(stream_id.as_u64())
   1752                }
   1753                _ => Self::SessionClosed {
   1754                    stream_id: stream_id.as_u64(),
   1755                    reason: SessionCloseReasonExternal::new(reason, data),
   1756                },
   1757            },
   1758            ConnectUdpEvent::Datagram {
   1759                session_id,
   1760                datagram,
   1761            } => {
   1762                data.extend_from_slice(datagram.as_ref());
   1763                Self::Datagram {
   1764                    session_id: session_id.as_u64(),
   1765                }
   1766            }
   1767        }
   1768    }
   1769 }
   1770 
   1771 #[repr(C)]
   1772 pub enum Http3Event {
   1773    /// A request stream has space for more data to be sent.
   1774    DataWritable {
   1775        stream_id: u64,
   1776    },
   1777    /// A server has sent a `STOP_SENDING` frame.
   1778    StopSending {
   1779        stream_id: u64,
   1780        error: u64,
   1781    },
   1782    HeaderReady {
   1783        stream_id: u64,
   1784        fin: bool,
   1785        interim: bool,
   1786    },
   1787    /// New bytes available for reading.
   1788    DataReadable {
   1789        stream_id: u64,
   1790    },
   1791    /// Peer reset the stream.
   1792    Reset {
   1793        stream_id: u64,
   1794        error: u64,
   1795        local: bool,
   1796    },
   1797    /// A `PushPromise`
   1798    PushPromise {
   1799        push_id: u64,
   1800        request_stream_id: u64,
   1801    },
   1802    /// A push response headers are ready.
   1803    PushHeaderReady {
   1804        push_id: u64,
   1805        fin: bool,
   1806    },
   1807    /// New bytes are available on a push stream for reading.
   1808    PushDataReadable {
   1809        push_id: u64,
   1810    },
   1811    /// A push has been canceled.
   1812    PushCanceled {
   1813        push_id: u64,
   1814    },
   1815    PushReset {
   1816        push_id: u64,
   1817        error: u64,
   1818    },
   1819    RequestsCreatable,
   1820    AuthenticationNeeded,
   1821    ZeroRttRejected,
   1822    ConnectionConnected,
   1823    GoawayReceived,
   1824    ConnectionClosing {
   1825        error: CloseError,
   1826    },
   1827    ConnectionClosed {
   1828        error: CloseError,
   1829    },
   1830    ResumptionToken {
   1831        expire_in: u64, // microseconds
   1832    },
   1833    EchFallbackAuthenticationNeeded,
   1834    WebTransport(WebTransportEventExternal),
   1835    ConnectUdp(ConnectUdpEventExternal),
   1836    NoEvent,
   1837 }
   1838 
   1839 fn sanitize_header(mut y: Cow<[u8]>) -> Cow<[u8]> {
   1840    for i in 0..y.len() {
   1841        if matches!(y[i], b'\n' | b'\r' | b'\0') {
   1842            y.to_mut()[i] = b' ';
   1843        }
   1844    }
   1845    y
   1846 }
   1847 
   1848 fn convert_h3_to_h1_headers(headers: &[Header], ret_headers: &mut ThinVec<u8>) -> nsresult {
   1849    if headers.iter().filter(|&h| h.name() == ":status").count() != 1 {
   1850        return NS_ERROR_ILLEGAL_VALUE;
   1851    }
   1852 
   1853    let status_val = headers
   1854        .iter()
   1855        .find(|&h| h.name() == ":status")
   1856        .expect("must be one")
   1857        .value();
   1858 
   1859    ret_headers.extend_from_slice(b"HTTP/3 ");
   1860    ret_headers.extend_from_slice(status_val);
   1861    ret_headers.extend_from_slice(b"\r\n");
   1862 
   1863    for hdr in headers.iter().filter(|&h| h.name() != ":status") {
   1864        ret_headers.extend_from_slice(&sanitize_header(Cow::from(hdr.name().as_bytes())));
   1865        ret_headers.extend_from_slice(b": ");
   1866        ret_headers.extend_from_slice(&sanitize_header(Cow::from(hdr.value())));
   1867        ret_headers.extend_from_slice(b"\r\n");
   1868    }
   1869    ret_headers.extend_from_slice(b"\r\n");
   1870    NS_OK
   1871 }
   1872 
   1873 #[expect(clippy::too_many_lines, reason = "Nothing to be done about it.")]
   1874 #[no_mangle]
   1875 pub extern "C" fn neqo_http3conn_event(
   1876    conn: &mut NeqoHttp3Conn,
   1877    ret_event: &mut Http3Event,
   1878    data: &mut ThinVec<u8>,
   1879 ) -> nsresult {
   1880    while let Some(evt) = conn.conn.next_event() {
   1881        let fe = match evt {
   1882            Http3ClientEvent::DataWritable { stream_id } => Http3Event::DataWritable {
   1883                stream_id: stream_id.as_u64(),
   1884            },
   1885            Http3ClientEvent::StopSending { stream_id, error } => Http3Event::StopSending {
   1886                stream_id: stream_id.as_u64(),
   1887                error,
   1888            },
   1889            Http3ClientEvent::HeaderReady {
   1890                stream_id,
   1891                headers,
   1892                fin,
   1893                interim,
   1894            } => {
   1895                let res = convert_h3_to_h1_headers(&headers, data);
   1896                if res != NS_OK {
   1897                    return res;
   1898                }
   1899                Http3Event::HeaderReady {
   1900                    stream_id: stream_id.as_u64(),
   1901                    fin,
   1902                    interim,
   1903                }
   1904            }
   1905            Http3ClientEvent::DataReadable { stream_id } => Http3Event::DataReadable {
   1906                stream_id: stream_id.as_u64(),
   1907            },
   1908            Http3ClientEvent::Reset {
   1909                stream_id,
   1910                error,
   1911                local,
   1912            } => Http3Event::Reset {
   1913                stream_id: stream_id.as_u64(),
   1914                error,
   1915                local,
   1916            },
   1917            Http3ClientEvent::PushPromise {
   1918                push_id,
   1919                request_stream_id,
   1920                headers,
   1921            } => {
   1922                let res = convert_h3_to_h1_headers(&headers, data);
   1923                if res != NS_OK {
   1924                    return res;
   1925                }
   1926                Http3Event::PushPromise {
   1927                    push_id: push_id.into(),
   1928                    request_stream_id: request_stream_id.as_u64(),
   1929                }
   1930            }
   1931            Http3ClientEvent::PushHeaderReady {
   1932                push_id,
   1933                headers,
   1934                fin,
   1935                interim,
   1936            } => {
   1937                if interim {
   1938                    Http3Event::NoEvent
   1939                } else {
   1940                    let res = convert_h3_to_h1_headers(&headers, data);
   1941                    if res != NS_OK {
   1942                        return res;
   1943                    }
   1944                    Http3Event::PushHeaderReady {
   1945                        push_id: push_id.into(),
   1946                        fin,
   1947                    }
   1948                }
   1949            }
   1950            Http3ClientEvent::PushDataReadable { push_id } => Http3Event::PushDataReadable {
   1951                push_id: push_id.into(),
   1952            },
   1953            Http3ClientEvent::PushCanceled { push_id } => Http3Event::PushCanceled {
   1954                push_id: push_id.into(),
   1955            },
   1956            Http3ClientEvent::PushReset { push_id, error } => Http3Event::PushReset {
   1957                push_id: push_id.into(),
   1958                error,
   1959            },
   1960            Http3ClientEvent::RequestsCreatable => Http3Event::RequestsCreatable,
   1961            Http3ClientEvent::AuthenticationNeeded => Http3Event::AuthenticationNeeded,
   1962            Http3ClientEvent::ZeroRttRejected => Http3Event::ZeroRttRejected,
   1963            Http3ClientEvent::ResumptionToken(token) => {
   1964                // expiration_time time is Instant, transform it into microseconds it will
   1965                // be valid for. Necko code will add the value to PR_Now() to get the expiration
   1966                // time in PRTime.
   1967                if token.expiration_time() > Instant::now() {
   1968                    let e = (token.expiration_time() - Instant::now()).as_micros();
   1969                    u64::try_from(e).map_or(Http3Event::NoEvent, |expire_in| {
   1970                        data.extend_from_slice(token.as_ref());
   1971                        Http3Event::ResumptionToken { expire_in }
   1972                    })
   1973                } else {
   1974                    Http3Event::NoEvent
   1975                }
   1976            }
   1977            Http3ClientEvent::GoawayReceived => Http3Event::GoawayReceived,
   1978            Http3ClientEvent::StateChange(state) => match state {
   1979                Http3State::Connected => Http3Event::ConnectionConnected,
   1980                Http3State::Closing(reason) => {
   1981                    if let neqo_transport::CloseReason::Transport(
   1982                        TransportError::Crypto(neqo_crypto::Error::EchRetry(c))
   1983                        | TransportError::EchRetry(c),
   1984                    ) = &reason
   1985                    {
   1986                        data.extend_from_slice(c.as_ref());
   1987                    }
   1988 
   1989                    #[cfg(not(target_os = "android"))]
   1990                    {
   1991                        let glean_label = match &reason {
   1992                            neqo_transport::CloseReason::Application(_) => "Application",
   1993                            neqo_transport::CloseReason::Transport(r) => {
   1994                                transport_error_to_glean_label(r)
   1995                            }
   1996                        };
   1997                        networking::http_3_connection_close_reason
   1998                            .get(glean_label)
   1999                            .add(1);
   2000                    }
   2001 
   2002                    Http3Event::ConnectionClosing {
   2003                        error: reason.into(),
   2004                    }
   2005                }
   2006                Http3State::Closed(error_code) => {
   2007                    if let neqo_transport::CloseReason::Transport(
   2008                        TransportError::Crypto(neqo_crypto::Error::EchRetry(c))
   2009                        | TransportError::EchRetry(c),
   2010                    ) = &error_code
   2011                    {
   2012                        data.extend_from_slice(c.as_ref());
   2013                    }
   2014                    Http3Event::ConnectionClosed {
   2015                        error: error_code.into(),
   2016                    }
   2017                }
   2018                _ => Http3Event::NoEvent,
   2019            },
   2020            Http3ClientEvent::EchFallbackAuthenticationNeeded { public_name } => {
   2021                data.extend_from_slice(public_name.as_ref());
   2022                Http3Event::EchFallbackAuthenticationNeeded
   2023            }
   2024            Http3ClientEvent::WebTransport(e) => {
   2025                Http3Event::WebTransport(WebTransportEventExternal::new(e, data))
   2026            }
   2027            Http3ClientEvent::ConnectUdp(e) => {
   2028                Http3Event::ConnectUdp(ConnectUdpEventExternal::new(e, data))
   2029            }
   2030        };
   2031 
   2032        if !matches!(fe, Http3Event::NoEvent) {
   2033            *ret_event = fe;
   2034            return NS_OK;
   2035        }
   2036    }
   2037 
   2038    *ret_event = Http3Event::NoEvent;
   2039    NS_OK
   2040 }
   2041 
   2042 // Read response data into buf.
   2043 ///
   2044 /// # Safety
   2045 ///
   2046 /// Marked as unsafe given exposition via FFI i.e. `extern "C"`.
   2047 #[no_mangle]
   2048 pub unsafe extern "C" fn neqo_http3conn_read_response_data(
   2049    conn: &mut NeqoHttp3Conn,
   2050    stream_id: u64,
   2051    buf: *mut u8,
   2052    len: u32,
   2053    read: &mut u32,
   2054    fin: &mut bool,
   2055 ) -> nsresult {
   2056    let array = slice::from_raw_parts_mut(buf, len as usize);
   2057    match conn
   2058        .conn
   2059        .read_data(Instant::now(), StreamId::from(stream_id), &mut array[..])
   2060    {
   2061        Ok((amount, fin_recvd)) => {
   2062            let Ok(amount) = u32::try_from(amount) else {
   2063                return NS_ERROR_NET_HTTP3_PROTOCOL_ERROR;
   2064            };
   2065            *read = amount;
   2066            *fin = fin_recvd;
   2067            if (amount == 0) && !fin_recvd {
   2068                NS_BASE_STREAM_WOULD_BLOCK
   2069            } else {
   2070                NS_OK
   2071            }
   2072        }
   2073        Err(Http3Error::InvalidStreamId | Http3Error::Transport(TransportError::NoMoreData)) => {
   2074            NS_ERROR_INVALID_ARG
   2075        }
   2076        Err(_) => NS_ERROR_NET_HTTP3_PROTOCOL_ERROR,
   2077    }
   2078 }
   2079 
   2080 #[repr(C)]
   2081 pub struct NeqoSecretInfo {
   2082    set: bool,
   2083    version: u16,
   2084    cipher: u16,
   2085    group: u16,
   2086    resumed: bool,
   2087    early_data: bool,
   2088    alpn: nsCString,
   2089    signature_scheme: u16,
   2090    ech_accepted: bool,
   2091 }
   2092 
   2093 #[no_mangle]
   2094 pub extern "C" fn neqo_http3conn_tls_info(
   2095    conn: &mut NeqoHttp3Conn,
   2096    sec_info: &mut NeqoSecretInfo,
   2097 ) -> nsresult {
   2098    match conn.conn.tls_info() {
   2099        Some(info) => {
   2100            sec_info.set = true;
   2101            sec_info.version = info.version();
   2102            sec_info.cipher = info.cipher_suite();
   2103            sec_info.group = info.key_exchange();
   2104            sec_info.resumed = info.resumed();
   2105            sec_info.early_data = info.early_data_accepted();
   2106            sec_info.alpn = info.alpn().map_or_else(nsCString::new, nsCString::from);
   2107            sec_info.signature_scheme = info.signature_scheme();
   2108            sec_info.ech_accepted = info.ech_accepted();
   2109            NS_OK
   2110        }
   2111        None => NS_ERROR_NOT_AVAILABLE,
   2112    }
   2113 }
   2114 
   2115 #[repr(C)]
   2116 pub struct NeqoCertificateInfo {
   2117    certs: ThinVec<ThinVec<u8>>,
   2118    stapled_ocsp_responses_present: bool,
   2119    stapled_ocsp_responses: ThinVec<ThinVec<u8>>,
   2120    signed_cert_timestamp_present: bool,
   2121    signed_cert_timestamp: ThinVec<u8>,
   2122 }
   2123 
   2124 #[no_mangle]
   2125 pub extern "C" fn neqo_http3conn_peer_certificate_info(
   2126    conn: &mut NeqoHttp3Conn,
   2127    neqo_certs_info: &mut NeqoCertificateInfo,
   2128 ) -> nsresult {
   2129    let Some(certs_info) = conn.conn.peer_certificate() else {
   2130        return NS_ERROR_NOT_AVAILABLE;
   2131    };
   2132 
   2133    neqo_certs_info.certs = certs_info.iter().map(ThinVec::from).collect();
   2134 
   2135    match &mut certs_info.stapled_ocsp_responses() {
   2136        Some(ocsp_val) => {
   2137            neqo_certs_info.stapled_ocsp_responses_present = true;
   2138            neqo_certs_info.stapled_ocsp_responses = ocsp_val
   2139                .iter()
   2140                .map(|ocsp| ocsp.iter().copied().collect())
   2141                .collect();
   2142        }
   2143        None => {
   2144            neqo_certs_info.stapled_ocsp_responses_present = false;
   2145        }
   2146    };
   2147 
   2148    match certs_info.signed_cert_timestamp() {
   2149        Some(sct_val) => {
   2150            neqo_certs_info.signed_cert_timestamp_present = true;
   2151            neqo_certs_info
   2152                .signed_cert_timestamp
   2153                .extend_from_slice(sct_val);
   2154        }
   2155        None => {
   2156            neqo_certs_info.signed_cert_timestamp_present = false;
   2157        }
   2158    };
   2159 
   2160    NS_OK
   2161 }
   2162 
   2163 #[no_mangle]
   2164 pub extern "C" fn neqo_http3conn_authenticated(conn: &mut NeqoHttp3Conn, error: PRErrorCode) {
   2165    conn.conn.authenticated(error.into(), Instant::now());
   2166 }
   2167 
   2168 #[no_mangle]
   2169 pub extern "C" fn neqo_http3conn_set_resumption_token(
   2170    conn: &mut NeqoHttp3Conn,
   2171    token: &mut ThinVec<u8>,
   2172 ) {
   2173    _ = conn.conn.enable_resumption(Instant::now(), token);
   2174 }
   2175 
   2176 #[no_mangle]
   2177 pub extern "C" fn neqo_http3conn_set_ech_config(
   2178    conn: &mut NeqoHttp3Conn,
   2179    ech_config: &mut ThinVec<u8>,
   2180 ) {
   2181    _ = conn.conn.enable_ech(ech_config);
   2182 }
   2183 
   2184 #[no_mangle]
   2185 pub extern "C" fn neqo_http3conn_is_zero_rtt(conn: &mut NeqoHttp3Conn) -> bool {
   2186    conn.conn.state() == Http3State::ZeroRtt
   2187 }
   2188 
   2189 #[repr(C)]
   2190 #[derive(Default)]
   2191 pub struct Http3Stats {
   2192    /// Total packets received, including all the bad ones.
   2193    pub packets_rx: usize,
   2194    /// Duplicate packets received.
   2195    pub dups_rx: usize,
   2196    /// Dropped packets or dropped garbage.
   2197    pub dropped_rx: usize,
   2198    /// The number of packet that were saved for later processing.
   2199    pub saved_datagrams: usize,
   2200    /// Total packets sent.
   2201    pub packets_tx: usize,
   2202    /// Total number of packets that are declared lost.
   2203    pub lost: usize,
   2204    /// Late acknowledgments, for packets that were declared lost already.
   2205    pub late_ack: usize,
   2206    /// Acknowledgments for packets that contained data that was marked
   2207    /// for retransmission when the PTO timer popped.
   2208    pub pto_ack: usize,
   2209    /// Count PTOs. Single PTOs, 2 PTOs in a row, 3 PTOs in row, etc. are counted
   2210    /// separately.
   2211    pub pto_counts: [usize; 16],
   2212    /// The count of WouldBlock errors encountered during receive operations on the UDP socket.
   2213    pub would_block_rx: usize,
   2214    /// The count of WouldBlock errors encountered during transmit operations on the UDP socket.
   2215    pub would_block_tx: usize,
   2216 }
   2217 
   2218 #[no_mangle]
   2219 pub extern "C" fn neqo_http3conn_get_stats(conn: &mut NeqoHttp3Conn, stats: &mut Http3Stats) {
   2220    let t_stats = conn.conn.transport_stats();
   2221    stats.packets_rx = t_stats.packets_rx;
   2222    stats.dups_rx = t_stats.dups_rx;
   2223    stats.dropped_rx = t_stats.dropped_rx;
   2224    stats.saved_datagrams = t_stats.saved_datagrams;
   2225    stats.packets_tx = t_stats.packets_tx;
   2226    stats.lost = t_stats.lost;
   2227    stats.late_ack = t_stats.late_ack;
   2228    stats.pto_ack = t_stats.pto_ack;
   2229    stats.pto_counts = t_stats.pto_counts;
   2230    stats.would_block_rx = conn.would_block_rx_count();
   2231    stats.would_block_tx = conn.would_block_tx_count();
   2232 }
   2233 
   2234 #[no_mangle]
   2235 pub extern "C" fn neqo_http3conn_webtransport_create_session(
   2236    conn: &mut NeqoHttp3Conn,
   2237    host: &nsACString,
   2238    path: &nsACString,
   2239    headers: &nsACString,
   2240    stream_id: &mut u64,
   2241 ) -> nsresult {
   2242    let hdrs = match parse_headers(headers) {
   2243        Err(e) => {
   2244            return e;
   2245        }
   2246        Ok(h) => h,
   2247    };
   2248    let Ok(host_tmp) = str::from_utf8(host) else {
   2249        return NS_ERROR_INVALID_ARG;
   2250    };
   2251    let Ok(path_tmp) = str::from_utf8(path) else {
   2252        return NS_ERROR_INVALID_ARG;
   2253    };
   2254 
   2255    match conn.conn.webtransport_create_session(
   2256        Instant::now(),
   2257        ("https", host_tmp, path_tmp),
   2258        &hdrs,
   2259    ) {
   2260        Ok(id) => {
   2261            *stream_id = id.as_u64();
   2262            NS_OK
   2263        }
   2264        Err(Http3Error::StreamLimit) => NS_BASE_STREAM_WOULD_BLOCK,
   2265        Err(_) => NS_ERROR_UNEXPECTED,
   2266    }
   2267 }
   2268 
   2269 #[no_mangle]
   2270 pub extern "C" fn neqo_http3conn_connect_udp_create_session(
   2271    conn: &mut NeqoHttp3Conn,
   2272    host: &nsACString,
   2273    path: &nsACString,
   2274    headers: &nsACString,
   2275    stream_id: &mut u64,
   2276 ) -> nsresult {
   2277    let hdrs = match parse_headers(headers) {
   2278        Err(e) => {
   2279            return e;
   2280        }
   2281        Ok(h) => h,
   2282    };
   2283    let Ok(host_tmp) = str::from_utf8(host) else {
   2284        return NS_ERROR_INVALID_ARG;
   2285    };
   2286    let Ok(path_tmp) = str::from_utf8(path) else {
   2287        return NS_ERROR_INVALID_ARG;
   2288    };
   2289 
   2290    match conn
   2291        .conn
   2292        .connect_udp_create_session(Instant::now(), ("https", host_tmp, path_tmp), &hdrs)
   2293    {
   2294        Ok(id) => {
   2295            *stream_id = id.as_u64();
   2296            NS_OK
   2297        }
   2298        Err(Http3Error::StreamLimit) => NS_BASE_STREAM_WOULD_BLOCK,
   2299        Err(_) => NS_ERROR_UNEXPECTED,
   2300    }
   2301 }
   2302 
   2303 #[no_mangle]
   2304 pub extern "C" fn neqo_http3conn_webtransport_close_session(
   2305    conn: &mut NeqoHttp3Conn,
   2306    session_id: u64,
   2307    error: u32,
   2308    message: &nsACString,
   2309 ) -> nsresult {
   2310    let Ok(message_tmp) = str::from_utf8(message) else {
   2311        return NS_ERROR_INVALID_ARG;
   2312    };
   2313    match conn.conn.webtransport_close_session(
   2314        StreamId::from(session_id),
   2315        error,
   2316        message_tmp,
   2317        Instant::now(),
   2318    ) {
   2319        Ok(()) => NS_OK,
   2320        Err(_) => NS_ERROR_INVALID_ARG,
   2321    }
   2322 }
   2323 
   2324 #[no_mangle]
   2325 pub extern "C" fn neqo_http3conn_connect_udp_close_session(
   2326    conn: &mut NeqoHttp3Conn,
   2327    session_id: u64,
   2328    error: u32,
   2329    message: &nsACString,
   2330 ) -> nsresult {
   2331    let Ok(message_tmp) = str::from_utf8(message) else {
   2332        return NS_ERROR_INVALID_ARG;
   2333    };
   2334    match conn.conn.connect_udp_close_session(
   2335        StreamId::from(session_id),
   2336        error,
   2337        message_tmp,
   2338        Instant::now(),
   2339    ) {
   2340        Ok(()) => NS_OK,
   2341        Err(_) => NS_ERROR_INVALID_ARG,
   2342    }
   2343 }
   2344 
   2345 #[no_mangle]
   2346 pub extern "C" fn neqo_http3conn_webtransport_create_stream(
   2347    conn: &mut NeqoHttp3Conn,
   2348    session_id: u64,
   2349    stream_type: WebTransportStreamType,
   2350    stream_id: &mut u64,
   2351 ) -> nsresult {
   2352    match conn
   2353        .conn
   2354        .webtransport_create_stream(StreamId::from(session_id), stream_type.into())
   2355    {
   2356        Ok(id) => {
   2357            *stream_id = id.as_u64();
   2358            NS_OK
   2359        }
   2360        Err(Http3Error::StreamLimit) => NS_BASE_STREAM_WOULD_BLOCK,
   2361        Err(_) => NS_ERROR_UNEXPECTED,
   2362    }
   2363 }
   2364 
   2365 #[no_mangle]
   2366 pub extern "C" fn neqo_http3conn_webtransport_send_datagram(
   2367    conn: &mut NeqoHttp3Conn,
   2368    session_id: u64,
   2369    data: &mut ThinVec<u8>,
   2370    tracking_id: u64,
   2371 ) -> nsresult {
   2372    let id = if tracking_id == 0 {
   2373        None
   2374    } else {
   2375        Some(tracking_id)
   2376    };
   2377    match conn
   2378        .conn
   2379        .webtransport_send_datagram(StreamId::from(session_id), data, id)
   2380    {
   2381        Ok(()) => NS_OK,
   2382        Err(Http3Error::Transport(TransportError::TooMuchData)) => NS_ERROR_NOT_AVAILABLE,
   2383        Err(_) => NS_ERROR_UNEXPECTED,
   2384    }
   2385 }
   2386 #[no_mangle]
   2387 pub extern "C" fn neqo_http3conn_connect_udp_send_datagram(
   2388    conn: &mut NeqoHttp3Conn,
   2389    session_id: u64,
   2390    data: &mut ThinVec<u8>,
   2391    tracking_id: u64,
   2392 ) -> nsresult {
   2393    let id = if tracking_id == 0 {
   2394        None
   2395    } else {
   2396        Some(tracking_id)
   2397    };
   2398    match conn
   2399        .conn
   2400        .connect_udp_send_datagram(StreamId::from(session_id), data, id)
   2401    {
   2402        Ok(()) => NS_OK,
   2403        Err(Http3Error::Transport(TransportError::TooMuchData)) => NS_ERROR_NOT_AVAILABLE,
   2404        Err(_) => NS_ERROR_UNEXPECTED,
   2405    }
   2406 }
   2407 
   2408 #[no_mangle]
   2409 pub extern "C" fn neqo_http3conn_webtransport_max_datagram_size(
   2410    conn: &mut NeqoHttp3Conn,
   2411    session_id: u64,
   2412    result: &mut u64,
   2413 ) -> nsresult {
   2414    conn.conn
   2415        .webtransport_max_datagram_size(StreamId::from(session_id))
   2416        .map_or(NS_ERROR_UNEXPECTED, |size| {
   2417            *result = size;
   2418            NS_OK
   2419        })
   2420 }
   2421 
   2422 /// # Safety
   2423 ///
   2424 /// Use of raw (i.e. unsafe) pointers as arguments.
   2425 #[no_mangle]
   2426 pub unsafe extern "C" fn neqo_http3conn_webtransport_set_sendorder(
   2427    conn: &mut NeqoHttp3Conn,
   2428    stream_id: u64,
   2429    sendorder: *const i64,
   2430 ) -> nsresult {
   2431    match conn
   2432        .conn
   2433        .webtransport_set_sendorder(StreamId::from(stream_id), sendorder.as_ref().copied())
   2434    {
   2435        Ok(()) => NS_OK,
   2436        Err(_) => NS_ERROR_UNEXPECTED,
   2437    }
   2438 }
   2439 
   2440 /// Convert a [`std::io::Error`] into a [`nsresult`].
   2441 ///
   2442 /// Note that this conversion is specific to `neqo_glue`, i.e. does not aim to
   2443 /// implement a general-purpose conversion.
   2444 /// Treat NS_ERROR_NET_RESET as a generic retryable error for the upper layer.
   2445 ///
   2446 /// Modeled after
   2447 /// [`ErrorAccordingToNSPR`](https://searchfox.org/mozilla-central/rev/a965e3c683ecc035dee1de72bd33a8d91b1203ed/netwerk/base/nsSocketTransport2.cpp#164-168).
   2448 //
   2449 // TODO: Use `non_exhaustive_omitted_patterns_lint` [once stablized](https://github.com/rust-lang/rust/issues/89554).
   2450 fn into_nsresult(e: &io::Error) -> nsresult {
   2451    #[expect(clippy::match_same_arms, reason = "It's cleaner this way.")]
   2452    match e.kind() {
   2453        io::ErrorKind::ConnectionRefused => NS_ERROR_CONNECTION_REFUSED,
   2454        io::ErrorKind::ConnectionReset => NS_ERROR_NET_RESET,
   2455 
   2456        // > We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
   2457        // > could get better diagnostics by adding distinct XPCOM error codes for
   2458        // > each of these, but there are a lot of places in Gecko that check
   2459        // > specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
   2460        // > be checked.
   2461        //
   2462        // <https://searchfox.org/mozilla-central/rev/a965e3c683ecc035dee1de72bd33a8d91b1203ed/netwerk/base/nsSocketTransport2.cpp#164-168>
   2463        //
   2464        // TODO: `HostUnreachable` and `NetworkUnreachable` available since Rust
   2465        // v1.83.0 only <https://doc.rust-lang.org/std/io/enum.ErrorKind.html>.
   2466        // io::ErrorKind::HostUnreachable | io::ErrorKind::NetworkUnreachable |
   2467        io::ErrorKind::AddrNotAvailable => NS_ERROR_CONNECTION_REFUSED,
   2468 
   2469        // <https://searchfox.org/mozilla-central/rev/a965e3c683ecc035dee1de72bd33a8d91b1203ed/netwerk/base/nsSocketTransport2.cpp#156>
   2470        io::ErrorKind::ConnectionAborted => NS_ERROR_NET_RESET,
   2471 
   2472        io::ErrorKind::NotConnected => NS_ERROR_NOT_CONNECTED,
   2473        io::ErrorKind::AddrInUse => NS_ERROR_SOCKET_ADDRESS_IN_USE,
   2474        io::ErrorKind::AlreadyExists => NS_ERROR_FILE_ALREADY_EXISTS,
   2475        io::ErrorKind::WouldBlock => NS_BASE_STREAM_WOULD_BLOCK,
   2476 
   2477        // TODO: available since Rust v1.83.0 only
   2478        // <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.NotADirectory>
   2479        // io::ErrorKind::NotADirectory => NS_ERROR_FILE_NOT_DIRECTORY,
   2480 
   2481        // TODO: available since Rust v1.83.0 only
   2482        // <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.IsADirectory>
   2483        // io::ErrorKind::IsADirectory => NS_ERROR_FILE_IS_DIRECTORY,
   2484 
   2485        // TODO: available since Rust v1.83.0 only
   2486        // <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.DirectoryNotEmpty>
   2487        // io::ErrorKind::DirectoryNotEmpty => NS_ERROR_FILE_DIR_NOT_EMPTY,
   2488 
   2489        // TODO: available since Rust v1.83.0 only
   2490        // <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.ReadOnlyFilesystem>
   2491        // io::ErrorKind::ReadOnlyFilesystem => NS_ERROR_FILE_READ_ONLY,
   2492 
   2493        // TODO: nightly-only for now <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.FilesystemLoop>.
   2494        // io::ErrorKind::FilesystemLoop => NS_ERROR_FILE_UNRESOLVABLE_SYMLINK,
   2495        io::ErrorKind::TimedOut => NS_ERROR_NET_TIMEOUT,
   2496        io::ErrorKind::Interrupted => NS_ERROR_NET_INTERRUPT,
   2497 
   2498        // <https://searchfox.org/mozilla-central/rev/a965e3c683ecc035dee1de72bd33a8d91b1203ed/netwerk/base/nsSocketTransport2.cpp#160-161>
   2499        io::ErrorKind::UnexpectedEof => NS_ERROR_NET_INTERRUPT,
   2500 
   2501        io::ErrorKind::OutOfMemory => NS_ERROR_OUT_OF_MEMORY,
   2502 
   2503        // TODO: nightly-only for now <https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.InProgress>.
   2504        // io::ErrorKind::InProgress => NS_ERROR_IN_PROGRESS,
   2505 
   2506        // The errors below are either not relevant for `neqo_glue`, or not
   2507        // defined as `nsresult`.
   2508        io::ErrorKind::NotFound
   2509        | io::ErrorKind::PermissionDenied
   2510        | io::ErrorKind::BrokenPipe
   2511        | io::ErrorKind::InvalidData
   2512        | io::ErrorKind::WriteZero
   2513        | io::ErrorKind::Unsupported
   2514        | io::ErrorKind::Other => NS_ERROR_NET_RESET,
   2515 
   2516        // TODO: available since Rust v1.83.0 only
   2517        // <https://doc.rust-lang.org/std/io/enum.ErrorKind.html>.
   2518        // io::ErrorKind::NotSeekable
   2519        // | io::ErrorKind::FilesystemQuotaExceeded
   2520        // | io::ErrorKind::FileTooLarge
   2521        // | io::ErrorKind::ResourceBusy
   2522        // | io::ErrorKind::ExecutableFileBusy
   2523        // | io::ErrorKind::Deadlock
   2524        // | io::ErrorKind::TooManyLinks
   2525        // | io::ErrorKind::ArgumentListTooLong
   2526        // | io::ErrorKind::NetworkDown
   2527        // | io::ErrorKind::StaleNetworkFileHandle
   2528        // | io::ErrorKind::StorageFull => NS_ERROR_NET_RESET,
   2529 
   2530        // TODO: nightly-only for now <https://doc.rust-lang.org/std/io/enum.ErrorKind.html>.
   2531        // io::ErrorKind::CrossesDevices
   2532        // | io::ErrorKind::InvalidFilename
   2533        // | io::ErrorKind::InvalidInput => NS_ERROR_NET_RESET,
   2534        _ => NS_ERROR_NET_RESET,
   2535    }
   2536 }
   2537 
   2538 #[repr(C)]
   2539 pub struct NeqoEncoder {
   2540    encoder: Encoder,
   2541    refcnt: AtomicRefcnt,
   2542 }
   2543 
   2544 impl NeqoEncoder {
   2545    fn new() -> Result<RefPtr<NeqoEncoder>, nsresult> {
   2546        let encoder = Encoder::default();
   2547        let encoder = Box::into_raw(Box::new(NeqoEncoder {
   2548            encoder,
   2549            refcnt: unsafe { AtomicRefcnt::new() },
   2550        }));
   2551        unsafe { Ok(RefPtr::from_raw(encoder).unwrap()) }
   2552    }
   2553 }
   2554 
   2555 #[no_mangle]
   2556 pub unsafe extern "C" fn neqo_encoder_addref(encoder: &NeqoEncoder) {
   2557    encoder.refcnt.inc();
   2558 }
   2559 
   2560 #[no_mangle]
   2561 pub unsafe extern "C" fn neqo_encoder_release(encoder: &NeqoEncoder) {
   2562    let rc = encoder.refcnt.dec();
   2563    if rc == 0 {
   2564        drop(Box::from_raw(encoder as *const _ as *mut NeqoEncoder));
   2565    }
   2566 }
   2567 
   2568 // xpcom::RefPtr support
   2569 unsafe impl RefCounted for NeqoEncoder {
   2570    unsafe fn addref(&self) {
   2571        neqo_encoder_addref(self);
   2572    }
   2573    unsafe fn release(&self) {
   2574        neqo_encoder_release(self);
   2575    }
   2576 }
   2577 
   2578 #[no_mangle]
   2579 pub extern "C" fn neqo_encoder_new(result: &mut *const NeqoEncoder) {
   2580    *result = ptr::null_mut();
   2581    if let Ok(encoder) = NeqoEncoder::new() {
   2582        encoder.forget(result);
   2583    }
   2584 }
   2585 
   2586 #[no_mangle]
   2587 pub extern "C" fn neqo_encode_byte(encoder: &mut NeqoEncoder, data: u8) {
   2588    encoder.encoder.encode_byte(data);
   2589 }
   2590 
   2591 #[no_mangle]
   2592 pub extern "C" fn neqo_encode_varint(encoder: &mut NeqoEncoder, data: u64) {
   2593    encoder.encoder.encode_varint(data);
   2594 }
   2595 
   2596 #[no_mangle]
   2597 pub extern "C" fn neqo_encode_uint(encoder: &mut NeqoEncoder, n: u32, data: u64) {
   2598    encoder.encoder.encode_uint(n as usize, data);
   2599 }
   2600 
   2601 #[no_mangle]
   2602 pub unsafe extern "C" fn neqo_encode_buffer(encoder: &mut NeqoEncoder, buf: *const u8, len: u32) {
   2603    let array = slice::from_raw_parts(buf, len as usize);
   2604    encoder.encoder.encode(array);
   2605 }
   2606 
   2607 #[no_mangle]
   2608 pub unsafe extern "C" fn neqo_encode_vvec(encoder: &mut NeqoEncoder, buf: *const u8, len: u32) {
   2609    let array = slice::from_raw_parts(buf, len as usize);
   2610    encoder.encoder.encode_vvec(array);
   2611 }
   2612 
   2613 #[no_mangle]
   2614 pub extern "C" fn neqo_encode_get_data(
   2615    encoder: &mut NeqoEncoder,
   2616    buf: *mut *const u8,
   2617    read: &mut u32,
   2618 ) {
   2619    let data = encoder.encoder.as_ref();
   2620    *read = data.len() as u32;
   2621    unsafe {
   2622        *buf = data.as_ptr();
   2623    }
   2624 }
   2625 
   2626 #[no_mangle]
   2627 pub extern "C" fn neqo_encode_varint_len(v: u64) -> usize {
   2628    return Encoder::varint_len(v);
   2629 }
   2630 
   2631 #[repr(C)]
   2632 pub struct NeqoDecoder {
   2633    decoder: *mut Decoder<'static>,
   2634    refcnt: AtomicRefcnt,
   2635 }
   2636 
   2637 impl NeqoDecoder {
   2638    fn new(buf: *const u8, len: u32) -> Result<RefPtr<NeqoDecoder>, nsresult> {
   2639        let slice = unsafe { slice::from_raw_parts(buf, len as usize) };
   2640        let decoder = Box::new(Decoder::new(slice));
   2641        let wrapper = Box::into_raw(Box::new(NeqoDecoder {
   2642            decoder: Box::into_raw(decoder),
   2643            refcnt: unsafe { AtomicRefcnt::new() },
   2644        }));
   2645 
   2646        unsafe { Ok(RefPtr::from_raw(wrapper).unwrap()) }
   2647    }
   2648 }
   2649 
   2650 #[no_mangle]
   2651 pub unsafe extern "C" fn neqo_decoder_addref(decoder: &NeqoDecoder) {
   2652    decoder.refcnt.inc();
   2653 }
   2654 
   2655 #[no_mangle]
   2656 pub unsafe extern "C" fn neqo_decoder_release(decoder: &NeqoDecoder) {
   2657    let rc = decoder.refcnt.dec();
   2658    if rc == 0 {
   2659        unsafe {
   2660            drop(Box::from_raw(decoder.decoder));
   2661            drop(Box::from_raw(decoder as *const _ as *mut NeqoDecoder));
   2662        }
   2663    }
   2664 }
   2665 
   2666 // xpcom::RefPtr support
   2667 unsafe impl RefCounted for NeqoDecoder {
   2668    unsafe fn addref(&self) {
   2669        neqo_decoder_addref(self);
   2670    }
   2671    unsafe fn release(&self) {
   2672        neqo_decoder_release(self);
   2673    }
   2674 }
   2675 
   2676 #[no_mangle]
   2677 pub extern "C" fn neqo_decoder_new(buf: *const u8, len: u32, result: &mut *const NeqoDecoder) {
   2678    *result = ptr::null_mut();
   2679    if let Ok(decoder) = NeqoDecoder::new(buf, len) {
   2680        decoder.forget(result);
   2681    }
   2682 }
   2683 
   2684 #[no_mangle]
   2685 pub unsafe extern "C" fn neqo_decode_uint32(decoder: &mut NeqoDecoder, result: &mut u32) -> bool {
   2686    let decoder = decoder.decoder.as_mut().unwrap();
   2687    if let Some(v) = decoder.decode_uint::<u32>() {
   2688        *result = v;
   2689        return true;
   2690    }
   2691    false
   2692 }
   2693 
   2694 #[no_mangle]
   2695 pub unsafe extern "C" fn neqo_decode_varint(decoder: &mut NeqoDecoder, result: &mut u64) -> bool {
   2696    let decoder = decoder.decoder.as_mut().unwrap();
   2697    if let Some(v) = decoder.decode_varint() {
   2698        *result = v;
   2699        return true;
   2700    }
   2701    false
   2702 }
   2703 
   2704 #[no_mangle]
   2705 pub unsafe extern "C" fn neqo_decode(
   2706    decoder: &mut NeqoDecoder,
   2707    n: u32,
   2708    buf: *mut *const u8,
   2709    read: &mut u32,
   2710 ) -> bool {
   2711    let decoder = decoder.decoder.as_mut().unwrap();
   2712    if let Some(data) = decoder.decode(n as usize) {
   2713        *buf = data.as_ptr();
   2714        *read = data.len() as u32;
   2715        return true;
   2716    }
   2717    false
   2718 }
   2719 
   2720 #[no_mangle]
   2721 pub unsafe extern "C" fn neqo_decode_remainder(
   2722    decoder: &mut NeqoDecoder,
   2723    buf: *mut *const u8,
   2724    read: &mut u32,
   2725 ) {
   2726    let decoder = decoder.decoder.as_mut().unwrap();
   2727    let data = decoder.decode_remainder();
   2728    *buf = data.as_ptr();
   2729    *read = data.len() as u32;
   2730 }
   2731 
   2732 #[no_mangle]
   2733 pub unsafe extern "C" fn neqo_decoder_remaining(decoder: &mut NeqoDecoder) -> u64 {
   2734    let decoder = decoder.decoder.as_mut().unwrap();
   2735    decoder.remaining() as u64
   2736 }
   2737 
   2738 #[no_mangle]
   2739 pub unsafe extern "C" fn neqo_decoder_offset(decoder: &mut NeqoDecoder) -> u64 {
   2740    let decoder = decoder.decoder.as_mut().unwrap();
   2741    decoder.offset() as u64
   2742 }
   2743 
   2744 // Test function called from C++ gtest
   2745 // Callback signature: fn(user_data, name_ptr, name_len, value_ptr, value_len)
   2746 type HeaderCallback = extern "C" fn(*mut c_void, *const u8, usize, *const u8, usize);
   2747 
   2748 #[no_mangle]
   2749 pub extern "C" fn neqo_glue_test_parse_headers(
   2750    headers_input: &nsACString,
   2751    callback: HeaderCallback,
   2752    user_data: *mut c_void,
   2753 ) -> bool {
   2754    match parse_headers(headers_input) {
   2755        Ok(headers) => {
   2756            for header in headers {
   2757                let name_bytes = header.name().as_bytes();
   2758                let value_bytes = header.value();
   2759                callback(
   2760                    user_data,
   2761                    name_bytes.as_ptr(),
   2762                    name_bytes.len(),
   2763                    value_bytes.as_ptr(),
   2764                    value_bytes.len(),
   2765                );
   2766            }
   2767            true
   2768        }
   2769        Err(_) => false,
   2770    }
   2771 }