1use crate::error::{BgpValidationWarning, ParserError};
15use crate::models::*;
16use crate::parser::mrt::dissect::{dissect_mrt_bytes, dissect_mrt_record};
17use crate::parser::mrt::mrt_record::{chunk_mrt_record_with_context, raw_record_uses_zebra_compat};
18use crate::parser::mrt::RawMrtRecord;
19use crate::parser::BgpkitParser;
20use std::collections::HashMap;
21use std::io::Read;
22
23#[derive(Debug)]
25#[non_exhaustive]
26pub enum DiagnosticEvent {
27 Record {
30 record: MrtRecord,
32 raw: RawMrtRecord,
34 warnings: Vec<BgpValidationWarning>,
36 },
37 ParseError {
39 error: ParserError,
41 common_header: Option<CommonHeader>,
43 raw_bytes: Option<Vec<u8>>,
45 partial: Option<DissectionNode>,
48 },
49}
50
51pub struct DiagnosticIterator<R> {
53 parser: BgpkitParser<R>,
54 terminated: bool,
55}
56
57impl<R> DiagnosticIterator<R> {
58 pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
59 Self {
60 parser,
61 terminated: false,
62 }
63 }
64
65 pub fn with_dissection(self) -> DissectingDiagnosticIterator<R> {
75 DissectingDiagnosticIterator { inner: self }
76 }
77}
78
79impl<R: Read> Iterator for DiagnosticIterator<R> {
80 type Item = DiagnosticEvent;
81
82 fn next(&mut self) -> Option<Self::Item> {
83 if self.terminated || self.parser.text_dump_iter.is_some() {
85 return None;
86 }
87
88 let raw_record = match chunk_mrt_record_with_context(&mut self.parser.reader) {
89 Ok(raw_record) => raw_record,
90 Err(error) if matches!(error.error, ParserError::EofExpected) => {
91 self.terminated = true;
92 return None;
93 }
94 Err(error) => {
95 self.terminated = true;
98 return Some(DiagnosticEvent::ParseError {
99 error: error.error,
100 common_header: error.common_header,
101 partial: error.bytes.as_deref().map(dissect_mrt_bytes),
102 raw_bytes: error.bytes,
103 });
104 }
105 };
106
107 let used_zebra_compat = raw_record_uses_zebra_compat(&raw_record);
108 let record = match raw_record.clone().parse() {
109 Ok(record) => record,
110 Err(error) => {
111 let raw_bytes = Some(raw_record.raw_bytes().to_vec());
112 return Some(DiagnosticEvent::ParseError {
113 error,
114 common_header: Some(raw_record.common_header),
115 partial: raw_bytes.as_deref().map(dissect_mrt_bytes),
116 raw_bytes,
117 });
118 }
119 };
120 if used_zebra_compat {
121 self.parser.warn_zebra_compat_once();
122 }
123
124 let warnings = record_validation_warnings(&record);
125 Some(DiagnosticEvent::Record {
126 record,
127 raw: raw_record,
128 warnings,
129 })
130 }
131}
132
133#[derive(Debug)]
135#[allow(clippy::large_enum_variant)]
138#[non_exhaustive]
139pub enum DissectedDiagnosticEvent {
140 Record {
142 record: MrtRecord,
144 raw: RawMrtRecord,
146 warnings: Vec<SpannedWarning>,
148 tree: DissectionNode,
150 },
151 ParseError {
154 error: ParserError,
156 common_header: Option<CommonHeader>,
158 raw_bytes: Option<Vec<u8>>,
160 partial: Option<DissectionNode>,
162 },
163}
164
165pub struct DissectingDiagnosticIterator<R> {
167 inner: DiagnosticIterator<R>,
168}
169
170impl<R: Read> Iterator for DissectingDiagnosticIterator<R> {
171 type Item = DissectedDiagnosticEvent;
172
173 fn next(&mut self) -> Option<Self::Item> {
174 self.inner.next().map(|event| match event {
175 DiagnosticEvent::Record {
176 record,
177 raw,
178 warnings,
179 } => {
180 let tree = dissect_mrt_record(&raw);
181 let warnings = span_record_warnings(&warnings, &tree);
182 DissectedDiagnosticEvent::Record {
183 record,
184 raw,
185 warnings,
186 tree,
187 }
188 }
189 DiagnosticEvent::ParseError {
190 error,
191 common_header,
192 raw_bytes,
193 partial,
194 } => DissectedDiagnosticEvent::ParseError {
195 error,
196 common_header,
197 raw_bytes,
198 partial,
199 },
200 })
201 }
202}
203
204pub fn record_validation_warnings(record: &MrtRecord) -> Vec<BgpValidationWarning> {
209 match &record.message {
210 MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(message)) => update_warnings(&message.bgp_message),
211 MrtMessage::LegacyBgp(LegacyBgp::Message(message)) => update_warnings(&message.bgp_message),
212 MrtMessage::TableDumpMessage(message) => message.attributes.validation_warnings().to_vec(),
213 MrtMessage::TableDumpMessageBatch(messages) => messages
214 .iter()
215 .flat_map(|message| message.attributes.validation_warnings().iter().cloned())
216 .collect(),
217 MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(rib)) => rib
218 .rib_entries
219 .iter()
220 .flat_map(|entry| entry.attributes.validation_warnings().iter().cloned())
221 .collect(),
222 _ => Vec::new(),
223 }
224}
225
226fn update_warnings(message: &BgpMessage) -> Vec<BgpValidationWarning> {
227 match message {
228 BgpMessage::Update(update) => update.attributes.validation_warnings().to_vec(),
229 BgpMessage::RouteRefresh(refresh) => refresh.validation_warnings(),
230 _ => Vec::new(),
231 }
232}
233
234pub fn span_record_warnings(
242 warnings: &[BgpValidationWarning],
243 tree: &DissectionNode,
244) -> Vec<SpannedWarning> {
245 let mut duplicate_counts: HashMap<u8, usize> = HashMap::new();
246 warnings
247 .iter()
248 .map(|warning| SpannedWarning {
249 span: locate_warning_span(warning, tree, &mut duplicate_counts),
250 warning: warning.clone(),
251 })
252 .collect()
253}
254
255fn locate_warning_span(
256 warning: &BgpValidationWarning,
257 tree: &DissectionNode,
258 duplicate_counts: &mut HashMap<u8, usize>,
259) -> Span {
260 use BgpValidationWarning as W;
261
262 let attr_span = |code: u8, occurrence: usize| -> Option<Span> {
263 let mut nodes = Vec::new();
264 tree.find_all(&format!("bgp.attr.{code}"), &mut nodes);
265 nodes.get(occurrence).map(|node| node.span())
266 };
267 let current_attr_span = |code: u8| -> Option<Span> {
273 let occurrence = duplicate_counts.get(&code).copied().unwrap_or(0);
274 attr_span(code, occurrence)
275 };
276 let section_span = |field: &str| -> Option<Span> { tree.find(field).map(|node| node.span()) };
277
278 let span = match warning {
279 W::AttributeFlagsError { attr_type, .. }
280 | W::AttributeLengthError { attr_type, .. }
281 | W::OptionalAttributeError { attr_type, .. }
282 | W::PartialAttributeError { attr_type, .. } => current_attr_span(u8::from(*attr_type))
283 .or_else(|| section_span("bgp.update.path_attributes")),
284 W::DuplicateAttribute { attr_type } => {
285 let code = u8::from(*attr_type);
286 let count = duplicate_counts.entry(code).or_insert(0);
287 *count += 1;
288 attr_span(code, *count).or_else(|| section_span("bgp.update.path_attributes"))
289 }
290 W::InvalidOriginAttribute { .. } => current_attr_span(1),
291 W::InvalidNextHopAttribute { .. } => current_attr_span(3),
292 W::MalformedAsPath { .. } => current_attr_span(2),
293 W::UnrecognizedWellKnownAttribute { attr_type_code } => current_attr_span(*attr_type_code),
294 W::MissingWellKnownAttribute { .. } | W::MalformedAttributeList { .. } => {
295 section_span("bgp.update.path_attributes")
296 }
297 W::InvalidNetworkField { .. } => {
298 section_span("bgp.update.nlri").or_else(|| section_span("bgp.update.withdrawn_routes"))
299 }
300 W::MalformedNlri { nlri_type, .. } => match *nlri_type {
301 "withdrawn" => section_span("bgp.update.withdrawn_routes"),
302 "announced" => section_span("bgp.update.nlri"),
303 "mp_reach" => current_attr_span(14),
304 "mp_unreach" => current_attr_span(15),
305 _ => None,
306 },
307 W::UnknownRouteRefreshSubtype { .. } | W::InvalidRouteRefreshLength { .. } => {
308 section_span("bgp.route_refresh")
309 }
310 };
311 span.unwrap_or_else(|| tree.span())
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use std::io::Cursor;
318 use std::net::IpAddr;
319 use std::str::FromStr;
320
321 fn update_record(attributes: Attributes) -> MrtRecord {
322 MrtRecord {
323 common_header: CommonHeader {
324 timestamp: 1_700_000_000,
325 microsecond_timestamp: None,
326 entry_type: EntryType::BGP4MP,
327 entry_subtype: Bgp4MpType::MessageAs4 as u16,
328 length: 0,
329 },
330 message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
331 msg_type: Bgp4MpType::MessageAs4,
332 peer_asn: Asn::new_32bit(64496),
333 local_asn: Asn::new_32bit(64497),
334 interface_index: 0,
335 peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
336 local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
337 bgp_message: BgpMessage::Update(BgpUpdateMessage {
338 withdrawn_prefixes: vec![],
339 attributes,
340 announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
341 }),
342 })),
343 }
344 }
345
346 fn valid_attributes() -> Attributes {
347 let mut attributes = Attributes::default();
348 attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
349 attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([64500])).into());
350 attributes
351 .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
352 attributes
353 }
354
355 fn update_wire_body(withdrawn: &[u8], attributes: &[u8], announced: &[u8]) -> Vec<u8> {
356 let mut body = Vec::new();
357 body.extend_from_slice(&(withdrawn.len() as u16).to_be_bytes());
358 body.extend_from_slice(withdrawn);
359 body.extend_from_slice(&(attributes.len() as u16).to_be_bytes());
360 body.extend_from_slice(attributes);
361 body.extend_from_slice(announced);
362 body
363 }
364
365 fn bgp4mp_update_wire(withdrawn: &[u8], attributes: &[u8], announced: &[u8]) -> Vec<u8> {
366 let update_body = update_wire_body(withdrawn, attributes, announced);
367 bgp4mp_message_wire(BgpMessageType::UPDATE, &update_body)
368 }
369
370 fn bgp4mp_route_refresh_wire(subtype: u8, orf_data: &[u8]) -> Vec<u8> {
371 let mut refresh_body = vec![0x00, 0x01, subtype, 0x01];
372 refresh_body.extend_from_slice(orf_data);
373 bgp4mp_message_wire(BgpMessageType::ROUTE_REFRESH, &refresh_body)
374 }
375
376 fn bgp4mp_message_wire(msg_type: BgpMessageType, msg_body: &[u8]) -> Vec<u8> {
377 let mut bgp_message = vec![0xff; 16];
378 bgp_message.extend_from_slice(&((19 + msg_body.len()) as u16).to_be_bytes());
379 bgp_message.push(msg_type as u8);
380 bgp_message.extend_from_slice(msg_body);
381
382 let mut body = Vec::new();
383 body.extend_from_slice(&64496u32.to_be_bytes());
384 body.extend_from_slice(&64497u32.to_be_bytes());
385 body.extend_from_slice(&0u16.to_be_bytes());
386 body.extend_from_slice(&1u16.to_be_bytes());
387 body.extend_from_slice(&[192, 0, 2, 1]);
388 body.extend_from_slice(&[192, 0, 2, 2]);
389 body.extend_from_slice(&bgp_message);
390
391 let header = CommonHeader {
392 timestamp: 1_700_000_000,
393 microsecond_timestamp: None,
394 entry_type: EntryType::BGP4MP,
395 entry_subtype: Bgp4MpType::MessageAs4 as u16,
396 length: body.len() as u32,
397 };
398 let mut wire = header.encode().to_vec();
399 wire.extend_from_slice(&body);
400 wire
401 }
402
403 fn valid_update_attributes_wire() -> Vec<u8> {
404 vec![
405 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 192, 0, 2, 254, ]
409 }
410
411 fn assert_wire_validation(
412 wire: Vec<u8>,
413 expected_warning: impl Fn(&BgpValidationWarning) -> bool,
414 ) {
415 let mut iter = BgpkitParser::from_reader(Cursor::new(wire.clone())).into_diagnostic_iter();
416 match iter.next().unwrap() {
417 DiagnosticEvent::Record { warnings, raw, .. } => {
418 assert!(
419 warnings.iter().any(expected_warning),
420 "expected validation warning, got {warnings:?}"
421 );
422 assert_eq!(raw.raw_bytes().as_ref(), wire.as_slice());
423 }
424 event => panic!("expected record event with warnings, got {event:?}"),
425 }
426 assert!(iter.next().is_none());
427 }
428
429 #[test]
430 fn diagnostic_iterator_flags_unknown_route_refresh_subtype() {
431 let wire = bgp4mp_route_refresh_wire(3, &[]);
433 assert_wire_validation(wire, |warning| {
434 matches!(
435 warning,
436 BgpValidationWarning::UnknownRouteRefreshSubtype { subtype: 3 }
437 )
438 });
439 }
440
441 #[test]
442 fn diagnostic_iterator_flags_borr_with_trailing_data() {
443 let wire = bgp4mp_route_refresh_wire(1, &[0xDE, 0xAD]);
445 assert_wire_validation(wire, |warning| {
446 matches!(
447 warning,
448 BgpValidationWarning::InvalidRouteRefreshLength {
449 subtype: 1,
450 length: 6
451 }
452 )
453 });
454 }
455
456 #[test]
457 fn diagnostic_iterator_passes_normal_route_refresh_with_orf_data_clean() {
458 let wire = bgp4mp_route_refresh_wire(0, &[0x01, 0x80, 0x00, 0x00]);
460 let mut iter = BgpkitParser::from_reader(Cursor::new(wire)).into_diagnostic_iter();
461 match iter.next().unwrap() {
462 DiagnosticEvent::Record {
463 record, warnings, ..
464 } => {
465 assert!(warnings.is_empty());
466 assert!(matches!(
467 record.message,
468 MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
469 bgp_message: BgpMessage::RouteRefresh(_),
470 ..
471 }))
472 ));
473 }
474 event => panic!("expected clean record event, got {event:?}"),
475 }
476 assert!(iter.next().is_none());
477 }
478
479 #[test]
480 fn diagnostic_iterator_yields_clean_record() {
481 let wire = update_record(valid_attributes()).encode().unwrap().to_vec();
482 let mut iter = BgpkitParser::from_reader(Cursor::new(wire)).into_diagnostic_iter();
483
484 match iter.next().unwrap() {
485 DiagnosticEvent::Record {
486 record, warnings, ..
487 } => {
488 assert!(warnings.is_empty());
489 assert!(matches!(record.message, MrtMessage::Bgp4Mp(_)));
490 }
491 event => panic!("expected clean record event, got {event:?}"),
492 }
493 assert!(iter.next().is_none());
494 }
495
496 #[test]
497 fn diagnostic_iterator_yields_validation_with_original_bytes() {
498 let wire = update_record(Attributes::default())
499 .encode()
500 .unwrap()
501 .to_vec();
502 let mut iter = BgpkitParser::from_reader(Cursor::new(wire.clone())).into_diagnostic_iter();
503
504 match iter.next().unwrap() {
505 DiagnosticEvent::Record {
506 record,
507 warnings,
508 raw,
509 } => {
510 assert!(matches!(record.message, MrtMessage::Bgp4Mp(_)));
511 assert!(warnings.iter().any(|warning| {
512 matches!(
513 warning,
514 BgpValidationWarning::MissingWellKnownAttribute {
515 attr_type: AttrType::ORIGIN
516 }
517 )
518 }));
519 assert_eq!(raw.raw_bytes().as_ref(), wire.as_slice());
520 }
521 event => panic!("expected record event with warnings, got {event:?}"),
522 }
523 }
524
525 #[test]
526 fn diagnostic_iterator_preserves_body_error_and_continues() {
527 let header = CommonHeader {
528 timestamp: 2,
529 microsecond_timestamp: None,
530 entry_type: EntryType::TABLE_DUMP,
531 entry_subtype: 0,
532 length: 4,
533 };
534 let mut input = header.encode().to_vec();
535 input.extend_from_slice(&[0xff; 4]);
536 let invalid_record = input.clone();
537 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
538
539 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
540 match iter.next().unwrap() {
541 DiagnosticEvent::ParseError {
542 common_header,
543 raw_bytes,
544 ..
545 } => {
546 assert_eq!(common_header, Some(header));
547 assert_eq!(raw_bytes.as_deref(), Some(invalid_record.as_slice()));
548 }
549 event => panic!("expected parse error event, got {event:?}"),
550 }
551 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record { .. })));
552 assert!(iter.next().is_none());
553 }
554
555 #[test]
556 fn diagnostic_iterator_preserves_partial_body_context() {
557 let header = CommonHeader {
558 timestamp: 3,
559 microsecond_timestamp: None,
560 entry_type: EntryType::BGP4MP,
561 entry_subtype: 0,
562 length: 4,
563 };
564 let mut input = header.encode().to_vec();
565 input.extend_from_slice(&[0, 1]);
566
567 match BgpkitParser::from_reader(Cursor::new(input.clone()))
568 .into_diagnostic_iter()
569 .next()
570 .unwrap()
571 {
572 DiagnosticEvent::ParseError {
573 common_header,
574 raw_bytes,
575 ..
576 } => {
577 assert_eq!(common_header, Some(header));
578 assert_eq!(raw_bytes.as_deref(), Some(input.as_slice()));
579 }
580 event => panic!("expected parse error event, got {event:?}"),
581 }
582 }
583
584 #[test]
585 fn diagnostic_iterator_parse_error_carries_partial_tree() {
586 let mut bgp = vec![0xFF; 16];
589 bgp.extend_from_slice(&21u16.to_be_bytes()); bgp.push(2);
591 bgp.extend_from_slice(&[0x00]); let mut body = Vec::new();
594 body.extend_from_slice(&64496u32.to_be_bytes());
595 body.extend_from_slice(&64497u32.to_be_bytes());
596 body.extend_from_slice(&0u16.to_be_bytes());
597 body.extend_from_slice(&1u16.to_be_bytes());
598 body.extend_from_slice(&[192, 0, 2, 1]);
599 body.extend_from_slice(&[192, 0, 2, 2]);
600 body.extend_from_slice(&bgp);
601
602 let mut wire = CommonHeader {
603 timestamp: 1,
604 microsecond_timestamp: None,
605 entry_type: EntryType::BGP4MP,
606 entry_subtype: Bgp4MpType::MessageAs4 as u16,
607 length: body.len() as u32,
608 }
609 .encode()
610 .to_vec();
611 wire.extend_from_slice(&body);
612
613 match BgpkitParser::from_reader(Cursor::new(wire))
614 .into_diagnostic_iter()
615 .next()
616 .unwrap()
617 {
618 DiagnosticEvent::ParseError { partial, .. } => {
619 let partial = partial.expect("partial tree on parse error");
620 assert!(partial.find("mrt.header.type").is_some());
621 assert!(partial.find("mrt.bgp4mp.peer_asn").is_some());
622 assert!(partial.find("bgp.header.marker").is_some());
623 assert!(partial.find("bgp.update.path_attributes").is_none());
624 }
625 event => panic!("expected parse error event, got {event:?}"),
626 }
627 }
628
629 #[test]
630 fn diagnostic_iterator_terminates_after_framing_error() {
631 let header = CommonHeader {
632 timestamp: 3,
633 microsecond_timestamp: None,
634 entry_type: EntryType::BGP4MP,
635 entry_subtype: Bgp4MpType::MessageAs4 as u16,
636 length: 16 * 1024 * 1024 + 1,
637 };
638 let mut input = header.encode().to_vec();
639 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
640
641 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
642 match iter.next().unwrap() {
643 DiagnosticEvent::ParseError {
644 common_header,
645 raw_bytes,
646 ..
647 } => {
648 assert_eq!(common_header, Some(header));
649 assert_eq!(raw_bytes.as_deref(), Some(header.encode().as_ref()));
650 }
651 event => panic!("expected parse error event, got {event:?}"),
652 }
653 assert!(iter.next().is_none());
654 }
655
656 #[test]
657 fn diagnostic_iterator_preserves_invalid_header_bytes() {
658 let invalid_header = vec![
659 0, 0, 0, 1, 0xff, 0xff, 0, 0, 0, 0, 0, 0, ];
664
665 match BgpkitParser::from_reader(Cursor::new(invalid_header.clone()))
666 .into_diagnostic_iter()
667 .next()
668 .unwrap()
669 {
670 DiagnosticEvent::ParseError {
671 common_header,
672 raw_bytes,
673 partial,
674 ..
675 } => {
676 assert_eq!(common_header, None);
677 assert_eq!(raw_bytes.as_deref(), Some(invalid_header.as_slice()));
678 let partial = partial.unwrap();
681 assert!(partial.find("mrt.header.type").is_some());
682 assert!(partial.find("mrt.body").is_none());
683 }
684 event => panic!("expected parse error event, got {event:?}"),
685 }
686 }
687
688 #[test]
689 fn diagnostic_iterator_ignores_filters() {
690 let wire = update_record(valid_attributes()).encode().unwrap().to_vec();
691 let parser = BgpkitParser::from_reader(Cursor::new(wire))
692 .add_filter("prefix", "198.51.100.0/24")
693 .unwrap();
694
695 assert!(matches!(
696 parser.into_diagnostic_iter().next(),
697 Some(DiagnosticEvent::Record { .. })
698 ));
699 }
700
701 #[test]
702 fn diagnostic_iterator_reports_wire_level_nlri_warnings_and_continues() {
703 let malformed_nlri = [0xc8, 0x01];
704 let invalid_wire = bgp4mp_update_wire(
705 &malformed_nlri,
706 &valid_update_attributes_wire(),
707 &malformed_nlri,
708 );
709 let mut input = invalid_wire.clone();
710 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
711
712 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
713 match iter.next().unwrap() {
714 DiagnosticEvent::Record { warnings, raw, .. } => {
715 assert!(warnings.iter().any(|warning| {
716 matches!(
717 warning,
718 BgpValidationWarning::MalformedNlri {
719 nlri_type: "withdrawn",
720 raw_bytes,
721 ..
722 } if raw_bytes == &malformed_nlri
723 )
724 }));
725 assert!(warnings.iter().any(|warning| {
726 matches!(
727 warning,
728 BgpValidationWarning::MalformedNlri {
729 nlri_type: "announced",
730 raw_bytes,
731 ..
732 } if raw_bytes == &malformed_nlri
733 )
734 }));
735 assert_eq!(raw.raw_bytes().as_ref(), invalid_wire.as_slice());
736 }
737 event => panic!("expected record event with warnings, got {event:?}"),
738 }
739 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record { .. })));
740 assert!(iter.next().is_none());
741 }
742
743 #[test]
744 fn diagnostic_iterator_reports_wire_level_attribute_warnings() {
745 let announced = [0x18, 10, 0, 0];
746 let mut flags_and_duplicate = valid_update_attributes_wire();
747 flags_and_duplicate[0] = 0x80;
748 flags_and_duplicate.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
749 assert_wire_validation(
750 bgp4mp_update_wire(&[], &flags_and_duplicate, &announced),
751 |warning| {
752 matches!(
753 warning,
754 BgpValidationWarning::AttributeFlagsError {
755 attr_type: AttrType::ORIGIN,
756 ..
757 }
758 )
759 },
760 );
761 assert_wire_validation(
762 bgp4mp_update_wire(&[], &flags_and_duplicate, &announced),
763 |warning| {
764 matches!(
765 warning,
766 BgpValidationWarning::DuplicateAttribute {
767 attr_type: AttrType::ORIGIN
768 }
769 )
770 },
771 );
772
773 let mut invalid_origin = valid_update_attributes_wire();
774 invalid_origin[3] = 3;
775 assert_wire_validation(
776 bgp4mp_update_wire(&[], &invalid_origin, &announced),
777 |warning| matches!(warning, BgpValidationWarning::MalformedAttributeList { .. }),
778 );
779
780 let malformed_as_path = vec![
781 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x02, 0x02, 0x01, 0x40, 0x03, 0x04, 192, 0, 2, 254, ];
785 assert_wire_validation(
786 bgp4mp_update_wire(&[], &malformed_as_path, &announced),
787 |warning| matches!(warning, BgpValidationWarning::MalformedAttributeList { .. }),
788 );
789
790 let invalid_next_hop = vec![
791 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x03, 192, 0, 2, ];
795 assert_wire_validation(
796 bgp4mp_update_wire(&[], &invalid_next_hop, &announced),
797 |warning| {
798 matches!(
799 warning,
800 BgpValidationWarning::AttributeLengthError {
801 attr_type: AttrType::NEXT_HOP,
802 ..
803 }
804 )
805 },
806 );
807 }
808
809 #[test]
810 fn diagnostic_iterator_collects_table_dump_batch_and_rib_warnings() {
811 let table_dump_messages = vec![
812 TableDumpMessage {
813 view_number: 0,
814 sequence_number: 1,
815 prefix: NetworkPrefix::from_str("192.0.2.0/24").unwrap(),
816 status: 1,
817 originated_time: 1,
818 peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
819 peer_asn: Asn::new_16bit(64512),
820 attributes: Attributes::default(),
821 },
822 TableDumpMessage {
823 view_number: 0,
824 sequence_number: 1,
825 prefix: NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
826 status: 1,
827 originated_time: 2,
828 peer_ip: IpAddr::from_str("192.0.2.2").unwrap(),
829 peer_asn: Asn::new_16bit(64513),
830 attributes: Attributes::default(),
831 },
832 ];
833 let table_dump_record = MrtRecord {
834 common_header: CommonHeader {
835 timestamp: 1,
836 microsecond_timestamp: None,
837 entry_type: EntryType::TABLE_DUMP,
838 entry_subtype: 1,
839 length: 0,
840 },
841 message: MrtMessage::TableDumpMessageBatch(table_dump_messages),
842 };
843 let table_dump_wire = table_dump_record.encode().unwrap().to_vec();
844 match BgpkitParser::from_reader(Cursor::new(table_dump_wire.clone()))
845 .into_diagnostic_iter()
846 .next()
847 .unwrap()
848 {
849 DiagnosticEvent::Record {
850 record, warnings, ..
851 } => {
852 assert!(matches!(
853 record.message,
854 MrtMessage::TableDumpMessageBatch(_)
855 ));
856 assert_eq!(warnings.len(), 6);
857 }
858 event => panic!("expected record event, got {event:?}"),
859 }
860
861 let rib_record = MrtRecord {
862 common_header: CommonHeader {
863 timestamp: 2,
864 microsecond_timestamp: None,
865 entry_type: EntryType::TABLE_DUMP_V2,
866 entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
867 length: 0,
868 },
869 message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
870 rib_type: TableDumpV2Type::RibIpv4Unicast,
871 sequence_number: 1,
872 prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
873 rib_entries: vec![RibEntry {
874 peer_index: 0,
875 originated_time: 1,
876 path_id: None,
877 attributes: Attributes::default(),
878 }],
879 })),
880 };
881 let rib_wire = rib_record.encode().unwrap().to_vec();
882 match BgpkitParser::from_reader(Cursor::new(rib_wire))
883 .into_diagnostic_iter()
884 .next()
885 .unwrap()
886 {
887 DiagnosticEvent::Record { warnings, .. } => {
888 assert_eq!(warnings.len(), 3);
889 }
890 event => panic!("expected record event, got {event:?}"),
891 }
892 }
893
894 #[test]
895 fn with_dissection_attaches_tree_and_spans() {
896 let announced = [0x18, 10, 0, 0];
899 let mut bad_flags = valid_update_attributes_wire();
900 bad_flags[0] = 0x80;
901 let wire = bgp4mp_update_wire(&[], &bad_flags, &announced);
902
903 let mut iter = BgpkitParser::from_reader(Cursor::new(wire))
904 .into_diagnostic_iter()
905 .with_dissection();
906 match iter.next().unwrap() {
907 DissectedDiagnosticEvent::Record { warnings, tree, .. } => {
908 assert!(tree.find("mrt.header.type").is_some());
910 assert!(tree.find("mrt.bgp4mp.peer_asn").is_some());
911 assert!(tree.find("bgp.header.type").is_some());
912 assert!(tree.find("bgp.attr.1").is_some());
913
914 let origin = tree.find("bgp.attr.1").unwrap();
915 assert_eq!(warnings.len(), 1);
916 assert!(matches!(
917 warnings[0].warning,
918 BgpValidationWarning::AttributeFlagsError { .. }
919 ));
920 assert_eq!(warnings[0].span, origin.span());
921 }
922 event => panic!("expected dissected record event, got {event:?}"),
923 }
924 }
925
926 #[test]
927 fn with_dissection_anchors_duplicate_to_second_occurrence() {
928 let mut attrs = Vec::new();
931 attrs.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]); attrs.extend_from_slice(&[0x80, 0x01, 0x01, 0x00]); let announced = [0x18, 10, 0, 0];
934 let wire = bgp4mp_update_wire(&[], &attrs, &announced);
935
936 let mut iter = BgpkitParser::from_reader(Cursor::new(wire))
937 .into_diagnostic_iter()
938 .with_dissection();
939 match iter.next().unwrap() {
940 DissectedDiagnosticEvent::Record { warnings, tree, .. } => {
941 let mut origins = Vec::new();
942 tree.find_all("bgp.attr.1", &mut origins);
943 assert_eq!(origins.len(), 2, "two ORIGIN attributes on the wire");
944
945 let duplicate = warnings
946 .iter()
947 .find(|w| matches!(w.warning, BgpValidationWarning::DuplicateAttribute { .. }))
948 .expect("duplicate warning");
949 assert_eq!(duplicate.span, origins[1].span());
951
952 let flags = warnings
955 .iter()
956 .find(|w| matches!(w.warning, BgpValidationWarning::AttributeFlagsError { .. }))
957 .expect("flags warning");
958 assert_eq!(flags.span, origins[1].span());
959 }
960 event => panic!("expected dissected record event, got {event:?}"),
961 }
962 }
963
964 #[test]
965 fn with_dissection_passes_through_parse_error_partial() {
966 let header = CommonHeader {
969 timestamp: 3,
970 microsecond_timestamp: None,
971 entry_type: EntryType::TABLE_DUMP,
972 entry_subtype: 0,
973 length: 4,
974 };
975 let mut input = header.encode().to_vec();
976 input.extend_from_slice(&[0xff; 4]);
977 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
978
979 let mut iter = BgpkitParser::from_reader(Cursor::new(input))
980 .into_diagnostic_iter()
981 .with_dissection();
982 match iter.next().unwrap() {
983 DissectedDiagnosticEvent::ParseError { partial, .. } => {
984 assert!(partial.is_some());
985 }
986 event => panic!("expected dissected parse error event, got {event:?}"),
987 }
988 assert!(matches!(
989 iter.next(),
990 Some(DissectedDiagnosticEvent::Record { .. })
991 ));
992 }
993
994 #[test]
995 fn record_validation_warnings_reachable_from_crate_root() {
996 let record = update_record(valid_attributes());
1000 assert!(crate::record_validation_warnings(&record).is_empty());
1001
1002 let mut attributes = Attributes::default();
1003 attributes.add_validation_warning(BgpValidationWarning::MissingWellKnownAttribute {
1004 attr_type: AttrType::ORIGIN,
1005 });
1006 let flagged = update_record(attributes);
1007 assert_eq!(crate::record_validation_warnings(&flagged).len(), 1);
1008 }
1009
1010 #[test]
1011 fn with_dissection_spans_nlri_warning() {
1012 let malformed_nlri = [0xc8, 0x01];
1013 let mut body = Vec::new();
1014 body.extend_from_slice(&(malformed_nlri.len() as u16).to_be_bytes());
1015 body.extend_from_slice(&malformed_nlri);
1016 body.extend_from_slice(&(valid_update_attributes_wire().len() as u16).to_be_bytes());
1017 body.extend_from_slice(&valid_update_attributes_wire());
1018 let wire = bgp4mp_message_wire(BgpMessageType::UPDATE, &body);
1019
1020 let mut iter = BgpkitParser::from_reader(Cursor::new(wire))
1021 .into_diagnostic_iter()
1022 .with_dissection();
1023 match iter.next().unwrap() {
1024 DissectedDiagnosticEvent::Record { warnings, tree, .. } => {
1025 let withdrawn = tree.find("bgp.update.withdrawn_routes").unwrap();
1026 let warning = warnings
1027 .iter()
1028 .find(|w| {
1029 matches!(
1030 w.warning,
1031 BgpValidationWarning::MalformedNlri {
1032 nlri_type: "withdrawn",
1033 ..
1034 }
1035 )
1036 })
1037 .expect("withdrawn NLRI warning");
1038 assert_eq!(warning.span, withdrawn.span());
1039 }
1040 event => panic!("expected dissected record event, got {event:?}"),
1041 }
1042 }
1043}