Skip to content

Commit

Permalink
feat: add socket address and simplify parse errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcsmith committed Nov 1, 2023
1 parent 8633112 commit 894fb77
Show file tree
Hide file tree
Showing 7 changed files with 557 additions and 111 deletions.
22 changes: 6 additions & 16 deletions crates/scion/src/address.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use thiserror;

mod asn;
pub use asn::Asn;

Expand All @@ -10,21 +8,13 @@ mod ia;
pub use ia::IsdAsn;

mod service;
pub use service::{ParseServiceAddressError, ServiceAddress};
pub use service::ServiceAddress;

mod host;
pub use host::{Host, HostAddress, HostType};

#[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),
}
mod socket_address;
pub use socket_address::{SocketAddr, SocketAddrSvc, SocketAddrV4, SocketAddrV6};

mod error;
pub use error::AddressParseError;
101 changes: 33 additions & 68 deletions crates/scion/src/address/asn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
str::FromStr,
};

use super::AddressParseError;
use super::{error::AddressKind, AddressParseError};

/// A SCION autonomous system (AS) number
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -77,7 +77,7 @@ impl TryFrom<u64> for Asn {

fn try_from(value: u64) -> Result<Self, Self::Error> {
if value > Asn::MAX_VALUE {
Err(AddressParseError::AsnOutOfRange)
Err(AddressKind::Asn.into())
} else {
Ok(Asn(value))
}
Expand All @@ -93,34 +93,28 @@ impl FromStr for Asn {
return if bgp_asn <= u16::MAX.into() {
Ok(Self(bgp_asn))
} else {
Err(Self::Err::InvalidAsnString(asn_string.into()))
Err(AddressKind::Asn.into())
};
}

let mut result = 0u64;
let mut n_parts = 0;

for asn_part in asn_string.split(':') {
n_parts += 1;
if n_parts > Asn::NUMBER_PARTS {
return Err(AddressParseError::InvalidAsnString(asn_string.into()));
}

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())),
}
}
let max_splits = usize::try_from(Asn::NUMBER_PARTS).expect("few parts");
let result = asn_string.splitn(max_splits, ':').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,
)
})
},
);

if n_parts != Asn::NUMBER_PARTS {
return Err(AddressParseError::InvalidAsnString(asn_string.into()));
if let Ok((value, Asn::NUMBER_PARTS)) = result {
Ok(Asn::new(value))
} else {
Err(AddressKind::Asn.into())
}

// Can not panic as the result is at most 48 bits (exactly 3 parts, 16 bits each)
Ok(Asn::new(result))
}
}

