use std::{
fmt::{Debug, Display, Formatter},
str::FromStr,
};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use utoipa::ToSchema;
use crate::{
core::{macros::impl_from, read::FromUnalignedRead},
scion::address::AddressParseError,
};
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Hash,
PartialOrd,
Ord,
SerializeDisplay,
DeserializeFromStr,
ToSchema,
)]
#[repr(transparent)]
pub struct Asn(pub u64);
impl Asn {
pub const WILDCARD: Self = Asn::new(0);
pub const MAX: Self = Self((1 << Self::BITS) - 1);
pub const BITS: u32 = 48;
const BITS_PER_PART: u32 = 16;
const NUMBER_PARTS: u32 = 3;
pub const fn new(id: u64) -> Self {
Self(id & Self::MAX.0)
}
pub const fn new_checked(id: u64) -> Option<Self> {
if id > Self::MAX.0 {
None
} else {
Some(Self(id))
}
}
pub const fn to_u64(&self) -> u64 {
self.0
}
pub const fn is_wildcard(&self) -> bool {
self.0 == Self::WILDCARD.0
}
pub const fn matches(&self, other: Asn) -> bool {
self.is_wildcard() || other.is_wildcard() || self.0 == other.0
}
pub fn matches_any_in<'a>(&self, collection: impl IntoIterator<Item = &'a Asn>) -> bool {
collection.into_iter().any(|other| self.matches(*other))
}
}
impl Debug for Asn {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl Display for Asn {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
const BGP_ASN_FORMAT_BOUNDARY: u64 = u32::MAX as u64;
if self.to_u64() <= BGP_ASN_FORMAT_BOUNDARY {
return write!(f, "{}", self.to_u64());
}
for i in (0..Asn::NUMBER_PARTS).rev() {
let asn_part = (self.to_u64() >> (Asn::BITS_PER_PART * i)) & u64::from(u16::MAX);
let separator = if i != 0 { ":" } else { "" };
write!(f, "{asn_part:x}{separator}")?;
}
Ok(())
}
}
impl FromStr for Asn {
type Err = AddressParseError;
fn from_str(asn_string: &str) -> Result<Self, Self::Err> {
if let Ok(bgp_asn) = u64::from_str(asn_string) {
return if bgp_asn <= u32::MAX.into() {
Ok(Self(bgp_asn))
} else {
Err(AddressParseError::Asn)
};
}
let result = asn_string.splitn(Asn::NUMBER_PARTS as usize, ':').try_fold(
(0u64, 0u32),
|(asn_value, n_parts), asn_part| {
u16::from_str_radix(asn_part, 16).map(|value| {
(
(asn_value << Asn::BITS_PER_PART) | u64::from(value),
n_parts + 1,
)
})
},
);
match result {
Ok((val, Asn::NUMBER_PARTS)) => {
match Asn::new_checked(val) {
Some(asn) => Ok(asn),
None => Err(AddressParseError::Asn),
}
}
_ => Err(AddressParseError::Asn),
}
}
}
impl TryFrom<u64> for Asn {
type Error = AddressParseError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
match Asn::new_checked(value) {
Some(asn) => Ok(asn),
None => Err(AddressParseError::Asn),
}
}
}
impl_from!(Asn, u64, |v| v.to_u64());
impl FromUnalignedRead for Asn {
fn from_unaligned_read(v: u128) -> Self {
Asn::new(u64::from_unaligned_read(v))
}
}
#[cfg(feature = "proptest")]
impl proptest::prelude::Arbitrary for Asn {
type Parameters = ();
type Strategy = proptest::strategy::Map<std::ops::RangeInclusive<u64>, fn(u64) -> Asn>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
use proptest::prelude::*;
(0u64..=Asn::MAX.0).prop_map(Asn::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{core::test::param_test, scion::address::AddressParseError};
param_test! {
converts_from_number: [
wildcard: (0, Ok(Asn::WILDCARD)),
max_value: (0xffff_ffff_ffff, Ok(Asn::MAX)),
out_of_range: (0xffff_ffff_ffff + 1, Err(AddressParseError::Asn))
]
}
fn converts_from_number(numeric_value: u64, expected: Result<Asn, AddressParseError>) {
assert_eq!(Asn::try_from(numeric_value), expected);
}
param_test! {
successfully_parses_valid_strings: [
zero: ("0", Asn::WILDCARD),
zero_with_colon: ("0:0:0", Asn::WILDCARD),
low_bit: ("0:0:1", Asn(1)),
high_bit: ("1:0:0", Asn(0x000100000000)),
max: ("ffff:ffff:ffff", Asn::MAX),
bgp_asn: ("65535", Asn(65535))
]
}
fn successfully_parses_valid_strings(asn_str: &str, expected: Asn) {
assert_eq!(Ok(expected), asn_str.parse());
}
param_test! {
parse_rejects_invalid_strings: [
large_decimal_format: ("4294967296"),
only_colon: (":"),
extra_colon: ("0:0:0:"),
too_few: ("0:0"),
invalid_part: (":0:0"),
out_of_range: ("10000:0:0"),
out_of_range2: ("0:0:10000"),
invalid_format: ("0:0x0:0"),
]
}
fn parse_rejects_invalid_strings(asn_str: &str) {
assert_eq!(Asn::from_str(asn_str), Err(AddressParseError::Asn));
}
param_test! {
correctly_displays_asn: [
large: (Asn(0xff00000000ab), "ff00:0:ab"),
large_symmetric: (Asn(0x0001fcd10001), "1:fcd1:1"),
max: (Asn::MAX, "ffff:ffff:ffff"),
wildcard: (Asn(0), "0"),
bgp_asn: (Asn(1), "1"),
bgp_asn_max: (Asn(u32::MAX.into()), "4294967295"),
outside_bgp_asn: (Asn(u32::MAX as u64 + 1), "1:0:0"),
]
}
fn correctly_displays_asn(asn: Asn, expected: &str) {
assert_eq!(asn.to_string(), expected);
}
}