Skip to content

Commit

Permalink
feat: add ISD, ASN, and IA address types
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcsmith committed Sep 20, 2023
1 parent c7915c6 commit 6b6949a
Show file tree
Hide file tree
Showing 6 changed files with 483 additions and 1 deletion.
2 changes: 2 additions & 0 deletions crates/scion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0.188", features = ["derive"] }
thiserror = "1.0.48"
24 changes: 24 additions & 0 deletions crates/scion/src/address.rs
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),
}
193 changes: 193 additions & 0 deletions crates/scion/src/address/asn.rs
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");
}
}
}
Loading

0 comments on commit 6b6949a

Please sign in to comment.