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