1use crate::transport::TransportError;
8
9use super::{DEFAULT_PSM, addr::BleAddr, bootstrap::BleBootstrap};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct BleCandidate {
14 pub addr: BleAddr,
15 pub bootstrap: BleBootstrap,
16}
17
18pub trait BleStream: Send + Sync {
24 fn send(
26 &self,
27 data: &[u8],
28 ) -> impl std::future::Future<Output = Result<(), TransportError>> + Send;
29
30 fn recv(
34 &self,
35 buf: &mut [u8],
36 ) -> impl std::future::Future<Output = Result<usize, TransportError>> + Send;
37
38 fn send_mtu(&self) -> u16;
40
41 fn recv_mtu(&self) -> u16;
43
44 fn remote_addr(&self) -> &BleAddr;
46}
47
48pub trait BleAcceptor: Send {
50 type Stream: BleStream + 'static;
52
53 fn accept(
55 &mut self,
56 ) -> impl std::future::Future<Output = Result<Self::Stream, TransportError>> + Send;
57
58 fn psm(&self) -> u16;
60}
61
62pub trait BleScanner: Send {
64 fn next(&mut self) -> impl std::future::Future<Output = Option<BleCandidate>> + Send;
68}
69
70pub trait BleIo: Send + Sync + 'static {
76 type Stream: BleStream + 'static;
78 type Acceptor: BleAcceptor<Stream = Self::Stream> + 'static;
80 type Scanner: BleScanner + 'static;
82
83 fn listen(
85 &self,
86 psm: u16,
87 ) -> impl std::future::Future<Output = Result<Self::Acceptor, TransportError>> + Send;
88
89 fn connect(
91 &self,
92 addr: &BleAddr,
93 psm: u16,
94 ) -> impl std::future::Future<Output = Result<Self::Stream, TransportError>> + Send;
95
96 fn start_advertising(
98 &self,
99 bootstrap: BleBootstrap,
100 ) -> impl std::future::Future<Output = Result<(), TransportError>> + Send;
101
102 fn stop_advertising(
104 &self,
105 ) -> impl std::future::Future<Output = Result<(), TransportError>> + Send;
106
107 fn start_scanning(
109 &self,
110 ) -> impl std::future::Future<Output = Result<Self::Scanner, TransportError>> + Send;
111
112 fn local_addr(&self) -> Result<BleAddr, TransportError>;
114
115 fn adapter_name(&self) -> &str;
117}
118
119#[cfg(bluer_available)]
124mod bluer_impl {
125 use super::*;
126 use crate::transport::TransportError;
127 use crate::transport::ble::bootstrap::{
128 FIPS_BLE_V2_BOOTSTRAP_CHARACTERISTIC_UUID, FIPS_BLE_V2_SERVICE_UUID,
129 };
130
131 use bluer::gatt::local::{Application, Characteristic, CharacteristicRead, Service};
132 use bluer::l2cap::{SeqPacket, SeqPacketListener, Socket, SocketAddr};
133 use bluer::{
134 AdapterEvent, AddressType, DiscoveryFilter, DiscoveryTransport, adv::Advertisement,
135 };
136 use futures::{FutureExt, StreamExt};
137 use std::collections::{BTreeSet, HashSet, VecDeque};
138 use std::pin::Pin;
139 use tokio::sync::Mutex;
140 use tracing::{debug, trace};
141
142 pub const FIPS_SERVICE_UUID: bluer::Uuid = bluer::Uuid::from_u128(FIPS_BLE_V2_SERVICE_UUID);
147 pub const FIPS_BOOTSTRAP_CHARACTERISTIC_UUID: bluer::Uuid =
148 bluer::Uuid::from_u128(FIPS_BLE_V2_BOOTSTRAP_CHARACTERISTIC_UUID);
149
150 fn map_err(context: &str, e: bluer::Error) -> TransportError {
152 TransportError::Io(std::io::Error::other(format!("{}: {}", context, e)))
153 }
154
155 fn map_io_err(context: &str, e: std::io::Error) -> TransportError {
157 TransportError::Io(std::io::Error::new(e.kind(), format!("{}: {}", context, e)))
158 }
159
160 pub struct BluerStream {
166 conn: SeqPacket,
167 remote: BleAddr,
168 send_mtu: u16,
169 recv_mtu: u16,
170 }
171
172 impl BluerStream {
173 pub fn new(conn: SeqPacket, remote: BleAddr) -> Result<Self, TransportError> {
175 let send_mtu = conn.send_mtu().map_err(|e| map_io_err("send_mtu", e))? as u16;
176 let recv_mtu = conn.recv_mtu().map_err(|e| map_io_err("recv_mtu", e))? as u16;
177
178 match conn.as_ref().phy() {
180 Ok(phy) => {
181 debug!(addr = %remote, phy, send_mtu, recv_mtu, "BLE connection established")
182 }
183 Err(_) => {
184 debug!(addr = %remote, send_mtu, recv_mtu, "BLE connection established (PHY query unsupported)")
185 }
186 }
187
188 Ok(Self {
189 conn,
190 remote,
191 send_mtu,
192 recv_mtu,
193 })
194 }
195 }
196
197 impl BleStream for BluerStream {
198 async fn send(&self, data: &[u8]) -> Result<(), TransportError> {
199 self.conn
200 .send(data)
201 .await
202 .map(|_| ())
203 .map_err(|e| TransportError::SendFailed(format!("{}", e)))
204 }
205
206 async fn recv(&self, buf: &mut [u8]) -> Result<usize, TransportError> {
207 self.conn
208 .recv(buf)
209 .await
210 .map_err(|e| TransportError::RecvFailed(format!("{}", e)))
211 }
212
213 fn send_mtu(&self) -> u16 {
214 self.send_mtu
215 }
216
217 fn recv_mtu(&self) -> u16 {
218 self.recv_mtu
219 }
220
221 fn remote_addr(&self) -> &BleAddr {
222 &self.remote
223 }
224 }
225
226 pub struct BluerAcceptor {
232 listener: SeqPacketListener,
233 adapter_name: String,
234 psm: u16,
235 }
236
237 impl BleAcceptor for BluerAcceptor {
238 type Stream = BluerStream;
239
240 async fn accept(&mut self) -> Result<BluerStream, TransportError> {
241 let (conn, peer_sa) = self
242 .listener
243 .accept()
244 .await
245 .map_err(|e| map_io_err("accept", e))?;
246
247 let remote = BleAddr::from_bluer(peer_sa.addr, &self.adapter_name);
248 BluerStream::new(conn, remote)
249 }
250
251 fn psm(&self) -> u16 {
252 self.psm
253 }
254 }
255
256 pub struct BluerScanner {
262 events: Pin<Box<dyn futures::Stream<Item = AdapterEvent> + Send>>,
263 adapter: bluer::Adapter,
264 adapter_name: String,
265 seeded: VecDeque<bluer::Address>,
266 }
267
268 impl BleScanner for BluerScanner {
269 async fn next(&mut self) -> Option<BleCandidate> {
270 loop {
271 let addr = match self.seeded.pop_front() {
272 Some(addr) => addr,
273 None => match self.events.next().await {
274 Some(AdapterEvent::DeviceAdded(addr)) => addr,
275 Some(_) => continue,
276 None => return None,
277 },
278 };
279 let Ok(device) = self.adapter.device(addr) else {
280 continue;
281 };
282 match device.uuids().await {
283 Ok(Some(uuids)) if uuids.contains(&FIPS_SERVICE_UUID) => {}
284 Ok(_) => {
285 trace!(addr = %addr, "BLE scanner: device without FIPS UUID");
286 continue;
287 }
288 Err(error) => {
289 trace!(addr = %addr, %error, "BLE scanner: failed to read UUIDs");
290 continue;
291 }
292 }
293 match read_bootstrap(&device).await {
294 Ok(bootstrap) => {
295 let addr = BleAddr::from_bluer(addr, &self.adapter_name);
296 debug!(addr = %addr, psm = bootstrap.psm, "BLE scanner: FIPS peer found");
297 return Some(BleCandidate { addr, bootstrap });
298 }
299 Err(error) => {
300 trace!(addr = %addr, %error, "BLE scanner: bootstrap read failed");
301 }
302 }
303 }
304 }
305 }
306
307 async fn read_bootstrap(device: &bluer::Device) -> Result<BleBootstrap, TransportError> {
308 tokio::time::timeout(
309 std::time::Duration::from_secs(10),
310 read_bootstrap_inner(device),
311 )
312 .await
313 .map_err(|_| TransportError::Timeout)?
314 }
315
316 async fn read_bootstrap_inner(device: &bluer::Device) -> Result<BleBootstrap, TransportError> {
317 if !device
318 .is_connected()
319 .await
320 .map_err(|error| map_err("is_connected", error))?
321 && let Err(error) = device.connect().await
322 && !device.is_connected().await.unwrap_or(false)
323 {
324 return Err(map_err("GATT connect", error));
325 }
326 for service in device
327 .services()
328 .await
329 .map_err(|error| map_err("enumerate GATT services", error))?
330 {
331 if service
332 .uuid()
333 .await
334 .map_err(|error| map_err("read GATT service UUID", error))?
335 != FIPS_SERVICE_UUID
336 {
337 continue;
338 }
339 for characteristic in service
340 .characteristics()
341 .await
342 .map_err(|error| map_err("enumerate GATT characteristics", error))?
343 {
344 if characteristic
345 .uuid()
346 .await
347 .map_err(|error| map_err("read GATT characteristic UUID", error))?
348 == FIPS_BOOTSTRAP_CHARACTERISTIC_UUID
349 {
350 let bytes = characteristic
351 .read()
352 .await
353 .map_err(|error| map_err("read BLE bootstrap", error))?;
354 return BleBootstrap::decode(&bytes).map_err(|error| {
355 TransportError::RecvFailed(format!("invalid BLE bootstrap: {error}"))
356 });
357 }
358 }
359 }
360 Err(TransportError::RecvFailed(
361 "FIPS BLE bootstrap characteristic not found".into(),
362 ))
363 }
364
365 pub struct BluerIo {
371 _session: bluer::Session,
372 adapter: bluer::Adapter,
373 adapter_name: String,
374 adv_handle: Mutex<Option<bluer::adv::AdvertisementHandle>>,
375 gatt_handle: Mutex<Option<bluer::gatt::local::ApplicationHandle>>,
376 mtu: u16,
377 }
378
379 impl BluerIo {
380 pub async fn new(adapter_name: &str, mtu: u16) -> Result<Self, TransportError> {
384 let session = bluer::Session::new()
385 .await
386 .map_err(|e| map_err("Session::new", e))?;
387
388 let adapter = if adapter_name == "default" {
389 session
390 .default_adapter()
391 .await
392 .map_err(|e| map_err("default_adapter", e))?
393 } else {
394 session
395 .adapter(adapter_name)
396 .map_err(|e| map_err("adapter", e))?
397 };
398
399 adapter
400 .set_powered(true)
401 .await
402 .map_err(|e| map_err("set_powered", e))?;
403
404 let name = adapter.name().to_string();
405 debug!(adapter = %name, "BluerIo initialized");
406
407 Ok(Self {
408 _session: session,
409 adapter,
410 adapter_name: name,
411 adv_handle: Mutex::new(None),
412 gatt_handle: Mutex::new(None),
413 mtu,
414 })
415 }
416 }
417
418 impl BleIo for BluerIo {
419 type Stream = BluerStream;
420 type Acceptor = BluerAcceptor;
421 type Scanner = BluerScanner;
422
423 async fn listen(&self, psm: u16) -> Result<Self::Acceptor, TransportError> {
424 let local_addr = self
425 .adapter
426 .address()
427 .await
428 .map_err(|e| map_err("address", e))?;
429
430 let sa = SocketAddr::new(local_addr, AddressType::LePublic, psm);
431 let listener = SeqPacketListener::bind(sa)
432 .await
433 .map_err(|e| map_io_err("bind", e))?;
434
435 listener
437 .as_ref()
438 .set_recv_mtu(self.mtu)
439 .map_err(|e| map_io_err("set_recv_mtu", e))?;
440
441 if let Err(e) = listener.as_ref().set_power_forced_active(true) {
443 debug!(error = %e, "BLE listener: set_power_forced_active not supported");
444 }
445
446 debug!(psm, mtu = self.mtu, "BLE listener bound");
447
448 Ok(BluerAcceptor {
449 listener,
450 adapter_name: self.adapter_name.clone(),
451 psm,
452 })
453 }
454
455 async fn connect(&self, addr: &BleAddr, psm: u16) -> Result<Self::Stream, TransportError> {
456 let target_sa = addr.to_socket_addr(psm)?;
457
458 let socket = Socket::<SeqPacket>::new_seq_packet()
459 .map_err(|e| map_io_err("new_seq_packet", e))?;
460 socket
461 .bind(SocketAddr::any_le())
462 .map_err(|e| map_io_err("bind", e))?;
463 socket
464 .set_recv_mtu(self.mtu)
465 .map_err(|e| map_io_err("set_recv_mtu", e))?;
466
467 if let Err(e) = socket.set_power_forced_active(true) {
469 debug!(error = %e, "BLE connect: set_power_forced_active not supported");
470 }
471
472 let conn = socket
473 .connect(target_sa)
474 .await
475 .map_err(|e| map_io_err("connect", e))?;
476
477 let remote = addr.clone();
478 BluerStream::new(conn, remote)
479 }
480
481 async fn start_advertising(&self, bootstrap: BleBootstrap) -> Result<(), TransportError> {
482 let bootstrap_bytes = bootstrap.encode().to_vec();
483 let app = Application {
484 services: vec![Service {
485 uuid: FIPS_SERVICE_UUID,
486 primary: true,
487 characteristics: vec![Characteristic {
488 uuid: FIPS_BOOTSTRAP_CHARACTERISTIC_UUID,
489 read: Some(CharacteristicRead {
490 read: true,
491 fun: Box::new(move |_| {
492 let value = bootstrap_bytes.clone();
493 async move { Ok(value) }.boxed()
494 }),
495 ..Default::default()
496 }),
497 ..Default::default()
498 }],
499 ..Default::default()
500 }],
501 ..Default::default()
502 };
503 let gatt_handle = self
504 .adapter
505 .serve_gatt_application(app)
506 .await
507 .map_err(|error| map_err("serve BLE bootstrap GATT service", error))?;
508 let adv = Advertisement {
509 advertisement_type: bluer::adv::Type::Peripheral,
510 service_uuids: {
511 let mut s = BTreeSet::new();
512 s.insert(FIPS_SERVICE_UUID);
513 s
514 },
515 local_name: Some("fips".to_string()),
516 min_interval: Some(std::time::Duration::from_millis(400)),
517 max_interval: Some(std::time::Duration::from_millis(600)),
518 ..Default::default()
519 };
520
521 let handle = match self.adapter.advertise(adv).await {
522 Ok(handle) => handle,
523 Err(error) => {
524 drop(gatt_handle);
525 return Err(map_err("advertise", error));
526 }
527 };
528
529 *self.gatt_handle.lock().await = Some(gatt_handle);
530 *self.adv_handle.lock().await = Some(handle);
531 debug!(
532 psm = bootstrap.psm,
533 max_packet = bootstrap.max_packet,
534 "BLE advertising started"
535 );
536 Ok(())
537 }
538
539 async fn stop_advertising(&self) -> Result<(), TransportError> {
540 let _ = self.adv_handle.lock().await.take();
541 let _ = self.gatt_handle.lock().await.take();
542 debug!("BLE advertising stopped");
543 Ok(())
544 }
545
546 async fn start_scanning(&self) -> Result<Self::Scanner, TransportError> {
547 let filter = DiscoveryFilter {
549 transport: DiscoveryTransport::Le,
550 uuids: {
551 let mut s = HashSet::new();
552 s.insert(FIPS_SERVICE_UUID);
553 s
554 },
555 ..Default::default()
556 };
557
558 self.adapter
559 .set_discovery_filter(filter)
560 .await
561 .map_err(|e| map_err("set_discovery_filter", e))?;
562
563 let events = self
564 .adapter
565 .discover_devices()
566 .await
567 .map_err(|e| map_err("discover_devices", e))?;
568
569 let mut seeded = VecDeque::new();
572 if let Ok(cached) = self.adapter.device_addresses().await {
573 for addr in cached {
574 let Ok(device) = self.adapter.device(addr) else {
575 continue;
576 };
577 if device
578 .uuids()
579 .await
580 .ok()
581 .flatten()
582 .is_some_and(|uuids| uuids.contains(&FIPS_SERVICE_UUID))
583 {
584 seeded.push_back(addr);
585 }
586 }
587 }
588
589 debug!("BLE scanning started");
590
591 Ok(BluerScanner {
592 events: Box::pin(events),
593 adapter: self.adapter.clone(),
594 adapter_name: self.adapter_name.clone(),
595 seeded,
596 })
597 }
598
599 fn local_addr(&self) -> Result<BleAddr, TransportError> {
600 let addr = futures::executor::block_on(self.adapter.address())
604 .map_err(|e| map_err("address", e))?;
605 Ok(BleAddr::from_bluer(addr, &self.adapter_name))
606 }
607
608 fn adapter_name(&self) -> &str {
609 &self.adapter_name
610 }
611 }
612
613 const _: () = {
615 fn require<T: Send + Sync>() {}
616 let _ = require::<BluerIo>;
617 };
618}
619
620#[cfg(bluer_available)]
621pub use bluer_impl::{BluerAcceptor, BluerIo, BluerScanner, BluerStream, FIPS_SERVICE_UUID};
622
623pub struct MockBleStream {
629 addr: BleAddr,
630 send_mtu: u16,
631 recv_mtu: u16,
632 tx: tokio::sync::mpsc::Sender<Vec<u8>>,
633 rx: tokio::sync::Mutex<tokio::sync::mpsc::Receiver<Vec<u8>>>,
634}
635
636impl MockBleStream {
637 pub fn pair(addr_a: BleAddr, addr_b: BleAddr, mtu: u16) -> (Self, Self) {
639 let (tx_a, rx_a) = tokio::sync::mpsc::channel(64);
640 let (tx_b, rx_b) = tokio::sync::mpsc::channel(64);
641 let stream_a = Self {
642 addr: addr_b.clone(),
643 send_mtu: mtu,
644 recv_mtu: mtu,
645 tx: tx_a,
646 rx: tokio::sync::Mutex::new(rx_b),
647 };
648 let stream_b = Self {
649 addr: addr_a,
650 send_mtu: mtu,
651 recv_mtu: mtu,
652 tx: tx_b,
653 rx: tokio::sync::Mutex::new(rx_a),
654 };
655 (stream_a, stream_b)
656 }
657}
658
659impl BleStream for MockBleStream {
660 async fn send(&self, data: &[u8]) -> Result<(), TransportError> {
661 self.tx
662 .send(data.to_vec())
663 .await
664 .map_err(|_| TransportError::SendFailed("channel closed".into()))
665 }
666
667 async fn recv(&self, buf: &mut [u8]) -> Result<usize, TransportError> {
668 let mut rx = self.rx.lock().await;
669 match rx.recv().await {
670 Some(data) => {
671 let len = data.len().min(buf.len());
672 buf[..len].copy_from_slice(&data[..len]);
673 Ok(len)
674 }
675 None => Ok(0), }
677 }
678
679 fn send_mtu(&self) -> u16 {
680 self.send_mtu
681 }
682
683 fn recv_mtu(&self) -> u16 {
684 self.recv_mtu
685 }
686
687 fn remote_addr(&self) -> &BleAddr {
688 &self.addr
689 }
690}
691
692pub struct MockBleAcceptor {
694 rx: tokio::sync::mpsc::Receiver<MockBleStream>,
695 psm: u16,
696}
697
698impl BleAcceptor for MockBleAcceptor {
699 type Stream = MockBleStream;
700
701 async fn accept(&mut self) -> Result<MockBleStream, TransportError> {
702 self.rx
703 .recv()
704 .await
705 .ok_or(TransportError::RecvFailed("acceptor channel closed".into()))
706 }
707
708 fn psm(&self) -> u16 {
709 self.psm
710 }
711}
712
713pub struct MockBleScanner {
715 rx: tokio::sync::mpsc::Receiver<BleCandidate>,
716}
717
718impl BleScanner for MockBleScanner {
719 async fn next(&mut self) -> Option<BleCandidate> {
720 self.rx.recv().await
721 }
722}
723
724type ConnectHandler =
726 Box<dyn Fn(&BleAddr, u16) -> Result<MockBleStream, TransportError> + Send + Sync>;
727
728pub struct MockBleIo {
733 adapter: String,
734 local_addr: BleAddr,
735 accept_tx: tokio::sync::mpsc::Sender<MockBleStream>,
736 accept_rx: std::sync::Mutex<Option<tokio::sync::mpsc::Receiver<MockBleStream>>>,
737 scan_tx: tokio::sync::mpsc::Sender<BleCandidate>,
738 scan_rx: std::sync::Mutex<Option<tokio::sync::mpsc::Receiver<BleCandidate>>>,
739 connect_handler: std::sync::Mutex<Option<ConnectHandler>>,
740 assigned_psm: u16,
741 advertised_bootstrap: std::sync::Mutex<Option<BleBootstrap>>,
742}
743
744impl MockBleIo {
745 pub fn new(adapter: &str, local_addr: BleAddr) -> Self {
747 let (accept_tx, accept_rx) = tokio::sync::mpsc::channel(16);
748 let (scan_tx, scan_rx) = tokio::sync::mpsc::channel(64);
749 Self {
750 adapter: adapter.to_string(),
751 local_addr,
752 accept_tx,
753 accept_rx: std::sync::Mutex::new(Some(accept_rx)),
754 scan_tx,
755 scan_rx: std::sync::Mutex::new(Some(scan_rx)),
756 connect_handler: std::sync::Mutex::new(None),
757 assigned_psm: DEFAULT_PSM,
758 advertised_bootstrap: std::sync::Mutex::new(None),
759 }
760 }
761
762 pub fn with_listener_psm(mut self, psm: u16) -> Self {
764 self.assigned_psm = psm;
765 self
766 }
767
768 pub async fn inject_inbound(&self, stream: MockBleStream) {
770 let _ = self.accept_tx.send(stream).await;
771 }
772
773 pub async fn inject_scan_result(&self, addr: BleAddr) {
775 let _ = self
776 .scan_tx
777 .send(BleCandidate {
778 addr,
779 bootstrap: BleBootstrap::new(DEFAULT_PSM, 2048)
780 .expect("default mock bootstrap is valid"),
781 })
782 .await;
783 }
784
785 pub async fn inject_scan_candidate(&self, candidate: BleCandidate) {
787 let _ = self.scan_tx.send(candidate).await;
788 }
789
790 pub fn advertised_bootstrap(&self) -> Option<BleBootstrap> {
792 *self
793 .advertised_bootstrap
794 .lock()
795 .unwrap_or_else(|error| error.into_inner())
796 }
797
798 pub fn set_connect_handler<F>(&self, handler: F)
800 where
801 F: Fn(&BleAddr, u16) -> Result<MockBleStream, TransportError> + Send + Sync + 'static,
802 {
803 *self
804 .connect_handler
805 .lock()
806 .unwrap_or_else(|e| e.into_inner()) = Some(Box::new(handler));
807 }
808}
809
810impl BleIo for MockBleIo {
811 type Stream = MockBleStream;
812 type Acceptor = MockBleAcceptor;
813 type Scanner = MockBleScanner;
814
815 async fn listen(&self, _psm: u16) -> Result<Self::Acceptor, TransportError> {
816 let rx = self
817 .accept_rx
818 .lock()
819 .unwrap_or_else(|e| e.into_inner())
820 .take()
821 .ok_or_else(|| TransportError::NotSupported("acceptor already taken".into()))?;
822 Ok(MockBleAcceptor {
823 rx,
824 psm: self.assigned_psm,
825 })
826 }
827
828 async fn connect(&self, addr: &BleAddr, psm: u16) -> Result<Self::Stream, TransportError> {
829 let handler = self
830 .connect_handler
831 .lock()
832 .unwrap_or_else(|e| e.into_inner());
833 match handler.as_ref() {
834 Some(f) => f(addr, psm),
835 None => Err(TransportError::ConnectionRefused),
836 }
837 }
838
839 async fn start_advertising(&self, bootstrap: BleBootstrap) -> Result<(), TransportError> {
840 *self
841 .advertised_bootstrap
842 .lock()
843 .unwrap_or_else(|error| error.into_inner()) = Some(bootstrap);
844 Ok(())
845 }
846
847 async fn stop_advertising(&self) -> Result<(), TransportError> {
848 Ok(())
849 }
850
851 async fn start_scanning(&self) -> Result<Self::Scanner, TransportError> {
852 let rx = self
853 .scan_rx
854 .lock()
855 .unwrap_or_else(|e| e.into_inner())
856 .take()
857 .ok_or_else(|| TransportError::NotSupported("scanner already taken".into()))?;
858 Ok(MockBleScanner { rx })
859 }
860
861 fn local_addr(&self) -> Result<BleAddr, TransportError> {
862 Ok(self.local_addr.clone())
863 }
864
865 fn adapter_name(&self) -> &str {
866 &self.adapter
867 }
868}
869
870#[cfg(test)]
871mod tests;