packet_parser 1.4.0

A powerful and modular Rust crate for network packet parsing.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
// Copyright (c) 2026 Cyprien Avico avicocyprien@yahoo.com
//
// Licensed under the MIT License <LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms.

use crate::checks::application::quic::{
    validate_fixed_bit, validate_length_field, validate_long_header, validate_payload_available,
    validate_version,
};

#[cfg_attr(doc, aquamarine::aquamarine)]
/// QUIC Long Header Packet
///
/// ```mermaid
/// ---
/// title: QuicPacket
/// ---
/// packet-beta
/// 0-0: "Header Form u1"
/// 1-1: "Fixed Bit u1"
/// 2-3: "Long Packet Type u2"
/// 4-5: "Reserved u2"
/// 6-7: "Packet Number Length u2"
/// 8-39: "Version u32"
/// 40-47: "DCID Length u8"
/// 48-207: "Destination Connection ID variable"
/// 208-215: "SCID Length u8"
/// 216-375: "Source Connection ID variable"
/// 376-439: "Token / Length / Packet Number / Payload variable"
/// ```
///
/// Modélisation minimale d'un paquet QUIC v1 (RFC 9000/9001) avec Long Header:
/// couvre `Initial` et `Handshake`, ainsi que quelques frames fréquentes.
///
/// Remarques :
/// - Un paquet `Initial` peut contenir un Token.
/// - Un paquet `Handshake` n'a pas de Token.
/// - Le champ `length` du Long Header inclut PN + payload chiffré (frames).
/// - Le `packet_number` est encodé sur 1..=4 octets ; on expose ici la longueur et la valeur étendue.
/// - Les frames peuvent rester chiffrées selon le contexte ; si tu ne déchiffres pas,
///   utilise `QuicFrame::EncryptedPayload(Vec<u8>)`.
#[derive(Debug, Clone)]
pub enum QuicPacket {
    /// Paquet QUIC avec Long Header de type Initial (Packet Type = 0x00)
    Initial {
        /// Entête Long Header commun.
        header: QuicLongHeader,
        /// Jeton fourni/retourné par le serveur (anti-DoS, address validation).
        token: Vec<u8>,
        /// Liste des frames décodées (si déchiffrement réussi) ou charge brute.
        payload: QuicPayload,
    },
    /// Paquet QUIC avec Long Header de type Handshake (Packet Type = 0x02)
    Handshake {
        /// Entête Long Header commun.
        header: QuicLongHeader,
        /// Liste des frames décodées (si déchiffrement réussi) ou charge brute.
        payload: QuicPayload,
    },
    /// Autres Long Headers (0-RTT, Retry) si besoin plus tard.
    OtherLong {
        header: QuicLongHeader,
        payload: QuicPayload,
    },
}

/// Entête commun aux paquets QUIC à Long Header.
#[derive(Debug, Clone)]
pub struct QuicLongHeader {
    /// Doit valoir 1 pour Long Header.
    pub header_form_long: bool,
    /// Bit fixé à 1 par la spec.
    pub fixed_bit: bool,
    /// Type de paquet (Initial, 0-RTT, Handshake, Retry).
    pub packet_type: QuicPacketType,
    /// Version QUIC (ex: 0x00000001 pour QUIC v1).
    pub version: u32,
    /// Destination Connection ID (DCID).
    pub dcid: ConnectionId,
    /// Source Connection ID (SCID).
    pub scid: ConnectionId,
    /// Longueur du champ `packet_number` en octets (1..=4).
    pub pn_length: u8,
    /// Longueur (varint) annoncée pour PN + payload chiffré.
    pub length_field: u64,
    /// Numéro de paquet reconstruit (valeur étendue), si PN decoding dispo.
    pub packet_number: Option<u64>,
}

/// Type de paquet Long Header.
#[derive(Debug, Clone, Copy)]
pub enum QuicPacketType {
    Initial,
    ZeroRtt,
    Handshake,
    Retry,
    Unknown(u8),
}

/// Connection ID générique (0..=20 octets courants, mais extensible).
#[derive(Debug, Clone)]
pub struct ConnectionId {
    /// Longueur du CID (0..=20 dans ta capture).
    pub len: u8,
    /// Octets du CID.
    pub bytes: Vec<u8>,
}

