Skip to main content

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