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//! ## Power Profiles
69//!
70//! | Profile | Duty Cycle | Watch Battery |
71//! |---------|------------|---------------|
72//! | Aggressive | 20% | ~6 hours |
73//! | Balanced | 10% | ~12 hours |
74//! | **LowPower** | **2%** | **~20+ hours** |
75//!
76//! ## Related ADRs
77//!
78//! - ADR-039: HIVE-BTLE Mesh Transport Crate
79//! - ADR-032: Pluggable Transport Abstraction
80//! - ADR-035: HIVE-Lite Embedded Nodes
81//! - ADR-037: Resource-Constrained Device Optimization
82
83#![cfg_attr(not(feature = "std"), no_std)]
84#![warn(missing_docs)]
85#![warn(rustdoc::missing_crate_level_docs)]
86
87#[cfg(not(feature = "std"))]
88extern crate alloc;
89
90pub mod config;
91pub mod discovery;
92pub mod document;
93pub mod document_sync;
94pub mod error;
95pub mod gatt;
96#[cfg(feature = "std")]
97pub mod gossip;
98pub mod hive_mesh;
99pub mod mesh;
100pub mod observer;
101pub mod peer;
102pub mod peer_manager;
103#[cfg(feature = "std")]
104pub mod persistence;
105pub mod phy;
106pub mod platform;
107pub mod power;
108pub mod security;
109pub mod sync;
110pub mod transport;
111
112// Re-exports for convenience
113pub use config::{
114    BleConfig, BlePhy, DiscoveryConfig, GattConfig, MeshConfig, PowerProfile, DEFAULT_MESH_ID,
115};
116#[cfg(feature = "std")]
117pub use discovery::Scanner;
118pub use discovery::{Advertiser, HiveBeacon, ScanFilter};
119pub use error::{BleError, Result};
120#[cfg(feature = "std")]
121pub use gatt::HiveGattService;
122pub use gatt::SyncProtocol;
123#[cfg(feature = "std")]
124pub use mesh::MeshManager;
125pub use mesh::{MeshRouter, MeshTopology, TopologyConfig, TopologyEvent};
126pub use phy::{PhyCapabilities, PhyController, PhyStrategy};
127pub use platform::{BleAdapter, ConnectionEvent, DisconnectReason, DiscoveredDevice, StubAdapter};
128pub use power::{BatteryState, RadioScheduler, SyncPriority};
129pub use sync::{GattSyncProtocol, SyncConfig, SyncState};
130pub use transport::{BleConnection, BluetoothLETransport, MeshTransport, TransportCapabilities};
131
132// New centralized mesh management types
133pub use document::{
134    HiveDocument, MergeResult, ENCRYPTED_MARKER, EXTENDED_MARKER, KEY_EXCHANGE_MARKER,
135    PEER_E2EE_MARKER,
136};
137
138// Security (mesh-wide and per-peer encryption)
139pub use document_sync::{DocumentCheck, DocumentSync};
140#[cfg(feature = "std")]
141pub use hive_mesh::{DataReceivedResult, HiveMesh, HiveMeshConfig};
142#[cfg(feature = "std")]
143pub use observer::{CollectingObserver, ObserverManager};
144pub use observer::{DisconnectReason as HiveDisconnectReason, HiveEvent, HiveObserver};
145pub use peer::{HivePeer, PeerManagerConfig, SignalStrength};
146pub use peer_manager::PeerManager;
147// Phase 1: Mesh-wide encryption
148pub use security::{EncryptedDocument, EncryptionError, MeshEncryptionKey};
149// Phase 2: Per-peer E2EE
150#[cfg(feature = "std")]
151pub use security::{
152    KeyExchangeMessage, PeerEncryptedMessage, PeerIdentityKey, PeerSession, PeerSessionKey,
153    PeerSessionManager, SessionState,
154};
155
156// Gossip and persistence abstractions
157#[cfg(feature = "std")]
158pub use gossip::{BroadcastAll, EmergencyAware, GossipStrategy, RandomFanout, SignalBasedFanout};
159#[cfg(feature = "std")]
160pub use persistence::{DocumentStore, FileStore, MemoryStore, SharedStore};
161
162/// HIVE BLE Service UUID (128-bit)
163///
164/// All HIVE nodes advertise this UUID for discovery.
165pub const HIVE_SERVICE_UUID: uuid::Uuid = uuid::uuid!("f47ac10b-58cc-4372-a567-0e02b2c3d479");
166
167/// HIVE BLE Service UUID (16-bit short form)
168///
169/// Derived from the first two bytes of the 128-bit UUID (0xF47A from f47ac10b).
170/// Used for space-constrained advertising to fit within 31-byte limit.
171pub const HIVE_SERVICE_UUID_16BIT: u16 = 0xF47A;
172
173/// HIVE Node Info Characteristic UUID
174pub const CHAR_NODE_INFO_UUID: u16 = 0x0001;
175
176/// HIVE Sync State Characteristic UUID
177pub const CHAR_SYNC_STATE_UUID: u16 = 0x0002;
178
179/// HIVE Sync Data Characteristic UUID
180pub const CHAR_SYNC_DATA_UUID: u16 = 0x0003;
181
182/// HIVE Command Characteristic UUID
183pub const CHAR_COMMAND_UUID: u16 = 0x0004;
184
185/// HIVE Status Characteristic UUID
186pub const CHAR_STATUS_UUID: u16 = 0x0005;
187
188/// Crate version
189pub const VERSION: &str = env!("CARGO_PKG_VERSION");
190
191/// Node identifier
192///
193/// Represents a unique node in the HIVE mesh. For BLE, this is typically
194/// derived from the Bluetooth MAC address or a configured value.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
196pub struct NodeId {
197    /// 32-bit node identifier
198    id: u32,
199}
200
201impl NodeId {
202    /// Create a new node ID from a 32-bit value
203    pub fn new(id: u32) -> Self {
204        Self { id }
205    }
206
207    /// Get the raw 32-bit ID value
208    pub fn as_u32(&self) -> u32 {
209        self.id
210    }
211
212    /// Create from a string representation (hex format)
213    pub fn parse(s: &str) -> Option<Self> {
214        // Try parsing as hex (with or without 0x prefix)
215        let s = s.trim_start_matches("0x").trim_start_matches("0X");
216        u32::from_str_radix(s, 16).ok().map(Self::new)
217    }
218
219    /// Derive a NodeId from a BLE MAC address.
220    ///
221    /// Uses the last 4 bytes of the 6-byte MAC address as the 32-bit node ID.
222    /// This provides a consistent node ID derived from the device's Bluetooth
223    /// hardware address.
224    ///
225    /// # Arguments
226    /// * `mac` - 6-byte MAC address array (e.g., [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])
227    ///
228    /// # Example
229    /// ```
230    /// use hive_btle::NodeId;
231    ///
232    /// let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
233    /// let node_id = NodeId::from_mac_address(&mac);
234    /// assert_eq!(node_id.as_u32(), 0x22334455);
235    /// ```
236    pub fn from_mac_address(mac: &[u8; 6]) -> Self {
237        // Use last 4 bytes: mac[2], mac[3], mac[4], mac[5]
238        let id = ((mac[2] as u32) << 24)
239            | ((mac[3] as u32) << 16)
240            | ((mac[4] as u32) << 8)
241            | (mac[5] as u32);
242        Self::new(id)
243    }
244
245    /// Derive a NodeId from a MAC address string.
246    ///
247    /// Parses a MAC address in "AA:BB:CC:DD:EE:FF" format and derives
248    /// the node ID from the last 4 bytes.
249    ///
250    /// # Arguments
251    /// * `mac_str` - MAC address string in colon-separated hex format
252    ///
253    /// # Returns
254    /// `Some(NodeId)` if parsing succeeds, `None` otherwise
255    ///
256    /// # Example
257    /// ```
258    /// use hive_btle::NodeId;
259    ///
260    /// let node_id = NodeId::from_mac_string("00:11:22:33:44:55").unwrap();
261    /// assert_eq!(node_id.as_u32(), 0x22334455);
262    /// ```
263    pub fn from_mac_string(mac_str: &str) -> Option<Self> {
264        let parts: Vec<&str> = mac_str.split(':').collect();
265        if parts.len() != 6 {
266            return None;
267        }
268
269        let mut mac = [0u8; 6];
270        for (i, part) in parts.iter().enumerate() {
271            mac[i] = u8::from_str_radix(part, 16).ok()?;
272        }
273
274        Some(Self::from_mac_address(&mac))
275    }
276}
277
278impl core::fmt::Display for NodeId {
279    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280        write!(f, "{:08X}", self.id)
281    }
282}
283
284impl From<u32> for NodeId {
285    fn from(id: u32) -> Self {
286        Self::new(id)
287    }
288}
289
290impl From<NodeId> for u32 {
291    fn from(node_id: NodeId) -> Self {
292        node_id.id
293    }
294}
295
296/// Node capability flags
297///
298/// Advertised in the HIVE beacon to indicate what this node can do.
299pub mod capabilities {
300    /// This is a HIVE-Lite node (minimal state, single parent)
301    pub const LITE_NODE: u16 = 0x0001;
302    /// Has accelerometer sensor
303    pub const SENSOR_ACCEL: u16 = 0x0002;
304    /// Has temperature sensor
305    pub const SENSOR_TEMP: u16 = 0x0004;
306    /// Has button input
307    pub const SENSOR_BUTTON: u16 = 0x0008;
308    /// Has LED output
309    pub const ACTUATOR_LED: u16 = 0x0010;
310    /// Has vibration motor
311    pub const ACTUATOR_VIBRATE: u16 = 0x0020;
312    /// Has display
313    pub const HAS_DISPLAY: u16 = 0x0040;
314    /// Can relay messages (not a leaf)
315    pub const CAN_RELAY: u16 = 0x0080;
316    /// Supports Coded PHY
317    pub const CODED_PHY: u16 = 0x0100;
318    /// Has GPS
319    pub const HAS_GPS: u16 = 0x0200;
320}
321
322/// Hierarchy levels in the HIVE mesh
323#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
324#[repr(u8)]
325pub enum HierarchyLevel {
326    /// Platform/soldier level (leaf nodes)
327    #[default]
328    Platform = 0,
329    /// Squad level
330    Squad = 1,
331    /// Platoon level
332    Platoon = 2,
333    /// Company level
334    Company = 3,
335}
336
337impl From<u8> for HierarchyLevel {
338    fn from(value: u8) -> Self {
339        match value {
340            0 => HierarchyLevel::Platform,
341            1 => HierarchyLevel::Squad,
342            2 => HierarchyLevel::Platoon,
343            3 => HierarchyLevel::Company,
344            _ => HierarchyLevel::Platform,
345        }
346    }
347}
348
349impl From<HierarchyLevel> for u8 {
350    fn from(level: HierarchyLevel) -> Self {
351        level as u8
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn test_node_id() {
361        let id = NodeId::new(0x12345678);
362        assert_eq!(id.as_u32(), 0x12345678);
363        assert_eq!(id.to_string(), "12345678");
364    }
365
366    #[test]
367    fn test_node_id_parse() {
368        assert_eq!(NodeId::parse("12345678").unwrap().as_u32(), 0x12345678);
369        assert_eq!(NodeId::parse("0x12345678").unwrap().as_u32(), 0x12345678);
370        assert!(NodeId::parse("not_hex").is_none());
371    }
372
373    #[test]
374    fn test_node_id_from_mac_address() {
375        // MAC: AA:BB:CC:DD:EE:FF -> NodeId from last 4 bytes: 0xCCDDEEFF
376        let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
377        let node_id = NodeId::from_mac_address(&mac);
378        assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
379    }
380
381    #[test]
382    fn test_node_id_from_mac_string() {
383        let node_id = NodeId::from_mac_string("AA:BB:CC:DD:EE:FF").unwrap();
384        assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
385
386        // Lowercase should work too
387        let node_id = NodeId::from_mac_string("aa:bb:cc:dd:ee:ff").unwrap();
388        assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
389
390        // Invalid formats
391        assert!(NodeId::from_mac_string("invalid").is_none());
392        assert!(NodeId::from_mac_string("AA:BB:CC:DD:EE").is_none()); // Too short
393        assert!(NodeId::from_mac_string("AA:BB:CC:DD:EE:FF:GG").is_none()); // Too long
394        assert!(NodeId::from_mac_string("ZZ:BB:CC:DD:EE:FF").is_none()); // Invalid hex
395    }
396
397    #[test]
398    fn test_hierarchy_level() {
399        assert_eq!(HierarchyLevel::from(0), HierarchyLevel::Platform);
400        assert_eq!(HierarchyLevel::from(3), HierarchyLevel::Company);
401        assert_eq!(u8::from(HierarchyLevel::Squad), 1);
402    }
403
404    #[test]
405    fn test_service_uuid() {
406        assert_eq!(
407            HIVE_SERVICE_UUID.to_string(),
408            "f47ac10b-58cc-4372-a567-0e02b2c3d479"
409        );
410    }
411
412    #[test]
413    fn test_capabilities() {
414        let caps = capabilities::LITE_NODE | capabilities::SENSOR_ACCEL | capabilities::HAS_GPS;
415        assert_eq!(caps, 0x0203);
416    }
417}