use crate::{
core::{
debug::Annotations,
encode::WireEncode,
layout::{BitRange, Layout, LayoutParseError, macros::gen_bitrange_const},
view::{View, ViewConversionError},
},
header::{model::ScionPacketHeader, view::ScionHeaderView},
path::{
layout::ScionHeaderPathLayout,
model::Path,
onehop::layout::OneHopPathLayout,
standard::{
layout::{StdPathDataLayout, StdPathMetaLayout},
view::StandardPathView,
},
types::PathType,
},
};
pub struct ScionHeaderLayout {
pub common: CommonHeaderLayout,
pub address: AddressHeaderLayout,
pub path: ScionHeaderPathLayout,
pub header_len: usize,
pub payload_len: usize,
}
impl ScionHeaderLayout {
pub const MAX_SIZE_BYTES: usize = 1020;
pub fn from_parts(
src_addr_len: u8,
dst_addr_len: u8,
path: ScionHeaderPathLayout,
payload_len: usize,
) -> Self {
let common = CommonHeaderLayout;
let address = AddressHeaderLayout::new(src_addr_len, dst_addr_len);
let header_len = common.size_bytes() + address.size_bytes() + path.size_bytes();
ScionHeaderLayout {
common,
address,
path,
header_len,
payload_len,
}
}
pub fn from_slice(buf: &[u8]) -> Result<Self, LayoutParseError> {
let len = buf.len();
let common = CommonHeaderLayout;
let (common_buf, _rest) = common.split_off_checked(buf).ok_or_else(|| {
LayoutParseError::BufferTooSmall {
at: "CommonHeader",
required: common.size_bytes(),
actual: buf.len(),
}
})?;
let common_view = unsafe { ScionHeaderView::from_slice_unchecked(common_buf) };
if common_view.version() != 0 {
return Err(LayoutParseError::UnsupportedVersion);
}
let path_type = common_view.path_type();
let src_addr_len = common_view.src_addr_type().size();
let dst_addr_len = common_view.dst_addr_type().size();
let total_header_size = common_view.header_len() as usize;
let payload_size = common_view.payload_len() as usize;
let addr_header_end =
common.size_bytes() + AddressHeaderLayout::new(src_addr_len, dst_addr_len).size_bytes();
if buf.len() < addr_header_end {
return Err(LayoutParseError::BufferTooSmall {
at: "AddressHeader",
required: addr_header_end,
actual: len,
});
}
let path = match path_type {
PathType::Scion => {
let (path_meta_buf, _rest) = StdPathMetaLayout
.split_off_checked(&buf[addr_header_end..])
.ok_or_else(|| {
LayoutParseError::BufferTooSmall {
at: "PathMeta",
required: StdPathMetaLayout.size_bytes(),
actual: buf.len() - addr_header_end,
}
})?;
let path_meta_view =
unsafe { StandardPathView::from_slice_unchecked(path_meta_buf) };
let seg0_len = path_meta_view.seg0_len();
let seg1_len = path_meta_view.seg1_len();
let seg2_len = path_meta_view.seg2_len();
let path_data_layout = StdPathDataLayout::new(seg0_len, seg1_len, seg2_len);
ScionHeaderPathLayout::Standard(StdPathMetaLayout, path_data_layout)
}
PathType::OneHop => ScionHeaderPathLayout::OneHop(OneHopPathLayout),
PathType::Empty => ScionHeaderPathLayout::Empty,
path_type => {
if total_header_size < addr_header_end {
return Err(LayoutParseError::BufferTooSmall {
at: "path",
required: addr_header_end * 8,
actual: total_header_size * 8,
});
}
ScionHeaderPathLayout::Unknown {
path_type,
range: BitRange::from_range(addr_header_end * 8..(total_header_size * 8)),
}
}
};
let calculated_size = common.size_bytes()
+ AddressHeaderLayout::new(src_addr_len, dst_addr_len).size_bytes()
+ path.size_bytes();
if calculated_size > buf.len() {
return Err(LayoutParseError::BufferTooSmall {
at: "TotalHeader",
required: calculated_size,
actual: buf.len(),
});
}
if calculated_size != total_header_size {
return Err(LayoutParseError::InvalidHeaderLength {
advertised: total_header_size,
actual: calculated_size,
});
}
Ok(Self {
common,
address: AddressHeaderLayout {
src_addr_len,
dst_addr_len,
},
path,
header_len: total_header_size,
payload_len: payload_size,
})
}
pub fn from_loaded(packet: &ScionPacketHeader) -> Self {
let common = CommonHeaderLayout;
let address = AddressHeaderLayout::new(
packet.address.src_host_addr.required_size() as u8,
packet.address.dst_host_addr.required_size() as u8,
);
let path = match &packet.path {
Path::Standard(std_path) => {
let (seg0, seg1, seg2) = std_path.segment_lengths();
ScionHeaderPathLayout::Standard(
StdPathMetaLayout,
StdPathDataLayout::new(seg0, seg1, seg2),
)
}
Path::OneHop(_) => ScionHeaderPathLayout::OneHop(OneHopPathLayout),
Path::Empty => ScionHeaderPathLayout::Empty,
Path::Unsupported { path_type, data } => {
let addr_end = common.size_bytes() + address.size_bytes();
ScionHeaderPathLayout::Unknown {
path_type: *path_type,
range: BitRange::from_range(addr_end * 8..(addr_end + data.len()) * 8),
}
}
};
let header_len = common.size_bytes() + address.size_bytes() + path.size_bytes();
Self {
common,
address,
path,
header_len,
payload_len: 0,
}
}
}
impl ScionHeaderLayout {
pub fn annotations(&self) -> Annotations {
let mut annotations = Annotations::new();
annotations.extend(CommonHeaderLayout.annotations());
annotations.extend(self.address.annotations());
annotations.extend(self.path.annotations());
annotations
}
}
impl Layout for ScionHeaderLayout {
#[inline]
fn size_bytes(&self) -> usize {
self.header_len
}
}
impl TryFrom<&[u8]> for ScionHeaderLayout {
type Error = ViewConversionError;
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
Self::from_slice(buf).map_err(ViewConversionError::from)
}
}
pub struct CommonHeaderLayout;
impl CommonHeaderLayout {
gen_bitrange_const!(VERSION_RNG, 0, 4);
gen_bitrange_const!(TRAFFIC_CLASS_RNG, 4, 8);
gen_bitrange_const!(FLOW_ID_RNG, 12, 20);
gen_bitrange_const!(NEXT_HEADER_RNG, 32, 8);
gen_bitrange_const!(HEADER_LEN_RNG, 40, 8);
gen_bitrange_const!(PAYLOAD_LEN_RNG, 48, 16);
gen_bitrange_const!(PATH_TYPE_RNG, 64, 8);
gen_bitrange_const!(DST_ADDR_INFO_RNG, 72, 4);
gen_bitrange_const!(SRC_ADDR_INFO_RNG, 76, 4);
gen_bitrange_const!(RSV_RNG, 80, 16);
gen_bitrange_const!(TOTAL_RNG, 0, 96);
pub const SIZE_BYTES: usize = Self::TOTAL_RNG.end / 8;
}
impl CommonHeaderLayout {
pub fn annotations(&self) -> Annotations {
let ann = vec![
(CommonHeaderLayout::VERSION_RNG, "version"),
(CommonHeaderLayout::TRAFFIC_CLASS_RNG, "traffic_class"),
(CommonHeaderLayout::FLOW_ID_RNG, "flow_id"),
(CommonHeaderLayout::NEXT_HEADER_RNG, "next_header"),
(CommonHeaderLayout::HEADER_LEN_RNG, "header_length"),
(CommonHeaderLayout::PAYLOAD_LEN_RNG, "payload_length"),
(CommonHeaderLayout::PATH_TYPE_RNG, "path_type"),
(CommonHeaderLayout::DST_ADDR_INFO_RNG, "dst_addr_info"),
(CommonHeaderLayout::SRC_ADDR_INFO_RNG, "src_addr_info"),
(CommonHeaderLayout::RSV_RNG, "rsv"),
];
Annotations::new_with("CommonHeader".to_string(), ann)
}
}
impl Layout for CommonHeaderLayout {
#[inline]
fn size_bytes(&self) -> usize {
Self::TOTAL_RNG.end / 8
}
}
pub struct AddressHeaderLayout {
pub src_addr_len: u8,
pub dst_addr_len: u8,
}
impl AddressHeaderLayout {
#[inline]
pub const fn new(src_addr_len: u8, dst_addr_len: u8) -> Self {
Self {
src_addr_len,
dst_addr_len,
}
}
gen_bitrange_const!(DST_ISD_RNG, 0, 16);
gen_bitrange_const!(DST_AS_RNG, 16, 48);
gen_bitrange_const!(DST_IA_RNG, 0, 64);
gen_bitrange_const!(SRC_ISD_RNG, 64, 16);
gen_bitrange_const!(SRC_AS_RNG, 80, 48);
gen_bitrange_const!(SRC_IA_RNG, 64, 64);
const FIXED_SIZE_BITS: usize = 128;
pub const MAX_SIZE_BITS: usize = Self::FIXED_SIZE_BITS + (16 * 8) * 2; pub const MAX_SIZE_BYTES: usize = Self::MAX_SIZE_BITS / 8;
pub const MIN_SIZE_BITS: usize = Self::FIXED_SIZE_BITS + (4 * 8) * 2; pub const MIN_SIZE_BYTES: usize = Self::MIN_SIZE_BITS / 8;
#[inline]
pub const fn dst_host_addr_range(&self) -> BitRange {
let start = Self::FIXED_SIZE_BITS;
let end = start + (self.dst_addr_len as usize) * 8;
BitRange::from_range(start..end)
}
#[inline]
pub const fn src_host_addr_range(&self) -> BitRange {
let start = Self::FIXED_SIZE_BITS + (self.dst_addr_len as usize) * 8;
let end = start + (self.src_addr_len as usize) * 8;
BitRange::from_range(start..end)
}
#[inline]
pub const fn total_range(&self) -> BitRange {
let mut last_field = self.src_host_addr_range();
last_field.start = 0;
last_field
}
}
impl AddressHeaderLayout {
pub fn annotations(&self) -> Annotations {
let ann = vec![
(AddressHeaderLayout::DST_ISD_RNG, "dst_isd"),
(AddressHeaderLayout::DST_AS_RNG, "dst_as"),
(AddressHeaderLayout::SRC_ISD_RNG, "src_isd"),
(AddressHeaderLayout::SRC_AS_RNG, "src_as"),
(self.dst_host_addr_range(), "dst_addr"),
(self.src_host_addr_range(), "src_addr"),
];
Annotations::new_with("AddressHeader".to_string(), ann)
}
}
impl Layout for AddressHeaderLayout {
#[inline]
fn size_bytes(&self) -> usize {
self.total_range().end / 8
}
}
#[cfg(test)]
mod tests {
use proptest::{prelude::*, prop_assert_eq};
use super::*;
struct SimpleTestCalc;
impl SimpleTestCalc {
fn common_size() -> usize {
12 }
fn address_size(dst_addr_len: u8, src_addr_len: u8) -> usize {
16 + (dst_addr_len as usize)
+ (src_addr_len as usize)
}
fn standard_path_size(seg0_len: u8, seg1_len: u8, seg2_len: u8) -> usize {
Self::standard_path_meta_size()
+ Self::standard_path_data_size(seg0_len, seg1_len, seg2_len)
}
fn standard_path_meta_size() -> usize {
4 }
fn standard_path_data_size(seg0_len: u8, seg1_len: u8, seg2_len: u8) -> usize {
let info_fields =
(seg0_len > 0) as usize + (seg1_len > 0) as usize + (seg2_len > 0) as usize;
let hop_fields = (seg0_len as usize) + (seg1_len as usize) + (seg2_len as usize);
info_fields * 8 + hop_fields * 12 }
}
fn seg() -> impl Strategy<Value = u8> {
prop_oneof![
1 => Just(0),
1 => Just(63),
5 => 1u8..62,
]
}
#[test]
fn should_calculate_correct_total_sizes() {
proptest!(
|(dst_addr_len_unit in 0u8..=4,
src_addr_len_unit in 0u8..=4,
seg0 in seg(),
seg1 in seg(),
seg2 in seg(),)| {
println!(
"{}, {}, {}, {}, {}",
dst_addr_len_unit, src_addr_len_unit, seg0, seg1, seg2
);
test_impl(
dst_addr_len_unit,
src_addr_len_unit,
seg0,
seg1,
seg2,
)?;
}
);
fn test_impl(
dst_addr_len_unit: u8,
src_addr_len_unit: u8,
seg0: u8,
seg1: u8,
seg2: u8,
) -> Result<(), proptest::prelude::TestCaseError> {
let dst_addr_len = (dst_addr_len_unit + 1) * 4; let src_addr_len = (src_addr_len_unit + 1) * 4;
let common = CommonHeaderLayout;
let address = AddressHeaderLayout::new(src_addr_len, dst_addr_len);
let meta = StdPathMetaLayout;
let data = StdPathDataLayout::new(seg0, seg1, seg2);
prop_assert_eq!(
common.size_bytes(),
SimpleTestCalc::common_size(),
"Common header size does not match"
);
prop_assert_eq!(
address.size_bytes(),
SimpleTestCalc::address_size(dst_addr_len, src_addr_len),
"Address header size does not match"
);
prop_assert_eq!(
meta.size_bytes(),
SimpleTestCalc::standard_path_meta_size(),
"Standard path meta size does not match"
);
prop_assert_eq!(
data.size_bytes(),
SimpleTestCalc::standard_path_data_size(seg0, seg1, seg2),
"Standard path data size does not match"
);
let path_layout = ScionHeaderPathLayout::Standard(meta, data);
prop_assert_eq!(
path_layout.size_bytes(),
SimpleTestCalc::standard_path_size(seg0, seg1, seg2),
"Path layout size does not match"
);
Ok(())
}
}
}