sciparse 0.6.1

Zero-copy SCION packet parsing, serialization and control plane components
Documentation
// Copyright 2026 Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! View for one-hop paths between neighboring border routers in SCION.

use std::{
    fmt::{Debug, Display},
    mem::transmute,
};

use crate::{
    core::view::{View, ViewConversionError},
    dataplane_path::{
        onehop::layout::OneHopPathLayout,
        standard::{
            mac::{ForwardingKey, HopMacCalculate, algo::mac_beta_step},
            types::{InfoFieldFlags, exp_time_to_duration},
            view::{HopFieldView, InfoFieldView},
        },
        types::PathReverseError,
    },
};

/// View for a one-hop path between neighboring border routers in SCION.
#[repr(transparent)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct OneHopPathView([u8; OneHopPathLayout::SIZE_BYTES]);
impl OneHopPathView {
    /// Returns a reference to the info field
    #[inline]
    pub fn info_field(&self) -> &InfoFieldView {
        unsafe {
            InfoFieldView::from_slice_unchecked(
                &self.0[OneHopPathLayout::INFO_FIELD.aligned_byte_range()],
            )
        }
    }

    /// Returns a mutable reference to the info field
    #[inline]
    pub fn info_field_mut(&mut self) -> &mut InfoFieldView {
        unsafe {
            InfoFieldView::from_mut_slice_unchecked(
                &mut self.0[OneHopPathLayout::INFO_FIELD.aligned_byte_range()],
            )
        }
    }

    /// Returns a reference to the hop fields
    #[inline]
    pub fn hop_fields(&self) -> [&HopFieldView; 2] {
        unsafe {
            [
                HopFieldView::from_slice_unchecked(
                    &self.0[OneHopPathLayout::HOP_FIELD_1.aligned_byte_range()],
                ),
                HopFieldView::from_slice_unchecked(
                    &self.0[OneHopPathLayout::HOP_FIELD_2.aligned_byte_range()],
                ),
            ]
        }
    }

    /// Returns a mutable reference to the hop fields
    #[inline]
    pub fn mut_hop_fields(&mut self) -> [&mut HopFieldView; 2] {
        unsafe {
            let [hop1, hop2] = self.0.get_disjoint_unchecked_mut([
                OneHopPathLayout::HOP_FIELD_1.aligned_byte_range(),
                OneHopPathLayout::HOP_FIELD_2.aligned_byte_range(),
            ]);

            [
                HopFieldView::from_mut_slice_unchecked(hop1),
                HopFieldView::from_mut_slice_unchecked(hop2),
            ]
        }
    }
}
impl OneHopPathView {
    /// Sets the second hop field with the given ingress interface and recalculates the MAC.
    ///
    /// Expiration is copied from the first hop.
    ///
    /// # Parameters
    /// * `ingress_interface` is the interface on which the packet is expected to arrive at the
    ///   second hop.
    /// * `forwarding_key` is the key used for MAC calculation
    /// * `segment_id_was_advanced` indicates whether the segment ID was advanced to the next hop
    ///   before calling this method, or if it is still at the first hop
    #[inline]
    pub fn set_second_hop(
        &mut self,
        ingress_interface: u16,
        forwarding_key: ForwardingKey,
        segment_id_was_advanced: bool,
    ) {
        let (beta, timestamp) = {
            let info = self.info_field();
            let beta = if segment_id_was_advanced {
                info.segment_id()
            } else {
                mac_beta_step(info.segment_id(), self.hop_fields()[0].mac().into())
            };

            (beta, info.timestamp())
        };

        let [hop1, hop2] = self.mut_hop_fields();
        hop2.set_cons_ingress(ingress_interface);
        hop2.set_cons_ingress(ingress_interface);
        hop2.set_cons_egress(0);
        hop2.set_cons_ingress(ingress_interface);
        hop2.set_exp_time(hop1.exp_time());

        let mac = hop2.calculate_mac(beta, timestamp, &forwarding_key);
        hop2.set_mac(mac);
    }

