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}
192
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
198pub struct MpUnreachNlri {
199 pub afi: Afi,
201 pub safi: Safi,
203 pub withdrawn: Vec<NlriEntry>,
205 pub flowspec_withdrawn: Vec<crate::flowspec::FlowSpecRule>,
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
214pub struct ExtendedCommunity(u64);
215
216impl ExtendedCommunity {
217 #[must_use]
219 pub fn new(raw: u64) -> Self {
220 Self(raw)
221 }
222
223 #[must_use]
225 pub fn as_u64(self) -> u64 {
226 self.0
227 }
228
229 #[must_use]
231 pub fn type_byte(self) -> u8 {
232 (self.0 >> 56) as u8
233 }
234
235 #[must_use]
237 pub fn subtype(self) -> u8 {
238 self.0.to_be_bytes()[1]
239 }
240
241 #[must_use]
243 pub fn is_transitive(self) -> bool {
244 self.type_byte() & 0x40 == 0
245 }
246
247 #[must_use]
249 pub fn value_bytes(self) -> [u8; 6] {
250 let b = self.0.to_be_bytes();
251 [b[2], b[3], b[4], b[5], b[6], b[7]]
252 }
253
254 #[must_use]
265 pub fn route_target(self) -> Option<(u32, u32)> {
266 if self.subtype() != 0x02 {
267 return None;
268 }
269 self.decode_two_part()
270 }
271
272 #[must_use]
279 pub fn route_origin(self) -> Option<(u32, u32)> {
280 if self.subtype() != 0x03 {
281 return None;
282 }
283 self.decode_two_part()
284 }
285
286 fn decode_two_part(self) -> Option<(u32, u32)> {
292 let v = self.value_bytes();
293 let t = self.type_byte() & 0x3F; match t {
295 0x00 => {
297 let global = u32::from(u16::from_be_bytes([v[0], v[1]]));
298 let local = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
299 Some((global, local))
300 }
301 0x01 | 0x02 => {
303 let global = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
304 let local = u32::from(u16::from_be_bytes([v[4], v[5]]));
305 Some((global, local))
306 }
307 _ => None,
308 }
309 }
310}
311
312impl fmt::Display for ExtendedCommunity {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 let is_ipv4 = self.type_byte() & 0x3F == 0x01;
315 if let Some((g, l)) = self.route_target() {
316 if is_ipv4 {
317 write!(f, "RT:{}:{l}", Ipv4Addr::from(g))
318 } else {
319 write!(f, "RT:{g}:{l}")
320 }
321 } else if let Some((g, l)) = self.route_origin() {
322 if is_ipv4 {
323 write!(f, "RO:{}:{l}", Ipv4Addr::from(g))
324 } else {
325 write!(f, "RO:{g}:{l}")
326 }
327 } else {
328 write!(f, "0x{:016x}", self.0)
329 }
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
337pub struct LargeCommunity {
338 pub global_admin: u32,
340 pub local_data1: u32,
342 pub local_data2: u32,
344}
345
346impl LargeCommunity {
347 #[must_use]
349 pub fn new(global_admin: u32, local_data1: u32, local_data2: u32) -> Self {
350 Self {
351 global_admin,
352 local_data1,
353 local_data2,
354 }
355 }
356}
357
358impl fmt::Display for LargeCommunity {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 write!(
361 f,
362 "{}:{}:{}",
363 self.global_admin, self.local_data1, self.local_data2
364 )
365 }
366}
367
368#[derive(Debug, Clone, PartialEq, Eq, Hash)]
373pub enum PathAttribute {
374 Origin(Origin),
376 AsPath(AsPath),
378 NextHop(Ipv4Addr),
380 LocalPref(u32),
382 Med(u32),
384 Communities(Vec<u32>),
386 ExtendedCommunities(Vec<ExtendedCommunity>),
388 LargeCommunities(Vec<LargeCommunity>),
390 OriginatorId(Ipv4Addr),
392 ClusterList(Vec<Ipv4Addr>),
394 MpReachNlri(MpReachNlri),
396 MpUnreachNlri(MpUnreachNlri),
398 Unknown(RawAttribute),
400}
401
402impl PathAttribute {
403 #[must_use]
405 pub fn type_code(&self) -> u8 {
406 match self {
407 Self::Origin(_) => attr_type::ORIGIN,
408 Self::AsPath(_) => attr_type::AS_PATH,
409 Self::NextHop(_) => attr_type::NEXT_HOP,
410 Self::LocalPref(_) => attr_type::LOCAL_PREF,
411 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
412 Self::Communities(_) => attr_type::COMMUNITIES,
413 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
414 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
415 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
416 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
417 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
418 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
419 Self::Unknown(raw) => raw.type_code,
420 }
421 }
422
423 #[must_use]
425 pub fn flags(&self) -> u8 {
426 match self {
427 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
428 attr_flags::TRANSITIVE
429 }
430 Self::Med(_)
431 | Self::OriginatorId(_)
432 | Self::ClusterList(_)
433 | Self::MpReachNlri(_)
434 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
435 Self::Communities(_) | Self::ExtendedCommunities(_) | Self::LargeCommunities(_) => {
436 attr_flags::OPTIONAL | attr_flags::TRANSITIVE
437 }
438 Self::Unknown(raw) => raw.flags,
439 }
440 }
441}
442
443#[derive(Debug, Clone, PartialEq, Eq, Hash)]
448pub struct RawAttribute {
449 pub flags: u8,
451 pub type_code: u8,
453 pub data: Bytes,
455}
456
457pub fn decode_path_attributes(
468 mut buf: &[u8],
469 four_octet_as: bool,
470 add_path_families: &[(Afi, Safi)],
471) -> Result<Vec<PathAttribute>, DecodeError> {
472 let mut attrs = Vec::new();
473
474 while !buf.is_empty() {
475 if buf.len() < 2 {
477 return Err(DecodeError::MalformedField {
478 message_type: "UPDATE",
479 detail: "truncated attribute header".to_string(),
480 });
481 }
482
483 let flags = buf[0];
484 let type_code = buf[1];
485 buf = &buf[2..];
486
487 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
488 let value_len = if extended {
489 if buf.len() < 2 {
490 return Err(DecodeError::MalformedField {
491 message_type: "UPDATE",
492 detail: "truncated extended-length attribute".to_string(),
493 });
494 }
495 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
496 buf = &buf[2..];
497 len
498 } else {
499 if buf.is_empty() {
500 return Err(DecodeError::MalformedField {
501 message_type: "UPDATE",
502 detail: "truncated attribute length".to_string(),
503 });
504 }
505 let len = buf[0] as usize;
506 buf = &buf[1..];
507 len
508 };
509
510 if buf.len() < value_len {
511 return Err(DecodeError::MalformedField {
512 message_type: "UPDATE",
513 detail: format!(
514 "attribute type {type_code} value truncated: need {value_len}, have {}",
515 buf.len()
516 ),
517 });
518 }
519
520 let value = &buf[..value_len];
521 buf = &buf[value_len..];
522
523 let attr =
524 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
525 attrs.push(attr);
526 }
527
528 Ok(attrs)
529}
530
531#[expect(clippy::too_many_lines)]
533fn decode_attribute_value(
534 flags: u8,
535 type_code: u8,
536 value: &[u8],
537 four_octet_as: bool,
538 add_path_families: &[(Afi, Safi)],
539) -> Result<PathAttribute, DecodeError> {
540 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
542 if let Some(expected) = expected_flags(type_code)
543 && (flags & flags_mask) != expected
544 {
545 return Err(DecodeError::UpdateAttributeError {
546 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
547 data: attr_error_data(flags, type_code, value),
548 detail: format!(
549 "type {} flags {:#04x} (expected {:#04x})",
550 type_code,
551 flags & flags_mask,
552 expected
553 ),
554 });
555 }
556
557 match type_code {
558 attr_type::ORIGIN => {
559 if value.len() != 1 {
560 return Err(DecodeError::UpdateAttributeError {
561 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
562 data: attr_error_data(flags, type_code, value),
563 detail: format!("ORIGIN length {} (expected 1)", value.len()),
564 });
565 }
566 match Origin::from_u8(value[0]) {
567 Some(origin) => Ok(PathAttribute::Origin(origin)),
568 None => Err(DecodeError::UpdateAttributeError {
569 subcode: update_subcode::INVALID_ORIGIN,
570 data: attr_error_data(flags, type_code, value),
571 detail: format!("invalid ORIGIN value {}", value[0]),
572 }),
573 }
574 }
575
576 attr_type::AS_PATH => {
577 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
578 DecodeError::UpdateAttributeError {
579 subcode: update_subcode::MALFORMED_AS_PATH,
580 data: attr_error_data(flags, type_code, value),
581 detail: e.to_string(),
582 }
583 })?;
584 Ok(PathAttribute::AsPath(AsPath { segments }))
585 }
586
587 attr_type::NEXT_HOP => {
588 if value.len() != 4 {
589 return Err(DecodeError::UpdateAttributeError {
590 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
591 data: attr_error_data(flags, type_code, value),
592 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
593 });
594 }
595 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
596 Ok(PathAttribute::NextHop(addr))
597 }
598
599 attr_type::MULTI_EXIT_DISC => {
600 if value.len() != 4 {
601 return Err(DecodeError::UpdateAttributeError {
602 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
603 data: attr_error_data(flags, type_code, value),
604 detail: format!("MED length {} (expected 4)", value.len()),
605 });
606 }
607 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
608 Ok(PathAttribute::Med(med))
609 }
610
611 attr_type::LOCAL_PREF => {
612 if value.len() != 4 {
613 return Err(DecodeError::UpdateAttributeError {
614 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
615 data: attr_error_data(flags, type_code, value),
616 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
617 });
618 }
619 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
620 Ok(PathAttribute::LocalPref(lp))
621 }
622
623 attr_type::COMMUNITIES => {
624 if !value.len().is_multiple_of(4) {
625 return Err(DecodeError::UpdateAttributeError {
626 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
627 data: attr_error_data(flags, type_code, value),
628 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
629 });
630 }
631 let communities = value
632 .chunks_exact(4)
633 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
634 .collect();
635 Ok(PathAttribute::Communities(communities))
636 }
637
638 attr_type::EXTENDED_COMMUNITIES => {
639 if !value.len().is_multiple_of(8) {
640 return Err(DecodeError::UpdateAttributeError {
641 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
642 data: attr_error_data(flags, type_code, value),
643 detail: format!(
644 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
645 value.len()
646 ),
647 });
648 }
649 let communities = value
650 .chunks_exact(8)
651 .map(|c| {
652 ExtendedCommunity::new(u64::from_be_bytes([
653 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
654 ]))
655 })
656 .collect();
657 Ok(PathAttribute::ExtendedCommunities(communities))
658 }
659
660 attr_type::ORIGINATOR_ID => {
661 if value.len() != 4 {
662 return Err(DecodeError::UpdateAttributeError {
663 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
664 data: attr_error_data(flags, type_code, value),
665 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
666 });
667 }
668 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
669 Ok(PathAttribute::OriginatorId(addr))
670 }
671
672 attr_type::CLUSTER_LIST => {
673 if !value.len().is_multiple_of(4) {
674 return Err(DecodeError::UpdateAttributeError {
675 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
676 data: attr_error_data(flags, type_code, value),
677 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
678 });
679 }
680 let ids = value
681 .chunks_exact(4)
682 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
683 .collect();
684 Ok(PathAttribute::ClusterList(ids))
685 }
686
687 attr_type::LARGE_COMMUNITIES => {
688 if value.is_empty() || !value.len().is_multiple_of(12) {
689 return Err(DecodeError::UpdateAttributeError {
690 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
691 data: attr_error_data(flags, type_code, value),
692 detail: format!(
693 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
694 value.len()
695 ),
696 });
697 }
698 let communities = value
699 .chunks_exact(12)
700 .map(|c| {
701 LargeCommunity::new(
702 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
703 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
704 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
705 )
706 })
707 .collect();
708 Ok(PathAttribute::LargeCommunities(communities))
709 }
710
711 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
712 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
713
714 _ => Ok(PathAttribute::Unknown(RawAttribute {
716 flags,
717 type_code,
718 data: Bytes::copy_from_slice(value),
719 })),
720 }
721}
722
723#[expect(clippy::too_many_lines)]
728fn decode_mp_reach_nlri(
729 value: &[u8],
730 add_path_families: &[(Afi, Safi)],
731) -> Result<PathAttribute, DecodeError> {
732 if value.len() < 5 {
733 return Err(DecodeError::MalformedField {
734 message_type: "UPDATE",
735 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
736 });
737 }
738
739 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
740 let safi_raw = value[2];
741 let nh_len = value[3] as usize;
742
743 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
744 message_type: "UPDATE",
745 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
746 })?;
747 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
748 message_type: "UPDATE",
749 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
750 })?;
751
752 if value.len() < 4 + nh_len + 1 {
754 return Err(DecodeError::MalformedField {
755 message_type: "UPDATE",
756 detail: format!(
757 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
758 value.len()
759 ),
760 });
761 }
762
763 let nh_bytes = &value[4..4 + nh_len];
764 let next_hop = if safi == Safi::FlowSpec {
766 if nh_len != 0 {
767 return Err(DecodeError::MalformedField {
768 message_type: "UPDATE",
769 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
770 });
771 }
772 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
773 } else {
774 match afi {
775 Afi::Ipv4 => match nh_len {
776 4 => IpAddr::V4(Ipv4Addr::new(
777 nh_bytes[0],
778 nh_bytes[1],
779 nh_bytes[2],
780 nh_bytes[3],
781 )),
782 16 | 32 => {
783 let mut octets = [0u8; 16];
784 octets.copy_from_slice(&nh_bytes[..16]);
785 IpAddr::V6(Ipv6Addr::from(octets))
786 }
787 _ => {
788 return Err(DecodeError::MalformedField {
789 message_type: "UPDATE",
790 detail: format!(
791 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
792 ),
793 });
794 }
795 },
796 Afi::Ipv6 => {
797 if nh_len != 16 && nh_len != 32 {
798 return Err(DecodeError::MalformedField {
799 message_type: "UPDATE",
800 detail: format!(
801 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
802 ),
803 });
804 }
805 let mut octets = [0u8; 16];
807 octets.copy_from_slice(&nh_bytes[..16]);
808 IpAddr::V6(Ipv6Addr::from(octets))
809 }
810 }
811 };
812
813 let nlri_start = 4 + nh_len + 1;
815 let nlri_bytes = &value[nlri_start..];
816
817 if safi == Safi::FlowSpec {
819 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
820 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
821 afi,
822 safi,
823 next_hop,
824 announced: vec![],
825 flowspec_announced: flowspec_rules,
826 }));
827 }
828
829 let add_path = add_path_families.contains(&(afi, safi));
830 let announced = match (afi, add_path) {
831 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
832 .into_iter()
833 .map(|p| NlriEntry {
834 path_id: 0,
835 prefix: Prefix::V4(p),
836 })
837 .collect(),
838 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
839 .into_iter()
840 .map(|e| NlriEntry {
841 path_id: e.path_id,
842 prefix: Prefix::V4(e.prefix),
843 })
844 .collect(),
845 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
846 .into_iter()
847 .map(|p| NlriEntry {
848 path_id: 0,
849 prefix: Prefix::V6(p),
850 })
851 .collect(),
852 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
853 };
854
855 Ok(PathAttribute::MpReachNlri(MpReachNlri {
856 afi,
857 safi,
858 next_hop,
859 announced,
860 flowspec_announced: vec![],
861 }))
862}
863
864fn decode_mp_unreach_nlri(
869 value: &[u8],
870 add_path_families: &[(Afi, Safi)],
871) -> Result<PathAttribute, DecodeError> {
872 if value.len() < 3 {
873 return Err(DecodeError::MalformedField {
874 message_type: "UPDATE",
875 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
876 });
877 }
878
879 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
880 let safi_raw = value[2];
881
882 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
883 message_type: "UPDATE",
884 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
885 })?;
886 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
887 message_type: "UPDATE",
888 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
889 })?;
890
891 let withdrawn_bytes = &value[3..];
892
893 if safi == Safi::FlowSpec {
895 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
896 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
897 afi,
898 safi,
899 withdrawn: vec![],
900 flowspec_withdrawn: flowspec_rules,
901 }));
902 }
903
904 let add_path = add_path_families.contains(&(afi, safi));
905 let withdrawn = match (afi, add_path) {
906 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
907 .into_iter()
908 .map(|p| NlriEntry {
909 path_id: 0,
910 prefix: Prefix::V4(p),
911 })
912 .collect(),
913 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
914 .into_iter()
915 .map(|e| NlriEntry {
916 path_id: e.path_id,
917 prefix: Prefix::V4(e.prefix),
918 })
919 .collect(),
920 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
921 .into_iter()
922 .map(|p| NlriEntry {
923 path_id: 0,
924 prefix: Prefix::V6(p),
925 })
926 .collect(),
927 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
928 };
929
930 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
931 afi,
932 safi,
933 withdrawn,
934 flowspec_withdrawn: vec![],
935 }))
936}
937
938fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
940 let as_size: usize = if four_octet_as { 4 } else { 2 };
941 let mut segments = Vec::new();
942
943 while !buf.is_empty() {
944 if buf.len() < 2 {
945 return Err(DecodeError::MalformedField {
946 message_type: "UPDATE",
947 detail: "truncated AS_PATH segment header".to_string(),
948 });
949 }
950
951 let seg_type = buf[0];
952 let seg_count = buf[1] as usize;
953 buf = &buf[2..];
954
955 let needed = seg_count * as_size;
956 if buf.len() < needed {
957 return Err(DecodeError::MalformedField {
958 message_type: "UPDATE",
959 detail: format!(
960 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
961 buf.len()
962 ),
963 });
964 }
965
966 let mut asns = Vec::with_capacity(seg_count);
967 for _ in 0..seg_count {
968 let asn = if four_octet_as {
969 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
970 buf = &buf[4..];
971 v
972 } else {
973 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
974 buf = &buf[2..];
975 v
976 };
977 asns.push(asn);
978 }
979
980 match seg_type {
981 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
982 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
983 _ => {
984 return Err(DecodeError::MalformedField {
985 message_type: "UPDATE",
986 detail: format!("unknown AS_PATH segment type {seg_type}"),
987 });
988 }
989 }
990 }
991
992 Ok(segments)
993}
994
995pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
998 let mut buf = Vec::with_capacity(3 + value.len());
999 if value.len() > 255 {
1000 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1001 buf.push(type_code);
1002 #[expect(clippy::cast_possible_truncation)]
1003 let len = value.len() as u16;
1004 buf.extend_from_slice(&len.to_be_bytes());
1005 } else {
1006 buf.push(flags);
1007 buf.push(type_code);
1008 #[expect(clippy::cast_possible_truncation)]
1009 buf.push(value.len() as u8);
1010 }
1011 buf.extend_from_slice(value);
1012 buf
1013}
1014
1015fn expected_flags(type_code: u8) -> Option<u8> {
1018 match type_code {
1019 attr_type::ORIGIN
1021 | attr_type::AS_PATH
1022 | attr_type::NEXT_HOP
1023 | attr_type::LOCAL_PREF
1024 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1025 attr_type::MULTI_EXIT_DISC
1028 | attr_type::ORIGINATOR_ID
1029 | attr_type::CLUSTER_LIST
1030 | attr_type::MP_REACH_NLRI
1031 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1032 attr_type::AGGREGATOR
1034 | attr_type::COMMUNITIES
1035 | attr_type::EXTENDED_COMMUNITIES
1036 | attr_type::LARGE_COMMUNITIES => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1037 _ => None,
1038 }
1039}
1040
1041pub fn encode_path_attributes(
1049 attrs: &[PathAttribute],
1050 buf: &mut Vec<u8>,
1051 four_octet_as: bool,
1052 add_path_mp: bool,
1053) {
1054 for attr in attrs {
1055 let mut value = Vec::new();
1056 let flags;
1057 let type_code;
1058
1059 match attr {
1060 PathAttribute::Origin(origin) => {
1061 flags = attr_flags::TRANSITIVE;
1062 type_code = attr_type::ORIGIN;
1063 value.push(*origin as u8);
1064 }
1065 PathAttribute::AsPath(as_path) => {
1066 flags = attr_flags::TRANSITIVE;
1067 type_code = attr_type::AS_PATH;
1068 encode_as_path(as_path, &mut value, four_octet_as);
1069 }
1070 PathAttribute::NextHop(addr) => {
1071 flags = attr_flags::TRANSITIVE;
1072 type_code = attr_type::NEXT_HOP;
1073 value.extend_from_slice(&addr.octets());
1074 }
1075 PathAttribute::Med(med) => {
1076 flags = attr_flags::OPTIONAL;
1077 type_code = attr_type::MULTI_EXIT_DISC;
1078 value.extend_from_slice(&med.to_be_bytes());
1079 }
1080 PathAttribute::LocalPref(lp) => {
1081 flags = attr_flags::TRANSITIVE;
1082 type_code = attr_type::LOCAL_PREF;
1083 value.extend_from_slice(&lp.to_be_bytes());
1084 }
1085 PathAttribute::Communities(communities) => {
1086 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1087 type_code = attr_type::COMMUNITIES;
1088 for &c in communities {
1089 value.extend_from_slice(&c.to_be_bytes());
1090 }
1091 }
1092 PathAttribute::ExtendedCommunities(communities) => {
1093 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1094 type_code = attr_type::EXTENDED_COMMUNITIES;
1095 for &c in communities {
1096 value.extend_from_slice(&c.as_u64().to_be_bytes());
1097 }
1098 }
1099 PathAttribute::LargeCommunities(communities) => {
1100 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1101 type_code = attr_type::LARGE_COMMUNITIES;
1102 for &c in communities {
1103 value.extend_from_slice(&c.global_admin.to_be_bytes());
1104 value.extend_from_slice(&c.local_data1.to_be_bytes());
1105 value.extend_from_slice(&c.local_data2.to_be_bytes());
1106 }
1107 }
1108 PathAttribute::OriginatorId(addr) => {
1109 flags = attr_flags::OPTIONAL;
1110 type_code = attr_type::ORIGINATOR_ID;
1111 value.extend_from_slice(&addr.octets());
1112 }
1113 PathAttribute::ClusterList(ids) => {
1114 flags = attr_flags::OPTIONAL;
1115 type_code = attr_type::CLUSTER_LIST;
1116 for id in ids {
1117 value.extend_from_slice(&id.octets());
1118 }
1119 }
1120 PathAttribute::MpReachNlri(mp) => {
1121 flags = attr_flags::OPTIONAL;
1122 type_code = attr_type::MP_REACH_NLRI;
1123 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1124 }
1125 PathAttribute::MpUnreachNlri(mp) => {
1126 flags = attr_flags::OPTIONAL;
1127 type_code = attr_type::MP_UNREACH_NLRI;
1128 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1129 }
1130 PathAttribute::Unknown(raw) => {
1131 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1135 flags = if (raw.flags & optional_transitive) == optional_transitive {
1136 raw.flags | attr_flags::PARTIAL
1137 } else {
1138 raw.flags
1139 };
1140 type_code = raw.type_code;
1141 value.extend_from_slice(&raw.data);
1142 }
1143 }
1144
1145 if value.len() > 255 {
1147 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1148 buf.push(type_code);
1149 #[expect(clippy::cast_possible_truncation)]
1150 let len = value.len() as u16;
1151 buf.extend_from_slice(&len.to_be_bytes());
1152 } else {
1153 buf.push(flags);
1154 buf.push(type_code);
1155 #[expect(clippy::cast_possible_truncation)]
1156 buf.push(value.len() as u8);
1157 }
1158 buf.extend_from_slice(&value);
1159 }
1160}
1161
1162fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1167 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1168 buf.push(mp.safi as u8);
1169
1170 if mp.safi == Safi::FlowSpec {
1172 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1175 return;
1176 }
1177
1178 match mp.next_hop {
1179 IpAddr::V4(addr) => {
1180 buf.push(4); buf.extend_from_slice(&addr.octets());
1182 }
1183 IpAddr::V6(addr) => {
1184 buf.push(16); buf.extend_from_slice(&addr.octets());
1186 }
1187 }
1188
1189 buf.push(0); if add_path {
1192 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1193 } else {
1194 for entry in &mp.announced {
1195 match entry.prefix {
1196 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1197 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1198 }
1199 }
1200 }
1201}
1202
1203fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1207 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1208 buf.push(mp.safi as u8);
1209
1210 if mp.safi == Safi::FlowSpec {
1212 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1213 return;
1214 }
1215
1216 if add_path {
1217 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1218 } else {
1219 for entry in &mp.withdrawn {
1220 match entry.prefix {
1221 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1222 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1223 }
1224 }
1225 }
1226}
1227
1228fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1230 for segment in &as_path.segments {
1231 let (seg_type, asns) = match segment {
1232 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1233 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1234 };
1235 for chunk in asns.chunks(u8::MAX as usize) {
1236 buf.push(seg_type);
1237 #[expect(clippy::cast_possible_truncation)]
1238 buf.push(chunk.len() as u8);
1239 for &asn in chunk {
1240 if four_octet_as {
1241 buf.extend_from_slice(&asn.to_be_bytes());
1242 } else {
1243 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1246 buf.extend_from_slice(&as2.to_be_bytes());
1247 }
1248 }
1249 }
1250 }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255 use super::*;
1256
1257 #[test]
1258 fn origin_from_u8_roundtrip() {
1259 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1260 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1261 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1262 assert_eq!(Origin::from_u8(3), None);
1263 }
1264
1265 #[test]
1266 fn origin_ordering() {
1267 assert!(Origin::Igp < Origin::Egp);
1268 assert!(Origin::Egp < Origin::Incomplete);
1269 }
1270
1271 #[test]
1272 fn as_path_length_calculation() {
1273 let path = AsPath {
1274 segments: vec![
1275 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1276 AsPathSegment::AsSet(vec![65004, 65005]),
1277 ],
1278 };
1279 assert_eq!(path.len(), 4);
1281 }
1282
1283 #[test]
1284 fn as_path_empty() {
1285 let path = AsPath { segments: vec![] };
1286 assert!(path.is_empty());
1287 assert_eq!(path.len(), 0);
1288 }
1289
1290 #[test]
1291 fn contains_asn_in_sequence() {
1292 let path = AsPath {
1293 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1294 };
1295 assert!(path.contains_asn(65002));
1296 assert!(!path.contains_asn(65004));
1297 }
1298
1299 #[test]
1300 fn contains_asn_in_set() {
1301 let path = AsPath {
1302 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1303 };
1304 assert!(path.contains_asn(65005));
1305 assert!(!path.contains_asn(65001));
1306 }
1307
1308 #[test]
1309 fn contains_asn_multiple_segments() {
1310 let path = AsPath {
1311 segments: vec![
1312 AsPathSegment::AsSequence(vec![65001, 65002]),
1313 AsPathSegment::AsSet(vec![65003]),
1314 ],
1315 };
1316 assert!(path.contains_asn(65001));
1317 assert!(path.contains_asn(65003));
1318 assert!(!path.contains_asn(65004));
1319 }
1320
1321 #[test]
1322 fn contains_asn_empty_path() {
1323 let path = AsPath { segments: vec![] };
1324 assert!(!path.contains_asn(65001));
1325 }
1326
1327 #[test]
1328 fn is_private_asn_boundaries() {
1329 assert!(!is_private_asn(64_511));
1331 assert!(is_private_asn(64_512));
1332 assert!(is_private_asn(65_534));
1333 assert!(!is_private_asn(65_535));
1334
1335 assert!(!is_private_asn(4_199_999_999));
1337 assert!(is_private_asn(4_200_000_000));
1338 assert!(is_private_asn(4_294_967_294));
1339 assert!(!is_private_asn(4_294_967_295));
1340 }
1341
1342 #[test]
1343 fn all_private_empty_path_is_false() {
1344 let path = AsPath { segments: vec![] };
1345 assert!(!path.all_private());
1346 }
1347
1348 #[test]
1349 fn all_private_mixed_segments() {
1350 let path = AsPath {
1351 segments: vec![
1352 AsPathSegment::AsSet(vec![64_512, 65_000]),
1353 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
1354 ],
1355 };
1356 assert!(path.all_private());
1357
1358 let non_private = AsPath {
1359 segments: vec![
1360 AsPathSegment::AsSet(vec![64_512, 65_000]),
1361 AsPathSegment::AsSequence(vec![65_535]),
1362 ],
1363 };
1364 assert!(!non_private.all_private());
1365 }
1366
1367 #[test]
1368 fn decode_origin_igp() {
1369 let buf = [0x40, 0x01, 0x01, 0x00];
1371 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1372 assert_eq!(attrs.len(), 1);
1373 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1374 }
1375
1376 #[test]
1377 fn decode_origin_egp() {
1378 let buf = [0x40, 0x01, 0x01, 0x01];
1379 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1380 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
1381 }
1382
1383 #[test]
1384 fn decode_origin_invalid_value() {
1385 let buf = [0x40, 0x01, 0x01, 0x05];
1387 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1388 match &err {
1389 DecodeError::UpdateAttributeError { subcode, .. } => {
1390 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1391 }
1392 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1393 }
1394 }
1395
1396 #[test]
1397 fn decode_next_hop() {
1398 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
1400 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1401 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1402 }
1403
1404 #[test]
1405 fn decode_med() {
1406 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
1408 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1409 assert_eq!(attrs[0], PathAttribute::Med(100));
1410 }
1411
1412 #[test]
1413 fn decode_local_pref() {
1414 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
1416 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1417 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
1418 }
1419
1420 #[test]
1421 fn decode_as_path_4byte() {
1422 let buf = [
1425 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1430 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1431 assert_eq!(
1432 attrs[0],
1433 PathAttribute::AsPath(AsPath {
1434 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1435 })
1436 );
1437 }
1438
1439 #[test]
1440 fn decode_as_path_2byte() {
1441 let buf = [
1444 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
1449 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
1450 assert_eq!(
1451 attrs[0],
1452 PathAttribute::AsPath(AsPath {
1453 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
1454 })
1455 );
1456 }
1457
1458 #[test]
1459 fn decode_unknown_attribute_preserved() {
1460 let buf = [0xC0, 99, 0x03, 1, 2, 3];
1462 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1463 assert_eq!(
1464 attrs[0],
1465 PathAttribute::Unknown(RawAttribute {
1466 flags: 0xC0,
1467 type_code: 99,
1468 data: Bytes::from_static(&[1, 2, 3]),
1469 })
1470 );
1471 }
1472
1473 #[test]
1474 fn decode_atomic_aggregate_as_unknown() {
1475 let buf = [0x40, 0x06, 0x00];
1477 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1478 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
1479 }
1480
1481 #[test]
1482 fn decode_extended_length() {
1483 let buf = [
1486 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1491 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1492 assert_eq!(
1493 attrs[0],
1494 PathAttribute::AsPath(AsPath {
1495 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1496 })
1497 );
1498 }
1499
1500 #[test]
1501 fn decode_multiple_attributes() {
1502 let mut buf = Vec::new();
1503 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
1505 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
1507 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
1509
1510 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1511 assert_eq!(attrs.len(), 3);
1512 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1513 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1514 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
1515 }
1516
1517 #[test]
1518 fn roundtrip_attributes_4byte() {
1519 let attrs = vec![
1520 PathAttribute::Origin(Origin::Igp),
1521 PathAttribute::AsPath(AsPath {
1522 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
1523 }),
1524 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
1525 PathAttribute::Med(100),
1526 PathAttribute::LocalPref(200),
1527 ];
1528
1529 let mut buf = Vec::new();
1530 encode_path_attributes(&attrs, &mut buf, true, false);
1531 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1532 assert_eq!(decoded, attrs);
1533 }
1534
1535 #[test]
1536 fn roundtrip_attributes_2byte() {
1537 let attrs = vec![
1538 PathAttribute::Origin(Origin::Egp),
1539 PathAttribute::AsPath(AsPath {
1540 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
1541 }),
1542 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
1543 ];
1544
1545 let mut buf = Vec::new();
1546 encode_path_attributes(&attrs, &mut buf, false, false);
1547 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
1548 assert_eq!(decoded, attrs);
1549 }
1550
1551 #[test]
1552 fn reject_truncated_attribute_header() {
1553 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
1555 }
1556
1557 #[test]
1558 fn reject_truncated_attribute_value() {
1559 let buf = [0x40, 0x01, 0x01];
1561 assert!(decode_path_attributes(&buf, true, &[]).is_err());
1562 }
1563
1564 #[test]
1565 fn reject_bad_origin_length() {
1566 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
1568 assert!(decode_path_attributes(&buf, true, &[]).is_err());
1569 }
1570
1571 #[test]
1572 fn as_path_with_set_and_sequence() {
1573 let attrs = vec![PathAttribute::AsPath(AsPath {
1575 segments: vec![
1576 AsPathSegment::AsSequence(vec![65001]),
1577 AsPathSegment::AsSet(vec![65002, 65003]),
1578 ],
1579 })];
1580
1581 let mut buf = Vec::new();
1582 encode_path_attributes(&attrs, &mut buf, true, false);
1583 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1584 assert_eq!(decoded, attrs);
1585 }
1586
1587 #[test]
1588 fn decode_communities_single() {
1589 let community: u32 = (65001 << 16) | 0x0064;
1592 let bytes = community.to_be_bytes();
1593 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
1594 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1595 assert_eq!(attrs.len(), 1);
1596 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
1597 }
1598
1599 #[test]
1600 fn decode_communities_multiple() {
1601 let c1: u32 = (65001 << 16) | 0x0064;
1602 let c2: u32 = (65002 << 16) | 0x00C8;
1603 let b1 = c1.to_be_bytes();
1604 let b2 = c2.to_be_bytes();
1605 let buf = [
1606 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
1607 ];
1608 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1609 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
1610 }
1611
1612 #[test]
1613 fn decode_communities_empty() {
1614 let buf = [0xC0, 0x08, 0x00];
1616 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1617 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
1618 }
1619
1620 #[test]
1621 fn decode_communities_odd_length_rejected() {
1622 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
1624 assert!(decode_path_attributes(&buf, true, &[]).is_err());
1625 }
1626
1627 #[test]
1628 fn communities_roundtrip() {
1629 let c1: u32 = (65001 << 16) | 0x0064;
1630 let c2: u32 = (65002 << 16) | 0x00C8;
1631 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
1632
1633 let mut buf = Vec::new();
1634 encode_path_attributes(&attrs, &mut buf, true, false);
1635 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1636 assert_eq!(decoded, attrs);
1637 }
1638
1639 #[test]
1640 fn communities_type_code_and_flags() {
1641 let attr = PathAttribute::Communities(vec![]);
1642 assert_eq!(attr.type_code(), 8);
1643 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
1644 }
1645
1646 #[test]
1649 fn decode_extended_communities_single() {
1650 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
1652 let bytes = ec.as_u64().to_be_bytes();
1653 let buf = [
1654 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
1655 bytes[7],
1656 ];
1657 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1658 assert_eq!(attrs.len(), 1);
1659 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
1660 }
1661
1662 #[test]
1663 fn decode_extended_communities_multiple() {
1664 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
1665 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
1666 let b1 = ec1.as_u64().to_be_bytes();
1667 let b2 = ec2.as_u64().to_be_bytes();
1668 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
1670 buf.extend_from_slice(&b2);
1671 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1672 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
1673 }
1674
1675 #[test]
1676 fn decode_extended_communities_empty() {
1677 let buf = [0xC0, 0x10, 0x00];
1678 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1679 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
1680 }
1681
1682 #[test]
1683 fn decode_extended_communities_bad_length() {
1684 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
1686 assert!(decode_path_attributes(&buf, true, &[]).is_err());
1687 }
1688
1689 #[test]
1690 fn extended_communities_roundtrip() {
1691 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
1692 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
1693 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
1694
1695 let mut buf = Vec::new();
1696 encode_path_attributes(&attrs, &mut buf, true, false);
1697 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1698 assert_eq!(decoded, attrs);
1699 }
1700
1701 #[test]
1702 fn extended_communities_type_code_and_flags() {
1703 let attr = PathAttribute::ExtendedCommunities(vec![]);
1704 assert_eq!(attr.type_code(), 16);
1705 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
1706 }
1707
1708 #[test]
1709 fn extended_community_type_subtype() {
1710 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
1712 assert_eq!(ec.type_byte(), 0x00);
1713 assert_eq!(ec.subtype(), 0x02);
1714 assert!(ec.is_transitive());
1715 }
1716
1717 #[test]
1718 fn extended_community_route_target() {
1719 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
1721 assert_eq!(ec.route_target(), Some((65001, 100)));
1722 assert_eq!(ec.route_origin(), None);
1723
1724 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
1726 assert_eq!(ec4.route_target(), Some((65537, 200)));
1727
1728 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
1731 let (g, l) = ec_ipv4.route_target().unwrap();
1732 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
1734 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
1736 }
1737
1738 #[test]
1739 fn extended_community_is_transitive() {
1740 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
1742 assert!(t.is_transitive());
1743
1744 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
1746 assert!(!nt.is_transitive());
1747 }
1748
1749 #[test]
1750 fn extended_community_display() {
1751 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
1752 assert_eq!(rt.to_string(), "RT:65001:100");
1753
1754 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
1755 assert_eq!(ro.to_string(), "RO:65001:100");
1756
1757 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
1759 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
1760
1761 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
1763 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
1764
1765 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
1767 assert_eq!(rt_as4.to_string(), "RT:65537:200");
1768
1769 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
1771 assert_eq!(opaque.to_string(), "0x4300123456789abc");
1772 }
1773
1774 #[test]
1775 fn unknown_attribute_roundtrip() {
1776 let attrs = vec![PathAttribute::Unknown(RawAttribute {
1779 flags: 0xC0,
1780 type_code: 99,
1781 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
1782 })];
1783
1784 let mut buf = Vec::new();
1785 encode_path_attributes(&attrs, &mut buf, true, false);
1786 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1787 assert_eq!(
1788 decoded,
1789 vec![PathAttribute::Unknown(RawAttribute {
1790 flags: 0xE0, type_code: 99,
1792 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
1793 })]
1794 );
1795 }
1796
1797 #[test]
1798 fn origin_with_optional_flag_rejected() {
1799 let buf = [0xC0, 0x01, 0x01, 0x00];
1801 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1802 match &err {
1803 DecodeError::UpdateAttributeError { subcode, .. } => {
1804 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
1805 }
1806 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1807 }
1808 }
1809
1810 #[test]
1811 fn med_with_transitive_flag_rejected() {
1812 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
1814 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1815 match &err {
1816 DecodeError::UpdateAttributeError { subcode, .. } => {
1817 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
1818 }
1819 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1820 }
1821 }
1822
1823 #[test]
1824 fn communities_without_optional_rejected() {
1825 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
1827 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1828 match &err {
1829 DecodeError::UpdateAttributeError { subcode, .. } => {
1830 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
1831 }
1832 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1833 }
1834 }
1835
1836 #[test]
1837 fn next_hop_length_error_subcode() {
1838 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
1840 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1841 match &err {
1842 DecodeError::UpdateAttributeError { subcode, .. } => {
1843 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
1844 }
1845 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1846 }
1847 }
1848
1849 #[test]
1850 fn invalid_origin_value_subcode() {
1851 let buf = [0x40, 0x01, 0x01, 0x05];
1853 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1854 match &err {
1855 DecodeError::UpdateAttributeError { subcode, .. } => {
1856 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1857 }
1858 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1859 }
1860 }
1861
1862 #[test]
1863 fn as_path_bad_segment_subcode() {
1864 let buf = [
1866 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
1870 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1871 match &err {
1872 DecodeError::UpdateAttributeError { subcode, .. } => {
1873 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
1874 }
1875 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1876 }
1877 }
1878
1879 #[test]
1880 fn encode_unknown_transitive_sets_partial() {
1881 let attr = PathAttribute::Unknown(RawAttribute {
1882 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
1884 data: Bytes::from_static(&[1, 2]),
1885 });
1886 let mut buf = Vec::new();
1887 encode_path_attributes(&[attr], &mut buf, true, false);
1888 assert_eq!(
1890 buf[0],
1891 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
1892 );
1893 }
1894
1895 #[test]
1896 fn encode_unknown_wellknown_transitive_no_partial() {
1897 let attr = PathAttribute::Unknown(RawAttribute {
1899 flags: attr_flags::TRANSITIVE, type_code: 99,
1901 data: Bytes::from_static(&[1, 2]),
1902 });
1903 let mut buf = Vec::new();
1904 encode_path_attributes(&[attr], &mut buf, true, false);
1905 assert_eq!(buf[0], attr_flags::TRANSITIVE);
1906 }
1907
1908 #[test]
1909 fn encode_unknown_nontransitive_no_partial() {
1910 let attr = PathAttribute::Unknown(RawAttribute {
1911 flags: attr_flags::OPTIONAL, type_code: 99,
1913 data: Bytes::from_static(&[1, 2]),
1914 });
1915 let mut buf = Vec::new();
1916 encode_path_attributes(&[attr], &mut buf, true, false);
1917 assert_eq!(buf[0], attr_flags::OPTIONAL);
1919 }
1920
1921 fn nlri(prefix: Prefix) -> NlriEntry {
1925 NlriEntry { path_id: 0, prefix }
1926 }
1927
1928 #[test]
1929 fn mp_reach_nlri_ipv6_roundtrip() {
1930 use crate::capability::{Afi, Safi};
1931 use crate::nlri::{Ipv6Prefix, Prefix};
1932
1933 let mp = MpReachNlri {
1934 afi: Afi::Ipv6,
1935 safi: Safi::Unicast,
1936 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
1937 announced: vec![
1938 nlri(Prefix::V6(Ipv6Prefix::new(
1939 "2001:db8:1::".parse().unwrap(),
1940 48,
1941 ))),
1942 nlri(Prefix::V6(Ipv6Prefix::new(
1943 "2001:db8:2::".parse().unwrap(),
1944 48,
1945 ))),
1946 ],
1947 flowspec_announced: vec![],
1948 };
1949 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
1950
1951 let mut buf = Vec::new();
1952 encode_path_attributes(&attrs, &mut buf, true, false);
1953 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1954 assert_eq!(decoded.len(), 1);
1955 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
1956 }
1957
1958 #[test]
1959 fn mp_unreach_nlri_ipv6_roundtrip() {
1960 use crate::capability::{Afi, Safi};
1961 use crate::nlri::{Ipv6Prefix, Prefix};
1962
1963 let mp = MpUnreachNlri {
1964 afi: Afi::Ipv6,
1965 safi: Safi::Unicast,
1966 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
1967 "2001:db8:1::".parse().unwrap(),
1968 48,
1969 )))],
1970 flowspec_withdrawn: vec![],
1971 };
1972 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
1973
1974 let mut buf = Vec::new();
1975 encode_path_attributes(&attrs, &mut buf, true, false);
1976 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1977 assert_eq!(decoded.len(), 1);
1978 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
1979 }
1980
1981 #[test]
1982 fn mp_reach_nlri_ipv4_roundtrip() {
1983 use crate::capability::{Afi, Safi};
1984 use crate::nlri::Prefix;
1985
1986 let mp = MpReachNlri {
1987 afi: Afi::Ipv4,
1988 safi: Safi::Unicast,
1989 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1990 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
1991 Ipv4Addr::new(10, 1, 0, 0),
1992 16,
1993 )))],
1994 flowspec_announced: vec![],
1995 };
1996 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
1997
1998 let mut buf = Vec::new();
1999 encode_path_attributes(&attrs, &mut buf, true, false);
2000 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2001 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2002 }
2003
2004 #[test]
2005 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2006 use crate::capability::{Afi, Safi};
2007 use crate::nlri::Prefix;
2008
2009 let mp = MpReachNlri {
2010 afi: Afi::Ipv4,
2011 safi: Safi::Unicast,
2012 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2013 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2014 Ipv4Addr::new(10, 1, 0, 0),
2015 16,
2016 )))],
2017 flowspec_announced: vec![],
2018 };
2019 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2020
2021 let mut buf = Vec::new();
2022 encode_path_attributes(&attrs, &mut buf, true, false);
2023 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2024 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2025 }
2026
2027 #[test]
2028 fn mp_reach_nlri_type_code_and_flags() {
2029 use crate::capability::{Afi, Safi};
2030
2031 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2032 afi: Afi::Ipv6,
2033 safi: Safi::Unicast,
2034 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2035 announced: vec![],
2036 flowspec_announced: vec![],
2037 });
2038 assert_eq!(attr.type_code(), 14);
2039 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2041 }
2042
2043 #[test]
2044 fn mp_unreach_nlri_type_code_and_flags() {
2045 use crate::capability::{Afi, Safi};
2046
2047 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2048 afi: Afi::Ipv6,
2049 safi: Safi::Unicast,
2050 withdrawn: vec![],
2051 flowspec_withdrawn: vec![],
2052 });
2053 assert_eq!(attr.type_code(), 15);
2054 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2055 }
2056
2057 #[test]
2058 fn mp_reach_nlri_empty_nlri() {
2059 use crate::capability::{Afi, Safi};
2060
2061 let mp = MpReachNlri {
2062 afi: Afi::Ipv6,
2063 safi: Safi::Unicast,
2064 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2065 announced: vec![],
2066 flowspec_announced: vec![],
2067 };
2068 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2069
2070 let mut buf = Vec::new();
2071 encode_path_attributes(&attrs, &mut buf, true, false);
2072 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2073 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2074 }
2075
2076 #[test]
2077 fn mp_reach_nlri_bad_flags_rejected() {
2078 let mut value = Vec::new();
2082 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();
2089 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2092 buf.push(value.len() as u8);
2093 buf.extend_from_slice(&value);
2094
2095 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2096 assert!(matches!(
2097 err,
2098 DecodeError::UpdateAttributeError {
2099 subcode: 4, ..
2101 }
2102 ));
2103 }
2104
2105 #[test]
2108 #[expect(clippy::cast_possible_truncation)]
2109 fn mp_reach_nlri_ipv4_addpath_decode() {
2110 use crate::capability::{Afi, Safi};
2111 use crate::nlri::Prefix;
2112
2113 let mut value = Vec::new();
2116 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());
2123 value.push(16);
2124 value.extend_from_slice(&[10, 1]);
2125
2126 let mut buf = Vec::new();
2127 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2130 buf.extend_from_slice(&value);
2131
2132 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2134 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2135 panic!("expected MpReachNlri");
2136 };
2137 assert_eq!(mp.announced.len(), 1);
2138 assert_eq!(mp.announced[0].path_id, 42);
2139 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2140
2141 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2144 }
2145
2146 #[test]
2147 #[expect(clippy::cast_possible_truncation)]
2148 fn mp_reach_nlri_ipv6_addpath_decode() {
2149 use crate::capability::{Afi, Safi};
2150 use crate::nlri::{Ipv6Prefix, Prefix};
2151
2152 let mut value = Vec::new();
2154 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());
2158 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2161 value.push(48);
2162 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2163
2164 let mut buf = Vec::new();
2165 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2168 buf.extend_from_slice(&value);
2169
2170 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2171 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2172 panic!("expected MpReachNlri");
2173 };
2174 assert_eq!(mp.announced.len(), 1);
2175 assert_eq!(mp.announced[0].path_id, 99);
2176 assert_eq!(
2177 mp.announced[0].prefix,
2178 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2179 );
2180 }
2181
2182 #[test]
2183 #[expect(clippy::cast_possible_truncation)]
2184 fn mp_unreach_nlri_ipv6_addpath_decode() {
2185 use crate::capability::{Afi, Safi};
2186 use crate::nlri::{Ipv6Prefix, Prefix};
2187
2188 let mut value = Vec::new();
2190 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2194 value.push(48);
2195 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2196
2197 let mut buf = Vec::new();
2198 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2201 buf.extend_from_slice(&value);
2202
2203 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2204 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2205 panic!("expected MpUnreachNlri");
2206 };
2207 assert_eq!(mp.withdrawn.len(), 1);
2208 assert_eq!(mp.withdrawn[0].path_id, 7);
2209 assert_eq!(
2210 mp.withdrawn[0].prefix,
2211 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2212 );
2213 }
2214
2215 #[test]
2216 fn mp_reach_addpath_only_applies_to_matching_family() {
2217 use crate::capability::{Afi, Safi};
2218 use crate::nlri::{Ipv6Prefix, Prefix};
2219
2220 let mp = MpReachNlri {
2222 afi: Afi::Ipv6,
2223 safi: Safi::Unicast,
2224 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2225 announced: vec![NlriEntry {
2226 path_id: 0,
2227 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2228 }],
2229 flowspec_announced: vec![],
2230 };
2231 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2232
2233 let mut buf = Vec::new();
2234 encode_path_attributes(&attrs, &mut buf, true, false);
2235
2236 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2238 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2239 }
2240
2241 #[test]
2244 fn decode_originator_id() {
2245 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2247 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2248 assert_eq!(
2249 attrs[0],
2250 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2251 );
2252 }
2253
2254 #[test]
2255 fn originator_id_roundtrip() {
2256 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
2257 let mut buf = Vec::new();
2258 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2259 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2260 assert_eq!(decoded, vec![attr]);
2261 }
2262
2263 #[test]
2264 fn originator_id_wrong_length() {
2265 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
2267 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2268 assert!(matches!(
2269 err,
2270 DecodeError::UpdateAttributeError {
2271 subcode: 5, ..
2273 }
2274 ));
2275 }
2276
2277 #[test]
2278 fn originator_id_wrong_flags() {
2279 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
2281 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2282 assert!(matches!(
2283 err,
2284 DecodeError::UpdateAttributeError {
2285 subcode: 4, ..
2287 }
2288 ));
2289 }
2290
2291 #[test]
2294 fn decode_cluster_list() {
2295 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
2297 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2298 assert_eq!(
2299 attrs[0],
2300 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
2301 );
2302 }
2303
2304 #[test]
2305 fn cluster_list_roundtrip() {
2306 let attr = PathAttribute::ClusterList(vec![
2307 Ipv4Addr::new(10, 0, 0, 1),
2308 Ipv4Addr::new(10, 0, 0, 2),
2309 ]);
2310 let mut buf = Vec::new();
2311 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2312 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2313 assert_eq!(decoded, vec![attr]);
2314 }
2315
2316 #[test]
2317 fn cluster_list_wrong_length() {
2318 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
2320 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2321 assert!(matches!(
2322 err,
2323 DecodeError::UpdateAttributeError {
2324 subcode: 5, ..
2326 }
2327 ));
2328 }
2329
2330 #[test]
2335 fn large_community_display() {
2336 let lc = LargeCommunity::new(65001, 100, 200);
2337 assert_eq!(lc.to_string(), "65001:100:200");
2338 }
2339
2340 #[test]
2341 fn large_community_type_code_and_flags() {
2342 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
2343 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
2344 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2345 }
2346
2347 #[test]
2348 fn decode_large_community_single() {
2349 let mut buf = vec![0xC0, 32, 12];
2351 buf.extend_from_slice(&65001u32.to_be_bytes());
2352 buf.extend_from_slice(&100u32.to_be_bytes());
2353 buf.extend_from_slice(&200u32.to_be_bytes());
2354 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2355 assert_eq!(attrs.len(), 1);
2356 assert_eq!(
2357 attrs[0],
2358 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
2359 );
2360 }
2361
2362 #[test]
2363 fn decode_large_community_multiple() {
2364 let mut buf = vec![0xC0, 32, 24];
2366 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
2367 buf.extend_from_slice(&g.to_be_bytes());
2368 buf.extend_from_slice(&l1.to_be_bytes());
2369 buf.extend_from_slice(&l2.to_be_bytes());
2370 }
2371 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2372 assert_eq!(
2373 attrs[0],
2374 PathAttribute::LargeCommunities(vec![
2375 LargeCommunity::new(65001, 100, 200),
2376 LargeCommunity::new(65002, 300, 400),
2377 ])
2378 );
2379 }
2380
2381 #[test]
2382 fn decode_large_community_bad_length() {
2383 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
2385 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2386 assert!(matches!(
2387 err,
2388 DecodeError::UpdateAttributeError {
2389 subcode: 5, ..
2391 }
2392 ));
2393 }
2394
2395 #[test]
2396 fn decode_large_community_empty_rejected() {
2397 let buf = [0xC0, 32, 0];
2399 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2400 assert!(matches!(
2401 err,
2402 DecodeError::UpdateAttributeError {
2403 subcode: 5, ..
2405 }
2406 ));
2407 }
2408
2409 #[test]
2410 fn large_community_roundtrip() {
2411 let lcs = vec![
2412 LargeCommunity::new(65001, 100, 200),
2413 LargeCommunity::new(0, u32::MAX, 42),
2414 ];
2415 let attr = PathAttribute::LargeCommunities(lcs.clone());
2416 let mut buf = Vec::new();
2417 encode_path_attributes(&[attr], &mut buf, true, false);
2418 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2419 assert_eq!(decoded.len(), 1);
2420 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
2421 }
2422
2423 #[test]
2424 fn large_community_expected_flags_validated() {
2425 let mut buf = vec![0x40, 32, 12];
2427 buf.extend_from_slice(&1u32.to_be_bytes());
2428 buf.extend_from_slice(&2u32.to_be_bytes());
2429 buf.extend_from_slice(&3u32.to_be_bytes());
2430 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2431 assert!(matches!(
2432 err,
2433 DecodeError::UpdateAttributeError {
2434 subcode: 4, ..
2436 }
2437 ));
2438 }
2439
2440 #[test]
2445 fn aspath_string_sequence() {
2446 let p = AsPath {
2447 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2448 };
2449 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
2450 }
2451
2452 #[test]
2453 fn aspath_string_set() {
2454 let p = AsPath {
2455 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
2456 };
2457 assert_eq!(p.to_aspath_string(), "{65003 65004}");
2458 }
2459
2460 #[test]
2461 fn aspath_string_mixed() {
2462 let p = AsPath {
2463 segments: vec![
2464 AsPathSegment::AsSequence(vec![65001, 65002]),
2465 AsPathSegment::AsSet(vec![65003, 65004]),
2466 ],
2467 };
2468 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
2469 }
2470
2471 #[test]
2472 fn aspath_string_empty() {
2473 let p = AsPath { segments: vec![] };
2474 assert_eq!(p.to_aspath_string(), "");
2475 }
2476}