e2e-protection 0.3.0

End-to-End protection core with pluggable profiles. AUTOSAR (P11/P22) profile family is optional via feature
Documentation
//! Core E2E traits, policies, session, and error/result types.

// ---------- Error & result ----------
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum E2eError {
    LengthError,
    CrcMismatch,
    DataIdMismatch,
    CounterError,
    Unsupported,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CheckInfo {
    pub counter: u32,
    pub ok: bool,
}

// ---------- Policy ----------
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CounterPolicy {
    MonotonicRollover { bits: u8, step: u32 },
    None,
}

impl CounterPolicy {
    pub fn is_valid_successor(self, prev: Option<u32>, next: u32) -> bool {
        match self {
            CounterPolicy::None => true,
            CounterPolicy::MonotonicRollover { bits, step } => {
                let m = (1u32 << bits) - 1;
                match prev { None => true, Some(p) => ((p + step) & m) == (next & m) }
            }
        }
    }
    pub fn next(self, prev: Option<u32>) -> u32 {
        match self {
            CounterPolicy::None => prev.unwrap_or(0),
            CounterPolicy::MonotonicRollover { bits, step } => {
                let m = (1u32 << bits) - 1;
                let p = prev.unwrap_or(m); // first => 0 (if step=1)
                (p + step) & m
            }
        }
    }
}

// ---------- Profile trait ----------
pub trait E2eProfile {
    fn protect(&self, payload: &[u8], counter: u32) -> Result<Vec<u8>, E2eError>;
    fn check(&self, frame: &[u8]) -> Result<CheckInfo, E2eError>;
    fn payload_len(&self, frame: &[u8]) -> Option<usize>;
}

// ---------- Session ----------
pub struct E2eSession<P: E2eProfile> {
    profile: P,
    policy: CounterPolicy,
    last_counter: Option<u32>,
}

impl<P: E2eProfile> E2eSession<P> {
    pub fn new(profile: P, policy: CounterPolicy) -> Self {
        Self { profile, policy, last_counter: None }
    }
    pub fn wrap(&mut self, payload: &[u8]) -> Result<Vec<u8>, E2eError> {
        let next = self.policy.next(self.last_counter);
        let frame = self.profile.protect(payload, next)?;
        self.last_counter = Some(next);
        Ok(frame)
    }
    pub fn unwrap<'a>(&mut self, frame: &'a [u8]) -> Result<&'a [u8], E2eError> {
        let info = self.profile.check(frame)?;
        if !self.policy.is_valid_successor(self.last_counter, info.counter) {
            return Err(E2eError::CounterError);
        }
        self.last_counter = Some(info.counter);
        let plen = self.profile.payload_len(frame).ok_or(E2eError::Unsupported)?;
        Ok(&frame[..plen])
    }
    pub fn last_counter(&self) -> Option<u32> { self.last_counter }
    pub fn profile(&self) -> &P { &self.profile }
}