coap_zero/message/
encoded_message.rs

1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7//! Encoded CoAP Message
8//!
9//! This module is meant to be used to transmit a message or to access the
10//! fields of an already encoded or freshly received message.
11
12use bondrewd::Bitfields;
13use core::cell::Cell;
14
15use super::{
16    codes::Code, header::MessageHeader, options::OptionIterator, token::Token, Error, Message, Type,
17};
18
19/// Encoded CoAP Message as it will be transmitted/received
20#[derive(Debug, Clone, Eq)]
21pub struct EncodedMessage<'data> {
22    pub(crate) data: &'data [u8],
23    /// Since the payload position is determined by iterating the whole list of options, accessing
24    /// the payload in a message with a few options might be unnecessarily expensive. When the
25    /// [`OptionIterator`] is drained for the first time, it will store the payload offset in this
26    /// field using [`EncodedMessage::set_payload_offset`]. Like this, the options only need to be
27    /// iterated once to access the payload as often as desired without further cost.
28    payload_offset_cache: Cell<Option<usize>>,
29}
30
31impl<'data> PartialEq for EncodedMessage<'data> {
32    fn eq(&self, other: &Self) -> bool {
33        self.data == other.data
34    }
35}
36
37impl<'data> EncodedMessage<'data> {
38    /// Make a new Message from the data. This method only fails if the message is shorter than 4
39    /// bytes, i.e. the 4-bytes header is not even complete. This is checked here first because
40    /// like that, some of the accessor methods do not need to return a `Result` but can access the
41    /// header directly in a safe way.
42    pub fn try_new(data: &'data [u8]) -> Result<Self, Error> {
43        if data.len() >= MessageHeader::BYTE_SIZE {
44            Ok(Self {
45                data,
46                payload_offset_cache: Cell::new(None),
47            })
48        } else {
49            Err(Error::MsgTooShort)
50        }
51    }
52
53    /// Convenience constructor for encoded ACKs (can not be used for piggybacked responses)
54    pub fn new_ack(message_id: u16, buf: &'data mut [u8; 4]) -> Self {
55        Message::<0>::new_ack(message_id).encode(buf).unwrap()
56    }
57
58    /// Convenience constructor for encoded RSTs
59    pub fn new_rst(message_id: u16, buf: &'data mut [u8; 4]) -> Self {
60        Message::<0>::new_rst(message_id).encode(buf).unwrap()
61    }
62
63    /// Convenience constructor for encoded pings
64    pub fn new_ping(message_id: u16, buf: &'data mut [u8; 4]) -> Self {
65        Message::<0>::new_ping(message_id).encode(buf).unwrap()
66    }
67
68    /// Convenience method for creating an encoded empty message
69    fn empty(message_type: Type, message_id: u16) -> [u8; 4] {
70        let mut buf = [0u8; 4];
71        Message::<0>::new_empty(message_type, message_id)
72            .encode(&mut buf)
73            .unwrap();
74        buf
75    }
76
77    /// Convenience method for creating an encoded ACK
78    pub fn ack(message_id: u16) -> [u8; 4] {
79        Self::empty(Type::Acknowledgement, message_id)
80    }
81
82    /// Convenience method for creating an encoded RST
83    pub fn rst(message_id: u16) -> [u8; 4] {
84        Self::empty(Type::Reset, message_id)
85    }
86
87    /// Convenience method for creating an encoded ping
88    pub fn ping(message_id: u16) -> [u8; 4] {
89        Self::empty(Type::Confirmable, message_id)
90    }
91
92    /// Length of the message
93    pub fn message_length(&self) -> usize {
94        self.data.len()
95    }
96
97    /// Return the parsed header of the message
98    fn header(&self) -> MessageHeader {
99        let mut buf = [0_u8; MessageHeader::BYTE_SIZE];
100        buf.clone_from_slice(&self.data[..MessageHeader::BYTE_SIZE]);
101        MessageHeader::from_bytes(buf)
102    }
103
104    /// CoAP Protocol Version
105    pub fn version(&self) -> u8 {
106        self.header().version()
107    }
108
109    /// Type of the message
110    pub fn message_type(&self) -> Type {
111        self.header().message_type()
112    }
113
114    /// Code of the message
115    pub fn code(&self) -> Result<Code, Error> {
116        self.header().code()
117    }
118
119    /// Id of the message
120    pub fn message_id(&self) -> u16 {
121        self.header().message_id()
122    }
123
124    /// Token of the message
125    pub fn token(&self) -> Result<Token, Error> {
126        let mut token = Token {
127            length: self.header().token_length()?,
128            ..Token::default()
129        };
130
131        token.bytes[..token.length.into()].copy_from_slice(
132            &self.data[MessageHeader::BYTE_SIZE
133                ..MessageHeader::BYTE_SIZE + self.header().token_length()? as usize],
134        );
135
136        Ok(token)
137    }
138
139    /// Iterator over all options in this message. This parses the message options every time it is
140    /// called, so be careful with calls to this iterator.
141    pub fn options_iter<'encmsg>(&'encmsg self) -> Result<OptionIterator<'encmsg, 'data>, Error> {
142        Ok(OptionIterator::new(
143            self,
144            MessageHeader::BYTE_SIZE + self.header().token_length()? as usize,
145        ))
146    }
147
148    /// Get the offset the payload has in the message
149    fn payload_offset(&self) -> Result<usize, Error> {
150        if let Some(payload_offset) = self.payload_offset_cache.get() {
151            return Ok(payload_offset);
152        }
153        let mut iter = self.options_iter()?;
154        // Consume the whole iterator to determine the index of the payload. The iterator sets the
155        // payload_offset automatically when it is fully consumed.
156        for option in iter.by_ref() {
157            let _ = option.map_err(Error::InvalidOption)?; // Ensure that all options can be decoded successfully
158        }
159
160        Ok(self.payload_offset_cache.get().unwrap())
161    }
162
163    pub(crate) fn set_payload_offset(&self, payload_offset: usize) {
164        self.payload_offset_cache.set(Some(payload_offset))
165    }
166
167    /// Return a reference to the payload of the message, if there is any
168    ///
169    /// To determine the payload offset (i.e. find the payload marker after the options), all
170    /// options must be iterated. Since the [`OptionIterator`] is generic on the maximum option size
171    /// and this requirement bubbles up, this method requires a `MAX_OPTION_SIZE`, too. Specify a
172    /// large enough value here (the same as for accessing the options), otherwise iterating the
173    /// options will fail.
174    pub fn payload(&self) -> Result<Option<&'data [u8]>, Error> {
175        let start_index = self.payload_offset()?;
176        // Check for the payload marker
177        if self.data.len() > start_index && self.data[start_index] == 0xFF {
178            Ok(Some(&self.data[start_index + 1..]))
179        } else {
180            // Iterating the options should only stop when
181            // a) No more data available
182            // b) Payload marker found
183            // c) Failure
184            // So if there is more data after the options which does not begin with the payload
185            // marker, something went wrong before -> Fix the bug.
186            assert!(
187                start_index == self.data.len(),
188                "No payload marker found but more data available"
189            );
190            Ok(None)
191        }
192    }
193
194    /// Checks if the message has Code::Empty
195    pub fn is_empty(&self) -> Result<bool, Error> {
196        Ok(matches!(self.code()?, Code::Empty))
197    }
198
199    /// Checks if the message has a RequestCode
200    pub fn is_request(&self) -> Result<bool, Error> {
201        Ok(matches!(self.code()?, Code::Request(_)))
202    }
203
204    /// Checks if the message has a ResponseCode
205    pub fn is_response(&self) -> Result<bool, Error> {
206        Ok(matches!(self.code()?, Code::Response(_)))
207    }
208
209    /// Checks if the message is an empty ACK (an ACK which is not a piggybacked response)
210    pub fn is_empty_ack(&self) -> Result<bool, Error> {
211        Ok(matches!(self.message_type(), Type::Acknowledgement) && self.is_empty()?)
212    }
213
214    /// Checks if the message is a RST (checks that the type is RST and the message is empty)
215    pub fn is_rst(&self) -> Result<bool, Error> {
216        Ok(matches!(self.message_type(), Type::Reset) && self.is_empty()?)
217    }
218
219    /// Checks if the message is a ping (an empty CON message)
220    pub fn is_ping(&self) -> Result<bool, Error> {
221        Ok(matches!(self.message_type(), Type::Confirmable) && self.is_empty()?)
222    }
223
224    /// 1. Checks that a valid code and token length is used. If this method succeeds,
225    /// [`EncodedMessage::code`], [`EncodedMessage::is_empty`], [`EncodedMessage::is_request`],
226    /// [`EncodedMessage::is_response`] and [`EncodedMessage::token`] are guaranteed to succeed and
227    /// the results can safely be `unwrap`ped.
228    ///
229    /// 2. Checks that "Table 1. Usage of Message Types from RFC 7252" is not violated:
230    /// ```ignore
231    /// +----------+-----+-----+-----+-----+
232    /// |          | CON | NON | ACK | RST |
233    /// +----------+-----+-----+-----+-----+
234    /// | Request  | X   | X   | -   | -   |
235    /// | Response | X   | X   | X   | -   |
236    /// | Empty    | *   | -   | X   | X   |
237    /// +----------+-----+-----+-----+-----+
238    /// ```
239    ///
240    /// 3. Checks that "empty" messages are actually empty (only the 4-bytes header is present,
241    /// i.e. zero-length token, no options, no payload).
242    ///
243    /// For efficiency, this method only does cheap checks. Specifically, it does not iterate the
244    /// options (after all, we are not decoding the full message here). So even if this check
245    /// succeeds, iterating the options or getting the payload may still fail.
246    // TODO It is extremely weird that the MAX_OPTION_SIZE generic bubbles through all methods
247    // which only slightly touch the options iterator. I guess this is some kind of design mistake.
248    pub fn check_msg_format<const MAX_OPTION_SIZE: usize>(&self) -> Result<(), Error> {
249        use Error::*;
250        self.token()?;
251        // The methods is_empty, is_request and is_response can only fail if code()? fails. So if
252        // the following line does not fail, none of these methods will fail.
253        let is_empty = self.is_empty()?;
254        if self.message_type() == Type::NonConfirmable && is_empty {
255            Err(EmptyNon)
256        } else if self.message_type() == Type::Acknowledgement && self.is_request().unwrap() {
257            Err(RequestAck)
258        } else if self.message_type() == Type::Reset && !is_empty {
259            Err(NonEmptyRst)
260        } else if is_empty {
261            // Getting the payload is cheap after verifying that there are no options
262            if self.token().unwrap() == Token::default()
263                && self.options_iter()?.next().is_none()
264                && self.payload()?.is_none()
265            {
266                Ok(())
267            } else {
268                Err(EmptyWithContent)
269            }
270        } else {
271            Ok(())
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::message::options::{CoapOption, CoapOptionName};
280    use crate::message::{self, Message};
281    use heapless::Vec;
282
283    /// Helper function to create a new EncodedMessage as we would encode it (payload_offset is
284    /// Some)
285    fn enc_message<'payload, 'buffer>(
286        options: Vec<CoapOption<'_>, 2>,
287        payload: Option<&'payload [u8]>,
288        buf: &'buffer mut [u8],
289    ) -> EncodedMessage<'buffer> {
290        Message::new(
291            message::Type::Confirmable,
292            message::Code::Empty,
293            0,
294            message::Token::default(),
295            options,
296            payload,
297        )
298        .encode(buf)
299        .unwrap()
300    }
301
302    /// Helper function to create a new EncodedMessage as we would receive it (payload_offset is
303    /// None)
304    fn rec_message<'payload, 'buffer>(
305        options: Vec<CoapOption<'_>, 2>,
306        payload: Option<&'payload [u8]>,
307        buf: &'buffer mut [u8],
308    ) -> EncodedMessage<'buffer> {
309        let message = enc_message(options, payload, buf);
310        message.payload_offset_cache.set(None);
311        message
312    }
313
314    /// Ensures that the payload_offset position is set after the options_iter has been iterated
315    /// over and that it is not set if the options_iter fails
316    #[test]
317    fn decode_payload_marker_positions() {
318        // reusable buffer
319        let mut buf = [0; 1000];
320        // dummy options
321        let option0 = CoapOption {
322            name: CoapOptionName::Accept,
323            value: b"",
324        };
325        let option1 = CoapOption {
326            name: CoapOptionName::Accept,
327            value: b"option value",
328        };
329        // No options, no payload
330        let m = rec_message(Vec::new(), None, &mut buf);
331        assert_eq!(m.payload_offset_cache.get(), None);
332        for _ in m.options_iter().unwrap() {}
333        assert!(m.payload_offset_cache.get().is_some());
334        // Options, no payload
335        let m = rec_message(
336            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
337            None,
338            &mut buf,
339        );
340        assert!(m.payload_offset_cache.get().is_none());
341        let mut iter = m.options_iter().unwrap();
342        iter.next();
343        iter.next();
344        assert!(m.payload_offset_cache.get().is_none());
345        iter.next();
346        assert!(m.payload_offset_cache.get().is_some());
347        // No options, payload
348        let m = rec_message(Vec::new(), Some(b"payload"), &mut buf);
349        assert_eq!(m.payload_offset_cache.get(), None);
350        m.options_iter().unwrap().next();
351        assert!(m.payload_offset_cache.get().is_some());
352        // Options, payload
353        let m = rec_message(
354            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
355            Some(b"payload"),
356            &mut buf,
357        );
358        let mut iter = m.options_iter().unwrap();
359        iter.next();
360        iter.next();
361        assert!(m.payload_offset_cache.get().is_none());
362        iter.next();
363        assert!(m.payload_offset_cache.get().is_some());
364        // The payload_offset is cloned which may require iterating for each cloned EncodedMessage
365        let original = rec_message(Vec::new(), None, &mut buf);
366        let clone = original.clone();
367        original.options_iter().unwrap().next();
368        assert!(original.payload_offset_cache.get().is_some());
369        assert!(clone.payload_offset_cache.get().is_none());
370        // A Some(payload_offset) may be cloned though
371        let original = rec_message(Vec::new(), None, &mut buf);
372        original.options_iter().unwrap().next();
373        let clone = original.clone();
374        assert!(original.payload_offset_cache.get().is_some());
375        assert!(clone.payload_offset_cache.get().is_some());
376        // Naturally, accessing the payload itself will also set the payload offset
377        let m = rec_message(Vec::new(), None, &mut buf);
378        m.payload_offset().unwrap();
379        assert!(m.payload_offset_cache.get().is_some());
380        // If something goes wrong along the way, the payload offset is not determined
381        let m = rec_message(
382            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
383            None,
384            &mut buf,
385        );
386        // We set the first option delta/length field to the invalid 0xF0. We have to work around
387        // the lifetimes though.
388        let len = m.data.len();
389        buf[4] = 0xF0;
390        let m = EncodedMessage::try_new(&buf[..len]).unwrap();
391        for _ in m.options_iter().unwrap() {}
392        assert!(m.payload_offset_cache.get().is_none());
393    }
394
395    /// Ensures that the payload_offset position is set after a message has been encoded
396    #[test]
397    fn encode_payload_marker_positions() {
398        // reusable buffer
399        let mut buf = [0; 1000];
400        // dummy options
401        let option0 = CoapOption {
402            name: CoapOptionName::Accept,
403            value: b"",
404        };
405        let option1 = CoapOption {
406            name: CoapOptionName::Accept,
407            value: b"option value",
408        };
409        // No options, no payload
410        let m = enc_message(Vec::new(), None, &mut buf);
411        assert!(m.payload_offset_cache.get().is_some());
412        // Options, no payload
413        let m = enc_message(
414            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
415            None,
416            &mut buf,
417        );
418        assert!(m.payload_offset_cache.get().is_some());
419        // No options, payload
420        let m = enc_message(Vec::new(), Some(b"payload"), &mut buf);
421        assert!(m.payload_offset_cache.get().is_some());
422        // Options, payload
423        let m = enc_message(
424            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
425            Some(b"payload"),
426            &mut buf,
427        );
428        assert!(m.payload_offset_cache.get().is_some());
429    }
430}