Skip to main content

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