Skip to main content

bgpkit_parser/parser/mrt/messages/
table_dump.rs

1use crate::error::*;
2use crate::models::*;
3use crate::parser::bgp::attributes::parse_attributes;
4use crate::parser::ReadUtils;
5use bytes::{BufMut, Bytes, BytesMut};
6use ipnet::IpNet;
7use std::net::IpAddr;
8
9/// Parse MRT TABLE_DUMP type message.
10///
11/// <https://www.rfc-editor.org/rfc/rfc6396#section-4.2>
12///
13/// ```text
14/// The TABLE_DUMP Type does not permit 4-byte Peer AS numbers, nor does
15//  it allow the AFI of the peer IP to differ from the AFI of the Prefix
16//  field.  The TABLE_DUMP_V2 Type MUST be used in these situations.
17/// ```
18///
19/// ```text
20///  0                   1                   2                   3
21///  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
22/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23/// |         View Number           |       Sequence Number         |
24/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25/// |                        Prefix (variable)                      |
26/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27/// | Prefix Length |    Status     |
28/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29/// |                         Originated Time                       |
30/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31/// |                    Peer IP Address (variable)                 |
32/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33/// |           Peer AS             |       Attribute Length        |
34/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35/// |                   BGP Attribute... (variable)
36/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37/// ```
38pub fn parse_table_dump_message(
39    sub_type: u16,
40    mut data: Bytes,
41) -> Result<TableDumpMessage, ParserError> {
42    // ####
43    // Step 0. prepare
44    //   - define AS number length
45    //   - determine address family
46    //   - create data slice reader cursor
47
48    // determine address family based on the sub_type value defined in the MRT [CommonHeader].
49    let afi = match sub_type {
50        1 => Afi::Ipv4,
51        2 => Afi::Ipv6,
52        _ => {
53            return Err(ParserError::ParseError(format!(
54                "Invalid subtype found for TABLE_DUMP (V1) message: {sub_type}"
55            )))
56        }
57    };
58
59    // ####
60    // Step 1. read simple fields
61    //   - view number
62    //   - sequence number
63    //   - prefix
64    //   - prefix-length
65    //   - status
66    //   - originated time
67    //   - peer IP address
68    //   - peer ASN
69    //   - attribute length
70
71    let view_number = data.read_u16()?;
72    let sequence_number = data.read_u16()?;
73    let prefix = match &afi {
74        Afi::Ipv4 => data.read_ipv4_prefix().map(ipnet::IpNet::V4),
75        Afi::Ipv6 => data.read_ipv6_prefix().map(ipnet::IpNet::V6),
76        Afi::LinkState => {
77            // Link-State doesn't use traditional prefixes, but we need a placeholder
78            // Use 0.0.0.0/0 as a placeholder for now
79            Ok(ipnet::IpNet::V4(
80                ipnet::Ipv4Net::new(std::net::Ipv4Addr::new(0, 0, 0, 0), 0).unwrap(),
81            ))
82        }
83    }?;
84
85    let status = data.read_u8()?;
86    let time = data.read_u32()? as u64;
87
88    let peer_ip: IpAddr = data.read_address(&afi)?;
89    let peer_asn = Asn::new_16bit(data.read_u16()?);
90
91    let attribute_length = data.read_u16()? as usize;
92
93    // ####
94    // Step 2. read the attributes
95    //   - create subslice based on the cursor's current position
96    //   - pass the data into the parser function
97
98    data.has_n_remaining(attribute_length)?;
99    let attr_data_slice = data.split_to(attribute_length);
100
101    // for TABLE_DUMP type, the AS number length is always 2-byte.
102    let mut attributes =
103        parse_attributes(attr_data_slice, &AsnLength::Bits16, false, None, None, None)?;
104
105    // validate mandatory attributes (TABLE_DUMP is always an announcement)
106    attributes.check_mandatory_attributes(true, afi == Afi::Ipv4);
107
108    Ok(TableDumpMessage {
109        view_number,
110        sequence_number,
111        prefix: NetworkPrefix::new(prefix, None),
112        status,
113        originated_time: time,
114        peer_ip,
115        peer_asn,
116        attributes,
117    })
118}
119
120impl TableDumpMessage {
121    pub fn encode(&self) -> Bytes {
122        let mut bytes = BytesMut::new();
123        bytes.put_u16(self.view_number);
124        bytes.put_u16(self.sequence_number);
125        match &self.prefix.prefix {
126            IpNet::V4(p) => {
127                bytes.put_u32(p.addr().into());
128                bytes.put_u8(p.prefix_len());
129            }
130            IpNet::V6(p) => {
131                bytes.put_u128(p.addr().into());
132                bytes.put_u8(p.prefix_len());
133            }
134        }
135        bytes.put_u8(self.status);
136        bytes.put_u32(self.originated_time as u32);
137
138        // peer address and peer asn
139        match self.peer_ip {
140            IpAddr::V4(a) => {
141                bytes.put_u32(a.into());
142            }
143            IpAddr::V6(a) => {
144                bytes.put_u128(a.into());
145            }
146        }
147        bytes.put_u16(self.peer_asn.into());
148
149        // encode attributes
150        let mut attr_bytes = BytesMut::new();
151        for attr in &self.attributes.inner {
152            // asn_len always 16 bites
153            attr_bytes.extend(attr.encode(AsnLength::Bits16));
154        }
155
156        bytes.put_u16(attr_bytes.len() as u16);
157        bytes.put_slice(&attr_bytes);
158
159        bytes.freeze()
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use bytes::{BufMut, BytesMut};
167    use std::net::{Ipv4Addr, Ipv6Addr};
168
169    const VIEW_NUMBER: u16 = 0;
170    const SEQUENCE_NUMBER: u16 = 0;
171    const IPV4_PREFIX: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
172    const IPV6_PREFIX: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
173    const PREFIX_LEN: u8 = 0;
174    const STATUS: u8 = 0;
175    const TIME: u64 = 0;
176    const PEER_IPV4: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
177    const PEER_IPV6: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
178    const PEER_ASN_16BIT: u16 = 0;
179    const ATTRIBUTE_LENGTH: usize = 0;
180    const DUMMY_ATTRIBUTES: &[u8] = &[];
181
182    #[test]
183    fn test_parse_table_dump_message_ipv4() {
184        let mut bytes_mut = BytesMut::new();
185        // Populate the bytes_mut with the same sequence that parse_table_dump_message() expects to parse
186        bytes_mut.put_u16(VIEW_NUMBER);
187        bytes_mut.put_u16(SEQUENCE_NUMBER);
188        bytes_mut.put_u32(IPV4_PREFIX.into());
189        bytes_mut.put_u8(PREFIX_LEN);
190        bytes_mut.put_u8(STATUS);
191        bytes_mut.put_u32(TIME as u32);
192        bytes_mut.put_u32(PEER_IPV4.into());
193        bytes_mut.put_u16(PEER_ASN_16BIT);
194        bytes_mut.put_u16(ATTRIBUTE_LENGTH as u16);
195        bytes_mut.put_slice(DUMMY_ATTRIBUTES);
196
197        // Convert from BytesMut to Bytes
198        let bytes = bytes_mut.freeze();
199
200        let table_dump_message_res = parse_table_dump_message(1, bytes.clone());
201        assert!(
202            table_dump_message_res.is_ok(),
203            "Failed to parse TABLE_DUMP_V1 message"
204        );
205
206        let table_dump_message = table_dump_message_res.unwrap();
207        assert_eq!(
208            table_dump_message.view_number, VIEW_NUMBER,
209            "VIEW_NUMBER mismatch"
210        );
211        assert_eq!(
212            table_dump_message.sequence_number, SEQUENCE_NUMBER,
213            "SEQUENCE_NUMBER mismatch"
214        );
215        // Add more assertions here as per your actual requirements
216        let encoded = table_dump_message.encode();
217        assert_eq!(encoded, bytes);
218    }
219    #[test]
220    fn test_parse_table_dump_message_ipv6() {
221        let mut bytes_mut = BytesMut::new();
222        // Populate the bytes_mut with the same sequence that parse_table_dump_message() expects to parse
223        bytes_mut.put_u16(VIEW_NUMBER);
224        bytes_mut.put_u16(SEQUENCE_NUMBER);
225        bytes_mut.put_u128(IPV6_PREFIX.into());
226        bytes_mut.put_u8(PREFIX_LEN);
227        bytes_mut.put_u8(STATUS);
228        bytes_mut.put_u32(TIME as u32);
229        bytes_mut.put_u128(PEER_IPV6.into());
230        bytes_mut.put_u16(PEER_ASN_16BIT);
231        bytes_mut.put_u16(ATTRIBUTE_LENGTH as u16);
232        bytes_mut.put_slice(DUMMY_ATTRIBUTES);
233
234        // Convert from BytesMut to Bytes
235        let bytes = bytes_mut.freeze();
236
237        let table_dump_message_res = parse_table_dump_message(2, bytes.clone());
238        assert!(
239            table_dump_message_res.is_ok(),
240            "Failed to parse TABLE_DUMP_V1 message"
241        );
242
243        let table_dump_message = table_dump_message_res.unwrap();
244        assert_eq!(
245            table_dump_message.view_number, VIEW_NUMBER,
246            "VIEW_NUMBER mismatch"
247        );
248        assert_eq!(
249            table_dump_message.sequence_number, SEQUENCE_NUMBER,
250            "SEQUENCE_NUMBER mismatch"
251        );
252        // Add more assertions here as per your actual requirements
253
254        // test encoding
255        let encoded = table_dump_message.encode();
256        assert_eq!(encoded, bytes);
257    }
258
259    #[test]
260    fn test_parse_table_dump_message_invalid_subtype() {
261        // Create a simple byte array for testing
262        let mut bytes_mut = BytesMut::new();
263        bytes_mut.put_u16(VIEW_NUMBER);
264        bytes_mut.put_u16(SEQUENCE_NUMBER);
265        let bytes = bytes_mut.freeze();
266
267        // Test with an invalid sub_type (not 1 or 2)
268        let result = parse_table_dump_message(0, bytes.clone());
269        assert!(result.is_err(), "Expected error for invalid sub_type");
270
271        if let Err(ParserError::ParseError(msg)) = result {
272            assert!(
273                msg.contains("Invalid subtype"),
274                "Expected error message to mention invalid subtype"
275            );
276        } else {
277            panic!("Expected ParseError for invalid sub_type");
278        }
279
280        // Test with another invalid sub_type
281        let result = parse_table_dump_message(3, bytes);
282        assert!(result.is_err(), "Expected error for invalid sub_type");
283
284        if let Err(ParserError::ParseError(msg)) = result {
285            assert!(
286                msg.contains("Invalid subtype"),
287                "Expected error message to mention invalid subtype"
288            );
289        } else {
290            panic!("Expected ParseError for invalid sub_type");
291        }
292    }
293
294    #[test]
295    fn test_table_dump_message_encode_with_attributes() {
296        use crate::models::{Asn, AttributeValue, Attributes, Origin};
297        use std::str::FromStr;
298
299        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
300        let mut attributes = Attributes::default();
301        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
302
303        let table_dump = TableDumpMessage {
304            view_number: 1,
305            sequence_number: 2,
306            prefix: NetworkPrefix::new(prefix, None),
307            status: 1,
308            originated_time: 12345,
309            peer_ip: IpAddr::V4("10.0.0.1".parse().unwrap()),
310            peer_asn: Asn::from(65000),
311            attributes,
312        };
313
314        // This should exercise the attr.encode(AsnLength::Bits16) line
315        let _encoded = table_dump.encode();
316    }
317}