hive_btle/
hive_mesh.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//! HiveMesh - Unified mesh management facade
17//!
18//! This module provides the main entry point for HIVE BLE mesh operations.
19//! It composes peer management, document sync, and observer notifications
20//! into a single interface that platform implementations can use.
21//!
22//! ## Usage
23//!
24//! ```ignore
25//! use hive_btle::hive_mesh::{HiveMesh, HiveMeshConfig};
26//! use hive_btle::observer::{HiveEvent, HiveObserver};
27//! use hive_btle::NodeId;
28//! use std::sync::Arc;
29//!
30//! // Create mesh configuration
31//! let config = HiveMeshConfig::new(NodeId::new(0x12345678), "ALPHA-1", "DEMO");
32//!
33//! // Create mesh instance
34//! let mesh = HiveMesh::new(config);
35//!
36//! // Add observer for events
37//! struct MyObserver;
38//! impl HiveObserver for MyObserver {
39//!     fn on_event(&self, event: HiveEvent) {
40//!         println!("Event: {:?}", event);
41//!     }
42//! }
43//! mesh.add_observer(Arc::new(MyObserver));
44//!
45//! // Platform BLE callbacks
46//! mesh.on_ble_discovered("device-uuid", Some("HIVE_DEMO-AABBCCDD"), -65, Some("DEMO"), now_ms);
47//! mesh.on_ble_connected("device-uuid", now_ms);
48//! mesh.on_ble_data_received("device-uuid", &data, now_ms);
49//!
50//! // Periodic maintenance
51//! if let Some(sync_data) = mesh.tick(now_ms) {
52//!     // Broadcast sync_data to connected peers
53//! }
54//! ```
55
56#[cfg(not(feature = "std"))]
57use alloc::{string::String, sync::Arc, vec::Vec};
58#[cfg(feature = "std")]
59use std::sync::Arc;
60
61use crate::document::{ENCRYPTED_MARKER, KEY_EXCHANGE_MARKER, PEER_E2EE_MARKER};
62use crate::document_sync::DocumentSync;
63use crate::observer::{DisconnectReason, HiveEvent, HiveObserver, SecurityViolationKind};
64use crate::peer::{
65    ConnectionStateGraph, HivePeer, PeerConnectionState, PeerManagerConfig, StateCountSummary,
66};
67use crate::peer_manager::PeerManager;
68use crate::security::{
69    KeyExchangeMessage, MeshEncryptionKey, PeerEncryptedMessage, PeerSessionManager, SessionState,
70};
71use crate::sync::crdt::{EventType, PeripheralType};
72use crate::NodeId;
73
74#[cfg(feature = "std")]
75use crate::observer::ObserverManager;
76
77/// Configuration for HiveMesh
78#[derive(Debug, Clone)]
79pub struct HiveMeshConfig {
80    /// Our node ID
81    pub node_id: NodeId,
82
83    /// Our callsign (e.g., "ALPHA-1")
84    pub callsign: String,
85
86    /// Mesh ID to filter peers (e.g., "DEMO")
87    pub mesh_id: String,
88
89    /// Peripheral type for this device
90    pub peripheral_type: PeripheralType,
91
92    /// Peer management configuration
93    pub peer_config: PeerManagerConfig,
94
95    /// Sync interval in milliseconds (how often to broadcast state)
96    pub sync_interval_ms: u64,
97
98    /// Whether to auto-broadcast on emergency/ack
99    pub auto_broadcast_events: bool,
100
101    /// Optional shared secret for mesh-wide encryption (32 bytes)
102    ///
103    /// When set, all documents are encrypted using ChaCha20-Poly1305 before
104    /// transmission and decrypted upon receipt. All nodes in the mesh must
105    /// share the same secret to communicate.
106    pub encryption_secret: Option<[u8; 32]>,
107
108    /// Strict encryption mode - reject unencrypted documents when encryption is enabled
109    ///
110    /// When true and encryption is enabled, any unencrypted documents received
111    /// will be rejected and trigger a SecurityViolation event. This prevents
112    /// downgrade attacks where an adversary sends unencrypted malicious documents.
113    ///
114    /// Default: false (backward compatible - accepts unencrypted for gradual rollout)
115    pub strict_encryption: bool,
116}
117
118impl HiveMeshConfig {
119    /// Create a new configuration with required fields
120    pub fn new(node_id: NodeId, callsign: &str, mesh_id: &str) -> Self {
121        Self {
122            node_id,
123            callsign: callsign.into(),
124            mesh_id: mesh_id.into(),
125            peripheral_type: PeripheralType::SoldierSensor,
126            peer_config: PeerManagerConfig::with_mesh_id(mesh_id),
127            sync_interval_ms: 5000,
128            auto_broadcast_events: true,
129            encryption_secret: None,
130            strict_encryption: false,
131        }
132    }
133
134    /// Enable mesh-wide encryption with a shared secret
135    ///
136    /// All documents will be encrypted using ChaCha20-Poly1305 before
137    /// transmission. All mesh participants must use the same secret.
138    pub fn with_encryption(mut self, secret: [u8; 32]) -> Self {
139        self.encryption_secret = Some(secret);
140        self
141    }
142
143    /// Set peripheral type
144    pub fn with_peripheral_type(mut self, ptype: PeripheralType) -> Self {
145        self.peripheral_type = ptype;
146        self
147    }
148
149    /// Set sync interval
150    pub fn with_sync_interval(mut self, interval_ms: u64) -> Self {
151        self.sync_interval_ms = interval_ms;
152        self
153    }
154
155    /// Set peer timeout
156    pub fn with_peer_timeout(mut self, timeout_ms: u64) -> Self {
157        self.peer_config.peer_timeout_ms = timeout_ms;
158        self
159    }
160
161    /// Set max peers (for embedded systems)
162    pub fn with_max_peers(mut self, max: usize) -> Self {
163        self.peer_config.max_peers = max;
164        self
165    }
166
167    /// Enable strict encryption mode
168    ///
169    /// When enabled (and encryption is also enabled), any unencrypted documents
170    /// received will be rejected and trigger a `SecurityViolation` event.
171    /// This prevents downgrade attacks.
172    ///
173    /// Note: This only has effect when encryption is enabled via `with_encryption()`.
174    pub fn with_strict_encryption(mut self) -> Self {
175        self.strict_encryption = true;
176        self
177    }
178}
179
180/// Main facade for HIVE BLE mesh operations
181///
182/// Composes peer management, document sync, and observer notifications.
183/// Platform implementations call into this from their BLE callbacks.
184#[cfg(feature = "std")]
185pub struct HiveMesh {
186    /// Configuration
187    config: HiveMeshConfig,
188
189    /// Peer manager
190    peer_manager: PeerManager,
191
192    /// Document sync
193    document_sync: DocumentSync,
194
195    /// Observer manager
196    observers: ObserverManager,
197
198    /// Last sync broadcast time (u32 wraps every ~49 days, sufficient for intervals)
199    last_sync_ms: std::sync::atomic::AtomicU32,
200
201    /// Last cleanup time
202    last_cleanup_ms: std::sync::atomic::AtomicU32,
203
204    /// Optional mesh-wide encryption key (derived from shared secret)
205    encryption_key: Option<MeshEncryptionKey>,
206
207    /// Optional per-peer E2EE session manager
208    peer_sessions: std::sync::Mutex<Option<PeerSessionManager>>,
209
210    /// Connection state graph for tracking peer connection lifecycle
211    connection_graph: std::sync::Mutex<ConnectionStateGraph>,
212}
213
214#[cfg(feature = "std")]
215impl HiveMesh {
216    /// Create a new HiveMesh instance
217    pub fn new(config: HiveMeshConfig) -> Self {
218        let peer_manager = PeerManager::new(config.node_id, config.peer_config.clone());
219        let document_sync = DocumentSync::with_peripheral_type(
220            config.node_id,
221            &config.callsign,
222            config.peripheral_type,
223        );
224
225        // Derive encryption key from shared secret if configured
226        let encryption_key = config
227            .encryption_secret
228            .map(|secret| MeshEncryptionKey::from_shared_secret(&config.mesh_id, &secret));
229
230        // Create connection state graph with config thresholds
231        let connection_graph = ConnectionStateGraph::with_config(
232            config.peer_config.rssi_degraded_threshold,
233            config.peer_config.lost_timeout_ms,
234        );
235
236        Self {
237            config,
238            peer_manager,
239            document_sync,
240            observers: ObserverManager::new(),
241            last_sync_ms: std::sync::atomic::AtomicU32::new(0),
242            last_cleanup_ms: std::sync::atomic::AtomicU32::new(0),
243            encryption_key,
244            peer_sessions: std::sync::Mutex::new(None),
245            connection_graph: std::sync::Mutex::new(connection_graph),
246        }
247    }
248
249    // ==================== Encryption ====================
250
251    /// Check if mesh-wide encryption is enabled
252    pub fn is_encryption_enabled(&self) -> bool {
253        self.encryption_key.is_some()
254    }
255
256    /// Check if strict encryption mode is enabled
257    ///
258    /// Returns true only if both encryption and strict_encryption are enabled.
259    pub fn is_strict_encryption_enabled(&self) -> bool {
260        self.config.strict_encryption && self.encryption_key.is_some()
261    }
262
263    /// Enable mesh-wide encryption with a shared secret
264    ///
265    /// Derives a ChaCha20-Poly1305 key from the secret using HKDF-SHA256.
266    /// All mesh participants must use the same secret to communicate.
267    pub fn enable_encryption(&mut self, secret: &[u8; 32]) {
268        self.encryption_key = Some(MeshEncryptionKey::from_shared_secret(
269            &self.config.mesh_id,
270            secret,
271        ));
272    }
273
274    /// Disable mesh-wide encryption
275    pub fn disable_encryption(&mut self) {
276        self.encryption_key = None;
277    }
278
279    /// Encrypt document bytes for transmission
280    ///
281    /// Returns the encrypted bytes with ENCRYPTED_MARKER prefix, or the
282    /// original bytes if encryption is disabled.
283    fn encrypt_document(&self, plaintext: &[u8]) -> Vec<u8> {
284        match &self.encryption_key {
285            Some(key) => {
286                // Encrypt and prepend marker
287                match key.encrypt_to_bytes(plaintext) {
288                    Ok(ciphertext) => {
289                        let mut buf = Vec::with_capacity(2 + ciphertext.len());
290                        buf.push(ENCRYPTED_MARKER);
291                        buf.push(0x00); // reserved
292                        buf.extend_from_slice(&ciphertext);
293                        buf
294                    }
295                    Err(e) => {
296                        log::error!("Encryption failed: {}", e);
297                        // Fall back to unencrypted on error (shouldn't happen)
298                        plaintext.to_vec()
299                    }
300                }
301            }
302            None => plaintext.to_vec(),
303        }
304    }
305
306    /// Decrypt document bytes received from peer
307    ///
308    /// Returns the decrypted bytes if encrypted and valid, or the original
309    /// bytes if not encrypted. Returns None if decryption fails.
310    ///
311    /// In strict encryption mode (when both encryption and strict_encryption are enabled),
312    /// unencrypted documents are rejected and trigger a SecurityViolation event.
313    fn decrypt_document<'a>(
314        &self,
315        data: &'a [u8],
316        source_hint: Option<&str>,
317    ) -> Option<std::borrow::Cow<'a, [u8]>> {
318        // Check for encrypted marker
319        if data.len() >= 2 && data[0] == ENCRYPTED_MARKER {
320            // Encrypted document
321            let _reserved = data[1];
322            let encrypted_payload = &data[2..];
323
324            match &self.encryption_key {
325                Some(key) => match key.decrypt_from_bytes(encrypted_payload) {
326                    Ok(plaintext) => Some(std::borrow::Cow::Owned(plaintext)),
327                    Err(e) => {
328                        log::warn!("Decryption failed (wrong key or corrupted): {}", e);
329                        self.notify(HiveEvent::SecurityViolation {
330                            kind: SecurityViolationKind::DecryptionFailed,
331                            source: source_hint.map(String::from),
332                        });
333                        None
334                    }
335                },
336                None => {
337                    log::warn!("Received encrypted document but encryption not enabled");
338                    None
339                }
340            }
341        } else {
342            // Unencrypted document
343            // Check strict encryption mode
344            if self.config.strict_encryption && self.encryption_key.is_some() {
345                log::warn!(
346                    "Rejected unencrypted document in strict encryption mode (source: {:?})",
347                    source_hint
348                );
349                self.notify(HiveEvent::SecurityViolation {
350                    kind: SecurityViolationKind::UnencryptedInStrictMode,
351                    source: source_hint.map(String::from),
352                });
353                None
354            } else {
355                // Permissive mode: accept unencrypted for backward compatibility
356                Some(std::borrow::Cow::Borrowed(data))
357            }
358        }
359    }
360
361    // ==================== Per-Peer E2EE ====================
362
363    /// Enable per-peer E2EE capability
364    ///
365    /// Creates a new identity key for this node. This allows establishing
366    /// encrypted sessions with specific peers where only the sender and
367    /// recipient can read messages (other mesh members cannot).
368    pub fn enable_peer_e2ee(&self) {
369        let mut sessions = self.peer_sessions.lock().unwrap();
370        if sessions.is_none() {
371            *sessions = Some(PeerSessionManager::new(self.config.node_id));
372            log::info!(
373                "Per-peer E2EE enabled for node {:08X}",
374                self.config.node_id.as_u32()
375            );
376        }
377    }
378
379    /// Disable per-peer E2EE capability
380    ///
381    /// Clears all peer sessions and disables E2EE.
382    pub fn disable_peer_e2ee(&self) {
383        let mut sessions = self.peer_sessions.lock().unwrap();
384        *sessions = None;
385        log::info!("Per-peer E2EE disabled");
386    }
387
388    /// Check if per-peer E2EE is enabled
389    pub fn is_peer_e2ee_enabled(&self) -> bool {
390        self.peer_sessions.lock().unwrap().is_some()
391    }
392
393    /// Get our E2EE public key (for sharing with peers)
394    ///
395    /// Returns None if per-peer E2EE is not enabled.
396    pub fn peer_e2ee_public_key(&self) -> Option<[u8; 32]> {
397        self.peer_sessions
398            .lock()
399            .unwrap()
400            .as_ref()
401            .map(|s| s.our_public_key())
402    }
403
404    /// Initiate E2EE session with a specific peer
405    ///
406    /// Returns the key exchange message bytes to send to the peer.
407    /// The message should be broadcast/sent to the peer.
408    /// Returns None if per-peer E2EE is not enabled.
409    pub fn initiate_peer_e2ee(&self, peer_node_id: NodeId, now_ms: u64) -> Option<Vec<u8>> {
410        let mut sessions = self.peer_sessions.lock().unwrap();
411        let session_mgr = sessions.as_mut()?;
412
413        let key_exchange = session_mgr.initiate_session(peer_node_id, now_ms);
414        let mut buf = Vec::with_capacity(2 + 37);
415        buf.push(KEY_EXCHANGE_MARKER);
416        buf.push(0x00); // reserved
417        buf.extend_from_slice(&key_exchange.encode());
418
419        log::info!(
420            "Initiated E2EE session with peer {:08X}",
421            peer_node_id.as_u32()
422        );
423        Some(buf)
424    }
425
426    /// Check if we have an established E2EE session with a peer
427    pub fn has_peer_e2ee_session(&self, peer_node_id: NodeId) -> bool {
428        self.peer_sessions
429            .lock()
430            .unwrap()
431            .as_ref()
432            .is_some_and(|s| s.has_session(peer_node_id))
433    }
434
435    /// Get E2EE session state with a peer
436    pub fn peer_e2ee_session_state(&self, peer_node_id: NodeId) -> Option<SessionState> {
437        self.peer_sessions
438            .lock()
439            .unwrap()
440            .as_ref()
441            .and_then(|s| s.session_state(peer_node_id))
442    }
443
444    /// Send an E2EE encrypted message to a specific peer
445    ///
446    /// Returns the encrypted message bytes to send, or None if no session exists.
447    /// The message should be sent directly to the peer (not broadcast).
448    pub fn send_peer_e2ee(
449        &self,
450        peer_node_id: NodeId,
451        plaintext: &[u8],
452        now_ms: u64,
453    ) -> Option<Vec<u8>> {
454        let mut sessions = self.peer_sessions.lock().unwrap();
455        let session_mgr = sessions.as_mut()?;
456
457        match session_mgr.encrypt_for_peer(peer_node_id, plaintext, now_ms) {
458            Ok(encrypted) => {
459                let mut buf = Vec::with_capacity(2 + encrypted.encode().len());
460                buf.push(PEER_E2EE_MARKER);
461                buf.push(0x00); // reserved
462                buf.extend_from_slice(&encrypted.encode());
463                Some(buf)
464            }
465            Err(e) => {
466                log::warn!(
467                    "Failed to encrypt for peer {:08X}: {:?}",
468                    peer_node_id.as_u32(),
469                    e
470                );
471                None
472            }
473        }
474    }
475
476    /// Close E2EE session with a peer
477    pub fn close_peer_e2ee(&self, peer_node_id: NodeId) {
478        let mut sessions = self.peer_sessions.lock().unwrap();
479        if let Some(session_mgr) = sessions.as_mut() {
480            session_mgr.close_session(peer_node_id);
481            self.notify(HiveEvent::PeerE2eeClosed { peer_node_id });
482            log::info!(
483                "Closed E2EE session with peer {:08X}",
484                peer_node_id.as_u32()
485            );
486        }
487    }
488
489    /// Get count of active E2EE sessions
490    pub fn peer_e2ee_session_count(&self) -> usize {
491        self.peer_sessions
492            .lock()
493            .unwrap()
494            .as_ref()
495            .map(|s| s.session_count())
496            .unwrap_or(0)
497    }
498
499    /// Get count of established E2EE sessions
500    pub fn peer_e2ee_established_count(&self) -> usize {
501        self.peer_sessions
502            .lock()
503            .unwrap()
504            .as_ref()
505            .map(|s| s.established_count())
506            .unwrap_or(0)
507    }
508
509    /// Handle incoming key exchange message
510    ///
511    /// Called internally when we receive a KEY_EXCHANGE_MARKER message.
512    /// Returns the response key exchange bytes to send back, or None if invalid.
513    fn handle_key_exchange(&self, data: &[u8], now_ms: u64) -> Option<Vec<u8>> {
514        if data.len() < 2 || data[0] != KEY_EXCHANGE_MARKER {
515            return None;
516        }
517
518        let payload = &data[2..];
519        let msg = KeyExchangeMessage::decode(payload)?;
520
521        let mut sessions = self.peer_sessions.lock().unwrap();
522        let session_mgr = sessions.as_mut()?;
523
524        let (response, established) = session_mgr.handle_key_exchange(&msg, now_ms)?;
525
526        if established {
527            self.notify(HiveEvent::PeerE2eeEstablished {
528                peer_node_id: msg.sender_node_id,
529            });
530            log::info!(
531                "E2EE session established with peer {:08X}",
532                msg.sender_node_id.as_u32()
533            );
534        }
535
536        // Return response key exchange
537        let mut buf = Vec::with_capacity(2 + 37);
538        buf.push(KEY_EXCHANGE_MARKER);
539        buf.push(0x00);
540        buf.extend_from_slice(&response.encode());
541        Some(buf)
542    }
543
544    /// Handle incoming E2EE encrypted message
545    ///
546    /// Called internally when we receive a PEER_E2EE_MARKER message.
547    /// Decrypts and notifies observers of the received message.
548    fn handle_peer_e2ee_message(&self, data: &[u8], now_ms: u64) -> Option<Vec<u8>> {
549        if data.len() < 2 || data[0] != PEER_E2EE_MARKER {
550            return None;
551        }
552
553        let payload = &data[2..];
554        let msg = PeerEncryptedMessage::decode(payload)?;
555
556        let mut sessions = self.peer_sessions.lock().unwrap();
557        let session_mgr = sessions.as_mut()?;
558
559        match session_mgr.decrypt_from_peer(&msg, now_ms) {
560            Ok(plaintext) => {
561                // Notify observers of the decrypted message
562                self.notify(HiveEvent::PeerE2eeMessageReceived {
563                    from_node: msg.sender_node_id,
564                    data: plaintext.clone(),
565                });
566                Some(plaintext)
567            }
568            Err(e) => {
569                log::warn!(
570                    "Failed to decrypt E2EE message from {:08X}: {:?}",
571                    msg.sender_node_id.as_u32(),
572                    e
573                );
574                None
575            }
576        }
577    }
578
579    // ==================== Configuration ====================
580
581    /// Get our node ID
582    pub fn node_id(&self) -> NodeId {
583        self.config.node_id
584    }
585
586    /// Get our callsign
587    pub fn callsign(&self) -> &str {
588        &self.config.callsign
589    }
590
591    /// Get the mesh ID
592    pub fn mesh_id(&self) -> &str {
593        &self.config.mesh_id
594    }
595
596    /// Get the device name for BLE advertising
597    pub fn device_name(&self) -> String {
598        format!(
599            "HIVE_{}-{:08X}",
600            self.config.mesh_id,
601            self.config.node_id.as_u32()
602        )
603    }
604
605    // ==================== Observer Management ====================
606
607    /// Add an observer for mesh events
608    pub fn add_observer(&self, observer: Arc<dyn HiveObserver>) {
609        self.observers.add(observer);
610    }
611
612    /// Remove an observer
613    pub fn remove_observer(&self, observer: &Arc<dyn HiveObserver>) {
614        self.observers.remove(observer);
615    }
616
617    // ==================== User Actions ====================
618
619    /// Send an emergency alert
620    ///
621    /// Returns the document bytes to broadcast to all peers.
622    /// If encryption is enabled, the document is encrypted.
623    pub fn send_emergency(&self, timestamp: u64) -> Vec<u8> {
624        let data = self.document_sync.send_emergency(timestamp);
625        self.notify(HiveEvent::MeshStateChanged {
626            peer_count: self.peer_manager.peer_count(),
627            connected_count: self.peer_manager.connected_count(),
628        });
629        self.encrypt_document(&data)
630    }
631
632    /// Send an ACK response
633    ///
634    /// Returns the document bytes to broadcast to all peers.
635    /// If encryption is enabled, the document is encrypted.
636    pub fn send_ack(&self, timestamp: u64) -> Vec<u8> {
637        let data = self.document_sync.send_ack(timestamp);
638        self.notify(HiveEvent::MeshStateChanged {
639            peer_count: self.peer_manager.peer_count(),
640            connected_count: self.peer_manager.connected_count(),
641        });
642        self.encrypt_document(&data)
643    }
644
645    /// Clear the current event (emergency or ack)
646    pub fn clear_event(&self) {
647        self.document_sync.clear_event();
648    }
649
650    /// Check if emergency is active
651    pub fn is_emergency_active(&self) -> bool {
652        self.document_sync.is_emergency_active()
653    }
654
655    /// Check if ACK is active
656    pub fn is_ack_active(&self) -> bool {
657        self.document_sync.is_ack_active()
658    }
659
660    /// Get current event type
661    pub fn current_event(&self) -> Option<EventType> {
662        self.document_sync.current_event()
663    }
664
665    // ==================== Emergency Management (Document-Based) ====================
666
667    /// Start a new emergency event with ACK tracking
668    ///
669    /// Creates an emergency event that tracks ACKs from all known peers.
670    /// Pass the list of known peer node IDs to track.
671    /// Returns the document bytes to broadcast.
672    /// If encryption is enabled, the document is encrypted.
673    pub fn start_emergency(&self, timestamp: u64, known_peers: &[u32]) -> Vec<u8> {
674        let data = self.document_sync.start_emergency(timestamp, known_peers);
675        self.notify(HiveEvent::MeshStateChanged {
676            peer_count: self.peer_manager.peer_count(),
677            connected_count: self.peer_manager.connected_count(),
678        });
679        self.encrypt_document(&data)
680    }
681
682    /// Start a new emergency using all currently known peers
683    ///
684    /// Convenience method that automatically includes all discovered peers.
685    pub fn start_emergency_with_known_peers(&self, timestamp: u64) -> Vec<u8> {
686        let peers: Vec<u32> = self
687            .peer_manager
688            .get_peers()
689            .iter()
690            .map(|p| p.node_id.as_u32())
691            .collect();
692        self.start_emergency(timestamp, &peers)
693    }
694
695    /// Record our ACK for the current emergency
696    ///
697    /// Returns the document bytes to broadcast, or None if no emergency is active.
698    /// If encryption is enabled, the document is encrypted.
699    pub fn ack_emergency(&self, timestamp: u64) -> Option<Vec<u8>> {
700        let result = self.document_sync.ack_emergency(timestamp);
701        if result.is_some() {
702            self.notify(HiveEvent::MeshStateChanged {
703                peer_count: self.peer_manager.peer_count(),
704                connected_count: self.peer_manager.connected_count(),
705            });
706        }
707        result.map(|data| self.encrypt_document(&data))
708    }
709
710    /// Clear the current emergency event
711    pub fn clear_emergency(&self) {
712        self.document_sync.clear_emergency();
713    }
714
715    /// Check if there's an active emergency
716    pub fn has_active_emergency(&self) -> bool {
717        self.document_sync.has_active_emergency()
718    }
719
720    /// Get emergency status info
721    ///
722    /// Returns (source_node, timestamp, acked_count, pending_count) if emergency is active.
723    pub fn get_emergency_status(&self) -> Option<(u32, u64, usize, usize)> {
724        self.document_sync.get_emergency_status()
725    }
726
727    /// Check if a specific peer has ACKed the current emergency
728    pub fn has_peer_acked(&self, peer_id: u32) -> bool {
729        self.document_sync.has_peer_acked(peer_id)
730    }
731
732    /// Check if all peers have ACKed the current emergency
733    pub fn all_peers_acked(&self) -> bool {
734        self.document_sync.all_peers_acked()
735    }
736
737    // ==================== BLE Callbacks (Platform -> Mesh) ====================
738
739    /// Called when a BLE device is discovered
740    ///
741    /// Returns `Some(HivePeer)` if this is a new HIVE peer on our mesh.
742    pub fn on_ble_discovered(
743        &self,
744        identifier: &str,
745        name: Option<&str>,
746        rssi: i8,
747        mesh_id: Option<&str>,
748        now_ms: u64,
749    ) -> Option<HivePeer> {
750        let (node_id, is_new) = self
751            .peer_manager
752            .on_discovered(identifier, name, rssi, mesh_id, now_ms)?;
753
754        let peer = self.peer_manager.get_peer(node_id)?;
755
756        // Update connection graph
757        {
758            let mut graph = self.connection_graph.lock().unwrap();
759            graph.on_discovered(
760                node_id,
761                identifier.to_string(),
762                name.map(|s| s.to_string()),
763                mesh_id.map(|s| s.to_string()),
764                rssi,
765                now_ms,
766            );
767        }
768
769        if is_new {
770            self.notify(HiveEvent::PeerDiscovered { peer: peer.clone() });
771            self.notify_mesh_state_changed();
772        }
773
774        Some(peer)
775    }
776
777    /// Called when a BLE connection is established (outgoing)
778    ///
779    /// Returns the NodeId if this identifier is known.
780    pub fn on_ble_connected(&self, identifier: &str, now_ms: u64) -> Option<NodeId> {
781        let node_id = self.peer_manager.on_connected(identifier, now_ms)?;
782
783        // Update connection graph
784        {
785            let mut graph = self.connection_graph.lock().unwrap();
786            graph.on_connected(node_id, now_ms);
787        }
788
789        self.notify(HiveEvent::PeerConnected { node_id });
790        self.notify_mesh_state_changed();
791        Some(node_id)
792    }
793
794    /// Called when a BLE connection is lost
795    pub fn on_ble_disconnected(
796        &self,
797        identifier: &str,
798        reason: DisconnectReason,
799    ) -> Option<NodeId> {
800        let (node_id, observer_reason) = self.peer_manager.on_disconnected(identifier, reason)?;
801
802        // Update connection graph (convert observer reason to platform reason)
803        {
804            let mut graph = self.connection_graph.lock().unwrap();
805            let platform_reason = match observer_reason {
806                DisconnectReason::LocalRequest => crate::platform::DisconnectReason::LocalRequest,
807                DisconnectReason::RemoteRequest => crate::platform::DisconnectReason::RemoteRequest,
808                DisconnectReason::Timeout => crate::platform::DisconnectReason::Timeout,
809                DisconnectReason::LinkLoss => crate::platform::DisconnectReason::LinkLoss,
810                DisconnectReason::ConnectionFailed => {
811                    crate::platform::DisconnectReason::ConnectionFailed
812                }
813                DisconnectReason::Unknown => crate::platform::DisconnectReason::Unknown,
814            };
815            let now_ms = std::time::SystemTime::now()
816                .duration_since(std::time::UNIX_EPOCH)
817                .map(|d| d.as_millis() as u64)
818                .unwrap_or(0);
819            graph.on_disconnected(node_id, platform_reason, now_ms);
820        }
821
822        self.notify(HiveEvent::PeerDisconnected {
823            node_id,
824            reason: observer_reason,
825        });
826        self.notify_mesh_state_changed();
827        Some(node_id)
828    }
829
830    /// Called when a BLE connection is lost, using NodeId directly
831    ///
832    /// Alternative to on_ble_disconnected() when only NodeId is known (e.g., ESP32).
833    pub fn on_peer_disconnected(&self, node_id: NodeId, reason: DisconnectReason) {
834        if self
835            .peer_manager
836            .on_disconnected_by_node_id(node_id, reason)
837        {
838            // Update connection graph
839            {
840                let mut graph = self.connection_graph.lock().unwrap();
841                let platform_reason = match reason {
842                    DisconnectReason::LocalRequest => {
843                        crate::platform::DisconnectReason::LocalRequest
844                    }
845                    DisconnectReason::RemoteRequest => {
846                        crate::platform::DisconnectReason::RemoteRequest
847                    }
848                    DisconnectReason::Timeout => crate::platform::DisconnectReason::Timeout,
849                    DisconnectReason::LinkLoss => crate::platform::DisconnectReason::LinkLoss,
850                    DisconnectReason::ConnectionFailed => {
851                        crate::platform::DisconnectReason::ConnectionFailed
852                    }
853                    DisconnectReason::Unknown => crate::platform::DisconnectReason::Unknown,
854                };
855                let now_ms = std::time::SystemTime::now()
856                    .duration_since(std::time::UNIX_EPOCH)
857                    .map(|d| d.as_millis() as u64)
858                    .unwrap_or(0);
859                graph.on_disconnected(node_id, platform_reason, now_ms);
860            }
861
862            self.notify(HiveEvent::PeerDisconnected { node_id, reason });
863            self.notify_mesh_state_changed();
864        }
865    }
866
867    /// Called when a remote device connects to us (incoming connection)
868    ///
869    /// Use this when we're acting as a peripheral and a central connects to us.
870    pub fn on_incoming_connection(&self, identifier: &str, node_id: NodeId, now_ms: u64) -> bool {
871        let is_new = self
872            .peer_manager
873            .on_incoming_connection(identifier, node_id, now_ms);
874
875        // Update connection graph
876        {
877            let mut graph = self.connection_graph.lock().unwrap();
878            if is_new {
879                graph.on_discovered(
880                    node_id,
881                    identifier.to_string(),
882                    None,
883                    Some(self.config.mesh_id.clone()),
884                    -50, // Default good RSSI for incoming connections
885                    now_ms,
886                );
887            }
888            graph.on_connected(node_id, now_ms);
889        }
890
891        if is_new {
892            if let Some(peer) = self.peer_manager.get_peer(node_id) {
893                self.notify(HiveEvent::PeerDiscovered { peer });
894            }
895        }
896
897        self.notify(HiveEvent::PeerConnected { node_id });
898        self.notify_mesh_state_changed();
899
900        is_new
901    }
902
903    /// Called when data is received from a peer
904    ///
905    /// Parses the document, merges it, and generates appropriate events.
906    /// If encryption is enabled, decrypts the document first.
907    /// Handles per-peer E2EE messages (KEY_EXCHANGE and PEER_E2EE markers).
908    /// Returns the source NodeId and whether the document contained an event.
909    pub fn on_ble_data_received(
910        &self,
911        identifier: &str,
912        data: &[u8],
913        now_ms: u64,
914    ) -> Option<DataReceivedResult> {
915        // Get node ID from identifier
916        let node_id = self.peer_manager.get_node_id(identifier)?;
917
918        // Check for per-peer E2EE messages first
919        if data.len() >= 2 {
920            match data[0] {
921                KEY_EXCHANGE_MARKER => {
922                    // Handle key exchange - returns response to send back
923                    let _response = self.handle_key_exchange(data, now_ms);
924                    // Return None as this isn't a document sync
925                    return None;
926                }
927                PEER_E2EE_MARKER => {
928                    // Handle encrypted peer message
929                    let _plaintext = self.handle_peer_e2ee_message(data, now_ms);
930                    // Return None as this isn't a document sync
931                    return None;
932                }
933                _ => {}
934            }
935        }
936
937        // Decrypt if encrypted (mesh-wide encryption)
938        let decrypted = self.decrypt_document(data, Some(identifier))?;
939
940        // Merge the document
941        let result = self.document_sync.merge_document(&decrypted)?;
942
943        // Record sync
944        self.peer_manager.record_sync(node_id, now_ms);
945
946        // Generate events based on what was received
947        if result.is_emergency() {
948            self.notify(HiveEvent::EmergencyReceived {
949                from_node: result.source_node,
950            });
951        } else if result.is_ack() {
952            self.notify(HiveEvent::AckReceived {
953                from_node: result.source_node,
954            });
955        }
956
957        if result.counter_changed {
958            self.notify(HiveEvent::DocumentSynced {
959                from_node: result.source_node,
960                total_count: result.total_count,
961            });
962        }
963
964        Some(DataReceivedResult {
965            source_node: result.source_node,
966            is_emergency: result.is_emergency(),
967            is_ack: result.is_ack(),
968            counter_changed: result.counter_changed,
969            emergency_changed: result.emergency_changed,
970            total_count: result.total_count,
971            event_timestamp: result.event.as_ref().map(|e| e.timestamp).unwrap_or(0),
972        })
973    }
974
975    /// Called when data is received but we don't have the identifier mapped
976    ///
977    /// Use this when receiving data from a peripheral we discovered.
978    /// If encryption is enabled, decrypts the document first.
979    /// Handles per-peer E2EE messages (KEY_EXCHANGE and PEER_E2EE markers).
980    pub fn on_ble_data_received_from_node(
981        &self,
982        node_id: NodeId,
983        data: &[u8],
984        now_ms: u64,
985    ) -> Option<DataReceivedResult> {
986        // Check for per-peer E2EE messages first
987        if data.len() >= 2 {
988            match data[0] {
989                KEY_EXCHANGE_MARKER => {
990                    let _response = self.handle_key_exchange(data, now_ms);
991                    return None;
992                }
993                PEER_E2EE_MARKER => {
994                    let _plaintext = self.handle_peer_e2ee_message(data, now_ms);
995                    return None;
996                }
997                _ => {}
998            }
999        }
1000
1001        // Decrypt if encrypted (mesh-wide encryption)
1002        let source_hint = format!("node:{:08X}", node_id.as_u32());
1003        let decrypted = self.decrypt_document(data, Some(&source_hint))?;
1004
1005        // Merge the document
1006        let result = self.document_sync.merge_document(&decrypted)?;
1007
1008        // Record sync
1009        self.peer_manager.record_sync(node_id, now_ms);
1010
1011        // Generate events based on what was received
1012        if result.is_emergency() {
1013            self.notify(HiveEvent::EmergencyReceived {
1014                from_node: result.source_node,
1015            });
1016        } else if result.is_ack() {
1017            self.notify(HiveEvent::AckReceived {
1018                from_node: result.source_node,
1019            });
1020        }
1021
1022        if result.counter_changed {
1023            self.notify(HiveEvent::DocumentSynced {
1024                from_node: result.source_node,
1025                total_count: result.total_count,
1026            });
1027        }
1028
1029        Some(DataReceivedResult {
1030            source_node: result.source_node,
1031            is_emergency: result.is_emergency(),
1032            is_ack: result.is_ack(),
1033            counter_changed: result.counter_changed,
1034            emergency_changed: result.emergency_changed,
1035            total_count: result.total_count,
1036            event_timestamp: result.event.as_ref().map(|e| e.timestamp).unwrap_or(0),
1037        })
1038    }
1039
1040    /// Called when data is received without a known identifier
1041    ///
1042    /// This is the simplest data receive method - it extracts the source node_id
1043    /// from the document itself. Use this when you don't track identifiers
1044    /// (e.g., ESP32 NimBLE).
1045    /// If encryption is enabled, decrypts the document first.
1046    /// Handles per-peer E2EE messages (KEY_EXCHANGE and PEER_E2EE markers).
1047    pub fn on_ble_data(
1048        &self,
1049        identifier: &str,
1050        data: &[u8],
1051        now_ms: u64,
1052    ) -> Option<DataReceivedResult> {
1053        // Check for per-peer E2EE messages first
1054        if data.len() >= 2 {
1055            match data[0] {
1056                KEY_EXCHANGE_MARKER => {
1057                    let _response = self.handle_key_exchange(data, now_ms);
1058                    return None;
1059                }
1060                PEER_E2EE_MARKER => {
1061                    let _plaintext = self.handle_peer_e2ee_message(data, now_ms);
1062                    return None;
1063                }
1064                _ => {}
1065            }
1066        }
1067
1068        // Decrypt if encrypted (mesh-wide encryption)
1069        let decrypted = self.decrypt_document(data, Some(identifier))?;
1070
1071        // Merge the document (extracts node_id internally)
1072        let result = self.document_sync.merge_document(&decrypted)?;
1073
1074        // Record sync using the source_node from the merged document
1075        self.peer_manager.record_sync(result.source_node, now_ms);
1076
1077        // Add the peer if not already known (creates peer entry from document data)
1078        self.peer_manager
1079            .on_incoming_connection(identifier, result.source_node, now_ms);
1080
1081        // Generate events based on what was received
1082        if result.is_emergency() {
1083            self.notify(HiveEvent::EmergencyReceived {
1084                from_node: result.source_node,
1085            });
1086        } else if result.is_ack() {
1087            self.notify(HiveEvent::AckReceived {
1088                from_node: result.source_node,
1089            });
1090        }
1091
1092        if result.counter_changed {
1093            self.notify(HiveEvent::DocumentSynced {
1094                from_node: result.source_node,
1095                total_count: result.total_count,
1096            });
1097        }
1098
1099        Some(DataReceivedResult {
1100            source_node: result.source_node,
1101            is_emergency: result.is_emergency(),
1102            is_ack: result.is_ack(),
1103            counter_changed: result.counter_changed,
1104            emergency_changed: result.emergency_changed,
1105            total_count: result.total_count,
1106            event_timestamp: result.event.as_ref().map(|e| e.timestamp).unwrap_or(0),
1107        })
1108    }
1109
1110    // ==================== Periodic Maintenance ====================
1111
1112    /// Periodic tick - call this regularly (e.g., every second)
1113    ///
1114    /// Performs:
1115    /// - Stale peer cleanup
1116    /// - Periodic sync broadcast (if interval elapsed)
1117    ///
1118    /// Returns `Some(data)` if a sync broadcast is needed.
1119    pub fn tick(&self, now_ms: u64) -> Option<Vec<u8>> {
1120        use std::sync::atomic::Ordering;
1121
1122        // Use u32 for atomic storage (wraps every ~49 days, intervals still work)
1123        let now_ms_32 = now_ms as u32;
1124
1125        // Cleanup stale peers
1126        let last_cleanup = self.last_cleanup_ms.load(Ordering::Relaxed);
1127        let cleanup_elapsed = now_ms_32.wrapping_sub(last_cleanup);
1128        if cleanup_elapsed >= self.config.peer_config.cleanup_interval_ms as u32 {
1129            self.last_cleanup_ms.store(now_ms_32, Ordering::Relaxed);
1130            let removed = self.peer_manager.cleanup_stale(now_ms);
1131            for node_id in &removed {
1132                self.notify(HiveEvent::PeerLost { node_id: *node_id });
1133            }
1134            if !removed.is_empty() {
1135                self.notify_mesh_state_changed();
1136            }
1137
1138            // Run connection graph maintenance (transition Disconnected -> Lost)
1139            {
1140                let mut graph = self.connection_graph.lock().unwrap();
1141                let newly_lost = graph.tick(now_ms);
1142                // Also cleanup peers lost for more than peer_timeout
1143                graph.cleanup_lost(self.config.peer_config.peer_timeout_ms, now_ms);
1144                drop(graph);
1145
1146                // Emit PeerLost events for newly lost peers from graph
1147                // (these may differ from peer_manager removals)
1148                for node_id in newly_lost {
1149                    // Only notify if not already notified by peer_manager
1150                    if !removed.contains(&node_id) {
1151                        self.notify(HiveEvent::PeerLost { node_id });
1152                    }
1153                }
1154            }
1155        }
1156
1157        // Check if sync broadcast is needed
1158        let last_sync = self.last_sync_ms.load(Ordering::Relaxed);
1159        let sync_elapsed = now_ms_32.wrapping_sub(last_sync);
1160        if sync_elapsed >= self.config.sync_interval_ms as u32 {
1161            self.last_sync_ms.store(now_ms_32, Ordering::Relaxed);
1162            // Only broadcast if we have connected peers
1163            if self.peer_manager.connected_count() > 0 {
1164                let doc = self.document_sync.build_document();
1165                return Some(self.encrypt_document(&doc));
1166            }
1167        }
1168
1169        None
1170    }
1171
1172    // ==================== State Queries ====================
1173
1174    /// Get all known peers
1175    pub fn get_peers(&self) -> Vec<HivePeer> {
1176        self.peer_manager.get_peers()
1177    }
1178
1179    /// Get connected peers only
1180    pub fn get_connected_peers(&self) -> Vec<HivePeer> {
1181        self.peer_manager.get_connected_peers()
1182    }
1183
1184    /// Get a specific peer by NodeId
1185    pub fn get_peer(&self, node_id: NodeId) -> Option<HivePeer> {
1186        self.peer_manager.get_peer(node_id)
1187    }
1188
1189    /// Get peer count
1190    pub fn peer_count(&self) -> usize {
1191        self.peer_manager.peer_count()
1192    }
1193
1194    /// Get connected peer count
1195    pub fn connected_count(&self) -> usize {
1196        self.peer_manager.connected_count()
1197    }
1198
1199    /// Check if a device mesh ID matches our mesh
1200    pub fn matches_mesh(&self, device_mesh_id: Option<&str>) -> bool {
1201        self.peer_manager.matches_mesh(device_mesh_id)
1202    }
1203
1204    // ==================== Connection State Graph ====================
1205
1206    /// Get the connection state graph with all peer states
1207    ///
1208    /// Returns a snapshot of all tracked peers and their connection lifecycle state.
1209    /// Apps can use this to display appropriate UI indicators:
1210    /// - Green for Connected peers
1211    /// - Yellow for Degraded or RecentlyDisconnected peers
1212    /// - Gray for Lost peers
1213    ///
1214    /// # Example
1215    /// ```ignore
1216    /// let states = mesh.get_connection_graph();
1217    /// for peer in states {
1218    ///     match peer.state {
1219    ///         ConnectionState::Connected => show_green_indicator(&peer),
1220    ///         ConnectionState::Degraded => show_yellow_indicator(&peer),
1221    ///         ConnectionState::Disconnected => show_stale_indicator(&peer),
1222    ///         ConnectionState::Lost => show_gray_indicator(&peer),
1223    ///         _ => {}
1224    ///     }
1225    /// }
1226    /// ```
1227    pub fn get_connection_graph(&self) -> Vec<PeerConnectionState> {
1228        self.connection_graph.lock().unwrap().get_all_owned()
1229    }
1230
1231    /// Get a specific peer's connection state
1232    pub fn get_peer_connection_state(&self, node_id: NodeId) -> Option<PeerConnectionState> {
1233        self.connection_graph
1234            .lock()
1235            .unwrap()
1236            .get_peer(node_id)
1237            .cloned()
1238    }
1239
1240    /// Get all currently connected peers from the connection graph
1241    pub fn get_connected_states(&self) -> Vec<PeerConnectionState> {
1242        self.connection_graph
1243            .lock()
1244            .unwrap()
1245            .get_connected()
1246            .into_iter()
1247            .cloned()
1248            .collect()
1249    }
1250
1251    /// Get peers in degraded state (connected but poor signal quality)
1252    pub fn get_degraded_peers(&self) -> Vec<PeerConnectionState> {
1253        self.connection_graph
1254            .lock()
1255            .unwrap()
1256            .get_degraded()
1257            .into_iter()
1258            .cloned()
1259            .collect()
1260    }
1261
1262    /// Get peers that disconnected within the specified time window
1263    ///
1264    /// Useful for showing "stale" peers that were recently connected.
1265    pub fn get_recently_disconnected(
1266        &self,
1267        within_ms: u64,
1268        now_ms: u64,
1269    ) -> Vec<PeerConnectionState> {
1270        self.connection_graph
1271            .lock()
1272            .unwrap()
1273            .get_recently_disconnected(within_ms, now_ms)
1274            .into_iter()
1275            .cloned()
1276            .collect()
1277    }
1278
1279    /// Get peers in Lost state (disconnected and no longer advertising)
1280    pub fn get_lost_peers(&self) -> Vec<PeerConnectionState> {
1281        self.connection_graph
1282            .lock()
1283            .unwrap()
1284            .get_lost()
1285            .into_iter()
1286            .cloned()
1287            .collect()
1288    }
1289
1290    /// Get summary counts of peers in each connection state
1291    pub fn get_connection_state_counts(&self) -> StateCountSummary {
1292        self.connection_graph.lock().unwrap().state_counts()
1293    }
1294
1295    /// Get total counter value
1296    pub fn total_count(&self) -> u64 {
1297        self.document_sync.total_count()
1298    }
1299
1300    /// Get document version
1301    pub fn document_version(&self) -> u32 {
1302        self.document_sync.version()
1303    }
1304
1305    /// Get document version (alias)
1306    pub fn version(&self) -> u32 {
1307        self.document_sync.version()
1308    }
1309
1310    /// Update health status (battery percentage)
1311    pub fn update_health(&self, battery_percent: u8) {
1312        self.document_sync.update_health(battery_percent);
1313    }
1314
1315    /// Update activity level (0=still, 1=walking, 2=running, 3=fall)
1316    pub fn update_activity(&self, activity: u8) {
1317        self.document_sync.update_activity(activity);
1318    }
1319
1320    /// Update full health status (battery and activity)
1321    pub fn update_health_full(&self, battery_percent: u8, activity: u8) {
1322        self.document_sync
1323            .update_health_full(battery_percent, activity);
1324    }
1325
1326    /// Build current document for transmission
1327    ///
1328    /// If encryption is enabled, the document is encrypted.
1329    pub fn build_document(&self) -> Vec<u8> {
1330        let doc = self.document_sync.build_document();
1331        self.encrypt_document(&doc)
1332    }
1333
1334    /// Get peers that should be synced with
1335    pub fn peers_needing_sync(&self, now_ms: u64) -> Vec<HivePeer> {
1336        self.peer_manager.peers_needing_sync(now_ms)
1337    }
1338
1339    // ==================== Internal Helpers ====================
1340
1341    fn notify(&self, event: HiveEvent) {
1342        self.observers.notify(event);
1343    }
1344
1345    fn notify_mesh_state_changed(&self) {
1346        self.notify(HiveEvent::MeshStateChanged {
1347            peer_count: self.peer_manager.peer_count(),
1348            connected_count: self.peer_manager.connected_count(),
1349        });
1350    }
1351}
1352
1353/// Result from receiving BLE data
1354#[derive(Debug, Clone)]
1355pub struct DataReceivedResult {
1356    /// Node that sent this data
1357    pub source_node: NodeId,
1358
1359    /// Whether this contained an emergency event
1360    pub is_emergency: bool,
1361
1362    /// Whether this contained an ACK event
1363    pub is_ack: bool,
1364
1365    /// Whether the counter changed (new data)
1366    pub counter_changed: bool,
1367
1368    /// Whether emergency state changed (new emergency or ACK updates)
1369    pub emergency_changed: bool,
1370
1371    /// Updated total count
1372    pub total_count: u64,
1373
1374    /// Event timestamp (if event present) - use to detect duplicate events
1375    pub event_timestamp: u64,
1376}
1377
1378#[cfg(all(test, feature = "std"))]
1379mod tests {
1380    use super::*;
1381    use crate::observer::CollectingObserver;
1382
1383    fn create_mesh(node_id: u32, callsign: &str) -> HiveMesh {
1384        let config = HiveMeshConfig::new(NodeId::new(node_id), callsign, "TEST");
1385        HiveMesh::new(config)
1386    }
1387
1388    #[test]
1389    fn test_mesh_creation() {
1390        let mesh = create_mesh(0x12345678, "ALPHA-1");
1391
1392        assert_eq!(mesh.node_id().as_u32(), 0x12345678);
1393        assert_eq!(mesh.callsign(), "ALPHA-1");
1394        assert_eq!(mesh.mesh_id(), "TEST");
1395        assert_eq!(mesh.device_name(), "HIVE_TEST-12345678");
1396    }
1397
1398    #[test]
1399    fn test_peer_discovery() {
1400        let mesh = create_mesh(0x11111111, "ALPHA-1");
1401        let observer = Arc::new(CollectingObserver::new());
1402        mesh.add_observer(observer.clone());
1403
1404        // Discover a peer
1405        let peer = mesh.on_ble_discovered(
1406            "device-uuid",
1407            Some("HIVE_TEST-22222222"),
1408            -65,
1409            Some("TEST"),
1410            1000,
1411        );
1412
1413        assert!(peer.is_some());
1414        let peer = peer.unwrap();
1415        assert_eq!(peer.node_id.as_u32(), 0x22222222);
1416
1417        // Check events were generated
1418        let events = observer.events();
1419        assert!(events
1420            .iter()
1421            .any(|e| matches!(e, HiveEvent::PeerDiscovered { .. })));
1422        assert!(events
1423            .iter()
1424            .any(|e| matches!(e, HiveEvent::MeshStateChanged { .. })));
1425    }
1426
1427    #[test]
1428    fn test_connection_lifecycle() {
1429        let mesh = create_mesh(0x11111111, "ALPHA-1");
1430        let observer = Arc::new(CollectingObserver::new());
1431        mesh.add_observer(observer.clone());
1432
1433        // Discover and connect
1434        mesh.on_ble_discovered(
1435            "device-uuid",
1436            Some("HIVE_TEST-22222222"),
1437            -65,
1438            Some("TEST"),
1439            1000,
1440        );
1441
1442        let node_id = mesh.on_ble_connected("device-uuid", 2000);
1443        assert_eq!(node_id, Some(NodeId::new(0x22222222)));
1444        assert_eq!(mesh.connected_count(), 1);
1445
1446        // Disconnect
1447        let node_id = mesh.on_ble_disconnected("device-uuid", DisconnectReason::RemoteRequest);
1448        assert_eq!(node_id, Some(NodeId::new(0x22222222)));
1449        assert_eq!(mesh.connected_count(), 0);
1450
1451        // Check events
1452        let events = observer.events();
1453        assert!(events
1454            .iter()
1455            .any(|e| matches!(e, HiveEvent::PeerConnected { .. })));
1456        assert!(events
1457            .iter()
1458            .any(|e| matches!(e, HiveEvent::PeerDisconnected { .. })));
1459    }
1460
1461    #[test]
1462    fn test_emergency_flow() {
1463        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1464        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1465
1466        let observer2 = Arc::new(CollectingObserver::new());
1467        mesh2.add_observer(observer2.clone());
1468
1469        // mesh1 sends emergency
1470        let doc = mesh1.send_emergency(1000);
1471        assert!(mesh1.is_emergency_active());
1472
1473        // mesh2 receives it
1474        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1475
1476        assert!(result.is_some());
1477        let result = result.unwrap();
1478        assert!(result.is_emergency);
1479        assert_eq!(result.source_node.as_u32(), 0x11111111);
1480
1481        // Check events on mesh2
1482        let events = observer2.events();
1483        assert!(events
1484            .iter()
1485            .any(|e| matches!(e, HiveEvent::EmergencyReceived { .. })));
1486    }
1487
1488    #[test]
1489    fn test_ack_flow() {
1490        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1491        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1492
1493        let observer2 = Arc::new(CollectingObserver::new());
1494        mesh2.add_observer(observer2.clone());
1495
1496        // mesh1 sends ACK
1497        let doc = mesh1.send_ack(1000);
1498        assert!(mesh1.is_ack_active());
1499
1500        // mesh2 receives it
1501        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1502
1503        assert!(result.is_some());
1504        let result = result.unwrap();
1505        assert!(result.is_ack);
1506
1507        // Check events on mesh2
1508        let events = observer2.events();
1509        assert!(events
1510            .iter()
1511            .any(|e| matches!(e, HiveEvent::AckReceived { .. })));
1512    }
1513
1514    #[test]
1515    fn test_tick_cleanup() {
1516        let config = HiveMeshConfig::new(NodeId::new(0x11111111), "ALPHA-1", "TEST")
1517            .with_peer_timeout(10_000);
1518        let mesh = HiveMesh::new(config);
1519
1520        let observer = Arc::new(CollectingObserver::new());
1521        mesh.add_observer(observer.clone());
1522
1523        // Discover a peer
1524        mesh.on_ble_discovered(
1525            "device-uuid",
1526            Some("HIVE_TEST-22222222"),
1527            -65,
1528            Some("TEST"),
1529            1000,
1530        );
1531        assert_eq!(mesh.peer_count(), 1);
1532
1533        // Tick at t=5000 - not stale yet
1534        mesh.tick(5000);
1535        assert_eq!(mesh.peer_count(), 1);
1536
1537        // Tick at t=20000 - peer is stale (10s timeout exceeded)
1538        mesh.tick(20000);
1539        assert_eq!(mesh.peer_count(), 0);
1540
1541        // Check PeerLost event
1542        let events = observer.events();
1543        assert!(events
1544            .iter()
1545            .any(|e| matches!(e, HiveEvent::PeerLost { .. })));
1546    }
1547
1548    #[test]
1549    fn test_tick_sync_broadcast() {
1550        let config = HiveMeshConfig::new(NodeId::new(0x11111111), "ALPHA-1", "TEST")
1551            .with_sync_interval(5000);
1552        let mesh = HiveMesh::new(config);
1553
1554        // Discover and connect a peer first
1555        mesh.on_ble_discovered(
1556            "device-uuid",
1557            Some("HIVE_TEST-22222222"),
1558            -65,
1559            Some("TEST"),
1560            1000,
1561        );
1562        mesh.on_ble_connected("device-uuid", 1000);
1563
1564        // First tick at t=0 sets last_sync
1565        let _result = mesh.tick(0);
1566        // May or may not broadcast depending on initial state
1567
1568        // Tick before interval - no broadcast
1569        let result = mesh.tick(3000);
1570        assert!(result.is_none());
1571
1572        // After interval - should broadcast
1573        let result = mesh.tick(6000);
1574        assert!(result.is_some());
1575
1576        // Immediate second tick - no broadcast (interval not elapsed)
1577        let result = mesh.tick(6100);
1578        assert!(result.is_none());
1579
1580        // After another interval - should broadcast again
1581        let result = mesh.tick(12000);
1582        assert!(result.is_some());
1583    }
1584
1585    #[test]
1586    fn test_incoming_connection() {
1587        let mesh = create_mesh(0x11111111, "ALPHA-1");
1588        let observer = Arc::new(CollectingObserver::new());
1589        mesh.add_observer(observer.clone());
1590
1591        // Incoming connection from unknown peer
1592        let is_new = mesh.on_incoming_connection("central-uuid", NodeId::new(0x22222222), 1000);
1593
1594        assert!(is_new);
1595        assert_eq!(mesh.peer_count(), 1);
1596        assert_eq!(mesh.connected_count(), 1);
1597
1598        // Check events
1599        let events = observer.events();
1600        assert!(events
1601            .iter()
1602            .any(|e| matches!(e, HiveEvent::PeerDiscovered { .. })));
1603        assert!(events
1604            .iter()
1605            .any(|e| matches!(e, HiveEvent::PeerConnected { .. })));
1606    }
1607
1608    #[test]
1609    fn test_mesh_filtering() {
1610        let mesh = create_mesh(0x11111111, "ALPHA-1");
1611
1612        // Wrong mesh - ignored
1613        let peer = mesh.on_ble_discovered(
1614            "device-uuid-1",
1615            Some("HIVE_OTHER-22222222"),
1616            -65,
1617            Some("OTHER"),
1618            1000,
1619        );
1620        assert!(peer.is_none());
1621        assert_eq!(mesh.peer_count(), 0);
1622
1623        // Correct mesh - accepted
1624        let peer = mesh.on_ble_discovered(
1625            "device-uuid-2",
1626            Some("HIVE_TEST-33333333"),
1627            -65,
1628            Some("TEST"),
1629            1000,
1630        );
1631        assert!(peer.is_some());
1632        assert_eq!(mesh.peer_count(), 1);
1633    }
1634
1635    // ==================== Encryption Tests ====================
1636
1637    fn create_encrypted_mesh(node_id: u32, callsign: &str, secret: [u8; 32]) -> HiveMesh {
1638        let config =
1639            HiveMeshConfig::new(NodeId::new(node_id), callsign, "TEST").with_encryption(secret);
1640        HiveMesh::new(config)
1641    }
1642
1643    #[test]
1644    fn test_encryption_enabled() {
1645        let secret = [0x42u8; 32];
1646        let mesh = create_encrypted_mesh(0x11111111, "ALPHA-1", secret);
1647
1648        assert!(mesh.is_encryption_enabled());
1649    }
1650
1651    #[test]
1652    fn test_encryption_disabled_by_default() {
1653        let mesh = create_mesh(0x11111111, "ALPHA-1");
1654
1655        assert!(!mesh.is_encryption_enabled());
1656    }
1657
1658    #[test]
1659    fn test_encrypted_document_exchange() {
1660        let secret = [0x42u8; 32];
1661        let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret);
1662        let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret);
1663
1664        // mesh1 sends document
1665        let doc = mesh1.build_document();
1666
1667        // Document should be encrypted (starts with ENCRYPTED_MARKER)
1668        assert!(doc.len() >= 2);
1669        assert_eq!(doc[0], crate::document::ENCRYPTED_MARKER);
1670
1671        // mesh2 receives and decrypts
1672        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1673
1674        assert!(result.is_some());
1675        let result = result.unwrap();
1676        assert_eq!(result.source_node.as_u32(), 0x11111111);
1677    }
1678
1679    #[test]
1680    fn test_encrypted_emergency_exchange() {
1681        let secret = [0x42u8; 32];
1682        let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret);
1683        let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret);
1684
1685        let observer = Arc::new(CollectingObserver::new());
1686        mesh2.add_observer(observer.clone());
1687
1688        // mesh1 sends emergency
1689        let doc = mesh1.send_emergency(1000);
1690
1691        // mesh2 receives and decrypts
1692        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1693
1694        assert!(result.is_some());
1695        let result = result.unwrap();
1696        assert!(result.is_emergency);
1697
1698        // Check EmergencyReceived event was fired
1699        let events = observer.events();
1700        assert!(events
1701            .iter()
1702            .any(|e| matches!(e, HiveEvent::EmergencyReceived { .. })));
1703    }
1704
1705    #[test]
1706    fn test_wrong_key_fails_decrypt() {
1707        let secret1 = [0x42u8; 32];
1708        let secret2 = [0x43u8; 32]; // Different key
1709        let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret1);
1710        let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret2);
1711
1712        // mesh1 sends document
1713        let doc = mesh1.build_document();
1714
1715        // mesh2 cannot decrypt (wrong key)
1716        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1717
1718        assert!(result.is_none());
1719    }
1720
1721    #[test]
1722    fn test_unencrypted_mesh_can_read_unencrypted() {
1723        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1724        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1725
1726        // mesh1 sends document (unencrypted)
1727        let doc = mesh1.build_document();
1728
1729        // mesh2 receives (also unencrypted)
1730        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1731
1732        assert!(result.is_some());
1733    }
1734
1735    #[test]
1736    fn test_encrypted_mesh_can_receive_unencrypted() {
1737        // Backward compatibility: encrypted mesh can receive unencrypted docs
1738        let secret = [0x42u8; 32];
1739        let mesh1 = create_mesh(0x11111111, "ALPHA-1"); // unencrypted
1740        let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret); // encrypted
1741
1742        // mesh1 sends unencrypted document
1743        let doc = mesh1.build_document();
1744
1745        // mesh2 can receive unencrypted (backward compat)
1746        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1747
1748        assert!(result.is_some());
1749    }
1750
1751    #[test]
1752    fn test_unencrypted_mesh_cannot_receive_encrypted() {
1753        let secret = [0x42u8; 32];
1754        let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret); // encrypted
1755        let mesh2 = create_mesh(0x22222222, "BRAVO-1"); // unencrypted
1756
1757        // mesh1 sends encrypted document
1758        let doc = mesh1.build_document();
1759
1760        // mesh2 cannot decrypt (no key)
1761        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
1762
1763        assert!(result.is_none());
1764    }
1765
1766    #[test]
1767    fn test_enable_disable_encryption() {
1768        let mut mesh = create_mesh(0x11111111, "ALPHA-1");
1769
1770        assert!(!mesh.is_encryption_enabled());
1771
1772        // Enable encryption
1773        let secret = [0x42u8; 32];
1774        mesh.enable_encryption(&secret);
1775        assert!(mesh.is_encryption_enabled());
1776
1777        // Build document should now be encrypted
1778        let doc = mesh.build_document();
1779        assert_eq!(doc[0], crate::document::ENCRYPTED_MARKER);
1780
1781        // Disable encryption
1782        mesh.disable_encryption();
1783        assert!(!mesh.is_encryption_enabled());
1784
1785        // Build document should now be unencrypted
1786        let doc = mesh.build_document();
1787        assert_ne!(doc[0], crate::document::ENCRYPTED_MARKER);
1788    }
1789
1790    #[test]
1791    fn test_encryption_overhead() {
1792        let secret = [0x42u8; 32];
1793        let mesh_encrypted = create_encrypted_mesh(0x11111111, "ALPHA-1", secret);
1794        let mesh_unencrypted = create_mesh(0x22222222, "BRAVO-1");
1795
1796        let doc_encrypted = mesh_encrypted.build_document();
1797        let doc_unencrypted = mesh_unencrypted.build_document();
1798
1799        // Encrypted doc should be larger by:
1800        // - 2 bytes marker header (0xAE + reserved)
1801        // - 12 bytes nonce
1802        // - 16 bytes auth tag
1803        // Total: 30 bytes overhead
1804        let overhead = doc_encrypted.len() - doc_unencrypted.len();
1805        assert_eq!(overhead, 30); // 2 (marker) + 12 (nonce) + 16 (tag)
1806    }
1807
1808    // ==================== Per-Peer E2EE Tests ====================
1809
1810    #[test]
1811    fn test_peer_e2ee_enable_disable() {
1812        let mesh = create_mesh(0x11111111, "ALPHA-1");
1813
1814        assert!(!mesh.is_peer_e2ee_enabled());
1815        assert!(mesh.peer_e2ee_public_key().is_none());
1816
1817        mesh.enable_peer_e2ee();
1818        assert!(mesh.is_peer_e2ee_enabled());
1819        assert!(mesh.peer_e2ee_public_key().is_some());
1820
1821        mesh.disable_peer_e2ee();
1822        assert!(!mesh.is_peer_e2ee_enabled());
1823    }
1824
1825    #[test]
1826    fn test_peer_e2ee_initiate_session() {
1827        let mesh = create_mesh(0x11111111, "ALPHA-1");
1828        mesh.enable_peer_e2ee();
1829
1830        let key_exchange = mesh.initiate_peer_e2ee(NodeId::new(0x22222222), 1000);
1831        assert!(key_exchange.is_some());
1832
1833        let key_exchange = key_exchange.unwrap();
1834        // Should start with KEY_EXCHANGE_MARKER
1835        assert_eq!(key_exchange[0], crate::document::KEY_EXCHANGE_MARKER);
1836
1837        // Should have a pending session
1838        assert_eq!(mesh.peer_e2ee_session_count(), 1);
1839        assert_eq!(mesh.peer_e2ee_established_count(), 0);
1840    }
1841
1842    #[test]
1843    fn test_peer_e2ee_full_handshake() {
1844        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1845        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1846
1847        mesh1.enable_peer_e2ee();
1848        mesh2.enable_peer_e2ee();
1849
1850        let observer1 = Arc::new(CollectingObserver::new());
1851        let observer2 = Arc::new(CollectingObserver::new());
1852        mesh1.add_observer(observer1.clone());
1853        mesh2.add_observer(observer2.clone());
1854
1855        // mesh1 initiates to mesh2
1856        let key_exchange1 = mesh1
1857            .initiate_peer_e2ee(NodeId::new(0x22222222), 1000)
1858            .unwrap();
1859
1860        // mesh2 receives and responds
1861        let response = mesh2.handle_key_exchange(&key_exchange1, 1000);
1862        assert!(response.is_some());
1863
1864        // Check mesh2 has established session
1865        assert!(mesh2.has_peer_e2ee_session(NodeId::new(0x11111111)));
1866
1867        // mesh1 receives mesh2's response
1868        let key_exchange2 = response.unwrap();
1869        let _ = mesh1.handle_key_exchange(&key_exchange2, 1000);
1870
1871        // Check mesh1 has established session
1872        assert!(mesh1.has_peer_e2ee_session(NodeId::new(0x22222222)));
1873
1874        // Both should have E2EE established events
1875        let events1 = observer1.events();
1876        assert!(events1
1877            .iter()
1878            .any(|e| matches!(e, HiveEvent::PeerE2eeEstablished { .. })));
1879
1880        let events2 = observer2.events();
1881        assert!(events2
1882            .iter()
1883            .any(|e| matches!(e, HiveEvent::PeerE2eeEstablished { .. })));
1884    }
1885
1886    #[test]
1887    fn test_peer_e2ee_encrypt_decrypt() {
1888        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1889        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1890
1891        mesh1.enable_peer_e2ee();
1892        mesh2.enable_peer_e2ee();
1893
1894        // Establish session via key exchange
1895        let key_exchange1 = mesh1
1896            .initiate_peer_e2ee(NodeId::new(0x22222222), 1000)
1897            .unwrap();
1898        let key_exchange2 = mesh2.handle_key_exchange(&key_exchange1, 1000).unwrap();
1899        mesh1.handle_key_exchange(&key_exchange2, 1000);
1900
1901        // mesh1 sends encrypted message to mesh2
1902        let plaintext = b"Secret message from mesh1";
1903        let encrypted = mesh1.send_peer_e2ee(NodeId::new(0x22222222), plaintext, 2000);
1904        assert!(encrypted.is_some());
1905
1906        let encrypted = encrypted.unwrap();
1907        // Should start with PEER_E2EE_MARKER
1908        assert_eq!(encrypted[0], crate::document::PEER_E2EE_MARKER);
1909
1910        // mesh2 receives and decrypts
1911        let observer2 = Arc::new(CollectingObserver::new());
1912        mesh2.add_observer(observer2.clone());
1913
1914        let decrypted = mesh2.handle_peer_e2ee_message(&encrypted, 2000);
1915        assert!(decrypted.is_some());
1916        assert_eq!(decrypted.unwrap(), plaintext);
1917
1918        // Should have received message event
1919        let events = observer2.events();
1920        assert!(events.iter().any(|e| matches!(
1921            e,
1922            HiveEvent::PeerE2eeMessageReceived { from_node, data }
1923            if from_node.as_u32() == 0x11111111 && data == plaintext
1924        )));
1925    }
1926
1927    #[test]
1928    fn test_peer_e2ee_bidirectional() {
1929        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1930        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1931
1932        mesh1.enable_peer_e2ee();
1933        mesh2.enable_peer_e2ee();
1934
1935        // Establish session
1936        let key_exchange1 = mesh1
1937            .initiate_peer_e2ee(NodeId::new(0x22222222), 1000)
1938            .unwrap();
1939        let key_exchange2 = mesh2.handle_key_exchange(&key_exchange1, 1000).unwrap();
1940        mesh1.handle_key_exchange(&key_exchange2, 1000);
1941
1942        // mesh1 -> mesh2
1943        let msg1 = mesh1
1944            .send_peer_e2ee(NodeId::new(0x22222222), b"Hello from mesh1", 2000)
1945            .unwrap();
1946        let dec1 = mesh2.handle_peer_e2ee_message(&msg1, 2000).unwrap();
1947        assert_eq!(dec1, b"Hello from mesh1");
1948
1949        // mesh2 -> mesh1
1950        let msg2 = mesh2
1951            .send_peer_e2ee(NodeId::new(0x11111111), b"Hello from mesh2", 2000)
1952            .unwrap();
1953        let dec2 = mesh1.handle_peer_e2ee_message(&msg2, 2000).unwrap();
1954        assert_eq!(dec2, b"Hello from mesh2");
1955    }
1956
1957    #[test]
1958    fn test_peer_e2ee_close_session() {
1959        let mesh = create_mesh(0x11111111, "ALPHA-1");
1960        mesh.enable_peer_e2ee();
1961
1962        let observer = Arc::new(CollectingObserver::new());
1963        mesh.add_observer(observer.clone());
1964
1965        // Initiate a session
1966        mesh.initiate_peer_e2ee(NodeId::new(0x22222222), 1000);
1967        assert_eq!(mesh.peer_e2ee_session_count(), 1);
1968
1969        // Close session
1970        mesh.close_peer_e2ee(NodeId::new(0x22222222));
1971
1972        // Check close event
1973        let events = observer.events();
1974        assert!(events
1975            .iter()
1976            .any(|e| matches!(e, HiveEvent::PeerE2eeClosed { .. })));
1977    }
1978
1979    #[test]
1980    fn test_peer_e2ee_without_enabling() {
1981        let mesh = create_mesh(0x11111111, "ALPHA-1");
1982
1983        // E2EE not enabled - should return None
1984        let result = mesh.initiate_peer_e2ee(NodeId::new(0x22222222), 1000);
1985        assert!(result.is_none());
1986
1987        let result = mesh.send_peer_e2ee(NodeId::new(0x22222222), b"test", 1000);
1988        assert!(result.is_none());
1989
1990        assert!(!mesh.has_peer_e2ee_session(NodeId::new(0x22222222)));
1991    }
1992
1993    #[test]
1994    fn test_peer_e2ee_overhead() {
1995        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
1996        let mesh2 = create_mesh(0x22222222, "BRAVO-1");
1997
1998        mesh1.enable_peer_e2ee();
1999        mesh2.enable_peer_e2ee();
2000
2001        // Establish session
2002        let key_exchange1 = mesh1
2003            .initiate_peer_e2ee(NodeId::new(0x22222222), 1000)
2004            .unwrap();
2005        let key_exchange2 = mesh2.handle_key_exchange(&key_exchange1, 1000).unwrap();
2006        mesh1.handle_key_exchange(&key_exchange2, 1000);
2007
2008        // Encrypt a message
2009        let plaintext = b"Test message";
2010        let encrypted = mesh1
2011            .send_peer_e2ee(NodeId::new(0x22222222), plaintext, 2000)
2012            .unwrap();
2013
2014        // Overhead should be:
2015        // - 2 bytes marker header
2016        // - 4 bytes recipient node ID
2017        // - 4 bytes sender node ID
2018        // - 8 bytes counter
2019        // - 12 bytes nonce
2020        // - 16 bytes auth tag
2021        // Total: 46 bytes overhead
2022        let overhead = encrypted.len() - plaintext.len();
2023        assert_eq!(overhead, 46);
2024    }
2025
2026    // ==================== Strict Encryption Mode Tests ====================
2027
2028    fn create_strict_encrypted_mesh(node_id: u32, callsign: &str, secret: [u8; 32]) -> HiveMesh {
2029        let config = HiveMeshConfig::new(NodeId::new(node_id), callsign, "TEST")
2030            .with_encryption(secret)
2031            .with_strict_encryption();
2032        HiveMesh::new(config)
2033    }
2034
2035    #[test]
2036    fn test_strict_encryption_enabled() {
2037        let secret = [0x42u8; 32];
2038        let mesh = create_strict_encrypted_mesh(0x11111111, "ALPHA-1", secret);
2039
2040        assert!(mesh.is_encryption_enabled());
2041        assert!(mesh.is_strict_encryption_enabled());
2042    }
2043
2044    #[test]
2045    fn test_strict_encryption_disabled_by_default() {
2046        let secret = [0x42u8; 32];
2047        let mesh = create_encrypted_mesh(0x11111111, "ALPHA-1", secret);
2048
2049        assert!(mesh.is_encryption_enabled());
2050        assert!(!mesh.is_strict_encryption_enabled());
2051    }
2052
2053    #[test]
2054    fn test_strict_encryption_requires_encryption_enabled() {
2055        // strict_encryption without encryption should have no effect
2056        let config = HiveMeshConfig::new(NodeId::new(0x11111111), "ALPHA-1", "TEST")
2057            .with_strict_encryption(); // No encryption!
2058        let mesh = HiveMesh::new(config);
2059
2060        assert!(!mesh.is_encryption_enabled());
2061        assert!(!mesh.is_strict_encryption_enabled());
2062    }
2063
2064    #[test]
2065    fn test_strict_mode_accepts_encrypted_documents() {
2066        let secret = [0x42u8; 32];
2067        let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret);
2068        let mesh2 = create_strict_encrypted_mesh(0x22222222, "BRAVO-1", secret);
2069
2070        // mesh1 sends encrypted document
2071        let doc = mesh1.build_document();
2072        assert_eq!(doc[0], crate::document::ENCRYPTED_MARKER);
2073
2074        // mesh2 (strict mode) should accept encrypted documents
2075        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
2076        assert!(result.is_some());
2077    }
2078
2079    #[test]
2080    fn test_strict_mode_rejects_unencrypted_documents() {
2081        let secret = [0x42u8; 32];
2082        let mesh1 = create_mesh(0x11111111, "ALPHA-1"); // Unencrypted sender
2083        let mesh2 = create_strict_encrypted_mesh(0x22222222, "BRAVO-1", secret); // Strict receiver
2084
2085        let observer = Arc::new(CollectingObserver::new());
2086        mesh2.add_observer(observer.clone());
2087
2088        // mesh1 sends unencrypted document
2089        let doc = mesh1.build_document();
2090        assert_ne!(doc[0], crate::document::ENCRYPTED_MARKER);
2091
2092        // mesh2 (strict mode) should reject unencrypted documents
2093        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
2094        assert!(result.is_none());
2095
2096        // Should have SecurityViolation event
2097        let events = observer.events();
2098        assert!(events.iter().any(|e| matches!(
2099            e,
2100            HiveEvent::SecurityViolation {
2101                kind: crate::observer::SecurityViolationKind::UnencryptedInStrictMode,
2102                ..
2103            }
2104        )));
2105    }
2106
2107    #[test]
2108    fn test_non_strict_mode_accepts_unencrypted_documents() {
2109        let secret = [0x42u8; 32];
2110        let mesh1 = create_mesh(0x11111111, "ALPHA-1"); // Unencrypted sender
2111        let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret); // Non-strict receiver
2112
2113        // mesh1 sends unencrypted document
2114        let doc = mesh1.build_document();
2115
2116        // mesh2 (non-strict) should accept unencrypted documents (backward compat)
2117        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
2118        assert!(result.is_some());
2119    }
2120
2121    #[test]
2122    fn test_strict_mode_security_violation_event_includes_source() {
2123        let secret = [0x42u8; 32];
2124        let mesh1 = create_mesh(0x11111111, "ALPHA-1");
2125        let mesh2 = create_strict_encrypted_mesh(0x22222222, "BRAVO-1", secret);
2126
2127        let observer = Arc::new(CollectingObserver::new());
2128        mesh2.add_observer(observer.clone());
2129
2130        let doc = mesh1.build_document();
2131
2132        // Use on_ble_data_received with identifier to test source is captured
2133        mesh2.on_ble_discovered(
2134            "test-device-uuid",
2135            Some("HIVE_TEST-11111111"),
2136            -65,
2137            Some("TEST"),
2138            500,
2139        );
2140        mesh2.on_ble_connected("test-device-uuid", 600);
2141
2142        let _result = mesh2.on_ble_data_received("test-device-uuid", &doc, 1000);
2143
2144        // Check SecurityViolation event has source
2145        let events = observer.events();
2146        let violation = events.iter().find(|e| {
2147            matches!(
2148                e,
2149                HiveEvent::SecurityViolation {
2150                    kind: crate::observer::SecurityViolationKind::UnencryptedInStrictMode,
2151                    ..
2152                }
2153            )
2154        });
2155        assert!(violation.is_some());
2156
2157        if let Some(HiveEvent::SecurityViolation { source, .. }) = violation {
2158            assert!(source.is_some());
2159            assert_eq!(source.as_ref().unwrap(), "test-device-uuid");
2160        }
2161    }
2162
2163    #[test]
2164    fn test_decryption_failure_emits_security_violation() {
2165        let secret1 = [0x42u8; 32];
2166        let secret2 = [0x43u8; 32]; // Different key
2167        let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret1);
2168        let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret2);
2169
2170        let observer = Arc::new(CollectingObserver::new());
2171        mesh2.add_observer(observer.clone());
2172
2173        // mesh1 sends encrypted document
2174        let doc = mesh1.build_document();
2175
2176        // mesh2 cannot decrypt (wrong key)
2177        let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
2178        assert!(result.is_none());
2179
2180        // Should have SecurityViolation event for decryption failure
2181        let events = observer.events();
2182        assert!(events.iter().any(|e| matches!(
2183            e,
2184            HiveEvent::SecurityViolation {
2185                kind: crate::observer::SecurityViolationKind::DecryptionFailed,
2186                ..
2187            }
2188        )));
2189    }
2190
2191    #[test]
2192    fn test_strict_mode_builder_chain() {
2193        let secret = [0x42u8; 32];
2194        let config = HiveMeshConfig::new(NodeId::new(0x11111111), "ALPHA-1", "TEST")
2195            .with_encryption(secret)
2196            .with_strict_encryption()
2197            .with_sync_interval(10_000)
2198            .with_peer_timeout(60_000);
2199
2200        let mesh = HiveMesh::new(config);
2201
2202        assert!(mesh.is_encryption_enabled());
2203        assert!(mesh.is_strict_encryption_enabled());
2204    }
2205}