summaryrefslogtreecommitdiffstats
path: root/netwerk/socket/neqo_glue
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/socket/neqo_glue/Cargo.toml36
-rw-r--r--netwerk/socket/neqo_glue/NeqoHttp3Conn.h167
-rw-r--r--netwerk/socket/neqo_glue/cbindgen.toml27
-rw-r--r--netwerk/socket/neqo_glue/moz.build21
-rw-r--r--netwerk/socket/neqo_glue/src/lib.rs1439
5 files changed, 1690 insertions, 0 deletions
diff --git a/netwerk/socket/neqo_glue/Cargo.toml b/netwerk/socket/neqo_glue/Cargo.toml
new file mode 100644
index 0000000000..0e506c8c2d
--- /dev/null
+++ b/netwerk/socket/neqo_glue/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "neqo_glue"
+version = "0.1.0"
+authors = ["Dragana Damjanovic <dd.mozilla@gmail.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[lib]
+name = "neqo_glue"
+
+[dependencies]
+neqo-http3 = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-transport = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-common = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+neqo-qpack = { tag = "v0.7.0", git = "https://github.com/mozilla/neqo" }
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+log = "0.4.0"
+qlog = { git = "https://github.com/cloudflare/quiche", rev = "09ea4b244096a013071cfe2175bbf2945fb7f8d1" }
+libc = "0.2.0"
+static_prefs = { path = "../../../modules/libpref/init/static_prefs"}
+uuid = { version = "1.0", features = ["v4"] }
+
+[target.'cfg(target_os = "windows")'.dependencies]
+winapi = {version = "0.3", features = ["ws2def"] }
+
+[dependencies.neqo-crypto]
+tag = "v0.7.0"
+git = "https://github.com/mozilla/neqo"
+default-features = false
+features = ["gecko"]
+
+[features]
+fuzzing = ["neqo-http3/fuzzing"]
diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
new file mode 100644
index 0000000000..63fbe389f3
--- /dev/null
+++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NeqoHttp3Conn_h__
+#define NeqoHttp3Conn_h__
+
+#include <cstdint>
+#include "mozilla/net/neqo_glue_ffi_generated.h"
+
+namespace mozilla {
+namespace net {
+
+class NeqoHttp3Conn final {
+ public:
+ static nsresult Init(const nsACString& aOrigin, const nsACString& aAlpn,
+ const NetAddr& aLocalAddr, const NetAddr& aRemoteAddr,
+ uint32_t aMaxTableSize, uint16_t aMaxBlockedStreams,
+ uint64_t aMaxData, uint64_t aMaxStreamData,
+ bool aVersionNegotiation, bool aWebTransport,
+ const nsACString& aQlogDir, uint32_t aDatagramSize,
+ uint32_t aMaxAccumulatedTime, NeqoHttp3Conn** aConn) {
+ return neqo_http3conn_new(
+ &aOrigin, &aAlpn, &aLocalAddr, &aRemoteAddr, aMaxTableSize,
+ aMaxBlockedStreams, aMaxData, aMaxStreamData, aVersionNegotiation,
+ aWebTransport, &aQlogDir, aDatagramSize, aMaxAccumulatedTime,
+ (const mozilla::net::NeqoHttp3Conn**)aConn);
+ }
+
+ void Close(uint64_t aError) { neqo_http3conn_close(this, aError); }
+
+ nsresult GetSecInfo(NeqoSecretInfo* aSecInfo) {
+ return neqo_http3conn_tls_info(this, aSecInfo);
+ }
+
+ nsresult PeerCertificateInfo(NeqoCertificateInfo* aCertInfo) {
+ return neqo_http3conn_peer_certificate_info(this, aCertInfo);
+ }
+
+ void PeerAuthenticated(PRErrorCode aError) {
+ neqo_http3conn_authenticated(this, aError);
+ }
+
+ nsresult ProcessInput(const NetAddr& aRemoteAddr,
+ const nsTArray<uint8_t>& aPacket) {
+ return neqo_http3conn_process_input(this, &aRemoteAddr, &aPacket);
+ }
+
+ nsresult ProcessOutputAndSend(void* aContext, SendFunc aSendFunc,
+ SetTimerFunc aSetTimerFunc) {
+ return neqo_http3conn_process_output_and_send(this, aContext, aSendFunc,
+ aSetTimerFunc);
+ }
+
+ nsresult GetEvent(Http3Event* aEvent, nsTArray<uint8_t>& aData) {
+ return neqo_http3conn_event(this, aEvent, &aData);
+ }
+
+ nsresult Fetch(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aHeaders, uint64_t* aStreamId,
+ uint8_t aUrgency, bool aIncremental) {
+ return neqo_http3conn_fetch(this, &aMethod, &aScheme, &aHost, &aPath,
+ &aHeaders, aStreamId, aUrgency, aIncremental);
+ }
+
+ nsresult PriorityUpdate(uint64_t aStreamId, uint8_t aUrgency,
+ bool aIncremental) {
+ return neqo_http3conn_priority_update(this, aStreamId, aUrgency,
+ aIncremental);
+ }
+
+ nsresult SendRequestBody(uint64_t aStreamId, const uint8_t* aBuf,
+ uint32_t aCount, uint32_t* aCountRead) {
+ return neqo_htttp3conn_send_request_body(this, aStreamId, aBuf, aCount,
+ aCountRead);
+ }
+
+ // This closes only the sending side of a stream.
+ nsresult CloseStream(uint64_t aStreamId) {
+ return neqo_http3conn_close_stream(this, aStreamId);
+ }
+
+ nsresult ReadResponseData(uint64_t aStreamId, uint8_t* aBuf, uint32_t aLen,
+ uint32_t* aRead, bool* aFin) {
+ return neqo_http3conn_read_response_data(this, aStreamId, aBuf, aLen, aRead,
+ aFin);
+ }
+
+ void CancelFetch(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_cancel_fetch(this, aStreamId, aError);
+ }
+
+ void ResetStream(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_reset_stream(this, aStreamId, aError);
+ }
+
+ void StreamStopSending(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_stream_stop_sending(this, aStreamId, aError);
+ }
+
+ void SetResumptionToken(nsTArray<uint8_t>& aToken) {
+ neqo_http3conn_set_resumption_token(this, &aToken);
+ }
+
+ void SetEchConfig(nsTArray<uint8_t>& aEchConfig) {
+ neqo_http3conn_set_ech_config(this, &aEchConfig);
+ }
+
+ bool IsZeroRtt() { return neqo_http3conn_is_zero_rtt(this); }
+
+ void AddRef() { neqo_http3conn_addref(this); }
+ void Release() { neqo_http3conn_release(this); }
+
+ void GetStats(Http3Stats* aStats) {
+ return neqo_http3conn_get_stats(this, aStats);
+ }
+
+ nsresult CreateWebTransport(const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aHeaders,
+ uint64_t* aSessionId) {
+ return neqo_http3conn_webtransport_create_session(this, &aHost, &aPath,
+ &aHeaders, aSessionId);
+ }
+
+ nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
+ const nsACString& aMessage) {
+ return neqo_http3conn_webtransport_close_session(this, aSessionId, aError,
+ &aMessage);
+ }
+
+ nsresult CreateWebTransportStream(uint64_t aSessionId,
+ WebTransportStreamType aStreamType,
+ uint64_t* aStreamId) {
+ return neqo_http3conn_webtransport_create_stream(this, aSessionId,
+ aStreamType, aStreamId);
+ }
+
+ nsresult WebTransportSendDatagram(uint64_t aSessionId,
+ nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ return neqo_http3conn_webtransport_send_datagram(this, aSessionId, &aData,
+ aTrackingId);
+ }
+
+ nsresult WebTransportMaxDatagramSize(uint64_t aSessionId, uint64_t* aResult) {
+ return neqo_http3conn_webtransport_max_datagram_size(this, aSessionId,
+ aResult);
+ }
+
+ nsresult WebTransportSetSendOrder(uint64_t aSessionId,
+ Maybe<int64_t> aSendOrder) {
+ return neqo_http3conn_webtransport_set_sendorder(this, aSessionId,
+ aSendOrder.ptrOr(nullptr));
+ }
+
+ private:
+ NeqoHttp3Conn() = delete;
+ ~NeqoHttp3Conn() = delete;
+ NeqoHttp3Conn(const NeqoHttp3Conn&) = delete;
+ NeqoHttp3Conn& operator=(const NeqoHttp3Conn&) = delete;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/socket/neqo_glue/cbindgen.toml b/netwerk/socket/neqo_glue/cbindgen.toml
new file mode 100644
index 0000000000..312e1731e5
--- /dev/null
+++ b/netwerk/socket/neqo_glue/cbindgen.toml
@@ -0,0 +1,27 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
+ */
+
+namespace mozilla {
+namespace net {
+class NeqoHttp3Conn;
+union NetAddr;
+} // namespace net
+} // namespace mozilla
+ """
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "net"]
+includes = ["certt.h", "prerror.h"]
+
+[export]
+exclude = ["NeqoHttp3Conn", "NetAddr"]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[export.rename]
+"ThinVec" = "nsTArray"
diff --git a/netwerk/socket/neqo_glue/moz.build b/netwerk/socket/neqo_glue/moz.build
new file mode 100644
index 0000000000..b3123dde4f
--- /dev/null
+++ b/netwerk/socket/neqo_glue/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "NeqoHttp3Conn.h",
+]
+
+LOCAL_INCLUDES += [
+ "/security/manager/ssl",
+ "/security/nss/lib/ssl",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ CbindgenHeader("neqo_glue_ffi_generated.h", inputs=["/netwerk/socket/neqo_glue"])
+
+ EXPORTS.mozilla.net += [
+ "!neqo_glue_ffi_generated.h",
+ ]
diff --git a/netwerk/socket/neqo_glue/src/lib.rs b/netwerk/socket/neqo_glue/src/lib.rs
new file mode 100644
index 0000000000..6c86211ed5
--- /dev/null
+++ b/netwerk/socket/neqo_glue/src/lib.rs
@@ -0,0 +1,1439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#[cfg(not(windows))]
+use libc::{AF_INET, AF_INET6};
+use neqo_common::event::Provider;
+use neqo_common::{self as common, qlog::NeqoQlog, qwarn, Datagram, Header, IpTos, Role};
+use neqo_crypto::{init, PRErrorCode};
+use neqo_http3::{
+ features::extended_connect::SessionCloseReason, Error as Http3Error, Http3Client,
+ Http3ClientEvent, Http3Parameters, Http3State, Priority, WebTransportEvent,
+};
+use neqo_transport::{
+ stream_id::StreamType, CongestionControlAlgorithm, ConnectionParameters,
+ Error as TransportError, Output, RandomConnectionIdGenerator, StreamId, Version,
+};
+use nserror::*;
+use nsstring::*;
+use qlog::streamer::QlogStreamer;
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::convert::TryFrom;
+use std::convert::TryInto;
+use std::ffi::c_void;
+use std::fs::OpenOptions;
+use std::net::SocketAddr;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+use std::path::PathBuf;
+use std::ptr;
+use std::rc::Rc;
+use std::slice;
+use std::str;
+#[cfg(feature = "fuzzing")]
+use std::time::Duration;
+use std::time::{Duration, Instant};
+use thin_vec::ThinVec;
+use uuid::Uuid;
+#[cfg(windows)]
+use winapi::shared::ws2def::{AF_INET, AF_INET6};
+use xpcom::{AtomicRefcnt, RefCounted, RefPtr};
+
+#[repr(C)]
+pub struct NeqoHttp3Conn {
+ conn: Http3Client,
+ local_addr: SocketAddr,
+ refcnt: AtomicRefcnt,
+ last_output_time: Instant,
+ max_accumlated_time: Duration,
+}
+
+// Opaque interface to mozilla::net::NetAddr defined in DNS.h
+#[repr(C)]
+pub union NetAddr {
+ private: [u8; 0],
+}
+
+extern "C" {
+ pub fn moz_netaddr_get_family(arg: *const NetAddr) -> u16;
+ pub fn moz_netaddr_get_network_order_ip(arg: *const NetAddr) -> u32;
+ pub fn moz_netaddr_get_ipv6(arg: *const NetAddr) -> *const u8;
+ pub fn moz_netaddr_get_network_order_port(arg: *const NetAddr) -> u16;
+}
+
+fn netaddr_to_socket_addr(arg: *const NetAddr) -> Result<SocketAddr, nsresult> {
+ if arg == ptr::null() {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ unsafe {
+ let family = moz_netaddr_get_family(arg) as i32;
+ if family == AF_INET {
+ let port = u16::from_be(moz_netaddr_get_network_order_port(arg));
+ let ipv4 = Ipv4Addr::from(u32::from_be(moz_netaddr_get_network_order_ip(arg)));
+ return Ok(SocketAddr::new(IpAddr::V4(ipv4), port));
+ }
+
+ if family == AF_INET6 {
+ let port = u16::from_be(moz_netaddr_get_network_order_port(arg));
+ let ipv6_slice: [u8; 16] = slice::from_raw_parts(moz_netaddr_get_ipv6(arg), 16)
+ .try_into()
+ .expect("slice with incorrect length");
+ let ipv6 = Ipv6Addr::from(ipv6_slice);
+ return Ok(SocketAddr::new(IpAddr::V6(ipv6), port));
+ }
+ }
+
+ Err(NS_ERROR_UNEXPECTED)
+}
+
+fn get_current_or_last_output_time(last_output_time: &Instant) -> Instant {
+ max(*last_output_time, Instant::now())
+}
+
+type SendFunc = extern "C" fn(
+ context: *mut c_void,
+ addr_family: u16,
+ addr: *const u8,
+ port: u16,
+ data: *const u8,
+ size: u32,
+) -> nsresult;
+
+type SetTimerFunc = extern "C" fn(context: *mut c_void, timeout: u64);
+
+impl NeqoHttp3Conn {
+ fn new(
+ origin: &nsACString,
+ alpn: &nsACString,
+ local_addr: *const NetAddr,
+ remote_addr: *const NetAddr,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ max_data: u64,
+ max_stream_data: u64,
+ version_negotiation: bool,
+ webtransport: bool,
+ qlog_dir: &nsACString,
+ webtransport_datagram_size: u32,
+ max_accumlated_time_ms: u32,
+ ) -> Result<RefPtr<NeqoHttp3Conn>, nsresult> {
+ // Nss init.
+ init();
+
+ let origin_conv = str::from_utf8(origin).map_err(|_| NS_ERROR_INVALID_ARG)?;
+
+ let alpn_conv = str::from_utf8(alpn).map_err(|_| NS_ERROR_INVALID_ARG)?;
+
+ let local: SocketAddr = netaddr_to_socket_addr(local_addr)?;
+
+ let remote: SocketAddr = netaddr_to_socket_addr(remote_addr)?;
+
+ let quic_version = match alpn_conv {
+ "h3-32" => Version::Draft32,
+ "h3-31" => Version::Draft31,
+ "h3-30" => Version::Draft30,
+ "h3-29" => Version::Draft29,
+ "h3" => Version::Version1,
+ _ => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let version_list = if version_negotiation {
+ Version::all()
+ } else {
+ vec![quic_version]
+ };
+
+ let cc_algorithm = match static_prefs::pref!("network.http.http3.cc_algorithm") {
+ 0 => CongestionControlAlgorithm::NewReno,
+ 1 => CongestionControlAlgorithm::Cubic,
+ _ => {
+ // Unknown preferences; default to Cubic
+ CongestionControlAlgorithm::Cubic
+ }
+ };
+
+ #[allow(unused_mut)]
+ let mut params = ConnectionParameters::default()
+ .versions(quic_version, version_list)
+ .cc_algorithm(cc_algorithm)
+ .max_data(max_data)
+ .max_stream_data(StreamType::BiDi, false, max_stream_data)
+ .grease(static_prefs::pref!("security.tls.grease_http3_enable"));
+
+ // Set a short timeout when fuzzing.
+ #[cfg(feature = "fuzzing")]
+ if static_prefs::pref!("fuzzing.necko.http3") {
+ params = params.idle_timeout(Duration::from_millis(10));
+ }
+
+ if webtransport_datagram_size > 0 {
+ params = params.datagram_size(webtransport_datagram_size.into());
+ }
+
+ let http3_settings = Http3Parameters::default()
+ .max_table_size_encoder(max_table_size)
+ .max_table_size_decoder(max_table_size)
+ .max_blocked_streams(max_blocked_streams)
+ .max_concurrent_push_streams(0)
+ .connection_parameters(params)
+ .webtransport(webtransport)
+ .http3_datagram(webtransport);
+
+ let mut conn = match Http3Client::new(
+ origin_conv,
+ Rc::new(RefCell::new(RandomConnectionIdGenerator::new(3))),
+ local,
+ remote,
+ http3_settings,
+ Instant::now(),
+ ) {
+ Ok(c) => c,
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ if !qlog_dir.is_empty() {
+ let qlog_dir_conv = str::from_utf8(qlog_dir).map_err(|_| NS_ERROR_INVALID_ARG)?;
+ let mut qlog_path = PathBuf::from(qlog_dir_conv);
+ qlog_path.push(format!("{}_{}.qlog", origin, Uuid::new_v4()));
+
+ // Emit warnings but to not return an error if qlog initialization
+ // fails.
+ match OpenOptions::new()
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&qlog_path)
+ {
+ Err(_) => qwarn!("Could not open qlog path: {}", qlog_path.display()),
+ Ok(f) => {
+ let streamer = QlogStreamer::new(
+ qlog::QLOG_VERSION.to_string(),
+ Some("Firefox Client qlog".to_string()),
+ Some("Firefox Client qlog".to_string()),
+ None,
+ std::time::Instant::now(),
+ common::qlog::new_trace(Role::Client),
+ qlog::events::EventImportance::Base,
+ Box::new(f),
+ );
+
+ match NeqoQlog::enabled(streamer, &qlog_path) {
+ Err(_) => qwarn!("Could not write to qlog path: {}", qlog_path.display()),
+ Ok(nq) => conn.set_qlog(nq),
+ }
+ }
+ }
+ }
+
+ let conn = Box::into_raw(Box::new(NeqoHttp3Conn {
+ conn,
+ local_addr: local,
+ refcnt: unsafe { AtomicRefcnt::new() },
+ last_output_time: Instant::now(),
+ max_accumlated_time: Duration::from_millis(max_accumlated_time_ms.into()),
+ }));
+ unsafe { Ok(RefPtr::from_raw(conn).unwrap()) }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_addref(conn: &NeqoHttp3Conn) {
+ conn.refcnt.inc();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_release(conn: &NeqoHttp3Conn) {
+ let rc = conn.refcnt.dec();
+ if rc == 0 {
+ std::mem::drop(Box::from_raw(conn as *const _ as *mut NeqoHttp3Conn));
+ }
+}
+
+// xpcom::RefPtr support
+unsafe impl RefCounted for NeqoHttp3Conn {
+ unsafe fn addref(&self) {
+ neqo_http3conn_addref(self);
+ }
+ unsafe fn release(&self) {
+ neqo_http3conn_release(self);
+ }
+}
+
+// Allocate a new NeqoHttp3Conn object.
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_new(
+ origin: &nsACString,
+ alpn: &nsACString,
+ local_addr: *const NetAddr,
+ remote_addr: *const NetAddr,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ max_data: u64,
+ max_stream_data: u64,
+ version_negotiation: bool,
+ webtransport: bool,
+ qlog_dir: &nsACString,
+ webtransport_datagram_size: u32,
+ max_accumlated_time_ms: u32,
+ result: &mut *const NeqoHttp3Conn,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ match NeqoHttp3Conn::new(
+ origin,
+ alpn,
+ local_addr,
+ remote_addr,
+ max_table_size,
+ max_blocked_streams,
+ max_data,
+ max_stream_data,
+ version_negotiation,
+ webtransport,
+ qlog_dir,
+ webtransport_datagram_size,
+ max_accumlated_time_ms,
+ ) {
+ Ok(http3_conn) => {
+ http3_conn.forget(result);
+ NS_OK
+ }
+ Err(e) => e,
+ }
+}
+
+/* Process a packet.
+ * packet holds packet data.
+ */
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_process_input(
+ conn: &mut NeqoHttp3Conn,
+ remote_addr: *const NetAddr,
+ packet: *const ThinVec<u8>,
+) -> nsresult {
+ let remote = match netaddr_to_socket_addr(remote_addr) {
+ Ok(addr) => addr,
+ Err(result) => return result,
+ };
+ let d = Datagram::new(
+ remote,
+ conn.local_addr,
+ IpTos::default(),
+ None,
+ (*packet).to_vec(),
+ );
+ conn.conn
+ .process_input(&d, get_current_or_last_output_time(&conn.last_output_time));
+ return NS_OK;
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_process_output_and_send(
+ conn: &mut NeqoHttp3Conn,
+ context: *mut c_void,
+ send_func: SendFunc,
+ set_timer_func: SetTimerFunc,
+) -> nsresult {
+ let now = Instant::now();
+ if conn.last_output_time > now {
+ // The timer fired too early, so reschedule it.
+ // The 1ms of extra delay is not ideal, but this is a fail
+ set_timer_func(
+ context,
+ u64::try_from((conn.last_output_time - now + conn.max_accumlated_time).as_millis())
+ .unwrap(),
+ );
+ return NS_OK;
+ }
+
+ let mut accumulated_time = Duration::from_nanos(0);
+ loop {
+ conn.last_output_time = if accumulated_time.is_zero() {
+ Instant::now()
+ } else {
+ now + accumulated_time
+ };
+ match conn.conn.process_output(conn.last_output_time) {
+ Output::Datagram(dg) => {
+ let rv = match dg.destination().ip() {
+ IpAddr::V4(v4) => send_func(
+ context,
+ u16::try_from(AF_INET).unwrap(),
+ v4.octets().as_ptr(),
+ dg.destination().port(),
+ dg.as_ptr(),
+ u32::try_from(dg.len()).unwrap(),
+ ),
+ IpAddr::V6(v6) => send_func(
+ context,
+ u16::try_from(AF_INET6).unwrap(),
+ v6.octets().as_ptr(),
+ dg.destination().port(),
+ dg.as_ptr(),
+ u32::try_from(dg.len()).unwrap(),
+ ),
+ };
+ if rv != NS_OK {
+ return rv;
+ }
+ }
+ Output::Callback(to) => {
+ if to.is_zero() {
+ set_timer_func(context, 1);
+ break;
+ }
+
+ let timeout = min(to, Duration::from_nanos(u64::MAX - 1));
+ accumulated_time += timeout;
+ if accumulated_time >= conn.max_accumlated_time {
+ let mut timeout = accumulated_time.as_millis() as u64;
+ if timeout == 0 {
+ timeout = 1;
+ }
+ set_timer_func(context, timeout);
+ break;
+ }
+ }
+ Output::None => {
+ set_timer_func(context, std::u64::MAX);
+ break;
+ }
+ }
+ }
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_close(conn: &mut NeqoHttp3Conn, error: u64) {
+ conn.conn.close(
+ get_current_or_last_output_time(&conn.last_output_time),
+ error,
+ "",
+ );
+}
+
+fn is_excluded_header(name: &str) -> bool {
+ if (name == "connection")
+ || (name == "host")
+ || (name == "keep-alive")
+ || (name == "proxy-connection")
+ || (name == "te")
+ || (name == "transfer-encoding")
+ || (name == "upgrade")
+ || (name == "sec-websocket-key")
+ {
+ true
+ } else {
+ false
+ }
+}
+
+fn parse_headers(headers: &nsACString) -> Result<Vec<Header>, nsresult> {
+ let mut hdrs = Vec::new();
+ // this is only used for headers built by Firefox.
+ // Firefox supplies all headers already prepared for sending over http1.
+ // They need to be split into (String, String) pairs.
+ match str::from_utf8(headers) {
+ Err(_) => {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+ Ok(h) => {
+ for elem in h.split("\r\n").skip(1) {
+ if elem.starts_with(':') {
+ // colon headers are for http/2 and 3 and this is http/1
+ // input, so that is probably a smuggling attack of some
+ // kind.
+ continue;
+ }
+ if elem.len() == 0 {
+ continue;
+ }
+ let hdr_str: Vec<_> = elem.splitn(2, ":").collect();
+ let name = hdr_str[0].trim().to_lowercase();
+ if is_excluded_header(&name) {
+ continue;
+ }
+ let value = if hdr_str.len() > 1 {
+ String::from(hdr_str[1].trim())
+ } else {
+ String::new()
+ };
+
+ hdrs.push(Header::new(name, value));
+ }
+ }
+ }
+ Ok(hdrs)
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_fetch(
+ conn: &mut NeqoHttp3Conn,
+ method: &nsACString,
+ scheme: &nsACString,
+ host: &nsACString,
+ path: &nsACString,
+ headers: &nsACString,
+ stream_id: &mut u64,
+ urgency: u8,
+ incremental: bool,
+) -> nsresult {
+ let hdrs = match parse_headers(headers) {
+ Err(e) => {
+ return e;
+ }
+ Ok(h) => h,
+ };
+ let method_tmp = match str::from_utf8(method) {
+ Ok(m) => m,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let scheme_tmp = match str::from_utf8(scheme) {
+ Ok(s) => s,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let host_tmp = match str::from_utf8(host) {
+ Ok(h) => h,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let path_tmp = match str::from_utf8(path) {
+ Ok(p) => p,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ if urgency >= 8 {
+ return NS_ERROR_INVALID_ARG;
+ }
+ let priority = Priority::new(urgency, incremental);
+ match conn.conn.fetch(
+ get_current_or_last_output_time(&conn.last_output_time),
+ method_tmp,
+ &(scheme_tmp, host_tmp, path_tmp),
+ &hdrs,
+ priority,
+ ) {
+ Ok(id) => {
+ *stream_id = id.as_u64();
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_priority_update(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ urgency: u8,
+ incremental: bool,
+) -> nsresult {
+ if urgency >= 8 {
+ return NS_ERROR_INVALID_ARG;
+ }
+ let priority = Priority::new(urgency, incremental);
+ match conn
+ .conn
+ .priority_update(StreamId::from(stream_id), priority)
+ {
+ Ok(_) => NS_OK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_htttp3conn_send_request_body(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ buf: *const u8,
+ len: u32,
+ read: &mut u32,
+) -> nsresult {
+ let array = slice::from_raw_parts(buf, len as usize);
+ match conn.conn.send_data(StreamId::from(stream_id), array) {
+ Ok(amount) => {
+ *read = u32::try_from(amount).unwrap();
+ if amount == 0 {
+ NS_BASE_STREAM_WOULD_BLOCK
+ } else {
+ NS_OK
+ }
+ }
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+fn crypto_error_code(err: neqo_crypto::Error) -> u64 {
+ match err {
+ neqo_crypto::Error::AeadError => 1,
+ neqo_crypto::Error::CertificateLoading => 2,
+ neqo_crypto::Error::CreateSslSocket => 3,
+ neqo_crypto::Error::HkdfError => 4,
+ neqo_crypto::Error::InternalError => 5,
+ neqo_crypto::Error::IntegerOverflow => 6,
+ neqo_crypto::Error::InvalidEpoch => 7,
+ neqo_crypto::Error::MixedHandshakeMethod => 8,
+ neqo_crypto::Error::NoDataAvailable => 9,
+ neqo_crypto::Error::NssError { .. } => 10,
+ neqo_crypto::Error::OverrunError => 11,
+ neqo_crypto::Error::SelfEncryptFailure => 12,
+ neqo_crypto::Error::TimeTravelError => 13,
+ neqo_crypto::Error::UnsupportedCipher => 14,
+ neqo_crypto::Error::UnsupportedVersion => 15,
+ neqo_crypto::Error::StringError => 16,
+ neqo_crypto::Error::EchRetry(_) => 17,
+ neqo_crypto::Error::CipherInitFailure => 18,
+ }
+}
+
+// This is only used for telemetry. Therefore we only return error code
+// numbers and do not label them. Recording telemetry is easier with a
+// number.
+#[repr(C)]
+pub enum CloseError {
+ TransportInternalError,
+ TransportInternalErrorOther(u16),
+ TransportError(u64),
+ CryptoError(u64),
+ CryptoAlert(u8),
+ PeerAppError(u64),
+ PeerError(u64),
+ AppError(u64),
+ EchRetry,
+}
+
+impl From<TransportError> for CloseError {
+ fn from(error: TransportError) -> CloseError {
+ match error {
+ TransportError::InternalError => CloseError::TransportInternalError,
+ TransportError::CryptoError(neqo_crypto::Error::EchRetry(_)) => CloseError::EchRetry,
+ TransportError::CryptoError(c) => CloseError::CryptoError(crypto_error_code(c)),
+ TransportError::CryptoAlert(c) => CloseError::CryptoAlert(c),
+ TransportError::PeerApplicationError(c) => CloseError::PeerAppError(c),
+ TransportError::PeerError(c) => CloseError::PeerError(c),
+ TransportError::NoError
+ | TransportError::IdleTimeout
+ | TransportError::ConnectionRefused
+ | TransportError::FlowControlError
+ | TransportError::StreamLimitError
+ | TransportError::StreamStateError
+ | TransportError::FinalSizeError
+ | TransportError::FrameEncodingError
+ | TransportError::TransportParameterError
+ | TransportError::ProtocolViolation
+ | TransportError::InvalidToken
+ | TransportError::KeysExhausted
+ | TransportError::ApplicationError
+ | TransportError::NoAvailablePath
+ | TransportError::CryptoBufferExceeded => CloseError::TransportError(error.code()),
+ TransportError::EchRetry(_) => CloseError::EchRetry,
+ TransportError::AckedUnsentPacket => CloseError::TransportInternalErrorOther(0),
+ TransportError::ConnectionIdLimitExceeded => CloseError::TransportInternalErrorOther(1),
+ TransportError::ConnectionIdsExhausted => CloseError::TransportInternalErrorOther(2),
+ TransportError::ConnectionState => CloseError::TransportInternalErrorOther(3),
+ TransportError::DecodingFrame => CloseError::TransportInternalErrorOther(4),
+ TransportError::DecryptError => CloseError::TransportInternalErrorOther(5),
+ TransportError::HandshakeFailed => CloseError::TransportInternalErrorOther(6),
+ TransportError::IntegerOverflow => CloseError::TransportInternalErrorOther(7),
+ TransportError::InvalidInput => CloseError::TransportInternalErrorOther(8),
+ TransportError::InvalidMigration => CloseError::TransportInternalErrorOther(9),
+ TransportError::InvalidPacket => CloseError::TransportInternalErrorOther(10),
+ TransportError::InvalidResumptionToken => CloseError::TransportInternalErrorOther(11),
+ TransportError::InvalidRetry => CloseError::TransportInternalErrorOther(12),
+ TransportError::InvalidStreamId => CloseError::TransportInternalErrorOther(13),
+ TransportError::KeysDiscarded(_) => CloseError::TransportInternalErrorOther(14),
+ TransportError::KeysPending(_) => CloseError::TransportInternalErrorOther(15),
+ TransportError::KeyUpdateBlocked => CloseError::TransportInternalErrorOther(16),
+ TransportError::NoMoreData => CloseError::TransportInternalErrorOther(17),
+ TransportError::NotConnected => CloseError::TransportInternalErrorOther(18),
+ TransportError::PacketNumberOverlap => CloseError::TransportInternalErrorOther(19),
+ TransportError::StatelessReset => CloseError::TransportInternalErrorOther(20),
+ TransportError::TooMuchData => CloseError::TransportInternalErrorOther(21),
+ TransportError::UnexpectedMessage => CloseError::TransportInternalErrorOther(22),
+ TransportError::UnknownConnectionId => CloseError::TransportInternalErrorOther(23),
+ TransportError::UnknownFrameType => CloseError::TransportInternalErrorOther(24),
+ TransportError::VersionNegotiation => CloseError::TransportInternalErrorOther(25),
+ TransportError::WrongRole => CloseError::TransportInternalErrorOther(26),
+ TransportError::QlogError => CloseError::TransportInternalErrorOther(27),
+ TransportError::NotAvailable => CloseError::TransportInternalErrorOther(28),
+ TransportError::DisabledVersion => CloseError::TransportInternalErrorOther(29),
+ }
+ }
+}
+
+impl From<neqo_transport::ConnectionError> for CloseError {
+ fn from(error: neqo_transport::ConnectionError) -> CloseError {
+ match error {
+ neqo_transport::ConnectionError::Transport(c) => c.into(),
+ neqo_transport::ConnectionError::Application(c) => CloseError::AppError(c),
+ }
+ }
+}
+
+// Reset a stream with streamId.
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_cancel_fetch(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ error: u64,
+) -> nsresult {
+ match conn.conn.cancel_fetch(StreamId::from(stream_id), error) {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+// Reset a stream with streamId.
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_reset_stream(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ error: u64,
+) -> nsresult {
+ match conn
+ .conn
+ .stream_reset_send(StreamId::from(stream_id), error)
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_stream_stop_sending(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ error: u64,
+) -> nsresult {
+ match conn
+ .conn
+ .stream_stop_sending(StreamId::from(stream_id), error)
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+// Close sending side of a stream with stream_id
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_close_stream(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+) -> nsresult {
+ match conn.conn.stream_close_send(StreamId::from(stream_id)) {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+// WebTransport streams can be unidirectional and bidirectional.
+// It is mapped to and from neqo's StreamType enum.
+#[repr(C)]
+pub enum WebTransportStreamType {
+ BiDi,
+ UniDi,
+}
+
+impl From<StreamType> for WebTransportStreamType {
+ fn from(t: StreamType) -> WebTransportStreamType {
+ match t {
+ StreamType::BiDi => WebTransportStreamType::BiDi,
+ StreamType::UniDi => WebTransportStreamType::UniDi,
+ }
+ }
+}
+
+impl From<WebTransportStreamType> for StreamType {
+ fn from(t: WebTransportStreamType) -> StreamType {
+ match t {
+ WebTransportStreamType::BiDi => StreamType::BiDi,
+ WebTransportStreamType::UniDi => StreamType::UniDi,
+ }
+ }
+}
+
+#[repr(C)]
+pub enum SessionCloseReasonExternal {
+ Error(u64),
+ Status(u16),
+ Clean(u32),
+}
+
+impl SessionCloseReasonExternal {
+ fn new(reason: SessionCloseReason, data: &mut ThinVec<u8>) -> SessionCloseReasonExternal {
+ match reason {
+ SessionCloseReason::Error(e) => SessionCloseReasonExternal::Error(e),
+ SessionCloseReason::Status(s) => SessionCloseReasonExternal::Status(s),
+ SessionCloseReason::Clean { error, message } => {
+ data.extend_from_slice(message.as_ref());
+ SessionCloseReasonExternal::Clean(error)
+ }
+ }
+ }
+}
+
+#[repr(C)]
+pub enum WebTransportEventExternal {
+ Negotiated(bool),
+ Session(u64),
+ SessionClosed {
+ stream_id: u64,
+ reason: SessionCloseReasonExternal,
+ },
+ NewStream {
+ stream_id: u64,
+ stream_type: WebTransportStreamType,
+ session_id: u64,
+ },
+ Datagram {
+ session_id: u64,
+ },
+}
+
+impl WebTransportEventExternal {
+ fn new(event: WebTransportEvent, data: &mut ThinVec<u8>) -> WebTransportEventExternal {
+ match event {
+ WebTransportEvent::Negotiated(n) => WebTransportEventExternal::Negotiated(n),
+ WebTransportEvent::Session {
+ stream_id,
+ status,
+ headers: _,
+ } => {
+ data.extend_from_slice(b"HTTP/3 ");
+ data.extend_from_slice(&status.to_string().as_bytes());
+ data.extend_from_slice(b"\r\n\r\n");
+ WebTransportEventExternal::Session(stream_id.as_u64())
+ }
+ WebTransportEvent::SessionClosed {
+ stream_id,
+ reason,
+ headers: _,
+ } => match reason {
+ SessionCloseReason::Status(status) => {
+ data.extend_from_slice(b"HTTP/3 ");
+ data.extend_from_slice(&status.to_string().as_bytes());
+ data.extend_from_slice(b"\r\n\r\n");
+ WebTransportEventExternal::Session(stream_id.as_u64())
+ }
+ _ => WebTransportEventExternal::SessionClosed {
+ stream_id: stream_id.as_u64(),
+ reason: SessionCloseReasonExternal::new(reason, data),
+ },
+ },
+ WebTransportEvent::NewStream {
+ stream_id,
+ session_id,
+ } => WebTransportEventExternal::NewStream {
+ stream_id: stream_id.as_u64(),
+ stream_type: stream_id.stream_type().into(),
+ session_id: session_id.as_u64(),
+ },
+ WebTransportEvent::Datagram {
+ session_id,
+ datagram,
+ } => {
+ data.extend_from_slice(datagram.as_ref());
+ WebTransportEventExternal::Datagram {
+ session_id: session_id.as_u64(),
+ }
+ }
+ }
+ }
+}
+
+#[repr(C)]
+pub enum Http3Event {
+ /// A request stream has space for more data to be sent.
+ DataWritable {
+ stream_id: u64,
+ },
+ /// A server has send STOP_SENDING frame.
+ StopSending {
+ stream_id: u64,
+ error: u64,
+ },
+ HeaderReady {
+ stream_id: u64,
+ fin: bool,
+ interim: bool,
+ },
+ /// New bytes available for reading.
+ DataReadable {
+ stream_id: u64,
+ },
+ /// Peer reset the stream.
+ Reset {
+ stream_id: u64,
+ error: u64,
+ local: bool,
+ },
+ /// A PushPromise
+ PushPromise {
+ push_id: u64,
+ request_stream_id: u64,
+ },
+ /// A push response headers are ready.
+ PushHeaderReady {
+ push_id: u64,
+ fin: bool,
+ },
+ /// New bytes are available on a push stream for reading.
+ PushDataReadable {
+ push_id: u64,
+ },
+ /// A push has been canceled.
+ PushCanceled {
+ push_id: u64,
+ },
+ PushReset {
+ push_id: u64,
+ error: u64,
+ },
+ RequestsCreatable,
+ AuthenticationNeeded,
+ ZeroRttRejected,
+ ConnectionConnected,
+ GoawayReceived,
+ ConnectionClosing {
+ error: CloseError,
+ },
+ ConnectionClosed {
+ error: CloseError,
+ },
+ ResumptionToken {
+ expire_in: u64, // microseconds
+ },
+ EchFallbackAuthenticationNeeded,
+ WebTransport(WebTransportEventExternal),
+ NoEvent,
+}
+
+fn sanitize_header(mut y: Cow<[u8]>) -> Cow<[u8]> {
+ for i in 0..y.len() {
+ if matches!(y[i], b'\n' | b'\r' | b'\0') {
+ y.to_mut()[i] = b' ';
+ }
+ }
+ y
+}
+
+fn convert_h3_to_h1_headers(headers: Vec<Header>, ret_headers: &mut ThinVec<u8>) -> nsresult {
+ if headers.iter().filter(|&h| h.name() == ":status").count() != 1 {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ let status_val = headers
+ .iter()
+ .find(|&h| h.name() == ":status")
+ .expect("must be one")
+ .value();
+
+ ret_headers.extend_from_slice(b"HTTP/3 ");
+ ret_headers.extend_from_slice(status_val.as_bytes());
+ ret_headers.extend_from_slice(b"\r\n");
+
+ for hdr in headers.iter().filter(|&h| h.name() != ":status") {
+ ret_headers.extend_from_slice(&sanitize_header(Cow::from(hdr.name().as_bytes())));
+ ret_headers.extend_from_slice(b": ");
+ ret_headers.extend_from_slice(&sanitize_header(Cow::from(hdr.value().as_bytes())));
+ ret_headers.extend_from_slice(b"\r\n");
+ }
+ ret_headers.extend_from_slice(b"\r\n");
+ return NS_OK;
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_event(
+ conn: &mut NeqoHttp3Conn,
+ ret_event: &mut Http3Event,
+ data: &mut ThinVec<u8>,
+) -> nsresult {
+ while let Some(evt) = conn.conn.next_event() {
+ let fe = match evt {
+ Http3ClientEvent::DataWritable { stream_id } => Http3Event::DataWritable {
+ stream_id: stream_id.as_u64(),
+ },
+ Http3ClientEvent::StopSending { stream_id, error } => Http3Event::StopSending {
+ stream_id: stream_id.as_u64(),
+ error,
+ },
+ Http3ClientEvent::HeaderReady {
+ stream_id,
+ headers,
+ fin,
+ interim,
+ } => {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::HeaderReady {
+ stream_id: stream_id.as_u64(),
+ fin,
+ interim,
+ }
+ }
+ Http3ClientEvent::DataReadable { stream_id } => Http3Event::DataReadable {
+ stream_id: stream_id.as_u64(),
+ },
+ Http3ClientEvent::Reset {
+ stream_id,
+ error,
+ local,
+ } => Http3Event::Reset {
+ stream_id: stream_id.as_u64(),
+ error,
+ local,
+ },
+ Http3ClientEvent::PushPromise {
+ push_id,
+ request_stream_id,
+ headers,
+ } => {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::PushPromise {
+ push_id,
+ request_stream_id: request_stream_id.as_u64(),
+ }
+ }
+ Http3ClientEvent::PushHeaderReady {
+ push_id,
+ headers,
+ fin,
+ interim,
+ } => {
+ if interim {
+ Http3Event::NoEvent
+ } else {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::PushHeaderReady { push_id, fin }
+ }
+ }
+ Http3ClientEvent::PushDataReadable { push_id } => {
+ Http3Event::PushDataReadable { push_id }
+ }
+ Http3ClientEvent::PushCanceled { push_id } => Http3Event::PushCanceled { push_id },
+ Http3ClientEvent::PushReset { push_id, error } => {
+ Http3Event::PushReset { push_id, error }
+ }
+ Http3ClientEvent::RequestsCreatable => Http3Event::RequestsCreatable,
+ Http3ClientEvent::AuthenticationNeeded => Http3Event::AuthenticationNeeded,
+ Http3ClientEvent::ZeroRttRejected => Http3Event::ZeroRttRejected,
+ Http3ClientEvent::ResumptionToken(token) => {
+ // expiration_time time is Instant, transform it into microseconds it will
+ // be valid for. Necko code will add the value to PR_Now() to get the expiration
+ // time in PRTime.
+ if token.expiration_time() > Instant::now() {
+ let e = (token.expiration_time() - Instant::now()).as_micros();
+ if let Ok(expire_in) = u64::try_from(e) {
+ data.extend_from_slice(token.as_ref());
+ Http3Event::ResumptionToken { expire_in }
+ } else {
+ Http3Event::NoEvent
+ }
+ } else {
+ Http3Event::NoEvent
+ }
+ }
+ Http3ClientEvent::GoawayReceived => Http3Event::GoawayReceived,
+ Http3ClientEvent::StateChange(state) => match state {
+ Http3State::Connected => Http3Event::ConnectionConnected,
+ Http3State::Closing(error_code) => {
+ match error_code {
+ neqo_transport::ConnectionError::Transport(
+ TransportError::CryptoError(neqo_crypto::Error::EchRetry(ref c)),
+ )
+ | neqo_transport::ConnectionError::Transport(TransportError::EchRetry(
+ ref c,
+ )) => {
+ data.extend_from_slice(c.as_ref());
+ }
+ _ => {}
+ }
+ Http3Event::ConnectionClosing {
+ error: error_code.into(),
+ }
+ }
+ Http3State::Closed(error_code) => {
+ match error_code {
+ neqo_transport::ConnectionError::Transport(
+ TransportError::CryptoError(neqo_crypto::Error::EchRetry(ref c)),
+ )
+ | neqo_transport::ConnectionError::Transport(TransportError::EchRetry(
+ ref c,
+ )) => {
+ data.extend_from_slice(c.as_ref());
+ }
+ _ => {}
+ }
+ Http3Event::ConnectionClosed {
+ error: error_code.into(),
+ }
+ }
+ _ => Http3Event::NoEvent,
+ },
+ Http3ClientEvent::EchFallbackAuthenticationNeeded { public_name } => {
+ data.extend_from_slice(public_name.as_ref());
+ Http3Event::EchFallbackAuthenticationNeeded
+ }
+ Http3ClientEvent::WebTransport(e) => {
+ Http3Event::WebTransport(WebTransportEventExternal::new(e, data))
+ }
+ };
+
+ if !matches!(fe, Http3Event::NoEvent) {
+ *ret_event = fe;
+ return NS_OK;
+ }
+ }
+
+ *ret_event = Http3Event::NoEvent;
+ NS_OK
+}
+
+// Read response data into buf.
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_read_response_data(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ buf: *mut u8,
+ len: u32,
+ read: &mut u32,
+ fin: &mut bool,
+) -> nsresult {
+ let array = slice::from_raw_parts_mut(buf, len as usize);
+ match conn.conn.read_data(
+ get_current_or_last_output_time(&conn.last_output_time),
+ StreamId::from(stream_id),
+ &mut array[..],
+ ) {
+ Ok((amount, fin_recvd)) => {
+ *read = u32::try_from(amount).unwrap();
+ *fin = fin_recvd;
+ if (amount == 0) && !fin_recvd {
+ NS_BASE_STREAM_WOULD_BLOCK
+ } else {
+ NS_OK
+ }
+ }
+ Err(Http3Error::InvalidStreamId)
+ | Err(Http3Error::TransportError(TransportError::NoMoreData)) => NS_ERROR_INVALID_ARG,
+ Err(_) => NS_ERROR_NET_HTTP3_PROTOCOL_ERROR,
+ }
+}
+
+#[repr(C)]
+pub struct NeqoSecretInfo {
+ set: bool,
+ version: u16,
+ cipher: u16,
+ group: u16,
+ resumed: bool,
+ early_data: bool,
+ alpn: nsCString,
+ signature_scheme: u16,
+ ech_accepted: bool,
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_tls_info(
+ conn: &mut NeqoHttp3Conn,
+ sec_info: &mut NeqoSecretInfo,
+) -> nsresult {
+ match conn.conn.tls_info() {
+ Some(info) => {
+ sec_info.set = true;
+ sec_info.version = info.version();
+ sec_info.cipher = info.cipher_suite();
+ sec_info.group = info.key_exchange();
+ sec_info.resumed = info.resumed();
+ sec_info.early_data = info.early_data_accepted();
+ sec_info.alpn = match info.alpn() {
+ Some(a) => nsCString::from(a),
+ None => nsCString::new(),
+ };
+ sec_info.signature_scheme = info.signature_scheme();
+ sec_info.ech_accepted = info.ech_accepted();
+ NS_OK
+ }
+ None => NS_ERROR_NOT_AVAILABLE,
+ }
+}
+
+#[repr(C)]
+pub struct NeqoCertificateInfo {
+ certs: ThinVec<ThinVec<u8>>,
+ stapled_ocsp_responses_present: bool,
+ stapled_ocsp_responses: ThinVec<ThinVec<u8>>,
+ signed_cert_timestamp_present: bool,
+ signed_cert_timestamp: ThinVec<u8>,
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_peer_certificate_info(
+ conn: &mut NeqoHttp3Conn,
+ neqo_certs_info: &mut NeqoCertificateInfo,
+) -> nsresult {
+ let mut certs_info = match conn.conn.peer_certificate() {
+ Some(certs) => certs,
+ None => return NS_ERROR_NOT_AVAILABLE,
+ };
+
+ neqo_certs_info.certs = certs_info
+ .map(|cert| cert.iter().cloned().collect())
+ .collect();
+
+ match &mut certs_info.stapled_ocsp_responses() {
+ Some(ocsp_val) => {
+ neqo_certs_info.stapled_ocsp_responses_present = true;
+ neqo_certs_info.stapled_ocsp_responses = ocsp_val
+ .iter()
+ .map(|ocsp| ocsp.iter().cloned().collect())
+ .collect();
+ }
+ None => {
+ neqo_certs_info.stapled_ocsp_responses_present = false;
+ }
+ };
+
+ match certs_info.signed_cert_timestamp() {
+ Some(sct_val) => {
+ neqo_certs_info.signed_cert_timestamp_present = true;
+ neqo_certs_info
+ .signed_cert_timestamp
+ .extend_from_slice(sct_val);
+ }
+ None => {
+ neqo_certs_info.signed_cert_timestamp_present = false;
+ }
+ };
+
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_authenticated(conn: &mut NeqoHttp3Conn, error: PRErrorCode) {
+ conn.conn.authenticated(
+ error.into(),
+ get_current_or_last_output_time(&conn.last_output_time),
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_set_resumption_token(
+ conn: &mut NeqoHttp3Conn,
+ token: &mut ThinVec<u8>,
+) {
+ let _ = conn.conn.enable_resumption(
+ get_current_or_last_output_time(&conn.last_output_time),
+ token,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_set_ech_config(
+ conn: &mut NeqoHttp3Conn,
+ ech_config: &mut ThinVec<u8>,
+) {
+ let _ = conn.conn.enable_ech(ech_config);
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_is_zero_rtt(conn: &mut NeqoHttp3Conn) -> bool {
+ conn.conn.state() == Http3State::ZeroRtt
+}
+
+#[repr(C)]
+#[derive(Default)]
+pub struct Http3Stats {
+ /// Total packets received, including all the bad ones.
+ pub packets_rx: usize,
+ /// Duplicate packets received.
+ pub dups_rx: usize,
+ /// Dropped packets or dropped garbage.
+ pub dropped_rx: usize,
+ /// The number of packet that were saved for later processing.
+ pub saved_datagrams: usize,
+ /// Total packets sent.
+ pub packets_tx: usize,
+ /// Total number of packets that are declared lost.
+ pub lost: usize,
+ /// Late acknowledgments, for packets that were declared lost already.
+ pub late_ack: usize,
+ /// Acknowledgments for packets that contained data that was marked
+ /// for retransmission when the PTO timer popped.
+ pub pto_ack: usize,
+ /// Count PTOs. Single PTOs, 2 PTOs in a row, 3 PTOs in row, etc. are counted
+ /// separately.
+ pub pto_counts: [usize; 16],
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_get_stats(conn: &mut NeqoHttp3Conn, stats: &mut Http3Stats) {
+ let t_stats = conn.conn.transport_stats();
+ stats.packets_rx = t_stats.packets_rx;
+ stats.dups_rx = t_stats.dups_rx;
+ stats.dropped_rx = t_stats.dropped_rx;
+ stats.saved_datagrams = t_stats.saved_datagrams;
+ stats.packets_tx = t_stats.packets_tx;
+ stats.lost = t_stats.lost;
+ stats.late_ack = t_stats.late_ack;
+ stats.pto_ack = t_stats.pto_ack;
+ stats.pto_counts = t_stats.pto_counts;
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_create_session(
+ conn: &mut NeqoHttp3Conn,
+ host: &nsACString,
+ path: &nsACString,
+ headers: &nsACString,
+ stream_id: &mut u64,
+) -> nsresult {
+ let hdrs = match parse_headers(headers) {
+ Err(e) => {
+ return e;
+ }
+ Ok(h) => h,
+ };
+ let host_tmp = match str::from_utf8(host) {
+ Ok(h) => h,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let path_tmp = match str::from_utf8(path) {
+ Ok(p) => p,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+
+ match conn.conn.webtransport_create_session(
+ get_current_or_last_output_time(&conn.last_output_time),
+ &("https", host_tmp, path_tmp),
+ &hdrs,
+ ) {
+ Ok(id) => {
+ *stream_id = id.as_u64();
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_close_session(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ error: u32,
+ message: &nsACString,
+) -> nsresult {
+ let message_tmp = match str::from_utf8(message) {
+ Ok(p) => p,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ match conn
+ .conn
+ .webtransport_close_session(StreamId::from(session_id), error, message_tmp)
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_create_stream(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ stream_type: WebTransportStreamType,
+ stream_id: &mut u64,
+) -> nsresult {
+ match conn
+ .conn
+ .webtransport_create_stream(StreamId::from(session_id), stream_type.into())
+ {
+ Ok(id) => {
+ *stream_id = id.as_u64();
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_send_datagram(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ data: &mut ThinVec<u8>,
+ tracking_id: u64,
+) -> nsresult {
+ let id = if tracking_id == 0 {
+ None
+ } else {
+ Some(tracking_id)
+ };
+ match conn
+ .conn
+ .webtransport_send_datagram(StreamId::from(session_id), data, id)
+ {
+ Ok(()) => NS_OK,
+ Err(Http3Error::TransportError(TransportError::TooMuchData)) => NS_ERROR_NOT_AVAILABLE,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_max_datagram_size(
+ conn: &mut NeqoHttp3Conn,
+ session_id: u64,
+ result: &mut u64,
+) -> nsresult {
+ match conn
+ .conn
+ .webtransport_max_datagram_size(StreamId::from(session_id))
+ {
+ Ok(size) => {
+ *result = size;
+ NS_OK
+ }
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_webtransport_set_sendorder(
+ conn: &mut NeqoHttp3Conn,
+ stream_id: u64,
+ sendorder: *const i64,
+) -> nsresult {
+ unsafe {
+ match conn
+ .conn
+ .webtransport_set_sendorder(StreamId::from(stream_id), sendorder.as_ref().copied())
+ {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+ }
+}