bgpkit_parser/models/mrt/mod.rs
1//! MRT message and relevant structs.
2
3pub mod bgp4mp;
4pub mod table_dump;
5pub mod table_dump_v2;
6
7pub use bgp4mp::*;
8use num_enum::{IntoPrimitive, TryFromPrimitive};
9pub use table_dump::*;
10pub use table_dump_v2::*;
11
12/// MrtRecord is a wrapper struct that contains a header and a message.
13///
14/// A MRT record is constructed as the following:
15/// ```text
16/// 0 1 2 3
17/// 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
18/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19/// | Header... (variable) |
20/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21/// | Message... (variable)
22/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23/// ```
24///
25/// See [CommonHeader] for the content in header, and [MrtMessage] for the
26/// message format.
27#[derive(Debug, PartialEq, Clone, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct MrtRecord {
30 pub common_header: CommonHeader,
31 pub message: MrtMessage,
32}
33
34/// MRT common header.
35///
36/// A CommonHeader ([RFC6396 section 2][header-link]) is constructed as the following:
37/// ```text
38/// 0 1 2 3
39/// 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
40/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
41/// | Timestamp |
42/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
43/// | Type | Subtype |
44/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
45/// | Length |
46/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
47/// ```
48///
49/// Or with extended timestamp:
50/// ```text
51/// 0 1 2 3
52/// 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
53/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
54/// | Timestamp |
55/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56/// | Type | Subtype |
57/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
58/// | Length |
59/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60/// | Microsecond Timestamp |
61/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62/// ```
63///
64/// The headers include the following:
65/// - timestamp: 32 bits
66/// - entry_type: [EntryType] enum
67/// - entry_subtype: entry subtype
68/// - length: length of the message in octets
69/// - (`ET` type only) microsecond_timestamp: microsecond part of the timestamp.
70/// only applicable to the MRT message type with `_ET` suffix, such as
71/// `BGP4MP_ET`
72///
73/// [header-link]: https://datatracker.ietf.org/doc/html/rfc6396#section-2
74#[derive(Debug, Copy, Clone, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct CommonHeader {
77 pub timestamp: u32,
78 pub microsecond_timestamp: Option<u32>,
79 pub entry_type: EntryType,
80 pub entry_subtype: u16,
81 pub length: u32,
82}
83
84impl PartialEq for CommonHeader {
85 fn eq(&self, other: &Self) -> bool {
86 self.timestamp == other.timestamp
87 && self.microsecond_timestamp == other.microsecond_timestamp
88 && self.entry_type == other.entry_type
89 && self.entry_subtype == other.entry_subtype
90 // && self.length == other.length
91 // relax the length check as it might be different due to incorrect encoding
92 }
93}
94
95#[derive(Debug, PartialEq, Clone, Eq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub enum MrtMessage {
98 TableDumpMessage(TableDumpMessage),
99 TableDumpV2Message(TableDumpV2Message),
100 Bgp4Mp(Bgp4MpEnum),
101}
102
103/// MRT entry type.
104///
105/// EntryType indicates the type of the current MRT record. Type 0 to 10 are deprecated.
106///
107/// Excerpt from [RFC6396 section 4](https://datatracker.ietf.org/doc/html/rfc6396#section-4):
108/// ```text
109/// The following MRT Types are currently defined for the MRT format.
110/// The MRT Types that contain the "_ET" suffix in their names identify
111/// those types that use an Extended Timestamp MRT Header. The Subtype
112/// and Message fields in these types remain as defined for the MRT Types
113/// of the same name without the "_ET" suffix.
114///
115/// 11 OSPFv2
116/// 12 TABLE_DUMP
117/// 13 TABLE_DUMP_V2
118/// 16 BGP4MP
119/// 17 BGP4MP_ET
120/// 32 ISIS
121/// 33 ISIS_ET
122/// 48 OSPFv3
123/// 49 OSPFv3_ET
124/// ```
125#[derive(Debug, TryFromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Eq, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127#[allow(non_camel_case_types)]
128#[repr(u16)]
129pub enum EntryType {
130 // START DEPRECATED
131 NULL = 0,
132 START = 1,
133 DIE = 2,
134 I_AM_DEAD = 3,
135 PEER_DOWN = 4,
136 BGP = 5,
137 RIP = 6,
138 IDRP = 7,
139 RIPNG = 8,
140 BGP4PLUS = 9,
141 BGP4PLUS_01 = 10,
142 // END DEPRECATED
143 OSPFv2 = 11,
144 TABLE_DUMP = 12,
145 TABLE_DUMP_V2 = 13,
146 BGP4MP = 16,
147 BGP4MP_ET = 17,
148 ISIS = 32,
149 ISIS_ET = 33,
150 OSPFv3 = 48,
151 OSPFv3_ET = 49,
152}
153
154#[cfg(test)]
155mod tests {
156
157 #[test]
158 #[cfg(feature = "serde")]
159 fn test_entry_type_serialize_and_deserialize() {
160 use super::*;
161 let types = vec![
162 EntryType::NULL,
163 EntryType::START,
164 EntryType::DIE,
165 EntryType::I_AM_DEAD,
166 EntryType::PEER_DOWN,
167 EntryType::BGP,
168 EntryType::RIP,
169 EntryType::IDRP,
170 EntryType::RIPNG,
171 EntryType::BGP4PLUS,
172 EntryType::BGP4PLUS_01,
173 EntryType::OSPFv2,
174 EntryType::TABLE_DUMP,
175 EntryType::TABLE_DUMP_V2,
176 EntryType::BGP4MP,
177 EntryType::BGP4MP_ET,
178 EntryType::ISIS,
179 EntryType::ISIS_ET,
180 EntryType::OSPFv3,
181 EntryType::OSPFv3_ET,
182 ];
183
184 for entry_type in types {
185 let serialized = serde_json::to_string(&entry_type).unwrap();
186 let deserialized: EntryType = serde_json::from_str(&serialized).unwrap();
187
188 assert_eq!(entry_type, deserialized);
189 }
190 }
191
192 #[test]
193 #[cfg(feature = "serde")]
194 fn test_serialization() {
195 use super::*;
196 use serde_json;
197 use std::net::IpAddr;
198 use std::str::FromStr;
199
200 let mrt_record = MrtRecord {
201 common_header: CommonHeader {
202 timestamp: 0,
203 microsecond_timestamp: None,
204 entry_type: EntryType::BGP4MP,
205 entry_subtype: 0,
206 length: 0,
207 },
208 message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
209 msg_type: Bgp4MpType::StateChange,
210 peer_asn: crate::models::Asn::new_32bit(0),
211 local_asn: crate::models::Asn::new_32bit(0),
212 interface_index: 1,
213 peer_ip: IpAddr::from_str("10.0.0.0").unwrap(),
214 local_addr: IpAddr::from_str("10.0.0.0").unwrap(),
215 old_state: BgpState::Idle,
216 new_state: BgpState::Connect,
217 })),
218 };
219
220 let serialized = serde_json::to_string(&mrt_record).unwrap();
221 let deserialized: MrtRecord = serde_json::from_str(&serialized).unwrap();
222 assert_eq!(mrt_record, deserialized);
223 }
224}