use crate::models::{
Attributes, BgpElem, CommonHeader, EntryType, MrtMessage, NetworkPrefix, Peer, PeerIndexTable,
RibAfiEntries, RibEntry, TableDumpV2Message, TableDumpV2Type,
};
use crate::utils::convert_timestamp;
use bytes::{Bytes, BytesMut};
use ipnet::IpNet;
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr};
#[derive(Default)]
pub struct MrtRibEncoder {
index_table: PeerIndexTable,
per_prefix_entries_map: HashMap<IpNet, HashMap<u16, RibEntry>>,
timestamp: f64,
}
impl MrtRibEncoder {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.index_table = PeerIndexTable::default();
self.per_prefix_entries_map = HashMap::default();
self.timestamp = 0.0;
}
pub fn process_elem(&mut self, elem: &BgpElem) {
if self.timestamp == 0.0 {
self.timestamp = elem.timestamp;
}
let bgp_identifier = match elem.peer_ip {
IpAddr::V4(ip) => ip,
IpAddr::V6(_ip) => Ipv4Addr::from(0),
};
let peer = Peer::new(bgp_identifier, elem.peer_ip, elem.peer_asn);
let peer_id = self.index_table.add_peer(peer);
let prefix = elem.prefix.prefix;
let entries_map = self.per_prefix_entries_map.entry(prefix).or_default();
let entry = RibEntry {
peer_index: peer_id,
originated_time: elem.timestamp as u32,
attributes: Attributes::from(elem),
};
entries_map.insert(peer_id, entry);
}
pub fn export_bytes(&mut self) -> Bytes {
let mut bytes = BytesMut::new();
let mrt_message = MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(
self.index_table.clone(),
));
let (seconds, _microseconds) = convert_timestamp(self.timestamp);
let subtype = TableDumpV2Type::PeerIndexTable as u16;
let data_bytes = mrt_message.encode(subtype);
let header = CommonHeader {
timestamp: seconds,
microsecond_timestamp: None,
entry_type: EntryType::TABLE_DUMP_V2,
entry_subtype: subtype,
length: data_bytes.len() as u32,
};
let header_bytes = header.encode();
bytes.extend(header_bytes);
bytes.extend(data_bytes);
for (entry_count, (prefix, entries_map)) in self.per_prefix_entries_map.iter().enumerate() {
let rib_type = match prefix.addr().is_ipv6() {
true => TableDumpV2Type::RibIpv6Unicast,
false => TableDumpV2Type::RibIpv4Unicast,
};
let mut prefix_rib_entry = RibAfiEntries {
rib_type,
sequence_number: entry_count as u32,
prefix: NetworkPrefix::new(*prefix, 0),
rib_entries: vec![],
};
for entry in entries_map.values() {
prefix_rib_entry.rib_entries.push(entry.clone());
}
let mrt_message =
MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(prefix_rib_entry));
let (seconds, _microseconds) = convert_timestamp(self.timestamp);
let subtype = rib_type as u16;
let data_bytes = mrt_message.encode(subtype);
let header_bytes = CommonHeader {
timestamp: seconds,
microsecond_timestamp: None,
entry_type: EntryType::TABLE_DUMP_V2,
entry_subtype: subtype,
length: data_bytes.len() as u32,
}
.encode();
bytes.extend(header_bytes);
bytes.extend(data_bytes);
}
self.reset();
bytes.freeze()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::Asn;
use crate::parse_mrt_record;
use bytes::Buf;
use std::io::Cursor;
#[test]
fn test_encoding_rib() {
let mut encoder = MrtRibEncoder::new();
let mut elem = BgpElem::default();
elem.peer_ip = IpAddr::V4("10.0.0.1".parse().unwrap());
elem.peer_asn = Asn::from(65000);
elem.prefix.prefix = "10.250.0.0/24".parse().unwrap();
encoder.process_elem(&elem);
elem.prefix.prefix = "10.251.0.0/24".parse().unwrap();
encoder.process_elem(&elem);
let bytes = encoder.export_bytes();
let mut cursor = Cursor::new(bytes.clone());
while cursor.has_remaining() {
let parsed = parse_mrt_record(&mut cursor).unwrap();
dbg!(&parsed);
}
let mut encoder = MrtRibEncoder::new();
let mut elem = BgpElem::default();
elem.peer_ip = IpAddr::V6("::1".parse().unwrap());
elem.peer_asn = Asn::from(65000);
elem.prefix.prefix = "2001:db8::/32".parse().unwrap();
encoder.process_elem(&elem);
let bytes = encoder.export_bytes();
let mut cursor = Cursor::new(bytes.clone());
while cursor.has_remaining() {
let parsed = parse_mrt_record(&mut cursor).unwrap();
dbg!(&parsed);
}
}
}