hive_btle/
observer.rs

1//! Observer pattern for HIVE mesh events
2//!
3//! This module provides the event types and observer trait for receiving
4//! notifications about mesh state changes. Platform implementations register
5//! observers to receive callbacks when peers are discovered, connected,
6//! disconnected, or when documents are synced.
7//!
8//! ## Usage
9//!
10//! ```ignore
11//! use hive_btle::observer::{HiveEvent, HiveObserver};
12//!
13//! struct MyObserver;
14//!
15//! impl HiveObserver for MyObserver {
16//!     fn on_event(&self, event: HiveEvent) {
17//!         match event {
18//!             HiveEvent::PeerDiscovered { peer } => {
19//!                 println!("Discovered: {}", peer.display_name());
20//!             }
21//!             HiveEvent::EmergencyReceived { from_node } => {
22//!                 println!("EMERGENCY from {:08X}", from_node.as_u32());
23//!             }
24//!             _ => {}
25//!         }
26//!     }
27//! }
28//! ```
29
30#[cfg(not(feature = "std"))]
31use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
32#[cfg(feature = "std")]
33use std::sync::Arc;
34
35// Re-import Vec for HiveEvent variants
36#[cfg(feature = "std")]
37use std::string::String;
38#[cfg(feature = "std")]
39use std::vec::Vec;
40
41use crate::peer::HivePeer;
42use crate::sync::crdt::EventType;
43use crate::NodeId;
44
45/// Events emitted by the HIVE mesh
46///
47/// These events notify observers about changes in mesh state, peer lifecycle,
48/// and document synchronization.
49#[derive(Debug, Clone)]
50pub enum HiveEvent {
51    // ==================== Peer Lifecycle Events ====================
52    /// A new peer was discovered via BLE scanning
53    PeerDiscovered {
54        /// The discovered peer
55        peer: HivePeer,
56    },
57
58    /// A peer connected to us (either direction)
59    PeerConnected {
60        /// Node ID of the connected peer
61        node_id: NodeId,
62    },
63
64    /// A peer disconnected
65    PeerDisconnected {
66        /// Node ID of the disconnected peer
67        node_id: NodeId,
68        /// Reason for disconnection
69        reason: DisconnectReason,
70    },
71
72    /// A peer was removed due to timeout (stale)
73    PeerLost {
74        /// Node ID of the lost peer
75        node_id: NodeId,
76    },
77
78    // ==================== Mesh Events ====================
79    /// An emergency event was received from a peer
80    EmergencyReceived {
81        /// Node ID that sent the emergency
82        from_node: NodeId,
83    },
84
85    /// An ACK event was received from a peer
86    AckReceived {
87        /// Node ID that sent the ACK
88        from_node: NodeId,
89    },
90
91    /// A generic event was received from a peer
92    EventReceived {
93        /// Node ID that sent the event
94        from_node: NodeId,
95        /// Type of event
96        event_type: EventType,
97    },
98
99    /// A document was synced with a peer
100    DocumentSynced {
101        /// Node ID that we synced with
102        from_node: NodeId,
103        /// Updated total counter value
104        total_count: u64,
105    },
106
107    // ==================== Mesh State Events ====================
108    /// Mesh state changed (peer count, connected count)
109    MeshStateChanged {
110        /// Total number of known peers
111        peer_count: usize,
112        /// Number of connected peers
113        connected_count: usize,
114    },
115
116    /// All peers have acknowledged an emergency
117    AllPeersAcked {
118        /// Number of peers that acknowledged
119        ack_count: usize,
120    },
121
122    // ==================== Per-Peer E2EE Events ====================
123    /// E2EE session established with a peer
124    PeerE2eeEstablished {
125        /// Node ID of the peer we established E2EE with
126        peer_node_id: NodeId,
127    },
128
129    /// E2EE session closed with a peer
130    PeerE2eeClosed {
131        /// Node ID of the peer whose E2EE session closed
132        peer_node_id: NodeId,
133    },
134
135    /// Received an E2EE encrypted message from a peer
136    PeerE2eeMessageReceived {
137        /// Node ID of the sender
138        from_node: NodeId,
139        /// Decrypted message data
140        data: Vec<u8>,
141    },
142
143    /// E2EE session failed to establish
144    PeerE2eeFailed {
145        /// Node ID of the peer
146        peer_node_id: NodeId,
147        /// Error description
148        error: String,
149    },
150
151    // ==================== Security Events ====================
152    /// A security violation was detected
153    SecurityViolation {
154        /// Type of violation
155        kind: SecurityViolationKind,
156        /// Optional source identifier (node_id, BLE identifier, etc.)
157        source: Option<String>,
158    },
159}
160
161impl HiveEvent {
162    /// Create a peer discovered event
163    pub fn peer_discovered(peer: HivePeer) -> Self {
164        Self::PeerDiscovered { peer }
165    }
166
167    /// Create a peer connected event
168    pub fn peer_connected(node_id: NodeId) -> Self {
169        Self::PeerConnected { node_id }
170    }
171
172    /// Create a peer disconnected event
173    pub fn peer_disconnected(node_id: NodeId, reason: DisconnectReason) -> Self {
174        Self::PeerDisconnected { node_id, reason }
175    }
176
177    /// Create a peer lost event (timeout)
178    pub fn peer_lost(node_id: NodeId) -> Self {
179        Self::PeerLost { node_id }
180    }
181
182    /// Create an emergency received event
183    pub fn emergency_received(from_node: NodeId) -> Self {
184        Self::EmergencyReceived { from_node }
185    }
186
187    /// Create an ACK received event
188    pub fn ack_received(from_node: NodeId) -> Self {
189        Self::AckReceived { from_node }
190    }
191
192    /// Create a generic event received
193    pub fn event_received(from_node: NodeId, event_type: EventType) -> Self {
194        Self::EventReceived {
195            from_node,
196            event_type,
197        }
198    }
199
200    /// Create a document synced event
201    pub fn document_synced(from_node: NodeId, total_count: u64) -> Self {
202        Self::DocumentSynced {
203            from_node,
204            total_count,
205        }
206    }
207
208    /// Create a peer E2EE established event
209    pub fn peer_e2ee_established(peer_node_id: NodeId) -> Self {
210        Self::PeerE2eeEstablished { peer_node_id }
211    }
212
213    /// Create a peer E2EE closed event
214    pub fn peer_e2ee_closed(peer_node_id: NodeId) -> Self {
215        Self::PeerE2eeClosed { peer_node_id }
216    }
217
218    /// Create a peer E2EE message received event
219    pub fn peer_e2ee_message_received(from_node: NodeId, data: Vec<u8>) -> Self {
220        Self::PeerE2eeMessageReceived { from_node, data }
221    }
222
223    /// Create a peer E2EE failed event
224    pub fn peer_e2ee_failed(peer_node_id: NodeId, error: String) -> Self {
225        Self::PeerE2eeFailed {
226            peer_node_id,
227            error,
228        }
229    }
230
231    /// Create a security violation event
232    pub fn security_violation(kind: SecurityViolationKind, source: Option<String>) -> Self {
233        Self::SecurityViolation { kind, source }
234    }
235}
236
237/// Reason for peer disconnection
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
239pub enum DisconnectReason {
240    /// Local initiated disconnect
241    LocalRequest,
242    /// Remote peer initiated disconnect
243    RemoteRequest,
244    /// Connection timed out
245    Timeout,
246    /// BLE link lost
247    LinkLoss,
248    /// Connection failed
249    ConnectionFailed,
250    /// Unknown reason
251    #[default]
252    Unknown,
253}
254
255/// Types of security violations that can be detected
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum SecurityViolationKind {
258    /// Received unencrypted document when strict encryption mode is enabled
259    UnencryptedInStrictMode,
260    /// Decryption failed (wrong key or corrupted data)
261    DecryptionFailed,
262    /// Replay attack detected (duplicate message counter)
263    ReplayDetected,
264    /// Message from unknown/unauthorized node
265    UnauthorizedNode,
266}
267
268/// Observer trait for receiving HIVE mesh events
269///
270/// Implement this trait to receive callbacks when mesh events occur.
271/// Observers must be thread-safe (Send + Sync) as they may be called
272/// from any thread.
273///
274/// ## Platform Notes
275///
276/// - **iOS/macOS**: Wrap in a Swift class that conforms to this protocol via UniFFI
277/// - **Android**: Implement via JNI callback interface
278/// - **ESP32**: Use direct Rust implementation with static callbacks
279pub trait HiveObserver: Send + Sync {
280    /// Called when a mesh event occurs
281    ///
282    /// This method should return quickly to avoid blocking the mesh.
283    /// If heavy processing is needed, dispatch to another thread.
284    fn on_event(&self, event: HiveEvent);
285}
286
287/// A simple observer that collects events into a vector (useful for testing)
288#[cfg(feature = "std")]
289#[derive(Debug, Default)]
290pub struct CollectingObserver {
291    events: std::sync::Mutex<Vec<HiveEvent>>,
292}
293
294#[cfg(feature = "std")]
295impl CollectingObserver {
296    /// Create a new collecting observer
297    pub fn new() -> Self {
298        Self {
299            events: std::sync::Mutex::new(Vec::new()),
300        }
301    }
302
303    /// Get all collected events
304    pub fn events(&self) -> Vec<HiveEvent> {
305        self.events.lock().unwrap().clone()
306    }
307
308    /// Clear collected events
309    pub fn clear(&self) {
310        self.events.lock().unwrap().clear();
311    }
312
313    /// Get count of collected events
314    pub fn count(&self) -> usize {
315        self.events.lock().unwrap().len()
316    }
317}
318
319#[cfg(feature = "std")]
320impl HiveObserver for CollectingObserver {
321    fn on_event(&self, event: HiveEvent) {
322        self.events.lock().unwrap().push(event);
323    }
324}
325
326/// Helper to manage multiple observers
327#[cfg(feature = "std")]
328pub struct ObserverManager {
329    observers: std::sync::RwLock<Vec<Arc<dyn HiveObserver>>>,
330}
331
332#[cfg(feature = "std")]
333impl Default for ObserverManager {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339#[cfg(feature = "std")]
340impl ObserverManager {
341    /// Create a new observer manager
342    pub fn new() -> Self {
343        Self {
344            observers: std::sync::RwLock::new(Vec::new()),
345        }
346    }
347
348    /// Add an observer
349    pub fn add(&self, observer: Arc<dyn HiveObserver>) {
350        self.observers.write().unwrap().push(observer);
351    }
352
353    /// Remove an observer (by Arc pointer equality)
354    pub fn remove(&self, observer: &Arc<dyn HiveObserver>) {
355        self.observers
356            .write()
357            .unwrap()
358            .retain(|o| !Arc::ptr_eq(o, observer));
359    }
360
361    /// Notify all observers of an event
362    pub fn notify(&self, event: HiveEvent) {
363        // Use try_read to avoid panicking on poisoned locks
364        if let Ok(observers) = self.observers.try_read() {
365            for observer in observers.iter() {
366                observer.on_event(event.clone());
367            }
368        }
369    }
370
371    /// Get the number of registered observers
372    pub fn count(&self) -> usize {
373        self.observers.read().unwrap().len()
374    }
375}
376
377#[cfg(all(test, feature = "std"))]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_collecting_observer() {
383        let observer = CollectingObserver::new();
384
385        observer.on_event(HiveEvent::peer_connected(NodeId::new(0x12345678)));
386        observer.on_event(HiveEvent::emergency_received(NodeId::new(0x87654321)));
387
388        assert_eq!(observer.count(), 2);
389
390        let events = observer.events();
391        assert!(matches!(events[0], HiveEvent::PeerConnected { .. }));
392        assert!(matches!(events[1], HiveEvent::EmergencyReceived { .. }));
393
394        observer.clear();
395        assert_eq!(observer.count(), 0);
396    }
397
398    #[test]
399    fn test_observer_manager() {
400        let manager = ObserverManager::new();
401
402        // Keep concrete references for count checks
403        let obs1_concrete = Arc::new(CollectingObserver::new());
404        let obs2_concrete = Arc::new(CollectingObserver::new());
405        let observer1: Arc<dyn HiveObserver> = obs1_concrete.clone();
406        let observer2: Arc<dyn HiveObserver> = obs2_concrete.clone();
407
408        manager.add(observer1.clone());
409        manager.add(observer2.clone());
410
411        assert_eq!(manager.count(), 2);
412
413        manager.notify(HiveEvent::peer_connected(NodeId::new(0x12345678)));
414
415        assert_eq!(obs1_concrete.count(), 1);
416        assert_eq!(obs2_concrete.count(), 1);
417
418        manager.remove(&observer1);
419        assert_eq!(manager.count(), 1);
420
421        manager.notify(HiveEvent::peer_lost(NodeId::new(0x12345678)));
422
423        assert_eq!(obs1_concrete.count(), 1); // Not notified
424        assert_eq!(obs2_concrete.count(), 2); // Got both events
425    }
426}