hive_btle/
lib.rs

1//! HIVE-BTLE: Bluetooth Low Energy mesh transport for HIVE Protocol
2//!
3//! This crate provides BLE-based peer-to-peer mesh networking for HIVE,
4//! supporting discovery, advertisement, connectivity, and HIVE-Lite sync.
5//!
6//! ## Overview
7//!
8//! HIVE-BTLE implements the pluggable transport abstraction (ADR-032) for
9//! Bluetooth Low Energy, enabling HIVE Protocol to operate over BLE in
10//! resource-constrained environments like smartwatches.
11//!
12//! ## Key Features
13//!
14//! - **Cross-platform**: Linux, Android, macOS, iOS, Windows, ESP32
15//! - **Power efficient**: Designed for 18+ hour battery life on watches
16//! - **Long range**: Coded PHY support for 300m+ range
17//! - **HIVE-Lite sync**: Optimized CRDT sync over GATT
18//!
19//! ## Architecture
20//!
21//! ```text
22//! ┌─────────────────────────────────────────────────┐
23//! │                  Application                     │
24//! ├─────────────────────────────────────────────────┤
25//! │           BluetoothLETransport                   │
26//! │  (implements MeshTransport from ADR-032)        │
27//! ├─────────────────────────────────────────────────┤
28//! │              BleAdapter Trait                    │
29//! ├──────────┬──────────┬──────────┬────────────────┤
30//! │  Linux   │ Android  │  Apple   │    Windows     │
31//! │ (BlueZ)  │  (JNI)   │(CoreBT)  │    (WinRT)     │
32//! └──────────┴──────────┴──────────┴────────────────┘
33//! ```
34//!
35//! ## Quick Start
36//!
37//! ```ignore
38//! use hive_btle::{BleConfig, BluetoothLETransport, NodeId};
39//!
40//! // Create HIVE-Lite optimized config for battery efficiency
41//! let config = BleConfig::hive_lite(NodeId::new(0x12345678));
42//!
43//! // Create transport with platform adapter
44//! #[cfg(feature = "linux")]
45//! let adapter = hive_btle::platform::linux::BluerAdapter::new()?;
46//!
47//! let transport = BluetoothLETransport::new(config, adapter);
48//!
49//! // Start advertising and scanning
50//! transport.start().await?;
51//!
52//! // Connect to a peer
53//! let conn = transport.connect(&peer_id).await?;
54//! ```
55//!
56//! ## Feature Flags
57//!
58//! - `std` (default): Standard library support
59//! - `linux`: Linux/BlueZ support via `bluer`
60//! - `android`: Android support via JNI
61//! - `macos`: macOS support via CoreBluetooth
62//! - `ios`: iOS support via CoreBluetooth
63//! - `windows`: Windows support via WinRT
64//! - `embedded`: Embedded/no_std support
65//! - `coded-phy`: Enable Coded PHY for extended range
66//! - `extended-adv`: Enable extended advertising
67//!
68//! ## External Crate Usage (hive-ffi)
69//!
70//! This crate exports platform adapters for use by external crates like `hive-ffi`.
71//! Each platform adapter is conditionally exported based on feature flags:
72//!
73//! ```toml
74//! # In your Cargo.toml
75//! [dependencies]
76//! hive-btle = { version = "0.0.5", features = ["linux"] }
77//! ```
78//!
79//! Then use the appropriate adapter:
80//!
81//! ```ignore
82//! use hive_btle::{BleConfig, BluerAdapter, HiveMesh, NodeId};
83//!
84//! // Platform adapter is automatically available via feature flag
85//! let adapter = BluerAdapter::new().await?;
86//! let config = BleConfig::hive_lite(NodeId::new(0x12345678));
87//! ```
88//!
89//! ### Platform → Adapter Mapping
90//!
91//! | Feature | Target | Adapter Type |
92//! |---------|--------|--------------|
93//! | `linux` | Linux | `BluerAdapter` |
94//! | `android` | Android | `AndroidAdapter` |
95//! | `macos` | macOS | `CoreBluetoothAdapter` |
96//! | `ios` | iOS | `CoreBluetoothAdapter` |
97//! | `windows` | Windows | `WinRtBleAdapter` |
98//!
99//! ### Document Encoding for Translation Layer
100//!
101//! For translating between Automerge (full HIVE) and hive-btle documents:
102//!
103//! ```ignore
104//! use hive_btle::HiveDocument;
105//!
106//! // Decode bytes received from BLE
107//! let doc = HiveDocument::from_bytes(&received_bytes)?;
108//!
109//! // Encode for BLE transmission
110//! let bytes = doc.to_bytes();
111//! ```
112//!
113//! ## Power Profiles
114//!
115//! | Profile | Duty Cycle | Watch Battery |
116//! |---------|------------|---------------|
117//! | Aggressive | 20% | ~6 hours |
118//! | Balanced | 10% | ~12 hours |
119//! | **LowPower** | **2%** | **~20+ hours** |
120//!
121//! ## Related ADRs
122//!
123//! - ADR-039: HIVE-BTLE Mesh Transport Crate
124//! - ADR-032: Pluggable Transport Abstraction
125//! - ADR-035: HIVE-Lite Embedded Nodes
126//! - ADR-037: Resource-Constrained Device Optimization
127
128#![cfg_attr(not(feature = "std"), no_std)]
129#![warn(missing_docs)]
130#![warn(rustdoc::missing_crate_level_docs)]
131
132#[cfg(not(feature = "std"))]
133extern crate alloc;
134
135pub mod config;
136pub mod discovery;
137pub mod document;
138pub mod document_sync;
139pub mod error;
140pub mod gatt;
141#[cfg(feature = "std")]
142pub mod gossip;
143pub mod hive_mesh;
144pub mod mesh;
145pub mod observer;
146pub mod peer;
147pub mod peer_manager;
148#[cfg(feature = "std")]
149pub mod persistence;
150pub mod phy;
151pub mod platform;
152pub mod power;
153pub mod security;
154pub mod sync;
155pub mod transport;
156
157// Re-exports for convenience
158pub use config::{
159    BleConfig, BlePhy, DiscoveryConfig, GattConfig, MeshConfig, PowerProfile, DEFAULT_MESH_ID,
160};
161#[cfg(feature = "std")]
162pub use discovery::Scanner;
163pub use discovery::{Advertiser, HiveBeacon, ScanFilter};
164pub use error::{BleError, Result};
165#[cfg(feature = "std")]
166pub use gatt::HiveGattService;
167pub use gatt::SyncProtocol;
168#[cfg(feature = "std")]
169pub use mesh::MeshManager;
170pub use mesh::{MeshRouter, MeshTopology, TopologyConfig, TopologyEvent};
171pub use phy::{PhyCapabilities, PhyController, PhyStrategy};
172pub use platform::{BleAdapter, ConnectionEvent, DisconnectReason, DiscoveredDevice, StubAdapter};
173
174// Platform-specific adapter re-exports for external crates (hive-ffi)
175// These allow external crates to use platform adapters via feature flags
176#[cfg(all(feature = "linux", target_os = "linux"))]
177pub use platform::linux::BluerAdapter;
178
179#[cfg(feature = "android")]
180pub use platform::android::AndroidAdapter;
181
182#[cfg(any(feature = "macos", feature = "ios"))]
183pub use platform::apple::CoreBluetoothAdapter;
184
185#[cfg(feature = "windows")]
186pub use platform::windows::WinRtBleAdapter;
187
188#[cfg(feature = "std")]
189pub use platform::mock::MockBleAdapter;
190pub use power::{BatteryState, RadioScheduler, SyncPriority};
191pub use sync::{GattSyncProtocol, SyncConfig, SyncState};
192pub use transport::{BleConnection, BluetoothLETransport, MeshTransport, TransportCapabilities};
193
194// New centralized mesh management types
195pub use document::{
196    HiveDocument, MergeResult, ENCRYPTED_MARKER, EXTENDED_MARKER, KEY_EXCHANGE_MARKER,
197    PEER_E2EE_MARKER,
198};
199
200// Security (mesh-wide and per-peer encryption)
201pub use document_sync::{DocumentCheck, DocumentSync};
202#[cfg(feature = "std")]
203pub use hive_mesh::{DataReceivedResult, HiveMesh, HiveMeshConfig};
204#[cfg(feature = "std")]
205pub use observer::{CollectingObserver, ObserverManager};
206pub use observer::{DisconnectReason as HiveDisconnectReason, HiveEvent, HiveObserver};
207pub use peer::{HivePeer, PeerManagerConfig, SignalStrength};
208pub use peer_manager::PeerManager;
209// Phase 1: Mesh-wide encryption
210pub use security::{EncryptedDocument, EncryptionError, MeshEncryptionKey};
211// Phase 2: Per-peer E2EE
212#[cfg(feature = "std")]
213pub use security::{
214    KeyExchangeMessage, PeerEncryptedMessage, PeerIdentityKey, PeerSession, PeerSessionKey,
215    PeerSessionManager, SessionState,
216};
217
218// Gossip and persistence abstractions
219#[cfg(feature = "std")]
220pub use gossip::{BroadcastAll, EmergencyAware, GossipStrategy, RandomFanout, SignalBasedFanout};
221#[cfg(feature = "std")]
222pub use persistence::{DocumentStore, FileStore, MemoryStore, SharedStore};
223
224/// HIVE BLE Service UUID (128-bit)
225///
226/// All HIVE nodes advertise this UUID for discovery.
227pub const HIVE_SERVICE_UUID: uuid::Uuid = uuid::uuid!("f47ac10b-58cc-4372-a567-0e02b2c3d479");
228
229/// HIVE BLE Service UUID (16-bit short form)
230///
231/// Derived from the first two bytes of the 128-bit UUID (0xF47A from f47ac10b).
232/// Used for space-constrained advertising to fit within 31-byte limit.
233pub const HIVE_SERVICE_UUID_16BIT: u16 = 0xF47A;
234
235/// HIVE Node Info Characteristic UUID
236pub const CHAR_NODE_INFO_UUID: u16 = 0x0001;
237
238/// HIVE Sync State Characteristic UUID
239pub const CHAR_SYNC_STATE_UUID: u16 = 0x0002;
240
241/// HIVE Sync Data Characteristic UUID
242pub const CHAR_SYNC_DATA_UUID: u16 = 0x0003;
243
244/// HIVE Command Characteristic UUID
245pub const CHAR_COMMAND_UUID: u16 = 0x0004;
246
247/// HIVE Status Characteristic UUID
248pub const CHAR_STATUS_UUID: u16 = 0x0005;
249
250/// Crate version
251pub const VERSION: &str = env!("CARGO_PKG_VERSION");
252
253/// Node identifier
254///
255/// Represents a unique node in the HIVE mesh. For BLE, this is typically
256/// derived from the Bluetooth MAC address or a configured value.
257#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
258pub struct NodeId {
259    /// 32-bit node identifier
260    id: u32,
261}
262
263impl NodeId {
264    /// Create a new node ID from a 32-bit value
265    pub fn new(id: u32) -> Self {
266        Self { id }
267    }
268
269    /// Get the raw 32-bit ID value
270    pub fn as_u32(&self) -> u32 {
271        self.id
272    }
273
274    /// Create from a string representation (hex format)
275    pub fn parse(s: &str) -> Option<Self> {
276        // Try parsing as hex (with or without 0x prefix)
277        let s = s.trim_start_matches("0x").trim_start_matches("0X");
278        u32::from_str_radix(s, 16).ok().map(Self::new)
279    }
280
281    /// Derive a NodeId from a BLE MAC address.
282    ///
283    /// Uses the last 4 bytes of the 6-byte MAC address as the 32-bit node ID.
284    /// This provides a consistent node ID derived from the device's Bluetooth
285    /// hardware address.
286    ///
287    /// # Arguments
288    /// * `mac` - 6-byte MAC address array (e.g., [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])
289    ///
290    /// # Example
291    /// ```
292    /// use hive_btle::NodeId;
293    ///
294    /// let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
295    /// let node_id = NodeId::from_mac_address(&mac);
296    /// assert_eq!(node_id.as_u32(), 0x22334455);
297    /// ```
298    pub fn from_mac_address(mac: &[u8; 6]) -> Self {
299        // Use last 4 bytes: mac[2], mac[3], mac[4], mac[5]
300        let id = ((mac[2] as u32) << 24)
301            | ((mac[3] as u32) << 16)
302            | ((mac[4] as u32) << 8)
303            | (mac[5] as u32);
304        Self::new(id)
305    }
306
307    /// Derive a NodeId from a MAC address string.
308    ///
309    /// Parses a MAC address in "AA:BB:CC:DD:EE:FF" format and derives
310    /// the node ID from the last 4 bytes.
311    ///
312    /// # Arguments
313    /// * `mac_str` - MAC address string in colon-separated hex format
314    ///
315    /// # Returns
316    /// `Some(NodeId)` if parsing succeeds, `None` otherwise
317    ///
318    /// # Example
319    /// ```
320    /// use hive_btle::NodeId;
321    ///
322    /// let node_id = NodeId::from_mac_string("00:11:22:33:44:55").unwrap();
323    /// assert_eq!(node_id.as_u32(), 0x22334455);
324    /// ```
325    pub fn from_mac_string(mac_str: &str) -> Option<Self> {
326        let parts: Vec<&str> = mac_str.split(':').collect();
327        if parts.len() != 6 {
328            return None;
329        }
330
331        let mut mac = [0u8; 6];
332        for (i, part) in parts.iter().enumerate() {
333            mac[i] = u8::from_str_radix(part, 16).ok()?;
334        }
335
336        Some(Self::from_mac_address(&mac))
337    }
338}
339
340impl core::fmt::Display for NodeId {
341    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342        write!(f, "{:08X}", self.id)
343    }
344}
345
346impl From<u32> for NodeId {
347    fn from(id: u32) -> Self {
348        Self::new(id)
349    }
350}
351
352impl From<NodeId> for u32 {
353    fn from(node_id: NodeId) -> Self {
354        node_id.id
355    }
356}
357
358/// Node capability flags
359///
360/// Advertised in the HIVE beacon to indicate what this node can do.
361pub mod capabilities {
362    /// This is a HIVE-Lite node (minimal state, single parent)
363    pub const LITE_NODE: u16 = 0x0001;
364    /// Has accelerometer sensor
365    pub const SENSOR_ACCEL: u16 = 0x0002;
366    /// Has temperature sensor
367    pub const SENSOR_TEMP: u16 = 0x0004;
368    /// Has button input
369    pub const SENSOR_BUTTON: u16 = 0x0008;
370    /// Has LED output
371    pub const ACTUATOR_LED: u16 = 0x0010;
372    /// Has vibration motor
373    pub const ACTUATOR_VIBRATE: u16 = 0x0020;
374    /// Has display
375    pub const HAS_DISPLAY: u16 = 0x0040;
376    /// Can relay messages (not a leaf)
377    pub const CAN_RELAY: u16 = 0x0080;
378    /// Supports Coded PHY
379    pub const CODED_PHY: u16 = 0x0100;
380    /// Has GPS
381    pub const HAS_GPS: u16 = 0x0200;
382}
383
384/// Hierarchy levels in the HIVE mesh
385#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
386#[repr(u8)]
387pub enum HierarchyLevel {
388    /// Platform/soldier level (leaf nodes)
389    #[default]
390    Platform = 0,
391    /// Squad level
392    Squad = 1,
393    /// Platoon level
394    Platoon = 2,
395    /// Company level
396    Company = 3,
397}
398
399impl From<u8> for HierarchyLevel {
400    fn from(value: u8) -> Self {
401        match value {
402            0 => HierarchyLevel::Platform,
403            1 => HierarchyLevel::Squad,
404            2 => HierarchyLevel::Platoon,
405            3 => HierarchyLevel::Company,
406            _ => HierarchyLevel::Platform,
407        }
408    }
409}
410
411impl From<HierarchyLevel> for u8 {
412    fn from(level: HierarchyLevel) -> Self {
413        level as u8
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn test_node_id() {
423        let id = NodeId::new(0x12345678);
424        assert_eq!(id.as_u32(), 0x12345678);
425        assert_eq!(id.to_string(), "12345678");
426    }
427
428    #[test]
429    fn test_node_id_parse() {
430        assert_eq!(NodeId::parse("12345678").unwrap().as_u32(), 0x12345678);
431        assert_eq!(NodeId::parse("0x12345678").unwrap().as_u32(), 0x12345678);
432        assert!(NodeId::parse("not_hex").is_none());
433    }
434
435    #[test]
436    fn test_node_id_from_mac_address() {
437        // MAC: AA:BB:CC:DD:EE:FF -> NodeId from last 4 bytes: 0xCCDDEEFF
438        let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
439        let node_id = NodeId::from_mac_address(&mac);
440        assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
441    }
442
443    #[test]
444    fn test_node_id_from_mac_string() {
445        let node_id = NodeId::from_mac_string("AA:BB:CC:DD:EE:FF").unwrap();
446        assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
447
448        // Lowercase should work too
449        let node_id = NodeId::from_mac_string("aa:bb:cc:dd:ee:ff").unwrap();
450        assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
451
452        // Invalid formats
453        assert!(NodeId::from_mac_string("invalid").is_none());
454        assert!(NodeId::from_mac_string("AA:BB:CC:DD:EE").is_none()); // Too short
455        assert!(NodeId::from_mac_string("AA:BB:CC:DD:EE:FF:GG").is_none()); // Too long
456        assert!(NodeId::from_mac_string("ZZ:BB:CC:DD:EE:FF").is_none()); // Invalid hex
457    }
458
459    #[test]
460    fn test_hierarchy_level() {
461        assert_eq!(HierarchyLevel::from(0), HierarchyLevel::Platform);
462        assert_eq!(HierarchyLevel::from(3), HierarchyLevel::Company);
463        assert_eq!(u8::from(HierarchyLevel::Squad), 1);
464    }
465
466    #[test]
467    fn test_service_uuid() {
468        assert_eq!(
469            HIVE_SERVICE_UUID.to_string(),
470            "f47ac10b-58cc-4372-a567-0e02b2c3d479"
471        );
472    }
473
474    #[test]
475    fn test_capabilities() {
476        let caps = capabilities::LITE_NODE | capabilities::SENSOR_ACCEL | capabilities::HAS_GPS;
477        assert_eq!(caps, 0x0203);
478    }
479}