1#![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
112pub 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
132pub use document::{
134 HiveDocument, MergeResult, ENCRYPTED_MARKER, EXTENDED_MARKER, KEY_EXCHANGE_MARKER,
135 PEER_E2EE_MARKER,
136};
137
138pub 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;
147pub use security::{EncryptedDocument, EncryptionError, MeshEncryptionKey};
149#[cfg(feature = "std")]
151pub use security::{
152 KeyExchangeMessage, PeerEncryptedMessage, PeerIdentityKey, PeerSession, PeerSessionKey,
153 PeerSessionManager, SessionState,
154};
155
156#[cfg(feature = "std")]
158pub use gossip::{BroadcastAll, EmergencyAware, GossipStrategy, RandomFanout, SignalBasedFanout};
159#[cfg(feature = "std")]
160pub use persistence::{DocumentStore, FileStore, MemoryStore, SharedStore};
161
162pub const HIVE_SERVICE_UUID: uuid::Uuid = uuid::uuid!("f47ac10b-58cc-4372-a567-0e02b2c3d479");
166
167pub const HIVE_SERVICE_UUID_16BIT: u16 = 0xF47A;
172
173pub const CHAR_NODE_INFO_UUID: u16 = 0x0001;
175
176pub const CHAR_SYNC_STATE_UUID: u16 = 0x0002;
178
179pub const CHAR_SYNC_DATA_UUID: u16 = 0x0003;
181
182pub const CHAR_COMMAND_UUID: u16 = 0x0004;
184
185pub const CHAR_STATUS_UUID: u16 = 0x0005;
187
188pub const VERSION: &str = env!("CARGO_PKG_VERSION");
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
196pub struct NodeId {
197 id: u32,
199}
200
201impl NodeId {
202 pub fn new(id: u32) -> Self {
204 Self { id }
205 }
206
207 pub fn as_u32(&self) -> u32 {
209 self.id
210 }
211
212 pub fn parse(s: &str) -> Option<Self> {
214 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 pub fn from_mac_address(mac: &[u8; 6]) -> Self {
237 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 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
296pub mod capabilities {
300 pub const LITE_NODE: u16 = 0x0001;
302 pub const SENSOR_ACCEL: u16 = 0x0002;
304 pub const SENSOR_TEMP: u16 = 0x0004;
306 pub const SENSOR_BUTTON: u16 = 0x0008;
308 pub const ACTUATOR_LED: u16 = 0x0010;
310 pub const ACTUATOR_VIBRATE: u16 = 0x0020;
312 pub const HAS_DISPLAY: u16 = 0x0040;
314 pub const CAN_RELAY: u16 = 0x0080;
316 pub const CODED_PHY: u16 = 0x0100;
318 pub const HAS_GPS: u16 = 0x0200;
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
324#[repr(u8)]
325pub enum HierarchyLevel {
326 #[default]
328 Platform = 0,
329 Squad = 1,
331 Platoon = 2,
333 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 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 let node_id = NodeId::from_mac_string("aa:bb:cc:dd:ee:ff").unwrap();
388 assert_eq!(node_id.as_u32(), 0xCCDDEEFF);
389
390 assert!(NodeId::from_mac_string("invalid").is_none());
392 assert!(NodeId::from_mac_string("AA:BB:CC:DD:EE").is_none()); assert!(NodeId::from_mac_string("AA:BB:CC:DD:EE:FF:GG").is_none()); assert!(NodeId::from_mac_string("ZZ:BB:CC:DD:EE:FF").is_none()); }
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}