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