hive_btle/sync/
crdt.rs

1//! CRDT (Conflict-free Replicated Data Types) for HIVE-Lite
2//!
3//! Provides lightweight CRDT implementations optimized for BLE sync:
4//! - LWW-Register: Last-Writer-Wins for single values
5//! - G-Counter: Grow-only counter for metrics
6//!
7//! These are designed for minimal memory footprint and efficient
8//! serialization over constrained BLE connections.
9
10#[cfg(not(feature = "std"))]
11use alloc::{collections::BTreeMap, string::String, string::ToString, vec, vec::Vec};
12#[cfg(feature = "std")]
13use std::collections::BTreeMap;
14
15use crate::NodeId;
16
17/// Timestamp for CRDT operations (milliseconds since epoch or monotonic)
18pub type Timestamp = u64;
19
20/// A Last-Writer-Wins Register
21///
22/// Stores a single value where concurrent writes are resolved by timestamp.
23/// Higher timestamp wins. In case of tie, higher node ID wins.
24#[derive(Debug, Clone, PartialEq)]
25pub struct LwwRegister<T: Clone> {
26    /// Current value
27    value: T,
28    /// Timestamp when value was set
29    timestamp: Timestamp,
30    /// Node that set the value
31    node_id: NodeId,
32}
33
34impl<T: Clone + Default> Default for LwwRegister<T> {
35    fn default() -> Self {
36        Self {
37            value: T::default(),
38            timestamp: 0,
39            node_id: NodeId::default(),
40        }
41    }
42}
43
44impl<T: Clone> LwwRegister<T> {
45    /// Create a new register with an initial value
46    pub fn new(value: T, timestamp: Timestamp, node_id: NodeId) -> Self {
47        Self {
48            value,
49            timestamp,
50            node_id,
51        }
52    }
53
54    /// Get the current value
55    pub fn get(&self) -> &T {
56        &self.value
57    }
58
59    /// Get the timestamp
60    pub fn timestamp(&self) -> Timestamp {
61        self.timestamp
62    }
63
64    /// Get the node that set the value
65    pub fn node_id(&self) -> &NodeId {
66        &self.node_id
67    }
68
69    /// Set a new value if it has a higher timestamp
70    ///
71    /// Returns true if the value was updated
72    pub fn set(&mut self, value: T, timestamp: Timestamp, node_id: NodeId) -> bool {
73        if self.should_update(timestamp, &node_id) {
74            self.value = value;
75            self.timestamp = timestamp;
76            self.node_id = node_id;
77            true
78        } else {
79            false
80        }
81    }
82
83    /// Merge with another register (LWW semantics)
84    ///
85    /// Returns true if our value was updated
86    pub fn merge(&mut self, other: &LwwRegister<T>) -> bool {
87        if self.should_update(other.timestamp, &other.node_id) {
88            self.value = other.value.clone();
89            self.timestamp = other.timestamp;
90            self.node_id = other.node_id;
91            true
92        } else {
93            false
94        }
95    }
96
97    /// Check if we should update based on timestamp/node_id
98    fn should_update(&self, timestamp: Timestamp, node_id: &NodeId) -> bool {
99        timestamp > self.timestamp
100            || (timestamp == self.timestamp && node_id.as_u32() > self.node_id.as_u32())
101    }
102}
103
104/// A Grow-only Counter (G-Counter)
105///
106/// Each node maintains its own count, total is the sum of all counts.
107/// Only supports increment operations.
108#[derive(Debug, Clone, Default)]
109pub struct GCounter {
110    /// Per-node counts
111    counts: BTreeMap<u32, u64>,
112}
113
114impl GCounter {
115    /// Create a new empty counter
116    pub fn new() -> Self {
117        Self {
118            counts: BTreeMap::new(),
119        }
120    }
121
122    /// Get the total count
123    pub fn value(&self) -> u64 {
124        self.counts.values().sum()
125    }
126
127    /// Increment the counter for a node
128    pub fn increment(&mut self, node_id: &NodeId, amount: u64) {
129        let count = self.counts.entry(node_id.as_u32()).or_insert(0);
130        *count = count.saturating_add(amount);
131    }
132
133    /// Get the count for a specific node
134    pub fn node_count(&self, node_id: &NodeId) -> u64 {
135        self.counts.get(&node_id.as_u32()).copied().unwrap_or(0)
136    }
137
138    /// Merge with another counter
139    ///
140    /// Takes the max of each node's count
141    pub fn merge(&mut self, other: &GCounter) {
142        for (&node_id, &count) in &other.counts {
143            let our_count = self.counts.entry(node_id).or_insert(0);
144            *our_count = (*our_count).max(count);
145        }
146    }
147
148    /// Get the number of nodes that have contributed
149    pub fn node_count_total(&self) -> usize {
150        self.counts.len()
151    }
152
153    /// Encode to bytes for transmission
154    pub fn encode(&self) -> Vec<u8> {
155        let mut buf = Vec::with_capacity(4 + self.counts.len() * 12);
156        // Number of entries
157        buf.extend_from_slice(&(self.counts.len() as u32).to_le_bytes());
158        // Each entry: node_id (4 bytes) + count (8 bytes)
159        for (&node_id, &count) in &self.counts {
160            buf.extend_from_slice(&node_id.to_le_bytes());
161            buf.extend_from_slice(&count.to_le_bytes());
162        }
163        buf
164    }
165
166    /// Decode from bytes
167    pub fn decode(data: &[u8]) -> Option<Self> {
168        if data.len() < 4 {
169            return None;
170        }
171        let num_entries = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
172        if data.len() < 4 + num_entries * 12 {
173            return None;
174        }
175
176        let mut counts = BTreeMap::new();
177        let mut offset = 4;
178        for _ in 0..num_entries {
179            let node_id = u32::from_le_bytes([
180                data[offset],
181                data[offset + 1],
182                data[offset + 2],
183                data[offset + 3],
184            ]);
185            let count = u64::from_le_bytes([
186                data[offset + 4],
187                data[offset + 5],
188                data[offset + 6],
189                data[offset + 7],
190                data[offset + 8],
191                data[offset + 9],
192                data[offset + 10],
193                data[offset + 11],
194            ]);
195            counts.insert(node_id, count);
196            offset += 12;
197        }
198
199        Some(Self { counts })
200    }
201}
202
203/// Position data with LWW semantics
204#[derive(Debug, Clone, Default, PartialEq)]
205pub struct Position {
206    /// Latitude in degrees
207    pub latitude: f32,
208    /// Longitude in degrees
209    pub longitude: f32,
210    /// Altitude in meters (optional)
211    pub altitude: Option<f32>,
212    /// Accuracy in meters (optional)
213    pub accuracy: Option<f32>,
214}
215
216impl Position {
217    /// Create a new position
218    pub fn new(latitude: f32, longitude: f32) -> Self {
219        Self {
220            latitude,
221            longitude,
222            altitude: None,
223            accuracy: None,
224        }
225    }
226
227    /// Create with altitude
228    pub fn with_altitude(mut self, altitude: f32) -> Self {
229        self.altitude = Some(altitude);
230        self
231    }
232
233    /// Create with accuracy
234    pub fn with_accuracy(mut self, accuracy: f32) -> Self {
235        self.accuracy = Some(accuracy);
236        self
237    }
238
239    /// Encode to bytes (12-20 bytes)
240    pub fn encode(&self) -> Vec<u8> {
241        let mut buf = Vec::with_capacity(20);
242        buf.extend_from_slice(&self.latitude.to_le_bytes());
243        buf.extend_from_slice(&self.longitude.to_le_bytes());
244
245        // Flags byte: bit 0 = has altitude, bit 1 = has accuracy
246        let mut flags = 0u8;
247        if self.altitude.is_some() {
248            flags |= 0x01;
249        }
250        if self.accuracy.is_some() {
251            flags |= 0x02;
252        }
253        buf.push(flags);
254
255        if let Some(alt) = self.altitude {
256            buf.extend_from_slice(&alt.to_le_bytes());
257        }
258        if let Some(acc) = self.accuracy {
259            buf.extend_from_slice(&acc.to_le_bytes());
260        }
261        buf
262    }
263
264    /// Decode from bytes
265    pub fn decode(data: &[u8]) -> Option<Self> {
266        if data.len() < 9 {
267            return None;
268        }
269
270        let latitude = f32::from_le_bytes([data[0], data[1], data[2], data[3]]);
271        let longitude = f32::from_le_bytes([data[4], data[5], data[6], data[7]]);
272        let flags = data[8];
273
274        let mut pos = Self::new(latitude, longitude);
275        let mut offset = 9;
276
277        if flags & 0x01 != 0 {
278            if data.len() < offset + 4 {
279                return None;
280            }
281            pos.altitude = Some(f32::from_le_bytes([
282                data[offset],
283                data[offset + 1],
284                data[offset + 2],
285                data[offset + 3],
286            ]));
287            offset += 4;
288        }
289
290        if flags & 0x02 != 0 {
291            if data.len() < offset + 4 {
292                return None;
293            }
294            pos.accuracy = Some(f32::from_le_bytes([
295                data[offset],
296                data[offset + 1],
297                data[offset + 2],
298                data[offset + 3],
299            ]));
300        }
301
302        Some(pos)
303    }
304}
305
306/// Health status data with LWW semantics
307#[derive(Debug, Clone, Default, PartialEq)]
308pub struct HealthStatus {
309    /// Battery percentage (0-100)
310    pub battery_percent: u8,
311    /// Heart rate BPM (optional)
312    pub heart_rate: Option<u8>,
313    /// Activity level (0=still, 1=walking, 2=running, 3=vehicle)
314    pub activity: u8,
315    /// Alert status flags
316    pub alerts: u8,
317}
318
319impl HealthStatus {
320    /// Alert flag: Man down
321    pub const ALERT_MAN_DOWN: u8 = 0x01;
322    /// Alert flag: Low battery
323    pub const ALERT_LOW_BATTERY: u8 = 0x02;
324    /// Alert flag: Out of range
325    pub const ALERT_OUT_OF_RANGE: u8 = 0x04;
326    /// Alert flag: Custom alert 1
327    pub const ALERT_CUSTOM_1: u8 = 0x08;
328
329    /// Create a new health status
330    pub fn new(battery_percent: u8) -> Self {
331        Self {
332            battery_percent,
333            heart_rate: None,
334            activity: 0,
335            alerts: 0,
336        }
337    }
338
339    /// Set heart rate
340    pub fn with_heart_rate(mut self, hr: u8) -> Self {
341        self.heart_rate = Some(hr);
342        self
343    }
344
345    /// Set activity level
346    pub fn with_activity(mut self, activity: u8) -> Self {
347        self.activity = activity;
348        self
349    }
350
351    /// Set alert flag
352    pub fn set_alert(&mut self, flag: u8) {
353        self.alerts |= flag;
354    }
355
356    /// Clear alert flag
357    pub fn clear_alert(&mut self, flag: u8) {
358        self.alerts &= !flag;
359    }
360
361    /// Check if alert is set
362    pub fn has_alert(&self, flag: u8) -> bool {
363        self.alerts & flag != 0
364    }
365
366    /// Encode to bytes (3-4 bytes)
367    pub fn encode(&self) -> Vec<u8> {
368        vec![
369            self.battery_percent,
370            self.activity,
371            self.alerts,
372            // Heart rate: 0 means not present, otherwise value
373            self.heart_rate.unwrap_or(0),
374        ]
375    }
376
377    /// Decode from bytes
378    pub fn decode(data: &[u8]) -> Option<Self> {
379        if data.len() < 4 {
380            return None;
381        }
382        let mut status = Self::new(data[0]);
383        status.activity = data[1];
384        status.alerts = data[2];
385        if data[3] > 0 {
386            status.heart_rate = Some(data[3]);
387        }
388        Some(status)
389    }
390}
391
392// ============================================================================
393// Peripheral (Sub-node) Types - for soldier-attached devices like M5Stack Core2
394// ============================================================================
395
396/// Type of peripheral device
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
398#[repr(u8)]
399pub enum PeripheralType {
400    /// Unknown/unspecified
401    #[default]
402    Unknown = 0,
403    /// Soldier-worn sensor (e.g., M5Stack Core2)
404    SoldierSensor = 1,
405    /// Fixed/stationary sensor
406    FixedSensor = 2,
407    /// Mesh relay only (no sensors)
408    Relay = 3,
409}
410
411impl PeripheralType {
412    /// Convert from u8 value
413    pub fn from_u8(v: u8) -> Self {
414        match v {
415            1 => Self::SoldierSensor,
416            2 => Self::FixedSensor,
417            3 => Self::Relay,
418            _ => Self::Unknown,
419        }
420    }
421}
422
423/// Event types that a peripheral can emit (e.g., from tap input)
424#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
425#[repr(u8)]
426pub enum EventType {
427    /// No event / cleared
428    #[default]
429    None = 0,
430    /// "I'm OK" ping
431    Ping = 1,
432    /// Request assistance
433    NeedAssist = 2,
434    /// Emergency / SOS
435    Emergency = 3,
436    /// Moving / in transit
437    Moving = 4,
438    /// In position / stationary
439    InPosition = 5,
440    /// Acknowledged / copy
441    Ack = 6,
442}
443
444impl EventType {
445    /// Convert from u8 value
446    pub fn from_u8(v: u8) -> Self {
447        match v {
448            1 => Self::Ping,
449            2 => Self::NeedAssist,
450            3 => Self::Emergency,
451            4 => Self::Moving,
452            5 => Self::InPosition,
453            6 => Self::Ack,
454            _ => Self::None,
455        }
456    }
457
458    /// Human-readable label for display
459    pub fn label(&self) -> &'static str {
460        match self {
461            Self::None => "",
462            Self::Ping => "PING",
463            Self::NeedAssist => "NEED ASSIST",
464            Self::Emergency => "EMERGENCY",
465            Self::Moving => "MOVING",
466            Self::InPosition => "IN POSITION",
467            Self::Ack => "ACK",
468        }
469    }
470}
471
472/// An event emitted by a peripheral (e.g., tap on Core2)
473#[derive(Debug, Clone, Default, PartialEq)]
474pub struct PeripheralEvent {
475    /// Type of event
476    pub event_type: EventType,
477    /// Timestamp when event occurred (ms since epoch or boot)
478    pub timestamp: u64,
479}
480
481impl PeripheralEvent {
482    /// Create a new peripheral event
483    pub fn new(event_type: EventType, timestamp: u64) -> Self {
484        Self {
485            event_type,
486            timestamp,
487        }
488    }
489
490    /// Encode to bytes (9 bytes)
491    pub fn encode(&self) -> Vec<u8> {
492        let mut buf = Vec::with_capacity(9);
493        buf.push(self.event_type as u8);
494        buf.extend_from_slice(&self.timestamp.to_le_bytes());
495        buf
496    }
497
498    /// Decode from bytes
499    pub fn decode(data: &[u8]) -> Option<Self> {
500        if data.len() < 9 {
501            return None;
502        }
503        Some(Self {
504            event_type: EventType::from_u8(data[0]),
505            timestamp: u64::from_le_bytes([
506                data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
507            ]),
508        })
509    }
510}
511
512/// An emergency event with acknowledgment tracking (CRDT)
513///
514/// Represents a single emergency incident with distributed ACK tracking.
515/// Each node in the mesh can acknowledge the emergency, and this state
516/// is replicated across all nodes using CRDT semantics.
517///
518/// ## CRDT Semantics
519///
520/// - **Identity**: Events are uniquely identified by (source_node, timestamp)
521/// - **Merge for same event**: ACK maps merge with OR (once acked, stays acked)
522/// - **Merge for different events**: Higher timestamp wins (newer emergency replaces older)
523/// - **Monotonic**: ACK state only moves from false → true, never back
524///
525/// ## Wire Format
526///
527/// ```text
528/// source_node: 4 bytes (LE)
529/// timestamp:   8 bytes (LE)
530/// num_acks:    4 bytes (LE)
531/// acks[N]:
532///   node_id:   4 bytes (LE)
533///   acked:     1 byte (0 or 1)
534/// ```
535#[derive(Debug, Clone, PartialEq, Default)]
536pub struct EmergencyEvent {
537    /// Node that triggered the emergency
538    source_node: u32,
539    /// Timestamp when emergency was triggered (for uniqueness)
540    timestamp: u64,
541    /// ACK status for each known peer: node_id -> has_acked
542    acks: BTreeMap<u32, bool>,
543}
544
545impl EmergencyEvent {
546    /// Create a new emergency event
547    ///
548    /// # Arguments
549    /// * `source_node` - Node ID that triggered the emergency
550    /// * `timestamp` - When the emergency was triggered
551    /// * `known_peers` - List of peer node IDs to track for ACKs
552    ///
553    /// The source node is automatically marked as acknowledged.
554    pub fn new(source_node: u32, timestamp: u64, known_peers: &[u32]) -> Self {
555        let mut acks = BTreeMap::new();
556
557        // Source node implicitly ACKs their own emergency
558        acks.insert(source_node, true);
559
560        // All other known peers start as not-acked
561        for &peer_id in known_peers {
562            if peer_id != source_node {
563                acks.entry(peer_id).or_insert(false);
564            }
565        }
566
567        Self {
568            source_node,
569            timestamp,
570            acks,
571        }
572    }
573
574    /// Get the source node that triggered the emergency
575    pub fn source_node(&self) -> u32 {
576        self.source_node
577    }
578
579    /// Get the timestamp when the emergency was triggered
580    pub fn timestamp(&self) -> u64 {
581        self.timestamp
582    }
583
584    /// Check if a specific node has acknowledged
585    pub fn has_acked(&self, node_id: u32) -> bool {
586        self.acks.get(&node_id).copied().unwrap_or(false)
587    }
588
589    /// Record an acknowledgment from a node
590    ///
591    /// Returns true if this was a new ACK (state changed)
592    pub fn ack(&mut self, node_id: u32) -> bool {
593        let entry = self.acks.entry(node_id).or_insert(false);
594        if !*entry {
595            *entry = true;
596            true
597        } else {
598            false
599        }
600    }
601
602    /// Add a peer to track (if not already present)
603    ///
604    /// New peers start as not-acked. This is useful when discovering
605    /// new peers after the emergency was created.
606    pub fn add_peer(&mut self, node_id: u32) {
607        self.acks.entry(node_id).or_insert(false);
608    }
609
610    /// Get list of nodes that have acknowledged
611    pub fn acked_nodes(&self) -> Vec<u32> {
612        self.acks
613            .iter()
614            .filter(|(_, &acked)| acked)
615            .map(|(&node_id, _)| node_id)
616            .collect()
617    }
618
619    /// Get list of nodes that have NOT acknowledged
620    pub fn pending_nodes(&self) -> Vec<u32> {
621        self.acks
622            .iter()
623            .filter(|(_, &acked)| !acked)
624            .map(|(&node_id, _)| node_id)
625            .collect()
626    }
627
628    /// Check if all tracked nodes have acknowledged
629    pub fn all_acked(&self) -> bool {
630        !self.acks.is_empty() && self.acks.values().all(|&acked| acked)
631    }
632
633    /// Get the total number of tracked nodes
634    pub fn peer_count(&self) -> usize {
635        self.acks.len()
636    }
637
638    /// Get the number of nodes that have acknowledged
639    pub fn ack_count(&self) -> usize {
640        self.acks.values().filter(|&&acked| acked).count()
641    }
642
643    /// Merge with another emergency event (CRDT semantics)
644    ///
645    /// # Returns
646    /// `true` if our state changed
647    ///
648    /// # Semantics
649    /// - Same event (source_node, timestamp): merge ACK maps with OR
650    /// - Different event: take the one with higher timestamp
651    pub fn merge(&mut self, other: &EmergencyEvent) -> bool {
652        // Different emergency - take newer one
653        if self.source_node != other.source_node || self.timestamp != other.timestamp {
654            if other.timestamp > self.timestamp {
655                *self = other.clone();
656                return true;
657            }
658            return false;
659        }
660
661        // Same emergency - merge ACK maps with OR
662        let mut changed = false;
663        for (&node_id, &other_acked) in &other.acks {
664            let entry = self.acks.entry(node_id).or_insert(false);
665            if other_acked && !*entry {
666                *entry = true;
667                changed = true;
668            }
669        }
670        changed
671    }
672
673    /// Encode to bytes for transmission
674    ///
675    /// Format: source_node(4) + timestamp(8) + num_acks(4) + acks[N](5 each)
676    pub fn encode(&self) -> Vec<u8> {
677        let mut buf = Vec::with_capacity(16 + self.acks.len() * 5);
678
679        buf.extend_from_slice(&self.source_node.to_le_bytes());
680        buf.extend_from_slice(&self.timestamp.to_le_bytes());
681        buf.extend_from_slice(&(self.acks.len() as u32).to_le_bytes());
682
683        for (&node_id, &acked) in &self.acks {
684            buf.extend_from_slice(&node_id.to_le_bytes());
685            buf.push(if acked { 1 } else { 0 });
686        }
687
688        buf
689    }
690
691    /// Decode from bytes
692    pub fn decode(data: &[u8]) -> Option<Self> {
693        if data.len() < 16 {
694            return None;
695        }
696
697        let source_node = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
698        let timestamp = u64::from_le_bytes([
699            data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
700        ]);
701        let num_acks = u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
702
703        if data.len() < 16 + num_acks * 5 {
704            return None;
705        }
706
707        let mut acks = BTreeMap::new();
708        let mut offset = 16;
709        for _ in 0..num_acks {
710            let node_id = u32::from_le_bytes([
711                data[offset],
712                data[offset + 1],
713                data[offset + 2],
714                data[offset + 3],
715            ]);
716            let acked = data[offset + 4] != 0;
717            acks.insert(node_id, acked);
718            offset += 5;
719        }
720
721        Some(Self {
722            source_node,
723            timestamp,
724            acks,
725        })
726    }
727}
728
729/// A peripheral device attached to a Node (soldier)
730///
731/// Peripherals are sub-tier devices that augment a soldier's capabilities
732/// with sensors and input (e.g., M5Stack Core2 wearable).
733#[derive(Debug, Clone, Default)]
734pub struct Peripheral {
735    /// Unique peripheral ID (derived from device MAC or similar)
736    pub id: u32,
737    /// Parent Node ID this peripheral is attached to (0 if not paired)
738    pub parent_node: u32,
739    /// Type of peripheral
740    pub peripheral_type: PeripheralType,
741    /// Callsign/name (inherited from parent or configured)
742    pub callsign: [u8; 12],
743    /// Current health status
744    pub health: HealthStatus,
745    /// Most recent event (if any)
746    pub last_event: Option<PeripheralEvent>,
747    /// Last update timestamp
748    pub timestamp: u64,
749}
750
751impl Peripheral {
752    /// Create a new peripheral
753    pub fn new(id: u32, peripheral_type: PeripheralType) -> Self {
754        Self {
755            id,
756            parent_node: 0,
757            peripheral_type,
758            callsign: [0u8; 12],
759            health: HealthStatus::default(),
760            last_event: None,
761            timestamp: 0,
762        }
763    }
764
765    /// Set the callsign (truncated to 12 bytes)
766    pub fn with_callsign(mut self, callsign: &str) -> Self {
767        let bytes = callsign.as_bytes();
768        let len = bytes.len().min(12);
769        self.callsign[..len].copy_from_slice(&bytes[..len]);
770        self
771    }
772
773    /// Get callsign as string
774    pub fn callsign_str(&self) -> &str {
775        let len = self.callsign.iter().position(|&b| b == 0).unwrap_or(12);
776        core::str::from_utf8(&self.callsign[..len]).unwrap_or("")
777    }
778
779    /// Set parent node
780    pub fn with_parent(mut self, parent_node: u32) -> Self {
781        self.parent_node = parent_node;
782        self
783    }
784
785    /// Record an event
786    pub fn set_event(&mut self, event_type: EventType, timestamp: u64) {
787        self.last_event = Some(PeripheralEvent::new(event_type, timestamp));
788        self.timestamp = timestamp;
789    }
790
791    /// Clear the last event
792    pub fn clear_event(&mut self) {
793        self.last_event = None;
794    }
795
796    /// Encode to bytes for BLE transmission
797    /// Format: [id:4][parent:4][type:1][callsign:12][health:4][has_event:1][event:9?][timestamp:8]
798    /// Size: 34 bytes without event, 43 bytes with event
799    pub fn encode(&self) -> Vec<u8> {
800        let mut buf = Vec::with_capacity(43);
801        buf.extend_from_slice(&self.id.to_le_bytes());
802        buf.extend_from_slice(&self.parent_node.to_le_bytes());
803        buf.push(self.peripheral_type as u8);
804        buf.extend_from_slice(&self.callsign);
805        buf.extend_from_slice(&self.health.encode());
806
807        if let Some(ref event) = self.last_event {
808            buf.push(1); // has event
809            buf.extend_from_slice(&event.encode());
810        } else {
811            buf.push(0); // no event
812        }
813
814        buf.extend_from_slice(&self.timestamp.to_le_bytes());
815        buf
816    }
817
818    /// Decode from bytes
819    pub fn decode(data: &[u8]) -> Option<Self> {
820        if data.len() < 34 {
821            return None;
822        }
823
824        let id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
825        let parent_node = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
826        let peripheral_type = PeripheralType::from_u8(data[8]);
827
828        let mut callsign = [0u8; 12];
829        callsign.copy_from_slice(&data[9..21]);
830
831        let health = HealthStatus::decode(&data[21..25])?;
832
833        let has_event = data[25] != 0;
834        let (last_event, timestamp_offset) = if has_event {
835            if data.len() < 43 {
836                return None;
837            }
838            (PeripheralEvent::decode(&data[26..35]), 35)
839        } else {
840            (None, 26)
841        };
842
843        if data.len() < timestamp_offset + 8 {
844            return None;
845        }
846
847        let timestamp = u64::from_le_bytes([
848            data[timestamp_offset],
849            data[timestamp_offset + 1],
850            data[timestamp_offset + 2],
851            data[timestamp_offset + 3],
852            data[timestamp_offset + 4],
853            data[timestamp_offset + 5],
854            data[timestamp_offset + 6],
855            data[timestamp_offset + 7],
856        ]);
857
858        Some(Self {
859            id,
860            parent_node,
861            peripheral_type,
862            callsign,
863            health,
864            last_event,
865            timestamp,
866        })
867    }
868}
869
870/// CRDT operation types for sync
871#[derive(Debug, Clone)]
872pub enum CrdtOperation {
873    /// Update a position register
874    UpdatePosition {
875        /// Node ID that owns this position
876        node_id: NodeId,
877        /// Position data
878        position: Position,
879        /// Timestamp of the update
880        timestamp: Timestamp,
881    },
882    /// Update health status register
883    UpdateHealth {
884        /// Node ID that owns this status
885        node_id: NodeId,
886        /// Health status data
887        status: HealthStatus,
888        /// Timestamp of the update
889        timestamp: Timestamp,
890    },
891    /// Increment a counter
892    IncrementCounter {
893        /// Counter identifier
894        counter_id: u8,
895        /// Node performing the increment
896        node_id: NodeId,
897        /// Amount to increment
898        amount: u64,
899    },
900    /// Generic LWW update (key-value)
901    UpdateRegister {
902        /// Key for the register
903        key: String,
904        /// Value data
905        value: Vec<u8>,
906        /// Timestamp of the update
907        timestamp: Timestamp,
908        /// Node that set the value
909        node_id: NodeId,
910    },
911}
912
913impl CrdtOperation {
914    /// Get the approximate size in bytes
915    pub fn size(&self) -> usize {
916        match self {
917            CrdtOperation::UpdatePosition { position, .. } => 4 + 8 + position.encode().len(),
918            CrdtOperation::UpdateHealth { status, .. } => 4 + 8 + status.encode().len(),
919            CrdtOperation::IncrementCounter { .. } => 1 + 4 + 8,
920            CrdtOperation::UpdateRegister { key, value, .. } => 4 + 8 + key.len() + value.len(),
921        }
922    }
923
924    /// Encode to bytes
925    pub fn encode(&self) -> Vec<u8> {
926        let mut buf = Vec::new();
927        match self {
928            CrdtOperation::UpdatePosition {
929                node_id,
930                position,
931                timestamp,
932            } => {
933                buf.push(0x01); // Type tag
934                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
935                buf.extend_from_slice(&timestamp.to_le_bytes());
936                buf.extend_from_slice(&position.encode());
937            }
938            CrdtOperation::UpdateHealth {
939                node_id,
940                status,
941                timestamp,
942            } => {
943                buf.push(0x02); // Type tag
944                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
945                buf.extend_from_slice(&timestamp.to_le_bytes());
946                buf.extend_from_slice(&status.encode());
947            }
948            CrdtOperation::IncrementCounter {
949                counter_id,
950                node_id,
951                amount,
952            } => {
953                buf.push(0x03); // Type tag
954                buf.push(*counter_id);
955                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
956                buf.extend_from_slice(&amount.to_le_bytes());
957            }
958            CrdtOperation::UpdateRegister {
959                key,
960                value,
961                timestamp,
962                node_id,
963            } => {
964                buf.push(0x04); // Type tag
965                buf.extend_from_slice(&node_id.as_u32().to_le_bytes());
966                buf.extend_from_slice(&timestamp.to_le_bytes());
967                buf.push(key.len() as u8);
968                buf.extend_from_slice(key.as_bytes());
969                buf.extend_from_slice(&(value.len() as u16).to_le_bytes());
970                buf.extend_from_slice(value);
971            }
972        }
973        buf
974    }
975
976    /// Decode from bytes
977    pub fn decode(data: &[u8]) -> Option<Self> {
978        if data.is_empty() {
979            return None;
980        }
981
982        match data[0] {
983            0x01 => {
984                // UpdatePosition
985                if data.len() < 13 {
986                    return None;
987                }
988                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
989                let timestamp = u64::from_le_bytes([
990                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
991                ]);
992                let position = Position::decode(&data[13..])?;
993                Some(CrdtOperation::UpdatePosition {
994                    node_id,
995                    position,
996                    timestamp,
997                })
998            }
999            0x02 => {
1000                // UpdateHealth
1001                if data.len() < 13 {
1002                    return None;
1003                }
1004                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1005                let timestamp = u64::from_le_bytes([
1006                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1007                ]);
1008                let status = HealthStatus::decode(&data[13..])?;
1009                Some(CrdtOperation::UpdateHealth {
1010                    node_id,
1011                    status,
1012                    timestamp,
1013                })
1014            }
1015            0x03 => {
1016                // IncrementCounter
1017                if data.len() < 14 {
1018                    return None;
1019                }
1020                let counter_id = data[1];
1021                let node_id = NodeId::new(u32::from_le_bytes([data[2], data[3], data[4], data[5]]));
1022                let amount = u64::from_le_bytes([
1023                    data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13],
1024                ]);
1025                Some(CrdtOperation::IncrementCounter {
1026                    counter_id,
1027                    node_id,
1028                    amount,
1029                })
1030            }
1031            0x04 => {
1032                // UpdateRegister
1033                if data.len() < 14 {
1034                    return None;
1035                }
1036                let node_id = NodeId::new(u32::from_le_bytes([data[1], data[2], data[3], data[4]]));
1037                let timestamp = u64::from_le_bytes([
1038                    data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12],
1039                ]);
1040                let key_len = data[13] as usize;
1041                if data.len() < 14 + key_len + 2 {
1042                    return None;
1043                }
1044                let key = core::str::from_utf8(&data[14..14 + key_len])
1045                    .ok()?
1046                    .to_string();
1047                let value_len =
1048                    u16::from_le_bytes([data[14 + key_len], data[15 + key_len]]) as usize;
1049                if data.len() < 16 + key_len + value_len {
1050                    return None;
1051                }
1052                let value = data[16 + key_len..16 + key_len + value_len].to_vec();
1053                Some(CrdtOperation::UpdateRegister {
1054                    key,
1055                    value,
1056                    timestamp,
1057                    node_id,
1058                })
1059            }
1060            _ => None,
1061        }
1062    }
1063}
1064
1065#[cfg(test)]
1066mod tests {
1067    use super::*;
1068
1069    #[test]
1070    fn test_lww_register_basic() {
1071        let mut reg = LwwRegister::new(42u32, 100, NodeId::new(1));
1072        assert_eq!(*reg.get(), 42);
1073        assert_eq!(reg.timestamp(), 100);
1074
1075        // Higher timestamp wins
1076        assert!(reg.set(99, 200, NodeId::new(2)));
1077        assert_eq!(*reg.get(), 99);
1078
1079        // Lower timestamp loses
1080        assert!(!reg.set(50, 150, NodeId::new(3)));
1081        assert_eq!(*reg.get(), 99);
1082    }
1083
1084    #[test]
1085    fn test_lww_register_tiebreak() {
1086        let mut reg = LwwRegister::new(1u32, 100, NodeId::new(1));
1087
1088        // Same timestamp, higher node_id wins
1089        assert!(reg.set(2, 100, NodeId::new(2)));
1090        assert_eq!(*reg.get(), 2);
1091
1092        // Same timestamp, lower node_id loses
1093        assert!(!reg.set(3, 100, NodeId::new(1)));
1094        assert_eq!(*reg.get(), 2);
1095    }
1096
1097    #[test]
1098    fn test_lww_register_merge() {
1099        let mut reg1 = LwwRegister::new(1u32, 100, NodeId::new(1));
1100        let reg2 = LwwRegister::new(2u32, 200, NodeId::new(2));
1101
1102        assert!(reg1.merge(&reg2));
1103        assert_eq!(*reg1.get(), 2);
1104    }
1105
1106    #[test]
1107    fn test_gcounter_basic() {
1108        let mut counter = GCounter::new();
1109        let node1 = NodeId::new(1);
1110        let node2 = NodeId::new(2);
1111
1112        counter.increment(&node1, 5);
1113        counter.increment(&node2, 3);
1114        counter.increment(&node1, 2);
1115
1116        assert_eq!(counter.value(), 10);
1117        assert_eq!(counter.node_count(&node1), 7);
1118        assert_eq!(counter.node_count(&node2), 3);
1119    }
1120
1121    #[test]
1122    fn test_gcounter_merge() {
1123        let mut counter1 = GCounter::new();
1124        let mut counter2 = GCounter::new();
1125        let node1 = NodeId::new(1);
1126        let node2 = NodeId::new(2);
1127
1128        counter1.increment(&node1, 5);
1129        counter2.increment(&node1, 3);
1130        counter2.increment(&node2, 4);
1131
1132        counter1.merge(&counter2);
1133
1134        assert_eq!(counter1.value(), 9); // max(5,3) + 4
1135        assert_eq!(counter1.node_count(&node1), 5);
1136        assert_eq!(counter1.node_count(&node2), 4);
1137    }
1138
1139    #[test]
1140    fn test_gcounter_encode_decode() {
1141        let mut counter = GCounter::new();
1142        counter.increment(&NodeId::new(1), 5);
1143        counter.increment(&NodeId::new(2), 10);
1144
1145        let encoded = counter.encode();
1146        let decoded = GCounter::decode(&encoded).unwrap();
1147
1148        assert_eq!(decoded.value(), counter.value());
1149        assert_eq!(decoded.node_count(&NodeId::new(1)), 5);
1150        assert_eq!(decoded.node_count(&NodeId::new(2)), 10);
1151    }
1152
1153    #[test]
1154    fn test_position_encode_decode() {
1155        let pos = Position::new(37.7749, -122.4194)
1156            .with_altitude(100.0)
1157            .with_accuracy(5.0);
1158
1159        let encoded = pos.encode();
1160        let decoded = Position::decode(&encoded).unwrap();
1161
1162        assert_eq!(decoded.latitude, pos.latitude);
1163        assert_eq!(decoded.longitude, pos.longitude);
1164        assert_eq!(decoded.altitude, pos.altitude);
1165        assert_eq!(decoded.accuracy, pos.accuracy);
1166    }
1167
1168    #[test]
1169    fn test_position_minimal_encode() {
1170        let pos = Position::new(0.0, 0.0);
1171        let encoded = pos.encode();
1172        assert_eq!(encoded.len(), 9); // lat + lon + flags
1173
1174        let pos_with_alt = Position::new(0.0, 0.0).with_altitude(0.0);
1175        let encoded_alt = pos_with_alt.encode();
1176        assert_eq!(encoded_alt.len(), 13);
1177    }
1178
1179    #[test]
1180    fn test_health_status() {
1181        let mut status = HealthStatus::new(85).with_heart_rate(72).with_activity(1);
1182
1183        assert_eq!(status.battery_percent, 85);
1184        assert_eq!(status.heart_rate, Some(72));
1185        assert!(!status.has_alert(HealthStatus::ALERT_MAN_DOWN));
1186
1187        status.set_alert(HealthStatus::ALERT_MAN_DOWN);
1188        assert!(status.has_alert(HealthStatus::ALERT_MAN_DOWN));
1189
1190        let encoded = status.encode();
1191        let decoded = HealthStatus::decode(&encoded).unwrap();
1192        assert_eq!(decoded.battery_percent, 85);
1193        assert_eq!(decoded.heart_rate, Some(72));
1194        assert!(decoded.has_alert(HealthStatus::ALERT_MAN_DOWN));
1195    }
1196
1197    #[test]
1198    fn test_crdt_operation_position() {
1199        let op = CrdtOperation::UpdatePosition {
1200            node_id: NodeId::new(0x1234),
1201            position: Position::new(37.0, -122.0),
1202            timestamp: 1000,
1203        };
1204
1205        let encoded = op.encode();
1206        let decoded = CrdtOperation::decode(&encoded).unwrap();
1207
1208        if let CrdtOperation::UpdatePosition {
1209            node_id,
1210            position,
1211            timestamp,
1212        } = decoded
1213        {
1214            assert_eq!(node_id.as_u32(), 0x1234);
1215            assert_eq!(timestamp, 1000);
1216            assert_eq!(position.latitude, 37.0);
1217        } else {
1218            panic!("Wrong operation type");
1219        }
1220    }
1221
1222    #[test]
1223    fn test_crdt_operation_counter() {
1224        let op = CrdtOperation::IncrementCounter {
1225            counter_id: 1,
1226            node_id: NodeId::new(0x5678),
1227            amount: 42,
1228        };
1229
1230        let encoded = op.encode();
1231        let decoded = CrdtOperation::decode(&encoded).unwrap();
1232
1233        if let CrdtOperation::IncrementCounter {
1234            counter_id,
1235            node_id,
1236            amount,
1237        } = decoded
1238        {
1239            assert_eq!(counter_id, 1);
1240            assert_eq!(node_id.as_u32(), 0x5678);
1241            assert_eq!(amount, 42);
1242        } else {
1243            panic!("Wrong operation type");
1244        }
1245    }
1246
1247    #[test]
1248    fn test_crdt_operation_size() {
1249        let pos_op = CrdtOperation::UpdatePosition {
1250            node_id: NodeId::new(1),
1251            position: Position::new(0.0, 0.0),
1252            timestamp: 0,
1253        };
1254        assert!(pos_op.size() > 0);
1255
1256        let counter_op = CrdtOperation::IncrementCounter {
1257            counter_id: 0,
1258            node_id: NodeId::new(1),
1259            amount: 1,
1260        };
1261        assert_eq!(counter_op.size(), 13);
1262    }
1263
1264    // ============================================================================
1265    // Peripheral Tests
1266    // ============================================================================
1267
1268    #[test]
1269    fn test_peripheral_type_from_u8() {
1270        assert_eq!(PeripheralType::from_u8(0), PeripheralType::Unknown);
1271        assert_eq!(PeripheralType::from_u8(1), PeripheralType::SoldierSensor);
1272        assert_eq!(PeripheralType::from_u8(2), PeripheralType::FixedSensor);
1273        assert_eq!(PeripheralType::from_u8(3), PeripheralType::Relay);
1274        assert_eq!(PeripheralType::from_u8(99), PeripheralType::Unknown);
1275    }
1276
1277    #[test]
1278    fn test_event_type_from_u8() {
1279        assert_eq!(EventType::from_u8(0), EventType::None);
1280        assert_eq!(EventType::from_u8(1), EventType::Ping);
1281        assert_eq!(EventType::from_u8(2), EventType::NeedAssist);
1282        assert_eq!(EventType::from_u8(3), EventType::Emergency);
1283        assert_eq!(EventType::from_u8(4), EventType::Moving);
1284        assert_eq!(EventType::from_u8(5), EventType::InPosition);
1285        assert_eq!(EventType::from_u8(6), EventType::Ack);
1286        assert_eq!(EventType::from_u8(99), EventType::None);
1287    }
1288
1289    #[test]
1290    fn test_event_type_labels() {
1291        assert_eq!(EventType::None.label(), "");
1292        assert_eq!(EventType::Emergency.label(), "EMERGENCY");
1293        assert_eq!(EventType::Ping.label(), "PING");
1294    }
1295
1296    #[test]
1297    fn test_peripheral_event_encode_decode() {
1298        let event = PeripheralEvent::new(EventType::Emergency, 1234567890);
1299        let encoded = event.encode();
1300        assert_eq!(encoded.len(), 9);
1301
1302        let decoded = PeripheralEvent::decode(&encoded).unwrap();
1303        assert_eq!(decoded.event_type, EventType::Emergency);
1304        assert_eq!(decoded.timestamp, 1234567890);
1305    }
1306
1307    #[test]
1308    fn test_peripheral_new() {
1309        let peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor);
1310        assert_eq!(peripheral.id, 0x12345678);
1311        assert_eq!(peripheral.peripheral_type, PeripheralType::SoldierSensor);
1312        assert_eq!(peripheral.parent_node, 0);
1313        assert!(peripheral.last_event.is_none());
1314    }
1315
1316    #[test]
1317    fn test_peripheral_with_callsign() {
1318        let peripheral = Peripheral::new(1, PeripheralType::SoldierSensor).with_callsign("ALPHA-1");
1319        assert_eq!(peripheral.callsign_str(), "ALPHA-1");
1320
1321        // Test truncation
1322        let peripheral2 = Peripheral::new(2, PeripheralType::SoldierSensor)
1323            .with_callsign("THIS_IS_A_VERY_LONG_CALLSIGN");
1324        assert_eq!(peripheral2.callsign_str(), "THIS_IS_A_VE");
1325    }
1326
1327    #[test]
1328    fn test_peripheral_set_event() {
1329        let mut peripheral = Peripheral::new(1, PeripheralType::SoldierSensor);
1330        peripheral.set_event(EventType::Emergency, 1000);
1331
1332        assert!(peripheral.last_event.is_some());
1333        let event = peripheral.last_event.as_ref().unwrap();
1334        assert_eq!(event.event_type, EventType::Emergency);
1335        assert_eq!(event.timestamp, 1000);
1336        assert_eq!(peripheral.timestamp, 1000);
1337
1338        peripheral.clear_event();
1339        assert!(peripheral.last_event.is_none());
1340    }
1341
1342    #[test]
1343    fn test_peripheral_encode_decode_without_event() {
1344        let peripheral = Peripheral::new(0xAABBCCDD, PeripheralType::SoldierSensor)
1345            .with_callsign("BRAVO-2")
1346            .with_parent(0x11223344);
1347
1348        let encoded = peripheral.encode();
1349        assert_eq!(encoded.len(), 34); // No event
1350
1351        let decoded = Peripheral::decode(&encoded).unwrap();
1352        assert_eq!(decoded.id, 0xAABBCCDD);
1353        assert_eq!(decoded.parent_node, 0x11223344);
1354        assert_eq!(decoded.peripheral_type, PeripheralType::SoldierSensor);
1355        assert_eq!(decoded.callsign_str(), "BRAVO-2");
1356        assert!(decoded.last_event.is_none());
1357    }
1358
1359    #[test]
1360    fn test_peripheral_encode_decode_with_event() {
1361        let mut peripheral = Peripheral::new(0x12345678, PeripheralType::SoldierSensor)
1362            .with_callsign("CHARLIE")
1363            .with_parent(0x87654321);
1364        peripheral.health = HealthStatus::new(85);
1365        peripheral.set_event(EventType::NeedAssist, 9999);
1366
1367        let encoded = peripheral.encode();
1368        assert_eq!(encoded.len(), 43); // With event
1369
1370        let decoded = Peripheral::decode(&encoded).unwrap();
1371        assert_eq!(decoded.id, 0x12345678);
1372        assert_eq!(decoded.parent_node, 0x87654321);
1373        assert_eq!(decoded.callsign_str(), "CHARLIE");
1374        assert_eq!(decoded.health.battery_percent, 85);
1375        assert!(decoded.last_event.is_some());
1376        let event = decoded.last_event.as_ref().unwrap();
1377        assert_eq!(event.event_type, EventType::NeedAssist);
1378        assert_eq!(event.timestamp, 9999);
1379    }
1380
1381    #[test]
1382    fn test_peripheral_decode_invalid_data() {
1383        // Too short
1384        assert!(Peripheral::decode(&[0u8; 10]).is_none());
1385
1386        // Valid length but no event
1387        let mut data = vec![0u8; 34];
1388        data[25] = 0; // no event flag
1389        assert!(Peripheral::decode(&data).is_some());
1390
1391        // Claims to have event but too short
1392        data[25] = 1; // has event flag
1393        assert!(Peripheral::decode(&data).is_none());
1394    }
1395
1396    // ============================================================================
1397    // EmergencyEvent Tests
1398    // ============================================================================
1399
1400    #[test]
1401    fn test_emergency_event_new() {
1402        let peers = vec![0x22222222, 0x33333333];
1403        let event = EmergencyEvent::new(0x11111111, 1000, &peers);
1404
1405        assert_eq!(event.source_node(), 0x11111111);
1406        assert_eq!(event.timestamp(), 1000);
1407        assert_eq!(event.peer_count(), 3); // source + 2 peers
1408
1409        // Source is auto-acked
1410        assert!(event.has_acked(0x11111111));
1411        // Others are not
1412        assert!(!event.has_acked(0x22222222));
1413        assert!(!event.has_acked(0x33333333));
1414    }
1415
1416    #[test]
1417    fn test_emergency_event_ack() {
1418        let peers = vec![0x22222222, 0x33333333];
1419        let mut event = EmergencyEvent::new(0x11111111, 1000, &peers);
1420
1421        assert_eq!(event.ack_count(), 1); // just source
1422        assert!(!event.all_acked());
1423
1424        // ACK from first peer
1425        assert!(event.ack(0x22222222)); // returns true - new ack
1426        assert_eq!(event.ack_count(), 2);
1427        assert!(!event.all_acked());
1428
1429        // Duplicate ACK
1430        assert!(!event.ack(0x22222222)); // returns false - already acked
1431
1432        // ACK from second peer
1433        assert!(event.ack(0x33333333));
1434        assert_eq!(event.ack_count(), 3);
1435        assert!(event.all_acked());
1436    }
1437
1438    #[test]
1439    fn test_emergency_event_pending_nodes() {
1440        let peers = vec![0x22222222, 0x33333333];
1441        let mut event = EmergencyEvent::new(0x11111111, 1000, &peers);
1442
1443        let pending = event.pending_nodes();
1444        assert_eq!(pending.len(), 2);
1445        assert!(pending.contains(&0x22222222));
1446        assert!(pending.contains(&0x33333333));
1447
1448        event.ack(0x22222222);
1449        let pending = event.pending_nodes();
1450        assert_eq!(pending.len(), 1);
1451        assert!(pending.contains(&0x33333333));
1452    }
1453
1454    #[test]
1455    fn test_emergency_event_encode_decode() {
1456        let peers = vec![0x22222222, 0x33333333];
1457        let mut event = EmergencyEvent::new(0x11111111, 1234567890, &peers);
1458        event.ack(0x22222222);
1459
1460        let encoded = event.encode();
1461        let decoded = EmergencyEvent::decode(&encoded).unwrap();
1462
1463        assert_eq!(decoded.source_node(), 0x11111111);
1464        assert_eq!(decoded.timestamp(), 1234567890);
1465        assert!(decoded.has_acked(0x11111111));
1466        assert!(decoded.has_acked(0x22222222));
1467        assert!(!decoded.has_acked(0x33333333));
1468    }
1469
1470    #[test]
1471    fn test_emergency_event_merge_same_event() {
1472        // Two nodes have the same emergency, different ack states
1473        let peers = vec![0x22222222, 0x33333333];
1474        let mut event1 = EmergencyEvent::new(0x11111111, 1000, &peers);
1475        let mut event2 = EmergencyEvent::new(0x11111111, 1000, &peers);
1476
1477        event1.ack(0x22222222);
1478        event2.ack(0x33333333);
1479
1480        // Merge event2 into event1
1481        let changed = event1.merge(&event2);
1482        assert!(changed);
1483        assert!(event1.has_acked(0x22222222));
1484        assert!(event1.has_acked(0x33333333));
1485        assert!(event1.all_acked());
1486    }
1487
1488    #[test]
1489    fn test_emergency_event_merge_different_events() {
1490        // Old emergency
1491        let mut old_event = EmergencyEvent::new(0x11111111, 1000, &[0x22222222]);
1492        old_event.ack(0x22222222);
1493
1494        // New emergency from different source
1495        let new_event = EmergencyEvent::new(0x33333333, 2000, &[0x11111111, 0x22222222]);
1496
1497        // Merge new into old - should replace
1498        let changed = old_event.merge(&new_event);
1499        assert!(changed);
1500        assert_eq!(old_event.source_node(), 0x33333333);
1501        assert_eq!(old_event.timestamp(), 2000);
1502        // Old ack state should be gone
1503        assert!(!old_event.has_acked(0x22222222));
1504    }
1505
1506    #[test]
1507    fn test_emergency_event_merge_older_event_ignored() {
1508        // Current emergency
1509        let mut current = EmergencyEvent::new(0x11111111, 2000, &[0x22222222]);
1510
1511        // Older emergency
1512        let older = EmergencyEvent::new(0x33333333, 1000, &[0x11111111]);
1513
1514        // Merge older into current - should NOT replace
1515        let changed = current.merge(&older);
1516        assert!(!changed);
1517        assert_eq!(current.source_node(), 0x11111111);
1518        assert_eq!(current.timestamp(), 2000);
1519    }
1520
1521    #[test]
1522    fn test_emergency_event_add_peer() {
1523        let mut event = EmergencyEvent::new(0x11111111, 1000, &[]);
1524
1525        // Add a peer discovered after emergency started
1526        event.add_peer(0x22222222);
1527        assert!(!event.has_acked(0x22222222));
1528        assert_eq!(event.peer_count(), 2);
1529
1530        // Adding same peer again doesn't change ack status
1531        event.ack(0x22222222);
1532        event.add_peer(0x22222222);
1533        assert!(event.has_acked(0x22222222)); // still acked
1534    }
1535
1536    #[test]
1537    fn test_emergency_event_decode_invalid() {
1538        // Too short
1539        assert!(EmergencyEvent::decode(&[0u8; 10]).is_none());
1540
1541        // Valid header but claims more acks than data
1542        let mut data = vec![0u8; 16];
1543        data[12] = 5; // claims 5 ack entries
1544        assert!(EmergencyEvent::decode(&data).is_none());
1545    }
1546}