webrtc_sniffer/dtls/
header.rs

1use std::fmt;
2
3use nom::{
4    bytes::complete::take,
5    error::{Error, ErrorKind},
6    number::complete::{be_u16, be_u64, be_u8},
7    Err, IResult,
8};
9
10#[repr(u8)]
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ContentType {
13    ChangeCipherSpec = 20,
14    Alert = 21,
15    Handshake = 22,
16    ApplicationData = 23,
17}
18
19impl ContentType {
20    pub fn from_u8(value: u8) -> Option<Self> {
21        match value {
22            20 => Some(ContentType::ChangeCipherSpec),
23            21 => Some(ContentType::Alert),
24            22 => Some(ContentType::Handshake),
25            23 => Some(ContentType::ApplicationData),
26            _ => None,
27        }
28    }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct Chunk<'a> {
33    pub ty: ContentType,
34    pub epoch: u16,
35    pub sequence_number: u64,
36    pub length: u16,
37    pub body: &'a [u8],
38}
39
40impl fmt::Display for Chunk<'_> {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        let Chunk {
43            ty,
44            epoch,
45            sequence_number: seq,
46            length,
47            ..
48        } = self;
49        write!(
50            f,
51            "{ty:?}, epoch={epoch}, seq={seq:012x}, len={length}, data={}",
52            hex::encode(self.body)
53        )
54    }
55}
56
57impl<'a> Chunk<'a> {
58    pub fn parse(input: &'a [u8]) -> IResult<&'a [u8], Self> {
59        let (input, ty_byte) = be_u8(input)?;
60        let ty = ContentType::from_u8(ty_byte)
61            .ok_or_else(|| Err::Error(Error::new(input, ErrorKind::Alt)))?;
62
63        let (input, legacy_record_version) = be_u16(input)?;
64        if legacy_record_version != 0xFEFD {
65            return Err(Err::Error(Error::new(input, ErrorKind::Alt)));
66        }
67
68        let (input, t) = be_u64(input)?;
69        let epoch = (t >> 48) as u16;
70        let sequence_number = t & ((1 << 48) - 1);
71        let (input, length) = be_u16(input)?;
72        let (input, body) = take(length as usize)(input)?;
73
74        let header = Chunk {
75            ty,
76            epoch,
77            sequence_number,
78            length,
79            body,
80        };
81
82        Ok((input, header))
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_parse_header() {
92        let bytes = &[
93            22, // ContentType::Handshake
94            0xFE, 0xFD, // legacy_record_version (0xFEFD for DTLS 1.0)
95            0x00, 0x01, // epoch
96            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // sequence_number
97            0x00, 0x03, // length
98            0x00, 0x00, 0x00,
99        ];
100
101        let result = Chunk::parse(bytes);
102        let (_, chunk) = result.unwrap();
103
104        assert_eq!(chunk.ty, ContentType::Handshake);
105        assert_eq!(chunk.epoch, 1);
106        assert_eq!(chunk.sequence_number, 1);
107        assert_eq!(chunk.length, 3);
108        assert_eq!(chunk.body, [0; 3]);
109    }
110}