libcoap_rs/message/
mod.rs

1// SPDX-License-Identifier: BSD-2-Clause
2/*
3 * message.rs - Types related to CoAP messages.
4 * This file is part of the libcoap-rs crate, see the README and LICENSE files for
5 * more information and terms of use.
6 * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
7 * See the README as well as the LICENSE file for more information.
8 */
9
10//! Types related to message handling, parsing and creation.
11//!
12//! The base unit that is transmitted between a CoAP client and a CoAP server is called a CoAP
13//! message (in libcoap also referred to as a "pdu"). This module contains both the basic
14//! representation for messages in libcoap-rs ([CoapMessage]) as well as builders that simplify the
15//! process of creating requests and responses and setting the appropriate options ([CoapRequest]
16//! and [CoapResponse]).
17
18use std::{ffi::c_void, mem::MaybeUninit, slice::Iter};
19
20use num_traits::FromPrimitive;
21
22use libcoap_sys::{
23    coap_add_data, coap_add_data_large_request, coap_add_optlist_pdu, coap_add_token, coap_delete_optlist,
24    coap_delete_pdu, coap_get_data, coap_insert_optlist, coap_new_optlist, coap_opt_length, coap_opt_t, coap_opt_value,
25    coap_option_iterator_init, coap_option_next, coap_option_num_t, coap_optlist_t, coap_pdu_get_code,
26    coap_pdu_get_mid, coap_pdu_get_token, coap_pdu_get_type, coap_pdu_init, coap_pdu_set_code, coap_pdu_set_type,
27    coap_pdu_t, coap_session_t,
28};
29pub use request::CoapRequest;
30pub use response::CoapResponse;
31
32use crate::types::{decode_var_len_u16, decode_var_len_u32, encode_var_len_u16, encode_var_len_u32, encode_var_len_u8};
33use crate::{
34    error::{MessageConversionError, OptionValueError},
35    protocol::{
36        Block, CoapMatch, CoapMessageCode, CoapMessageType, CoapOptionNum, CoapOptionType, ContentFormat, ETag,
37        HopLimit, MaxAge, NoResponse, Observe, ProxyScheme, ProxyUri, Size, UriHost, UriPath, UriPort, UriQuery,
38    },
39    session::CoapSessionCommon,
40    types::CoapMessageId,
41};
42
43pub mod request;
44pub mod response;
45
46/// Representation of a CoAP option including its value.
47///
48/// For an enum describing the possible option types (and their associated option numbers), see
49/// [CoapOptionType], for the data type representing option numbers, see [CoapOptionNum]
50#[derive(Debug, Hash, Eq, PartialEq, Clone)]
51pub enum CoapOption {
52    IfMatch(CoapMatch),
53    IfNoneMatch,
54    UriHost(UriHost),
55    UriPort(UriPort),
56    UriPath(UriPath),
57    UriQuery(UriQuery),
58    LocationPath(UriPath),
59    LocationQuery(UriQuery),
60    ProxyUri(ProxyUri),
61    ProxyScheme(ProxyScheme),
62    ContentFormat(ContentFormat),
63    Accept(ContentFormat),
64    Size1(Size),
65    Size2(Size),
66    Block1(Block),
67    Block2(Block),
68    // TODO
69    // OsCore
70    HopLimit(HopLimit),
71    NoResponse(NoResponse),
72    ETag(ETag),
73    MaxAge(MaxAge),
74    Observe(Observe),
75    Other(CoapOptionNum, Box<[u8]>),
76}
77
78impl CoapOption {
79    /// Create a CoAP option from its raw representation in the C library.
80    ///
81    /// # Safety
82    /// `opt` must be a valid pointer to a well formed coap_opt_t value as returned by
83    /// [coap_option_next()].
84    pub(crate) unsafe fn from_raw_opt(
85        number: coap_option_num_t,
86        opt: *const coap_opt_t,
87    ) -> Result<CoapOption, OptionValueError> {
88        let value = Vec::from(std::slice::from_raw_parts(
89            coap_opt_value(opt),
90            coap_opt_length(opt) as usize,
91        ));
92        match CoapOptionType::try_from(number) {
93            Ok(opt_type) => {
94                if opt_type.min_len() > value.len() {
95                    return Err(OptionValueError::TooShort);
96                } else if opt_type.max_len() < value.len() {
97                    return Err(OptionValueError::TooLong);
98                }
99                match opt_type {
100                    CoapOptionType::IfMatch => Ok(CoapOption::IfMatch(if value.is_empty() {
101                        CoapMatch::Empty
102                    } else {
103                        CoapMatch::ETag(value.into_boxed_slice())
104                    })),
105                    CoapOptionType::UriHost => Ok(CoapOption::UriHost(String::from_utf8(value)?)),
106                    CoapOptionType::ETag => Ok(CoapOption::ETag(value.into_boxed_slice())),
107                    CoapOptionType::IfNoneMatch => Ok(CoapOption::IfNoneMatch),
108                    CoapOptionType::UriPort => Ok(CoapOption::UriPort(decode_var_len_u16(value.as_slice()))),
109                    CoapOptionType::LocationPath => Ok(CoapOption::LocationPath(String::from_utf8(value)?)),
110                    CoapOptionType::UriPath => Ok(CoapOption::UriPath(String::from_utf8(value)?)),
111                    CoapOptionType::ContentFormat => {
112                        Ok(CoapOption::ContentFormat(decode_var_len_u16(value.as_slice())))
113                    },
114                    CoapOptionType::MaxAge => Ok(CoapOption::MaxAge(decode_var_len_u32(value.as_slice()))),
115                    CoapOptionType::UriQuery => Ok(CoapOption::UriQuery(String::from_utf8(value)?)),
116                    CoapOptionType::Accept => Ok(CoapOption::Accept(decode_var_len_u16(value.as_slice()))),
117                    CoapOptionType::LocationQuery => Ok(CoapOption::LocationQuery(String::from_utf8(value)?)),
118                    CoapOptionType::ProxyUri => Ok(CoapOption::ProxyUri(String::from_utf8(value)?)),
119                    CoapOptionType::ProxyScheme => Ok(CoapOption::ProxyScheme(String::from_utf8(value)?)),
120                    CoapOptionType::Size1 => Ok(CoapOption::Size1(decode_var_len_u32(value.as_slice()))),
121                    CoapOptionType::Size2 => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
122                    CoapOptionType::Block1 => Ok(CoapOption::Block1(decode_var_len_u32(value.as_slice()))),
123                    CoapOptionType::Block2 => Ok(CoapOption::Block2(decode_var_len_u32(value.as_slice()))),
124                    CoapOptionType::HopLimit => Ok(CoapOption::HopLimit(decode_var_len_u16(value.as_slice()))),
125                    CoapOptionType::NoResponse => Ok(CoapOption::Size2(decode_var_len_u32(value.as_slice()))),
126                    CoapOptionType::Observe => Ok(CoapOption::Observe(decode_var_len_u32(value.as_slice()))),
127                }
128            },
129            _ => Ok(CoapOption::Other(number, value.into_boxed_slice())),
130        }
131    }
132
133    /// Returns the option number associated with this option.
134    pub fn number(&self) -> CoapOptionNum {
135        match self {
136            CoapOption::IfMatch(_) => CoapOptionType::IfMatch as u16,
137            CoapOption::IfNoneMatch => CoapOptionType::IfNoneMatch as u16,
138            CoapOption::UriHost(_) => CoapOptionType::UriHost as u16,
139            CoapOption::UriPort(_) => CoapOptionType::UriPort as u16,
140            CoapOption::UriPath(_) => CoapOptionType::UriPath as u16,
141            CoapOption::UriQuery(_) => CoapOptionType::UriQuery as u16,
142            CoapOption::LocationPath(_) => CoapOptionType::LocationPath as u16,
143            CoapOption::LocationQuery(_) => CoapOptionType::LocationQuery as u16,
144            CoapOption::ProxyUri(_) => CoapOptionType::ProxyUri as u16,
145            CoapOption::ProxyScheme(_) => CoapOptionType::ProxyScheme as u16,
146            CoapOption::ContentFormat(_) => CoapOptionType::ContentFormat as u16,
147            CoapOption::Accept(_) => CoapOptionType::Accept as u16,
148            CoapOption::Size1(_) => CoapOptionType::Size1 as u16,
149            CoapOption::Size2(_) => CoapOptionType::Size2 as u16,
150            CoapOption::Block1(_) => CoapOptionType::Block1 as u16,
151            CoapOption::Block2(_) => CoapOptionType::Block2 as u16,
152            CoapOption::HopLimit(_) => CoapOptionType::HopLimit as u16,
153            CoapOption::NoResponse(_) => CoapOptionType::NoResponse as u16,
154            CoapOption::ETag(_) => CoapOptionType::ETag as u16,
155            CoapOption::MaxAge(_) => CoapOptionType::MaxAge as u16,
156            CoapOption::Observe(_) => CoapOptionType::Observe as u16,
157            CoapOption::Other(num, _) => *num,
158        }
159    }
160
161    /// Converts the option into a `Box<[u8]>` containing the value bytes.
162    pub fn into_value_bytes(self) -> Result<Box<[u8]>, OptionValueError> {
163        let num = self.number();
164        let bytes = match self {
165            CoapOption::IfMatch(val) => match val {
166                CoapMatch::ETag(tag) => tag,
167                CoapMatch::Empty => Box::new([]),
168            },
169            CoapOption::IfNoneMatch => Box::new([]),
170            CoapOption::UriHost(value) => value.into_boxed_str().into_boxed_bytes(),
171            CoapOption::UriPort(value) => encode_var_len_u16(value),
172            CoapOption::UriPath(value) => value.into_boxed_str().into_boxed_bytes(),
173            CoapOption::UriQuery(value) => value.into_boxed_str().into_boxed_bytes(),
174            CoapOption::LocationPath(value) => value.into_boxed_str().into_boxed_bytes(),
175            CoapOption::LocationQuery(value) => value.into_boxed_str().into_boxed_bytes(),
176            CoapOption::ProxyUri(value) => value.into_boxed_str().into_boxed_bytes(),
177            CoapOption::ProxyScheme(value) => value.into_boxed_str().into_boxed_bytes(),
178            CoapOption::ContentFormat(value) => encode_var_len_u16(value),
179            CoapOption::Accept(value) => encode_var_len_u16(value),
180            CoapOption::Size1(value) => encode_var_len_u32(value),
181            CoapOption::Size2(value) => encode_var_len_u32(value),
182            CoapOption::Block1(value) => encode_var_len_u32(value),
183            CoapOption::Block2(value) => encode_var_len_u32(value),
184            CoapOption::HopLimit(value) => encode_var_len_u16(value),
185            CoapOption::NoResponse(value) => encode_var_len_u8(value),
186            CoapOption::ETag(value) => value,
187            CoapOption::MaxAge(value) => encode_var_len_u32(value),
188            CoapOption::Observe(value) => encode_var_len_u32(value),
189            CoapOption::Other(_num, data) => data,
190        };
191        if let Some(opt_type) = <CoapOptionType as FromPrimitive>::from_u16(num) {
192            if bytes.len() < opt_type.min_len() {
193                return Err(OptionValueError::TooShort);
194            } else if bytes.len() > opt_type.max_len() {
195                return Err(OptionValueError::TooLong);
196            }
197        }
198        Ok(bytes)
199    }
200
201    /// Converts this option into a raw coap_optlist_t instance, suitable for addition to a raw
202    /// coap_pdu_t.
203    pub(crate) fn into_optlist_entry(self) -> Result<*mut coap_optlist_t, OptionValueError> {
204        let num = self.number();
205        let value = self.into_value_bytes()?;
206        Ok(unsafe { coap_new_optlist(num, value.len(), value.as_ptr()) })
207    }
208}
209
210/// Interface for CoAP messages common between requests, responses and other messages.
211pub trait CoapMessageCommon {
212    /// Add the supplied CoAP option to this message.
213    fn add_option(&mut self, option: CoapOption) {
214        self.as_message_mut().options.push(option);
215    }
216
217    /// Clear the list of options that were added to this message using [add_option()](CoapMessageCommon::add_option()).
218    fn clear_options(&mut self) {
219        self.as_message_mut().options.clear();
220    }
221
222    /// Returns an iterator over the options contained in this message.
223    fn options_iter(&self) -> Iter<CoapOption> {
224        self.as_message().options.iter()
225    }
226
227    /// Returns the CoAP message type (confirmable, non-confirmable, acknowledgement, rst) of this message.
228    fn type_(&self) -> CoapMessageType {
229        self.as_message().type_
230    }
231
232    /// Sets the CoAP message type (confirmable, non-confirmable, acknowledgement, rst) of this message.
233    fn set_type_(&mut self, type_: CoapMessageType) {
234        self.as_message_mut().type_ = type_;
235    }
236
237    /// Returns the message code of this message.
238    /// To determine whether the message is a request or response, use [CoapMessageCode::try_from()]
239    /// and match for the enum variants.
240    fn code(&self) -> CoapMessageCode {
241        self.as_message().code
242    }
243
244    /// Sets the message code of this message.
245    fn set_code<C: Into<CoapMessageCode>>(&mut self, code: C) {
246        self.as_message_mut().code = code.into();
247    }
248
249    /// Returns the CoAP message ID for this message.
250    fn mid(&self) -> Option<CoapMessageId> {
251        self.as_message().mid
252    }
253
254    /// Sets the CoAP message ID for this message.
255    fn set_mid(&mut self, mid: Option<CoapMessageId>) {
256        self.as_message_mut().mid = mid;
257    }
258
259    /// Returns a reference to the data/body of this message.
260    fn data(&self) -> Option<&[u8]> {
261        self.as_message().data.as_ref().map(|v| v.as_ref())
262    }
263
264    /// Sets the data/body of this message.
265    fn set_data<D: Into<Box<[u8]>>>(&mut self, data: Option<D>) {
266        self.as_message_mut().data = data.map(Into::into);
267    }
268
269    /// Returns the message token.
270    fn token(&self) -> Option<&[u8]> {
271        self.as_message().token.as_ref().map(|v| v.as_ref())
272    }
273
274    /// Sets the message token.
275    ///
276    /// Note that [CoapSessionCommon::send_request()] will automatically set the token to a random
277    /// value if you don't.
278    fn set_token<D: Into<Box<[u8]>>>(&mut self, token: Option<D>) {
279        self.as_message_mut().token = token.map(Into::into);
280    }
281
282    /// Returns a reference to this message.
283    fn as_message(&self) -> &CoapMessage;
284    /// Returns a mutable reference to this message.
285    fn as_message_mut(&mut self) -> &mut CoapMessage;
286}
287
288/// Representation of a CoAP message.
289#[derive(Debug, Clone, PartialEq, Eq, Hash)]
290pub struct CoapMessage {
291    /// CoAP message type (CON, NON, ACK, RST).
292    type_: CoapMessageType,
293    /// CoAP message code (e.g. 2.00 Content or 4.04 Not Found)
294    code: CoapMessageCode,
295    /// ID of this message – unique to each message sent over this session.
296    mid: Option<CoapMessageId>,
297    /// List of CoAP options associated with this message.
298    options: Vec<CoapOption>,
299    /// CoAP message token – used for request-response-matching.
300    token: Option<Box<[u8]>>,
301    /// Message body of this message.
302    data: Option<Box<[u8]>>,
303}
304
305impl CoapMessage {
306    /// Creates a new CoAP message with the given type and code.
307    pub fn new(type_: CoapMessageType, code: CoapMessageCode) -> CoapMessage {
308        CoapMessage {
309            type_,
310            code,
311            mid: None,
312            options: Vec::new(),
313            token: None,
314            data: None,
315        }
316    }
317
318    /// Parses the given raw coap_pdu_t into a CoapMessage.
319    ///
320    /// # Safety
321    /// raw_pdu must point to a valid instance of coap_pdu_t.
322    pub unsafe fn from_raw_pdu(raw_pdu: *const coap_pdu_t) -> Result<CoapMessage, MessageConversionError> {
323        let mut option_iter = MaybeUninit::zeroed();
324        coap_option_iterator_init(raw_pdu, option_iter.as_mut_ptr(), std::ptr::null());
325        let mut option_iter = option_iter.assume_init();
326        let mut options = Vec::new();
327        while let Some(read_option) = coap_option_next(&mut option_iter).as_ref() {
328            options.push(CoapOption::from_raw_opt(option_iter.number, read_option).map_err(|e| {
329                MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(option_iter.number).ok(), e)
330            })?);
331        }
332        let mut len: usize = 0;
333        let mut data = std::ptr::null();
334        coap_get_data(raw_pdu, &mut len, &mut data);
335        let data = match len {
336            0 => None,
337            len => Some(Vec::from(std::slice::from_raw_parts(data, len)).into_boxed_slice()),
338        };
339        let raw_token = coap_pdu_get_token(raw_pdu);
340        let token = Vec::from(std::slice::from_raw_parts(raw_token.s, raw_token.length));
341        Ok(CoapMessage {
342            type_: coap_pdu_get_type(raw_pdu).into(),
343            code: coap_pdu_get_code(raw_pdu).try_into().unwrap(),
344            mid: Some(coap_pdu_get_mid(raw_pdu)),
345            options,
346            token: Some(token.into_boxed_slice()),
347            data,
348        })
349    }
350
351    /// Converts this message into a raw PDU suitable for sending using the raw [coap_send()](libcoap_sys::coap_send())
352    /// function.
353    ///
354    /// The caller is responsible for freeing the returned PDU, either by calling [coap_send()](libcoap_sys::coap_send()) or
355    /// [coap_delete_pdu()].
356    pub fn into_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>(
357        mut self,
358        session: &S,
359    ) -> Result<*mut coap_pdu_t, MessageConversionError> {
360        let message = self.as_message_mut();
361
362        // SAFETY: all values are valid, cannot cause UB.
363        let pdu = unsafe {
364            coap_pdu_init(
365                message.type_.to_raw_pdu_type(),
366                message.code.to_raw_pdu_code(),
367                message.mid.ok_or(MessageConversionError::MissingMessageId)?,
368                session.max_pdu_size(),
369            )
370        };
371        if pdu.is_null() {
372            return Err(MessageConversionError::Unknown);
373        }
374        // SAFETY: We just checked that pdu is a valid pointer.
375        unsafe {
376            let result = self.apply_to_raw_pdu(pdu, session);
377            if result.is_err() {
378                coap_delete_pdu(pdu);
379            }
380            result
381        }
382    }
383
384    /// Applies this message to the given raw PDU.
385    ///
386    /// Note that existing parameters of the PDU are not explicitly removed. However, because this
387    /// function calls [coap_add_token()](libcoap_sys::coap_add_token()), any pre-added options and
388    /// data will probably be destroyed.
389    ///
390    /// If you don't have a raw pdu instance, use [CoapMessage::into_raw_pdu()] instead of this
391    /// function.
392    ///
393    /// # Panics
394    /// Panics if the provided `raw_pdu` is a null pointer.
395    ///
396    /// # Safety
397    /// raw_pdu must point to a valid mutable instance of coap_pdu_t.
398    pub(crate) unsafe fn apply_to_raw_pdu<'a, S: CoapSessionCommon<'a> + ?Sized>(
399        mut self,
400        raw_pdu: *mut coap_pdu_t,
401        session: &S,
402    ) -> Result<*mut coap_pdu_t, MessageConversionError> {
403        assert!(!raw_pdu.is_null(), "attempted to apply CoapMessage to null pointer");
404        coap_pdu_set_type(raw_pdu, self.type_.to_raw_pdu_type());
405        coap_pdu_set_code(raw_pdu, self.code.to_raw_pdu_code());
406        let message = self.as_message_mut();
407        let token: &[u8] = message.token.as_ref().ok_or(MessageConversionError::MissingToken)?;
408        if coap_add_token(raw_pdu, token.len(), token.as_ptr()) == 0 {
409            return Err(MessageConversionError::Unknown);
410        }
411        let mut optlist = None;
412        let option_iter = std::mem::take(&mut message.options).into_iter();
413        for option in option_iter {
414            let optnum = option.number();
415            let entry = option
416                .into_optlist_entry()
417                .map_err(|e| MessageConversionError::InvalidOptionValue(CoapOptionType::try_from(optnum).ok(), e))?;
418            if entry.is_null() {
419                if let Some(optlist) = optlist {
420                    coap_delete_optlist(optlist);
421                    return Err(MessageConversionError::Unknown);
422                }
423            }
424            match optlist {
425                None => {
426                    optlist = Some(entry);
427                },
428                Some(mut optlist) => {
429                    coap_insert_optlist(&mut optlist, entry);
430                },
431            }
432        }
433        if let Some(mut optlist) = optlist {
434            let optlist_add_success = coap_add_optlist_pdu(raw_pdu, &mut optlist);
435            coap_delete_optlist(optlist);
436            if optlist_add_success == 0 {
437                return Err(MessageConversionError::Unknown);
438            }
439        }
440        if let Some(data) = message.data.take() {
441            match message.code {
442                CoapMessageCode::Empty => return Err(MessageConversionError::DataInEmptyMessage),
443                CoapMessageCode::Request(_) => {
444                    let len = data.len();
445                    let box_ptr = Box::into_raw(data);
446                    coap_add_data_large_request(
447                        session.raw_session_mut(),
448                        raw_pdu,
449                        len,
450                        box_ptr as *mut u8,
451                        Some(large_data_cleanup_handler),
452                        box_ptr as *mut c_void,
453                    );
454                },
455                CoapMessageCode::Response(_) => {
456                    // TODO blockwise transfer here as well.
457                    // (for some reason libcoap needs the request PDU here?)
458                    let data: &[u8] = data.as_ref();
459                    if coap_add_data(raw_pdu, data.len(), data.as_ptr()) == 0 {
460                        return Err(MessageConversionError::Unknown);
461                    }
462                },
463            }
464        }
465        Ok(raw_pdu)
466    }
467}
468
469impl CoapMessageCommon for CoapMessage {
470    fn as_message(&self) -> &CoapMessage {
471        self
472    }
473
474    fn as_message_mut(&mut self) -> &mut CoapMessage {
475        self
476    }
477}
478
479impl From<CoapRequest> for CoapMessage {
480    fn from(val: CoapRequest) -> Self {
481        val.into_message()
482    }
483}
484
485impl From<CoapResponse> for CoapMessage {
486    fn from(val: CoapResponse) -> Self {
487        val.into_message()
488    }
489}
490
491/// Handler provided to libcoap to cleanup large message bodies.
492unsafe extern "C" fn large_data_cleanup_handler(_session: *mut coap_session_t, app_ptr: *mut c_void) {
493    std::mem::drop(Box::from_raw(app_ptr as *mut u8));
494}