use std::fmt::Formatter;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use pin_project::{pin_project, pinned_drop};
use structbuf::{Pack, Packer, StructBuf};
use tokio_util::sync::WaitForCancellationFutureOwned;
use tracing::debug;
use burble_const::Uuid;
use crate::name_of;
use super::*;
pub type IoResult = std::result::Result<(), ErrorCode>;
#[derive(Clone)]
#[repr(transparent)]
pub struct Io(Arc<dyn for<'a> Fn(IoReq<'a>) -> IoResult + Send + Sync>);
impl Io {
pub const NONE: () = ();
#[inline(always)]
pub fn map<T: Send + Sync + 'static>(
this: &Arc<T>,
f: impl Fn(&T, IoReq) -> IoResult + Send + Sync + 'static,
) -> Self {
let this = Arc::clone(this);
Self(Arc::new(move |req: IoReq| f(&this, req)))
}
}
impl Debug for Io {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
(f.debug_tuple(name_of!(Io)).field(&Arc::as_ptr(&self.0))).finish()
}
}
impl<T: Fn(IoReq) -> IoResult + Send + Sync + 'static> From<T> for Io {
#[inline(always)]
fn from(f: T) -> Self {
Self(Arc::new(f))
}
}
#[doc(hidden)]
impl From<()> for Io {
fn from(_: ()) -> Self {
lazy_static::lazy_static! {
static ref IO: Io = Io(Arc::new(|_: IoReq| unreachable!()));
}
Self(Arc::clone(&IO.0))
}
}
#[derive(Debug, Default)]
pub(super) struct IoMap(pub(super) BTreeMap<Handle, Io>);
impl IoMap {
#[inline(always)]
pub fn read(&self, r: &mut ReadReq) -> IoResult {
self.exec(r.hdl, IoReq::Read(r))
}
#[inline(always)]
pub fn write(&self, w: &WriteReq) -> IoResult {
self.exec(w.hdl, IoReq::Write(w))
}
#[inline(always)]
pub fn notify(&self, n: NotifyReq) -> IoResult {
self.exec(n.hdl, IoReq::Notify(n))
}
#[inline]
fn exec(&self, hdl: Handle, req: IoReq) -> IoResult {
(self.0.get(&hdl).ok_or(ErrorCode::UnlikelyError)).and_then(|io| io.0(req))
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum IoReq<'a> {
Read(&'a mut ReadReq),
Write(&'a WriteReq<'a>),
Notify(NotifyReq),
}
#[derive(Debug)]
pub struct ReadReq {
pub(super) op: Opcode,
pub(super) hdl: Handle,
pub(super) uuid: Uuid,
pub(super) off: u16,
pub(super) buf: StructBuf,
}
impl ReadReq {
#[inline(always)]
pub(super) const fn new(op: Opcode, mtu: u16) -> Self {
Self {
op,
hdl: Handle::MAX,
uuid: Uuid::MAX,
off: 0,
buf: StructBuf::new(mtu as _),
}
}
#[inline(always)]
pub(super) fn with(&mut self, hdl: Handle, uuid: Uuid, off: u16) -> &mut Self {
self.hdl = hdl;
self.uuid = uuid;
self.off = off;
self.buf.clear();
self
}
#[inline(always)]
#[must_use]
pub const fn handle(&self) -> Handle {
self.hdl
}
#[inline(always)]
#[must_use]
pub const fn uuid(&self) -> Uuid {
self.uuid
}
#[inline(always)]
#[must_use]
pub const fn offset(&self) -> usize {
self.off as _
}
#[inline]
pub fn complete(&mut self, v: impl AsRef<[u8]>) -> IoResult {
self.partial((v.as_ref().get(self.offset()..)).ok_or(ErrorCode::InvalidOffset)?)
}
#[inline]
pub fn partial(&mut self, v: impl AsRef<[u8]>) -> IoResult {
let v = v.as_ref();
self.buf.clear();
(self.buf).put_at(0, unsafe { v.get_unchecked(..v.len().min(self.buf.lim())) });
Ok(())
}
}
#[derive(Debug)]
pub struct WriteReq<'a> {
pub(super) op: Opcode,
pub(super) hdl: Handle,
pub(super) uuid: Uuid,
pub(super) off: u16,
pub(super) val: &'a [u8],
}
impl<'a> WriteReq<'a> {
#[inline(always)]
#[must_use]
pub const fn handle(&self) -> Handle {
self.hdl
}
#[inline(always)]
#[must_use]
pub const fn uuid(&self) -> Uuid {
self.uuid
}
#[inline(always)]
#[must_use]
pub const fn offset(&self) -> usize {
self.off as _
}
#[inline(always)]
#[must_use]
pub const fn value(&self) -> &'a [u8] {
self.val
}
#[inline]
pub fn update(&self, mut dst: impl AsMut<[u8]>) -> IoResult {
let Some(dst) = dst.as_mut().get_mut(self.off as usize..) else {
return Err(ErrorCode::InvalidOffset);
};
let Some(dst) = dst.get_mut(..self.val.len()) else {
return Err(ErrorCode::InvalidAttributeValueLength);
};
dst.copy_from_slice(self.val);
Ok(())
}
}
impl<'a> AsRef<[u8]> for WriteReq<'a> {
#[inline(always)]
fn as_ref(&self) -> &'a [u8] {
self.val
}
}
#[derive(Debug)]
pub struct NotifyReq {
pub(super) hdl: Handle,
pub(super) uuid: Uuid,
pub(super) mtu: u16,
pub(super) ind: bool,
pub(super) tx: tokio::sync::mpsc::Sender<NotifyVal>,
pub(super) ct: tokio_util::sync::CancellationToken,
}
impl NotifyReq {
#[inline(always)]
#[must_use]
pub const fn handle(&self) -> Handle {
self.hdl
}
#[inline(always)]
#[must_use]
pub const fn uuid(&self) -> Uuid {
self.uuid
}
pub fn notify(&self, f: impl FnOnce(&mut Packer)) -> Notify {
let mut val = StructBuf::new(usize::from(self.mtu) - 3);
f(&mut val.append());
let (hdl, ind) = (self.hdl, self.ind);
let ct = self.ct.clone().cancelled_owned();
let (tx, rx) = tokio::sync::oneshot::channel();
Notify {
val: Some(NotifyVal { hdl, val, ind, tx }),
tx: tokio_util::sync::PollSender::new(self.tx.clone()),
rx,
ct,
}
}
#[inline(always)]
pub async fn closed(&self) {
self.ct.cancelled().await;
}
#[inline(always)]
#[must_use]
pub fn is_closed(&self) -> bool {
self.ct.is_cancelled()
}
}
impl Drop for NotifyReq {
fn drop(&mut self) {
if !self.ct.is_cancelled() {
debug!(
"Service cancelled notify request for {} {}",
self.uuid.typ(),
self.hdl
);
self.ct.cancel();
}
}
}
#[pin_project(PinnedDrop)]
#[derive(Debug)]
pub struct Notify {
val: Option<NotifyVal>,
tx: tokio_util::sync::PollSender<NotifyVal>,
#[pin]
rx: tokio::sync::oneshot::Receiver<Result<()>>,
#[pin]
ct: WaitForCancellationFutureOwned,
}
impl Future for Notify {
type Output = Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
const CLOSED: Poll<Result<()>> = Poll::Ready(Err(Error::NotifyClosed));
let this = self.project();
if this.ct.poll(cx).is_ready() {
return CLOSED;
}
if this.val.is_some() {
if ready!(this.tx.poll_reserve(cx)).is_err() {
return CLOSED;
}
let val = unsafe { this.val.take().unwrap_unchecked() };
if this.tx.send_item(val).is_err() {
return CLOSED;
}
this.tx.close();
}
match this.rx.poll(cx) {
Poll::Ready(Ok(r)) => Poll::Ready(r),
Poll::Ready(Err(_)) => CLOSED,
Poll::Pending => Poll::Pending,
}
}
}
#[pinned_drop]
impl PinnedDrop for Notify {
#[inline(always)]
fn drop(self: Pin<&mut Self>) {
self.project().tx.abort_send();
}
}
#[derive(Debug)]
pub(super) struct NotifyVal {
hdl: Handle,
val: StructBuf,
ind: bool,
tx: tokio::sync::oneshot::Sender<Result<()>>, }
impl NotifyVal {
#[inline]
pub async fn exec(self, br: &mut Bearer) {
let _ = self.tx.send(if self.ind {
br.handle_value_ind(self.hdl, self.val.as_ref()).await
} else {
br.handle_value_ntf(self.hdl, self.val.as_ref()).await
});
}
}