Skip to main content

simoom_quic/
packet.rs

1//! Packet headers, packet numbers, header protection, Version Negotiation, Retry and
2//! stateless reset (RFC 9000 §17, RFC 9001 §5).
3
4use crate::coding::{put_varint, Reader};
5use crate::crypto::{CryptoProvider, HeaderKey};
6use std::ops::Range;
7
8pub const VERSION_1: u32 = 0x0000_0001;
9/// QUIC version 2 (RFC 9369).
10pub const VERSION_2: u32 = 0x6b33_43cf;
11pub const MAX_CID_LEN: usize = 20;
12/// Smallest datagram that may carry a client Initial (RFC 9000 §14.1).
13pub const MIN_INITIAL_SIZE: usize = 1200;
14/// Smallest stateless reset (RFC 9000 §10.3).
15pub const MIN_RESET_SIZE: usize = 21;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum PacketType {
19    Initial,
20    ZeroRtt,
21    Handshake,
22    Retry,
23    OneRtt,
24    VersionNegotiation,
25}
26
27/// Long-header type bits for `version` (they differ in QUIC v2, RFC 9369 §3.2).
28pub fn long_type_bits(version: u32, ty: PacketType) -> u8 {
29    let v1 = match ty {
30        PacketType::Initial => 0,
31        PacketType::ZeroRtt => 1,
32        PacketType::Handshake => 2,
33        _ => 3,
34    };
35    if version == VERSION_2 {
36        (v1 + 1) & 3
37    } else {
38        v1
39    }
40}
41
42fn long_type_from_bits(version: u32, bits: u8) -> PacketType {
43    let v1 = if version == VERSION_2 { (bits + 3) & 3 } else { bits };
44    match v1 {
45        0 => PacketType::Initial,
46        1 => PacketType::ZeroRtt,
47        2 => PacketType::Handshake,
48        _ => PacketType::Retry,
49    }
50}
51
52/// A header parsed before header protection is removed. Ranges index the datagram.
53#[derive(Clone, Debug)]
54pub struct Header {
55    pub ty: PacketType,
56    pub version: u32,
57    pub dcid: Range<usize>,
58    pub scid: Range<usize>,
59    /// Initial: the token; Retry: the token; VN: the version list.
60    pub token: Range<usize>,
61    /// Offset of the packet number (protected).
62    pub pn_offset: usize,
63    /// End of this packet in the datagram (coalesced packets follow).
64    pub end: usize,
65}
66
67/// Parses the header starting at `start`. `short_dcid_len` is the length of this
68/// endpoint's connection IDs (short headers do not carry it).
69pub fn parse_header(buf: &[u8], start: usize, short_dcid_len: usize) -> Option<Header> {
70    let mut r = Reader::new(&buf[start..]);
71    let first = r.u8()?;
72    if first & 0x80 == 0 {
73        // Short header (fixed bit checked by the caller after decryption fails, RFC 9000 §17.3).
74        let dcid = start + 1..start + 1 + short_dcid_len;
75        if dcid.end > buf.len() {
76            return None;
77        }
78        return Some(Header {
79            ty: PacketType::OneRtt,
80            version: 0,
81            dcid: dcid.clone(),
82            scid: 0..0,
83            token: 0..0,
84            pn_offset: dcid.end,
85            end: buf.len(),
86        });
87    }
88    let version = r.u32()?;
89    let dlen = r.u8()? as usize;
90    let d0 = start + r.position();
91    r.bytes(dlen)?;
92    let slen = r.u8()? as usize;
93    let s0 = start + r.position();
94    r.bytes(slen)?;
95    let dcid = d0..d0 + dlen;
96    let scid = s0..s0 + slen;
97    // Versions other than 1 and 2 may use longer IDs (RFC 8999); we neither route nor
98    // answer those.
99    if dlen > MAX_CID_LEN || slen > MAX_CID_LEN {
100        return None;
101    }
102    if version == 0 {
103        let t0 = start + r.position();
104        return Some(Header {
105            ty: PacketType::VersionNegotiation,
106            version,
107            dcid,
108            scid,
109            token: t0..buf.len(),
110            pn_offset: buf.len(),
111            end: buf.len(),
112        });
113    }
114    if !is_supported(version) {
115        // Unknown version: only the invariant fields are meaningful.
116        return Some(Header {
117            ty: PacketType::Initial,
118            version,
119            dcid,
120            scid,
121            token: 0..0,
122            pn_offset: buf.len(),
123            end: buf.len(),
124        });
125    }
126    let ty = long_type_from_bits(version, (first >> 4) & 3);
127    let mut token = 0..0;
128    if ty == PacketType::Retry {
129        let t0 = start + r.position();
130        if buf.len() < t0 + 16 {
131            return None;
132        }
133        return Some(Header { ty, version, dcid, scid, token: t0..buf.len() - 16, pn_offset: buf.len(), end: buf.len() });
134    }
135    if ty == PacketType::Initial {
136        let t = r.vbytes()?;
137        let t0 = start + r.position() - t.len();
138        token = t0..t0 + t.len();
139    }
140    let len = r.varint()? as usize;
141    let pn_offset = start + r.position();
142    if len > buf.len() - pn_offset {
143        return None;
144    }
145    Some(Header { ty, version, dcid, scid, token, pn_offset, end: pn_offset + len })
146}
147
148pub fn is_supported(version: u32) -> bool {
149    version == VERSION_1 || version == VERSION_2
150}
151
152/// Removes header protection in place; returns (truncated packet number, its length).
153pub fn unprotect(buf: &mut [u8], start: usize, pn_offset: usize, end: usize, hp: &dyn HeaderKey) -> Option<(u64, usize)> {
154    let sample_at = pn_offset + 4;
155    if sample_at + 16 > end {
156        return None;
157    }
158    let mask = hp.mask(&buf[sample_at..sample_at + 16]);
159    let long = buf[start] & 0x80 != 0;
160    buf[start] ^= mask[0] & if long { 0x0f } else { 0x1f };
161    let pn_len = (buf[start] & 3) as usize + 1;
162    let mut pn = 0u64;
163    for i in 0..pn_len {
164        buf[pn_offset + i] ^= mask[1 + i];
165        pn = (pn << 8) | buf[pn_offset + i] as u64;
166    }
167    Some((pn, pn_len))
168}
169
170/// Applies header protection to a sealed packet `buf[start..end]`.
171pub fn protect(buf: &mut [u8], start: usize, pn_offset: usize, pn_len: usize, hp: &dyn HeaderKey) {
172    let sample_at = pn_offset + 4;
173    let mask = hp.mask(&buf[sample_at..sample_at + 16]);
174    let long = buf[start] & 0x80 != 0;
175    buf[start] ^= mask[0] & if long { 0x0f } else { 0x1f };
176    for i in 0..pn_len {
177        buf[pn_offset + i] ^= mask[1 + i];
178    }
179}
180
181/// Recovers a full packet number (RFC 9000 Appendix A.3).
182pub fn decode_pn(largest: Option<u64>, truncated: u64, pn_len: usize) -> u64 {
183    let expected = largest.map_or(0, |l| l + 1);
184    let win = 1u64 << (pn_len * 8);
185    let hwin = win / 2;
186    let mask = win - 1;
187    let candidate = (expected & !mask) | truncated;
188    if candidate + hwin <= expected && candidate < (1 << 62) - win {
189        candidate + win
190    } else if candidate > expected + hwin && candidate >= win {
191        candidate - win
192    } else {
193        candidate
194    }
195}
196
197/// Bytes needed to encode `pn` given the largest acknowledged (RFC 9000 Appendix A.2).
198pub fn pn_len(pn: u64, largest_acked: Option<u64>) -> usize {
199    let unacked = match largest_acked {
200        Some(l) => pn - l,
201        None => pn + 1,
202    };
203    let range = unacked.saturating_mul(2);
204    if range < 1 << 8 {
205        1
206    } else if range < 1 << 16 {
207        2
208    } else if range < 1 << 24 {
209        3
210    } else {
211        4
212    }
213}
214
215/// Writes a Version Negotiation packet answering a packet with `dcid`/`scid`.
216pub fn version_negotiation(out: &mut Vec<u8>, random: u8, dcid: &[u8], scid: &[u8], versions: &[u32]) {
217    out.push(0x80 | (random & 0x7f) | 0x40);
218    out.extend_from_slice(&0u32.to_be_bytes());
219    out.push(scid.len() as u8);
220    out.extend_from_slice(scid);
221    out.push(dcid.len() as u8);
222    out.extend_from_slice(dcid);
223    for v in versions {
224        out.extend_from_slice(&v.to_be_bytes());
225    }
226    // A reserved version (RFC 9000 §15) exercises the peer's handling of unknown versions.
227    out.extend_from_slice(&0x1a2a_3a4au32.to_be_bytes());
228}
229
230/// Writes a Retry packet with its integrity tag (RFC 9001 §5.8).
231#[allow(clippy::too_many_arguments)]
232pub fn retry(
233    out: &mut Vec<u8>,
234    provider: &dyn CryptoProvider,
235    version: u32,
236    random: u8,
237    odcid: &[u8],
238    dcid: &[u8],
239    scid: &[u8],
240    token: &[u8],
241) {
242    let start = out.len();
243    out.push(0xc0 | (long_type_bits(version, PacketType::Retry) << 4) | (random & 0x0f));
244    out.extend_from_slice(&version.to_be_bytes());
245    out.push(dcid.len() as u8);
246    out.extend_from_slice(dcid);
247    out.push(scid.len() as u8);
248    out.extend_from_slice(scid);
249    out.extend_from_slice(token);
250    let tag = retry_tag(provider, version, odcid, &out[start..]);
251    out.extend_from_slice(&tag);
252}
253
254/// The Retry integrity tag for a Retry packet (without its tag) sent in reply to `odcid`.
255pub fn retry_tag(provider: &dyn CryptoProvider, version: u32, odcid: &[u8], packet: &[u8]) -> [u8; 16] {
256    let mut pseudo = Vec::with_capacity(1 + odcid.len() + packet.len());
257    pseudo.push(odcid.len() as u8);
258    pseudo.extend_from_slice(odcid);
259    pseudo.extend_from_slice(packet);
260    provider.retry_tag(version, &pseudo)
261}
262
263/// Writes the long header up to and including the packet number; returns
264/// (offset of the 2-byte length field, packet number offset).
265#[allow(clippy::too_many_arguments)]
266pub fn put_long_header(
267    out: &mut Vec<u8>,
268    version: u32,
269    ty: PacketType,
270    dcid: &[u8],
271    scid: &[u8],
272    token: Option<&[u8]>,
273    pn: u64,
274    pn_len: usize,
275) -> (usize, usize) {
276    out.push(0xc0 | (long_type_bits(version, ty) << 4) | (pn_len as u8 - 1));
277    out.extend_from_slice(&version.to_be_bytes());
278    out.push(dcid.len() as u8);
279    out.extend_from_slice(dcid);
280    out.push(scid.len() as u8);
281    out.extend_from_slice(scid);
282    if let Some(t) = token {
283        put_varint(out, t.len() as u64);
284        out.extend_from_slice(t);
285    }
286    let len_at = out.len();
287    out.extend_from_slice(&[0x40, 0]);
288    let pn_at = out.len();
289    out.extend_from_slice(&pn.to_be_bytes()[8 - pn_len..]);
290    (len_at, pn_at)
291}
292
293/// Writes a short header; returns the packet number offset.
294pub fn put_short_header(out: &mut Vec<u8>, spin: bool, key_phase: bool, dcid: &[u8], pn: u64, pn_len: usize) -> usize {
295    out.push(0x40 | (spin as u8) << 5 | (key_phase as u8) << 2 | (pn_len as u8 - 1));
296    out.extend_from_slice(dcid);
297    let pn_at = out.len();
298    out.extend_from_slice(&pn.to_be_bytes()[8 - pn_len..]);
299    pn_at
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn rfc9000_appendix_a_packet_numbers() {
308        // A.2: full 0xac5c02 with largest acked 0xabe8b3 needs 2 bytes; 0xace8fe needs 3.
309        assert_eq!(pn_len(0xac5c02, Some(0xabe8b3)), 2);
310        assert_eq!(pn_len(0xace8fe, Some(0xabe8b3)), 3);
311        // A.3: largest 0xa82f30ea, truncated 0x9b32 (16 bits) decodes to 0xa82f9b32.
312        assert_eq!(decode_pn(Some(0xa82f30ea), 0x9b32, 2), 0xa82f9b32);
313        assert_eq!(decode_pn(None, 0, 1), 0);
314        assert_eq!(decode_pn(Some(0xff), 0x00, 1), 0x100);
315        assert_eq!(decode_pn(Some(0x100), 0xff, 1), 0xff);
316        assert_eq!(pn_len(0, None), 1);
317    }
318
319    #[test]
320    fn header_roundtrip_and_types() {
321        for v in [VERSION_1, VERSION_2] {
322            let mut out = Vec::new();
323            let (len_at, pn_at) =
324                put_long_header(&mut out, v, PacketType::Handshake, &[1, 2, 3], &[4, 5], None, 7, 2);
325            out.extend_from_slice(&[0; 20]);
326            let n = out.len() - pn_at;
327            crate::coding::put_varint_fixed(&mut out[len_at..], n as u64, 2);
328            let h = parse_header(&out, 0, 0).unwrap();
329            assert_eq!(h.ty, PacketType::Handshake);
330            assert_eq!(&out[h.dcid.clone()], &[1, 2, 3]);
331            assert_eq!(&out[h.scid.clone()], &[4, 5]);
332            assert_eq!(h.pn_offset, pn_at);
333            assert_eq!(h.end, out.len());
334        }
335        assert_eq!(long_type_bits(VERSION_2, PacketType::Initial), 1);
336        assert_eq!(long_type_bits(VERSION_2, PacketType::Retry), 0);
337        // Truncated Initial length.
338        let mut out = Vec::new();
339        put_long_header(&mut out, VERSION_1, PacketType::Initial, &[1; 8], &[], Some(b"t"), 0, 1);
340        assert!(parse_header(&out, 0, 0).is_some());
341        out[17] = 0x7f;
342        out[18] = 0xff;
343        assert!(parse_header(&out, 0, 0).is_none());
344    }
345}