Skip to main content

bluetooth_rust/
lib.rs

1#![deny(missing_docs)]
2#![deny(clippy::missing_docs_in_private_items)]
3#![warn(unused_extern_crates)]
4
5//! This library is intended to eventually be a cross-platform bluetooth handling platform
6//! Android portions adapted from <https://github.com/wuwbobo2021/android-bluetooth-serial-rs>
7
8#[cfg(target_os = "android")]
9use std::sync::Arc;
10#[cfg(target_os = "android")]
11use std::sync::Mutex;
12#[cfg(target_os = "android")]
13mod android;
14#[cfg(target_os = "android")]
15pub use android::Bluetooth;
16#[cfg(target_os = "android")]
17pub use android::Java;
18#[cfg(target_os = "android")]
19use winit::platform::android::activity::AndroidApp;
20
21#[cfg(target_os = "linux")]
22mod linux;
23
24#[cfg(target_os = "windows")]
25mod windows;
26
27mod bluetooth_uuid;
28pub use bluetooth_uuid::BluetoothUuid;
29
30/// Commands issued to the library
31#[derive(Debug, serde::Deserialize, serde::Serialize)]
32pub enum BluetoothCommand {
33    /// Detect all bluetooth adapters present on the system
34    DetectAdapters,
35    /// Find out how many bluetooth adapters are detected
36    QueryNumAdapters,
37}
38
39/// Messages that can be sent specifically to the app user hosting the bluetooth controls
40pub enum MessageToBluetoothHost {
41    /// The passkey used for pairing devices
42    DisplayPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
43    /// The passkey to confirm for pairing
44    ConfirmPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
45    /// Cancal the passkey display
46    CancelDisplayPasskey,
47}
48
49#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
50/// Messages that are send directly from the bluetooth host
51pub enum MessageFromBluetoothHost {
52    /// A response about the active pairing passkey
53    PasskeyMessage(ResponseToPasskey),
54}
55
56#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
57/// The user response to a bluetooth passkey
58pub enum ResponseToPasskey {
59    /// The passkey is accepted
60    Yes,
61    /// The passkey is not accepted
62    No,
63    /// The process is canceled by the user
64    Cancel,
65    /// Waiting on the user to decide
66    Waiting,
67}
68
69/// Responses issued by the library
70pub enum BluetoothResponse {
71    /// The number of bluetooth adapters detected
72    Adapters(usize),
73}
74
75/// Settings for an rfcomm profile
76#[derive(Clone)]
77pub struct BluetoothRfcommProfileSettings {
78    /// The uuid for the profile
79    pub uuid: String,
80    /// User readable name for the profile
81    pub name: Option<String>,
82    /// The service uuid for the profile (can be the same as service)
83    pub service_uuid: Option<String>,
84    /// The channel to use
85    pub channel: Option<u16>,
86    /// PSM number used for UUIDS and SDP (if applicable)
87    pub psm: Option<u16>,
88    /// Is authentication required for a connection
89    pub authenticate: Option<bool>,
90    /// Is authorization required for a connection
91    pub authorize: Option<bool>,
92    /// For client profiles, This will force connection of the channel when a remote device is connected
93    pub auto_connect: Option<bool>,
94    /// manual SDP record
95    pub sdp_record: Option<String>,
96    /// SDP version
97    pub sdp_version: Option<u16>,
98    /// SDP profile features
99    pub sdp_features: Option<u16>,
100}
101
102/// Settings for an rfcomm profile
103#[derive(Clone)]
104pub struct BluetoothL2capProfileSettings {
105    /// The uuid for the profile
106    pub uuid: String,
107    /// User readable name for the profile
108    pub name: Option<String>,
109    /// The service uuid for the profile (can be the same as service)
110    pub service_uuid: Option<String>,
111    /// The channel to use
112    pub channel: Option<u16>,
113    /// PSM number used for UUIDS and SDP (if applicable)
114    pub psm: Option<u16>,
115    /// Is authentication required for a connection
116    pub authenticate: Option<bool>,
117    /// Is authorization required for a connection
118    pub authorize: Option<bool>,
119    /// For client profiles, This will force connection of the channel when a remote device is connected
120    pub auto_connect: Option<bool>,
121    /// manual SDP record
122    pub sdp_record: Option<String>,
123    /// SDP version
124    pub sdp_version: Option<u16>,
125    /// SDP profile features
126    pub sdp_features: Option<u16>,
127}
128
129/// The trait that implements managing when bluetooth discovery is enabled
130#[enum_dispatch::enum_dispatch]
131pub trait BluetoothDiscoveryTrait {}
132
133/// The trait for the object that manages bluetooth discovery
134#[enum_dispatch::enum_dispatch(BluetoothDiscoveryTrait)]
135pub enum BluetoothDiscovery {
136    /// The android version
137    #[cfg(target_os = "android")]
138    Android(android::BluetoothDiscovery),
139    /// Linux bluez library implementation
140    #[cfg(target_os = "linux")]
141    Bluez(linux::BluetoothDiscovery),
142    /// Windows implementation
143    #[cfg(target_os = "windows")]
144    Windows(windows::BluetoothDiscovery),
145}
146
147/// The address of a bluetooth adapter
148pub enum BluetoothAdapterAddress {
149    /// The address in string form
150    String(String),
151    /// The address in byte form
152    Byte([u8; 6]),
153}
154
155/// Common async functionality for the bluetooth adapter
156#[enum_dispatch::enum_dispatch]
157#[async_trait::async_trait]
158pub trait AsyncBluetoothAdapterTrait {
159    /// Attempt to register a new rfcomm profile
160    async fn register_rfcomm_profile(
161        &self,
162        settings: BluetoothRfcommProfileSettings,
163    ) -> Result<BluetoothRfcommProfileAsync, String>;
164    /// Attempt to register a new l2cap profile
165    async fn register_l2cap_profile(
166        &self,
167        settings: BluetoothL2capProfileSettings,
168    ) -> Result<BluetoothL2capProfileAsync, String>;
169    ///Get a list of paired bluetooth devices
170    fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
171    /// Start discovery of bluetooth devices. Run this and drop the result to cancel discovery
172    fn start_discovery(&self) -> BluetoothDiscovery;
173    /// Get the mac addresses of all bluetooth adapters for the system
174    async fn addresses(&self) -> Vec<BluetoothAdapterAddress>;
175    /// Set the discoverable property
176    async fn set_discoverable(&self, d: bool) -> Result<(), ()>;
177}
178
179/// Common sync functionality for the bluetooth adapter
180#[enum_dispatch::enum_dispatch]
181pub trait SyncBluetoothAdapterTrait {
182    /// Attempt to register a new rfcomm profile
183    fn register_rfcomm_profile(
184        &self,
185        settings: BluetoothRfcommProfileSettings,
186    ) -> Result<BluetoothRfcommProfileSync, String>;
187    /// Attempt to register a new lc2ap profile
188    fn register_l2cap_profile(
189        &self,
190        settings: BluetoothL2capProfileSettings,
191    ) -> Result<BluetoothL2capProfileAsync, String>;
192    ///Get a list of paired bluetooth devices
193    fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
194    /// Start discovery of bluetooth devices. Run this and drop the result to cancel discovery
195    fn start_discovery(&self) -> BluetoothDiscovery;
196    /// Get the mac addresses of all bluetooth adapters for the system
197    fn addresses(&self) -> Vec<BluetoothAdapterAddress>;
198    /// Set the discoverable property
199    fn set_discoverable(&self, d: bool) -> Result<(), ()>;
200}
201
202/// Common functionality for the bluetooth adapter
203#[enum_dispatch::enum_dispatch]
204pub trait BluetoothAdapterTrait {
205    /// Returns Some when the async interface is supported
206    fn supports_async(&mut self) -> Option<&mut dyn AsyncBluetoothAdapterTrait>;
207    /// Returns Some when the sync interface is supported
208    fn supports_sync(&mut self) -> Option<&mut dyn SyncBluetoothAdapterTrait>;
209}
210
211/// The pairing status of a bluetooth device
212pub enum PairingStatus {
213    /// The device is not paired
214    NotPaired,
215    /// The device is in the pairing process
216    Pairing,
217    /// The device is paired
218    Paired,
219    /// The status is unknown or invalid
220    Unknown,
221}
222
223/// The trait that all bluetooth devices must implement
224#[enum_dispatch::enum_dispatch]
225pub trait BluetoothDeviceTrait {
226    /// Get all known uuids for this device
227    fn get_uuids(&mut self) -> Result<Vec<BluetoothUuid>, std::io::Error>;
228
229    /// Retrieve the device name
230    fn get_name(&self) -> Result<String, std::io::Error>;
231
232    /// Retrieve the device address
233    fn get_address(&mut self) -> Result<String, std::io::Error>;
234
235    /// Retrieve the device pairing status
236    fn get_pair_state(&self) -> Result<PairingStatus, std::io::Error>;
237
238    /// Attempt to get an rfcomm socket for the given uuid and security setting
239    fn get_rfcomm_socket(
240        &mut self,
241        uuid: BluetoothUuid,
242        is_secure: bool,
243    ) -> Result<BluetoothSocket, String>;
244
245    /// Attempt to get an l2cap socket for the given uuid and security setting
246    fn get_l2cap_socket(
247        &mut self,
248        uuid: BluetoothUuid,
249        is_secure: bool,
250    ) -> Result<BluetoothSocket, String>;
251
252    /// Run the service discovery protocol
253    fn run_sdp(&mut self);
254}
255
256/// A bluetooth device
257#[enum_dispatch::enum_dispatch(BluetoothDeviceTrait)]
258pub enum BluetoothDevice {
259    /// Bluetooth device on android
260    #[cfg(target_os = "android")]
261    Android(android::BluetoothDevice),
262    /// Bluetooth device on linux using the bluez library
263    #[cfg(target_os = "linux")]
264    Bluez(linux::LinuxBluetoothDevice),
265    /// Bluetooth device on Windows
266    #[cfg(target_os = "windows")]
267    Windows(windows::BluetoothDevice),
268}
269
270/// Represents a bluetooth adapter that communicates to bluetooth devices
271#[enum_dispatch::enum_dispatch(BluetoothAdapterTrait)]
272pub enum BluetoothAdapter {
273    /// The bluetooth adapter for android systems
274    #[cfg(target_os = "android")]
275    Android(android::Bluetooth),
276    /// On linux, bluetooth adapter using the bluez library
277    #[cfg(target_os = "linux")]
278    Bluez(linux::BluetoothHandler),
279    /// On Windows, bluetooth adapter using the windows crate
280    #[cfg(target_os = "windows")]
281    Windows(windows::BluetoothHandler),
282}
283
284/// A builder for `BluetoothAdapter`
285pub struct BluetoothAdapterBuilder {
286    /// The androidapp object
287    #[cfg(target_os = "android")]
288    app: Option<AndroidApp>,
289    /// The sender to send messages to the bluetooth host
290    s: Option<tokio::sync::mpsc::Sender<MessageToBluetoothHost>>,
291}
292
293impl Default for BluetoothAdapterBuilder {
294    fn default() -> Self {
295        Self::new()
296    }
297}
298
299impl BluetoothAdapterBuilder {
300    /// Construct a new self
301    pub fn new() -> Self {
302        Self {
303            #[cfg(target_os = "android")]
304            app: None,
305            s: None,
306        }
307    }
308
309    /// Put the required `AndroidApp` object into the builder
310    #[cfg(target_os = "android")]
311    pub fn with_android_app(&mut self, app: AndroidApp) {
312        self.app = Some(app);
313    }
314
315    /// Add the sender to the builder
316    pub fn with_sender(&mut self, s: tokio::sync::mpsc::Sender<MessageToBluetoothHost>) {
317        self.s = Some(s);
318    }
319
320    /// Do the build
321    pub fn build(self) -> Result<BluetoothAdapter, String> {
322        #[cfg(target_os = "android")]
323        {
324            return Ok(BluetoothAdapter::Android(android::Bluetooth::new(
325                self.app.unwrap(),
326            )));
327        }
328        Err("No synchronous builders available".to_string())
329    }
330
331    /// Do the build
332    pub async fn async_build(self) -> Result<BluetoothAdapter, String> {
333        #[cfg(target_os = "android")]
334        {
335            return self.build();
336        }
337        #[cfg(target_os = "linux")]
338        {
339            return Ok(BluetoothAdapter::Bluez(
340                linux::BluetoothHandler::new(self.s.unwrap()).await?,
341            ));
342        }
343        #[cfg(target_os = "windows")]
344        {
345            return Ok(BluetoothAdapter::Windows(
346                windows::BluetoothHandler::new(self.s.unwrap()).await?,
347            ));
348        }
349        Err("No async builders available".to_string())
350    }
351}
352
353/// An active stream for bluetooth communications
354pub enum BluetoothStream {
355    /// On linux, a stream using the bluez library
356    #[cfg(target_os = "linux")]
357    Bluez(std::pin::Pin<Box<bluer::rfcomm::Stream>>),
358    /// Android code for a bluetooth stream
359    #[cfg(target_os = "android")]
360    Android(android::RfcommStream),
361    /// Windows RFCOMM stream
362    #[cfg(target_os = "windows")]
363    Windows(windows::WindowsRfcommStream),
364}
365
366impl BluetoothStream {
367    /// Used to check to see if the object supports async read, and then use the functionality
368    pub fn supports_async_read(
369        self: std::pin::Pin<&mut Self>,
370    ) -> Option<&mut dyn tokio::io::AsyncRead> {
371        match self.get_mut() {
372            #[cfg(target_os = "linux")]
373            BluetoothStream::Bluez(pin) => Some(pin),
374            #[cfg(target_os = "android")]
375            BluetoothStream::Android(_pin) => None,
376            #[cfg(target_os = "windows")]
377            BluetoothStream::Windows(_pin) => None,
378        }
379    }
380
381    /// Used to check to see if the object supports async write, and then use the functionality
382    pub fn supports_async_write(
383        self: std::pin::Pin<&mut Self>,
384    ) -> Option<&mut dyn tokio::io::AsyncWrite> {
385        match self.get_mut() {
386            #[cfg(target_os = "linux")]
387            BluetoothStream::Bluez(pin) => Some(pin),
388            #[cfg(target_os = "android")]
389            BluetoothStream::Android(_pin) => None,
390            #[cfg(target_os = "windows")]
391            BluetoothStream::Windows(_pin) => None,
392        }
393    }
394
395    /// Used to try to use synchronous read functionality
396    pub fn supports_sync_read(self: std::pin::Pin<&mut Self>) -> Option<&mut dyn std::io::Read> {
397        match self.get_mut() {
398            #[cfg(target_os = "linux")]
399            BluetoothStream::Bluez(_pin) => None,
400            #[cfg(target_os = "android")]
401            BluetoothStream::Android(pin) => Some(pin),
402            #[cfg(target_os = "windows")]
403            BluetoothStream::Windows(pin) => Some(pin),
404        }
405    }
406
407    /// Used to try to use synchronous write functionality
408    pub fn supports_sync_write(self: std::pin::Pin<&mut Self>) -> Option<&mut dyn std::io::Write> {
409        match self.get_mut() {
410            #[cfg(target_os = "linux")]
411            BluetoothStream::Bluez(_pin) => None,
412            #[cfg(target_os = "android")]
413            BluetoothStream::Android(pin) => Some(pin),
414            #[cfg(target_os = "windows")]
415            BluetoothStream::Windows(pin) => Some(pin),
416        }
417    }
418}
419
420/// The trait for bluetooth rfcomm objects that can be connected or accepted
421#[enum_dispatch::enum_dispatch]
422pub trait BluetoothRfcommConnectableAsyncTrait {
423    /// Accept a connection from a bluetooth peer
424    async fn accept(self) -> Result<BluetoothStream, String>;
425}
426
427/// A bluetooth profile for rfcomm channels
428#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableTrait)]
429pub enum BluetoothRfcommConnectableAsync {
430    /// The android object for the profile
431    #[cfg(target_os = "android")]
432    Android(android::BluetoothRfcommConnectable),
433    /// The bluez library in linux is responsible for the profile
434    #[cfg(target_os = "linux")]
435    Bluez(bluer::rfcomm::ConnectRequest),
436    /// Windows RFCOMM connectable
437    #[cfg(target_os = "windows")]
438    Windows(windows::BluetoothRfcommConnectable),
439}
440
441/// The trait for bluetooth rfcomm objects that can be connected or accepted
442#[enum_dispatch::enum_dispatch]
443pub trait BluetoothRfcommConnectableSyncTrait {
444    /// Accept a connection from a bluetooth peer
445    fn accept(self, timeout: std::time::Duration) -> Result<BluetoothStream, String>;
446}
447
448/// A bluetooth profile for rfcomm channels
449#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableSyncTrait)]
450pub enum BluetoothRfcommConnectableSync {
451    /// The android object for the profile
452    #[cfg(target_os = "android")]
453    Android(android::BluetoothRfcommConnectable),
454}
455
456/// The trait for bluetooth rfcomm objects that can be connected or accepted
457#[enum_dispatch::enum_dispatch]
458pub trait BluetoothL2capConnectableAsyncTrait {
459    /// Accept a connection from a bluetooth peer
460    async fn accept(self) -> Result<BluetoothStream, String>;
461}
462
463/// A bluetooth profile for rfcomm channels
464#[enum_dispatch::enum_dispatch(BluetoothL2capConnectableTrait)]
465pub enum BluetoothL2capConnectableAsync {
466    /// The android object for the profile
467    #[cfg(target_os = "android")]
468    Android(android::BluetoothRfcommConnectable),
469    /// The bluez library in linux is responsible for the profile
470    #[cfg(target_os = "linux")]
471    Bluez(bluer::rfcomm::ConnectRequest),
472}
473
474/// The trait for bluetooth rfcomm objects that can be connected or accepted
475#[enum_dispatch::enum_dispatch]
476pub trait BluetoothL2capConnectableSyncTrait {
477    /// Accept a connection from a bluetooth peer
478    fn accept(self, timeout: std::time::Duration) -> Result<BluetoothStream, String>;
479}
480
481/// A bluetooth profile for rfcomm channels
482#[enum_dispatch::enum_dispatch(BluetoothL2capConnectableSyncTrait)]
483pub enum BluetoothL2capConnectableSync {
484    /// The android object for the profile
485    #[cfg(target_os = "android")]
486    Android(android::BluetoothRfcommConnectable),
487}
488
489/// Allows building an object to connect to bluetooth devices
490#[enum_dispatch::enum_dispatch]
491pub trait BluetoothRfcommProfileAsyncTrait {
492    /// Get an object in order to accept a connection from or connect to a bluetooth peer
493    async fn connectable(&mut self) -> Result<BluetoothRfcommConnectableAsync, String>;
494}
495
496/// Allows building an object to connect to bluetooth devices
497#[enum_dispatch::enum_dispatch]
498pub trait BluetoothRfcommProfileSyncTrait {
499    /// Get an object in order to accept a connection from or connect to a bluetooth peer
500    fn connectable(&mut self) -> Result<BluetoothRfcommConnectableSync, String>;
501}
502
503/// A bluetooth profile for rfcomm channels
504#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileAsyncTrait)]
505pub enum BluetoothRfcommProfileAsync {
506    /// The bluez library in linux is responsible for the profile
507    #[cfg(target_os = "linux")]
508    Bluez(bluer::rfcomm::ProfileHandle),
509    /// Windows RFCOMM profile
510    #[cfg(target_os = "windows")]
511    Windows(windows::BluetoothRfcommProfile),
512    /// A dummy handler
513    Dummy(Dummy),
514}
515
516/// A bluetooth profile for rfcomm channels
517#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileSyncTrait)]
518pub enum BluetoothRfcommProfileSync {
519    /// Android rfcomm profile
520    #[cfg(target_os = "android")]
521    Android(android::BluetoothRfcommProfile),
522    /// A dummy handler
523    Dummy(Dummy),
524}
525
526/// A bluetooth profile for rfcomm channels
527#[enum_dispatch::enum_dispatch(BluetoothL2capProfileAsyncTrait)]
528pub enum BluetoothL2capProfileAsync {
529    /// The bluez library in linux is responsible for the profile
530    #[cfg(target_os = "linux")]
531    Bluez(bluer::rfcomm::ProfileHandle),
532    /// A dummy handler
533    Dummy(Dummy),
534}
535
536/// A bluetooth profile for rfcomm channels
537#[enum_dispatch::enum_dispatch(BluetoothL2capProfileSyncTrait)]
538pub enum BluetoothL2capProfileSync {
539    /// Android rfcomm profile
540    #[cfg(target_os = "android")]
541    Android(android::BluetoothRfcommProfile),
542    /// A dummy handler
543    Dummy(Dummy),
544}
545
546/// A dummy struct for ensuring enums are not empty
547pub struct Dummy {}
548
549impl BluetoothRfcommProfileSyncTrait for Dummy {
550    fn connectable(&mut self) -> Result<BluetoothRfcommConnectableSync, String> {
551        unimplemented!()
552    }
553}
554
555impl BluetoothRfcommProfileAsyncTrait for Dummy {
556    async fn connectable(&mut self) -> Result<BluetoothRfcommConnectableAsync, String> {
557        unimplemented!()
558    }
559}
560
561/// The common functions for all bluetooth rfcomm sockets
562#[enum_dispatch::enum_dispatch]
563pub trait BluetoothSocketTrait {
564    /// Is the socket connected
565    fn is_connected(&self) -> Result<bool, std::io::Error>;
566    /// connect the socket
567    fn connect(&mut self) -> Result<(), std::io::Error>;
568}
569
570impl<'a> std::io::Read for BluetoothSocket<'a> {
571    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
572        match self {
573            #[cfg(target_os = "android")]
574            BluetoothSocket::Android(a) => a.read(buf),
575            #[cfg(target_os = "linux")]
576            BluetoothSocket::Bluez(b) => b.read(buf),
577            #[cfg(target_os = "windows")]
578            BluetoothSocket::Windows(w) => w.read(buf),
579        }
580    }
581}
582
583impl<'a> std::io::Write for BluetoothSocket<'a> {
584    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
585        match self {
586            #[cfg(target_os = "android")]
587            BluetoothSocket::Android(a) => a.write(buf),
588            #[cfg(target_os = "linux")]
589            BluetoothSocket::Bluez(b) => b.write(buf),
590            #[cfg(target_os = "windows")]
591            BluetoothSocket::Windows(w) => w.write(buf),
592        }
593    }
594
595    fn flush(&mut self) -> std::io::Result<()> {
596        match self {
597            #[cfg(target_os = "android")]
598            BluetoothSocket::Android(a) => a.flush(),
599            #[cfg(target_os = "linux")]
600            BluetoothSocket::Bluez(b) => b.flush(),
601            #[cfg(target_os = "windows")]
602            BluetoothSocket::Windows(w) => w.flush(),
603        }
604    }
605}
606
607/// A bluetooth rfcomm socket
608#[enum_dispatch::enum_dispatch(BluetoothSocketTrait)]
609pub enum BluetoothSocket<'a> {
610    /// The android based rfcomm socket
611    #[cfg(target_os = "android")]
612    Android(&'a mut android::BluetoothSocket),
613    /// Linux using bluez library
614    #[cfg(target_os = "linux")]
615    Bluez(&'a mut linux::BluetoothRfcommSocket),
616    /// Windows bluetooth socket
617    #[cfg(target_os = "windows")]
618    Windows(&'a mut windows::BluetoothRfcommSocket),
619}