1pub mod bgp4mp;
4pub mod table_dump;
5pub mod table_dump_v2;
6
7pub use bgp4mp::*;
8use num_enum::{IntoPrimitive, TryFromPrimitive};
9use std::fmt::{Display, Formatter};
10pub use table_dump::*;
11pub use table_dump_v2::*;
12
13#[derive(Debug, PartialEq, Clone, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct MrtRecord {
31 pub common_header: CommonHeader,
32 pub message: MrtMessage,
33}
34
35#[derive(Debug, Copy, Clone, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77pub struct CommonHeader {
78 pub timestamp: u32,
79 pub microsecond_timestamp: Option<u32>,
80 pub entry_type: EntryType,
81 pub entry_subtype: u16,
82 pub length: u32,
83}
84
85impl PartialEq for CommonHeader {
86 fn eq(&self, other: &Self) -> bool {
87 self.timestamp == other.timestamp
88 && self.microsecond_timestamp == other.microsecond_timestamp
89 && self.entry_type == other.entry_type
90 && self.entry_subtype == other.entry_subtype
91 }
94}
95
96impl Display for CommonHeader {
97 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
98 let ts = match self.microsecond_timestamp {
99 Some(us) => format!("{}.{:06}", self.timestamp, us),
100 None => self.timestamp.to_string(),
101 };
102 write!(
103 f,
104 "MRT|{}|{:?}|{}|{}",
105 ts, self.entry_type, self.entry_subtype, self.length
106 )
107 }
108}
109
110impl Display for MrtRecord {
111 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115 let ts = match self.common_header.microsecond_timestamp {
116 Some(us) => format!("{}.{:06}", self.common_header.timestamp, us),
117 None => self.common_header.timestamp.to_string(),
118 };
119 write!(
120 f,
121 "MRT|{}|{:?}|{}|{}",
122 ts, self.common_header.entry_type, self.common_header.entry_subtype, self.message
123 )
124 }
125}
126
127impl Display for MrtMessage {
128 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129 match self {
130 MrtMessage::TableDumpMessage(msg) => {
131 write!(f, "TABLE_DUMP|{}|{}", msg.prefix, msg.peer_ip)
132 }
133 MrtMessage::TableDumpV2Message(msg) => match msg {
134 TableDumpV2Message::PeerIndexTable(pit) => {
135 write!(f, "PEER_INDEX_TABLE|{}", pit.id_peer_map.len())
136 }
137 TableDumpV2Message::RibAfi(rib) => {
138 write!(
139 f,
140 "RIB|{:?}|{}|{} entries",
141 rib.rib_type,
142 rib.prefix,
143 rib.rib_entries.len()
144 )
145 }
146 TableDumpV2Message::RibGeneric(rib) => {
147 write!(
148 f,
149 "RIB_GENERIC|AFI {:?}|SAFI {:?}|{} entries",
150 rib.afi,
151 rib.safi,
152 rib.rib_entries.len()
153 )
154 }
155 TableDumpV2Message::GeoPeerTable(gpt) => {
156 write!(f, "GEO_PEER_TABLE|{} peers", gpt.geo_peers.len())
157 }
158 },
159 MrtMessage::Bgp4Mp(bgp4mp) => match bgp4mp {
160 Bgp4MpEnum::StateChange(sc) => {
161 write!(
162 f,
163 "STATE_CHANGE|{}|{}|{:?}->{:?}",
164 sc.peer_ip, sc.peer_asn, sc.old_state, sc.new_state
165 )
166 }
167 Bgp4MpEnum::Message(msg) => {
168 let msg_type = match &msg.bgp_message {
169 crate::models::BgpMessage::Open(_) => "OPEN",
170 crate::models::BgpMessage::Update(_) => "UPDATE",
171 crate::models::BgpMessage::Notification(_) => "NOTIFICATION",
172 crate::models::BgpMessage::KeepAlive => "KEEPALIVE",
173 };
174 write!(f, "BGP4MP|{}|{}|{}", msg.peer_ip, msg.peer_asn, msg_type)
175 }
176 },
177 }
178 }
179}
180
181#[derive(Debug, PartialEq, Clone, Eq)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183pub enum MrtMessage {
184 TableDumpMessage(TableDumpMessage),
185 TableDumpV2Message(TableDumpV2Message),
186 Bgp4Mp(Bgp4MpEnum),
187}
188
189#[derive(Debug, TryFromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Eq, Hash)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213#[allow(non_camel_case_types)]
214#[repr(u16)]
215pub enum EntryType {
216 NULL = 0,
218 START = 1,
219 DIE = 2,
220 I_AM_DEAD = 3,
221 PEER_DOWN = 4,
222 BGP = 5,
223 RIP = 6,
224 IDRP = 7,
225 RIPNG = 8,
226 BGP4PLUS = 9,
227 BGP4PLUS_01 = 10,
228 OSPFv2 = 11,
230 TABLE_DUMP = 12,
231 TABLE_DUMP_V2 = 13,
232 BGP4MP = 16,
233 BGP4MP_ET = 17,
234 ISIS = 32,
235 ISIS_ET = 33,
236 OSPFv3 = 48,
237 OSPFv3_ET = 49,
238}
239
240#[cfg(test)]
241mod tests {
242
243 #[test]
244 fn test_mrt_record_display() {
245 use super::*;
246 use crate::models::Asn;
247 use std::net::IpAddr;
248 use std::str::FromStr;
249
250 let mrt_record = MrtRecord {
251 common_header: CommonHeader {
252 timestamp: 1609459200,
253 microsecond_timestamp: None,
254 entry_type: EntryType::BGP4MP,
255 entry_subtype: 0,
256 length: 0,
257 },
258 message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
259 msg_type: Bgp4MpType::StateChange,
260 peer_asn: Asn::new_32bit(65000),
261 local_asn: Asn::new_32bit(65001),
262 interface_index: 1,
263 peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
264 local_addr: IpAddr::from_str("10.0.0.2").unwrap(),
265 old_state: BgpState::Idle,
266 new_state: BgpState::Connect,
267 })),
268 };
269
270 let display = format!("{}", mrt_record);
271 assert!(display.contains("1609459200"));
272 assert!(display.contains("BGP4MP"));
273 assert!(display.contains("STATE_CHANGE"));
274 assert!(display.contains("10.0.0.1"));
275 assert!(display.contains("65000"));
276 }
277
278 #[test]
279 fn test_common_header_display() {
280 use super::*;
281
282 let header = CommonHeader {
283 timestamp: 1609459200,
284 microsecond_timestamp: Some(500000),
285 entry_type: EntryType::BGP4MP_ET,
286 entry_subtype: 4,
287 length: 128,
288 };
289
290 let display = format!("{}", header);
291 assert!(display.contains("1609459200.500000"));
292 assert!(display.contains("BGP4MP_ET"));
293 assert!(display.contains("128"));
294 }
295
296 #[test]
297 #[cfg(feature = "serde")]
298 fn test_entry_type_serialize_and_deserialize() {
299 use super::*;
300 let types = vec![
301 EntryType::NULL,
302 EntryType::START,
303 EntryType::DIE,
304 EntryType::I_AM_DEAD,
305 EntryType::PEER_DOWN,
306 EntryType::BGP,
307 EntryType::RIP,
308 EntryType::IDRP,
309 EntryType::RIPNG,
310 EntryType::BGP4PLUS,
311 EntryType::BGP4PLUS_01,
312 EntryType::OSPFv2,
313 EntryType::TABLE_DUMP,
314 EntryType::TABLE_DUMP_V2,
315 EntryType::BGP4MP,
316 EntryType::BGP4MP_ET,
317 EntryType::ISIS,
318 EntryType::ISIS_ET,
319 EntryType::OSPFv3,
320 EntryType::OSPFv3_ET,
321 ];
322
323 for entry_type in types {
324 let serialized = serde_json::to_string(&entry_type).unwrap();
325 let deserialized: EntryType = serde_json::from_str(&serialized).unwrap();
326
327 assert_eq!(entry_type, deserialized);
328 }
329 }
330
331 #[test]
332 #[cfg(feature = "serde")]
333 fn test_serialization() {
334 use super::*;
335 use serde_json;
336 use std::net::IpAddr;
337 use std::str::FromStr;
338
339 let mrt_record = MrtRecord {
340 common_header: CommonHeader {
341 timestamp: 0,
342 microsecond_timestamp: None,
343 entry_type: EntryType::BGP4MP,
344 entry_subtype: 0,
345 length: 0,
346 },
347 message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
348 msg_type: Bgp4MpType::StateChange,
349 peer_asn: crate::models::Asn::new_32bit(0),
350 local_asn: crate::models::Asn::new_32bit(0),
351 interface_index: 1,
352 peer_ip: IpAddr::from_str("10.0.0.0").unwrap(),
353 local_addr: IpAddr::from_str("10.0.0.0").unwrap(),
354 old_state: BgpState::Idle,
355 new_state: BgpState::Connect,
356 })),
357 };
358
359 let serialized = serde_json::to_string(&mrt_record).unwrap();
360 let deserialized: MrtRecord = serde_json::from_str(&serialized).unwrap();
361 assert_eq!(mrt_record, deserialized);
362 }
363}