iroh_quinn_proto/
lib.rs

1//! Low-level protocol logic for the QUIC protoocol
2//!
3//! quinn-proto contains a fully deterministic implementation of QUIC protocol logic. It contains
4//! no networking code and does not get any relevant timestamps from the operating system. Most
5//! users may want to use the futures-based quinn API instead.
6//!
7//! The quinn-proto API might be of interest if you want to use it from a C or C++ project
8//! through C bindings or if you want to use a different event loop than the one tokio provides.
9//!
10//! The most important types are `Endpoint`, which conceptually represents the protocol state for
11//! a single socket and mostly manages configuration and dispatches incoming datagrams to the
12//! related `Connection`. `Connection` types contain the bulk of the protocol logic related to
13//! managing a single connection and all the related state (such as streams).
14
15#![cfg_attr(not(fuzzing), warn(missing_docs))]
16#![cfg_attr(test, allow(dead_code))]
17// Fixes welcome:
18#![warn(unreachable_pub)]
19#![allow(clippy::cognitive_complexity)]
20#![allow(clippy::too_many_arguments)]
21#![warn(clippy::use_self)]
22
23use std::{
24    fmt,
25    net::{IpAddr, SocketAddr},
26    ops,
27};
28
29mod cid_queue;
30pub mod coding;
31mod constant_time;
32mod range_set;
33#[cfg(all(test, any(feature = "rustls-aws-lc-rs", feature = "rustls-ring")))]
34mod tests;
35pub mod transport_parameters;
36mod varint;
37
38pub use varint::{VarInt, VarIntBoundsExceeded};
39
40mod connection;
41pub use crate::connection::{
42    BytesSource, Chunk, Chunks, ClosedStream, Connection, ConnectionError, ConnectionStats,
43    Datagrams, Event, FinishError, FrameStats, PathStats, ReadError, ReadableError, RecvStream,
44    RttEstimator, SendDatagramError, SendStream, ShouldTransmit, StreamEvent, Streams, UdpStats,
45    WriteError, Written,
46};
47
48#[cfg(feature = "rustls")]
49pub use rustls;
50
51mod config;
52pub use config::{
53    AckFrequencyConfig, ClientConfig, ConfigError, EndpointConfig, IdleTimeout, MtuDiscoveryConfig,
54    ServerConfig, StdSystemTime, TimeSource, TransportConfig, ValidationTokenConfig,
55};
56
57pub mod crypto;
58
59mod frame;
60use crate::frame::Frame;
61pub use crate::frame::{ApplicationClose, ConnectionClose, Datagram, FrameType};
62
63mod endpoint;
64pub use crate::endpoint::{
65    AcceptError, ConnectError, ConnectionHandle, DatagramEvent, Endpoint, Incoming, RetryError,
66};
67
68mod packet;
69pub use packet::{
70    ConnectionIdParser, FixedLengthConnectionIdParser, LongType, PacketDecodeError, PartialDecode,
71    ProtectedHeader, ProtectedInitialHeader,
72};
73
74mod shared;
75pub use crate::shared::{ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent};
76
77mod transport_error;
78pub use crate::transport_error::{Code as TransportErrorCode, Error as TransportError};
79
80pub mod congestion;
81
82mod cid_generator;
83pub use crate::cid_generator::{
84    ConnectionIdGenerator, HashedConnectionIdGenerator, InvalidCid, RandomConnectionIdGenerator,
85};
86
87mod token;
88use token::ResetToken;
89pub use token::{NoneTokenLog, NoneTokenStore, TokenLog, TokenReuseError, TokenStore};
90
91mod address_discovery;
92
93#[cfg(feature = "arbitrary")]
94use arbitrary::Arbitrary;
95
96// Deal with time
97#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
98pub(crate) use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
99#[cfg(all(target_family = "wasm", target_os = "unknown"))]
100pub(crate) use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH};
101
102#[cfg(fuzzing)]
103pub mod fuzzing {
104    pub use crate::connection::{Retransmits, State as ConnectionState, StreamsState};
105    pub use crate::frame::ResetStream;
106    pub use crate::packet::PartialDecode;
107    pub use crate::transport_parameters::TransportParameters;
108    pub use bytes::{BufMut, BytesMut};
109
110    #[cfg(feature = "arbitrary")]
111    use arbitrary::{Arbitrary, Result, Unstructured};
112
113    #[cfg(feature = "arbitrary")]
114    impl<'arbitrary> Arbitrary<'arbitrary> for TransportParameters {
115        fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> {
116            Ok(Self {
117                initial_max_streams_bidi: u.arbitrary()?,
118                initial_max_streams_uni: u.arbitrary()?,
119                ack_delay_exponent: u.arbitrary()?,
120                max_udp_payload_size: u.arbitrary()?,
121                ..Self::default()
122            })
123        }
124    }
125
126    #[derive(Debug)]
127    pub struct PacketParams {
128        pub local_cid_len: usize,
129        pub buf: BytesMut,
130        pub grease_quic_bit: bool,
131    }
132
133    #[cfg(feature = "arbitrary")]
134    impl<'arbitrary> Arbitrary<'arbitrary> for PacketParams {
135        fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> {
136            let local_cid_len: usize = u.int_in_range(0..=crate::MAX_CID_SIZE)?;
137            let bytes: Vec<u8> = Vec::arbitrary(u)?;
138            let mut buf = BytesMut::new();
139            buf.put_slice(&bytes[..]);
140            Ok(Self {
141                local_cid_len,
142                buf,
143                grease_quic_bit: bool::arbitrary(u)?,
144            })
145        }
146    }
147}
148
149/// The QUIC protocol version implemented.
150pub const DEFAULT_SUPPORTED_VERSIONS: &[u32] = &[
151    0x00000001,
152    0xff00_001d,
153    0xff00_001e,
154    0xff00_001f,
155    0xff00_0020,
156    0xff00_0021,
157    0xff00_0022,
158];
159
160/// Whether an endpoint was the initiator of a connection
161#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
162#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
163pub enum Side {
164    /// The initiator of a connection
165    Client = 0,
166    /// The acceptor of a connection
167    Server = 1,
168}
169
170impl Side {
171    #[inline]
172    /// Shorthand for `self == Side::Client`
173    pub fn is_client(self) -> bool {
174        self == Self::Client
175    }
176
177    #[inline]
178    /// Shorthand for `self == Side::Server`
179    pub fn is_server(self) -> bool {
180        self == Self::Server
181    }
182}
183
184impl ops::Not for Side {
185    type Output = Self;
186    fn not(self) -> Self {
187        match self {
188            Self::Client => Self::Server,
189            Self::Server => Self::Client,
190        }
191    }
192}
193
194/// Whether a stream communicates data in both directions or only from the initiator
195#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
196#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
197pub enum Dir {
198    /// Data flows in both directions
199    Bi = 0,
200    /// Data flows only from the stream's initiator
201    Uni = 1,
202}
203
204impl Dir {
205    fn iter() -> impl Iterator<Item = Self> {
206        [Self::Bi, Self::Uni].iter().cloned()
207    }
208}
209
210impl fmt::Display for Dir {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        use Dir::*;
213        f.pad(match *self {
214            Bi => "bidirectional",
215            Uni => "unidirectional",
216        })
217    }
218}
219
220/// Identifier for a stream within a particular connection
221#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
222#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
223pub struct StreamId(u64);
224
225impl fmt::Display for StreamId {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        let initiator = match self.initiator() {
228            Side::Client => "client",
229            Side::Server => "server",
230        };
231        let dir = match self.dir() {
232            Dir::Uni => "uni",
233            Dir::Bi => "bi",
234        };
235        write!(
236            f,
237            "{} {}directional stream {}",
238            initiator,
239            dir,
240            self.index()
241        )
242    }
243}
244
245impl StreamId {
246    /// Create a new StreamId
247    pub fn new(initiator: Side, dir: Dir, index: u64) -> Self {
248        Self(index << 2 | (dir as u64) << 1 | initiator as u64)
249    }
250    /// Which side of a connection initiated the stream
251    pub fn initiator(self) -> Side {
252        if self.0 & 0x1 == 0 {
253            Side::Client
254        } else {
255            Side::Server
256        }
257    }
258    /// Which directions data flows in
259    pub fn dir(self) -> Dir {
260        if self.0 & 0x2 == 0 {
261            Dir::Bi
262        } else {
263            Dir::Uni
264        }
265    }
266    /// Distinguishes streams of the same initiator and directionality
267    pub fn index(self) -> u64 {
268        self.0 >> 2
269    }
270}
271
272impl From<StreamId> for VarInt {
273    fn from(x: StreamId) -> Self {
274        unsafe { Self::from_u64_unchecked(x.0) }
275    }
276}
277
278impl From<VarInt> for StreamId {
279    fn from(v: VarInt) -> Self {
280        Self(v.0)
281    }
282}
283
284impl From<StreamId> for u64 {
285    fn from(x: StreamId) -> Self {
286        x.0
287    }
288}
289
290impl coding::Codec for StreamId {
291    fn decode<B: bytes::Buf>(buf: &mut B) -> coding::Result<Self> {
292        VarInt::decode(buf).map(|x| Self(x.into_inner()))
293    }
294    fn encode<B: bytes::BufMut>(&self, buf: &mut B) {
295        VarInt::from_u64(self.0).unwrap().encode(buf);
296    }
297}
298
299/// An outgoing packet
300#[derive(Debug)]
301#[must_use]
302pub struct Transmit {
303    /// The socket this datagram should be sent to
304    pub destination: SocketAddr,
305    /// Explicit congestion notification bits to set on the packet
306    pub ecn: Option<EcnCodepoint>,
307    /// Amount of data written to the caller-supplied buffer
308    pub size: usize,
309    /// The segment size if this transmission contains multiple datagrams.
310    /// This is `None` if the transmit only contains a single datagram
311    pub segment_size: Option<usize>,
312    /// Optional source IP address for the datagram
313    pub src_ip: Option<IpAddr>,
314}
315
316//
317// Useful internal constants
318//
319
320/// The maximum number of CIDs we bother to issue per connection
321const LOC_CID_COUNT: u64 = 8;
322const RESET_TOKEN_SIZE: usize = 16;
323const MAX_CID_SIZE: usize = 20;
324const MIN_INITIAL_SIZE: u16 = 1200;
325/// <https://www.rfc-editor.org/rfc/rfc9000.html#name-datagram-size>
326const INITIAL_MTU: u16 = 1200;
327const MAX_UDP_PAYLOAD: u16 = 65527;
328const TIMER_GRANULARITY: Duration = Duration::from_millis(1);
329/// Maximum number of streams that can be uniquely identified by a stream ID
330const MAX_STREAM_COUNT: u64 = 1 << 60;