use std::{
fmt::{Debug, Display, Formatter},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use tinyvec::ArrayVec;
use crate::{
address::ip_addr::ScionIpAddr,
core::{
encode::{InvalidStructureError, WireEncode},
macros::impl_from,
},
scion::address::{AddressParseError, addr::ScionAddr},
};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr,
)]
pub enum ScionHostAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
Svc(ServiceAddr),
}
impl ScionHostAddr {
#[inline]
pub const fn from_ip(ip: IpAddr) -> Self {
match ip {
IpAddr::V4(v4) => ScionHostAddr::V4(v4),
IpAddr::V6(v6) => ScionHostAddr::V6(v6),
}
}
#[inline]
pub const fn ip(&self) -> Option<IpAddr> {
match self {
ScionHostAddr::V4(v4) => Some(IpAddr::V4(*v4)),
ScionHostAddr::V6(v6) => Some(IpAddr::V6(*v6)),
_ => None,
}
}
#[inline]
pub const fn service(&self) -> Option<ServiceAddr> {
match self {
ScionHostAddr::Svc(svc) => Some(*svc),
_ => None,
}
}
#[inline]
pub fn to_wire_host_addr(&self) -> WireHostAddr {
(*self).into()
}
#[inline]
pub const fn is_ipv4(&self) -> bool {
matches!(self, ScionHostAddr::V4(_))
}
#[inline]
pub const fn is_ipv6(&self) -> bool {
matches!(self, ScionHostAddr::V6(_))
}
#[inline]
pub const fn is_service(&self) -> bool {
matches!(self, ScionHostAddr::Svc(_))
}
}
impl FromStr for ScionHostAddr {
type Err = AddressParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(ipv4) = s.parse::<Ipv4Addr>() {
Ok(ScionHostAddr::V4(ipv4))
} else if let Ok(ipv6) = s.parse::<Ipv6Addr>() {
Ok(ScionHostAddr::V6(ipv6))
} else if let Ok(svc) = s.parse::<ServiceAddr>() {
Ok(ScionHostAddr::Svc(svc))
} else {
Err(AddressParseError::HostAddr)
}
}
}
impl Display for ScionHostAddr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ScionHostAddr::V4(v4) => write!(f, "{}", v4)?,
ScionHostAddr::V6(v6) => write!(f, "{}", v6)?,
ScionHostAddr::Svc(svc) => write!(f, "{}", svc)?,
}
Ok(())
}
}
impl TryFrom<ScionHostAddr> for Ipv4Addr {
type Error = &'static str;
#[inline]
fn try_from(value: ScionHostAddr) -> Result<Self, Self::Error> {
match value {
ScionHostAddr::V4(v4) => Ok(v4),
_ => Err("HostAddr is not an Ipv4Addr"),
}
}
}
impl TryFrom<ScionHostAddr> for Ipv6Addr {
type Error = &'static str;
#[inline]
fn try_from(value: ScionHostAddr) -> Result<Self, Self::Error> {
match value {
ScionHostAddr::V6(v6) => Ok(v6),
_ => Err("HostAddr is not an Ipv6Addr"),
}
}
}
impl TryFrom<WireHostAddr> for ScionHostAddr {
type Error = UnknownAddressTypeError;
#[inline]
fn try_from(value: WireHostAddr) -> Result<Self, Self::Error> {
value.scion_host_addr()
}
}
impl_from!(IpAddr, ScionHostAddr, |value| ScionHostAddr::from_ip(value));
impl_from!(Ipv4Addr, ScionHostAddr, |value| ScionHostAddr::V4(value));
impl_from!(Ipv6Addr, ScionHostAddr, |value| ScionHostAddr::V6(value));
impl_from!(ServiceAddr, ScionHostAddr, |value| {
ScionHostAddr::Svc(value)
});
impl_from!(ScionAddr, ScionHostAddr, |value| value.host());
impl_from!(ScionIpAddr, ScionHostAddr, |value| value.host());
#[derive(Eq, PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "proptest", derive(proptest_derive::Arbitrary))]
pub struct ServiceAddr(pub u16);
impl ServiceAddr {
pub const DAEMON: Self = Self(0x0001);
pub const CONTROL: Self = Self(0x0002);
pub const WILDCARD: Self = Self(0x0010);
pub const NONE: Self = Self(0xffff);
const MULTICAST_FLAG: u16 = 0x8000;
#[inline]
pub const fn to_u16(&self) -> u16 {
self.0
}
#[inline]
pub const fn is_multicast(&self) -> bool {
(self.0 & Self::MULTICAST_FLAG) == Self::MULTICAST_FLAG
}
#[inline]
pub const fn to_multicast(self) -> Self {
Self(self.0 | Self::MULTICAST_FLAG)
}
#[inline]
pub const fn to_anycast(self) -> Self {
Self(self.0 & !Self::MULTICAST_FLAG)
}
#[inline]
pub const fn is_anycast(&self) -> bool {
(self.0 & Self::MULTICAST_FLAG) == 0
}
}
impl Display for ServiceAddr {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.to_anycast() {
ServiceAddr::DAEMON => write!(f, "DS")?,
ServiceAddr::CONTROL => write!(f, "CS")?,
ServiceAddr::WILDCARD => write!(f, "Wildcard")?,
ServiceAddr(value) => write!(f, "<SVC:{value:#06x}>")?,
}
if self.is_multicast() {
write!(f, "_M")?;
}
Ok(())
}
}
impl FromStr for ServiceAddr {
type Err = &'static str;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
const ERR: &str = "invalid service address";
let (service, suffix) = s.split_once('_').unwrap_or((s, "A"));
let address = match service {
"CS" => ServiceAddr::CONTROL,
"DS" => ServiceAddr::DAEMON,
"Wildcard" => ServiceAddr::WILDCARD,
_ => return Err(ERR),
};
match suffix {
"A" => Ok(address),
"M" => Ok(address.to_multicast()),
_ => Err(ERR),
}
}
}
impl TryFrom<ScionHostAddr> for ServiceAddr {
type Error = &'static str;
#[inline]
fn try_from(value: ScionHostAddr) -> Result<Self, Self::Error> {
match value {
ScionHostAddr::Svc(svc) => Ok(svc),
_ => Err("HostAddr is not a ServiceAddr"),
}
}
}
impl_from!(u16, ServiceAddr, |value| ServiceAddr(value));
impl_from!(ServiceAddr, u16, |value| value.0);
impl_from!(ServiceAddr, WireHostAddr, |value| {
WireHostAddr::Svc(value)
});
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WireHostAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
Svc(ServiceAddr),
Unknown {
id: u8,
bytes: ArrayVec<[u8; 16]>,
},
}
impl WireHostAddr {
pub fn try_from_parts(
addr_type: WireHostAddrType,
buf: &[u8],
) -> Result<Self, HostAddressSizeError> {
let addr = match addr_type {
WireHostAddrType::IPV4 => {
let buf: [u8; 4] = buf.try_into().map_err(|_| {
HostAddressSizeError {
address_type: addr_type,
expected_size: 4,
actual_size: buf.len(),
}
})?;
WireHostAddr::V4(Ipv4Addr::from(buf))
}
WireHostAddrType::IPV6 => {
let buf: [u8; 16] = buf.try_into().map_err(|_| {
HostAddressSizeError {
address_type: addr_type,
expected_size: 16,
actual_size: buf.len(),
}
})?;
WireHostAddr::V6(Ipv6Addr::from(buf))
}
WireHostAddrType::Service => {
let buf: [u8; 4] = buf.try_into().map_err(|_| {
HostAddressSizeError {
address_type: addr_type,
expected_size: 4,
actual_size: buf.len(),
}
})?;
let svc_addr = u16::from_be_bytes([buf[0], buf[1]]);
let svc_addr = ServiceAddr(svc_addr);
WireHostAddr::Svc(svc_addr)
}
WireHostAddrType::Unknown { id, size } => {
let bytes = buf.try_into().map_err(|_| {
HostAddressSizeError {
address_type: addr_type,
expected_size: size as usize,
actual_size: buf.len(),
}
})?;
WireHostAddr::Unknown { id, bytes }
}
};
Ok(addr)
}
#[inline]
pub const fn ip(&self) -> Option<IpAddr> {
match self {
WireHostAddr::V4(v4) => Some(IpAddr::V4(*v4)),
WireHostAddr::V6(v6) => Some(IpAddr::V6(*v6)),
_ => None,
}
}
#[inline]
pub const fn service(&self) -> Option<ServiceAddr> {
match self {
WireHostAddr::Svc(svc) => Some(*svc),
_ => None,
}
}
#[inline]
pub const fn scion_host_addr(&self) -> Result<ScionHostAddr, UnknownAddressTypeError> {
match self {
WireHostAddr::V4(v4) => Ok(ScionHostAddr::V4(*v4)),
WireHostAddr::V6(v6) => Ok(ScionHostAddr::V6(*v6)),
WireHostAddr::Svc(svc) => Ok(ScionHostAddr::Svc(*svc)),
WireHostAddr::Unknown { id, .. } => Err(UnknownAddressTypeError { id: *id }),
}
}
#[inline]
pub fn addr_type(&self) -> WireHostAddrType {
match self {
WireHostAddr::V4(_) => WireHostAddrType::IPV4,
WireHostAddr::V6(_) => WireHostAddrType::IPV6,
WireHostAddr::Svc(_) => WireHostAddrType::Service,
WireHostAddr::Unknown { id, bytes } => {
WireHostAddrType::Unknown {
id: *id,
size: bytes.len() as u8,
}
}
}
}
}
impl WireEncode for WireHostAddr {
#[inline]
fn required_size(&self) -> usize {
match self {
WireHostAddr::V4(_) => 4,
WireHostAddr::V6(_) => 16,
WireHostAddr::Svc(_) => 4,
WireHostAddr::Unknown { bytes, .. } => bytes.len(),
}
}
#[inline]
fn wire_valid(&self) -> Result<(), InvalidStructureError> {
match self {
WireHostAddr::V4(_) => Ok(()),
WireHostAddr::V6(_) => Ok(()),
WireHostAddr::Svc(_) => Ok(()),
WireHostAddr::Unknown { bytes, .. } => {
if bytes.is_empty() {
Err("ScionHostAddr::Unknown bytes.len() must be non-zero".into())
} else if !bytes.len().is_multiple_of(4) {
Err("ScionHostAddr::Unknown bytes.len() must be a multiple of 4".into())
} else {
Ok(())
}
}
}
}
unsafe fn encode_unchecked(&self, buf: &mut [u8]) -> usize {
match self {
WireHostAddr::V4(v4) => {
let bytes = v4.to_bits().to_be_bytes();
unsafe {
buf.get_unchecked_mut(..4).copy_from_slice(&bytes);
}
4
}
WireHostAddr::V6(v6) => {
let bytes = v6.to_bits().to_be_bytes();
unsafe {
buf.get_unchecked_mut(..16).copy_from_slice(&bytes);
}
16
}
WireHostAddr::Svc(addr) => {
let val = addr.to_u16().to_be_bytes();
let bytes = [val[0], val[1], 0, 0];
unsafe {
buf.get_unchecked_mut(..4).copy_from_slice(&bytes);
}
4
}
WireHostAddr::Unknown { bytes, .. } => {
let len = bytes.len();
unsafe {
buf.get_unchecked_mut(..len).copy_from_slice(bytes);
}
len
}
}
}
}
impl_from!(ScionHostAddr, WireHostAddr, |value| {
match value {
ScionHostAddr::V4(v4) => WireHostAddr::V4(v4),
ScionHostAddr::V6(v6) => WireHostAddr::V6(v6),
ScionHostAddr::Svc(svc) => WireHostAddr::Svc(svc),
}
});
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum WireHostAddrType {
IPV4 = 0b0000,
IPV6 = 0b0011,
Service = 0b0100,
Unknown {
id: u8,
size: u8,
},
}
impl WireHostAddrType {
#[inline]
pub const fn size(&self) -> u8 {
match self {
WireHostAddrType::IPV4 => 4,
WireHostAddrType::IPV6 => 16,
WireHostAddrType::Service => 4,
WireHostAddrType::Unknown { size, .. } => *size,
}
}
}
impl From<u8> for WireHostAddrType {
#[inline]
fn from(value: u8) -> Self {
match value {
0 => WireHostAddrType::IPV4,
0b0011 => WireHostAddrType::IPV6,
0b0100 => WireHostAddrType::Service,
other => {
let id = other >> 2;
let size = ((other & 0b11) + 1) * 4;
WireHostAddrType::Unknown { id, size }
}
}
}
}
impl From<WireHostAddrType> for u8 {
#[inline]
fn from(val: WireHostAddrType) -> Self {
match val {
WireHostAddrType::IPV4 => 0,
WireHostAddrType::IPV6 => 0b0011,
WireHostAddrType::Service => 0b0100,
WireHostAddrType::Unknown { id: type_id, size } => {
(type_id << 2) | (size / 4).saturating_sub(1)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum WireHostAddrError {
#[error(transparent)]
HostAddressSizeError(#[from] HostAddressSizeError),
#[error(transparent)]
UnknownAddressType(#[from] UnknownAddressTypeError),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
#[error("Unknown address type with id {id}")]
pub struct UnknownAddressTypeError {
pub id: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error(
"address size error: address type {address_type:?} expects {expected_size} bytes, got {actual_size} bytes"
)]
pub struct HostAddressSizeError {
pub address_type: WireHostAddrType,
pub expected_size: usize,
pub actual_size: usize,
}
#[cfg(feature = "proptest")]
pub mod ptest {
use ::proptest::prelude::*;
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ArbitraryWireHostAddrParams {
pub v4: u32,
pub v6: u32,
pub svc: u32,
pub unknown: u32,
}
impl Default for ArbitraryWireHostAddrParams {
fn default() -> Self {
Self {
v4: 3,
v6: 3,
svc: 3,
unknown: 1,
}
}
}
impl Arbitrary for WireHostAddr {
type Parameters = ArbitraryWireHostAddrParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
prop_oneof![
params.v4 => any::<Ipv4Addr>().prop_map(WireHostAddr::V4),
params.v6 => any::<Ipv6Addr>().prop_map(WireHostAddr::V6),
params.svc => any::<ServiceAddr>().prop_map(WireHostAddr::Svc),
params.unknown => arbitrary_unknown_wire_host_addr(),
]
.boxed()
}
}
fn arbitrary_unknown_wire_host_addr() -> impl Strategy<Value = WireHostAddr> {
(
2u8..=3,
proptest::collection::vec(prop::num::u8::ANY, 4..=16),
)
.prop_map(|(id, bytes_vec)| {
let chunks = bytes_vec.chunks_exact(4);
let mut bytes = ArrayVec::new();
for chunk in chunks {
for &b in chunk {
bytes.push(b);
}
}
WireHostAddr::Unknown { id, bytes }
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ArbitraryWireHostAddrTypeParams {
pub ipv4: u32,
pub ipv6: u32,
pub service: u32,
pub unknown: u32,
}
impl Default for ArbitraryWireHostAddrTypeParams {
fn default() -> Self {
Self {
ipv4: 3,
ipv6: 3,
service: 3,
unknown: 1,
}
}
}
impl Arbitrary for WireHostAddrType {
type Parameters = ArbitraryWireHostAddrTypeParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
prop_oneof![
params.ipv4 => Just(WireHostAddrType::IPV4),
params.ipv6 => Just(WireHostAddrType::IPV6),
params.service => Just(WireHostAddrType::Service),
params.unknown => arbitrary_unknown_wire_host_addr_type(),
]
.boxed()
}
}
fn arbitrary_unknown_wire_host_addr_type() -> impl Strategy<Value = WireHostAddrType> {
let size_strategy = prop::num::u8::ANY.prop_map(|size| ((size % 4) + 1) * 4);
(2u8..=3, size_strategy).prop_map(|(id, size)| WireHostAddrType::Unknown { id, size })
}
}