1use std::fmt;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4use bytes::Bytes;
5
6use crate::capability::{Afi, Safi};
7use crate::constants::{as_path_segment, attr_flags, attr_type};
8use crate::error::DecodeError;
9use crate::nlri::{NlriEntry, Prefix};
10use crate::notification::update_subcode;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[repr(u8)]
15pub enum Origin {
16 Igp = 0,
18 Egp = 1,
20 Incomplete = 2,
22}
23
24impl Origin {
25 #[must_use]
27 pub fn from_u8(value: u8) -> Option<Self> {
28 match value {
29 0 => Some(Self::Igp),
30 1 => Some(Self::Egp),
31 2 => Some(Self::Incomplete),
32 _ => None,
33 }
34 }
35}
36
37impl std::fmt::Display for Origin {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::Igp => write!(f, "IGP"),
41 Self::Egp => write!(f, "EGP"),
42 Self::Incomplete => write!(f, "INCOMPLETE"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub enum AsPathSegment {
50 AsSet(Vec<u32>),
52 AsSequence(Vec<u32>),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct AsPath {
59 pub segments: Vec<AsPathSegment>,
61}
62
63impl AsPath {
64 #[must_use]
67 pub fn len(&self) -> usize {
68 self.segments
69 .iter()
70 .map(|seg| match seg {
71 AsPathSegment::AsSequence(asns) => asns.len(),
72 AsPathSegment::AsSet(_) => 1,
73 })
74 .sum()
75 }
76
77 #[must_use]
79 pub fn is_empty(&self) -> bool {
80 self.segments.is_empty()
81 }
82
83 #[must_use]
86 pub fn contains_asn(&self, asn: u32) -> bool {
87 self.segments.iter().any(|seg| match seg {
88 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => asns.contains(&asn),
89 })
90 }
91
92 #[must_use]
98 pub fn origin_asn(&self) -> Option<u32> {
99 self.segments.iter().rev().find_map(|seg| match seg {
100 AsPathSegment::AsSequence(asns) => asns.last().copied(),
101 AsPathSegment::AsSet(_) => None,
102 })
103 }
104
105 #[must_use]
109 pub fn all_private(&self) -> bool {
110 let mut count = 0;
111 for seg in &self.segments {
112 match seg {
113 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => {
114 for asn in asns {
115 count += 1;
116 if !is_private_asn(*asn) {
117 return false;
118 }
119 }
120 }
121 }
122 }
123 count > 0
124 }
125
126 #[must_use]
134 pub fn to_aspath_string(&self) -> String {
135 let mut parts = Vec::new();
136 for seg in &self.segments {
137 match seg {
138 AsPathSegment::AsSequence(asns) => {
139 for asn in asns {
140 parts.push(asn.to_string());
141 }
142 }
143 AsPathSegment::AsSet(asns) => {
144 let inner: Vec<String> = asns.iter().map(ToString::to_string).collect();
145 parts.push(format!("{{{}}}", inner.join(" ")));
146 }
147 }
148 }
149 parts.join(" ")
150 }
151}
152
153#[must_use]
159pub fn is_private_asn(asn: u32) -> bool {
160 (64512..=65534).contains(&asn) || (4_200_000_000..=4_294_967_294).contains(&asn)
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub struct MpReachNlri {
169 pub afi: Afi,
171 pub safi: Safi,
173 pub next_hop: IpAddr,
187 pub announced: Vec<NlriEntry>,
189 pub flowspec_announced: Vec<crate::flowspec::FlowSpecRule>,
191 pub evpn_announced: Vec<crate::evpn::EvpnRoute>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
200pub struct MpUnreachNlri {
201 pub afi: Afi,
203 pub safi: Safi,
205 pub withdrawn: Vec<NlriEntry>,
207 pub flowspec_withdrawn: Vec<crate::flowspec::FlowSpecRule>,
209 pub evpn_withdrawn: Vec<crate::evpn::EvpnRoute>,
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
218pub struct ExtendedCommunity(u64);
219
220impl ExtendedCommunity {
221 #[must_use]
223 pub fn new(raw: u64) -> Self {
224 Self(raw)
225 }
226
227 #[must_use]
229 pub fn as_u64(self) -> u64 {
230 self.0
231 }
232
233 #[must_use]
235 pub fn type_byte(self) -> u8 {
236 (self.0 >> 56) as u8
237 }
238
239 #[must_use]
241 pub fn subtype(self) -> u8 {
242 self.0.to_be_bytes()[1]
243 }
244
245 #[must_use]
247 pub fn is_transitive(self) -> bool {
248 self.type_byte() & 0x40 == 0
249 }
250
251 #[must_use]
253 pub fn value_bytes(self) -> [u8; 6] {
254 let b = self.0.to_be_bytes();
255 [b[2], b[3], b[4], b[5], b[6], b[7]]
256 }
257
258 #[must_use]
269 pub fn route_target(self) -> Option<(u32, u32)> {
270 if self.subtype() != 0x02 {
271 return None;
272 }
273 self.decode_two_part()
274 }
275
276 #[must_use]
283 pub fn route_origin(self) -> Option<(u32, u32)> {
284 if self.subtype() != 0x03 {
285 return None;
286 }
287 self.decode_two_part()
288 }
289
290 #[must_use]
308 pub fn as_bgp_encapsulation(self) -> Option<u16> {
309 if self.type_byte() & 0x3F != 0x03 || self.subtype() != 0x0C {
310 return None;
311 }
312 let v = self.value_bytes();
313 Some(u16::from_be_bytes([v[4], v[5]]))
314 }
315
316 #[must_use]
320 pub fn bgp_encapsulation(tunnel_type: u16) -> Self {
321 let tt = tunnel_type.to_be_bytes();
322 let raw = u64::from_be_bytes([0x03, 0x0C, 0, 0, 0, 0, tt[0], tt[1]]);
323 Self(raw)
324 }
325
326 #[must_use]
333 pub fn as_mac_mobility(self) -> Option<(bool, u32)> {
334 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x00 {
335 return None;
336 }
337 let v = self.value_bytes();
338 let sticky = (v[0] & 0x01) != 0;
339 let seq = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
340 Some((sticky, seq))
341 }
342
343 #[must_use]
345 pub fn mac_mobility(sticky: bool, sequence: u32) -> Self {
346 let flags = u8::from(sticky);
347 let s = sequence.to_be_bytes();
348 let raw = u64::from_be_bytes([0x06, 0x00, flags, 0, s[0], s[1], s[2], s[3]]);
349 Self(raw)
350 }
351
352 #[must_use]
358 pub fn as_esi_label(self) -> Option<(bool, u32)> {
359 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x01 {
360 return None;
361 }
362 let v = self.value_bytes();
363 let single_active = (v[0] & 0x01) != 0;
364 let label = (u32::from(v[3]) << 16) | (u32::from(v[4]) << 8) | u32::from(v[5]);
365 Some((single_active, label))
366 }
367
368 #[must_use]
372 pub fn esi_label(single_active: bool, label: u32) -> Self {
373 let flags = u8::from(single_active);
374 let l = label & 0x00FF_FFFF;
375 #[expect(clippy::cast_possible_truncation)]
376 let raw = u64::from_be_bytes([
377 0x06,
378 0x01,
379 flags,
380 0,
381 0,
382 (l >> 16) as u8,
383 (l >> 8) as u8,
384 l as u8,
385 ]);
386 Self(raw)
387 }
388
389 #[must_use]
395 pub fn as_es_import_rt(self) -> Option<[u8; 6]> {
396 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x02 {
397 return None;
398 }
399 Some(self.value_bytes())
400 }
401
402 #[must_use]
404 pub fn es_import_rt(mac: [u8; 6]) -> Self {
405 let raw = u64::from_be_bytes([0x06, 0x02, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
406 Self(raw)
407 }
408
409 #[must_use]
414 pub fn as_router_mac(self) -> Option<[u8; 6]> {
415 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x03 {
416 return None;
417 }
418 Some(self.value_bytes())
419 }
420
421 #[must_use]
423 pub fn router_mac(mac: [u8; 6]) -> Self {
424 let raw = u64::from_be_bytes([0x06, 0x03, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
425 Self(raw)
426 }
427
428 #[must_use]
435 pub fn as_default_gateway(self) -> bool {
436 self.type_byte() & 0x3F == 0x03 && self.subtype() == 0x0D && self.value_bytes() == [0u8; 6]
437 }
438
439 #[must_use]
441 pub fn default_gateway() -> Self {
442 let raw = u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0]);
443 Self(raw)
444 }
445
446 fn decode_two_part(self) -> Option<(u32, u32)> {
452 let v = self.value_bytes();
453 let t = self.type_byte() & 0x3F; match t {
455 0x00 => {
457 let global = u32::from(u16::from_be_bytes([v[0], v[1]]));
458 let local = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
459 Some((global, local))
460 }
461 0x01 | 0x02 => {
463 let global = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
464 let local = u32::from(u16::from_be_bytes([v[4], v[5]]));
465 Some((global, local))
466 }
467 _ => None,
468 }
469 }
470}
471
472impl fmt::Display for ExtendedCommunity {
473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474 let is_ipv4 = self.type_byte() & 0x3F == 0x01;
475 if let Some((g, l)) = self.route_target() {
476 if is_ipv4 {
477 write!(f, "RT:{}:{l}", Ipv4Addr::from(g))
478 } else {
479 write!(f, "RT:{g}:{l}")
480 }
481 } else if let Some((g, l)) = self.route_origin() {
482 if is_ipv4 {
483 write!(f, "RO:{}:{l}", Ipv4Addr::from(g))
484 } else {
485 write!(f, "RO:{g}:{l}")
486 }
487 } else {
488 write!(f, "0x{:016x}", self.0)
489 }
490 }
491}
492
493#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
497pub struct LargeCommunity {
498 pub global_admin: u32,
500 pub local_data1: u32,
502 pub local_data2: u32,
504}
505
506impl LargeCommunity {
507 #[must_use]
509 pub fn new(global_admin: u32, local_data1: u32, local_data2: u32) -> Self {
510 Self {
511 global_admin,
512 local_data1,
513 local_data2,
514 }
515 }
516}
517
518impl fmt::Display for LargeCommunity {
519 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
520 write!(
521 f,
522 "{}:{}:{}",
523 self.global_admin, self.local_data1, self.local_data2
524 )
525 }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Hash)]
533pub enum PathAttribute {
534 Origin(Origin),
536 AsPath(AsPath),
538 NextHop(Ipv4Addr),
540 LocalPref(u32),
542 Med(u32),
544 Communities(Vec<u32>),
546 ExtendedCommunities(Vec<ExtendedCommunity>),
548 LargeCommunities(Vec<LargeCommunity>),
550 OriginatorId(Ipv4Addr),
552 ClusterList(Vec<Ipv4Addr>),
554 MpReachNlri(MpReachNlri),
556 MpUnreachNlri(MpUnreachNlri),
558 Unknown(RawAttribute),
560}
561
562impl PathAttribute {
563 #[must_use]
565 pub fn type_code(&self) -> u8 {
566 match self {
567 Self::Origin(_) => attr_type::ORIGIN,
568 Self::AsPath(_) => attr_type::AS_PATH,
569 Self::NextHop(_) => attr_type::NEXT_HOP,
570 Self::LocalPref(_) => attr_type::LOCAL_PREF,
571 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
572 Self::Communities(_) => attr_type::COMMUNITIES,
573 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
574 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
575 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
576 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
577 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
578 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
579 Self::Unknown(raw) => raw.type_code,
580 }
581 }
582
583 #[must_use]
585 pub fn flags(&self) -> u8 {
586 match self {
587 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
588 attr_flags::TRANSITIVE
589 }
590 Self::Med(_)
591 | Self::OriginatorId(_)
592 | Self::ClusterList(_)
593 | Self::MpReachNlri(_)
594 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
595 Self::Communities(_) | Self::ExtendedCommunities(_) | Self::LargeCommunities(_) => {
596 attr_flags::OPTIONAL | attr_flags::TRANSITIVE
597 }
598 Self::Unknown(raw) => raw.flags,
599 }
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Hash)]
608pub struct RawAttribute {
609 pub flags: u8,
611 pub type_code: u8,
613 pub data: Bytes,
615}
616
617pub fn decode_path_attributes(
628 mut buf: &[u8],
629 four_octet_as: bool,
630 add_path_families: &[(Afi, Safi)],
631) -> Result<Vec<PathAttribute>, DecodeError> {
632 let mut attrs = Vec::new();
633
634 while !buf.is_empty() {
635 if buf.len() < 2 {
637 return Err(DecodeError::MalformedField {
638 message_type: "UPDATE",
639 detail: "truncated attribute header".to_string(),
640 });
641 }
642
643 let flags = buf[0];
644 let type_code = buf[1];
645 buf = &buf[2..];
646
647 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
648 let value_len = if extended {
649 if buf.len() < 2 {
650 return Err(DecodeError::MalformedField {
651 message_type: "UPDATE",
652 detail: "truncated extended-length attribute".to_string(),
653 });
654 }
655 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
656 buf = &buf[2..];
657 len
658 } else {
659 if buf.is_empty() {
660 return Err(DecodeError::MalformedField {
661 message_type: "UPDATE",
662 detail: "truncated attribute length".to_string(),
663 });
664 }
665 let len = buf[0] as usize;
666 buf = &buf[1..];
667 len
668 };
669
670 if buf.len() < value_len {
671 return Err(DecodeError::MalformedField {
672 message_type: "UPDATE",
673 detail: format!(
674 "attribute type {type_code} value truncated: need {value_len}, have {}",
675 buf.len()
676 ),
677 });
678 }
679
680 let value = &buf[..value_len];
681 buf = &buf[value_len..];
682
683 let attr =
684 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
685 attrs.push(attr);
686 }
687
688 Ok(attrs)
689}
690
691#[expect(clippy::too_many_lines)]
693fn decode_attribute_value(
694 flags: u8,
695 type_code: u8,
696 value: &[u8],
697 four_octet_as: bool,
698 add_path_families: &[(Afi, Safi)],
699) -> Result<PathAttribute, DecodeError> {
700 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
702 if let Some(expected) = expected_flags(type_code)
703 && (flags & flags_mask) != expected
704 {
705 return Err(DecodeError::UpdateAttributeError {
706 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
707 data: attr_error_data(flags, type_code, value),
708 detail: format!(
709 "type {} flags {:#04x} (expected {:#04x})",
710 type_code,
711 flags & flags_mask,
712 expected
713 ),
714 });
715 }
716
717 match type_code {
718 attr_type::ORIGIN => {
719 if value.len() != 1 {
720 return Err(DecodeError::UpdateAttributeError {
721 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
722 data: attr_error_data(flags, type_code, value),
723 detail: format!("ORIGIN length {} (expected 1)", value.len()),
724 });
725 }
726 match Origin::from_u8(value[0]) {
727 Some(origin) => Ok(PathAttribute::Origin(origin)),
728 None => Err(DecodeError::UpdateAttributeError {
729 subcode: update_subcode::INVALID_ORIGIN,
730 data: attr_error_data(flags, type_code, value),
731 detail: format!("invalid ORIGIN value {}", value[0]),
732 }),
733 }
734 }
735
736 attr_type::AS_PATH => {
737 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
738 DecodeError::UpdateAttributeError {
739 subcode: update_subcode::MALFORMED_AS_PATH,
740 data: attr_error_data(flags, type_code, value),
741 detail: e.to_string(),
742 }
743 })?;
744 Ok(PathAttribute::AsPath(AsPath { segments }))
745 }
746
747 attr_type::NEXT_HOP => {
748 if value.len() != 4 {
749 return Err(DecodeError::UpdateAttributeError {
750 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
751 data: attr_error_data(flags, type_code, value),
752 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
753 });
754 }
755 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
756 Ok(PathAttribute::NextHop(addr))
757 }
758
759 attr_type::MULTI_EXIT_DISC => {
760 if value.len() != 4 {
761 return Err(DecodeError::UpdateAttributeError {
762 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
763 data: attr_error_data(flags, type_code, value),
764 detail: format!("MED length {} (expected 4)", value.len()),
765 });
766 }
767 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
768 Ok(PathAttribute::Med(med))
769 }
770
771 attr_type::LOCAL_PREF => {
772 if value.len() != 4 {
773 return Err(DecodeError::UpdateAttributeError {
774 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
775 data: attr_error_data(flags, type_code, value),
776 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
777 });
778 }
779 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
780 Ok(PathAttribute::LocalPref(lp))
781 }
782
783 attr_type::COMMUNITIES => {
784 if !value.len().is_multiple_of(4) {
785 return Err(DecodeError::UpdateAttributeError {
786 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
787 data: attr_error_data(flags, type_code, value),
788 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
789 });
790 }
791 let communities = value
792 .chunks_exact(4)
793 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
794 .collect();
795 Ok(PathAttribute::Communities(communities))
796 }
797
798 attr_type::EXTENDED_COMMUNITIES => {
799 if !value.len().is_multiple_of(8) {
800 return Err(DecodeError::UpdateAttributeError {
801 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
802 data: attr_error_data(flags, type_code, value),
803 detail: format!(
804 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
805 value.len()
806 ),
807 });
808 }
809 let communities = value
810 .chunks_exact(8)
811 .map(|c| {
812 ExtendedCommunity::new(u64::from_be_bytes([
813 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
814 ]))
815 })
816 .collect();
817 Ok(PathAttribute::ExtendedCommunities(communities))
818 }
819
820 attr_type::ORIGINATOR_ID => {
821 if value.len() != 4 {
822 return Err(DecodeError::UpdateAttributeError {
823 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
824 data: attr_error_data(flags, type_code, value),
825 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
826 });
827 }
828 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
829 Ok(PathAttribute::OriginatorId(addr))
830 }
831
832 attr_type::CLUSTER_LIST => {
833 if !value.len().is_multiple_of(4) {
834 return Err(DecodeError::UpdateAttributeError {
835 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
836 data: attr_error_data(flags, type_code, value),
837 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
838 });
839 }
840 let ids = value
841 .chunks_exact(4)
842 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
843 .collect();
844 Ok(PathAttribute::ClusterList(ids))
845 }
846
847 attr_type::LARGE_COMMUNITIES => {
848 if value.is_empty() || !value.len().is_multiple_of(12) {
849 return Err(DecodeError::UpdateAttributeError {
850 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
851 data: attr_error_data(flags, type_code, value),
852 detail: format!(
853 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
854 value.len()
855 ),
856 });
857 }
858 let communities = value
859 .chunks_exact(12)
860 .map(|c| {
861 LargeCommunity::new(
862 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
863 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
864 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
865 )
866 })
867 .collect();
868 Ok(PathAttribute::LargeCommunities(communities))
869 }
870
871 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
872 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
873
874 _ => Ok(PathAttribute::Unknown(RawAttribute {
876 flags,
877 type_code,
878 data: Bytes::copy_from_slice(value),
879 })),
880 }
881}
882
883#[expect(clippy::too_many_lines)]
888fn decode_mp_reach_nlri(
889 value: &[u8],
890 add_path_families: &[(Afi, Safi)],
891) -> Result<PathAttribute, DecodeError> {
892 if value.len() < 5 {
893 return Err(DecodeError::MalformedField {
894 message_type: "UPDATE",
895 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
896 });
897 }
898
899 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
900 let safi_raw = value[2];
901 let nh_len = value[3] as usize;
902
903 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
904 message_type: "UPDATE",
905 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
906 })?;
907 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
908 message_type: "UPDATE",
909 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
910 })?;
911
912 if value.len() < 4 + nh_len + 1 {
914 return Err(DecodeError::MalformedField {
915 message_type: "UPDATE",
916 detail: format!(
917 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
918 value.len()
919 ),
920 });
921 }
922
923 let nh_bytes = &value[4..4 + nh_len];
924 let next_hop = if safi == Safi::FlowSpec {
926 if nh_len != 0 {
927 return Err(DecodeError::MalformedField {
928 message_type: "UPDATE",
929 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
930 });
931 }
932 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
933 } else {
934 match afi {
935 Afi::Ipv4 => match nh_len {
936 4 => IpAddr::V4(Ipv4Addr::new(
937 nh_bytes[0],
938 nh_bytes[1],
939 nh_bytes[2],
940 nh_bytes[3],
941 )),
942 16 | 32 => {
943 let mut octets = [0u8; 16];
944 octets.copy_from_slice(&nh_bytes[..16]);
945 IpAddr::V6(Ipv6Addr::from(octets))
946 }
947 _ => {
948 return Err(DecodeError::MalformedField {
949 message_type: "UPDATE",
950 detail: format!(
951 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
952 ),
953 });
954 }
955 },
956 Afi::Ipv6 => {
957 if nh_len != 16 && nh_len != 32 {
958 return Err(DecodeError::MalformedField {
959 message_type: "UPDATE",
960 detail: format!(
961 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
962 ),
963 });
964 }
965 let mut octets = [0u8; 16];
967 octets.copy_from_slice(&nh_bytes[..16]);
968 IpAddr::V6(Ipv6Addr::from(octets))
969 }
970 Afi::L2Vpn => match nh_len {
971 4 => IpAddr::V4(Ipv4Addr::new(
972 nh_bytes[0],
973 nh_bytes[1],
974 nh_bytes[2],
975 nh_bytes[3],
976 )),
977 16 => {
978 let mut octets = [0u8; 16];
979 octets.copy_from_slice(&nh_bytes[..16]);
980 IpAddr::V6(Ipv6Addr::from(octets))
981 }
982 _ => {
983 return Err(DecodeError::MalformedField {
984 message_type: "UPDATE",
985 detail: format!(
986 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
987 ),
988 });
989 }
990 },
991 }
992 };
993
994 let nlri_start = 4 + nh_len + 1;
996 let nlri_bytes = &value[nlri_start..];
997
998 if safi == Safi::FlowSpec {
1000 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1001 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1002 afi,
1003 safi,
1004 next_hop,
1005 announced: vec![],
1006 flowspec_announced: flowspec_rules,
1007 evpn_announced: vec![],
1008 }));
1009 }
1010
1011 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1013 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1014 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1015 afi,
1016 safi,
1017 next_hop,
1018 announced: vec![],
1019 flowspec_announced: vec![],
1020 evpn_announced: routes,
1021 }));
1022 }
1023
1024 if safi == Safi::Evpn {
1028 return Err(DecodeError::MalformedField {
1029 message_type: "UPDATE",
1030 detail: format!(
1031 "MP_REACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1032 afi as u16
1033 ),
1034 });
1035 }
1036
1037 let add_path = add_path_families.contains(&(afi, safi));
1038 let announced = match (afi, add_path) {
1039 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1040 .into_iter()
1041 .map(|p| NlriEntry {
1042 path_id: 0,
1043 prefix: Prefix::V4(p),
1044 })
1045 .collect(),
1046 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1047 .into_iter()
1048 .map(|e| NlriEntry {
1049 path_id: e.path_id,
1050 prefix: Prefix::V4(e.prefix),
1051 })
1052 .collect(),
1053 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1054 .into_iter()
1055 .map(|p| NlriEntry {
1056 path_id: 0,
1057 prefix: Prefix::V6(p),
1058 })
1059 .collect(),
1060 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1061 (Afi::L2Vpn, _) => {
1062 return Err(DecodeError::MalformedField {
1063 message_type: "UPDATE",
1064 detail: format!(
1065 "MP_REACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1066 safi as u8
1067 ),
1068 });
1069 }
1070 };
1071
1072 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1073 afi,
1074 safi,
1075 next_hop,
1076 announced,
1077 flowspec_announced: vec![],
1078 evpn_announced: vec![],
1079 }))
1080}
1081
1082fn decode_mp_unreach_nlri(
1087 value: &[u8],
1088 add_path_families: &[(Afi, Safi)],
1089) -> Result<PathAttribute, DecodeError> {
1090 if value.len() < 3 {
1091 return Err(DecodeError::MalformedField {
1092 message_type: "UPDATE",
1093 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1094 });
1095 }
1096
1097 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1098 let safi_raw = value[2];
1099
1100 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1101 message_type: "UPDATE",
1102 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1103 })?;
1104 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1105 message_type: "UPDATE",
1106 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1107 })?;
1108
1109 let withdrawn_bytes = &value[3..];
1110
1111 if safi == Safi::FlowSpec {
1113 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1114 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1115 afi,
1116 safi,
1117 withdrawn: vec![],
1118 flowspec_withdrawn: flowspec_rules,
1119 evpn_withdrawn: vec![],
1120 }));
1121 }
1122
1123 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1125 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1126 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1127 afi,
1128 safi,
1129 withdrawn: vec![],
1130 flowspec_withdrawn: vec![],
1131 evpn_withdrawn: routes,
1132 }));
1133 }
1134
1135 if safi == Safi::Evpn {
1139 return Err(DecodeError::MalformedField {
1140 message_type: "UPDATE",
1141 detail: format!(
1142 "MP_UNREACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1143 afi as u16
1144 ),
1145 });
1146 }
1147
1148 let add_path = add_path_families.contains(&(afi, safi));
1149 let withdrawn = match (afi, add_path) {
1150 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1151 .into_iter()
1152 .map(|p| NlriEntry {
1153 path_id: 0,
1154 prefix: Prefix::V4(p),
1155 })
1156 .collect(),
1157 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1158 .into_iter()
1159 .map(|e| NlriEntry {
1160 path_id: e.path_id,
1161 prefix: Prefix::V4(e.prefix),
1162 })
1163 .collect(),
1164 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1165 .into_iter()
1166 .map(|p| NlriEntry {
1167 path_id: 0,
1168 prefix: Prefix::V6(p),
1169 })
1170 .collect(),
1171 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1172 (Afi::L2Vpn, _) => {
1173 return Err(DecodeError::MalformedField {
1174 message_type: "UPDATE",
1175 detail: format!(
1176 "MP_UNREACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1177 safi as u8
1178 ),
1179 });
1180 }
1181 };
1182
1183 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1184 afi,
1185 safi,
1186 withdrawn,
1187 flowspec_withdrawn: vec![],
1188 evpn_withdrawn: vec![],
1189 }))
1190}
1191
1192fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1194 let as_size: usize = if four_octet_as { 4 } else { 2 };
1195 let mut segments = Vec::new();
1196
1197 while !buf.is_empty() {
1198 if buf.len() < 2 {
1199 return Err(DecodeError::MalformedField {
1200 message_type: "UPDATE",
1201 detail: "truncated AS_PATH segment header".to_string(),
1202 });
1203 }
1204
1205 let seg_type = buf[0];
1206 let seg_count = buf[1] as usize;
1207 buf = &buf[2..];
1208
1209 let needed = seg_count * as_size;
1210 if buf.len() < needed {
1211 return Err(DecodeError::MalformedField {
1212 message_type: "UPDATE",
1213 detail: format!(
1214 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1215 buf.len()
1216 ),
1217 });
1218 }
1219
1220 let mut asns = Vec::with_capacity(seg_count);
1221 for _ in 0..seg_count {
1222 let asn = if four_octet_as {
1223 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1224 buf = &buf[4..];
1225 v
1226 } else {
1227 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1228 buf = &buf[2..];
1229 v
1230 };
1231 asns.push(asn);
1232 }
1233
1234 match seg_type {
1235 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1236 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1237 _ => {
1238 return Err(DecodeError::MalformedField {
1239 message_type: "UPDATE",
1240 detail: format!("unknown AS_PATH segment type {seg_type}"),
1241 });
1242 }
1243 }
1244 }
1245
1246 Ok(segments)
1247}
1248
1249pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1252 let mut buf = Vec::with_capacity(3 + value.len());
1253 if value.len() > 255 {
1254 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1255 buf.push(type_code);
1256 #[expect(clippy::cast_possible_truncation)]
1257 let len = value.len() as u16;
1258 buf.extend_from_slice(&len.to_be_bytes());
1259 } else {
1260 buf.push(flags);
1261 buf.push(type_code);
1262 #[expect(clippy::cast_possible_truncation)]
1263 buf.push(value.len() as u8);
1264 }
1265 buf.extend_from_slice(value);
1266 buf
1267}
1268
1269fn expected_flags(type_code: u8) -> Option<u8> {
1272 match type_code {
1273 attr_type::ORIGIN
1275 | attr_type::AS_PATH
1276 | attr_type::NEXT_HOP
1277 | attr_type::LOCAL_PREF
1278 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1279 attr_type::MULTI_EXIT_DISC
1282 | attr_type::ORIGINATOR_ID
1283 | attr_type::CLUSTER_LIST
1284 | attr_type::MP_REACH_NLRI
1285 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1286 attr_type::AGGREGATOR
1288 | attr_type::COMMUNITIES
1289 | attr_type::EXTENDED_COMMUNITIES
1290 | attr_type::LARGE_COMMUNITIES => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1291 _ => None,
1292 }
1293}
1294
1295pub fn encode_path_attributes(
1303 attrs: &[PathAttribute],
1304 buf: &mut Vec<u8>,
1305 four_octet_as: bool,
1306 add_path_mp: bool,
1307) {
1308 for attr in attrs {
1309 let mut value = Vec::new();
1310 let flags;
1311 let type_code;
1312
1313 match attr {
1314 PathAttribute::Origin(origin) => {
1315 flags = attr_flags::TRANSITIVE;
1316 type_code = attr_type::ORIGIN;
1317 value.push(*origin as u8);
1318 }
1319 PathAttribute::AsPath(as_path) => {
1320 flags = attr_flags::TRANSITIVE;
1321 type_code = attr_type::AS_PATH;
1322 encode_as_path(as_path, &mut value, four_octet_as);
1323 }
1324 PathAttribute::NextHop(addr) => {
1325 flags = attr_flags::TRANSITIVE;
1326 type_code = attr_type::NEXT_HOP;
1327 value.extend_from_slice(&addr.octets());
1328 }
1329 PathAttribute::Med(med) => {
1330 flags = attr_flags::OPTIONAL;
1331 type_code = attr_type::MULTI_EXIT_DISC;
1332 value.extend_from_slice(&med.to_be_bytes());
1333 }
1334 PathAttribute::LocalPref(lp) => {
1335 flags = attr_flags::TRANSITIVE;
1336 type_code = attr_type::LOCAL_PREF;
1337 value.extend_from_slice(&lp.to_be_bytes());
1338 }
1339 PathAttribute::Communities(communities) => {
1340 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1341 type_code = attr_type::COMMUNITIES;
1342 for &c in communities {
1343 value.extend_from_slice(&c.to_be_bytes());
1344 }
1345 }
1346 PathAttribute::ExtendedCommunities(communities) => {
1347 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1348 type_code = attr_type::EXTENDED_COMMUNITIES;
1349 for &c in communities {
1350 value.extend_from_slice(&c.as_u64().to_be_bytes());
1351 }
1352 }
1353 PathAttribute::LargeCommunities(communities) => {
1354 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1355 type_code = attr_type::LARGE_COMMUNITIES;
1356 for &c in communities {
1357 value.extend_from_slice(&c.global_admin.to_be_bytes());
1358 value.extend_from_slice(&c.local_data1.to_be_bytes());
1359 value.extend_from_slice(&c.local_data2.to_be_bytes());
1360 }
1361 }
1362 PathAttribute::OriginatorId(addr) => {
1363 flags = attr_flags::OPTIONAL;
1364 type_code = attr_type::ORIGINATOR_ID;
1365 value.extend_from_slice(&addr.octets());
1366 }
1367 PathAttribute::ClusterList(ids) => {
1368 flags = attr_flags::OPTIONAL;
1369 type_code = attr_type::CLUSTER_LIST;
1370 for id in ids {
1371 value.extend_from_slice(&id.octets());
1372 }
1373 }
1374 PathAttribute::MpReachNlri(mp) => {
1375 flags = attr_flags::OPTIONAL;
1376 type_code = attr_type::MP_REACH_NLRI;
1377 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1378 }
1379 PathAttribute::MpUnreachNlri(mp) => {
1380 flags = attr_flags::OPTIONAL;
1381 type_code = attr_type::MP_UNREACH_NLRI;
1382 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1383 }
1384 PathAttribute::Unknown(raw) => {
1385 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1389 flags = if (raw.flags & optional_transitive) == optional_transitive {
1390 raw.flags | attr_flags::PARTIAL
1391 } else {
1392 raw.flags
1393 };
1394 type_code = raw.type_code;
1395 value.extend_from_slice(&raw.data);
1396 }
1397 }
1398
1399 if value.len() > 255 {
1401 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1402 buf.push(type_code);
1403 #[expect(clippy::cast_possible_truncation)]
1404 let len = value.len() as u16;
1405 buf.extend_from_slice(&len.to_be_bytes());
1406 } else {
1407 buf.push(flags);
1408 buf.push(type_code);
1409 #[expect(clippy::cast_possible_truncation)]
1410 buf.push(value.len() as u8);
1411 }
1412 buf.extend_from_slice(&value);
1413 }
1414}
1415
1416fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1421 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1422 buf.push(mp.safi as u8);
1423
1424 if mp.safi == Safi::FlowSpec {
1426 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1429 return;
1430 }
1431
1432 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1434 match mp.next_hop {
1435 IpAddr::V4(addr) => {
1436 buf.push(4);
1437 buf.extend_from_slice(&addr.octets());
1438 }
1439 IpAddr::V6(addr) => {
1440 buf.push(16);
1441 buf.extend_from_slice(&addr.octets());
1442 }
1443 }
1444 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1446 return;
1447 }
1448
1449 match mp.next_hop {
1450 IpAddr::V4(addr) => {
1451 buf.push(4); buf.extend_from_slice(&addr.octets());
1453 }
1454 IpAddr::V6(addr) => {
1455 buf.push(16); buf.extend_from_slice(&addr.octets());
1457 }
1458 }
1459
1460 buf.push(0); if add_path {
1463 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1464 } else {
1465 for entry in &mp.announced {
1466 match entry.prefix {
1467 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1468 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1469 }
1470 }
1471 }
1472}
1473
1474fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1478 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1479 buf.push(mp.safi as u8);
1480
1481 if mp.safi == Safi::FlowSpec {
1483 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1484 return;
1485 }
1486
1487 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1489 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1490 return;
1491 }
1492
1493 if add_path {
1494 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1495 } else {
1496 for entry in &mp.withdrawn {
1497 match entry.prefix {
1498 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1499 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1500 }
1501 }
1502 }
1503}
1504
1505fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1507 for segment in &as_path.segments {
1508 let (seg_type, asns) = match segment {
1509 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1510 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1511 };
1512 for chunk in asns.chunks(u8::MAX as usize) {
1513 buf.push(seg_type);
1514 #[expect(clippy::cast_possible_truncation)]
1515 buf.push(chunk.len() as u8);
1516 for &asn in chunk {
1517 if four_octet_as {
1518 buf.extend_from_slice(&asn.to_be_bytes());
1519 } else {
1520 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1523 buf.extend_from_slice(&as2.to_be_bytes());
1524 }
1525 }
1526 }
1527 }
1528}
1529
1530#[cfg(test)]
1531mod tests {
1532 use super::*;
1533
1534 #[test]
1535 fn mp_reach_evpn_attribute_roundtrip() {
1536 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1537
1538 let mp = MpReachNlri {
1539 afi: Afi::L2Vpn,
1540 safi: Safi::Evpn,
1541 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1542 announced: vec![],
1543 flowspec_announced: vec![],
1544 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1545 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1546 ethernet_tag: EthernetTagId(100),
1547 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1548 })],
1549 };
1550 let attr = PathAttribute::MpReachNlri(mp);
1551
1552 let mut buf = Vec::new();
1553 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1554 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1555 assert_eq!(decoded.len(), 1);
1556 assert_eq!(attr, decoded[0]);
1557
1558 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1559 panic!("not MP_REACH after decode");
1560 };
1561 assert_eq!(dec.afi, Afi::L2Vpn);
1562 assert_eq!(dec.safi, Safi::Evpn);
1563 assert_eq!(dec.evpn_announced.len(), 1);
1564 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1565 }
1566
1567 #[test]
1568 fn mp_unreach_evpn_attribute_roundtrip() {
1569 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1570
1571 let mp = MpUnreachNlri {
1572 afi: Afi::L2Vpn,
1573 safi: Safi::Evpn,
1574 withdrawn: vec![],
1575 flowspec_withdrawn: vec![],
1576 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1577 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1578 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1579 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1580 })],
1581 };
1582 let attr = PathAttribute::MpUnreachNlri(mp);
1583 let mut buf = Vec::new();
1584 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1585 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1586 assert_eq!(decoded.len(), 1);
1587 assert_eq!(attr, decoded[0]);
1588 }
1589
1590 #[test]
1593 fn ext_comm_bgp_encapsulation_vxlan() {
1594 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1596 assert_eq!(c.subtype(), 0x0C);
1597 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1598 let b = c.as_u64().to_be_bytes();
1600 assert_eq!(b[2..6], [0, 0, 0, 0]);
1601 assert_eq!(&b[6..8], &[0, 8]);
1602 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1604 }
1605
1606 #[test]
1607 fn ext_comm_mac_mobility_sticky_and_sequence() {
1608 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1609 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1610 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1611 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1612 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1614 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1615 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1616 }
1617
1618 #[test]
1619 fn ext_comm_esi_label_flags_and_label() {
1620 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1621 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1622 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1623 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1624 }
1625
1626 #[test]
1627 fn ext_comm_es_import_rt_mac() {
1628 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1629 let e = ExtendedCommunity::es_import_rt(mac);
1630 assert_eq!(e.as_es_import_rt(), Some(mac));
1631 assert_eq!(e.type_byte(), 0x06);
1632 assert_eq!(e.subtype(), 0x02);
1633 }
1634
1635 #[test]
1636 fn ext_comm_router_mac() {
1637 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1638 let e = ExtendedCommunity::router_mac(mac);
1639 assert_eq!(e.as_router_mac(), Some(mac));
1640 }
1641
1642 #[test]
1643 fn ext_comm_default_gateway_flag_only() {
1644 let d = ExtendedCommunity::default_gateway();
1645 assert!(d.as_default_gateway());
1646 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1648 }
1649
1650 #[test]
1654 fn ext_comm_default_gateway_rejects_nonzero_value() {
1655 let malformed =
1657 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1658 assert!(
1659 !malformed.as_default_gateway(),
1660 "default-gateway accessor must require all-zero value bytes"
1661 );
1662 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1664 }
1665
1666 #[test]
1667 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1668 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1670 assert_eq!(rt.as_mac_mobility(), None);
1671 assert_eq!(rt.as_esi_label(), None);
1672 assert_eq!(rt.as_es_import_rt(), None);
1673 assert_eq!(rt.as_router_mac(), None);
1674 assert!(!rt.as_default_gateway());
1675 }
1676
1677 #[test]
1678 fn origin_from_u8_roundtrip() {
1679 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1680 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1681 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1682 assert_eq!(Origin::from_u8(3), None);
1683 }
1684
1685 #[test]
1686 fn origin_ordering() {
1687 assert!(Origin::Igp < Origin::Egp);
1688 assert!(Origin::Egp < Origin::Incomplete);
1689 }
1690
1691 #[test]
1692 fn as_path_length_calculation() {
1693 let path = AsPath {
1694 segments: vec![
1695 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1696 AsPathSegment::AsSet(vec![65004, 65005]),
1697 ],
1698 };
1699 assert_eq!(path.len(), 4);
1701 }
1702
1703 #[test]
1704 fn as_path_empty() {
1705 let path = AsPath { segments: vec![] };
1706 assert!(path.is_empty());
1707 assert_eq!(path.len(), 0);
1708 }
1709
1710 #[test]
1711 fn contains_asn_in_sequence() {
1712 let path = AsPath {
1713 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1714 };
1715 assert!(path.contains_asn(65002));
1716 assert!(!path.contains_asn(65004));
1717 }
1718
1719 #[test]
1720 fn contains_asn_in_set() {
1721 let path = AsPath {
1722 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1723 };
1724 assert!(path.contains_asn(65005));
1725 assert!(!path.contains_asn(65001));
1726 }
1727
1728 #[test]
1729 fn contains_asn_multiple_segments() {
1730 let path = AsPath {
1731 segments: vec![
1732 AsPathSegment::AsSequence(vec![65001, 65002]),
1733 AsPathSegment::AsSet(vec![65003]),
1734 ],
1735 };
1736 assert!(path.contains_asn(65001));
1737 assert!(path.contains_asn(65003));
1738 assert!(!path.contains_asn(65004));
1739 }
1740
1741 #[test]
1742 fn contains_asn_empty_path() {
1743 let path = AsPath { segments: vec![] };
1744 assert!(!path.contains_asn(65001));
1745 }
1746
1747 #[test]
1748 fn is_private_asn_boundaries() {
1749 assert!(!is_private_asn(64_511));
1751 assert!(is_private_asn(64_512));
1752 assert!(is_private_asn(65_534));
1753 assert!(!is_private_asn(65_535));
1754
1755 assert!(!is_private_asn(4_199_999_999));
1757 assert!(is_private_asn(4_200_000_000));
1758 assert!(is_private_asn(4_294_967_294));
1759 assert!(!is_private_asn(4_294_967_295));
1760 }
1761
1762 #[test]
1763 fn all_private_empty_path_is_false() {
1764 let path = AsPath { segments: vec![] };
1765 assert!(!path.all_private());
1766 }
1767
1768 #[test]
1769 fn all_private_mixed_segments() {
1770 let path = AsPath {
1771 segments: vec![
1772 AsPathSegment::AsSet(vec![64_512, 65_000]),
1773 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
1774 ],
1775 };
1776 assert!(path.all_private());
1777
1778 let non_private = AsPath {
1779 segments: vec![
1780 AsPathSegment::AsSet(vec![64_512, 65_000]),
1781 AsPathSegment::AsSequence(vec![65_535]),
1782 ],
1783 };
1784 assert!(!non_private.all_private());
1785 }
1786
1787 #[test]
1788 fn decode_origin_igp() {
1789 let buf = [0x40, 0x01, 0x01, 0x00];
1791 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1792 assert_eq!(attrs.len(), 1);
1793 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1794 }
1795
1796 #[test]
1797 fn decode_origin_egp() {
1798 let buf = [0x40, 0x01, 0x01, 0x01];
1799 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1800 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
1801 }
1802
1803 #[test]
1804 fn decode_origin_invalid_value() {
1805 let buf = [0x40, 0x01, 0x01, 0x05];
1807 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1808 match &err {
1809 DecodeError::UpdateAttributeError { subcode, .. } => {
1810 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1811 }
1812 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1813 }
1814 }
1815
1816 #[test]
1817 fn decode_next_hop() {
1818 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
1820 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1821 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1822 }
1823
1824 #[test]
1825 fn decode_med() {
1826 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
1828 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1829 assert_eq!(attrs[0], PathAttribute::Med(100));
1830 }
1831
1832 #[test]
1833 fn decode_local_pref() {
1834 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
1836 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1837 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
1838 }
1839
1840 #[test]
1841 fn decode_as_path_4byte() {
1842 let buf = [
1845 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1850 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1851 assert_eq!(
1852 attrs[0],
1853 PathAttribute::AsPath(AsPath {
1854 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1855 })
1856 );
1857 }
1858
1859 #[test]
1860 fn decode_as_path_2byte() {
1861 let buf = [
1864 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
1869 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
1870 assert_eq!(
1871 attrs[0],
1872 PathAttribute::AsPath(AsPath {
1873 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
1874 })
1875 );
1876 }
1877
1878 #[test]
1879 fn decode_unknown_attribute_preserved() {
1880 let buf = [0xC0, 99, 0x03, 1, 2, 3];
1882 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1883 assert_eq!(
1884 attrs[0],
1885 PathAttribute::Unknown(RawAttribute {
1886 flags: 0xC0,
1887 type_code: 99,
1888 data: Bytes::from_static(&[1, 2, 3]),
1889 })
1890 );
1891 }
1892
1893 #[test]
1894 fn decode_atomic_aggregate_as_unknown() {
1895 let buf = [0x40, 0x06, 0x00];
1897 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1898 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
1899 }
1900
1901 #[test]
1902 fn decode_extended_length() {
1903 let buf = [
1906 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1911 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1912 assert_eq!(
1913 attrs[0],
1914 PathAttribute::AsPath(AsPath {
1915 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1916 })
1917 );
1918 }
1919
1920 #[test]
1921 fn decode_multiple_attributes() {
1922 let mut buf = Vec::new();
1923 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
1925 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
1927 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
1929
1930 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1931 assert_eq!(attrs.len(), 3);
1932 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1933 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1934 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
1935 }
1936
1937 #[test]
1938 fn roundtrip_attributes_4byte() {
1939 let attrs = vec![
1940 PathAttribute::Origin(Origin::Igp),
1941 PathAttribute::AsPath(AsPath {
1942 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
1943 }),
1944 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
1945 PathAttribute::Med(100),
1946 PathAttribute::LocalPref(200),
1947 ];
1948
1949 let mut buf = Vec::new();
1950 encode_path_attributes(&attrs, &mut buf, true, false);
1951 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1952 assert_eq!(decoded, attrs);
1953 }
1954
1955 #[test]
1956 fn roundtrip_attributes_2byte() {
1957 let attrs = vec![
1958 PathAttribute::Origin(Origin::Egp),
1959 PathAttribute::AsPath(AsPath {
1960 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
1961 }),
1962 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
1963 ];
1964
1965 let mut buf = Vec::new();
1966 encode_path_attributes(&attrs, &mut buf, false, false);
1967 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
1968 assert_eq!(decoded, attrs);
1969 }
1970
1971 #[test]
1972 fn reject_truncated_attribute_header() {
1973 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
1975 }
1976
1977 #[test]
1978 fn reject_truncated_attribute_value() {
1979 let buf = [0x40, 0x01, 0x01];
1981 assert!(decode_path_attributes(&buf, true, &[]).is_err());
1982 }
1983
1984 #[test]
1985 fn reject_bad_origin_length() {
1986 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
1988 assert!(decode_path_attributes(&buf, true, &[]).is_err());
1989 }
1990
1991 #[test]
1992 fn as_path_with_set_and_sequence() {
1993 let attrs = vec![PathAttribute::AsPath(AsPath {
1995 segments: vec![
1996 AsPathSegment::AsSequence(vec![65001]),
1997 AsPathSegment::AsSet(vec![65002, 65003]),
1998 ],
1999 })];
2000
2001 let mut buf = Vec::new();
2002 encode_path_attributes(&attrs, &mut buf, true, false);
2003 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2004 assert_eq!(decoded, attrs);
2005 }
2006
2007 #[test]
2008 fn decode_communities_single() {
2009 let community: u32 = (65001 << 16) | 0x0064;
2012 let bytes = community.to_be_bytes();
2013 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2014 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2015 assert_eq!(attrs.len(), 1);
2016 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2017 }
2018
2019 #[test]
2020 fn decode_communities_multiple() {
2021 let c1: u32 = (65001 << 16) | 0x0064;
2022 let c2: u32 = (65002 << 16) | 0x00C8;
2023 let b1 = c1.to_be_bytes();
2024 let b2 = c2.to_be_bytes();
2025 let buf = [
2026 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2027 ];
2028 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2029 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2030 }
2031
2032 #[test]
2033 fn decode_communities_empty() {
2034 let buf = [0xC0, 0x08, 0x00];
2036 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2037 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2038 }
2039
2040 #[test]
2041 fn decode_communities_odd_length_rejected() {
2042 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2044 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2045 }
2046
2047 #[test]
2048 fn communities_roundtrip() {
2049 let c1: u32 = (65001 << 16) | 0x0064;
2050 let c2: u32 = (65002 << 16) | 0x00C8;
2051 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2052
2053 let mut buf = Vec::new();
2054 encode_path_attributes(&attrs, &mut buf, true, false);
2055 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2056 assert_eq!(decoded, attrs);
2057 }
2058
2059 #[test]
2060 fn communities_type_code_and_flags() {
2061 let attr = PathAttribute::Communities(vec![]);
2062 assert_eq!(attr.type_code(), 8);
2063 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2064 }
2065
2066 #[test]
2069 fn decode_extended_communities_single() {
2070 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2072 let bytes = ec.as_u64().to_be_bytes();
2073 let buf = [
2074 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2075 bytes[7],
2076 ];
2077 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2078 assert_eq!(attrs.len(), 1);
2079 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2080 }
2081
2082 #[test]
2083 fn decode_extended_communities_multiple() {
2084 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2085 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2086 let b1 = ec1.as_u64().to_be_bytes();
2087 let b2 = ec2.as_u64().to_be_bytes();
2088 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2090 buf.extend_from_slice(&b2);
2091 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2092 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2093 }
2094
2095 #[test]
2096 fn decode_extended_communities_empty() {
2097 let buf = [0xC0, 0x10, 0x00];
2098 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2099 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2100 }
2101
2102 #[test]
2103 fn decode_extended_communities_bad_length() {
2104 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2106 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2107 }
2108
2109 #[test]
2110 fn extended_communities_roundtrip() {
2111 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2112 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2113 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2114
2115 let mut buf = Vec::new();
2116 encode_path_attributes(&attrs, &mut buf, true, false);
2117 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2118 assert_eq!(decoded, attrs);
2119 }
2120
2121 #[test]
2122 fn extended_communities_type_code_and_flags() {
2123 let attr = PathAttribute::ExtendedCommunities(vec![]);
2124 assert_eq!(attr.type_code(), 16);
2125 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2126 }
2127
2128 #[test]
2129 fn extended_community_type_subtype() {
2130 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2132 assert_eq!(ec.type_byte(), 0x00);
2133 assert_eq!(ec.subtype(), 0x02);
2134 assert!(ec.is_transitive());
2135 }
2136
2137 #[test]
2138 fn extended_community_route_target() {
2139 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2141 assert_eq!(ec.route_target(), Some((65001, 100)));
2142 assert_eq!(ec.route_origin(), None);
2143
2144 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2146 assert_eq!(ec4.route_target(), Some((65537, 200)));
2147
2148 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2151 let (g, l) = ec_ipv4.route_target().unwrap();
2152 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2154 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2156 }
2157
2158 #[test]
2159 fn extended_community_is_transitive() {
2160 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2162 assert!(t.is_transitive());
2163
2164 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2166 assert!(!nt.is_transitive());
2167 }
2168
2169 #[test]
2170 fn extended_community_display() {
2171 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2172 assert_eq!(rt.to_string(), "RT:65001:100");
2173
2174 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2175 assert_eq!(ro.to_string(), "RO:65001:100");
2176
2177 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2179 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2180
2181 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2183 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2184
2185 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2187 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2188
2189 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2191 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2192 }
2193
2194 #[test]
2195 fn unknown_attribute_roundtrip() {
2196 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2199 flags: 0xC0,
2200 type_code: 99,
2201 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2202 })];
2203
2204 let mut buf = Vec::new();
2205 encode_path_attributes(&attrs, &mut buf, true, false);
2206 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2207 assert_eq!(
2208 decoded,
2209 vec![PathAttribute::Unknown(RawAttribute {
2210 flags: 0xE0, type_code: 99,
2212 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2213 })]
2214 );
2215 }
2216
2217 #[test]
2218 fn origin_with_optional_flag_rejected() {
2219 let buf = [0xC0, 0x01, 0x01, 0x00];
2221 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2222 match &err {
2223 DecodeError::UpdateAttributeError { subcode, .. } => {
2224 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2225 }
2226 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2227 }
2228 }
2229
2230 #[test]
2231 fn med_with_transitive_flag_rejected() {
2232 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2234 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2235 match &err {
2236 DecodeError::UpdateAttributeError { subcode, .. } => {
2237 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2238 }
2239 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2240 }
2241 }
2242
2243 #[test]
2244 fn communities_without_optional_rejected() {
2245 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2247 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2248 match &err {
2249 DecodeError::UpdateAttributeError { subcode, .. } => {
2250 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2251 }
2252 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2253 }
2254 }
2255
2256 #[test]
2257 fn next_hop_length_error_subcode() {
2258 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2260 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2261 match &err {
2262 DecodeError::UpdateAttributeError { subcode, .. } => {
2263 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2264 }
2265 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2266 }
2267 }
2268
2269 #[test]
2270 fn invalid_origin_value_subcode() {
2271 let buf = [0x40, 0x01, 0x01, 0x05];
2273 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2274 match &err {
2275 DecodeError::UpdateAttributeError { subcode, .. } => {
2276 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2277 }
2278 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2279 }
2280 }
2281
2282 #[test]
2283 fn as_path_bad_segment_subcode() {
2284 let buf = [
2286 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2290 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2291 match &err {
2292 DecodeError::UpdateAttributeError { subcode, .. } => {
2293 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2294 }
2295 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2296 }
2297 }
2298
2299 #[test]
2300 fn encode_unknown_transitive_sets_partial() {
2301 let attr = PathAttribute::Unknown(RawAttribute {
2302 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2304 data: Bytes::from_static(&[1, 2]),
2305 });
2306 let mut buf = Vec::new();
2307 encode_path_attributes(&[attr], &mut buf, true, false);
2308 assert_eq!(
2310 buf[0],
2311 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2312 );
2313 }
2314
2315 #[test]
2316 fn encode_unknown_wellknown_transitive_no_partial() {
2317 let attr = PathAttribute::Unknown(RawAttribute {
2319 flags: attr_flags::TRANSITIVE, type_code: 99,
2321 data: Bytes::from_static(&[1, 2]),
2322 });
2323 let mut buf = Vec::new();
2324 encode_path_attributes(&[attr], &mut buf, true, false);
2325 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2326 }
2327
2328 #[test]
2329 fn encode_unknown_nontransitive_no_partial() {
2330 let attr = PathAttribute::Unknown(RawAttribute {
2331 flags: attr_flags::OPTIONAL, type_code: 99,
2333 data: Bytes::from_static(&[1, 2]),
2334 });
2335 let mut buf = Vec::new();
2336 encode_path_attributes(&[attr], &mut buf, true, false);
2337 assert_eq!(buf[0], attr_flags::OPTIONAL);
2339 }
2340
2341 fn nlri(prefix: Prefix) -> NlriEntry {
2345 NlriEntry { path_id: 0, prefix }
2346 }
2347
2348 #[test]
2349 fn mp_reach_nlri_ipv6_roundtrip() {
2350 use crate::capability::{Afi, Safi};
2351 use crate::nlri::{Ipv6Prefix, Prefix};
2352
2353 let mp = MpReachNlri {
2354 afi: Afi::Ipv6,
2355 safi: Safi::Unicast,
2356 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2357 announced: vec![
2358 nlri(Prefix::V6(Ipv6Prefix::new(
2359 "2001:db8:1::".parse().unwrap(),
2360 48,
2361 ))),
2362 nlri(Prefix::V6(Ipv6Prefix::new(
2363 "2001:db8:2::".parse().unwrap(),
2364 48,
2365 ))),
2366 ],
2367 flowspec_announced: vec![],
2368 evpn_announced: vec![],
2369 };
2370 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2371
2372 let mut buf = Vec::new();
2373 encode_path_attributes(&attrs, &mut buf, true, false);
2374 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2375 assert_eq!(decoded.len(), 1);
2376 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2377 }
2378
2379 #[test]
2380 fn mp_unreach_nlri_ipv6_roundtrip() {
2381 use crate::capability::{Afi, Safi};
2382 use crate::nlri::{Ipv6Prefix, Prefix};
2383
2384 let mp = MpUnreachNlri {
2385 afi: Afi::Ipv6,
2386 safi: Safi::Unicast,
2387 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2388 "2001:db8:1::".parse().unwrap(),
2389 48,
2390 )))],
2391 flowspec_withdrawn: vec![],
2392 evpn_withdrawn: vec![],
2393 };
2394 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2395
2396 let mut buf = Vec::new();
2397 encode_path_attributes(&attrs, &mut buf, true, false);
2398 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2399 assert_eq!(decoded.len(), 1);
2400 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2401 }
2402
2403 #[test]
2404 fn mp_reach_nlri_ipv4_roundtrip() {
2405 use crate::capability::{Afi, Safi};
2406 use crate::nlri::Prefix;
2407
2408 let mp = MpReachNlri {
2409 afi: Afi::Ipv4,
2410 safi: Safi::Unicast,
2411 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2412 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2413 Ipv4Addr::new(10, 1, 0, 0),
2414 16,
2415 )))],
2416 flowspec_announced: vec![],
2417 evpn_announced: vec![],
2418 };
2419 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2420
2421 let mut buf = Vec::new();
2422 encode_path_attributes(&attrs, &mut buf, true, false);
2423 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2424 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2425 }
2426
2427 #[test]
2428 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2429 use crate::capability::{Afi, Safi};
2430 use crate::nlri::Prefix;
2431
2432 let mp = MpReachNlri {
2433 afi: Afi::Ipv4,
2434 safi: Safi::Unicast,
2435 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2436 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2437 Ipv4Addr::new(10, 1, 0, 0),
2438 16,
2439 )))],
2440 flowspec_announced: vec![],
2441 evpn_announced: vec![],
2442 };
2443 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2444
2445 let mut buf = Vec::new();
2446 encode_path_attributes(&attrs, &mut buf, true, false);
2447 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2448 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2449 }
2450
2451 #[test]
2452 fn mp_reach_nlri_type_code_and_flags() {
2453 use crate::capability::{Afi, Safi};
2454
2455 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2456 afi: Afi::Ipv6,
2457 safi: Safi::Unicast,
2458 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2459 announced: vec![],
2460 flowspec_announced: vec![],
2461 evpn_announced: vec![],
2462 });
2463 assert_eq!(attr.type_code(), 14);
2464 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2466 }
2467
2468 #[test]
2469 fn mp_unreach_nlri_type_code_and_flags() {
2470 use crate::capability::{Afi, Safi};
2471
2472 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2473 afi: Afi::Ipv6,
2474 safi: Safi::Unicast,
2475 withdrawn: vec![],
2476 flowspec_withdrawn: vec![],
2477 evpn_withdrawn: vec![],
2478 });
2479 assert_eq!(attr.type_code(), 15);
2480 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2481 }
2482
2483 #[test]
2484 fn mp_reach_nlri_empty_nlri() {
2485 use crate::capability::{Afi, Safi};
2486
2487 let mp = MpReachNlri {
2488 afi: Afi::Ipv6,
2489 safi: Safi::Unicast,
2490 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2491 announced: vec![],
2492 flowspec_announced: vec![],
2493 evpn_announced: vec![],
2494 };
2495 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2496
2497 let mut buf = Vec::new();
2498 encode_path_attributes(&attrs, &mut buf, true, false);
2499 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2500 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2501 }
2502
2503 #[test]
2504 fn mp_reach_nlri_bad_flags_rejected() {
2505 let mut value = Vec::new();
2509 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.push(16); value.extend_from_slice(&"::1".parse::<Ipv6Addr>().unwrap().octets()); value.push(0); let mut buf = Vec::new();
2516 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2519 buf.push(value.len() as u8);
2520 buf.extend_from_slice(&value);
2521
2522 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2523 assert!(matches!(
2524 err,
2525 DecodeError::UpdateAttributeError {
2526 subcode: 4, ..
2528 }
2529 ));
2530 }
2531
2532 #[test]
2535 #[expect(clippy::cast_possible_truncation)]
2536 fn mp_reach_nlri_ipv4_addpath_decode() {
2537 use crate::capability::{Afi, Safi};
2538 use crate::nlri::Prefix;
2539
2540 let mut value = Vec::new();
2543 value.extend_from_slice(&1u16.to_be_bytes()); value.push(1); value.push(4); value.extend_from_slice(&[10, 0, 0, 1]); value.push(0); value.extend_from_slice(&42u32.to_be_bytes());
2550 value.push(16);
2551 value.extend_from_slice(&[10, 1]);
2552
2553 let mut buf = Vec::new();
2554 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2557 buf.extend_from_slice(&value);
2558
2559 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2561 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2562 panic!("expected MpReachNlri");
2563 };
2564 assert_eq!(mp.announced.len(), 1);
2565 assert_eq!(mp.announced[0].path_id, 42);
2566 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2567
2568 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2571 }
2572
2573 #[test]
2574 #[expect(clippy::cast_possible_truncation)]
2575 fn mp_reach_nlri_ipv6_addpath_decode() {
2576 use crate::capability::{Afi, Safi};
2577 use crate::nlri::{Ipv6Prefix, Prefix};
2578
2579 let mut value = Vec::new();
2581 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.push(16); value.extend_from_slice(&"2001:db8::1".parse::<Ipv6Addr>().unwrap().octets());
2585 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2588 value.push(48);
2589 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2590
2591 let mut buf = Vec::new();
2592 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2595 buf.extend_from_slice(&value);
2596
2597 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2598 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2599 panic!("expected MpReachNlri");
2600 };
2601 assert_eq!(mp.announced.len(), 1);
2602 assert_eq!(mp.announced[0].path_id, 99);
2603 assert_eq!(
2604 mp.announced[0].prefix,
2605 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2606 );
2607 }
2608
2609 #[test]
2610 #[expect(clippy::cast_possible_truncation)]
2611 fn mp_unreach_nlri_ipv6_addpath_decode() {
2612 use crate::capability::{Afi, Safi};
2613 use crate::nlri::{Ipv6Prefix, Prefix};
2614
2615 let mut value = Vec::new();
2617 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2621 value.push(48);
2622 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2623
2624 let mut buf = Vec::new();
2625 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2628 buf.extend_from_slice(&value);
2629
2630 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2631 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2632 panic!("expected MpUnreachNlri");
2633 };
2634 assert_eq!(mp.withdrawn.len(), 1);
2635 assert_eq!(mp.withdrawn[0].path_id, 7);
2636 assert_eq!(
2637 mp.withdrawn[0].prefix,
2638 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2639 );
2640 }
2641
2642 #[test]
2643 fn mp_reach_addpath_only_applies_to_matching_family() {
2644 use crate::capability::{Afi, Safi};
2645 use crate::nlri::{Ipv6Prefix, Prefix};
2646
2647 let mp = MpReachNlri {
2649 afi: Afi::Ipv6,
2650 safi: Safi::Unicast,
2651 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2652 announced: vec![NlriEntry {
2653 path_id: 0,
2654 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2655 }],
2656 flowspec_announced: vec![],
2657 evpn_announced: vec![],
2658 };
2659 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2660
2661 let mut buf = Vec::new();
2662 encode_path_attributes(&attrs, &mut buf, true, false);
2663
2664 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2666 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2667 }
2668
2669 #[test]
2672 fn decode_originator_id() {
2673 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2675 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2676 assert_eq!(
2677 attrs[0],
2678 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2679 );
2680 }
2681
2682 #[test]
2683 fn originator_id_roundtrip() {
2684 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
2685 let mut buf = Vec::new();
2686 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2687 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2688 assert_eq!(decoded, vec![attr]);
2689 }
2690
2691 #[test]
2692 fn originator_id_wrong_length() {
2693 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
2695 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2696 assert!(matches!(
2697 err,
2698 DecodeError::UpdateAttributeError {
2699 subcode: 5, ..
2701 }
2702 ));
2703 }
2704
2705 #[test]
2706 fn originator_id_wrong_flags() {
2707 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
2709 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2710 assert!(matches!(
2711 err,
2712 DecodeError::UpdateAttributeError {
2713 subcode: 4, ..
2715 }
2716 ));
2717 }
2718
2719 #[test]
2722 fn decode_cluster_list() {
2723 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
2725 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2726 assert_eq!(
2727 attrs[0],
2728 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
2729 );
2730 }
2731
2732 #[test]
2733 fn cluster_list_roundtrip() {
2734 let attr = PathAttribute::ClusterList(vec![
2735 Ipv4Addr::new(10, 0, 0, 1),
2736 Ipv4Addr::new(10, 0, 0, 2),
2737 ]);
2738 let mut buf = Vec::new();
2739 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2740 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2741 assert_eq!(decoded, vec![attr]);
2742 }
2743
2744 #[test]
2745 fn cluster_list_wrong_length() {
2746 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
2748 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2749 assert!(matches!(
2750 err,
2751 DecodeError::UpdateAttributeError {
2752 subcode: 5, ..
2754 }
2755 ));
2756 }
2757
2758 #[test]
2763 fn large_community_display() {
2764 let lc = LargeCommunity::new(65001, 100, 200);
2765 assert_eq!(lc.to_string(), "65001:100:200");
2766 }
2767
2768 #[test]
2769 fn large_community_type_code_and_flags() {
2770 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
2771 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
2772 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2773 }
2774
2775 #[test]
2776 fn decode_large_community_single() {
2777 let mut buf = vec![0xC0, 32, 12];
2779 buf.extend_from_slice(&65001u32.to_be_bytes());
2780 buf.extend_from_slice(&100u32.to_be_bytes());
2781 buf.extend_from_slice(&200u32.to_be_bytes());
2782 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2783 assert_eq!(attrs.len(), 1);
2784 assert_eq!(
2785 attrs[0],
2786 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
2787 );
2788 }
2789
2790 #[test]
2791 fn decode_large_community_multiple() {
2792 let mut buf = vec![0xC0, 32, 24];
2794 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
2795 buf.extend_from_slice(&g.to_be_bytes());
2796 buf.extend_from_slice(&l1.to_be_bytes());
2797 buf.extend_from_slice(&l2.to_be_bytes());
2798 }
2799 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2800 assert_eq!(
2801 attrs[0],
2802 PathAttribute::LargeCommunities(vec![
2803 LargeCommunity::new(65001, 100, 200),
2804 LargeCommunity::new(65002, 300, 400),
2805 ])
2806 );
2807 }
2808
2809 #[test]
2810 fn decode_large_community_bad_length() {
2811 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
2813 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2814 assert!(matches!(
2815 err,
2816 DecodeError::UpdateAttributeError {
2817 subcode: 5, ..
2819 }
2820 ));
2821 }
2822
2823 #[test]
2824 fn decode_large_community_empty_rejected() {
2825 let buf = [0xC0, 32, 0];
2827 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2828 assert!(matches!(
2829 err,
2830 DecodeError::UpdateAttributeError {
2831 subcode: 5, ..
2833 }
2834 ));
2835 }
2836
2837 #[test]
2838 fn large_community_roundtrip() {
2839 let lcs = vec![
2840 LargeCommunity::new(65001, 100, 200),
2841 LargeCommunity::new(0, u32::MAX, 42),
2842 ];
2843 let attr = PathAttribute::LargeCommunities(lcs.clone());
2844 let mut buf = Vec::new();
2845 encode_path_attributes(&[attr], &mut buf, true, false);
2846 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2847 assert_eq!(decoded.len(), 1);
2848 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
2849 }
2850
2851 #[test]
2852 fn large_community_expected_flags_validated() {
2853 let mut buf = vec![0x40, 32, 12];
2855 buf.extend_from_slice(&1u32.to_be_bytes());
2856 buf.extend_from_slice(&2u32.to_be_bytes());
2857 buf.extend_from_slice(&3u32.to_be_bytes());
2858 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2859 assert!(matches!(
2860 err,
2861 DecodeError::UpdateAttributeError {
2862 subcode: 4, ..
2864 }
2865 ));
2866 }
2867
2868 #[test]
2873 fn aspath_string_sequence() {
2874 let p = AsPath {
2875 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2876 };
2877 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
2878 }
2879
2880 #[test]
2881 fn aspath_string_set() {
2882 let p = AsPath {
2883 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
2884 };
2885 assert_eq!(p.to_aspath_string(), "{65003 65004}");
2886 }
2887
2888 #[test]
2889 fn aspath_string_mixed() {
2890 let p = AsPath {
2891 segments: vec![
2892 AsPathSegment::AsSequence(vec![65001, 65002]),
2893 AsPathSegment::AsSet(vec![65003, 65004]),
2894 ],
2895 };
2896 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
2897 }
2898
2899 #[test]
2900 fn aspath_string_empty() {
2901 let p = AsPath { segments: vec![] };
2902 assert_eq!(p.to_aspath_string(), "");
2903 }
2904
2905 #[test]
2910 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
2911 let bytes = vec![
2914 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
2920 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
2921 match err {
2922 DecodeError::MalformedField { detail, .. } => {
2923 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
2924 }
2925 other => panic!("expected MalformedField, got {other:?}"),
2926 }
2927 }
2928
2929 #[test]
2930 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
2931 let bytes = vec![
2932 0x00, 0x02, 70, 3, 0, ];
2936 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
2937 match err {
2938 DecodeError::MalformedField { detail, .. } => {
2939 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
2940 }
2941 other => panic!("expected MalformedField, got {other:?}"),
2942 }
2943 }
2944}