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}