use tinyvec::{ArrayVec, TinyVec};
use crate::{
core::{
encode::{InvalidStructureError, WireEncode},
layout::Layout,
write::unchecked_bit_range_be_write,
},
path::{
layout::ScionHeaderPathLayout,
standard::{
layout::{HopFieldLayout, InfoFieldLayout, StdPathDataLayout, StdPathMetaLayout},
mac::{ForwardingKey, HopMacCalculate, HopMacInput, HopMacInputSource},
types::{HopFieldFlags, HopFieldMac, InfoFieldFlags},
view::{HopFieldView, InfoFieldView, StandardPathView},
},
},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StandardPath {
pub current_info_field: u8,
pub current_hop_field: u8,
pub segments: ArrayVec<[Segment; 3]>,
}
impl StandardPath {
pub fn from_view(view: &StandardPathView) -> Self {
let info_fields = view.info_fields();
let hop_fields = view.hop_fields();
let segment_sizes = [view.seg0_len(), view.seg1_len(), view.seg2_len()];
let mut segments = ArrayVec::new();
let mut hop_fields_iter = hop_fields.iter();
for (info_field, segment_size) in info_fields.iter().zip(segment_sizes.iter()) {
let segment = Segment {
info_field: InfoField::from_view(info_field),
hop_fields: hop_fields_iter
.by_ref()
.take(*segment_size as usize)
.map(HopField::from_view)
.collect(),
};
segments.push(segment);
}
StandardPath {
current_info_field: view.curr_info_field(),
current_hop_field: view.curr_hop_field(),
segments,
}
}
}
impl StandardPath {
pub fn hop_field_count(&self) -> usize {
self.segments
.iter()
.map(|segment| segment.hop_fields.len())
.sum()
}
pub fn info_field_count(&self) -> usize {
self.segments.len()
}
pub fn segment_lengths(&self) -> (u8, u8, u8) {
let seg0 = self.segments.first().map_or(0, |s| s.hop_fields.len()) as u8;
let seg1 = self.segments.get(1).map_or(0, |s| s.hop_fields.len()) as u8;
let seg2 = self.segments.get(2).map_or(0, |s| s.hop_fields.len()) as u8;
(seg0, seg1, seg2)
}
pub fn iter_hop_fields(&self) -> impl Iterator<Item = &HopField> {
self.segments
.iter()
.flat_map(|segment| segment.hop_fields.iter())
}
pub fn iter_info_fields(&self) -> impl Iterator<Item = &InfoField> {
self.segments.iter().map(|segment| &segment.info_field)
}
pub fn segment_sizes(&self) -> [u8; 3] {
let seg0 = self.segments.first().map_or(0, |s| s.hop_fields.len()) as u8;
let seg1 = self.segments.get(1).map_or(0, |s| s.hop_fields.len()) as u8;
let seg2 = self.segments.get(2).map_or(0, |s| s.hop_fields.len()) as u8;
[seg0, seg1, seg2]
}
}
impl WireEncode for StandardPath {
fn required_size(&self) -> usize {
let [seg0, seg1, seg2] = self.segment_sizes();
StdPathMetaLayout::SIZE_BYTES + StdPathDataLayout::new(seg0, seg1, seg2).size_bytes()
}
fn wire_valid(&self) -> Result<(), InvalidStructureError> {
if self.required_size() > ScionHeaderPathLayout::MAX_SIZE_BYTES {
return Err("Encoded path size exceeds maximum allowed".into());
}
if self.segments.len() > StdPathMetaLayout::MAX_SEGMENTS {
return Err("Number of segments exceeds maximum allowed".into());
}
if self.segments.is_empty() {
return Err("Standard path must contain at least one segment".into());
}
if self.current_hop_field as usize >= self.hop_field_count() {
return Err("curr_hop_field exceeds total number of hop fields".into());
}
if self.current_info_field as usize >= self.info_field_count() {
return Err("current_info_field exceeds total number of info fields".into());
}
for segment in &self.segments {
if segment.hop_fields.len() > StdPathMetaLayout::MAX_SEGMENT_HOPS {
return Err("Number of hop fields in segment exceeds maximum allowed".into());
}
if segment.hop_fields.is_empty() {
return Err("Segment must contain at least one hop field".into());
}
segment.info_field.wire_valid()?;
for hop_field in &segment.hop_fields {
hop_field.wire_valid()?;
}
}
Ok(())
}
unsafe fn encode_unchecked(&self, buf: &mut [u8]) -> usize {
use StdPathMetaLayout as SL;
let [seg0, seg1, seg2] = self.segment_sizes();
unsafe {
unchecked_bit_range_be_write(buf, SL::CURR_INFO_FIELD_RNG, self.current_info_field);
unchecked_bit_range_be_write(buf, SL::CURR_HOP_FIELD_RNG, self.current_hop_field);
unchecked_bit_range_be_write(buf, SL::SEG0_LEN_RNG, seg0);
unchecked_bit_range_be_write(buf, SL::SEG1_LEN_RNG, seg1);
unchecked_bit_range_be_write(buf, SL::SEG2_LEN_RNG, seg2);
}
let data_buf = unsafe { buf.get_unchecked_mut(SL::SIZE_BYTES..) };
let data_layout = StdPathDataLayout::new(seg0, seg1, seg2);
for (i, info_field) in self.iter_info_fields().enumerate() {
let range = data_layout.info_field_range(i).aligned_byte_range();
unsafe {
let info_field_buf = data_buf.get_unchecked_mut(range);
info_field.encode_unchecked(info_field_buf);
}
}
for (i, hop_field) in self.iter_hop_fields().enumerate() {
let range = data_layout.hop_field_range(i).aligned_byte_range();
unsafe {
let hop_field_buf = data_buf.get_unchecked_mut(range);
hop_field.encode_unchecked(hop_field_buf);
}
}
SL::SIZE_BYTES + data_layout.size_bytes()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Segment {
pub info_field: InfoField,
pub hop_fields: TinyVec<[HopField; 12]>,
}
impl Default for Segment {
fn default() -> Self {
Self {
info_field: InfoField {
flags: InfoFieldFlags::empty(),
segment_id: 0,
timestamp: 0,
},
hop_fields: TinyVec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InfoField {
pub flags: InfoFieldFlags,
pub segment_id: u16,
pub timestamp: u32,
}
impl InfoField {
pub fn from_view(view: &InfoFieldView) -> Self {
InfoField {
flags: view.flags(),
segment_id: view.segment_id(),
timestamp: view.timestamp(),
}
}
}
impl WireEncode for InfoField {
fn required_size(&self) -> usize {
InfoFieldLayout::SIZE_BYTES
}
fn wire_valid(&self) -> Result<(), InvalidStructureError> {
Ok(())
}
unsafe fn encode_unchecked(&self, buf: &mut [u8]) -> usize {
unsafe {
use InfoFieldLayout as IFL;
unchecked_bit_range_be_write(buf, IFL::FLAGS_RNG, self.flags.bits());
unchecked_bit_range_be_write(buf, IFL::RSV_RNG, 0u8);
unchecked_bit_range_be_write(buf, IFL::SEGMENT_ID_RNG, self.segment_id);
unchecked_bit_range_be_write(buf, IFL::TIMESTAMP_RNG, self.timestamp);
}
self.required_size()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HopField {
pub flags: HopFieldFlags,
pub expiration_units: u8,
pub cons_ingress: u16,
pub cons_egress: u16,
pub mac: HopFieldMac,
}
impl Default for HopField {
fn default() -> Self {
Self {
flags: HopFieldFlags::empty(),
expiration_units: 0,
cons_ingress: 0,
cons_egress: 0,
mac: HopFieldMac([0; 6]),
}
}
}
impl HopField {
pub fn from_view(view: &HopFieldView) -> Self {
HopField {
flags: view.flags(),
expiration_units: view.exp_time(),
cons_ingress: view.cons_ingress(),
cons_egress: view.cons_egress(),
mac: view.mac(),
}
}
}
impl HopField {
pub fn empty() -> Self {
Self {
flags: HopFieldFlags::empty(),
expiration_units: 0,
cons_ingress: 0,
cons_egress: 0,
mac: HopFieldMac([0; 6]),
}
}
}
impl HopField {
pub fn with_calculated_mac(
mut self,
mac_chain_beta: u16,
timestamp_epoch: u32,
forwarding_key: &ForwardingKey,
) -> Self {
self.mac = self.calculate_mac(mac_chain_beta, timestamp_epoch, forwarding_key);
self
}
}
impl HopMacInputSource for HopField {
#[inline]
fn get_mac_input(&self) -> HopMacInput {
HopMacInput {
exp_time: self.expiration_units,
cons_ingress: self.cons_ingress,
cons_egress: self.cons_egress,
}
}
}
impl WireEncode for HopField {
fn required_size(&self) -> usize {
HopFieldLayout::SIZE_BYTES
}
fn wire_valid(&self) -> Result<(), InvalidStructureError> {
Ok(())
}
unsafe fn encode_unchecked(&self, buf: &mut [u8]) -> usize {
unsafe {
use HopFieldLayout as HFL;
unchecked_bit_range_be_write(buf, HFL::FLAGS_RNG, self.flags.bits());
unchecked_bit_range_be_write(buf, HFL::EXP_TIME_RNG, self.expiration_units);
unchecked_bit_range_be_write(buf, HFL::CONS_INGRESS_RNG, self.cons_ingress);
unchecked_bit_range_be_write(buf, HFL::CONS_EGRESS_RNG, self.cons_egress);
buf.get_unchecked_mut(HFL::MAC_RNG.aligned_byte_range())
.copy_from_slice(&self.mac.0);
}
self.required_size()
}
}
#[cfg(feature = "proptest")]
pub mod ptest {
use ::proptest::prelude::*;
use super::*;
#[derive(Debug, Clone, Default)]
pub struct ArbitraryPathContext {
}
impl Arbitrary for StandardPath {
type Parameters = ArbitraryPathContext;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(ctx: Self::Parameters) -> Self::Strategy {
(
any::<u8>(),
prop::collection::vec(Segment::arbitrary_with(ctx), 1..=3),
)
.prop_map(|(curr_hop, segments): (u8, Vec<Segment>)| {
let max_total_hops = StdPathMetaLayout::MAX_TOTAL_HOPS;
let total_hops: usize = segments.iter().map(|s| s.hop_fields.len()).sum();
let mut segments = segments;
if total_hops > max_total_hops {
let n = segments.len();
let base = max_total_hops / n;
let extra = max_total_hops % n;
for (i, seg) in segments.iter_mut().enumerate() {
let limit = base + if i < extra { 1 } else { 0 };
seg.hop_fields.truncate(limit.max(1));
}
}
let total_hops: usize = segments.iter().map(|s| s.hop_fields.len()).sum();
let curr_hop = match total_hops {
0 => 0,
_ => curr_hop % (total_hops as u8),
};
let mut hop_count = 0;
let mut curr_info = 0;
for (i, seg) in segments.iter().enumerate() {
hop_count += seg.hop_fields.len();
if (curr_hop as usize) < hop_count {
curr_info = i as u8;
break;
}
}
let segments = segments.into_iter().collect();
StandardPath {
current_info_field: curr_info,
current_hop_field: curr_hop,
segments,
}
})
.boxed()
}
}
impl Arbitrary for Segment {
type Parameters = ArbitraryPathContext;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_ctx: Self::Parameters) -> Self::Strategy {
(
any::<InfoField>(),
prop::collection::vec(any::<HopField>(), 1..=63),
)
.prop_map(|(info_field, hop_fields)| {
Segment {
info_field,
hop_fields: TinyVec::Heap(hop_fields),
}
})
.boxed()
}
}
impl Arbitrary for InfoField {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
(any::<InfoFieldFlags>(), any::<u16>(), any::<u32>())
.prop_map(|(flags, segment_id, timestamp)| {
InfoField {
flags,
segment_id,
timestamp,
}
})
.boxed()
}
}
impl Arbitrary for HopField {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
(
any::<HopFieldFlags>(),
any::<u8>(),
any::<u16>(),
any::<u16>(),
any::<[u8; 6]>(),
)
.prop_map(
|(flags, expiration_units, cons_ingress, cons_egress, mac_bytes)| {
HopField {
flags,
expiration_units,
cons_ingress,
cons_egress,
mac: HopFieldMac(mac_bytes),
}
},
)
.boxed()
}
}
}