/// Charge utile d’un paquet QUIC une fois l’entête parsé.
#[derive(Debug, Clone)]
pub enum QuicPayload {
    /// Ensemble de frames décodées (après déchiffrement).
    Frames(Vec<QuicFrame>),
    /// Payload encore chiffré ou non interprété.
    EncryptedPayload(Vec<u8>),
}

/// Ensemble minimal de frames QUIC utiles au handshake.
#[derive(Debug, Clone)]
pub enum QuicFrame {
    /// Frame ACK (RFC 9000 §19.3)
    Ack(AckFrame),
    /// Frame CRYPTO (RFC 9001 §4) — transporte les enregistrements TLS 1.3.
    Crypto(CryptoFrame),
    /// PADDING (0x00)
    Padding { length: u64 },
    /// PING (0x01)
    Ping,
    /// Autre type de frame non gérée ici.
    Unknown { frame_type: u64, raw: Vec<u8> },
}

/// Frame ACK (schéma simplifié : first range + ranges supplémentaires).
#[derive(Debug, Clone)]
pub struct AckFrame {
    /// Plus grand numéro de paquet accusé de réception.
    pub largest_acknowledged: u64,
    /// Délai d’ACK en microsecondes (valeur QUIC encodée en exponentiel; ici post-décodée).
    pub ack_delay_us: u64,
    /// Nombre de ranges additionnels (peut être 0).
    pub ack_range_count: u64,
    /// Premier intervalle (taille du range commençant à `largest_acknowledged`).
    pub first_ack_range: u64,
    /// Ranges additionnels : (gap, ack_range_len)
    pub additional_ranges: Vec<AckRange>,
}

/// Un intervalle d’ACK supplémentaire (gap + longueur du range).
#[derive(Debug, Clone)]
pub struct AckRange {
    /// Ecart (paquets non accusés) avant le prochain range.
    pub gap: u64,
    /// Taille du range suivant.
    pub ack_range_len: u64,
}

/// Frame CRYPTO : transporte des fragments TLS 1.3 (ClientHello, ServerHello, etc.).
#[derive(Debug, Clone)]
pub struct CryptoFrame {
    /// Offset dans le flux CRYPTO (peut arriver en fragments).
    pub offset: u64,
    /// Taille des données (facultatif si `data.len()` suffit).
    pub length: u64,
    /// Données TLS (potentiellement fragmentées) — par ex. ServerHello, EncryptedExtensions…
    pub data: Vec<u8>,
}

// Helper cursor for parsing byte slices
struct Cur<'a> {
    b: &'a [u8],
    i: usize,
}

impl<'a> Cur<'a> {
    fn new(b: &'a [u8]) -> Self {
        Self { b, i: 0 }
    }

    fn left(&self) -> usize {
        self.b.len().saturating_sub(self.i)
    }

    fn take(&mut self, n: usize) -> Result<&'a [u8], crate::parse::application::ApplicationError> {
        if self.left() < n {
            return Err(crate::parse::application::ApplicationError::QuicParseError);
        }
        let s = &self.b[self.i..self.i + n];
        self.i += n;
        Ok(s)
    }

    fn take_u8(&mut self) -> Result<u8, crate::parse::application::ApplicationError> {
        Ok(*self.take(1)?.first().unwrap())
    }
}

// Implement Clone for Cur to allow cloning the cursor
impl<'a> Clone for Cur<'a> {
    fn clone(&self) -> Self {
        Self {
            b: self.b,
            i: self.i,
        }
    }
}

impl TryFrom<&[u8]> for QuicPacket {
    type Error = crate::parse::application::ApplicationError;

    fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
        // Helper function to create errors
        fn err<S: Into<String>>(_s: S) -> crate::parse::application::ApplicationError {
            crate::parse::application::ApplicationError::QuicParseError
        }

