e2e-protection 0.4.0

End-to-End protection core with pluggable profiles. AUTOSAR profile family is optional via feature
Documentation
//! Profile 8 (Ethernet / SOMEIP-like, AUTOSAR E2E)
//!
//! **Intent (simplified)**:
//! - **Counter**: 32-bit
//! - **DataIDList[256? simplified→16 for demo]**: DataID chosen by counter
//! - **CRC**: 32-bit CRC-32/P4
//! - **Length**: 32-bit
//!
//! **Frame layout (educational mapping)**:
//! ```text
//! [ payload ... | Length(4B) | Counter(4B) | CRC32(4B) ]
//! (CRC input includes implicit DataID from list)
//! ```

use crc::{Crc, CRC_32_AUTOSAR};

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

const CRC32P4: Crc<u32> = Crc::<u32>::new(&CRC_32_AUTOSAR);

#[derive(Clone, Debug)]
pub struct P8 {
    pub data_id_list: [u8; 16], // simplified demo
}

impl P8 {
    fn crc32(bytes: &[u8]) -> u32 {
        CRC32P4.checksum(bytes)
    }
}

impl E2eProfile for P8 {
    fn protect(&self, payload: &[u8], counter: u32) -> Result<Vec<u8>, E2eError> {
        let c4 = (counter & 0x0F) as usize;
        let mut out = Vec::with_capacity(payload.len() + 12);
        out.extend_from_slice(payload);
        out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
        out.extend_from_slice(&counter.to_be_bytes());

        // CRC input includes implicit DataID
        let mut crc_in = out.clone();
        crc_in.push(self.data_id_list[c4]);

        let crc = Self::crc32(&crc_in);
        out.extend_from_slice(&crc.to_be_bytes());
        Ok(out)
    }

    fn check(&self, frame: &[u8]) -> Result<CheckInfo, E2eError> {
        if frame.len() < 12 {
            return Err(E2eError::Length);
        }
        let crc_recv = u32::from_be_bytes(frame[frame.len() - 4..].try_into().unwrap());
        let without_crc = &frame[..frame.len() - 4];

        let counter = u32::from_be_bytes(without_crc[without_crc.len() - 4..].try_into().unwrap());
        let idx = (counter & 0x0F) as usize;

        let mut crc_in = without_crc.to_vec();
        crc_in.push(self.data_id_list[idx]);

        let want = Self::crc32(&crc_in);
        if want != crc_recv {
            return Err(E2eError::Crc);
        }

        let len = u32::from_be_bytes(without_crc[without_crc.len() - 8..without_crc.len() - 4].try_into().unwrap()) as usize;
        let payload_len = without_crc.len() - 8;
        if len != payload_len {
            return Err(E2eError::Length);
        }

        Ok(CheckInfo { counter, ok: true })
    }

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

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

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

        let mut rx = E2eSession::new(P8{data_id_list:dil}, CounterPolicy::Rollover{bits:32, step:1});
        let pl = rx.unwrap(&f).unwrap();
        assert_eq!(pl, &[0x44,0x55]);
    }

    #[test]
    fn p8_crc_detects_error() {
        let p = P8{ data_id_list: [0;16] };
        let mut f = p.protect(&[1,2,3,4], 7).unwrap();
        f[1] ^= 0x80;
        assert!(matches!(p.check(&f), Err(E2eError::Crc)));
    }
}