use crate::bgp::attributes::parse_attributes;
use crate::models::{
Afi, AsnLength, NetworkPrefix, RibAfiEntries, RibEntry, Safi, TableDumpV2Type,
};
use crate::parser::ReadUtils;
use crate::ParserError;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use log::warn;
fn extract_afi_safi_from_rib_type(rib_type: &TableDumpV2Type) -> Result<(Afi, Safi), ParserError> {
let afi: Afi;
let safi: Safi;
match rib_type {
TableDumpV2Type::RibIpv4Unicast | TableDumpV2Type::RibIpv4UnicastAddPath => {
afi = Afi::Ipv4;
safi = Safi::Unicast
}
TableDumpV2Type::RibIpv4Multicast | TableDumpV2Type::RibIpv4MulticastAddPath => {
afi = Afi::Ipv4;
safi = Safi::Multicast
}
TableDumpV2Type::RibIpv6Unicast | TableDumpV2Type::RibIpv6UnicastAddPath => {
afi = Afi::Ipv6;
safi = Safi::Unicast
}
TableDumpV2Type::RibIpv6Multicast | TableDumpV2Type::RibIpv6MulticastAddPath => {
afi = Afi::Ipv6;
safi = Safi::Multicast
}
_ => {
return Err(ParserError::ParseError(format!(
"wrong RIB type for parsing: {rib_type:?}"
)))
}
};
Ok((afi, safi))
}
fn is_add_path_rib_type(rib_type: TableDumpV2Type) -> bool {
matches!(
rib_type,
TableDumpV2Type::RibIpv4UnicastAddPath
| TableDumpV2Type::RibIpv4MulticastAddPath
| TableDumpV2Type::RibIpv6UnicastAddPath
| TableDumpV2Type::RibIpv6MulticastAddPath
)
}
pub fn parse_rib_afi_entries(
data: &mut Bytes,
rib_type: TableDumpV2Type,
) -> Result<RibAfiEntries, ParserError> {
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type)?;
let is_add_path = is_add_path_rib_type(rib_type);
let sequence_number = data.read_u32()?;
let prefix = data.read_nlri_prefix(&afi, false)?;
let entry_count = data.read_u16()?;
let min_entry_size =
2 + 4 + 2 + if is_add_path { 4 } else { 0 };
let max_possible = data.remaining() / min_entry_size;
let reserve = (entry_count as usize).min(max_possible).saturating_mul(2);
let mut rib_entries = Vec::with_capacity(reserve);
for _i in 0..entry_count {
let entry = match parse_rib_entry(data, is_add_path, &afi, &safi, prefix) {
Ok(entry) => entry,
Err(e) => {
warn!(
"early break due to error {} while parsing RIB AFI entries",
e
);
break;
}
};
rib_entries.push(entry);
}
Ok(RibAfiEntries {
rib_type,
sequence_number,
prefix,
rib_entries,
})
}
pub fn parse_rib_entry(
input: &mut Bytes,
is_add_path: bool,
afi: &Afi,
safi: &Safi,
prefix: NetworkPrefix,
) -> Result<RibEntry, ParserError> {
if input.remaining() < 8 {
return Err(ParserError::TruncatedMsg("truncated msg".to_string()));
}
let peer_index = input.read_u16()?;
let originated_time = input.read_u32()?;
let path_id = match is_add_path {
true => Some(input.read_u32()?),
false => None,
};
let attribute_length = input.read_u16()? as usize;
input.has_n_remaining(attribute_length)?;
let attr_data_slice = input.split_to(attribute_length);
let attributes = parse_attributes(
attr_data_slice,
&AsnLength::Bits32,
is_add_path,
Some(*afi),
Some(*safi),
Some(&[prefix]),
)?;
Ok(RibEntry {
peer_index,
originated_time,
path_id,
attributes,
})
}
impl RibAfiEntries {
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::new();
let is_add_path = is_add_path_rib_type(self.rib_type);
bytes.put_u32(self.sequence_number);
bytes.extend(self.prefix.encode());
let entry_count = self.rib_entries.len();
bytes.put_u16(entry_count as u16);
for entry in &self.rib_entries {
bytes.extend(entry.encode_for_rib_type(is_add_path));
}
bytes.freeze()
}
}
impl RibEntry {
pub fn encode(&self) -> Bytes {
self.encode_for_rib_type(self.path_id.is_some())
}
fn encode_for_rib_type(&self, include_path_id: bool) -> Bytes {
let mut bytes = BytesMut::new();
bytes.put_u16(self.peer_index);
bytes.put_u32(self.originated_time);
if include_path_id {
if let Some(path_id) = self.path_id {
bytes.put_u32(path_id);
}
}
let attr_bytes = self.attributes.encode(AsnLength::Bits32);
bytes.put_u16(attr_bytes.len() as u16);
bytes.extend(attr_bytes);
bytes.freeze()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Buf;
use std::str::FromStr;
#[test]
fn test_extract_afi_safi_from_rib_type() {
let rib_type = TableDumpV2Type::RibIpv4Unicast;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv4);
assert_eq!(safi, Safi::Unicast);
let rib_type = TableDumpV2Type::RibIpv4Multicast;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv4);
assert_eq!(safi, Safi::Multicast);
let rib_type = TableDumpV2Type::RibIpv6Unicast;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv6);
assert_eq!(safi, Safi::Unicast);
let rib_type = TableDumpV2Type::RibIpv6Multicast;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv6);
assert_eq!(safi, Safi::Multicast);
let rib_type = TableDumpV2Type::RibIpv4UnicastAddPath;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv4);
assert_eq!(safi, Safi::Unicast);
let rib_type = TableDumpV2Type::RibIpv4MulticastAddPath;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv4);
assert_eq!(safi, Safi::Multicast);
let rib_type = TableDumpV2Type::RibIpv6UnicastAddPath;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv6);
assert_eq!(safi, Safi::Unicast);
let rib_type = TableDumpV2Type::RibIpv6MulticastAddPath;
let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
assert_eq!(afi, Afi::Ipv6);
assert_eq!(safi, Safi::Multicast);
let rib_type = TableDumpV2Type::RibGeneric;
let res = extract_afi_safi_from_rib_type(&rib_type);
assert!(res.is_err());
}
#[test]
fn test_rib_entry_encode() {
use crate::models::{AttributeValue, Attributes, Origin};
let mut attributes = Attributes::default();
attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
let rib_entry = RibEntry {
peer_index: 1,
originated_time: 12345,
path_id: Some(42),
attributes,
};
let mut encoded = rib_entry.encode();
assert_eq!(encoded.read_u16().unwrap(), 1);
assert_eq!(encoded.read_u32().unwrap(), 12345);
assert_eq!(encoded.read_u32().unwrap(), 42);
let attr_len = encoded.read_u16().unwrap() as usize;
assert_eq!(encoded.remaining(), attr_len);
}
#[test]
fn test_rib_afi_entries_encode_roundtrip_add_path() {
use crate::models::{AttributeValue, Attributes, Origin};
let mut attributes = Attributes::default();
attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
let rib = RibAfiEntries {
rib_type: TableDumpV2Type::RibIpv4UnicastAddPath,
sequence_number: 7,
prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
rib_entries: vec![RibEntry {
peer_index: 3,
originated_time: 12345,
path_id: Some(42),
attributes,
}],
};
let encoded = rib.encode();
let parsed = parse_rib_afi_entries(&mut encoded.clone(), rib.rib_type).unwrap();
assert_eq!(parsed.rib_type, rib.rib_type);
assert_eq!(parsed.sequence_number, rib.sequence_number);
assert_eq!(parsed.prefix, rib.prefix);
assert_eq!(parsed.rib_entries.len(), 1);
assert_eq!(parsed.rib_entries[0].peer_index, 3);
assert_eq!(parsed.rib_entries[0].originated_time, 12345);
assert_eq!(parsed.rib_entries[0].path_id, Some(42));
assert_eq!(
parsed.rib_entries[0].attributes.inner,
rib.rib_entries[0].attributes.inner
);
}
}