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