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}