bgpkit_parser/parser/mrt/messages/table_dump_v2/
rib_afi_entries.rs

1use crate::bgp::attributes::parse_attributes;
2use crate::models::{
3    Afi, AsnLength, NetworkPrefix, RibAfiEntries, RibEntry, Safi, TableDumpV2Type,
4};
5use crate::parser::ReadUtils;
6use crate::ParserError;
7use bytes::{Buf, BufMut, Bytes, BytesMut};
8use log::warn;
9
10fn extract_afi_safi_from_rib_type(rib_type: &TableDumpV2Type) -> Result<(Afi, Safi), ParserError> {
11    let afi: Afi;
12    let safi: Safi;
13    match rib_type {
14        TableDumpV2Type::RibIpv4Unicast | TableDumpV2Type::RibIpv4UnicastAddPath => {
15            afi = Afi::Ipv4;
16            safi = Safi::Unicast
17        }
18        TableDumpV2Type::RibIpv4Multicast | TableDumpV2Type::RibIpv4MulticastAddPath => {
19            afi = Afi::Ipv4;
20            safi = Safi::Multicast
21        }
22        TableDumpV2Type::RibIpv6Unicast | TableDumpV2Type::RibIpv6UnicastAddPath => {
23            afi = Afi::Ipv6;
24            safi = Safi::Unicast
25        }
26        TableDumpV2Type::RibIpv6Multicast | TableDumpV2Type::RibIpv6MulticastAddPath => {
27            afi = Afi::Ipv6;
28            safi = Safi::Multicast
29        }
30        _ => {
31            return Err(ParserError::ParseError(format!(
32                "wrong RIB type for parsing: {rib_type:?}"
33            )))
34        }
35    };
36
37    Ok((afi, safi))
38}
39
40/// RIB AFI-specific entries
41///
42/// https://tools.ietf.org/html/rfc6396#section-4.3
43pub fn parse_rib_afi_entries(
44    data: &mut Bytes,
45    rib_type: TableDumpV2Type,
46) -> Result<RibAfiEntries, ParserError> {
47    let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type)?;
48
49    let is_add_path = matches!(
50        rib_type,
51        TableDumpV2Type::RibIpv4UnicastAddPath
52            | TableDumpV2Type::RibIpv4MulticastAddPath
53            | TableDumpV2Type::RibIpv6UnicastAddPath
54            | TableDumpV2Type::RibIpv6MulticastAddPath
55    );
56
57    let sequence_number = data.read_u32()?;
58
59    // NOTE: here we parse the prefix as only length and prefix, the path identifier for add_path
60    //       entry is not handled here. We follow RFC6396 here https://www.rfc-editor.org/rfc/rfc6396.html#section-4.3.2
61    let prefix = data.read_nlri_prefix(&afi, false)?;
62
63    let entry_count = data.read_u16()?;
64    // Pre-allocate cautiously to avoid overflow/OOM with malformed inputs
65    let min_entry_size =
66        2 /*peer_index*/ + 4 /*time*/ + 2 /*attr_len*/ + if is_add_path { 4 } else { 0 };
67    let max_possible = data.remaining() / min_entry_size;
68    let reserve = (entry_count as usize).min(max_possible).saturating_mul(2);
69    let mut rib_entries = Vec::with_capacity(reserve);
70
71    // get the u8 slice of the rest of the data
72    // let attr_data_slice = &input.into_inner()[(input.position() as usize)..];
73
74    for _i in 0..entry_count {
75        let entry = match parse_rib_entry(data, is_add_path, &afi, &safi, prefix) {
76            Ok(entry) => entry,
77            Err(e) => {
78                warn!("early break due to error {}", e);
79                break;
80            }
81        };
82        rib_entries.push(entry);
83    }
84
85    Ok(RibAfiEntries {
86        rib_type,
87        sequence_number,
88        prefix,
89        rib_entries,
90    })
91}
92
93/// RIB entry: one prefix per entry
94///
95///
96/// https://datatracker.ietf.org/doc/html/rfc6396#section-4.3.4
97/// ```text
98///         0                   1                   2                   3
99///         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
100///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
101///        |         Peer Index            |
102///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
103///        |                         Originated Time                       |
104///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
105///        |      Attribute Length         |
106///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
107///        |                    BGP Attributes... (variable)
108///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
109///
110///                           Figure 10: RIB Entries
111/// ```
112pub fn parse_rib_entry(
113    input: &mut Bytes,
114    is_add_path: bool,
115    afi: &Afi,
116    safi: &Safi,
117    prefix: NetworkPrefix,
118) -> Result<RibEntry, ParserError> {
119    if input.remaining() < 8 {
120        // total length - current position less than 16 --
121        // meaning less than 16 bytes available to read
122        return Err(ParserError::TruncatedMsg("truncated msg".to_string()));
123    }
124
125    let peer_index = input.read_u16()?;
126    let originated_time = input.read_u32()?;
127
128    let path_id = match is_add_path {
129        true => Some(input.read_u32()?),
130        false => None,
131    };
132
133    let attribute_length = input.read_u16()? as usize;
134
135    input.has_n_remaining(attribute_length)?;
136    let attr_data_slice = input.split_to(attribute_length);
137    let attributes = parse_attributes(
138        attr_data_slice,
139        &AsnLength::Bits32,
140        is_add_path,
141        Some(*afi),
142        Some(*safi),
143        Some(&[prefix]),
144    )?;
145
146    Ok(RibEntry {
147        peer_index,
148        originated_time,
149        path_id,
150        attributes,
151    })
152}
153
154impl RibAfiEntries {
155    pub fn encode(&self) -> Bytes {
156        let mut bytes = BytesMut::new();
157
158        bytes.put_u32(self.sequence_number);
159        bytes.extend(self.prefix.encode());
160
161        let entry_count = self.rib_entries.len();
162        bytes.put_u16(entry_count as u16);
163
164        for entry in &self.rib_entries {
165            bytes.extend(entry.encode());
166        }
167
168        bytes.freeze()
169    }
170}
171
172impl RibEntry {
173    pub fn encode(&self) -> Bytes {
174        let mut bytes = BytesMut::new();
175        bytes.put_u16(self.peer_index);
176        bytes.put_u32(self.originated_time);
177        let attr_bytes = self.attributes.encode(AsnLength::Bits32);
178        bytes.put_u16(attr_bytes.len() as u16);
179        bytes.extend(attr_bytes);
180        bytes.freeze()
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_extract_afi_safi_from_rib_type() {
190        let rib_type = TableDumpV2Type::RibIpv4Unicast;
191        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
192        assert_eq!(afi, Afi::Ipv4);
193        assert_eq!(safi, Safi::Unicast);
194
195        let rib_type = TableDumpV2Type::RibIpv4Multicast;
196        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
197        assert_eq!(afi, Afi::Ipv4);
198        assert_eq!(safi, Safi::Multicast);
199
200        let rib_type = TableDumpV2Type::RibIpv6Unicast;
201        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
202        assert_eq!(afi, Afi::Ipv6);
203        assert_eq!(safi, Safi::Unicast);
204
205        let rib_type = TableDumpV2Type::RibIpv6Multicast;
206        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
207        assert_eq!(afi, Afi::Ipv6);
208        assert_eq!(safi, Safi::Multicast);
209
210        let rib_type = TableDumpV2Type::RibIpv4UnicastAddPath;
211        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
212        assert_eq!(afi, Afi::Ipv4);
213        assert_eq!(safi, Safi::Unicast);
214
215        let rib_type = TableDumpV2Type::RibIpv4MulticastAddPath;
216        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
217        assert_eq!(afi, Afi::Ipv4);
218        assert_eq!(safi, Safi::Multicast);
219
220        let rib_type = TableDumpV2Type::RibIpv6UnicastAddPath;
221        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
222        assert_eq!(afi, Afi::Ipv6);
223        assert_eq!(safi, Safi::Unicast);
224
225        let rib_type = TableDumpV2Type::RibIpv6MulticastAddPath;
226        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
227        assert_eq!(afi, Afi::Ipv6);
228        assert_eq!(safi, Safi::Multicast);
229
230        let rib_type = TableDumpV2Type::RibGeneric;
231        let res = extract_afi_safi_from_rib_type(&rib_type);
232        assert!(res.is_err());
233    }
234
235    #[test]
236    fn test_rib_entry_encode() {
237        use crate::models::{AttributeValue, Attributes, Origin};
238
239        let mut attributes = Attributes::default();
240        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
241
242        let rib_entry = RibEntry {
243            peer_index: 1,
244            originated_time: 12345,
245            path_id: Some(42),
246            attributes,
247        };
248
249        // This should exercise the self.attributes.encode(AsnLength::Bits32) line
250        let _encoded = rib_entry.encode();
251    }
252}