diff options
Diffstat (limited to 'netwerk/socket/neqo_glue')
-rw-r--r-- | netwerk/socket/neqo_glue/Cargo.toml | 26 | ||||
-rw-r--r-- | netwerk/socket/neqo_glue/NeqoHttp3Conn.h | 107 | ||||
-rw-r--r-- | netwerk/socket/neqo_glue/cbindgen.toml | 26 | ||||
-rw-r--r-- | netwerk/socket/neqo_glue/moz.build | 21 | ||||
-rw-r--r-- | netwerk/socket/neqo_glue/src/lib.rs | 816 |
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; +} |