use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)]
#[display("{}", self.to_asdot())]
pub struct Asn(u32);
impl Asn {
pub const TRANS: Self = Self(23456);
pub const MAX: Self = Self(4_294_967_294);
#[inline]
pub const fn new(value: u32) -> Self {
Self(value)
}
#[inline]
pub const fn value(self) -> u32 {
self.0
}
const fn high(self) -> u16 {
(self.0 >> 16) as u16
}
const fn low(self) -> u16 {
self.0 as u16
}
pub fn to_asplain(self) -> String {
self.0.to_string()
}
pub fn to_asdot(self) -> String {
if self.high() > 0 {
self.to_asdot_plus()
} else {
self.low().to_string()
}
}
pub fn to_asdot_plus(self) -> String {
format!("{}.{}", self.high(), self.low())
}
}
impl From<u32> for Asn {
fn from(v: u32) -> Self {
Self(v)
}
}
impl From<u16> for Asn {
fn from(v: u16) -> Self {
Self(v as u32)
}
}
impl From<Asn> for u32 {
fn from(asn: Asn) -> u32 {
asn.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseAsnError {
#[error("empty string")]
Empty,
#[error("value exceeds maximum ASN (4294967295)")]
Overflow,
#[error("dot-notation component exceeds 65535")]
ComponentOverflow,
#[error("invalid AS number format")]
Invalid,
}
impl FromStr for Asn {
type Err = ParseAsnError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(ParseAsnError::Empty);
}
if let Some(dot_pos) = s.find('.') {
let high_str = &s[..dot_pos];
let low_str = &s[dot_pos + 1..];
if high_str.is_empty() || low_str.is_empty() {
return Err(ParseAsnError::Invalid);
}
let high: u32 = high_str
.parse::<u32>()
.map_err(|_| ParseAsnError::Invalid)?;
let low: u32 = low_str.parse::<u32>().map_err(|_| ParseAsnError::Invalid)?;
if high > 0xFFFF {
return Err(ParseAsnError::ComponentOverflow);
}
if low > 0xFFFF {
return Err(ParseAsnError::ComponentOverflow);
}
Ok(Self((high << 16) | low))
} else {
let value: u32 = s.parse::<u32>().map_err(|e| match e.kind() {
std::num::IntErrorKind::PosOverflow => ParseAsnError::Overflow,
_ => ParseAsnError::Invalid,
})?;
Ok(Self(value))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_asplain() {
assert_eq!("0".parse::<Asn>().unwrap(), Asn::new(0));
assert_eq!("65535".parse::<Asn>().unwrap(), Asn::new(65535));
assert_eq!("65536".parse::<Asn>().unwrap(), Asn::new(65536));
assert_eq!(
"4294967295".parse::<Asn>().unwrap(),
Asn::new(4_294_967_295)
);
}
#[test]
fn parse_asdot() {
assert_eq!("0.0".parse::<Asn>().unwrap(), Asn::new(0));
assert_eq!("0.1".parse::<Asn>().unwrap(), Asn::new(1));
assert_eq!("0.65535".parse::<Asn>().unwrap(), Asn::new(65535));
assert_eq!("1.0".parse::<Asn>().unwrap(), Asn::new(65536));
assert_eq!("1.1".parse::<Asn>().unwrap(), Asn::new(65537));
assert_eq!(
"65535.65535".parse::<Asn>().unwrap(),
Asn::new(4_294_967_295)
);
}
#[test]
fn asplain_overflow() {
assert_eq!("4294967296".parse::<Asn>(), Err(ParseAsnError::Overflow));
assert_eq!("99999999999".parse::<Asn>(), Err(ParseAsnError::Overflow));
}
#[test]
fn asplain_invalid() {
assert_eq!("abc".parse::<Asn>(), Err(ParseAsnError::Invalid));
assert_eq!("-1".parse::<Asn>(), Err(ParseAsnError::Invalid));
}
#[test]
fn parse_errors() {
assert_eq!("".parse::<Asn>(), Err(ParseAsnError::Empty));
assert_eq!(
"65536.0".parse::<Asn>(),
Err(ParseAsnError::ComponentOverflow)
);
assert_eq!(
"0.65536".parse::<Asn>(),
Err(ParseAsnError::ComponentOverflow)
);
assert_eq!("1.2.3".parse::<Asn>(), Err(ParseAsnError::Invalid));
assert_eq!(".1".parse::<Asn>(), Err(ParseAsnError::Invalid));
assert_eq!("1.".parse::<Asn>(), Err(ParseAsnError::Invalid));
}
#[test]
fn roundtrip_asplain() {
for v in [0u32, 1, 65535, 65536, 100_000, 4_294_967_295] {
let asn = Asn::new(v);
assert_eq!(asn.to_asplain().parse::<Asn>().unwrap(), asn);
}
}
#[test]
fn roundtrip_asdot() {
for v in [0u32, 1, 65535, 65536, 65537, 4_294_967_295] {
let asn = Asn::new(v);
assert_eq!(asn.to_asdot().parse::<Asn>().unwrap(), asn);
}
}
#[test]
fn roundtrip_asdot_plus() {
for v in [0u32, 1, 65535, 65536, 65537, 4_294_967_295] {
let asn = Asn::new(v);
assert_eq!(asn.to_asdot_plus().parse::<Asn>().unwrap(), asn);
}
}
#[test]
fn conversions() {
assert_eq!(Asn::from(65536u32), Asn::new(65536));
assert_eq!(Asn::from(1u16), Asn::new(1));
assert_eq!(u32::from(Asn::new(65536)), 65536u32);
}
}