mod checksum_type;
mod lin_flags;
mod schedule_entry_type;
pub use checksum_type::ChecksumType;
pub use lin_flags::LinFlags;
pub use schedule_entry_type::ScheduleEntryType;
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)]
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 }
}
#[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);
}
}