liboscore_msgbackend/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
//! Backend for liboscore's native message API
//!
//! This is (and, unless one wants to go through `Box<dyn ReadableMessage>`) necessarily a bit
//! opinionated, in that it binds to a concrete message type (or set thereof).
//!
//! It wraps different implementations of coap-message in an enum.
#![no_std]
#![allow(non_camel_case_types)]

#[cfg(feature = "alloc")]
extern crate alloc;

use core::mem::MaybeUninit;

use coap_message::{
    error::RenderableOnMinimal, MessageOption as _, MinimalWritableMessage, MutableWritableMessage,
    ReadableMessage,
};

/// Void stand-in recognized by the cbindgen library by its name
#[allow(non_camel_case_types)]
pub enum c_void {}

struct Message<'a> {
    data: MessageVariant<'a>,
    /// We have to keep the payload length that is not kept in the message because libOSCORE does
    /// not differentiate between writable and readable messages. coap-message has writable
    /// messages whose payload you could map to whichever length you want (as long as it's within
    /// the available length), and readable whose payload you could map exactly as it is. But
    /// libOSCORE (at least its tests) expect to truncate a message, and then map it (in full
    /// written length) to read it back. coap-message doesn't support that, so we keep extra data
    /// to make it work.
    ///
    /// As a future optimization, one could consider removing it, but then tests (standalone-demo)
    /// would fail.
    payload_length: Option<usize>,
}

enum MessageVariant<'a> {
    #[cfg(feature = "alloc")]
    Heap(coap_message_implementations::heap::HeapMessage),
    InMemory(&'a mut coap_message_implementations::inmemory_write::Message<'a>),
    // Note that there's little point in wrapping anything that's Readable but not MutableWritable:
    // All our decryption happens in-place.
}

impl<'a> Message<'a> {
    unsafe fn from_ptr(ptr: oscore_msg_native_t) -> &'a mut Self {
        unsafe { &mut *(ptr.0 as *mut Message<'a>) }
    }
}

impl<'a> ReadableMessage for Message<'a> {
    type Code = u8;
    type MessageOption<'b> = MessageOption<'b> where Self: 'b;
    type OptionsIter<'b> = OptionsIter<'b> where Self: 'b;
    fn code(&self) -> u8 {
        match &self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => m.code(),
            MessageVariant::InMemory(m) => m.code(),
        }
    }
    fn payload(&self) -> &[u8] {
        // Panic rather than having a trivial yet still untested implementation
        panic!("This function is not used by the oscore_msg_native implementation");
    }
    fn options(&self) -> Self::OptionsIter<'_> {
        match &self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => OptionsIter::Heap(m.options()),
            MessageVariant::InMemory(m) => OptionsIter::InMemory(m.options()),
        }
    }
}

impl<'a> MinimalWritableMessage for Message<'a> {
    // Actually it's always the InternalMessageError variant and in particular never successful,
    // but having the right type here makes later conversion easier.
    type AddOptionError = oscore_msgerr_native_t;
    type SetPayloadError = oscore_msgerr_native_t;
    type UnionError = oscore_msgerr_native_t;

    type Code = u8;
    type OptionNumber = u16;

    fn set_code(&mut self, code: u8) {
        match &mut self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => m.set_code(code),
            MessageVariant::InMemory(m) => m.set_code(code),
        }
    }
    fn add_option(&mut self, option: u16, data: &[u8]) -> Result<(), oscore_msgerr_native_t> {
        match &mut self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => map_internal_error(m.add_option(option, data)),
            MessageVariant::InMemory(m) => map_internal_error(m.add_option(option, data)),
        }
    }
    fn set_payload(&mut self, _: &[u8]) -> Result<(), oscore_msgerr_native_t> {
        // Panic rather than having a trivial yet still untested implementation
        panic!("This function is not used by the oscore_msg_native implementation");
    }
}

impl<'a> MutableWritableMessage for Message<'a> {
    fn available_space(&self) -> usize {
        match &self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => m.available_space(),
            MessageVariant::InMemory(m) => m.available_space(),
        }
    }
    fn payload_mut_with_len(&mut self, len: usize) -> Result<&mut [u8], oscore_msgerr_native_t> {
        match &mut self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => map_internal_error(m.payload_mut_with_len(len)),
            MessageVariant::InMemory(m) => map_internal_error(m.payload_mut_with_len(len)),
        }
    }
    fn truncate(&mut self, len: usize) -> Result<(), oscore_msgerr_native_t> {
        self.payload_length = Some(len);

        match &mut self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => map_internal_error(m.truncate(len)),
            MessageVariant::InMemory(m) => map_internal_error(m.truncate(len)),
        }
    }
    fn mutate_options<F: FnMut(u16, &mut [u8])>(&mut self, f: F) {
        match &mut self.data {
            #[cfg(feature = "alloc")]
            MessageVariant::Heap(m) => m.mutate_options(f),
            MessageVariant::InMemory(m) => m.mutate_options(f),
        }
    }
}

