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