#![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(unused_extern_crates)]
#[cfg(target_os = "android")]
use std::sync::Arc;
#[cfg(target_os = "android")]
use std::sync::Mutex;
#[cfg(target_os = "android")]
mod android;
#[cfg(target_os = "android")]
pub use android::Bluetooth;
#[cfg(target_os = "android")]
pub use android::Java;
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "windows")]
mod windows;
mod bluetooth_uuid;
pub use bluetooth_uuid::BluetoothUuid;
mod sdp;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum BluetoothCommand {
DetectAdapters,
QueryNumAdapters,
}
pub enum MessageToBluetoothHost {
DisplayPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
ConfirmPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
CancelDisplayPasskey,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum MessageFromBluetoothHost {
PasskeyMessage(ResponseToPasskey),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum ResponseToPasskey {
Yes,
No,
Cancel,
Waiting,
}
pub enum BluetoothResponse {
Adapters(usize),
}
#[derive(Clone, Debug)]
pub struct BluetoothRfcommProfileSettings {
pub uuid: String,
pub name: Option<String>,
pub service_uuid: Option<String>,
pub channel: Option<u16>,
pub psm: Option<u16>,
pub authenticate: Option<bool>,
pub authorize: Option<bool>,
pub auto_connect: Option<bool>,
pub sdp_record: Option<String>,
pub sdp_version: Option<u16>,
pub sdp_features: Option<u16>,
}
#[derive(Clone)]
pub struct BluetoothL2capProfileSettings {
pub uuid: String,
pub name: Option<String>,
pub service_uuid: Option<String>,
pub channel: Option<u16>,
pub psm: Option<u16>,
pub authenticate: Option<bool>,
pub authorize: Option<bool>,
pub auto_connect: Option<bool>,
pub sdp_record: Option<String>,
pub sdp_version: Option<u16>,
pub sdp_features: Option<u16>,
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothDiscoveryTrait {}
#[enum_dispatch::enum_dispatch(BluetoothDiscoveryTrait)]
pub enum BluetoothDiscovery {
#[cfg(target_os = "android")]
Android(android::BluetoothDiscovery),
#[cfg(target_os = "linux")]
Bluez(linux::BluetoothDiscovery),
#[cfg(target_os = "windows")]
Windows(windows::BluetoothDiscovery),
}
pub enum BluetoothAdapterAddress {
String(String),
Byte([u8; 6]),
}
#[enum_dispatch::enum_dispatch]
#[async_trait::async_trait]
pub trait AsyncBluetoothAdapterTrait {
async fn register_rfcomm_profile(
&self,
settings: BluetoothRfcommProfileSettings,
) -> Result<BluetoothRfcommProfileAsync, String>;
async fn register_l2cap_profile(
&self,
settings: BluetoothL2capProfileSettings,
) -> Result<BluetoothL2capProfileAsync, String>;
fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
fn start_discovery(&self) -> BluetoothDiscovery;
async fn addresses(&self) -> Vec<BluetoothAdapterAddress>;
async fn set_discoverable(&self, d: bool) -> Result<(), ()>;
}
#[enum_dispatch::enum_dispatch]
pub trait SyncBluetoothAdapterTrait {
fn register_rfcomm_profile(
&self,
settings: BluetoothRfcommProfileSettings,
) -> Result<BluetoothRfcommProfileSync, String>;
fn register_l2cap_profile(
&self,
settings: BluetoothL2capProfileSettings,
) -> Result<BluetoothL2capProfileAsync, String>;
fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
fn start_discovery(&self) -> BluetoothDiscovery;
fn addresses(&self) -> Vec<BluetoothAdapterAddress>;
fn set_discoverable(&self, d: bool) -> Result<(), ()>;
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothAdapterTrait {
fn supports_async(&self) -> Option<&dyn AsyncBluetoothAdapterTrait>;
fn supports_sync(&self) -> Option<&dyn SyncBluetoothAdapterTrait>;
}
pub enum PairingStatus {
NotPaired,
Pairing,
Paired,
Unknown,
}
fn uuid16(uuid: u16) -> Vec<u8> {
let mut v = Vec::new();
v.push(0x19); v.extend_from_slice(&uuid.to_be_bytes());
v
}
fn build_sdp_request(uuid: u16, transaction_id: u16) -> Vec<u8> {
let mut pdu = Vec::new();
pdu.push(0x06);
let mut params = Vec::new();
params.extend_from_slice(&transaction_id.to_be_bytes());
let uuid = uuid16(uuid);
let mut search = Vec::new();
search.push(0x35); search.push(uuid.len() as u8);
search.extend_from_slice(&uuid);
params.extend_from_slice(&search);
let mut attrs = Vec::new();
attrs.push(0x35);
attrs.push(7);
attrs.extend_from_slice(&[
0x09, 0x00, 0x00, 0x09, 0xFF, 0xFF, ]);
params.extend_from_slice(&attrs);
params.extend_from_slice(&0xFFFFu16.to_be_bytes());
params.push(0x00);
let len = params.len() as u16;
let mut out = Vec::new();
out.push(0x06);
out.extend_from_slice(&transaction_id.to_be_bytes());
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(¶ms);
out
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothDeviceTrait {
fn get_uuids(&mut self) -> Result<Vec<BluetoothUuid>, std::io::Error>;
fn get_name(&self) -> Result<String, std::io::Error>;
fn get_address(&mut self) -> Result<String, std::io::Error>;
fn get_pair_state(&self) -> Result<PairingStatus, std::io::Error>;
fn get_rfcomm_socket(
&mut self,
channel: u8,
is_secure: bool,
) -> Result<BluetoothSocket, String>;
fn get_l2cap_socket(&mut self, psm: u16, is_secure: bool) -> Result<BluetoothSocket, String>;
fn run_sdp(&mut self, uuid: BluetoothUuid) -> Result<sdp::ServiceRecord, String> {
if let Ok(a) = self.get_address() {
return sdp::run_sdp(&a, uuid.get_16_bit_id()).map_err(|e| e.to_string());
}
Err("Sdp failed".to_string())
}
}
#[enum_dispatch::enum_dispatch(BluetoothDeviceTrait)]
pub enum BluetoothDevice {
#[cfg(target_os = "android")]
Android(android::BluetoothDevice),
#[cfg(target_os = "linux")]
Bluez(linux::LinuxBluetoothDevice),
#[cfg(target_os = "windows")]
Windows(windows::BluetoothDevice),
}
#[enum_dispatch::enum_dispatch(BluetoothAdapterTrait)]
pub enum BluetoothAdapter {
#[cfg(target_os = "android")]
Android(android::Bluetooth),
#[cfg(target_os = "linux")]
Bluez(linux::BluetoothHandler),
#[cfg(target_os = "windows")]
Windows(windows::BluetoothHandler),
}
pub struct BluetoothAdapterBuilder {
#[cfg(target_os = "android")]
app: Option<AndroidApp>,
s: Option<tokio::sync::mpsc::Sender<MessageToBluetoothHost>>,
}
impl Default for BluetoothAdapterBuilder {
fn default() -> Self {
Self::new()
}
}
impl BluetoothAdapterBuilder {
pub fn new() -> Self {
Self {
#[cfg(target_os = "android")]
app: None,
s: None,
}
}
#[cfg(target_os = "android")]
pub fn with_android_app(&mut self, app: AndroidApp) {
self.app = Some(app);
}
pub fn with_sender(&mut self, s: tokio::sync::mpsc::Sender<MessageToBluetoothHost>) {
self.s = Some(s);
}
pub fn build(self) -> Result<BluetoothAdapter, String> {
#[cfg(target_os = "android")]
{
return Ok(BluetoothAdapter::Android(android::Bluetooth::new(
self.app.unwrap(),
)));
}
Err("No synchronous builders available".to_string())
}
pub async fn async_build(self) -> Result<BluetoothAdapter, String> {
#[cfg(target_os = "android")]
{
return self.build();
}
#[cfg(target_os = "linux")]
{
return Ok(BluetoothAdapter::Bluez(
linux::BluetoothHandler::new(self.s.unwrap()).await?,
));
}
#[cfg(target_os = "windows")]
{
return Ok(BluetoothAdapter::Windows(
windows::BluetoothHandler::new(self.s.unwrap()).await?,
));
}
Err("No async builders available".to_string())
}
}
pub enum BluetoothStream {
#[cfg(target_os = "linux")]
Bluez(std::pin::Pin<Box<bluer::rfcomm::Stream>>),
#[cfg(target_os = "android")]
Android(android::RfcommStream),
#[cfg(target_os = "windows")]
Windows(windows::WindowsRfcommStream),
}
macro_rules! pin_match {
($this:expr, $s:ident => $body:expr) => {
match $this.get_mut() {
#[cfg(target_os = "linux")]
BluetoothStream::Bluez($s) => $body,
#[cfg(target_os = "android")]
BluetoothStream::Android($s) => $body,
#[cfg(target_os = "windows")]
BluetoothStream::Windows($s) => $body,
}
};
}
impl tokio::io::AsyncWrite for BluetoothStream {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
pin_match!(self, s => {
tokio::io::AsyncWrite::poll_write(std::pin::Pin::new(s), cx, buf)
})
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
pin_match!(self, s => {
tokio::io::AsyncWrite::poll_flush(std::pin::Pin::new(s), cx)
})
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
pin_match!(self, s => {
tokio::io::AsyncWrite::poll_shutdown(std::pin::Pin::new(s), cx)
})
}
}
impl tokio::io::AsyncRead for BluetoothStream {
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
pin_match!(self, s => {
tokio::io::AsyncRead::poll_read(std::pin::Pin::new(s), cx, buf)
})
}
}
impl BluetoothStream {
pub fn supports_async_read(&mut self) -> Option<&mut dyn tokio::io::AsyncRead> {
match self {
#[cfg(target_os = "linux")]
BluetoothStream::Bluez(pin) => Some(pin),
#[cfg(target_os = "android")]
BluetoothStream::Android(_pin) => None,
#[cfg(target_os = "windows")]
BluetoothStream::Windows(_pin) => None,
}
}
pub fn supports_async_write(&mut self) -> Option<&mut dyn tokio::io::AsyncWrite> {
match self {
#[cfg(target_os = "linux")]
BluetoothStream::Bluez(pin) => Some(pin),
#[cfg(target_os = "android")]
BluetoothStream::Android(_pin) => None,
#[cfg(target_os = "windows")]
BluetoothStream::Windows(_pin) => None,
}
}
pub fn supports_sync_read(&mut self) -> Option<&mut dyn std::io::Read> {
match self {
#[cfg(target_os = "linux")]
BluetoothStream::Bluez(_pin) => None,
#[cfg(target_os = "android")]
BluetoothStream::Android(pin) => Some(pin),
#[cfg(target_os = "windows")]
BluetoothStream::Windows(pin) => Some(pin),
}
}
pub fn supports_sync_write(&mut self) -> Option<&mut dyn std::io::Write> {
match self {
#[cfg(target_os = "linux")]
BluetoothStream::Bluez(_pin) => None,
#[cfg(target_os = "android")]
BluetoothStream::Android(pin) => Some(pin),
#[cfg(target_os = "windows")]
BluetoothStream::Windows(pin) => Some(pin),
}
}
}
#[async_trait::async_trait]
#[enum_dispatch::enum_dispatch]
pub trait BluetoothRfcommConnectableAsyncTrait {
async fn accept(self) -> Result<BluetoothStream, String>;
}
#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableAsyncTrait)]
pub enum BluetoothRfcommConnectableAsync {
#[cfg(target_os = "android")]
Android(android::BluetoothRfcommConnectable),
#[cfg(target_os = "linux")]
Bluez(bluer::rfcomm::ConnectRequest),
#[cfg(target_os = "windows")]
Windows(windows::BluetoothRfcommConnectable),
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothRfcommConnectableSyncTrait {
fn accept(self, timeout: std::time::Duration) -> Result<BluetoothStream, String>;
}
#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableSyncTrait)]
pub enum BluetoothRfcommConnectableSync {
#[cfg(target_os = "android")]
Android(android::BluetoothRfcommConnectable),
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothL2capConnectableAsyncTrait {
async fn accept(self) -> Result<BluetoothStream, String>;
}
#[enum_dispatch::enum_dispatch(BluetoothL2capConnectableTrait)]
pub enum BluetoothL2capConnectableAsync {
#[cfg(target_os = "android")]
Android(android::BluetoothRfcommConnectable),
#[cfg(target_os = "linux")]
Bluez(bluer::rfcomm::ConnectRequest),
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothL2capConnectableSyncTrait {
fn accept(self, timeout: std::time::Duration) -> Result<BluetoothStream, String>;
}
#[enum_dispatch::enum_dispatch(BluetoothL2capConnectableSyncTrait)]
pub enum BluetoothL2capConnectableSync {
#[cfg(target_os = "android")]
Android(android::BluetoothRfcommConnectable),
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothRfcommProfileAsyncTrait {
async fn connectable(&mut self) -> Result<BluetoothRfcommConnectableAsync, String>;
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothRfcommProfileSyncTrait {
fn connectable(&mut self) -> Result<BluetoothRfcommConnectableSync, String>;
}
#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileAsyncTrait)]
pub enum BluetoothRfcommProfileAsync {
#[cfg(target_os = "linux")]
Bluez(bluer::rfcomm::ProfileHandle),
#[cfg(target_os = "windows")]
Windows(windows::BluetoothRfcommProfile),
Dummy(Dummy),
}
#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileSyncTrait)]
pub enum BluetoothRfcommProfileSync {
#[cfg(target_os = "android")]
Android(android::BluetoothRfcommProfile),
Dummy(Dummy),
}
#[enum_dispatch::enum_dispatch(BluetoothL2capProfileAsyncTrait)]
pub enum BluetoothL2capProfileAsync {
#[cfg(target_os = "linux")]
Bluez(bluer::rfcomm::ProfileHandle),
Dummy(Dummy),
}
#[enum_dispatch::enum_dispatch(BluetoothL2capProfileSyncTrait)]
pub enum BluetoothL2capProfileSync {
#[cfg(target_os = "android")]
Android(android::BluetoothRfcommProfile),
Dummy(Dummy),
}
pub struct Dummy {}
impl BluetoothRfcommProfileSyncTrait for Dummy {
fn connectable(&mut self) -> Result<BluetoothRfcommConnectableSync, String> {
unimplemented!()
}
}
impl BluetoothRfcommProfileAsyncTrait for Dummy {
async fn connectable(&mut self) -> Result<BluetoothRfcommConnectableAsync, String> {
unimplemented!()
}
}
#[enum_dispatch::enum_dispatch]
pub trait BluetoothSocketTrait {
fn is_connected(&self) -> Result<bool, std::io::Error>;
fn connect(&mut self) -> Result<(), std::io::Error>;
}
impl std::io::Read for BluetoothSocket {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
#[cfg(target_os = "android")]
BluetoothSocket::Android(a) => a.read(buf),
#[cfg(target_os = "linux")]
BluetoothSocket::Bluez(b) => b.read(buf),
#[cfg(target_os = "windows")]
BluetoothSocket::Windows(w) => w.read(buf),
}
}
}
impl std::io::Write for BluetoothSocket {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
#[cfg(target_os = "android")]
BluetoothSocket::Android(a) => a.write(buf),
#[cfg(target_os = "linux")]
BluetoothSocket::Bluez(b) => b.write(buf),
#[cfg(target_os = "windows")]
BluetoothSocket::Windows(w) => w.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
#[cfg(target_os = "android")]
BluetoothSocket::Android(a) => a.flush(),
#[cfg(target_os = "linux")]
BluetoothSocket::Bluez(b) => b.flush(),
#[cfg(target_os = "windows")]
BluetoothSocket::Windows(w) => w.flush(),
}
}
}
#[enum_dispatch::enum_dispatch(BluetoothSocketTrait)]
pub enum BluetoothSocket {
#[cfg(target_os = "android")]
Android(android::BluetoothSocket),
#[cfg(target_os = "linux")]
Bluez(linux::BluetoothRfcommSocket),
#[cfg(target_os = "windows")]
Windows(windows::BluetoothRfcommSocket),
}