Skip to main content

runsync_transfer/transport/
quic.rs

1//! QUIC transport backed by [`quinn`].
2//!
3//! Two entry points. [`QuicTransport::from_connection`] wraps a connection the
4//! host application already established — this is the one to use when
5//! embedding in a service that has its own QUIC endpoint, since it costs no
6//! extra handshake, port, or certificate story. [`client_endpoint`] and
7//! [`server_endpoint`] are there for standalone use and for the test suite.
8
9use super::{BoxRecv, BoxSend, Transport};
10use crate::error::{Error, Result};
11use std::net::SocketAddr;
12use std::sync::Arc;
13use std::time::Duration;
14
15/// Wraps a live `quinn::Connection`.
16#[derive(Clone)]
17pub struct QuicTransport {
18    conn: quinn::Connection,
19}
20
21impl QuicTransport {
22    pub fn from_connection(conn: quinn::Connection) -> Self {
23        Self { conn }
24    }
25
26    pub fn connection(&self) -> &quinn::Connection {
27        &self.conn
28    }
29}
30
31#[async_trait::async_trait]
32impl Transport for QuicTransport {
33    async fn open_uni(&self) -> Result<BoxSend> {
34        let s = self.conn.open_uni().await.map_err(Error::transport)?;
35        Ok(Box::new(s))
36    }
37
38    async fn accept_uni(&self) -> Result<BoxRecv> {
39        let s = self.conn.accept_uni().await.map_err(Error::transport)?;
40        Ok(Box::new(s))
41    }
42
43    async fn open_bi(&self) -> Result<(BoxSend, BoxRecv)> {
44        let (w, r) = self.conn.open_bi().await.map_err(Error::transport)?;
45        Ok((Box::new(w), Box::new(r)))
46    }
47
48    async fn accept_bi(&self) -> Result<(BoxSend, BoxRecv)> {
49        let (w, r) = self.conn.accept_bi().await.map_err(Error::transport)?;
50        Ok((Box::new(w), Box::new(r)))
51    }
52
53    fn close(&self, code: u32, reason: &[u8]) {
54        self.conn.close(code.into(), reason);
55    }
56
57    fn peer_label(&self) -> String {
58        self.conn.remote_address().to_string()
59    }
60
61    fn bytes_sent(&self) -> Option<u64> {
62        Some(self.conn.stats().udp_tx.bytes)
63    }
64}
65
66/// Transport parameters tuned for bulk transfer rather than for request/response.
67///
68/// The defaults that ship with QUIC stacks assume interactive traffic. Three
69/// of them will cap a bulk transfer well below link rate on a fat pipe:
70/// stream and connection receive windows (the bandwidth-delay product ceiling),
71/// and the concurrent stream limit. These are sized for a ~1 Gbps × 200 ms path.
72pub fn bulk_transport_config() -> quinn::TransportConfig {
73    let mut t = quinn::TransportConfig::default();
74    // 64 MiB connection window covers 1 Gbps at ~500 ms RTT.
75    t.receive_window(quinn::VarInt::from_u32(64 * 1024 * 1024));
76    t.stream_receive_window(quinn::VarInt::from_u32(16 * 1024 * 1024));
77    t.send_window(64 * 1024 * 1024);
78    t.max_concurrent_uni_streams(quinn::VarInt::from_u32(256));
79    t.max_concurrent_bidi_streams(quinn::VarInt::from_u32(16));
80    // A bulk transfer can legitimately stall behind a slow disk; do not treat
81    // that as a dead peer.
82    t.keep_alive_interval(Some(Duration::from_secs(5)));
83    t.max_idle_timeout(Some(Duration::from_secs(60).try_into().unwrap()));
84    t
85}
86
87/// Server endpoint from a certificate chain and key.
88pub fn server_endpoint(
89    addr: SocketAddr,
90    cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>,
91    key: rustls::pki_types::PrivateKeyDer<'static>,
92) -> Result<quinn::Endpoint> {
93    let mut server_crypto = rustls::ServerConfig::builder_with_provider(Arc::new(
94        rustls::crypto::ring::default_provider(),
95    ))
96    .with_protocol_versions(&[&rustls::version::TLS13])
97    .map_err(Error::transport)?
98    .with_no_client_auth()
99    .with_single_cert(cert_chain, key)
100    .map_err(Error::transport)?;
101    server_crypto.alpn_protocols = vec![ALPN.to_vec()];
102
103    let mut cfg = quinn::ServerConfig::with_crypto(Arc::new(
104        quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto)
105            .map_err(Error::transport)?,
106    ));
107    cfg.transport_config(Arc::new(bulk_transport_config()));
108    quinn::Endpoint::server(cfg, addr).map_err(Error::from)
109}
110
111/// Client endpoint that pins the server certificate by exact bytes.
112///
113/// Pinning rather than a CA chain, because the common deployment here is two
114/// peers that already know each other out of band, not a browser trusting the
115/// public web PKI.
116pub fn client_endpoint(
117    bind: SocketAddr,
118    server_cert: rustls::pki_types::CertificateDer<'static>,
119) -> Result<quinn::Endpoint> {
120    let mut roots = rustls::RootCertStore::empty();
121    roots.add(server_cert).map_err(Error::transport)?;
122
123    let mut client_crypto = rustls::ClientConfig::builder_with_provider(Arc::new(
124        rustls::crypto::ring::default_provider(),
125    ))
126    .with_protocol_versions(&[&rustls::version::TLS13])
127    .map_err(Error::transport)?
128    .with_root_certificates(roots)
129    .with_no_client_auth();
130    client_crypto.alpn_protocols = vec![ALPN.to_vec()];
131
132    let mut cfg = quinn::ClientConfig::new(Arc::new(
133        quinn::crypto::rustls::QuicClientConfig::try_from(client_crypto)
134            .map_err(Error::transport)?,
135    ));
136    cfg.transport_config(Arc::new(bulk_transport_config()));
137
138    let mut ep = quinn::Endpoint::client(bind).map_err(Error::from)?;
139    ep.set_default_client_config(cfg);
140    Ok(ep)
141}
142
143pub const ALPN: &[u8] = b"runsync-transfer/1";
144
145/// Self-signed certificate for tests and for peers that pin out of band.
146#[cfg(feature = "test-certs")]
147pub fn self_signed(
148    names: Vec<String>,
149) -> Result<(
150    rustls::pki_types::CertificateDer<'static>,
151    rustls::pki_types::PrivateKeyDer<'static>,
152)> {
153    let c = rcgen::generate_simple_self_signed(names).map_err(Error::transport)?;
154    let cert = rustls::pki_types::CertificateDer::from(c.cert);
155    let key = rustls::pki_types::PrivateKeyDer::try_from(c.key_pair.serialize_der())
156        .map_err(Error::transport)?;
157    Ok((cert, key))
158}