From 19af3c2d4f7451d90abd9b58e420a1151f29f9db Mon Sep 17 00:00:00 2001 From: Jean-Pierre Smith Date: Fri, 27 Oct 2023 13:04:38 +0200 Subject: [PATCH] feat: add other headers --- crates/scion/src/address/host.rs | 22 +- crates/scion/src/address/service.rs | 8 +- crates/scion/src/packet.rs | 243 +++++----------------- crates/scion/src/packet/address_header.rs | 240 +++++++++++++++++++++ crates/scion/src/packet/common_header.rs | 83 ++++---- crates/scion/src/packet/path_header.rs | 197 ++++++++++++++++++ crates/scion/src/packet/wire_encoding.rs | 76 +++++++ 7 files changed, 635 insertions(+), 234 deletions(-) create mode 100644 crates/scion/src/packet/address_header.rs create mode 100644 crates/scion/src/packet/path_header.rs create mode 100644 crates/scion/src/packet/wire_encoding.rs diff --git a/crates/scion/src/address/host.rs b/crates/scion/src/address/host.rs index f59853d..ab553b6 100644 --- a/crates/scion/src/address/host.rs +++ b/crates/scion/src/address/host.rs @@ -1,9 +1,9 @@ -use std::net::{IpAddr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use super::ServiceAddress; /// The AS-local host identifier of a SCION address. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Host { /// An IPv4 or IPv6 host address Ip(IpAddr), @@ -92,3 +92,21 @@ where .unwrap_or(HostType::None) } } + +impl From for Host { + fn from(value: IpAddr) -> Self { + Host::Ip(value) + } +} + +impl From for Host { + fn from(value: Ipv4Addr) -> Self { + Host::Ip(value.into()) + } +} + +impl From for Host { + fn from(value: Ipv6Addr) -> Self { + Host::Ip(value.into()) + } +} diff --git a/crates/scion/src/address/service.rs b/crates/scion/src/address/service.rs index cf546f1..6a3ad69 100644 --- a/crates/scion/src/address/service.rs +++ b/crates/scion/src/address/service.rs @@ -5,7 +5,7 @@ use std::{ use thiserror; -use super::{HostAddress, HostType}; +use super::{Host, HostAddress, HostType}; /// A SCION service address. /// @@ -112,6 +112,12 @@ impl From for u16 { } } +impl From for Host { + fn from(value: ServiceAddress) -> Self { + Host::Svc(value) + } +} + impl Display for ServiceAddress { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.anycast() { diff --git a/crates/scion/src/packet.rs b/crates/scion/src/packet.rs index a92dbf5..b73435e 100644 --- a/crates/scion/src/packet.rs +++ b/crates/scion/src/packet.rs @@ -1,110 +1,48 @@ //! SCION packet module. //! -use std::{ - convert::Infallible, - net::{Ipv4Addr, Ipv6Addr}, -}; - -// TODO(jsmith): Document the module. -// This is the format for the scion packet on the wire. The goal is to enable using this in -// other libraries where they may want to do their own stuff with SCION packets and, as such, -// this should be permissible. use bytes::{Buf, Bytes}; mod common_header; pub use common_header::{AddressInfo, CommonHeader, FlowId, Version}; -use crate::address::{Host, HostType, IsdAsn, ServiceAddress}; - -pub trait WireDecode -where - T: Buf, - Self: Sized, -{ - type Error; - - fn decode(data: &mut T) -> Result; -} +mod address_header; +pub use address_header::{AddressHeader, RawHostAddress}; + +mod path_header; +pub use path_header::{ + HopFieldIndex, + InfoFieldIndex, + PathHeader, + PathMetaHeader, + PathMetaReserved, + SegmentLength, +}; -pub trait WireDecodeWithContext -where - T: Buf, - Self: Sized, -{ - type Error; +mod wire_encoding; +use wire_encoding::{bounded_uint, DecodeError, MaybeEncoded, WireDecode, WireDecodeWithContext}; - fn decode_with_context(data: &mut T, context: U) -> Result; +/// Instances of an object associated with both a source and destination endpoint. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ByEndpoint { + destination: T, + source: T, } -/// Represents a Scion packet with no assumptions made on the fields +/// A SCION network packet. #[allow(unused)] -pub struct Scion { +pub struct Scion { + /// Metadata about the remaining headers and payload. common_header: CommonHeader, + /// Source and destination addresses. address_header: AddressHeader, + /// The path to the destination, when necessary. path_header: Option, - extensions_header: Option<()>, - - payload: T, -} - -pub enum PathHeader { - Standard(StandardPathHeader), -} - -pub struct StandardPathHeader { - pub meta_header: PathMetaHeader, - pub hop_fields: Bytes, -} - -impl WireDecodeWithContext for StandardPathHeader -where - T: Buf + Into, -{ - type Error = Infallible; - - /// SOmething - fn decode_with_context(data: &mut T, path_length: usize) -> Result { - let mut path_data = data.take(path_length); - assert_eq!(path_data.remaining(), path_length); - - Ok(Self { - meta_header: PathMetaHeader::decode(&mut path_data)?, - hop_fields: path_data.copy_to_bytes(path_data.remaining()), - }) - } -} - -pub struct PathMetaHeader { - pub current_info_field: u8, - pub current_hop_field: u8, - pub reserved: u8, - pub segment_length: [u8; 3], -} - -impl WireDecode for PathMetaHeader { - type Error = Infallible; - - fn decode(data: &mut T) -> Result { - const BIT_LENGTH: usize = 6; - const MASK: u32 = 0b11_1111; - - let fields = data.get_u32(); - - Ok(Self { - current_info_field: (fields >> (5 * BIT_LENGTH)) as u8, - current_hop_field: (fields >> (4 * BIT_LENGTH) & MASK) as u8, - reserved: (fields >> (3 * BIT_LENGTH) & MASK) as u8, - segment_length: [ - (fields >> (2 * BIT_LENGTH) & MASK) as u8, - (fields >> BIT_LENGTH & MASK) as u8, - (fields & MASK) as u8, - ], - }) - } + /// The packet payload. + payload: Bytes, } -impl WireDecode for Scion +impl WireDecode for Scion where T: Buf, { @@ -114,118 +52,31 @@ where let initial_length = data.remaining(); let common_header = CommonHeader::decode(data)?; - let address_header = AddressHeader::decode_with_context(data, common_header.address_info); + let address_header = AddressHeader::decode_with_context(data, common_header.address_info)?; let consumed = initial_length - data.remaining(); - // let path_length = common_header.header_length() - consumed; - - todo!() - } -} - -#[repr(u8)] -#[derive(Debug, PartialEq, Eq)] -pub enum PathType { - Empty = 0, - Scion, - OneHop, - Epic, - Colibri, -} - -#[derive(Debug, thiserror::Error)] -#[error("Unsupported path type {0}")] -pub struct UnsupportedPathType(pub u8); - -impl TryFrom for PathType { - type Error = UnsupportedPathType; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Empty), - 1 => Ok(Self::Scion), - 2 => Ok(Self::OneHop), - 3 => Ok(Self::Epic), - 4 => Ok(Self::Colibri), - _ => Err(UnsupportedPathType(value)), + let path_length = common_header + .header_length() + .expect("decoded headers always have a length") + - consumed; + + let path_header = + match PathHeader::decode_with_context(data, (common_header.path_type, path_length)) { + Err(DecodeError::EmptyPath) => None, + other => Some(other?), + }; + + if data.remaining() < common_header.payload_size() { + return Err(DecodeError::PacketTruncated); } - } -} - -impl From for u8 { - fn from(value: PathType) -> Self { - value as u8 - } -} - -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum DecodeError { - #[error("Cannot decode packet with unsupported header version {0:?}")] - UnsupportedVersion(Version), - #[error("Header length factor is inconsistent with the SCION specification: {0}")] - InvalidHeaderLength(u8), -} - -#[derive(Debug, PartialEq, Eq)] -pub enum MaybeEncoded { - Decoded(T), - Encoded(U), -} - -pub type RawHostAddress = [u8; AddressInfo::MAX_ADDRESS_BYTES]; - -pub struct AddressHeader { - pub ia: ByEndpoint, - pub host: ByEndpoint>, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct ByEndpoint { - destination: T, - source: T, -} -impl WireDecodeWithContext> for AddressHeader -where - T: Buf, -{ - type Error = Infallible; + let payload = data.copy_to_bytes(common_header.payload_size()); - fn decode_with_context( - data: &mut T, - context: ByEndpoint, - ) -> Result { - Ok(Self { - ia: ByEndpoint { - destination: IsdAsn(data.get_u64()), - source: IsdAsn(data.get_u64()), - }, - host: ByEndpoint { - destination: maybe_decode_host(data, context.destination), - source: maybe_decode_host(data, context.source), - }, + Ok(Scion { + common_header, + address_header, + path_header, + payload, }) } } - -fn maybe_decode_host(data: &mut T, info: AddressInfo) -> MaybeEncoded -where - T: Buf, -{ - match info.host_type() { - MaybeEncoded::Decoded(host_type) => MaybeEncoded::Decoded(match host_type { - HostType::None => unreachable!("AddressInfo never returns None host type"), - HostType::Ipv4 => Host::Ip(Ipv4Addr::from(data.get_u32()).into()), - HostType::Ipv6 => Host::Ip(Ipv6Addr::from(data.get_u128()).into()), - HostType::Svc => Host::Svc(ServiceAddress(data.get_u16())), - }), - MaybeEncoded::Encoded(_) => { - let mut host_address: RawHostAddress = [0u8; AddressInfo::MAX_ADDRESS_BYTES]; - - assert!(info.address_length() <= AddressInfo::MAX_ADDRESS_BYTES); - data.copy_to_slice(&mut host_address[..info.address_length()]); - - MaybeEncoded::Encoded(host_address) - } - } -} diff --git a/crates/scion/src/packet/address_header.rs b/crates/scion/src/packet/address_header.rs new file mode 100644 index 0000000..7fa0b6c --- /dev/null +++ b/crates/scion/src/packet/address_header.rs @@ -0,0 +1,240 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; + +use bytes::Buf; + +use super::{AddressInfo, ByEndpoint, DecodeError, MaybeEncoded, WireDecodeWithContext}; +use crate::address::{Host, HostType, IsdAsn, ServiceAddress}; + +/// The bytes of an encoded host address. +/// +/// The length of the host address is stored in an associated [`AddressInfo`] instance. +pub type RawHostAddress = [u8; AddressInfo::MAX_ADDRESS_BYTES]; + +/// The address header of a SCION packet. +/// +/// It contains the SCION ISD-AS number and host address of the destination and +/// source endpoints. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AddressHeader { + /// The ISD-AS numbers of the source and destination hosts. + pub ia: ByEndpoint, + /// The host addresses of the source and destination. + pub host: ByEndpoint>, +} + +impl WireDecodeWithContext for AddressHeader +where + T: Buf, +{ + type Error = DecodeError; + type Context = ByEndpoint; + + fn decode_with_context( + data: &mut T, + context: ByEndpoint, + ) -> Result { + const IA_BYTE_LENGTH: usize = 8; + + if data.remaining() < 2 * IA_BYTE_LENGTH { + return Err(Self::Error::PacketTruncated); + } + + Ok(Self { + ia: ByEndpoint { + destination: IsdAsn(data.get_u64()), + source: IsdAsn(data.get_u64()), + }, + host: ByEndpoint { + destination: maybe_decode_host(data, context.destination)?, + source: maybe_decode_host(data, context.source)?, + }, + }) + } +} + +fn maybe_decode_host( + data: &mut T, + info: AddressInfo, +) -> Result, DecodeError> +where + T: Buf, +{ + if data.remaining() < info.address_length() { + return Err(DecodeError::PacketTruncated); + } + + Ok(match info.host_type() { + MaybeEncoded::Decoded(host_type) => MaybeEncoded::Decoded(match host_type { + HostType::None => unreachable!("AddressInfo never returns None host type"), + HostType::Ipv4 => Host::Ip(Ipv4Addr::from(data.get_u32()).into()), + HostType::Ipv6 => Host::Ip(Ipv6Addr::from(data.get_u128()).into()), + HostType::Svc => { + let address = Host::Svc(ServiceAddress(data.get_u16())); + // Remove service address's 2-byte padding + let _ = data.get_u16(); + + address + } + }), + MaybeEncoded::Encoded(_) => { + let mut host_address: RawHostAddress = [0u8; AddressInfo::MAX_ADDRESS_BYTES]; + + assert!(info.address_length() <= AddressInfo::MAX_ADDRESS_BYTES); + data.copy_to_slice(&mut host_address[..info.address_length()]); + + MaybeEncoded::Encoded(host_address) + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + mod decode { + use bytes::{BufMut, BytesMut}; + + use super::*; + use crate::test_utils::parse; + + #[test] + fn ipv4_to_ipv4() { + let destination: IsdAsn = parse!("1-ff00:0:ab"); + let source: IsdAsn = parse!("1-ff00:0:cd"); + + let mut data = BytesMut::new(); + + data.put_u64(destination.as_u64()); + data.put_u64(source.as_u64()); + data.put_slice(&[10, 0, 0, 1, 192, 168, 0, 1]); + + let mut data = data.freeze(); + + let result = AddressHeader::decode_with_context( + &mut data, + ByEndpoint { + destination: AddressInfo::IPV4, + source: AddressInfo::IPV4, + }, + ) + .expect("should successfully decode"); + + assert_eq!( + result, + AddressHeader { + ia: ByEndpoint { + destination, + source + }, + host: ByEndpoint { + destination: MaybeEncoded::Decoded(Ipv4Addr::new(10, 0, 0, 1).into()), + source: MaybeEncoded::Decoded(Ipv4Addr::new(192, 168, 0, 1).into()), + } + } + ); + assert_eq!(data.remaining(), 0); + } + + #[test] + fn ipv6_to_service() { + let destination: IsdAsn = parse!("31-ff00:96:0"); + let source: IsdAsn = parse!("47-ff13:0:cd"); + let ipv6_source: Ipv6Addr = parse!("2001:0db8:ac10:fe01::"); + let service_destination = ServiceAddress::DAEMON.multicast(); + + let mut data = BytesMut::new(); + + data.put_u64(destination.as_u64()); + data.put_u64(source.as_u64()); + data.put_u16(service_destination.into()); + data.put_u16(0); + data.put_slice(&ipv6_source.octets()); + + let mut data = data.freeze(); + + let result = AddressHeader::decode_with_context( + &mut data, + ByEndpoint { + destination: AddressInfo::SERVICE, + source: AddressInfo::IPV6, + }, + ) + .expect("should successfully decode"); + + assert_eq!( + result, + AddressHeader { + ia: ByEndpoint { + destination, + source + }, + host: ByEndpoint { + destination: MaybeEncoded::Decoded(service_destination.into()), + source: MaybeEncoded::Decoded(ipv6_source.into()), + } + } + ); + assert_eq!(data.remaining(), 0); + } + + #[test] + fn unknown_to_unknown() { + let destination: IsdAsn = parse!("31-ff00:96:0"); + let source: IsdAsn = parse!("47-ff13:0:cd"); + let destination_host = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + let source_host = [8u8, 7, 6, 5, 4, 3, 2, 1]; + + let mut data = BytesMut::new(); + + data.put_u64(destination.as_u64()); + data.put_u64(source.as_u64()); + data.put_slice(&destination_host); + data.put_slice(&source_host); + + let mut data = data.freeze(); + + let result = AddressHeader::decode_with_context( + &mut data, + ByEndpoint { + source: AddressInfo::new(0b1001).unwrap(), + destination: AddressInfo::new(0b0110).unwrap(), + }, + ) + .expect("should successfully decode"); + + assert_eq!( + result, + AddressHeader { + ia: ByEndpoint { + destination, + source + }, + host: ByEndpoint { + destination: MaybeEncoded::Encoded([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 0 + ]), + source: MaybeEncoded::Encoded([ + 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0 + ]), + } + } + ); + assert_eq!(data.remaining(), 0); + } + + #[test] + fn truncated() { + let data = [0u8; 19]; + + let result = AddressHeader::decode_with_context( + &mut data.as_slice(), + ByEndpoint { + destination: AddressInfo::SERVICE, + source: AddressInfo::IPV6, + }, + ); + + assert_eq!(result, Err(DecodeError::PacketTruncated)); + } + } +} diff --git a/crates/scion/src/packet/common_header.rs b/crates/scion/src/packet/common_header.rs index 569dfce..32ab750 100644 --- a/crates/scion/src/packet/common_header.rs +++ b/crates/scion/src/packet/common_header.rs @@ -2,7 +2,14 @@ use std::num::NonZeroU8; use bytes::Buf; -use super::{ByEndpoint, DecodeError, MaybeEncoded, PathType, UnsupportedPathType, WireDecode}; +use super::{ + bounded_uint, + path_header::{PathType, UnsupportedPathType}, + ByEndpoint, + DecodeError, + MaybeEncoded, + WireDecode, +}; use crate::address::HostType; /// SCION packet common header. @@ -64,6 +71,9 @@ pub struct CommonHeader { } impl CommonHeader { + /// The length of a version 0 common header in bytes. + pub const LENGTH: usize = 12; + /// Minimum header length factor defined as 36 bytes / 4. const MIN_HEADER_LENGTH_FACTOR: u8 = 9; @@ -74,6 +84,11 @@ impl CommonHeader { self.header_length_factor .map(|value| usize::from(value.get()) * 4) } + + /// The payload length as a usize. + pub fn payload_size(&self) -> usize { + usize::try_from(self.payload_length).expect("usize to be larger than 16-bites") + } } /// 4-bit SCION packet version number. @@ -94,42 +109,33 @@ impl Version { } } -/// 20-bit SCION packet flow identifier. -#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct FlowId(u32); - -impl FlowId { - /// The maximum valid Flow ID. - pub const MAX: Self = FlowId((1 << 20) - 1); - - /// Create a new flow identifier if the value is less than 2^20. - pub const fn new(flow_id: u32) -> Option { - if flow_id <= Self::MAX.0 { - Some(Self(flow_id)) - } else { - None - } - } +bounded_uint! { + /// A 20-bit SCION packet flow identifier. + pub struct FlowId(u32 : 20); } -/// An AddressInfo instance describes the type and length of a host address in the SCION -/// common header. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct AddressInfo(u8); +bounded_uint!( + /// An AddressInfo instance describes the type and length of a host address in + /// the SCION common header. + pub struct AddressInfo(u8 : 4); +); impl AddressInfo { /// The maximum length of an address. pub const MAX_ADDRESS_BYTES: usize = 16; - /// Creates a new address info object if the value is less than 16. - pub const fn new(value: u8) -> Option { - if value < (1 << 4) { - Some(Self(value)) - } else { - None - } - } + /// The address info for an IPv4 host. + pub const IPV4: Self = Self(0b0000); + + /// The address info for an IPv6 host. + pub const IPV6: Self = Self(0b0011); + + /// The address info for a Service host. + pub const SERVICE: Self = Self(0b0100); + /// Create an AddressInfo from the underlying host type. + /// + /// Returns None for a [`HostType::None`]. pub fn from_host_type(value: HostType) -> Option { match value { HostType::None => None, @@ -172,11 +178,18 @@ where type Error = DecodeError; fn decode(data: &mut T) -> Result { + if !data.has_remaining() { + return Err(Self::Error::PacketTruncated); + } + // Check the version without advancing the buffer. let version = Version(data.chunk()[0] >> 4); if version != Version(0) { return Err(Self::Error::UnsupportedVersion(version)); } + if data.remaining() < Self::LENGTH { + return Err(Self::Error::PacketTruncated); + } let traffic_and_flow_info = data.get_u32(); let traffic_class = ((traffic_and_flow_info & 0x0f_f0_00_00) >> 20) as u8; @@ -226,7 +239,7 @@ where mod tests { use super::*; - fn base_packet() -> ([u8; 12], CommonHeader) { + fn base_header() -> ([u8; 12], CommonHeader) { let data: [u8; 12] = [ 0x08, 0x18, 0x00, 0x01, 0x11, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; @@ -250,7 +263,7 @@ mod tests { #[test] fn valid_ipv4_to_ipv4() { - let (data, expected) = base_packet(); + let (data, expected) = base_header(); let decoded = CommonHeader::decode(&mut data.as_slice()).expect("must successfully decode"); @@ -260,7 +273,7 @@ mod tests { #[test] fn valid_svc_to_ipv6() { - let (mut data, expected) = base_packet(); + let (mut data, expected) = base_header(); data[9] = 0x34; @@ -278,7 +291,7 @@ mod tests { #[test] fn valid_unknown_path_type() { - let (mut data, expected) = base_packet(); + let (mut data, expected) = base_header(); data[8] = 0x05; assert_eq!( @@ -292,7 +305,7 @@ mod tests { #[test] fn unsupported_version() { - let (mut data, _) = base_packet(); + let (mut data, _) = base_header(); // Unset and reset the version data[0] = (data[0] & 0b0000_1111) | 0b0001_0000; @@ -305,7 +318,7 @@ mod tests { #[test] fn invalid_header_length() { - let (mut data, _) = base_packet(); + let (mut data, _) = base_header(); data[5] = 0x08; assert_eq!( diff --git a/crates/scion/src/packet/path_header.rs b/crates/scion/src/packet/path_header.rs new file mode 100644 index 0000000..48585b1 --- /dev/null +++ b/crates/scion/src/packet/path_header.rs @@ -0,0 +1,197 @@ +use std::mem; + +use bytes::{Buf, Bytes}; + +use super::{bounded_uint, DecodeError, MaybeEncoded, WireDecode, WireDecodeWithContext}; + +/// SCION path types that may be encountered in a packet +#[repr(u8)] +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum PathType { + /// The empty path type. + Empty = 0, + /// The standard SCION path type. + Scion, + /// One-hop paths between neighbouring border routers. + OneHop, + /// Experimental Epic path type. + Epic, + /// Experimental Colibri path type. + Colibri, +} + +impl From for u8 { + fn from(value: PathType) -> Self { + value as u8 + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Unsupported path type {0}")] +pub struct UnsupportedPathType(pub u8); + +impl TryFrom for PathType { + type Error = UnsupportedPathType; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Empty), + 1 => Ok(Self::Scion), + 2 => Ok(Self::OneHop), + 3 => Ok(Self::Epic), + 4 => Ok(Self::Colibri), + _ => Err(UnsupportedPathType(value)), + } + } +} + +/// Path header found in a SCION packet +pub enum PathHeader { + /// The standard SCION path header. + Standard(StandardPathHeader), + /// The raw bytes of an unsupported path header type. + Unsupported(Bytes), +} + +impl WireDecodeWithContext for PathHeader +where + T: Buf, +{ + type Error = DecodeError; + type Context = (MaybeEncoded, usize); + + fn decode_with_context( + data: &mut T, + type_and_length: (MaybeEncoded, usize), + ) -> Result { + let (path_type, path_length) = type_and_length; + if data.remaining() < path_length { + return Err(DecodeError::PacketTruncated); + } + + match path_type { + MaybeEncoded::Decoded(PathType::Empty) => Err(Self::Error::EmptyPath), + MaybeEncoded::Decoded(PathType::Scion) => { + Ok(StandardPathHeader::decode_with_context(data, path_length)?.into()) + } + MaybeEncoded::Decoded(_) | MaybeEncoded::Encoded(_) => { + Ok(PathHeader::Unsupported(data.copy_to_bytes(path_length))) + } + } + } +} + +bounded_uint! { + /// A 2-bit index into the info fields + #[derive(Default)] + pub struct InfoFieldIndex(u8 : 2); +} + +bounded_uint! { + /// A 6-bit index into the hop fields + #[derive(Default)] + pub struct HopFieldIndex(u8 : 6); +} + +bounded_uint! { + /// A 6-bit count of the number of hop fields in a path segment. + #[derive(Default)] + pub struct SegmentLength(u8 : 6); +} + +bounded_uint! { + /// A 6-bit reserved field withiin the [`PathMetaHeader`]. + #[derive(Default)] + pub struct PathMetaReserved(u8 : 6); +} + +/// Meta information about the SCION path contained in a [`StandardPathHeader`]. +#[derive(Debug, Default)] +pub struct PathMetaHeader { + /// An index to the current info field for the packet on its way through the network. + pub current_info_field: InfoFieldIndex, + + /// An index to the current hop field within the segment pointed to by the info field. + /// + /// For valid SCION packets, this should point at a hop field associated with the + /// current info field. + pub current_hop_field: HopFieldIndex, + + /// Unused bits in the path path meta header. + pub reserved: PathMetaReserved, + + /// The number of hop fields in a given segment. + /// + /// For valid SCION packets, the SegmentLengths at indices 1 and 2 should be non-zero + /// only if the preceding SegmentLengths are non-zero. + pub segment_length: [SegmentLength; 3], +} + +impl WireDecode for PathMetaHeader { + type Error = DecodeError; + + fn decode(data: &mut T) -> Result { + const BIT_LENGTH: usize = 6; + const MASK: u32 = 0b11_1111; + + if data.remaining() < mem::size_of::() { + return Err(DecodeError::PacketTruncated); + } + + let fields = data.get_u32(); + + Ok(Self { + current_info_field: InfoFieldIndex((fields >> (5 * BIT_LENGTH)) as u8), + current_hop_field: HopFieldIndex((fields >> (4 * BIT_LENGTH) & MASK) as u8), + reserved: PathMetaReserved((fields >> (3 * BIT_LENGTH) & MASK) as u8), + segment_length: [ + SegmentLength((fields >> (2 * BIT_LENGTH) & MASK) as u8), + SegmentLength((fields >> BIT_LENGTH & MASK) as u8), + SegmentLength((fields & MASK) as u8), + ], + }) + } +} + +/// The standard SCION path header. +/// +/// Consists of a [`PathMetaHeader`] along with one or more [`InfoField`]s +/// and [`HopField`]s. +/// +/// No invariants are maintained between the meta header and the raw bytes +/// containing the info and hop fields. +pub struct StandardPathHeader { + /// The meta information about the stored path. + pub meta_header: PathMetaHeader, + /// The raw data containing the info and hop fields. + pub path: Bytes, +} + +impl WireDecodeWithContext for StandardPathHeader +where + T: Buf, +{ + type Error = DecodeError; + type Context = usize; + + fn decode_with_context(data: &mut T, path_length: usize) -> Result { + if data.remaining() < path_length { + return Err(DecodeError::PacketTruncated); + } + + let mut path_data = data.take(path_length); + debug_assert_eq!(path_data.remaining(), path_length); + + Ok(Self { + meta_header: PathMetaHeader::decode(&mut path_data)?, + path: path_data.copy_to_bytes(path_data.remaining()), + }) + } +} + +impl From for PathHeader { + fn from(value: StandardPathHeader) -> Self { + PathHeader::Standard(value) + } +} diff --git a/crates/scion/src/packet/wire_encoding.rs b/crates/scion/src/packet/wire_encoding.rs new file mode 100644 index 0000000..26e14eb --- /dev/null +++ b/crates/scion/src/packet/wire_encoding.rs @@ -0,0 +1,76 @@ +use bytes::Buf; + +use super::Version; + +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum DecodeError { + #[error("Cannot decode packet with unsupported header version {0:?}")] + UnsupportedVersion(Version), + #[error("Header length factor is inconsistent with the SCION specification: {0}")] + InvalidHeaderLength(u8), + #[error("The provided bytes did not include the full packet")] + PacketTruncated, + #[error("Attempted to decode the empty path type")] + EmptyPath, +} + +pub trait WireDecode +where + T: Buf, + Self: Sized, +{ + type Error; + + fn decode(data: &mut T) -> Result; +} + +pub trait WireDecodeWithContext +where + T: Buf, + Self: Sized, +{ + type Error; + type Context; + + fn decode_with_context(data: &mut T, context: Self::Context) -> Result; +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum MaybeEncoded { + Decoded(T), + Encoded(U), +} + +macro_rules! bounded_uint { + ( + $(#[$outer:meta])* + pub struct $name:ident($type:ty : $bits:literal); + ) => { + $(#[$outer])* + #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] + pub struct $name($type); + + impl $name { + /// The number of bits useable for an instance of this type. + pub const BITS: u32 = $bits; + + /// The maximum possible value for an instance of this type. + pub const MAX: Self = Self((1 << $bits) - 1); + + /// Create a new instance if the value is less than `Self::MAX.value()`. + pub const fn new(value: $type) -> Option { + if value <= Self::MAX.0 { + Some(Self(value)) + } else { + None + } + } + + /// Get the value of this instance as its underlying type. + pub const fn value(&self) -> $type { + self.0 + } + } + }; +} +pub(crate) use bounded_uint;