Expand Down Expand Up @@ -148,7 +142,7 @@ mod tests {
fn out_of_range() {
assert_eq!(
Asn::try_from(Asn::MAX_VALUE + 1).unwrap_err(),
AddressParseError::AsnOutOfRange
AddressParseError(AddressKind::Asn)
);
}
}
Expand All @@ -173,54 +167,25 @@ mod tests {
test_success!(bgp_asn, "65535", Asn(65535));

macro_rules! test_error {
($name:ident, $input:expr, $expected:expr) => {
($name:ident, $input:expr) => {
#[test]
fn $name() {
assert_eq!(Asn::from_str($input).unwrap_err(), $expected);
assert_eq!(
Asn::from_str($input).unwrap_err(),
AddressParseError(AddressKind::Asn)
);
}
};
}

test_error!(
errs_large_decimal_format,
"65536",
AddressParseError::InvalidAsnString("65536".into())
);
test_error!(
errs_on_only_colon,
":",
AddressParseError::InvalidAsnPart(":".into())
);
test_error!(
errs_extra_colon,
"0:0:0:",
AddressParseError::InvalidAsnString("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())
);
test_error!(errs_large_decimal_format, "65536");
test_error!(errs_on_only_colon, ":");
test_error!(errs_extra_colon, "0:0:0:");
test_error!(errs_too_few, "0:0");
test_error!(errs_invalid_part, ":0:0");
test_error!(errs_out_of_range, "10000:0:0");
test_error!(errs_out_of_range2, "0:0:10000");
test_error!(errs_invalid_format, "0:0x0:0");
}

mod display {
Expand Down
41 changes: 41 additions & 0 deletions crates/scion/src/address/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::fmt::Display;

/// An error which can be returned when parsing a SCION socket address, host address, or
/// ISD identifier, AS number, or ISD-AS number.
#[derive(Eq, PartialEq, Clone, Debug, thiserror::Error)]
pub struct AddressParseError(pub(super) AddressKind);

impl Display for AddressParseError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let description = match self.0 {
AddressKind::Isd => "invalid ISD number syntax",
AddressKind::Asn => "invalid AS number syntax",
AddressKind::IsdAsn => "invalid ISD-AS number syntax",
AddressKind::Service => "invalid service address syntax",
AddressKind::SocketV4 => "invalid SCION-IPv4 socket address syntax",
AddressKind::SocketV6 => "invalid SCION-IPv6 socket address syntax",
AddressKind::SocketSvc => "invalid service socket address syntax",
AddressKind::Socket => "invalid socket address syntax",
};

fmt.write_str(description)
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum AddressKind {
Isd,
Asn,
IsdAsn,
Service,
Socket,
SocketV4,
SocketV6,
SocketSvc,
}

impl From<AddressKind> for AddressParseError {
fn from(value: AddressKind) -> Self {
Self(value)
}
}
20 changes: 11 additions & 9 deletions crates/scion/src/address/ia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use std::{

use serde::Deserialize;

use super::{AddressParseError, Asn, Isd};
use super::{error::AddressKind, AddressParseError, Asn, Isd};

/// The combined ISD and AS identifier of a SCION AS (sometimes abbreviated as IA).
#[derive(Copy, Clone, Eq, PartialEq, Deserialize, Hash)]
#[derive(Copy, Clone, Eq, PartialEq, Deserialize, Hash, PartialOrd, Ord)]
#[serde(try_from = "String")]
#[repr(transparent)]
pub struct IsdAsn(pub u64);
Expand Down Expand Up @@ -73,16 +73,18 @@ impl FromStr for IsdAsn {
fn from_str(string: &str) -> Result<Self, Self::Err> {
let n_separators = string.chars().filter(|c| *c == '-').take(2).count();
if n_separators != 1 {
return Err(Self::Err::InvalidIaString(string.into()));
return Err(AddressKind::IsdAsn.into());
}

let (isd_str, asn_str) = string
.split_once('-')
.expect("already checked that the string contains exactly one '-'");
Ok(IsdAsn::new(
Isd::from_str(isd_str)?,
Asn::from_str(asn_str)?,
))

if let (Ok(isd), Ok(asn)) = (Isd::from_str(isd_str), Asn::from_str(asn_str)) {
Ok(IsdAsn::new(isd, asn))
} else {
Err(AddressKind::IsdAsn.into())
}
}
}

Expand Down Expand Up @@ -230,15 +232,15 @@ mod tests {
fn invalid() {
assert_eq!(
IsdAsn::from_str("a-0:0:1").unwrap_err(),
AddressParseError::InvalidIsdString("a".into())
AddressParseError(AddressKind::IsdAsn)
);
}

#[test]
fn invalid_parts() {
assert_eq!(
IsdAsn::from_str("1-1-0:0:1").unwrap_err(),
AddressParseError::InvalidIaString("1-1-0:0:1".into())
AddressParseError(AddressKind::IsdAsn)
);
}
}
Expand Down
10 changes: 4 additions & 6 deletions crates/scion/src/address/isd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
str::FromStr,
};

use super::AddressParseError;
use super::{error::AddressKind, AddressParseError};

/// Identifier of a SCION Isolation Domain
///
Expand Down Expand Up @@ -55,11 +55,9 @@ impl FromStr for Isd {
///
/// ISD 0 is parsed without any errors.
fn from_str(string: &str) -> Result<Self, Self::Err> {
if let Ok(value) = u16::from_str(string) {
Ok(Isd::new(value))
} else {
Err(Self::Err::InvalidIsdString(string.into()))
}
u16::from_str(string)
.map(Isd::new)
.or(Err(AddressKind::Isd.into()))
}
}

Expand Down
18 changes: 6 additions & 12 deletions crates/scion/src/address/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ use std::{
str::FromStr,
};

use thiserror;

use super::{Host, HostAddress, HostType};
use super::{error::AddressKind, AddressParseError, Host, HostAddress, HostType};

/// A SCION service address.
///
Expand All @@ -17,7 +15,7 @@ use super::{Host, HostAddress, HostType};
/// Service addresses can also be represented as strings, for example CS and CS_A
/// both represent the anycast service address ServiceAddress::CONTROL. The
/// corresponding multicast service address would be CS_M.
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
#[derive(Eq, PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Hash)]
pub struct ServiceAddress(pub u16);

impl ServiceAddress {
Expand Down Expand Up @@ -82,12 +80,8 @@ impl ServiceAddress {
}
}

#[derive(Eq, PartialEq, Clone, Debug, thiserror::Error)]
#[error("invalid service address string: {0}")]
pub struct ParseServiceAddressError(String);

impl FromStr for ServiceAddress {
type Err = ParseServiceAddressError;
type Err = AddressParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let (service, suffix) = s.split_once('_').unwrap_or((s, "A"));
Expand All @@ -96,12 +90,12 @@ impl FromStr for ServiceAddress {
"CS" => ServiceAddress::CONTROL,
"DS" => ServiceAddress::DAEMON,
"Wildcard" => ServiceAddress::WILDCARD,
_ => return Err(ParseServiceAddressError(s.into())),
_ => return Err(AddressKind::Service.into()),
};
match suffix {
"A" => Ok(address),
"M" => Ok(address.multicast()),
_ => Err(ParseServiceAddressError(s.into())),
_ => Err(AddressKind::Service.into()),
}
}
}
Expand Down Expand Up @@ -187,7 +181,7 @@ mod tests {
fn $name() {
assert_eq!(
ServiceAddress::from_str($str).unwrap_err(),
ParseServiceAddressError($str.into())
AddressParseError(AddressKind::Service)
);
}
};
Expand Down
Loading

0 comments on commit 894fb77

Please sign in to comment.