use crate::node_id::NodeId;
use crate::wire::{
CANNED_MESSAGE_MARKER, CANNED_MESSAGE_SIGNED_SIZE, CANNED_MESSAGE_UNSIGNED_SIZE, SIGNATURE_SIZE,
};
use heapless::FnvIndexMap;
pub const MAX_CANNED_ACKS: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u8)]
pub enum CannedMessage {
#[default]
Ack = 0x00,
AckWilco = 0x01,
AckNegative = 0x02,
AckSayAgain = 0x03,
CheckIn = 0x10,
Moving = 0x11,
Holding = 0x12,
OnStation = 0x13,
Returning = 0x14,
Complete = 0x15,
Emergency = 0x20,
Alert = 0x21,
AllClear = 0x22,
Contact = 0x23,
UnderFire = 0x24,
NeedExtract = 0x30,
NeedSupport = 0x31,
NeedMedic = 0x32,
NeedResupply = 0x33,
Custom = 0xFF,
}
impl CannedMessage {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0x00 => Some(Self::Ack),
0x01 => Some(Self::AckWilco),
0x02 => Some(Self::AckNegative),
0x03 => Some(Self::AckSayAgain),
0x10 => Some(Self::CheckIn),
0x11 => Some(Self::Moving),
0x12 => Some(Self::Holding),
0x13 => Some(Self::OnStation),
0x14 => Some(Self::Returning),
0x15 => Some(Self::Complete),
0x20 => Some(Self::Emergency),
0x21 => Some(Self::Alert),
0x22 => Some(Self::AllClear),
0x23 => Some(Self::Contact),
0x24 => Some(Self::UnderFire),
0x30 => Some(Self::NeedExtract),
0x31 => Some(Self::NeedSupport),
0x32 => Some(Self::NeedMedic),
0x33 => Some(Self::NeedResupply),
0xFF => Some(Self::Custom),
_ => None,
}
}
#[inline]
pub const fn as_u8(self) -> u8 {
self as u8
}
#[inline]
pub const fn is_alert(self) -> bool {
matches!(
self,
Self::Emergency | Self::Alert | Self::Contact | Self::UnderFire
)
}
#[inline]
pub const fn is_ack(self) -> bool {
matches!(
self,
Self::Ack | Self::AckWilco | Self::AckNegative | Self::AckSayAgain
)
}
pub const fn short_name(self) -> &'static str {
match self {
Self::Ack => "ACK",
Self::AckWilco => "WILCO",
Self::AckNegative => "NEGATIVE",
Self::AckSayAgain => "SAY AGAIN",
Self::CheckIn => "CHECK IN",
Self::Moving => "MOVING",
Self::Holding => "HOLDING",
Self::OnStation => "ON STATION",
Self::Returning => "RTB",
Self::Complete => "COMPLETE",
Self::Emergency => "EMERGENCY",
Self::Alert => "ALERT",
Self::AllClear => "ALL CLEAR",
Self::Contact => "CONTACT",
Self::UnderFire => "UNDER FIRE",
Self::NeedExtract => "NEED EXTRACT",
Self::NeedSupport => "NEED SUPPORT",
Self::NeedMedic => "MEDIC",
Self::NeedResupply => "RESUPPLY",
Self::Custom => "CUSTOM",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CannedMessageEvent {
pub message: CannedMessage,
pub source_node: NodeId,
pub target_node: Option<NodeId>,
pub timestamp: u64,
pub sequence: u32,
}
impl CannedMessageEvent {
pub fn new(
message: CannedMessage,
source_node: NodeId,
target_node: Option<NodeId>,
timestamp: u64,
) -> Self {
Self {
message,
source_node,
target_node,
timestamp,
sequence: 0,
}
}
pub fn with_sequence(
message: CannedMessage,
source_node: NodeId,
target_node: Option<NodeId>,
timestamp: u64,
sequence: u32,
) -> Self {
Self {
message,
source_node,
target_node,
timestamp,
sequence,
}
}
pub fn encode(&self) -> heapless::Vec<u8, 22> {
let mut buf = heapless::Vec::new();
let _ = buf.push(CANNED_MESSAGE_MARKER);
let _ = buf.push(self.message.as_u8());
for b in self.source_node.to_le_bytes() {
let _ = buf.push(b);
}
let target = self.target_node.unwrap_or(NodeId::NULL);
for b in target.to_le_bytes() {
let _ = buf.push(b);
}
for b in self.timestamp.to_le_bytes() {
let _ = buf.push(b);
}
for b in self.sequence.to_le_bytes() {
let _ = buf.push(b);
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 22 {
return None;
}
if data[0] != CANNED_MESSAGE_MARKER {
return None;
}
let message = CannedMessage::from_u8(data[1])?;
let source_node = NodeId::from_le_bytes([data[2], data[3], data[4], data[5]]);
let target_bytes = [data[6], data[7], data[8], data[9]];
let target_node = if target_bytes == [0, 0, 0, 0] {
None
} else {
Some(NodeId::from_le_bytes(target_bytes))
};
let timestamp = u64::from_le_bytes([
data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17],
]);
let sequence = u32::from_le_bytes([data[18], data[19], data[20], data[21]]);
Some(Self {
message,
source_node,
target_node,
timestamp,
sequence,
})
}
pub fn is_newer_than(&self, other: &Self) -> bool {
self.timestamp > other.timestamp
|| (self.timestamp == other.timestamp && self.sequence > other.sequence)
}
pub fn encode_signed(&self, signature: &[u8; SIGNATURE_SIZE]) -> heapless::Vec<u8, 86> {
let mut buf = heapless::Vec::new();
let unsigned = self.encode();
for b in unsigned.iter() {
let _ = buf.push(*b);
}
for b in signature.iter() {
let _ = buf.push(*b);
}
buf
}
pub fn decode_signed(data: &[u8]) -> Option<(Self, [u8; SIGNATURE_SIZE])> {
if data.len() != CANNED_MESSAGE_SIGNED_SIZE {
return None;
}
let event = Self::decode(&data[..CANNED_MESSAGE_UNSIGNED_SIZE])?;
let mut signature = [0u8; SIGNATURE_SIZE];
signature.copy_from_slice(&data[CANNED_MESSAGE_UNSIGNED_SIZE..]);
Some((event, signature))
}
#[inline]
pub fn signable_payload(&self) -> heapless::Vec<u8, 22> {
self.encode()
}
#[inline]
pub fn is_signed_format(data: &[u8]) -> bool {
data.len() == CANNED_MESSAGE_SIGNED_SIZE && data.first() == Some(&CANNED_MESSAGE_MARKER)
}
#[inline]
pub fn is_unsigned_format(data: &[u8]) -> bool {
data.len() == CANNED_MESSAGE_UNSIGNED_SIZE && data.first() == Some(&CANNED_MESSAGE_MARKER)
}
pub fn decode_auto(data: &[u8]) -> Option<(Self, Option<[u8; SIGNATURE_SIZE]>)> {
if Self::is_signed_format(data) {
Self::decode_signed(data).map(|(e, s)| (e, Some(s)))
} else if Self::is_unsigned_format(data) {
Self::decode(data).map(|e| (e, None))
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct CannedMessageAckEvent {
pub message: CannedMessage,
pub source_node: NodeId,
pub target_node: Option<NodeId>,
pub timestamp: u64,
pub sequence: u32,
acks: FnvIndexMap<NodeId, u64, MAX_CANNED_ACKS>,
}
impl CannedMessageAckEvent {
pub fn new(
message: CannedMessage,
source_node: NodeId,
target_node: Option<NodeId>,
timestamp: u64,
) -> Self {
let mut acks = FnvIndexMap::new();
let _ = acks.insert(source_node, timestamp);
Self {
message,
source_node,
target_node,
timestamp,
sequence: 0,
acks,
}
}
pub fn with_sequence(
message: CannedMessage,
source_node: NodeId,
target_node: Option<NodeId>,
timestamp: u64,
sequence: u32,
) -> Self {
let mut event = Self::new(message, source_node, target_node, timestamp);
event.sequence = sequence;
event
}
pub fn ack(&mut self, node_id: NodeId, ack_timestamp: u64) -> bool {
if node_id == NodeId::NULL {
return false;
}
match self.acks.get(&node_id) {
Some(&existing_ts) if existing_ts >= ack_timestamp => false,
Some(_) => {
let _ = self.acks.insert(node_id, ack_timestamp);
true
}
None => {
self.acks.insert(node_id, ack_timestamp).is_ok()
}
}
}
pub fn has_acked(&self, node_id: NodeId) -> bool {
self.acks.contains_key(&node_id)
}
pub fn acked_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
self.acks.keys().copied()
}
pub fn ack_timestamp(&self, node_id: NodeId) -> Option<u64> {
self.acks.get(&node_id).copied()
}
pub fn ack_count(&self) -> usize {
self.acks.len()
}
pub fn merge(&mut self, other: &Self) -> bool {
if self.source_node != other.source_node || self.timestamp != other.timestamp {
if other.timestamp > self.timestamp {
*self = other.clone();
return true;
}
return false;
}
let mut changed = false;
for (node_id, &other_ts) in other.acks.iter() {
match self.acks.get(node_id) {
Some(&existing_ts) if existing_ts >= other_ts => {}
_ => {
if self.acks.insert(*node_id, other_ts).is_ok() {
changed = true;
}
}
}
}
changed
}
pub fn encode(&self) -> heapless::Vec<u8, 792> {
let mut buf = heapless::Vec::new();
let _ = buf.push(CANNED_MESSAGE_MARKER);
let _ = buf.push(self.message.as_u8());
for b in self.source_node.to_le_bytes() {
let _ = buf.push(b);
}
let target = self.target_node.unwrap_or(NodeId::NULL);
for b in target.to_le_bytes() {
let _ = buf.push(b);
}
for b in self.timestamp.to_le_bytes() {
let _ = buf.push(b);
}
for b in self.sequence.to_le_bytes() {
let _ = buf.push(b);
}
let num_acks = self.acks.len() as u16;
for b in num_acks.to_le_bytes() {
let _ = buf.push(b);
}
for (node_id, &ack_ts) in self.acks.iter() {
for b in node_id.to_le_bytes() {
let _ = buf.push(b);
}
for b in ack_ts.to_le_bytes() {
let _ = buf.push(b);
}
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 22 {
return None;
}
if data[0] != CANNED_MESSAGE_MARKER {
return None;
}
let message = CannedMessage::from_u8(data[1])?;
let source_node = NodeId::from_le_bytes([data[2], data[3], data[4], data[5]]);
if source_node == NodeId::NULL {
return None;
}
let target_bytes = [data[6], data[7], data[8], data[9]];
let target_node = if target_bytes == [0, 0, 0, 0] {
None
} else {
Some(NodeId::from_le_bytes(target_bytes))
};
let timestamp = u64::from_le_bytes([
data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17],
]);
let sequence = u32::from_le_bytes([data[18], data[19], data[20], data[21]]);
let mut acks = FnvIndexMap::new();
let _ = acks.insert(source_node, timestamp);
if data.len() >= 24 {
let num_acks = u16::from_le_bytes([data[22], data[23]]);
if num_acks as usize > MAX_CANNED_ACKS {
return None;
}
let expected_len = 24 + (num_acks as usize * 12);
if data.len() < expected_len {
return None;
}
let mut offset = 24;
for _ in 0..num_acks {
let acker_node = NodeId::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
let ack_ts = u64::from_le_bytes([
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
data[offset + 8],
data[offset + 9],
data[offset + 10],
data[offset + 11],
]);
offset += 12;
if acker_node != NodeId::NULL {
let _ = acks.insert(acker_node, ack_ts);
}
}
}
Some(Self {
message,
source_node,
target_node,
timestamp,
sequence,
acks,
})
}
pub fn as_event(&self) -> CannedMessageEvent {
CannedMessageEvent {
message: self.message,
source_node: self.source_node,
target_node: self.target_node,
timestamp: self.timestamp,
sequence: self.sequence,
}
}
pub fn from_event(event: CannedMessageEvent) -> Self {
let mut acks = FnvIndexMap::new();
let _ = acks.insert(event.source_node, event.timestamp);
Self {
message: event.message,
source_node: event.source_node,
target_node: event.target_node,
timestamp: event.timestamp,
sequence: event.sequence,
acks,
}
}
}
impl PartialEq for CannedMessageAckEvent {
fn eq(&self, other: &Self) -> bool {
self.message == other.message
&& self.source_node == other.source_node
&& self.target_node == other.target_node
&& self.timestamp == other.timestamp
&& self.sequence == other.sequence
&& self.acks.len() == other.acks.len()
&& self.acks.iter().all(|(k, v)| other.acks.get(k) == Some(v))
}
}
impl Eq for CannedMessageAckEvent {}
pub struct CannedMessageStore<const MAX_ENTRIES: usize = 256> {
events: FnvIndexMap<(NodeId, CannedMessage), CannedMessageEvent, MAX_ENTRIES>,
}
impl<const MAX_ENTRIES: usize> Default for CannedMessageStore<MAX_ENTRIES> {
fn default() -> Self {
Self::new()
}
}
impl<const MAX_ENTRIES: usize> CannedMessageStore<MAX_ENTRIES> {
pub const fn new() -> Self {
Self {
events: FnvIndexMap::new(),
}
}
pub fn insert(&mut self, event: CannedMessageEvent) -> bool {
let key = (event.source_node, event.message);
match self.events.get(&key) {
Some(existing) if !event.is_newer_than(existing) => false,
_ => {
if self.events.len() >= MAX_ENTRIES {
if let Some(oldest_key) = self.find_oldest_key() {
self.events.remove(&oldest_key);
}
}
self.events.insert(key, event).is_ok()
}
}
}
pub fn get(&self, source: NodeId, message: CannedMessage) -> Option<&CannedMessageEvent> {
self.events.get(&(source, message))
}
pub fn events_from(&self, source: NodeId) -> impl Iterator<Item = &CannedMessageEvent> {
self.events
.iter()
.filter(move |((src, _), _)| *src == source)
.map(|(_, event)| event)
}
pub fn events_of_type(
&self,
message: CannedMessage,
) -> impl Iterator<Item = &CannedMessageEvent> {
self.events
.iter()
.filter(move |((_, msg), _)| *msg == message)
.map(|(_, event)| event)
}
pub fn alerts(&self) -> impl Iterator<Item = &CannedMessageEvent> {
self.events
.iter()
.filter(|((_, msg), _)| msg.is_alert())
.map(|(_, event)| event)
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
pub fn clear(&mut self) {
self.events.clear();
}
fn find_oldest_key(&self) -> Option<(NodeId, CannedMessage)> {
self.events
.iter()
.min_by_key(|(_, event)| (event.timestamp, event.sequence))
.map(|(key, _)| *key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_canned_message_roundtrip() {
for code in [
CannedMessage::Ack,
CannedMessage::Emergency,
CannedMessage::CheckIn,
CannedMessage::Custom,
] {
let recovered = CannedMessage::from_u8(code.as_u8()).unwrap();
assert_eq!(code, recovered);
}
}
#[test]
fn test_event_encode_decode() {
let event = CannedMessageEvent::with_sequence(
CannedMessage::Ack,
NodeId::new(0x12345678),
Some(NodeId::new(0xDEADBEEF)),
1706234567000,
42,
);
let encoded = event.encode();
assert_eq!(encoded.len(), 22);
assert_eq!(encoded[0], CANNED_MESSAGE_MARKER);
let decoded = CannedMessageEvent::decode(&encoded).unwrap();
assert_eq!(decoded.message, event.message);
assert_eq!(decoded.source_node, event.source_node);
assert_eq!(decoded.target_node, event.target_node);
assert_eq!(decoded.timestamp, event.timestamp);
assert_eq!(decoded.sequence, event.sequence);
}
#[test]
fn test_event_no_target() {
let event = CannedMessageEvent::new(
CannedMessage::Emergency,
NodeId::new(0x12345678),
None,
1706234567000,
);
let encoded = event.encode();
let decoded = CannedMessageEvent::decode(&encoded).unwrap();
assert_eq!(decoded.target_node, None);
}
#[test]
fn test_store_lww() {
let mut store = CannedMessageStore::<16>::new();
let node = NodeId::new(0x123);
let event1 = CannedMessageEvent::with_sequence(CannedMessage::Ack, node, None, 1000, 1);
assert!(store.insert(event1));
let event_old = CannedMessageEvent::with_sequence(CannedMessage::Ack, node, None, 500, 1);
assert!(!store.insert(event_old));
let event2 = CannedMessageEvent::with_sequence(CannedMessage::Ack, node, None, 2000, 1);
assert!(store.insert(event2));
let stored = store.get(node, CannedMessage::Ack).unwrap();
assert_eq!(stored.timestamp, 2000);
}
#[test]
fn test_store_different_types() {
let mut store = CannedMessageStore::<16>::new();
let node = NodeId::new(0x123);
store.insert(CannedMessageEvent::new(
CannedMessage::Ack,
node,
None,
1000,
));
store.insert(CannedMessageEvent::new(
CannedMessage::Emergency,
node,
None,
1000,
));
store.insert(CannedMessageEvent::new(
CannedMessage::CheckIn,
node,
None,
1000,
));
assert_eq!(store.len(), 3);
assert!(store.get(node, CannedMessage::Ack).is_some());
assert!(store.get(node, CannedMessage::Emergency).is_some());
assert!(store.get(node, CannedMessage::CheckIn).is_some());
}
#[test]
fn test_ack_event_creation() {
let source = NodeId::new(0x12345678);
let event = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1706234567000);
assert!(event.has_acked(source));
assert_eq!(event.ack_count(), 1);
assert_eq!(event.ack_timestamp(source), Some(1706234567000));
}
#[test]
fn test_ack_recording() {
let source = NodeId::new(0x111);
let acker = NodeId::new(0x222);
let mut event = CannedMessageAckEvent::new(CannedMessage::Emergency, source, None, 1000);
assert!(event.ack(acker, 1500));
assert!(event.has_acked(acker));
assert_eq!(event.ack_count(), 2);
assert!(!event.ack(acker, 1500));
assert!(!event.ack(acker, 1400));
assert!(event.ack(acker, 1600));
assert_eq!(event.ack_timestamp(acker), Some(1600));
assert!(!event.ack(NodeId::NULL, 2000));
}
#[test]
fn test_ack_merge_same_event() {
let source = NodeId::new(0x111);
let node_a = NodeId::new(0x222);
let node_b = NodeId::new(0x333);
let mut event1 = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
event1.ack(node_a, 1100);
let mut event2 = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
event2.ack(node_b, 1200);
let changed = event1.merge(&event2);
assert!(changed);
assert!(event1.has_acked(source));
assert!(event1.has_acked(node_a));
assert!(event1.has_acked(node_b));
assert_eq!(event1.ack_count(), 3);
assert!(!event1.merge(&event2));
}
#[test]
fn test_ack_merge_different_event() {
let source = NodeId::new(0x111);
let acker = NodeId::new(0x222);
let mut older = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
older.ack(acker, 1100);
let newer = CannedMessageAckEvent::new(
CannedMessage::Alert, source,
None,
2000,
);
let changed = older.merge(&newer);
assert!(changed);
assert_eq!(older.timestamp, 2000);
assert_eq!(older.message, CannedMessage::Alert);
assert!(!older.has_acked(acker));
assert_eq!(older.ack_count(), 1);
let mut newer2 = CannedMessageAckEvent::new(CannedMessage::Alert, source, None, 2000);
let older2 = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
assert!(!newer2.merge(&older2));
}
#[test]
fn test_ack_encode_decode() {
let source = NodeId::new(0x12345678);
let target = NodeId::new(0xDEADBEEF);
let acker1 = NodeId::new(0xAAAA);
let acker2 = NodeId::new(0xBBBB);
let mut event = CannedMessageAckEvent::with_sequence(
CannedMessage::Emergency,
source,
Some(target),
1706234567000,
42,
);
event.ack(acker1, 1706234568000);
event.ack(acker2, 1706234569000);
let encoded = event.encode();
assert_eq!(encoded.len(), 24 + 3 * 12);
assert_eq!(encoded[0], CANNED_MESSAGE_MARKER);
let decoded = CannedMessageAckEvent::decode(&encoded).unwrap();
assert_eq!(decoded.message, event.message);
assert_eq!(decoded.source_node, event.source_node);
assert_eq!(decoded.target_node, event.target_node);
assert_eq!(decoded.timestamp, event.timestamp);
assert_eq!(decoded.sequence, event.sequence);
assert_eq!(decoded.ack_count(), 3);
assert!(decoded.has_acked(source));
assert!(decoded.has_acked(acker1));
assert!(decoded.has_acked(acker2));
assert_eq!(decoded.ack_timestamp(acker1), Some(1706234568000));
assert_eq!(decoded.ack_timestamp(acker2), Some(1706234569000));
}
#[test]
fn test_ack_decode_base_event() {
let base_event = CannedMessageEvent::with_sequence(
CannedMessage::CheckIn,
NodeId::new(0x12345678),
None,
1706234567000,
5,
);
let encoded = base_event.encode();
assert_eq!(encoded.len(), 22);
let decoded = CannedMessageAckEvent::decode(&encoded).unwrap();
assert_eq!(decoded.message, base_event.message);
assert_eq!(decoded.source_node, base_event.source_node);
assert_eq!(decoded.timestamp, base_event.timestamp);
assert_eq!(decoded.sequence, base_event.sequence);
assert_eq!(decoded.ack_count(), 1);
assert!(decoded.has_acked(base_event.source_node));
}
#[test]
fn test_ack_max_limit() {
let source = NodeId::new(0x111);
let mut event = CannedMessageAckEvent::new(CannedMessage::Emergency, source, None, 1000);
for i in 1..MAX_CANNED_ACKS {
let acker = NodeId::new(i as u32 + 1000);
assert!(
event.ack(acker, 2000 + i as u64),
"ack {} should succeed",
i
);
}
assert_eq!(event.ack_count(), MAX_CANNED_ACKS);
let overflow_acker = NodeId::new(0xFFFFFF);
assert!(!event.ack(overflow_acker, 9999));
assert!(!event.has_acked(overflow_acker));
}
#[test]
fn test_ack_validation() {
assert!(CannedMessageAckEvent::decode(&[0xAF]).is_none());
assert!(CannedMessageAckEvent::decode(&[0xAF; 21]).is_none());
let mut bad_marker = [0u8; 22];
bad_marker[0] = 0x00;
assert!(CannedMessageAckEvent::decode(&bad_marker).is_none());
let mut null_source = [0u8; 22];
null_source[0] = CANNED_MESSAGE_MARKER;
null_source[1] = CannedMessage::Ack.as_u8();
assert!(CannedMessageAckEvent::decode(&null_source).is_none());
let mut bad_code = [0u8; 22];
bad_code[0] = CANNED_MESSAGE_MARKER;
bad_code[1] = 0xEE; bad_code[2] = 1; assert!(CannedMessageAckEvent::decode(&bad_code).is_none());
let mut excessive_acks = [0u8; 24];
excessive_acks[0] = CANNED_MESSAGE_MARKER;
excessive_acks[1] = CannedMessage::Ack.as_u8();
excessive_acks[2] = 1; excessive_acks[22] = 0xFF;
excessive_acks[23] = 0xFF;
assert!(CannedMessageAckEvent::decode(&excessive_acks).is_none());
let mut truncated = [0u8; 24];
truncated[0] = CANNED_MESSAGE_MARKER;
truncated[1] = CannedMessage::Ack.as_u8();
truncated[2] = 1; truncated[22] = 5; truncated[23] = 0;
assert!(CannedMessageAckEvent::decode(&truncated).is_none());
}
#[test]
fn test_ack_event_as_event_roundtrip() {
let source = NodeId::new(0x12345678);
let target = NodeId::new(0xDEADBEEF);
let ack_event = CannedMessageAckEvent::with_sequence(
CannedMessage::NeedMedic,
source,
Some(target),
1706234567000,
99,
);
let base = ack_event.as_event();
assert_eq!(base.message, CannedMessage::NeedMedic);
assert_eq!(base.source_node, source);
assert_eq!(base.target_node, Some(target));
assert_eq!(base.timestamp, 1706234567000);
assert_eq!(base.sequence, 99);
let restored = CannedMessageAckEvent::from_event(base);
assert_eq!(restored.message, ack_event.message);
assert_eq!(restored.source_node, ack_event.source_node);
assert_eq!(restored.target_node, ack_event.target_node);
assert_eq!(restored.timestamp, ack_event.timestamp);
assert_eq!(restored.sequence, ack_event.sequence);
assert_eq!(restored.ack_count(), 1);
assert!(restored.has_acked(source));
}
#[test]
fn test_ack_event_acked_nodes_iterator() {
let source = NodeId::new(0x111);
let mut event = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
event.ack(NodeId::new(0x222), 1100);
event.ack(NodeId::new(0x333), 1200);
let nodes: heapless::Vec<NodeId, 8> = event.acked_nodes().collect();
assert_eq!(nodes.len(), 3);
assert!(nodes.contains(&source));
assert!(nodes.contains(&NodeId::new(0x222)));
assert!(nodes.contains(&NodeId::new(0x333)));
}
#[test]
fn test_signed_event_encode_decode() {
let event = CannedMessageEvent::with_sequence(
CannedMessage::Emergency,
NodeId::new(0x12345678),
Some(NodeId::new(0xDEADBEEF)),
1706234567000,
42,
);
let signature = [0xABu8; 64];
let encoded = event.encode_signed(&signature);
assert_eq!(encoded.len(), 86);
assert_eq!(encoded[0], CANNED_MESSAGE_MARKER);
let (decoded, decoded_sig) = CannedMessageEvent::decode_signed(&encoded).unwrap();
assert_eq!(decoded.message, event.message);
assert_eq!(decoded.source_node, event.source_node);
assert_eq!(decoded.target_node, event.target_node);
assert_eq!(decoded.timestamp, event.timestamp);
assert_eq!(decoded.sequence, event.sequence);
assert_eq!(decoded_sig, signature);
}
#[test]
fn test_signed_format_detection() {
let event = CannedMessageEvent::new(
CannedMessage::CheckIn,
NodeId::new(0x12345678),
None,
1706234567000,
);
let unsigned = event.encode();
assert!(CannedMessageEvent::is_unsigned_format(&unsigned));
assert!(!CannedMessageEvent::is_signed_format(&unsigned));
let signature = [0x00u8; 64];
let signed = event.encode_signed(&signature);
assert!(CannedMessageEvent::is_signed_format(&signed));
assert!(!CannedMessageEvent::is_unsigned_format(&signed));
assert!(!CannedMessageEvent::is_signed_format(&[0xAF; 50]));
assert!(!CannedMessageEvent::is_unsigned_format(&[0xAF; 50]));
assert!(!CannedMessageEvent::is_signed_format(&[0x00; 86])); }
#[test]
fn test_decode_auto() {
let event = CannedMessageEvent::with_sequence(
CannedMessage::Alert,
NodeId::new(0xAAAA),
None,
1000,
5,
);
let unsigned = event.encode();
let (decoded, sig_opt) = CannedMessageEvent::decode_auto(&unsigned).unwrap();
assert_eq!(decoded.message, event.message);
assert!(sig_opt.is_none());
let signature = [0xFFu8; 64];
let signed = event.encode_signed(&signature);
let (decoded, sig_opt) = CannedMessageEvent::decode_auto(&signed).unwrap();
assert_eq!(decoded.message, event.message);
assert_eq!(sig_opt, Some(signature));
assert!(CannedMessageEvent::decode_auto(&[0xAF; 50]).is_none());
}
#[test]
fn test_signable_payload() {
let event = CannedMessageEvent::new(
CannedMessage::Moving,
NodeId::new(0x12345678),
None,
1706234567000,
);
let payload = event.signable_payload();
let encoded = event.encode();
assert_eq!(payload.as_slice(), encoded.as_slice());
assert_eq!(payload.len(), 22);
}
#[test]
fn test_signed_decode_wrong_size() {
assert!(CannedMessageEvent::decode_signed(&[0xAF; 85]).is_none());
assert!(CannedMessageEvent::decode_signed(&[0xAF; 87]).is_none());
let mut bad = [0u8; 86];
bad[0] = 0x00;
assert!(CannedMessageEvent::decode_signed(&bad).is_none());
}
#[test]
fn test_wire_size_constants() {
use crate::wire::{
CANNED_MESSAGE_SIGNED_SIZE, CANNED_MESSAGE_UNSIGNED_SIZE, SIGNATURE_SIZE,
};
assert_eq!(CANNED_MESSAGE_UNSIGNED_SIZE, 22);
assert_eq!(SIGNATURE_SIZE, 64);
assert_eq!(CANNED_MESSAGE_SIGNED_SIZE, 86);
assert_eq!(
CANNED_MESSAGE_SIGNED_SIZE,
CANNED_MESSAGE_UNSIGNED_SIZE + SIGNATURE_SIZE
);
}
}