e2e-protection 0.4.0

End-to-End protection core with pluggable profiles. AUTOSAR profile family is optional via feature
Documentation
//! Core E2E traits and the session wrapper.

/// Errors that may happen during protect/check/unwrap.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum E2eError {
    /// Payload or frame length is out of the profile’s constraints.
    Length,
    /// CRC (integrity) verification failed.
    Crc,
    /// Data-ID (or Data-ID-List based mapping) check failed.
    DataId,
    /// Counter policy violation (gap, wrong step, illegal value).
    Counter,
    /// Feature or operation is not supported by the active profile.
    Unsupported,
}

/// Minimal report returned by `E2eProfile::check`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CheckInfo {
    /// Extracted counter (zero-extended).
    pub counter: u32,
    /// True if profile-level checks (CRC, DataId, basic length) passed.
    pub ok: bool,
}

/// How counters should evolve across a session.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CounterPolicy {
    /// Monotonic with rollover of `bits` width and fixed `step` (usually 1).
    Rollover { bits: u8, step: u32 },
    /// No policy (accept any counter).
    None,
}
impl CounterPolicy {
    /// Compute the next counter to use for transmission given the previous one.
    pub fn next(self, prev: Option<u32>) -> u32 {
        match self {
            CounterPolicy::None => prev.unwrap_or(0),
            CounterPolicy::Rollover { bits, step } => {
                if bits == 32 {
                    // full 32-bit rollover
                    prev.unwrap_or(u32::MAX).wrapping_add(step)
                } else {
                    let m = (1u32 << bits) - 1;
                    let p = prev.unwrap_or(m);
                    (p + step) & m
                }
            }
        }
    }
    /// Validate whether `next` is the correct successor of `prev`.
    pub fn ok(self, prev: Option<u32>, next: u32) -> bool {
        match self {
            CounterPolicy::None => true,
            CounterPolicy::Rollover { bits, step } => {
                if bits == 32 {
                    prev.map(|p| p.wrapping_add(step) == next).unwrap_or(true)
                } else {
                    let m = (1u32 << bits) - 1;
                    prev.map(|p| ((p + step) & m) == (next & m)).unwrap_or(true)
                }
            }
        }
    }
}

/// A single E2E profile.
/// Implementations encapsulate packing, CRC calculation, and validation.
pub trait E2eProfile {
    /// Create a protected frame for the given `payload` and `counter`.
    fn protect(&self, payload: &[u8], counter: u32) -> Result<Vec<u8>, E2eError>;

    /// Validate a received `frame`, returning parsed `CheckInfo` if valid.
    fn check(&self, frame: &[u8]) -> Result<CheckInfo, E2eError>;

    /// Return the original payload length (for zero-copy `unwrap`) if derivable.
    fn payload_len(&self, frame: &[u8]) -> Option<usize>;
}

/// High-level session object that holds a profile and a counter policy.
pub struct E2eSession<P: E2eProfile> {
    p: P,
    pol: CounterPolicy,
    last: Option<u32>,
}
impl<P: E2eProfile> E2eSession<P> {
    /// Create a new session with a profile and a counter policy.
    pub fn new(p: P, pol: CounterPolicy) -> Self {
        Self { p, pol, last: None }
    }

    /// Wrap `payload` with the next session counter according to the policy.
    pub fn wrap(&mut self, payload: &[u8]) -> Result<Vec<u8>, E2eError> {
        let c = self.pol.next(self.last);
        let f = self.p.protect(payload, c)?;
        self.last = Some(c);
        Ok(f)
    }

    /// Unwrap a `frame`: profile validation + policy check → return payload slice.
    pub fn unwrap<'a>(&mut self, frame: &'a [u8]) -> Result<&'a [u8], E2eError> {
        let info = self.p.check(frame)?;
        if !self.pol.ok(self.last, info.counter) {
            return Err(E2eError::Counter);
        }
        self.last = Some(info.counter);
        let n = self.p.payload_len(frame).ok_or(E2eError::Unsupported)?;
        Ok(&frame[..n])
    }

    /// The last successfully transmitted/accepted counter (if any).
    pub fn last_counter(&self) -> Option<u32> { self.last }
}