use std::collections::BTreeMap;
use std::fmt::{Debug, Display, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use std::time::Duration;
use structbuf::{Pack, Packer};
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, warn};
pub use {adv::*, cmd::*, consts::*, event::*, handle::*};
use crate::{host, le, smp, SyncMutex};
mod adv;
#[path = "cmd/cmd.rs"]
mod cmd;
mod consts;
#[path = "event/event.rs"]
mod event;
mod handle;
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Host(#[from] host::Error),
#[error("HCI error: {status}")]
Hci {
#[from]
status: Status,
},
#[error("invalid event: {0:02X?}")]
InvalidEvent(Vec<u8>),
#[error("unknown event [code={code:#04X}, subcode={subcode:#04X}]: {params:02X?}")]
UnknownEvent {
code: u8,
subcode: u8,
params: Vec<u8>,
},
#[error("duplicate {opcode} commands issued")]
DuplicateCommands { opcode: Opcode },
#[error("command quota exceeded")]
CommandQuotaExceeded,
#[error("{opcode} command failed: {status}")]
CommandFailed { opcode: Opcode, status: Status },
#[error("{opcode} command aborted: {status}")]
CommandAborted { opcode: Opcode, status: Status },
#[error("{opcode} command timeout")]
CommandTimeout { opcode: Opcode },
}
impl Error {
#[must_use]
pub const fn status(&self) -> Option<Status> {
use Error::*;
match *self {
Hci { status } | CommandFailed { status, .. } | CommandAborted { status, .. } => {
Some(status)
}
Host(_)
| InvalidEvent(_)
| UnknownEvent { .. }
| DuplicateCommands { .. }
| CommandQuotaExceeded
| CommandTimeout { .. } => None,
}
}
#[must_use]
pub const fn is_timeout(&self) -> bool {
use Error::*;
match *self {
Host(e) => e.is_timeout(),
CommandTimeout { .. } => true,
Hci { .. }
| InvalidEvent(_)
| UnknownEvent { .. }
| DuplicateCommands { .. }
| CommandQuotaExceeded
| CommandFailed { .. }
| CommandAborted { .. } => false,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
type CommandTransfer = SyncMutex<Option<Box<dyn host::Transfer>>>;
#[derive(Clone, Debug)]
pub struct Host {
transport: Arc<dyn host::Transport>,
router: Arc<EventRouter>,
cmd: Arc<CommandTransfer>,
}
impl Host {
#[inline]
#[must_use]
pub fn new(t: Arc<dyn host::Transport>) -> Self {
Self {
transport: t,
router: EventRouter::new(),
cmd: Arc::new(CommandTransfer::default()),
}
}
#[inline]
#[must_use]
pub const fn transport(&self) -> &Arc<dyn host::Transport> {
&self.transport
}
#[inline]
pub(crate) fn events(&self) -> EventStream {
self.router.events(Opcode::None).unwrap() }
#[inline(always)]
pub(crate) fn conn(&self, hdl: ConnHandle) -> Option<ConnWatch> {
self.router.conn(hdl)
}
#[inline]
pub(crate) fn update_conn(&self, hdl: ConnHandle, f: impl FnOnce(&mut Conn)) {
self.router.update_conn(hdl, f);
}
pub async fn init(&self) -> Result<()> {
self.reset().await?;
let all = enum_iterator::all().collect();
match self.set_event_mask(&all).await {
Err(e) if e.is_timeout() => self.set_event_mask(&all).await,
r => r,
}?;
let _ignore_unknown = self.set_event_mask_page_2(&all).await;
self.le_set_event_mask(&all).await?;
let _ignore_unknown = self.write_le_host_support(true).await;
Ok(())
}
#[inline]
async fn next_event(&self) -> Result<Event> {
self.router.next(self.transport.as_ref()).await
}
#[inline]
#[must_use]
pub fn event_loop(&self) -> EventLoop {
let c = CancellationToken::new();
EventLoop {
h: tokio::spawn(EventLoop::run(self.clone(), c.clone())),
c: c.clone(),
_g: c.drop_guard(),
}
}
#[inline]
async fn exec(&self, opcode: Opcode) -> Result<Event> {
self.exec_params(opcode, |_| {}).await
}
#[inline]
async fn exec_params(
&self,
opcode: Opcode,
f: impl FnOnce(&mut Packer) + Send,
) -> Result<Event> {
let mut cmd = Command::new(self, opcode);
f(&mut cmd.append());
cmd.exec().await.map_err(|e| {
error!("{opcode} error: {e}");
e
})
}
#[inline]
fn new_cmd(&self) -> Box<dyn host::Transfer> {
self.cmd.lock().take().map_or_else(
|| self.transport.command(),
|mut cmd| {
cmd.reset();
cmd
},
)
}
}
#[derive(Debug)]
pub struct EventLoop {
h: tokio::task::JoinHandle<Result<()>>,
c: CancellationToken,
_g: tokio_util::sync::DropGuard,
}
impl EventLoop {
#[inline]
pub async fn stop(self) -> Result<()> {
self.c.cancel();
self.h.await.expect("event loop panic")
}
async fn run(h: Host, c: CancellationToken) -> Result<()> {
debug!("Event loop started");
let r = loop {
let r: Result<Event> = tokio::select! {
r = h.next_event() => r,
_ = c.cancelled() => {
debug!("Event loop terminating");
break Ok(());
}
};
if let Err(e) = r {
error!("Event loop error: {e}");
break Err(e);
}
};
let mut reset = h.new_cmd();
reset.append().u16(Opcode::Reset).u8(0);
match reset.submit() {
Ok(fut) => {
if let Err(e) = fut.await {
warn!("Failed to reset controller: {e}");
} else {
debug!("Submitted controller reset command");
}
}
Err(e) => warn!("Failed to submit reset command: {e}"),
}
r
}
}
impl Future for EventLoop {
type Output = Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(ready!(Pin::new(&mut self.h).poll(cx)).unwrap())
}
}
pub(crate) type ConnWatch = tokio::sync::watch::Receiver<Conn>;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Conn {
pub role: Role,
pub local_addr: Option<le::Addr>,
pub peer_addr: le::Addr,
pub sec: ConnSec,
pub bond_id: Option<smp::BondId>,
}
impl Conn {
#[inline(always)]
const fn new(e: &LeConnectionComplete) -> Self {
Self {
role: e.role,
local_addr: None,
peer_addr: e.peer_addr,
sec: ConnSec::empty(),
bond_id: None,
}
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(transparent)]
#[serde(transparent)]
pub(crate) struct ConnSec: u8 {
const AUTHN = 1 << 0;
const AUTHZ = 1 << 1;
const BOND = 1 << 2;
const KEY_LEN = 0x1F << 3;
}
}
impl ConnSec {
#[inline(always)]
pub const fn key_len(n: u8) -> Self {
assert!(56 <= n && n <= 128 && n % 8 == 0);
Self::from_bits_retain(n)
}
}
impl Display for ConnSec {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let k = self.intersection(Self::KEY_LEN).bits();
if k == 0 {
return f.write_str("Unencrypted");
}
let mut t = f.debug_tuple("Encrypted");
t.field(&format_args!("{k}-bit"));
if self.contains(Self::AUTHN) {
t.field(&format_args!("AUTHN"));
}
if self.contains(Self::AUTHZ) {
t.field(&format_args!("AUTHZ"));
}
if self.contains(Self::BOND) {
t.field(&format_args!("BOND"));
}
t.finish()
}
}
#[inline]
pub(crate) fn duration_10ms(ticks: u16) -> Duration {
Duration::from_millis(u64::from(ticks) * 10)
}
#[inline]
pub(crate) fn duration_1250us(ticks: u16) -> Duration {
Duration::from_micros(u64::from(ticks) * 1250)
}
#[inline]
pub(crate) fn ticks_10ms(d: Duration) -> Option<u16> {
ticks_ms(d, 10)
}
#[inline]
pub(crate) fn ticks_1250us(d: Duration) -> Option<u16> {
ticks_us(d, 1250)
}
#[inline]
pub(crate) fn ticks_625us(d: Duration) -> Option<u32> {
ticks_us(d, 625)
}
#[inline]
fn ticks_ms<T>(d: Duration, tick: u16) -> Option<T>
where
T: Default + Eq + Ord + TryFrom<u128>,
{
if d.is_zero() {
return Some(T::default());
}
T::try_from((d.as_millis() / u128::from(tick)).max(1)).ok()
}
#[inline]
fn ticks_us<T>(d: Duration, tick: u16) -> Option<T>
where
T: Default + Ord + TryFrom<u128>,
{
if d.is_zero() {
return Some(T::default());
}
T::try_from((d.as_micros() / u128::from(tick)).max(1)).ok()
}