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