summaryrefslogtreecommitdiffstats
path: root/netwerk/socket/neqo_glue
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /netwerk/socket/neqo_glue
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/socket/neqo_glue')
-rw-r--r--netwerk/socket/neqo_glue/Cargo.toml26
-rw-r--r--netwerk/socket/neqo_glue/NeqoHttp3Conn.h107
-rw-r--r--netwerk/socket/neqo_glue/cbindgen.toml26
-rw-r--r--netwerk/socket/neqo_glue/moz.build21
-rw-r--r--netwerk/socket/neqo_glue/src/lib.rs816
5 files changed, 996 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..84ef23841c
--- /dev/null
+++ b/netwerk/socket/neqo_glue/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "neqo_glue"
+version = "0.1.0"
+authors = ["Dragana Damjanovic <dd.mozilla@gmail.com>"]
+edition = "2018"
+
+[lib]
+name = "neqo_glue"
+
+[dependencies]
+neqo-http3 = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-transport = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-common = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-qpack = { tag = "v0.4.19", 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 = "0.4.0"
+
+[dependencies.neqo-crypto]
+tag = "v0.4.19"
+git = "https://github.com/mozilla/neqo"
+default-features = false
+features = ["gecko"]
diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
new file mode 100644
index 0000000000..c2ba79fac2
--- /dev/null
+++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
@@ -0,0 +1,107 @@
+/* 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 "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 nsACString& aLocalAddr,
+ const nsACString& aRemoteAddr, uint32_t aMaxTableSize,
+ uint16_t aMaxBlockedStreams, const nsACString& aQlogDir,
+ NeqoHttp3Conn** aConn) {
+ return neqo_http3conn_new(&aOrigin, &aAlpn, &aLocalAddr, &aRemoteAddr,
+ aMaxTableSize, aMaxBlockedStreams, &aQlogDir,
+ (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);
+ }
+
+ void ProcessInput(uint8_t* aPacket, uint32_t aLen) {
+ neqo_http3conn_process_input(this, aPacket, aLen);
+ }
+
+ uint64_t ProcessOutput() { return neqo_http3conn_process_output(this); }
+
+ bool HasDataToSend() { return neqo_http3conn_has_data_to_send(this); }
+
+ nsresult GetDataToSend(nsTArray<uint8_t>& aData) {
+ aData.TruncateLength(0);
+ return neqo_http3conn_get_data_to_send(this, &aData);
+ }
+
+ 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) {
+ return neqo_http3conn_fetch(this, &aMethod, &aScheme, &aHost, &aPath,
+ &aHeaders, aStreamId);
+ }
+
+ 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 ResetStream(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_reset_stream(this, aStreamId, aError);
+ }
+
+ void SetResumptionToken(nsTArray<uint8_t>& aToken) {
+ neqo_http3conn_set_resumption_token(this, &aToken);
+ }
+
+ bool IsZeroRtt() { return neqo_http3conn_is_zero_rtt(this); }
+
+ nsrefcnt AddRef() { return neqo_http3conn_addref(this); }
+ nsrefcnt Release() { return neqo_http3conn_release(this); }
+
+ void GetStats(Http3Stats* aStats) {
+ return neqo_http3conn_get_stats(this, aStats);
+ }
+
+ 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..406c8dbc66
--- /dev/null
+++ b/netwerk/socket/neqo_glue/cbindgen.toml
@@ -0,0 +1,26 @@
+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;
+} // 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"]
+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..9d04aba3f5
--- /dev/null
+++ b/netwerk/socket/neqo_glue/src/lib.rs
@@ -0,0 +1,816 @@
+/* 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/. */
+
+use neqo_common::event::Provider;
+use neqo_common::{self as common, qlog::NeqoQlog, qwarn, Datagram, Role};
+use neqo_crypto::{init, PRErrorCode};
+use neqo_http3::Error as Http3Error;
+use neqo_http3::{Http3Client, Http3ClientEvent, Http3Parameters, Http3State};
+use neqo_qpack::QpackSettings;
+use neqo_transport::{
+ ConnectionParameters, Error as TransportError, FixedConnectionIdManager, Output, QuicVersion,
+};
+use nserror::*;
+use nsstring::*;
+use qlog::QlogStreamer;
+use std::cell::RefCell;
+use std::convert::TryFrom;
+use std::fs::OpenOptions;
+use std::net::SocketAddr;
+use std::path::PathBuf;
+use std::ptr;
+use std::rc::Rc;
+use std::slice;
+use std::str;
+use std::time::Instant;
+use thin_vec::ThinVec;
+use xpcom::{interfaces::nsrefcnt, AtomicRefcnt, RefCounted, RefPtr};
+
+#[repr(C)]
+pub struct NeqoHttp3Conn {
+ conn: Http3Client,
+ local_addr: SocketAddr,
+ remote_addr: SocketAddr,
+ refcnt: AtomicRefcnt,
+ packets_to_send: Vec<Datagram>,
+}
+
+impl NeqoHttp3Conn {
+ fn new(
+ origin: &nsACString,
+ alpn: &nsACString,
+ local_addr: &nsACString,
+ remote_addr: &nsACString,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ qlog_dir: &nsACString,
+ ) -> 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 = match str::from_utf8(local_addr) {
+ Ok(s) => match s.parse() {
+ Ok(addr) => addr,
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ },
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let remote: SocketAddr = match str::from_utf8(remote_addr) {
+ Ok(s) => match s.parse() {
+ Ok(addr) => addr,
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ },
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let http3_settings = Http3Parameters {
+ qpack_settings: QpackSettings {
+ max_table_size_encoder: max_table_size,
+ max_table_size_decoder: max_table_size,
+ max_blocked_streams,
+ },
+ max_concurrent_push_streams: 0,
+ };
+
+ let quic_version = match alpn_conv {
+ "h3-30" => QuicVersion::Draft30,
+ "h3-29" => QuicVersion::Draft29,
+ "h3-28" => QuicVersion::Draft28,
+ "h3-27" => QuicVersion::Draft27,
+ _ => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let mut conn = match Http3Client::new(
+ origin_conv,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(3))),
+ local,
+ remote,
+ &ConnectionParameters::default().quic_version(quic_version),
+ &http3_settings,
+ ) {
+ 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));
+
+ // 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),
+ 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,
+ remote_addr: remote,
+ refcnt: unsafe { AtomicRefcnt::new() },
+ packets_to_send: Vec::new(),
+ }));
+ unsafe { Ok(RefPtr::from_raw(conn).unwrap()) }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_addref(conn: &NeqoHttp3Conn) -> nsrefcnt {
+ conn.refcnt.inc()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_release(conn: &NeqoHttp3Conn) -> nsrefcnt {
+ let rc = conn.refcnt.dec();
+ if rc == 0 {
+ Box::from_raw(conn as *const _ as *mut NeqoHttp3Conn);
+ }
+ rc
+}
+
+// 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: &nsACString,
+ remote_addr: &nsACString,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ qlog_dir: &nsACString,
+ result: &mut *const NeqoHttp3Conn,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ match NeqoHttp3Conn::new(
+ origin,
+ alpn,
+ local_addr,
+ remote_addr,
+ max_table_size,
+ max_blocked_streams,
+ qlog_dir,
+ ) {
+ Ok(http3_conn) => {
+ http3_conn.forget(result);
+ NS_OK
+ }
+ Err(e) => e,
+ }
+}
+
+/* Process a packet.
+ * packet holds packet data.
+ */
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_process_input(
+ conn: &mut NeqoHttp3Conn,
+ packet: *const u8,
+ len: u32,
+) {
+ let array = unsafe { slice::from_raw_parts(packet, len as usize) };
+ conn.conn.process_input(
+ Datagram::new(conn.remote_addr, conn.local_addr, array.to_vec()),
+ Instant::now(),
+ );
+}
+
+/* Process output and store data to be sent into conn.packets_to_send.
+ * neqo_http3conn_get_data_to_send will be called to pick up this data.
+ */
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_process_output(conn: &mut NeqoHttp3Conn) -> u64 {
+ loop {
+ let out = conn.conn.process_output(Instant::now());
+ match out {
+ Output::Datagram(dg) => {
+ conn.packets_to_send.push(dg);
+ }
+ Output::Callback(to) => {
+ let timeout = to.as_millis() as u64;
+ // Necko resolution is in milliseconds whereas neqo resolution
+ // is in nanoseconds. If we called process_output too soon due
+ // to this difference, we might do few unnecessary loops until
+ // we waste the remaining time. To avoid it, we return 1ms when
+ // the timeout is less than 1ms.
+ if timeout == 0 {
+ break 1;
+ }
+ break timeout;
+ }
+ Output::None => break std::u64::MAX,
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_has_data_to_send(conn: &mut NeqoHttp3Conn) -> bool {
+ !conn.packets_to_send.is_empty()
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_get_data_to_send(
+ conn: &mut NeqoHttp3Conn,
+ packet: &mut ThinVec<u8>,
+) -> nsresult {
+ match conn.packets_to_send.pop() {
+ None => NS_BASE_STREAM_WOULD_BLOCK,
+ Some(d) => {
+ packet.extend_from_slice(&d);
+ NS_OK
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_close(conn: &mut NeqoHttp3Conn, error: u64) {
+ conn.conn.close(Instant::now(), 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
+ }
+}
+
+#[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,
+) -> 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 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((name, value));
+ }
+ }
+ }
+
+ 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;
+ }
+ };
+ match conn.conn.fetch(
+ Instant::now(),
+ method_tmp,
+ scheme_tmp,
+ host_tmp,
+ path_tmp,
+ &hdrs,
+ ) {
+ Ok(id) => {
+ *stream_id = id;
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub 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 = unsafe { slice::from_raw_parts(buf, len as usize) };
+ match conn.conn.send_request_body(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,
+ }
+}
+
+// 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 {
+ QuicTransportError(u64),
+ Http3AppError(u64),
+}
+
+impl From<neqo_transport::CloseError> for CloseError {
+ fn from(error: neqo_transport::CloseError) -> CloseError {
+ match error {
+ neqo_transport::CloseError::Transport(c) => CloseError::QuicTransportError(c),
+ neqo_transport::CloseError::Application(c) => CloseError::Http3AppError(c),
+ }
+ }
+}
+
+// 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(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(stream_id) {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[repr(C)]
+pub enum Http3Event {
+ /// A request stream has space for more data to be send.
+ DataWritable {
+ stream_id: u64,
+ },
+ /// A server has send STOP_SENDING frame.
+ StopSending {
+ stream_id: u64,
+ error: u64,
+ },
+ HeaderReady {
+ stream_id: u64,
+ fin: 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
+ },
+ NoEvent,
+}
+
+fn convert_h3_to_h1_headers(
+ headers: Vec<(String, String)>,
+ ret_headers: &mut ThinVec<u8>,
+) -> nsresult {
+ if headers.iter().filter(|(k, _)| k == ":status").count() != 1 {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ let (_, status_val) = headers
+ .iter()
+ .find(|(k, _)| k == ":status")
+ .expect("must be one");
+
+ 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 (key, value) in headers.iter().filter(|(k, _)| k != ":status") {
+ ret_headers.extend_from_slice(key.as_bytes());
+ ret_headers.extend_from_slice(b": ");
+ ret_headers.extend_from_slice(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 },
+ Http3ClientEvent::StopSending { stream_id, error } => {
+ Http3Event::StopSending { stream_id, error }
+ }
+ Http3ClientEvent::HeaderReady {
+ stream_id,
+ headers,
+ fin,
+ interim,
+ } => {
+ if interim {
+ // This are 1xx responses and they are ignored.
+ Http3Event::NoEvent
+ } else {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::HeaderReady { stream_id, fin }
+ }
+ }
+ Http3ClientEvent::DataReadable { stream_id } => Http3Event::DataReadable { stream_id },
+ Http3ClientEvent::Reset {
+ stream_id,
+ error,
+ local,
+ } => Http3Event::Reset {
+ stream_id,
+ 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,
+ }
+ }
+ 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) => Http3Event::ConnectionClosing {
+ error: error_code.into(),
+ },
+ Http3State::Closed(error_code) => Http3Event::ConnectionClosed {
+ error: error_code.into(),
+ },
+ _ => Http3Event::NoEvent,
+ },
+ };
+
+ 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 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 = unsafe { slice::from_raw_parts_mut(buf, len as usize) };
+ match conn
+ .conn
+ .read_response_data(Instant::now(), 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,
+}
+
+#[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();
+ 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(), Instant::now());
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_set_resumption_token(
+ conn: &mut NeqoHttp3Conn,
+ token: &mut ThinVec<u8>,
+) {
+ let _ = conn.conn.enable_resumption(Instant::now(), token);
+}
+
+#[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;
+}