use core::ffi::c_void;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
use crate::abi::{CallSpec, Header, Layout, LayoutField, Status, Str};
use crate::load::{hand_over, ok};
use crate::points::DescribedField;
use crate::{Host, Load, Off};
pub unsafe trait Call: Copy + 'static {
const NAME: &'static str;
const MAJOR: u32;
const LAYOUT: &'static [LayoutField];
const OUTPUT: &'static [LayoutField];
const SCHEMA: &'static str;
type Output: Copy + 'static;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, thiserror::Error)]
#[non_exhaustive]
pub enum CallError {
#[error("no running mod serves the call as declared here")]
Unmatched,
#[error("the serving mod or its handler is off")]
Off,
#[error("the server's handler failed")]
Failed,
}
fn error_of(status: Status) -> Option<CallError> {
match status {
Status::OK => None,
Status::MISSING => Some(CallError::Unmatched),
Status::REFUSED => Some(CallError::Off),
_ => Some(CallError::Failed),
}
}
fn spec<C: Call>() -> CallSpec {
CallSpec {
header: Header::of::<CallSpec>(),
name: Str::new(C::NAME),
major: C::MAJOR,
input: crate::services::layout(C::LAYOUT, size_of::<C>()),
output: crate::services::layout(C::OUTPUT, size_of::<C::Output>()),
}
}
#[derive(Debug)]
pub struct Caller<C> {
host: Host,
id: u32,
_call: PhantomData<fn(C)>,
}
impl<C> Clone for Caller<C> {
fn clone(&self) -> Self {
*self
}
}
impl<C> Copy for Caller<C> {}
impl<C: Call> Caller<C> {
pub fn call(&self, input: &C) -> Result<C::Output, CallError> {
let mut answer = core::mem::MaybeUninit::<C::Output>::uninit();
let status = (self.host.api().call)(
self.host.handle(),
self.id,
core::ptr::from_ref(input).cast(),
answer.as_mut_ptr().cast(),
);
match error_of(status) {
None => Ok(unsafe { answer.assume_init() }),
Some(error) => Err(error),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct DescribedCall {
pub name: String,
pub major: u32,
pub input: Vec<DescribedField>,
pub input_size: u32,
pub output: Vec<DescribedField>,
pub output_size: u32,
}
impl DescribedCall {
#[must_use]
pub fn new(
name: &str,
major: u32,
input: (Vec<DescribedField>, u32),
output: (Vec<DescribedField>, u32),
) -> Self {
Self {
name: name.to_owned(),
major,
input: input.0,
input_size: input.1,
output: output.0,
output_size: output.1,
}
}
fn with_spec<R>(&self, with: impl FnOnce(&CallSpec) -> R) -> R {
let raw = |fields: &[DescribedField]| -> Vec<LayoutField> {
fields
.iter()
.map(|f| LayoutField {
name: Str::new(&f.name),
hash: f.hash,
})
.collect()
};
let (input, output) = (raw(&self.input), raw(&self.output));
let layout = |fields: &[LayoutField], size: u32| Layout {
fields: fields.as_ptr(),
count: u32::try_from(fields.len()).unwrap_or(u32::MAX),
size,
};
let spec = CallSpec {
header: Header::of::<CallSpec>(),
name: Str::new(&self.name),
major: self.major,
input: layout(&input, self.input_size),
output: layout(&output, self.output_size),
};
with(&spec)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DescribedCaller {
host: Host,
id: u32,
input_size: usize,
output_size: usize,
}
impl DescribedCaller {
pub fn call(&self, input: &[u8], output: &mut [u8]) -> Result<(), CallError> {
if input.len() != self.input_size || output.len() != self.output_size {
return Err(CallError::Failed);
}
let mut none = 0u8;
let input_ptr = if input.is_empty() {
(&raw const none).cast()
} else {
input.as_ptr().cast()
};
let output_ptr = if output.is_empty() {
(&raw mut none).cast()
} else {
output.as_mut_ptr().cast()
};
let status = (self.host.api().call)(self.host.handle(), self.id, input_ptr, output_ptr);
error_of(status).map_or(Ok(()), Err)
}
}
struct Described<F> {
input_size: usize,
output_size: usize,
run: F,
}
impl Load<'_> {
pub fn serve_call<C, F>(&mut self, handler: F) -> Result<(), Off>
where
C: Call,
F: Fn(&C) -> C::Output + Send + Sync + 'static,
{
let host = self.host();
let spec = spec::<C>();
hand_over(
Served::new(C::NAME, handler),
|user| (host.api().serve_call)(host.handle(), &raw const spec, run_typed::<C, F>, user),
&format!(
"call {} v{}: named after another mod or served twice",
C::NAME,
C::MAJOR
),
)
}
pub fn caller<C: Call>(&mut self) -> Result<Caller<C>, Off> {
let spec = spec::<C>();
let mut id = 0;
let status =
(self.host().api().find_call)(self.host().handle(), &raw const spec, &raw mut id);
ok(
status,
Caller {
host: self.host(),
id,
_call: PhantomData,
},
|| format!("call {} v{}", C::NAME, C::MAJOR),
)
}
pub unsafe fn serve_described_call<F>(
&mut self,
call: &DescribedCall,
handler: F,
) -> Result<(), Off>
where
F: Fn(&[u8], &mut [u8]) -> bool + Send + Sync + 'static,
{
let host = self.host();
let server = Served::new(
&call.name,
Described {
input_size: usize::try_from(call.input_size).unwrap_or(0),
output_size: usize::try_from(call.output_size).unwrap_or(0),
run: handler,
},
);
call.with_spec(|spec| {
hand_over(
server,
|user| (host.api().serve_call)(host.handle(), spec, run_described::<F>, user),
&format!(
"call {} v{}: named after another mod or served twice",
call.name, call.major
),
)
})
}
pub unsafe fn described_caller(
&mut self,
call: &DescribedCall,
) -> Result<DescribedCaller, Off> {
let host = self.host();
let mut id = 0;
let status =
call.with_spec(|spec| (host.api().find_call)(host.handle(), spec, &raw mut id));
ok(
status,
DescribedCaller {
host,
id,
input_size: usize::try_from(call.input_size).unwrap_or(0),
output_size: usize::try_from(call.output_size).unwrap_or(0),
},
|| format!("call {} v{}", call.name, call.major),
)
}
}
pub(crate) struct Served<F> {
name: String,
off: AtomicBool,
run: F,
}
impl<F> Served<F> {
pub(crate) fn new(name: &str, run: F) -> Self {
Self {
name: name.to_owned(),
off: AtomicBool::new(false),
run,
}
}
fn panicked(&self) -> Status {
if !self.off.swap(true, Ordering::AcqRel) {
crate::warn!(
"call {}: the handler panicked and is off until the next start; calls answer that the server is off",
self.name
);
}
Status::FAULTED
}
}
pub(crate) unsafe extern "system" fn run_typed<C: Call, F: Fn(&C) -> C::Output>(
input: *const c_void,
output: *mut c_void,
user: *mut c_void,
) -> Status {
let served = unsafe { &*user.cast::<Served<F>>() };
if served.off.load(Ordering::Acquire) {
return Status::REFUSED;
}
if input.is_null() || output.is_null() {
return Status::BAD_ARGS;
}
let input = unsafe { input.cast::<C>().read_unaligned() };
let output = output.cast::<C::Output>();
let Ok(answer) =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (served.run)(&input)))
else {
return served.panicked();
};
unsafe { output.write_unaligned(answer) };
Status::OK
}
unsafe extern "system" fn run_described<F: Fn(&[u8], &mut [u8]) -> bool>(
input: *const c_void,
output: *mut c_void,
user: *mut c_void,
) -> Status {
let served = unsafe { &*user.cast::<Served<Described<F>>>() };
if served.off.load(Ordering::Acquire) {
return Status::REFUSED;
}
if input.is_null() || output.is_null() {
return Status::BAD_ARGS;
}
let sized = &served.run;
let input = unsafe { core::slice::from_raw_parts(input.cast::<u8>(), sized.input_size) };
unsafe { core::ptr::write_bytes(output.cast::<u8>(), 0, sized.output_size) };
let answer = unsafe { core::slice::from_raw_parts_mut(output.cast::<u8>(), sized.output_size) };
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (sized.run)(input, answer))) {
Ok(true) => Status::OK,
Ok(false) => Status::FAULTED,
Err(_) => served.panicked(),
}
}