pluginop_wasm/
lib.rs

1//! A sub-crate of `pluginop` that should be imported by plugins.
2//!
3//! Playing directly with WebAssembly export functions can be cumbersome.
4//! Instead, we propose a crate offering wrappers for these external calls,
5//! making the plugin development possible by only relying on safe Rust.
6
7use std::cell::UnsafeCell;
8use std::convert::TryInto;
9use std::mem;
10use std::ops::Deref;
11
12pub use pluginop_common::quic;
13use pluginop_common::APIResult;
14pub use pluginop_common::PluginOp;
15use pluginop_common::WASMLen;
16use pluginop_common::WASMPtr;
17
18use pluginop_common::quic::Registration;
19pub use pluginop_common::Bytes;
20pub use pluginop_common::PluginVal;
21use serde::{Deserialize, Serialize};
22use std::convert::TryFrom;
23pub use std::time::Duration;
24pub use unix_time::Instant as UnixInstant;
25
26/// The maximum size of a result, may be subject to future changes.
27const SIZE: usize = 1500;
28
29/// Errors that may occur when interacting with the Plugin API.
30#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
31pub enum Error {
32    /// An error occurred in the host-side API function.
33    APICallError,
34    /// Requested operation on [`Bytes`] is invalid.
35    BadBytes,
36    /// Type mismatch with what is expected.
37    BadType,
38    /// The internal plugin buffer is too short to carry the data.
39    ShortInternalBuffer,
40    /// An error occurred during the (de)serialization process.
41    SerializeError,
42}
43
44pub type Result<T> = std::result::Result<T, Error>;
45
46extern "C" {
47    /* General output function */
48    fn save_output_from_plugin(ptr: WASMPtr, len: WASMLen) -> APIResult;
49    /* Output function to call only once, with all the outputs */
50    fn save_outputs_from_plugin(ptr: WASMPtr, len: WASMLen) -> APIResult;
51    /* Classical debug function, from
52     * https://github.com/wasmerio/wasmer-rust-example/blob/master/examples/string.rs */
53    fn print_from_plugin(ptr: WASMPtr, len: WASMLen);
54    /* Gets a connection field */
55    fn get_connection_from_plugin(
56        field_ptr: WASMPtr,
57        field_len: WASMLen,
58        res_ptr: WASMPtr,
59        res_len: WASMLen,
60    ) -> APIResult;
61    /* Sets a connection field */
62    fn set_connection_from_plugin(
63        field_ptr: WASMPtr,
64        field_len: WASMLen,
65        value_ptr: WASMPtr,
66        value_len: WASMLen,
67    ) -> APIResult;
68    /* Gets an input */
69    fn get_input_from_plugin(index: u32, res_ptr: WASMPtr, res_len: WASMLen) -> APIResult;
70    /* Gets all inputs */
71    fn get_inputs_from_plugin(res_ptr: WASMPtr, res_len: WASMLen) -> APIResult;
72    /* Read the bytes */
73    fn get_bytes_from_plugin(tag: u64, len: u64, res_ptr: WASMPtr, res_len: WASMLen) -> i64;
74    /* Put some bytes */
75    fn put_bytes_from_plugin(tag: u64, ptr: WASMPtr, len: WASMLen) -> i64;
76    /* Register a parametrized protocol operation */
77    fn register_from_plugin(ptr: WASMPtr, len: WASMLen) -> i64;
78    /* Set a custom timer */
79    fn set_timer_from_plugin(ts_ptr: WASMPtr, ts_len: WASMLen, id: u64, timer_id: u64)
80        -> APIResult;
81    /* Cancel the timer with the given id */
82    fn cancel_timer_from_plugin(id: u64) -> APIResult;
83    /* Gets the current UNIX time */
84    fn get_unix_instant_from_plugin(res_ptr: WASMPtr, res_len: WASMLen) -> APIResult;
85    /* Fully enable the plugin operations */
86    fn enable_from_plugin();
87    /* Gets a recovery field */
88    fn get_recovery_from_plugin(
89        field_ptr: WASMPtr,
90        field_len: WASMLen,
91        res_ptr: WASMPtr,
92        res_len: WASMLen,
93    ) -> APIResult;
94    /* Sets a recovery field */
95    fn set_recovery_from_plugin(
96        field_ptr: WASMPtr,
97        field_len: WASMLen,
98        value_ptr: WASMPtr,
99        value_len: WASMLen,
100    ) -> APIResult;
101    /* Calls a plugin control */
102    fn poctl_from_plugin(
103        id: u64,
104        input_ptr: WASMPtr,
105        input_len: WASMLen,
106        res_ptr: WASMPtr,
107        res_len: WASMLen,
108    ) -> APIResult;
109}
110
111/// A companion structure, always passed as first argument of any plugin operation function,
112/// enabling the plugin to interact with the host implementation.
113#[repr(C)]
114pub struct PluginEnv(WASMPtr);
115
116impl PluginEnv {
117    /// Store a new plugin output.
118    pub fn save_output(&self, v: PluginVal) -> Result<()> {
119        let serialized_value = postcard::to_allocvec(&v).map_err(|_| Error::SerializeError)?;
120        match unsafe {
121            save_output_from_plugin(
122                serialized_value.as_ptr() as WASMPtr,
123                serialized_value.len() as WASMLen,
124            )
125        } {
126            0 => Ok(()),
127            _ => Err(Error::APICallError),
128        }
129    }
130
131    /// Store all the plugin outputs.
132    pub fn save_outputs(&self, v: &[PluginVal]) -> Result<()> {
133        let serialized_value = postcard::to_allocvec(&v).map_err(|_| Error::SerializeError)?;
134        match unsafe {
135            save_outputs_from_plugin(
136                serialized_value.as_ptr() as WASMPtr,
137                serialized_value.len() as WASMLen,
138            )
139        } {
140            0 => Ok(()),
141            _ => Err(Error::APICallError),
142        }
143    }
144
145    /// Print the provided string on the standard output.
146    pub fn print(&self, s: &str) {
147        unsafe { print_from_plugin(s.as_ptr() as WASMPtr, s.len() as WASMLen) }
148    }
149
150    /// Get a connection field.
151    pub fn get_connection<T>(&self, field: quic::ConnectionField) -> Result<T>
152    where
153        T: TryFrom<PluginVal>,
154    {
155        let serialized_field = postcard::to_allocvec(&field).map_err(|_| Error::SerializeError)?;
156        let mut res = Vec::<u8>::with_capacity(SIZE).into_boxed_slice();
157        let err = unsafe {
158            get_connection_from_plugin(
159                serialized_field.as_ptr() as WASMPtr,
160                serialized_field.len() as WASMLen,
161                res.as_mut_ptr() as WASMPtr,
162                SIZE as WASMLen,
163            )
164        };
165        if err != 0 {
166            return Err(Error::APICallError);
167        }
168        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), SIZE) };
169        let plugin_val: PluginVal =
170            postcard::from_bytes(slice).map_err(|_| Error::SerializeError)?;
171        plugin_val.try_into().map_err(|_| Error::BadType)
172    }
173
174    /// Set a connection field.
175    pub fn set_connection<T>(&mut self, field: quic::ConnectionField, v: T) -> Result<()>
176    where
177        T: Into<PluginVal>,
178    {
179        let serialized_field = postcard::to_allocvec(&field).map_err(|_| Error::SerializeError)?;
180        let serialized_value =
181            postcard::to_allocvec(&v.into()).map_err(|_| Error::SerializeError)?;
182        match unsafe {
183            set_connection_from_plugin(
184                serialized_field.as_ptr() as WASMPtr,
185                serialized_field.len() as WASMLen,
186                serialized_value.as_ptr() as WASMPtr,
187                serialized_value.len() as WASMLen,
188            )
189        } {
190            0 => Ok(()),
191            _ => Err(Error::APICallError),
192        }
193    }
194
195    /// Get a recovery field.
196    pub fn get_recovery<'de, T>(&self, field: quic::RecoveryField) -> T
197    where
198        T: Deserialize<'de>,
199    {
200        let serialized_field = postcard::to_allocvec(&field).expect("serialized field");
201        let mut res = Vec::<u8>::with_capacity(SIZE).into_boxed_slice();
202        unsafe {
203            get_recovery_from_plugin(
204                serialized_field.as_ptr() as WASMPtr,
205                serialized_field.len() as WASMLen,
206                res.as_mut_ptr() as WASMPtr,
207                SIZE as WASMLen,
208            );
209        }
210        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), SIZE) };
211        postcard::from_bytes(slice).expect("no error")
212    }
213
214    /// Set a recovery field.
215    pub fn set_recovery<T>(&mut self, field: quic::RecoveryField, v: T) -> Result<()>
216    where
217        T: Into<PluginVal>,
218    {
219        let serialized_field = postcard::to_allocvec(&field).map_err(|_| Error::SerializeError)?;
220        let serialized_value =
221            postcard::to_allocvec(&v.into()).map_err(|_| Error::SerializeError)?;
222        match unsafe {
223            set_recovery_from_plugin(
224                serialized_field.as_ptr() as WASMPtr,
225                serialized_field.len() as WASMLen,
226                serialized_value.as_ptr() as WASMPtr,
227                serialized_value.len() as WASMLen,
228            )
229        } {
230            0 => Ok(()),
231            _ => Err(Error::APICallError),
232        }
233    }
234
235    /// Get an input.
236    pub fn get_input<T>(&self, index: u32) -> Result<T>
237    where
238        T: TryFrom<PluginVal>,
239        <T as TryFrom<PluginVal>>::Error: std::fmt::Debug,
240    {
241        let mut res = Vec::<u8>::with_capacity(SIZE).into_boxed_slice();
242        if unsafe { get_input_from_plugin(index, res.as_mut_ptr() as WASMPtr, SIZE as WASMLen) }
243            != 0
244        {
245            return Err(Error::ShortInternalBuffer);
246        }
247        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), SIZE) };
248        let input: PluginVal = match postcard::from_bytes(slice) {
249            Ok(i) => i,
250            Err(_) => return Err(Error::SerializeError),
251        };
252        input.try_into().map_err(|_| Error::SerializeError)
253    }
254
255    /// Get the inputs.
256    pub fn get_inputs(&self) -> Result<Vec<PluginVal>> {
257        let mut res = Vec::<u8>::with_capacity(SIZE).into_boxed_slice();
258        if unsafe { get_inputs_from_plugin(res.as_mut_ptr() as WASMPtr, SIZE as WASMLen) } != 0 {
259            return Err(Error::ShortInternalBuffer);
260        }
261        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), SIZE) };
262        postcard::from_bytes(slice).map_err(|_| Error::SerializeError)
263    }
264
265    /// Read some bytes and advances the related buffer (i.e., multiple calls give different results).
266    pub fn get_bytes(&mut self, tag: u64, len: u64) -> Result<Vec<u8>> {
267        let mut res = Vec::<u8>::with_capacity(len as usize).into_boxed_slice();
268        let len =
269            unsafe { get_bytes_from_plugin(tag, len, res.as_mut_ptr() as WASMPtr, len as WASMLen) };
270        if len < 0 {
271            return Err(Error::BadBytes);
272        }
273        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), len as usize) };
274        Ok(slice.to_vec())
275    }
276
277    /// Write some bytes and advances the related buffer (i.e., multiple calls gives different results).
278    pub fn put_bytes(&mut self, tag: u64, b: &[u8]) -> Result<usize> {
279        let written =
280            unsafe { put_bytes_from_plugin(tag, b.as_ptr() as WASMPtr, b.len() as WASMLen) };
281        if written < 0 {
282            return Err(Error::BadBytes);
283        }
284        Ok(written as usize)
285    }
286
287    /// Perform a registration to the host implementation. This operation is usually performed during
288    /// the initialization of the plugin.
289    pub fn register(&mut self, r: Registration) -> Result<()> {
290        let serialized = postcard::to_allocvec(&r).map_err(|_| Error::SerializeError)?;
291        match unsafe {
292            register_from_plugin(serialized.as_ptr() as WASMPtr, serialized.len() as WASMLen)
293        } {
294            0 => Ok(()),
295            _ => Err(Error::APICallError),
296        }
297    }
298
299    /// Set a timer at the provided time to call the given callback function with the
300    /// provided name.
301    ///
302    /// Returns the identifier to the timer event, as provided as argument.
303    pub fn set_timer(&mut self, ts: UnixInstant, id: u64, timer_id: u64) -> Result<()> {
304        let serialized_ts = postcard::to_allocvec(&ts).map_err(|_| Error::SerializeError)?;
305        match unsafe {
306            set_timer_from_plugin(
307                serialized_ts.as_ptr() as WASMPtr,
308                serialized_ts.len() as WASMLen,
309                id,
310                timer_id,
311            )
312        } {
313            0 => Ok(()),
314            _ => Err(Error::APICallError),
315        }
316    }
317
318    /// Cancel the timer event having the identifier provided.
319    pub fn cancel_timer(&mut self, id: u64) -> Result<()> {
320        match unsafe { cancel_timer_from_plugin(id) } {
321            0 => Ok(()),
322            _ => Err(Error::APICallError),
323        }
324    }
325
326    /// Get the current UNIX instant.
327    pub fn get_unix_instant(&self) -> Result<UnixInstant> {
328        let size = mem::size_of::<UnixInstant>();
329        let mut res = Vec::<u8>::with_capacity(size).into_boxed_slice();
330        let err =
331            unsafe { get_unix_instant_from_plugin(res.as_mut_ptr() as WASMPtr, size as WASMLen) };
332        if err != 0 {
333            return Err(Error::APICallError);
334        }
335        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), size) };
336        postcard::from_bytes(slice).map_err(|_| Error::SerializeError)
337    }
338
339    /// Fully enable the plugin operations.
340    /// Such a call is needed to enable plugin operations that are not
341    /// `always_enabled()`.
342    pub fn enable(&self) {
343        unsafe { enable_from_plugin() };
344    }
345
346    /// Invoke a plugin operation control operation.
347    pub fn poctl(&mut self, id: u64, params: &[PluginVal]) -> Result<Vec<PluginVal>> {
348        let serialized_inputs =
349            postcard::to_allocvec(&params).map_err(|_| Error::SerializeError)?;
350        let mut res = Vec::<u8>::with_capacity(SIZE).into_boxed_slice();
351        let err = unsafe {
352            poctl_from_plugin(
353                id,
354                serialized_inputs.as_ptr() as WASMPtr,
355                serialized_inputs.len() as WASMLen,
356                res.as_mut_ptr() as WASMPtr,
357                SIZE as WASMLen,
358            )
359        };
360        if err != 0 {
361            return Err(Error::APICallError);
362        }
363        let slice = unsafe { std::slice::from_raw_parts(res.as_ptr(), SIZE) };
364        postcard::from_bytes(slice).map_err(|_| Error::SerializeError)
365    }
366}
367
368/// A cell structure to be used in single-threaded plugins.
369pub struct PluginCell<T>(UnsafeCell<T>);
370
371impl<T> PluginCell<T> {
372    pub fn new(v: T) -> Self {
373        Self(UnsafeCell::new(v))
374    }
375
376    /// Get a mutable reference to the cell.
377    // TODO: solve this lint.
378    #[allow(clippy::mut_from_ref)]
379    pub fn get_mut(&self) -> &mut T {
380        // SAFETY: only valid in single-threaded mode, which is the case in the scope of the plugins.
381        unsafe { &mut *self.0.get() }
382    }
383}
384
385impl<T: Sync + Send> Deref for PluginCell<T> {
386    type Target = T;
387
388    fn deref(&self) -> &Self::Target {
389        // SAFETY: only valid in single-threaded mode, which is the case in the scope of the plugins.
390        unsafe { &*self.0.get() }
391    }
392}
393
394// SAFETY: only valid in single-threaded mode, which is the case in the scope of the plugins.
395unsafe impl<T: Send> Send for PluginCell<T> {}
396// SAFETY: only valid in single-threaded mode, which is the case in the scope of the plugins.
397unsafe impl<T: Sync> Sync for PluginCell<T> {}
398
399pub mod fd;