-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add ISD, ASN, and IA address types
- Loading branch information
Showing
6 changed files
with
483 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use thiserror; | ||
|
||
mod asn; | ||
pub use asn::Asn; | ||
|
||
mod isd; | ||
pub use isd::Isd; | ||
|
||
mod ia; | ||
pub use ia::IA; | ||
|
||
#[derive(Eq, PartialEq, Clone, Debug, thiserror::Error)] | ||
pub enum AddressParseError { | ||
#[error("AS number out of range, expected at most 2^48 - 1")] | ||
AsnOutOfRange, | ||
#[error("AS string contains a part that is not a 2-byte HEX")] | ||
InvalidAsnPart(String), | ||
#[error("invalid AS number string (expected format xxxx:xxxx:xxxx, found {0})")] | ||
InvalidAsnString(String), | ||
#[error("ISD number not parsable as u16")] | ||
InvalidIsdString(String), | ||
#[error("invalid string (expected format d-xxxx:xxxx:xxxx, found {0})")] | ||
InvalidIaString(String), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
use std::{ | ||
fmt::{Display, Formatter}, | ||
str::FromStr, | ||
}; | ||
|
||
use super::AddressParseError; | ||
|
||
/// A SCION autonomous system (AS) number | ||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] | ||
pub struct Asn(u64); | ||
|
||
impl Asn { | ||
/// A SCION AS number representing the wildcard AS. | ||
pub const WILDCARD: Self = Self(0); | ||
/// The number of bits in a SCION AS number | ||
pub const BITS: u32 = 48; | ||
|
||
const BITS_PER_PART: u32 = 16; | ||
const MAX_VALUE: u64 = (1 << Self::BITS) - 1; | ||
|
||
/// Creates a new AS from a u64 value. | ||
/// | ||
/// # Panics | ||
/// | ||
/// This function panics if the provided id is greater than the maximum AS number, 2^48 - 1. | ||
pub fn new(id: u64) -> Self { | ||
Asn::try_from(id).expect("value within AS number range") | ||
} | ||
|
||
/// Returns the AS number as a u64 integer. | ||
pub fn as_u64(&self) -> u64 { | ||
self.0 | ||
} | ||
|
||
/// Return true for the special 'wildcard' AS number, 0. | ||
pub fn is_wildcard(&self) -> bool { | ||
self == &Self::WILDCARD | ||
} | ||
} | ||
|
||
impl Display for Asn { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
if self.is_wildcard() { | ||
return write!(f, "0"); | ||
} | ||
|
||
for i in (0..(Asn::BITS / Asn::BITS_PER_PART)).rev() { | ||
let asn_part = self.as_u64() >> (Asn::BITS_PER_PART * i) & u64::from(u16::MAX); | ||
let separator = if i != 0 { ":" } else { "" }; | ||
|
||
write!(f, "{:x}{}", asn_part, separator)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
impl From<Asn> for u64 { | ||
fn from(value: Asn) -> Self { | ||
value.as_u64() | ||
} | ||
} | ||
|
||
impl TryFrom<u64> for Asn { | ||
type Error = AddressParseError; | ||
|
||
fn try_from(value: u64) -> Result<Self, Self::Error> { | ||
if value > Asn::MAX_VALUE { | ||
Err(AddressParseError::AsnOutOfRange) | ||
} else { | ||
Ok(Asn(value)) | ||
} | ||
} | ||
} | ||
|
||
impl FromStr for Asn { | ||
type Err = AddressParseError; | ||
|
||
fn from_str(asn_string: &str) -> Result<Self, Self::Err> { | ||
if asn_string == "0" { | ||
return Ok(Self::WILDCARD); | ||
} | ||
|
||
let mut result = 0u64; | ||
let mut n_parts = 0; | ||
|
||
for asn_part in asn_string.split(':') { | ||
match u16::from_str_radix(asn_part, 16) { | ||
Ok(value) => { | ||
result <<= Asn::BITS_PER_PART; | ||
result |= u64::from(value); | ||
} | ||
Err(_) => return Err(AddressParseError::InvalidAsnPart(asn_string.into())), | ||
} | ||
n_parts += 1; | ||
} | ||
|
||
if n_parts != (Asn::BITS / Asn::BITS_PER_PART) { | ||
return Err(AddressParseError::InvalidAsnString(asn_string.into())); | ||
} | ||
|
||
// Should not panic as there are exactly 3 parts, each of 16 bits | ||
Ok(Asn::new(result)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
mod parse { | ||
use super::*; | ||
|
||
macro_rules! test_success { | ||
($name:ident, $input:expr, $expected:expr) => { | ||
#[test] | ||
fn $name() { | ||
assert_eq!(Asn::from_str($input).unwrap(), $expected); | ||
} | ||
}; | ||
} | ||
|
||
test_success!(zero, "0", Asn::WILDCARD); | ||
test_success!(zero_with_colon, "0:0:0", Asn::WILDCARD); | ||
test_success!(low_bit, "0:0:1", Asn(1)); | ||
test_success!(high_bit, "1:0:0", Asn(0x000100000000)); | ||
test_success!(max, "ffff:ffff:ffff", Asn(Asn::MAX_VALUE)); | ||
|
||
macro_rules! test_error { | ||
($name:ident, $input:expr, $expected:expr) => { | ||
#[test] | ||
fn $name() { | ||
assert_eq!(Asn::from_str($input).unwrap_err(), $expected); | ||
} | ||
}; | ||
} | ||
|
||
test_error!( | ||
errs_on_only_colon, | ||
":", | ||
AddressParseError::InvalidAsnPart(":".into()) | ||
); | ||
test_error!( | ||
errs_extra_colon, | ||
"0:0:0:", | ||
AddressParseError::InvalidAsnPart("0:0:0:".into()) | ||
); | ||
test_error!( | ||
errs_too_few, | ||
"0:0", | ||
AddressParseError::InvalidAsnString("0:0".into()) | ||
); | ||
test_error!( | ||
errs_invalid_part, | ||
":0:0", | ||
AddressParseError::InvalidAsnPart(":0:0".into()) | ||
); | ||
test_error!( | ||
errs_out_of_range, | ||
"10000:0:0", | ||
AddressParseError::InvalidAsnPart("10000:0:0".into()) | ||
); | ||
test_error!( | ||
errs_out_of_range2, | ||
"0:0:10000", | ||
AddressParseError::InvalidAsnPart("0:0:10000".into()) | ||
); | ||
test_error!( | ||
errs_invalid_format, | ||
"0:0x0:0", | ||
AddressParseError::InvalidAsnPart("0:0x0:0".into()) | ||
); | ||
} | ||
|
||
mod display { | ||
use super::*; | ||
|
||
#[test] | ||
fn large() { | ||
assert_eq!(Asn(0xff00000000ab).to_string(), "ff00:0:ab"); | ||
} | ||
|
||
#[test] | ||
fn large_symmetric() { | ||
assert_eq!(Asn(0x0001fcd10001).to_string(), "1:fcd1:1"); | ||
} | ||
|
||
#[test] | ||
fn max() { | ||
assert_eq!(Asn(Asn::MAX_VALUE).to_string(), "ffff:ffff:ffff"); | ||
} | ||
} | ||
} |
Oops, something went wrong.