use core::ffi::c_void;
use crate::abi::{Header, Layout, LayoutField, PointFlags, PointVerdict, Str};
use crate::load::hand_over;
use crate::points::{Mode, Order};
use crate::{Load, Off};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct DescribedField {
pub name: String,
pub hash: u64,
}
impl DescribedField {
#[must_use]
pub fn new(name: &str, hash: u64) -> Self {
Self {
name: name.to_owned(),
hash,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct DescribedPoint {
pub name: String,
pub mode: Mode,
pub refusable: bool,
pub input: Vec<DescribedField>,
pub input_size: u32,
pub output_hash: u64,
pub output_size: u32,
}
impl DescribedPoint {
#[must_use]
pub fn new(name: &str, mode: Mode) -> Self {
Self {
name: name.to_owned(),
mode,
refusable: false,
input: Vec::new(),
input_size: 0,
output_hash: 0,
output_size: 0,
}
}
#[must_use]
pub fn with_input(mut self, fields: Vec<DescribedField>, size: u32) -> Self {
self.input = fields;
self.input_size = size;
self
}
#[must_use]
pub const fn with_output(mut self, hash: u64, size: u32) -> Self {
self.output_hash = hash;
self.output_size = size;
self
}
#[must_use]
pub const fn refusable(mut self, refusable: bool) -> Self {
self.refusable = refusable;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DescribedVerdict {
Pass,
Set(Vec<u8>),
Refuse,
}
struct Link<F> {
input_size: usize,
output_size: usize,
changes: bool,
refusable: bool,
run: F,
}
impl Load<'_> {
pub unsafe fn extend_described_point<F>(
&mut self,
point: &DescribedPoint,
order: &Order,
link: F,
) -> Result<(), Off>
where
F: Fn(&[u8], &[u8]) -> DescribedVerdict + Send + Sync + 'static,
{
let input: Vec<LayoutField> = point
.input
.iter()
.map(|f| LayoutField {
name: Str::new(&f.name),
hash: f.hash,
})
.collect();
let output = [LayoutField {
name: Str::new("output"),
hash: point.output_hash,
}];
let count = |n: usize| u32::try_from(n).unwrap_or(u32::MAX);
let spec = crate::abi::PointSpec {
header: Header::of::<crate::abi::PointSpec>(),
name: Str::new(&point.name),
mode: point.mode.to_abi(),
flags: if point.refusable {
PointFlags::REFUSE
} else {
PointFlags::NONE
},
input: Layout {
fields: input.as_ptr(),
count: count(input.len()),
size: point.input_size,
},
output: Layout {
fields: output.as_ptr(),
count: 1,
size: point.output_size,
},
};
let before: Vec<Str> = order.before.iter().map(|id| Str::new(id)).collect();
let after: Vec<Str> = order.after.iter().map(|id| Str::new(id)).collect();
let raw = crate::abi::PointLink {
header: Header::of::<crate::abi::PointLink>(),
point: &raw const spec,
before: before.as_ptr(),
after: after.as_ptr(),
before_count: count(before.len()),
after_count: count(after.len()),
priority: order.priority,
};
let host = self.host();
let link = Link {
input_size: usize::try_from(point.input_size).unwrap_or(0),
output_size: usize::try_from(point.output_size).unwrap_or(0),
changes: point.mode.changes(),
refusable: point.refusable,
run: link,
};
hand_over(
link,
|user| {
(host.api().attach_point)(host.handle(), &raw const raw, run_described::<F>, user)
},
&format!("point {}", point.name),
)
}
}
unsafe extern "system" fn run_described<F>(
input: *const c_void,
value: *mut c_void,
user: *mut c_void,
) -> PointVerdict
where
F: Fn(&[u8], &[u8]) -> DescribedVerdict,
{
let link = unsafe { &*user.cast::<Link<F>>() };
if input.is_null() || value.is_null() {
return PointVerdict::PASS;
}
let input = unsafe { core::slice::from_raw_parts(input.cast::<u8>(), link.input_size) };
let answer = unsafe { core::slice::from_raw_parts_mut(value.cast::<u8>(), link.output_size) };
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (link.run)(input, answer))) {
Ok(DescribedVerdict::Set(bytes)) if link.changes && bytes.len() == answer.len() => {
answer.copy_from_slice(&bytes);
PointVerdict::SET
}
Ok(DescribedVerdict::Refuse) if link.refusable && link.changes => PointVerdict::REFUSE,
Ok(_) => PointVerdict::PASS,
Err(_) => PointVerdict::PANICKED,
}
}