Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add socket address and simplify parse errors #33

Merged
merged 2 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
102 changes: 34 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,29 @@ 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 {
// Can not panic as the result is at most 48 bits (exactly 3 parts, 16 bits each)
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 +143,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 +168,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