1pub mod addr;
22pub mod discovery;
23pub mod io;
24pub mod pool;
25pub mod stats;
26mod tasks;
27
28use tasks::{accept_loop, pubkey_exchange, receive_loop, scan_probe_loop};
29
30use super::{
31 ConnectionState, DiscoveredPeer, PacketTx, Transport, TransportAddr, TransportError,
32 TransportId, TransportState, TransportType,
33};
34use crate::config::BleConfig;
35use crate::identity::NodeAddr;
36use addr::BleAddr;
37use discovery::DiscoveryBuffer;
38use io::{BleIo, BleStream};
39use pool::{BleConnection, ConnectionPool};
40use stats::BleStats;
41
42use secp256k1::XOnlyPublicKey;
43use std::collections::HashMap;
44use std::sync::Arc;
45use tokio::sync::Mutex;
46use tokio::task::JoinHandle;
47use tracing::{debug, info, warn};
48
49pub const DEFAULT_PSM: u16 = 0x0085;
53
54#[cfg(all(bluer_available, not(test)))]
59pub type DefaultBleTransport = BleTransport<io::BluerIo>;
60
61#[cfg(any(not(bluer_available), test))]
62pub type DefaultBleTransport = BleTransport<io::MockBleIo>;
63
64pub struct BleTransport<I: BleIo> {
74 transport_id: TransportId,
76 name: Option<String>,
78 config: BleConfig,
80 state: TransportState,
82 io: Arc<I>,
84 pool: Arc<Mutex<ConnectionPool<Arc<I::Stream>>>>,
86 connecting: Arc<Mutex<HashMap<TransportAddr, ConnectingEntry>>>,
88 packet_tx: PacketTx,
90 accept_task: Option<JoinHandle<()>>,
92 scan_probe_task: Option<JoinHandle<()>>,
94 discovery_buffer: Arc<DiscoveryBuffer>,
96 stats: Arc<BleStats>,
98 local_pubkey: Option<[u8; 32]>,
105}
106
107struct ConnectingEntry {
109 task: JoinHandle<()>,
110}
111
112impl<I: BleIo> BleTransport<I> {
113 pub fn new(
115 transport_id: TransportId,
116 name: Option<String>,
117 config: BleConfig,
118 io: I,
119 packet_tx: PacketTx,
120 ) -> Self {
121 let max_conns = config.max_connections();
122 Self {
123 transport_id,
124 name,
125 config,
126 state: TransportState::Configured,
127 io: Arc::new(io),
128 pool: Arc::new(Mutex::new(ConnectionPool::new(max_conns))),
129 connecting: Arc::new(Mutex::new(HashMap::new())),
130 packet_tx,
131 accept_task: None,
132 scan_probe_task: None,
133 discovery_buffer: Arc::new(DiscoveryBuffer::new(transport_id)),
134 stats: Arc::new(BleStats::new()),
135 local_pubkey: None,
136 }
137 }
138
139 pub fn name(&self) -> Option<&str> {
141 self.name.as_deref()
142 }
143
144 pub fn stats(&self) -> &Arc<BleStats> {
146 &self.stats
147 }
148
149 pub fn io(&self) -> &Arc<I> {
151 &self.io
152 }
153
154 pub fn set_local_pubkey(&mut self, pubkey: [u8; 32]) {
160 self.local_pubkey = Some(pubkey);
161 }
162
163 pub async fn start_async(&mut self) -> Result<(), TransportError> {
165 if !self.state.can_start() {
166 return Err(TransportError::AlreadyStarted);
167 }
168 self.state = TransportState::Starting;
169
170 let psm = self.config.psm();
171 let adapter = self.io.adapter_name().to_string();
172
173 let local_node_addr = self.local_pubkey.and_then(|pk| {
175 XOnlyPublicKey::from_slice(&pk)
176 .ok()
177 .map(|xonly| NodeAddr::from_pubkey(&xonly))
178 });
179
180 if self.config.accept_connections() {
182 match self.io.listen(psm).await {
183 Ok(acceptor) => {
184 let pool = Arc::clone(&self.pool);
185 let packet_tx = self.packet_tx.clone();
186 let transport_id = self.transport_id;
187 let stats = Arc::clone(&self.stats);
188 let max_conns = self.config.max_connections();
189
190 self.accept_task = Some(tokio::spawn(accept_loop(
191 acceptor,
192 pool,
193 packet_tx,
194 transport_id,
195 stats,
196 max_conns,
197 self.local_pubkey,
198 Arc::clone(&self.discovery_buffer),
199 local_node_addr,
200 )));
201 debug!(adapter = %adapter, psm = psm, "BLE accept loop started");
202 }
203 Err(e) => {
204 warn!(adapter = %adapter, error = %e, "failed to start BLE listener");
205 self.state = TransportState::Failed;
206 return Err(e);
207 }
208 }
209 }
210
211 if self.config.advertise() {
213 if let Err(e) = self.io.start_advertising().await {
214 warn!(adapter = %adapter, error = %e, "failed to start BLE advertising");
215 } else {
216 self.stats.record_advertisement();
217 debug!(adapter = %adapter, "BLE advertising started (continuous)");
218 }
219 }
220
221 if self.config.scan() {
223 match self.io.start_scanning().await {
224 Ok(scanner) => {
225 self.scan_probe_task = Some(tokio::spawn(scan_probe_loop::<I>(
226 scanner,
227 Arc::clone(&self.io),
228 Arc::clone(&self.pool),
229 Arc::clone(&self.discovery_buffer),
230 Arc::clone(&self.stats),
231 self.local_pubkey,
232 self.config.psm(),
233 self.config.connect_timeout_ms(),
234 self.config.probe_cooldown_secs(),
235 local_node_addr,
236 self.packet_tx.clone(),
237 self.transport_id,
238 )));
239 debug!(adapter = %adapter, "BLE scan+probe loop started");
240 }
241 Err(e) => {
242 warn!(adapter = %adapter, error = %e, "failed to start BLE scanning");
243 }
244 }
245 }
246
247 self.state = TransportState::Up;
248 info!(adapter = %adapter, psm = psm, "BLE transport started");
249 Ok(())
250 }
251
252 pub async fn stop_async(&mut self) -> Result<(), TransportError> {
254 let _ = self.io.stop_advertising().await;
256
257 if let Some(task) = self.accept_task.take() {
259 task.abort();
260 }
261
262 if let Some(task) = self.scan_probe_task.take() {
264 task.abort();
265 }
266
267 {
269 let mut connecting = self.connecting.lock().await;
270 for (_, entry) in connecting.drain() {
271 entry.task.abort();
272 }
273 }
274
275 {
277 let mut pool = self.pool.lock().await;
278 for addr in pool.addrs() {
279 pool.remove(&addr);
280 }
281 }
282
283 self.state = TransportState::Down;
284 info!("BLE transport stopped");
285 Ok(())
286 }
287
288 pub async fn send_async(
295 &self,
296 addr: &TransportAddr,
297 data: &[u8],
298 ) -> Result<usize, TransportError> {
299 let pool = self.pool.lock().await;
300 let conn = match pool.get(addr) {
301 Some(c) => c,
302 None => {
303 drop(pool);
305 let _ = self.connect_async(addr).await;
307 return Err(TransportError::SendFailed("not connected".into()));
308 }
309 };
310
311 let mtu = conn.effective_mtu() as usize;
313 if data.len() > mtu {
314 self.stats.record_mtu_exceeded();
315 return Err(TransportError::MtuExceeded {
316 packet_size: data.len(),
317 mtu: mtu as u16,
318 });
319 }
320
321 match conn.stream.send(data).await {
322 Ok(()) => {
323 self.stats.record_send(data.len());
324 Ok(data.len())
325 }
326 Err(e) => {
327 self.stats.record_send_error();
328 drop(pool);
330 let mut pool = self.pool.lock().await;
331 pool.remove(addr);
332 warn!(addr = %addr, error = %e, "BLE send failed, connection removed");
333 Err(e)
334 }
335 }
336 }
337
338 #[allow(dead_code)]
343 async fn connect_inline(&self, addr: &TransportAddr) -> Result<(), TransportError> {
344 let ble_addr = BleAddr::parse(
345 addr.as_str()
346 .ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?,
347 )?;
348
349 let psm = self.config.psm();
350 let timeout_ms = self.config.connect_timeout_ms();
351
352 let stream = match tokio::time::timeout(
353 std::time::Duration::from_millis(timeout_ms),
354 self.io.connect(&ble_addr, psm),
355 )
356 .await
357 {
358 Ok(Ok(stream)) => stream,
359 Ok(Err(e)) => {
360 debug!(addr = %addr, error = %e, "BLE connect-on-send failed");
361 return Err(TransportError::ConnectionRefused);
362 }
363 Err(_) => {
364 self.stats.record_connect_timeout();
365 debug!(addr = %addr, "BLE connect-on-send timeout");
366 return Err(TransportError::Timeout);
367 }
368 };
369
370 if let Some(ref our_pubkey) = self.local_pubkey {
372 match pubkey_exchange(&stream, our_pubkey).await {
373 Ok(peer_pubkey) => {
374 debug!(addr = %addr, "BLE outbound pubkey exchange complete");
375 self.discovery_buffer
376 .add_peer_with_pubkey(&ble_addr, peer_pubkey);
377 }
378 Err(e) => {
379 warn!(addr = %addr, error = %e, "BLE outbound pubkey exchange failed");
380 return Err(e);
381 }
382 }
383 }
384
385 self.promote_connection(addr, &ble_addr, stream).await
386 }
387
388 async fn promote_connection(
392 &self,
393 addr: &TransportAddr,
394 ble_addr: &BleAddr,
395 stream: I::Stream,
396 ) -> Result<(), TransportError> {
397 let send_mtu = stream.send_mtu();
398 let recv_mtu = stream.recv_mtu();
399 let stream = Arc::new(stream);
400
401 let recv_task = tokio::spawn(receive_loop(
402 Arc::clone(&stream),
403 addr.clone(),
404 Arc::clone(&self.pool),
405 self.packet_tx.clone(),
406 self.transport_id,
407 Arc::clone(&self.stats),
408 recv_mtu,
409 ));
410
411 let conn = BleConnection {
412 stream,
413 recv_task: Some(recv_task),
414 send_mtu,
415 recv_mtu,
416 established_at: tokio::time::Instant::now(),
417 is_static: false,
418 addr: ble_addr.clone(),
419 };
420
421 let mut pool = self.pool.lock().await;
422 match pool.insert(addr.clone(), conn) {
423 Ok(Some(evicted)) => {
424 self.stats.record_pool_eviction();
425 debug!(addr = %addr, evicted = %evicted, "BLE connection established (evicted peer)");
426 }
427 Ok(None) => {
428 debug!(addr = %addr, "BLE connection established");
429 }
430 Err(e) => {
431 warn!(addr = %addr, error = %e, "BLE pool full, connection dropped");
432 self.stats.record_connection_rejected();
433 return Err(TransportError::SendFailed("pool full".into()));
434 }
435 }
436 self.stats.record_connection_established();
437 Ok(())
438 }
439
440 pub async fn connect_async(&self, addr: &TransportAddr) -> Result<(), TransportError> {
445 {
447 let pool = self.pool.lock().await;
448 if pool.contains(addr) {
449 return Ok(());
450 }
451 }
452
453 {
455 let connecting = self.connecting.lock().await;
456 if connecting.contains_key(addr) {
457 return Ok(());
458 }
459 }
460
461 let ble_addr = BleAddr::parse(
462 addr.as_str()
463 .ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?,
464 )?;
465
466 let io = Arc::clone(&self.io);
467 let pool = Arc::clone(&self.pool);
468 let connecting = Arc::clone(&self.connecting);
469 let packet_tx = self.packet_tx.clone();
470 let transport_id = self.transport_id;
471 let stats = Arc::clone(&self.stats);
472 let psm = self.config.psm();
473 let timeout_ms = self.config.connect_timeout_ms();
474 let addr_clone = addr.clone();
475 let local_pubkey = self.local_pubkey;
476 let discovery_buffer = Arc::clone(&self.discovery_buffer);
477
478 let task = tokio::spawn(async move {
479 let result = tokio::time::timeout(
480 std::time::Duration::from_millis(timeout_ms),
481 io.connect(&ble_addr, psm),
482 )
483 .await;
484
485 connecting.lock().await.remove(&addr_clone);
487
488 match result {
489 Ok(Ok(stream)) => {
490 if let Some(ref our_pubkey) = local_pubkey {
492 match pubkey_exchange(&stream, our_pubkey).await {
493 Ok(peer_pubkey) => {
494 debug!(addr = %addr_clone, "BLE outbound pubkey exchange complete");
495 discovery_buffer.add_peer_with_pubkey(&ble_addr, peer_pubkey);
496 }
497 Err(e) => {
498 warn!(
499 addr = %addr_clone, error = %e,
500 "BLE outbound pubkey exchange failed"
501 );
502 return;
503 }
504 }
505 }
506
507 let send_mtu = stream.send_mtu();
508 let recv_mtu = stream.recv_mtu();
509 let stream = Arc::new(stream);
510
511 let recv_task = tokio::spawn(receive_loop(
512 Arc::clone(&stream),
513 addr_clone.clone(),
514 Arc::clone(&pool),
515 packet_tx,
516 transport_id,
517 Arc::clone(&stats),
518 recv_mtu,
519 ));
520
521 let conn = BleConnection {
522 stream,
523 recv_task: Some(recv_task),
524 send_mtu,
525 recv_mtu,
526 established_at: tokio::time::Instant::now(),
527 is_static: false,
528 addr: ble_addr,
529 };
530
531 let mut pool = pool.lock().await;
532 match pool.insert(addr_clone.clone(), conn) {
533 Ok(Some(evicted)) => {
534 stats.record_pool_eviction();
535 debug!(addr = %addr_clone, evicted = %evicted, "BLE connection established (evicted peer)");
536 }
537 Ok(None) => {
538 debug!(addr = %addr_clone, "BLE connection established");
539 }
540 Err(e) => {
541 warn!(addr = %addr_clone, error = %e, "BLE pool full, connection dropped");
542 stats.record_connection_rejected();
543 return;
544 }
545 }
546 stats.record_connection_established();
547 }
548 Ok(Err(e)) => {
549 debug!(addr = %addr_clone, error = %e, "BLE connect failed");
550 }
551 Err(_) => {
552 stats.record_connect_timeout();
553 debug!(addr = %addr_clone, "BLE connect timeout");
554 }
555 }
556 });
557
558 self.connecting
559 .lock()
560 .await
561 .insert(addr.clone(), ConnectingEntry { task });
562
563 Ok(())
564 }
565
566 pub fn connection_state_sync(&self, addr: &TransportAddr) -> ConnectionState {
568 if let Ok(pool) = self.pool.try_lock()
570 && pool.contains(addr)
571 {
572 return ConnectionState::Connected;
573 }
574
575 if let Ok(connecting) = self.connecting.try_lock()
577 && connecting.contains_key(addr)
578 {
579 return ConnectionState::Connecting;
580 }
581
582 ConnectionState::None
583 }
584
585 pub async fn close_connection_async(&self, addr: &TransportAddr) {
587 let mut pool = self.pool.lock().await;
588 if let Some(conn) = pool.remove(addr) {
589 debug!(addr = %addr, "BLE connection closed");
590 drop(conn); }
592 }
593
594 pub fn link_mtu(&self, addr: &TransportAddr) -> u16 {
596 if let Ok(pool) = self.pool.try_lock()
597 && let Some(conn) = pool.get(addr)
598 {
599 return conn.effective_mtu();
600 }
601 self.config.mtu()
602 }
603}
604
605impl<I: BleIo> Transport for BleTransport<I> {
606 fn transport_id(&self) -> TransportId {
607 self.transport_id
608 }
609
610 fn transport_type(&self) -> &TransportType {
611 &TransportType::BLE
612 }
613
614 fn state(&self) -> TransportState {
615 self.state
616 }
617
618 fn mtu(&self) -> u16 {
619 self.config.mtu()
620 }
621
622 fn link_mtu(&self, addr: &TransportAddr) -> u16 {
623 self.link_mtu(addr)
624 }
625
626 fn start(&mut self) -> Result<(), TransportError> {
627 Err(TransportError::NotSupported(
628 "use start_async() for BLE transport".into(),
629 ))
630 }
631
632 fn stop(&mut self) -> Result<(), TransportError> {
633 Err(TransportError::NotSupported(
634 "use stop_async() for BLE transport".into(),
635 ))
636 }
637
638 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
639 Err(TransportError::NotSupported(
640 "use send_async() for BLE transport".into(),
641 ))
642 }
643
644 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
645 Ok(self.discovery_buffer.take())
646 }
647
648 fn auto_connect(&self) -> bool {
649 self.config.auto_connect()
650 }
651
652 fn accept_connections(&self) -> bool {
653 self.config.accept_connections()
654 }
655
656 fn close_connection(&self, _addr: &TransportAddr) {
657 }
659}
660
661#[cfg(test)]
666mod tests;