#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::doc_markdown,
clippy::identity_op
)]
mod tests {
use alloc::vec;
use crate::address::{DestinationAddress, GroupAddress, IndividualAddress};
use crate::cemi::{CemiError, CemiFrame};
use crate::message::MessageCode;
use crate::types::{
AckType, AddressType, Confirm, FrameFormat, Priority, Repetition, SystemBroadcast,
};
const GROUP_WRITE_FRAME: &[u8] = &[
0x29, 0x00, 0xBC, 0xE0, 0x11, 0x01, 0x08, 0x01, 0x01, 0x00, 0x81, ];
#[test]
fn parse_group_write() {
let frame = CemiFrame::parse(GROUP_WRITE_FRAME).unwrap();
assert_eq!(frame.message_code_raw(), 0x29);
assert_eq!(frame.frame_type(), FrameFormat::Standard);
assert_eq!(frame.repetition(), Repetition::WasNotRepeated);
assert_eq!(frame.system_broadcast(), SystemBroadcast::Broadcast);
assert_eq!(frame.priority(), Priority::Low);
assert_eq!(frame.ack(), AckType::DontCare);
assert_eq!(frame.confirm(), Confirm::NoError);
assert_eq!(frame.address_type(), AddressType::Group);
assert_eq!(frame.hop_count(), 6);
assert_eq!(frame.source_address(), IndividualAddress::from_raw(0x1101));
assert_eq!(
frame.destination_address(),
DestinationAddress::Group(GroupAddress::from_raw(0x0801))
);
assert_eq!(frame.npdu_length(), 1);
assert_eq!(frame.payload(), &[0x00, 0x81]);
}
#[test]
fn new_l_data_roundtrip() {
let src = IndividualAddress::new(1, 1, 1).unwrap();
let dst = DestinationAddress::Group(GroupAddress::new_3level(1, 0, 1).unwrap());
let payload = &[0x00, 0x80];
let frame = CemiFrame::new_l_data(MessageCode::LDataInd, src, dst, Priority::Low, payload);
assert_eq!(frame.message_code_raw(), 0x29);
assert_eq!(frame.source_address(), src);
assert_eq!(frame.destination_address(), dst);
assert_eq!(frame.priority(), Priority::Low);
assert_eq!(frame.address_type(), AddressType::Group);
assert_eq!(frame.hop_count(), 6);
assert_eq!(frame.npdu_length(), 1);
assert_eq!(frame.payload(), payload);
let reparsed = CemiFrame::parse(frame.as_bytes()).unwrap();
assert_eq!(reparsed.source_address(), src);
assert_eq!(reparsed.destination_address(), dst);
assert_eq!(reparsed.payload(), payload);
}
#[test]
fn individual_destination() {
let src = IndividualAddress::new(1, 0, 1).unwrap();
let dst = DestinationAddress::Individual(IndividualAddress::new(1, 1, 5).unwrap());
let payload = &[0x00];
let frame =
CemiFrame::new_l_data(MessageCode::LDataReq, src, dst, Priority::System, payload);
assert_eq!(frame.address_type(), AddressType::Individual);
assert_eq!(frame.priority(), Priority::System);
assert_eq!(
frame.destination_address(),
DestinationAddress::Individual(IndividualAddress::new(1, 1, 5).unwrap())
);
}
#[test]
fn priority_encoding() {
let src = IndividualAddress::from_raw(0);
let dst = DestinationAddress::Group(GroupAddress::from_raw(1));
let payload = &[0x00, 0x00];
for prio in [
Priority::System,
Priority::Normal,
Priority::Urgent,
Priority::Low,
] {
let frame = CemiFrame::new_l_data(MessageCode::LDataInd, src, dst, prio, payload);
assert_eq!(frame.priority(), prio);
}
}
#[test]
fn tp_crc_calculation() {
assert_eq!(CemiFrame::calc_crc_tp(&[0x00]), 0xFF);
assert_eq!(CemiFrame::calc_crc_tp(&[0xFF]), 0x00);
assert_eq!(
CemiFrame::calc_crc_tp(&[0xBC, 0x11, 0x01, 0x08, 0x01, 0x01, 0x00, 0x81]),
0xBC ^ 0x11 ^ 0x01 ^ 0x08 ^ 0x01 ^ 0x01 ^ 0x00 ^ 0x81 ^ 0xFF
);
}
#[test]
fn parse_too_short() {
assert_eq!(CemiFrame::parse(&[]), Err(CemiError::TooShort));
assert_eq!(CemiFrame::parse(&[0x29]), Err(CemiError::TooShort));
assert_eq!(
CemiFrame::parse(&[0x29, 0x00, 0xBC]),
Err(CemiError::TooShort)
);
}
#[test]
fn parse_length_mismatch() {
let bad = &[0x29, 0x00, 0xBC, 0xE0, 0x11, 0x01, 0x08, 0x01, 0x05, 0x00];
assert_eq!(CemiFrame::parse(bad), Err(CemiError::LengthMismatch));
}
#[test]
fn parse_rejects_unknown_message_code() {
let mut bad = GROUP_WRITE_FRAME.to_vec();
bad[0] = 0x00;
assert_eq!(
CemiFrame::parse(&bad),
Err(CemiError::UnknownMessageCode(0))
);
}
#[test]
fn parse_rejects_trailing_bytes() {
let mut bad = GROUP_WRITE_FRAME.to_vec();
bad.push(0x00);
assert_eq!(CemiFrame::parse(&bad), Err(CemiError::LengthMismatch));
}
#[test]
fn try_new_l_data_rejects_oversized_payload() {
let src = IndividualAddress::from_raw(0);
let dst = DestinationAddress::Group(GroupAddress::from_raw(1));
let payload = vec![0; usize::from(u8::MAX) + 2];
assert_eq!(
CemiFrame::try_new_l_data(MessageCode::LDataInd, src, dst, Priority::Low, &payload),
Err(CemiError::PayloadTooLong(payload.len()))
);
}
#[test]
fn empty_l_data_roundtrip_has_minimum_tpdu() {
let src = IndividualAddress::from_raw(0);
let dst = DestinationAddress::Group(GroupAddress::from_raw(1));
let frame = CemiFrame::new_l_data(MessageCode::LDataInd, src, dst, Priority::Low, &[]);
assert_eq!(frame.npdu_length(), 0);
assert_eq!(frame.payload(), &[0x00, 0x00]);
CemiFrame::parse(frame.as_bytes()).unwrap();
}
#[test]
fn parse_with_additional_info() {
let frame_data = &[
0x29, 0x02, 0xAA, 0xBB, 0xBC, 0xE0, 0x11, 0x01, 0x08, 0x01, 0x01, 0x00, 0x81, ];
let frame = CemiFrame::parse(frame_data).unwrap();
assert_eq!(frame.additional_info_length(), 2);
assert_eq!(frame.source_address(), IndividualAddress::from_raw(0x1101));
assert_eq!(frame.npdu_length(), 1);
}
#[test]
fn total_length_matches() {
let frame = CemiFrame::parse(GROUP_WRITE_FRAME).unwrap();
assert_eq!(frame.total_length(), GROUP_WRITE_FRAME.len());
assert_eq!(frame.as_bytes(), GROUP_WRITE_FRAME);
}
}