use alloc::vec::Vec;
use core::fmt;
use crate::address::{DestinationAddress, GroupAddress, IndividualAddress};
use crate::message::{ApduType, MessageCode};
use crate::tpdu::Tpdu;
use crate::types::{
AckType, AddressType, Confirm, FrameFormat, Priority, Repetition, SystemBroadcast,
};
const MIN_FRAME_SIZE: usize = 9;
const NPDU_LEN_OFFSET: usize = 6;
const TPDU_OFFSET: usize = 7;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CemiError {
TooShort,
LengthMismatch,
UnknownMessageCode(u8),
InvalidControlField,
PayloadTooLong(usize),
}
impl fmt::Display for CemiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooShort => f.write_str("cEMI frame too short"),
Self::LengthMismatch => f.write_str("cEMI frame length mismatch"),
Self::UnknownMessageCode(c) => write!(f, "unknown cEMI message code: {c:#04x}"),
Self::InvalidControlField => f.write_str("invalid cEMI control field"),
Self::PayloadTooLong(len) => write!(f, "cEMI payload too long: {len} bytes"),
}
}
}
impl core::error::Error for CemiError {}
#[derive(Clone, PartialEq, Eq)]
pub struct CemiFrame {
data: Vec<u8>,
ctrl_offset: usize,
}
impl CemiFrame {
pub fn parse(data: &[u8]) -> Result<Self, CemiError> {
if data.len() < 2 {
return Err(CemiError::TooShort);
}
MessageCode::try_from(data[0]).map_err(|_| CemiError::UnknownMessageCode(data[0]))?;
let add_info_len = data[1] as usize;
let ctrl_offset = 2 + add_info_len;
if data.len() < ctrl_offset + 7 {
return Err(CemiError::TooShort);
}
let npdu_octet_count = data[ctrl_offset + NPDU_LEN_OFFSET] as usize;
let payload_len = (npdu_octet_count + 1).max(2);
let expected_len = ctrl_offset + TPDU_OFFSET + payload_len;
if data.len() != expected_len {
return Err(CemiError::LengthMismatch);
}
Ok(Self {
data: data[..expected_len].to_vec(),
ctrl_offset,
})
}
pub fn try_new_l_data(
message_code: MessageCode,
source: IndividualAddress,
destination: DestinationAddress,
priority: Priority,
payload: &[u8],
) -> Result<Self, CemiError> {
if payload.len() > usize::from(u8::MAX) + 1 {
return Err(CemiError::PayloadTooLong(payload.len()));
}
let npdu_octet_count = if payload.is_empty() {
0
} else {
payload.len() - 1
};
let encoded_payload_len = payload.len().max(2);
let total_len = MIN_FRAME_SIZE + encoded_payload_len;
let mut data = alloc::vec![0u8; total_len];
data[0] = message_code as u8;
data[1] = 0;
let ctrl_offset = 2;
data[ctrl_offset] = FrameFormat::Standard as u8
| Repetition::WasNotRepeated as u8
| SystemBroadcast::Broadcast as u8
| priority as u8;
let addr_type = match destination {
DestinationAddress::Group(_) => AddressType::Group,
DestinationAddress::Individual(_) => AddressType::Individual,
};
data[ctrl_offset + 1] = addr_type as u8 | (6 << 4);
let src_bytes = source.to_bytes();
data[ctrl_offset + 2] = src_bytes[0];
data[ctrl_offset + 3] = src_bytes[1];
let dst_raw = match destination {
DestinationAddress::Group(ga) => ga.raw(),
DestinationAddress::Individual(ia) => ia.raw(),
};
let dst_bytes = dst_raw.to_be_bytes();
data[ctrl_offset + 4] = dst_bytes[0];
data[ctrl_offset + 5] = dst_bytes[1];
data[ctrl_offset + NPDU_LEN_OFFSET] =
u8::try_from(npdu_octet_count).map_err(|_| CemiError::PayloadTooLong(payload.len()))?;
if !payload.is_empty() {
data[ctrl_offset + TPDU_OFFSET..][..payload.len()].copy_from_slice(payload);
}
Ok(Self { data, ctrl_offset })
}
pub fn new_l_data(
message_code: MessageCode,
source: IndividualAddress,
destination: DestinationAddress,
priority: Priority,
payload: &[u8],
) -> Self {
match Self::try_new_l_data(message_code, source, destination, priority, payload) {
Ok(frame) => frame,
Err(CemiError::PayloadTooLong(len)) => {
panic!("cEMI payload length {len} exceeds the NPDU length field")
}
Err(_) => unreachable!("constructing an L_Data frame cannot fail for this reason"),
}
}
pub fn message_code_raw(&self) -> u8 {
self.data[0]
}
pub fn additional_info_length(&self) -> u8 {
self.data[1]
}
fn ctrl1(&self) -> u8 {
self.data[self.ctrl_offset]
}
fn ctrl2(&self) -> u8 {
self.data[self.ctrl_offset + 1]
}
pub fn frame_type(&self) -> FrameFormat {
if self.ctrl1() & 0x80 != 0 {
FrameFormat::Standard
} else {
FrameFormat::Extended
}
}
pub fn repetition(&self) -> Repetition {
if self.ctrl1() & 0x20 != 0 {
Repetition::WasNotRepeated
} else {
Repetition::WasRepeated
}
}
pub fn system_broadcast(&self) -> SystemBroadcast {
if self.ctrl1() & 0x10 != 0 {
SystemBroadcast::Broadcast
} else {
SystemBroadcast::System
}
}
pub fn priority(&self) -> Priority {
match self.ctrl1() & 0x0C {
0x00 => Priority::System,
0x04 => Priority::Normal,
0x08 => Priority::Urgent,
_ => Priority::Low,
}
}
pub fn ack(&self) -> AckType {
if self.ctrl1() & 0x02 != 0 {
AckType::Requested
} else {
AckType::DontCare
}
}
pub fn confirm(&self) -> Confirm {
if self.ctrl1() & 0x01 != 0 {
Confirm::Error
} else {
Confirm::NoError
}
}
pub fn address_type(&self) -> AddressType {
if self.ctrl2() & 0x80 != 0 {
AddressType::Group
} else {
AddressType::Individual
}
}
pub fn hop_count(&self) -> u8 {
(self.ctrl2() >> 4) & 0x07
}
pub fn source_address(&self) -> IndividualAddress {
let off = self.ctrl_offset + 2;
IndividualAddress::from_bytes([self.data[off], self.data[off + 1]])
}
pub fn destination_address_raw(&self) -> u16 {
let off = self.ctrl_offset + 4;
u16::from_be_bytes([self.data[off], self.data[off + 1]])
}
pub fn destination_address(&self) -> DestinationAddress {
let raw = self.destination_address_raw();
match self.address_type() {
AddressType::Group => DestinationAddress::Group(GroupAddress::from_raw(raw)),
AddressType::Individual => {
DestinationAddress::Individual(IndividualAddress::from_raw(raw))
}
}
}
pub fn npdu_length(&self) -> u8 {
self.data[self.ctrl_offset + NPDU_LEN_OFFSET]
}
pub fn payload(&self) -> &[u8] {
let start = self.ctrl_offset + TPDU_OFFSET;
&self.data[start..]
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn total_length(&self) -> usize {
self.data.len()
}
pub fn set_frame_type(&mut self, value: FrameFormat) {
self.data[self.ctrl_offset] = (self.data[self.ctrl_offset] & !0x80) | (value as u8);
}
pub fn set_repetition(&mut self, value: Repetition) {
self.data[self.ctrl_offset] = (self.data[self.ctrl_offset] & !0x20) | (value as u8);
}
pub fn set_system_broadcast(&mut self, value: SystemBroadcast) {
self.data[self.ctrl_offset] = (self.data[self.ctrl_offset] & !0x10) | (value as u8);
}
pub fn set_priority(&mut self, value: Priority) {
self.data[self.ctrl_offset] = (self.data[self.ctrl_offset] & !0x0C) | (value as u8);
}
pub fn set_ack(&mut self, value: AckType) {
self.data[self.ctrl_offset] = (self.data[self.ctrl_offset] & !0x02) | (value as u8);
}
pub fn set_confirm(&mut self, value: Confirm) {
self.data[self.ctrl_offset] = (self.data[self.ctrl_offset] & !0x01) | (value as u8);
}
pub fn set_address_type(&mut self, value: AddressType) {
self.data[self.ctrl_offset + 1] = (self.data[self.ctrl_offset + 1] & !0x80) | (value as u8);
}
pub fn set_hop_count(&mut self, value: u8) {
self.data[self.ctrl_offset + 1] =
(self.data[self.ctrl_offset + 1] & !0x70) | ((value & 0x07) << 4);
}
pub fn set_source_address(&mut self, addr: IndividualAddress) {
let bytes = addr.to_bytes();
let off = self.ctrl_offset + 2;
self.data[off] = bytes[0];
self.data[off + 1] = bytes[1];
}
pub fn set_destination_address_raw(&mut self, addr: u16) {
let bytes = addr.to_be_bytes();
let off = self.ctrl_offset + 4;
self.data[off] = bytes[0];
self.data[off + 1] = bytes[1];
}
pub fn as_group_write(&self) -> Option<(GroupAddress, Vec<u8>)> {
if self.address_type() != AddressType::Group {
return None;
}
let tpdu = self.tpdu()?;
let apdu = tpdu.apdu()?;
match apdu.apdu_type {
ApduType::GroupValueWrite | ApduType::GroupValueResponse => {}
_ => return None,
}
let ga = GroupAddress::from_raw(self.destination_address_raw());
Some((ga, apdu.data.clone()))
}
pub fn as_group_read(&self) -> Option<GroupAddress> {
if self.address_type() != AddressType::Group {
return None;
}
let tpdu = self.tpdu()?;
let apdu = tpdu.apdu()?;
if apdu.apdu_type != ApduType::GroupValueRead {
return None;
}
Some(GroupAddress::from_raw(self.destination_address_raw()))
}
pub fn calc_crc_tp(buffer: &[u8]) -> u8 {
let mut crc: u8 = 0xFF;
for &b in buffer {
crc ^= b;
}
crc
}
pub fn tpdu(&self) -> Option<Tpdu> {
Tpdu::parse(
self.payload(),
self.npdu_length(),
self.address_type(),
self.destination_address_raw(),
)
}
}
impl fmt::Debug for CemiFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CemiFrame")
.field("message_code", &self.message_code_raw())
.field("frame_type", &self.frame_type())
.field("priority", &self.priority())
.field("source", &self.source_address())
.field("destination", &self.destination_address())
.field("hop_count", &self.hop_count())
.field("payload_len", &self.npdu_length())
.finish()
}
}