hive_btle/sync/
crdt.rs

1// Copyright (c) 2025-2026 (r)evolve - Revolve Team LLC
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! CRDT (Conflict-free Replicated Data Types) for HIVE-Lite
17//!
18//! Provides lightweight CRDT implementations optimized for BLE sync:
19//! - LWW-Register: Last-Writer-Wins for single values
20//! - G-Counter: Grow-only counter for metrics
21//!
22//! These are designed for minimal memory footprint and efficient
23//! serialization over constrained BLE connections.
24
25#[cfg(not(feature = "std"))]
26use alloc::{collections::BTreeMap, string::String, string::ToString, vec, vec::Vec};
27#[cfg(feature = "std")]
28use std::collections::BTreeMap;
29
30use crate::NodeId;
31
32// ============================================================================
33// Security Validation Constants
34// ============================================================================
35// These constants define bounds for validating decoded data to prevent
36// spoofing, injection attacks, and garbage data from corrupting state.
37
38/// Minimum valid timestamp: 2020-01-01 00:00:00 UTC (milliseconds)
39/// Any timestamp before this is rejected as invalid/spoofed.
40pub const MIN_VALID_TIMESTAMP: u64 = 1577836800000;
41
42/// Maximum valid battery percentage (100%)
43pub const MAX_BATTERY_PERCENT: u8 = 100;
44
45/// Minimum valid heart rate (BPM) - anything below is likely garbage
46pub const MIN_HEART_RATE: u8 = 20;
47
48/// Maximum valid heart rate (BPM) - anything above is likely garbage
49pub const MAX_HEART_RATE: u8 = 250;
50
51/// Maximum number of ACK entries in an emergency event (prevents DoS)
52pub const MAX_EMERGENCY_ACKS: usize = 256;
53
54/// Maximum number of counter entries (prevents DoS)
55pub const MAX_COUNTER_ENTRIES: usize = 256;
56
57/// Timestamp for CRDT operations (milliseconds since epoch or monotonic)
58pub type Timestamp = u64;
59
60/// A Last-Writer-Wins Register
61///
62/// Stores a single value where concurrent writes are resolved by timestamp.
63/// Higher timestamp wins. In case of tie, higher node ID wins.
64#[derive(Debug, Clone, PartialEq)]
65pub struct LwwRegister<T: Clone> {
66    /// Current value
67    value: T,
68    /// Timestamp when value was set
69    timestamp: Timestamp,
70    /// Node that set the value
71    node_id: NodeId,
72}
73
74impl<T: Clone + Default> Default for LwwRegister<T> {
75    fn default() -> Self {
76        Self {
77            value: T::default(),
78            timestamp: 0,
79            node_id: NodeId::default(),
80        }
81    }
82}
83
84impl<T: Clone> LwwRegister<T> {
85    /// Create a new register with an initial value
86    pub fn new(value: T, timestamp: Timestamp, node_id: NodeId) -> Self {
87        Self {
88            value,
89            timestamp,
90            node_id,
91        }
92    }
93
94    /// Get the current value
95    pub fn get(&self) -> &T {
96        &self.value
97    }
98
99    /// Get the timestamp
100    pub fn timestamp(&self) -> Timestamp {
101        self.timestamp
102    }
103
104    /// Get the node that set the value
105    pub fn node_id(&self) -> &NodeId {
106        &self.node_id
107    }
108
109    /// Set a new value if it has a higher timestamp
110    ///
111    /// Returns true if the value was updated
112    pub fn set(&mut self, value: T, timestamp: Timestamp, node_id: NodeId) -> bool {
113        if self.should_update(timestamp, &node_id) {
114            self.value = value;
115            self.timestamp = timestamp;
116            self.node_id = node_id;
117            true
118        } else {
119            false
120        }
121    }
122
123    /// Merge with another register (LWW semantics)
124    ///
125    /// Returns true if our value was updated
126    pub fn merge(&mut self, other: &LwwRegister<T>) -> bool {
127        if self.should_update(other.timestamp, &other.node_id) {
128            self.value = other.value.clone();
129            self.timestamp = other.timestamp;
130            self.node_id = other.node_id;
131            true
132        } else {
133            false
134        }
135    }
136
137    /// Check if we should update based on timestamp/node_id
138    fn should_update(&self, timestamp: Timestamp, node_id: &NodeId) -> bool {
139        timestamp > self.timestamp
140            || (timestamp == self.timestamp && node_id.as_u32() > self.node_id.as_u32())
141    }
142}
143
144/// A Grow-only Counter (G-Counter)
145///
146/// Each node maintains its own count, total is the sum of all counts.
147/// Only supports increment operations.
148#[derive(Debug, Clone, Default)]
149pub struct GCounter {
150    /// Per-node counts
151    counts: BTreeMap<u32, u64>,
152}
153
154impl GCounter {
155    /// Create a new empty counter
156    pub fn new() -> Self {
157        Self {
158            counts: BTreeMap::new(),
159        }
160    }
161
162    /// Get the total count
163    pub fn value(&self) -> u64 {
164        self.counts.values().sum()
165    }
166
167    /// Increment the counter for a node
168    pub fn increment(&mut self, node_id: &NodeId, amount: u64) {
169        let count = self.counts.entry(node_id.as_u32()).or_insert(0);
170        *count = count.saturating_add(amount);
171    }
172
173    /// Get the count for a specific node
174    pub fn node_count(&self, node_id: &NodeId) -> u64 {
175        self.counts.get(&node_id.as_u32()).copied().unwrap_or(0)
176    }
177
178    /// Merge with another counter
179    ///
180    /// Takes the max of each node's count
181    pub fn merge(&mut self, other: &GCounter) {
182        for (&node_id, &count) in &other.counts {
183            let our_count = self.counts.entry(node_id).or_insert(0);
184            *our_count = (*our_count).max(count);
185        }
186    }
187
188    /// Get the number of nodes that have contributed
189    pub fn node_count_total(&self) -> usize {
190        self.counts.len()
191    }
192
193    /// Get all entries as (node_id, count) pairs
194    ///
195    /// Returns an iterator over all nodes and their counts.
196    /// Used for building delta documents.
197    pub fn entries(&self) -> impl Iterator<Item = (u32, u64)> + '_ {
198        self.counts.iter().map(|(&k, &v)| (k, v))
199    }
200
201    /// Encode to bytes for transmission
202    pub fn encode(&self) -> Vec<u8> {
203        let mut buf = Vec::with_capacity(4 + self.counts.len() * 12);
204        // Number of entries
205        buf.extend_from_slice(&(self.counts.len() as u32).to_le_bytes());
206        // Each entry: node_id (4 bytes) + count (8 bytes)
207        for (&node_id, &count) in &self.counts {
208            buf.extend_from_slice(&node_id.to_le_bytes());
209            buf.extend_from_slice(&count.to_le_bytes());
210        }
211        buf
212    }
213
214    /// Decode from bytes with validation
215    ///
216    /// # Security
217    /// Limits number of entries to prevent DoS via huge allocations.
218    /// Skips zero node IDs.
219    pub fn decode(data: &[u8]) -> Option<Self> {
220        if data.len() < 4 {
221            return None;
222        }
223        let num_entries = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
224
225        // Security: Limit entries to prevent DoS via huge allocations
226        if num_entries > MAX_COUNTER_ENTRIES {
227            return None;
228        }
229
230        if data.len() < 4 + num_entries * 12 {
231            return None;
232        }
233
234        let mut counts = BTreeMap::new();
235        let mut offset = 4;
236        for _ in 0..num_entries {
237            let node_id = u32::from_le_bytes([
238                data[offset],
239                data[offset + 1],
240                data[offset + 2],
241                data[offset + 3],
242            ]);
243            let count = u64::from_le_bytes([
244                data[offset + 4],
245                data[offset + 5],
246                data[offset + 6],
247                data[offset + 7],
248                data[offset + 8],
249                data[offset + 9],
250                data[offset + 10],
251                data[offset + 11],
252            ]);
253            // Security: Skip zero node IDs
254            if node_id != 0 {
255                counts.insert(node_id, count);
256            }
257            offset += 12;
258        }
259
260        Some(Self { counts })
261    }
262}
263
264/// Position data with LWW semantics
265#[derive(Debug, Clone, Default, PartialEq)]
266pub struct Position {
267    /// Latitude in degrees
268    pub latitude: f32,
269    /// Longitude in degrees
270    pub longitude: f32,
271    /// Altitude in meters (optional)
272    pub altitude: Option<f32>,
273    /// Accuracy in meters (optional)
274    pub accuracy: Option<f32>,
275}
276
277impl Position {
278    /// Create a new position
279    pub fn new(latitude: f32, longitude: f32) -> Self {
280        Self {
281            latitude,
282            longitude,
283            altitude: None,
284            accuracy: None,
285        }
286    }
287
288    /// Create with altitude
289    pub fn with_altitude(mut self, altitude: f32) -> Self {
290        self.altitude = Some(altitude);
291        self
292    }
293
294    /// Create with accuracy
295    pub fn with_accuracy(mut self, accuracy: f32) -> Self {
296        self.accuracy = Some(accuracy);
297        self
298    }
299
300    /// Encode to bytes (12-20 bytes)
301    pub fn encode(&self) -> Vec<u8> {
302        let mut buf = Vec::with_capacity(20);
303        buf.extend_from_slice(&self.latitude.to_le_bytes());
304        buf.extend_from_slice(&self.longitude.to_le_bytes());
305
306        // Flags byte: bit 0 = has altitude, bit 1 = has accuracy
307        let mut flags = 0u8;
308        if self.altitude.is_some() {
309            flags |= 0x01;
310        }
311        if self.accuracy.is_some() {
312            flags |= 0x02;
313        }
314        buf.push(flags);
315
316        if let Some(alt) = self.altitude {
317            buf.extend_from_slice(&alt.to_le_bytes());
318        }
319        if let Some(acc) = self.accuracy {
320            buf.extend_from_slice(&acc.to_le_bytes());
321        }
322        buf
323    }
324
325    /// Decode from bytes with validation
326    ///
327    /// # Security
328    /// Validates that latitude/longitude are within valid ranges and not NaN/Inf.
329    /// Rejects garbage data that could indicate spoofing or corruption.
330    pub fn decode(data: &[u8]) -> Option<Self> {
331        if data.len() < 9 {
332            return None;
333        }
334
335        let latitude = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
336        let longitude = f32::from_le_bytes([data[4], data[5], data[6], data[7]]);
337        let flags = data[8];
338
339        // Security: Reject NaN, Inf, or out-of-range coordinates
340        if !latitude.is_finite() || !longitude.is_finite() {
341            return None;
342        }
343        if !(-90.0..=90.0).contains(&latitude) {
344            return None;
345        }
346        if !(-180.0..=180.0).contains(&longitude) {
347            return None;
348        }
349
350        let mut pos = Self::new(latitude, longitude);
351        let mut offset = 9;
352
353        if flags & 0x01 != 0 {
354            if data.len() < offset + 4 {
355                return None;
356            }
357            let alt = f32::from_le_bytes([
358                data[offset],
359                data[offset + 1],
360                data[offset + 2],
361                data[offset + 3],
362            ]);
363            // Security: Reject NaN/Inf altitude, allow wide range for valid Earth elevations
364            if !alt.is_finite() || !(-1000.0..=100000.0).contains(&alt) {
365                return None;
366            }
367            pos.altitude = Some(alt);
368            offset += 4;
369        }
370
371        if flags & 0x02 != 0 {
372            if data.len() < offset + 4 {
373                return None;
374            }
375            let acc = f32::from_le_bytes([
376                data[offset],
377                data[offset + 1],
378                data[offset + 2],
379                data[offset + 3],
380            ]);
381            // Security: Reject NaN/Inf/negative accuracy
382            if !acc.is_finite() || acc < 0.0 {
383                return None;
384            }
385            pos.accuracy = Some(acc);
386        }
387
388        Some(pos)
389    }
390}
391
392/// Health status data with LWW semantics
393#[derive(Debug, Clone, Default, PartialEq)]
394pub struct HealthStatus {
395    /// Battery percentage (0-100)
396    pub battery_percent: u8,
397    /// Heart rate BPM (optional)
398    pub heart_rate: Option<u8>,
399    /// Activity level (0=still, 1=walking, 2=running, 3=vehicle)
400    pub activity: u8,
401    /// Alert status flags
402    pub alerts: u8,
403}
404
405impl HealthStatus {
406    /// Alert flag: Man down
407    pub const ALERT_MAN_DOWN: u8 = 0x01;
408    /// Alert flag: Low battery
409    pub const ALERT_LOW_BATTERY: u8 = 0x02;
410    /// Alert flag: Out of range
411    pub const ALERT_OUT_OF_RANGE: u8 = 0x04;
412    /// Alert flag: Custom alert 1
413    pub const ALERT_CUSTOM_1: u8 = 0x08;
414
415    /// Create a new health status
416    pub fn new(battery_percent: u8) -> Self {
417        Self {
418            battery_percent,
419            heart_rate: None,
420            activity: 0,
421            alerts: 0,
422        }
423    }
424
425    /// Set heart rate
426    pub fn with_heart_rate(mut self, hr: u8) -> Self {
427        self.heart_rate = Some(hr);
428        self
429    }
430
431    /// Set activity level
432    pub fn with_activity(mut self, activity: u8) -> Self {
433        self.activity = activity;
434        self
435    }
436
437    /// Set alert flag
438    pub fn set_alert(&mut self, flag: u8) {
439        self.alerts |= flag;
440    }
441
442    /// Clear alert flag
443    pub fn clear_alert(&mut self, flag: u8) {
444        self.alerts &= !flag;
445    }
446
447    /// Check if alert is set
448    pub fn has_alert(&self, flag: u8) -> bool {
449        self.alerts & flag != 0
450    }
451
452    /// Encode to bytes (3-4 bytes)
453    pub fn encode(&self) -> Vec<u8> {
454        vec![
455            self.battery_percent,
456            self.activity,
457            self.alerts,
458            // Heart rate: 0 means not present, otherwise value
459            self.heart_rate.unwrap_or(0),
460        ]
461    }
462
463    /// Decode from bytes with validation
464    ///
465    /// # Security
466    /// Validates battery percentage and heart rate are within physiological bounds.
467    /// Rejects garbage data that could indicate spoofing or corruption.
468    pub fn decode(data: &[u8]) -> Option<Self> {
469        if data.len() < 4 {
470            return None;
471        }
472
473        let battery_percent = data[0];
474        let activity = data[1];
475        let alerts = data[2];
476        let heart_rate_raw = data[3];
477
478        // Security: Reject battery percentage > 100%
479        if battery_percent > MAX_BATTERY_PERCENT {
480            return None;
481        }
482
483        // Security: Validate activity is a known value (0-3)
484        if activity > 3 {
485            return None;
486        }
487
488        let mut status = Self::new(battery_percent);
489        status.activity = activity;
490        status.alerts = alerts;
491
492        // Security: Validate heart rate is in physiological range if present
493        if heart_rate_raw > 0 {
494            if !(MIN_HEART_RATE..=MAX_HEART_RATE).contains(&heart_rate_raw) {
495                return None;
496            }
497            status.heart_rate = Some(heart_rate_raw);
498        }
499
500        Some(status)
501    }
502}
503
504// ============================================================================
505// Peripheral (Sub-node) Types - for soldier-attached devices like M5Stack Core2
506// ============================================================================
507
508/// Type of peripheral device
509#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
510#[repr(u8)]
511pub enum PeripheralType {
512    /// Unknown/unspecified
513    #[default]
514    Unknown = 0,
515    /// Soldier-worn sensor (e.g., M5Stack Core2)
516    SoldierSensor = 1,
517    /// Fixed/stationary sensor
518    FixedSensor = 2,
519    /// Mesh relay only (no sensors)
520    Relay = 3,
521}
522
523impl PeripheralType {
524    /// Convert from u8 value
525    pub fn from_u8(v: u8) -> Self {
526        match v {
527            1 => Self::SoldierSensor,
528            2 => Self::FixedSensor,
529            3 => Self::Relay,
530            _ => Self::Unknown,
531        }
532    }
533}
534
535/// Event types that a peripheral can emit (e.g., from tap input)
536#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
537#[repr(u8)]
538pub enum EventType {
539    /// No event / cleared
540    #[default]
541    None = 0,
542    /// "I'm OK" ping
543    Ping = 1,
544    /// Request assistance
545    NeedAssist = 2,
546    /// Emergency / SOS
547    Emergency = 3,
548    /// Moving / in transit
549    Moving = 4,
550    /// In position / stationary
551    InPosition = 5,
552    /// Acknowledged / copy
553    Ack = 6,
554}
555
556impl EventType {
557    /// Convert from u8 value
558    pub fn from_u8(v: u8) -> Self {
559        match v {
560            1 => Self::Ping,
561            2 => Self::NeedAssist,
562            3 => Self::Emergency,
563            4 => Self::Moving,
564            5 => Self::InPosition,
565            6 => Self::Ack,
566            _ => Self::None,
567        }
568    }
569
570    /// Human-readable label for display
571    pub fn label(&self) -> &'static str {
572        match self {
573            Self::None => "",
574            Self::Ping => "PING",
575            Self::NeedAssist => "NEED ASSIST",
576            Self::Emergency => "EMERGENCY",
577            Self::Moving => "MOVING",
578            Self::InPosition => "IN POSITION",
579            Self::Ack => "ACK",
580        }
581    }
582}
583
584/// An event emitted by a peripheral (e.g., tap on Core2)
585#[derive(Debug, Clone, Default, PartialEq)]
586pub struct PeripheralEvent {
587    /// Type of event
588    pub event_type: EventType,
589    /// Timestamp when event occurred (ms since epoch or boot)
590    pub timestamp: u64,
591}
592
593impl PeripheralEvent {
594    /// Create a new peripheral event
595    pub fn new(event_type: EventType, timestamp: u64) -> Self {
596        Self {
597            event_type,
598            timestamp,
599        }
600    }
601
602    /// Encode to bytes (9 bytes)
603    pub fn encode(&self) -> Vec<u8> {
604        let mut buf = Vec::with_capacity(9);
605        buf.push(self.event_type as u8);
606        buf.extend_from_slice(&self.timestamp.to_le_bytes());
607        buf
608    }
609
610    /// Decode from bytes with validation
611    ///
612    /// # Security
613    /// Validates timestamp is within reasonable bounds.
614    /// Rejects garbage data that could indicate spoofing or corruption.
615    pub fn decode(data: &[u8]) -> Option<Self> {
616        if data.len() < 9 {
617            return None;
618        }
619
620        let event_type = EventType::from_u8(data[0]);
621        let timestamp = u64::from_le_bytes([
622            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
623        ]);
624
625        // Security: Reject events with timestamps before 2020
626        // Note: timestamp could be 0 for "no event" scenarios, so we allow that
627        if timestamp != 0 && timestamp < MIN_VALID_TIMESTAMP {
628            return None;
629        }
630
631        Some(Self {
632            event_type,
633            timestamp,
634        })
635    }
636}
637
638/// An emergency event with acknowledgment tracking (CRDT)
639///
640/// Represents a single emergency incident with distributed ACK tracking.
641/// Each node in the mesh can acknowledge the emergency, and this state
642/// is replicated across all nodes using CRDT semantics.
643///
644/// ## CRDT Semantics
645///
646/// - **Identity**: Events are uniquely identified by (source_node, timestamp)
647/// - **Merge for same event**: ACK maps merge with OR (once acked, stays acked)
648/// - **Merge for different events**: Higher timestamp wins (newer emergency replaces older)
649/// - **Monotonic**: ACK state only moves from false → true, never back
650///
651/// ## Wire Format
652///
653/// ```text
654/// source_node: 4 bytes (LE)
655/// timestamp:   8 bytes (LE)
656/// num_acks:    4 bytes (LE)
657/// acks[N]:
658///   node_id:   4 bytes (LE)
659///   acked:     1 byte (0 or 1)
660/// ```
661#[derive(Debug, Clone, PartialEq, Default)]
662pub struct EmergencyEvent {
663    /// Node that triggered the emergency
664    source_node: u32,
665    /// Timestamp when emergency was triggered (for uniqueness)
666    timestamp: u64,
667    /// ACK status for each known peer: node_id -> has_acked
668    acks: BTreeMap<u32, bool>,
669}
670
671impl EmergencyEvent {
672    /// Create a new emergency event
673    ///
674    /// # Arguments
675    /// * `source_node` - Node ID that triggered the emergency
676    /// * `timestamp` - When the emergency was triggered
677    /// * `known_peers` - List of peer node IDs to track for ACKs
678    ///
679    /// The source node is automatically marked as acknowledged.
680    pub fn new(source_node: u32, timestamp: u64, known_peers: &[u32]) -> Self {
681        let mut acks = BTreeMap::new();
682
683        // Source node implicitly ACKs their own emergency
684        acks.insert(source_node, true);
685
686        // All other known peers start as not-acked
687        for &peer_id in known_peers {
688            if peer_id != source_node {
689                acks.entry(peer_id).or_insert(false);
690            }
691        }
692
693        Self {
694            source_node,
695            timestamp,
696            acks,
697        }
698    }
699
700    /// Get the source node that triggered the emergency
701    pub fn source_node(&self) -> u32 {
702        self.source_node
703    }
704
705    /// Get the timestamp when the emergency was triggered
706    pub fn timestamp(&self) -> u64 {
707        self.timestamp
708    }
709
710    /// Check if a specific node has acknowledged
711    pub fn has_acked(&self, node_id: u32) -> bool {
712        self.acks.get(&node_id).copied().unwrap_or(false)
713    }
714
715    /// Record an acknowledgment from a node
716    ///
717    /// Returns true if this was a new ACK (state changed)
718    pub fn ack(&mut self, node_id: u32) -> bool {
719        let entry = self.acks.entry(node_id).or_insert(false);
720        if !*entry {
721            *entry = true;
722            true
723        } else {
724            false
725        }
726    }
727
728    /// Add a peer to track (if not already present)
729    ///
730    /// New peers start as not-acked. This is useful when discovering
731    /// new peers after the emergency was created.
732    pub fn add_peer(&mut self, node_id: u32) {
733        self.acks.entry(node_id).or_insert(false);
734    }
735
736    /// Get list of nodes that have acknowledged
737    pub fn acked_nodes(&self) -> Vec<u32> {
738        self.acks
739            .iter()
740            .filter(|(_, &acked)| acked)
741            .map(|(&node_id, _)| node_id)
742            .collect()
743    }
744
745    /// Get list of nodes that have NOT acknowledged
746    pub fn pending_nodes(&self) -> Vec<u32> {
747        self.acks
748            .iter()
749            .filter(|(_, &acked)| !acked)
750            .map(|(&node_id, _)| node_id)
751            .collect()
752    }
753
754    /// Get all tracked node IDs (both acked and pending)
755    pub fn all_nodes(&self) -> Vec<u32> {
756        self.acks.keys().copied().collect()
757    }
758
759    /// Check if all tracked nodes have acknowledged
760    pub fn all_acked(&self) -> bool {
761        !self.acks.is_empty() && self.acks.values().all(|&acked| acked)
762    }
763
764    /// Get the total number of tracked nodes
765    pub fn peer_count(&self) -> usize {
766        self.acks.len()
767    }
768
769    /// Get the number of nodes that have acknowledged
770    pub fn ack_count(&self) -> usize {
771        self.acks.values().filter(|&&acked| acked).count()
772    }
773
774    /// Merge with another emergency event (CRDT semantics)
775    ///
776    /// # Returns
777    /// `true` if our state changed
778    ///
779    /// # Semantics
780    /// - Same event (source_node, timestamp): merge ACK maps with OR
781    /// - Different event: take the one with higher timestamp
782    pub fn merge(&mut self, other: &EmergencyEvent) -> bool {
783        // Different emergency - take newer one
784        if self.source_node != other.source_node || self.timestamp != other.timestamp {
785            if other.timestamp > self.timestamp {
786                *self = other.clone();
787                return true;
788            }
789            return false;
790        }
791
792        // Same emergency - merge ACK maps with OR
793        let mut changed = false;
794        for (&node_id, &other_acked) in &other.acks {
795            let entry = self.acks.entry(node_id).or_insert(false);
796            if other_acked && !*entry {
797                *entry = true;
798                changed = true;
799            }
800        }
801        changed
802    }
803
804    /// Encode to bytes for transmission
805    ///
806    /// Format: source_node(4) + timestamp(8) + num_acks(4) + acks[N](5 each)
807    pub fn encode(&self) -> Vec<u8> {
808        let mut buf = Vec::with_capacity(16 + self.acks.len() * 5);
809
810        buf.extend_from_slice(&self.source_node.to_le_bytes());
811        buf.extend_from_slice(&self.timestamp.to_le_bytes());
812        buf.extend_from_slice(&(self.acks.len() as u32).to_le_bytes());
813
814        for (&node_id, &acked) in &self.acks {
815            buf.extend_from_slice(&node_id.to_le_bytes());
816            buf.push(if acked { 1 } else { 0 });
817        }
818
819        buf
820    }
821
822    /// Decode from bytes with validation
823    ///
824    /// # Security
825    /// Validates source_node, timestamp, and limits ACK entries to prevent DoS.
826    /// Rejects garbage data that could indicate spoofing or corruption.
827    pub fn decode(data: &[u8]) -> Option<Self> {
828        if data.len() < 16 {
829            return None;
830        }
831
832        let source_node = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
833        let timestamp = u64::from_le_bytes([
834            data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
835        ]);
836        let num_acks = u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
837
838        // Security: Reject zero source_node (invalid)
839        if source_node == 0 {
840            return None;
841        }
842
843        // Security: Reject timestamps before 2020
844        if timestamp < MIN_VALID_TIMESTAMP {
845            return None;
846        }
847
848        // Security: Limit ACK entries to prevent DoS via huge allocations
849        if num_acks > MAX_EMERGENCY_ACKS {
850            return None;
851        }
852
853        if data.len() < 16 + num_acks * 5 {
854            return None;
855        }
856
857        let mut acks = BTreeMap::new();
858        let mut offset = 16;
859        for _ in 0..num_acks {
860            let node_id = u32::from_le_bytes([
861                data[offset],
862                data[offset + 1],
863                data[offset + 2],
864                data[offset + 3],
865            ]);
866            // Security: Skip zero node_ids in ACK list
867            if node_id != 0 {
868                let acked = data[offset + 4] != 0;
869                acks.insert(node_id, acked);
870            }
871            offset += 5;
872        }
873
874        Some(Self {
875            source_node,
876            timestamp,
877            acks,
878        })
879    }
880}
881
882/// A peripheral device attached to a Node (soldier)
883///
884/// Peripherals are sub-tier devices that augment a soldier's capabilities
885/// with sensors and input (e.g., M5Stack Core2 wearable).
886#[derive(Debug, Clone, Default)]
887pub struct Peripheral {
888    /// Unique peripheral ID (derived from device MAC or similar)
889    pub id: u32,
890    /// Parent Node ID this peripheral is attached to (0 if not paired)
891    pub parent_node: u32,
892    /// Type of peripheral
893    pub peripheral_type: PeripheralType,
894    /// Callsign/name (inherited from parent or configured)
895    pub callsign: [u8; 12],
896    /// Current health status
897    pub health: HealthStatus,
898    /// Most recent event (if any)
899    pub last_event: Option<PeripheralEvent>,
900    /// Last update timestamp
901    pub timestamp: u64,
902}
903
904impl Peripheral {
905    /// Create a new peripheral
906    pub fn new(id: u32, peripheral_type: PeripheralType) -> Self {
907        Self {
908            id,
909            parent_node: 0,
910            peripheral_type,
911            callsign: [0u8; 12],
912            health: HealthStatus::default(),
913            last_event: None,
914            timestamp: 0,
915        }
916    }
917
918    /// Set the callsign (truncated to 12 bytes)
919    pub fn with_callsign(mut self, callsign: &str) -> Self {
920        let bytes = callsign.as_bytes();
921        let len = bytes.len().min(12);
922        self.callsign[..len].copy_from_slice(&bytes[..len]);
923        self
924    }
925
926    /// Get callsign as string
927    pub fn callsign_str(&self) -> &str {
928        let len = self.callsign.iter().position(|&b| b == 0).unwrap_or(12);
929        core::str::from_utf8(&self.callsign[..len]).unwrap_or("")
930    }
931
932    /// Set parent node
933    pub fn with_parent(mut self, parent_node: u32) -> Self {
934        self.parent_node = parent_node;
935        self
936    }
937
938    /// Record an event
939    pub fn set_event(&mut self, event_type: EventType, timestamp: u64) {
940        self.last_event = Some(PeripheralEvent::new(event_type, timestamp));
941        self.timestamp = timestamp;
942    }
943
944    /// Clear the last event
945    pub fn clear_event(&mut self) {
946        self.last_event = None;
947    }
948
949    /// Encode to bytes for BLE transmission
950    /// Format: [id:4][parent:4][type:1][callsign:12][health:4][has_event:1][event:9?][timestamp:8]
951    /// Size: 34 bytes without event, 43 bytes with event
952    pub fn encode(&self) -> Vec<u8> {
953        let mut buf = Vec::with_capacity(43);
954        buf.extend_from_slice(&self.id.to_le_bytes());
955        buf.extend_from_slice(&self.parent_node.to_le_bytes());
956        buf.push(self.peripheral_type as u8);
957        buf.extend_from_slice(&self.callsign);
958        buf.extend_from_slice(&self.health.encode());
959
960        if let Some(ref event) = self.last_event {
961            buf.push(1); // has event
962            buf.extend_from_slice(&event.encode());
963        } else {
964            buf.push(0); // no event
965        }
966
967        buf.extend_from_slice(&self.timestamp.to_le_bytes());
968        buf
969    }
970
971    /// Decode from bytes with validation
972    ///
973    /// # Security
974    /// Validates peripheral ID, callsign UTF-8, and timestamp.
975    /// Rejects garbage data that could indicate spoofing or corruption.
976    pub fn decode(data: &[u8]) -> Option<Self> {
977        if data.len() < 34 {
978            return None;
979        }
980
981        let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
982        let parent_node = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
983        let peripheral_type = PeripheralType::from_u8(data[8]);
984
985        // Security: Reject zero peripheral ID (invalid)
986        if id == 0 {
987            return None;
988        }
989
990        let mut callsign = [0u8; 12];
991        callsign.copy_from_slice(&data[9..21]);
992
993        // Security: Validate callsign is valid UTF-8
994        // Find the actual length (up to first null or end)
995        let callsign_len = callsign.iter().position(|&b| b == 0).unwrap_or(12);
996        if callsign_len > 0 && core::str::from_utf8(&callsign[..callsign_len]).is_err() {
997            return None;
998        }
999
1000        // HealthStatus::decode now validates internally
1001        let health = HealthStatus::decode(&data[21..25])?;
1002
1003        let has_event = data[25] != 0;
1004        let (last_event, timestamp_offset) = if has_event {
1005            if data.len() < 43 {
1006                return None;
1007            }
1008            // PeripheralEvent::decode now validates timestamp internally
1009            (PeripheralEvent::decode(&data[26..35]), 35)
1010        } else {
1011            (None, 26)
1012        };
1013
1014        if data.len() < timestamp_offset + 8 {
1015            return None;
1016        }
1017
1018        let timestamp = u64::from_le_bytes([
1019            data[timestamp_offset],
1020            data[timestamp_offset + 1],
1021            data[timestamp_offset + 2],
1022            data[timestamp_offset + 3],
1023            data[timestamp_offset + 4],
1024            data[timestamp_offset + 5],
1025            data[timestamp_offset + 6],
1026            data[timestamp_offset + 7],
1027        ]);
1028
1029        // Security: Reject timestamps before 2020 (0 allowed for "not set")
1030        if timestamp != 0 && timestamp < MIN_VALID_TIMESTAMP {
1031            return None;
1032        }
1033
1034        Some(Self {
1035            id,
1036            parent_node,
1037            peripheral_type,
1038            callsign,
1039            health,
1040            last_event,
1041            timestamp,
1042        })
1043    }
1044}
1045
1046// ============================================================================
1047// ChatCRDT - Add-only set of chat messages for mesh-wide messaging
1048// ============================================================================
1049
1050/// Maximum message text length in bytes
1051pub const CHAT_MAX_TEXT_LEN: usize = 128;
1052
1053/// Maximum sender name length in bytes
1054pub const CHAT_MAX_SENDER_LEN: usize = 12;
1055
1056/// Maximum number of messages to retain in the CRDT
1057///
1058/// Older messages are pruned to keep memory bounded on embedded devices.
1059pub const CHAT_MAX_MESSAGES: usize = 32;
1060
1061/// Maximum number of chat messages to include in sync documents
1062///
1063/// BLE GATT notifications have a limited MTU (typically 512 bytes).
1064/// To avoid exceeding this limit, sync documents only include the
1065/// most recent messages. The full history is kept in the local CRDT.
1066pub const CHAT_SYNC_LIMIT: usize = 8;
1067
1068/// A single chat message in the mesh
1069///
1070/// Messages are uniquely identified by `(origin_node, timestamp)`.
1071/// This allows deduplication across mesh sync while preserving message ordering.
1072#[derive(Debug, Clone, PartialEq)]
1073pub struct ChatMessage {
1074    /// Node that originated this message
1075    pub origin_node: u32,
1076    /// Timestamp when message was created (ms since epoch)
1077    pub timestamp: u64,
1078    /// Sender name/callsign (up to 12 bytes)
1079    sender: [u8; CHAT_MAX_SENDER_LEN],
1080    sender_len: u8,
1081    /// Message text (up to 128 bytes)
1082    text: [u8; CHAT_MAX_TEXT_LEN],
1083    text_len: u8,
1084    /// Whether this is a broadcast message (vs directed)
1085    pub is_broadcast: bool,
1086    /// Whether ACK is requested
1087    pub requires_ack: bool,
1088    /// Reply-to: origin node of the message being replied to (0 = not a reply)
1089    pub reply_to_node: u32,
1090    /// Reply-to: timestamp of the message being replied to (0 = not a reply)
1091    pub reply_to_timestamp: u64,
1092}
1093
1094impl Default for ChatMessage {
1095    fn default() -> Self {
1096        Self {
1097            origin_node: 0,
1098            timestamp: 0,
1099            sender: [0u8; CHAT_MAX_SENDER_LEN],
1100            sender_len: 0,
1101            text: [0u8; CHAT_MAX_TEXT_LEN],
1102            text_len: 0,
1103            is_broadcast: true,
1104            requires_ack: false,
1105            reply_to_node: 0,
1106            reply_to_timestamp: 0,
1107        }
1108    }
1109}
1110
1111impl ChatMessage {
1112    /// Create a new chat message
1113    pub fn new(origin_node: u32, timestamp: u64, sender: &str, text: &str) -> Self {
1114        let mut msg = Self {
1115            origin_node,
1116            timestamp,
1117            ..Default::default()
1118        };
1119        msg.set_sender(sender);
1120        msg.set_text(text);
1121        msg
1122    }
1123
1124    /// Set the sender name (truncated to 12 bytes)
1125    pub fn set_sender(&mut self, sender: &str) {
1126        let bytes = sender.as_bytes();
1127        let len = bytes.len().min(CHAT_MAX_SENDER_LEN);
1128        self.sender[..len].copy_from_slice(&bytes[..len]);
1129        self.sender_len = len as u8;
1130    }
1131
1132    /// Get the sender name as a string
1133    pub fn sender(&self) -> &str {
1134        core::str::from_utf8(&self.sender[..self.sender_len as usize]).unwrap_or("")
1135    }
1136
1137    /// Set the message text (truncated to 128 bytes)
1138    pub fn set_text(&mut self, text: &str) {
1139        let bytes = text.as_bytes();
1140        let len = bytes.len().min(CHAT_MAX_TEXT_LEN);
1141        self.text[..len].copy_from_slice(&bytes[..len]);
1142        self.text_len = len as u8;
1143    }
1144
1145    /// Get the message text as a string
1146    pub fn text(&self) -> &str {
1147        core::str::from_utf8(&self.text[..self.text_len as usize]).unwrap_or("")
1148    }
1149
1150    /// Set reply-to information
1151    pub fn set_reply_to(&mut self, node: u32, timestamp: u64) {
1152        self.reply_to_node = node;
1153        self.reply_to_timestamp = timestamp;
1154    }
1155
1156    /// Check if this is a reply to another message
1157    pub fn is_reply(&self) -> bool {
1158        self.reply_to_node != 0 || self.reply_to_timestamp != 0
1159    }
1160
1161    /// Get the unique message ID (combines origin_node and timestamp)
1162    ///
1163    /// Format: `(origin_node as u64) << 32 | (timestamp & 0xFFFFFFFF)`
1164    /// This provides a sortable key where messages from same node are ordered by time.
1165    pub fn message_id(&self) -> u64 {
1166        ((self.origin_node as u64) << 32) | (self.timestamp & 0xFFFFFFFF)
1167    }
1168
1169    /// Encode to bytes for transmission
1170    ///
1171    /// Wire format:
1172    /// ```text
1173    /// origin_node:       4 bytes (LE)
1174    /// timestamp:         8 bytes (LE)
1175    /// sender_len:        1 byte
1176    /// sender:            sender_len bytes
1177    /// text_len:          1 byte
1178    /// text:              text_len bytes
1179    /// flags:             1 byte (bit 0: is_broadcast, bit 1: requires_ack)
1180    /// reply_to_node:     4 bytes (LE)
1181    /// reply_to_timestamp: 8 bytes (LE)
1182    /// ```
1183    pub fn encode(&self) -> Vec<u8> {
1184        let size = 4 + 8 + 1 + self.sender_len as usize + 1 + self.text_len as usize + 1 + 4 + 8;
1185        let mut buf = Vec::with_capacity(size);
1186
1187        buf.extend_from_slice(&self.origin_node.to_le_bytes());
1188        buf.extend_from_slice(&self.timestamp.to_le_bytes());
1189        buf.push(self.sender_len);
1190        buf.extend_from_slice(&self.sender[..self.sender_len as usize]);
1191        buf.push(self.text_len);
1192        buf.extend_from_slice(&self.text[..self.text_len as usize]);
1193
1194        let mut flags = 0u8;
1195        if self.is_broadcast {
1196            flags |= 0x01;
1197        }
1198        if self.requires_ack {
1199            flags |= 0x02;
1200        }
1201        buf.push(flags);
1202
1203        buf.extend_from_slice(&self.reply_to_node.to_le_bytes());
1204        buf.extend_from_slice(&self.reply_to_timestamp.to_le_bytes());
1205
1206        buf
1207    }
1208
1209    /// Decode from bytes with strict validation
1210    ///
1211    /// Returns `None` if the data is malformed or fails validation checks.
1212    /// This is a security-critical function - malformed messages could be
1213    /// spoofed or part of an attack.
1214    ///
1215    /// # Validation checks:
1216    /// - origin_node must be non-zero
1217    /// - timestamp must be non-zero and within reasonable bounds
1218    /// - sender must be non-empty and valid UTF-8
1219    /// - text must be valid UTF-8 (can be empty for ACK messages)
1220    /// - All length fields must be within bounds
1221    pub fn decode(data: &[u8]) -> Option<(Self, usize)> {
1222        if data.len() < 14 {
1223            // Minimum: 4 + 8 + 1 + 0 + 1 + 0 + 0 (no reply fields in old format)
1224            return None;
1225        }
1226
1227        let origin_node = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1228        let timestamp = u64::from_le_bytes([
1229            data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
1230        ]);
1231
1232        // Security: Reject messages with zero origin_node (invalid/spoofed)
1233        if origin_node == 0 {
1234            return None;
1235        }
1236
1237        // Security: Reject messages with timestamps before 2020
1238        if timestamp < MIN_VALID_TIMESTAMP {
1239            return None;
1240        }
1241
1242        let sender_len = data[12] as usize;
1243        if sender_len > CHAT_MAX_SENDER_LEN || data.len() < 13 + sender_len + 1 {
1244            return None;
1245        }
1246
1247        // Security: Reject messages with empty sender (required field)
1248        if sender_len == 0 {
1249            return None;
1250        }
1251
1252        let mut sender = [0u8; CHAT_MAX_SENDER_LEN];
1253        sender[..sender_len].copy_from_slice(&data[13..13 + sender_len]);
1254
1255        // Security: Validate sender is valid UTF-8 (reject garbage/binary data)
1256        if core::str::from_utf8(&sender[..sender_len]).is_err() {
1257            return None;
1258        }
1259
1260        let text_offset = 13 + sender_len;
1261        let text_len = data[text_offset] as usize;
1262        if text_len > CHAT_MAX_TEXT_LEN || data.len() < text_offset + 1 + text_len + 1 {
1263            return None;
1264        }
1265
1266        let mut text = [0u8; CHAT_MAX_TEXT_LEN];
1267        text[..text_len].copy_from_slice(&data[text_offset + 1..text_offset + 1 + text_len]);
1268
1269        // Security: Validate text is valid UTF-8 (reject garbage/binary data)
1270        // Empty text is allowed (e.g., for ACK messages)
1271        if text_len > 0 && core::str::from_utf8(&text[..text_len]).is_err() {
1272            return None;
1273        }
1274
1275        let flags_offset = text_offset + 1 + text_len;
1276        let flags = data[flags_offset];
1277        let is_broadcast = flags & 0x01 != 0;
1278        let requires_ack = flags & 0x02 != 0;
1279
1280        // Reply-to fields (optional for backward compat)
1281        let mut reply_to_node = 0u32;
1282        let mut reply_to_timestamp = 0u64;
1283        let mut total_len = flags_offset + 1;
1284
1285        if data.len() >= flags_offset + 1 + 12 {
1286            reply_to_node = u32::from_le_bytes([
1287                data[flags_offset + 1],
1288                data[flags_offset + 2],
1289                data[flags_offset + 3],
1290                data[flags_offset + 4],
1291            ]);
1292            reply_to_timestamp = u64::from_le_bytes([
1293                data[flags_offset + 5],
1294                data[flags_offset + 6],
1295                data[flags_offset + 7],
1296                data[flags_offset + 8],
1297                data[flags_offset + 9],
1298                data[flags_offset + 10],
1299                data[flags_offset + 11],
1300                data[flags_offset + 12],
1301            ]);
1302            total_len = flags_offset + 13;
1303        }
1304
1305        Some((
1306            Self {
1307                origin_node,
1308                timestamp,
1309                sender,
1310                sender_len: sender_len as u8,
1311                text,
1312                text_len: text_len as u8,
1313                is_broadcast,
1314                requires_ack,
1315                reply_to_node,
1316                reply_to_timestamp,
1317            },
1318            total_len,
1319        ))
1320    }
1321}
1322
1323/// Chat CRDT - Add-only set of messages
1324///
1325/// Implements add-only set semantics where messages are identified by
1326/// `(origin_node, timestamp)`. Once a message is added, it cannot be removed
1327/// (tombstone-free design optimized for mesh networks).
1328///
1329/// ## CRDT Semantics
1330///
1331/// - **Merge**: Union of all messages from both sets
1332/// - **Identity**: `(origin_node, timestamp)` - duplicates are ignored
1333/// - **Ordering**: Messages are stored sorted by message_id for efficient iteration
1334/// - **Pruning**: Oldest messages are removed when exceeding `CHAT_MAX_MESSAGES`
1335///
1336/// ## Wire Format
1337///
1338/// ```text
1339/// num_messages: 2 bytes (LE)
1340/// messages[N]:  variable (see ChatMessage::encode)
1341/// ```
1342#[derive(Debug, Clone, Default)]
1343pub struct ChatCRDT {
1344    /// Messages indexed by message_id for deduplication
1345    messages: BTreeMap<u64, ChatMessage>,
1346}
1347
1348impl ChatCRDT {
1349    /// Create a new empty chat CRDT
1350    pub fn new() -> Self {
1351        Self {
1352            messages: BTreeMap::new(),
1353        }
1354    }
1355
1356    /// Add a message to the chat
1357    ///
1358    /// Returns `true` if the message was new (not a duplicate)
1359    pub fn add_message(&mut self, message: ChatMessage) -> bool {
1360        let id = message.message_id();
1361        if self.messages.contains_key(&id) {
1362            return false;
1363        }
1364
1365        self.messages.insert(id, message);
1366        self.prune_if_needed();
1367        true
1368    }
1369
1370    /// Create and add a new message
1371    pub fn send_message(
1372        &mut self,
1373        origin_node: u32,
1374        timestamp: u64,
1375        sender: &str,
1376        text: &str,
1377    ) -> bool {
1378        let msg = ChatMessage::new(origin_node, timestamp, sender, text);
1379        self.add_message(msg)
1380    }
1381
1382    /// Get a message by its ID
1383    pub fn get_message(&self, origin_node: u32, timestamp: u64) -> Option<&ChatMessage> {
1384        let id = ((origin_node as u64) << 32) | (timestamp & 0xFFFFFFFF);
1385        self.messages.get(&id)
1386    }
1387
1388    /// Get all messages, ordered by message_id
1389    pub fn messages(&self) -> impl Iterator<Item = &ChatMessage> {
1390        self.messages.values()
1391    }
1392
1393    /// Get messages newer than a given timestamp
1394    pub fn messages_since(&self, since_timestamp: u64) -> impl Iterator<Item = &ChatMessage> {
1395        self.messages
1396            .values()
1397            .filter(move |m| m.timestamp > since_timestamp)
1398    }
1399
1400    /// Get the number of messages
1401    pub fn len(&self) -> usize {
1402        self.messages.len()
1403    }
1404
1405    /// Check if there are no messages
1406    pub fn is_empty(&self) -> bool {
1407        self.messages.is_empty()
1408    }
1409
1410    /// Get the newest message timestamp (if any)
1411    pub fn newest_timestamp(&self) -> Option<u64> {
1412        self.messages.values().map(|m| m.timestamp).max()
1413    }
1414
1415    /// Merge with another ChatCRDT
1416    ///
1417    /// Returns `true` if any new messages were added
1418    pub fn merge(&mut self, other: &ChatCRDT) -> bool {
1419        let mut changed = false;
1420        for (id, msg) in &other.messages {
1421            if !self.messages.contains_key(id) {
1422                self.messages.insert(*id, msg.clone());
1423                changed = true;
1424            }
1425        }
1426        if changed {
1427            self.prune_if_needed();
1428        }
1429        changed
1430    }
1431
1432    /// Prune oldest messages if we exceed the limit
1433    fn prune_if_needed(&mut self) {
1434        while self.messages.len() > CHAT_MAX_MESSAGES {
1435            // Remove the entry with the lowest timestamp
1436            if let Some(&oldest_id) = self.messages.keys().next() {
1437                self.messages.remove(&oldest_id);
1438            }
1439        }
1440    }
1441
1442    /// Encode to bytes for transmission
1443    pub fn encode(&self) -> Vec<u8> {
1444        let mut buf = Vec::new();
1445
1446        // Number of messages
1447        buf.extend_from_slice(&(self.messages.len() as u16).to_le_bytes());
1448
1449        // Each message
1450        for msg in self.messages.values() {
1451            buf.extend_from_slice(&msg.encode());
1452        }
1453
1454        buf
1455    }
1456
1457    /// Decode from bytes
1458    pub fn decode(data: &[u8]) -> Option<Self> {
1459        if data.len() < 2 {
1460            return None;
1461        }
1462
1463        let num_messages = u16::from_le_bytes([data[0], data[1]]) as usize;
1464        let mut messages = BTreeMap::new();
1465        let mut offset = 2;
1466
1467        for _ in 0..num_messages {
1468            if offset >= data.len() {
1469                break;
1470            }
1471            if let Some((msg, len)) = ChatMessage::decode(&data[offset..]) {
1472                let id = msg.message_id();
1473                messages.insert(id, msg);
1474                offset += len;
1475            } else {
1476                break;
1477            }
1478        }
1479
1480        Some(Self { messages })
1481    }
1482
1483    /// Get the encoded size of this CRDT
1484    pub fn encoded_size(&self) -> usize {
1485        2 + self
1486            .messages
1487            .values()
1488            .map(|m| m.encode().len())
1489            .sum::<usize>()
1490    }
1491
1492    /// Create a copy limited to the most recent messages for sync
1493    ///
1494    /// Returns a new ChatCRDT containing only the N most recent messages,
1495    /// where N is `CHAT_SYNC_LIMIT`. This is used when building sync documents
1496    /// to avoid exceeding BLE MTU limits.
1497    ///
1498    /// The local CRDT retains all messages up to `CHAT_MAX_MESSAGES`.
1499    pub fn for_sync(&self) -> Self {
1500        if self.messages.len() <= CHAT_SYNC_LIMIT {
1501            return self.clone();
1502        }
1503
1504        // BTreeMap is ordered by key (message_id), and message_id encodes
1505        // timestamp in lower bits, so we take from the end for newest
1506        let messages: BTreeMap<u64, ChatMessage> = self
1507            .messages
1508            .iter()
1509            .rev()
1510            .take(CHAT_SYNC_LIMIT)
1511            .map(|(&k, v)| (k, v.clone()))
1512            .collect();
1513
1514        Self { messages }
1515    }
1516}
1517
1518/// CRDT operation types for sync
1519#[derive(Debug, Clone)]
1520pub enum CrdtOperation {
1521    /// Update a position register
1522    UpdatePosition {
1523        /// Node ID that owns this position
1524        node_id: NodeId,
1525        /// Position data
1526        position: Position,
1527        /// Timestamp of the update
1528        timestamp: Timestamp,
1529    },
1530    /// Update health status register
1531    UpdateHealth {
1532        /// Node ID that owns this status
1533        node_id: NodeId,
1534        /// Health status data
1535        status: HealthStatus,
1536        /// Timestamp of the update
1537        timestamp: Timestamp,
1538    },
1539    /// Increment a counter
1540    IncrementCounter {
1541        /// Counter identifier
1542        counter_id: u8,
1543        /// Node performing the increment
1544        node_id: NodeId,
1545        /// Amount to increment
1546        amount: u64,
1547    },
1548    /// Generic LWW update (key-value)
1549    UpdateRegister {
1550        /// Key for the register
1551        key: String,
1552        /// Value data
1553        value: Vec<u8>,
1554        /// Timestamp of the update
1555        timestamp: Timestamp,
1556        /// Node that set the value
1557        node_id: NodeId,
1558    },
1559}
1560
1561impl CrdtOperation {
1562    /// Get the approximate size in bytes
1563    pub fn size(&self) -> usize {
1564        match self {
1565            CrdtOperation::UpdatePosition { position, .. } => 4 + 8 + position.encode().len(),
1566            CrdtOperation::UpdateHealth { status, .. } => 4 + 8 + status.encode().len(),
1567            CrdtOperation::IncrementCounter { .. } => 1 + 4 + 8,
1568            CrdtOperation::UpdateRegister { key, value, .. } => 4 + 8 + key.len() + value.len(),
1569        }
1570    }
1571
1572    /// Encode to bytes
1573    pub fn encode(&self) -> Vec<u8> {
1574        let mut buf = Vec::new();
1575        match self {
1576            CrdtOperation::UpdatePosition {
1577                node_id,
1578                position,
1579                timestamp,
1580            } => {
1581                buf.push(0x01); // Type tag
1582                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1583                buf.extend_from_slice(&timestamp.to_le_bytes());
1584                buf.extend_from_slice(&position.encode());
1585            }
1586            CrdtOperation::UpdateHealth {
1587                node_id,
1588                status,
1589                timestamp,
1590            } => {
1591                buf.push(0x02); // Type tag
1592                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1593                buf.extend_from_slice(&timestamp.to_le_bytes());
1594                buf.extend_from_slice(&status.encode());
1595            }
1596            CrdtOperation::IncrementCounter {
1597                counter_id,
1598                node_id,
1599                amount,
1600            } => {
1601                buf.push(0x03); // Type tag
1602                buf.push(*counter_id);
1603                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1604                buf.extend_from_slice(&amount.to_le_bytes());
1605            }
1606            CrdtOperation::UpdateRegister {
1607                key,
1608                value,
1609                timestamp,
1610                node_id,
1611            } => {
1612                buf.push(0x04); // Type tag
1613                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1614                buf.extend_from_slice(&timestamp.to_le_bytes());
1615                buf.push(key.len() as u8);
1616                buf.extend_from_slice(key.as_bytes());
1617                buf.extend_from_slice(&(value.len() as u16).to_le_bytes());
1618                buf.extend_from_slice(value);
1619            }
1620        }
1621        buf
1622    }
1623
1624    /// Decode from bytes
1625    pub fn decode(data: &[u8]) -> Option<Self> {
1626        if data.is_empty() {
1627            return None;
1628        }
1629
1630        match data[0] {
1631            0x01 => {
1632                // UpdatePosition
1633                if data.len() < 13 {
1634                    return None;
1635                }
1636                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1637                let timestamp = u64::from_le_bytes([
1638                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1639                ]);
1640                let position = Position::decode(&data[13..])?;
1641                Some(CrdtOperation::UpdatePosition {
1642                    node_id,
1643                    position,
1644                    timestamp,
1645                })
1646            }
1647            0x02 => {
1648                // UpdateHealth
1649                if data.len() < 13 {
1650                    return None;
1651                }
1652                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1653                let timestamp = u64::from_le_bytes([
1654                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1655                ]);
1656                let status = HealthStatus::decode(&data[13..])?;
1657                Some(CrdtOperation::UpdateHealth {
1658                    node_id,
1659                    status,
1660                    timestamp,
1661                })
1662            }
1663            0x03 => {
1664                // IncrementCounter
1665                if data.len() < 14 {
1666                    return None;
1667                }
1668                let counter_id = data[1];
1669                let node_id = NodeId::new(u32::from_le_bytes([data[2], data[3], data[4], data[5]]));
1670                let amount = u64::from_le_bytes([
1671                    data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13],
1672                ]);
1673                Some(CrdtOperation::IncrementCounter {
1674                    counter_id,
1675                    node_id,
1676                    amount,
1677                })
1678            }
1679            0x04 => {
1680                // UpdateRegister
1681                if data.len() < 14 {
1682                    return None;
1683                }
1684                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1685                let timestamp = u64::from_le_bytes([
1686                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1687                ]);
1688                let key_len = data[13] as usize;
1689                if data.len() < 14 + key_len + 2 {
1690                    return None;
1691                }
1692                let key = core::str::from_utf8(&data[14..14 + key_len])
1693                    .ok()?
1694                    .to_string();
1695                let value_len =
1696                    u16::from_le_bytes([data[14 + key_len], data[15 + key_len]]) as usize;
1697                if data.len() < 16 + key_len + value_len {
1698                    return None;
1699                }
1700                let value = data[16 + key_len..16 + key_len + value_len].to_vec();
1701                Some(CrdtOperation::UpdateRegister {
1702                    key,
1703                    value,
1704                    timestamp,
1705                    node_id,
1706                })
1707            }
1708            _ => None,
1709        }
1710    }
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715    use super::*;
1716
1717    // Valid timestamp for tests: 2024-01-15 00:00:00 UTC
1718    // All tests should use this or timestamps derived from it
1719    const TEST_TIMESTAMP: u64 = 1705276800000;
1720
1721    #[test]
1722    fn test_lww_register_basic() {
1723        let mut reg = LwwRegister::new(42u32, 100, NodeId::new(1));
1724        assert_eq!(*reg.get(), 42);
1725        assert_eq!(reg.timestamp(), 100);
1726
1727        // Higher timestamp wins
1728        assert!(reg.set(99, 200, NodeId::new(2)));
1729        assert_eq!(*reg.get(), 99);
1730
1731        // Lower timestamp loses
1732        assert!(!reg.set(50, 150, NodeId::new(3)));
1733        assert_eq!(*reg.get(), 99);
1734    }
1735
1736    #[test]
1737    fn test_lww_register_tiebreak() {
1738        let mut reg = LwwRegister::new(1u32, 100, NodeId::new(1));
1739
1740        // Same timestamp, higher node_id wins
1741        assert!(reg.set(2, 100, NodeId::new(2)));
1742        assert_eq!(*reg.get(), 2);
1743
1744        // Same timestamp, lower node_id loses
1745        assert!(!reg.set(3, 100, NodeId::new(1)));
1746        assert_eq!(*reg.get(), 2);
1747    }
1748
1749    #[test]
1750    fn test_lww_register_merge() {
1751        let mut reg1 = LwwRegister::new(1u32, 100, NodeId::new(1));
1752        let reg2 = LwwRegister::new(2u32, 200, NodeId::new(2));
1753
1754        assert!(reg1.merge(&reg2));
1755        assert_eq!(*reg1.get(), 2);
1756    }
1757
1758    #[test]
1759    fn test_gcounter_basic() {
1760        let mut counter = GCounter::new();
1761        let node1 = NodeId::new(1);
1762        let node2 = NodeId::new(2);
1763
1764        counter.increment(&node1, 5);
1765        counter.increment(&node2, 3);
1766        counter.increment(&node1, 2);
1767
1768        assert_eq!(counter.value(), 10);
1769        assert_eq!(counter.node_count(&node1), 7);
1770        assert_eq!(counter.node_count(&node2), 3);
1771    }
1772
1773    #[test]
1774    fn test_gcounter_merge() {
1775        let mut counter1 = GCounter::new();
1776        let mut counter2 = GCounter::new();
1777        let node1 = NodeId::new(1);
1778        let node2 = NodeId::new(2);
1779
1780        counter1.increment(&node1, 5);
1781        counter2.increment(&node1, 3);
1782        counter2.increment(&node2, 4);
1783
1784        counter1.merge(&counter2);
1785
1786        assert_eq!(counter1.value(), 9); // max(5,3) + 4
1787        assert_eq!(counter1.node_count(&node1), 5);
1788        assert_eq!(counter1.node_count(&node2), 4);
1789    }
1790
1791    #[test]
1792    fn test_gcounter_encode_decode() {
1793        let mut counter = GCounter::new();
1794        counter.increment(&NodeId::new(1), 5);
1795        counter.increment(&NodeId::new(2), 10);
1796
1797        let encoded = counter.encode();
1798        let decoded = GCounter::decode(&encoded).unwrap();
1799
1800        assert_eq!(decoded.value(), counter.value());
1801        assert_eq!(decoded.node_count(&NodeId::new(1)), 5);
1802        assert_eq!(decoded.node_count(&NodeId::new(2)), 10);
1803    }
1804
1805    #[test]
1806    fn test_position_encode_decode() {
1807        let pos = Position::new(37.7749, -122.4194)
1808            .with_altitude(100.0)
1809            .with_accuracy(5.0);
1810
1811        let encoded = pos.encode();
1812        let decoded = Position::decode(&encoded).unwrap();
1813
1814        assert_eq!(decoded.latitude, pos.latitude);
1815        assert_eq!(decoded.longitude, pos.longitude);
1816        assert_eq!(decoded.altitude, pos.altitude);
1817        assert_eq!(decoded.accuracy, pos.accuracy);
1818    }
1819
1820    #[test]
1821    fn test_position_minimal_encode() {
1822        let pos = Position::new(0.0, 0.0);
1823        let encoded = pos.encode();
1824        assert_eq!(encoded.len(), 9); // lat + lon + flags
1825
1826        let pos_with_alt = Position::new(0.0, 0.0).with_altitude(0.0);
1827        let encoded_alt = pos_with_alt.encode();
1828        assert_eq!(encoded_alt.len(), 13);
1829    }
1830
1831    #[test]
1832    fn test_health_status() {
1833        let mut status = HealthStatus::new(85).with_heart_rate(72).with_activity(1);
1834
1835        assert_eq!(status.battery_percent, 85);
1836        assert_eq!(status.heart_rate, Some(72));
1837        assert!(!status.has_alert(HealthStatus::ALERT_MAN_DOWN));
1838
1839        status.set_alert(HealthStatus::ALERT_MAN_DOWN);
1840        assert!(status.has_alert(HealthStatus::ALERT_MAN_DOWN));
1841
1842        let encoded = status.encode();
1843        let decoded = HealthStatus::decode(&encoded).unwrap();
1844        assert_eq!(decoded.battery_percent, 85);
1845        assert_eq!(decoded.heart_rate, Some(72));
1846        assert!(decoded.has_alert(HealthStatus::ALERT_MAN_DOWN));
1847    }
1848
1849    #[test]
1850    fn test_crdt_operation_position() {
1851        let op = CrdtOperation::UpdatePosition {
1852            node_id: NodeId::new(0x1234),
1853            position: Position::new(37.0, -122.0),
1854            timestamp: 1000,
1855        };
1856
1857        let encoded = op.encode();
1858        let decoded = CrdtOperation::decode(&encoded).unwrap();
1859
1860        if let CrdtOperation::UpdatePosition {
1861            node_id,
1862            position,
1863            timestamp,
1864        } = decoded
1865        {
1866            assert_eq!(node_id.as_u32(), 0x1234);
1867            assert_eq!(timestamp, 1000);
1868            assert_eq!(position.latitude, 37.0);
1869        } else {
1870            panic!("Wrong operation type");
1871        }
1872    }
1873
1874    #[test]
1875    fn test_crdt_operation_counter() {
1876        let op = CrdtOperation::IncrementCounter {
1877            counter_id: 1,
1878            node_id: NodeId::new(0x5678),
1879            amount: 42,
1880        };
1881
1882        let encoded = op.encode();
1883        let decoded = CrdtOperation::decode(&encoded).unwrap();
1884
1885        if let CrdtOperation::IncrementCounter {
1886            counter_id,
1887            node_id,
1888            amount,
1889        } = decoded
1890        {
1891            assert_eq!(counter_id, 1);
1892            assert_eq!(node_id.as_u32(), 0x5678);
1893            assert_eq!(amount, 42);
1894        } else {
1895            panic!("Wrong operation type");
1896        }
1897    }
1898
1899    #[test]
1900    fn test_crdt_operation_size() {
1901        let pos_op = CrdtOperation::UpdatePosition {
1902            node_id: NodeId::new(1),
1903            position: Position::new(0.0, 0.0),
1904            timestamp: 0,
1905        };
1906        assert!(pos_op.size() > 0);
1907
1908        let counter_op = CrdtOperation::IncrementCounter {
1909            counter_id: 0,
1910            node_id: NodeId::new(1),
1911            amount: 1,
1912        };
1913        assert_eq!(counter_op.size(), 13);
1914    }
1915
1916    // ============================================================================
1917    // Peripheral Tests
1918    // ============================================================================
1919
1920    #[test]
1921    fn test_peripheral_type_from_u8() {
1922        assert_eq!(PeripheralType::from_u8(0), PeripheralType::Unknown);
1923        assert_eq!(PeripheralType::from_u8(1), PeripheralType::SoldierSensor);
1924        assert_eq!(PeripheralType::from_u8(2), PeripheralType::FixedSensor);
1925        assert_eq!(PeripheralType::from_u8(3), PeripheralType::Relay);
1926        assert_eq!(PeripheralType::from_u8(99), PeripheralType::Unknown);
1927    }
1928
1929    #[test]
1930    fn test_event_type_from_u8() {
1931        assert_eq!(EventType::from_u8(0), EventType::None);
1932        assert_eq!(EventType::from_u8(1), EventType::Ping);
1933        assert_eq!(EventType::from_u8(2), EventType::NeedAssist);
1934        assert_eq!(EventType::from_u8(3), EventType::Emergency);
1935        assert_eq!(EventType::from_u8(4), EventType::Moving);
1936        assert_eq!(EventType::from_u8(5), EventType::InPosition);
1937        assert_eq!(EventType::from_u8(6), EventType::Ack);
1938        assert_eq!(EventType::from_u8(99), EventType::None);
1939    }
1940
1941    #[test]
1942    fn test_event_type_labels() {
1943        assert_eq!(EventType::None.label(), "");
1944        assert_eq!(EventType::Emergency.label(), "EMERGENCY");
1945        assert_eq!(EventType::Ping.label(), "PING");
1946    }
1947
1948    #[test]
1949    fn test_peripheral_event_encode_decode() {
1950        let event = PeripheralEvent::new(EventType::Emergency, TEST_TIMESTAMP);
1951        let encoded = event.encode();
1952        assert_eq!(encoded.len(), 9);
1953
1954        let decoded = PeripheralEvent::decode(&encoded).unwrap();
1955        assert_eq!(decoded.event_type, EventType::Emergency);
1956        assert_eq!(decoded.timestamp, TEST_TIMESTAMP);
1957    }
1958
1959    #[test]
1960    fn test_peripheral_new() {
1961        let peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor);
1962        assert_eq!(peripheral.id, 0x12345678);
1963        assert_eq!(peripheral.peripheral_type, PeripheralType::SoldierSensor);
1964        assert_eq!(peripheral.parent_node, 0);
1965        assert!(peripheral.last_event.is_none());
1966    }
1967
1968    #[test]
1969    fn test_peripheral_with_callsign() {
1970        let peripheral = Peripheral::new(1, PeripheralType::SoldierSensor).with_callsign("ALPHA-1");
1971        assert_eq!(peripheral.callsign_str(), "ALPHA-1");
1972
1973        // Test truncation
1974        let peripheral2 = Peripheral::new(2, PeripheralType::SoldierSensor)
1975            .with_callsign("THIS_IS_A_VERY_LONG_CALLSIGN");
1976        assert_eq!(peripheral2.callsign_str(), "THIS_IS_A_VE");
1977    }
1978
1979    #[test]
1980    fn test_peripheral_set_event() {
1981        let mut peripheral = Peripheral::new(1, PeripheralType::SoldierSensor);
1982        peripheral.set_event(EventType::Emergency, TEST_TIMESTAMP);
1983
1984        assert!(peripheral.last_event.is_some());
1985        let event = peripheral.last_event.as_ref().unwrap();
1986        assert_eq!(event.event_type, EventType::Emergency);
1987        assert_eq!(event.timestamp, TEST_TIMESTAMP);
1988        assert_eq!(peripheral.timestamp, TEST_TIMESTAMP);
1989
1990        peripheral.clear_event();
1991        assert!(peripheral.last_event.is_none());
1992    }
1993
1994    #[test]
1995    fn test_peripheral_encode_decode_without_event() {
1996        let peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor)
1997            .with_callsign("BRAVO-2")
1998            .with_parent(0x11223344);
1999
2000        let encoded = peripheral.encode();
2001        assert_eq!(encoded.len(), 34); // No event
2002
2003        let decoded = Peripheral::decode(&encoded).unwrap();
2004        assert_eq!(decoded.id, 0xAABBCCDD);
2005        assert_eq!(decoded.parent_node, 0x11223344);
2006        assert_eq!(decoded.peripheral_type, PeripheralType::SoldierSensor);
2007        assert_eq!(decoded.callsign_str(), "BRAVO-2");
2008        assert!(decoded.last_event.is_none());
2009    }
2010
2011    #[test]
2012    fn test_peripheral_encode_decode_with_event() {
2013        let mut peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor)
2014            .with_callsign("CHARLIE")
2015            .with_parent(0x87654321);
2016        peripheral.health = HealthStatus::new(85);
2017        peripheral.set_event(EventType::NeedAssist, TEST_TIMESTAMP);
2018
2019        let encoded = peripheral.encode();
2020        assert_eq!(encoded.len(), 43); // With event
2021
2022        let decoded = Peripheral::decode(&encoded).unwrap();
2023        assert_eq!(decoded.id, 0x12345678);
2024        assert_eq!(decoded.parent_node, 0x87654321);
2025        assert_eq!(decoded.callsign_str(), "CHARLIE");
2026        assert_eq!(decoded.health.battery_percent, 85);
2027        assert!(decoded.last_event.is_some());
2028        let event = decoded.last_event.as_ref().unwrap();
2029        assert_eq!(event.event_type, EventType::NeedAssist);
2030        assert_eq!(event.timestamp, TEST_TIMESTAMP);
2031    }
2032
2033    #[test]
2034    fn test_peripheral_decode_invalid_data() {
2035        // Too short
2036        assert!(Peripheral::decode(&[0u8; 10]).is_none());
2037
2038        // Valid length but id=0 is rejected
2039        let mut data = vec![0u8; 34];
2040        data[25] = 0; // no event flag
2041        assert!(Peripheral::decode(&data).is_none()); // id=0 rejected
2042
2043        // Valid id but no event - should succeed
2044        data[0..4].copy_from_slice(&1u32.to_le_bytes()); // id=1
2045        assert!(Peripheral::decode(&data).is_some());
2046
2047        // Claims to have event but too short
2048        data[25] = 1; // has event flag
2049        assert!(Peripheral::decode(&data).is_none());
2050    }
2051
2052    // ============================================================================
2053    // EmergencyEvent Tests
2054    // ============================================================================
2055
2056    #[test]
2057    fn test_emergency_event_new() {
2058        let peers = vec![0x22222222, 0x33333333];
2059        let event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2060
2061        assert_eq!(event.source_node(), 0x11111111);
2062        assert_eq!(event.timestamp(), TEST_TIMESTAMP);
2063        assert_eq!(event.peer_count(), 3); // source + 2 peers
2064
2065        // Source is auto-acked
2066        assert!(event.has_acked(0x11111111));
2067        // Others are not
2068        assert!(!event.has_acked(0x22222222));
2069        assert!(!event.has_acked(0x33333333));
2070    }
2071
2072    #[test]
2073    fn test_emergency_event_ack() {
2074        let peers = vec![0x22222222, 0x33333333];
2075        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2076
2077        assert_eq!(event.ack_count(), 1); // just source
2078        assert!(!event.all_acked());
2079
2080        // ACK from first peer
2081        assert!(event.ack(0x22222222)); // returns true - new ack
2082        assert_eq!(event.ack_count(), 2);
2083        assert!(!event.all_acked());
2084
2085        // Duplicate ACK
2086        assert!(!event.ack(0x22222222)); // returns false - already acked
2087
2088        // ACK from second peer
2089        assert!(event.ack(0x33333333));
2090        assert_eq!(event.ack_count(), 3);
2091        assert!(event.all_acked());
2092    }
2093
2094    #[test]
2095    fn test_emergency_event_pending_nodes() {
2096        let peers = vec![0x22222222, 0x33333333];
2097        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2098
2099        let pending = event.pending_nodes();
2100        assert_eq!(pending.len(), 2);
2101        assert!(pending.contains(&0x22222222));
2102        assert!(pending.contains(&0x33333333));
2103
2104        event.ack(0x22222222);
2105        let pending = event.pending_nodes();
2106        assert_eq!(pending.len(), 1);
2107        assert!(pending.contains(&0x33333333));
2108    }
2109
2110    #[test]
2111    fn test_emergency_event_encode_decode() {
2112        let peers = vec![0x22222222, 0x33333333];
2113        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2114        event.ack(0x22222222);
2115
2116        let encoded = event.encode();
2117        let decoded = EmergencyEvent::decode(&encoded).unwrap();
2118
2119        assert_eq!(decoded.source_node(), 0x11111111);
2120        assert_eq!(decoded.timestamp(), TEST_TIMESTAMP);
2121        assert!(decoded.has_acked(0x11111111));
2122        assert!(decoded.has_acked(0x22222222));
2123        assert!(!decoded.has_acked(0x33333333));
2124    }
2125
2126    #[test]
2127    fn test_emergency_event_merge_same_event() {
2128        // Two nodes have the same emergency, different ack states
2129        let peers = vec![0x22222222, 0x33333333];
2130        let mut event1 = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2131        let mut event2 = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2132
2133        event1.ack(0x22222222);
2134        event2.ack(0x33333333);
2135
2136        // Merge event2 into event1
2137        let changed = event1.merge(&event2);
2138        assert!(changed);
2139        assert!(event1.has_acked(0x22222222));
2140        assert!(event1.has_acked(0x33333333));
2141        assert!(event1.all_acked());
2142    }
2143
2144    #[test]
2145    fn test_emergency_event_merge_different_events() {
2146        // Old emergency
2147        let mut old_event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &[0x22222222]);
2148        old_event.ack(0x22222222);
2149
2150        // New emergency from different source
2151        let new_event =
2152            EmergencyEvent::new(0x33333333, TEST_TIMESTAMP + 1000, &[0x11111111, 0x22222222]);
2153
2154        // Merge new into old - should replace
2155        let changed = old_event.merge(&new_event);
2156        assert!(changed);
2157        assert_eq!(old_event.source_node(), 0x33333333);
2158        assert_eq!(old_event.timestamp(), TEST_TIMESTAMP + 1000);
2159        // Old ack state should be gone
2160        assert!(!old_event.has_acked(0x22222222));
2161    }
2162
2163    #[test]
2164    fn test_emergency_event_merge_older_event_ignored() {
2165        // Current emergency
2166        let mut current = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP + 1000, &[0x22222222]);
2167
2168        // Older emergency
2169        let older = EmergencyEvent::new(0x33333333, TEST_TIMESTAMP, &[0x11111111]);
2170
2171        // Merge older into current - should NOT replace
2172        let changed = current.merge(&older);
2173        assert!(!changed);
2174        assert_eq!(current.source_node(), 0x11111111);
2175        assert_eq!(current.timestamp(), TEST_TIMESTAMP + 1000);
2176    }
2177
2178    #[test]
2179    fn test_emergency_event_add_peer() {
2180        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &[]);
2181
2182        // Add a peer discovered after emergency started
2183        event.add_peer(0x22222222);
2184        assert!(!event.has_acked(0x22222222));
2185        assert_eq!(event.peer_count(), 2);
2186
2187        // Adding same peer again doesn't change ack status
2188        event.ack(0x22222222);
2189        event.add_peer(0x22222222);
2190        assert!(event.has_acked(0x22222222)); // still acked
2191    }
2192
2193    #[test]
2194    fn test_emergency_event_decode_invalid() {
2195        // Too short
2196        assert!(EmergencyEvent::decode(&[0u8; 10]).is_none());
2197
2198        // Valid header but claims more acks than data
2199        let mut data = vec![0u8; 16];
2200        data[12] = 5; // claims 5 ack entries
2201        assert!(EmergencyEvent::decode(&data).is_none());
2202    }
2203
2204    // ============================================================================
2205    // ChatMessage Tests
2206    // ============================================================================
2207
2208    #[test]
2209    fn test_chat_message_new() {
2210        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "ALPHA-1", "Hello mesh!");
2211        assert_eq!(msg.origin_node, 0x12345678);
2212        assert_eq!(msg.timestamp, TEST_TIMESTAMP);
2213        assert_eq!(msg.sender(), "ALPHA-1");
2214        assert_eq!(msg.text(), "Hello mesh!");
2215        assert!(msg.is_broadcast);
2216        assert!(!msg.requires_ack);
2217        assert!(!msg.is_reply());
2218    }
2219
2220    #[test]
2221    fn test_chat_message_reply_to() {
2222        let mut msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP + 1000, "BRAVO", "Roger that");
2223        msg.set_reply_to(0xAABBCCDD, TEST_TIMESTAMP);
2224
2225        assert!(msg.is_reply());
2226        assert_eq!(msg.reply_to_node, 0xAABBCCDD);
2227        assert_eq!(msg.reply_to_timestamp, TEST_TIMESTAMP);
2228    }
2229
2230    #[test]
2231    fn test_chat_message_truncation() {
2232        // Test sender truncation (max 12 bytes)
2233        let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "VERY_LONG_CALLSIGN", "Hi");
2234        assert_eq!(msg.sender(), "VERY_LONG_CA"); // 12 chars
2235
2236        // Test text truncation (max 128 bytes)
2237        let long_text = "A".repeat(200);
2238        let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "X", &long_text);
2239        assert_eq!(msg.text().len(), 128);
2240    }
2241
2242    #[test]
2243    fn test_chat_message_id() {
2244        let msg = ChatMessage::new(0x12345678, 0x18D4A51_ABCDEF01, "X", "Y");
2245        let id = msg.message_id();
2246        // ID = (origin << 32) | (timestamp & 0xFFFFFFFF)
2247        assert_eq!(id, (0x12345678u64 << 32) | 0xABCDEF01);
2248    }
2249
2250    #[test]
2251    fn test_chat_message_encode_decode() {
2252        let mut msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "CHARLIE", "Test message");
2253        msg.is_broadcast = true;
2254        msg.requires_ack = true;
2255        msg.set_reply_to(0xAABBCCDD, TEST_TIMESTAMP - 1000);
2256
2257        let encoded = msg.encode();
2258        let (decoded, len) = ChatMessage::decode(&encoded).unwrap();
2259
2260        assert_eq!(len, encoded.len());
2261        assert_eq!(decoded.origin_node, 0x12345678);
2262        assert_eq!(decoded.timestamp, TEST_TIMESTAMP);
2263        assert_eq!(decoded.sender(), "CHARLIE");
2264        assert_eq!(decoded.text(), "Test message");
2265        assert!(decoded.is_broadcast);
2266        assert!(decoded.requires_ack);
2267        assert_eq!(decoded.reply_to_node, 0xAABBCCDD);
2268        assert_eq!(decoded.reply_to_timestamp, TEST_TIMESTAMP - 1000);
2269    }
2270
2271    #[test]
2272    fn test_chat_message_decode_empty_text() {
2273        // Message with valid sender but empty text (allowed for ACK messages)
2274        let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "ACK-NODE", "");
2275        let encoded = msg.encode();
2276        let (decoded, _) = ChatMessage::decode(&encoded).unwrap();
2277        assert_eq!(decoded.sender(), "ACK-NODE");
2278        assert_eq!(decoded.text(), "");
2279    }
2280
2281    // ============================================================================
2282    // ChatMessage Validation/Security Tests
2283    // ============================================================================
2284
2285    #[test]
2286    fn test_chat_message_decode_rejects_zero_origin() {
2287        // Build message bytes manually with origin_node = 0
2288        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "TEST", "msg");
2289        let mut encoded = msg.encode();
2290        // Zero out the origin_node (first 4 bytes)
2291        encoded[0] = 0;
2292        encoded[1] = 0;
2293        encoded[2] = 0;
2294        encoded[3] = 0;
2295
2296        assert!(ChatMessage::decode(&encoded).is_none());
2297    }
2298
2299    #[test]
2300    fn test_chat_message_decode_rejects_old_timestamp() {
2301        // Build message bytes manually with timestamp before 2020
2302        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "TEST", "msg");
2303        let mut encoded = msg.encode();
2304        // Set timestamp to 1000 (way before 2020)
2305        let old_ts: u64 = 1000;
2306        encoded[4..12].copy_from_slice(&old_ts.to_le_bytes());
2307
2308        assert!(ChatMessage::decode(&encoded).is_none());
2309    }
2310
2311    #[test]
2312    fn test_chat_message_decode_rejects_empty_sender() {
2313        // Build message bytes manually with sender_len = 0
2314        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "X", "msg");
2315        let mut encoded = msg.encode();
2316        // Set sender_len to 0 at offset 12
2317        encoded[12] = 0;
2318        // Adjust text position (move text_len and text to right after sender_len)
2319        // This is tricky - we need to rebuild the encoding properly
2320        // Actually easier to just build raw bytes:
2321        let mut raw = Vec::new();
2322        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2323        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2324        raw.push(0); // sender_len = 0 (INVALID)
2325        raw.push(3); // text_len = 3
2326        raw.extend_from_slice(b"msg"); // text
2327        raw.push(0x01); // flags
2328
2329        assert!(ChatMessage::decode(&raw).is_none());
2330    }
2331
2332    #[test]
2333    fn test_chat_message_decode_rejects_invalid_utf8_sender() {
2334        // Build message with invalid UTF-8 in sender
2335        let mut raw = Vec::new();
2336        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2337        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2338        raw.push(4); // sender_len = 4
2339        raw.extend_from_slice(&[0x66, 0x59, 0xFF, 0xFE]); // "fY" + invalid UTF-8
2340        raw.push(3); // text_len = 3
2341        raw.extend_from_slice(b"msg"); // text
2342        raw.push(0x01); // flags
2343        raw.extend_from_slice(&0u32.to_le_bytes()); // reply_to_node
2344        raw.extend_from_slice(&0u64.to_le_bytes()); // reply_to_timestamp
2345
2346        assert!(ChatMessage::decode(&raw).is_none());
2347    }
2348
2349    #[test]
2350    fn test_chat_message_decode_rejects_invalid_utf8_text() {
2351        // Build message with invalid UTF-8 in text
2352        let mut raw = Vec::new();
2353        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2354        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2355        raw.push(4); // sender_len = 4
2356        raw.extend_from_slice(b"TEST"); // valid sender
2357        raw.push(4); // text_len = 4
2358        raw.extend_from_slice(&[0x80, 0x81, 0x82, 0x83]); // invalid UTF-8
2359        raw.push(0x01); // flags
2360        raw.extend_from_slice(&0u32.to_le_bytes()); // reply_to_node
2361        raw.extend_from_slice(&0u64.to_le_bytes()); // reply_to_timestamp
2362
2363        assert!(ChatMessage::decode(&raw).is_none());
2364    }
2365
2366    #[test]
2367    fn test_chat_message_decode_accepts_valid_utf8() {
2368        // Build message with valid UTF-8 including unicode
2369        let mut raw = Vec::new();
2370        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2371        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2372        raw.push(6); // sender_len = 6 (UTF-8 encoded)
2373        raw.extend_from_slice("TËST".as_bytes()); // valid UTF-8 with umlaut (5 bytes: T, Ë=2bytes, S, T)
2374                                                  // Wait, "TËST" is 5 bytes not 6. Let me fix:
2375        let sender_bytes = "TËST1".as_bytes(); // T(1) + Ë(2) + S(1) + T(1) + 1(1) = 6 bytes
2376        raw[12] = sender_bytes.len() as u8;
2377        raw.truncate(13);
2378        raw.extend_from_slice(sender_bytes);
2379        raw.push(4); // text_len = 4
2380        raw.extend_from_slice(b"test"); // text
2381        raw.push(0x01); // flags
2382        raw.extend_from_slice(&0u32.to_le_bytes()); // reply_to_node
2383        raw.extend_from_slice(&0u64.to_le_bytes()); // reply_to_timestamp
2384
2385        let result = ChatMessage::decode(&raw);
2386        assert!(result.is_some());
2387        let (msg, _) = result.unwrap();
2388        assert_eq!(msg.sender(), "TËST1");
2389    }
2390
2391    // ============================================================================
2392    // ChatCRDT Tests
2393    // ============================================================================
2394
2395    #[test]
2396    fn test_chat_crdt_new() {
2397        let chat = ChatCRDT::new();
2398        assert!(chat.is_empty());
2399        assert_eq!(chat.len(), 0);
2400    }
2401
2402    #[test]
2403    fn test_chat_crdt_add_message() {
2404        let mut chat = ChatCRDT::new();
2405
2406        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello");
2407        assert!(chat.add_message(msg.clone()));
2408        assert_eq!(chat.len(), 1);
2409
2410        // Duplicate should be rejected
2411        assert!(!chat.add_message(msg));
2412        assert_eq!(chat.len(), 1);
2413    }
2414
2415    #[test]
2416    fn test_chat_crdt_send_message() {
2417        let mut chat = ChatCRDT::new();
2418
2419        assert!(chat.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "First"));
2420        assert!(chat.send_message(0x2, TEST_TIMESTAMP + 1, "BRAVO", "Second"));
2421        assert_eq!(chat.len(), 2);
2422
2423        // Same node, same timestamp = duplicate
2424        assert!(!chat.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Duplicate"));
2425        assert_eq!(chat.len(), 2);
2426    }
2427
2428    #[test]
2429    fn test_chat_crdt_get_message() {
2430        let mut chat = ChatCRDT::new();
2431        chat.send_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Test");
2432
2433        let msg = chat.get_message(0x12345678, TEST_TIMESTAMP);
2434        assert!(msg.is_some());
2435        assert_eq!(msg.unwrap().text(), "Test");
2436
2437        // Non-existent message
2438        assert!(chat.get_message(0x99999999, TEST_TIMESTAMP).is_none());
2439    }
2440
2441    #[test]
2442    fn test_chat_crdt_merge() {
2443        let mut chat1 = ChatCRDT::new();
2444        let mut chat2 = ChatCRDT::new();
2445
2446        chat1.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "From 1");
2447        chat2.send_message(0x2, TEST_TIMESTAMP + 1, "BRAVO", "From 2");
2448
2449        // Merge chat2 into chat1
2450        let changed = chat1.merge(&chat2);
2451        assert!(changed);
2452        assert_eq!(chat1.len(), 2);
2453
2454        // Merge again - no changes
2455        let changed = chat1.merge(&chat2);
2456        assert!(!changed);
2457        assert_eq!(chat1.len(), 2);
2458    }
2459
2460    #[test]
2461    fn test_chat_crdt_merge_duplicates() {
2462        let mut chat1 = ChatCRDT::new();
2463        let mut chat2 = ChatCRDT::new();
2464
2465        // Both have the same message
2466        chat1.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Same message");
2467        chat2.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Same message");
2468
2469        // Merge should not create duplicates
2470        chat1.merge(&chat2);
2471        assert_eq!(chat1.len(), 1);
2472    }
2473
2474    #[test]
2475    fn test_chat_crdt_pruning() {
2476        let mut chat = ChatCRDT::new();
2477
2478        // Add more than CHAT_MAX_MESSAGES with valid timestamps
2479        for i in 0..(CHAT_MAX_MESSAGES + 10) {
2480            chat.send_message(i as u32 + 1, TEST_TIMESTAMP + i as u64, "X", "Y");
2481        }
2482
2483        // Should be pruned to max
2484        assert_eq!(chat.len(), CHAT_MAX_MESSAGES);
2485
2486        // Oldest messages should be removed (first 10)
2487        // Message IDs are (node << 32) | (timestamp & 0xFFFFFFFF)
2488        // node=1, ts=TEST_TIMESTAMP has lowest ID, etc.
2489        assert!(chat.get_message(1, TEST_TIMESTAMP).is_none());
2490        assert!(chat.get_message(10, TEST_TIMESTAMP + 9).is_none());
2491        // Newer messages should remain
2492        assert!(chat.get_message(11, TEST_TIMESTAMP + 10).is_some());
2493    }
2494
2495    #[test]
2496    fn test_chat_crdt_encode_decode() {
2497        let mut chat = ChatCRDT::new();
2498        chat.send_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "First message");
2499        chat.send_message(0xAABBCCDD, TEST_TIMESTAMP + 1000, "BRAVO", "Second message");
2500
2501        let encoded = chat.encode();
2502        let decoded = ChatCRDT::decode(&encoded).unwrap();
2503
2504        assert_eq!(decoded.len(), 2);
2505        assert!(decoded.get_message(0x12345678, TEST_TIMESTAMP).is_some());
2506        assert!(decoded
2507            .get_message(0xAABBCCDD, TEST_TIMESTAMP + 1000)
2508            .is_some());
2509    }
2510
2511    #[test]
2512    fn test_chat_crdt_messages_since() {
2513        let mut chat = ChatCRDT::new();
2514        chat.send_message(0x1, TEST_TIMESTAMP, "A", "Old");
2515        chat.send_message(0x2, TEST_TIMESTAMP + 1000, "B", "Mid");
2516        chat.send_message(0x3, TEST_TIMESTAMP + 2000, "C", "New");
2517
2518        let recent: Vec<_> = chat.messages_since(TEST_TIMESTAMP + 500).collect();
2519        assert_eq!(recent.len(), 2);
2520    }
2521
2522    #[test]
2523    fn test_chat_crdt_newest_timestamp() {
2524        let mut chat = ChatCRDT::new();
2525        assert!(chat.newest_timestamp().is_none());
2526
2527        chat.send_message(0x1, TEST_TIMESTAMP, "A", "1");
2528        assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP));
2529
2530        chat.send_message(0x2, TEST_TIMESTAMP + 2000, "B", "2");
2531        assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP + 2000));
2532
2533        chat.send_message(0x3, TEST_TIMESTAMP + 1000, "C", "3"); // older timestamp
2534        assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP + 2000));
2535    }
2536
2537    // ============================================================================
2538    // ChatCRDT Security/Validation Tests
2539    // ============================================================================
2540
2541    #[test]
2542    fn test_chat_crdt_decode_skips_invalid_messages() {
2543        // Build a CRDT with 2 messages, one valid and one invalid
2544        let mut valid_chat = ChatCRDT::new();
2545        valid_chat.send_message(0x12345678, TEST_TIMESTAMP, "VALID", "Good message");
2546
2547        let encoded = valid_chat.encode();
2548
2549        // Decode should work and contain the valid message
2550        let decoded = ChatCRDT::decode(&encoded).unwrap();
2551        assert_eq!(decoded.len(), 1);
2552        assert!(decoded.get_message(0x12345678, TEST_TIMESTAMP).is_some());
2553    }
2554
2555    #[test]
2556    fn test_chat_crdt_decode_handles_truncated_data() {
2557        let mut chat = ChatCRDT::new();
2558        chat.send_message(0x12345678, TEST_TIMESTAMP, "TEST", "Message");
2559
2560        let encoded = chat.encode();
2561
2562        // Truncate to just the message count
2563        let truncated = &encoded[..2];
2564        let decoded = ChatCRDT::decode(truncated);
2565        assert!(decoded.is_some());
2566        assert_eq!(decoded.unwrap().len(), 0); // No messages decoded
2567    }
2568}