coap-zero 0.3.0

CoAP protocol implementation for no_std without alloc
Documentation
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
// Copyright Open Logistics Foundation
//
// Licensed under the Open Logistics Foundation License 1.3.
// For details on the licensing terms, see the LICENSE file.
// SPDX-License-Identifier: OLFL-1.3

//! Encoded CoAP Message
//!
//! This module is meant to be used to transmit a message or to access the
//! fields of an already encoded or freshly received message.

use bondrewd::Bitfields;
use core::cell::Cell;

use super::{
    codes::Code, header::MessageHeader, options::OptionIterator, token::Token, Error, Message, Type,
};

/// Encoded CoAP Message as it will be transmitted/received
#[derive(Debug, Clone, Eq)]
pub struct EncodedMessage<'data> {
    pub(crate) data: &'data [u8],
    /// Since the payload position is determined by iterating the whole list of options, accessing
    /// the payload in a message with a few options might be unnecessarily expensive. When the
    /// [`OptionIterator`] is drained for the first time, it will store the payload offset in this
    /// field using [`EncodedMessage::set_payload_offset`]. Like this, the options only need to be
    /// iterated once to access the payload as often as desired without further cost.
    payload_offset_cache: Cell<Option<usize>>,
}

impl<'data> PartialEq for EncodedMessage<'data> {
    fn eq(&self, other: &Self) -> bool {
        self.data == other.data
    }
}

impl<'data> EncodedMessage<'data> {
    /// Make a new Message from the data. This method only fails if the message is shorter than 4
    /// bytes, i.e. the 4-bytes header is not even complete. This is checked here first because
    /// like that, some of the accessor methods do not need to return a `Result` but can access the
    /// header directly in a safe way.
    pub fn try_new(data: &'data [u8]) -> Result<Self, Error> {
        if data.len() >= MessageHeader::BYTE_SIZE {
            Ok(Self {
                data,
                payload_offset_cache: Cell::new(None),
            })
        } else {
            Err(Error::MsgTooShort)
        }
    }

    /// Convenience constructor for encoded ACKs (can not be used for piggybacked responses)
    pub fn new_ack(message_id: u16, buf: &'data mut [u8; 4]) -> Self {
        Message::<0>::new_ack(message_id).encode(buf).unwrap()
    }

    /// Convenience constructor for encoded RSTs
    pub fn new_rst(message_id: u16, buf: &'data mut [u8; 4]) -> Self {
        Message::<0>::new_rst(message_id).encode(buf).unwrap()
    }

    /// Convenience constructor for encoded pings
    pub fn new_ping(message_id: u16, buf: &'data mut [u8; 4]) -> Self {
        Message::<0>::new_ping(message_id).encode(buf).unwrap()
    }

    /// Convenience method for creating an encoded empty message
    fn empty(message_type: Type, message_id: u16) -> [u8; 4] {
        let mut buf = [0u8; 4];
        Message::<0>::new_empty(message_type, message_id)
            .encode(&mut buf)
            .unwrap();
        buf
    }

    /// Convenience method for creating an encoded ACK
    pub fn ack(message_id: u16) -> [u8; 4] {
        Self::empty(Type::Acknowledgement, message_id)
    }

    /// Convenience method for creating an encoded RST
    pub fn rst(message_id: u16) -> [u8; 4] {
        Self::empty(Type::Reset, message_id)
    }

    /// Convenience method for creating an encoded ping
    pub fn ping(message_id: u16) -> [u8; 4] {
        Self::empty(Type::Confirmable, message_id)
    }

    /// Length of the message
    pub fn message_length(&self) -> usize {
        self.data.len()
    }

    /// Return the parsed header of the message
    fn header(&self) -> MessageHeader {
        let mut buf = [0_u8; MessageHeader::BYTE_SIZE];
        buf.clone_from_slice(&self.data[..MessageHeader::BYTE_SIZE]);
        MessageHeader::from_bytes(buf)
    }

