liboscore_msgbackend/
lib.rs

1//! Backend for liboscore's native message API
2//!
3//! This is (and, unless one wants to go through `Box<dyn ReadableMessage>`) necessarily a bit
4//! opinionated, in that it binds to a concrete message type (or set thereof).
5//!
6//! It wraps different implementations of coap-message in an enum.
7#![no_std]
8#![allow(non_camel_case_types)]
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12
13use core::mem::MaybeUninit;
14
15use coap_message::{
16    error::RenderableOnMinimal, MessageOption as _, MinimalWritableMessage, MutableWritableMessage,
17    ReadableMessage,
18};
19
20/// Void stand-in recognized by the cbindgen library by its name
21#[allow(non_camel_case_types)]
22pub enum c_void {}
23
24struct Message<'a> {
25    data: MessageVariant<'a>,
26    /// We have to keep the payload length that is not kept in the message because libOSCORE does
27    /// not differentiate between writable and readable messages. coap-message has writable
28    /// messages whose payload you could map to whichever length you want (as long as it's within
29    /// the available length), and readable whose payload you could map exactly as it is. But
30    /// libOSCORE (at least its tests) expect to truncate a message, and then map it (in full
31    /// written length) to read it back. coap-message doesn't support that, so we keep extra data
32    /// to make it work.
33    ///
34    /// As a future optimization, one could consider removing it, but then tests (standalone-demo)
35    /// would fail.
36    payload_length: Option<usize>,
37}
38
39enum MessageVariant<'a> {
40    #[cfg(feature = "alloc")]
41    Heap(coap_message_implementations::heap::HeapMessage),
42    InMemory(&'a mut coap_message_implementations::inmemory_write::Message<'a>),
43    // Note that there's little point in wrapping anything that's Readable but not MutableWritable:
44    // All our decryption happens in-place.
45}
46
47impl<'a> Message<'a> {
48    unsafe fn from_ptr(ptr: oscore_msg_native_t) -> &'a mut Self {
49        unsafe { &mut *(ptr.0 as *mut Message<'a>) }
50    }
51}
52
53impl<'a> ReadableMessage for Message<'a> {
54    type Code = u8;
55    type MessageOption<'b>
56        = MessageOption<'b>
57    where
58        Self: 'b;
59    type OptionsIter<'b>
60        = OptionsIter<'b>
61    where
62        Self: 'b;
63    fn code(&self) -> u8 {
64        match &self.data {
65            #[cfg(feature = "alloc")]
66            MessageVariant::Heap(m) => m.code(),
67            MessageVariant::InMemory(m) => m.code(),
68        }
69    }
70    fn payload(&self) -> &[u8] {
71        // Panic rather than having a trivial yet still untested implementation
72        panic!("This function is not used by the oscore_msg_native implementation");
73    }
74    fn options(&self) -> Self::OptionsIter<'_> {
75        match &self.data {
76            #[cfg(feature = "alloc")]
77            MessageVariant::Heap(m) => OptionsIter::Heap(m.options()),
78            MessageVariant::InMemory(m) => OptionsIter::InMemory(m.options()),
79        }
80    }
81}
82
83impl<'a> MinimalWritableMessage for Message<'a> {
84    // Actually it's always the InternalMessageError variant and in particular never successful,
85    // but having the right type here makes later conversion easier.
86    type AddOptionError = oscore_msgerr_native_t;
87    type SetPayloadError = oscore_msgerr_native_t;
88    type UnionError = oscore_msgerr_native_t;
89
90    type Code = u8;
91    type OptionNumber = u16;
92
93    fn set_code(&mut self, code: u8) {
94        match &mut self.data {
95            #[cfg(feature = "alloc")]
96            MessageVariant::Heap(m) => m.set_code(code),
97            MessageVariant::InMemory(m) => m.set_code(code),
98        }
99    }
100    fn add_option(&mut self, option: u16, data: &[u8]) -> Result<(), oscore_msgerr_native_t> {
101        match &mut self.data {
102            #[cfg(feature = "alloc")]
103            MessageVariant::Heap(m) => map_internal_error(m.add_option(option, data)),
104            MessageVariant::InMemory(m) => map_internal_error(m.add_option(option, data)),
105        }
106    }
107    fn set_payload(&mut self, _: &[u8]) -> Result<(), oscore_msgerr_native_t> {
108        // Panic rather than having a trivial yet still untested implementation
109        panic!("This function is not used by the oscore_msg_native implementation");
110    }
111}
112
113impl<'a> MutableWritableMessage for Message<'a> {
114    fn available_space(&self) -> usize {
115        match &self.data {
116            #[cfg(feature = "alloc")]
117            MessageVariant::Heap(m) => m.available_space(),
118            MessageVariant::InMemory(m) => m.available_space(),
119        }
120    }
121    fn payload_mut_with_len(&mut self, len: usize) -> Result<&mut [u8], oscore_msgerr_native_t> {
122        match &mut self.data {
123            #[cfg(feature = "alloc")]
124            MessageVariant::Heap(m) => map_internal_error(m.payload_mut_with_len(len)),
125            MessageVariant::InMemory(m) => map_internal_error(m.payload_mut_with_len(len)),
126        }
127    }
128    fn truncate(&mut self, len: usize) -> Result<(), oscore_msgerr_native_t> {
129        self.payload_length = Some(len);
130
131        match &mut self.data {
132            #[cfg(feature = "alloc")]
133            MessageVariant::Heap(m) => map_internal_error(m.truncate(len)),
134            MessageVariant::InMemory(m) => map_internal_error(m.truncate(len)),
135        }
136    }
137    fn mutate_options<F: FnMut(u16, &mut [u8])>(&mut self, f: F) {
138        match &mut self.data {
139            #[cfg(feature = "alloc")]
140            MessageVariant::Heap(m) => m.mutate_options(f),
141            MessageVariant::InMemory(m) => m.mutate_options(f),
142        }
143    }
144}
145
146enum OptionsIter<'a> {
147    #[cfg(feature = "alloc")]
148    Heap(<coap_message_implementations::heap::HeapMessage as ReadableMessage>::OptionsIter<'a>),
149    InMemory(<coap_message_implementations::inmemory_write::Message<'a> as ReadableMessage>::OptionsIter<'a>),
150}
151
152impl<'a> core::iter::Iterator for OptionsIter<'a> {
153    type Item = MessageOption<'a>;
154
155    fn next(&mut self) -> Option<MessageOption<'a>> {
156        match self {
157            #[cfg(feature = "alloc")]
158            OptionsIter::Heap(i) => i.next().map(MessageOption::Heap),
159            OptionsIter::InMemory(i) => i.next().map(MessageOption::InMemory),
160        }
161    }
162}
163
164enum MessageOption<'a> {
165    #[cfg(feature = "alloc")]
166    Heap(<coap_message_implementations::heap::HeapMessage as ReadableMessage>::MessageOption<'a>),
167    InMemory(
168        <coap_message_implementations::inmemory_write::Message<'a> as ReadableMessage>::MessageOption<'a>,
169    ),
170}
171
172impl<'a> coap_message::MessageOption for MessageOption<'a> {
173    fn number(&self) -> u16 {
174        match self {
175            #[cfg(feature = "alloc")]
176            MessageOption::Heap(m) => m.number(),
177            MessageOption::InMemory(m) => m.number(),
178        }
179    }
180
181    fn value(&self) -> &[u8] {
182        match self {
183            #[cfg(feature = "alloc")]
184            MessageOption::Heap(m) => m.value(),
185            MessageOption::InMemory(m) => m.value(),
186        }
187    }
188}
189
190/// The message type is conveniently already more pointer-like; given that we pass around pointers
191/// to a concrete type (albeit possibly an enum), it's just that.
192///
193/// The current convention this crate adheres to is to go through a level of indirection (being a
194/// pointer to the Message enum) rather than the full enum itself. The latter is well within the
195/// design space of libOSCORE, but given that there is as of 2022 some confusion about WASM's ABI
196/// (C and Rust-on-some-target-triples disagree on whether they are passed by value or by
197/// reference on the ABI level when passed by value on the API level), a small struct is
198/// preferable.
199///
200/// If `&dyn the-required-traits` were possible, we could also pass that (subject to the same
201/// limitations as passing the full enum).
202///
203/// The void pointer hides a [Message] enum (because it can't be repr(C)) that always has
204/// "sufficient" lifetime (we have to trust the C side on that).
205#[repr(C)]
206pub struct oscore_msg_native_t(*mut c_void);
207
208#[repr(C)]
209pub struct oscore_msg_native_optiter_t([u64; 12]);
210
211/// Errors out of message operations
212///
213/// As errors for the underlying message may be diverse, this only contains detials for error
214/// values of things where coap-message is markedly distinct from msg_native, and thus this crate
215/// actually implements anything (that can fail) rather than passing on.
216// All need unique names as they do get mapped out to the C side as well
217#[derive(Debug, PartialEq)]
218#[repr(u8)]
219pub enum oscore_msgerr_native_t {
220    ResultOk,
221    UpdateOptionWrongLength,
222    UpdateOptionNotFound,
223    /// Some operation on the underlying MinimalWritableMessage went wrong.
224    ///
225    /// As we do not know the size of the error, and multiple backends could have different errors,
226    /// all errors returned from methods of the message traits are converted into this value.
227    InternalMessageError,
228}
229
230fn map_internal_error<T, E>(r: Result<T, E>) -> Result<T, oscore_msgerr_native_t> {
231    r.map_err(|_| oscore_msgerr_native_t::InternalMessageError)
232}
233
234impl RenderableOnMinimal for oscore_msgerr_native_t {
235    type Error<IE: RenderableOnMinimal + core::fmt::Debug> = IE;
236    fn render<M: MinimalWritableMessage>(
237        self,
238        msg: &mut M,
239    ) -> Result<(), Self::Error<M::UnionError>> {
240        use coap_message::Code;
241        msg.set_code(Code::new(coap_numbers::code::INTERNAL_SERVER_ERROR)?);
242        Ok(())
243    }
244}
245
246impl From<core::convert::Infallible> for oscore_msgerr_native_t {
247    fn from(other: core::convert::Infallible) -> Self {
248        match other {}
249    }
250}
251
252#[no_mangle]
253pub extern "C" fn oscore_msg_native_get_code(msg: oscore_msg_native_t) -> u8 {
254    unsafe { Message::from_ptr(msg) }.code()
255}
256
257#[no_mangle]
258pub extern "C" fn oscore_msg_native_set_code(msg: oscore_msg_native_t, code: u8) {
259    unsafe { Message::from_ptr(msg) }.set_code(code)
260}
261
262#[no_mangle]
263pub extern "C" fn oscore_msg_native_append_option(
264    msg: oscore_msg_native_t,
265    option_number: u16,
266    value: *const u8,
267    value_len: usize,
268) -> oscore_msgerr_native_t {
269    let value = unsafe { core::slice::from_raw_parts(value, value_len) };
270    match unsafe { Message::from_ptr(msg) }.add_option(option_number, value) {
271        Ok(()) => oscore_msgerr_native_t::ResultOk,
272        Err(e) => e,
273    }
274}
275
276#[no_mangle]
277pub extern "C" fn oscore_msg_native_update_option(
278    msg: oscore_msg_native_t,
279    option_number: u16,
280    option_occurrence: usize,
281    value: *const u8,
282    value_len: usize,
283) -> oscore_msgerr_native_t {
284    let msg = unsafe { Message::from_ptr(msg) };
285    let value = unsafe { core::slice::from_raw_parts(value, value_len) };
286
287    let mut result = oscore_msgerr_native_t::ResultOk;
288    let mut occurrence = Some(option_occurrence);
289    msg.mutate_options(|onum, oval| {
290        if onum == option_number {
291            occurrence = match occurrence {
292                Some(0) => {
293                    if oval.len() == value.len() {
294                        oval[..].copy_from_slice(value);
295                    } else {
296                        result = oscore_msgerr_native_t::UpdateOptionWrongLength;
297                    }
298                    None
299                }
300                Some(i) => Some(i - 1),
301                None => None,
302            }
303        }
304    });
305    if occurrence.is_some() {
306        result = oscore_msgerr_native_t::UpdateOptionNotFound;
307    }
308    result
309}
310
311#[no_mangle]
312pub extern "C" fn oscore_msg_native_optiter_init(
313    msg: oscore_msg_native_t,
314    iter: &mut MaybeUninit<oscore_msg_native_optiter_t>,
315) {
316    let msg = unsafe { Message::from_ptr(msg) };
317    assert!(
318        core::mem::size_of::<oscore_msg_native_optiter_t>()
319            >= core::mem::size_of::<OptionsIter<'static>>(),
320        "OptionsIter doesn't fit in oscore_msg_native_optiter_t"
321    );
322    assert!(
323        core::mem::align_of::<oscore_msg_native_optiter_t>()
324            >= core::mem::align_of::<OptionsIter<'static>>(),
325        "oscore_msg_native_optiter_t is insufficiently aligned for OptionsIter"
326    );
327    let iter: &mut MaybeUninit<OptionsIter> = unsafe { core::mem::transmute(iter) };
328    iter.write(msg.options());
329}
330
331#[no_mangle]
332pub extern "C" fn oscore_msg_native_map_payload(
333    msg: oscore_msg_native_t,
334    payload: &mut *mut u8,
335    payload_len: &mut usize,
336) -> oscore_msgerr_native_t {
337    let msg = unsafe { Message::from_ptr(msg) };
338    if let Some(len) = msg.payload_length {
339        *payload_len = len;
340    } else {
341        let original_space = msg.available_space();
342        // FIXME: Heap versions would report SIZE_MAX, which is technically correct but highly
343        // impractical for the implementation that'd just map it all rater than bounding.
344        let available_space = original_space.clamp(0, 4097);
345        *payload_len = available_space.saturating_sub(1);
346    }
347    match msg.payload_mut_with_len(*payload_len) {
348        Ok(result) => {
349            *payload = result.as_mut_ptr();
350            oscore_msgerr_native_t::ResultOk
351        }
352        Err(e) => e,
353    }
354}
355
356#[no_mangle]
357pub extern "C" fn oscore_msgerr_native_is_error(msgerr: oscore_msgerr_native_t) -> bool {
358    msgerr != oscore_msgerr_native_t::ResultOk
359}
360
361#[no_mangle]
362pub extern "C" fn oscore_msg_native_trim_payload(
363    msg: oscore_msg_native_t,
364    payload_len: usize,
365) -> oscore_msgerr_native_t {
366    match unsafe { Message::from_ptr(msg) }.truncate(payload_len) {
367        Ok(()) => oscore_msgerr_native_t::ResultOk,
368        Err(e) => e,
369    }
370}
371
372#[no_mangle]
373pub extern "C" fn oscore_msg_native_optiter_next(
374    _: oscore_msg_native_t,
375    iter: &mut oscore_msg_native_optiter_t,
376    option_number: &mut u16,
377    value: &mut *const u8,
378    value_len: &mut usize,
379) -> bool {
380    let iter: &mut OptionsIter = unsafe { core::mem::transmute(iter) };
381    if let Some(o) = iter.next() {
382        *option_number = o.number();
383        let value_slice = o.value();
384        *value = value_slice.as_ptr();
385        *value_len = value_slice.len();
386        true
387    } else {
388        false
389    }
390}
391
392#[no_mangle]
393pub extern "C" fn oscore_msg_native_optiter_finish(
394    _: oscore_msg_native_t,
395    iter: &mut MaybeUninit<oscore_msg_native_optiter_t>,
396) -> oscore_msgerr_native_t {
397    let iter: &mut MaybeUninit<OptionsIter> = unsafe { core::mem::transmute(iter) };
398    unsafe { iter.assume_init_drop() };
399    // Here the error models differ: oscore_msg_native would report errors here, whereas
400    // coap-message implementations are expected to produce garbage options to indicate somethign
401    // went awry
402    oscore_msgerr_native_t::ResultOk
403}
404
405#[cfg(feature = "alloc")]
406#[no_mangle]
407pub extern "C" fn oscore_test_msg_create() -> oscore_msg_native_t {
408    let msg = alloc::boxed::Box::new(Message {
409        data: MessageVariant::Heap(coap_message_implementations::heap::HeapMessage::new()),
410        payload_length: None,
411    });
412
413    oscore_msg_native_t(alloc::boxed::Box::into_raw(msg) as *mut _)
414}
415
416#[cfg(feature = "alloc")]
417#[no_mangle]
418pub extern "C" fn oscore_test_msg_destroy(msg: oscore_msg_native_t) {
419    let boxed = unsafe { alloc::boxed::Box::from_raw(msg.0) };
420    drop(boxed);
421}
422
423// Functions for interacting with messages from the Rust side
424
425/// Make a [coap_message_implementations::heap::HeapMessage] usable as a [raw::oscore_msg_native_t]
426///
427/// This is a low-level function mainly used by tests.
428#[cfg(feature = "alloc")]
429// FIXME: This drops the heapmessage afterwards; that's OK for decoding (and for encoding we'll
430// probably want to create it freshly anyway)
431pub fn with_heapmessage_as_msg_native<F, R>(
432    msg: coap_message_implementations::heap::HeapMessage,
433    f: F,
434) -> R
435where
436    F: FnOnce(oscore_msg_native_t) -> R,
437{
438    // This is just the kind of message that does need its payload length known and set
439    let payload_len = msg.payload().len();
440    let mut wrapped_message = Message {
441        data: MessageVariant::Heap(msg),
442        payload_length: Some(payload_len),
443    };
444    f(oscore_msg_native_t(
445        &mut wrapped_message as *mut _ as *mut _,
446    ))
447}
448
449/// Make a [coap_message_implementations::inmemory_write::Message] usable as a [raw::oscore_msg_native_t]
450///
451/// This is a low-level function used by the high-level wrappers in the liboscore crate.
452pub fn with_inmemory_as_msg_native<'a, 'b: 'a, F, R>(
453    msg: &'a mut coap_message_implementations::inmemory_write::Message<'b>,
454    f: F,
455) -> R
456where
457    F: FnOnce(oscore_msg_native_t) -> R,
458{
459    // FIXME: find a safe way to do this
460    // Safety: Message is for some reason considered invariant over its lifetime argument, when
461    // from how it's working it should be coercible into a shorter lifetime argument.
462    let msg: &'a mut coap_message_implementations::inmemory_write::Message<'a> =
463        unsafe { core::mem::transmute(msg) };
464
465    // coap-message doesn't distinguish between "unpopulated message" and "populated message",
466    // which is bad when we can have either, need to store the length of either, but can't really
467    // find out the state.
468    //
469    // Conveniently, at least this type allows mapping the payload read-only, and we can take the
470    // length of that to find out.
471
472    let read_payload_len = msg.payload().len();
473    let mut wrapped_message = Message {
474        data: MessageVariant::InMemory(msg),
475        payload_length: if read_payload_len == 0 {
476            // It's unpopulated (because any message coming into libOSCORE would be nonempty if it was
477            // received)
478            None
479        } else {
480            Some(read_payload_len)
481        },
482    };
483    f(oscore_msg_native_t(
484        &mut wrapped_message as *mut _ as *mut _,
485    ))
486}
487
488/// Sealed helper trait to implement with_msg_native
489pub trait WithMsgNative: sealed::Sealed {
490    fn with_msg_native<F: FnOnce(oscore_msg_native_t) -> R, R>(self, f: F) -> R;
491}
492
493impl<'a, 'b> WithMsgNative for &'a mut coap_message_implementations::inmemory_write::Message<'b> {
494    fn with_msg_native<F: FnOnce(oscore_msg_native_t) -> R, R>(self, f: F) -> R {
495        with_inmemory_as_msg_native(self, f)
496    }
497}
498#[cfg(feature = "alloc")]
499impl WithMsgNative for coap_message_implementations::heap::HeapMessage {
500    fn with_msg_native<F: FnOnce(oscore_msg_native_t) -> R, R>(self, f: F) -> R {
501        with_heapmessage_as_msg_native(self, f)
502    }
503}
504
505mod sealed {
506    pub trait Sealed {}
507
508    impl<'a, 'b> Sealed for &'a mut coap_message_implementations::inmemory_write::Message<'b> {}
509    #[cfg(feature = "alloc")]
510    impl Sealed for coap_message_implementations::heap::HeapMessage {}
511}