enum OptionsIter<'a> {
    #[cfg(feature = "alloc")]
    Heap(<coap_message_implementations::heap::HeapMessage as ReadableMessage>::OptionsIter<'a>),
    InMemory(<coap_message_implementations::inmemory_write::Message<'a> as ReadableMessage>::OptionsIter<'a>),
}

impl<'a> core::iter::Iterator for OptionsIter<'a> {
    type Item = MessageOption<'a>;

    fn next(&mut self) -> Option<MessageOption<'a>> {
        match self {
            #[cfg(feature = "alloc")]
            OptionsIter::Heap(i) => i.next().map(MessageOption::Heap),
            OptionsIter::InMemory(i) => i.next().map(MessageOption::InMemory),
        }
    }
}

enum MessageOption<'a> {
    #[cfg(feature = "alloc")]
    Heap(<coap_message_implementations::heap::HeapMessage as ReadableMessage>::MessageOption<'a>),
    InMemory(
        <coap_message_implementations::inmemory_write::Message<'a> as ReadableMessage>::MessageOption<'a>,
    ),
}

impl<'a> coap_message::MessageOption for MessageOption<'a> {
    fn number(&self) -> u16 {
        match self {
            #[cfg(feature = "alloc")]
            MessageOption::Heap(m) => m.number(),
            MessageOption::InMemory(m) => m.number(),
        }
    }

    fn value(&self) -> &[u8] {
        match self {
            #[cfg(feature = "alloc")]
            MessageOption::Heap(m) => m.value(),
            MessageOption::InMemory(m) => m.value(),
        }
    }
}

/// The message type is conveniently already more pointer-like; given that we pass around pointers
/// to a concrete type (albeit possibly an enum), it's just that.
///
/// The current convention this crate adheres to is to go through a level of indirection (being a
/// pointer to the Message enum) rather than the full enum itself. The latter is well within the
/// design space of libOSCORE, but given that there is as of 2022 some confusion about WASM's ABI
/// (C and Rust-on-some-target-triples disagree on whether they are passed by value or by
/// reference on the ABI level when passed by value on the API level), a small struct is
/// preferable.
///
/// If `&dyn the-required-traits` were possible, we could also pass that (subject to the same
/// limitations as passing the full enum).
///
/// The void pointer hides a [Message] enum (because it can't be repr(C)) that always has
/// "sufficient" lifetime (we have to trust the C side on that).
#[repr(C)]
pub struct oscore_msg_native_t(*mut c_void);

#[repr(C)]
pub struct oscore_msg_native_optiter_t([u64; 12]);

/// Errors out of message operations
///
/// As errors for the underlying message may be diverse, this only contains detials for error
/// values of things where coap-message is markedly distinct from msg_native, and thus this crate
/// actually implements anything (that can fail) rather than passing on.
// All need unique names as they do get mapped out to the C side as well
#[derive(Debug, PartialEq)]
#[repr(u8)]
pub enum oscore_msgerr_native_t {
    ResultOk,
    UpdateOptionWrongLength,
    UpdateOptionNotFound,
    /// Some operation on the underlying MinimalWritableMessage went wrong.
    ///
    /// As we do not know the size of the error, and multiple backends could have different errors,
    /// all errors returned from methods of the message traits are converted into this value.
    InternalMessageError,
}

fn map_internal_error<T, E>(r: Result<T, E>) -> Result<T, oscore_msgerr_native_t> {
    r.map_err(|_| oscore_msgerr_native_t::InternalMessageError)
}

impl RenderableOnMinimal for oscore_msgerr_native_t {
    type Error<IE: RenderableOnMinimal + core::fmt::Debug> = IE;
    fn render<M: MinimalWritableMessage>(
        self,
        msg: &mut M,
    ) -> Result<(), Self::Error<M::UnionError>> {
        use coap_message::Code;
        msg.set_code(Code::new(coap_numbers::code::INTERNAL_SERVER_ERROR)?);
        Ok(())
    }
}

