Skip to main content

stackforge_core/layer/quic/
builder.rs

1//! QUIC packet builder (RFC 9000 Sections 17.2 and 17.3).
2//!
3//! Provides [`QuicBuilder`] for constructing QUIC packets in byte form.
4//! All encryption / key scheduling is deliberately out of scope here: the
5//! builder emits the plaintext wire bytes only.
6
7use super::headers::QuicPacketType;
8use super::varint;
9
10/// Builder for QUIC packets.
11///
12/// Supports Initial, Handshake (long header) and 1-RTT (short header) packet
13/// construction.
14///
15/// # Example
16///
17/// ```rust
18/// use stackforge_core::layer::quic::builder::QuicBuilder;
19///
20/// let bytes = QuicBuilder::initial()
21///     .dst_conn_id(vec![0x01, 0x02, 0x03, 0x04])
22///     .src_conn_id(vec![0xAA, 0xBB])
23///     .payload(vec![0xDE, 0xAD, 0xBE, 0xEF])
24///     .build();
25/// ```
26#[derive(Debug, Clone)]
27pub struct QuicBuilder {
28    /// Whether this packet uses a long header (false = short/1-RTT).
29    is_long_header: bool,
30    /// Logical packet type.
31    packet_type: QuicPacketType,
32    /// QUIC version (big-endian u32).  Default = 1 (QUIC v1, RFC 9000).
33    version: u32,
34    /// Destination Connection ID.
35    dst_conn_id: Vec<u8>,
36    /// Source Connection ID (long header only).
37    src_conn_id: Vec<u8>,
38    /// Token (Initial packets only, RFC 9000 Section 17.2.2).
39    token: Vec<u8>,
40    /// Decrypted payload bytes (frames).
41    payload: Vec<u8>,
42    /// Packet number (used verbatim; no truncation is applied).
43    packet_number: u32,
44}
45
46impl Default for QuicBuilder {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl QuicBuilder {
53    /// Create a bare builder.  Most callers should use [`initial`], [`handshake`],
54    /// or [`one_rtt`] instead.
55    #[must_use]
56    pub fn new() -> Self {
57        Self {
58            is_long_header: true,
59            packet_type: QuicPacketType::Initial,
60            version: 1,
61            dst_conn_id: Vec::new(),
62            src_conn_id: Vec::new(),
63            token: Vec::new(),
64            payload: Vec::new(),
65            packet_number: 0,
66        }
67    }
68
69    /// Create a builder pre-configured for a QUIC Initial packet (RFC 9000 Section 17.2.2).
70    #[must_use]
71    pub fn initial() -> Self {
72        Self {
73            is_long_header: true,
74            packet_type: QuicPacketType::Initial,
75            version: 1,
76            ..Self::new()
77        }
78    }
79
80    /// Create a builder pre-configured for a QUIC Handshake packet (RFC 9000 Section 17.2.4).
81    #[must_use]
82    pub fn handshake() -> Self {
83        Self {
84            is_long_header: true,
85            packet_type: QuicPacketType::Handshake,
86            version: 1,
87            ..Self::new()
88        }
89    }
90
91    /// Create a builder pre-configured for a QUIC 1-RTT (short header) packet
92    /// (RFC 9000 Section 17.3).
93    #[must_use]
94    pub fn one_rtt() -> Self {
95        Self {
96            is_long_header: false,
97            packet_type: QuicPacketType::OneRtt,
98            version: 1,
99            ..Self::new()
100        }
101    }
102
103    /// Set the destination connection ID.
104    #[must_use]
105    pub fn dst_conn_id(mut self, id: Vec<u8>) -> Self {
106        self.dst_conn_id = id;
107        self
108    }
109
110    /// Set the source connection ID (ignored for short-header packets).
111    #[must_use]
112    pub fn src_conn_id(mut self, id: Vec<u8>) -> Self {
113        self.src_conn_id = id;
114        self
115    }
116
117    /// Set the QUIC version.
118    #[must_use]
119    pub fn version(mut self, v: u32) -> Self {
120        self.version = v;
121        self
122    }
123
124    /// Set the token (Initial packets only).
125    #[must_use]
126    pub fn token(mut self, t: Vec<u8>) -> Self {
127        self.token = t;
128        self
129    }
130
131    /// Set the (plaintext) payload bytes.
132    #[must_use]
133    pub fn payload(mut self, p: Vec<u8>) -> Self {
134        self.payload = p;
135        self
136    }
137
138    /// Set the packet number.
139    #[must_use]
140    pub fn packet_number(mut self, n: u32) -> Self {
141        self.packet_number = n;
142        self
143    }
144
145    /// Build the QUIC packet into a raw byte buffer.
146    ///
147    /// The packet number is encoded in the minimum number of bytes needed
148    /// (1 through 4) according to RFC 9000 Section 17.1.
149    #[must_use]
150    pub fn build(&self) -> Vec<u8> {
151        if self.is_long_header {
152            self.build_long()
153        } else {
154            self.build_short()
155        }
156    }
157
158    // -------------------------------------------------------------------------
159    // Internal helpers
160    // -------------------------------------------------------------------------
161
162    /// Packet-number byte length (1..=4) for the current `packet_number`.
163    fn pn_len(&self) -> usize {
164        if self.packet_number < 0x100 {
165            1
166        } else if self.packet_number < 0x1_0000 {
167            2
168        } else if self.packet_number < 0x100_0000 {
169            3
170        } else {
171            4
172        }
173    }
174
175    /// Encode the packet number into bytes (big-endian, minimal width).
176    fn encode_pn(&self) -> Vec<u8> {
177        let n = self.pn_len();
178        let pn = self.packet_number;
179        match n {
180            1 => vec![pn as u8],
181            2 => (pn as u16).to_be_bytes().to_vec(),
182            3 => {
183                let b = pn.to_be_bytes();
184                vec![b[1], b[2], b[3]]
185            },
186            _ => pn.to_be_bytes().to_vec(),
187        }
188    }
189
190    /// Long-header packet type bits (bits 5-4 of byte 0).
191    fn long_type_bits(&self) -> u8 {
192        match self.packet_type {
193            QuicPacketType::Initial => 0x00,
194            QuicPacketType::ZeroRtt => 0x01,
195            QuicPacketType::Handshake => 0x02,
196            QuicPacketType::Retry => 0x03,
197            // Short header / VersionNeg don't use this path.
198            _ => 0x00,
199        }
200    }
201
202    /// Build a long-header packet (Initial or Handshake).
203    fn build_long(&self) -> Vec<u8> {
204        let pn_len = self.pn_len();
205        let pn_bytes = self.encode_pn();
206
207        // Length field = len(packet_number) + len(payload)
208        let length_val = (pn_len + self.payload.len()) as u64;
209        let length_varint = varint::encode(length_val);
210
211        // --- Byte 0 ---
212        // Header Form (1) = 1
213        // Fixed Bit (1)   = 1
214        // Long Packet Type (2)
215        // Reserved (2)    = 0
216        // Packet Number Length (2) = pn_len - 1
217        let first_byte: u8 = 0x80                        // Header Form = Long
218            | 0x40                      // Fixed Bit
219            | (self.long_type_bits() << 4)
220            | ((pn_len as u8) - 1); // Packet Number Length (0-based)
221
222        let mut out = Vec::new();
223
224        // Byte 0
225        out.push(first_byte);
226
227        // Bytes 1-4: Version
228        out.extend_from_slice(&self.version.to_be_bytes());
229
230        // DCIL + DCID
231        out.push(self.dst_conn_id.len() as u8);
232        out.extend_from_slice(&self.dst_conn_id);
233
234        // SCIL + SCID
235        out.push(self.src_conn_id.len() as u8);
236        out.extend_from_slice(&self.src_conn_id);
237
238        // Token (Initial only)
239        if self.packet_type == QuicPacketType::Initial {
240            out.extend_from_slice(&varint::encode(self.token.len() as u64));
241            out.extend_from_slice(&self.token);
242        }
243
244        // Length (varint)
245        out.extend_from_slice(&length_varint);
246
247        // Packet Number
248        out.extend_from_slice(&pn_bytes);
249
250        // Payload
251        out.extend_from_slice(&self.payload);
252
253        out
254    }
255
256    /// Build a short-header (1-RTT) packet.
257    fn build_short(&self) -> Vec<u8> {
258        let pn_len = self.pn_len();
259        let pn_bytes = self.encode_pn();
260
261        // Byte 0:
262        // Header Form (1) = 0
263        // Fixed Bit (1)   = 1
264        // Spin Bit (1)    = 0
265        // Reserved (2)    = 0
266        // Key Phase (1)   = 0
267        // Packet Number Length (2) = pn_len - 1
268        let first_byte: u8 = 0x40 | ((pn_len as u8) - 1);
269
270        let mut out = Vec::new();
271        out.push(first_byte);
272
273        // Destination Connection ID (0 bytes for simplicity when not set)
274        out.extend_from_slice(&self.dst_conn_id);
275
276        // Packet Number
277        out.extend_from_slice(&pn_bytes);
278
279        // Payload
280        out.extend_from_slice(&self.payload);
281
282        out
283    }
284}
285
286/// Type alias for building QUIC Initial packets — same as `QuicBuilder::initial()`.
287pub type QuicInitialBuilder = QuicBuilder;
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use crate::layer::quic::headers::{QuicLongHeader, QuicShortHeader};
293
294    #[test]
295    fn test_build_initial_basic() {
296        let bytes = QuicBuilder::initial()
297            .dst_conn_id(vec![0x01, 0x02, 0x03, 0x04])
298            .src_conn_id(vec![])
299            .payload(vec![0xAA, 0xBB])
300            .build();
301
302        // Must start with long-header first byte
303        assert_eq!(bytes[0] & 0x80, 0x80, "Header Form must be 1 (long)");
304        assert_eq!(bytes[0] & 0x40, 0x40, "Fixed Bit must be 1");
305        // Long packet type bits 5-4 = 00 => Initial
306        assert_eq!((bytes[0] & 0x30) >> 4, 0x00);
307
308        // Version = 1
309        assert_eq!(&bytes[1..5], &[0x00, 0x00, 0x00, 0x01]);
310
311        // DCIL = 4
312        assert_eq!(bytes[5], 4);
313        assert_eq!(&bytes[6..10], &[0x01, 0x02, 0x03, 0x04]);
314
315        // SCIL = 0
316        assert_eq!(bytes[10], 0);
317    }
318
319    #[test]
320    fn test_build_initial_parse_back() {
321        let bytes = QuicBuilder::initial()
322            .dst_conn_id(vec![0xDE, 0xAD, 0xBE, 0xEF])
323            .src_conn_id(vec![0x11, 0x22])
324            .build();
325
326        let hdr = QuicLongHeader::parse(&bytes).unwrap();
327        assert_eq!(hdr.packet_type, QuicPacketType::Initial);
328        assert_eq!(hdr.dst_conn_id, vec![0xDE, 0xAD, 0xBE, 0xEF]);
329        assert_eq!(hdr.src_conn_id, vec![0x11, 0x22]);
330        assert_eq!(hdr.version, 1);
331    }
332
333    #[test]
334    fn test_build_handshake() {
335        let bytes = QuicBuilder::handshake()
336            .dst_conn_id(vec![0x01])
337            .payload(vec![0x06, 0x00, 0x01, 0xFF]) // pretend CRYPTO frame
338            .build();
339
340        assert_eq!(bytes[0] & 0x80, 0x80, "long header");
341        assert_eq!((bytes[0] & 0x30) >> 4, 0x02, "Handshake type bits");
342
343        let hdr = QuicLongHeader::parse(&bytes).unwrap();
344        assert_eq!(hdr.packet_type, QuicPacketType::Handshake);
345    }
346
347    #[test]
348    fn test_build_one_rtt() {
349        let bytes = QuicBuilder::one_rtt()
350            .payload(vec![0x01]) // PING
351            .build();
352
353        // Short header: bit 7 = 0
354        assert_eq!(bytes[0] & 0x80, 0x00, "short header");
355        assert_eq!(bytes[0] & 0x40, 0x40, "Fixed Bit must be 1");
356
357        let hdr = QuicShortHeader::parse(&bytes).unwrap();
358        assert_eq!(hdr.header_len, 1);
359    }
360
361    #[test]
362    fn test_build_with_token() {
363        let bytes = QuicBuilder::initial()
364            .token(vec![0xCA, 0xFE])
365            .payload(vec![])
366            .build();
367
368        // Token len varint should be present after SCID
369        // Minimal: [byte0][ver:4][DCIL=0][SCIL=0][TokenLen=2(varint)][CA][FE][Length][PN]
370        // Token length byte at offset 6 (after byte0 + ver4 + DCIL=0 + SCIL=0 = 7 bytes)
371        // Actually: byte0=1, ver=4, DCIL_byte=1, DCID=0, SCIL_byte=1, SCID=0 => pos=7
372        // Then TokenLen varint (2 => encodes as 0x02) at pos=7
373        assert!(bytes.len() >= 12);
374    }
375
376    #[test]
377    fn test_packet_number_encoding() {
378        // Small packet number => 1 byte
379        let bytes = QuicBuilder::initial().packet_number(0).build();
380        assert_eq!(bytes[0] & 0x03, 0, "pn_len-1 = 0 for 1-byte PN");
381
382        // Large packet number => 4 bytes
383        let bytes = QuicBuilder::initial().packet_number(0x1234_5678).build();
384        assert_eq!(bytes[0] & 0x03, 3, "pn_len-1 = 3 for 4-byte PN");
385    }
386}