Skip to main content

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    /// Current location (if available)
901    pub location: Option<Position>,
902    /// Last update timestamp
903    pub timestamp: u64,
904}
905
906impl Peripheral {
907    /// Create a new peripheral
908    pub fn new(id: u32, peripheral_type: PeripheralType) -> Self {
909        Self {
910            id,
911            parent_node: 0,
912            peripheral_type,
913            callsign: [0u8; 12],
914            health: HealthStatus::default(),
915            last_event: None,
916            location: None,
917            timestamp: 0,
918        }
919    }
920
921    /// Set the callsign (truncated to 12 bytes)
922    pub fn with_callsign(mut self, callsign: &str) -> Self {
923        let bytes = callsign.as_bytes();
924        let len = bytes.len().min(12);
925        self.callsign[..len].copy_from_slice(&bytes[..len]);
926        self
927    }
928
929    /// Update callsign in place
930    pub fn set_callsign(&mut self, callsign: &str) {
931        self.callsign = [0u8; 12];
932        let bytes = callsign.as_bytes();
933        let len = bytes.len().min(12);
934        self.callsign[..len].copy_from_slice(&bytes[..len]);
935    }
936
937    /// Get callsign as string
938    pub fn callsign_str(&self) -> &str {
939        let len = self.callsign.iter().position(|&b| b == 0).unwrap_or(12);
940        core::str::from_utf8(&self.callsign[..len]).unwrap_or("")
941    }
942
943    /// Set parent node
944    pub fn with_parent(mut self, parent_node: u32) -> Self {
945        self.parent_node = parent_node;
946        self
947    }
948
949    /// Set location (builder pattern)
950    pub fn with_location(mut self, location: Position) -> Self {
951        self.location = Some(location);
952        self
953    }
954
955    /// Update location in place
956    pub fn set_location(&mut self, latitude: f32, longitude: f32, altitude: Option<f32>) {
957        let mut pos = Position::new(latitude, longitude);
958        if let Some(alt) = altitude {
959            pos = pos.with_altitude(alt);
960        }
961        self.location = Some(pos);
962    }
963
964    /// Clear location
965    pub fn clear_location(&mut self) {
966        self.location = None;
967    }
968
969    /// Record an event
970    pub fn set_event(&mut self, event_type: EventType, timestamp: u64) {
971        self.last_event = Some(PeripheralEvent::new(event_type, timestamp));
972        self.timestamp = timestamp;
973    }
974
975    /// Clear the last event
976    pub fn clear_event(&mut self) {
977        self.last_event = None;
978    }
979
980    /// Encode to bytes for BLE transmission
981    /// Format: [id:4][parent:4][type:1][callsign:12][health:4][has_event:1][event:9?][timestamp:8][has_location:1][location:9-20?]
982    /// Size: 35 bytes minimum (no event, no location), up to 64 bytes with both
983    pub fn encode(&self) -> Vec<u8> {
984        let mut buf = Vec::with_capacity(64);
985        buf.extend_from_slice(&self.id.to_le_bytes());
986        buf.extend_from_slice(&self.parent_node.to_le_bytes());
987        buf.push(self.peripheral_type as u8);
988        buf.extend_from_slice(&self.callsign);
989        buf.extend_from_slice(&self.health.encode());
990
991        if let Some(ref event) = self.last_event {
992            buf.push(1); // has event
993            buf.extend_from_slice(&event.encode());
994        } else {
995            buf.push(0); // no event
996        }
997
998        buf.extend_from_slice(&self.timestamp.to_le_bytes());
999
1000        // Location encoding (added in v0.1.0-rc.7)
1001        if let Some(ref location) = self.location {
1002            buf.push(1); // has location
1003            buf.extend_from_slice(&location.encode());
1004        } else {
1005            buf.push(0); // no location
1006        }
1007
1008        buf
1009    }
1010
1011    /// Decode from bytes with validation
1012    ///
1013    /// # Security
1014    /// Validates peripheral ID, callsign UTF-8, and timestamp.
1015    /// Rejects garbage data that could indicate spoofing or corruption.
1016    pub fn decode(data: &[u8]) -> Option<Self> {
1017        if data.len() < 34 {
1018            return None;
1019        }
1020
1021        let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1022        let parent_node = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
1023        let peripheral_type = PeripheralType::from_u8(data[8]);
1024
1025        // Security: Reject zero peripheral ID (invalid)
1026        if id == 0 {
1027            return None;
1028        }
1029
1030        let mut callsign = [0u8; 12];
1031        callsign.copy_from_slice(&data[9..21]);
1032
1033        // Security: Validate callsign is valid UTF-8
1034        // Find the actual length (up to first null or end)
1035        let callsign_len = callsign.iter().position(|&b| b == 0).unwrap_or(12);
1036        if callsign_len > 0 && core::str::from_utf8(&callsign[..callsign_len]).is_err() {
1037            return None;
1038        }
1039
1040        // HealthStatus::decode now validates internally
1041        let health = HealthStatus::decode(&data[21..25])?;
1042
1043        let has_event = data[25] != 0;
1044        let (last_event, timestamp_offset) = if has_event {
1045            if data.len() < 43 {
1046                return None;
1047            }
1048            // PeripheralEvent::decode now validates timestamp internally
1049            (PeripheralEvent::decode(&data[26..35]), 35)
1050        } else {
1051            (None, 26)
1052        };
1053
1054        if data.len() < timestamp_offset + 8 {
1055            return None;
1056        }
1057
1058        let timestamp = u64::from_le_bytes([
1059            data[timestamp_offset],
1060            data[timestamp_offset + 1],
1061            data[timestamp_offset + 2],
1062            data[timestamp_offset + 3],
1063            data[timestamp_offset + 4],
1064            data[timestamp_offset + 5],
1065            data[timestamp_offset + 6],
1066            data[timestamp_offset + 7],
1067        ]);
1068
1069        // Security: Reject timestamps before 2020 (0 allowed for "not set")
1070        if timestamp != 0 && timestamp < MIN_VALID_TIMESTAMP {
1071            return None;
1072        }
1073
1074        // Decode location if present (added in v0.1.0-rc.7)
1075        // For backward compatibility, treat missing has_location byte as no location
1076        let location_offset = timestamp_offset + 8;
1077        let location = if data.len() > location_offset {
1078            let has_location = data[location_offset] != 0;
1079            if has_location && data.len() > location_offset + 1 {
1080                Position::decode(&data[location_offset + 1..])
1081            } else {
1082                None
1083            }
1084        } else {
1085            None
1086        };
1087
1088        Some(Self {
1089            id,
1090            parent_node,
1091            peripheral_type,
1092            callsign,
1093            health,
1094            last_event,
1095            location,
1096            timestamp,
1097        })
1098    }
1099}
1100
1101// ============================================================================
1102// ChatCRDT - Add-only set of chat messages for mesh-wide messaging
1103// ============================================================================
1104
1105/// Maximum message text length in bytes
1106pub const CHAT_MAX_TEXT_LEN: usize = 128;
1107
1108/// Maximum sender name length in bytes
1109pub const CHAT_MAX_SENDER_LEN: usize = 12;
1110
1111/// Maximum number of messages to retain in the CRDT
1112///
1113/// Older messages are pruned to keep memory bounded on embedded devices.
1114pub const CHAT_MAX_MESSAGES: usize = 32;
1115
1116/// Maximum number of chat messages to include in sync documents
1117///
1118/// BLE GATT notifications have a limited MTU (typically 512 bytes).
1119/// To avoid exceeding this limit, sync documents only include the
1120/// most recent messages. The full history is kept in the local CRDT.
1121pub const CHAT_SYNC_LIMIT: usize = 8;
1122
1123/// A single chat message in the mesh
1124///
1125/// Messages are uniquely identified by `(origin_node, timestamp)`.
1126/// This allows deduplication across mesh sync while preserving message ordering.
1127#[derive(Debug, Clone, PartialEq)]
1128pub struct ChatMessage {
1129    /// Node that originated this message
1130    pub origin_node: u32,
1131    /// Timestamp when message was created (ms since epoch)
1132    pub timestamp: u64,
1133    /// Sender name/callsign (up to 12 bytes)
1134    sender: [u8; CHAT_MAX_SENDER_LEN],
1135    sender_len: u8,
1136    /// Message text (up to 128 bytes)
1137    text: [u8; CHAT_MAX_TEXT_LEN],
1138    text_len: u8,
1139    /// Whether this is a broadcast message (vs directed)
1140    pub is_broadcast: bool,
1141    /// Whether ACK is requested
1142    pub requires_ack: bool,
1143    /// Reply-to: origin node of the message being replied to (0 = not a reply)
1144    pub reply_to_node: u32,
1145    /// Reply-to: timestamp of the message being replied to (0 = not a reply)
1146    pub reply_to_timestamp: u64,
1147}
1148
1149impl Default for ChatMessage {
1150    fn default() -> Self {
1151        Self {
1152            origin_node: 0,
1153            timestamp: 0,
1154            sender: [0u8; CHAT_MAX_SENDER_LEN],
1155            sender_len: 0,
1156            text: [0u8; CHAT_MAX_TEXT_LEN],
1157            text_len: 0,
1158            is_broadcast: true,
1159            requires_ack: false,
1160            reply_to_node: 0,
1161            reply_to_timestamp: 0,
1162        }
1163    }
1164}
1165
1166impl ChatMessage {
1167    /// Create a new chat message
1168    pub fn new(origin_node: u32, timestamp: u64, sender: &str, text: &str) -> Self {
1169        let mut msg = Self {
1170            origin_node,
1171            timestamp,
1172            ..Default::default()
1173        };
1174        msg.set_sender(sender);
1175        msg.set_text(text);
1176        msg
1177    }
1178
1179    /// Set the sender name (truncated to 12 bytes)
1180    pub fn set_sender(&mut self, sender: &str) {
1181        let bytes = sender.as_bytes();
1182        let len = bytes.len().min(CHAT_MAX_SENDER_LEN);
1183        self.sender[..len].copy_from_slice(&bytes[..len]);
1184        self.sender_len = len as u8;
1185    }
1186
1187    /// Get the sender name as a string
1188    pub fn sender(&self) -> &str {
1189        core::str::from_utf8(&self.sender[..self.sender_len as usize]).unwrap_or("")
1190    }
1191
1192    /// Set the message text (truncated to 128 bytes)
1193    pub fn set_text(&mut self, text: &str) {
1194        let bytes = text.as_bytes();
1195        let len = bytes.len().min(CHAT_MAX_TEXT_LEN);
1196        self.text[..len].copy_from_slice(&bytes[..len]);
1197        self.text_len = len as u8;
1198    }
1199
1200    /// Get the message text as a string
1201    pub fn text(&self) -> &str {
1202        core::str::from_utf8(&self.text[..self.text_len as usize]).unwrap_or("")
1203    }
1204
1205    /// Set reply-to information
1206    pub fn set_reply_to(&mut self, node: u32, timestamp: u64) {
1207        self.reply_to_node = node;
1208        self.reply_to_timestamp = timestamp;
1209    }
1210
1211    /// Check if this is a reply to another message
1212    pub fn is_reply(&self) -> bool {
1213        self.reply_to_node != 0 || self.reply_to_timestamp != 0
1214    }
1215
1216    /// Get the unique message ID (combines origin_node and timestamp)
1217    ///
1218    /// Format: `(origin_node as u64) << 32 | (timestamp & 0xFFFFFFFF)`
1219    /// This provides a sortable key where messages from same node are ordered by time.
1220    pub fn message_id(&self) -> u64 {
1221        ((self.origin_node as u64) << 32) | (self.timestamp & 0xFFFFFFFF)
1222    }
1223
1224    /// Encode to bytes for transmission
1225    ///
1226    /// Wire format:
1227    /// ```text
1228    /// origin_node:       4 bytes (LE)
1229    /// timestamp:         8 bytes (LE)
1230    /// sender_len:        1 byte
1231    /// sender:            sender_len bytes
1232    /// text_len:          1 byte
1233    /// text:              text_len bytes
1234    /// flags:             1 byte (bit 0: is_broadcast, bit 1: requires_ack)
1235    /// reply_to_node:     4 bytes (LE)
1236    /// reply_to_timestamp: 8 bytes (LE)
1237    /// ```
1238    pub fn encode(&self) -> Vec<u8> {
1239        let size = 4 + 8 + 1 + self.sender_len as usize + 1 + self.text_len as usize + 1 + 4 + 8;
1240        let mut buf = Vec::with_capacity(size);
1241
1242        buf.extend_from_slice(&self.origin_node.to_le_bytes());
1243        buf.extend_from_slice(&self.timestamp.to_le_bytes());
1244        buf.push(self.sender_len);
1245        buf.extend_from_slice(&self.sender[..self.sender_len as usize]);
1246        buf.push(self.text_len);
1247        buf.extend_from_slice(&self.text[..self.text_len as usize]);
1248
1249        let mut flags = 0u8;
1250        if self.is_broadcast {
1251            flags |= 0x01;
1252        }
1253        if self.requires_ack {
1254            flags |= 0x02;
1255        }
1256        buf.push(flags);
1257
1258        buf.extend_from_slice(&self.reply_to_node.to_le_bytes());
1259        buf.extend_from_slice(&self.reply_to_timestamp.to_le_bytes());
1260
1261        buf
1262    }
1263
1264    /// Decode from bytes with strict validation
1265    ///
1266    /// Returns `None` if the data is malformed or fails validation checks.
1267    /// This is a security-critical function - malformed messages could be
1268    /// spoofed or part of an attack.
1269    ///
1270    /// # Validation checks:
1271    /// - origin_node must be non-zero
1272    /// - timestamp must be non-zero and within reasonable bounds
1273    /// - sender must be non-empty and valid UTF-8
1274    /// - text must be valid UTF-8 (can be empty for ACK messages)
1275    /// - All length fields must be within bounds
1276    pub fn decode(data: &[u8]) -> Option<(Self, usize)> {
1277        if data.len() < 14 {
1278            // Minimum: 4 + 8 + 1 + 0 + 1 + 0 + 0 (no reply fields in old format)
1279            return None;
1280        }
1281
1282        let origin_node = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1283        let timestamp = u64::from_le_bytes([
1284            data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
1285        ]);
1286
1287        // Security: Reject messages with zero origin_node (invalid/spoofed)
1288        if origin_node == 0 {
1289            return None;
1290        }
1291
1292        // Security: Reject messages with timestamps before 2020
1293        if timestamp < MIN_VALID_TIMESTAMP {
1294            return None;
1295        }
1296
1297        let sender_len = data[12] as usize;
1298        if sender_len > CHAT_MAX_SENDER_LEN || data.len() < 13 + sender_len + 1 {
1299            return None;
1300        }
1301
1302        // Security: Reject messages with empty sender (required field)
1303        if sender_len == 0 {
1304            return None;
1305        }
1306
1307        let mut sender = [0u8; CHAT_MAX_SENDER_LEN];
1308        sender[..sender_len].copy_from_slice(&data[13..13 + sender_len]);
1309
1310        // Security: Validate sender is valid UTF-8 (reject garbage/binary data)
1311        if core::str::from_utf8(&sender[..sender_len]).is_err() {
1312            return None;
1313        }
1314
1315        let text_offset = 13 + sender_len;
1316        let text_len = data[text_offset] as usize;
1317        if text_len > CHAT_MAX_TEXT_LEN || data.len() < text_offset + 1 + text_len + 1 {
1318            return None;
1319        }
1320
1321        let mut text = [0u8; CHAT_MAX_TEXT_LEN];
1322        text[..text_len].copy_from_slice(&data[text_offset + 1..text_offset + 1 + text_len]);
1323
1324        // Security: Validate text is valid UTF-8 (reject garbage/binary data)
1325        // Empty text is allowed (e.g., for ACK messages)
1326        if text_len > 0 && core::str::from_utf8(&text[..text_len]).is_err() {
1327            return None;
1328        }
1329
1330        let flags_offset = text_offset + 1 + text_len;
1331        let flags = data[flags_offset];
1332        let is_broadcast = flags & 0x01 != 0;
1333        let requires_ack = flags & 0x02 != 0;
1334
1335        // Reply-to fields (optional for backward compat)
1336        let mut reply_to_node = 0u32;
1337        let mut reply_to_timestamp = 0u64;
1338        let mut total_len = flags_offset + 1;
1339
1340        if data.len() >= flags_offset + 1 + 12 {
1341            reply_to_node = u32::from_le_bytes([
1342                data[flags_offset + 1],
1343                data[flags_offset + 2],
1344                data[flags_offset + 3],
1345                data[flags_offset + 4],
1346            ]);
1347            reply_to_timestamp = u64::from_le_bytes([
1348                data[flags_offset + 5],
1349                data[flags_offset + 6],
1350                data[flags_offset + 7],
1351                data[flags_offset + 8],
1352                data[flags_offset + 9],
1353                data[flags_offset + 10],
1354                data[flags_offset + 11],
1355                data[flags_offset + 12],
1356            ]);
1357            total_len = flags_offset + 13;
1358        }
1359
1360        Some((
1361            Self {
1362                origin_node,
1363                timestamp,
1364                sender,
1365                sender_len: sender_len as u8,
1366                text,
1367                text_len: text_len as u8,
1368                is_broadcast,
1369                requires_ack,
1370                reply_to_node,
1371                reply_to_timestamp,
1372            },
1373            total_len,
1374        ))
1375    }
1376}
1377
1378/// Chat CRDT - Add-only set of messages
1379///
1380/// Implements add-only set semantics where messages are identified by
1381/// `(origin_node, timestamp)`. Once a message is added, it cannot be removed
1382/// (tombstone-free design optimized for mesh networks).
1383///
1384/// ## CRDT Semantics
1385///
1386/// - **Merge**: Union of all messages from both sets
1387/// - **Identity**: `(origin_node, timestamp)` - duplicates are ignored
1388/// - **Ordering**: Messages are stored sorted by message_id for efficient iteration
1389/// - **Pruning**: Oldest messages are removed when exceeding `CHAT_MAX_MESSAGES`
1390///
1391/// ## Wire Format
1392///
1393/// ```text
1394/// num_messages: 2 bytes (LE)
1395/// messages[N]:  variable (see ChatMessage::encode)
1396/// ```
1397#[derive(Debug, Clone, Default)]
1398pub struct ChatCRDT {
1399    /// Messages indexed by message_id for deduplication
1400    messages: BTreeMap<u64, ChatMessage>,
1401}
1402
1403impl ChatCRDT {
1404    /// Create a new empty chat CRDT
1405    pub fn new() -> Self {
1406        Self {
1407            messages: BTreeMap::new(),
1408        }
1409    }
1410
1411    /// Add a message to the chat
1412    ///
1413    /// Returns `true` if the message was new (not a duplicate)
1414    pub fn add_message(&mut self, message: ChatMessage) -> bool {
1415        let id = message.message_id();
1416        if self.messages.contains_key(&id) {
1417            return false;
1418        }
1419
1420        self.messages.insert(id, message);
1421        self.prune_if_needed();
1422        true
1423    }
1424
1425    /// Create and add a new message
1426    pub fn send_message(
1427        &mut self,
1428        origin_node: u32,
1429        timestamp: u64,
1430        sender: &str,
1431        text: &str,
1432    ) -> bool {
1433        let msg = ChatMessage::new(origin_node, timestamp, sender, text);
1434        self.add_message(msg)
1435    }
1436
1437    /// Get a message by its ID
1438    pub fn get_message(&self, origin_node: u32, timestamp: u64) -> Option<&ChatMessage> {
1439        let id = ((origin_node as u64) << 32) | (timestamp & 0xFFFFFFFF);
1440        self.messages.get(&id)
1441    }
1442
1443    /// Get all messages, ordered by message_id
1444    pub fn messages(&self) -> impl Iterator<Item = &ChatMessage> {
1445        self.messages.values()
1446    }
1447
1448    /// Get messages newer than a given timestamp
1449    pub fn messages_since(&self, since_timestamp: u64) -> impl Iterator<Item = &ChatMessage> {
1450        self.messages
1451            .values()
1452            .filter(move |m| m.timestamp > since_timestamp)
1453    }
1454
1455    /// Get the number of messages
1456    pub fn len(&self) -> usize {
1457        self.messages.len()
1458    }
1459
1460    /// Check if there are no messages
1461    pub fn is_empty(&self) -> bool {
1462        self.messages.is_empty()
1463    }
1464
1465    /// Get the newest message timestamp (if any)
1466    pub fn newest_timestamp(&self) -> Option<u64> {
1467        self.messages.values().map(|m| m.timestamp).max()
1468    }
1469
1470    /// Merge with another ChatCRDT
1471    ///
1472    /// Returns `true` if any new messages were added
1473    pub fn merge(&mut self, other: &ChatCRDT) -> bool {
1474        let mut changed = false;
1475        for (id, msg) in &other.messages {
1476            if !self.messages.contains_key(id) {
1477                self.messages.insert(*id, msg.clone());
1478                changed = true;
1479            }
1480        }
1481        if changed {
1482            self.prune_if_needed();
1483        }
1484        changed
1485    }
1486
1487    /// Prune oldest messages if we exceed the limit
1488    fn prune_if_needed(&mut self) {
1489        while self.messages.len() > CHAT_MAX_MESSAGES {
1490            // Remove the entry with the lowest timestamp
1491            if let Some(&oldest_id) = self.messages.keys().next() {
1492                self.messages.remove(&oldest_id);
1493            }
1494        }
1495    }
1496
1497    /// Encode to bytes for transmission
1498    pub fn encode(&self) -> Vec<u8> {
1499        let mut buf = Vec::new();
1500
1501        // Number of messages
1502        buf.extend_from_slice(&(self.messages.len() as u16).to_le_bytes());
1503
1504        // Each message
1505        for msg in self.messages.values() {
1506            buf.extend_from_slice(&msg.encode());
1507        }
1508
1509        buf
1510    }
1511
1512    /// Decode from bytes
1513    pub fn decode(data: &[u8]) -> Option<Self> {
1514        if data.len() < 2 {
1515            return None;
1516        }
1517
1518        let num_messages = u16::from_le_bytes([data[0], data[1]]) as usize;
1519        let mut messages = BTreeMap::new();
1520        let mut offset = 2;
1521
1522        for _ in 0..num_messages {
1523            if offset >= data.len() {
1524                break;
1525            }
1526            if let Some((msg, len)) = ChatMessage::decode(&data[offset..]) {
1527                let id = msg.message_id();
1528                messages.insert(id, msg);
1529                offset += len;
1530            } else {
1531                break;
1532            }
1533        }
1534
1535        Some(Self { messages })
1536    }
1537
1538    /// Get the encoded size of this CRDT
1539    pub fn encoded_size(&self) -> usize {
1540        2 + self
1541            .messages
1542            .values()
1543            .map(|m| m.encode().len())
1544            .sum::<usize>()
1545    }
1546
1547    /// Create a copy limited to the most recent messages for sync
1548    ///
1549    /// Returns a new ChatCRDT containing only the N most recent messages,
1550    /// where N is `CHAT_SYNC_LIMIT`. This is used when building sync documents
1551    /// to avoid exceeding BLE MTU limits.
1552    ///
1553    /// The local CRDT retains all messages up to `CHAT_MAX_MESSAGES`.
1554    pub fn for_sync(&self) -> Self {
1555        if self.messages.len() <= CHAT_SYNC_LIMIT {
1556            return self.clone();
1557        }
1558
1559        // BTreeMap is ordered by key (message_id), and message_id encodes
1560        // timestamp in lower bits, so we take from the end for newest
1561        let messages: BTreeMap<u64, ChatMessage> = self
1562            .messages
1563            .iter()
1564            .rev()
1565            .take(CHAT_SYNC_LIMIT)
1566            .map(|(&k, v)| (k, v.clone()))
1567            .collect();
1568
1569        Self { messages }
1570    }
1571}
1572
1573/// CRDT operation types for sync
1574#[derive(Debug, Clone)]
1575pub enum CrdtOperation {
1576    /// Update a position register
1577    UpdatePosition {
1578        /// Node ID that owns this position
1579        node_id: NodeId,
1580        /// Position data
1581        position: Position,
1582        /// Timestamp of the update
1583        timestamp: Timestamp,
1584    },
1585    /// Update health status register
1586    UpdateHealth {
1587        /// Node ID that owns this status
1588        node_id: NodeId,
1589        /// Health status data
1590        status: HealthStatus,
1591        /// Timestamp of the update
1592        timestamp: Timestamp,
1593    },
1594    /// Increment a counter
1595    IncrementCounter {
1596        /// Counter identifier
1597        counter_id: u8,
1598        /// Node performing the increment
1599        node_id: NodeId,
1600        /// Amount to increment
1601        amount: u64,
1602    },
1603    /// Generic LWW update (key-value)
1604    UpdateRegister {
1605        /// Key for the register
1606        key: String,
1607        /// Value data
1608        value: Vec<u8>,
1609        /// Timestamp of the update
1610        timestamp: Timestamp,
1611        /// Node that set the value
1612        node_id: NodeId,
1613    },
1614}
1615
1616impl CrdtOperation {
1617    /// Get the approximate size in bytes
1618    pub fn size(&self) -> usize {
1619        match self {
1620            CrdtOperation::UpdatePosition { position, .. } => 4 + 8 + position.encode().len(),
1621            CrdtOperation::UpdateHealth { status, .. } => 4 + 8 + status.encode().len(),
1622            CrdtOperation::IncrementCounter { .. } => 1 + 4 + 8,
1623            CrdtOperation::UpdateRegister { key, value, .. } => 4 + 8 + key.len() + value.len(),
1624        }
1625    }
1626
1627    /// Encode to bytes
1628    pub fn encode(&self) -> Vec<u8> {
1629        let mut buf = Vec::new();
1630        match self {
1631            CrdtOperation::UpdatePosition {
1632                node_id,
1633                position,
1634                timestamp,
1635            } => {
1636                buf.push(0x01); // Type tag
1637                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1638                buf.extend_from_slice(&timestamp.to_le_bytes());
1639                buf.extend_from_slice(&position.encode());
1640            }
1641            CrdtOperation::UpdateHealth {
1642                node_id,
1643                status,
1644                timestamp,
1645            } => {
1646                buf.push(0x02); // Type tag
1647                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1648                buf.extend_from_slice(&timestamp.to_le_bytes());
1649                buf.extend_from_slice(&status.encode());
1650            }
1651            CrdtOperation::IncrementCounter {
1652                counter_id,
1653                node_id,
1654                amount,
1655            } => {
1656                buf.push(0x03); // Type tag
1657                buf.push(*counter_id);
1658                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1659                buf.extend_from_slice(&amount.to_le_bytes());
1660            }
1661            CrdtOperation::UpdateRegister {
1662                key,
1663                value,
1664                timestamp,
1665                node_id,
1666            } => {
1667                buf.push(0x04); // Type tag
1668                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
1669                buf.extend_from_slice(&timestamp.to_le_bytes());
1670                buf.push(key.len() as u8);
1671                buf.extend_from_slice(key.as_bytes());
1672                buf.extend_from_slice(&(value.len() as u16).to_le_bytes());
1673                buf.extend_from_slice(value);
1674            }
1675        }
1676        buf
1677    }
1678
1679    /// Decode from bytes
1680    pub fn decode(data: &[u8]) -> Option<Self> {
1681        if data.is_empty() {
1682            return None;
1683        }
1684
1685        match data[0] {
1686            0x01 => {
1687                // UpdatePosition
1688                if data.len() < 13 {
1689                    return None;
1690                }
1691                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1692                let timestamp = u64::from_le_bytes([
1693                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1694                ]);
1695                let position = Position::decode(&data[13..])?;
1696                Some(CrdtOperation::UpdatePosition {
1697                    node_id,
1698                    position,
1699                    timestamp,
1700                })
1701            }
1702            0x02 => {
1703                // UpdateHealth
1704                if data.len() < 13 {
1705                    return None;
1706                }
1707                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1708                let timestamp = u64::from_le_bytes([
1709                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1710                ]);
1711                let status = HealthStatus::decode(&data[13..])?;
1712                Some(CrdtOperation::UpdateHealth {
1713                    node_id,
1714                    status,
1715                    timestamp,
1716                })
1717            }
1718            0x03 => {
1719                // IncrementCounter
1720                if data.len() < 14 {
1721                    return None;
1722                }
1723                let counter_id = data[1];
1724                let node_id = NodeId::new(u32::from_le_bytes([data[2], data[3], data[4], data[5]]));
1725                let amount = u64::from_le_bytes([
1726                    data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13],
1727                ]);
1728                Some(CrdtOperation::IncrementCounter {
1729                    counter_id,
1730                    node_id,
1731                    amount,
1732                })
1733            }
1734            0x04 => {
1735                // UpdateRegister
1736                if data.len() < 14 {
1737                    return None;
1738                }
1739                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1740                let timestamp = u64::from_le_bytes([
1741                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1742                ]);
1743                let key_len = data[13] as usize;
1744                if data.len() < 14 + key_len + 2 {
1745                    return None;
1746                }
1747                let key = core::str::from_utf8(&data[14..14 + key_len])
1748                    .ok()?
1749                    .to_string();
1750                let value_len =
1751                    u16::from_le_bytes([data[14 + key_len], data[15 + key_len]]) as usize;
1752                if data.len() < 16 + key_len + value_len {
1753                    return None;
1754                }
1755                let value = data[16 + key_len..16 + key_len + value_len].to_vec();
1756                Some(CrdtOperation::UpdateRegister {
1757                    key,
1758                    value,
1759                    timestamp,
1760                    node_id,
1761                })
1762            }
1763            _ => None,
1764        }
1765    }
1766}
1767
1768#[cfg(test)]
1769mod tests {
1770    use super::*;
1771
1772    // Valid timestamp for tests: 2024-01-15 00:00:00 UTC
1773    // All tests should use this or timestamps derived from it
1774    const TEST_TIMESTAMP: u64 = 1705276800000;
1775
1776    #[test]
1777    fn test_lww_register_basic() {
1778        let mut reg = LwwRegister::new(42u32, 100, NodeId::new(1));
1779        assert_eq!(*reg.get(), 42);
1780        assert_eq!(reg.timestamp(), 100);
1781
1782        // Higher timestamp wins
1783        assert!(reg.set(99, 200, NodeId::new(2)));
1784        assert_eq!(*reg.get(), 99);
1785
1786        // Lower timestamp loses
1787        assert!(!reg.set(50, 150, NodeId::new(3)));
1788        assert_eq!(*reg.get(), 99);
1789    }
1790
1791    #[test]
1792    fn test_lww_register_tiebreak() {
1793        let mut reg = LwwRegister::new(1u32, 100, NodeId::new(1));
1794
1795        // Same timestamp, higher node_id wins
1796        assert!(reg.set(2, 100, NodeId::new(2)));
1797        assert_eq!(*reg.get(), 2);
1798
1799        // Same timestamp, lower node_id loses
1800        assert!(!reg.set(3, 100, NodeId::new(1)));
1801        assert_eq!(*reg.get(), 2);
1802    }
1803
1804    #[test]
1805    fn test_lww_register_merge() {
1806        let mut reg1 = LwwRegister::new(1u32, 100, NodeId::new(1));
1807        let reg2 = LwwRegister::new(2u32, 200, NodeId::new(2));
1808
1809        assert!(reg1.merge(&reg2));
1810        assert_eq!(*reg1.get(), 2);
1811    }
1812
1813    #[test]
1814    fn test_gcounter_basic() {
1815        let mut counter = GCounter::new();
1816        let node1 = NodeId::new(1);
1817        let node2 = NodeId::new(2);
1818
1819        counter.increment(&node1, 5);
1820        counter.increment(&node2, 3);
1821        counter.increment(&node1, 2);
1822
1823        assert_eq!(counter.value(), 10);
1824        assert_eq!(counter.node_count(&node1), 7);
1825        assert_eq!(counter.node_count(&node2), 3);
1826    }
1827
1828    #[test]
1829    fn test_gcounter_merge() {
1830        let mut counter1 = GCounter::new();
1831        let mut counter2 = GCounter::new();
1832        let node1 = NodeId::new(1);
1833        let node2 = NodeId::new(2);
1834
1835        counter1.increment(&node1, 5);
1836        counter2.increment(&node1, 3);
1837        counter2.increment(&node2, 4);
1838
1839        counter1.merge(&counter2);
1840
1841        assert_eq!(counter1.value(), 9); // max(5,3) + 4
1842        assert_eq!(counter1.node_count(&node1), 5);
1843        assert_eq!(counter1.node_count(&node2), 4);
1844    }
1845
1846    #[test]
1847    fn test_gcounter_encode_decode() {
1848        let mut counter = GCounter::new();
1849        counter.increment(&NodeId::new(1), 5);
1850        counter.increment(&NodeId::new(2), 10);
1851
1852        let encoded = counter.encode();
1853        let decoded = GCounter::decode(&encoded).unwrap();
1854
1855        assert_eq!(decoded.value(), counter.value());
1856        assert_eq!(decoded.node_count(&NodeId::new(1)), 5);
1857        assert_eq!(decoded.node_count(&NodeId::new(2)), 10);
1858    }
1859
1860    #[test]
1861    fn test_position_encode_decode() {
1862        let pos = Position::new(37.7749, -122.4194)
1863            .with_altitude(100.0)
1864            .with_accuracy(5.0);
1865
1866        let encoded = pos.encode();
1867        let decoded = Position::decode(&encoded).unwrap();
1868
1869        assert_eq!(decoded.latitude, pos.latitude);
1870        assert_eq!(decoded.longitude, pos.longitude);
1871        assert_eq!(decoded.altitude, pos.altitude);
1872        assert_eq!(decoded.accuracy, pos.accuracy);
1873    }
1874
1875    #[test]
1876    fn test_position_minimal_encode() {
1877        let pos = Position::new(0.0, 0.0);
1878        let encoded = pos.encode();
1879        assert_eq!(encoded.len(), 9); // lat + lon + flags
1880
1881        let pos_with_alt = Position::new(0.0, 0.0).with_altitude(0.0);
1882        let encoded_alt = pos_with_alt.encode();
1883        assert_eq!(encoded_alt.len(), 13);
1884    }
1885
1886    #[test]
1887    fn test_health_status() {
1888        let mut status = HealthStatus::new(85).with_heart_rate(72).with_activity(1);
1889
1890        assert_eq!(status.battery_percent, 85);
1891        assert_eq!(status.heart_rate, Some(72));
1892        assert!(!status.has_alert(HealthStatus::ALERT_MAN_DOWN));
1893
1894        status.set_alert(HealthStatus::ALERT_MAN_DOWN);
1895        assert!(status.has_alert(HealthStatus::ALERT_MAN_DOWN));
1896
1897        let encoded = status.encode();
1898        let decoded = HealthStatus::decode(&encoded).unwrap();
1899        assert_eq!(decoded.battery_percent, 85);
1900        assert_eq!(decoded.heart_rate, Some(72));
1901        assert!(decoded.has_alert(HealthStatus::ALERT_MAN_DOWN));
1902    }
1903
1904    #[test]
1905    fn test_crdt_operation_position() {
1906        let op = CrdtOperation::UpdatePosition {
1907            node_id: NodeId::new(0x1234),
1908            position: Position::new(37.0, -122.0),
1909            timestamp: 1000,
1910        };
1911
1912        let encoded = op.encode();
1913        let decoded = CrdtOperation::decode(&encoded).unwrap();
1914
1915        if let CrdtOperation::UpdatePosition {
1916            node_id,
1917            position,
1918            timestamp,
1919        } = decoded
1920        {
1921            assert_eq!(node_id.as_u32(), 0x1234);
1922            assert_eq!(timestamp, 1000);
1923            assert_eq!(position.latitude, 37.0);
1924        } else {
1925            panic!("Wrong operation type");
1926        }
1927    }
1928
1929    #[test]
1930    fn test_crdt_operation_counter() {
1931        let op = CrdtOperation::IncrementCounter {
1932            counter_id: 1,
1933            node_id: NodeId::new(0x5678),
1934            amount: 42,
1935        };
1936
1937        let encoded = op.encode();
1938        let decoded = CrdtOperation::decode(&encoded).unwrap();
1939
1940        if let CrdtOperation::IncrementCounter {
1941            counter_id,
1942            node_id,
1943            amount,
1944        } = decoded
1945        {
1946            assert_eq!(counter_id, 1);
1947            assert_eq!(node_id.as_u32(), 0x5678);
1948            assert_eq!(amount, 42);
1949        } else {
1950            panic!("Wrong operation type");
1951        }
1952    }
1953
1954    #[test]
1955    fn test_crdt_operation_size() {
1956        let pos_op = CrdtOperation::UpdatePosition {
1957            node_id: NodeId::new(1),
1958            position: Position::new(0.0, 0.0),
1959            timestamp: 0,
1960        };
1961        assert!(pos_op.size() > 0);
1962
1963        let counter_op = CrdtOperation::IncrementCounter {
1964            counter_id: 0,
1965            node_id: NodeId::new(1),
1966            amount: 1,
1967        };
1968        assert_eq!(counter_op.size(), 13);
1969    }
1970
1971    // ============================================================================
1972    // Peripheral Tests
1973    // ============================================================================
1974
1975    #[test]
1976    fn test_peripheral_type_from_u8() {
1977        assert_eq!(PeripheralType::from_u8(0), PeripheralType::Unknown);
1978        assert_eq!(PeripheralType::from_u8(1), PeripheralType::SoldierSensor);
1979        assert_eq!(PeripheralType::from_u8(2), PeripheralType::FixedSensor);
1980        assert_eq!(PeripheralType::from_u8(3), PeripheralType::Relay);
1981        assert_eq!(PeripheralType::from_u8(99), PeripheralType::Unknown);
1982    }
1983
1984    #[test]
1985    fn test_event_type_from_u8() {
1986        assert_eq!(EventType::from_u8(0), EventType::None);
1987        assert_eq!(EventType::from_u8(1), EventType::Ping);
1988        assert_eq!(EventType::from_u8(2), EventType::NeedAssist);
1989        assert_eq!(EventType::from_u8(3), EventType::Emergency);
1990        assert_eq!(EventType::from_u8(4), EventType::Moving);
1991        assert_eq!(EventType::from_u8(5), EventType::InPosition);
1992        assert_eq!(EventType::from_u8(6), EventType::Ack);
1993        assert_eq!(EventType::from_u8(99), EventType::None);
1994    }
1995
1996    #[test]
1997    fn test_event_type_labels() {
1998        assert_eq!(EventType::None.label(), "");
1999        assert_eq!(EventType::Emergency.label(), "EMERGENCY");
2000        assert_eq!(EventType::Ping.label(), "PING");
2001    }
2002
2003    #[test]
2004    fn test_peripheral_event_encode_decode() {
2005        let event = PeripheralEvent::new(EventType::Emergency, TEST_TIMESTAMP);
2006        let encoded = event.encode();
2007        assert_eq!(encoded.len(), 9);
2008
2009        let decoded = PeripheralEvent::decode(&encoded).unwrap();
2010        assert_eq!(decoded.event_type, EventType::Emergency);
2011        assert_eq!(decoded.timestamp, TEST_TIMESTAMP);
2012    }
2013
2014    #[test]
2015    fn test_peripheral_new() {
2016        let peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor);
2017        assert_eq!(peripheral.id, 0x12345678);
2018        assert_eq!(peripheral.peripheral_type, PeripheralType::SoldierSensor);
2019        assert_eq!(peripheral.parent_node, 0);
2020        assert!(peripheral.last_event.is_none());
2021    }
2022
2023    #[test]
2024    fn test_peripheral_with_callsign() {
2025        let peripheral = Peripheral::new(1, PeripheralType::SoldierSensor).with_callsign("ALPHA-1");
2026        assert_eq!(peripheral.callsign_str(), "ALPHA-1");
2027
2028        // Test truncation
2029        let peripheral2 = Peripheral::new(2, PeripheralType::SoldierSensor)
2030            .with_callsign("THIS_IS_A_VERY_LONG_CALLSIGN");
2031        assert_eq!(peripheral2.callsign_str(), "THIS_IS_A_VE");
2032    }
2033
2034    #[test]
2035    fn test_peripheral_set_event() {
2036        let mut peripheral = Peripheral::new(1, PeripheralType::SoldierSensor);
2037        peripheral.set_event(EventType::Emergency, TEST_TIMESTAMP);
2038
2039        assert!(peripheral.last_event.is_some());
2040        let event = peripheral.last_event.as_ref().unwrap();
2041        assert_eq!(event.event_type, EventType::Emergency);
2042        assert_eq!(event.timestamp, TEST_TIMESTAMP);
2043        assert_eq!(peripheral.timestamp, TEST_TIMESTAMP);
2044
2045        peripheral.clear_event();
2046        assert!(peripheral.last_event.is_none());
2047    }
2048
2049    #[test]
2050    fn test_peripheral_encode_decode_without_event() {
2051        let peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor)
2052            .with_callsign("BRAVO-2")
2053            .with_parent(0x11223344);
2054
2055        let encoded = peripheral.encode();
2056        assert_eq!(encoded.len(), 35); // No event + 1 byte has_location flag
2057
2058        let decoded = Peripheral::decode(&encoded).unwrap();
2059        assert_eq!(decoded.id, 0xAABBCCDD);
2060        assert_eq!(decoded.parent_node, 0x11223344);
2061        assert_eq!(decoded.peripheral_type, PeripheralType::SoldierSensor);
2062        assert_eq!(decoded.callsign_str(), "BRAVO-2");
2063        assert!(decoded.last_event.is_none());
2064        assert!(decoded.location.is_none());
2065    }
2066
2067    #[test]
2068    fn test_peripheral_encode_decode_with_event() {
2069        let mut peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor)
2070            .with_callsign("CHARLIE")
2071            .with_parent(0x87654321);
2072        peripheral.health = HealthStatus::new(85);
2073        peripheral.set_event(EventType::NeedAssist, TEST_TIMESTAMP);
2074
2075        let encoded = peripheral.encode();
2076        assert_eq!(encoded.len(), 44); // With event + 1 byte has_location flag
2077
2078        let decoded = Peripheral::decode(&encoded).unwrap();
2079        assert_eq!(decoded.id, 0x12345678);
2080        assert_eq!(decoded.parent_node, 0x87654321);
2081        assert_eq!(decoded.callsign_str(), "CHARLIE");
2082        assert_eq!(decoded.health.battery_percent, 85);
2083        assert!(decoded.last_event.is_some());
2084        let event = decoded.last_event.as_ref().unwrap();
2085        assert_eq!(event.event_type, EventType::NeedAssist);
2086        assert_eq!(event.timestamp, TEST_TIMESTAMP);
2087        assert!(decoded.location.is_none());
2088    }
2089
2090    #[test]
2091    fn test_peripheral_encode_decode_with_location() {
2092        let location = Position::new(37.7749, -122.4194).with_altitude(10.0);
2093        let peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor)
2094            .with_callsign("DELTA")
2095            .with_location(location);
2096
2097        let encoded = peripheral.encode();
2098        // 35 base + 13 location bytes (lat:4 + lon:4 + flags:1 + alt:4)
2099        assert_eq!(encoded.len(), 48);
2100
2101        let decoded = Peripheral::decode(&encoded).unwrap();
2102        assert_eq!(decoded.id, 0x12345678);
2103        assert_eq!(decoded.callsign_str(), "DELTA");
2104        assert!(decoded.location.is_some());
2105
2106        let loc = decoded.location.unwrap();
2107        assert!((loc.latitude - 37.7749).abs() < 0.0001);
2108        assert!((loc.longitude - (-122.4194)).abs() < 0.0001);
2109        assert!(loc.altitude.is_some());
2110        assert!((loc.altitude.unwrap() - 10.0).abs() < 1.0);
2111    }
2112
2113    #[test]
2114    fn test_peripheral_decode_invalid_data() {
2115        // Too short
2116        assert!(Peripheral::decode(&[0u8; 10]).is_none());
2117
2118        // Valid length but id=0 is rejected
2119        let mut data = vec![0u8; 34];
2120        data[25] = 0; // no event flag
2121        assert!(Peripheral::decode(&data).is_none()); // id=0 rejected
2122
2123        // Valid id but no event - should succeed
2124        data[0..4].copy_from_slice(&1u32.to_le_bytes()); // id=1
2125        assert!(Peripheral::decode(&data).is_some());
2126
2127        // Claims to have event but too short
2128        data[25] = 1; // has event flag
2129        assert!(Peripheral::decode(&data).is_none());
2130    }
2131
2132    // ============================================================================
2133    // EmergencyEvent Tests
2134    // ============================================================================
2135
2136    #[test]
2137    fn test_emergency_event_new() {
2138        let peers = vec![0x22222222, 0x33333333];
2139        let event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2140
2141        assert_eq!(event.source_node(), 0x11111111);
2142        assert_eq!(event.timestamp(), TEST_TIMESTAMP);
2143        assert_eq!(event.peer_count(), 3); // source + 2 peers
2144
2145        // Source is auto-acked
2146        assert!(event.has_acked(0x11111111));
2147        // Others are not
2148        assert!(!event.has_acked(0x22222222));
2149        assert!(!event.has_acked(0x33333333));
2150    }
2151
2152    #[test]
2153    fn test_emergency_event_ack() {
2154        let peers = vec![0x22222222, 0x33333333];
2155        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2156
2157        assert_eq!(event.ack_count(), 1); // just source
2158        assert!(!event.all_acked());
2159
2160        // ACK from first peer
2161        assert!(event.ack(0x22222222)); // returns true - new ack
2162        assert_eq!(event.ack_count(), 2);
2163        assert!(!event.all_acked());
2164
2165        // Duplicate ACK
2166        assert!(!event.ack(0x22222222)); // returns false - already acked
2167
2168        // ACK from second peer
2169        assert!(event.ack(0x33333333));
2170        assert_eq!(event.ack_count(), 3);
2171        assert!(event.all_acked());
2172    }
2173
2174    #[test]
2175    fn test_emergency_event_pending_nodes() {
2176        let peers = vec![0x22222222, 0x33333333];
2177        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2178
2179        let pending = event.pending_nodes();
2180        assert_eq!(pending.len(), 2);
2181        assert!(pending.contains(&0x22222222));
2182        assert!(pending.contains(&0x33333333));
2183
2184        event.ack(0x22222222);
2185        let pending = event.pending_nodes();
2186        assert_eq!(pending.len(), 1);
2187        assert!(pending.contains(&0x33333333));
2188    }
2189
2190    #[test]
2191    fn test_emergency_event_encode_decode() {
2192        let peers = vec![0x22222222, 0x33333333];
2193        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2194        event.ack(0x22222222);
2195
2196        let encoded = event.encode();
2197        let decoded = EmergencyEvent::decode(&encoded).unwrap();
2198
2199        assert_eq!(decoded.source_node(), 0x11111111);
2200        assert_eq!(decoded.timestamp(), TEST_TIMESTAMP);
2201        assert!(decoded.has_acked(0x11111111));
2202        assert!(decoded.has_acked(0x22222222));
2203        assert!(!decoded.has_acked(0x33333333));
2204    }
2205
2206    #[test]
2207    fn test_emergency_event_merge_same_event() {
2208        // Two nodes have the same emergency, different ack states
2209        let peers = vec![0x22222222, 0x33333333];
2210        let mut event1 = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2211        let mut event2 = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &peers);
2212
2213        event1.ack(0x22222222);
2214        event2.ack(0x33333333);
2215
2216        // Merge event2 into event1
2217        let changed = event1.merge(&event2);
2218        assert!(changed);
2219        assert!(event1.has_acked(0x22222222));
2220        assert!(event1.has_acked(0x33333333));
2221        assert!(event1.all_acked());
2222    }
2223
2224    #[test]
2225    fn test_emergency_event_merge_different_events() {
2226        // Old emergency
2227        let mut old_event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &[0x22222222]);
2228        old_event.ack(0x22222222);
2229
2230        // New emergency from different source
2231        let new_event =
2232            EmergencyEvent::new(0x33333333, TEST_TIMESTAMP + 1000, &[0x11111111, 0x22222222]);
2233
2234        // Merge new into old - should replace
2235        let changed = old_event.merge(&new_event);
2236        assert!(changed);
2237        assert_eq!(old_event.source_node(), 0x33333333);
2238        assert_eq!(old_event.timestamp(), TEST_TIMESTAMP + 1000);
2239        // Old ack state should be gone
2240        assert!(!old_event.has_acked(0x22222222));
2241    }
2242
2243    #[test]
2244    fn test_emergency_event_merge_older_event_ignored() {
2245        // Current emergency
2246        let mut current = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP + 1000, &[0x22222222]);
2247
2248        // Older emergency
2249        let older = EmergencyEvent::new(0x33333333, TEST_TIMESTAMP, &[0x11111111]);
2250
2251        // Merge older into current - should NOT replace
2252        let changed = current.merge(&older);
2253        assert!(!changed);
2254        assert_eq!(current.source_node(), 0x11111111);
2255        assert_eq!(current.timestamp(), TEST_TIMESTAMP + 1000);
2256    }
2257
2258    #[test]
2259    fn test_emergency_event_add_peer() {
2260        let mut event = EmergencyEvent::new(0x11111111, TEST_TIMESTAMP, &[]);
2261
2262        // Add a peer discovered after emergency started
2263        event.add_peer(0x22222222);
2264        assert!(!event.has_acked(0x22222222));
2265        assert_eq!(event.peer_count(), 2);
2266
2267        // Adding same peer again doesn't change ack status
2268        event.ack(0x22222222);
2269        event.add_peer(0x22222222);
2270        assert!(event.has_acked(0x22222222)); // still acked
2271    }
2272
2273    #[test]
2274    fn test_emergency_event_decode_invalid() {
2275        // Too short
2276        assert!(EmergencyEvent::decode(&[0u8; 10]).is_none());
2277
2278        // Valid header but claims more acks than data
2279        let mut data = vec![0u8; 16];
2280        data[12] = 5; // claims 5 ack entries
2281        assert!(EmergencyEvent::decode(&data).is_none());
2282    }
2283
2284    // ============================================================================
2285    // ChatMessage Tests
2286    // ============================================================================
2287
2288    #[test]
2289    fn test_chat_message_new() {
2290        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "ALPHA-1", "Hello mesh!");
2291        assert_eq!(msg.origin_node, 0x12345678);
2292        assert_eq!(msg.timestamp, TEST_TIMESTAMP);
2293        assert_eq!(msg.sender(), "ALPHA-1");
2294        assert_eq!(msg.text(), "Hello mesh!");
2295        assert!(msg.is_broadcast);
2296        assert!(!msg.requires_ack);
2297        assert!(!msg.is_reply());
2298    }
2299
2300    #[test]
2301    fn test_chat_message_reply_to() {
2302        let mut msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP + 1000, "BRAVO", "Roger that");
2303        msg.set_reply_to(0xAABBCCDD, TEST_TIMESTAMP);
2304
2305        assert!(msg.is_reply());
2306        assert_eq!(msg.reply_to_node, 0xAABBCCDD);
2307        assert_eq!(msg.reply_to_timestamp, TEST_TIMESTAMP);
2308    }
2309
2310    #[test]
2311    fn test_chat_message_truncation() {
2312        // Test sender truncation (max 12 bytes)
2313        let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "VERY_LONG_CALLSIGN", "Hi");
2314        assert_eq!(msg.sender(), "VERY_LONG_CA"); // 12 chars
2315
2316        // Test text truncation (max 128 bytes)
2317        let long_text = "A".repeat(200);
2318        let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "X", &long_text);
2319        assert_eq!(msg.text().len(), 128);
2320    }
2321
2322    #[test]
2323    fn test_chat_message_id() {
2324        let msg = ChatMessage::new(0x12345678, 0x18D4A51_ABCDEF01, "X", "Y");
2325        let id = msg.message_id();
2326        // ID = (origin << 32) | (timestamp & 0xFFFFFFFF)
2327        assert_eq!(id, (0x12345678u64 << 32) | 0xABCDEF01);
2328    }
2329
2330    #[test]
2331    fn test_chat_message_encode_decode() {
2332        let mut msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "CHARLIE", "Test message");
2333        msg.is_broadcast = true;
2334        msg.requires_ack = true;
2335        msg.set_reply_to(0xAABBCCDD, TEST_TIMESTAMP - 1000);
2336
2337        let encoded = msg.encode();
2338        let (decoded, len) = ChatMessage::decode(&encoded).unwrap();
2339
2340        assert_eq!(len, encoded.len());
2341        assert_eq!(decoded.origin_node, 0x12345678);
2342        assert_eq!(decoded.timestamp, TEST_TIMESTAMP);
2343        assert_eq!(decoded.sender(), "CHARLIE");
2344        assert_eq!(decoded.text(), "Test message");
2345        assert!(decoded.is_broadcast);
2346        assert!(decoded.requires_ack);
2347        assert_eq!(decoded.reply_to_node, 0xAABBCCDD);
2348        assert_eq!(decoded.reply_to_timestamp, TEST_TIMESTAMP - 1000);
2349    }
2350
2351    #[test]
2352    fn test_chat_message_decode_empty_text() {
2353        // Message with valid sender but empty text (allowed for ACK messages)
2354        let msg = ChatMessage::new(0x1, TEST_TIMESTAMP, "ACK-NODE", "");
2355        let encoded = msg.encode();
2356        let (decoded, _) = ChatMessage::decode(&encoded).unwrap();
2357        assert_eq!(decoded.sender(), "ACK-NODE");
2358        assert_eq!(decoded.text(), "");
2359    }
2360
2361    // ============================================================================
2362    // ChatMessage Validation/Security Tests
2363    // ============================================================================
2364
2365    #[test]
2366    fn test_chat_message_decode_rejects_zero_origin() {
2367        // Build message bytes manually with origin_node = 0
2368        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "TEST", "msg");
2369        let mut encoded = msg.encode();
2370        // Zero out the origin_node (first 4 bytes)
2371        encoded[0] = 0;
2372        encoded[1] = 0;
2373        encoded[2] = 0;
2374        encoded[3] = 0;
2375
2376        assert!(ChatMessage::decode(&encoded).is_none());
2377    }
2378
2379    #[test]
2380    fn test_chat_message_decode_rejects_old_timestamp() {
2381        // Build message bytes manually with timestamp before 2020
2382        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "TEST", "msg");
2383        let mut encoded = msg.encode();
2384        // Set timestamp to 1000 (way before 2020)
2385        let old_ts: u64 = 1000;
2386        encoded[4..12].copy_from_slice(&old_ts.to_le_bytes());
2387
2388        assert!(ChatMessage::decode(&encoded).is_none());
2389    }
2390
2391    #[test]
2392    fn test_chat_message_decode_rejects_empty_sender() {
2393        // Build message bytes manually with sender_len = 0
2394        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "X", "msg");
2395        let mut encoded = msg.encode();
2396        // Set sender_len to 0 at offset 12
2397        encoded[12] = 0;
2398        // Adjust text position (move text_len and text to right after sender_len)
2399        // This is tricky - we need to rebuild the encoding properly
2400        // Actually easier to just build raw bytes:
2401        let mut raw = Vec::new();
2402        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2403        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2404        raw.push(0); // sender_len = 0 (INVALID)
2405        raw.push(3); // text_len = 3
2406        raw.extend_from_slice(b"msg"); // text
2407        raw.push(0x01); // flags
2408
2409        assert!(ChatMessage::decode(&raw).is_none());
2410    }
2411
2412    #[test]
2413    fn test_chat_message_decode_rejects_invalid_utf8_sender() {
2414        // Build message with invalid UTF-8 in sender
2415        let mut raw = Vec::new();
2416        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2417        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2418        raw.push(4); // sender_len = 4
2419        raw.extend_from_slice(&[0x66, 0x59, 0xFF, 0xFE]); // "fY" + invalid UTF-8
2420        raw.push(3); // text_len = 3
2421        raw.extend_from_slice(b"msg"); // text
2422        raw.push(0x01); // flags
2423        raw.extend_from_slice(&0u32.to_le_bytes()); // reply_to_node
2424        raw.extend_from_slice(&0u64.to_le_bytes()); // reply_to_timestamp
2425
2426        assert!(ChatMessage::decode(&raw).is_none());
2427    }
2428
2429    #[test]
2430    fn test_chat_message_decode_rejects_invalid_utf8_text() {
2431        // Build message with invalid UTF-8 in text
2432        let mut raw = Vec::new();
2433        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2434        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2435        raw.push(4); // sender_len = 4
2436        raw.extend_from_slice(b"TEST"); // valid sender
2437        raw.push(4); // text_len = 4
2438        raw.extend_from_slice(&[0x80, 0x81, 0x82, 0x83]); // invalid UTF-8
2439        raw.push(0x01); // flags
2440        raw.extend_from_slice(&0u32.to_le_bytes()); // reply_to_node
2441        raw.extend_from_slice(&0u64.to_le_bytes()); // reply_to_timestamp
2442
2443        assert!(ChatMessage::decode(&raw).is_none());
2444    }
2445
2446    #[test]
2447    fn test_chat_message_decode_accepts_valid_utf8() {
2448        // Build message with valid UTF-8 including unicode
2449        let mut raw = Vec::new();
2450        raw.extend_from_slice(&0x12345678u32.to_le_bytes()); // origin_node
2451        raw.extend_from_slice(&TEST_TIMESTAMP.to_le_bytes()); // timestamp
2452        raw.push(6); // sender_len = 6 (UTF-8 encoded)
2453        raw.extend_from_slice("TËST".as_bytes()); // valid UTF-8 with umlaut (5 bytes: T, Ë=2bytes, S, T)
2454                                                  // Wait, "TËST" is 5 bytes not 6. Let me fix:
2455        let sender_bytes = "TËST1".as_bytes(); // T(1) + Ë(2) + S(1) + T(1) + 1(1) = 6 bytes
2456        raw[12] = sender_bytes.len() as u8;
2457        raw.truncate(13);
2458        raw.extend_from_slice(sender_bytes);
2459        raw.push(4); // text_len = 4
2460        raw.extend_from_slice(b"test"); // text
2461        raw.push(0x01); // flags
2462        raw.extend_from_slice(&0u32.to_le_bytes()); // reply_to_node
2463        raw.extend_from_slice(&0u64.to_le_bytes()); // reply_to_timestamp
2464
2465        let result = ChatMessage::decode(&raw);
2466        assert!(result.is_some());
2467        let (msg, _) = result.unwrap();
2468        assert_eq!(msg.sender(), "TËST1");
2469    }
2470
2471    // ============================================================================
2472    // ChatCRDT Tests
2473    // ============================================================================
2474
2475    #[test]
2476    fn test_chat_crdt_new() {
2477        let chat = ChatCRDT::new();
2478        assert!(chat.is_empty());
2479        assert_eq!(chat.len(), 0);
2480    }
2481
2482    #[test]
2483    fn test_chat_crdt_add_message() {
2484        let mut chat = ChatCRDT::new();
2485
2486        let msg = ChatMessage::new(0x12345678, TEST_TIMESTAMP, "ALPHA", "Hello");
2487        assert!(chat.add_message(msg.clone()));
2488        assert_eq!(chat.len(), 1);
2489
2490        // Duplicate should be rejected
2491        assert!(!chat.add_message(msg));
2492        assert_eq!(chat.len(), 1);
2493    }
2494
2495    #[test]
2496    fn test_chat_crdt_send_message() {
2497        let mut chat = ChatCRDT::new();
2498
2499        assert!(chat.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "First"));
2500        assert!(chat.send_message(0x2, TEST_TIMESTAMP + 1, "BRAVO", "Second"));
2501        assert_eq!(chat.len(), 2);
2502
2503        // Same node, same timestamp = duplicate
2504        assert!(!chat.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Duplicate"));
2505        assert_eq!(chat.len(), 2);
2506    }
2507
2508    #[test]
2509    fn test_chat_crdt_get_message() {
2510        let mut chat = ChatCRDT::new();
2511        chat.send_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "Test");
2512
2513        let msg = chat.get_message(0x12345678, TEST_TIMESTAMP);
2514        assert!(msg.is_some());
2515        assert_eq!(msg.unwrap().text(), "Test");
2516
2517        // Non-existent message
2518        assert!(chat.get_message(0x99999999, TEST_TIMESTAMP).is_none());
2519    }
2520
2521    #[test]
2522    fn test_chat_crdt_merge() {
2523        let mut chat1 = ChatCRDT::new();
2524        let mut chat2 = ChatCRDT::new();
2525
2526        chat1.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "From 1");
2527        chat2.send_message(0x2, TEST_TIMESTAMP + 1, "BRAVO", "From 2");
2528
2529        // Merge chat2 into chat1
2530        let changed = chat1.merge(&chat2);
2531        assert!(changed);
2532        assert_eq!(chat1.len(), 2);
2533
2534        // Merge again - no changes
2535        let changed = chat1.merge(&chat2);
2536        assert!(!changed);
2537        assert_eq!(chat1.len(), 2);
2538    }
2539
2540    #[test]
2541    fn test_chat_crdt_merge_duplicates() {
2542        let mut chat1 = ChatCRDT::new();
2543        let mut chat2 = ChatCRDT::new();
2544
2545        // Both have the same message
2546        chat1.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Same message");
2547        chat2.send_message(0x1, TEST_TIMESTAMP, "ALPHA", "Same message");
2548
2549        // Merge should not create duplicates
2550        chat1.merge(&chat2);
2551        assert_eq!(chat1.len(), 1);
2552    }
2553
2554    #[test]
2555    fn test_chat_crdt_pruning() {
2556        let mut chat = ChatCRDT::new();
2557
2558        // Add more than CHAT_MAX_MESSAGES with valid timestamps
2559        for i in 0..(CHAT_MAX_MESSAGES + 10) {
2560            chat.send_message(i as u32 + 1, TEST_TIMESTAMP + i as u64, "X", "Y");
2561        }
2562
2563        // Should be pruned to max
2564        assert_eq!(chat.len(), CHAT_MAX_MESSAGES);
2565
2566        // Oldest messages should be removed (first 10)
2567        // Message IDs are (node << 32) | (timestamp & 0xFFFFFFFF)
2568        // node=1, ts=TEST_TIMESTAMP has lowest ID, etc.
2569        assert!(chat.get_message(1, TEST_TIMESTAMP).is_none());
2570        assert!(chat.get_message(10, TEST_TIMESTAMP + 9).is_none());
2571        // Newer messages should remain
2572        assert!(chat.get_message(11, TEST_TIMESTAMP + 10).is_some());
2573    }
2574
2575    #[test]
2576    fn test_chat_crdt_encode_decode() {
2577        let mut chat = ChatCRDT::new();
2578        chat.send_message(0x12345678, TEST_TIMESTAMP, "ALPHA", "First message");
2579        chat.send_message(0xAABBCCDD, TEST_TIMESTAMP + 1000, "BRAVO", "Second message");
2580
2581        let encoded = chat.encode();
2582        let decoded = ChatCRDT::decode(&encoded).unwrap();
2583
2584        assert_eq!(decoded.len(), 2);
2585        assert!(decoded.get_message(0x12345678, TEST_TIMESTAMP).is_some());
2586        assert!(decoded
2587            .get_message(0xAABBCCDD, TEST_TIMESTAMP + 1000)
2588            .is_some());
2589    }
2590
2591    #[test]
2592    fn test_chat_crdt_messages_since() {
2593        let mut chat = ChatCRDT::new();
2594        chat.send_message(0x1, TEST_TIMESTAMP, "A", "Old");
2595        chat.send_message(0x2, TEST_TIMESTAMP + 1000, "B", "Mid");
2596        chat.send_message(0x3, TEST_TIMESTAMP + 2000, "C", "New");
2597
2598        let recent: Vec<_> = chat.messages_since(TEST_TIMESTAMP + 500).collect();
2599        assert_eq!(recent.len(), 2);
2600    }
2601
2602    #[test]
2603    fn test_chat_crdt_newest_timestamp() {
2604        let mut chat = ChatCRDT::new();
2605        assert!(chat.newest_timestamp().is_none());
2606
2607        chat.send_message(0x1, TEST_TIMESTAMP, "A", "1");
2608        assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP));
2609
2610        chat.send_message(0x2, TEST_TIMESTAMP + 2000, "B", "2");
2611        assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP + 2000));
2612
2613        chat.send_message(0x3, TEST_TIMESTAMP + 1000, "C", "3"); // older timestamp
2614        assert_eq!(chat.newest_timestamp(), Some(TEST_TIMESTAMP + 2000));
2615    }
2616
2617    // ============================================================================
2618    // ChatCRDT Security/Validation Tests
2619    // ============================================================================
2620
2621    #[test]
2622    fn test_chat_crdt_decode_skips_invalid_messages() {
2623        // Build a CRDT with 2 messages, one valid and one invalid
2624        let mut valid_chat = ChatCRDT::new();
2625        valid_chat.send_message(0x12345678, TEST_TIMESTAMP, "VALID", "Good message");
2626
2627        let encoded = valid_chat.encode();
2628
2629        // Decode should work and contain the valid message
2630        let decoded = ChatCRDT::decode(&encoded).unwrap();
2631        assert_eq!(decoded.len(), 1);
2632        assert!(decoded.get_message(0x12345678, TEST_TIMESTAMP).is_some());
2633    }
2634
2635    #[test]
2636    fn test_chat_crdt_decode_handles_truncated_data() {
2637        let mut chat = ChatCRDT::new();
2638        chat.send_message(0x12345678, TEST_TIMESTAMP, "TEST", "Message");
2639
2640        let encoded = chat.encode();
2641
2642        // Truncate to just the message count
2643        let truncated = &encoded[..2];
2644        let decoded = ChatCRDT::decode(truncated);
2645        assert!(decoded.is_some());
2646        assert_eq!(decoded.unwrap().len(), 0); // No messages decoded
2647    }
2648}