Skip to main content

myrmic_sdk/
codec.rs

1use serde::Serialize;
2use serde::de::DeserializeOwned;
3
4use crate::{Bytes, Result};
5use alloc::string::String;
6use myrmic_common::cells::Sri;
7
8/// Turns a raw payload buffer into `Self`.
9///
10/// Message types get this for free via `#[derive(Message)]`, which delegates to
11/// the bound [`Codec`]. The `#[cmd]`/`#[evt]` handler macros call
12/// [`from_args`](Decoder::from_args) on the handler's argument type.
13pub trait Decoder: Sized {
14    /// Reads this invocation's `length`-byte payload from the host (via
15    /// [`get_arguments`](crate::get_arguments)) and decodes it with
16    /// [`from_bytes`](Self::from_bytes).
17    ///
18    /// A failure on an empty buffer is reported as a missing payload instead: a
19    /// decoder for a mandatory payload can only describe absence as malformed
20    /// input, which points at nothing the handler's author can act on.
21    fn from_args(length: usize) -> Result<Self> {
22        let mut bytes = alloc::vec![0u8; length];
23        let n = crate::get_arguments(&mut bytes).map_err(|_| "failed to read arguments")?;
24        bytes.truncate(n);
25        let absent = bytes.is_empty();
26
27        Self::from_bytes(bytes).map_err(|err| {
28            if absent {
29                "no payload was sent; declare the handler's payload as `Option<_>` \
30                 to accept an invocation sent without one"
31            } else {
32                err
33            }
34        })
35    }
36
37    /// Decodes `Self` from the raw payload buffer.
38    fn from_bytes(bytes: Bytes) -> Result<Self>;
39}
40
41/// Turns `Self` into a raw payload buffer.
42///
43/// Message types get this for free via `#[derive(Message)]`, which delegates to
44/// the bound [`Codec`].
45pub trait Encoder {
46    /// Encodes `self` into a raw payload buffer.
47    fn to_bytes(&self) -> Result<Bytes>;
48}
49
50/// A wire serialization format.
51///
52/// Implement this to add a custom codec, then bind it to a message type with
53/// `#[derive(Message)]` + `#[codec(YourCodec)]`. The SDK ships [`Json`] and
54/// [`Postcard`].
55///
56/// ```
57/// struct MsgPack;
58/// impl myrmic_sdk::Codec for MsgPack {
59///     fn encode<T: serde::Serialize + ?Sized>(value: &T) -> myrmic_sdk::Result<myrmic_sdk::Bytes> {
60///         rmp_serde::to_vec(value).map_err(|_| "encode failed")
61///     }
62///     fn decode<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> myrmic_sdk::Result<T> {
63///         rmp_serde::from_slice(bytes).map_err(|_| "decode failed")
64///     }
65/// }
66///
67/// use myrmic_sdk::Codec;
68/// let bytes = MsgPack::encode(&42u32)?;
69/// assert_eq!(MsgPack::decode::<u32>(&bytes)?, 42);
70/// # Ok::<(), &'static str>(())
71/// ```
72pub trait Codec {
73    /// Serializes `value` into bytes.
74    fn encode<T: Serialize + ?Sized>(value: &T) -> Result<Bytes>;
75    /// Deserializes a `T` from `bytes`.
76    fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T>;
77}
78
79/// JSON codec.
80pub struct Json;
81
82impl Codec for Json {
83    fn encode<T: Serialize + ?Sized>(value: &T) -> Result<Bytes> {
84        serde_json::to_vec(value).map_err(|_| "failed to serialize json")
85    }
86
87    fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
88        serde_json::from_slice(bytes).map_err(|_| "failed to deserialize json")
89    }
90}
91
92/// Postcard codec.
93pub struct Postcard;
94
95impl Codec for Postcard {
96    fn encode<T: Serialize + ?Sized>(value: &T) -> Result<Bytes> {
97        postcard::to_allocvec(value).map_err(|_| "failed to serialize postcard")
98    }
99
100    fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
101        postcard::from_bytes(bytes).map_err(|_| "failed to deserialize postcard")
102    }
103}
104
105/// Raw, uncodec'd bytes — a handler parameter of type `Bytes` receives the
106/// payload verbatim.
107impl Decoder for Bytes {
108    fn from_bytes(bytes: Bytes) -> Result<Self> {
109        Ok(bytes)
110    }
111}
112
113/// Used to send custom payloads, for example, raw image data, etc.
114impl Encoder for Bytes {
115    fn to_bytes(&self) -> Result<Bytes> {
116        Ok(self.clone())
117    }
118}
119
120impl Decoder for Sri {
121    fn from_bytes(bytes: Bytes) -> Result<Self> {
122        let (hi, lo): (i64, i64) = <Postcard as Codec>::decode(&bytes)?;
123        Ok(Sri::from_parts(hi, lo))
124    }
125}
126
127impl Encoder for Sri {
128    fn to_bytes(&self) -> Result<Bytes> {
129        let parts = self.to_parts();
130        <Postcard as Codec>::encode(&parts)
131    }
132}
133
134/// The absence of a payload. This is the default [`Decoder`] the `#[cmd]` /
135/// `#[evt]` macros use for a handler declared with only a `Metadata` parameter:
136/// decoding succeeds only when the argument buffer is empty, so sending a
137/// payload to such a handler is rejected rather than silently ignored.
138pub struct Void;
139
140impl Decoder for Void {
141    fn from_args(length: usize) -> Result<Self> {
142        if length == 0 {
143            Ok(Void)
144        } else {
145            Err("this handler does not accept a payload")
146        }
147    }
148
149    fn from_bytes(bytes: Bytes) -> Result<Self> {
150        if bytes.is_empty() {
151            Ok(Void)
152        } else {
153            Err("this handler does not accept a payload")
154        }
155    }
156}
157
158impl Encoder for Void {
159    fn to_bytes(&self) -> Result<Bytes> {
160        Ok(Bytes::new())
161    }
162}
163
164impl<T: Decoder> Decoder for Option<T> {
165    fn from_args(length: usize) -> Result<Self> {
166        if length == 0 {
167            Ok(None)
168        } else {
169            T::from_args(length).map(Some)
170        }
171    }
172
173    fn from_bytes(bytes: Bytes) -> Result<Self> {
174        if bytes.is_empty() {
175            Ok(None)
176        } else {
177            T::from_bytes(bytes).map(Some)
178        }
179    }
180}
181
182impl<T: Encoder> Encoder for Option<T> {
183    fn to_bytes(&self) -> Result<Bytes> {
184        match self {
185            Some(value) => T::to_bytes(value),
186            None => Ok(Bytes::new()),
187        }
188    }
189}
190
191/// Bare `String`, `char`, `bool`, and float payloads, carried on the wire as
192/// JSON.
193///
194/// A handler parameter — or a `send`/`publish`/callback value — of one of these
195/// types is encoded directly, with no wrapper message struct, so a bare scalar
196/// travels exactly as an external caller (e.g. the gateway) would naturally send
197/// it: `true`, `"hi"`, `1.5`. This matches the [`Json`] default that
198/// `#[derive(Message)]` gives struct payloads.
199macro_rules! json_scalar {
200    ($($ty:ty),* $(,)?) => {$(
201        impl Decoder for $ty {
202            fn from_bytes(bytes: Bytes) -> Result<Self> {
203                <Json as Codec>::decode(&bytes)
204            }
205        }
206
207        impl Encoder for $ty {
208            fn to_bytes(&self) -> Result<Bytes> {
209                <Json as Codec>::encode(self)
210            }
211        }
212    )*};
213}
214
215// `f32`/`f64` fall here too: `serde_json` already coerces an integer literal
216// (`42`) into a float, so a plain decode covers both `42` and `1.5`.
217json_scalar!(String, bool, char, f32, f64);
218
219/// Bare integer payloads, carried on the wire as JSON numbers.
220///
221/// An exact decode is tried first so a full-width integer literal (including
222/// 128-bit) round-trips losslessly. Failing that, the payload is read as a
223/// [`serde_json::Number`] and accepted when its value is a whole number in range
224/// for the target type — so a `42.0` sent for a `u32` still decodes to `42`,
225/// while `42.5` or an out-of-range value is rejected.
226macro_rules! json_int {
227    ($($ty:ty),* $(,)?) => {$(
228        impl Decoder for $ty {
229            fn from_bytes(bytes: Bytes) -> Result<Self> {
230                if let Ok(value) = <Json as Codec>::decode::<$ty>(&bytes) {
231                    return Ok(value);
232                }
233                let number: serde_json::Number = <Json as Codec>::decode(&bytes)?;
234                let f = number.as_f64().ok_or("expected a number")?;
235                // `f as i128` round-tripping equal proves `f` is a whole number
236                // within i128 range; the bounds check then confines it to `$ty`.
237                // (`f64::fract` is unavailable under `no_std`.)
238                if f as i128 as f64 == f && f >= <$ty>::MIN as f64 && f <= <$ty>::MAX as f64 {
239                    Ok(f as $ty)
240                } else {
241                    Err("number is not a whole value in range for the target type")
242                }
243            }
244        }
245
246        impl Encoder for $ty {
247            fn to_bytes(&self) -> Result<Bytes> {
248                <Json as Codec>::encode(self)
249            }
250        }
251    )*};
252}
253
254json_int!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,);
255
256#[cfg(test)]
257mod tests {
258    use core::ffi::c_int;
259
260    use spin::Mutex;
261
262    use super::{Decoder, Encoder};
263    use crate::{Bytes, Callback, JsonValue, Result};
264    use alloc::string::String;
265    use alloc::vec::Vec;
266    use myrmic_common::cells::Command;
267
268    fn dec<T: Decoder>(bytes: &[u8]) -> Result<T> {
269        T::from_bytes(Bytes::from(bytes))
270    }
271
272    fn enc<T: Encoder>(value: &T) -> Vec<u8> {
273        value.to_bytes().unwrap()
274    }
275
276    fn enc_str<T: Encoder>(value: &T) -> String {
277        String::from_utf8(enc(value)).unwrap()
278    }
279
280    #[test]
281    fn integer_encodes_as_json_number() {
282        assert_eq!(enc_str(&42u32), "42");
283    }
284
285    #[test]
286    fn json_number_decodes_into_integer() {
287        // A bare `42` as the gateway sends it on the wire.
288        assert_eq!(dec::<u32>(b"42").unwrap(), 42);
289    }
290
291    #[test]
292    fn integral_float_decodes_into_integer() {
293        assert_eq!(dec::<u32>(b"42.0").unwrap(), 42);
294    }
295
296    #[test]
297    fn fractional_number_rejected_for_integer() {
298        assert!(dec::<u32>(b"42.5").is_err());
299    }
300
301    #[test]
302    fn out_of_range_number_rejected_for_integer() {
303        assert!(dec::<u8>(b"300").is_err());
304    }
305
306    #[test]
307    fn signed_integer_round_trips() {
308        assert_eq!(dec::<i64>(&enc(&-5i64)).unwrap(), -5);
309    }
310
311    #[test]
312    fn max_u128_round_trips_exactly() {
313        let v = u128::MAX;
314        assert_eq!(dec::<u128>(&enc(&v)).unwrap(), v);
315    }
316
317    #[test]
318    fn float_decodes_from_any_json_number() {
319        assert_eq!(dec::<f64>(b"42").unwrap(), 42.0);
320        assert_eq!(dec::<f32>(b"1.5").unwrap(), 1.5);
321    }
322
323    #[test]
324    fn float_encodes_as_json_number() {
325        assert_eq!(enc_str(&1.5f64), "1.5");
326    }
327
328    #[test]
329    fn bool_round_trips_as_json() {
330        assert_eq!(enc_str(&true), "true");
331        assert!(dec::<bool>(b"true").unwrap());
332    }
333
334    #[test]
335    fn char_round_trips_as_json_string() {
336        assert_eq!(enc_str(&'a'), "\"a\"");
337        assert_eq!(dec::<char>(b"\"a\"").unwrap(), 'a');
338    }
339
340    #[test]
341    fn string_round_trips_as_json_string() {
342        // A bareword the gateway wraps as a JSON string.
343        assert_eq!(enc_str(&String::from("jsontest")), "\"jsontest\"");
344        assert_eq!(dec::<String>(b"\"jsontest\"").unwrap(), "jsontest");
345    }
346
347    #[test]
348    fn optional_callback_absorbs_only_the_empty_buffer() {
349        // What `myrmic send <cell> count` puts on the wire.
350        assert!(dec::<Option<Callback<JsonValue>>>(b"").unwrap().is_none());
351
352        let decoded = dec::<Option<Callback<JsonValue>>>(b"on_reply").unwrap();
353        assert_eq!(Command::from(decoded.unwrap()).as_ref(), "on_reply");
354
355        // A non-empty buffer that is not a command name still fails: the
356        // optional form absorbs absence, never malformedness. `on_reply`
357        // without `--raw` arrives JSON-quoted, which is what this is.
358        assert!(dec::<Option<Callback<JsonValue>>>(b"\"on_reply\"").is_err());
359
360        // The bare form still rejects the empty buffer.
361        assert!(dec::<Callback<JsonValue>>(b"").is_err());
362    }
363
364    #[test]
365    fn zero_length_from_args_never_touches_the_inner_decoder() {
366        // `Probe` panics from both of its methods, so a branch that delegated
367        // to `T` would fail loudly instead of returning `None`. These come
368        // first so that failure is the one a broken branch reports.
369        assert!(<Option<Probe>>::from_args(0).unwrap().is_none());
370        assert!(<Option<Probe>>::from_bytes(Bytes::new()).unwrap().is_none());
371    }
372
373    #[test]
374    fn an_absent_payload_is_reported_with_advice() {
375        // An invocation carrying nothing, which is what `myrmic send <cell>
376        // <cmd>` puts on the wire. No mandatory payload can decode that,
377        // whatever its type, so the failure names the fix instead.
378        assert_eq!(
379            from_args_with::<u32>(b"").unwrap_err(),
380            "no payload was sent; declare the handler's payload as `Option<_>` \
381             to accept an invocation sent without one"
382        );
383        assert_eq!(
384            from_args_with::<Callback<JsonValue>>(b"").unwrap_err(),
385            "no payload was sent; declare the handler's payload as `Option<_>` \
386             to accept an invocation sent without one"
387        );
388
389        // The optional form absorbs absence, and stays silent about it.
390        assert!(from_args_with::<Option<u32>>(b"").unwrap().is_none());
391        assert_eq!(from_args_with::<Option<u32>>(b"42").unwrap(), Some(42));
392
393        // A payload that did arrive keeps its decoder's own error: the advice
394        // is about absence, not about malformedness.
395        assert_eq!(
396            from_args_with::<u32>(b"42.5").unwrap_err(),
397            "number is not a whole value in range for the target type"
398        );
399        assert_eq!(
400            from_args_with::<Callback<JsonValue>>(b"\"on_reply\"").unwrap_err(),
401            "name can only contain ASCII alphanumeric characters and underscores"
402        );
403    }
404
405    /// A decoder that fails loudly if it is ever reached.
406    struct Probe;
407
408    impl Decoder for Probe {
409        fn from_args(_length: usize) -> Result<Self> {
410            panic!("a zero-length argument buffer was delegated to the inner decoder");
411        }
412
413        fn from_bytes(_bytes: Bytes) -> Result<Self> {
414            panic!("an empty byte buffer was delegated to the inner decoder");
415        }
416    }
417
418    /// Decodes `payload` through the real [`Decoder::from_args`] path, as a host
419    /// invocation carrying it would.
420    fn from_args_with<T: Decoder>(payload: &[u8]) -> Result<T> {
421        *ARGUMENTS.lock() = payload.to_vec();
422
423        T::from_args(payload.len())
424    }
425
426    /// What the stubbed `get_arguments` below hands back.
427    static ARGUMENTS: Mutex<Vec<u8>> = Mutex::new(Vec::new());
428
429    /// Stands in for the host's `arguments` import, which a test binary has no
430    /// other definition of.
431    #[unsafe(no_mangle)]
432    extern "C" fn get_arguments(buffer: *mut u8, length: c_int) -> c_int {
433        let payload = ARGUMENTS.lock();
434        let n = payload.len().min(length as usize);
435        // SAFETY: the caller guarantees `buffer` is writable for `length`
436        // bytes, and `n` is capped at `length`.
437        unsafe { core::ptr::copy_nonoverlapping(payload.as_ptr(), buffer, n) };
438
439        n as c_int
440    }
441}