use alloc::boxed::Box;
use alloc::format;
use core::any::Any;
use core::future::Future;
use core::ops::Deref;
use core::pin::Pin;
use anyhow::{Context, ensure};
use wasefire_wire::Yoke;
use crate::{Api, ApiResult, Request, Service, VERSION};
#[derive(PartialEq, Eq)]
pub struct Device<T: Connection> {
version: u32,
connection: T,
}
pub type DynDevice = Device<Box<dyn Connection>>;
impl<T: Connection> Device<T> {
pub async fn new(connection: T) -> anyhow::Result<Self> {
let version = *connection.call::<crate::ApiVersion>(()).await?.get();
ensure!(version <= VERSION, "the device is more recent than the host");
Ok(Device { version, connection })
}
pub fn version(&self) -> u32 {
self.version
}
pub fn connection(self) -> T {
self.connection
}
pub fn supports<S: Service>(&self) -> bool {
S::VERSIONS.contains(self.version).unwrap()
}
pub async fn platform_info(&self) -> anyhow::Result<crate::platform::DynInfo> {
if self.supports::<crate::PlatformInfo3>() {
Ok(crate::platform::DynInfo::V3(self.call::<crate::PlatformInfo3>(()).await?))
} else if self.supports::<crate::_PlatformInfo2>() {
Ok(crate::platform::DynInfo::V2(self.call::<crate::_PlatformInfo2>(()).await?))
} else if self.supports::<crate::_PlatformInfo1>() {
Ok(crate::platform::DynInfo::V1(self.call::<crate::_PlatformInfo1>(()).await?))
} else if self.supports::<crate::_PlatformInfo0>() {
Ok(crate::platform::DynInfo::V0(self.call::<crate::_PlatformInfo0>(()).await?))
} else {
Err(wasefire_error::Error::world(wasefire_error::Code::NotImplemented).into())
}
}
}
impl<T: Connection> Deref for Device<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.connection
}
}
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output = anyhow::Result<T>> + 'a>>;
pub trait Connection: Any {
fn write<'a>(&'a self, request: &'a [u8]) -> DynFuture<'a, ()>;
fn read(&self) -> DynFuture<'_, Box<[u8]>>;
}
impl Connection for Box<dyn Connection> {
fn write<'a>(&'a self, request: &'a [u8]) -> DynFuture<'a, ()> {
(**self).write(request)
}
fn read(&self) -> DynFuture<'_, Box<[u8]>> {
(**self).read()
}
}
pub trait ConnectionExt: Connection {
fn call<S: Service>(
&self, request: S::Request<'_>,
) -> impl Future<Output = anyhow::Result<Yoke<S::Response<'static>>>> {
async { self.call_ref::<S>(&S::request(request)).await }
}
fn call_ref<S: Service>(
&self, request: &Api<Request>,
) -> impl Future<Output = anyhow::Result<Yoke<S::Response<'static>>>> {
async {
self.send(request).await.with_context(|| format!("sending {}", S::NAME))?;
self.receive::<S>().await.with_context(|| format!("receiving {}", S::NAME))
}
}
fn send(&self, request: &Api<'_, Request>) -> impl Future<Output = anyhow::Result<()>> {
async {
let request = request.encode().context("encoding request")?;
self.write(&request).await
}
}
fn receive<S: Service>(
&self,
) -> impl Future<Output = anyhow::Result<Yoke<S::Response<'static>>>> {
async {
let response = self.read().await?;
let response = ApiResult::<S>::decode_yoke(response).context("decoding response")?;
response.try_map(|x| match x {
ApiResult::Ok(x) => Ok(x),
ApiResult::Err(error) => Err(anyhow::Error::new(error)),
})
}
}
}
impl<T: Connection + ?Sized> ConnectionExt for T {}