    /// CoAP Protocol Version
    pub fn version(&self) -> u8 {
        self.header().version()
    }

    /// Type of the message
    pub fn message_type(&self) -> Type {
        self.header().message_type()
    }

    /// Code of the message
    pub fn code(&self) -> Result<Code, Error> {
        self.header().code()
    }

    /// Id of the message
    pub fn message_id(&self) -> u16 {
        self.header().message_id()
    }

    /// Token of the message
    pub fn token(&self) -> Result<Token, Error> {
        let mut token = Token {
            length: self.header().token_length()?,
            ..Token::default()
        };

        token.bytes[..token.length.into()].copy_from_slice(
            &self.data[MessageHeader::BYTE_SIZE
                ..MessageHeader::BYTE_SIZE + self.header().token_length()? as usize],
        );

        Ok(token)
    }

    /// Iterator over all options in this message. This parses the message options every time it is
    /// called, so be careful with calls to this iterator.
    pub fn options_iter<'encmsg>(&'encmsg self) -> Result<OptionIterator<'encmsg, 'data>, Error> {
        Ok(OptionIterator::new(
            self,
            MessageHeader::BYTE_SIZE + self.header().token_length()? as usize,
        ))
    }

    /// Get the offset the payload has in the message
    fn payload_offset(&self) -> Result<usize, Error> {
        if let Some(payload_offset) = self.payload_offset_cache.get() {
            return Ok(payload_offset);
        }
        let mut iter = self.options_iter()?;
        // Consume the whole iterator to determine the index of the payload. The iterator sets the
        // payload_offset automatically when it is fully consumed.
        for option in iter.by_ref() {
            let _ = option.map_err(Error::InvalidOption)?; // Ensure that all options can be decoded successfully
        }

        Ok(self.payload_offset_cache.get().unwrap())
    }

    pub(crate) fn set_payload_offset(&self, payload_offset: usize) {
        self.payload_offset_cache.set(Some(payload_offset))
    }

    /// Return a reference to the payload of the message, if there is any
    ///
    /// To determine the payload offset (i.e. find the payload marker after the options), all
    /// options must be iterated. Since the [`OptionIterator`] is generic on the maximum option size
    /// and this requirement bubbles up, this method requires a `MAX_OPTION_SIZE`, too. Specify a
    /// large enough value here (the same as for accessing the options), otherwise iterating the
    /// options will fail.
    pub fn payload(&self) -> Result<Option<&'data [u8]>, Error> {
        let start_index = self.payload_offset()?;
        // Check for the payload marker
        if self.data.len() > start_index && self.data[start_index] == 0xFF {
            Ok(Some(&self.data[start_index + 1..]))
        } else {
            // Iterating the options should only stop when
            // a) No more data available
            // b) Payload marker found
            // c) Failure
            // So if there is more data after the options which does not begin with the payload
            // marker, something went wrong before -> Fix the bug.
            assert!(
                start_index == self.data.len(),
                "No payload marker found but more data available"
            );
            Ok(None)
        }
    }

    /// Checks if the message has Code::Empty
    pub fn is_empty(&self) -> Result<bool, Error> {
        Ok(matches!(self.code()?, Code::Empty))
    }

    /// Checks if the message has a RequestCode
    pub fn is_request(&self) -> Result<bool, Error> {
        Ok(matches!(self.code()?, Code::Request(_)))
    }

    /// Checks if the message has a ResponseCode
    pub fn is_response(&self) -> Result<bool, Error> {
        Ok(matches!(self.code()?, Code::Response(_)))
    }

    /// Checks if the message is an empty ACK (an ACK which is not a piggybacked response)
    pub fn is_empty_ack(&self) -> Result<bool, Error> {
        Ok(matches!(self.message_type(), Type::Acknowledgement) && self.is_empty()?)
    }

    /// Checks if the message is a RST (checks that the type is RST and the message is empty)
    pub fn is_rst(&self) -> Result<bool, Error> {
        Ok(matches!(self.message_type(), Type::Reset) && self.is_empty()?)
    }

    /// Checks if the message is a ping (an empty CON message)
    pub fn is_ping(&self) -> Result<bool, Error> {
        Ok(matches!(self.message_type(), Type::Confirmable) && self.is_empty()?)
    }

    /// 1. Checks that a valid code and token length is used. If this method succeeds,
    /// [`EncodedMessage::code`], [`EncodedMessage::is_empty`], [`EncodedMessage::is_request`],
    /// [`EncodedMessage::is_response`] and [`EncodedMessage::token`] are guaranteed to succeed and
    /// the results can safely be `unwrap`ped.
    ///
    /// 2. Checks that "Table 1. Usage of Message Types from RFC 7252" is not violated:
    /// ```ignore
    /// +----------+-----+-----+-----+-----+
    /// |          | CON | NON | ACK | RST |
    /// +----------+-----+-----+-----+-----+
    /// | Request  | X   | X   | -   | -   |
    /// | Response | X   | X   | X   | -   |
    /// | Empty    | *   | -   | X   | X   |
    /// +----------+-----+-----+-----+-----+
    /// ```
    ///
    /// 3. Checks that "empty" messages are actually empty (only the 4-bytes header is present,
    /// i.e. zero-length token, no options, no payload).
    ///
    /// For efficiency, this method only does cheap checks. Specifically, it does not iterate the
    /// options (after all, we are not decoding the full message here). So even if this check
    /// succeeds, iterating the options or getting the payload may still fail.
    // TODO It is extremely weird that the MAX_OPTION_SIZE generic bubbles through all methods
    // which only slightly touch the options iterator. I guess this is some kind of design mistake.
    pub fn check_msg_format<const MAX_OPTION_SIZE: usize>(&self) -> Result<(), Error> {
        use Error::*;
        self.token()?;
        // The methods is_empty, is_request and is_response can only fail if code()? fails. So if
        // the following line does not fail, none of these methods will fail.
        let is_empty = self.is_empty()?;
        if self.message_type() == Type::NonConfirmable && is_empty {
            Err(EmptyNon)
        } else if self.message_type() == Type::Acknowledgement && self.is_request().unwrap() {
            Err(RequestAck)
        } else if self.message_type() == Type::Reset && !is_empty {
            Err(NonEmptyRst)
        } else if is_empty {
            // Getting the payload is cheap after verifying that there are no options
            if self.token().unwrap() == Token::default()
                && self.options_iter()?.next().is_none()
                && self.payload()?.is_none()
            {
                Ok(())
            } else {
                Err(EmptyWithContent)
            }
        } else {
            Ok(())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::message::options::{CoapOption, CoapOptionName};
    use crate::message::{self, Message};
    use heapless::Vec;

    /// Helper function to create a new EncodedMessage as we would encode it (payload_offset is
    /// Some)
    fn enc_message<'payload, 'buffer>(
        options: Vec<CoapOption<'_>, 2>,
        payload: Option<&'payload [u8]>,
        buf: &'buffer mut [u8],
    ) -> EncodedMessage<'buffer> {
        Message::new(
            message::Type::Confirmable,
            message::Code::Empty,
            0,
            message::Token::default(),
            options,
            payload,
        )
        .encode(buf)
        .unwrap()
    }

    /// Helper function to create a new EncodedMessage as we would receive it (payload_offset is
    /// None)
    fn rec_message<'payload, 'buffer>(
        options: Vec<CoapOption<'_>, 2>,
        payload: Option<&'payload [u8]>,
        buf: &'buffer mut [u8],
    ) -> EncodedMessage<'buffer> {
        let message = enc_message(options, payload, buf);
        message.payload_offset_cache.set(None);
        message
    }

    /// Ensures that the payload_offset position is set after the options_iter has been iterated
    /// over and that it is not set if the options_iter fails
    #[test]
    fn decode_payload_marker_positions() {
        // reusable buffer
        let mut buf = [0; 1000];
        // dummy options
        let option0 = CoapOption {
            name: CoapOptionName::Accept,
            value: b"",
        };
        let option1 = CoapOption {
            name: CoapOptionName::Accept,
            value: b"option value",
        };
        // No options, no payload
        let m = rec_message(Vec::new(), None, &mut buf);
        assert_eq!(m.payload_offset_cache.get(), None);
        for _ in m.options_iter().unwrap() {}
        assert!(m.payload_offset_cache.get().is_some());
        // Options, no payload
        let m = rec_message(
            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
            None,
            &mut buf,
        );
        assert!(m.payload_offset_cache.get().is_none());
        let mut iter = m.options_iter().unwrap();
        iter.next();
        iter.next();
        assert!(m.payload_offset_cache.get().is_none());
        iter.next();
        assert!(m.payload_offset_cache.get().is_some());
        // No options, payload
        let m = rec_message(Vec::new(), Some(b"payload"), &mut buf);
        assert_eq!(m.payload_offset_cache.get(), None);
        m.options_iter().unwrap().next();
        assert!(m.payload_offset_cache.get().is_some());
        // Options, payload
        let m = rec_message(
            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
            Some(b"payload"),
            &mut buf,
        );
        let mut iter = m.options_iter().unwrap();
        iter.next();
        iter.next();
        assert!(m.payload_offset_cache.get().is_none());
        iter.next();
        assert!(m.payload_offset_cache.get().is_some());
        // The payload_offset is cloned which may require iterating for each cloned EncodedMessage
        let original = rec_message(Vec::new(), None, &mut buf);
        let clone = original.clone();
        original.options_iter().unwrap().next();
        assert!(original.payload_offset_cache.get().is_some());
        assert!(clone.payload_offset_cache.get().is_none());
        // A Some(payload_offset) may be cloned though
        let original = rec_message(Vec::new(), None, &mut buf);
        original.options_iter().unwrap().next();
        let clone = original.clone();
        assert!(original.payload_offset_cache.get().is_some());
        assert!(clone.payload_offset_cache.get().is_some());
        // Naturally, accessing the payload itself will also set the payload offset
        let m = rec_message(Vec::new(), None, &mut buf);
        m.payload_offset().unwrap();
        assert!(m.payload_offset_cache.get().is_some());
        // If something goes wrong along the way, the payload offset is not determined
        let m = rec_message(
            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
            None,
            &mut buf,
        );
        // We set the first option delta/length field to the invalid 0xF0. We have to work around
        // the lifetimes though.
        let len = m.data.len();
        buf[4] = 0xF0;
        let m = EncodedMessage::try_new(&buf[..len]).unwrap();
        for _ in m.options_iter().unwrap() {}
        assert!(m.payload_offset_cache.get().is_none());
    }

    /// Ensures that the payload_offset position is set after a message has been encoded
    #[test]
    fn encode_payload_marker_positions() {
        // reusable buffer
        let mut buf = [0; 1000];
        // dummy options
        let option0 = CoapOption {
            name: CoapOptionName::Accept,
            value: b"",
        };
        let option1 = CoapOption {
            name: CoapOptionName::Accept,
            value: b"option value",
        };
        // No options, no payload
        let m = enc_message(Vec::new(), None, &mut buf);
        assert!(m.payload_offset_cache.get().is_some());
        // Options, no payload
        let m = enc_message(
            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
            None,
            &mut buf,
        );
        assert!(m.payload_offset_cache.get().is_some());
        // No options, payload
        let m = enc_message(Vec::new(), Some(b"payload"), &mut buf);
        assert!(m.payload_offset_cache.get().is_some());
        // Options, payload
        let m = enc_message(
            Vec::from_slice(&[option0.clone(), option1.clone()]).unwrap(),
            Some(b"payload"),
            &mut buf,
        );
        assert!(m.payload_offset_cache.get().is_some());
    }
}