1#![deny(missing_docs)]
2#![deny(clippy::missing_docs_in_private_items)]
3#![warn(unused_extern_crates)]
4
5#[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
30mod sdp;
31
32#[derive(Debug, serde::Deserialize, serde::Serialize)]
34pub enum BluetoothCommand {
35 DetectAdapters,
37 QueryNumAdapters,
39}
40
41pub enum MessageToBluetoothHost {
43 DisplayPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
45 ConfirmPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
47 CancelDisplayPasskey,
49}
50
51#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
52pub enum MessageFromBluetoothHost {
54 PasskeyMessage(ResponseToPasskey),
56}
57
58#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
59pub enum ResponseToPasskey {
61 Yes,
63 No,
65 Cancel,
67 Waiting,
69}
70
71pub enum BluetoothResponse {
73 Adapters(usize),
75}
76
77#[derive(Clone, Debug)]
79pub struct BluetoothRfcommProfileSettings {
80 pub uuid: String,
82 pub name: Option<String>,
84 pub service_uuid: Option<String>,
86 pub channel: Option<u16>,
88 pub psm: Option<u16>,
90 pub authenticate: Option<bool>,
92 pub authorize: Option<bool>,
94 pub auto_connect: Option<bool>,
96 pub sdp_record: Option<String>,
98 pub sdp_version: Option<u16>,
100 pub sdp_features: Option<u16>,
102}
103
104#[derive(Clone)]
106pub struct BluetoothL2capProfileSettings {
107 pub uuid: String,
109 pub name: Option<String>,
111 pub service_uuid: Option<String>,
113 pub channel: Option<u16>,
115 pub psm: Option<u16>,
117 pub authenticate: Option<bool>,
119 pub authorize: Option<bool>,
121 pub auto_connect: Option<bool>,
123 pub sdp_record: Option<String>,
125 pub sdp_version: Option<u16>,
127 pub sdp_features: Option<u16>,
129}
130
131#[enum_dispatch::enum_dispatch]
133pub trait BluetoothDiscoveryTrait {}
134
135#[enum_dispatch::enum_dispatch(BluetoothDiscoveryTrait)]
137pub enum BluetoothDiscovery {
138 #[cfg(target_os = "android")]
140 Android(android::BluetoothDiscovery),
141 #[cfg(target_os = "linux")]
143 Bluez(linux::BluetoothDiscovery),
144 #[cfg(target_os = "windows")]
146 Windows(windows::BluetoothDiscovery),
147}
148
149pub enum BluetoothAdapterAddress {
151 String(String),
153 Byte([u8; 6]),
155}
156
157#[enum_dispatch::enum_dispatch]
159#[async_trait::async_trait]
160pub trait AsyncBluetoothAdapterTrait {
161 async fn register_rfcomm_profile(
163 &self,
164 settings: BluetoothRfcommProfileSettings,
165 ) -> Result<BluetoothRfcommProfileAsync, String>;
166 async fn register_l2cap_profile(
168 &self,
169 settings: BluetoothL2capProfileSettings,
170 ) -> Result<BluetoothL2capProfileAsync, String>;
171 fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
173 fn start_discovery(&self) -> BluetoothDiscovery;
175 async fn addresses(&self) -> Vec<BluetoothAdapterAddress>;
177 async fn set_discoverable(&self, d: bool) -> Result<(), ()>;
179}
180
181#[enum_dispatch::enum_dispatch]
183pub trait SyncBluetoothAdapterTrait {
184 fn register_rfcomm_profile(
186 &self,
187 settings: BluetoothRfcommProfileSettings,
188 ) -> Result<BluetoothRfcommProfileSync, String>;
189 fn register_l2cap_profile(
191 &self,
192 settings: BluetoothL2capProfileSettings,
193 ) -> Result<BluetoothL2capProfileAsync, String>;
194 fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
196 fn start_discovery(&self) -> BluetoothDiscovery;
198 fn addresses(&self) -> Vec<BluetoothAdapterAddress>;
200 fn set_discoverable(&self, d: bool) -> Result<(), ()>;
202}
203
204#[enum_dispatch::enum_dispatch]
206pub trait BluetoothAdapterTrait {
207 fn supports_async(&self) -> Option<&dyn AsyncBluetoothAdapterTrait>;
209 fn supports_sync(&self) -> Option<&dyn SyncBluetoothAdapterTrait>;
211}
212
213pub enum PairingStatus {
215 NotPaired,
217 Pairing,
219 Paired,
221 Unknown,
223}
224
225fn uuid16(uuid: u16) -> Vec<u8> {
226 let mut v = Vec::new();
227 v.push(0x19); v.extend_from_slice(&uuid.to_be_bytes());
229 v
230}
231
232fn build_sdp_request(uuid: u16, transaction_id: u16) -> Vec<u8> {
233 let mut pdu = Vec::new();
234
235 pdu.push(0x06);
237
238 let mut params = Vec::new();
240
241 params.extend_from_slice(&transaction_id.to_be_bytes());
243
244 let uuid = uuid16(uuid);
248 let mut search = Vec::new();
249 search.push(0x35); search.push(uuid.len() as u8);
251 search.extend_from_slice(&uuid);
252 params.extend_from_slice(&search);
253
254 let mut attrs = Vec::new();
256 attrs.push(0x35);
257 attrs.push(7);
258 attrs.extend_from_slice(&[
259 0x09, 0x00, 0x00, 0x09, 0xFF, 0xFF, ]);
262 params.extend_from_slice(&attrs);
263
264 params.extend_from_slice(&0xFFFFu16.to_be_bytes());
266
267 params.push(0x00);
269
270 let len = params.len() as u16;
272 let mut out = Vec::new();
273 out.push(0x06);
274 out.extend_from_slice(&transaction_id.to_be_bytes());
275 out.extend_from_slice(&len.to_be_bytes());
276 out.extend_from_slice(¶ms);
277
278 out
279}
280
281#[enum_dispatch::enum_dispatch]
283pub trait BluetoothDeviceTrait {
284 fn get_uuids(&mut self) -> Result<Vec<BluetoothUuid>, std::io::Error>;
286
287 fn get_name(&self) -> Result<String, std::io::Error>;
289
290 fn get_address(&mut self) -> Result<String, std::io::Error>;
292
293 fn get_pair_state(&self) -> Result<PairingStatus, std::io::Error>;
295
296 fn get_rfcomm_socket(
298 &mut self,
299 channel: u8,
300 is_secure: bool,
301 ) -> Result<BluetoothSocket, String>;
302
303 fn get_l2cap_socket(&mut self, psm: u16, is_secure: bool) -> Result<BluetoothSocket, String>;
305
306 fn run_sdp(&mut self, uuid: BluetoothUuid) -> Result<sdp::ServiceRecord, String> {
308 if let Ok(a) = self.get_address() {
309 return sdp::run_sdp(&a, uuid.get_16_bit_id()).map_err(|e| e.to_string());
310 }
311 Err("Sdp failed".to_string())
312 }
313}
314
315#[enum_dispatch::enum_dispatch(BluetoothDeviceTrait)]
317pub enum BluetoothDevice {
318 #[cfg(target_os = "android")]
320 Android(android::BluetoothDevice),
321 #[cfg(target_os = "linux")]
323 Bluez(linux::LinuxBluetoothDevice),
324 #[cfg(target_os = "windows")]
326 Windows(windows::BluetoothDevice),
327}
328
329#[enum_dispatch::enum_dispatch(BluetoothAdapterTrait)]
331pub enum BluetoothAdapter {
332 #[cfg(target_os = "android")]
334 Android(android::Bluetooth),
335 #[cfg(target_os = "linux")]
337 Bluez(linux::BluetoothHandler),
338 #[cfg(target_os = "windows")]
340 Windows(windows::BluetoothHandler),
341}
342
343pub struct BluetoothAdapterBuilder {
345 #[cfg(target_os = "android")]
347 app: Option<AndroidApp>,
348 s: Option<tokio::sync::mpsc::Sender<MessageToBluetoothHost>>,
350}
351
352impl Default for BluetoothAdapterBuilder {
353 fn default() -> Self {
354 Self::new()
355 }
356}
357
358impl BluetoothAdapterBuilder {
359 pub fn new() -> Self {
361 Self {
362 #[cfg(target_os = "android")]
363 app: None,
364 s: None,
365 }
366 }
367
368 #[cfg(target_os = "android")]
370 pub fn with_android_app(&mut self, app: AndroidApp) {
371 self.app = Some(app);
372 }
373
374 pub fn with_sender(&mut self, s: tokio::sync::mpsc::Sender<MessageToBluetoothHost>) {
376 self.s = Some(s);
377 }
378
379 pub fn build(self) -> Result<BluetoothAdapter, String> {
381 #[cfg(target_os = "android")]
382 {
383 return Ok(BluetoothAdapter::Android(android::Bluetooth::new(
384 self.app.unwrap(),
385 )));
386 }
387 Err("No synchronous builders available".to_string())
388 }
389
390 pub async fn async_build(self) -> Result<BluetoothAdapter, String> {
392 #[cfg(target_os = "android")]
393 {
394 return self.build();
395 }
396 #[cfg(target_os = "linux")]
397 {
398 return Ok(BluetoothAdapter::Bluez(
399 linux::BluetoothHandler::new(self.s.unwrap()).await?,
400 ));
401 }
402 #[cfg(target_os = "windows")]
403 {
404 return Ok(BluetoothAdapter::Windows(
405 windows::BluetoothHandler::new(self.s.unwrap()).await?,
406 ));
407 }
408 Err("No async builders available".to_string())
409 }
410}
411
412pub enum BluetoothStream {
414 #[cfg(target_os = "linux")]
416 Bluez(std::pin::Pin<Box<bluer::rfcomm::Stream>>),
417 #[cfg(target_os = "android")]
419 Android(android::RfcommStream),
420 #[cfg(target_os = "windows")]
422 Windows(windows::WindowsRfcommStream),
423}
424
425macro_rules! pin_match {
426 ($this:expr, $s:ident => $body:expr) => {
427 match $this.get_mut() {
428 #[cfg(target_os = "linux")]
429 BluetoothStream::Bluez($s) => $body,
430
431 #[cfg(target_os = "android")]
432 BluetoothStream::Android($s) => $body,
433
434 #[cfg(target_os = "windows")]
435 BluetoothStream::Windows($s) => $body,
436 }
437 };
438}
439
440impl tokio::io::AsyncWrite for BluetoothStream {
441 fn poll_write(
442 self: std::pin::Pin<&mut Self>,
443 cx: &mut std::task::Context<'_>,
444 buf: &[u8],
445 ) -> std::task::Poll<std::io::Result<usize>> {
446 pin_match!(self, s => {
447 tokio::io::AsyncWrite::poll_write(std::pin::Pin::new(s), cx, buf)
449 })
450 }
451
452 fn poll_flush(
453 self: std::pin::Pin<&mut Self>,
454 cx: &mut std::task::Context<'_>,
455 ) -> std::task::Poll<std::io::Result<()>> {
456 pin_match!(self, s => {
457 tokio::io::AsyncWrite::poll_flush(std::pin::Pin::new(s), cx)
458 })
459 }
460
461 fn poll_shutdown(
462 self: std::pin::Pin<&mut Self>,
463 cx: &mut std::task::Context<'_>,
464 ) -> std::task::Poll<std::io::Result<()>> {
465 pin_match!(self, s => {
466 tokio::io::AsyncWrite::poll_shutdown(std::pin::Pin::new(s), cx)
467 })
468 }
469}
470
471impl tokio::io::AsyncRead for BluetoothStream {
472 fn poll_read(
473 self: std::pin::Pin<&mut Self>,
474 cx: &mut std::task::Context<'_>,
475 buf: &mut tokio::io::ReadBuf<'_>,
476 ) -> std::task::Poll<std::io::Result<()>> {
477 pin_match!(self, s => {
478 tokio::io::AsyncRead::poll_read(std::pin::Pin::new(s), cx, buf)
479 })
480 }
481}
482
483impl BluetoothStream {
484 pub fn supports_async_read(&mut self) -> Option<&mut dyn tokio::io::AsyncRead> {
486 match self {
487 #[cfg(target_os = "linux")]
488 BluetoothStream::Bluez(pin) => Some(pin),
489 #[cfg(target_os = "android")]
490 BluetoothStream::Android(_pin) => None,
491 #[cfg(target_os = "windows")]
492 BluetoothStream::Windows(_pin) => None,
493 }
494 }
495
496 pub fn supports_async_write(&mut self) -> Option<&mut dyn tokio::io::AsyncWrite> {
498 match self {
499 #[cfg(target_os = "linux")]
500 BluetoothStream::Bluez(pin) => Some(pin),
501 #[cfg(target_os = "android")]
502 BluetoothStream::Android(_pin) => None,
503 #[cfg(target_os = "windows")]
504 BluetoothStream::Windows(_pin) => None,
505 }
506 }
507
508 pub fn supports_sync_read(&mut self) -> Option<&mut dyn std::io::Read> {
510 match self {
511 #[cfg(target_os = "linux")]
512 BluetoothStream::Bluez(_pin) => None,
513 #[cfg(target_os = "android")]
514 BluetoothStream::Android(pin) => Some(pin),
515 #[cfg(target_os = "windows")]
516 BluetoothStream::Windows(pin) => Some(pin),
517 }
518 }
519
520 pub fn supports_sync_write(&mut self) -> Option<&mut dyn std::io::Write> {
522 match self {
523 #[cfg(target_os = "linux")]
524 BluetoothStream::Bluez(_pin) => None,
525 #[cfg(target_os = "android")]
526 BluetoothStream::Android(pin) => Some(pin),
527 #[cfg(target_os = "windows")]
528 BluetoothStream::Windows(pin) => Some(pin),
529 }
530 }
531}
532
533#[async_trait::async_trait]
535#[enum_dispatch::enum_dispatch]
536pub trait BluetoothRfcommConnectableAsyncTrait {
537 async fn accept(self) -> Result<(BluetoothStream, [u8; 6], u8), String>;
539}
540
541#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableAsyncTrait)]
543pub enum BluetoothRfcommConnectableAsync {
544 #[cfg(target_os = "android")]
546 Android(android::BluetoothRfcommConnectable),
547 #[cfg(target_os = "linux")]
549 Bluez(bluer::rfcomm::ConnectRequest),
550 #[cfg(target_os = "windows")]
552 Windows(windows::BluetoothRfcommConnectable),
553}
554
555#[enum_dispatch::enum_dispatch]
557pub trait BluetoothRfcommConnectableSyncTrait {
558 fn accept(self, timeout: std::time::Duration)
560 -> Result<(BluetoothStream, [u8; 6], u8), String>;
561}
562
563#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableSyncTrait)]
565pub enum BluetoothRfcommConnectableSync {
566 #[cfg(target_os = "android")]
568 Android(android::BluetoothRfcommConnectable),
569}
570
571#[enum_dispatch::enum_dispatch]
573pub trait BluetoothL2capConnectableAsyncTrait {
574 async fn accept(self) -> Result<BluetoothStream, String>;
576}
577
578#[enum_dispatch::enum_dispatch(BluetoothL2capConnectableTrait)]
580pub enum BluetoothL2capConnectableAsync {
581 #[cfg(target_os = "android")]
583 Android(android::BluetoothRfcommConnectable),
584 #[cfg(target_os = "linux")]
586 Bluez(bluer::rfcomm::ConnectRequest),
587}
588
589#[enum_dispatch::enum_dispatch]
591pub trait BluetoothL2capConnectableSyncTrait {
592 fn accept(self, timeout: std::time::Duration) -> Result<BluetoothStream, String>;
594}
595
596#[enum_dispatch::enum_dispatch(BluetoothL2capConnectableSyncTrait)]
598pub enum BluetoothL2capConnectableSync {
599 #[cfg(target_os = "android")]
601 Android(android::BluetoothRfcommConnectable),
602}
603
604#[enum_dispatch::enum_dispatch]
606pub trait BluetoothRfcommProfileAsyncTrait {
607 async fn connectable(&mut self) -> Result<BluetoothRfcommConnectableAsync, String>;
609}
610
611#[enum_dispatch::enum_dispatch]
613pub trait BluetoothRfcommProfileSyncTrait {
614 fn connectable(&mut self) -> Result<BluetoothRfcommConnectableSync, String>;
616}
617
618#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileAsyncTrait)]
620pub enum BluetoothRfcommProfileAsync {
621 #[cfg(target_os = "linux")]
623 Bluez(bluer::rfcomm::ProfileHandle),
624 #[cfg(target_os = "windows")]
626 Windows(windows::BluetoothRfcommProfile),
627 Dummy(Dummy),
629}
630
631#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileSyncTrait)]
633pub enum BluetoothRfcommProfileSync {
634 #[cfg(target_os = "android")]
636 Android(android::BluetoothRfcommProfile),
637 Dummy(Dummy),
639}
640
641#[enum_dispatch::enum_dispatch(BluetoothL2capProfileAsyncTrait)]
643pub enum BluetoothL2capProfileAsync {
644 #[cfg(target_os = "linux")]
646 Bluez(bluer::rfcomm::ProfileHandle),
647 Dummy(Dummy),
649}
650
651#[enum_dispatch::enum_dispatch(BluetoothL2capProfileSyncTrait)]
653pub enum BluetoothL2capProfileSync {
654 #[cfg(target_os = "android")]
656 Android(android::BluetoothRfcommProfile),
657 Dummy(Dummy),
659}
660
661pub struct Dummy {}
663
664impl BluetoothRfcommProfileSyncTrait for Dummy {
665 fn connectable(&mut self) -> Result<BluetoothRfcommConnectableSync, String> {
666 unimplemented!()
667 }
668}
669
670impl BluetoothRfcommProfileAsyncTrait for Dummy {
671 async fn connectable(&mut self) -> Result<BluetoothRfcommConnectableAsync, String> {
672 unimplemented!()
673 }
674}
675
676#[enum_dispatch::enum_dispatch]
678pub trait BluetoothSocketTrait {
679 fn is_connected(&self) -> Result<bool, std::io::Error>;
681 fn connect(&mut self) -> Result<(), std::io::Error>;
683}
684
685impl std::io::Read for BluetoothSocket {
686 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
687 match self {
688 #[cfg(target_os = "android")]
689 BluetoothSocket::Android(a) => a.read(buf),
690 #[cfg(target_os = "linux")]
691 BluetoothSocket::Bluez(b) => b.read(buf),
692 #[cfg(target_os = "windows")]
693 BluetoothSocket::Windows(w) => w.read(buf),
694 }
695 }
696}
697
698impl std::io::Write for BluetoothSocket {
699 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
700 match self {
701 #[cfg(target_os = "android")]
702 BluetoothSocket::Android(a) => a.write(buf),
703 #[cfg(target_os = "linux")]
704 BluetoothSocket::Bluez(b) => b.write(buf),
705 #[cfg(target_os = "windows")]
706 BluetoothSocket::Windows(w) => w.write(buf),
707 }
708 }
709
710 fn flush(&mut self) -> std::io::Result<()> {
711 match self {
712 #[cfg(target_os = "android")]
713 BluetoothSocket::Android(a) => a.flush(),
714 #[cfg(target_os = "linux")]
715 BluetoothSocket::Bluez(b) => b.flush(),
716 #[cfg(target_os = "windows")]
717 BluetoothSocket::Windows(w) => w.flush(),
718 }
719 }
720}
721
722#[enum_dispatch::enum_dispatch(BluetoothSocketTrait)]
724pub enum BluetoothSocket {
725 #[cfg(target_os = "android")]
727 Android(android::BluetoothSocket),
728 #[cfg(target_os = "linux")]
730 Bluez(linux::BluetoothRfcommSocket),
731 #[cfg(target_os = "windows")]
733 Windows(windows::BluetoothRfcommSocket),
734}