        // QUIC varint (RFC 9000 §16)
        fn read_varint(cur: &mut Cur) -> Result<u64, crate::parse::application::ApplicationError> {
            let first = cur.take_u8()?;
            let prefix = first >> 6; // 00, 01, 10, 11
            let (total, mask): (usize, u64) = match prefix {
                0 => (1, 0b0011_1111),
                1 => (2, 0b0011_1111),
                2 => (4, 0b0011_1111),
                3 => (8, 0b0011_1111),
                _ => unreachable!(),
            };
            let mut val = (first & (mask as u8)) as u64;
            for _ in 1..total {
                val = (val << 8) | (cur.take_u8()? as u64);
            }
            Ok(val)
        }

        fn read_cid(
            cur: &mut Cur,
        ) -> Result<ConnectionId, crate::parse::application::ApplicationError> {
            let len = cur.take_u8()?;
            let bytes = cur.take(len as usize)?.to_vec();
            Ok(ConnectionId { len, bytes })
        }

        // --- Parsing --------------------------------------------------------
        let mut cur = Cur::new(buf);

        // 1) Octet 0 : Long Header bits
        let b0 = cur.take_u8()?;

        let header_form_long = (b0 & 0b1000_0000) != 0;
        validate_long_header(header_form_long)?;

        let fixed_bit = (b0 & 0b0100_0000) != 0;
        validate_fixed_bit(fixed_bit)?;

        let lptype = (b0 >> 4) & 0b11; // Long Packet Type (2 bits)
        let _reserved = (b0 >> 2) & 0b11; // reserved
        let pn_len_code = b0 & 0b11; // PN length code
        let pn_length = pn_len_code + 1; // 1..=4

        let packet_type = match lptype {
            0 => QuicPacketType::Initial,
            1 => QuicPacketType::ZeroRtt,
            2 => QuicPacketType::Handshake,
            3 => QuicPacketType::Retry,
            x => QuicPacketType::Unknown(x),
        };

        // 2) Version
        let ver_bytes = cur.take(4)?;
        let version = u32::from_be_bytes([ver_bytes[0], ver_bytes[1], ver_bytes[2], ver_bytes[3]]);
        validate_version(version)?;

        // 3) DCID / SCID
        let dcid = read_cid(&mut cur)?;
        let scid = read_cid(&mut cur)?;

        // 4) En-tête commun assemblé (length_field/pn/… à compléter après)
        let mut header = QuicLongHeader {
            header_form_long,
            fixed_bit,
            packet_type,
            version,
            dcid,
            scid,
            pn_length,
            length_field: 0,     // placeholder, on remplit après
            packet_number: None, // idem
        };

