Skip to main content

peat_lite/
canned.rs

1// Copyright (c) 2025-2026 Defense Unicorns, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Canned (predefined) message types for resource-constrained devices.
5//!
6//! These message codes are designed for button-based interaction on devices
7//! without keyboard input (e.g., WearTAK on Samsung watches).
8
9use crate::node_id::NodeId;
10use crate::wire::{
11    CANNED_MESSAGE_MARKER, CANNED_MESSAGE_SIGNED_SIZE, CANNED_MESSAGE_UNSIGNED_SIZE, SIGNATURE_SIZE,
12};
13use heapless::FnvIndexMap;
14
15/// Maximum ACK entries per CannedMessageAckEvent (memory bound for embedded).
16pub const MAX_CANNED_ACKS: usize = 64;
17
18/// Predefined message codes for resource-constrained devices.
19///
20/// Designed for button-based interaction (no keyboard input).
21/// Each code fits in a single byte, making wire format compact.
22///
23/// # Code Ranges
24///
25/// - `0x00-0x0F`: Acknowledgments
26/// - `0x10-0x1F`: Status updates
27/// - `0x20-0x2F`: Alerts and emergencies
28/// - `0x30-0x3F`: Requests
29/// - `0xF0-0xFF`: Reserved/custom
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
31#[repr(u8)]
32pub enum CannedMessage {
33    // ===== Acknowledgments (0x00-0x0F) =====
34    /// "Message received" - general acknowledgment
35    #[default]
36    Ack = 0x00,
37    /// "Will comply" - affirmative acknowledgment
38    AckWilco = 0x01,
39    /// "Cannot comply" - negative acknowledgment
40    AckNegative = 0x02,
41    /// "Say again" - request repeat
42    AckSayAgain = 0x03,
43
44    // ===== Status (0x10-0x1F) =====
45    /// "I'm here / still alive" - periodic check-in
46    CheckIn = 0x10,
47    /// "En route" - moving to objective
48    Moving = 0x11,
49    /// "Stationary / waiting" - holding position
50    Holding = 0x12,
51    /// "Arrived at position" - on station
52    OnStation = 0x13,
53    /// "Returning" - heading back
54    Returning = 0x14,
55    /// "Mission complete" - task finished
56    Complete = 0x15,
57
58    // ===== Alerts (0x20-0x2F) =====
59    /// "Need immediate help" - emergency distress
60    Emergency = 0x20,
61    /// "Attention needed" - non-emergency alert
62    Alert = 0x21,
63    /// "Situation resolved" - cancel previous alert
64    AllClear = 0x22,
65    /// "Contact" - enemy/threat spotted
66    Contact = 0x23,
67    /// "Under fire" - taking fire
68    UnderFire = 0x24,
69
70    // ===== Requests (0x30-0x3F) =====
71    /// "Request pickup" - need extraction
72    NeedExtract = 0x30,
73    /// "Request assistance" - need support
74    NeedSupport = 0x31,
75    /// "Medical emergency" - need medic
76    NeedMedic = 0x32,
77    /// "Need resupply" - ammunition/supplies
78    NeedResupply = 0x33,
79
80    // ===== Reserved (0xF0-0xFF) =====
81    /// Custom/application-specific message
82    Custom = 0xFF,
83}
84
85impl CannedMessage {
86    /// Convert from raw byte value.
87    ///
88    /// Returns `None` for undefined codes.
89    pub fn from_u8(value: u8) -> Option<Self> {
90        match value {
91            0x00 => Some(Self::Ack),
92            0x01 => Some(Self::AckWilco),
93            0x02 => Some(Self::AckNegative),
94            0x03 => Some(Self::AckSayAgain),
95            0x10 => Some(Self::CheckIn),
96            0x11 => Some(Self::Moving),
97            0x12 => Some(Self::Holding),
98            0x13 => Some(Self::OnStation),
99            0x14 => Some(Self::Returning),
100            0x15 => Some(Self::Complete),
101            0x20 => Some(Self::Emergency),
102            0x21 => Some(Self::Alert),
103            0x22 => Some(Self::AllClear),
104            0x23 => Some(Self::Contact),
105            0x24 => Some(Self::UnderFire),
106            0x30 => Some(Self::NeedExtract),
107            0x31 => Some(Self::NeedSupport),
108            0x32 => Some(Self::NeedMedic),
109            0x33 => Some(Self::NeedResupply),
110            0xFF => Some(Self::Custom),
111            _ => None,
112        }
113    }
114
115    /// Convert to raw byte value.
116    #[inline]
117    pub const fn as_u8(self) -> u8 {
118        self as u8
119    }
120
121    /// Check if this is an emergency/alert type message.
122    #[inline]
123    pub const fn is_alert(self) -> bool {
124        matches!(
125            self,
126            Self::Emergency | Self::Alert | Self::Contact | Self::UnderFire
127        )
128    }
129
130    /// Check if this is an acknowledgment type message.
131    #[inline]
132    pub const fn is_ack(self) -> bool {
133        matches!(
134            self,
135            Self::Ack | Self::AckWilco | Self::AckNegative | Self::AckSayAgain
136        )
137    }
138
139    /// Get a human-readable short name for display.
140    pub const fn short_name(self) -> &'static str {
141        match self {
142            Self::Ack => "ACK",
143            Self::AckWilco => "WILCO",
144            Self::AckNegative => "NEGATIVE",
145            Self::AckSayAgain => "SAY AGAIN",
146            Self::CheckIn => "CHECK IN",
147            Self::Moving => "MOVING",
148            Self::Holding => "HOLDING",
149            Self::OnStation => "ON STATION",
150            Self::Returning => "RTB",
151            Self::Complete => "COMPLETE",
152            Self::Emergency => "EMERGENCY",
153            Self::Alert => "ALERT",
154            Self::AllClear => "ALL CLEAR",
155            Self::Contact => "CONTACT",
156            Self::UnderFire => "UNDER FIRE",
157            Self::NeedExtract => "NEED EXTRACT",
158            Self::NeedSupport => "NEED SUPPORT",
159            Self::NeedMedic => "MEDIC",
160            Self::NeedResupply => "RESUPPLY",
161            Self::Custom => "CUSTOM",
162        }
163    }
164}
165
166/// A canned message event with metadata.
167///
168/// Contains the message code plus source, optional target, timestamp,
169/// and sequence number for deduplication.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct CannedMessageEvent {
172    /// The message type
173    pub message: CannedMessage,
174    /// Source node that sent this message
175    pub source_node: NodeId,
176    /// Target node (if directed message, e.g., ACK to specific node)
177    pub target_node: Option<NodeId>,
178    /// Timestamp in milliseconds (epoch or boot time)
179    pub timestamp: u64,
180    /// Sequence number for deduplication
181    pub sequence: u32,
182}
183
184impl CannedMessageEvent {
185    /// Create a new canned message event.
186    pub fn new(
187        message: CannedMessage,
188        source_node: NodeId,
189        target_node: Option<NodeId>,
190        timestamp: u64,
191    ) -> Self {
192        Self {
193            message,
194            source_node,
195            target_node,
196            timestamp,
197            sequence: 0,
198        }
199    }
200
201    /// Create with explicit sequence number.
202    pub fn with_sequence(
203        message: CannedMessage,
204        source_node: NodeId,
205        target_node: Option<NodeId>,
206        timestamp: u64,
207        sequence: u32,
208    ) -> Self {
209        Self {
210            message,
211            source_node,
212            target_node,
213            timestamp,
214            sequence,
215        }
216    }
217
218    /// Encode to wire format.
219    ///
220    /// Format:
221    /// ```text
222    /// ┌──────┬──────────┬──────────┬──────────┬───────────┬──────┐
223    /// │ 0xAF │ msg_code │ src_node │ tgt_node │ timestamp │ seq  │
224    /// │ 1B   │ 1B       │ 4B       │ 4B (opt) │ 8B        │ 4B   │
225    /// └──────┴──────────┴──────────┴──────────┴───────────┴──────┘
226    /// ```
227    ///
228    /// If target_node is None, those 4 bytes are 0x00000000.
229    pub fn encode(&self) -> heapless::Vec<u8, 22> {
230        let mut buf = heapless::Vec::new();
231
232        // Marker
233        let _ = buf.push(CANNED_MESSAGE_MARKER);
234
235        // Message code
236        let _ = buf.push(self.message.as_u8());
237
238        // Source node (4 bytes LE)
239        for b in self.source_node.to_le_bytes() {
240            let _ = buf.push(b);
241        }
242
243        // Target node (4 bytes LE, 0 if None)
244        let target = self.target_node.unwrap_or(NodeId::NULL);
245        for b in target.to_le_bytes() {
246            let _ = buf.push(b);
247        }
248
249        // Timestamp (8 bytes LE)
250        for b in self.timestamp.to_le_bytes() {
251            let _ = buf.push(b);
252        }
253
254        // Sequence (4 bytes LE)
255        for b in self.sequence.to_le_bytes() {
256            let _ = buf.push(b);
257        }
258
259        buf
260    }
261
262    /// Decode from wire format.
263    ///
264    /// Returns `None` if data is malformed.
265    pub fn decode(data: &[u8]) -> Option<Self> {
266        if data.len() < 22 {
267            return None;
268        }
269
270        if data[0] != CANNED_MESSAGE_MARKER {
271            return None;
272        }
273
274        let message = CannedMessage::from_u8(data[1])?;
275
276        let source_node = NodeId::from_le_bytes([data[2], data[3], data[4], data[5]]);
277
278        let target_bytes = [data[6], data[7], data[8], data[9]];
279        let target_node = if target_bytes == [0, 0, 0, 0] {
280            None
281        } else {
282            Some(NodeId::from_le_bytes(target_bytes))
283        };
284
285        let timestamp = u64::from_le_bytes([
286            data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17],
287        ]);
288
289        let sequence = u32::from_le_bytes([data[18], data[19], data[20], data[21]]);
290
291        Some(Self {
292            message,
293            source_node,
294            target_node,
295            timestamp,
296            sequence,
297        })
298    }
299
300    /// Check if this event is newer than another from the same source.
301    pub fn is_newer_than(&self, other: &Self) -> bool {
302        self.timestamp > other.timestamp
303            || (self.timestamp == other.timestamp && self.sequence > other.sequence)
304    }
305
306    /// Encode to signed wire format with Ed25519 signature.
307    ///
308    /// Format:
309    /// ```text
310    /// ┌──────┬──────────┬──────────┬──────────┬───────────┬──────┬───────────┐
311    /// │ 0xAF │ msg_code │ src_node │ tgt_node │ timestamp │ seq  │ signature │
312    /// │ 1B   │ 1B       │ 4B       │ 4B       │ 8B        │ 4B   │ 64B       │
313    /// └──────┴──────────┴──────────┴──────────┴───────────┴──────┴───────────┘
314    /// ```
315    ///
316    /// The signature should cover the first 22 bytes (marker through seq).
317    /// Caller is responsible for computing the signature using their identity.
318    ///
319    /// # Arguments
320    /// * `signature` - Ed25519 signature over the unsigned payload (22 bytes)
321    pub fn encode_signed(&self, signature: &[u8; SIGNATURE_SIZE]) -> heapless::Vec<u8, 86> {
322        let mut buf = heapless::Vec::new();
323
324        // Encode unsigned portion (22 bytes)
325        let unsigned = self.encode();
326        for b in unsigned.iter() {
327            let _ = buf.push(*b);
328        }
329
330        // Append signature (64 bytes)
331        for b in signature.iter() {
332            let _ = buf.push(*b);
333        }
334
335        buf
336    }
337
338    /// Decode from signed wire format.
339    ///
340    /// Returns the event and signature if the data is exactly 86 bytes
341    /// and has a valid marker.
342    ///
343    /// **Note:** This does NOT verify the signature. Caller must verify
344    /// using the sender's public key from the identity registry.
345    ///
346    /// # Returns
347    /// `Some((event, signature))` if valid signed format, `None` otherwise.
348    pub fn decode_signed(data: &[u8]) -> Option<(Self, [u8; SIGNATURE_SIZE])> {
349        if data.len() != CANNED_MESSAGE_SIGNED_SIZE {
350            return None;
351        }
352
353        // Decode the unsigned portion
354        let event = Self::decode(&data[..CANNED_MESSAGE_UNSIGNED_SIZE])?;
355
356        // Extract signature
357        let mut signature = [0u8; SIGNATURE_SIZE];
358        signature.copy_from_slice(&data[CANNED_MESSAGE_UNSIGNED_SIZE..]);
359
360        Some((event, signature))
361    }
362
363    /// Get the payload bytes that should be signed.
364    ///
365    /// Returns the 22-byte unsigned wire format suitable for signing.
366    /// Use this with your identity's sign() method:
367    ///
368    /// ```ignore
369    /// let payload = event.signable_payload();
370    /// let signature = identity.sign(&payload);
371    /// let wire = event.encode_signed(&signature);
372    /// ```
373    #[inline]
374    pub fn signable_payload(&self) -> heapless::Vec<u8, 22> {
375        self.encode()
376    }
377
378    /// Check if wire data is in signed format (86 bytes) vs unsigned (22 bytes).
379    ///
380    /// Useful for protocol negotiation and backward compatibility.
381    #[inline]
382    pub fn is_signed_format(data: &[u8]) -> bool {
383        data.len() == CANNED_MESSAGE_SIGNED_SIZE && data.first() == Some(&CANNED_MESSAGE_MARKER)
384    }
385
386    /// Check if wire data is in unsigned format (22 bytes).
387    #[inline]
388    pub fn is_unsigned_format(data: &[u8]) -> bool {
389        data.len() == CANNED_MESSAGE_UNSIGNED_SIZE && data.first() == Some(&CANNED_MESSAGE_MARKER)
390    }
391
392    /// Decode from either signed or unsigned format.
393    ///
394    /// Returns `(event, Some(signature))` for signed format,
395    /// or `(event, None)` for unsigned format.
396    ///
397    /// # Returns
398    /// `Some((event, optional_signature))` if valid format, `None` if malformed.
399    pub fn decode_auto(data: &[u8]) -> Option<(Self, Option<[u8; SIGNATURE_SIZE]>)> {
400        if Self::is_signed_format(data) {
401            Self::decode_signed(data).map(|(e, s)| (e, Some(s)))
402        } else if Self::is_unsigned_format(data) {
403            Self::decode(data).map(|e| (e, None))
404        } else {
405            None
406        }
407    }
408}
409
410/// A CannedMessage event with distributed ACK tracking (CRDT).
411///
412/// This extends [`CannedMessageEvent`] with a map of acknowledgments from other nodes.
413/// The ACK map uses OR-set semantics: once a node has acknowledged, it stays acknowledged.
414///
415/// # CRDT Merge Semantics
416///
417/// When merging two `CannedMessageAckEvent` instances:
418/// - If they represent the same event (same source + timestamp): merge ACK maps with OR semantics
419/// - If they represent different events: higher timestamp wins (LWW)
420///
421/// # Wire Format
422///
423/// ```text
424/// ┌──────┬──────────┬──────────┬──────────┬───────────┬──────┬──────────┬───────────────┐
425/// │ 0xAF │ msg_code │ src_node │ tgt_node │ timestamp │ seq  │ num_acks │ acks[N]...    │
426/// │ 1B   │ 1B       │ 4B       │ 4B       │ 8B        │ 4B   │ 2B       │ 12B each      │
427/// └──────┴──────────┴──────────┴──────────┴───────────┴──────┴──────────┴───────────────┘
428/// ```
429///
430/// Each ACK entry is 12 bytes: acker_node_id (4B LE) + ack_timestamp (8B LE).
431#[derive(Debug, Clone)]
432pub struct CannedMessageAckEvent {
433    /// The message type
434    pub message: CannedMessage,
435    /// Source node that sent this message
436    pub source_node: NodeId,
437    /// Target node (if directed, e.g., ACK to specific node)
438    pub target_node: Option<NodeId>,
439    /// Timestamp when message was sent
440    pub timestamp: u64,
441    /// Sequence number for deduplication
442    pub sequence: u32,
443    /// ACK tracking: acker_node_id -> ack_timestamp
444    acks: FnvIndexMap<NodeId, u64, MAX_CANNED_ACKS>,
445}
446
447impl CannedMessageAckEvent {
448    /// Create a new event. The source node auto-acknowledges.
449    pub fn new(
450        message: CannedMessage,
451        source_node: NodeId,
452        target_node: Option<NodeId>,
453        timestamp: u64,
454    ) -> Self {
455        let mut acks = FnvIndexMap::new();
456        // Source node implicitly acknowledges their own message
457        let _ = acks.insert(source_node, timestamp);
458
459        Self {
460            message,
461            source_node,
462            target_node,
463            timestamp,
464            sequence: 0,
465            acks,
466        }
467    }
468
469    /// Create with explicit sequence number.
470    pub fn with_sequence(
471        message: CannedMessage,
472        source_node: NodeId,
473        target_node: Option<NodeId>,
474        timestamp: u64,
475        sequence: u32,
476    ) -> Self {
477        let mut event = Self::new(message, source_node, target_node, timestamp);
478        event.sequence = sequence;
479        event
480    }
481
482    /// Record an ACK from a node.
483    ///
484    /// Returns `true` if this is a new ACK or updates an existing one to a later timestamp.
485    /// Returns `false` if the ACK was already recorded with an equal or later timestamp,
486    /// or if the ACK map is full.
487    pub fn ack(&mut self, node_id: NodeId, ack_timestamp: u64) -> bool {
488        if node_id == NodeId::NULL {
489            return false;
490        }
491
492        match self.acks.get(&node_id) {
493            Some(&existing_ts) if existing_ts >= ack_timestamp => false,
494            Some(_) => {
495                // Update existing entry with newer timestamp
496                let _ = self.acks.insert(node_id, ack_timestamp);
497                true
498            }
499            None => {
500                // New ACK entry
501                self.acks.insert(node_id, ack_timestamp).is_ok()
502            }
503        }
504    }
505
506    /// Check if a node has acknowledged this message.
507    pub fn has_acked(&self, node_id: NodeId) -> bool {
508        self.acks.contains_key(&node_id)
509    }
510
511    /// Get all node IDs that have acknowledged this message.
512    pub fn acked_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
513        self.acks.keys().copied()
514    }
515
516    /// Get the ACK timestamp for a specific node.
517    pub fn ack_timestamp(&self, node_id: NodeId) -> Option<u64> {
518        self.acks.get(&node_id).copied()
519    }
520
521    /// Number of ACKs received (including source's implicit ACK).
522    pub fn ack_count(&self) -> usize {
523        self.acks.len()
524    }
525
526    /// CRDT merge with another event.
527    ///
528    /// - Same event (source + timestamp match): merge ACK maps with OR semantics
529    /// - Different event: higher timestamp wins (LWW)
530    ///
531    /// Returns `true` if this event's state changed.
532    pub fn merge(&mut self, other: &Self) -> bool {
533        // Different message identity - higher timestamp wins
534        if self.source_node != other.source_node || self.timestamp != other.timestamp {
535            if other.timestamp > self.timestamp {
536                *self = other.clone();
537                return true;
538            }
539            return false;
540        }
541
542        // Same message - merge ACK maps with OR, keep latest timestamp per acker
543        let mut changed = false;
544        for (node_id, &other_ts) in other.acks.iter() {
545            match self.acks.get(node_id) {
546                Some(&existing_ts) if existing_ts >= other_ts => {}
547                _ => {
548                    if self.acks.insert(*node_id, other_ts).is_ok() {
549                        changed = true;
550                    }
551                }
552            }
553        }
554        changed
555    }
556
557    /// Encode to wire format.
558    ///
559    /// Returns a buffer containing the base event (22 bytes) plus ACK state.
560    /// Format: 24 base bytes + (12 bytes per ACK entry).
561    pub fn encode(&self) -> heapless::Vec<u8, 792> {
562        let mut buf = heapless::Vec::new();
563
564        // Marker
565        let _ = buf.push(CANNED_MESSAGE_MARKER);
566
567        // Message code
568        let _ = buf.push(self.message.as_u8());
569
570        // Source node (4 bytes LE)
571        for b in self.source_node.to_le_bytes() {
572            let _ = buf.push(b);
573        }
574
575        // Target node (4 bytes LE, 0 if None)
576        let target = self.target_node.unwrap_or(NodeId::NULL);
577        for b in target.to_le_bytes() {
578            let _ = buf.push(b);
579        }
580
581        // Timestamp (8 bytes LE)
582        for b in self.timestamp.to_le_bytes() {
583            let _ = buf.push(b);
584        }
585
586        // Sequence (4 bytes LE)
587        for b in self.sequence.to_le_bytes() {
588            let _ = buf.push(b);
589        }
590
591        // Number of ACKs (2 bytes LE)
592        let num_acks = self.acks.len() as u16;
593        for b in num_acks.to_le_bytes() {
594            let _ = buf.push(b);
595        }
596
597        // ACK entries (12 bytes each: 4B node_id + 8B timestamp)
598        for (node_id, &ack_ts) in self.acks.iter() {
599            for b in node_id.to_le_bytes() {
600                let _ = buf.push(b);
601            }
602            for b in ack_ts.to_le_bytes() {
603                let _ = buf.push(b);
604            }
605        }
606
607        buf
608    }
609
610    /// Decode from wire format.
611    ///
612    /// Handles both base format (22 bytes, no ACKs) and extended format (24+ bytes with ACKs).
613    /// Returns `None` if data is malformed.
614    pub fn decode(data: &[u8]) -> Option<Self> {
615        // Minimum: 22 bytes for base event (backward compat)
616        if data.len() < 22 {
617            return None;
618        }
619
620        if data[0] != CANNED_MESSAGE_MARKER {
621            return None;
622        }
623
624        let message = CannedMessage::from_u8(data[1])?;
625
626        let source_node = NodeId::from_le_bytes([data[2], data[3], data[4], data[5]]);
627
628        // Security check: reject NULL source
629        if source_node == NodeId::NULL {
630            return None;
631        }
632
633        let target_bytes = [data[6], data[7], data[8], data[9]];
634        let target_node = if target_bytes == [0, 0, 0, 0] {
635            None
636        } else {
637            Some(NodeId::from_le_bytes(target_bytes))
638        };
639
640        let timestamp = u64::from_le_bytes([
641            data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17],
642        ]);
643
644        let sequence = u32::from_le_bytes([data[18], data[19], data[20], data[21]]);
645
646        // Build base ACK map with source's implicit ACK
647        let mut acks = FnvIndexMap::new();
648        let _ = acks.insert(source_node, timestamp);
649
650        // Check for extended format with ACK state
651        if data.len() >= 24 {
652            let num_acks = u16::from_le_bytes([data[22], data[23]]);
653
654            // Security check: reject excessive ACK counts
655            if num_acks as usize > MAX_CANNED_ACKS {
656                return None;
657            }
658
659            let expected_len = 24 + (num_acks as usize * 12);
660            if data.len() < expected_len {
661                return None;
662            }
663
664            // Parse ACK entries
665            let mut offset = 24;
666            for _ in 0..num_acks {
667                let acker_node = NodeId::from_le_bytes([
668                    data[offset],
669                    data[offset + 1],
670                    data[offset + 2],
671                    data[offset + 3],
672                ]);
673                let ack_ts = u64::from_le_bytes([
674                    data[offset + 4],
675                    data[offset + 5],
676                    data[offset + 6],
677                    data[offset + 7],
678                    data[offset + 8],
679                    data[offset + 9],
680                    data[offset + 10],
681                    data[offset + 11],
682                ]);
683                offset += 12;
684
685                // Skip NULL node IDs (invalid entries)
686                if acker_node != NodeId::NULL {
687                    let _ = acks.insert(acker_node, ack_ts);
688                }
689            }
690        }
691
692        Some(Self {
693            message,
694            source_node,
695            target_node,
696            timestamp,
697            sequence,
698            acks,
699        })
700    }
701
702    /// Convert to base [`CannedMessageEvent`] (without ACK state).
703    pub fn as_event(&self) -> CannedMessageEvent {
704        CannedMessageEvent {
705            message: self.message,
706            source_node: self.source_node,
707            target_node: self.target_node,
708            timestamp: self.timestamp,
709            sequence: self.sequence,
710        }
711    }
712
713    /// Create from a base [`CannedMessageEvent`] (no ACKs except source).
714    pub fn from_event(event: CannedMessageEvent) -> Self {
715        let mut acks = FnvIndexMap::new();
716        let _ = acks.insert(event.source_node, event.timestamp);
717
718        Self {
719            message: event.message,
720            source_node: event.source_node,
721            target_node: event.target_node,
722            timestamp: event.timestamp,
723            sequence: event.sequence,
724            acks,
725        }
726    }
727}
728
729impl PartialEq for CannedMessageAckEvent {
730    fn eq(&self, other: &Self) -> bool {
731        self.message == other.message
732            && self.source_node == other.source_node
733            && self.target_node == other.target_node
734            && self.timestamp == other.timestamp
735            && self.sequence == other.sequence
736            && self.acks.len() == other.acks.len()
737            && self.acks.iter().all(|(k, v)| other.acks.get(k) == Some(v))
738    }
739}
740
741impl Eq for CannedMessageAckEvent {}
742
743/// Bounded storage for canned message events.
744///
745/// Uses LWW (Last-Writer-Wins) semantics per (source_node, message_type) pair.
746/// Only stores the latest event of each type from each peer.
747///
748/// Memory usage: approximately `MAX_ENTRIES * 24 bytes`.
749/// Default capacity of 256 entries ≈ 6KB.
750pub struct CannedMessageStore<const MAX_ENTRIES: usize = 256> {
751    /// Map from (source_node, message) to event
752    events: FnvIndexMap<(NodeId, CannedMessage), CannedMessageEvent, MAX_ENTRIES>,
753}
754
755impl<const MAX_ENTRIES: usize> Default for CannedMessageStore<MAX_ENTRIES> {
756    fn default() -> Self {
757        Self::new()
758    }
759}
760
761impl<const MAX_ENTRIES: usize> CannedMessageStore<MAX_ENTRIES> {
762    /// Create a new empty store.
763    pub const fn new() -> Self {
764        Self {
765            events: FnvIndexMap::new(),
766        }
767    }
768
769    /// Insert or update an event.
770    ///
771    /// Only updates if the new event is newer than the existing one.
772    /// Returns `true` if the event was inserted/updated.
773    pub fn insert(&mut self, event: CannedMessageEvent) -> bool {
774        let key = (event.source_node, event.message);
775
776        match self.events.get(&key) {
777            Some(existing) if !event.is_newer_than(existing) => false,
778            _ => {
779                // Insert, possibly evicting oldest if full
780                if self.events.len() >= MAX_ENTRIES {
781                    // Find and remove oldest entry
782                    if let Some(oldest_key) = self.find_oldest_key() {
783                        self.events.remove(&oldest_key);
784                    }
785                }
786                self.events.insert(key, event).is_ok()
787            }
788        }
789    }
790
791    /// Get the latest event of a specific type from a specific node.
792    pub fn get(&self, source: NodeId, message: CannedMessage) -> Option<&CannedMessageEvent> {
793        self.events.get(&(source, message))
794    }
795
796    /// Get all events from a specific node.
797    pub fn events_from(&self, source: NodeId) -> impl Iterator<Item = &CannedMessageEvent> {
798        self.events
799            .iter()
800            .filter(move |((src, _), _)| *src == source)
801            .map(|(_, event)| event)
802    }
803
804    /// Get all events of a specific type.
805    pub fn events_of_type(
806        &self,
807        message: CannedMessage,
808    ) -> impl Iterator<Item = &CannedMessageEvent> {
809        self.events
810            .iter()
811            .filter(move |((_, msg), _)| *msg == message)
812            .map(|(_, event)| event)
813    }
814
815    /// Get all emergency/alert events.
816    pub fn alerts(&self) -> impl Iterator<Item = &CannedMessageEvent> {
817        self.events
818            .iter()
819            .filter(|((_, msg), _)| msg.is_alert())
820            .map(|(_, event)| event)
821    }
822
823    /// Number of stored events.
824    pub fn len(&self) -> usize {
825        self.events.len()
826    }
827
828    /// Check if store is empty.
829    pub fn is_empty(&self) -> bool {
830        self.events.is_empty()
831    }
832
833    /// Clear all events.
834    pub fn clear(&mut self) {
835        self.events.clear();
836    }
837
838    /// Find the key of the oldest event (for eviction).
839    fn find_oldest_key(&self) -> Option<(NodeId, CannedMessage)> {
840        self.events
841            .iter()
842            .min_by_key(|(_, event)| (event.timestamp, event.sequence))
843            .map(|(key, _)| *key)
844    }
845}
846
847#[cfg(test)]
848mod tests {
849    use super::*;
850
851    #[test]
852    fn test_canned_message_roundtrip() {
853        for code in [
854            CannedMessage::Ack,
855            CannedMessage::Emergency,
856            CannedMessage::CheckIn,
857            CannedMessage::Custom,
858        ] {
859            let recovered = CannedMessage::from_u8(code.as_u8()).unwrap();
860            assert_eq!(code, recovered);
861        }
862    }
863
864    #[test]
865    fn test_event_encode_decode() {
866        let event = CannedMessageEvent::with_sequence(
867            CannedMessage::Ack,
868            NodeId::new(0x12345678),
869            Some(NodeId::new(0xDEADBEEF)),
870            1706234567000,
871            42,
872        );
873
874        let encoded = event.encode();
875        assert_eq!(encoded.len(), 22);
876        assert_eq!(encoded[0], CANNED_MESSAGE_MARKER);
877
878        let decoded = CannedMessageEvent::decode(&encoded).unwrap();
879        assert_eq!(decoded.message, event.message);
880        assert_eq!(decoded.source_node, event.source_node);
881        assert_eq!(decoded.target_node, event.target_node);
882        assert_eq!(decoded.timestamp, event.timestamp);
883        assert_eq!(decoded.sequence, event.sequence);
884    }
885
886    #[test]
887    fn test_event_no_target() {
888        let event = CannedMessageEvent::new(
889            CannedMessage::Emergency,
890            NodeId::new(0x12345678),
891            None,
892            1706234567000,
893        );
894
895        let encoded = event.encode();
896        let decoded = CannedMessageEvent::decode(&encoded).unwrap();
897        assert_eq!(decoded.target_node, None);
898    }
899
900    #[test]
901    fn test_store_lww() {
902        let mut store = CannedMessageStore::<16>::new();
903
904        let node = NodeId::new(0x123);
905
906        // Insert first event
907        let event1 = CannedMessageEvent::with_sequence(CannedMessage::Ack, node, None, 1000, 1);
908        assert!(store.insert(event1));
909
910        // Insert older event - should be rejected
911        let event_old = CannedMessageEvent::with_sequence(CannedMessage::Ack, node, None, 500, 1);
912        assert!(!store.insert(event_old));
913
914        // Insert newer event - should replace
915        let event2 = CannedMessageEvent::with_sequence(CannedMessage::Ack, node, None, 2000, 1);
916        assert!(store.insert(event2));
917
918        let stored = store.get(node, CannedMessage::Ack).unwrap();
919        assert_eq!(stored.timestamp, 2000);
920    }
921
922    #[test]
923    fn test_store_different_types() {
924        let mut store = CannedMessageStore::<16>::new();
925        let node = NodeId::new(0x123);
926
927        store.insert(CannedMessageEvent::new(
928            CannedMessage::Ack,
929            node,
930            None,
931            1000,
932        ));
933        store.insert(CannedMessageEvent::new(
934            CannedMessage::Emergency,
935            node,
936            None,
937            1000,
938        ));
939        store.insert(CannedMessageEvent::new(
940            CannedMessage::CheckIn,
941            node,
942            None,
943            1000,
944        ));
945
946        assert_eq!(store.len(), 3);
947        assert!(store.get(node, CannedMessage::Ack).is_some());
948        assert!(store.get(node, CannedMessage::Emergency).is_some());
949        assert!(store.get(node, CannedMessage::CheckIn).is_some());
950    }
951
952    // ===== CannedMessageAckEvent tests =====
953
954    #[test]
955    fn test_ack_event_creation() {
956        let source = NodeId::new(0x12345678);
957        let event = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1706234567000);
958
959        // Source should auto-ack
960        assert!(event.has_acked(source));
961        assert_eq!(event.ack_count(), 1);
962        assert_eq!(event.ack_timestamp(source), Some(1706234567000));
963    }
964
965    #[test]
966    fn test_ack_recording() {
967        let source = NodeId::new(0x111);
968        let acker = NodeId::new(0x222);
969
970        let mut event = CannedMessageAckEvent::new(CannedMessage::Emergency, source, None, 1000);
971
972        // New ACK returns true
973        assert!(event.ack(acker, 1500));
974        assert!(event.has_acked(acker));
975        assert_eq!(event.ack_count(), 2);
976
977        // Same ACK with same timestamp returns false
978        assert!(!event.ack(acker, 1500));
979
980        // Same ACK with older timestamp returns false
981        assert!(!event.ack(acker, 1400));
982
983        // Same ACK with newer timestamp returns true (updates)
984        assert!(event.ack(acker, 1600));
985        assert_eq!(event.ack_timestamp(acker), Some(1600));
986
987        // NULL node ID rejected
988        assert!(!event.ack(NodeId::NULL, 2000));
989    }
990
991    #[test]
992    fn test_ack_merge_same_event() {
993        let source = NodeId::new(0x111);
994        let node_a = NodeId::new(0x222);
995        let node_b = NodeId::new(0x333);
996
997        // Event 1: source + node_a acked
998        let mut event1 = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
999        event1.ack(node_a, 1100);
1000
1001        // Event 2 (same message): source + node_b acked
1002        let mut event2 = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
1003        event2.ack(node_b, 1200);
1004
1005        // Merge should combine ACKs (OR semantics)
1006        let changed = event1.merge(&event2);
1007        assert!(changed);
1008        assert!(event1.has_acked(source));
1009        assert!(event1.has_acked(node_a));
1010        assert!(event1.has_acked(node_b));
1011        assert_eq!(event1.ack_count(), 3);
1012
1013        // Merging again should not change
1014        assert!(!event1.merge(&event2));
1015    }
1016
1017    #[test]
1018    fn test_ack_merge_different_event() {
1019        let source = NodeId::new(0x111);
1020        let acker = NodeId::new(0x222);
1021
1022        // Older event with ACK
1023        let mut older = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
1024        older.ack(acker, 1100);
1025
1026        // Newer event without that ACK
1027        let newer = CannedMessageAckEvent::new(
1028            CannedMessage::Alert, // Different message type, same source
1029            source,
1030            None,
1031            2000,
1032        );
1033
1034        // Higher timestamp wins (LWW)
1035        let changed = older.merge(&newer);
1036        assert!(changed);
1037        assert_eq!(older.timestamp, 2000);
1038        assert_eq!(older.message, CannedMessage::Alert);
1039        // The old ACK is gone, only source's implicit ACK remains
1040        assert!(!older.has_acked(acker));
1041        assert_eq!(older.ack_count(), 1);
1042
1043        // Merging older into newer should not change
1044        let mut newer2 = CannedMessageAckEvent::new(CannedMessage::Alert, source, None, 2000);
1045        let older2 = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
1046        assert!(!newer2.merge(&older2));
1047    }
1048
1049    #[test]
1050    fn test_ack_encode_decode() {
1051        let source = NodeId::new(0x12345678);
1052        let target = NodeId::new(0xDEADBEEF);
1053        let acker1 = NodeId::new(0xAAAA);
1054        let acker2 = NodeId::new(0xBBBB);
1055
1056        let mut event = CannedMessageAckEvent::with_sequence(
1057            CannedMessage::Emergency,
1058            source,
1059            Some(target),
1060            1706234567000,
1061            42,
1062        );
1063        event.ack(acker1, 1706234568000);
1064        event.ack(acker2, 1706234569000);
1065
1066        let encoded = event.encode();
1067        // 24 base + 3 ACKs * 12 = 60 bytes
1068        assert_eq!(encoded.len(), 24 + 3 * 12);
1069        assert_eq!(encoded[0], CANNED_MESSAGE_MARKER);
1070
1071        let decoded = CannedMessageAckEvent::decode(&encoded).unwrap();
1072        assert_eq!(decoded.message, event.message);
1073        assert_eq!(decoded.source_node, event.source_node);
1074        assert_eq!(decoded.target_node, event.target_node);
1075        assert_eq!(decoded.timestamp, event.timestamp);
1076        assert_eq!(decoded.sequence, event.sequence);
1077        assert_eq!(decoded.ack_count(), 3);
1078        assert!(decoded.has_acked(source));
1079        assert!(decoded.has_acked(acker1));
1080        assert!(decoded.has_acked(acker2));
1081        assert_eq!(decoded.ack_timestamp(acker1), Some(1706234568000));
1082        assert_eq!(decoded.ack_timestamp(acker2), Some(1706234569000));
1083    }
1084
1085    #[test]
1086    fn test_ack_decode_base_event() {
1087        // Create a base CannedMessageEvent (22 bytes)
1088        let base_event = CannedMessageEvent::with_sequence(
1089            CannedMessage::CheckIn,
1090            NodeId::new(0x12345678),
1091            None,
1092            1706234567000,
1093            5,
1094        );
1095
1096        let encoded = base_event.encode();
1097        assert_eq!(encoded.len(), 22);
1098
1099        // CannedMessageAckEvent should decode it with implicit source ACK
1100        let decoded = CannedMessageAckEvent::decode(&encoded).unwrap();
1101        assert_eq!(decoded.message, base_event.message);
1102        assert_eq!(decoded.source_node, base_event.source_node);
1103        assert_eq!(decoded.timestamp, base_event.timestamp);
1104        assert_eq!(decoded.sequence, base_event.sequence);
1105        // Only source's implicit ACK
1106        assert_eq!(decoded.ack_count(), 1);
1107        assert!(decoded.has_acked(base_event.source_node));
1108    }
1109
1110    #[test]
1111    fn test_ack_max_limit() {
1112        let source = NodeId::new(0x111);
1113        let mut event = CannedMessageAckEvent::new(CannedMessage::Emergency, source, None, 1000);
1114
1115        // Fill up to MAX_CANNED_ACKS - 1 (since source already has one)
1116        for i in 1..MAX_CANNED_ACKS {
1117            let acker = NodeId::new(i as u32 + 1000);
1118            assert!(
1119                event.ack(acker, 2000 + i as u64),
1120                "ack {} should succeed",
1121                i
1122            );
1123        }
1124
1125        assert_eq!(event.ack_count(), MAX_CANNED_ACKS);
1126
1127        // Next ACK should fail (map full)
1128        let overflow_acker = NodeId::new(0xFFFFFF);
1129        assert!(!event.ack(overflow_acker, 9999));
1130        assert!(!event.has_acked(overflow_acker));
1131    }
1132
1133    #[test]
1134    fn test_ack_validation() {
1135        // Too short
1136        assert!(CannedMessageAckEvent::decode(&[0xAF]).is_none());
1137        assert!(CannedMessageAckEvent::decode(&[0xAF; 21]).is_none());
1138
1139        // Wrong marker
1140        let mut bad_marker = [0u8; 22];
1141        bad_marker[0] = 0x00;
1142        assert!(CannedMessageAckEvent::decode(&bad_marker).is_none());
1143
1144        // NULL source node
1145        let mut null_source = [0u8; 22];
1146        null_source[0] = CANNED_MESSAGE_MARKER;
1147        null_source[1] = CannedMessage::Ack.as_u8();
1148        // source bytes 2-5 are 0 (NULL)
1149        assert!(CannedMessageAckEvent::decode(&null_source).is_none());
1150
1151        // Invalid message code
1152        let mut bad_code = [0u8; 22];
1153        bad_code[0] = CANNED_MESSAGE_MARKER;
1154        bad_code[1] = 0xEE; // Invalid code
1155        bad_code[2] = 1; // Non-null source
1156        assert!(CannedMessageAckEvent::decode(&bad_code).is_none());
1157
1158        // Excessive num_acks
1159        let mut excessive_acks = [0u8; 24];
1160        excessive_acks[0] = CANNED_MESSAGE_MARKER;
1161        excessive_acks[1] = CannedMessage::Ack.as_u8();
1162        excessive_acks[2] = 1; // Non-null source
1163                               // num_acks = 0xFFFF (65535) at bytes 22-23
1164        excessive_acks[22] = 0xFF;
1165        excessive_acks[23] = 0xFF;
1166        assert!(CannedMessageAckEvent::decode(&excessive_acks).is_none());
1167
1168        // num_acks declares more than data provides
1169        let mut truncated = [0u8; 24];
1170        truncated[0] = CANNED_MESSAGE_MARKER;
1171        truncated[1] = CannedMessage::Ack.as_u8();
1172        truncated[2] = 1; // Non-null source
1173        truncated[22] = 5; // Claims 5 ACKs but no data follows
1174        truncated[23] = 0;
1175        assert!(CannedMessageAckEvent::decode(&truncated).is_none());
1176    }
1177
1178    #[test]
1179    fn test_ack_event_as_event_roundtrip() {
1180        let source = NodeId::new(0x12345678);
1181        let target = NodeId::new(0xDEADBEEF);
1182
1183        let ack_event = CannedMessageAckEvent::with_sequence(
1184            CannedMessage::NeedMedic,
1185            source,
1186            Some(target),
1187            1706234567000,
1188            99,
1189        );
1190
1191        let base = ack_event.as_event();
1192        assert_eq!(base.message, CannedMessage::NeedMedic);
1193        assert_eq!(base.source_node, source);
1194        assert_eq!(base.target_node, Some(target));
1195        assert_eq!(base.timestamp, 1706234567000);
1196        assert_eq!(base.sequence, 99);
1197
1198        // Convert back
1199        let restored = CannedMessageAckEvent::from_event(base);
1200        assert_eq!(restored.message, ack_event.message);
1201        assert_eq!(restored.source_node, ack_event.source_node);
1202        assert_eq!(restored.target_node, ack_event.target_node);
1203        assert_eq!(restored.timestamp, ack_event.timestamp);
1204        assert_eq!(restored.sequence, ack_event.sequence);
1205        // Only source ACK restored
1206        assert_eq!(restored.ack_count(), 1);
1207        assert!(restored.has_acked(source));
1208    }
1209
1210    #[test]
1211    fn test_ack_event_acked_nodes_iterator() {
1212        let source = NodeId::new(0x111);
1213        let mut event = CannedMessageAckEvent::new(CannedMessage::CheckIn, source, None, 1000);
1214        event.ack(NodeId::new(0x222), 1100);
1215        event.ack(NodeId::new(0x333), 1200);
1216
1217        let nodes: heapless::Vec<NodeId, 8> = event.acked_nodes().collect();
1218        assert_eq!(nodes.len(), 3);
1219        assert!(nodes.contains(&source));
1220        assert!(nodes.contains(&NodeId::new(0x222)));
1221        assert!(nodes.contains(&NodeId::new(0x333)));
1222    }
1223
1224    // ===== Signed CannedMessageEvent tests =====
1225
1226    #[test]
1227    fn test_signed_event_encode_decode() {
1228        let event = CannedMessageEvent::with_sequence(
1229            CannedMessage::Emergency,
1230            NodeId::new(0x12345678),
1231            Some(NodeId::new(0xDEADBEEF)),
1232            1706234567000,
1233            42,
1234        );
1235
1236        // Create a dummy signature (in real use, this comes from identity.sign())
1237        let signature = [0xABu8; 64];
1238
1239        let encoded = event.encode_signed(&signature);
1240        assert_eq!(encoded.len(), 86);
1241        assert_eq!(encoded[0], CANNED_MESSAGE_MARKER);
1242
1243        let (decoded, decoded_sig) = CannedMessageEvent::decode_signed(&encoded).unwrap();
1244        assert_eq!(decoded.message, event.message);
1245        assert_eq!(decoded.source_node, event.source_node);
1246        assert_eq!(decoded.target_node, event.target_node);
1247        assert_eq!(decoded.timestamp, event.timestamp);
1248        assert_eq!(decoded.sequence, event.sequence);
1249        assert_eq!(decoded_sig, signature);
1250    }
1251
1252    #[test]
1253    fn test_signed_format_detection() {
1254        let event = CannedMessageEvent::new(
1255            CannedMessage::CheckIn,
1256            NodeId::new(0x12345678),
1257            None,
1258            1706234567000,
1259        );
1260
1261        // Unsigned format (22 bytes)
1262        let unsigned = event.encode();
1263        assert!(CannedMessageEvent::is_unsigned_format(&unsigned));
1264        assert!(!CannedMessageEvent::is_signed_format(&unsigned));
1265
1266        // Signed format (86 bytes)
1267        let signature = [0x00u8; 64];
1268        let signed = event.encode_signed(&signature);
1269        assert!(CannedMessageEvent::is_signed_format(&signed));
1270        assert!(!CannedMessageEvent::is_unsigned_format(&signed));
1271
1272        // Invalid formats
1273        assert!(!CannedMessageEvent::is_signed_format(&[0xAF; 50]));
1274        assert!(!CannedMessageEvent::is_unsigned_format(&[0xAF; 50]));
1275        assert!(!CannedMessageEvent::is_signed_format(&[0x00; 86])); // Wrong marker
1276    }
1277
1278    #[test]
1279    fn test_decode_auto() {
1280        let event = CannedMessageEvent::with_sequence(
1281            CannedMessage::Alert,
1282            NodeId::new(0xAAAA),
1283            None,
1284            1000,
1285            5,
1286        );
1287
1288        // Test unsigned
1289        let unsigned = event.encode();
1290        let (decoded, sig_opt) = CannedMessageEvent::decode_auto(&unsigned).unwrap();
1291        assert_eq!(decoded.message, event.message);
1292        assert!(sig_opt.is_none());
1293
1294        // Test signed
1295        let signature = [0xFFu8; 64];
1296        let signed = event.encode_signed(&signature);
1297        let (decoded, sig_opt) = CannedMessageEvent::decode_auto(&signed).unwrap();
1298        assert_eq!(decoded.message, event.message);
1299        assert_eq!(sig_opt, Some(signature));
1300
1301        // Test invalid
1302        assert!(CannedMessageEvent::decode_auto(&[0xAF; 50]).is_none());
1303    }
1304
1305    #[test]
1306    fn test_signable_payload() {
1307        let event = CannedMessageEvent::new(
1308            CannedMessage::Moving,
1309            NodeId::new(0x12345678),
1310            None,
1311            1706234567000,
1312        );
1313
1314        let payload = event.signable_payload();
1315        let encoded = event.encode();
1316
1317        // Should be identical to unsigned encode
1318        assert_eq!(payload.as_slice(), encoded.as_slice());
1319        assert_eq!(payload.len(), 22);
1320    }
1321
1322    #[test]
1323    fn test_signed_decode_wrong_size() {
1324        // Too short
1325        assert!(CannedMessageEvent::decode_signed(&[0xAF; 85]).is_none());
1326
1327        // Too long
1328        assert!(CannedMessageEvent::decode_signed(&[0xAF; 87]).is_none());
1329
1330        // Wrong marker
1331        let mut bad = [0u8; 86];
1332        bad[0] = 0x00;
1333        assert!(CannedMessageEvent::decode_signed(&bad).is_none());
1334    }
1335
1336    #[test]
1337    fn test_wire_size_constants() {
1338        use crate::wire::{
1339            CANNED_MESSAGE_SIGNED_SIZE, CANNED_MESSAGE_UNSIGNED_SIZE, SIGNATURE_SIZE,
1340        };
1341
1342        assert_eq!(CANNED_MESSAGE_UNSIGNED_SIZE, 22);
1343        assert_eq!(SIGNATURE_SIZE, 64);
1344        assert_eq!(CANNED_MESSAGE_SIGNED_SIZE, 86);
1345        assert_eq!(
1346            CANNED_MESSAGE_SIGNED_SIZE,
1347            CANNED_MESSAGE_UNSIGNED_SIZE + SIGNATURE_SIZE
1348        );
1349    }
1350}