coap_zero/message/
mod.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//! The Message Module represents a decoded CoAP Message
8
9pub mod codes;
10pub mod encoded_message;
11mod header;
12pub mod options;
13pub mod token;
14
15use bondrewd::Bitfields;
16use heapless::Vec;
17
18use self::{
19    codes::Code, encoded_message::EncodedMessage, header::MessageHeader, options::CoapOption,
20    token::Token,
21};
22
23/// Version of the CoAP Protocol this library implements
24pub const PROTOCOL_VERSION: u8 = 1;
25
26/// Message Type as defined in <https://www.rfc-editor.org/rfc/rfc7252#page-8>
27#[derive(Debug, Clone, Copy, bondrewd::BitfieldEnum, PartialEq, Eq)]
28pub enum Type {
29    /// From <https://www.rfc-editor.org/rfc/rfc7252#page-8>:
30    /// "Some messages require an acknowledgement.  These messages are
31    /// called "Confirmable".  When no packets are lost, each Confirmable
32    /// message elicits exactly one return message of type Acknowledgement
33    /// or type Reset."
34    Confirmable = 0,
35    /// From <https://www.rfc-editor.org/rfc/rfc7252#page-8>:
36    /// "Some other messages do not require an acknowledgement.  This is
37    /// particularly true for messages that are repeated regularly for
38    /// application requirements, such as repeated readings from a sensor."
39    NonConfirmable = 1,
40    /// From <https://www.rfc-editor.org/rfc/rfc7252#page-8>:
41    /// "An Acknowledgement message acknowledges that a specific
42    /// Confirmable message arrived.  By itself, an Acknowledgement
43    /// message does not indicate success or failure of any request
44    /// encapsulated in the Confirmable message, but the Acknowledgement
45    /// message may also carry a Piggybacked Response [...]."
46    Acknowledgement = 2,
47    /// From <https://www.rfc-editor.org/rfc/rfc7252#page-8>:
48    /// "A Reset message indicates that a specific message (Confirmable or
49    /// Non-confirmable) was received, but some context is missing to
50    /// properly process it.  This condition is usually caused when the
51    /// receiving node has rebooted and has forgotten some state that
52    /// would be required to interpret the message.  Provoking a Reset
53    /// message (e.g., by sending an Empty Confirmable message) is also
54    /// useful as an inexpensive check of the liveness of an endpoint
55    /// ("CoAP ping")."
56    Reset = 3,
57}
58
59/// Combined error type for message encoding, parsing and checking
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Error {
62    /// Invalid Code used
63    InvalidCode,
64    /// Option could not be parsed
65    InvalidOption(options::Error),
66    /// Invalid token length (greater 8)
67    TokenLength,
68    /// The provided buffer is too small
69    OutOfMemory,
70    /// The message is shorter than 4 bytes which is the minimum CoAP message length (the length of
71    /// the message header)
72    MsgTooShort,
73    /// Empty Non-Confirmable message (Non-Confirmable messages may only be requests or responses).
74    EmptyNon,
75    /// Request in an ACK (ACKs may only be piggybacked responses or empty).
76    RequestAck,
77    /// Request or response in a RST message (RSTs _must_ be empty).
78    NonEmptyRst,
79    /// Empty message (code 0.00) with token, options or payload (empty messages _must_ be actually
80    /// empty, "An Empty message only contains the 4-byte header.")
81    EmptyWithContent,
82}
83
84/// A CoAP Message. Use this as a builder structure and then encode the message
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct Message<'data, const MAX_OPTION_COUNT: usize = { crate::DEFAULT_MAX_OPTION_COUNT }> {
87    version: u8,
88    message_type: Type,
89    code: Code,
90    // The Message Id and the Token are stored as Options here so they can be set later when the
91    // endpoint assigns a Message ID and a (possibly new) Token. The user should be able to
92    // construct a Message without knowing the Message ID and Token yet so the Options allow to
93    // defer these.
94    message_id: u16,
95    token: Token,
96    options: Vec<CoapOption<'data>, MAX_OPTION_COUNT>,
97    payload: Option<&'data [u8]>,
98}
99
100impl<'data, const MAX_OPTION_COUNT: usize> TryFrom<EncodedMessage<'data>>
101    for Message<'data, MAX_OPTION_COUNT>
102{
103    type Error = Error;
104
105    fn try_from(encoded: EncodedMessage<'data>) -> Result<Self, Error> {
106        let mut options: Vec<CoapOption<'_>, MAX_OPTION_COUNT> = Vec::new();
107
108        // The payload offset can only be determined after iterating over all options. Therefore,
109        // we iterate first so that the EncodedMessage may remember the payload offset. So when we
110        // request the payload (offset) afterwards, it should be available immediately.
111        for option in encoded.options_iter()? {
112            options.push(option.map_err(Error::InvalidOption)?).unwrap();
113        }
114
115        Ok(Self {
116            version: encoded.version(),
117            message_type: encoded.message_type(),
118            code: encoded.code()?,
119            message_id: encoded.message_id(),
120            token: encoded.token()?,
121            options,
122            payload: encoded.payload()?,
123        })
124    }
125}
126
127impl<'data, const MAX_OPTION_COUNT: usize> Message<'data, MAX_OPTION_COUNT> {
128    /// Initialize with minimum required message parameters.
129    ///
130    /// The Message_Id and the Token are handled internally by CoAP-Zero.
131    /// Options and a Payload are not required for a valid CoAP Message,
132    /// but both can be set by the user if needed.
133    pub fn new(
134        message_type: Type,
135        code: Code,
136        message_id: u16,
137        token: Token,
138        options: Vec<CoapOption<'data>, MAX_OPTION_COUNT>,
139        payload: Option<&'data [u8]>,
140    ) -> Self {
141        let options = crate::message::options::sort_options_vec(options);
142        Self {
143            version: PROTOCOL_VERSION,
144            message_type,
145            code,
146            message_id,
147            token,
148            options,
149            payload,
150        }
151    }
152
153    /// Convenience constructor for empty messages. Not public but used internally for the other
154    /// convenience constructors.
155    fn new_empty(message_type: Type, message_id: u16) -> Self {
156        Self {
157            version: PROTOCOL_VERSION,
158            message_type,
159            code: Code::Empty,
160            message_id,
161            token: Token::default(),
162            options: Vec::new(),
163            payload: None,
164        }
165    }
166
167    /// Convenience constructor for empty ACKs (can not be used for piggybacked responses)
168    pub fn new_ack(message_id: u16) -> Self {
169        Self::new_empty(Type::Acknowledgement, message_id)
170    }
171
172    /// Convenience constructor for RSTs
173    pub fn new_rst(message_id: u16) -> Self {
174        Self::new_empty(Type::Reset, message_id)
175    }
176
177    /// Convenience constructor for pings
178    pub fn new_ping(message_id: u16) -> Self {
179        Self::new_empty(Type::Confirmable, message_id)
180    }
181
182    /// Returns the message type
183    pub fn message_type(&self) -> Type {
184        self.message_type
185    }
186
187    /// Returns the message code
188    pub fn code(&self) -> Code {
189        self.code
190    }
191
192    /// Returns the message ID
193    pub fn message_id(&self) -> u16 {
194        self.message_id
195    }
196
197    /// Returns the token
198    pub fn token(&self) -> Token {
199        self.token
200    }
201
202    /// Returns a reference to the options vector
203    pub fn options(&self) -> &Vec<CoapOption<'_>, MAX_OPTION_COUNT> {
204        &self.options
205    }
206
207    /// Returns a reference to the payload, if any
208    pub fn payload(&self) -> Option<&'data [u8]> {
209        self.payload
210    }
211
212    /// Returns true if the message code is [Code::Empty]
213    pub fn is_empty(&self) -> bool {
214        matches!(self.code, Code::Empty)
215    }
216
217    /// Returns true if the message has a code of [Code::Request]
218    pub fn is_request(&self) -> bool {
219        matches!(self.code, Code::Request(_))
220    }
221
222    /// Returns true if the message has a code of [Code::Response]
223    pub fn is_response(&self) -> bool {
224        matches!(self.code, Code::Response(_))
225    }
226
227    /// Encode the message into the passed byte-slice, returns the resulting size of the message
228    pub(crate) fn encode<'buffer>(
229        &self,
230        buf: &'buffer mut [u8],
231    ) -> Result<EncodedMessage<'buffer>, Error> {
232        let header = MessageHeader::new(
233            self.version,
234            self.message_type,
235            self.token.length,
236            self.code.into(),
237            self.message_id,
238        );
239
240        let header = header.into_bytes();
241
242        let mut index = 0;
243
244        for x in header {
245            *buf.get_mut(index).ok_or(Error::OutOfMemory)? = x;
246            index += 1;
247        }
248
249        for i in 0..self.token.length as usize {
250            *buf.get_mut(index).ok_or(Error::OutOfMemory)? = self.token.bytes[i];
251            index += 1;
252        }
253
254        index += options::encode_to_buf(&mut buf[index..], &self.options)?;
255
256        if let Some(payload) = self.payload {
257            *buf.get_mut(index).ok_or(Error::OutOfMemory)? = 0xFF; // Payload Marker
258            index += 1;
259
260            for x in payload {
261                *buf.get_mut(index).ok_or(Error::OutOfMemory)? = *x;
262                index += 1;
263            }
264        }
265
266        let encoded = EncodedMessage::try_new(&buf[..index]).unwrap();
267        encoded.set_payload_offset(index);
268
269        Ok(encoded)
270    }
271}