use std::fmt;
use ::btleplug::api::Peripheral;
use async_trait::async_trait;
use hex::ToHex;
use tracing::{info, instrument};
use crate::transport::device::Device;
use crate::transport::error::TransportError;
use crate::webauthn::error::Error;
use super::btleplug::manager::SupportedRevisions;
use super::btleplug::{supported_fido_revisions, FidoDevice as BtleplugFidoDevice};
use super::channel::BleChannel;
use super::{btleplug, Ble};
pub async fn is_available() -> bool {
btleplug::is_available().await
}
#[instrument]
pub async fn list_devices() -> Result<Vec<BleDevice>, Error> {
let devices: Vec<_> = btleplug::list_fido_devices()
.await
.or(Err(Error::Transport(TransportError::TransportUnavailable)))?
.iter()
.map(|bluez_device| bluez_device.into())
.collect();
info!({ count = devices.len() }, "Listing available BLE devices");
Ok(devices)
}
#[derive(Debug, Clone)]
pub struct BleDevice {
pub btleplug_device: BtleplugFidoDevice,
pub revisions: Option<SupportedRevisions>,
}
impl BleDevice {
pub fn alias(&self) -> String {
match &self.btleplug_device.properties.local_name {
Some(local_name) => local_name.clone(),
None => self.btleplug_device.properties.address.encode_hex(),
}
}
pub async fn is_connected(&self) -> bool {
self.btleplug_device
.peripheral
.is_connected()
.await
.unwrap_or(false)
}
}
impl From<&BtleplugFidoDevice> for BleDevice {
fn from(btleplug_device: &BtleplugFidoDevice) -> Self {
Self {
btleplug_device: btleplug_device.clone(),
revisions: None,
}
}
}
impl From<&BleDevice> for BtleplugFidoDevice {
fn from(device: &BleDevice) -> Self {
device.btleplug_device.clone()
}
}
impl fmt::Display for BleDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.alias())
}
}
#[async_trait]
impl<'d> Device<'d, Ble, BleChannel<'d>> for BleDevice {
async fn channel(&'d mut self) -> Result<BleChannel<'d>, Error> {
let revisions = self.supported_revisions().await?;
let channel = BleChannel::new(self, &revisions).await?;
Ok(channel)
}
}
impl BleDevice {
async fn supported_revisions(&mut self) -> Result<SupportedRevisions, Error> {
let revisions = match self.revisions {
None => {
let revisions = supported_fido_revisions(&self.btleplug_device.peripheral)
.await
.or(Err(Error::Transport(TransportError::NegotiationFailed)))?;
self.revisions = Some(revisions);
revisions
}
Some(revisions) => revisions,
};
Ok(revisions)
}
}