Skip to main content

simoom_quic/
frame.rs

1//! QUIC frames (RFC 9000 §19): zero-copy parsing and encoding.
2
3use crate::coding::{put_varint, varint_len, Reader, VARINT_MAX};
4use crate::error::{TransportError, FRAME_ENCODING_ERROR, PROTOCOL_VIOLATION};
5use crate::ranges::RangeSet;
6
7pub const PADDING: u64 = 0x00;
8pub const PING: u64 = 0x01;
9pub const ACK: u64 = 0x02;
10pub const ACK_ECN: u64 = 0x03;
11pub const RESET_STREAM: u64 = 0x04;
12pub const STOP_SENDING: u64 = 0x05;
13pub const CRYPTO: u64 = 0x06;
14pub const NEW_TOKEN: u64 = 0x07;
15pub const STREAM: u64 = 0x08;
16pub const MAX_DATA: u64 = 0x10;
17pub const MAX_STREAM_DATA: u64 = 0x11;
18pub const MAX_STREAMS_BIDI: u64 = 0x12;
19pub const MAX_STREAMS_UNI: u64 = 0x13;
20pub const DATA_BLOCKED: u64 = 0x14;
21pub const STREAM_DATA_BLOCKED: u64 = 0x15;
22pub const STREAMS_BLOCKED_BIDI: u64 = 0x16;
23pub const STREAMS_BLOCKED_UNI: u64 = 0x17;
24pub const NEW_CONNECTION_ID: u64 = 0x18;
25pub const RETIRE_CONNECTION_ID: u64 = 0x19;
26pub const PATH_CHALLENGE: u64 = 0x1a;
27pub const PATH_RESPONSE: u64 = 0x1b;
28pub const CONNECTION_CLOSE: u64 = 0x1c;
29pub const CONNECTION_CLOSE_APP: u64 = 0x1d;
30pub const HANDSHAKE_DONE: u64 = 0x1e;
31
32/// Streams per direction may not exceed 2^60 (RFC 9000 §4.6).
33pub const MAX_STREAMS_LIMIT: u64 = 1 << 60;
34
35/// The ACK ranges of an ACK frame, decoded lazily (descending, inclusive).
36#[derive(Clone, Copy, Debug)]
37pub struct AckRanges<'a> {
38    pub largest: u64,
39    pub first: u64,
40    count: u64,
41    raw: &'a [u8],
42}
43
44impl<'a> AckRanges<'a> {
45    /// Iterates `(smallest, largest)` pairs, highest first. Already validated by parsing.
46    pub fn iter(&self) -> impl Iterator<Item = (u64, u64)> + 'a {
47        let mut r = Reader::new(self.raw);
48        let mut hi = self.largest;
49        let mut lo = self.largest - self.first;
50        let mut left = self.count;
51        let mut first = true;
52        std::iter::from_fn(move || {
53            if first {
54                first = false;
55                return Some((lo, hi));
56            }
57            if left == 0 {
58                return None;
59            }
60            left -= 1;
61            let gap = r.varint()?;
62            let len = r.varint()?;
63            hi = lo - gap - 2;
64            lo = hi - len;
65            Some((lo, hi))
66        })
67    }
68    pub fn smallest(&self) -> u64 {
69        self.iter().last().map(|x| x.0).unwrap_or(0)
70    }
71}
72
73#[derive(Clone, Copy, Debug)]
74pub enum Frame<'a> {
75    Padding,
76    Ping,
77    Ack { ranges: AckRanges<'a>, delay: u64, ecn: Option<[u64; 3]> },
78    ResetStream { id: u64, code: u64, final_size: u64 },
79    StopSending { id: u64, code: u64 },
80    Crypto { offset: u64, data: &'a [u8] },
81    NewToken { token: &'a [u8] },
82    Stream { id: u64, offset: u64, data: &'a [u8], fin: bool },
83    MaxData(u64),
84    MaxStreamData { id: u64, max: u64 },
85    MaxStreams { bidi: bool, max: u64 },
86    DataBlocked(u64),
87    StreamDataBlocked { id: u64, limit: u64 },
88    StreamsBlocked { bidi: bool, limit: u64 },
89    NewConnectionId { seq: u64, retire_prior_to: u64, cid: &'a [u8], reset_token: [u8; 16] },
90    RetireConnectionId { seq: u64 },
91    PathChallenge([u8; 8]),
92    PathResponse([u8; 8]),
93    ConnectionClose { code: u64, frame_type: Option<u64>, reason: &'a [u8] },
94    HandshakeDone,
95}
96
97impl Frame<'_> {
98    /// True for frames that require an acknowledgement (RFC 9002 §2).
99    pub fn is_ack_eliciting(&self) -> bool {
100        !matches!(self, Frame::Padding | Frame::Ack { .. } | Frame::ConnectionClose { .. })
101    }
102    /// Probing frames (RFC 9000 §9.1).
103    pub fn is_probing(&self) -> bool {
104        matches!(self, Frame::Padding | Frame::PathChallenge(_) | Frame::PathResponse(_) | Frame::NewConnectionId { .. })
105    }
106}
107
108fn enc(ty: u64) -> TransportError {
109    TransportError::frame(FRAME_ENCODING_ERROR, ty, "malformed frame")
110}
111
112/// Parses one frame. Returns the frame type with the frame.
113pub fn parse<'a>(r: &mut Reader<'a>) -> Result<(u64, Frame<'a>), TransportError> {
114    let (ty, tlen) = r.varint_with_len().ok_or(enc(0))?;
115    if tlen != varint_len(ty) {
116        return Err(TransportError::frame(PROTOCOL_VIOLATION, ty, "non-minimal frame type"));
117    }
118    let e = || enc(ty);
119    let v = |r: &mut Reader<'a>| r.varint().ok_or_else(e);
120    let f = match ty {
121        PADDING => {
122            // Consume a run of padding in one go.
123            let rest = r.rest();
124            let n = rest.iter().position(|&b| b != 0).unwrap_or(rest.len());
125            r.bytes(n);
126            Frame::Padding
127        }
128        PING => Frame::Ping,
129        ACK | ACK_ECN => {
130            let largest = v(r)?;
131            let delay = v(r)?;
132            let count = v(r)?;
133            let first = v(r)?;
134            if first > largest {
135                return Err(e());
136            }
137            let start = r.position();
138            let raw_all = r.rest();
139            let mut lo = largest - first;
140            for _ in 0..count {
141                let gap = v(r)?;
142                let len = v(r)?;
143                // hi = lo - gap - 2 must not underflow; lo' = hi - len likewise.
144                if lo < gap + 2 || lo - gap - 2 < len {
145                    return Err(e());
146                }
147                lo = lo - gap - 2 - len;
148            }
149            let raw = &raw_all[..r.position() - start];
150            let ecn = if ty == ACK_ECN { Some([v(r)?, v(r)?, v(r)?]) } else { None };
151            Frame::Ack { ranges: AckRanges { largest, first, count, raw }, delay, ecn }
152        }
153        RESET_STREAM => Frame::ResetStream { id: v(r)?, code: v(r)?, final_size: v(r)? },
154        STOP_SENDING => Frame::StopSending { id: v(r)?, code: v(r)? },
155        CRYPTO => {
156            let offset = v(r)?;
157            let data = r.vbytes().ok_or_else(e)?;
158            if offset + data.len() as u64 > VARINT_MAX {
159                return Err(TransportError::frame(FRAME_ENCODING_ERROR, ty, "crypto offset overflow"));
160            }
161            Frame::Crypto { offset, data }
162        }
163        NEW_TOKEN => {
164            let token = r.vbytes().ok_or_else(e)?;
165            if token.is_empty() {
166                return Err(e());
167            }
168            Frame::NewToken { token }
169        }
170        0x08..=0x0f => {
171            let id = v(r)?;
172            let offset = if ty & 0x04 != 0 { v(r)? } else { 0 };
173            let data = if ty & 0x02 != 0 {
174                r.vbytes().ok_or_else(e)?
175            } else {
176                let d = r.rest();
177                r.bytes(d.len());
178                d
179            };
180            if offset + data.len() as u64 > VARINT_MAX {
181                return Err(TransportError::frame(FRAME_ENCODING_ERROR, ty, "stream offset overflow"));
182            }
183            Frame::Stream { id, offset, data, fin: ty & 0x01 != 0 }
184        }
185        MAX_DATA => Frame::MaxData(v(r)?),
186        MAX_STREAM_DATA => Frame::MaxStreamData { id: v(r)?, max: v(r)? },
187        MAX_STREAMS_BIDI | MAX_STREAMS_UNI => {
188            let max = v(r)?;
189            if max > MAX_STREAMS_LIMIT {
190                return Err(e());
191            }
192            Frame::MaxStreams { bidi: ty == MAX_STREAMS_BIDI, max }
193        }
194        DATA_BLOCKED => Frame::DataBlocked(v(r)?),
195        STREAM_DATA_BLOCKED => Frame::StreamDataBlocked { id: v(r)?, limit: v(r)? },
196        STREAMS_BLOCKED_BIDI | STREAMS_BLOCKED_UNI => {
197            let limit = v(r)?;
198            if limit > MAX_STREAMS_LIMIT {
199                return Err(e());
200            }
201            Frame::StreamsBlocked { bidi: ty == STREAMS_BLOCKED_BIDI, limit }
202        }
203        NEW_CONNECTION_ID => {
204            let seq = v(r)?;
205            let retire_prior_to = v(r)?;
206            let len = r.u8().ok_or_else(e)? as usize;
207            if len == 0 || len > 20 || retire_prior_to > seq {
208                return Err(e());
209            }
210            let cid = r.bytes(len).ok_or_else(e)?;
211            let reset_token = r.bytes(16).ok_or_else(e)?.try_into().unwrap();
212            Frame::NewConnectionId { seq, retire_prior_to, cid, reset_token }
213        }
214        RETIRE_CONNECTION_ID => Frame::RetireConnectionId { seq: v(r)? },
215        PATH_CHALLENGE => Frame::PathChallenge(r.bytes(8).ok_or_else(e)?.try_into().unwrap()),
216        PATH_RESPONSE => Frame::PathResponse(r.bytes(8).ok_or_else(e)?.try_into().unwrap()),
217        CONNECTION_CLOSE | CONNECTION_CLOSE_APP => {
218            let code = v(r)?;
219            let frame_type = if ty == CONNECTION_CLOSE { Some(v(r)?) } else { None };
220            let reason = r.vbytes().ok_or_else(e)?;
221            Frame::ConnectionClose { code, frame_type, reason }
222        }
223        HANDSHAKE_DONE => Frame::HandshakeDone,
224        _ => return Err(TransportError::frame(FRAME_ENCODING_ERROR, ty, "unknown frame type")),
225    };
226    Ok((ty, f))
227}
228
229// ---- encoding ----
230
231/// Encodes an ACK frame for `ranges` (ascending set of received packet numbers), keeping
232/// at most `max_ranges` of the highest ranges. Returns false if the set is empty.
233pub fn put_ack(out: &mut Vec<u8>, ranges: &RangeSet, delay: u64, max_ranges: usize) -> bool {
234    let mut it = ranges.iter().rev().take(max_ranges.max(1));
235    let Some((s, e)) = it.next() else { return false };
236    let n = ranges.len().min(max_ranges.max(1)) - 1;
237    put_varint(out, ACK);
238    put_varint(out, e - 1);
239    put_varint(out, delay);
240    put_varint(out, n as u64);
241    put_varint(out, e - 1 - s);
242    let mut lo = s;
243    for (s2, e2) in it {
244        // gap: number of missing packets minus one; hi = e2 - 1.
245        put_varint(out, lo - e2 - 1);
246        put_varint(out, e2 - 1 - s2);
247        lo = s2;
248    }
249    true
250}
251
252pub fn put_crypto(out: &mut Vec<u8>, offset: u64, data: &[u8]) {
253    put_varint(out, CRYPTO);
254    put_varint(out, offset);
255    put_varint(out, data.len() as u64);
256    out.extend_from_slice(data);
257}
258
259/// Overhead of a CRYPTO frame header for this offset and length.
260pub fn crypto_overhead(offset: u64, len: usize) -> usize {
261    1 + varint_len(offset) + varint_len(len as u64)
262}
263
264/// STREAM frame header (always with an explicit length); the caller appends the data.
265pub fn put_stream_header(out: &mut Vec<u8>, id: u64, offset: u64, len: usize, fin: bool) {
266    let mut ty = STREAM | 0x02;
267    if offset != 0 {
268        ty |= 0x04;
269    }
270    if fin {
271        ty |= 0x01;
272    }
273    out.push(ty as u8);
274    put_varint(out, id);
275    if offset != 0 {
276        put_varint(out, offset);
277    }
278    put_varint(out, len as u64);
279}
280
281pub fn stream_overhead(id: u64, offset: u64, len: usize) -> usize {
282    1 + varint_len(id) + if offset != 0 { varint_len(offset) } else { 0 } + varint_len(len as u64)
283}
284
285pub fn put_simple(out: &mut Vec<u8>, ty: u64, vals: &[u64]) {
286    put_varint(out, ty);
287    for &v in vals {
288        put_varint(out, v);
289    }
290}
291
292pub fn put_new_connection_id(out: &mut Vec<u8>, seq: u64, retire_prior_to: u64, cid: &[u8], token: &[u8; 16]) {
293    put_varint(out, NEW_CONNECTION_ID);
294    put_varint(out, seq);
295    put_varint(out, retire_prior_to);
296    out.push(cid.len() as u8);
297    out.extend_from_slice(cid);
298    out.extend_from_slice(token);
299}
300
301pub fn put_path(out: &mut Vec<u8>, response: bool, data: &[u8; 8]) {
302    out.push(if response { PATH_RESPONSE } else { PATH_CHALLENGE } as u8);
303    out.extend_from_slice(data);
304}
305
306pub fn put_new_token(out: &mut Vec<u8>, token: &[u8]) {
307    put_varint(out, NEW_TOKEN);
308    put_varint(out, token.len() as u64);
309    out.extend_from_slice(token);
310}
311
312/// CONNECTION_CLOSE; `frame_type` is `None` for the application variant.
313pub fn put_close(out: &mut Vec<u8>, code: u64, frame_type: Option<u64>, reason: &[u8]) {
314    match frame_type {
315        Some(t) => {
316            put_varint(out, CONNECTION_CLOSE);
317            put_varint(out, code);
318            put_varint(out, t);
319        }
320        None => {
321            put_varint(out, CONNECTION_CLOSE_APP);
322            put_varint(out, code);
323        }
324    }
325    put_varint(out, reason.len() as u64);
326    out.extend_from_slice(reason);
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    fn one(b: &[u8]) -> Result<Frame<'_>, TransportError> {
334        let mut r = Reader::new(b);
335        let (_, f) = parse(&mut r)?;
336        assert!(r.is_empty(), "trailing bytes");
337        Ok(f)
338    }
339
340    #[test]
341    fn ack_roundtrip() {
342        let mut s = RangeSet::new();
343        s.insert(0, 3);
344        s.insert(5, 6);
345        s.insert(10, 20);
346        let mut out = Vec::new();
347        assert!(put_ack(&mut out, &s, 7, 32));
348        match one(&out).unwrap() {
349            Frame::Ack { ranges, delay, ecn } => {
350                assert_eq!(delay, 7);
351                assert!(ecn.is_none());
352                assert_eq!(ranges.iter().collect::<Vec<_>>(), vec![(10, 19), (5, 5), (0, 2)]);
353                assert_eq!(ranges.smallest(), 0);
354            }
355            _ => panic!(),
356        }
357        // Range underflow is a FRAME_ENCODING_ERROR.
358        assert_eq!(one(&[0x02, 5, 0, 1, 2, 5, 0]).unwrap_err().code, FRAME_ENCODING_ERROR);
359        assert!(one(&[0x02, 1, 0, 0, 2]).is_err());
360    }
361
362    #[test]
363    fn all_frames_roundtrip() {
364        let mut out = Vec::new();
365        put_simple(&mut out, PING, &[]);
366        put_simple(&mut out, RESET_STREAM, &[4, 9, 100]);
367        put_simple(&mut out, STOP_SENDING, &[4, 9]);
368        put_crypto(&mut out, 7, b"abc");
369        put_new_token(&mut out, b"tok");
370        put_stream_header(&mut out, 8, 0, 2, false);
371        out.extend_from_slice(b"hi");
372        put_stream_header(&mut out, 8, 2, 1, true);
373        out.push(b'!');
374        put_simple(&mut out, MAX_DATA, &[1000]);
375        put_simple(&mut out, MAX_STREAM_DATA, &[4, 1000]);
376        put_simple(&mut out, MAX_STREAMS_BIDI, &[10]);
377        put_simple(&mut out, MAX_STREAMS_UNI, &[3]);
378        put_simple(&mut out, DATA_BLOCKED, &[5]);
379        put_simple(&mut out, STREAM_DATA_BLOCKED, &[4, 5]);
380        put_simple(&mut out, STREAMS_BLOCKED_BIDI, &[2]);
381        put_simple(&mut out, STREAMS_BLOCKED_UNI, &[2]);
382        put_new_connection_id(&mut out, 1, 0, &[1, 2, 3, 4], &[7; 16]);
383        put_simple(&mut out, RETIRE_CONNECTION_ID, &[1]);
384        put_path(&mut out, false, &[1; 8]);
385        put_path(&mut out, true, &[2; 8]);
386        put_close(&mut out, 0xa, Some(0x08), b"bad");
387        put_close(&mut out, 0x100, None, b"");
388        put_simple(&mut out, HANDSHAKE_DONE, &[]);
389        out.extend_from_slice(&[0, 0, 0]);
390        let mut r = Reader::new(&out);
391        let mut n = 0;
392        while !r.is_empty() {
393            parse(&mut r).unwrap();
394            n += 1;
395        }
396        assert_eq!(n, 23);
397        // Spot checks.
398        assert!(matches!(one(&[0x0b, 4, 2, b'x', b'y']).unwrap(), Frame::Stream { id: 4, offset: 0, fin: true, .. }));
399        assert!(matches!(one(&[0x0c, 4, 9]).unwrap(), Frame::Stream { id: 4, offset: 9, fin: false, data: &[] }));
400        // Malformed.
401        assert!(one(&[0x18, 1, 2, 4, 1, 2, 3, 4]).is_err()); // retire_prior_to > seq
402        assert!(one(&[0x18, 1, 0, 21]).is_err()); // CID too long
403        assert!(one(&[0x07, 0]).is_err()); // empty token
404        assert!(one(&[0x12, 0xd0, 0, 0, 0, 0, 0, 0, 1]).is_err()); // > 2^60 streams
405        assert!(one(&[0x1f]).is_err()); // unknown
406        assert_eq!(one(&[0x40, 0x01]).unwrap_err().code, PROTOCOL_VIOLATION); // non-minimal type
407        assert!(one(&[0x06, 0, 5, 1]).is_err()); // truncated
408        assert!(one(&[0x0e, 0x04, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0]).is_err()); // overflow
409    }
410}