#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub use oxiquic_core::{
ConnectionId, ConnectionStats, Direction, FrameType, Initiator, OxiQuicError, PacketType,
QuicVersion, StreamId, TransportErrorCode, TransportParams,
};
#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
#[cfg(feature = "transport")]
pub use oxiquic_transport::{
ClientEndpoint, CongestionAlgorithm, Connection, ConnectionState, QuicConnection, Role,
ServerEndpoint, TransportConfig, ZeroRttAccepted,
};
#[cfg_attr(docsrs, doc(cfg(feature = "h3")))]
#[cfg(feature = "h3")]
pub use oxiquic_h3::{
accept_h3_server, connect_h3_client, H3Client, H3ClientBuilder, H3Connection, H3Error,
H3ErrorCode, H3Request, H3RequestContext, H3Responder, H3Response, H3Server, H3ServerBuilder,
H3ServerEndpoint, H3Settings, RequestStream,
};
#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
#[cfg(feature = "transport")]
pub async fn connect(
addr: std::net::SocketAddr,
server_name: &str,
) -> Result<QuicConnection, OxiQuicError> {
use std::sync::Arc;
use rustls::version::TLS13;
use rustls::ClientConfig;
let provider = Arc::new(oxiquic_crypto::quic_crypto_provider());
let roots = oxitls::webpki_root_certs();
let client_cfg = ClientConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.map_err(|e| OxiQuicError::Tls(e.to_string()))?
.with_root_certificates(roots)
.with_no_client_auth();
let bind_addr = std::net::SocketAddr::from(([0, 0, 0, 0], 0));
let endpoint =
ClientEndpoint::bind(bind_addr, Arc::new(client_cfg), TransportConfig::default()).await?;
endpoint.connect(addr, server_name).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous")))]
#[cfg(feature = "dangerous")]
pub async fn connect_insecure(
addr: std::net::SocketAddr,
server_name: &str,
) -> Result<QuicConnection, OxiQuicError> {
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::{ServerName, UnixTime};
use rustls::{DigitallySignedStruct, Error as TlsError, SignatureScheme};
use std::sync::Arc;
#[derive(Debug)]
struct NoVerifier;
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer,
_intermediates: &[rustls::pki_types::CertificateDer],
_server_name: &ServerName<'_>,
_ocsp: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, TlsError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::ED25519,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
]
}
}
let provider = Arc::new(oxiquic_crypto::quic_crypto_provider());
let client_cfg = rustls::ClientConfig::builder_with_provider(provider)
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| OxiQuicError::Tls(e.to_string()))?
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier))
.with_no_client_auth();
let bind_addr = std::net::SocketAddr::from(([0, 0, 0, 0], 0));
let endpoint =
ClientEndpoint::bind(bind_addr, Arc::new(client_cfg), TransportConfig::default()).await?;
endpoint.connect(addr, server_name).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
#[cfg(feature = "transport")]
pub async fn connect_0rtt(
addr: std::net::SocketAddr,
server_name: &str,
) -> Result<(QuicConnection, ZeroRttAccepted), OxiQuicError> {
use std::sync::{Arc, OnceLock};
use rustls::client::ClientSessionMemoryCache;
use rustls::client::Resumption;
use rustls::version::TLS13;
use rustls::ClientConfig;
static SESSION_CACHE: OnceLock<Arc<ClientSessionMemoryCache>> = OnceLock::new();
let cache = SESSION_CACHE
.get_or_init(|| Arc::new(ClientSessionMemoryCache::new(64)))
.clone();
let provider = Arc::new(oxiquic_crypto::quic_crypto_provider());
let roots = oxitls::webpki_root_certs();
let mut client_cfg = ClientConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.map_err(|e| OxiQuicError::Tls(e.to_string()))?
.with_root_certificates(roots)
.with_no_client_auth();
client_cfg.enable_early_data = true;
client_cfg.resumption = Resumption::store(cache);
let bind_addr = std::net::SocketAddr::from(([0, 0, 0, 0], 0));
let endpoint =
ClientEndpoint::bind(bind_addr, Arc::new(client_cfg), TransportConfig::default()).await?;
endpoint.connect_0rtt(addr, server_name).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
#[cfg(feature = "transport")]
pub async fn listen(
addr: std::net::SocketAddr,
cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>,
private_key: rustls::pki_types::PrivateKeyDer<'static>,
) -> Result<ServerEndpoint, OxiQuicError> {
use std::sync::Arc;
use rustls::version::TLS13;
use rustls::ServerConfig;
let provider = Arc::new(oxiquic_crypto::quic_crypto_provider());
let server_cfg = ServerConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.map_err(|e| OxiQuicError::Tls(e.to_string()))?
.with_no_client_auth()
.with_single_cert(cert_chain, private_key)
.map_err(|e| OxiQuicError::Tls(e.to_string()))?;
ServerEndpoint::bind(addr, Arc::new(server_cfg), TransportConfig::default()).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "h3")))]
#[cfg(feature = "h3")]
pub async fn connect_h3(
addr: std::net::SocketAddr,
server_name: &str,
) -> Result<H3Client, OxiQuicError> {
use std::sync::Arc;
use rustls::version::TLS13;
use rustls::ClientConfig;
let provider = Arc::new(oxiquic_crypto::quic_crypto_provider());
let roots = oxitls::webpki_root_certs();
let mut client_cfg = ClientConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.map_err(|e| OxiQuicError::Tls(e.to_string()))?
.with_root_certificates(roots)
.with_no_client_auth();
client_cfg.alpn_protocols = vec![b"h3".to_vec()];
H3ClientBuilder::new()
.with_server_name(server_name)
.with_tls_config(client_cfg)
.connect(addr)
.await
.map_err(OxiQuicError::from)
}
#[cfg_attr(docsrs, doc(cfg(feature = "h3")))]
#[cfg(feature = "h3")]
pub async fn listen_h3(
addr: std::net::SocketAddr,
cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>,
private_key: rustls::pki_types::PrivateKeyDer<'static>,
) -> Result<H3ServerEndpoint, OxiQuicError> {
use std::sync::Arc;
use rustls::version::TLS13;
use rustls::ServerConfig;
let provider = Arc::new(oxiquic_crypto::quic_crypto_provider());
let server_cfg = ServerConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.map_err(|e| OxiQuicError::Tls(e.to_string()))?
.with_no_client_auth()
.with_single_cert(cert_chain, private_key)
.map_err(|e| OxiQuicError::Tls(e.to_string()))?;
H3ServerBuilder::new(addr)
.with_tls_config(server_cfg)
.build()
.await
}
#[must_use]
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[must_use]
pub fn quic_version() -> QuicVersion {
QuicVersion::V1
}
#[cfg_attr(docsrs, doc(cfg(feature = "transport")))]
#[cfg(feature = "transport")]
pub mod prelude {
pub use oxiquic_core::{
ConnectionId, ConnectionStats, Direction, FrameType, Initiator, OxiQuicError, PacketType,
QuicVersion, StreamId, TransportErrorCode, TransportParams,
};
pub use oxiquic_transport::{
ClientEndpoint, CongestionAlgorithm, Connection, ConnectionState, QuicConnection, Role,
ServerEndpoint, TransportConfig, ZeroRttAccepted,
};
}
#[cfg_attr(docsrs, doc(cfg(feature = "h3")))]
#[cfg(feature = "h3")]
pub mod h3_prelude {
pub use oxiquic_core::OxiQuicError;
pub use oxiquic_h3::{
accept_h3_server, connect_h3_client, H3Client, H3ClientBuilder, H3Connection, H3Error,
H3ErrorCode, H3Request, H3RequestContext, H3Responder, H3Response, H3Server,
H3ServerBuilder, H3ServerEndpoint, H3Settings, RequestStream,
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_is_nonempty_semverish() {
let v = version();
assert!(!v.is_empty());
assert!(v.contains('.'), "version {v} should look like a semver");
}
#[test]
fn quic_version_is_v1() {
assert_eq!(quic_version(), QuicVersion::V1);
assert_eq!(quic_version().to_u32(), 1);
}
#[test]
fn facade_reexports_core_types_by_identity() {
fn assert_same<T>(_: T) {}
let id: StreamId = oxiquic_core::StreamId(0);
assert_same::<StreamId>(id);
let _: fn(u32) -> QuicVersion = QuicVersion::from_u32;
let _: OxiQuicError = oxiquic_core::OxiQuicError::Timeout;
}
#[test]
fn version_and_quic_version_unchanged() {
assert!(!version().is_empty());
assert_eq!(quic_version(), QuicVersion::V1);
}
#[test]
fn no_default_features_compile_test() {
let v = version();
assert!(v.contains('.'));
}
#[cfg(feature = "transport")]
#[test]
fn transport_reexports_available() {
let _: fn() -> TransportConfig = TransportConfig::default;
let _ = CongestionAlgorithm::Cubic;
let _ = Role::Client;
let _ = ConnectionState::Established;
fn _assert_types(
_: Option<ClientEndpoint>,
_: Option<ServerEndpoint>,
_: Option<QuicConnection>,
_: Option<Connection>,
) {
}
}
#[cfg(feature = "h3")]
#[test]
fn h3_reexports_available() {
let res = H3Response::new(200).with_body("ok");
assert!(res.is_success());
let _ = H3ErrorCode::NoError;
}
#[cfg(feature = "h3")]
#[test]
fn h3_types_reexported() {
let _ = std::any::TypeId::of::<H3Client>();
let _ = std::any::TypeId::of::<H3ClientBuilder>();
let _ = std::any::TypeId::of::<H3ServerBuilder>();
let _ = std::any::TypeId::of::<H3ServerEndpoint>();
let _ = std::any::TypeId::of::<H3Connection>();
let _ = std::any::TypeId::of::<H3Responder>();
let _ = std::any::TypeId::of::<RequestStream>();
let _ = std::any::TypeId::of::<H3Error>();
let _ = std::any::TypeId::of::<H3Response>();
let _ = std::any::TypeId::of::<H3Request>();
}
#[cfg(feature = "dangerous")]
#[test]
fn connect_insecure_is_exported() {
let _ = connect_insecure;
}
#[cfg(feature = "transport")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn facade_full_round_trip() {
use std::sync::Arc;
use oxiquic_crypto::quic_crypto_provider;
use oxitls_rcgen::generate_self_signed_ed25519;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use rustls::version::TLS13;
use rustls::{ClientConfig, RootCertStore, ServerConfig};
let ck = generate_self_signed_ed25519(&["localhost"]).expect("generate self-signed cert");
let cert_der = CertificateDer::from(ck.cert_der.clone());
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(ck.pkcs8_der.clone()));
let provider = Arc::new(quic_crypto_provider());
let mut roots = RootCertStore::empty();
roots.add(cert_der.clone()).expect("trust self-signed cert");
let client_cfg: Arc<ClientConfig> = Arc::new(
ClientConfig::builder_with_provider(provider.clone())
.with_protocol_versions(&[&TLS13])
.expect("client TLS1.3")
.with_root_certificates(roots)
.with_no_client_auth(),
);
let server_cfg: Arc<ServerConfig> = Arc::new(
ServerConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.expect("server TLS1.3")
.with_no_client_auth()
.with_single_cert(vec![cert_der], key_der)
.expect("server single cert"),
);
let loopback: std::net::SocketAddr = "127.0.0.1:0".parse().expect("valid addr");
let server = ServerEndpoint::bind(loopback, server_cfg, TransportConfig::default())
.await
.expect("facade ServerEndpoint::bind");
let server_addr = server.local_addr().expect("server local_addr");
let server_task = tokio::spawn(async move {
let mut conn = server.accept().await.expect("server accept");
assert!(!conn.is_closed(), "server connection open");
let (_id, data, _fin) = conn
.accept_uni_or_bidi_data()
.await
.expect("server read stream");
data
});
let client = ClientEndpoint::bind(loopback, client_cfg, TransportConfig::default())
.await
.expect("facade ClientEndpoint::bind");
let mut conn: QuicConnection = client
.connect(server_addr, "localhost")
.await
.expect("facade connect");
assert!(!conn.is_closed(), "client connection open");
assert!(
conn.peer_transport_params().is_some(),
"client has server transport params"
);
let stream = conn.open_bidi().expect("open bidi stream");
conn.send(stream, b"facade-round-trip", false)
.await
.expect("client send");
let received = server_task.await.expect("server task");
assert_eq!(received, b"facade-round-trip", "data delivered via facade");
}
#[cfg(feature = "h3")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn facade_h3_get_roundtrip() {
use std::sync::Arc;
use oxiquic_crypto::quic_crypto_provider;
use oxitls_rcgen::generate_self_signed_ed25519;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use rustls::version::TLS13;
use rustls::{ClientConfig, RootCertStore, ServerConfig};
let ck = generate_self_signed_ed25519(&["localhost"]).expect("generate self-signed cert");
let cert_der = CertificateDer::from(ck.cert_der.clone());
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(ck.pkcs8_der.clone()));
let provider = Arc::new(quic_crypto_provider());
let mut roots = RootCertStore::empty();
roots.add(cert_der.clone()).expect("trust self-signed cert");
let client_cfg: Arc<ClientConfig> = Arc::new(
ClientConfig::builder_with_provider(provider.clone())
.with_protocol_versions(&[&TLS13])
.expect("client TLS1.3")
.with_root_certificates(roots)
.with_no_client_auth(),
);
let server_cfg: Arc<ServerConfig> = Arc::new(
ServerConfig::builder_with_provider(provider)
.with_protocol_versions(&[&TLS13])
.expect("server TLS1.3")
.with_no_client_auth()
.with_single_cert(vec![cert_der], key_der)
.expect("server single cert"),
);
let loopback: std::net::SocketAddr = "127.0.0.1:0".parse().expect("valid addr");
let server_ep = ServerEndpoint::bind(loopback, server_cfg, TransportConfig::default())
.await
.expect("facade ServerEndpoint::bind");
let server_addr = server_ep.local_addr().expect("server local_addr");
let server_task = tokio::spawn(async move {
let quic_conn = server_ep.accept().await.expect("server accept QUIC");
let driven = quic_conn.into_driven();
let mut h3_server = H3Server::new(driven).await.expect("H3Server::new");
let ctx = h3_server
.accept()
.await
.expect("H3Server::accept")
.expect("expected Some(request)");
let resp = H3Response::new(200).with_body("facade-h3-roundtrip");
ctx.respond(resp).await.expect("respond");
});
let client_ep = ClientEndpoint::bind(loopback, client_cfg, TransportConfig::default())
.await
.expect("facade ClientEndpoint::bind");
let quic_conn = client_ep
.connect(server_addr, "localhost")
.await
.expect("facade connect");
let driven = quic_conn.into_driven();
let mut h3_client = H3Client::new(driven).await.expect("H3Client::new");
let resp = h3_client.get("https://localhost/").await.expect("GET /");
server_task.await.expect("server task panicked");
assert!(resp.is_success(), "expected 2xx, got {}", resp.status());
assert_eq!(
resp.body_text().expect("utf-8 body"),
"facade-h3-roundtrip",
"h3 response body via facade"
);
let _ = h3_client.close().await;
}
#[cfg(all(test, feature = "transport"))]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn connect_to_unreachable_server_returns_descriptive_error() {
use std::net::SocketAddr;
let addr: SocketAddr = "127.0.0.1:1".parse().expect("valid addr");
let result = connect(addr, "localhost").await;
assert!(
result.is_err(),
"connecting to unreachable server must fail"
);
match result {
Err(err) => {
let msg = err.to_string();
assert!(
!msg.is_empty(),
"error message must not be empty: got '{msg}'"
);
}
Ok(_) => panic!("expected an error connecting to an unreachable server"),
}
}
}