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 = !error.record_consumed;
99 return Some(DiagnosticEvent::ParseError {
100 error: error.error,
101 common_header: error.common_header,
102 partial: error.bytes.as_deref().map(dissect_mrt_bytes),
103 raw_bytes: error.bytes,
104 });
105 }
106 };
107
108 let used_zebra_compat = raw_record_uses_zebra_compat(&raw_record);
109 let record = match raw_record.clone().parse() {
110 Ok(record) => record,
111 Err(error) => {
112 let raw_bytes = Some(raw_record.raw_bytes().to_vec());
113 return Some(DiagnosticEvent::ParseError {
114 error,
115 common_header: Some(raw_record.common_header),
116 partial: raw_bytes.as_deref().map(dissect_mrt_bytes),
117 raw_bytes,
118 });
119 }
120 };
121 if used_zebra_compat {
122 self.parser.warn_zebra_compat_once();
123 }
124
125 let warnings = record_validation_warnings(&record);
126 Some(DiagnosticEvent::Record {
127 record,
128 raw: raw_record,
129 warnings,
130 })
131 }
132}
133
134#[derive(Debug)]
136#[allow(clippy::large_enum_variant)]
139#[non_exhaustive]
140pub enum DissectedDiagnosticEvent {
141 Record {
143 record: MrtRecord,
145 raw: RawMrtRecord,
147 warnings: Vec<SpannedWarning>,
149 tree: DissectionNode,
151 },
152 ParseError {
155 error: ParserError,
157 common_header: Option<CommonHeader>,
159 raw_bytes: Option<Vec<u8>>,
161 partial: Option<DissectionNode>,
163 },
164}
165
166pub struct DissectingDiagnosticIterator<R> {
168 inner: DiagnosticIterator<R>,
169}
170
171impl<R: Read> Iterator for DissectingDiagnosticIterator<R> {
172 type Item = DissectedDiagnosticEvent;
173
174 fn next(&mut self) -> Option<Self::Item> {
175 self.inner.next().map(|event| match event {
176 DiagnosticEvent::Record {
177 record,
178 raw,
179 warnings,
180 } => {
181 let tree = dissect_mrt_record(&raw);
182 let warnings = span_record_warnings(&warnings, &tree);
183 DissectedDiagnosticEvent::Record {
184 record,
185 raw,
186 warnings,
187 tree,
188 }
189 }
190 DiagnosticEvent::ParseError {
191 error,
192 common_header,
193 raw_bytes,
194 partial,
195 } => DissectedDiagnosticEvent::ParseError {
196 error,
197 common_header,
198 raw_bytes,
199 partial,
200 },
201 })
202 }
203}
204
205pub fn record_validation_warnings(record: &MrtRecord) -> Vec<BgpValidationWarning> {
210 match &record.message {
211 MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(message)) => update_warnings(&message.bgp_message),
212 MrtMessage::LegacyBgp(LegacyBgp::Message(message)) => update_warnings(&message.bgp_message),
213 MrtMessage::TableDumpMessage(message) => message.attributes.validation_warnings().to_vec(),
214 MrtMessage::TableDumpMessageBatch(messages) => messages
215 .iter()
216 .flat_map(|message| message.attributes.validation_warnings().iter().cloned())
217 .collect(),
218 MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(rib)) => rib
219 .rib_entries
220 .iter()
221 .flat_map(|entry| entry.attributes.validation_warnings().iter().cloned())
222 .collect(),
223 _ => Vec::new(),
224 }
225}
226
227fn update_warnings(message: &BgpMessage) -> Vec<BgpValidationWarning> {
228 match message {
229 BgpMessage::Update(update) => update.attributes.validation_warnings().to_vec(),
230 BgpMessage::RouteRefresh(refresh) => refresh.validation_warnings(),
231 _ => Vec::new(),
232 }
233}
234
235pub fn span_record_warnings(
243 warnings: &[BgpValidationWarning],
244 tree: &DissectionNode,
245) -> Vec<SpannedWarning> {
246 if warnings.is_empty() {
248 return Vec::new();
249 }
250 let index = SpanIndex::build(tree);
253 let mut duplicate_counts: HashMap<u8, usize> = HashMap::new();
254 warnings
255 .iter()
256 .map(|warning| SpannedWarning {
257 span: locate_warning_span(warning, &index, &mut duplicate_counts)
258 .unwrap_or_else(|| tree.span()),
259 warning: warning.clone(),
260 })
261 .collect()
262}
263
264struct SpanIndex<'a> {
268 spans: HashMap<&'a str, Vec<Span>>,
269}
270
271impl<'a> SpanIndex<'a> {
272 fn build(tree: &'a DissectionNode) -> Self {
273 let mut spans = HashMap::new();
274 Self::walk(tree, &mut spans);
275 Self { spans }
276 }
277
278 fn walk(node: &'a DissectionNode, spans: &mut HashMap<&'a str, Vec<Span>>) {
279 spans
280 .entry(node.field.as_str())
281 .or_default()
282 .push(node.span());
283 for child in &node.children {
284 Self::walk(child, spans);
285 }
286 }
287
288 fn nth(&self, field: &str, occurrence: usize) -> Option<Span> {
289 self.spans.get(field)?.get(occurrence).copied()
290 }
291}
292
293fn locate_warning_span(
294 warning: &BgpValidationWarning,
295 index: &SpanIndex<'_>,
296 duplicate_counts: &mut HashMap<u8, usize>,
297) -> Option<Span> {
298 use BgpValidationWarning as W;
299
300 let attr_span = |code: u8, occurrence: usize| -> Option<Span> {
301 index.nth(&format!("bgp.attr.{code}"), occurrence)
302 };
303 let current_attr_span = |code: u8| -> Option<Span> {
309 let occurrence = duplicate_counts.get(&code).copied().unwrap_or(0);
310 attr_span(code, occurrence)
311 };
312 let section_span = |field: &str| -> Option<Span> { index.nth(field, 0) };
313
314 match warning {
315 W::AttributeFlagsError { attr_type, .. }
316 | W::AttributeLengthError { attr_type, .. }
317 | W::OptionalAttributeError { attr_type, .. }
318 | W::PartialAttributeError { attr_type, .. } => current_attr_span(u8::from(*attr_type))
319 .or_else(|| section_span("bgp.update.path_attributes")),
320 W::DuplicateAttribute { attr_type } => {
321 let code = u8::from(*attr_type);
322 let count = duplicate_counts.entry(code).or_insert(0);
323 *count += 1;
324 attr_span(code, *count).or_else(|| section_span("bgp.update.path_attributes"))
325 }
326 W::InvalidOriginAttribute { .. } => current_attr_span(1),
327 W::InvalidNextHopAttribute { .. } => current_attr_span(3),
328 W::MalformedAsPath { .. } => current_attr_span(2),
329 W::UnrecognizedWellKnownAttribute { attr_type_code } => current_attr_span(*attr_type_code),
330 W::MissingWellKnownAttribute { .. } | W::MalformedAttributeList { .. } => {
331 section_span("bgp.update.path_attributes")
332 }
333 W::InvalidNetworkField { .. } => {
334 section_span("bgp.update.nlri").or_else(|| section_span("bgp.update.withdrawn_routes"))
335 }
336 W::MalformedNlri { nlri_type, .. } => match *nlri_type {
337 "withdrawn" => section_span("bgp.update.withdrawn_routes"),
338 "announced" => section_span("bgp.update.nlri"),
339 "mp_reach" => current_attr_span(14),
340 "mp_unreach" => current_attr_span(15),
341 _ => None,
342 },
343 W::UnknownRouteRefreshSubtype { .. } | W::InvalidRouteRefreshLength { .. } => {
344 section_span("bgp.route_refresh")
345 }
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use std::io::Cursor;
353 use std::net::IpAddr;
354 use std::str::FromStr;
355
356 fn update_record(attributes: Attributes) -> MrtRecord {
357 MrtRecord {
358 common_header: CommonHeader {
359 timestamp: 1_700_000_000,
360 microsecond_timestamp: None,
361 entry_type: EntryType::BGP4MP,
362 entry_subtype: Bgp4MpType::MessageAs4 as u16,
363 length: 0,
364 },
365 message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
366 msg_type: Bgp4MpType::MessageAs4,
367 peer_asn: Asn::new_32bit(64496),
368 local_asn: Asn::new_32bit(64497),
369 interface_index: 0,
370 peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
371 local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
372 bgp_message: BgpMessage::Update(BgpUpdateMessage {
373 withdrawn_prefixes: vec![],
374 attributes,
375 announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
376 }),
377 })),
378 }
379 }
380
381 fn valid_attributes() -> Attributes {
382 let mut attributes = Attributes::default();
383 attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
384 attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([64500])).into());
385 attributes
386 .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
387 attributes
388 }
389
390 fn update_wire_body(withdrawn: &[u8], attributes: &[u8], announced: &[u8]) -> Vec<u8> {
391 let mut body = Vec::new();
392 body.extend_from_slice(&(withdrawn.len() as u16).to_be_bytes());
393 body.extend_from_slice(withdrawn);
394 body.extend_from_slice(&(attributes.len() as u16).to_be_bytes());
395 body.extend_from_slice(attributes);
396 body.extend_from_slice(announced);
397 body
398 }
399
400 fn bgp4mp_update_wire(withdrawn: &[u8], attributes: &[u8], announced: &[u8]) -> Vec<u8> {
401 let update_body = update_wire_body(withdrawn, attributes, announced);
402 bgp4mp_message_wire(BgpMessageType::UPDATE, &update_body)
403 }
404
405 fn bgp4mp_route_refresh_wire(subtype: u8, orf_data: &[u8]) -> Vec<u8> {
406 let mut refresh_body = vec![0x00, 0x01, subtype, 0x01];
407 refresh_body.extend_from_slice(orf_data);
408 bgp4mp_message_wire(BgpMessageType::ROUTE_REFRESH, &refresh_body)
409 }
410
411 fn bgp4mp_message_wire(msg_type: BgpMessageType, msg_body: &[u8]) -> Vec<u8> {
412 let mut bgp_message = vec![0xff; 16];
413 bgp_message.extend_from_slice(&((19 + msg_body.len()) as u16).to_be_bytes());
414 bgp_message.push(msg_type as u8);
415 bgp_message.extend_from_slice(msg_body);
416
417 let mut body = Vec::new();
418 body.extend_from_slice(&64496u32.to_be_bytes());
419 body.extend_from_slice(&64497u32.to_be_bytes());
420 body.extend_from_slice(&0u16.to_be_bytes());
421 body.extend_from_slice(&1u16.to_be_bytes());
422 body.extend_from_slice(&[192, 0, 2, 1]);
423 body.extend_from_slice(&[192, 0, 2, 2]);
424 body.extend_from_slice(&bgp_message);
425
426 let header = CommonHeader {
427 timestamp: 1_700_000_000,
428 microsecond_timestamp: None,
429 entry_type: EntryType::BGP4MP,
430 entry_subtype: Bgp4MpType::MessageAs4 as u16,
431 length: body.len() as u32,
432 };
433 let mut wire = header.encode().to_vec();
434 wire.extend_from_slice(&body);
435 wire
436 }
437
438 fn valid_update_attributes_wire() -> Vec<u8> {
439 vec![
440 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 192, 0, 2, 254, ]
444 }
445
446 fn assert_wire_validation(
447 wire: Vec<u8>,
448 expected_warning: impl Fn(&BgpValidationWarning) -> bool,
449 ) {
450 let mut iter = BgpkitParser::from_reader(Cursor::new(wire.clone())).into_diagnostic_iter();
451 match iter.next().unwrap() {
452 DiagnosticEvent::Record { warnings, raw, .. } => {
453 assert!(
454 warnings.iter().any(expected_warning),
455 "expected validation warning, got {warnings:?}"
456 );
457 assert_eq!(raw.raw_bytes().as_ref(), wire.as_slice());
458 }
459 event => panic!("expected record event with warnings, got {event:?}"),
460 }
461 assert!(iter.next().is_none());
462 }
463
464 #[test]
465 fn diagnostic_iterator_flags_unknown_route_refresh_subtype() {
466 let wire = bgp4mp_route_refresh_wire(3, &[]);
468 assert_wire_validation(wire, |warning| {
469 matches!(
470 warning,
471 BgpValidationWarning::UnknownRouteRefreshSubtype { subtype: 3 }
472 )
473 });
474 }
475
476 #[test]
477 fn diagnostic_iterator_flags_borr_with_trailing_data() {
478 let wire = bgp4mp_route_refresh_wire(1, &[0xDE, 0xAD]);
480 assert_wire_validation(wire, |warning| {
481 matches!(
482 warning,
483 BgpValidationWarning::InvalidRouteRefreshLength {
484 subtype: 1,
485 length: 6
486 }
487 )
488 });
489 }
490
491 #[test]
492 fn diagnostic_iterator_passes_normal_route_refresh_with_orf_data_clean() {
493 let wire = bgp4mp_route_refresh_wire(0, &[0x01, 0x80, 0x00, 0x00]);
495 let mut iter = BgpkitParser::from_reader(Cursor::new(wire)).into_diagnostic_iter();
496 match iter.next().unwrap() {
497 DiagnosticEvent::Record {
498 record, warnings, ..
499 } => {
500 assert!(warnings.is_empty());
501 assert!(matches!(
502 record.message,
503 MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
504 bgp_message: BgpMessage::RouteRefresh(_),
505 ..
506 }))
507 ));
508 }
509 event => panic!("expected clean record event, got {event:?}"),
510 }
511 assert!(iter.next().is_none());
512 }
513
514 #[test]
515 fn diagnostic_iterator_yields_clean_record() {
516 let wire = update_record(valid_attributes()).encode().unwrap().to_vec();
517 let mut iter = BgpkitParser::from_reader(Cursor::new(wire)).into_diagnostic_iter();
518
519 match iter.next().unwrap() {
520 DiagnosticEvent::Record {
521 record, warnings, ..
522 } => {
523 assert!(warnings.is_empty());
524 assert!(matches!(record.message, MrtMessage::Bgp4Mp(_)));
525 }
526 event => panic!("expected clean record event, got {event:?}"),
527 }
528 assert!(iter.next().is_none());
529 }
530
531 #[test]
532 fn diagnostic_iterator_yields_validation_with_original_bytes() {
533 let wire = update_record(Attributes::default())
534 .encode()
535 .unwrap()
536 .to_vec();
537 let mut iter = BgpkitParser::from_reader(Cursor::new(wire.clone())).into_diagnostic_iter();
538
539 match iter.next().unwrap() {
540 DiagnosticEvent::Record {
541 record,
542 warnings,
543 raw,
544 } => {
545 assert!(matches!(record.message, MrtMessage::Bgp4Mp(_)));
546 assert!(warnings.iter().any(|warning| {
547 matches!(
548 warning,
549 BgpValidationWarning::MissingWellKnownAttribute {
550 attr_type: AttrType::ORIGIN
551 }
552 )
553 }));
554 assert_eq!(raw.raw_bytes().as_ref(), wire.as_slice());
555 }
556 event => panic!("expected record event with warnings, got {event:?}"),
557 }
558 }
559
560 #[test]
561 fn diagnostic_iterator_preserves_body_error_and_continues() {
562 let header = CommonHeader {
563 timestamp: 2,
564 microsecond_timestamp: None,
565 entry_type: EntryType::TABLE_DUMP,
566 entry_subtype: 0,
567 length: 4,
568 };
569 let mut input = header.encode().to_vec();
570 input.extend_from_slice(&[0xff; 4]);
571 let invalid_record = input.clone();
572 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
573
574 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
575 match iter.next().unwrap() {
576 DiagnosticEvent::ParseError {
577 common_header,
578 raw_bytes,
579 ..
580 } => {
581 assert_eq!(common_header, Some(header));
582 assert_eq!(raw_bytes.as_deref(), Some(invalid_record.as_slice()));
583 }
584 event => panic!("expected parse error event, got {event:?}"),
585 }
586 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record { .. })));
587 assert!(iter.next().is_none());
588 }
589
590 #[test]
591 fn diagnostic_iterator_preserves_partial_body_context() {
592 let header = CommonHeader {
593 timestamp: 3,
594 microsecond_timestamp: None,
595 entry_type: EntryType::BGP4MP,
596 entry_subtype: 0,
597 length: 4,
598 };
599 let mut input = header.encode().to_vec();
600 input.extend_from_slice(&[0, 1]);
601
602 match BgpkitParser::from_reader(Cursor::new(input.clone()))
603 .into_diagnostic_iter()
604 .next()
605 .unwrap()
606 {
607 DiagnosticEvent::ParseError {
608 common_header,
609 raw_bytes,
610 ..
611 } => {
612 assert_eq!(common_header, Some(header));
613 assert_eq!(raw_bytes.as_deref(), Some(input.as_slice()));
614 }
615 event => panic!("expected parse error event, got {event:?}"),
616 }
617 }
618
619 #[test]
620 fn diagnostic_iterator_parse_error_carries_partial_tree() {
621 let mut bgp = vec![0xFF; 16];
624 bgp.extend_from_slice(&21u16.to_be_bytes()); bgp.push(2);
626 bgp.extend_from_slice(&[0x00]); let mut body = Vec::new();
629 body.extend_from_slice(&64496u32.to_be_bytes());
630 body.extend_from_slice(&64497u32.to_be_bytes());
631 body.extend_from_slice(&0u16.to_be_bytes());
632 body.extend_from_slice(&1u16.to_be_bytes());
633 body.extend_from_slice(&[192, 0, 2, 1]);
634 body.extend_from_slice(&[192, 0, 2, 2]);
635 body.extend_from_slice(&bgp);
636
637 let mut wire = CommonHeader {
638 timestamp: 1,
639 microsecond_timestamp: None,
640 entry_type: EntryType::BGP4MP,
641 entry_subtype: Bgp4MpType::MessageAs4 as u16,
642 length: body.len() as u32,
643 }
644 .encode()
645 .to_vec();
646 wire.extend_from_slice(&body);
647
648 match BgpkitParser::from_reader(Cursor::new(wire))
649 .into_diagnostic_iter()
650 .next()
651 .unwrap()
652 {
653 DiagnosticEvent::ParseError { partial, .. } => {
654 let partial = partial.expect("partial tree on parse error");
655 assert!(partial.find("mrt.header.type").is_some());
656 assert!(partial.find("mrt.bgp4mp.peer_asn").is_some());
657 assert!(partial.find("bgp.header.marker").is_some());
658 assert!(partial.find("bgp.update.path_attributes").is_none());
659 }
660 event => panic!("expected parse error event, got {event:?}"),
661 }
662 }
663
664 #[test]
665 fn diagnostic_iterator_terminates_after_framing_error() {
666 let header = CommonHeader {
667 timestamp: 3,
668 microsecond_timestamp: None,
669 entry_type: EntryType::BGP4MP,
670 entry_subtype: Bgp4MpType::MessageAs4 as u16,
671 length: 16 * 1024 * 1024 + 1,
672 };
673 let mut input = header.encode().to_vec();
674 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
675
676 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
677 match iter.next().unwrap() {
678 DiagnosticEvent::ParseError {
679 common_header,
680 raw_bytes,
681 ..
682 } => {
683 assert_eq!(common_header, Some(header));
684 assert_eq!(raw_bytes.as_deref(), Some(header.encode().as_ref()));
685 }
686 event => panic!("expected parse error event, got {event:?}"),
687 }
688 assert!(iter.next().is_none());
689 }
690
691 #[test]
692 fn diagnostic_iterator_continues_after_unknown_entry_type() {
693 let mut unknown = vec![
696 0, 0, 0, 1, 0x77, 0x77, 0, 0, 0, 0, 0, 20, ];
701 unknown.extend_from_slice(&[0, 0, 0, 1, 0, 5, 0, 7, 0, 0, 0, 32]);
702 unknown.extend_from_slice(&[0xaa; 8]);
703 let mut input = unknown.clone();
704 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
705
706 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
707 match iter.next().unwrap() {
708 DiagnosticEvent::ParseError {
709 error,
710 common_header,
711 raw_bytes,
712 ..
713 } => {
714 assert!(matches!(error, ParserError::ParseError(_)));
715 assert_eq!(common_header, None);
716 assert_eq!(raw_bytes.as_deref(), Some(unknown.as_slice()));
717 }
718 event => panic!("expected parse error event, got {event:?}"),
719 }
720 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record { .. })));
721 assert!(iter.next().is_none());
722 }
723
724 #[test]
725 fn diagnostic_iterator_terminates_after_truncated_unknown_entry_type() {
726 let input = vec![
729 0, 0, 0, 1, 0x77, 0x77, 0, 0, 0, 0, 0, 8, 1, 2, 3, 4,
734 ];
735
736 let mut iter = BgpkitParser::from_reader(Cursor::new(input.clone())).into_diagnostic_iter();
737 match iter.next().unwrap() {
738 DiagnosticEvent::ParseError { raw_bytes, .. } => {
739 assert_eq!(raw_bytes.as_deref(), Some(input.as_slice()));
740 }
741 event => panic!("expected parse error event, got {event:?}"),
742 }
743 assert!(iter.next().is_none());
744 }
745
746 #[test]
747 fn diagnostic_iterator_preserves_invalid_header_bytes() {
748 let invalid_header = vec![
749 0, 0, 0, 1, 0xff, 0xff, 0, 0, 0, 0, 0, 0, ];
754
755 match BgpkitParser::from_reader(Cursor::new(invalid_header.clone()))
756 .into_diagnostic_iter()
757 .next()
758 .unwrap()
759 {
760 DiagnosticEvent::ParseError {
761 common_header,
762 raw_bytes,
763 partial,
764 ..
765 } => {
766 assert_eq!(common_header, None);
767 assert_eq!(raw_bytes.as_deref(), Some(invalid_header.as_slice()));
768 let partial = partial.unwrap();
771 assert!(partial.find("mrt.header.type").is_some());
772 assert!(partial.find("mrt.body").is_none());
773 }
774 event => panic!("expected parse error event, got {event:?}"),
775 }
776 }
777
778 #[test]
779 fn diagnostic_iterator_ignores_filters() {
780 let wire = update_record(valid_attributes()).encode().unwrap().to_vec();
781 let parser = BgpkitParser::from_reader(Cursor::new(wire))
782 .add_filter("prefix", "198.51.100.0/24")
783 .unwrap();
784
785 assert!(matches!(
786 parser.into_diagnostic_iter().next(),
787 Some(DiagnosticEvent::Record { .. })
788 ));
789 }
790
791 #[test]
792 fn diagnostic_iterator_reports_wire_level_nlri_warnings_and_continues() {
793 let malformed_nlri = [0xc8, 0x01];
794 let invalid_wire = bgp4mp_update_wire(
795 &malformed_nlri,
796 &valid_update_attributes_wire(),
797 &malformed_nlri,
798 );
799 let mut input = invalid_wire.clone();
800 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
801
802 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
803 match iter.next().unwrap() {
804 DiagnosticEvent::Record { warnings, raw, .. } => {
805 assert!(warnings.iter().any(|warning| {
806 matches!(
807 warning,
808 BgpValidationWarning::MalformedNlri {
809 nlri_type: "withdrawn",
810 raw_bytes,
811 ..
812 } if raw_bytes == &malformed_nlri
813 )
814 }));
815 assert!(warnings.iter().any(|warning| {
816 matches!(
817 warning,
818 BgpValidationWarning::MalformedNlri {
819 nlri_type: "announced",
820 raw_bytes,
821 ..
822 } if raw_bytes == &malformed_nlri
823 )
824 }));
825 assert_eq!(raw.raw_bytes().as_ref(), invalid_wire.as_slice());
826 }
827 event => panic!("expected record event with warnings, got {event:?}"),
828 }
829 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record { .. })));
830 assert!(iter.next().is_none());
831 }
832
833 #[test]
834 fn diagnostic_iterator_reports_wire_level_attribute_warnings() {
835 let announced = [0x18, 10, 0, 0];
836 let mut flags_and_duplicate = valid_update_attributes_wire();
837 flags_and_duplicate[0] = 0x80;
838 flags_and_duplicate.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
839 assert_wire_validation(
840 bgp4mp_update_wire(&[], &flags_and_duplicate, &announced),
841 |warning| {
842 matches!(
843 warning,
844 BgpValidationWarning::AttributeFlagsError {
845 attr_type: AttrType::ORIGIN,
846 ..
847 }
848 )
849 },
850 );
851 assert_wire_validation(
852 bgp4mp_update_wire(&[], &flags_and_duplicate, &announced),
853 |warning| {
854 matches!(
855 warning,
856 BgpValidationWarning::DuplicateAttribute {
857 attr_type: AttrType::ORIGIN
858 }
859 )
860 },
861 );
862
863 let mut invalid_origin = valid_update_attributes_wire();
864 invalid_origin[3] = 3;
865 assert_wire_validation(
866 bgp4mp_update_wire(&[], &invalid_origin, &announced),
867 |warning| matches!(warning, BgpValidationWarning::MalformedAttributeList { .. }),
868 );
869
870 let malformed_as_path = vec![
871 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x02, 0x02, 0x01, 0x40, 0x03, 0x04, 192, 0, 2, 254, ];
875 assert_wire_validation(
876 bgp4mp_update_wire(&[], &malformed_as_path, &announced),
877 |warning| matches!(warning, BgpValidationWarning::MalformedAttributeList { .. }),
878 );
879
880 let invalid_next_hop = vec![
881 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x03, 192, 0, 2, ];
885 assert_wire_validation(
886 bgp4mp_update_wire(&[], &invalid_next_hop, &announced),
887 |warning| {
888 matches!(
889 warning,
890 BgpValidationWarning::AttributeLengthError {
891 attr_type: AttrType::NEXT_HOP,
892 ..
893 }
894 )
895 },
896 );
897 }
898
899 #[test]
900 fn diagnostic_iterator_collects_table_dump_batch_and_rib_warnings() {
901 let table_dump_messages = vec![
902 TableDumpMessage {
903 view_number: 0,
904 sequence_number: 1,
905 prefix: NetworkPrefix::from_str("192.0.2.0/24").unwrap(),
906 status: 1,
907 originated_time: 1,
908 peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
909 peer_asn: Asn::new_16bit(64512),
910 attributes: Attributes::default(),
911 },
912 TableDumpMessage {
913 view_number: 0,
914 sequence_number: 1,
915 prefix: NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
916 status: 1,
917 originated_time: 2,
918 peer_ip: IpAddr::from_str("192.0.2.2").unwrap(),
919 peer_asn: Asn::new_16bit(64513),
920 attributes: Attributes::default(),
921 },
922 ];
923 let table_dump_record = MrtRecord {
924 common_header: CommonHeader {
925 timestamp: 1,
926 microsecond_timestamp: None,
927 entry_type: EntryType::TABLE_DUMP,
928 entry_subtype: 1,
929 length: 0,
930 },
931 message: MrtMessage::TableDumpMessageBatch(table_dump_messages),
932 };
933 let table_dump_wire = table_dump_record.encode().unwrap().to_vec();
934 match BgpkitParser::from_reader(Cursor::new(table_dump_wire.clone()))
935 .into_diagnostic_iter()
936 .next()
937 .unwrap()
938 {
939 DiagnosticEvent::Record {
940 record, warnings, ..
941 } => {
942 assert!(matches!(
943 record.message,
944 MrtMessage::TableDumpMessageBatch(_)
945 ));
946 assert_eq!(warnings.len(), 6);
947 }
948 event => panic!("expected record event, got {event:?}"),
949 }
950
951 let rib_record = MrtRecord {
952 common_header: CommonHeader {
953 timestamp: 2,
954 microsecond_timestamp: None,
955 entry_type: EntryType::TABLE_DUMP_V2,
956 entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
957 length: 0,
958 },
959 message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
960 rib_type: TableDumpV2Type::RibIpv4Unicast,
961 sequence_number: 1,
962 prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
963 rib_entries: vec![RibEntry {
964 peer_index: 0,
965 originated_time: 1,
966 path_id: None,
967 attributes: Attributes::default(),
968 }],
969 })),
970 };
971 let rib_wire = rib_record.encode().unwrap().to_vec();
972 match BgpkitParser::from_reader(Cursor::new(rib_wire))
973 .into_diagnostic_iter()
974 .next()
975 .unwrap()
976 {
977 DiagnosticEvent::Record { warnings, .. } => {
978 assert_eq!(warnings.len(), 3);
979 }
980 event => panic!("expected record event, got {event:?}"),
981 }
982 }
983
984 #[test]
985 fn with_dissection_attaches_tree_and_spans() {
986 let announced = [0x18, 10, 0, 0];
989 let mut bad_flags = valid_update_attributes_wire();
990 bad_flags[0] = 0x80;
991 let wire = bgp4mp_update_wire(&[], &bad_flags, &announced);
992
993 let mut iter = BgpkitParser::from_reader(Cursor::new(wire))
994 .into_diagnostic_iter()
995 .with_dissection();
996 match iter.next().unwrap() {
997 DissectedDiagnosticEvent::Record { warnings, tree, .. } => {
998 assert!(tree.find("mrt.header.type").is_some());
1000 assert!(tree.find("mrt.bgp4mp.peer_asn").is_some());
1001 assert!(tree.find("bgp.header.type").is_some());
1002 assert!(tree.find("bgp.attr.1").is_some());
1003
1004 let origin = tree.find("bgp.attr.1").unwrap();
1005 assert_eq!(warnings.len(), 1);
1006 assert!(matches!(
1007 warnings[0].warning,
1008 BgpValidationWarning::AttributeFlagsError { .. }
1009 ));
1010 assert_eq!(warnings[0].span, origin.span());
1011 }
1012 event => panic!("expected dissected record event, got {event:?}"),
1013 }
1014 }
1015
1016 #[test]
1017 fn with_dissection_anchors_duplicate_to_second_occurrence() {
1018 let mut attrs = Vec::new();
1021 attrs.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]); attrs.extend_from_slice(&[0x80, 0x01, 0x01, 0x00]); let announced = [0x18, 10, 0, 0];
1024 let wire = bgp4mp_update_wire(&[], &attrs, &announced);
1025
1026 let mut iter = BgpkitParser::from_reader(Cursor::new(wire))
1027 .into_diagnostic_iter()
1028 .with_dissection();
1029 match iter.next().unwrap() {
1030 DissectedDiagnosticEvent::Record { warnings, tree, .. } => {
1031 let mut origins = Vec::new();
1032 tree.find_all("bgp.attr.1", &mut origins);
1033 assert_eq!(origins.len(), 2, "two ORIGIN attributes on the wire");
1034
1035 let duplicate = warnings
1036 .iter()
1037 .find(|w| matches!(w.warning, BgpValidationWarning::DuplicateAttribute { .. }))
1038 .expect("duplicate warning");
1039 assert_eq!(duplicate.span, origins[1].span());
1041
1042 let flags = warnings
1045 .iter()
1046 .find(|w| matches!(w.warning, BgpValidationWarning::AttributeFlagsError { .. }))
1047 .expect("flags warning");
1048 assert_eq!(flags.span, origins[1].span());
1049 }
1050 event => panic!("expected dissected record event, got {event:?}"),
1051 }
1052 }
1053
1054 #[test]
1055 fn with_dissection_passes_through_parse_error_partial() {
1056 let header = CommonHeader {
1059 timestamp: 3,
1060 microsecond_timestamp: None,
1061 entry_type: EntryType::TABLE_DUMP,
1062 entry_subtype: 0,
1063 length: 4,
1064 };
1065 let mut input = header.encode().to_vec();
1066 input.extend_from_slice(&[0xff; 4]);
1067 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
1068
1069 let mut iter = BgpkitParser::from_reader(Cursor::new(input))
1070 .into_diagnostic_iter()
1071 .with_dissection();
1072 match iter.next().unwrap() {
1073 DissectedDiagnosticEvent::ParseError { partial, .. } => {
1074 assert!(partial.is_some());
1075 }
1076 event => panic!("expected dissected parse error event, got {event:?}"),
1077 }
1078 assert!(matches!(
1079 iter.next(),
1080 Some(DissectedDiagnosticEvent::Record { .. })
1081 ));
1082 }
1083
1084 #[test]
1085 fn record_validation_warnings_reachable_from_crate_root() {
1086 let record = update_record(valid_attributes());
1090 assert!(crate::record_validation_warnings(&record).is_empty());
1091
1092 let mut attributes = Attributes::default();
1093 attributes.add_validation_warning(BgpValidationWarning::MissingWellKnownAttribute {
1094 attr_type: AttrType::ORIGIN,
1095 });
1096 let flagged = update_record(attributes);
1097 assert_eq!(crate::record_validation_warnings(&flagged).len(), 1);
1098 }
1099
1100 #[test]
1101 fn with_dissection_spans_nlri_warning() {
1102 let malformed_nlri = [0xc8, 0x01];
1103 let mut body = Vec::new();
1104 body.extend_from_slice(&(malformed_nlri.len() as u16).to_be_bytes());
1105 body.extend_from_slice(&malformed_nlri);
1106 body.extend_from_slice(&(valid_update_attributes_wire().len() as u16).to_be_bytes());
1107 body.extend_from_slice(&valid_update_attributes_wire());
1108 let wire = bgp4mp_message_wire(BgpMessageType::UPDATE, &body);
1109
1110 let mut iter = BgpkitParser::from_reader(Cursor::new(wire))
1111 .into_diagnostic_iter()
1112 .with_dissection();
1113 match iter.next().unwrap() {
1114 DissectedDiagnosticEvent::Record { warnings, tree, .. } => {
1115 let withdrawn = tree.find("bgp.update.withdrawn_routes").unwrap();
1116 let warning = warnings
1117 .iter()
1118 .find(|w| {
1119 matches!(
1120 w.warning,
1121 BgpValidationWarning::MalformedNlri {
1122 nlri_type: "withdrawn",
1123 ..
1124 }
1125 )
1126 })
1127 .expect("withdrawn NLRI warning");
1128 assert_eq!(warning.span, withdrawn.span());
1129 }
1130 event => panic!("expected dissected record event, got {event:?}"),
1131 }
1132 }
1133}