Skip to main content

bgpkit_parser/encoder/
rib_encoder.rs

1//! MRT Encoder module
2//!
3//! `mrt_encoder` module handles serializing BGP/MRT messages back to MRT binary files. The main
4//! difficulty part of this process is the handling of TableDumpV2 RIB dumps, which requires
5//! reconstructing the peer index table before encoding all other contents.
6
7use crate::models::{
8    Attributes, BgpElem, CommonHeader, EntryType, MrtMessage, NetworkPrefix, Peer, PeerIndexTable,
9    RibAfiEntries, RibEntry, TableDumpV2Message, TableDumpV2Type,
10};
11use crate::utils::convert_timestamp;
12use bytes::{Bytes, BytesMut};
13use ipnet::IpNet;
14use std::collections::HashMap;
15use std::net::{IpAddr, Ipv4Addr};
16
17fn rib_type_for_prefix(prefix: &IpNet, has_add_path: bool) -> TableDumpV2Type {
18    match (prefix.addr().is_ipv6(), has_add_path) {
19        (true, true) => TableDumpV2Type::RibIpv6UnicastAddPath,
20        (true, false) => TableDumpV2Type::RibIpv6Unicast,
21        (false, true) => TableDumpV2Type::RibIpv4UnicastAddPath,
22        (false, false) => TableDumpV2Type::RibIpv4Unicast,
23    }
24}
25
26#[derive(Default)]
27pub struct MrtRibEncoder {
28    index_table: PeerIndexTable,
29
30    per_prefix_entries_map: HashMap<IpNet, HashMap<u16, RibEntry>>,
31
32    timestamp: f64,
33}
34
35impl MrtRibEncoder {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    pub fn reset(&mut self) {
41        self.index_table = PeerIndexTable::default();
42        self.per_prefix_entries_map = HashMap::default();
43        self.timestamp = 0.0;
44    }
45
46    /// Processes a BgpElem and updates the internal data structures.
47    ///
48    /// # Arguments
49    ///
50    /// * `elem` - A reference to a BgpElem that contains the information to be processed.
51    pub fn process_elem(&mut self, elem: &BgpElem) {
52        if self.timestamp == 0.0 {
53            self.timestamp = elem.timestamp;
54        }
55        let bgp_identifier = match elem.peer_ip {
56            IpAddr::V4(ip) => ip,
57            IpAddr::V6(_ip) => Ipv4Addr::from(0),
58        };
59        let peer = Peer::new(bgp_identifier, elem.peer_ip, elem.peer_asn);
60        let peer_index = self.index_table.add_peer(peer);
61        let path_id = elem.prefix.path_id;
62        let prefix = elem.prefix.prefix;
63
64        let entries_map = self.per_prefix_entries_map.entry(prefix).or_default();
65        let entry = RibEntry {
66            peer_index,
67            path_id,
68            originated_time: elem.timestamp as u32,
69            attributes: Attributes::from(elem),
70        };
71        entries_map.insert(peer_index, entry);
72    }
73
74    /// Export the data stored in the struct to a byte array.
75    ///
76    /// The function first encodes the peer-index-table data into a `MrtMessage` and appends it to the `BytesMut` object.
77    /// Then, for each prefix in the `per_prefix_entries_map`, it creates a `RibAfiEntries` object and encodes it as a `MrtMessage`.
78    /// The resulting `BytesMut` object is then converted to an immutable `Bytes` object using `freeze()` and returned.
79    ///
80    /// # Return
81    /// Returns a `Bytes` object containing the exported data as a byte array.
82    pub fn export_bytes(&mut self) -> Bytes {
83        let mut bytes = BytesMut::new();
84
85        // encode peer-index-table
86        let mrt_message = MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(
87            self.index_table.clone(),
88        ));
89        let (seconds, _microseconds) = convert_timestamp(self.timestamp);
90        let subtype = TableDumpV2Type::PeerIndexTable as u16;
91        let data_bytes = mrt_message.encode(subtype);
92        let header = CommonHeader {
93            timestamp: seconds,
94            microsecond_timestamp: None,
95            entry_type: EntryType::TABLE_DUMP_V2,
96            entry_subtype: subtype,
97            length: data_bytes.len() as u32,
98        };
99        let header_bytes = header.encode();
100        bytes.extend(header_bytes);
101        bytes.extend(data_bytes);
102
103        // encode each RibAfiEntries
104        for (entry_count, (prefix, entries_map)) in self.per_prefix_entries_map.iter().enumerate() {
105            let has_add_path = entries_map.values().any(|entry| entry.path_id.is_some());
106            let rib_type = rib_type_for_prefix(prefix, has_add_path);
107
108            let mut prefix_rib_entry = RibAfiEntries {
109                rib_type,
110                sequence_number: entry_count as u32,
111                prefix: NetworkPrefix::new(*prefix, None),
112                rib_entries: vec![],
113            };
114            for entry in entries_map.values() {
115                prefix_rib_entry.rib_entries.push(entry.clone());
116            }
117
118            let mrt_message =
119                MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(prefix_rib_entry));
120
121            let (seconds, _microseconds) = convert_timestamp(self.timestamp);
122            let subtype = rib_type as u16;
123            let data_bytes = mrt_message.encode(subtype);
124            let header_bytes = CommonHeader {
125                timestamp: seconds,
126                microsecond_timestamp: None,
127                entry_type: EntryType::TABLE_DUMP_V2,
128                entry_subtype: subtype,
129                length: data_bytes.len() as u32,
130            }
131            .encode();
132            bytes.extend(header_bytes);
133            bytes.extend(data_bytes);
134        }
135
136        self.reset();
137
138        bytes.freeze()
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::models::Asn;
146    use crate::parse_mrt_record;
147    use bytes::Buf;
148    use std::io::Cursor;
149
150    #[test]
151    fn test_encoding_rib() {
152        let mut encoder = MrtRibEncoder::new();
153        let mut elem = BgpElem {
154            peer_ip: IpAddr::V4("10.0.0.1".parse().unwrap()),
155            peer_asn: Asn::from(65000),
156            ..Default::default()
157        };
158        elem.prefix.prefix = "10.250.0.0/24".parse().unwrap();
159        encoder.process_elem(&elem);
160        elem.prefix.prefix = "10.251.0.0/24".parse().unwrap();
161        encoder.process_elem(&elem);
162        let bytes = encoder.export_bytes();
163
164        let mut cursor = Cursor::new(bytes.clone());
165        while cursor.has_remaining() {
166            let _parsed = parse_mrt_record(&mut cursor).unwrap();
167        }
168
169        // v6
170        let mut encoder = MrtRibEncoder::new();
171        let mut elem = BgpElem {
172            peer_ip: IpAddr::V6("::1".parse().unwrap()),
173            peer_asn: Asn::from(65000),
174            ..Default::default()
175        };
176        // ipv6 prefix
177        elem.prefix.prefix = "2001:db8::/32".parse().unwrap();
178        encoder.process_elem(&elem);
179        let bytes = encoder.export_bytes();
180
181        let mut cursor = Cursor::new(bytes.clone());
182        while cursor.has_remaining() {
183            let _parsed = parse_mrt_record(&mut cursor).unwrap();
184        }
185    }
186
187    #[test]
188    fn test_encoding_rib_with_add_path() {
189        let mut encoder = MrtRibEncoder::new();
190        let mut elem = BgpElem {
191            peer_ip: IpAddr::V4("10.0.0.1".parse().unwrap()),
192            peer_asn: Asn::from(65000),
193            ..Default::default()
194        };
195        elem.prefix = NetworkPrefix::new("10.250.0.0/24".parse().unwrap(), Some(42));
196        encoder.process_elem(&elem);
197
198        let bytes = encoder.export_bytes();
199        let mut cursor = Cursor::new(bytes);
200        let _peer_table = parse_mrt_record(&mut cursor).unwrap();
201        let parsed = parse_mrt_record(&mut cursor).unwrap();
202
203        match parsed.message {
204            MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(rib)) => {
205                assert_eq!(rib.rib_type, TableDumpV2Type::RibIpv4UnicastAddPath);
206                assert_eq!(rib.rib_entries.len(), 1);
207                assert_eq!(rib.rib_entries[0].path_id, Some(42));
208            }
209            other => panic!("unexpected MRT message: {other:?}"),
210        }
211    }
212}