e2e-protection 0.4.0

End-to-End protection core with pluggable profiles. AUTOSAR profile family is optional via feature
Documentation
//! Profile 22 (CAN/FlexRay-like)
//!
//! **Intent (simplified)**:
//! - 4-bit **counter** (0x0..=0xF are used).
//! - **DataIDList[16]**: for each counter value c∈[0..15], there is an
//!   8-bit Data-ID value used *implicitly* in the CRC.
//! - **CRC**: 8-bit **CRC-8/H2F** (poly 0x2F).
//! - No explicit length (suited for fixed-size signals).
//!
//! **Frame layout used here**:
//! ```text
//! [ payload ... | CRC8 (1B) | HDR (1B) ]
//!   HDR (bits 3..0) : counter (0..15)
//!   HDR (bits 7..4) : reserved (0)
//! ```
//!
//! **CRC input** (this implementation):
//! `payload || DataIDList[counter] || HDR_byte`
//!
//! The `crc` crate is used for the CRC8 computation.

use crc::{Crc, CRC_8_AUTOSAR};

use crate::e2e::{CheckInfo, E2eError, E2eProfile};

const CRC8P2: Crc<u8> = Crc::<u8>::new(&CRC_8_AUTOSAR);

/// Profile 22 with a fixed 16-entry Data-ID list (one for each counter nibble).
#[derive(Clone, Debug)]
pub struct P22 {
    /// Data-ID list used implicitly in CRC (index = counter value 0..15).
    pub data_id_list: [u8; 16],
}

impl E2eProfile for P22 {
    fn protect(&self, payload: &[u8], counter: u32) -> Result<Vec<u8>, E2eError> {
        let c4 = (counter & 0x0F) as u8;

        let mut out = Vec::with_capacity(payload.len() + 2);
        out.extend_from_slice(payload);
        out.resize(payload.len() + 2, 0u8);

        // HDR: high-nibble=0, low-nibble=counter
        let hdr = c4 & 0x0F;
        // CRC input: payload + implicit DataID + hdr
        let mut crc_in = Vec::with_capacity(payload.len() + 2);
        crc_in.extend_from_slice(payload);
        crc_in.push(self.data_id_list[c4 as usize]);
        crc_in.push(hdr);

        out[payload.len()] = CRC8P2.checksum(&crc_in);
        out[payload.len() + 1] = hdr;
        Ok(out)
    }

    fn check(&self, frame: &[u8]) -> Result<CheckInfo, E2eError> {
        if frame.len() < 2 { return Err(E2eError::Length); }
        let (crc_rx, hdr) = (frame[frame.len()-2], frame[frame.len()-1]);
        let counter = hdr & 0x0F;

        let payload = &frame[..frame.len()-2];
        let mut crc_in = Vec::with_capacity(payload.len() + 2);
        crc_in.extend_from_slice(payload);
        crc_in.push(self.data_id_list[counter as usize]);
        crc_in.push(hdr);

        let want = CRC8P2.checksum(&crc_in);
        if want != crc_rx { return Err(E2eError::Crc); }
        Ok(CheckInfo { counter: counter as u32, ok: true })
    }

    fn payload_len(&self, frame: &[u8]) -> Option<usize> {
        frame.len().checked_sub(2)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{E2eProfile, E2eSession, CounterPolicy, E2eError};

    #[test]
    fn p22_roundtrip() {
        let mut tx = E2eSession::new(
            P22 { data_id_list: [0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, 0x18,0x19,0x1A,0x1B, 0x1C,0x1D,0x1E,0x1F] },
            CounterPolicy::Rollover { bits: 4, step: 1 },
        );
        let f = tx.wrap(&[0xAA]).unwrap();

        let mut rx = E2eSession::new(
            P22 { data_id_list: [0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, 0x18,0x19,0x1A,0x1B, 0x1C,0x1D,0x1E,0x1F] },
            CounterPolicy::Rollover { bits: 4, step: 1 },
        );
        let pl = rx.unwrap(&f).unwrap();
        assert_eq!(pl, &[0xAA]);
    }

    #[test]
    fn p22_crc_detects_payload_bitflip() {
        let p = P22 { data_id_list: [0;16] };
        let mut f = p.protect(&[1,2,3], 3).unwrap();
        f[1] ^= 0x80; // flip a bit
        assert!(matches!(p.check(&f), Err(E2eError::Crc)));
    }

    #[test]
    fn p22_counter_policy() {
        let mut tx = E2eSession::new(
            P22 { data_id_list: [0;16] },
            CounterPolicy::Rollover { bits: 4, step: 1 },
        );
        let f0 = tx.wrap(&[]).unwrap();
        let f1 = tx.wrap(&[]).unwrap();
        let f3 = P22 { data_id_list: [0;16] }.protect(&[], 3).unwrap();

        let mut rx = E2eSession::new(
            P22 { data_id_list: [0;16] },
            CounterPolicy::Rollover { bits: 4, step: 1 },
        );
        rx.unwrap(&f0).unwrap();
        rx.unwrap(&f1).unwrap();
        // now expecting counter=2, but we feed counter=3 → Counter error
        assert!(matches!(rx.unwrap(&f3), Err(E2eError::Counter)));
    }
}