    /// Reverses the one-hop path in place.
    ///
    /// Returns an error if the second hop field has not been set yet (i.e., its ingress
    /// interface is still 0, meaning the path is incomplete).
    #[inline]
    pub fn try_reverse(&mut self) -> Result<(), PathReverseError> {
        if self.hop_fields()[1].cons_ingress() == 0 {
            return Err(PathReverseError::new(
                "Cannot reverse a one-hop path whose second hop has not been set yet",
            ));
        }

        let [hop1, hop2] = self.mut_hop_fields();
        std::mem::swap(hop1, hop2);

        let mut flags = self.info_field().flags();
        flags.toggle(InfoFieldFlags::CONS_DIR);
        self.info_field_mut().set_flags(flags);

        Ok(())
    }
}
// Util
impl OneHopPathView {
    /// Returns the expiration time of the path in seconds since the UNIX epoch.
    #[inline]
    pub fn expiration(&self) -> u32 {
        let base = self.info_field().timestamp();

        let min_exp = self
            .hop_fields()
            .iter()
            .map(|hop| hop.exp_time())
            .min()
            .unwrap_or(0);

        base + exp_time_to_duration(min_exp).as_secs() as u32
    }
}
impl Debug for OneHopPathView {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("OneHopPathView")
            .field("info_field", &self.info_field())
            .field("hop_fields", &self.hop_fields())
            .finish()
    }
}
impl Display for OneHopPathView {
    /// Displays the one-hop path in a human-readable format.
    ///
    /// Example output:
    ///
    /// ```text
    /// [onehop] cons_dir: true  hops: [1,2; 3,4]
    /// ```
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let cons_dir = self.info_field().flags().cons_dir();
        let [hop1, hop2] = self.hop_fields();

        match cons_dir {
            true => {
                write!(
                    f,
                    "[onehop] cons_dir: {cons_dir}  hops: [{},{}; {},{}]",
                    hop1.cons_ingress(),
                    hop1.cons_egress(),
                    hop2.cons_ingress(),
                    hop2.cons_egress()
                )
            }
            false => {
                write!(
                    f,
                    "[onehop] cons_dir: {cons_dir}  hops: [{},{}: {},{}]",
                    hop2.cons_egress(),
                    hop2.cons_ingress(),
                    hop1.cons_egress(),
                    hop1.cons_ingress(),
                )
            }
        }
    }
}

// Note: can't use gen_view_impl! as self is a fixed size array
impl View for OneHopPathView {
    #[inline]
    fn has_required_size(buf: &[u8]) -> Result<usize, ViewConversionError> {
        use crate::core::layout::Layout;
        let layout = OneHopPathLayout::try_from(buf)?;
        Ok(layout.size_bytes())
    }

    #[inline]
    fn as_slice(&self) -> &[u8] {
        &self.0
    }

    #[inline]
    unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
        &mut self.0
    }

    #[inline]
    fn as_slice_boxed(self: Box<Self>) -> Box<[u8]> {
        // SAFETY: repr(transparent) over [u8; N]
        let sized: Box<[u8; OneHopPathLayout::SIZE_BYTES]> = unsafe { transmute(self) };
        sized
    }

    #[inline]
    unsafe fn from_slice_unchecked(buf: &[u8]) -> &Self {
        // SAFETY: see View trait documentation
        let sized: &[u8; OneHopPathLayout::SIZE_BYTES] =
            unsafe { buf.try_into().unwrap_unchecked() };
        unsafe { transmute(sized) }
    }

    #[inline]
    unsafe fn from_mut_slice_unchecked(buf: &mut [u8]) -> &mut Self {
        // SAFETY: see View trait documentation
        let sized: &mut [u8; OneHopPathLayout::SIZE_BYTES] =
            unsafe { buf.try_into().unwrap_unchecked() };
        unsafe { transmute(sized) }
    }

    #[inline]
    unsafe fn from_boxed_unchecked(buf: Box<[u8]>) -> Box<Self> {
        // SAFETY: see View trait documentation
        let sized: Box<[u8; OneHopPathLayout::SIZE_BYTES]> =
            unsafe { buf.try_into().unwrap_unchecked() };
        unsafe { transmute(sized) }
    }
}