use super::{Transport, TransportCapability, TransportMessage, MessageMetadata};
use crate::{Error, Result};
use async_trait::async_trait;
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::sync::Mutex;
pub const BITCHAT_SERVICE_UUID: &str = "00001800-0000-1000-8000-00805f9b34fb";
pub const MESSAGE_CHARACTERISTIC_UUID: &str = "00002a00-0000-1000-8000-00805f9b34fb";
pub const MAX_BLE_MESSAGE_SIZE: usize = 512;
pub const MAX_HOP_COUNT: u8 = 10;
pub const DEFAULT_TTL: u8 = 5;
#[derive(Debug, Clone)]
pub struct BlePeer {
pub address: String,
pub name: Option<String>,
pub rssi: i16,
pub last_seen: u64,
pub supports_pqc: bool,
}
pub struct BleTransport {
connected: bool,
peers: Arc<Mutex<Vec<BlePeer>>>,
incoming: Arc<Mutex<VecDeque<TransportMessage>>>,
outgoing: Arc<Mutex<VecDeque<TransportMessage>>>,
device_address: String,
}
impl BleTransport {
pub fn new() -> Self {
Self {
connected: false,
peers: Arc::new(Mutex::new(Vec::new())),
incoming: Arc::new(Mutex::new(VecDeque::new())),
outgoing: Arc::new(Mutex::new(VecDeque::new())),
device_address: Self::generate_device_address(),
}
}
fn generate_device_address() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
format!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>(),
rng.gen::<u8>()
)
}
pub async fn peers(&self) -> Vec<BlePeer> {
self.peers.lock().await.clone()
}
pub async fn start_scanning(&self) -> Result<()> {
if !self.connected {
return Err(Error::Ble("Not connected".into()));
}
tracing::info!("Started BLE scanning for BitChat peers");
Ok(())
}
pub async fn stop_scanning(&self) -> Result<()> {
tracing::info!("Stopped BLE scanning");
Ok(())
}
pub async fn start_advertising(&self) -> Result<()> {
if !self.connected {
return Err(Error::Ble("Not connected".into()));
}
tracing::info!("Started BLE advertising as BitChat peer");
Ok(())
}
async fn route_message(&self, message: &TransportMessage) -> Result<()> {
let metadata = match &message.metadata {
MessageMetadata::Ble { hop_count, ttl } => (*hop_count, *ttl),
_ => (0, DEFAULT_TTL),
};
let (hop_count, ttl) = metadata;
if hop_count >= MAX_HOP_COUNT || ttl == 0 {
return Err(Error::Ble("Message TTL expired".into()));
}
let peers = self.peers.lock().await;
if peers.is_empty() {
return Err(Error::Ble("No peers available".into()));
}
let mut outgoing = self.outgoing.lock().await;
let mut forwarded = message.clone();
forwarded.metadata = MessageMetadata::Ble {
hop_count: hop_count + 1,
ttl: ttl - 1,
};
outgoing.push_back(forwarded);
Ok(())
}
fn chunk_message(data: &[u8]) -> Vec<Vec<u8>> {
data.chunks(MAX_BLE_MESSAGE_SIZE)
.map(|c| c.to_vec())
.collect()
}
fn reassemble_message(chunks: &[Vec<u8>]) -> Vec<u8> {
chunks.iter().flatten().copied().collect()
}
}
impl Default for BleTransport {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Transport for BleTransport {
fn name(&self) -> &str {
"BLE Mesh"
}
fn capabilities(&self) -> Vec<TransportCapability> {
vec![
TransportCapability::Send,
TransportCapability::Receive,
TransportCapability::PostQuantum, ]
}
async fn is_connected(&self) -> bool {
self.connected
}
async fn connect(&mut self) -> Result<()> {
#[cfg(feature = "btleplug")]
{
use btleplug::api::{Central, Manager as _};
use btleplug::platform::Manager;
let manager = Manager::new().await
.map_err(|e| Error::Ble(e.to_string()))?;
let adapters = manager.adapters().await
.map_err(|e| Error::Ble(e.to_string()))?;
if adapters.is_empty() {
return Err(Error::Ble("No BLE adapters found".into()));
}
let adapter = &adapters[0];
adapter.start_scan(btleplug::api::ScanFilter::default()).await
.map_err(|e| Error::Ble(e.to_string()))?;
}
self.connected = true;
tracing::info!("BLE transport connected");
Ok(())
}
async fn disconnect(&mut self) -> Result<()> {
self.connected = false;
tracing::info!("BLE transport disconnected");
Ok(())
}
async fn send(&self, message: TransportMessage) -> Result<()> {
if !self.connected {
return Err(Error::Ble("Not connected".into()));
}
let data = bincode::serialize(&message)
.map_err(|e| Error::Serialization(e.to_string()))?;
if data.len() > MAX_BLE_MESSAGE_SIZE * 10 {
return Err(Error::Ble("Message too large for BLE".into()));
}
self.route_message(&message).await?;
Ok(())
}
async fn receive(&mut self) -> Result<TransportMessage> {
loop {
if let Some(msg) = self.incoming.lock().await.pop_front() {
return Ok(msg);
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
if !self.connected {
return Err(Error::Ble("Disconnected while waiting".into()));
}
}
}
async fn poll(&mut self) -> Result<Option<TransportMessage>> {
Ok(self.incoming.lock().await.pop_front())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_ble_transport_creation() {
let transport = BleTransport::new();
assert!(!transport.is_connected().await);
assert_eq!(transport.name(), "BLE Mesh");
}
#[tokio::test]
async fn test_chunk_message() {
let data = vec![0u8; 1500];
let chunks = BleTransport::chunk_message(&data);
assert_eq!(chunks.len(), 3);
let reassembled = BleTransport::reassemble_message(&chunks);
assert_eq!(reassembled, data);
}
}