Skip to main content

stackforge_core/layer/quic/
frames.rs

1//! QUIC frame types and frame parsing (RFC 9000 Section 19 / Table 3).
2
3use super::varint;
4
5/// QUIC frame types as defined in RFC 9000 Table 3.
6///
7/// Discriminants are not used here (the `Unknown(u8)` variant holds the raw byte
8/// for unrecognised frame types).  Use [`FrameType::from_u8`] to construct and
9/// [`FrameType::as_u8`] to convert back to the wire byte.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FrameType {
12    Padding,
13    Ping,
14    Ack,
15    AckEcn,
16    ResetStream,
17    StopSending,
18    Crypto,
19    NewToken,
20    /// Stream frames (raw type byte 0x08-0x0F depending on flags).
21    Stream,
22    MaxData,
23    MaxStreamData,
24    MaxStreams,
25    MaxStreamsBidi,
26    DataBlocked,
27    StreamDataBlocked,
28    StreamsBlocked,
29    StreamsBlockedBidi,
30    NewConnectionId,
31    RetireConnectionId,
32    PathChallenge,
33    PathResponse,
34    ConnectionClose,
35    ConnectionCloseApp,
36    HandshakeDone,
37    Unknown(u8),
38}
39
40impl FrameType {
41    /// Return the canonical wire byte for this frame type (the lowest byte in
42    /// any range, e.g. `Stream` → `0x08`).
43    #[must_use]
44    pub fn as_u8(&self) -> u8 {
45        match self {
46            Self::Padding => 0x00,
47            Self::Ping => 0x01,
48            Self::Ack => 0x02,
49            Self::AckEcn => 0x03,
50            Self::ResetStream => 0x04,
51            Self::StopSending => 0x05,
52            Self::Crypto => 0x06,
53            Self::NewToken => 0x07,
54            Self::Stream => 0x08,
55            Self::MaxData => 0x10,
56            Self::MaxStreamData => 0x11,
57            Self::MaxStreams => 0x12,
58            Self::MaxStreamsBidi => 0x13,
59            Self::DataBlocked => 0x14,
60            Self::StreamDataBlocked => 0x15,
61            Self::StreamsBlocked => 0x16,
62            Self::StreamsBlockedBidi => 0x17,
63            Self::NewConnectionId => 0x18,
64            Self::RetireConnectionId => 0x19,
65            Self::PathChallenge => 0x1A,
66            Self::PathResponse => 0x1B,
67            Self::ConnectionClose => 0x1C,
68            Self::ConnectionCloseApp => 0x1D,
69            Self::HandshakeDone => 0x1E,
70            Self::Unknown(t) => *t,
71        }
72    }
73}
74
75impl FrameType {
76    /// Convert a raw byte to a `FrameType`.
77    ///
78    /// Stream frames in the range 0x08-0x0F are all mapped to `Stream`;
79    /// the low 3 bits carry flags (OFF, LEN, FIN) and are preserved in the
80    /// raw byte but collapsed to the `Stream` variant here.
81    #[must_use]
82    pub fn from_u8(t: u8) -> Self {
83        match t {
84            0x00 => Self::Padding,
85            0x01 => Self::Ping,
86            0x02 => Self::Ack,
87            0x03 => Self::AckEcn,
88            0x04 => Self::ResetStream,
89            0x05 => Self::StopSending,
90            0x06 => Self::Crypto,
91            0x07 => Self::NewToken,
92            0x08..=0x0F => Self::Stream,
93            0x10 => Self::MaxData,
94            0x11 => Self::MaxStreamData,
95            0x12 => Self::MaxStreams,
96            0x13 => Self::MaxStreamsBidi,
97            0x14 => Self::DataBlocked,
98            0x15 => Self::StreamDataBlocked,
99            0x16 => Self::StreamsBlocked,
100            0x17 => Self::StreamsBlockedBidi,
101            0x18 => Self::NewConnectionId,
102            0x19 => Self::RetireConnectionId,
103            0x1A => Self::PathChallenge,
104            0x1B => Self::PathResponse,
105            0x1C => Self::ConnectionClose,
106            0x1D => Self::ConnectionCloseApp,
107            0x1E => Self::HandshakeDone,
108            _ => Self::Unknown(t),
109        }
110    }
111
112    /// Return a human-readable name for this frame type.
113    #[must_use]
114    pub fn name(&self) -> &'static str {
115        match self {
116            Self::Padding => "PADDING",
117            Self::Ping => "PING",
118            Self::Ack => "ACK",
119            Self::AckEcn => "ACK_ECN",
120            Self::ResetStream => "RESET_STREAM",
121            Self::StopSending => "STOP_SENDING",
122            Self::Crypto => "CRYPTO",
123            Self::NewToken => "NEW_TOKEN",
124            Self::Stream => "STREAM",
125            Self::MaxData => "MAX_DATA",
126            Self::MaxStreamData => "MAX_STREAM_DATA",
127            Self::MaxStreams => "MAX_STREAMS",
128            Self::MaxStreamsBidi => "MAX_STREAMS_BIDI",
129            Self::DataBlocked => "DATA_BLOCKED",
130            Self::StreamDataBlocked => "STREAM_DATA_BLOCKED",
131            Self::StreamsBlocked => "STREAMS_BLOCKED",
132            Self::StreamsBlockedBidi => "STREAMS_BLOCKED_BIDI",
133            Self::NewConnectionId => "NEW_CONNECTION_ID",
134            Self::RetireConnectionId => "RETIRE_CONNECTION_ID",
135            Self::PathChallenge => "PATH_CHALLENGE",
136            Self::PathResponse => "PATH_RESPONSE",
137            Self::ConnectionClose => "CONNECTION_CLOSE",
138            Self::ConnectionCloseApp => "CONNECTION_CLOSE_APP",
139            Self::HandshakeDone => "HANDSHAKE_DONE",
140            Self::Unknown(_) => "UNKNOWN",
141        }
142    }
143}
144
145/// A parsed QUIC frame — a lightweight view into the payload buffer.
146///
147/// Does not own data; `offset` and `length` describe the region of the
148/// original buffer that contains this frame.
149#[derive(Debug, Clone)]
150pub struct QuicFrame {
151    /// The frame type.
152    pub frame_type: FrameType,
153    /// Byte offset in the payload buffer where this frame starts.
154    pub offset: usize,
155    /// Total byte length of this frame (including the type byte).
156    pub length: usize,
157}
158
159/// Parse all frames from a decrypted QUIC payload buffer.
160///
161/// This is a best-effort parser: it stops at the first parse error or unknown
162/// frame type rather than returning an error.  The returned `Vec` may therefore
163/// be incomplete if the payload is malformed or encrypted.
164#[must_use]
165pub fn parse_frames(buf: &[u8]) -> Vec<QuicFrame> {
166    let mut frames = Vec::new();
167    let mut pos = 0;
168
169    while pos < buf.len() {
170        let frame_start = pos;
171        let raw_type = buf[pos];
172        let frame_type = FrameType::from_u8(raw_type);
173        pos += 1; // consume type byte
174
175        match frame_type {
176            FrameType::Padding => {
177                // A run of 0x00 bytes; each counts as a separate PADDING frame.
178                frames.push(QuicFrame {
179                    frame_type,
180                    offset: frame_start,
181                    length: 1,
182                });
183            },
184
185            FrameType::Ping => {
186                frames.push(QuicFrame {
187                    frame_type,
188                    offset: frame_start,
189                    length: 1,
190                });
191            },
192
193            FrameType::Ack | FrameType::AckEcn => {
194                // ACK: type(1) + largest_ack(varint) + ack_delay(varint) +
195                //      ack_range_count(varint) + first_ack_range(varint) +
196                //      [ack_range_count * (gap(varint) + ack_range(varint))] +
197                //      [ECT0_count, ECT1_count, ECN_CE_count if AckEcn]
198                let remaining = &buf[pos..];
199                let (largest_ack, n1) = match varint::decode(remaining) {
200                    Some(v) => v,
201                    None => break,
202                };
203                let _ = largest_ack;
204                pos += n1;
205
206                let remaining = &buf[pos..];
207                let (_ack_delay, n2) = match varint::decode(remaining) {
208                    Some(v) => v,
209                    None => break,
210                };
211                pos += n2;
212
213                let remaining = &buf[pos..];
214                let (ack_range_count, n3) = match varint::decode(remaining) {
215                    Some(v) => v,
216                    None => break,
217                };
218                pos += n3;
219
220                // first_ack_range
221                let remaining = &buf[pos..];
222                let (_first_range, n4) = match varint::decode(remaining) {
223                    Some(v) => v,
224                    None => break,
225                };
226                pos += n4;
227
228                // subsequent ack ranges: gap + ack_range for each
229                let mut ok = true;
230                for _ in 0..ack_range_count {
231                    if let Some((_, ng)) = varint::decode(&buf[pos..]) {
232                        pos += ng;
233                    } else {
234                        ok = false;
235                        break;
236                    }
237                    if let Some((_, nr)) = varint::decode(&buf[pos..]) {
238                        pos += nr;
239                    } else {
240                        ok = false;
241                        break;
242                    }
243                }
244                if !ok {
245                    break;
246                }
247
248                // ECN counts (AckEcn only)
249                if frame_type == FrameType::AckEcn {
250                    for _ in 0..3 {
251                        if let Some((_, n)) = varint::decode(&buf[pos..]) {
252                            pos += n;
253                        } else {
254                            break;
255                        }
256                    }
257                }
258
259                frames.push(QuicFrame {
260                    frame_type,
261                    offset: frame_start,
262                    length: pos - frame_start,
263                });
264            },
265
266            FrameType::Crypto => {
267                // CRYPTO: type(1) + offset(varint) + length(varint) + data
268                let (_crypto_offset, n1) = match varint::decode(&buf[pos..]) {
269                    Some(v) => v,
270                    None => break,
271                };
272                pos += n1;
273
274                let (data_len, n2) = match varint::decode(&buf[pos..]) {
275                    Some(v) => v,
276                    None => break,
277                };
278                pos += n2;
279
280                let data_len = data_len as usize;
281                if pos + data_len > buf.len() {
282                    break;
283                }
284                pos += data_len;
285
286                frames.push(QuicFrame {
287                    frame_type,
288                    offset: frame_start,
289                    length: pos - frame_start,
290                });
291            },
292
293            FrameType::NewToken => {
294                // NEW_TOKEN: type(1) + token_length(varint) + token
295                let (token_len, n1) = match varint::decode(&buf[pos..]) {
296                    Some(v) => v,
297                    None => break,
298                };
299                pos += n1;
300
301                let token_len = token_len as usize;
302                if pos + token_len > buf.len() {
303                    break;
304                }
305                pos += token_len;
306
307                frames.push(QuicFrame {
308                    frame_type,
309                    offset: frame_start,
310                    length: pos - frame_start,
311                });
312            },
313
314            FrameType::Stream => {
315                // Stream type byte low 3 bits:
316                //   bit 0x01 = OFF bit (offset field present)
317                //   bit 0x02 = LEN bit (length field present)
318                //   bit 0x04 = FIN bit
319                let off_bit = raw_type & 0x01 != 0;
320                let len_bit = raw_type & 0x02 != 0;
321
322                // stream_id (varint)
323                let (_stream_id, n1) = match varint::decode(&buf[pos..]) {
324                    Some(v) => v,
325                    None => break,
326                };
327                pos += n1;
328
329                // optional offset field
330                if off_bit {
331                    let (_stream_offset, n2) = match varint::decode(&buf[pos..]) {
332                        Some(v) => v,
333                        None => break,
334                    };
335                    pos += n2;
336                }
337
338                // optional length field
339                if len_bit {
340                    let (data_len, n3) = match varint::decode(&buf[pos..]) {
341                        Some(v) => v,
342                        None => break,
343                    };
344                    pos += n3;
345
346                    let data_len = data_len as usize;
347                    if pos + data_len > buf.len() {
348                        break;
349                    }
350                    pos += data_len;
351                } else {
352                    // No length field — data extends to end of packet.
353                    pos = buf.len();
354                }
355
356                frames.push(QuicFrame {
357                    frame_type,
358                    offset: frame_start,
359                    length: pos - frame_start,
360                });
361            },
362
363            FrameType::MaxData
364            | FrameType::MaxStreams
365            | FrameType::MaxStreamsBidi
366            | FrameType::DataBlocked
367            | FrameType::StreamsBlocked
368            | FrameType::StreamsBlockedBidi
369            | FrameType::RetireConnectionId => {
370                // Single varint argument.
371                let (_, n1) = match varint::decode(&buf[pos..]) {
372                    Some(v) => v,
373                    None => break,
374                };
375                pos += n1;
376
377                frames.push(QuicFrame {
378                    frame_type,
379                    offset: frame_start,
380                    length: pos - frame_start,
381                });
382            },
383
384            FrameType::MaxStreamData | FrameType::StreamDataBlocked => {
385                // Two varint arguments: stream_id + limit/offset.
386                let (_, n1) = match varint::decode(&buf[pos..]) {
387                    Some(v) => v,
388                    None => break,
389                };
390                pos += n1;
391
392                let (_, n2) = match varint::decode(&buf[pos..]) {
393                    Some(v) => v,
394                    None => break,
395                };
396                pos += n2;
397
398                frames.push(QuicFrame {
399                    frame_type,
400                    offset: frame_start,
401                    length: pos - frame_start,
402                });
403            },
404
405            FrameType::ResetStream => {
406                // RESET_STREAM: stream_id(varint) + app_error_code(varint) + final_size(varint)
407                for _ in 0..3 {
408                    let (_, n) = if let Some(v) = varint::decode(&buf[pos..]) {
409                        v
410                    } else {
411                        frames.push(QuicFrame {
412                            frame_type,
413                            offset: frame_start,
414                            length: pos - frame_start,
415                        });
416                        return frames;
417                    };
418                    pos += n;
419                }
420                frames.push(QuicFrame {
421                    frame_type,
422                    offset: frame_start,
423                    length: pos - frame_start,
424                });
425            },
426
427            FrameType::StopSending => {
428                // STOP_SENDING: stream_id(varint) + app_error_code(varint)
429                for _ in 0..2 {
430                    let (_, n) = if let Some(v) = varint::decode(&buf[pos..]) {
431                        v
432                    } else {
433                        frames.push(QuicFrame {
434                            frame_type,
435                            offset: frame_start,
436                            length: pos - frame_start,
437                        });
438                        return frames;
439                    };
440                    pos += n;
441                }
442                frames.push(QuicFrame {
443                    frame_type,
444                    offset: frame_start,
445                    length: pos - frame_start,
446                });
447            },
448
449            FrameType::NewConnectionId => {
450                // NEW_CONNECTION_ID: seq_no(varint) + retire_prior_to(varint) +
451                //   conn_id_len(u8) + conn_id(N bytes) + stateless_reset_token(16 bytes)
452                let (_, n1) = match varint::decode(&buf[pos..]) {
453                    Some(v) => v,
454                    None => break,
455                };
456                pos += n1;
457
458                let (_, n2) = match varint::decode(&buf[pos..]) {
459                    Some(v) => v,
460                    None => break,
461                };
462                pos += n2;
463
464                if pos >= buf.len() {
465                    break;
466                }
467                let conn_id_len = buf[pos] as usize;
468                pos += 1;
469
470                if pos + conn_id_len + 16 > buf.len() {
471                    break;
472                }
473                pos += conn_id_len + 16;
474
475                frames.push(QuicFrame {
476                    frame_type,
477                    offset: frame_start,
478                    length: pos - frame_start,
479                });
480            },
481
482            FrameType::PathChallenge | FrameType::PathResponse => {
483                // Fixed 8-byte data field.
484                if pos + 8 > buf.len() {
485                    break;
486                }
487                pos += 8;
488
489                frames.push(QuicFrame {
490                    frame_type,
491                    offset: frame_start,
492                    length: pos - frame_start,
493                });
494            },
495
496            FrameType::ConnectionClose => {
497                // CONNECTION_CLOSE: error_code(varint) + frame_type(varint) +
498                //   reason_phrase_len(varint) + reason_phrase
499                let (_, n1) = match varint::decode(&buf[pos..]) {
500                    Some(v) => v,
501                    None => break,
502                };
503                pos += n1;
504
505                let (_, n2) = match varint::decode(&buf[pos..]) {
506                    Some(v) => v,
507                    None => break,
508                };
509                pos += n2;
510
511                let (phrase_len, n3) = match varint::decode(&buf[pos..]) {
512                    Some(v) => v,
513                    None => break,
514                };
515                pos += n3;
516
517                let phrase_len = phrase_len as usize;
518                if pos + phrase_len > buf.len() {
519                    break;
520                }
521                pos += phrase_len;
522
523                frames.push(QuicFrame {
524                    frame_type,
525                    offset: frame_start,
526                    length: pos - frame_start,
527                });
528            },
529
530            FrameType::ConnectionCloseApp => {
531                // CONNECTION_CLOSE (app): error_code(varint) +
532                //   reason_phrase_len(varint) + reason_phrase
533                let (_, n1) = match varint::decode(&buf[pos..]) {
534                    Some(v) => v,
535                    None => break,
536                };
537                pos += n1;
538
539                let (phrase_len, n2) = match varint::decode(&buf[pos..]) {
540                    Some(v) => v,
541                    None => break,
542                };
543                pos += n2;
544
545                let phrase_len = phrase_len as usize;
546                if pos + phrase_len > buf.len() {
547                    break;
548                }
549                pos += phrase_len;
550
551                frames.push(QuicFrame {
552                    frame_type,
553                    offset: frame_start,
554                    length: pos - frame_start,
555                });
556            },
557
558            FrameType::HandshakeDone => {
559                // HANDSHAKE_DONE: no fields.
560                frames.push(QuicFrame {
561                    frame_type,
562                    offset: frame_start,
563                    length: 1,
564                });
565            },
566
567            FrameType::Unknown(_) => {
568                // Stop on unknown frame type to avoid mis-parsing.
569                break;
570            },
571        }
572    }
573
574    frames
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    #[test]
582    fn test_frame_type_from_u8() {
583        assert_eq!(FrameType::from_u8(0x00), FrameType::Padding);
584        assert_eq!(FrameType::from_u8(0x01), FrameType::Ping);
585        assert_eq!(FrameType::from_u8(0x02), FrameType::Ack);
586        assert_eq!(FrameType::from_u8(0x06), FrameType::Crypto);
587        assert_eq!(FrameType::from_u8(0x08), FrameType::Stream);
588        assert_eq!(FrameType::from_u8(0x0F), FrameType::Stream);
589        assert_eq!(FrameType::from_u8(0x1E), FrameType::HandshakeDone);
590        assert_eq!(FrameType::from_u8(0xFF), FrameType::Unknown(0xFF));
591    }
592
593    #[test]
594    fn test_frame_type_names() {
595        assert_eq!(FrameType::Padding.name(), "PADDING");
596        assert_eq!(FrameType::Crypto.name(), "CRYPTO");
597        assert_eq!(FrameType::Stream.name(), "STREAM");
598        assert_eq!(FrameType::Unknown(0xAB).name(), "UNKNOWN");
599    }
600
601    #[test]
602    fn test_parse_padding_and_ping() {
603        let buf = [0x00u8, 0x00, 0x01]; // two PADDING, one PING
604        let frames = parse_frames(&buf);
605        assert_eq!(frames.len(), 3);
606        assert_eq!(frames[0].frame_type, FrameType::Padding);
607        assert_eq!(frames[1].frame_type, FrameType::Padding);
608        assert_eq!(frames[2].frame_type, FrameType::Ping);
609        assert_eq!(frames[2].offset, 2);
610        assert_eq!(frames[2].length, 1);
611    }
612
613    #[test]
614    fn test_parse_handshake_done() {
615        let buf = [0x1Eu8];
616        let frames = parse_frames(&buf);
617        assert_eq!(frames.len(), 1);
618        assert_eq!(frames[0].frame_type, FrameType::HandshakeDone);
619        assert_eq!(frames[0].length, 1);
620    }
621
622    #[test]
623    fn test_parse_crypto_frame() {
624        // CRYPTO: type=0x06, offset=0(varint:1 byte=0x00), length=4(varint:1 byte=0x04), data
625        let buf = [0x06u8, 0x00, 0x04, 0xDE, 0xAD, 0xBE, 0xEF];
626        let frames = parse_frames(&buf);
627        assert_eq!(frames.len(), 1);
628        assert_eq!(frames[0].frame_type, FrameType::Crypto);
629        assert_eq!(frames[0].offset, 0);
630        assert_eq!(frames[0].length, 7); // 1 + 1 + 1 + 4
631    }
632
633    #[test]
634    fn test_parse_stream_frame_with_len() {
635        // STREAM type=0x0A (OFF=0, LEN=1, FIN=0): stream_id=1, length=2, data
636        // 0x0A = 0b00001010: OFF bit=0, LEN bit=1, FIN bit=0
637        let buf = [0x0Au8, 0x01, 0x02, 0xAA, 0xBB];
638        let frames = parse_frames(&buf);
639        assert_eq!(frames.len(), 1);
640        assert_eq!(frames[0].frame_type, FrameType::Stream);
641        assert_eq!(frames[0].length, 5);
642    }
643
644    #[test]
645    fn test_parse_stops_on_unknown() {
646        let buf = [0x01u8, 0xFF, 0x01]; // PING, Unknown(0xFF), PING
647        let frames = parse_frames(&buf);
648        assert_eq!(frames.len(), 1);
649        assert_eq!(frames[0].frame_type, FrameType::Ping);
650    }
651}