1use crate::encoder::sink::with_u16_len;
2use crate::error::*;
3use crate::models::*;
4use crate::parser::bgp::attributes::parse_attributes;
5use crate::parser::ReadUtils;
6use bytes::{BufMut, Bytes, BytesMut};
7use ipnet::IpNet;
8use std::net::IpAddr;
9
10pub fn parse_table_dump_message(
40 sub_type: u16,
41 data: Bytes,
42) -> Result<TableDumpMessage, ParserError> {
43 let mut messages = parse_table_dump_messages(sub_type, data)?;
44 if messages.len() != 1 {
45 return Err(ParserError::ParseError(format!(
46 "expected one TABLE_DUMP entry, found {}",
47 messages.len()
48 )));
49 }
50 Ok(messages.remove(0))
51}
52
53pub fn parse_table_dump_messages(
58 sub_type: u16,
59 mut data: Bytes,
60) -> Result<Vec<TableDumpMessage>, ParserError> {
61 let afi = match sub_type {
69 1 => Afi::Ipv4,
70 2 => Afi::Ipv6,
71 _ => {
72 return Err(ParserError::ParseError(format!(
73 "Invalid subtype found for TABLE_DUMP (V1) message: {sub_type}"
74 )))
75 }
76 };
77
78 let view_number = data.read_u16()?;
91 let sequence_number = data.read_u16()?;
92 let mut messages = Vec::new();
93
94 while !data.is_empty() {
95 messages.push(parse_table_dump_entry(
96 &mut data,
97 &afi,
98 view_number,
99 sequence_number,
100 )?);
101 }
102
103 if messages.is_empty() {
104 return Err(ParserError::TruncatedMsg(
105 "TABLE_DUMP record contains no entries".to_string(),
106 ));
107 }
108
109 Ok(messages)
110}
111
112fn parse_table_dump_entry(
113 data: &mut Bytes,
114 afi: &Afi,
115 view_number: u16,
116 sequence_number: u16,
117) -> Result<TableDumpMessage, ParserError> {
118 let prefix = match afi {
119 Afi::Ipv4 => data.read_ipv4_prefix().map(ipnet::IpNet::V4),
120 Afi::Ipv6 => data.read_ipv6_prefix().map(ipnet::IpNet::V6),
121 Afi::LinkState => {
122 Ok(ipnet::IpNet::V4(
125 ipnet::Ipv4Net::new(std::net::Ipv4Addr::new(0, 0, 0, 0), 0).unwrap(),
126 ))
127 }
128 }?;
129
130 let status = data.read_u8()?;
131 let time = data.read_u32()? as u64;
132
133 let peer_ip: IpAddr = data.read_address(afi)?;
134 let peer_asn = Asn::new_16bit(data.read_u16()?);
135
136 let attribute_length = data.read_u16()? as usize;
137
138 data.has_n_remaining(attribute_length)?;
144 let attr_data_slice = data.split_to(attribute_length);
145
146 let mut attributes = parse_attributes(
149 attr_data_slice,
150 &AsnLength::Bits16,
151 false,
152 Some(*afi),
153 Some(Safi::Unicast),
154 None,
155 )?;
156
157 attributes.check_mandatory_attributes(true, *afi == Afi::Ipv4);
159
160 Ok(TableDumpMessage {
161 view_number,
162 sequence_number,
163 prefix: NetworkPrefix::new(prefix, None),
164 status,
165 originated_time: time,
166 peer_ip,
167 peer_asn,
168 attributes,
169 })
170}
171
172impl TableDumpMessage {
173 pub fn encode(&self) -> Result<Bytes, EncodingError> {
174 let mut bytes = BytesMut::new();
175 bytes.put_u16(self.view_number);
176 bytes.put_u16(self.sequence_number);
177 self.encode_entry_to(&mut bytes)?;
178 Ok(bytes.freeze())
179 }
180
181 fn encode_entry_to(&self, bytes: &mut BytesMut) -> Result<(), EncodingError> {
182 match &self.prefix.prefix {
183 IpNet::V4(p) => {
184 bytes.put_u32(p.addr().into());
185 bytes.put_u8(p.prefix_len());
186 }
187 IpNet::V6(p) => {
188 bytes.put_u128(p.addr().into());
189 bytes.put_u8(p.prefix_len());
190 }
191 }
192 bytes.put_u8(self.status);
193 bytes.put_u32(self.originated_time as u32);
194
195 match self.peer_ip {
197 IpAddr::V4(a) => {
198 bytes.put_u32(a.into());
199 }
200 IpAddr::V6(a) => {
201 bytes.put_u128(a.into());
202 }
203 }
204 bytes.put_u16(self.peer_asn.into());
205
206 with_u16_len(bytes, "TABLE_DUMP attribute length", |b| {
208 self.attributes.encode_to(AsnLength::Bits16, b)
209 })?;
210 Ok(())
211 }
212}
213
214pub(crate) fn encode_table_dump_batch(
215 messages: &[TableDumpMessage],
216 sub_type: u16,
217) -> Result<Bytes, EncodingError> {
218 let Some(first) = messages.first() else {
219 return Err(EncodingError::unencodable(
220 "TABLE_DUMP batch",
221 "batch is empty",
222 ));
223 };
224
225 let expected_ipv4 = match sub_type {
226 1 => true,
227 2 => false,
228 _ => {
229 return Err(EncodingError::unencodable(
230 "TABLE_DUMP batch",
231 format!("invalid subtype {sub_type}"),
232 ));
233 }
234 };
235
236 let mut bytes = BytesMut::new();
237 bytes.put_u16(first.view_number);
238 bytes.put_u16(first.sequence_number);
239 for message in messages {
240 if message.view_number != first.view_number
241 || message.sequence_number != first.sequence_number
242 {
243 return Err(EncodingError::unencodable(
244 "TABLE_DUMP batch",
245 "all entries must have the same view and sequence numbers",
246 ));
247 }
248 if matches!(message.prefix.prefix, IpNet::V4(_)) != expected_ipv4
249 || matches!(message.peer_ip, IpAddr::V4(_)) != expected_ipv4
250 {
251 return Err(EncodingError::unencodable(
252 "TABLE_DUMP batch",
253 "entry address family does not match the MRT subtype",
254 ));
255 }
256 message.encode_entry_to(&mut bytes)?;
257 }
258 Ok(bytes.freeze())
259}
260
261pub(crate) fn needs_legacy_length_correction(sub_type: u16, data: &[u8]) -> bool {
271 let address_len = match sub_type {
272 1 => 4usize,
273 2 => 16usize,
274 _ => return false,
275 };
276 if data.len() < 4 {
277 return false;
278 }
279
280 let fixed_entry_len = address_len * 2 + 10;
283 let mut offset = 4usize;
284 while offset < data.len() {
285 let Some(attr_len_offset) = offset.checked_add(fixed_entry_len - 2) else {
286 return false;
287 };
288 if attr_len_offset + 2 > data.len() {
289 return false;
290 }
291 let attr_len =
292 u16::from_be_bytes([data[attr_len_offset], data[attr_len_offset + 1]]) as usize;
293 let Some(entry_end) = offset
294 .checked_add(fixed_entry_len)
295 .and_then(|value| value.checked_add(attr_len))
296 else {
297 return false;
298 };
299 if entry_end == data.len() + 4 {
300 return true;
301 }
302 if entry_end <= offset || entry_end > data.len() {
303 return false;
304 }
305 offset = entry_end;
306 }
307 false
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use bytes::{BufMut, BytesMut};
314 use std::net::{Ipv4Addr, Ipv6Addr};
315
316 const VIEW_NUMBER: u16 = 0;
317 const SEQUENCE_NUMBER: u16 = 0;
318 const IPV4_PREFIX: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
319 const IPV6_PREFIX: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
320 const PREFIX_LEN: u8 = 0;
321 const STATUS: u8 = 0;
322 const TIME: u64 = 0;
323 const PEER_IPV4: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
324 const PEER_IPV6: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
325 const PEER_ASN_16BIT: u16 = 0;
326 const ATTRIBUTE_LENGTH: usize = 0;
327 const DUMMY_ATTRIBUTES: &[u8] = &[];
328
329 #[test]
330 fn test_parse_table_dump_message_ipv4() {
331 let mut bytes_mut = BytesMut::new();
332 bytes_mut.put_u16(VIEW_NUMBER);
334 bytes_mut.put_u16(SEQUENCE_NUMBER);
335 bytes_mut.put_u32(IPV4_PREFIX.into());
336 bytes_mut.put_u8(PREFIX_LEN);
337 bytes_mut.put_u8(STATUS);
338 bytes_mut.put_u32(TIME as u32);
339 bytes_mut.put_u32(PEER_IPV4.into());
340 bytes_mut.put_u16(PEER_ASN_16BIT);
341 bytes_mut.put_u16(ATTRIBUTE_LENGTH as u16);
342 bytes_mut.put_slice(DUMMY_ATTRIBUTES);
343
344 let bytes = bytes_mut.freeze();
346
347 let table_dump_message_res = parse_table_dump_message(1, bytes.clone());
348 assert!(
349 table_dump_message_res.is_ok(),
350 "Failed to parse TABLE_DUMP_V1 message"
351 );
352
353 let table_dump_message = table_dump_message_res.unwrap();
354 assert_eq!(
355 table_dump_message.view_number, VIEW_NUMBER,
356 "VIEW_NUMBER mismatch"
357 );
358 assert_eq!(
359 table_dump_message.sequence_number, SEQUENCE_NUMBER,
360 "SEQUENCE_NUMBER mismatch"
361 );
362 let encoded = table_dump_message.encode().unwrap();
364 assert_eq!(encoded, bytes);
365 }
366 #[test]
367 fn test_parse_table_dump_message_ipv6() {
368 let mut bytes_mut = BytesMut::new();
369 bytes_mut.put_u16(VIEW_NUMBER);
371 bytes_mut.put_u16(SEQUENCE_NUMBER);
372 bytes_mut.put_u128(IPV6_PREFIX.into());
373 bytes_mut.put_u8(PREFIX_LEN);
374 bytes_mut.put_u8(STATUS);
375 bytes_mut.put_u32(TIME as u32);
376 bytes_mut.put_u128(PEER_IPV6.into());
377 bytes_mut.put_u16(PEER_ASN_16BIT);
378 bytes_mut.put_u16(ATTRIBUTE_LENGTH as u16);
379 bytes_mut.put_slice(DUMMY_ATTRIBUTES);
380
381 let bytes = bytes_mut.freeze();
383
384 let table_dump_message_res = parse_table_dump_message(2, bytes.clone());
385 assert!(
386 table_dump_message_res.is_ok(),
387 "Failed to parse TABLE_DUMP_V1 message"
388 );
389
390 let table_dump_message = table_dump_message_res.unwrap();
391 assert_eq!(
392 table_dump_message.view_number, VIEW_NUMBER,
393 "VIEW_NUMBER mismatch"
394 );
395 assert_eq!(
396 table_dump_message.sequence_number, SEQUENCE_NUMBER,
397 "SEQUENCE_NUMBER mismatch"
398 );
399 let encoded = table_dump_message.encode().unwrap();
403 assert_eq!(encoded, bytes);
404 }
405
406 #[test]
407 fn test_parse_table_dump_message_invalid_subtype() {
408 let mut bytes_mut = BytesMut::new();
410 bytes_mut.put_u16(VIEW_NUMBER);
411 bytes_mut.put_u16(SEQUENCE_NUMBER);
412 let bytes = bytes_mut.freeze();
413
414 let result = parse_table_dump_message(0, bytes.clone());
416 assert!(result.is_err(), "Expected error for invalid sub_type");
417
418 if let Err(ParserError::ParseError(msg)) = result {
419 assert!(
420 msg.contains("Invalid subtype"),
421 "Expected error message to mention invalid subtype"
422 );
423 } else {
424 panic!("Expected ParseError for invalid sub_type");
425 }
426
427 let result = parse_table_dump_message(3, bytes);
429 assert!(result.is_err(), "Expected error for invalid sub_type");
430
431 if let Err(ParserError::ParseError(msg)) = result {
432 assert!(
433 msg.contains("Invalid subtype"),
434 "Expected error message to mention invalid subtype"
435 );
436 } else {
437 panic!("Expected ParseError for invalid sub_type");
438 }
439 }
440
441 #[test]
442 fn test_table_dump_message_encode_with_attributes() {
443 use crate::models::{Asn, AttributeValue, Attributes, Origin};
444 use std::str::FromStr;
445
446 let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
447 let mut attributes = Attributes::default();
448 attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
449
450 let table_dump = TableDumpMessage {
451 view_number: 1,
452 sequence_number: 2,
453 prefix: NetworkPrefix::new(prefix, None),
454 status: 1,
455 originated_time: 12345,
456 peer_ip: IpAddr::V4("10.0.0.1".parse().unwrap()),
457 peer_asn: Asn::from(65000),
458 attributes,
459 };
460
461 let _encoded = table_dump.encode().unwrap();
463 }
464
465 #[test]
466 fn parses_and_encodes_batched_table_dump_messages() {
467 use crate::models::{AttributeValue, Origin};
468 use std::str::FromStr;
469
470 let mut attributes = Attributes::default();
471 attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
472 let first = TableDumpMessage {
473 view_number: 7,
474 sequence_number: 9,
475 prefix: NetworkPrefix::from_str("192.0.2.0/24").unwrap(),
476 status: 1,
477 originated_time: 12345,
478 peer_ip: IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
479 peer_asn: Asn::new_16bit(64512),
480 attributes: attributes.clone(),
481 };
482 let second = TableDumpMessage {
483 prefix: NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
484 ..first.clone()
485 };
486
487 let wire = encode_table_dump_batch(&[first.clone(), second.clone()], 1).unwrap();
488 let parsed = parse_table_dump_messages(1, wire.clone()).unwrap();
489 assert_eq!(parsed.len(), 2);
490 assert_eq!(parsed[0].prefix, first.prefix);
491 assert_eq!(parsed[1].prefix, second.prefix);
492 assert!(!needs_legacy_length_correction(1, &wire));
493 assert!(needs_legacy_length_correction(1, &wire[..wire.len() - 4]));
494 assert!(!needs_legacy_length_correction(1, &wire[..wire.len() - 3]));
495 }
496 #[test]
497 fn test_table_dump_entry_domain_path_is_validated_against_its_family() {
498 let mut data = Vec::new();
500 data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&[192, 0, 2, 0]); data.push(24); data.push(1); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&[10, 0, 0, 1]); data.extend_from_slice(&65001u16.to_be_bytes()); let attributes = [
509 0xc0, 0x24, 0x08, 0x01, 0x00, 0x00, 0xfd, 0xe8, 0x00, 0x01, 0x46,
510 ];
511 data.extend_from_slice(&(attributes.len() as u16).to_be_bytes());
512 data.extend_from_slice(&attributes);
513
514 let message = parse_table_dump_message(1, Bytes::from(data)).unwrap();
515
516 assert!(message
517 .attributes
518 .validation_warnings()
519 .iter()
520 .any(|warning| {
521 matches!(
522 warning,
523 crate::error::BgpValidationWarning::OptionalAttributeError { attr_type, .. }
524 if *attr_type == crate::models::AttrType::BGP_DOMAIN_PATH
525 )
526 }));
527 }
528}