use dbus::nonblock::Proxy;
use dbus_crossroads::{Crossroads, IfaceBuilder, IfaceToken};
use futures::{pin_mut, Future};
use std::{fmt, pin::Pin, sync::Arc};
use strum::IntoStaticStr;
use tokio::{
select,
sync::{oneshot, Mutex},
};
use uuid::Uuid;
use crate::{method_call, Address, Device, Result, SessionInner, ERR_PREFIX, SERVICE_NAME, TIMEOUT};
pub(crate) const INTERFACE: &str = "org.bluez.Agent1";
pub(crate) const MANAGER_INTERFACE: &str = "org.bluez.AgentManager1";
pub(crate) const MANAGER_PATH: &str = "/org/bluez";
pub(crate) const AGENT_PREFIX: &str = publish_path!("agent/");
#[derive(Clone, Copy, Debug, displaydoc::Display, Eq, PartialEq, Ord, PartialOrd, Hash, IntoStaticStr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum ReqError {
Rejected,
Canceled,
}
impl std::error::Error for ReqError {}
impl Default for ReqError {
fn default() -> Self {
Self::Canceled
}
}
impl From<ReqError> for dbus::MethodErr {
fn from(err: ReqError) -> Self {
let name: &'static str = err.into();
Self::from((ERR_PREFIX.to_string() + name, &err.to_string()))
}
}
pub type ReqResult<T> = std::result::Result<T, ReqError>;
#[derive(Debug)]
#[non_exhaustive]
pub struct RequestPinCode {
pub adapter: String,
pub device: Address,
}
pub type RequestPinCodeFn =
Box<dyn (Fn(RequestPinCode) -> Pin<Box<dyn Future<Output = ReqResult<String>> + Send>>) + Send + Sync>;
#[derive(custom_debug::Debug)]
#[non_exhaustive]
pub struct DisplayPinCode {
pub adapter: String,
pub device: Address,
pub pincode: String,
#[debug(skip)]
pub cancel: oneshot::Receiver<()>,
}
pub type DisplayPinCodeFn =
Box<dyn (Fn(DisplayPinCode) -> Pin<Box<dyn Future<Output = ReqResult<()>> + Send>>) + Send + Sync>;
#[derive(Debug)]
#[non_exhaustive]
pub struct RequestPasskey {
pub adapter: String,
pub device: Address,
}
pub type RequestPasskeyFn =
Box<dyn (Fn(RequestPasskey) -> Pin<Box<dyn Future<Output = ReqResult<u32>> + Send>>) + Send + Sync>;
#[derive(custom_debug::Debug)]
#[non_exhaustive]
pub struct DisplayPasskey {
pub adapter: String,
pub device: Address,
pub passkey: u32,
pub entered: u16,
#[debug(skip)]
pub cancel: oneshot::Receiver<()>,
}
pub type DisplayPasskeyFn =
Box<dyn (Fn(DisplayPasskey) -> Pin<Box<dyn Future<Output = ReqResult<()>> + Send>>) + Send + Sync>;
#[derive(Debug)]
#[non_exhaustive]
pub struct RequestConfirmation {
pub adapter: String,
pub device: Address,
pub passkey: u32,
}
pub type RequestConfirmationFn =
Box<dyn (Fn(RequestConfirmation) -> Pin<Box<dyn Future<Output = ReqResult<()>> + Send>>) + Send + Sync>;
#[derive(Debug)]
#[non_exhaustive]
pub struct RequestAuthorization {
pub adapter: String,
pub device: Address,
}
pub type RequestAuthorizationFn =
Box<dyn (Fn(RequestAuthorization) -> Pin<Box<dyn Future<Output = ReqResult<()>> + Send>>) + Send + Sync>;
#[derive(Debug)]
#[non_exhaustive]
pub struct AuthorizeService {
pub adapter: String,
pub device: Address,
pub service: Uuid,
}
pub type AuthorizeServiceFn =
Box<dyn (Fn(AuthorizeService) -> Pin<Box<dyn Future<Output = ReqResult<()>> + Send>>) + Send + Sync>;
#[derive(Default)]
pub struct Agent {
pub request_default: bool,
pub request_pin_code: Option<RequestPinCodeFn>,
pub display_pin_code: Option<DisplayPinCodeFn>,
pub request_passkey: Option<RequestPasskeyFn>,
pub display_passkey: Option<DisplayPasskeyFn>,
pub request_confirmation: Option<RequestConfirmationFn>,
pub request_authorization: Option<RequestAuthorizationFn>,
pub authorize_service: Option<AuthorizeServiceFn>,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl Agent {
pub(crate) fn capability(&self) -> &'static str {
let keyboard = self.request_passkey.is_some() || self.request_pin_code.is_some();
let display_only = self.display_passkey.is_some() || self.display_pin_code.is_some();
let yes_no = self.request_confirmation.is_some()
|| self.request_authorization.is_some()
|| self.authorize_service.is_some();
match (keyboard, display_only, yes_no) {
(true, false, false) => "KeyboardOnly",
(false, true, false) => "DisplayOnly",
(false, _, true) => "DisplayYesNo",
(true, true, _) | (true, _, true) => "KeyboardDisplay",
(false, false, false) => "NoInputNoOutput",
}
}
}
pub(crate) struct RegisteredAgent {
a: Agent,
cancel: Mutex<Option<oneshot::Sender<()>>>,
}
impl RegisteredAgent {
pub(crate) fn new(agent: Agent) -> Self {
Self { a: agent, cancel: Mutex::new(None) }
}
async fn get_cancel(&self) -> oneshot::Receiver<()> {
let (cancel_tx, cancel_rx) = oneshot::channel();
*self.cancel.lock().await = Some(cancel_tx);
cancel_rx
}
async fn call<A, F, R>(&self, f: &Option<impl Fn(A) -> F>, arg: A) -> ReqResult<R>
where
F: Future<Output = ReqResult<R>> + Send + 'static,
{
match f {
Some(f) => f(arg).await,
None => Err(ReqError::Rejected),
}
}
async fn call_with_cancel<A, F, R>(&self, f: &Option<impl Fn(A) -> F>, arg: A) -> ReqResult<R>
where
F: Future<Output = ReqResult<R>> + Send + 'static,
{
let cancel_rx = self.get_cancel().await;
match f {
Some(f) => {
let fut = f(arg);
pin_mut!(fut);
select! {
result = fut => result,
_ = cancel_rx => Err(ReqError::Canceled)
}
}
None => Err(ReqError::Rejected),
}
}
fn parse_device_path(device: &dbus::Path<'static>) -> ReqResult<(String, Address)> {
match Device::parse_dbus_path(device) {
Some((adapter, addr)) => Ok((adapter.to_string(), addr)),
None => {
log::error!("Cannot parse device path {}", &device);
Err(ReqError::Rejected)
}
}
}
pub(crate) fn register_interface(cr: &mut Crossroads) -> IfaceToken<Arc<Self>> {
cr.register(INTERFACE, |ib: &mut IfaceBuilder<Arc<Self>>| {
ib.method_with_cr_async("Cancel", (), (), |ctx, cr, ()| {
method_call(ctx, cr, move |reg: Arc<Self>| async move {
if let Some(cancel_tx) = reg.cancel.lock().await.take() {
let _ = cancel_tx.send(());
}
Ok(())
})
});
ib.method_with_cr_async(
"RequestPinCode",
("device",),
("value",),
|ctx, cr, (device,): (dbus::Path<'static>,)| {
method_call(ctx, cr, |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
Ok((reg
.call_with_cancel(®.a.request_pin_code, RequestPinCode { adapter, device })
.await?,))
})
},
);
ib.method_with_cr_async(
"DisplayPinCode",
("device", "pincode"),
(),
|ctx, cr, (device, pincode): (dbus::Path<'static>, String)| {
method_call(ctx, cr, |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
reg.call(
®.a.display_pin_code,
DisplayPinCode { adapter, device, pincode, cancel: reg.get_cancel().await },
)
.await?;
Ok(())
})
},
);
ib.method_with_cr_async(
"RequestPasskey",
("device",),
("value",),
|ctx, cr, (device,): (dbus::Path<'static>,)| {
method_call(ctx, cr, |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
Ok((reg
.call_with_cancel(®.a.request_passkey, RequestPasskey { adapter, device })
.await?,))
})
},
);
ib.method_with_cr_async(
"DisplayPasskey",
("device", "passkey", "entered"),
(),
|ctx, cr, (device, passkey, entered): (dbus::Path<'static>, u32, u16)| {
method_call(ctx, cr, move |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
reg.call(
®.a.display_passkey,
DisplayPasskey { adapter, device, passkey, entered, cancel: reg.get_cancel().await },
)
.await?;
Ok(())
})
},
);
ib.method_with_cr_async(
"RequestConfirmation",
("device", "passkey"),
(),
|ctx, cr, (device, passkey): (dbus::Path<'static>, u32)| {
method_call(ctx, cr, move |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
reg.call_with_cancel(
®.a.request_confirmation,
RequestConfirmation { adapter, device, passkey },
)
.await?;
Ok(())
})
},
);
ib.method_with_cr_async(
"RequestAuthorization",
("device",),
(),
|ctx, cr, (device,): (dbus::Path<'static>,)| {
method_call(ctx, cr, move |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
reg.call_with_cancel(
®.a.request_authorization,
RequestAuthorization { adapter, device },
)
.await?;
Ok(())
})
},
);
ib.method_with_cr_async(
"AuthorizeService",
("device", "uuid"),
(),
|ctx, cr, (device, uuid): (dbus::Path<'static>, String)| {
method_call(ctx, cr, move |reg: Arc<Self>| async move {
let (adapter, device) = Self::parse_device_path(&device)?;
let service: Uuid = match uuid.parse() {
Ok(service) => service,
Err(_) => {
log::error!("Invalid UUID: {}", &uuid);
return Err(ReqError::Rejected.into());
}
};
reg.call_with_cancel(
®.a.authorize_service,
AuthorizeService { adapter, device, service },
)
.await?;
Ok(())
})
},
);
})
}
pub(crate) async fn register(self, inner: Arc<SessionInner>) -> Result<AgentHandle> {
let name = dbus::Path::new(format!("{}{}", AGENT_PREFIX, Uuid::new_v4().as_simple())).unwrap();
let capability = self.a.capability();
let request_default = self.a.request_default;
log::trace!("Publishing agent at {} with capability {}", &name, &capability);
{
let mut cr = inner.crossroads.lock().await;
cr.insert(name.clone(), &[inner.agent_token], Arc::new(self));
}
log::trace!("Registering agent at {}", &name);
let proxy = Proxy::new(SERVICE_NAME, MANAGER_PATH, TIMEOUT, inner.connection.clone());
let () = proxy.method_call(MANAGER_INTERFACE, "RegisterAgent", (name.clone(), capability)).await?;
let connection = inner.connection.clone();
let (drop_tx, drop_rx) = oneshot::channel();
let unreg_name = name.clone();
tokio::spawn(async move {
let _ = drop_rx.await;
log::trace!("Unregistering agent at {}", &unreg_name);
let _: std::result::Result<(), dbus::Error> =
proxy.method_call(MANAGER_INTERFACE, "UnregisterAgent", (unreg_name.clone(),)).await;
log::trace!("Unpublishing agent at {}", &unreg_name);
let mut cr = inner.crossroads.lock().await;
let _: Option<Self> = cr.remove(&unreg_name);
});
if request_default {
log::trace!("Requesting default agent for {}", &name);
let proxy = Proxy::new(SERVICE_NAME, MANAGER_PATH, TIMEOUT, connection);
let () = proxy.method_call(MANAGER_INTERFACE, "RequestDefaultAgent", (name.clone(),)).await?;
}
Ok(AgentHandle { name, _drop_tx: drop_tx })
}
}
#[must_use = "AgentHandle must be held for agent to be registered"]
pub struct AgentHandle {
name: dbus::Path<'static>,
_drop_tx: oneshot::Sender<()>,
}
impl Drop for AgentHandle {
fn drop(&mut self) {
}
}
impl fmt::Debug for AgentHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "AgentHandle {{ {} }}", &self.name)
}
}