1#[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#[derive(Debug, Clone)]
79pub struct HiveMeshConfig {
80 pub node_id: NodeId,
82
83 pub callsign: String,
85
86 pub mesh_id: String,
88
89 pub peripheral_type: PeripheralType,
91
92 pub peer_config: PeerManagerConfig,
94
95 pub sync_interval_ms: u64,
97
98 pub auto_broadcast_events: bool,
100
101 pub encryption_secret: Option<[u8; 32]>,
107
108 pub strict_encryption: bool,
116}
117
118impl HiveMeshConfig {
119 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 pub fn with_encryption(mut self, secret: [u8; 32]) -> Self {
139 self.encryption_secret = Some(secret);
140 self
141 }
142
143 pub fn with_peripheral_type(mut self, ptype: PeripheralType) -> Self {
145 self.peripheral_type = ptype;
146 self
147 }
148
149 pub fn with_sync_interval(mut self, interval_ms: u64) -> Self {
151 self.sync_interval_ms = interval_ms;
152 self
153 }
154
155 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 pub fn with_max_peers(mut self, max: usize) -> Self {
163 self.peer_config.max_peers = max;
164 self
165 }
166
167 pub fn with_strict_encryption(mut self) -> Self {
175 self.strict_encryption = true;
176 self
177 }
178}
179
180#[cfg(feature = "std")]
185pub struct HiveMesh {
186 config: HiveMeshConfig,
188
189 peer_manager: PeerManager,
191
192 document_sync: DocumentSync,
194
195 observers: ObserverManager,
197
198 last_sync_ms: std::sync::atomic::AtomicU32,
200
201 last_cleanup_ms: std::sync::atomic::AtomicU32,
203
204 encryption_key: Option<MeshEncryptionKey>,
206
207 peer_sessions: std::sync::Mutex<Option<PeerSessionManager>>,
209
210 connection_graph: std::sync::Mutex<ConnectionStateGraph>,
212}
213
214#[cfg(feature = "std")]
215impl HiveMesh {
216 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 let encryption_key = config
227 .encryption_secret
228 .map(|secret| MeshEncryptionKey::from_shared_secret(&config.mesh_id, &secret));
229
230 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 pub fn is_encryption_enabled(&self) -> bool {
253 self.encryption_key.is_some()
254 }
255
256 pub fn is_strict_encryption_enabled(&self) -> bool {
260 self.config.strict_encryption && self.encryption_key.is_some()
261 }
262
263 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 pub fn disable_encryption(&mut self) {
276 self.encryption_key = None;
277 }
278
279 fn encrypt_document(&self, plaintext: &[u8]) -> Vec<u8> {
284 match &self.encryption_key {
285 Some(key) => {
286 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); buf.extend_from_slice(&ciphertext);
293 buf
294 }
295 Err(e) => {
296 log::error!("Encryption failed: {}", e);
297 plaintext.to_vec()
299 }
300 }
301 }
302 None => plaintext.to_vec(),
303 }
304 }
305
306 fn decrypt_document<'a>(
314 &self,
315 data: &'a [u8],
316 source_hint: Option<&str>,
317 ) -> Option<std::borrow::Cow<'a, [u8]>> {
318 if data.len() >= 2 && data[0] == ENCRYPTED_MARKER {
320 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 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 Some(std::borrow::Cow::Borrowed(data))
357 }
358 }
359 }
360
361 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 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 pub fn is_peer_e2ee_enabled(&self) -> bool {
390 self.peer_sessions.lock().unwrap().is_some()
391 }
392
393 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 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); 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 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 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 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); 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 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 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 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 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 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 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 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 pub fn node_id(&self) -> NodeId {
583 self.config.node_id
584 }
585
586 pub fn callsign(&self) -> &str {
588 &self.config.callsign
589 }
590
591 pub fn mesh_id(&self) -> &str {
593 &self.config.mesh_id
594 }
595
596 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 pub fn add_observer(&self, observer: Arc<dyn HiveObserver>) {
609 self.observers.add(observer);
610 }
611
612 pub fn remove_observer(&self, observer: &Arc<dyn HiveObserver>) {
614 self.observers.remove(observer);
615 }
616
617 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 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 pub fn clear_event(&self) {
647 self.document_sync.clear_event();
648 }
649
650 pub fn is_emergency_active(&self) -> bool {
652 self.document_sync.is_emergency_active()
653 }
654
655 pub fn is_ack_active(&self) -> bool {
657 self.document_sync.is_ack_active()
658 }
659
660 pub fn current_event(&self) -> Option<EventType> {
662 self.document_sync.current_event()
663 }
664
665 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 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 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 pub fn clear_emergency(&self) {
712 self.document_sync.clear_emergency();
713 }
714
715 pub fn has_active_emergency(&self) -> bool {
717 self.document_sync.has_active_emergency()
718 }
719
720 pub fn get_emergency_status(&self) -> Option<(u32, u64, usize, usize)> {
724 self.document_sync.get_emergency_status()
725 }
726
727 pub fn has_peer_acked(&self, peer_id: u32) -> bool {
729 self.document_sync.has_peer_acked(peer_id)
730 }
731
732 pub fn all_peers_acked(&self) -> bool {
734 self.document_sync.all_peers_acked()
735 }
736
737 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 {
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 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 {
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 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 {
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 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 {
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 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 {
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, 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 pub fn on_ble_data_received(
910 &self,
911 identifier: &str,
912 data: &[u8],
913 now_ms: u64,
914 ) -> Option<DataReceivedResult> {
915 let node_id = self.peer_manager.get_node_id(identifier)?;
917
918 if data.len() >= 2 {
920 match data[0] {
921 KEY_EXCHANGE_MARKER => {
922 let _response = self.handle_key_exchange(data, now_ms);
924 return None;
926 }
927 PEER_E2EE_MARKER => {
928 let _plaintext = self.handle_peer_e2ee_message(data, now_ms);
930 return None;
932 }
933 _ => {}
934 }
935 }
936
937 let decrypted = self.decrypt_document(data, Some(identifier))?;
939
940 let result = self.document_sync.merge_document(&decrypted)?;
942
943 self.peer_manager.record_sync(node_id, now_ms);
945
946 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 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 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 let source_hint = format!("node:{:08X}", node_id.as_u32());
1003 let decrypted = self.decrypt_document(data, Some(&source_hint))?;
1004
1005 let result = self.document_sync.merge_document(&decrypted)?;
1007
1008 self.peer_manager.record_sync(node_id, now_ms);
1010
1011 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 pub fn on_ble_data(
1048 &self,
1049 identifier: &str,
1050 data: &[u8],
1051 now_ms: u64,
1052 ) -> Option<DataReceivedResult> {
1053 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 let decrypted = self.decrypt_document(data, Some(identifier))?;
1070
1071 let result = self.document_sync.merge_document(&decrypted)?;
1073
1074 self.peer_manager.record_sync(result.source_node, now_ms);
1076
1077 self.peer_manager
1079 .on_incoming_connection(identifier, result.source_node, now_ms);
1080
1081 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 pub fn tick(&self, now_ms: u64) -> Option<Vec<u8>> {
1120 use std::sync::atomic::Ordering;
1121
1122 let now_ms_32 = now_ms as u32;
1124
1125 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 {
1140 let mut graph = self.connection_graph.lock().unwrap();
1141 let newly_lost = graph.tick(now_ms);
1142 graph.cleanup_lost(self.config.peer_config.peer_timeout_ms, now_ms);
1144 drop(graph);
1145
1146 for node_id in newly_lost {
1149 if !removed.contains(&node_id) {
1151 self.notify(HiveEvent::PeerLost { node_id });
1152 }
1153 }
1154 }
1155 }
1156
1157 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 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 pub fn get_peers(&self) -> Vec<HivePeer> {
1176 self.peer_manager.get_peers()
1177 }
1178
1179 pub fn get_connected_peers(&self) -> Vec<HivePeer> {
1181 self.peer_manager.get_connected_peers()
1182 }
1183
1184 pub fn get_peer(&self, node_id: NodeId) -> Option<HivePeer> {
1186 self.peer_manager.get_peer(node_id)
1187 }
1188
1189 pub fn peer_count(&self) -> usize {
1191 self.peer_manager.peer_count()
1192 }
1193
1194 pub fn connected_count(&self) -> usize {
1196 self.peer_manager.connected_count()
1197 }
1198
1199 pub fn matches_mesh(&self, device_mesh_id: Option<&str>) -> bool {
1201 self.peer_manager.matches_mesh(device_mesh_id)
1202 }
1203
1204 pub fn get_connection_graph(&self) -> Vec<PeerConnectionState> {
1228 self.connection_graph.lock().unwrap().get_all_owned()
1229 }
1230
1231 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 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 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 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 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 pub fn get_connection_state_counts(&self) -> StateCountSummary {
1292 self.connection_graph.lock().unwrap().state_counts()
1293 }
1294
1295 pub fn total_count(&self) -> u64 {
1297 self.document_sync.total_count()
1298 }
1299
1300 pub fn document_version(&self) -> u32 {
1302 self.document_sync.version()
1303 }
1304
1305 pub fn version(&self) -> u32 {
1307 self.document_sync.version()
1308 }
1309
1310 pub fn update_health(&self, battery_percent: u8) {
1312 self.document_sync.update_health(battery_percent);
1313 }
1314
1315 pub fn update_activity(&self, activity: u8) {
1317 self.document_sync.update_activity(activity);
1318 }
1319
1320 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 pub fn build_document(&self) -> Vec<u8> {
1330 let doc = self.document_sync.build_document();
1331 self.encrypt_document(&doc)
1332 }
1333
1334 pub fn peers_needing_sync(&self, now_ms: u64) -> Vec<HivePeer> {
1336 self.peer_manager.peers_needing_sync(now_ms)
1337 }
1338
1339 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#[derive(Debug, Clone)]
1355pub struct DataReceivedResult {
1356 pub source_node: NodeId,
1358
1359 pub is_emergency: bool,
1361
1362 pub is_ack: bool,
1364
1365 pub counter_changed: bool,
1367
1368 pub emergency_changed: bool,
1370
1371 pub total_count: u64,
1373
1374 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 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 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 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 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 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 let doc = mesh1.send_emergency(1000);
1471 assert!(mesh1.is_emergency_active());
1472
1473 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 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 let doc = mesh1.send_ack(1000);
1498 assert!(mesh1.is_ack_active());
1499
1500 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 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 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 mesh.tick(5000);
1535 assert_eq!(mesh.peer_count(), 1);
1536
1537 mesh.tick(20000);
1539 assert_eq!(mesh.peer_count(), 0);
1540
1541 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 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 let _result = mesh.tick(0);
1566 let result = mesh.tick(3000);
1570 assert!(result.is_none());
1571
1572 let result = mesh.tick(6000);
1574 assert!(result.is_some());
1575
1576 let result = mesh.tick(6100);
1578 assert!(result.is_none());
1579
1580 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 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 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 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 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 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 let doc = mesh1.build_document();
1666
1667 assert!(doc.len() >= 2);
1669 assert_eq!(doc[0], crate::document::ENCRYPTED_MARKER);
1670
1671 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 let doc = mesh1.send_emergency(1000);
1690
1691 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 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]; let mesh1 = create_encrypted_mesh(0x11111111, "ALPHA-1", secret1);
1710 let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret2);
1711
1712 let doc = mesh1.build_document();
1714
1715 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 let doc = mesh1.build_document();
1728
1729 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 let secret = [0x42u8; 32];
1739 let mesh1 = create_mesh(0x11111111, "ALPHA-1"); let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret); let doc = mesh1.build_document();
1744
1745 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); let mesh2 = create_mesh(0x22222222, "BRAVO-1"); let doc = mesh1.build_document();
1759
1760 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 let secret = [0x42u8; 32];
1774 mesh.enable_encryption(&secret);
1775 assert!(mesh.is_encryption_enabled());
1776
1777 let doc = mesh.build_document();
1779 assert_eq!(doc[0], crate::document::ENCRYPTED_MARKER);
1780
1781 mesh.disable_encryption();
1783 assert!(!mesh.is_encryption_enabled());
1784
1785 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 let overhead = doc_encrypted.len() - doc_unencrypted.len();
1805 assert_eq!(overhead, 30); }
1807
1808 #[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 assert_eq!(key_exchange[0], crate::document::KEY_EXCHANGE_MARKER);
1836
1837 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 let key_exchange1 = mesh1
1857 .initiate_peer_e2ee(NodeId::new(0x22222222), 1000)
1858 .unwrap();
1859
1860 let response = mesh2.handle_key_exchange(&key_exchange1, 1000);
1862 assert!(response.is_some());
1863
1864 assert!(mesh2.has_peer_e2ee_session(NodeId::new(0x11111111)));
1866
1867 let key_exchange2 = response.unwrap();
1869 let _ = mesh1.handle_key_exchange(&key_exchange2, 1000);
1870
1871 assert!(mesh1.has_peer_e2ee_session(NodeId::new(0x22222222)));
1873
1874 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 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 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 assert_eq!(encrypted[0], crate::document::PEER_E2EE_MARKER);
1909
1910 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 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 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 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 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 mesh.initiate_peer_e2ee(NodeId::new(0x22222222), 1000);
1967 assert_eq!(mesh.peer_e2ee_session_count(), 1);
1968
1969 mesh.close_peer_e2ee(NodeId::new(0x22222222));
1971
1972 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 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 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 let plaintext = b"Test message";
2010 let encrypted = mesh1
2011 .send_peer_e2ee(NodeId::new(0x22222222), plaintext, 2000)
2012 .unwrap();
2013
2014 let overhead = encrypted.len() - plaintext.len();
2023 assert_eq!(overhead, 46);
2024 }
2025
2026 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 let config = HiveMeshConfig::new(NodeId::new(0x11111111), "ALPHA-1", "TEST")
2057 .with_strict_encryption(); 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 let doc = mesh1.build_document();
2072 assert_eq!(doc[0], crate::document::ENCRYPTED_MARKER);
2073
2074 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"); let mesh2 = create_strict_encrypted_mesh(0x22222222, "BRAVO-1", secret); let observer = Arc::new(CollectingObserver::new());
2086 mesh2.add_observer(observer.clone());
2087
2088 let doc = mesh1.build_document();
2090 assert_ne!(doc[0], crate::document::ENCRYPTED_MARKER);
2091
2092 let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
2094 assert!(result.is_none());
2095
2096 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"); let mesh2 = create_encrypted_mesh(0x22222222, "BRAVO-1", secret); let doc = mesh1.build_document();
2115
2116 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 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 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]; 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 let doc = mesh1.build_document();
2175
2176 let result = mesh2.on_ble_data_received_from_node(NodeId::new(0x11111111), &doc, 1000);
2178 assert!(result.is_none());
2179
2180 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}