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