mod aspath;
mod nlri;
mod origin;
use crate::models::network::*;
use bitflags::bitflags;
use num_enum::{FromPrimitive, IntoPrimitive};
use std::cmp::Ordering;
use std::iter::{FromIterator, Map};
use std::net::IpAddr;
use std::slice::Iter;
use std::vec::IntoIter;
use crate::models::*;
pub use aspath::*;
pub use nlri::*;
pub use origin::*;
bitflags! {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AttrFlags: u8 {
const OPTIONAL = 0b10000000;
const TRANSITIVE = 0b01000000;
const PARTIAL = 0b00100000;
const EXTENDED = 0b00010000;
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, FromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum AttrType {
RESERVED = 0,
ORIGIN = 1,
AS_PATH = 2,
NEXT_HOP = 3,
MULTI_EXIT_DISCRIMINATOR = 4,
LOCAL_PREFERENCE = 5,
ATOMIC_AGGREGATE = 6,
AGGREGATOR = 7,
COMMUNITIES = 8,
ORIGINATOR_ID = 9,
CLUSTER_LIST = 10,
CLUSTER_ID = 13,
MP_REACHABLE_NLRI = 14,
MP_UNREACHABLE_NLRI = 15,
EXTENDED_COMMUNITIES = 16,
AS4_PATH = 17,
AS4_AGGREGATOR = 18,
PMSI_TUNNEL = 22,
TUNNEL_ENCAPSULATION = 23,
TRAFFIC_ENGINEERING = 24,
IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES = 25,
AIGP = 26,
PE_DISTINGUISHER_LABELS = 27,
BGP_LS_ATTRIBUTE = 29,
LARGE_COMMUNITIES = 32,
BGPSEC_PATH = 33,
ONLY_TO_CUSTOMER = 35,
SFP_ATTRIBUTE = 37,
BFD_DISCRIMINATOR = 38,
BGP_PREFIX_SID = 40,
ATTR_SET = 128,
DEVELOPMENT = 255,
#[num_enum(catch_all)]
Unknown(u8) = 254,
}
impl PartialOrd for AttrType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AttrType {
fn cmp(&self, other: &Self) -> Ordering {
u8::from(*self).cmp(&u8::from(*other))
}
}
pub fn get_deprecated_attr_type(attr_type: u8) -> Option<&'static str> {
match attr_type {
11 => Some("DPA"),
12 => Some("ADVERTISER"),
13 => Some("RCID_PATH"),
19 => Some("SAFI Specific Attribute"),
20 => Some("Connector Attribute"),
21 => Some("AS_PATHLIMIT"),
28 => Some("BGP Entropy Label Capability"),
30 | 31 | 129 | 241 | 242 | 243 => Some("RFC8093"),
_ => None,
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct Attributes {
pub(crate) inner: Vec<Attribute>,
}
impl Attributes {
pub fn has_attr(&self, ty: AttrType) -> bool {
self.inner.iter().any(|x| x.value.attr_type() == ty)
}
pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
self.inner
.iter()
.find(|x| x.value.attr_type() == ty)
.cloned()
}
pub fn add_attr(&mut self, attr: Attribute) {
self.inner.push(attr);
}
pub fn origin(&self) -> Origin {
self.inner
.iter()
.find_map(|x| match &x.value {
AttributeValue::Origin(x) => Some(*x),
_ => None,
})
.unwrap_or(Origin::INCOMPLETE)
}
pub fn origin_id(&self) -> Option<BgpIdentifier> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::OriginatorId(x) => Some(*x),
_ => None,
})
}
pub fn next_hop(&self) -> Option<IpAddr> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::NextHop(x) => Some(*x),
_ => None,
})
}
pub fn multi_exit_discriminator(&self) -> Option<u32> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::MultiExitDiscriminator(x) => Some(*x),
_ => None,
})
}
pub fn local_preference(&self) -> Option<u32> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::LocalPreference(x) => Some(*x),
_ => None,
})
}
pub fn only_to_customer(&self) -> Option<Asn> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::OnlyToCustomer(x) => Some(*x),
_ => None,
})
}
pub fn atomic_aggregate(&self) -> bool {
self.inner
.iter()
.any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
}
pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
self.inner.iter().rev().find_map(|x| match &x.value {
AttributeValue::Aggregator { asn, id, .. } => Some((*asn, *id)),
_ => None,
})
}
pub fn clusters(&self) -> Option<&[u32]> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::Clusters(x) => Some(x.as_ref()),
_ => None,
})
}
pub fn as_path(&self) -> Option<&AsPath> {
self.inner.iter().rev().find_map(|x| match &x.value {
AttributeValue::AsPath { path, .. } => Some(path),
_ => None,
})
}
pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::MpReachNlri(x) => Some(x),
_ => None,
})
}
pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
self.inner.iter().find_map(|x| match &x.value {
AttributeValue::MpUnreachNlri(x) => Some(x),
_ => None,
})
}
pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
MetaCommunitiesIter {
attributes: &self.inner,
index: 0,
}
}
pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
self.into_iter()
}
pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
self.inner.into_iter()
}
}
pub struct MetaCommunitiesIter<'a> {
attributes: &'a [Attribute],
index: usize,
}
impl Iterator for MetaCommunitiesIter<'_> {
type Item = MetaCommunity;
fn next(&mut self) -> Option<Self::Item> {
loop {
match &self.attributes.first()?.value {
AttributeValue::Communities(x) if self.index < x.len() => {
self.index += 1;
return Some(MetaCommunity::Plain(x[self.index - 1]));
}
AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
self.index += 1;
return Some(MetaCommunity::Extended(x[self.index - 1]));
}
AttributeValue::LargeCommunities(x) if self.index < x.len() => {
self.index += 1;
return Some(MetaCommunity::Large(x[self.index - 1]));
}
_ => {
self.attributes = &self.attributes[1..];
self.index = 0;
}
}
}
}
}
impl FromIterator<Attribute> for Attributes {
fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
Attributes {
inner: iter.into_iter().collect(),
}
}
}
impl From<Vec<Attribute>> for Attributes {
fn from(value: Vec<Attribute>) -> Self {
Attributes { inner: value }
}
}
impl Extend<Attribute> for Attributes {
fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
self.inner.extend(iter)
}
}
impl Extend<AttributeValue> for Attributes {
fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(Attribute::from))
}
}
impl FromIterator<AttributeValue> for Attributes {
fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
Attributes {
inner: iter
.into_iter()
.map(|value| Attribute {
value,
flag: AttrFlags::empty(),
})
.collect(),
}
}
}
impl IntoIterator for Attributes {
type Item = AttributeValue;
type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter().map(|x| x.value)
}
}
impl<'a> IntoIterator for &'a Attributes {
type Item = &'a AttributeValue;
type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter().map(|x| &x.value)
}
}
#[cfg(feature = "serde")]
mod serde_impl {
use super::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
impl Serialize for Attributes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.inner.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Attributes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Attributes {
inner: <Vec<Attribute>>::deserialize(deserializer)?,
})
}
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Attribute {
pub value: AttributeValue,
pub flag: AttrFlags,
}
impl Attribute {
pub const fn is_optional(&self) -> bool {
self.flag.contains(AttrFlags::OPTIONAL)
}
pub const fn is_transitive(&self) -> bool {
self.flag.contains(AttrFlags::TRANSITIVE)
}
pub const fn is_partial(&self) -> bool {
self.flag.contains(AttrFlags::PARTIAL)
}
pub const fn is_extended(&self) -> bool {
self.flag.contains(AttrFlags::EXTENDED)
}
}
impl From<AttributeValue> for Attribute {
fn from(value: AttributeValue) -> Self {
Attribute {
flag: value.default_flags(),
value,
}
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AttributeValue {
Origin(Origin),
AsPath {
path: AsPath,
is_as4: bool,
},
NextHop(IpAddr),
MultiExitDiscriminator(u32),
LocalPreference(u32),
OnlyToCustomer(Asn),
AtomicAggregate,
Aggregator {
asn: Asn,
id: BgpIdentifier,
is_as4: bool,
},
Communities(Vec<Community>),
ExtendedCommunities(Vec<ExtendedCommunity>),
Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
LargeCommunities(Vec<LargeCommunity>),
OriginatorId(BgpIdentifier),
Clusters(Vec<u32>),
MpReachNlri(Nlri),
MpUnreachNlri(Nlri),
Development(Vec<u8>),
Deprecated(AttrRaw),
Unknown(AttrRaw),
}
impl From<Origin> for AttributeValue {
fn from(value: Origin) -> Self {
AttributeValue::Origin(value)
}
}
impl From<AsPath> for AttributeValue {
fn from(path: AsPath) -> Self {
AttributeValue::AsPath {
path,
is_as4: false,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AttributeCategory {
WellKnownMandatory,
WellKnownDiscretionary,
OptionalTransitive,
OptionalNonTransitive,
}
impl AttributeValue {
pub const fn attr_type(&self) -> AttrType {
match self {
AttributeValue::Origin(_) => AttrType::ORIGIN,
AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
AttributeValue::Communities(_) => AttrType::COMMUNITIES,
AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
}
AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
AttributeValue::Development(_) => AttrType::DEVELOPMENT,
AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => x.attr_type,
}
}
pub fn attr_category(&self) -> Option<AttributeCategory> {
use AttributeCategory::*;
match self {
AttributeValue::Origin(_) => Some(WellKnownMandatory),
AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
AttributeValue::NextHop(_) => Some(WellKnownMandatory),
AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
AttributeValue::Communities(_) => Some(OptionalTransitive),
AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
_ => None,
}
}
pub fn default_flags(&self) -> AttrFlags {
match self.attr_category() {
None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
Some(AttributeCategory::OptionalTransitive) => {
AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
}
Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
}
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AttrRaw {
pub attr_type: AttrType,
pub bytes: Vec<u8>,
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
use std::str::FromStr;
#[test]
fn test_attr_type() {
let attr_value = AttributeValue::Origin(Origin::IGP);
assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
}
#[test]
fn test_attr_category() {
let attr_value = AttributeValue::Origin(Origin::IGP);
let category = attr_value.attr_category().unwrap();
assert_eq!(category, AttributeCategory::WellKnownMandatory);
}
#[test]
fn test_default_flags() {
let attr_value = AttributeValue::Origin(Origin::IGP);
let flags = attr_value.default_flags();
assert_eq!(flags, AttrFlags::TRANSITIVE);
}
#[test]
fn test_get_attr() {
let attribute = Attribute {
value: AttributeValue::Origin(Origin::IGP),
flag: AttrFlags::TRANSITIVE,
};
let mut attributes = Attributes::default();
attributes.add_attr(attribute.clone());
assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
}
#[test]
fn test_has_attr() {
let attribute = Attribute {
value: AttributeValue::Origin(Origin::IGP),
flag: AttrFlags::TRANSITIVE,
};
let mut attributes = Attributes::default();
attributes.add_attr(attribute);
assert!(attributes.has_attr(AttrType::ORIGIN));
}
#[test]
fn test_getting_all_attributes() {
let mut attributes = Attributes::default();
attributes.add_attr(Attribute {
value: AttributeValue::Origin(Origin::IGP),
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::AsPath {
path: AsPath::new(),
is_as4: false,
},
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::MultiExitDiscriminator(1),
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::LocalPreference(1),
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::AtomicAggregate,
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::Clusters(vec![1, 2, 3]),
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::Aggregator {
asn: Asn::new_32bit(1),
id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
is_as4: false,
},
flag: AttrFlags::TRANSITIVE,
});
attributes.add_attr(Attribute {
value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
flag: AttrFlags::TRANSITIVE,
});
assert_eq!(attributes.origin(), Origin::IGP);
assert_eq!(attributes.as_path(), Some(&AsPath::new()));
assert_eq!(
attributes.next_hop(),
Some(IpAddr::from_str("10.0.0.0").unwrap())
);
assert_eq!(attributes.multi_exit_discriminator(), Some(1));
assert_eq!(attributes.local_preference(), Some(1));
assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
assert!(attributes.atomic_aggregate());
assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
assert_eq!(
attributes.aggregator(),
Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
);
assert_eq!(
attributes.origin_id(),
Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
);
let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
assert!(aspath_attr.is_transitive());
assert!(!aspath_attr.is_extended());
assert!(!aspath_attr.is_partial());
assert!(!aspath_attr.is_optional());
for attr in attributes.iter() {
println!("{:?}", attr);
}
}
#[test]
fn test_from() {
let origin = Origin::IGP;
let attr_value = AttributeValue::from(origin);
assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
let aspath = AsPath::new();
let attr_value = AttributeValue::from(aspath);
assert_eq!(
attr_value,
AttributeValue::AsPath {
path: AsPath::new(),
is_as4: false
}
);
}
#[test]
fn test_well_known_mandatory_attrs() {
let origin_attr = AttributeValue::Origin(Origin::IGP);
assert_eq!(
origin_attr.attr_category(),
Some(AttributeCategory::WellKnownMandatory)
);
let as_path_attr = AttributeValue::AsPath {
path: AsPath::new(),
is_as4: false,
};
assert_eq!(
as_path_attr.attr_category(),
Some(AttributeCategory::WellKnownMandatory)
);
let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
assert_eq!(
next_hop_attr.attr_category(),
Some(AttributeCategory::WellKnownMandatory)
);
let local_preference_attr = AttributeValue::LocalPreference(1);
assert_eq!(
local_preference_attr.attr_category(),
Some(AttributeCategory::WellKnownMandatory)
);
}
#[test]
fn test_well_known_discretionary_attrs() {
let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
assert_eq!(
atomic_aggregate_attr.attr_category(),
Some(AttributeCategory::WellKnownDiscretionary)
);
}
#[test]
fn test_optional_transitive_attrs() {
let as_path_attr = AttributeValue::AsPath {
path: AsPath::new(),
is_as4: true,
};
assert_eq!(
as_path_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
let aggregator_attr = AttributeValue::Aggregator {
asn: Asn::new_32bit(1),
id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
is_as4: false,
};
assert_eq!(
aggregator_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
assert_eq!(
only_to_customer_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
let communities_attr =
AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
assert_eq!(
communities_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
let extended_communities_attr =
AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
assert_eq!(
extended_communities_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
let large_communities_attr =
AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
assert_eq!(
large_communities_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
let aggregator_attr = AttributeValue::Aggregator {
asn: Asn::new_32bit(1),
id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
is_as4: true,
};
assert_eq!(
aggregator_attr.attr_category(),
Some(AttributeCategory::OptionalTransitive)
);
}
#[test]
fn test_optional_non_transitive_attrs() {
let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
assert_eq!(
multi_exit_discriminator_attr.attr_category(),
Some(AttributeCategory::OptionalNonTransitive)
);
let originator_id_attr =
AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
assert_eq!(
originator_id_attr.attr_category(),
Some(AttributeCategory::OptionalNonTransitive)
);
let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
assert_eq!(
clusters_attr.attr_category(),
Some(AttributeCategory::OptionalNonTransitive)
);
let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
));
assert_eq!(
mp_unreach_nlri_attr.attr_category(),
Some(AttributeCategory::OptionalNonTransitive)
);
let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
));
assert_eq!(
mp_reach_nlri_attr.attr_category(),
Some(AttributeCategory::OptionalNonTransitive)
);
}
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
let attributes = Attributes::from_iter(vec![
Attribute {
value: AttributeValue::Origin(Origin::IGP),
flag: AttrFlags::TRANSITIVE,
},
Attribute {
value: AttributeValue::AsPath {
path: AsPath::new(),
is_as4: false,
},
flag: AttrFlags::TRANSITIVE,
},
]);
let serialized = serde_json::to_string(&attributes).unwrap();
let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
assert_eq!(attributes, deserialized);
}
}