        // 5) Champs spécifiques selon type
        match packet_type {
            QuicPacketType::Initial => {
                // Token Length (varint) + Token
                let token_len = read_varint(&mut cur)? as usize;
                let token = cur.take(token_len)?.to_vec();

                // Length (varint) = PN + payload chiffré
                let length_field = read_varint(&mut cur)?;
                header.length_field = length_field;

                // PN
                let pn_raw = cur.take(header.pn_length as usize)?;
                let mut pn: u64 = 0;
                for &b in pn_raw {
                    pn = (pn << 8) | (b as u64);
                }
                header.packet_number = Some(pn);

                // Payload (length_field inclut PN + payload)
                let remaining_for_payload = validate_length_field(length_field, header.pn_length)?;
                validate_payload_available(cur.left(), remaining_for_payload)?;

                let payload_bytes = cur.take(remaining_for_payload)?.to_vec();

                Ok(QuicPacket::Initial {
                    header,
                    token,
                    payload: QuicPayload::EncryptedPayload(payload_bytes),
                })
            }

            QuicPacketType::Handshake => {
                // Pas de Token, directement Length (varint)
                let length_field = read_varint(&mut cur)?;
                header.length_field = length_field;

                // PN
                let pn_raw = cur.take(header.pn_length as usize)?;
                let mut pn: u64 = 0;
                for &b in pn_raw {
                    pn = (pn << 8) | (b as u64);
                }
                header.packet_number = Some(pn);

                // Payload
                let remaining_for_payload = validate_length_field(length_field, header.pn_length)?;
                validate_payload_available(cur.left(), remaining_for_payload)?;

                let payload_bytes = cur.take(remaining_for_payload)?.to_vec();

                Ok(QuicPacket::Handshake {
                    header,
                    payload: QuicPayload::EncryptedPayload(payload_bytes),
                })
            }

            QuicPacketType::ZeroRtt => {
                // Length (varint), PN, payload (non déchiffré ici)
                let length_field = read_varint(&mut cur)?;
                header.length_field = length_field;

                let pn_raw = cur.take(header.pn_length as usize)?;
                let mut pn: u64 = 0;
                for &b in pn_raw {
                    pn = (pn << 8) | (b as u64);
                }
                header.packet_number = Some(pn);

                // Payload
                let remaining_for_payload = validate_length_field(length_field, header.pn_length)?;
                validate_payload_available(cur.left(), remaining_for_payload)?;

                let payload_bytes = cur.take(remaining_for_payload)?.to_vec();

                Ok(QuicPacket::OtherLong {
                    header,
                    payload: QuicPayload::EncryptedPayload(payload_bytes),
                })
            }

            QuicPacketType::Retry => {
                // Retry a un format spécifique (pas de Length ni PN).
                // On met tout le reste en payload brut.
                let rest = cur.take(cur.left())?.to_vec();
                Ok(QuicPacket::OtherLong {
                    header,
                    payload: QuicPayload::EncryptedPayload(rest),
                })
            }

            QuicPacketType::Unknown(_t) => {
                // Tentative générique: Length (varint) si possible, sinon tout en brut
                let mut snapshot = cur.clone();
                let length_field = match read_varint(&mut cur) {
                    Ok(v) => v,
                    Err(_) => {
                        // pas de varint plausible, tout en brut
                        let rest = snapshot
                            .take(snapshot.left())
                            .map_err(|_| err("internal"))?
                            .to_vec();
                        return Ok(QuicPacket::OtherLong {
                            header,
                            payload: QuicPayload::EncryptedPayload(rest),
                        });
                    }
                };
                header.length_field = length_field;

                // PN
                let pn_raw = cur.take(header.pn_length as usize)?;
                let mut pn: u64 = 0;
                for &b in pn_raw {
                    pn = (pn << 8) | (b as u64);
                }
                header.packet_number = Some(pn);

                // Payload
                let remaining_for_payload = validate_length_field(length_field, header.pn_length)?;
                validate_payload_available(cur.left(), remaining_for_payload)?;

                let payload_bytes = cur.take(remaining_for_payload)?.to_vec();

                Ok(QuicPacket::OtherLong {
                    header,
                    payload: QuicPayload::EncryptedPayload(payload_bytes),
                })
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*; // adapte si tes types sont dans un autre module

    // -------- Tests --------

    #[test]
    fn test_valid_quic_initial_minimal() {
        // construit un QUIC Initial minimal (comme tes autres tests),
        // pas besoin ETH/IPv6/UDP pour l’instant.
        let mut buf = Vec::new();
        buf.push(0xC0); // Long=1, Fixed=1, Initial, PN len=1
        buf.extend_from_slice(&0x00000001u32.to_be_bytes());
        buf.push(0); // dcid len
        buf.push(0); // scid len
        buf.push(0x00); // token len = 0
        buf.push(0x01); // length = 1 (PN seul)
        buf.push(0x00); // PN

        let pkt = QuicPacket::try_from(buf.as_slice()).expect("must parse");
        match pkt {
            QuicPacket::Initial {
                header,
                token,
                payload,
            } => {
                assert_eq!(header.version, 1);
                assert!(token.is_empty());
                matches!(payload, QuicPayload::EncryptedPayload(ref v) if v.is_empty());
            }
            _ => panic!("expected Initial"),
        }
    }

    // #[test]
    // fn test_error_short_buffer() {
    //     let buf = [0xC0u8]; // beaucoup trop court
    //     let res = QuicPacket::try_from(&buf);
    //     assert!(matches!(res, Err(crate::parse::application::ApplicationError::QuicParseError)));
    // }

    #[test]
    fn test_error_not_long_header() {
        // MSB = 0 → Short Header → notre parseur Long Header doit refuser
        // 0x40 = 0100_0000 (fixed bit = 1, mais header_form_long = 0)
        let mut buf = Vec::new();
        buf.push(0x40);
        buf.extend_from_slice(&0x00000001u32.to_be_bytes()); // version (ne sera pas lu)
        buf.push(0); // dcid len
        buf.push(0); // scid len
        let res = QuicPacket::try_from(buf.as_slice());
        assert!(matches!(
            res,
            Err(crate::parse::application::ApplicationError::QuicParseError)
        ));
    }

    #[test]
    fn test_error_fixed_bit_zero() {
        // Long Header (MSB=1) mais fixed bit = 0 → doit échouer
        // 1000_0000 = 0x80 : header_form_long=1, fixed=0, type=00, pnlen=00
        let mut buf = Vec::new();
        buf.push(0x80);
        buf.extend_from_slice(&0x00000001u32.to_be_bytes()); // version
        buf.push(0); // dcid len
        buf.push(0); // scid len
        // Pour Initial: token_len=0 varint (0x00)
        buf.push(0x00);
        // length varint: 1 (PN seul)
        buf.push(0x01);
        // PN sur 1 octet (pn_length=1 via b0=0x80 -> pn_len_code=0 -> 1)
        buf.push(0x00);
        let res = QuicPacket::try_from(buf.as_slice());
        assert!(matches!(
            res,
            Err(crate::parse::application::ApplicationError::QuicParseError)
        ));
    }

    #[test]
    fn test_error_truncated_payload_length() {
        // Construire un Initial valide en apparence mais avec length trop grand
        // b0: 1100_0000 = 0xC0 (Long=1, Fixed=1, Type=Initial(00), PN len code=00 -> 1 octet)
        let mut buf = Vec::new();
        buf.push(0xC0);
        // version v1
        buf.extend_from_slice(&0x00000001u32.to_be_bytes());
        // dcid/scid vides
        buf.push(0); // dcid len
        buf.push(0); // scid len
        // token_len = 0 (varint 1o)
        buf.push(0x00);
        // length = 5 (varint 1o) => PN(1) + payload(4) attendus
        buf.push(0x05);
        // PN (1 octet)
        buf.push(0xAA);
        // MAIS on ne met que 2 octets de payload au lieu de 4 → doit échouer
        buf.extend_from_slice(&[0x01, 0x02]);

        let res = QuicPacket::try_from(buf.as_slice());
        assert!(matches!(
            res,
            Err(crate::parse::application::ApplicationError::QuicParseError)
        ));
    }

    #[test]
    fn test_initial_pkn_1_ack() {
        let bytes = hex::decode(
            "c4000000010008f409517248c4ab52004016dae7c01a42788f5049396532534a03ae8ebd63bf94e4",
        )
        .expect("Invalid hex string");

        let parsed = QuicPacket::try_from(bytes.as_slice()).expect("must parse");

        match parsed {
            QuicPacket::Initial {
                header,
                token,
                payload,
            } => {
                // Header bits
                assert!(header.header_form_long);
                assert!(header.fixed_bit);

                // Type / version
                assert!(matches!(header.packet_type, QuicPacketType::Initial));
                assert_eq!(header.version, 1);

                // DCID / SCID
                assert_eq!(header.dcid.len, 0);
                assert!(header.dcid.bytes.is_empty());

                assert_eq!(header.scid.len, 8);
                assert_eq!(header.scid.bytes, hex::decode("f409517248c4ab52").unwrap());

                // Token
                assert!(token.is_empty());

                // Length field (PN + payload chiffré)
                assert_eq!(header.length_field, 22);
                assert_eq!(header.pn_length, 1);

                // IMPORTANT: ici tu lis le PN "protégé" (pas celui affiché par Wireshark)
                assert_eq!(header.packet_number, Some(0xDA));

                // Payload (22 - 1 = 21 bytes)
                match payload {
                    QuicPayload::EncryptedPayload(v) => {
                        assert_eq!(
                            v,
                            hex::decode("e7c01a42788f5049396532534a03ae8ebd63bf94e4").unwrap()
                        );
                    }
                    _ => panic!("expected EncryptedPayload"),
                }
            }
            _ => panic!("expected Initial"),
        }
    }
}