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