impl From<core::convert::Infallible> for oscore_msgerr_native_t {
    fn from(other: core::convert::Infallible) -> Self {
        match other {}
    }
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_get_code(msg: oscore_msg_native_t) -> u8 {
    unsafe { Message::from_ptr(msg) }.code()
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_set_code(msg: oscore_msg_native_t, code: u8) {
    unsafe { Message::from_ptr(msg) }.set_code(code)
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_append_option(
    msg: oscore_msg_native_t,
    option_number: u16,
    value: *const u8,
    value_len: usize,
) -> oscore_msgerr_native_t {
    let value = unsafe { core::slice::from_raw_parts(value, value_len) };
    match unsafe { Message::from_ptr(msg) }.add_option(option_number, value) {
        Ok(()) => oscore_msgerr_native_t::ResultOk,
        Err(e) => e,
    }
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_update_option(
    msg: oscore_msg_native_t,
    option_number: u16,
    option_occurrence: usize,
    value: *const u8,
    value_len: usize,
) -> oscore_msgerr_native_t {
    let msg = unsafe { Message::from_ptr(msg) };
    let value = unsafe { core::slice::from_raw_parts(value, value_len) };

    let mut result = oscore_msgerr_native_t::ResultOk;
    let mut occurrence = Some(option_occurrence);
    msg.mutate_options(|onum, oval| {
        if onum == option_number {
            occurrence = match occurrence {
                Some(0) => {
                    if oval.len() == value.len() {
                        oval[..].copy_from_slice(value);
                    } else {
                        result = oscore_msgerr_native_t::UpdateOptionWrongLength;
                    }
                    None
                }
                Some(i) => Some(i - 1),
                None => None,
            }
        }
    });
    if occurrence.is_some() {
        result = oscore_msgerr_native_t::UpdateOptionNotFound;
    }
    result
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_optiter_init(
    msg: oscore_msg_native_t,
    iter: &mut MaybeUninit<oscore_msg_native_optiter_t>,
) {
    let msg = unsafe { Message::from_ptr(msg) };
    assert!(
        core::mem::size_of::<oscore_msg_native_optiter_t>()
            >= core::mem::size_of::<OptionsIter<'static>>(),
        "OptionsIter doesn't fit in oscore_msg_native_optiter_t"
    );
    assert!(
        core::mem::align_of::<oscore_msg_native_optiter_t>()
            >= core::mem::align_of::<OptionsIter<'static>>(),
        "oscore_msg_native_optiter_t is insufficiently aligned for OptionsIter"
    );
    let iter: &mut MaybeUninit<OptionsIter> = unsafe { core::mem::transmute(iter) };
    iter.write(msg.options());
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_map_payload(
    msg: oscore_msg_native_t,
    payload: &mut *mut u8,
    payload_len: &mut usize,
) -> oscore_msgerr_native_t {
    let msg = unsafe { Message::from_ptr(msg) };
    if let Some(len) = msg.payload_length {
        *payload_len = len;
    } else {
        let original_space = msg.available_space();
        // FIXME: Heap versions would report SIZE_MAX, which is technically correct but highly
        // impractical for the implementation that'd just map it all rater than bounding.
        let available_space = original_space.clamp(0, 4097);
        *payload_len = available_space.saturating_sub(1);
    }
    match msg.payload_mut_with_len(*payload_len) {
        Ok(result) => {
            *payload = result.as_mut_ptr();
            oscore_msgerr_native_t::ResultOk
        }
        Err(e) => e,
    }
}

#[no_mangle]
pub extern "C" fn oscore_msgerr_native_is_error(msgerr: oscore_msgerr_native_t) -> bool {
    msgerr != oscore_msgerr_native_t::ResultOk
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_trim_payload(
    msg: oscore_msg_native_t,
    payload_len: usize,
) -> oscore_msgerr_native_t {
    match unsafe { Message::from_ptr(msg) }.truncate(payload_len) {
        Ok(()) => oscore_msgerr_native_t::ResultOk,
        Err(e) => e,
    }
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_optiter_next(
    _: oscore_msg_native_t,
    iter: &mut oscore_msg_native_optiter_t,
    option_number: &mut u16,
    value: &mut *const u8,
    value_len: &mut usize,
) -> bool {
    let iter: &mut OptionsIter = unsafe { core::mem::transmute(iter) };
    if let Some(o) = iter.next() {
        *option_number = o.number();
        let value_slice = o.value();
        *value = value_slice.as_ptr();
        *value_len = value_slice.len();
        true
    } else {
        false
    }
}

#[no_mangle]
pub extern "C" fn oscore_msg_native_optiter_finish(
    _: oscore_msg_native_t,
    iter: &mut MaybeUninit<oscore_msg_native_optiter_t>,
) -> oscore_msgerr_native_t {
    let iter: &mut MaybeUninit<OptionsIter> = unsafe { core::mem::transmute(iter) };
    unsafe { iter.assume_init_drop() };
    // Here the error models differ: oscore_msg_native would report errors here, whereas
    // coap-message implementations are expected to produce garbage options to indicate somethign
    // went awry
    oscore_msgerr_native_t::ResultOk
}

#[cfg(feature = "alloc")]
#[no_mangle]
pub extern "C" fn oscore_test_msg_create() -> oscore_msg_native_t {
    let msg = alloc::boxed::Box::new(Message {
        data: MessageVariant::Heap(coap_message_implementations::heap::HeapMessage::new()),
        payload_length: None,
    });

    oscore_msg_native_t(alloc::boxed::Box::into_raw(msg) as *mut _)
}

#[cfg(feature = "alloc")]
#[no_mangle]
pub extern "C" fn oscore_test_msg_destroy(msg: oscore_msg_native_t) {
    let boxed = unsafe { alloc::boxed::Box::from_raw(msg.0) };
    drop(boxed);
}

// Functions for interacting with messages from the Rust side

/// Make a [coap_message_implementations::heap::HeapMessage] usable as a [raw::oscore_msg_native_t]
///
/// This is a low-level function mainly used by tests.
#[cfg(feature = "alloc")]
// FIXME: This drops the heapmessage afterwards; that's OK for decoding (and for encoding we'll
// probably want to create it freshly anyway)
pub fn with_heapmessage_as_msg_native<F, R>(
    msg: coap_message_implementations::heap::HeapMessage,
    f: F,
) -> R
where
    F: FnOnce(oscore_msg_native_t) -> R,
{
    // This is just the kind of message that does need its payload length known and set
    let payload_len = msg.payload().len();
    let mut wrapped_message = Message {
        data: MessageVariant::Heap(msg),
        payload_length: Some(payload_len),
    };
    f(oscore_msg_native_t(
        &mut wrapped_message as *mut _ as *mut _,
    ))
}

/// Make a [coap_message_implementations::inmemory_write::Message] usable as a [raw::oscore_msg_native_t]
///
/// This is a low-level function used by the high-level wrappers in the liboscore crate.
pub fn with_inmemory_as_msg_native<'a, 'b: 'a, F, R>(
    msg: &'a mut coap_message_implementations::inmemory_write::Message<'b>,
    f: F,
) -> R
where
    F: FnOnce(oscore_msg_native_t) -> R,
{
    // FIXME: find a safe way to do this
    // Safety: Message is for some reason considered invariant over its lifetime argument, when
    // from how it's working it should be coercible into a shorter lifetime argument.
    let msg: &'a mut coap_message_implementations::inmemory_write::Message<'a> =
        unsafe { core::mem::transmute(msg) };

    // coap-message doesn't distinguish between "unpopulated message" and "populated message",
    // which is bad when we can have either, need to store the length of either, but can't really
    // find out the state.
    //
    // Conveniently, at least this type allows mapping the payload read-only, and we can take the
    // length of that to find out.

    let read_payload_len = msg.payload().len();
    let mut wrapped_message = Message {
        data: MessageVariant::InMemory(msg),
        payload_length: if read_payload_len == 0 {
            // It's unpopulated (because any message coming into libOSCORE would be nonempty if it was
            // received)
            None
        } else {
            Some(read_payload_len)
        },
    };
    f(oscore_msg_native_t(
        &mut wrapped_message as *mut _ as *mut _,
    ))
}

/// Sealed helper trait to implement with_msg_native
pub trait WithMsgNative: sealed::Sealed {
    fn with_msg_native<F: FnOnce(oscore_msg_native_t) -> R, R>(self, f: F) -> R;
}

impl<'a, 'b> WithMsgNative for &'a mut coap_message_implementations::inmemory_write::Message<'b> {
    fn with_msg_native<F: FnOnce(oscore_msg_native_t) -> R, R>(self, f: F) -> R {
        with_inmemory_as_msg_native(self, f)
    }
}
#[cfg(feature = "alloc")]
impl WithMsgNative for coap_message_implementations::heap::HeapMessage {
    fn with_msg_native<F: FnOnce(oscore_msg_native_t) -> R, R>(self, f: F) -> R {
        with_heapmessage_as_msg_native(self, f)
    }
}

mod sealed {
    pub trait Sealed {}

    impl<'a, 'b> Sealed for &'a mut coap_message_implementations::inmemory_write::Message<'b> {}
    #[cfg(feature = "alloc")]
    impl Sealed for coap_message_implementations::heap::HeapMessage {}
}