use std::{
fmt::{Debug, Display},
mem::transmute,
ops::Range,
};
use crate::{
core::{
read::unchecked_bit_range_be_read,
view::{
View, ViewConversionError,
macros::{gen_field_read, gen_field_write, gen_unsafe_field_write, gen_view_impl},
},
write::unchecked_bit_range_be_write,
},
dataplane_path::{
standard::{
layout::{
HopFieldLayout, InfoFieldLayout, StdPathDataLayout, StdPathLayout,
StdPathMetaLayout,
},
mac::{HopMacInput, HopMacInputSource},
types::{HopFieldFlags, HopFieldMac, InfoFieldFlags, exp_time_to_duration},
},
types::PathReverseError,
},
};
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct StandardPathView([u8]);
gen_view_impl!(StandardPathView, StdPathLayout);
impl StandardPathView {
gen_field_read!(
curr_info_field_idx,
StdPathMetaLayout::CURR_INFO_FIELD_RNG,
u8
);
gen_field_read!(
curr_hop_field_idx,
StdPathMetaLayout::CURR_HOP_FIELD_RNG,
u8
);
gen_field_read!(seg0_len, StdPathMetaLayout::SEG0_LEN_RNG, u8);
gen_field_read!(seg1_len, StdPathMetaLayout::SEG1_LEN_RNG, u8);
gen_field_read!(seg2_len, StdPathMetaLayout::SEG2_LEN_RNG, u8);
#[inline]
pub fn info_field_count(&self) -> u8 {
(self.seg0_len() > 0) as u8 + (self.seg1_len() > 0) as u8 + (self.seg2_len() > 0) as u8
}
#[inline]
pub fn hop_field_count(&self) -> u8 {
self.seg0_len() + self.seg1_len() + self.seg2_len()
}
}
impl StandardPathView {
gen_field_write!(
set_curr_info_field,
StdPathMetaLayout::CURR_INFO_FIELD_RNG,
u8
);
gen_field_write!(
set_curr_hop_field,
StdPathMetaLayout::CURR_HOP_FIELD_RNG,
u8
);
gen_unsafe_field_write!(set_seg0_len, StdPathMetaLayout::SEG0_LEN_RNG, u8);
gen_unsafe_field_write!(set_seg1_len, StdPathMetaLayout::SEG1_LEN_RNG, u8);
gen_unsafe_field_write!(set_seg2_len, StdPathMetaLayout::SEG2_LEN_RNG, u8);
}
impl StandardPathView {
#[inline]
fn checked_info_field_range(&self, index: usize) -> Option<Range<usize>> {
let info_field_count = self.info_field_count() as usize;
if index >= info_field_count {
return None;
}
Some(
StdPathDataLayout::new(self.seg0_len(), self.seg1_len(), self.seg2_len())
.info_field_range(index)
.shift(StdPathMetaLayout::SIZE_BYTES)
.aligned_byte_range(),
)
}
#[inline]
pub fn checked_hop_field_range(&self, index: usize) -> Option<Range<usize>> {
let hop_field_count = self.hop_field_count() as usize;
if index >= hop_field_count {
return None;
}
Some(
StdPathDataLayout::new(self.seg0_len(), self.seg1_len(), self.seg2_len())
.hop_field_range(index)
.shift(StdPathMetaLayout::SIZE_BYTES)
.aligned_byte_range(),
)
}
}
impl StandardPathView {
#[inline]
pub fn curr_info_field(&self) -> Option<&InfoFieldView> {
let index = self.curr_info_field_idx() as usize;
self.info_field(index)
}
#[inline]
pub fn info_field(&self, index: usize) -> Option<&InfoFieldView> {
let field_range = self.checked_info_field_range(index)?;
let field =
unsafe { InfoFieldView::from_slice_unchecked(self.0.get_unchecked(field_range)) };
Some(field)
}
#[inline]
pub fn curr_hop_field(&self) -> Option<&HopFieldView> {
let index = self.curr_hop_field_idx() as usize;
self.hop_field(index)
}
#[inline]
pub fn hop_field(&self, index: usize) -> Option<&HopFieldView> {
let field_range = self.checked_hop_field_range(index)?;
let field =
unsafe { HopFieldView::from_slice_unchecked(self.0.get_unchecked(field_range)) };
Some(field)
}
#[inline]
pub fn info_fields(&self) -> &[InfoFieldView] {
let layout = StdPathDataLayout::new(self.seg0_len(), self.seg1_len(), self.seg2_len());
let info_fields_range = layout
.info_fields_range()
.shift(StdPathMetaLayout::SIZE_BYTES)
.aligned_byte_range();
let slice = unsafe { self.0.get_unchecked(info_fields_range) };
debug_assert!(slice.len() == layout.info_field_count() * InfoFieldLayout::SIZE_BYTES);
unsafe {
std::slice::from_raw_parts(
slice.as_ptr() as *const InfoFieldView,
layout.info_field_count(),
)
}
}
#[inline]
pub fn hop_fields(&self) -> &[HopFieldView] {
let layout = StdPathDataLayout::new(self.seg0_len(), self.seg1_len(), self.seg2_len());
let hop_fields_range = layout
.hop_fields_range()
.shift(StdPathMetaLayout::SIZE_BYTES)
.aligned_byte_range();
let slice = unsafe { self.0.get_unchecked(hop_fields_range) };
debug_assert!(slice.len() == layout.hop_field_count() * HopFieldLayout::SIZE_BYTES);
unsafe {
std::slice::from_raw_parts(
slice.as_ptr() as *const HopFieldView,
layout.hop_field_count(),
)
}
}
}
impl StandardPathView {
#[inline]
pub fn curr_info_field_mut(&mut self) -> Option<&mut InfoFieldView> {
let index = self.curr_info_field_idx() as usize;
self.info_field_mut(index)
}
#[inline]
pub fn info_field_mut(&mut self, index: usize) -> Option<&mut InfoFieldView> {
let field_range = self.checked_info_field_range(index)?;
let field = unsafe {
InfoFieldView::from_mut_slice_unchecked(self.0.get_unchecked_mut(field_range))
};
Some(field)
}
#[inline]
pub fn curr_hop_field_mut(&mut self) -> Option<&mut HopFieldView> {
let index = self.curr_hop_field_idx() as usize;
self.hop_field_mut(index)
}
#[inline]
pub fn hop_field_mut(&mut self, index: usize) -> Option<&mut HopFieldView> {
let field_range = self.checked_hop_field_range(index)?;
let field = unsafe {
HopFieldView::from_mut_slice_unchecked(self.0.get_unchecked_mut(field_range))
};
Some(field)
}
#[inline]
pub fn info_fields_mut(&mut self) -> &mut [InfoFieldView] {
let layout = StdPathDataLayout::new(self.seg0_len(), self.seg1_len(), self.seg2_len());
let info_fields_range = layout
.info_fields_range()
.shift(StdPathMetaLayout::SIZE_BYTES)
.aligned_byte_range();
let slice = unsafe { self.0.get_unchecked_mut(info_fields_range) };
debug_assert!(slice.len() == layout.info_field_count() * InfoFieldLayout::SIZE_BYTES);
unsafe {
std::slice::from_raw_parts_mut(
slice.as_mut_ptr() as *mut InfoFieldView,
layout.info_field_count(),
)
}
}
#[inline]
pub fn hop_fields_mut(&mut self) -> &mut [HopFieldView] {
let layout = StdPathDataLayout::new(self.seg0_len(), self.seg1_len(), self.seg2_len());
let hop_fields_range = layout
.hop_fields_range()
.shift(StdPathMetaLayout::SIZE_BYTES)
.aligned_byte_range();
let slice = unsafe { self.0.get_unchecked_mut(hop_fields_range) };
debug_assert!(slice.len() == layout.hop_field_count() * HopFieldLayout::SIZE_BYTES);
unsafe {
std::slice::from_raw_parts_mut(
slice.as_mut_ptr() as *mut HopFieldView,
layout.hop_field_count(),
)
}
}
#[inline]
pub fn curr_egress_interface(&self) -> Option<u16> {
let curr_hop = self.curr_hop_field()?;
let curr_info = self.curr_info_field()?;
Some(curr_hop.egress_interface(curr_info))
}
#[inline]
pub fn try_reverse(&mut self) -> Result<(), PathReverseError> {
let seg0 = self.seg0_len();
let seg1 = self.seg1_len();
let seg2 = self.seg2_len();
let seg_count;
let curr_hop_idx = self.curr_hop_field_idx() as usize;
let curr_info_idx = self.curr_info_field_idx() as usize;
{
match (seg0, seg1, seg2) {
(0, ..) => {
return Err(PathReverseError::new(
"Cannot reverse a path with no segments",
));
}
(_, 0, _) => {
seg_count = 1;
}
(_, _, 0) => {
seg_count = 2;
unsafe {
self.set_seg0_len(seg1);
self.set_seg1_len(seg0);
}
}
(..) => {
seg_count = 3;
unsafe {
self.set_seg0_len(seg2);
self.set_seg1_len(seg1);
self.set_seg2_len(seg0);
}
}
}
}
let total_hops = seg0 as usize + seg1 as usize + seg2 as usize;
if curr_hop_idx >= total_hops {
return Err(PathReverseError::new(
"Current hop field index is out of bounds",
));
}
if curr_info_idx >= seg_count {
return Err(PathReverseError::new(
"Current info field index is out of bounds",
));
}
debug_assert!(
total_hops > 0,
"0 hops should have been caught by the check at the beginning of the function"
);
debug_assert!(
seg_count > 0,
"0 segments should have been caught by the check at the beginning of the function"
);
{
let info_fields = self.info_fields_mut();
for info_field in info_fields.iter_mut() {
let mut flags = info_field.flags();
flags.toggle(InfoFieldFlags::CONS_DIR);
info_field.set_flags(flags);
}
info_fields.reverse();
}
self.hop_fields_mut().reverse();
let new_hop_idx = (total_hops - curr_hop_idx) - 1;
let new_info_idx = (seg_count - curr_info_idx) - 1;
self.set_curr_hop_field(new_hop_idx as u8);
self.set_curr_info_field(new_info_idx as u8);
Ok(())
}
}
impl StandardPathView {
#[inline]
pub fn segments(&self) -> SegmentIterator<'_> {
SegmentIterator::new(self)
}
#[inline]
pub fn expiration(&self) -> u32 {
let segment_iter = self.segments();
if segment_iter.is_empty() {
return 0;
}
let mut expiry_time = u32::MAX;
for (info_field, hop_fields) in self.segments() {
let exp_time = hop_fields
.iter()
.map(|hop_field| hop_field.exp_time())
.min()
.expect("segment iterator ensures at least one hop field per segment");
let info_expiry = info_field.timestamp();
let exp_time: u32 = exp_time_to_duration(exp_time)
.as_secs()
.try_into()
.expect("maximum expiry time fits in u32");
let segment_expiry = info_expiry.saturating_add(exp_time);
expiry_time = expiry_time.min(segment_expiry);
}
expiry_time
}
#[inline]
pub fn calculate_segment_index(&self, hop_idx: usize) -> Option<(usize, bool, bool)> {
let segment_lengths = [self.seg0_len(), self.seg1_len(), self.seg2_len()];
Self::_calculate_segment_index(hop_idx, segment_lengths)
}
#[inline]
fn _calculate_segment_index(
hop_idx: usize,
segment_lengths: [u8; 3],
) -> Option<(usize, bool, bool)> {
let mut seg_len_agg = 0;
for (seg_idx, seg_len) in segment_lengths.into_iter().enumerate() {
if hop_idx < seg_len_agg + seg_len as usize {
let is_segment_start = hop_idx == seg_len_agg;
let is_segment_end = (hop_idx + 1) == (seg_len_agg + seg_len as usize);
return Some((seg_idx, is_segment_start, is_segment_end));
}
seg_len_agg += seg_len as usize;
}
None
}
}
impl Debug for StandardPathView {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hop_fields = self.hop_fields();
let info_fields = self.info_fields();
f.debug_struct("StandardPathMetaHeaderView")
.field("current_info_field", &self.curr_info_field_idx())
.field("curr_hop_field", &self.curr_hop_field_idx())
.field("seg0_len", &self.seg0_len())
.field("seg1_len", &self.seg1_len())
.field("seg2_len", &self.seg2_len())
.field("info_fields", &info_fields)
.field("hop_fields", &hop_fields)
.finish()
}
}
impl Display for StandardPathView {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[std] ci:{} ch:{} segs:",
self.curr_info_field_idx(),
self.curr_hop_field_idx(),
)?;
for (info, hops) in self.segments() {
let const_dir = match info.flags().contains(InfoFieldFlags::CONS_DIR) {
true => "",
false => "r",
};
write!(f, " {}[", const_dir)?;
let Some((last, head)) = hops.split_last() else {
write!(f, "]")?;
continue;
};
for hop in head {
let ingress = hop.ingress_interface(info);
let egress = hop.egress_interface(info);
write!(f, "{},{}; ", ingress, egress)?;
}
let last_ingress = last.ingress_interface(info);
let last_egress = last.egress_interface(info);
write!(f, "{},{}", last_ingress, last_egress)?;
write!(f, "]")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SegmentIterator<'a> {
segment_lengths: [u8; 3],
hop_fields: &'a [HopFieldView],
info_fields: &'a [InfoFieldView],
seg_idx: usize,
total_segments: usize,
hop_idx: usize,
}
impl SegmentIterator<'_> {
#[inline]
fn new(path_view: &StandardPathView) -> SegmentIterator<'_> {
let segment_lengths = [
path_view.seg0_len(),
path_view.seg1_len(),
path_view.seg2_len(),
];
let mut total_segments = 0;
for &len in &segment_lengths {
if len == 0 {
break;
}
total_segments += 1;
}
SegmentIterator {
total_segments: total_segments as usize,
hop_fields: path_view.hop_fields(),
info_fields: path_view.info_fields(),
segment_lengths,
seg_idx: 0,
hop_idx: 0,
}
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.total_segments == 0
}
#[inline]
pub const fn segment_count(&self) -> usize {
self.total_segments
}
#[inline]
pub const fn hop_field_count(&self) -> usize {
self.hop_fields.len()
}
}
impl<'a> Iterator for SegmentIterator<'a> {
type Item = (&'a InfoFieldView, &'a [HopFieldView]);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.seg_idx >= self.total_segments {
return None;
}
let info_field = self.info_fields.get(self.seg_idx)?;
let hop_fields = &self.hop_fields
[self.hop_idx..(self.hop_idx + self.segment_lengths[self.seg_idx] as usize)];
self.seg_idx += 1;
self.hop_idx += hop_fields.len();
Some((info_field, hop_fields))
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InfoFieldView([u8; InfoFieldLayout::SIZE_BYTES]);
impl View for InfoFieldView {
#[inline]
fn has_required_size(buf: &[u8]) -> Result<usize, ViewConversionError> {
if buf.len() < InfoFieldLayout::SIZE_BYTES {
return Err(ViewConversionError::BufferTooSmall {
at: "InfoFieldView",
required: InfoFieldLayout::SIZE_BYTES,
actual: buf.len(),
});
}
Ok(InfoFieldLayout::SIZE_BYTES)
}
#[inline]
unsafe fn from_slice_unchecked(buf: &[u8]) -> &Self {
let sized: &[u8; InfoFieldLayout::SIZE_BYTES] =
unsafe { buf.try_into().unwrap_unchecked() };
unsafe { transmute(sized) }
}
#[inline]
unsafe fn from_mut_slice_unchecked(buf: &mut [u8]) -> &mut Self {
let sized: &mut [u8; InfoFieldLayout::SIZE_BYTES] =
unsafe { buf.try_into().unwrap_unchecked() };
unsafe { transmute(sized) }
}
#[inline]
unsafe fn from_boxed_unchecked(buf: Box<[u8]>) -> Box<Self> {
let sized: Box<[u8; InfoFieldLayout::SIZE_BYTES]> =
unsafe { buf.try_into().unwrap_unchecked() };
unsafe { transmute(sized) }
}
#[inline]
unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
&mut self.0
}
#[inline]
fn as_slice_boxed(self: Box<Self>) -> Box<[u8]> {
let sized: Box<[u8; InfoFieldLayout::SIZE_BYTES]> = unsafe { transmute(self) };
sized
}
#[inline]
fn as_slice(&self) -> &[u8] {
&self.0
}
}
impl InfoFieldView {
#[inline]
pub fn flags(&self) -> InfoFieldFlags {
let val = unsafe { unchecked_bit_range_be_read::<u8>(&self.0, InfoFieldLayout::FLAGS_RNG) };
InfoFieldFlags::from_bits_retain(val)
}
gen_field_read!(segment_id, InfoFieldLayout::SEGMENT_ID_RNG, u16);
#[inline]
pub fn timestamp(&self) -> u32 {
use crate::core::read::unchecked_bit_range_be_read;
unsafe { unchecked_bit_range_be_read::<u32>(&self.0, InfoFieldLayout::TIMESTAMP_RNG) }
}
}
impl InfoFieldView {
#[inline]
pub fn set_flags(&mut self, flags: InfoFieldFlags) {
let val = flags.bits();
unsafe { unchecked_bit_range_be_write::<u8>(&mut self.0, InfoFieldLayout::FLAGS_RNG, val) }
}
gen_field_write!(set_segment_id, InfoFieldLayout::SEGMENT_ID_RNG, u16);
gen_field_write!(set_timestamp, InfoFieldLayout::TIMESTAMP_RNG, u32);
}
impl Debug for InfoFieldView {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StandardPathInfoFieldView")
.field("flags", &self.flags())
.field("segment_id", &self.segment_id())
.field("timestamp", &self.timestamp())
.finish()
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct HopFieldView([u8; HopFieldLayout::SIZE_BYTES]);
impl View for HopFieldView {
#[inline]
fn has_required_size(buf: &[u8]) -> Result<usize, ViewConversionError> {
if buf.len() < HopFieldLayout::SIZE_BYTES {
return Err(ViewConversionError::BufferTooSmall {
at: "HopFieldView",
required: HopFieldLayout::SIZE_BYTES,
actual: buf.len(),
});
}
Ok(HopFieldLayout::SIZE_BYTES)
}
#[inline]
unsafe fn from_slice_unchecked(buf: &[u8]) -> &Self {
let sized: &[u8; HopFieldLayout::SIZE_BYTES] = unsafe { buf.try_into().unwrap_unchecked() };
unsafe { transmute(sized) }
}
#[inline]
unsafe fn from_mut_slice_unchecked(buf: &mut [u8]) -> &mut Self {
let sized: &mut [u8; HopFieldLayout::SIZE_BYTES] =
unsafe { buf.try_into().unwrap_unchecked() };
unsafe { transmute(sized) }
}
#[inline]
unsafe fn from_boxed_unchecked(buf: Box<[u8]>) -> Box<Self> {
let sized: Box<[u8; HopFieldLayout::SIZE_BYTES]> =
unsafe { buf.try_into().unwrap_unchecked() };
unsafe { transmute(sized) }
}
#[inline]
unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
&mut self.0
}
#[inline]
fn as_slice_boxed(self: Box<Self>) -> Box<[u8]> {
let sized: Box<[u8; HopFieldLayout::SIZE_BYTES]> = unsafe { transmute(self) };
sized
}
#[inline]
fn as_slice(&self) -> &[u8] {
&self.0
}
}
impl HopFieldView {
#[inline]
pub fn flags(&self) -> HopFieldFlags {
let value =
unsafe { unchecked_bit_range_be_read::<u8>(&self.0, HopFieldLayout::FLAGS_RNG) };
HopFieldFlags::from_bits_retain(value)
}
gen_field_read!(exp_time, HopFieldLayout::EXP_TIME_RNG, u8);
gen_field_read!(cons_ingress, HopFieldLayout::CONS_INGRESS_RNG, u16);
gen_field_read!(cons_egress, HopFieldLayout::CONS_EGRESS_RNG, u16);
#[inline]
pub fn mac(&self) -> HopFieldMac {
let mac: [u8; 6] = unsafe {
self.0
.get_unchecked(HopFieldLayout::MAC_RNG.aligned_byte_range())
.try_into()
.unwrap_unchecked()
};
HopFieldMac(mac)
}
#[inline]
pub fn ingress_interface(&self, info_field: &InfoFieldView) -> u16 {
if info_field.flags().cons_dir() {
self.cons_ingress()
} else {
self.cons_egress()
}
}
#[inline]
pub fn egress_interface(&self, info_field: &InfoFieldView) -> u16 {
if info_field.flags().cons_dir() {
self.cons_egress()
} else {
self.cons_ingress()
}
}
#[inline]
pub fn ingress_scmp_alert(&self, info_field: &InfoFieldView) -> bool {
let cons_dir = info_field.flags().cons_dir();
self.flags().normalized_ingress_router_alert(cons_dir)
}
#[inline]
pub fn egress_scmp_alert(&self, info_field: &InfoFieldView) -> bool {
let cons_dir = info_field.flags().cons_dir();
self.flags().normalized_egress_router_alert(cons_dir)
}
}
impl HopFieldView {
#[inline]
pub fn set_flags(&mut self, flags: HopFieldFlags) {
let value = flags.bits();
unsafe { unchecked_bit_range_be_write::<u8>(&mut self.0, HopFieldLayout::FLAGS_RNG, value) }
}
gen_field_write!(set_exp_time, HopFieldLayout::EXP_TIME_RNG, u8);
gen_field_write!(set_cons_ingress, HopFieldLayout::CONS_INGRESS_RNG, u16);
gen_field_write!(set_cons_egress, HopFieldLayout::CONS_EGRESS_RNG, u16);
#[inline]
pub fn set_mac(&mut self, mac: HopFieldMac) {
unsafe {
self.0
.get_unchecked_mut(HopFieldLayout::MAC_RNG.aligned_byte_range())
.copy_from_slice(&mac.0);
}
}
}
impl HopFieldView {
#[inline]
pub fn expiry_timestamp(&self, info: &InfoFieldView) -> u32 {
let info_expiry = info.timestamp();
let exp_time = exp_time_to_duration(self.exp_time()).as_secs();
info_expiry.saturating_add(exp_time.try_into().expect("expiry time fits in u32"))
}
}
impl Debug for HopFieldView {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StandardPathHopFieldView")
.field("flags", &self.flags())
.field("exp_time", &self.exp_time())
.field("cons_ingress", &self.cons_ingress())
.field("cons_egress", &self.cons_egress())
.field("mac", &self.mac())
.finish()
}
}
impl HopMacInputSource for HopFieldView {
#[inline]
fn get_mac_input(&self) -> HopMacInput {
HopMacInput {
exp_time: self.exp_time(),
cons_ingress: self.cons_ingress(),
cons_egress: self.cons_egress(),
}
}
}
#[cfg(test)]
mod test {
mod standard_path_view {
use tinyvec::{array_vec, tiny_vec};
use crate::{
core::{encode::WireEncode, view::View},
dataplane_path::standard::{
model::{HopField, InfoField, Segment, StandardPath},
types::{HopFieldFlags, HopFieldMac, InfoFieldFlags},
view::StandardPathView,
},
};
#[test]
fn should_correclty_determine_segment_position() {
let tests = [
(0, [1, 0, 0], Some((0, true, true))),
(1, [1, 0, 0], None),
(0, [2, 0, 0], Some((0, true, false))),
(1, [2, 0, 0], Some((0, false, true))),
(2, [2, 0, 0], None),
(0, [3, 0, 0], Some((0, true, false))),
(1, [3, 0, 0], Some((0, false, false))),
(2, [3, 0, 0], Some((0, false, true))),
(3, [3, 0, 0], None),
(2, [2, 3, 0], Some((1, true, false))),
(3, [2, 3, 0], Some((1, false, false))),
(4, [2, 3, 0], Some((1, false, true))),
(5, [2, 3, 0], None),
(5, [2, 3, 4], Some((2, true, false))),
(6, [2, 3, 4], Some((2, false, false))),
(7, [2, 3, 4], Some((2, false, false))),
(8, [2, 3, 4], Some((2, false, true))),
(9, [2, 3, 4], None),
];
for (test_idx, (hop_idx, seg_lens, expected)) in tests.iter().enumerate() {
let result = StandardPathView::_calculate_segment_index(*hop_idx, *seg_lens);
assert_eq!(
result, *expected,
"Failed for test {} hop_idx: {}, seg_lens: {:?}",
test_idx, hop_idx, seg_lens
);
}
}
#[test]
fn std_path_should_correctly_display() {
let mac = HopFieldMac::zero();
let flags = HopFieldFlags::empty();
let std = StandardPath {
current_info_field: 1,
current_hop_field: 2,
segments: array_vec!([_;3] =>
Segment {
info_field: InfoField {
flags: InfoFieldFlags::empty(),
segment_id: 1,
timestamp: 2,
},
hop_fields: tiny_vec![
[_; 12] =>
HopField { flags, expiration_units: 1, cons_ingress: 1, cons_egress: 0, mac },
HopField { flags, expiration_units: 1, cons_ingress: 0, cons_egress: 2, mac }
]
},
Segment {
info_field: InfoField {
flags: InfoFieldFlags::CONS_DIR,
segment_id: 2,
timestamp: 3,
},
hop_fields: tiny_vec![
[_; 12] =>
HopField { flags, expiration_units: 1, cons_ingress: 0, cons_egress: 3, mac },
HopField { flags, expiration_units: 1, cons_ingress: 5, cons_egress: 8, mac },
HopField { flags, expiration_units: 1, cons_ingress: 2, cons_egress: 0, mac }
]
}
),
};
let data = std.try_encode_to_vec().unwrap();
let (v, _) = StandardPathView::try_from_slice(&data).unwrap();
let std_path_fmt = format!("{}", v);
assert_eq!(
std_path_fmt,
"[std] ci:1 ch:2 segs: r[0,1; 2,0] [0,3; 5,8; 2,0]"
);
}
}
}