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}