use alloc::vec::Vec;
use crate::bus_logging::BusFrame;
pub const MAX_LIN_DATA_LEN: usize = 8;
pub const MAX_LIN_ID: u8 = 63;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum ChecksumType {
#[default]
Classic = 0,
Enhanced = 1,
}
impl ChecksumType {
pub fn from_u8(value: u8) -> Self {
match value {
1 => Self::Enhanced,
_ => Self::Classic,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LinFlags(u8);
impl LinFlags {
pub const TX: u8 = 0x01;
pub const WAKEUP: u8 = 0x02;
pub const CHECKSUM_ERROR: u8 = 0x04;
pub const NO_RESPONSE: u8 = 0x08;
pub const SYNC_ERROR: u8 = 0x10;
pub const FRAMING_ERROR: u8 = 0x20;
pub const SHORT_RESPONSE: u8 = 0x40;
pub const ENHANCED_CHECKSUM: u8 = 0x80;
pub fn from_byte(value: u8) -> Self {
Self(value)
}
pub fn to_byte(self) -> u8 {
self.0
}
pub fn rx() -> Self {
Self(0)
}
pub fn tx() -> Self {
Self(Self::TX)
}
pub fn is_tx(self) -> bool {
self.0 & Self::TX != 0
}
pub fn is_rx(self) -> bool {
!self.is_tx()
}
pub fn is_wakeup(self) -> bool {
self.0 & Self::WAKEUP != 0
}
pub fn has_checksum_error(self) -> bool {
self.0 & Self::CHECKSUM_ERROR != 0
}
pub fn has_no_response(self) -> bool {
self.0 & Self::NO_RESPONSE != 0
}
pub fn has_sync_error(self) -> bool {
self.0 & Self::SYNC_ERROR != 0
}
pub fn has_framing_error(self) -> bool {
self.0 & Self::FRAMING_ERROR != 0
}
pub fn has_short_response(self) -> bool {
self.0 & Self::SHORT_RESPONSE != 0
}
pub fn uses_enhanced_checksum(self) -> bool {
self.0 & Self::ENHANCED_CHECKSUM != 0
}
pub fn has_error(self) -> bool {
self.0
& (Self::CHECKSUM_ERROR
| Self::NO_RESPONSE
| Self::SYNC_ERROR
| Self::FRAMING_ERROR
| Self::SHORT_RESPONSE)
!= 0
}
pub fn with_tx(self, tx: bool) -> Self {
if tx {
Self(self.0 | Self::TX)
} else {
Self(self.0 & !Self::TX)
}
}
pub fn with_enhanced_checksum(self, enhanced: bool) -> Self {
if enhanced {
Self(self.0 | Self::ENHANCED_CHECKSUM)
} else {
Self(self.0 & !Self::ENHANCED_CHECKSUM)
}
}
}
#[derive(Debug, Clone)]
pub struct LinFrame {
pub id: u8,
pub data: [u8; MAX_LIN_DATA_LEN],
pub data_len: u8,
pub flags: LinFlags,
pub checksum: u8,
}
impl LinFrame {
pub fn new(id: u8, data: &[u8]) -> Self {
let mut frame_data = [0u8; MAX_LIN_DATA_LEN];
let len = data.len().min(MAX_LIN_DATA_LEN);
frame_data[..len].copy_from_slice(&data[..len]);
Self {
id: id & MAX_LIN_ID,
data: frame_data,
data_len: len as u8,
flags: LinFlags::default(),
checksum: 0,
}
}
pub fn with_classic_checksum(id: u8, data: &[u8]) -> Self {
let mut frame = Self::new(id, data);
frame.checksum = frame.calculate_classic_checksum();
frame
}
pub fn with_enhanced_checksum(id: u8, data: &[u8]) -> Self {
let mut frame = Self::new(id, data);
frame.checksum = frame.calculate_enhanced_checksum();
frame.flags = frame.flags.with_enhanced_checksum(true);
frame
}
pub fn calculate_classic_checksum(&self) -> u8 {
let mut sum: u16 = 0;
for i in 0..self.data_len as usize {
sum += self.data[i] as u16;
if sum > 0xFF {
sum = (sum & 0xFF) + 1;
}
}
!sum as u8
}
pub fn calculate_enhanced_checksum(&self) -> u8 {
let protected_id = self.protected_id();
let mut sum: u16 = protected_id as u16;
for i in 0..self.data_len as usize {
sum += self.data[i] as u16;
if sum > 0xFF {
sum = (sum & 0xFF) + 1;
}
}
!sum as u8
}
pub fn protected_id(&self) -> u8 {
let id = self.id & 0x3F;
let p0 = (id ^ (id >> 1) ^ (id >> 2) ^ (id >> 4)) & 0x01;
let p1 = !((id >> 1) ^ (id >> 3) ^ (id >> 4) ^ (id >> 5)) & 0x01;
id | (p0 << 6) | (p1 << 7)
}
pub fn with_tx(mut self) -> Self {
self.flags = self.flags.with_tx(true);
self
}
pub fn with_rx(mut self) -> Self {
self.flags = self.flags.with_tx(false);
self
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(12);
bytes.push(self.id);
bytes.push(self.data_len);
bytes.push(self.flags.to_byte());
bytes.push(self.checksum);
bytes.extend_from_slice(&self.data);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 12 {
return None;
}
let mut data = [0u8; MAX_LIN_DATA_LEN];
data.copy_from_slice(&bytes[4..12]);
Some(Self {
id: bytes[0] & MAX_LIN_ID,
data_len: bytes[1].min(MAX_LIN_DATA_LEN as u8),
flags: LinFlags::from_byte(bytes[2]),
checksum: bytes[3],
data,
})
}
pub fn data(&self) -> &[u8] {
&self.data[..self.data_len as usize]
}
}
impl Default for LinFrame {
fn default() -> Self {
Self::new(0, &[])
}
}
impl BusFrame for LinFrame {
fn to_mdf_bytes(&self) -> Vec<u8> {
self.to_bytes()
}
fn mdf_size(&self) -> usize {
12 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ScheduleEntryType {
Unconditional = 0,
EventTriggered = 1,
Sporadic = 2,
DiagnosticRequest = 3,
DiagnosticResponse = 4,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lin_frame_basic() {
let frame = LinFrame::new(0x20, &[0x01, 0x02, 0x03, 0x04]);
assert_eq!(frame.id, 0x20);
assert_eq!(frame.data_len, 4);
assert_eq!(frame.data(), &[0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn test_lin_frame_id_masking() {
let frame = LinFrame::new(0xFF, &[0x01]);
assert_eq!(frame.id, 0x3F);
}
#[test]
fn test_lin_frame_data_truncation() {
let data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A];
let frame = LinFrame::new(0x10, &data);
assert_eq!(frame.data_len, 8);
}
#[test]
fn test_lin_flags() {
let flags = LinFlags::tx();
assert!(flags.is_tx());
assert!(!flags.is_rx());
let flags = LinFlags::rx();
assert!(flags.is_rx());
assert!(!flags.is_tx());
let flags = LinFlags::from_byte(LinFlags::CHECKSUM_ERROR | LinFlags::NO_RESPONSE);
assert!(flags.has_checksum_error());
assert!(flags.has_no_response());
assert!(flags.has_error());
}
#[test]
fn test_protected_id() {
let frame = LinFrame::new(0x00, &[]);
assert_eq!(frame.protected_id() & 0x3F, 0x00);
let frame = LinFrame::new(0x3C, &[]); let pid = frame.protected_id();
assert_eq!(pid & 0x3F, 0x3C);
}
#[test]
fn test_classic_checksum() {
let frame = LinFrame::new(0x20, &[0x01, 0x02, 0x03, 0x04]);
let checksum = frame.calculate_classic_checksum();
assert_eq!(checksum, 0xF5);
}
#[test]
fn test_frame_roundtrip() {
let original = LinFrame::with_enhanced_checksum(0x20, &[0x01, 0x02, 0x03, 0x04]).with_tx();
let bytes = original.to_bytes();
let parsed = LinFrame::from_bytes(&bytes).unwrap();
assert_eq!(parsed.id, original.id);
assert_eq!(parsed.data_len, original.data_len);
assert_eq!(parsed.data(), original.data());
assert_eq!(parsed.checksum, original.checksum);
assert!(parsed.flags.is_tx());
}
#[test]
fn test_checksum_types() {
assert_eq!(ChecksumType::from_u8(0), ChecksumType::Classic);
assert_eq!(ChecksumType::from_u8(1), ChecksumType::Enhanced);
assert_eq!(ChecksumType::from_u8(99), ChecksumType::Classic);
}
}