bgpkit_parser/parser/mrt/
mrt_header.rs

1use crate::models::{CommonHeader, EntryType};
2use crate::ParserError;
3use bytes::{Buf, BufMut, Bytes, BytesMut};
4use std::io::Read;
5
6/// MRT common header [RFC6396][header].
7///
8/// [header]: https://tools.ietf.org/html/rfc6396#section-4.1
9///
10/// A MRT record is constructed as the following:
11/// ```text
12///  0                   1                   2                   3
13///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
14/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15/// |                           Timestamp                           |
16/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17/// |             Type              |            Subtype            |
18/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19/// |                             Length                            |
20/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21/// |                      Message... (variable)
22/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23///
24/// ```
25///
26/// Or with extended timestamp:
27/// ```text
28///  0                   1                   2                   3
29///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
30/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31/// |                           Timestamp                           |
32/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33/// |             Type              |            Subtype            |
34/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35/// |                             Length                            |
36/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37/// |                      Microsecond Timestamp                    |
38/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
39/// |                      Message... (variable)
40/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
41/// ```
42pub fn parse_common_header<T: Read>(input: &mut T) -> Result<CommonHeader, ParserError> {
43    let mut raw_bytes = [0u8; 12];
44    input.read_exact(&mut raw_bytes)?;
45    let mut data = BytesMut::from(&raw_bytes[..]);
46
47    let timestamp = data.get_u32();
48    let entry_type_raw = data.get_u16();
49    let entry_type = EntryType::try_from(entry_type_raw)?;
50    let entry_subtype = data.get_u16();
51    // the length field does not include the length of the common header
52    let mut length = data.get_u32();
53
54    let microsecond_timestamp = match &entry_type {
55        EntryType::BGP4MP_ET => {
56            length -= 4;
57            let mut raw_bytes: [u8; 4] = [0; 4];
58            input.read_exact(&mut raw_bytes)?;
59            Some(BytesMut::from(&raw_bytes[..]).get_u32())
60        }
61        _ => None,
62    };
63
64    Ok(CommonHeader {
65        timestamp,
66        microsecond_timestamp,
67        entry_type,
68        entry_subtype,
69        length,
70    })
71}
72
73impl CommonHeader {
74    pub fn encode(&self) -> Bytes {
75        let mut bytes = BytesMut::new();
76        bytes.put_slice(&self.timestamp.to_be_bytes());
77        bytes.put_u16(self.entry_type as u16);
78        bytes.put_u16(self.entry_subtype);
79
80        match self.microsecond_timestamp {
81            None => bytes.put_u32(self.length),
82            Some(microseconds) => {
83                // When the microsecond timestamp is present, the length must be adjusted to account
84                // for the stace used by the extra timestamp data.
85                bytes.put_u32(self.length + 4);
86                bytes.put_u32(microseconds);
87            }
88        };
89        bytes.freeze()
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::models::EntryType;
97    use bytes::Buf;
98
99    /// Test that the length is not adjusted when the microsecond timestamp is not present.
100    #[test]
101    fn test_encode_common_header() {
102        let header = CommonHeader {
103            timestamp: 1,
104            microsecond_timestamp: None,
105            entry_type: EntryType::BGP4MP,
106            entry_subtype: 4,
107            length: 5,
108        };
109
110        let expected = Bytes::from_static(&[
111            0, 0, 0, 1, // timestamp
112            0, 16, // entry type
113            0, 4, // entry subtype
114            0, 0, 0, 5, // length
115        ]);
116
117        let encoded = header.encode();
118        assert_eq!(encoded, expected);
119
120        let mut reader = expected.reader();
121        let parsed = parse_common_header(&mut reader).unwrap();
122        assert_eq!(parsed, header);
123    }
124
125    /// Test that the length is adjusted when the microsecond timestamp is present.
126    #[test]
127    fn test_encode_common_header_et() {
128        let header = CommonHeader {
129            timestamp: 1,
130            microsecond_timestamp: Some(230_000),
131            entry_type: EntryType::BGP4MP_ET,
132            entry_subtype: 4,
133            length: 5,
134        };
135
136        let expected = Bytes::from_static(&[
137            0, 0, 0, 1, // timestamp
138            0, 17, // entry type
139            0, 4, // entry subtype
140            0, 0, 0, 9, // length
141            0, 3, 130, 112, // microsecond timestamp
142        ]);
143
144        let encoded = header.encode();
145        assert_eq!(encoded, expected);
146
147        let mut reader = expected.reader();
148        let parsed = parse_common_header(&mut reader).unwrap();
149        assert_eq!(parsed, header);
150    }
151}