#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};
use async_trait::async_trait;
use core::time::Duration;
use crate::config::{BleConfig, BlePhy};
use crate::error::Result;
use crate::platform::BleAdapter;
use crate::NodeId;
#[derive(Debug, Clone)]
pub struct TransportCapabilities {
pub max_bandwidth_bps: u64,
pub typical_latency_ms: u32,
pub max_range_meters: u32,
pub bidirectional: bool,
pub reliable: bool,
pub battery_impact: u8,
pub supports_broadcast: bool,
pub requires_pairing: bool,
pub max_message_size: usize,
}
impl TransportCapabilities {
pub fn bluetooth_le() -> Self {
Self {
max_bandwidth_bps: 250_000, typical_latency_ms: 30,
max_range_meters: 100,
bidirectional: true,
reliable: true,
battery_impact: 15,
supports_broadcast: true,
requires_pairing: false,
max_message_size: 512,
}
}
pub fn bluetooth_le_coded() -> Self {
Self {
max_bandwidth_bps: 125_000, typical_latency_ms: 100,
max_range_meters: 400,
bidirectional: true,
reliable: true,
battery_impact: 20, supports_broadcast: true,
requires_pairing: false,
max_message_size: 512,
}
}
pub fn for_phy(phy: BlePhy) -> Self {
match phy {
BlePhy::Le1M => Self::bluetooth_le(),
BlePhy::Le2M => Self {
max_bandwidth_bps: 500_000,
typical_latency_ms: 20,
max_range_meters: 50,
..Self::bluetooth_le()
},
BlePhy::LeCodedS2 => Self {
max_bandwidth_bps: 250_000,
typical_latency_ms: 50,
max_range_meters: 200,
..Self::bluetooth_le()
},
BlePhy::LeCodedS8 => Self::bluetooth_le_coded(),
}
}
}
impl Default for TransportCapabilities {
fn default() -> Self {
Self::bluetooth_le()
}
}
pub trait BleConnection: Send + Sync {
fn peer_id(&self) -> &NodeId;
fn is_alive(&self) -> bool;
fn mtu(&self) -> u16;
fn phy(&self) -> BlePhy;
fn rssi(&self) -> Option<i8>;
fn connected_duration(&self) -> Duration;
}
pub struct BluetoothLETransport<A: BleAdapter> {
config: BleConfig,
adapter: A,
capabilities: TransportCapabilities,
}
impl<A: BleAdapter> BluetoothLETransport<A> {
pub fn new(config: BleConfig, adapter: A) -> Self {
let capabilities = TransportCapabilities::for_phy(config.phy.preferred_phy);
Self {
config,
adapter,
capabilities,
}
}
pub fn config(&self) -> &BleConfig {
&self.config
}
pub fn capabilities(&self) -> &TransportCapabilities {
&self.capabilities
}
pub fn node_id(&self) -> &NodeId {
&self.config.node_id
}
pub fn peer_link_info(&self, peer_id: &NodeId) -> Option<crate::peer::BlePeerLinkInfo> {
self.adapter.peer_link_info(peer_id)
}
}
#[async_trait]
pub trait MeshTransport: Send + Sync {
async fn start(&self) -> Result<()>;
async fn stop(&self) -> Result<()>;
async fn connect(&self, peer_id: &NodeId) -> Result<Box<dyn BleConnection>>;
async fn disconnect(&self, peer_id: &NodeId) -> Result<()>;
fn get_connection(&self, peer_id: &NodeId) -> Option<Box<dyn BleConnection>>;
fn peer_count(&self) -> usize;
fn connected_peers(&self) -> Vec<NodeId>;
fn is_connected(&self, peer_id: &NodeId) -> bool {
self.get_connection(peer_id).is_some()
}
async fn send_to(&self, peer_id: &NodeId, data: &[u8]) -> Result<usize> {
let _ = (peer_id, data);
Err(crate::error::BleError::NotSupported(
"send_to not implemented".into(),
))
}
fn capabilities(&self) -> &TransportCapabilities;
}
fn ble_uuid_from_u16(short: u16) -> uuid::Uuid {
uuid::Uuid::from_fields(
short as u32,
0x0000,
0x1000,
&[0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB],
)
}
#[async_trait]
impl<A: BleAdapter + Send + Sync> MeshTransport for BluetoothLETransport<A> {
async fn start(&self) -> Result<()> {
self.adapter.start().await
}
async fn stop(&self) -> Result<()> {
self.adapter.stop().await
}
async fn connect(&self, peer_id: &NodeId) -> Result<Box<dyn BleConnection>> {
self.adapter.connect(peer_id).await
}
async fn disconnect(&self, peer_id: &NodeId) -> Result<()> {
self.adapter.disconnect(peer_id).await
}
fn get_connection(&self, peer_id: &NodeId) -> Option<Box<dyn BleConnection>> {
self.adapter.get_connection(peer_id)
}
fn peer_count(&self) -> usize {
self.adapter.peer_count()
}
fn connected_peers(&self) -> Vec<NodeId> {
self.adapter.connected_peers()
}
async fn send_to(&self, peer_id: &NodeId, data: &[u8]) -> Result<usize> {
use crate::sync::protocol::chunk_data;
let conn = self.get_connection(peer_id).ok_or_else(|| {
crate::error::BleError::ConnectionFailed(format!("No connection to {}", peer_id))
})?;
let mtu = conn.mtu() as usize;
let chunks = chunk_data(data, mtu, 0);
let char_uuid = ble_uuid_from_u16(crate::CHAR_SYNC_DATA_UUID);
for chunk in &chunks {
self.adapter
.write_to_peer(peer_id, char_uuid, &chunk.encode())
.await?;
}
Ok(data.len())
}
fn capabilities(&self) -> &TransportCapabilities {
&self.capabilities
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_capabilities_for_phy() {
let caps = TransportCapabilities::for_phy(BlePhy::LeCodedS8);
assert_eq!(caps.max_range_meters, 400);
assert_eq!(caps.max_bandwidth_bps, 125_000);
}
#[test]
fn test_capabilities_le2m() {
let caps = TransportCapabilities::for_phy(BlePhy::Le2M);
assert_eq!(caps.max_range_meters, 50);
assert_eq!(caps.max_bandwidth_bps, 500_000);
}
#[test]
fn test_ble_uuid_from_u16() {
let uuid = ble_uuid_from_u16(0x0003);
assert_eq!(uuid.to_string(), "00000003-0000-1000-8000-00805f9b34fb");
}
#[test]
fn test_send_to_default_returns_error() {
use crate::platform::StubAdapter;
let config = BleConfig::default();
let adapter = StubAdapter::default();
let transport = BluetoothLETransport::new(config, adapter);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let result = rt.block_on(transport.send_to(&NodeId::new(0x222), b"hello"));
assert!(result.is_err());
}
#[tokio::test]
async fn test_mock_send_to() {
use crate::platform::mock::{MockBleAdapter, MockNetwork};
let network = MockNetwork::new();
let mut adapter1 = MockBleAdapter::new(NodeId::new(0x111), network.clone());
let mut adapter2 = MockBleAdapter::new(NodeId::new(0x222), network.clone());
adapter1.init(&BleConfig::default()).await.unwrap();
adapter2.init(&BleConfig::default()).await.unwrap();
adapter2
.start_advertising(&crate::config::DiscoveryConfig::default())
.await
.unwrap();
let _conn = adapter1.connect(&NodeId::new(0x222)).await.unwrap();
let transport = BluetoothLETransport::new(BleConfig::default(), adapter1);
let result = transport.send_to(&NodeId::new(0x222), b"hello mesh").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 10);
let packets = network.receive_data(&NodeId::new(0x222));
assert!(!packets.is_empty());
}
#[tokio::test]
async fn test_send_to_disconnected_peer() {
use crate::platform::mock::{MockBleAdapter, MockNetwork};
let network = MockNetwork::new();
let adapter = MockBleAdapter::new(NodeId::new(0x111), network);
let transport = BluetoothLETransport::new(BleConfig::default(), adapter);
let result = transport.send_to(&NodeId::new(0x999), b"hello").await;
assert!(result.is_err());
}
}