use crc::{Crc, CRC_8_SAE_J1850};
use crate::e2e::{CheckInfo, E2eError, E2eProfile};
const CRC8P1: Crc<u8> = Crc::<u8>::new(&CRC_8_SAE_J1850);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum P11DataIdMode {
Both { data_id: u16 },
Nibble { data_id12: u16 }, }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct P11 {
pub mode: P11DataIdMode,
}
impl P11 {
#[inline]
fn is_counter_valid(c4: u8) -> bool {
c4 <= 0x0E }
}
impl E2eProfile for P11 {
fn protect(&self, payload: &[u8], counter: u32) -> Result<Vec<u8>, E2eError> {
let c4 = (counter & 0x0F) as u8;
if !Self::is_counter_valid(c4) { return Err(E2eError::Counter); }
let mut out = Vec::with_capacity(payload.len() + 2);
out.extend_from_slice(payload);
out.resize(payload.len() + 2, 0u8);
let hdr = match self.mode {
P11DataIdMode::Both { data_id } => {
let hdr = (c4 << 4) | 0x0;
let mut crc_in = Vec::with_capacity(payload.len() + 3);
crc_in.extend_from_slice(payload);
crc_in.push((data_id & 0xFF) as u8);
crc_in.push((data_id >> 8) as u8);
crc_in.push(hdr);
out[payload.len()] = CRC8P1.checksum(&crc_in);
out[payload.len() + 1] = hdr;
hdr
}
P11DataIdMode::Nibble { data_id12 } => {
let di_hi = ((data_id12 >> 8) & 0x0F) as u8;
let hdr = ((di_hi & 0x0F) << 4) | c4;
let mut crc_in = Vec::with_capacity(payload.len() + 2);
crc_in.extend_from_slice(payload);
crc_in.push((data_id12 & 0xFF) as u8);
crc_in.push(hdr);
out[payload.len()] = CRC8P1.checksum(&crc_in);
out[payload.len() + 1] = hdr;
hdr
}
};
debug_assert_eq!(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 cnt = hdr & 0x0F;
let counter = match self.mode {
P11DataIdMode::Both { .. } => (hdr >> 4) & 0x0F,
P11DataIdMode::Nibble { .. } => cnt,
};
if !Self::is_counter_valid(counter) {
return Err(E2eError::Counter);
}
let payload = &frame[..frame.len()-2];
let crc_in = match self.mode {
P11DataIdMode::Both { data_id } => {
let mut crc_in = Vec::with_capacity(payload.len() + 3);
crc_in.extend_from_slice(payload);
crc_in.push((data_id & 0xFF) as u8);
crc_in.push((data_id >> 8) as u8);
crc_in.push(hdr); crc_in
}
P11DataIdMode::Nibble { data_id12 } => {
let mut crc_in = Vec::with_capacity(payload.len() + 2);
crc_in.extend_from_slice(payload);
crc_in.push((data_id12 & 0xFF) as u8);
crc_in.push(hdr);
crc_in
}
};
let want = CRC8P1.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 p11_both_roundtrip() {
let mut tx = E2eSession::new(
P11 { mode: P11DataIdMode::Both { data_id: 0x1234 } },
CounterPolicy::Rollover { bits: 4, step: 1 },
);
let f = tx.wrap(&[1,2,3]).unwrap();
let mut rx = E2eSession::new(
P11 { mode: P11DataIdMode::Both { data_id: 0x1234 } },
CounterPolicy::Rollover { bits: 4, step: 1 },
);
let pl = rx.unwrap(&f).unwrap();
assert_eq!(pl, &[1,2,3]);
}
#[test]
fn p11_nibble_roundtrip() {
let mut tx = E2eSession::new(
P11 { mode: P11DataIdMode::Nibble { data_id12: 0x0A7 } }, CounterPolicy::Rollover { bits: 4, step: 1 },
);
let f = tx.wrap(&[0xAA, 0xBB]).unwrap();
let mut rx = E2eSession::new(
P11 { mode: P11DataIdMode::Nibble { data_id12: 0x0A7 } },
CounterPolicy::Rollover { bits: 4, step: 1 },
);
let pl = rx.unwrap(&f).unwrap();
assert_eq!(pl, &[0xAA, 0xBB]);
}
#[test]
fn p11_detect_crc_error() {
let p = P11 { mode: P11DataIdMode::Both { data_id: 0xBEEF } };
let mut f = p.protect(&[1,2,3,4], 1).unwrap();
f[0] ^= 0x01; assert!(matches!(p.check(&f), Err(E2eError::Crc)));
}
#[test]
fn p11_illegal_counter() {
let p = P11 { mode: P11DataIdMode::Both { data_id: 0x55AA } };
assert!(matches!(p.protect(&[], 0x0F), Err(E2eError::Counter)));
}
}