#![deny(unsafe_code)]
#![deny(warnings)]
#![no_std]
pub mod diagnostic;
mod name;
mod pgn;
pub mod protocol;
mod sa;
mod slots;
pub mod spn;
pub mod transport;
pub use name::*;
pub use pgn::*;
pub use sa::*;
pub const PGN_MAX_LENGTH: usize = 3;
pub const PDU_MAX_LENGTH: usize = 8;
pub const PDU_ERROR: u8 = 0xfe;
pub const PDU_NOT_AVAILABLE: u8 = 0xff;
pub const FIELD_DELIMITER: u8 = b'*';
pub const ID_BIT_MASK: u32 = 0x1fff_ffff;
#[derive(Debug, PartialEq, Eq)]
pub enum PDUFormat {
PDU1(u8),
PDU2(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Id(u32);
impl Id {
#[must_use]
pub const fn new(id: u32) -> Self {
Self(id & ID_BIT_MASK)
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> u32 {
self.0
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn priority(&self) -> u8 {
(self.0 >> 26) as u8
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn data_page(&self) -> u8 {
((self.0 >> 24) & 0x1) as u8
}
#[must_use]
pub fn pgn(&self) -> PGN {
self.pgn_raw().into()
}
#[must_use]
pub fn pgn_raw(&self) -> u32 {
match self.pdu_format() {
PDUFormat::PDU1(_) => (self.0 >> 8) & 0xff00,
PDUFormat::PDU2(_) => (self.0 >> 8) & 0xffff,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn pdu_format(&self) -> PDUFormat {
let format: u8 = ((self.0 >> 16) & 0xff) as u8;
if format < 0xf0 {
PDUFormat::PDU1(format)
} else {
PDUFormat::PDU2(format)
}
}
#[must_use]
pub fn is_broadcast(&self) -> bool {
match self.pdu_format() {
PDUFormat::PDU1(_) => self.destination_address() == Some(0xff),
PDUFormat::PDU2(_) => true,
}
}
#[must_use]
pub fn destination_address(&self) -> Option<u8> {
match self.pdu_format() {
PDUFormat::PDU1(_) => Some(self.pdu_specific()),
PDUFormat::PDU2(_) => None,
}
}
#[must_use]
pub fn group_extension(&self) -> Option<u8> {
match self.pdu_format() {
PDUFormat::PDU2(_) => Some(self.pdu_specific()),
PDUFormat::PDU1(_) => None,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn pdu_specific(&self) -> u8 {
((self.0 >> 8) & 0xff) as u8
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn source_address(&self) -> u8 {
(self.0 & 0xff) as u8
}
}
impl core::fmt::Display for Id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Some(da) = self.destination_address() {
write!(
f,
"[{:08X?}] Prio: {} PGN: {} DA: 0x{:X?}",
self.as_raw(),
self.priority(),
self.pgn_raw(),
da
)
} else {
write!(
f,
"[{:08X?}] Prio: {} PGN: {}",
self.as_raw(),
self.priority(),
self.pgn_raw()
)
}
}
}
pub struct IdBuilder {
priority: u8,
pgn: u32,
source_address: u8,
destination_address: u8,
}
impl IdBuilder {
#[must_use]
pub fn from_pgn(pgn: PGN) -> Self {
Self {
priority: 6,
pgn: pgn.into(),
source_address: 0,
destination_address: 0,
}
}
#[inline]
#[must_use]
pub fn priority(mut self, priority: u8) -> Self {
self.priority = priority.min(7);
self
}
#[inline]
#[must_use]
pub fn sa(mut self, address: u8) -> Self {
self.source_address = address;
self
}
#[inline]
#[must_use]
pub fn da(mut self, address: u8) -> Self {
self.destination_address = address;
self
}
#[must_use]
pub fn build(self) -> Id {
let mut id =
u32::from(self.priority) << 26 | self.pgn << 8 | u32::from(self.source_address);
if let PDUFormat::PDU1(_) = Id::new(id).pdu_format() {
id |= u32::from(self.destination_address) << 8;
}
Id::new(id)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Frame {
id: Id,
pdu: [u8; PDU_MAX_LENGTH],
pdu_length: usize,
}
impl Frame {
#[must_use]
pub fn new(id: Id, pdu: [u8; PDU_MAX_LENGTH]) -> Self {
Self {
id,
pdu,
pdu_length: PDU_MAX_LENGTH,
}
}
#[must_use]
pub fn from_raw(id: u32, pdu: [u8; PDU_MAX_LENGTH]) -> Self {
Self {
id: Id::new(id),
pdu,
pdu_length: PDU_MAX_LENGTH,
}
}
#[inline]
#[must_use]
pub fn id(&self) -> &Id {
&self.id
}
#[inline]
#[must_use]
pub fn pdu(&self) -> &[u8] {
&self.pdu[..self.pdu_length]
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.pdu_length
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.pdu_length == 0
}
}
impl core::fmt::Display for Frame {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} {:02X?}", self.id(), self.pdu())
}
}
impl AsRef<[u8]> for Frame {
fn as_ref(&self) -> &[u8] {
&self.pdu[..self.pdu_length]
}
}
pub struct FrameBuilder {
id: Id,
pdu: [u8; PDU_MAX_LENGTH],
pdu_length: usize,
}
impl Default for FrameBuilder {
fn default() -> Self {
Self {
id: Id::new(0),
pdu: [PDU_NOT_AVAILABLE; PDU_MAX_LENGTH],
pdu_length: 0,
}
}
}
impl FrameBuilder {
#[must_use]
pub fn new(id: Id) -> Self {
Self::default().id(id)
}
#[inline]
#[must_use]
pub fn id(mut self, id: Id) -> Self {
self.id = id;
self
}
#[must_use]
pub fn copy_from_slice(mut self, src: &[u8]) -> Self {
let pdu_length = src.len().min(PDU_MAX_LENGTH);
self.pdu[..pdu_length].copy_from_slice(&src[..pdu_length]);
self.pdu_length = pdu_length;
self
}
#[inline]
#[must_use]
pub fn set_len(mut self, len: usize) -> Self {
self.pdu_length = len.min(PDU_MAX_LENGTH);
self
}
#[must_use]
pub fn build(self) -> Frame {
Frame {
id: self.id,
pdu: self.pdu,
pdu_length: self.pdu_length,
}
}
}
impl AsMut<[u8]> for FrameBuilder {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.pdu
}
}
#[cfg(test)]
mod tests {
use crate::{FrameBuilder, Id, IdBuilder, PDUFormat, PDU_MAX_LENGTH, PDU_NOT_AVAILABLE, PGN};
#[test]
fn id_decode_1() {
let id = Id::new(0x18EA_FF00);
assert_eq!(id.as_raw(), 0x18EA_FF00);
assert_eq!(id.priority(), 6);
assert_eq!(id.data_page(), 0);
assert_eq!(id.pgn_raw(), 59904);
assert_eq!(id.pgn(), PGN::Request);
assert_eq!(id.pdu_format(), PDUFormat::PDU1(234));
assert!(id.is_broadcast());
assert_eq!(id.pdu_specific(), 255);
assert_eq!(id.destination_address(), Some(255));
assert_eq!(id.group_extension(), None);
assert_eq!(id.source_address(), 0);
}
#[test]
fn id_decode_2() {
let id = Id::new(0x18EA_687A);
assert_eq!(id.as_raw(), 0x18EA_687A);
assert_eq!(id.priority(), 6);
assert_eq!(id.data_page(), 0);
assert_eq!(id.pgn_raw(), 59904);
assert_eq!(id.pgn(), PGN::Request);
assert_eq!(id.pdu_format(), PDUFormat::PDU1(234));
assert!(!id.is_broadcast());
assert_eq!(id.pdu_specific(), 104);
assert_eq!(id.destination_address(), Some(0x68));
assert_eq!(id.group_extension(), None);
assert_eq!(id.source_address(), 0x7A);
}
#[test]
fn id_decode_3() {
let id = Id::new(0x0CFE_6CEE);
assert_eq!(id.as_raw(), 0x0CFE_6CEE);
assert_eq!(id.priority(), 3);
assert_eq!(id.data_page(), 0);
assert_eq!(id.pgn_raw(), 65132);
assert_eq!(id.pdu_format(), PDUFormat::PDU2(254));
assert!(id.is_broadcast());
assert_eq!(id.pdu_specific(), 108);
assert_eq!(id.destination_address(), None);
assert_eq!(id.group_extension(), Some(108));
assert_eq!(id.source_address(), 238);
}
#[test]
fn id_decode_4() {
let id = Id::new(0x0DFE_6CEE);
assert_eq!(id.as_raw(), 0x0DFE_6CEE);
assert_eq!(id.priority(), 3);
assert_eq!(id.data_page(), 1);
assert_eq!(id.pgn_raw(), 65132);
assert_eq!(id.pdu_format(), PDUFormat::PDU2(254));
assert!(id.is_broadcast());
assert_eq!(id.pdu_specific(), 108);
assert_eq!(id.destination_address(), None);
assert_eq!(id.group_extension(), Some(108));
assert_eq!(id.source_address(), 238);
}
#[test]
fn id_build_1() {
let id = IdBuilder::from_pgn(PGN::Transfer)
.priority(3)
.sa(139)
.build();
assert_eq!(id, Id::new(0x0CCA_008B));
}
#[test]
fn id_build_2() {
let id = IdBuilder::from_pgn(PGN::Transfer)
.priority(3)
.da(0x34)
.sa(139)
.build();
assert_eq!(id, Id::new(0x0CCA_348B));
}
#[test]
fn id_build_3() {
let id = IdBuilder::from_pgn(PGN::ElectronicEngineController1)
.priority(3)
.da(0)
.sa(12)
.build();
assert_eq!(id, Id::new(0x0CF0_040C));
assert_eq!(id.pgn_raw(), 61444);
}
#[test]
fn id_build_4() {
let id = IdBuilder::from_pgn(PGN::VehicleElectricalPower1)
.sa(234)
.build();
assert_eq!(id, Id::new(0x18FE_F7EA));
}
#[test]
fn id_build_5() {
let id = IdBuilder::from_pgn(PGN::Other(126_720)).sa(234).build();
assert_eq!(id, Id::new(0x19EF_00EA));
}
#[test]
fn frame_build_1() {
let frame = FrameBuilder::new(IdBuilder::from_pgn(PGN::Request).da(0x20).sa(0x10).build())
.copy_from_slice(&[0x1, 0x2, 0x3])
.build();
assert_eq!(frame.id(), &Id::new(0x18EA_2010));
assert_eq!(frame.pdu(), &[0x1, 0x2, 0x3]);
assert_eq!(frame.len(), 3);
assert!(!frame.is_empty());
}
#[test]
fn frame_build_2() {
let frame = FrameBuilder::new(
IdBuilder::from_pgn(PGN::AddressClaimed)
.priority(3)
.sa(0x10)
.build(),
)
.copy_from_slice(&[PDU_NOT_AVAILABLE; PDU_MAX_LENGTH])
.build();
assert_eq!(frame.id(), &Id::new(0x0CEE_0010));
assert_eq!(frame.pdu(), &[PDU_NOT_AVAILABLE; PDU_MAX_LENGTH]);
assert_eq!(frame.len(), PDU_MAX_LENGTH);
assert!(!frame.is_empty());
}
#[test]
fn frame_build_3() {
let frame = FrameBuilder::new(IdBuilder::from_pgn(PGN::Transfer).build()).build();
assert_eq!(frame.id(), &Id::new(0x18CA_0000));
assert_eq!(frame.pdu(), &[]);
assert_eq!(frame.len(), 0);
assert!(frame.is_empty());
}
#[test]
fn frame_build_4() {
let mut frame_builder =
FrameBuilder::default().id(IdBuilder::from_pgn(PGN::Transfer).build());
frame_builder
.as_mut()
.copy_from_slice(&[0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1]);
frame_builder = frame_builder.set_len(8);
let frame = frame_builder.build();
assert_eq!(frame.id(), &Id::new(0x18CA_0000));
assert_eq!(frame.pdu(), &[0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1]);
assert_eq!(frame.len(), PDU_MAX_LENGTH);
assert!(!frame.is_empty());
}
#[test]
fn frame_build_5() {
let frame = FrameBuilder::default()
.id(IdBuilder::from_pgn(PGN::ElectronicEngineController2).build())
.set_len(8)
.build();
assert_eq!(frame.pdu(), &[PDU_NOT_AVAILABLE; PDU_MAX_LENGTH]);
assert_eq!(frame.len(), PDU_MAX_LENGTH);
assert!(!frame.is_empty());
}
#[test]
fn pdu_format_boundary() {
let pdu1_max = Id::new(0x0CEF_FF00); let pdu2_min = Id::new(0x0CF0_0000);
assert!(matches!(pdu1_max.pdu_format(), PDUFormat::PDU1(_)));
assert!(matches!(pdu2_min.pdu_format(), PDUFormat::PDU2(_)));
assert!(pdu1_max.destination_address().is_some());
assert!(pdu2_min.destination_address().is_none());
assert!(pdu1_max.group_extension().is_none());
assert!(pdu2_min.group_extension().is_some());
}
#[test]
fn id_29bit_mask() {
let over_29bits = 0xFFFF_FFFF;
let id = Id::new(over_29bits);
assert_eq!(id.as_raw(), 0x1FFF_FFFF); assert!(id.as_raw() <= 0x1FFF_FFFF);
}
#[test]
fn priority_range() {
let id = IdBuilder::from_pgn(PGN::Request)
.priority(255) .sa(0x10)
.build();
assert_eq!(id.priority(), 7); }
#[test]
fn frame_empty() {
let frame = FrameBuilder::new(Id::new(0)).build();
assert!(frame.is_empty());
assert_eq!(frame.len(), 0);
assert_eq!(frame.pdu(), &[]);
}
#[test]
fn frame_zero_length_slice() {
let frame = FrameBuilder::new(Id::new(0x18EA_0000))
.copy_from_slice(&[])
.build();
assert!(frame.is_empty());
assert_eq!(frame.len(), 0);
}
#[test]
fn pgn_extraction_pdu1_zeros_ps() {
let id = Id::new(0x0CEA_5500);
assert_eq!(id.pgn_raw(), 0xEA00); assert!(id.pgn_raw() & 0xFF == 0); }
#[test]
fn pgn_extraction_pdu2_includes_ps() {
let id = Id::new(0x0CFE_6C00);
assert_eq!(id.pgn_raw(), 0xFE6C);
assert!(id.pgn_raw() & 0xFF != 0); }
}