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