Skip to main content

bluetooth_rust/
linux.rs

1//! Linux specific bluetooth code
2
3use std::collections::{BTreeMap, HashMap};
4
5use bluer::AdapterEvent;
6use futures::FutureExt;
7use futures::StreamExt;
8
9// ────────────────────────────────────────────────────────────────────────────
10// BluetoothRfcommConnectableAsyncTrait for bluer::rfcomm::ConnectRequest
11// ────────────────────────────────────────────────────────────────────────────
12
13#[async_trait::async_trait]
14impl super::BluetoothRfcommConnectableAsyncTrait for bluer::rfcomm::ConnectRequest {
15    async fn accept(self) -> Result<crate::BluetoothStream, String> {
16        bluer::rfcomm::ConnectRequest::accept(self)
17            .map(|a| crate::BluetoothStream::Bluez(Box::pin(a)))
18            .map_err(|e| e.to_string())
19    }
20}
21
22// ────────────────────────────────────────────────────────────────────────────
23// BluetoothRfcommProfileAsyncTrait for bluer::rfcomm::ProfileHandle
24// ────────────────────────────────────────────────────────────────────────────
25
26impl super::BluetoothRfcommProfileAsyncTrait for bluer::rfcomm::ProfileHandle {
27    async fn connectable(&mut self) -> Result<crate::BluetoothRfcommConnectableAsync, String> {
28        self.next()
29            .await
30            .map(|a| crate::BluetoothRfcommConnectableAsync::Bluez(a))
31            .ok_or_else(|| "Failed to get bluetooth connection".to_string())
32    }
33}
34
35// ────────────────────────────────────────────────────────────────────────────
36// Internal active-connection holder (RFCOMM or L2CAP stream)
37// ────────────────────────────────────────────────────────────────────────────
38
39/// Holds the active stream for either an RFCOMM or L2CAP connection.
40enum BluetoothConnection {
41    /// An active RFCOMM stream
42    Rfcomm(bluer::rfcomm::Stream),
43    /// An active L2CAP stream
44    L2cap(bluer::l2cap::Stream),
45}
46
47impl std::io::Read for BluetoothConnection {
48    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
49        tokio::task::block_in_place(|| {
50            tokio::runtime::Handle::current().block_on(async {
51                use tokio::io::AsyncReadExt;
52                match self {
53                    BluetoothConnection::Rfcomm(s) => s.read(buf).await,
54                    BluetoothConnection::L2cap(s) => s.read(buf).await,
55                }
56            })
57        })
58    }
59}
60
61impl std::io::Write for BluetoothConnection {
62    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
63        tokio::task::block_in_place(|| {
64            tokio::runtime::Handle::current().block_on(async {
65                use tokio::io::AsyncWriteExt;
66                match self {
67                    BluetoothConnection::Rfcomm(s) => s.write(buf).await,
68                    BluetoothConnection::L2cap(s) => s.write(buf).await,
69                }
70            })
71        })
72    }
73
74    fn flush(&mut self) -> std::io::Result<()> {
75        tokio::task::block_in_place(|| {
76            tokio::runtime::Handle::current().block_on(async {
77                use tokio::io::AsyncWriteExt;
78                match self {
79                    BluetoothConnection::Rfcomm(s) => s.flush().await,
80                    BluetoothConnection::L2cap(s) => s.flush().await,
81                }
82            })
83        })
84    }
85}
86
87// ────────────────────────────────────────────────────────────────────────────
88// BluetoothRfcommSocket – used for both RFCOMM and L2CAP outgoing connections
89// ────────────────────────────────────────────────────────────────────────────
90
91/// An outgoing bluetooth socket that may carry either an RFCOMM or L2CAP
92/// connection.  The socket is created lazily; call `connect()` before doing
93/// any I/O.
94pub struct BluetoothRfcommSocket {
95    /// Address of the remote device
96    device_addr: bluer::Address,
97    /// RFCOMM channel to connect on (mutually exclusive with `l2cap_psm`).
98    ///
99    /// BlueZ does not expose a direct SDP query API through the bluer crate, so
100    /// callers are responsible for resolving the correct channel via
101    /// `run_sdp` / `get_uuids` before creating the socket, or by relying on
102    /// a well-known channel number for the service.  Channel 1 is used as a
103    /// default placeholder when the channel is not otherwise known.
104    rfcomm_channel: Option<u8>,
105    /// L2CAP PSM to connect on (mutually exclusive with `rfcomm_channel`).
106    l2cap_psm: Option<u16>,
107    /// Whether to request an encrypted / authenticated link
108    is_secure: bool,
109    /// The live connection, present after a successful `connect()` call
110    connection: Option<BluetoothConnection>,
111}
112
113impl BluetoothRfcommSocket {
114    /// Create a new (unconnected) RFCOMM socket.
115    fn new_rfcomm(device_addr: bluer::Address, channel: u8, is_secure: bool) -> Self {
116        Self {
117            device_addr,
118            rfcomm_channel: Some(channel),
119            l2cap_psm: None,
120            is_secure,
121            connection: None,
122        }
123    }
124
125    /// Create a new (unconnected) L2CAP socket.
126    fn new_l2cap(device_addr: bluer::Address, psm: u16, is_secure: bool) -> Self {
127        Self {
128            device_addr,
129            rfcomm_channel: None,
130            l2cap_psm: Some(psm),
131            is_secure,
132            connection: None,
133        }
134    }
135}
136
137impl crate::BluetoothSocketTrait for &mut BluetoothRfcommSocket {
138    fn is_connected(&self) -> Result<bool, std::io::Error> {
139        Ok(self.connection.is_some())
140    }
141
142    fn connect(&mut self) -> Result<(), std::io::Error> {
143        if self.connection.is_some() {
144            return Ok(());
145        }
146        tokio::task::block_in_place(|| {
147            tokio::runtime::Handle::current().block_on(async {
148                if let Some(channel) = self.rfcomm_channel {
149                    let addr = bluer::rfcomm::SocketAddr::new(self.device_addr, channel);
150                    let socket = bluer::rfcomm::Socket::new()
151                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
152                    if self.is_secure {
153                        socket
154                            .set_security(bluer::rfcomm::Security {
155                                level: bluer::rfcomm::SecurityLevel::Medium,
156                                key_size: 0,
157                            })
158                            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
159                    }
160                    let stream = socket
161                        .connect(addr)
162                        .await
163                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
164                    self.connection = Some(BluetoothConnection::Rfcomm(stream));
165                } else if let Some(psm) = self.l2cap_psm {
166                    let addr = bluer::l2cap::SocketAddr::new(
167                        self.device_addr,
168                        bluer::AddressType::BrEdr,
169                        psm,
170                    );
171                    let socket = bluer::l2cap::Socket::<bluer::l2cap::Stream>::new_stream()
172                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
173                    if self.is_secure {
174                        socket
175                            .set_security(bluer::l2cap::Security {
176                                level: bluer::l2cap::SecurityLevel::Medium,
177                                key_size: 0,
178                            })
179                            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
180                    }
181                    let stream = socket
182                        .connect(addr)
183                        .await
184                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
185                    self.connection = Some(BluetoothConnection::L2cap(stream));
186                } else {
187                    return Err(std::io::Error::new(
188                        std::io::ErrorKind::InvalidInput,
189                        "BluetoothRfcommSocket has neither an RFCOMM channel nor an L2CAP PSM configured",
190                    ));
191                }
192                Ok(())
193            })
194        })
195    }
196}
197
198impl std::io::Read for BluetoothRfcommSocket {
199    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
200        match &mut self.connection {
201            Some(conn) => conn.read(buf),
202            None => Err(std::io::Error::from(std::io::ErrorKind::NotConnected)),
203        }
204    }
205}
206
207impl std::io::Write for BluetoothRfcommSocket {
208    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
209        match &mut self.connection {
210            Some(conn) => conn.write(buf),
211            None => Err(std::io::Error::from(std::io::ErrorKind::NotConnected)),
212        }
213    }
214
215    fn flush(&mut self) -> std::io::Result<()> {
216        match &mut self.connection {
217            Some(conn) => conn.flush(),
218            None => Err(std::io::Error::from(std::io::ErrorKind::NotConnected)),
219        }
220    }
221}
222
223// ────────────────────────────────────────────────────────────────────────────
224// LinuxBluetoothDevice – wraps bluer::Device and owns its open sockets
225// ────────────────────────────────────────────────────────────────────────────
226
227/// A Linux bluetooth device backed by bluer.  Wraps a `bluer::Device` and
228/// stores open RFCOMM / L2CAP sockets so that returned `BluetoothSocket`
229/// references remain valid for the lifetime of this struct.
230pub struct LinuxBluetoothDevice {
231    /// The underlying bluer device handle
232    device: bluer::Device,
233    /// Open sockets keyed by the UUID string of the service they connect to
234    sockets: BTreeMap<String, BluetoothRfcommSocket>,
235}
236
237impl LinuxBluetoothDevice {
238    /// Wrap a `bluer::Device`.
239    pub fn new(device: bluer::Device) -> Self {
240        Self {
241            device,
242            sockets: BTreeMap::new(),
243        }
244    }
245}
246
247impl super::BluetoothDeviceTrait for LinuxBluetoothDevice {
248    /// Trigger service-discovery for this device.
249    ///
250    /// BlueZ resolves SDP service records automatically when a device is
251    /// connected.  Calling this method ensures the device is connected so that
252    /// `get_uuids` returns fresh data.  Any connection errors are silently
253    /// ignored because the device may already be connected.
254    fn run_sdp(&mut self) {
255        let device = self.device.clone();
256        let _ = tokio::task::block_in_place(|| {
257            tokio::runtime::Handle::current().block_on(async move { device.connect().await })
258        });
259    }
260
261    fn get_uuids(&mut self) -> Result<Vec<crate::BluetoothUuid>, std::io::Error> {
262        tokio::task::block_in_place(|| {
263            tokio::runtime::Handle::current().block_on(async {
264                let uuids =
265                    self.device.uuids().await.map_err(|e| {
266                        std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
267                    })?;
268                Ok(uuids
269                    .unwrap_or_default()
270                    .into_iter()
271                    .map(|u| {
272                        use std::str::FromStr;
273                        crate::BluetoothUuid::from_str(&u.to_string())
274                            .unwrap_or_else(|_| crate::BluetoothUuid::Unknown(u.to_string()))
275                    })
276                    .collect())
277            })
278        })
279    }
280
281    /// Returns the alias (display name) of the device, falling back to the
282    /// hardware name when no alias is set.
283    fn get_name(&self) -> Result<String, std::io::Error> {
284        let device = self.device.clone();
285        tokio::task::block_in_place(|| {
286            tokio::runtime::Handle::current().block_on(async move {
287                device
288                    .alias()
289                    .await
290                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
291            })
292        })
293    }
294
295    fn get_address(&mut self) -> Result<String, std::io::Error> {
296        Ok(self.device.address().to_string())
297    }
298
299    fn get_pair_state(&self) -> Result<crate::PairingStatus, std::io::Error> {
300        let device = self.device.clone();
301        tokio::task::block_in_place(|| {
302            tokio::runtime::Handle::current().block_on(async move {
303                let paired = device
304                    .is_paired()
305                    .await
306                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
307                Ok(if paired {
308                    crate::PairingStatus::Paired
309                } else {
310                    crate::PairingStatus::NotPaired
311                })
312            })
313        })
314    }
315
316    /// Return a socket suitable for an outgoing L2CAP connection to the given
317    /// service UUID.
318    ///
319    /// The PSM for the service must be known in advance.  Because bluer does
320    /// not expose a raw SDP query API, a proper PSM lookup is left as a
321    /// `// TODO` below; the default PSM of 1 (SDP channel itself) is a
322    /// placeholder and will need to be replaced with the actual dynamic PSM
323    /// discovered via SDP for real services.
324    fn get_l2cap_socket(
325        &mut self,
326        uuid: crate::BluetoothUuid,
327        is_secure: bool,
328    ) -> Result<crate::BluetoothSocket<'_>, String> {
329        let uuid_str = uuid.as_str().to_string();
330        if !self.sockets.contains_key(&uuid_str) {
331            // TODO: perform a proper SDP query to resolve the PSM for `uuid`.
332            // For now a PSM of 1 is used as a placeholder.
333            let psm: u16 = 1;
334            let addr = self.device.address();
335            let socket = BluetoothRfcommSocket::new_l2cap(addr, psm, is_secure);
336            self.sockets.insert(uuid_str.clone(), socket);
337        }
338        self.sockets
339            .get_mut(&uuid_str)
340            .map(|s| crate::BluetoothSocket::Bluez(s))
341            .ok_or_else(|| "Socket does not exist".to_string())
342    }
343
344    /// Return a socket suitable for an outgoing RFCOMM connection to the given
345    /// service UUID.
346    ///
347    /// The RFCOMM channel for the service must be known in advance.  Because
348    /// bluer does not expose a raw SDP query API, a proper channel lookup is
349    /// left as a `// TODO` below; channel 1 is used as a placeholder and will
350    /// need to be replaced with the channel discovered via SDP for real
351    /// services (e.g. by using `sdptool` or an external SDP library).
352    fn get_rfcomm_socket(
353        &mut self,
354        uuid: crate::BluetoothUuid,
355        is_secure: bool,
356    ) -> Result<crate::BluetoothSocket<'_>, String> {
357        let uuid_str = uuid.as_str().to_string();
358        if !self.sockets.contains_key(&uuid_str) {
359            // TODO: perform a proper SDP query to resolve the RFCOMM channel
360            // for `uuid`.  Channel 1 is used here as a placeholder.
361            let channel: u8 = 1;
362            let addr = self.device.address();
363            let socket = BluetoothRfcommSocket::new_rfcomm(addr, channel, is_secure);
364            self.sockets.insert(uuid_str.clone(), socket);
365        }
366        self.sockets
367            .get_mut(&uuid_str)
368            .map(|s| crate::BluetoothSocket::Bluez(s))
369            .ok_or_else(|| "Socket does not exist".to_string())
370    }
371}
372
373// ────────────────────────────────────────────────────────────────────────────
374// BluetoothDiscovery
375// ────────────────────────────────────────────────────────────────────────────
376
377/// A struct for managing discovery of bluetooth devices
378pub struct BluetoothDiscovery {}
379
380impl BluetoothDiscovery {
381    /// Construct a new self
382    fn new() -> Self {
383        Self {}
384    }
385}
386
387impl super::BluetoothDiscoveryTrait for BluetoothDiscovery {}
388
389impl Drop for BluetoothDiscovery {
390    fn drop(&mut self) {}
391}
392
393// ────────────────────────────────────────────────────────────────────────────
394// TryFrom conversions for profile settings → bluer::rfcomm::Profile
395// ────────────────────────────────────────────────────────────────────────────
396
397impl TryFrom<super::BluetoothRfcommProfileSettings> for bluer::rfcomm::Profile {
398    type Error = String;
399    fn try_from(value: super::BluetoothRfcommProfileSettings) -> Result<Self, Self::Error> {
400        let service = if let Some(v) = value.service_uuid {
401            Some(bluer::Uuid::parse_str(&v).map_err(|e| e.to_string())?)
402        } else {
403            None
404        };
405        Ok(Self {
406            uuid: bluer::Uuid::parse_str(&value.uuid).map_err(|e| e.to_string())?,
407            name: value.name,
408            service,
409            role: None,
410            channel: value.channel,
411            psm: value.psm,
412            require_authentication: value.authenticate,
413            require_authorization: value.authorize,
414            auto_connect: value.auto_connect,
415            service_record: value.sdp_record,
416            version: value.sdp_version,
417            features: value.sdp_features,
418            ..Default::default()
419        })
420    }
421}
422
423/// L2CAP profiles are registered through the same BlueZ D-Bus
424/// ProfileManager1 mechanism as RFCOMM profiles, but with the `psm` field
425/// set instead of `channel`.
426impl TryFrom<super::BluetoothL2capProfileSettings> for bluer::rfcomm::Profile {
427    type Error = String;
428    fn try_from(value: super::BluetoothL2capProfileSettings) -> Result<Self, Self::Error> {
429        let service = if let Some(v) = value.service_uuid {
430            Some(bluer::Uuid::parse_str(&v).map_err(|e| e.to_string())?)
431        } else {
432            None
433        };
434        Ok(Self {
435            uuid: bluer::Uuid::parse_str(&value.uuid).map_err(|e| e.to_string())?,
436            name: value.name,
437            service,
438            role: None,
439            channel: None,
440            psm: value.psm,
441            require_authentication: value.authenticate,
442            require_authorization: value.authorize,
443            auto_connect: value.auto_connect,
444            service_record: value.sdp_record,
445            version: value.sdp_version,
446            features: value.sdp_features,
447            ..Default::default()
448        })
449    }
450}
451
452// ────────────────────────────────────────────────────────────────────────────
453// BluetoothHandler – main adapter / session manager
454// ────────────────────────────────────────────────────────────────────────────
455
456/// The general bluetooth handler for the library. There should be only one per application on linux.
457pub struct BluetoothHandler {
458    /// The current bluetooth session
459    session: bluer::Session,
460    /// The list of bluetooth adapters for the system
461    adapters: Vec<bluer::Adapter>,
462    /// The agent for the handler
463    _blue_agent_handle: bluer::agent::AgentHandle,
464}
465
466impl super::BluetoothAdapterTrait for BluetoothHandler {
467    fn supports_async(&self) -> Option<&dyn super::AsyncBluetoothAdapterTrait> {
468        Some(self)
469    }
470
471    fn supports_sync(&self) -> Option<&dyn super::SyncBluetoothAdapterTrait> {
472        None
473    }
474}
475
476#[async_trait::async_trait]
477impl super::AsyncBluetoothAdapterTrait for BluetoothHandler {
478    async fn register_rfcomm_profile(
479        &self,
480        settings: super::BluetoothRfcommProfileSettings,
481    ) -> Result<crate::BluetoothRfcommProfileAsync, String> {
482        self.session
483            .register_profile(settings.try_into()?)
484            .await
485            .map(|a| super::BluetoothRfcommProfileAsync::Bluez(a.into()))
486            .map_err(|e| e.to_string())
487    }
488
489    /// Register an L2CAP profile with BlueZ.
490    ///
491    /// BlueZ exposes a single `RegisterProfile` D-Bus method (ProfileManager1)
492    /// that handles both RFCOMM and L2CAP profiles.  The `psm` field in the
493    /// settings selects L2CAP; the RFCOMM `channel` field is left as `None`.
494    async fn register_l2cap_profile(
495        &self,
496        settings: super::BluetoothL2capProfileSettings,
497    ) -> Result<crate::BluetoothL2capProfileAsync, String> {
498        self.session
499            .register_profile(settings.try_into()?)
500            .await
501            .map(|a| super::BluetoothL2capProfileAsync::Bluez(a.into()))
502            .map_err(|e| e.to_string())
503    }
504
505    fn start_discovery(&self) -> crate::BluetoothDiscovery {
506        BluetoothDiscovery::new().into()
507    }
508
509    /// Return all paired devices across every adapter.
510    fn get_paired_devices(&self) -> Option<Vec<crate::BluetoothDevice>> {
511        let mut list = Vec::new();
512        for adapter in &self.adapters {
513            let result = tokio::task::block_in_place(|| {
514                tokio::runtime::Handle::current().block_on(async {
515                    let addrs = adapter.device_addresses().await?;
516                    let mut paired = Vec::new();
517                    for addr in addrs {
518                        if let Ok(dev) = adapter.device(addr) {
519                            if dev.is_paired().await.unwrap_or(false) {
520                                paired.push(dev);
521                            }
522                        }
523                    }
524                    Ok::<Vec<bluer::Device>, bluer::Error>(paired)
525                })
526            });
527            if let Ok(devices) = result {
528                for dev in devices {
529                    list.push(crate::BluetoothDevice::Bluez(LinuxBluetoothDevice::new(
530                        dev,
531                    )));
532                }
533            }
534        }
535        Some(list)
536    }
537
538    async fn addresses(&self) -> Vec<super::BluetoothAdapterAddress> {
539        let mut a = Vec::new();
540        for adapter in &self.adapters {
541            if let Ok(adr) = adapter.address().await {
542                a.push(super::BluetoothAdapterAddress::Byte(adr.0));
543            }
544        }
545        a
546    }
547
548    async fn set_discoverable(&self, d: bool) -> Result<(), ()> {
549        for adapter in &self.adapters {
550            adapter.set_discoverable(d).await.map_err(|_| ())?;
551        }
552        Ok(())
553    }
554}
555
556impl BluetoothHandler {
557    /// Retrieve the bluetooth addresses for all bluetooth adapters present
558    pub async fn addresses(&self) -> Vec<bluer::Address> {
559        let mut addrs = Vec::new();
560        for a in &self.adapters {
561            if let Ok(addr) = a.address().await {
562                addrs.push(addr);
563            }
564        }
565        addrs
566    }
567
568    /// Construct a new self
569    pub async fn new(
570        s: tokio::sync::mpsc::Sender<super::MessageToBluetoothHost>,
571    ) -> Result<Self, String> {
572        let session = bluer::Session::new().await.map_err(|e| e.to_string())?;
573
574        let adapter_names = session.adapter_names().await.map_err(|e| e.to_string())?;
575        let adapters: Vec<bluer::Adapter> = adapter_names
576            .iter()
577            .filter_map(|n| session.adapter(n).ok())
578            .collect();
579
580        let blue_agent = Self::build_agent(s);
581        let blue_agent_handle = session.register_agent(blue_agent).await;
582        println!("Registered a bluetooth agent {}", blue_agent_handle.is_ok());
583        Ok(Self {
584            session,
585            adapters,
586            _blue_agent_handle: blue_agent_handle.map_err(|e| e.to_string())?,
587        })
588    }
589
590    /// Enable all bluetooth adapters
591    async fn enable(&mut self) {
592        for adapter in &self.adapters {
593            adapter.set_powered(true).await.unwrap();
594            adapter.set_pairable(true).await.unwrap();
595        }
596    }
597
598    /// Disable all bluetooth adapters
599    async fn disable(&mut self) {
600        self.set_discoverable(false).await;
601        for adapter in &self.adapters {
602            adapter.set_powered(false).await.unwrap();
603            adapter.set_pairable(false).await.unwrap();
604        }
605    }
606
607    /// Enable or disable discoverable mode on all bluetooth adapters
608    pub async fn set_discoverable(&mut self, d: bool) {
609        for adapter in &self.adapters {
610            adapter.set_discoverable(d).await.unwrap();
611        }
612    }
613
614    /// Register an RFCOMM profile with the bluetooth session
615    pub async fn register_rfcomm_profile(
616        &mut self,
617        profile: bluer::rfcomm::Profile,
618    ) -> Result<bluer::rfcomm::ProfileHandle, bluer::Error> {
619        self.session.register_profile(profile).await
620    }
621
622    /// Build a bluetooth agent for the handler
623    fn build_agent(
624        s: tokio::sync::mpsc::Sender<super::MessageToBluetoothHost>,
625    ) -> bluer::agent::Agent {
626        let mut blue_agent = bluer::agent::Agent::default();
627        blue_agent.request_default = true;
628        blue_agent.request_pin_code = None;
629        blue_agent.request_passkey = None;
630        let s2 = s.clone();
631        blue_agent.display_passkey = Some(Box::new(move |mut a| {
632            println!("Running process for display_passkey: {:?}", a);
633            let s3 = s2.clone();
634            async move {
635                let mut chan = tokio::sync::mpsc::channel(5);
636                let _ = s3
637                    .send(super::MessageToBluetoothHost::DisplayPasskey(a.passkey, chan.0))
638                    .await;
639                loop {
640                    let f = tokio::time::timeout(std::time::Duration::from_secs(5), chan.1.recv());
641                    tokio::select! {
642                        asdf = f => {
643                            match asdf {
644                                Ok(Some(m)) => match m {
645                                    super::ResponseToPasskey::Yes => {
646                                        let _ = s3
647                                            .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
648                                            .await;
649                                        return Ok(());
650                                    }
651                                    super::ResponseToPasskey::No => {
652                                        let _ = s3
653                                            .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
654                                            .await;
655                                        return Err(bluer::agent::ReqError::Rejected);
656                                    }
657                                    super::ResponseToPasskey::Cancel => {
658                                        let _ = s3
659                                            .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
660                                            .await;
661                                        return Err(bluer::agent::ReqError::Canceled);
662                                    }
663                                    super::ResponseToPasskey::Waiting => {}
664                                },
665                                Ok(None) => {}
666                                _ => {
667                                    let _ = s3
668                                        .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
669                                        .await;
670                                    return Err(bluer::agent::ReqError::Canceled);
671                                }
672                            }
673                        }
674                        _ = &mut a.cancel => {
675                            let _ = s3
676                                .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
677                                .await;
678                            break Err(bluer::agent::ReqError::Canceled);
679                        }
680                    }
681                }
682            }
683            .boxed()
684        }));
685        blue_agent.display_pin_code = Some(Box::new(|a| {
686            async move {
687                println!("Need to display pin code {:?}", a);
688                a.cancel.await.unwrap();
689                Ok(())
690            }
691            .boxed()
692        }));
693        let s2 = s.clone();
694        blue_agent.request_confirmation = Some(Box::new(move |a| {
695            println!("Need to confirm {:?}", a);
696            let s3 = s2.clone();
697            async move {
698                let mut chan = tokio::sync::mpsc::channel(5);
699                let _ = s3
700                    .send(super::MessageToBluetoothHost::ConfirmPasskey(
701                        a.passkey, chan.0,
702                    ))
703                    .await;
704                loop {
705                    let f = tokio::time::timeout(std::time::Duration::from_secs(5), chan.1.recv());
706                    let asdf = f.await;
707                    match asdf {
708                        Ok(Some(m)) => match m {
709                            super::ResponseToPasskey::Yes => {
710                                let _ = s3
711                                    .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
712                                    .await;
713                                return Ok(());
714                            }
715                            super::ResponseToPasskey::No => {
716                                let _ = s3
717                                    .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
718                                    .await;
719                                return Err(bluer::agent::ReqError::Rejected);
720                            }
721                            super::ResponseToPasskey::Cancel => {
722                                let _ = s3
723                                    .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
724                                    .await;
725                                return Err(bluer::agent::ReqError::Canceled);
726                            }
727                            super::ResponseToPasskey::Waiting => {}
728                        },
729                        Ok(None) => {}
730                        _ => {
731                            let _ = s3
732                                .send(super::MessageToBluetoothHost::CancelDisplayPasskey)
733                                .await;
734                            return Err(bluer::agent::ReqError::Canceled);
735                        }
736                    }
737                }
738            }
739            .boxed()
740        }));
741        blue_agent.request_authorization = Some(Box::new(|a| {
742            async move {
743                println!("Need to authorize {:?}", a);
744                Ok(())
745            }
746            .boxed()
747        }));
748        blue_agent.authorize_service = Some(Box::new(|a| {
749            async move {
750                println!("Need to authorize service {:?}", a);
751                Ok(())
752            }
753            .boxed()
754        }));
755        blue_agent
756    }
757
758    /// Issues the specified bluetooth command, with an optional response for the command
759    pub async fn issue_command(
760        &mut self,
761        cmd: super::BluetoothCommand,
762    ) -> Option<super::BluetoothResponse> {
763        match cmd {
764            super::BluetoothCommand::QueryNumAdapters => {
765                Some(super::BluetoothResponse::Adapters(self.adapters.len()))
766            }
767            _ => None,
768        }
769    }
770
771    /// Run a scan on all the bluetooth adapters, updating `bluetooth_devices`
772    /// with newly discovered or removed devices.
773    pub async fn scan<'a>(
774        &'a mut self,
775        bluetooth_devices: &mut HashMap<
776            bluer::Address,
777            (&'a bluer::Adapter, Option<bluer::Device>),
778        >,
779    ) {
780        let mut adapter_scanner = Vec::new();
781        for a in &self.adapters {
782            let da = a.discover_devices_with_changes().await.unwrap();
783            adapter_scanner.push((a, da));
784        }
785
786        for (adapt, da) in &mut adapter_scanner {
787            if let Some(e) = da.next().await {
788                match e {
789                    AdapterEvent::DeviceAdded(addr) => {
790                        log::debug!("Device added {:?}", addr);
791                        bluetooth_devices.insert(addr, (adapt, None));
792                    }
793                    AdapterEvent::DeviceRemoved(addr) => {
794                        log::debug!("Device removed {:?}", addr);
795                        bluetooth_devices.remove_entry(&addr);
796                    }
797                    AdapterEvent::PropertyChanged(prop) => {
798                        log::debug!("Adapter property changed {:?}", prop);
799                    }
800                }
801            }
802        }
803
804        for (addr, (adapter, dev)) in bluetooth_devices.iter_mut() {
805            if dev.is_none() {
806                if let Ok(d) = adapter.device(*addr) {
807                    if let Ok(ps) = d.all_properties().await {
808                        for p in ps {
809                            log::debug!("Device {:?} property: {:?}", addr, p);
810                        }
811                    }
812                    *dev = Some(d);
813                }
814            }
815        }
816    }
817}