1use crate::models::*;
7use crate::ParserError;
8use crate::ParserError::ParseError;
9use log::error;
10use std::fmt::{Display, Formatter};
11use std::net::{IpAddr, Ipv4Addr};
12
13#[derive(Default, Debug, Clone)]
14pub struct Elementor {
15 pub peer_table: Option<PeerIndexTable>,
16}
17
18#[derive(Debug)]
20pub enum ElemError {
21 UnexpectedPeerIndexTable(Box<PeerIndexTable>),
24 MissingPeerTable,
27 UnsupportedRibGeneric,
29}
30
31impl Display for ElemError {
32 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
33 match self {
34 ElemError::UnexpectedPeerIndexTable(_) => {
35 write!(f, "unexpected PeerIndexTable record")
36 }
37 ElemError::MissingPeerTable => {
38 write!(
39 f,
40 "peer table not set; call set_peer_table or use with_peer_table first"
41 )
42 }
43 ElemError::UnsupportedRibGeneric => {
44 write!(f, "RibGenericEntries not yet supported")
45 }
46 }
47 }
48}
49
50impl std::error::Error for ElemError {}
51
52struct RelevantAttributes {
54 as_path: Option<AsPath>,
55 as4_path: Option<AsPath>,
56 origin: Option<Origin>,
57 next_hop: Option<IpAddr>,
58 local_pref: Option<u32>,
59 med: Option<u32>,
60 communities: Option<Vec<MetaCommunity>>,
61 atomic: bool,
62 aggregator: Option<(Asn, BgpIdentifier)>,
63 announced_prefixes: Vec<NetworkPrefix>,
64 withdrawn_prefixes: Vec<NetworkPrefix>,
65 only_to_customer: Option<Asn>,
66 unknown: Option<Vec<AttrRaw>>,
67 deprecated: Option<Vec<AttrRaw>>,
68}
69
70impl RelevantAttributes {
71 fn path(&mut self) -> Option<AsPath> {
73 match (self.as_path.take(), self.as4_path.take()) {
74 (None, None) => None,
75 (Some(v), None) => Some(v),
76 (None, Some(v)) => Some(v),
77 (Some(v1), Some(v2)) => Some(AsPath::merge_aspath_as4path(&v1, &v2)),
78 }
79 }
80}
81
82fn get_relevant_attributes(mut attributes: Attributes) -> RelevantAttributes {
87 let mut as_path = None;
88 let mut as4_path = None;
89 let mut origin = None;
90 let mut next_hop = None;
91 let mut local_pref = Some(0);
92 let mut med = Some(0);
93 let mut atomic = false;
94 let mut aggregator = None;
95 let mut announced_prefixes = Vec::new();
96 let mut announced_next_hop = None;
97 let mut withdrawn_prefixes = Vec::new();
98 let mut otc = None;
99 let mut unknown = vec![];
100 let mut deprecated = vec![];
101
102 let mut communities_vec: Vec<MetaCommunity> = vec![];
103
104 let take_raw = |t: &mut AttrRaw| AttrRaw {
105 code: t.code,
106 bytes: std::mem::take(&mut t.bytes),
107 };
108
109 for attr in attributes.inner.iter_mut() {
110 match &mut attr.value {
111 AttributeValue::Origin(v) => origin = Some(*v),
112 AttributeValue::AsPath(path) => as_path = Some(std::mem::take(path)),
113 AttributeValue::As4Path(path) => as4_path = Some(std::mem::take(path)),
114 AttributeValue::NextHop(v) => next_hop = Some(*v),
115 AttributeValue::MultiExitDiscriminator(v) => med = Some(*v),
116 AttributeValue::LocalPreference(v) => local_pref = Some(*v),
117 AttributeValue::AtomicAggregate => atomic = true,
118 AttributeValue::Communities(v) => {
119 communities_vec.extend(v.drain(..).map(MetaCommunity::Plain))
120 }
121 AttributeValue::ExtendedCommunities(v) => {
122 communities_vec.extend(v.drain(..).map(MetaCommunity::Extended))
123 }
124 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
125 communities_vec.extend(v.drain(..).map(MetaCommunity::Ipv6Extended))
126 }
127 AttributeValue::LargeCommunities(v) => {
128 communities_vec.extend(v.drain(..).map(MetaCommunity::Large))
129 }
130 AttributeValue::Aggregator { asn, id } | AttributeValue::As4Aggregator { asn, id } => {
131 aggregator = Some((*asn, *id))
132 }
133 AttributeValue::MpReachNlri(nlri) => {
134 announced_prefixes = std::mem::take(&mut nlri.prefixes);
135 announced_next_hop = nlri.next_hop.as_ref().map(NextHopAddress::global_addr);
136 }
137 AttributeValue::MpUnreachNlri(nlri) => {
138 withdrawn_prefixes = std::mem::take(&mut nlri.prefixes)
139 }
140 AttributeValue::OnlyToCustomer(o) => otc = Some(*o),
141
142 AttributeValue::Unknown(t) | AttributeValue::Raw(t) => {
143 unknown.push(take_raw(t));
144 }
145 AttributeValue::Deprecated(t) => {
146 deprecated.push(take_raw(t));
147 }
148
149 AttributeValue::OriginatorId(_)
150 | AttributeValue::Clusters(_)
151 | AttributeValue::Development(_)
152 | AttributeValue::LinkState(_)
153 | AttributeValue::TunnelEncapsulation(_)
154 | AttributeValue::TrafficEngineering(_)
155 | AttributeValue::Aigp(_)
156 | AttributeValue::DomainPath(_)
157 | AttributeValue::BfdDiscriminator(_)
158 | AttributeValue::BgpPrefixSid(_)
159 | AttributeValue::Bier(_)
160 | AttributeValue::Sfp(_)
161 | AttributeValue::AttrSet(_) => {}
162 };
163 }
164
165 let communities = match !communities_vec.is_empty() {
166 true => Some(communities_vec),
167 false => None,
168 };
169
170 RelevantAttributes {
171 as_path,
172 as4_path,
173 origin,
174 next_hop: next_hop.or(announced_next_hop),
176 local_pref,
177 med,
178 communities,
179 atomic,
180 aggregator,
181 announced_prefixes,
182 withdrawn_prefixes,
183 only_to_customer: otc,
184 unknown: if unknown.is_empty() {
185 None
186 } else {
187 Some(unknown)
188 },
189 deprecated: if deprecated.is_empty() {
190 None
191 } else {
192 Some(deprecated)
193 },
194 }
195}
196
197fn rib_entry_to_elem(prefix: NetworkPrefix, peer: &Peer, entry: RibEntry) -> BgpElem {
198 let mut attrs = get_relevant_attributes(entry.attributes);
199 let path = attrs.path();
200
201 let origin_asns = path
202 .as_ref()
203 .map(|as_path| as_path.iter_origins().collect());
204
205 BgpElem {
206 timestamp: entry.originated_time as f64,
207 elem_type: ElemType::ANNOUNCE,
208 peer_ip: peer.peer_ip,
209 peer_asn: peer.peer_asn,
210 peer_bgp_id: Some(peer.peer_bgp_id),
211 prefix,
212 next_hop: attrs.next_hop,
213 as_path: path,
214 origin: attrs.origin,
215 origin_asns,
216 local_pref: attrs.local_pref,
217 med: attrs.med,
218 communities: attrs.communities,
219 atomic: attrs.atomic,
220 aggr_asn: attrs.aggregator.map(|v| v.0),
221 aggr_ip: attrs.aggregator.map(|v| v.1),
222 only_to_customer: attrs.only_to_customer,
223 unknown: attrs.unknown,
224 deprecated: attrs.deprecated,
225 }
226}
227
228pub enum RecordElemIter<'a> {
234 #[doc(hidden)]
235 Empty,
236 #[doc(hidden)]
237 TableDump(Option<BgpElem>),
238 #[doc(hidden)]
239 TableDumpBatch(std::vec::IntoIter<TableDumpMessage>),
240 #[doc(hidden)]
241 RibAfi {
242 peer_table: &'a PeerIndexTable,
243 prefix: NetworkPrefix,
244 entries: std::vec::IntoIter<RibEntry>,
245 },
246 #[doc(hidden)]
247 Bgp4Mp(BgpUpdateElemIter),
248}
249
250fn next_rib_elem(
255 peer_table: &PeerIndexTable,
256 prefix: NetworkPrefix,
257 entries: &mut std::vec::IntoIter<RibEntry>,
258) -> Result<Option<BgpElem>, ()> {
259 let Some(entry) = entries.next() else {
260 return Ok(None);
261 };
262 let pid = entry.peer_index;
263 match peer_table.get_peer_by_id(&pid) {
264 Some(peer) => Ok(Some(rib_entry_to_elem(prefix, peer, entry))),
265 None => {
266 error!("peer ID {} not found in peer_index table", pid);
267 Err(())
268 }
269 }
270}
271
272pub(crate) enum PendingElems {
278 Empty,
279 TableDump(Option<BgpElem>),
280 TableDumpBatch(std::vec::IntoIter<TableDumpMessage>),
281 RibAfi {
282 prefix: NetworkPrefix,
283 entries: std::vec::IntoIter<RibEntry>,
284 },
285 Bgp4Mp(BgpUpdateElemIter),
286}
287
288impl PendingElems {
289 pub(crate) fn next_elem(&mut self, peer_table: Option<&PeerIndexTable>) -> Option<BgpElem> {
291 match self {
292 PendingElems::Empty => None,
293 PendingElems::TableDump(elem) => elem.take(),
294 PendingElems::TableDumpBatch(entries) => entries.next().map(table_dump_to_elem),
295 PendingElems::Bgp4Mp(iter) => iter.next(),
296 PendingElems::RibAfi { prefix, entries } => {
297 let next = peer_table
299 .ok_or(())
300 .and_then(|t| next_rib_elem(t, *prefix, entries));
301 next.unwrap_or_else(|()| {
302 *self = PendingElems::Empty;
303 None
304 })
305 }
306 }
307 }
308}
309
310impl Iterator for RecordElemIter<'_> {
311 type Item = BgpElem;
312
313 fn next(&mut self) -> Option<BgpElem> {
314 match self {
315 RecordElemIter::Empty => None,
316 RecordElemIter::TableDump(elem) => elem.take(),
317 RecordElemIter::TableDumpBatch(entries) => entries.next().map(table_dump_to_elem),
318 RecordElemIter::Bgp4Mp(iter) => iter.next(),
319 RecordElemIter::RibAfi {
320 peer_table,
321 prefix,
322 entries,
323 } => next_rib_elem(peer_table, *prefix, entries).unwrap_or_else(|()| {
324 *self = RecordElemIter::Empty;
325 None
326 }),
327 }
328 }
329
330 fn size_hint(&self) -> (usize, Option<usize>) {
331 match self {
332 RecordElemIter::Empty => (0, Some(0)),
333 RecordElemIter::TableDump(elem) => {
334 let n = elem.is_some() as usize;
335 (n, Some(n))
336 }
337 RecordElemIter::TableDumpBatch(entries) => {
338 let len = entries.len();
339 (len, Some(len))
340 }
341 RecordElemIter::Bgp4Mp(iter) => iter.size_hint(),
342 RecordElemIter::RibAfi { entries, .. } => {
343 let len = entries.len();
344 (len, Some(len))
345 }
346 }
347 }
348}
349
350pub struct BgpUpdateElemIter {
354 timestamp: f64,
355 peer_ip: IpAddr,
356 peer_asn: Asn,
357 peer_bgp_id: Option<BgpIdentifier>,
358 only_to_customer: Option<Asn>,
359 path: Option<AsPath>,
361 origin_asns: Option<Vec<Asn>>,
362 origin: Option<Origin>,
363 next_hop: Option<IpAddr>,
364 local_pref: Option<u32>,
365 med: Option<u32>,
366 communities: Option<Vec<MetaCommunity>>,
367 atomic: bool,
368 aggr_asn: Option<Asn>,
369 aggr_ip: Option<BgpIdentifier>,
370 unknown: Option<Vec<AttrRaw>>,
371 deprecated: Option<Vec<AttrRaw>>,
372 announced:
374 std::iter::Chain<std::vec::IntoIter<NetworkPrefix>, std::vec::IntoIter<NetworkPrefix>>,
375 withdrawn:
376 std::iter::Chain<std::vec::IntoIter<NetworkPrefix>, std::vec::IntoIter<NetworkPrefix>>,
377 in_withdrawn_phase: bool,
378}
379
380impl Iterator for BgpUpdateElemIter {
381 type Item = BgpElem;
382
383 fn next(&mut self) -> Option<BgpElem> {
384 if !self.in_withdrawn_phase {
385 if let Some(prefix) = self.announced.next() {
386 let last = self.announced.size_hint().1 == Some(0);
389 let (as_path, origin_asns, communities, unknown, deprecated) = if last {
390 (
391 self.path.take(),
392 self.origin_asns.take(),
393 self.communities.take(),
394 self.unknown.take(),
395 self.deprecated.take(),
396 )
397 } else {
398 (
399 self.path.clone(),
400 self.origin_asns.clone(),
401 self.communities.clone(),
402 self.unknown.clone(),
403 self.deprecated.clone(),
404 )
405 };
406 return Some(BgpElem {
407 timestamp: self.timestamp,
408 elem_type: ElemType::ANNOUNCE,
409 peer_ip: self.peer_ip,
410 peer_asn: self.peer_asn,
411 peer_bgp_id: self.peer_bgp_id,
412 prefix,
413 next_hop: self.next_hop,
414 as_path,
415 origin: self.origin,
416 origin_asns,
417 local_pref: self.local_pref,
418 med: self.med,
419 communities,
420 atomic: self.atomic,
421 aggr_asn: self.aggr_asn,
422 aggr_ip: self.aggr_ip,
423 only_to_customer: self.only_to_customer,
424 unknown,
425 deprecated,
426 });
427 }
428 self.in_withdrawn_phase = true;
429 }
430
431 self.withdrawn.next().map(|prefix| BgpElem {
432 timestamp: self.timestamp,
433 elem_type: ElemType::WITHDRAW,
434 peer_ip: self.peer_ip,
435 peer_asn: self.peer_asn,
436 peer_bgp_id: self.peer_bgp_id,
437 prefix,
438 next_hop: None,
439 as_path: None,
440 origin: None,
441 origin_asns: None,
442 local_pref: None,
443 med: None,
444 communities: None,
445 atomic: false,
446 aggr_asn: None,
447 aggr_ip: None,
448 only_to_customer: None,
449 unknown: None,
450 deprecated: None,
451 })
452 }
453
454 fn size_hint(&self) -> (usize, Option<usize>) {
455 let (ann_lo, ann_hi) = if self.in_withdrawn_phase {
456 (0, Some(0))
457 } else {
458 self.announced.size_hint()
459 };
460 let (wd_lo, wd_hi) = self.withdrawn.size_hint();
461 (ann_lo + wd_lo, ann_hi.and_then(|a| wd_hi.map(|w| a + w)))
462 }
463}
464
465impl Elementor {
466 pub fn new() -> Elementor {
467 Self::default()
468 }
469
470 pub fn set_peer_table(&mut self, record: MrtRecord) -> Result<(), ParserError> {
499 if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(p)) =
500 record.message
501 {
502 self.peer_table = Some(p);
503 Ok(())
504 } else {
505 Err(ParseError("peer_table is not a PeerIndexTable".to_string()))
506 }
507 }
508
509 pub fn with_peer_table(peer_table: PeerIndexTable) -> Elementor {
511 Elementor {
512 peer_table: Some(peer_table),
513 }
514 }
515
516 pub fn record_to_elems_iter(&self, record: MrtRecord) -> Result<RecordElemIter<'_>, ElemError> {
533 Ok(match self.pending_elems(record)? {
534 PendingElems::Empty => RecordElemIter::Empty,
535 PendingElems::TableDump(elem) => RecordElemIter::TableDump(elem),
536 PendingElems::TableDumpBatch(entries) => RecordElemIter::TableDumpBatch(entries),
537 PendingElems::RibAfi { prefix, entries } => RecordElemIter::RibAfi {
538 peer_table: self
539 .peer_table
540 .as_ref()
541 .ok_or(ElemError::MissingPeerTable)?,
542 prefix,
543 entries,
544 },
545 PendingElems::Bgp4Mp(iter) => RecordElemIter::Bgp4Mp(iter),
546 })
547 }
548
549 fn pending_elems(&self, record: MrtRecord) -> Result<PendingElems, ElemError> {
552 let timestamp = {
553 let t = record.common_header.timestamp;
554 if let Some(micro) = &record.common_header.microsecond_timestamp {
555 let m = (*micro as f64) / 1000000.0;
556 t as f64 + m
557 } else {
558 f64::from(t)
559 }
560 };
561
562 match record.message {
563 MrtMessage::TableDumpMessage(msg) => {
564 Ok(PendingElems::TableDump(Some(table_dump_to_elem(msg))))
565 }
566 MrtMessage::TableDumpMessageBatch(messages) => {
567 Ok(PendingElems::TableDumpBatch(messages.into_iter()))
568 }
569
570 MrtMessage::TableDumpV2Message(msg) => match msg {
571 TableDumpV2Message::PeerIndexTable(p) => {
572 Err(ElemError::UnexpectedPeerIndexTable(Box::new(p)))
573 }
574 TableDumpV2Message::RibAfi(t) => {
575 if self.peer_table.is_none() {
576 return Err(ElemError::MissingPeerTable);
577 }
578 Ok(PendingElems::RibAfi {
579 prefix: t.prefix,
580 entries: t.rib_entries.into_iter(),
581 })
582 }
583 TableDumpV2Message::RibGeneric(_) => Err(ElemError::UnsupportedRibGeneric),
584 TableDumpV2Message::GeoPeerTable(_) => Ok(PendingElems::Empty),
585 },
586
587 MrtMessage::Bgp4Mp(msg) => match msg {
588 Bgp4MpEnum::StateChange(_) => Ok(PendingElems::Empty),
589 Bgp4MpEnum::Message(v) => {
590 match Elementor::bgp_to_elems_iter(
591 v.bgp_message,
592 timestamp,
593 &v.peer_ip,
594 &v.peer_asn,
595 ) {
596 Some(iter) => Ok(PendingElems::Bgp4Mp(iter)),
597 None => Ok(PendingElems::Empty),
598 }
599 }
600 },
601 MrtMessage::LegacyBgp(msg) => match msg {
602 LegacyBgp::StateChange(_) => Ok(PendingElems::Empty),
603 LegacyBgp::Message(message) => match Elementor::bgp_to_elems_iter(
604 message.bgp_message,
605 timestamp,
606 &message.peer_ip,
607 &message.peer_asn,
608 ) {
609 Some(iter) => Ok(PendingElems::Bgp4Mp(iter)),
610 None => Ok(PendingElems::Empty),
611 },
612 },
613 }
614 }
615
616 pub(crate) fn ingest(&mut self, record: MrtRecord) -> PendingElems {
621 match record.message {
622 MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_)) => {
623 if let Err(e) = self.set_peer_table(record) {
624 error!("{}", e);
625 }
626 PendingElems::Empty
627 }
628 _ => self.pending_elems(record).unwrap_or_else(|e| {
629 error!("{}", e);
630 PendingElems::Empty
631 }),
632 }
633 }
634
635 pub fn bgp_to_elems(
640 msg: BgpMessage,
641 timestamp: f64,
642 peer_ip: &IpAddr,
643 peer_asn: &Asn,
644 ) -> Vec<BgpElem> {
645 Elementor::bgp_to_elems_iter(msg, timestamp, peer_ip, peer_asn)
646 .map(|iter| iter.collect())
647 .unwrap_or_default()
648 }
649
650 pub fn bgp_to_elems_iter(
654 msg: BgpMessage,
655 timestamp: f64,
656 peer_ip: &IpAddr,
657 peer_asn: &Asn,
658 ) -> Option<BgpUpdateElemIter> {
659 match msg {
660 BgpMessage::Update(msg) => Some(Elementor::bgp_update_to_elems_iter(
661 msg, timestamp, peer_ip, peer_asn,
662 )),
663 BgpMessage::Open(_)
664 | BgpMessage::Notification(_)
665 | BgpMessage::KeepAlive
666 | BgpMessage::RouteRefresh(_) => None,
667 }
668 }
669
670 pub fn bgp_update_to_elems(
672 msg: BgpUpdateMessage,
673 timestamp: f64,
674 peer_ip: &IpAddr,
675 peer_asn: &Asn,
676 ) -> Vec<BgpElem> {
677 Elementor::bgp_update_to_elems_iter(msg, timestamp, peer_ip, peer_asn).collect()
678 }
679
680 pub fn bgp_update_to_elems_iter(
683 msg: BgpUpdateMessage,
684 timestamp: f64,
685 peer_ip: &IpAddr,
686 peer_asn: &Asn,
687 ) -> BgpUpdateElemIter {
688 let mut attrs = get_relevant_attributes(msg.attributes);
689 let path = attrs.path();
690
691 let origin_asns = path
692 .as_ref()
693 .map(|as_path| as_path.iter_origins().collect());
694
695 BgpUpdateElemIter {
696 timestamp,
697 peer_ip: *peer_ip,
698 peer_asn: *peer_asn,
699 peer_bgp_id: None,
700 only_to_customer: attrs.only_to_customer,
701 path,
702 origin_asns,
703 origin: attrs.origin,
704 next_hop: attrs.next_hop,
705 local_pref: attrs.local_pref,
706 med: attrs.med,
707 communities: attrs.communities,
708 atomic: attrs.atomic,
709 aggr_asn: attrs.aggregator.map(|v| v.0),
710 aggr_ip: attrs.aggregator.map(|v| v.1),
711 unknown: attrs.unknown,
712 deprecated: attrs.deprecated,
713 announced: msg
714 .announced_prefixes
715 .into_iter()
716 .chain(attrs.announced_prefixes),
717 withdrawn: msg
718 .withdrawn_prefixes
719 .into_iter()
720 .chain(attrs.withdrawn_prefixes),
721 in_withdrawn_phase: false,
722 }
723 }
724
725 pub fn record_to_elems(&mut self, record: MrtRecord) -> Vec<BgpElem> {
733 match record.message {
734 MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_)) => {
735 if let Err(e) = self.set_peer_table(record) {
736 error!("{}", e);
737 }
738 vec![]
739 }
740 _ => match self.record_to_elems_iter(record) {
741 Ok(iter) => iter.collect(),
742 Err(e) => {
743 error!("{}", e);
744 vec![]
745 }
746 },
747 }
748 }
749}
750
751fn table_dump_to_elem(msg: TableDumpMessage) -> BgpElem {
752 let attrs = get_relevant_attributes(msg.attributes);
753
754 let origin_asns = attrs
755 .as_path
756 .as_ref()
757 .map(|as_path| as_path.iter_origins().collect());
758
759 BgpElem {
760 timestamp: msg.originated_time as f64,
761 elem_type: ElemType::ANNOUNCE,
762 peer_ip: msg.peer_ip,
763 peer_asn: msg.peer_asn,
764 peer_bgp_id: None,
765 prefix: msg.prefix,
766 next_hop: attrs.next_hop,
767 as_path: attrs.as_path,
768 origin: attrs.origin,
769 origin_asns,
770 local_pref: attrs.local_pref,
771 med: attrs.med,
772 communities: attrs.communities,
773 atomic: attrs.atomic,
774 aggr_asn: attrs.aggregator.map(|v| v.0),
775 aggr_ip: attrs.aggregator.map(|v| v.1),
776 only_to_customer: attrs.only_to_customer,
777 unknown: attrs.unknown,
778 deprecated: attrs.deprecated,
779 }
780}
781
782#[inline(always)]
783pub fn option_to_string<T>(o: &Option<T>) -> String
784where
785 T: Display,
786{
787 if let Some(v) = o {
788 v.to_string()
789 } else {
790 String::new()
791 }
792}
793
794impl From<&BgpElem> for Attributes {
795 fn from(value: &BgpElem) -> Self {
796 let mut values = Vec::<AttributeValue>::new();
797 let mut attributes = Attributes::default();
798 let prefix = value.prefix;
799
800 if value.elem_type == ElemType::WITHDRAW {
801 values.push(AttributeValue::MpUnreachNlri(Nlri::new_unreachable(prefix)));
802 attributes.extend(values);
803 return attributes;
804 }
805
806 values.push(AttributeValue::MpReachNlri(Nlri::new_reachable(
807 prefix,
808 value.next_hop,
809 )));
810
811 if let Some(v) = value.next_hop {
812 values.push(AttributeValue::NextHop(v));
813 }
814
815 if let Some(v) = value.as_path.as_ref() {
816 values.push(AttributeValue::AsPath(v.clone()));
820 }
821
822 if let Some(v) = value.origin {
823 values.push(AttributeValue::Origin(v));
824 }
825
826 if let Some(v) = value.local_pref {
827 values.push(AttributeValue::LocalPreference(v));
828 }
829
830 if let Some(v) = value.med {
831 values.push(AttributeValue::MultiExitDiscriminator(v));
832 }
833
834 if let Some(v) = value.communities.as_ref() {
835 let mut communites = vec![];
836 let mut extended_communities = vec![];
837 let mut ipv6_extended_communities = vec![];
838 let mut large_communities = vec![];
839 for c in v {
840 match c {
841 MetaCommunity::Plain(v) => communites.push(*v),
842 MetaCommunity::Extended(v) => extended_communities.push(*v),
843 MetaCommunity::Large(v) => large_communities.push(*v),
844 MetaCommunity::Ipv6Extended(v) => ipv6_extended_communities.push(*v),
845 }
846 }
847 if !communites.is_empty() {
848 values.push(AttributeValue::Communities(communites));
849 }
850 if !extended_communities.is_empty() {
851 values.push(AttributeValue::ExtendedCommunities(extended_communities));
852 }
853 if !large_communities.is_empty() {
854 values.push(AttributeValue::LargeCommunities(large_communities));
855 }
856 if !ipv6_extended_communities.is_empty() {
857 values.push(AttributeValue::Ipv6AddressSpecificExtendedCommunities(
858 ipv6_extended_communities,
859 ));
860 }
861 }
862
863 if let Some(v) = value.aggr_asn {
864 let aggregator_id = match value.aggr_ip {
865 Some(v) => v,
866 None => Ipv4Addr::UNSPECIFIED,
867 };
868 values.push(AttributeValue::Aggregator {
869 asn: v,
870 id: aggregator_id,
871 });
872 }
873
874 if let Some(v) = value.only_to_customer {
875 values.push(AttributeValue::OnlyToCustomer(v));
876 }
877
878 if let Some(v) = value.unknown.as_ref() {
879 for t in v {
880 values.push(AttributeValue::Unknown(t.clone()));
881 }
882 }
883
884 if let Some(v) = value.deprecated.as_ref() {
885 for t in v {
886 values.push(AttributeValue::Deprecated(t.clone()));
887 }
888 }
889
890 attributes.extend(values);
891 attributes
892 }
893}
894
895#[cfg(test)]
896mod tests {
897 use super::*;
898 use crate::BgpkitParser;
899 use bytes::Bytes;
900 use std::net::{Ipv4Addr, Ipv6Addr};
901 use std::str::FromStr;
902
903 #[test]
904 fn test_option_to_string() {
905 let o1 = Some(1);
906 let o2: Option<u32> = None;
907 assert_eq!(option_to_string(&o1), "1");
908 assert_eq!(option_to_string(&o2), "");
909 }
910
911 #[test]
912 fn test_record_to_elems() {
913 let url_table_dump_v1 = "https://data.ris.ripe.net/rrc00/2003.01/bview.20030101.0000.gz";
914 let url_table_dump_v2 = "https://data.ris.ripe.net/rrc00/2023.01/bview.20230101.0000.gz";
915 let url_bgp4mp = "https://data.ris.ripe.net/rrc00/2021.10/updates.20211001.0000.gz";
916
917 let mut elementor = Elementor::new();
918 let parser = BgpkitParser::new(url_table_dump_v1).unwrap();
919 let mut record_iter = parser.into_record_iter();
920 let record = record_iter.next().unwrap();
921 let elems = elementor.record_to_elems(record);
922 assert_eq!(elems.len(), 1);
923
924 let parser = BgpkitParser::new(url_table_dump_v2).unwrap();
925 let mut record_iter = parser.into_record_iter();
926 let peer_index_table = record_iter.next().unwrap();
927 let _elems = elementor.record_to_elems(peer_index_table);
928 let record = record_iter.next().unwrap();
929 let elems = elementor.record_to_elems(record);
930 assert!(!elems.is_empty());
931
932 let parser = BgpkitParser::new(url_bgp4mp).unwrap();
933 let mut record_iter = parser.into_record_iter();
934 let record = record_iter.next().unwrap();
935 let elems = elementor.record_to_elems(record);
936 assert!(!elems.is_empty());
937 }
938
939 #[test]
940 fn test_attributes_from_bgp_elem() {
941 let mut elem = BgpElem {
942 timestamp: 0.0,
943 elem_type: ElemType::ANNOUNCE,
944 peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
945 peer_asn: Asn::new_32bit(65000),
946 peer_bgp_id: None,
947 prefix: NetworkPrefix::from_str("10.0.1.0/24").unwrap(),
948 next_hop: Some(IpAddr::from_str("10.0.0.2").unwrap()),
949 as_path: Some(AsPath::from_sequence([65000, 65001, 65002])),
950 origin: Some(Origin::EGP),
951 origin_asns: Some(vec![Asn::new_32bit(65000)]),
952 local_pref: Some(100),
953 med: Some(200),
954 communities: Some(vec![
955 MetaCommunity::Plain(Community::NoAdvertise),
956 MetaCommunity::Extended(ExtendedCommunity::Raw([0, 0, 0, 0, 0, 0, 0, 0])),
957 MetaCommunity::Large(LargeCommunity {
958 global_admin: 0,
959 local_data: [0, 0],
960 }),
961 MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
962 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
963 subtype: 0,
964 global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
965 local_admin: [0, 0],
966 }),
967 ]),
968 atomic: false,
969 aggr_asn: Some(Asn::new_32bit(65000)),
970 aggr_ip: Some(Ipv4Addr::from_str("10.2.0.0").unwrap()),
971 only_to_customer: Some(Asn::new_32bit(65000)),
972 unknown: Some(vec![AttrRaw {
973 code: AttrType::RESERVED.into(),
974 bytes: Bytes::new(),
975 }]),
976 deprecated: Some(vec![AttrRaw {
977 code: AttrType::RESERVED.into(),
978 bytes: Bytes::new(),
979 }]),
980 };
981
982 let _attributes = Attributes::from(&elem);
983 elem.elem_type = ElemType::WITHDRAW;
984 let _attributes = Attributes::from(&elem);
985 }
986
987 #[test]
988 fn test_get_relevant_attributes() {
989 let attributes = vec![
990 AttributeValue::Origin(Origin::IGP),
991 AttributeValue::As4Path(AsPath::from_sequence([65000, 65001, 65002])),
992 AttributeValue::NextHop(IpAddr::from_str("10.0.0.1").unwrap()),
993 AttributeValue::MultiExitDiscriminator(100),
994 AttributeValue::LocalPreference(200),
995 AttributeValue::AtomicAggregate,
996 AttributeValue::Aggregator {
997 asn: Asn::new_32bit(65000),
998 id: Ipv4Addr::from_str("10.0.0.1").unwrap(),
999 },
1000 AttributeValue::Communities(vec![Community::NoExport]),
1001 AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([
1002 0, 0, 0, 0, 0, 0, 0, 0,
1003 ])]),
1004 AttributeValue::LargeCommunities(vec![LargeCommunity {
1005 global_admin: 0,
1006 local_data: [0, 0],
1007 }]),
1008 AttributeValue::Ipv6AddressSpecificExtendedCommunities(vec![Ipv6AddrExtCommunity {
1009 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
1010 subtype: 0,
1011 global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
1012 local_admin: [0, 0],
1013 }]),
1014 AttributeValue::MpReachNlri(Nlri::new_reachable(
1015 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1016 Some(IpAddr::from_str("10.0.0.1").unwrap()),
1017 )),
1018 AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1019 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1020 )),
1021 AttributeValue::OnlyToCustomer(Asn::new_32bit(65000)),
1022 AttributeValue::Unknown(AttrRaw {
1023 code: AttrType::RESERVED.into(),
1024 bytes: Bytes::new(),
1025 }),
1026 AttributeValue::Deprecated(AttrRaw {
1027 code: AttrType::RESERVED.into(),
1028 bytes: Bytes::new(),
1029 }),
1030 ]
1031 .into_iter()
1032 .map(Attribute::from)
1033 .collect::<Vec<Attribute>>();
1034
1035 let attributes = Attributes::from(attributes);
1036
1037 let attrs = get_relevant_attributes(attributes);
1038
1039 assert_eq!(attrs.origin, Some(Origin::IGP));
1040 assert_eq!(
1041 attrs.as4_path,
1042 Some(AsPath::from_sequence([65000, 65001, 65002]))
1043 );
1044 assert_eq!(attrs.next_hop, Some(IpAddr::from_str("10.0.0.1").unwrap()));
1045 assert_eq!((attrs.med, attrs.local_pref), (Some(100), Some(200)));
1046 assert!(attrs.atomic);
1047 let communities = attrs.communities.unwrap();
1049 assert_eq!(communities.len(), 4);
1050 assert_eq!(communities[0], MetaCommunity::Plain(Community::NoExport));
1051 let prefix = NetworkPrefix::from_str("10.0.0.0/24").unwrap();
1052 assert_eq!(attrs.announced_prefixes, vec![prefix]);
1053 assert_eq!(attrs.withdrawn_prefixes, vec![prefix]);
1054 assert_eq!(attrs.only_to_customer, Some(Asn::new_32bit(65000)));
1055 assert_eq!(attrs.unknown.map(|v| v.len()), Some(1));
1056 assert_eq!(attrs.deprecated.map(|v| v.len()), Some(1));
1057 }
1058
1059 #[test]
1060 fn test_next_hop_from_nlri() {
1061 let attributes = vec![AttributeValue::NextHop(
1062 IpAddr::from_str("10.0.0.1").unwrap(),
1063 )]
1064 .into_iter()
1065 .map(Attribute::from)
1066 .collect::<Vec<Attribute>>();
1067
1068 let attributes = Attributes::from(attributes);
1069
1070 let next_hop = get_relevant_attributes(attributes).next_hop;
1071
1072 assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.1").unwrap()));
1073
1074 let attributes = vec![AttributeValue::MpReachNlri(Nlri::new_reachable(
1075 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1076 Some(IpAddr::from_str("10.0.0.2").unwrap()),
1077 ))]
1078 .into_iter()
1079 .map(Attribute::from)
1080 .collect::<Vec<Attribute>>();
1081
1082 let attributes = Attributes::from(attributes);
1083
1084 let next_hop = get_relevant_attributes(attributes).next_hop;
1085
1086 assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.2").unwrap()));
1087 }
1088
1089 #[test]
1090 fn test_record_to_elems_iter_equivalence_tabledumpv2_small() {
1091 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1093
1094 let mut elementor = Elementor::new();
1095 let parser = BgpkitParser::new(url).unwrap();
1096 let mut record_iter = parser.into_record_iter();
1097
1098 let peer_index_table = record_iter.next().unwrap();
1100 let _ = elementor.record_to_elems(peer_index_table);
1101
1102 let record = record_iter.next().unwrap();
1104 let elems_vec = elementor.record_to_elems(record.clone());
1105 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1106 assert_eq!(elems_vec, elems_iter);
1107 assert!(!elems_vec.is_empty());
1108 }
1109
1110 #[test]
1111 fn test_record_to_elems_iter_equivalence_bgp4mp() {
1112 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1113
1114 let mut elementor = Elementor::new();
1115 let parser = BgpkitParser::new(url).unwrap();
1116 let mut record_iter = parser.into_record_iter();
1117 let record = record_iter.next().unwrap();
1118
1119 let elems_vec = elementor.record_to_elems(record.clone());
1120 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1121 assert_eq!(elems_vec, elems_iter);
1122 assert!(!elems_vec.is_empty());
1123 }
1124
1125 #[test]
1126 #[ignore = "requires large RIB file download"]
1127 fn test_record_to_elems_iter_equivalence_tabledumpv2() {
1128 let url = "https://data.ris.ripe.net/rrc00/2023.01/bview.20230101.0000.gz";
1129
1130 let mut elementor = Elementor::new();
1131 let parser = BgpkitParser::new(url).unwrap();
1132 let mut record_iter = parser.into_record_iter();
1133
1134 let peer_index_table = record_iter.next().unwrap();
1135 let _ = elementor.record_to_elems(peer_index_table);
1136
1137 let record = record_iter.next().unwrap();
1138 let elems_vec = elementor.record_to_elems(record.clone());
1139 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1140 assert_eq!(elems_vec, elems_iter);
1141 assert!(!elems_vec.is_empty());
1142 }
1143
1144 #[test]
1145 fn test_record_to_elems_iter_tabledumpv2_with_peer_table() {
1146 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1147
1148 let parser = BgpkitParser::new(url).unwrap();
1149 let mut record_iter = parser.into_record_iter();
1150
1151 let peer_index_table = record_iter.next().unwrap();
1152 let mut elementor = Elementor::with_peer_table(
1153 if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(pit)) =
1154 peer_index_table.message
1155 {
1156 pit
1157 } else {
1158 panic!("Expected PeerIndexTable");
1159 },
1160 );
1161
1162 let record = record_iter.next().unwrap();
1163 let elems_vec = elementor.record_to_elems(record.clone());
1164 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1165 assert_eq!(elems_vec, elems_iter);
1166 assert!(!elems_vec.is_empty());
1167 }
1168
1169 #[test]
1170 fn test_record_to_elems_iter_error_unexpected_peer_index_table() {
1171 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1172
1173 let elementor = Elementor::new();
1174 let parser = BgpkitParser::new(url).unwrap();
1175 let mut record_iter = parser.into_record_iter();
1176 let record = record_iter.next().unwrap();
1177
1178 let result = elementor.record_to_elems_iter(record);
1179 assert!(matches!(
1180 result,
1181 Err(ElemError::UnexpectedPeerIndexTable(_))
1182 ));
1183 }
1184
1185 #[test]
1186 fn test_record_to_elems_iter_error_missing_peer_table() {
1187 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1189
1190 let elementor = Elementor::new();
1191 let parser = BgpkitParser::new(url).unwrap();
1192 let mut record_iter = parser.into_record_iter();
1193
1194 let _peer_index_table = record_iter.next().unwrap();
1197
1198 let record = record_iter.next().unwrap();
1200 let result = elementor.record_to_elems_iter(record);
1201 assert!(matches!(result, Err(ElemError::MissingPeerTable)));
1202 }
1203
1204 #[test]
1205 fn test_bgp_to_elems_iter_equivalence() {
1206 let timestamp = 0.0;
1207 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1208 let peer_asn = Asn::new_32bit(65000);
1209
1210 let attributes = vec![
1211 AttributeValue::Origin(Origin::IGP),
1212 AttributeValue::AsPath(AsPath::from_sequence([65000, 65001, 65002])),
1213 AttributeValue::NextHop(peer_ip),
1214 ]
1215 .into_iter()
1216 .map(Attribute::from)
1217 .collect::<Vec<Attribute>>();
1218 let attributes = Attributes::from(attributes);
1219
1220 let announced_prefixes = vec![NetworkPrefix::from_str("10.0.0.0/24").unwrap()];
1221
1222 let bgp_message = BgpMessage::Update(BgpUpdateMessage {
1223 attributes,
1224 announced_prefixes,
1225 withdrawn_prefixes: vec![],
1226 });
1227
1228 let elems_vec =
1229 Elementor::bgp_to_elems(bgp_message.clone(), timestamp, &peer_ip, &peer_asn);
1230 let elems_iter: Vec<BgpElem> =
1231 Elementor::bgp_to_elems_iter(bgp_message, timestamp, &peer_ip, &peer_asn)
1232 .unwrap()
1233 .collect();
1234 assert_eq!(elems_vec, elems_iter);
1235 assert_eq!(elems_vec.len(), 1);
1236 }
1237
1238 #[test]
1239 fn test_bgp_to_elems_iter_non_update_messages() {
1240 use std::net::Ipv4Addr;
1241
1242 let timestamp = 0.0;
1243 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1244 let peer_asn = Asn::new_32bit(65000);
1245
1246 let open_msg = BgpOpenMessage {
1247 version: 4,
1248 asn: Asn::new_32bit(1),
1249 hold_time: 180,
1250 bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
1251 extended_length: false,
1252 opt_params: vec![],
1253 };
1254 assert!(Elementor::bgp_to_elems_iter(
1255 BgpMessage::Open(open_msg),
1256 timestamp,
1257 &peer_ip,
1258 &peer_asn
1259 )
1260 .is_none());
1261
1262 let notification_msg = BgpNotificationMessage {
1263 error: BgpError::Unknown(0, 0),
1264 data: vec![],
1265 };
1266 assert!(Elementor::bgp_to_elems_iter(
1267 BgpMessage::Notification(notification_msg),
1268 timestamp,
1269 &peer_ip,
1270 &peer_asn
1271 )
1272 .is_none());
1273
1274 assert!(Elementor::bgp_to_elems_iter(
1275 BgpMessage::KeepAlive,
1276 timestamp,
1277 &peer_ip,
1278 &peer_asn
1279 )
1280 .is_none());
1281 }
1282
1283 #[test]
1284 fn test_bgp_update_to_elems_iter_equivalence() {
1285 let timestamp = 0.0;
1286 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1287 let peer_asn = Asn::new_32bit(65000);
1288
1289 let attributes = vec![
1290 AttributeValue::Origin(Origin::IGP),
1291 AttributeValue::AsPath(AsPath::from_sequence([65000, 65001, 65002])),
1292 AttributeValue::NextHop(peer_ip),
1293 ]
1294 .into_iter()
1295 .map(Attribute::from)
1296 .collect::<Vec<Attribute>>();
1297 let attributes = Attributes::from(attributes);
1298
1299 let announced_prefixes = vec![NetworkPrefix::from_str("10.0.0.0/24").unwrap()];
1300 let withdrawn_prefixes = vec![NetworkPrefix::from_str("10.0.1.0/24").unwrap()];
1301
1302 let update = BgpUpdateMessage {
1303 attributes,
1304 announced_prefixes,
1305 withdrawn_prefixes,
1306 };
1307
1308 let elems_vec =
1309 Elementor::bgp_update_to_elems(update.clone(), timestamp, &peer_ip, &peer_asn);
1310 let elems_iter: Vec<BgpElem> =
1311 Elementor::bgp_update_to_elems_iter(update, timestamp, &peer_ip, &peer_asn).collect();
1312 assert_eq!(elems_vec, elems_iter);
1313 assert_eq!(elems_vec.len(), 2);
1314 }
1315
1316 #[test]
1317 fn test_bgp_update_to_elems_iter_shares_attributes_across_prefixes() {
1318 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1319 let peer_asn = Asn::new_32bit(65000);
1320 let as_path = AsPath::from_sequence([65000, 65001, 65002]);
1321 let unknown = AttrRaw {
1322 code: 254,
1323 bytes: Bytes::from_static(&[1, 2]),
1324 };
1325
1326 let attributes = vec![
1327 AttributeValue::AsPath(as_path.clone()),
1328 AttributeValue::Communities(vec![Community::NoExport]),
1329 AttributeValue::Unknown(unknown.clone()),
1330 AttributeValue::MpReachNlri(Nlri::new_reachable(
1331 NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1332 Some(IpAddr::from_str("2001:db8::1").unwrap()),
1333 )),
1334 ]
1335 .into_iter()
1336 .map(Attribute::from)
1337 .collect::<Vec<Attribute>>();
1338
1339 let update = BgpUpdateMessage {
1340 attributes: Attributes::from(attributes),
1341 announced_prefixes: vec![
1342 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1343 NetworkPrefix::from_str("10.0.2.0/24").unwrap(),
1344 ],
1345 withdrawn_prefixes: vec![NetworkPrefix::from_str("10.0.1.0/24").unwrap()],
1346 };
1347
1348 let mut iter = Elementor::bgp_update_to_elems_iter(update, 0.0, &peer_ip, &peer_asn);
1349 assert_eq!(iter.size_hint(), (4, Some(4)));
1350 let elems: Vec<BgpElem> = iter.by_ref().collect();
1351 assert_eq!(iter.size_hint(), (0, Some(0)));
1352
1353 let announced: Vec<&BgpElem> = elems.iter().filter(|e| e.elem_type.is_announce()).collect();
1356 assert_eq!(announced.len(), 3);
1357 assert_eq!(
1358 announced[2].prefix,
1359 NetworkPrefix::from_str("2001:db8::/32").unwrap()
1360 );
1361 for elem in &announced {
1362 assert_eq!(elem.as_path.as_ref(), Some(&as_path));
1363 assert_eq!(elem.origin_asns, Some(vec![Asn::new_32bit(65002)]));
1364 assert_eq!(
1365 elem.communities,
1366 Some(vec![MetaCommunity::Plain(Community::NoExport)])
1367 );
1368 assert_eq!(elem.unknown, Some(vec![unknown.clone()]));
1369 }
1370
1371 let withdrawn = elems.last().unwrap();
1372 assert_eq!(withdrawn.elem_type, ElemType::WITHDRAW);
1373 assert_eq!(withdrawn.as_path, None);
1374 assert_eq!(withdrawn.communities, None);
1375 }
1376
1377 #[test]
1378 fn test_record_elem_iter_size_hint() {
1379 use std::collections::HashMap;
1380
1381 let peer_table = PeerIndexTable {
1382 collector_bgp_id: BgpIdentifier::from_str("10.0.0.1").unwrap(),
1383 view_name: "".to_string(),
1384 id_peer_map: HashMap::new(),
1385 peer_ip_id_map: HashMap::new(),
1386 };
1387
1388 let entries: Vec<RibEntry> = vec![];
1389 let iter = RecordElemIter::RibAfi {
1390 peer_table: &peer_table,
1391 prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1392 entries: entries.into_iter(),
1393 };
1394 assert_eq!(iter.size_hint(), (0, Some(0)));
1395
1396 let entries: Vec<RibEntry> = (0..5)
1397 .map(|i| RibEntry {
1398 peer_index: i as u16,
1399 originated_time: 0,
1400 path_id: None,
1401 attributes: Attributes::default(),
1402 })
1403 .collect();
1404 let iter = RecordElemIter::RibAfi {
1405 peer_table: &peer_table,
1406 prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1407 entries: entries.into_iter(),
1408 };
1409 assert_eq!(iter.size_hint(), (5, Some(5)));
1410 }
1411
1412 #[test]
1413 fn test_pending_elems_rib_entries() {
1414 use std::collections::HashMap;
1415
1416 let peer = Peer::new(
1417 BgpIdentifier::from_str("10.0.0.2").unwrap(),
1418 IpAddr::from_str("10.0.0.2").unwrap(),
1419 Asn::new_32bit(65002),
1420 );
1421 let peer_table = PeerIndexTable {
1422 collector_bgp_id: BgpIdentifier::from_str("10.0.0.1").unwrap(),
1423 view_name: "".to_string(),
1424 id_peer_map: HashMap::from([(0, peer)]),
1425 peer_ip_id_map: HashMap::new(),
1426 };
1427 let prefix = NetworkPrefix::from_str("10.0.0.0/24").unwrap();
1428 let pending = || PendingElems::RibAfi {
1430 prefix,
1431 entries: [0, 0, 7, 0]
1432 .map(|peer_index| RibEntry {
1433 peer_index,
1434 originated_time: 0,
1435 path_id: None,
1436 attributes: Attributes::default(),
1437 })
1438 .to_vec()
1439 .into_iter(),
1440 };
1441
1442 let mut elems = pending();
1444 let first = elems.next_elem(Some(&peer_table)).unwrap();
1445 assert_eq!(first.prefix, prefix);
1446 assert_eq!(first.peer_asn, Asn::new_32bit(65002));
1447 assert!(elems.next_elem(Some(&peer_table)).is_some());
1448 assert!(elems.next_elem(Some(&peer_table)).is_none());
1449 assert!(matches!(elems, PendingElems::Empty));
1450
1451 let mut elems = pending();
1453 assert!(elems.next_elem(None).is_none());
1454 assert!(matches!(elems, PendingElems::Empty));
1455 }
1456}