kwap_msg/lib.rs
1//! Low-level representation of CoAP messages.
2//!
3//! The most notable item in `kwap_msg` is `Message`;
4//! a CoAP message very close to the actual byte layout.
5//!
6//! ## Allocation
7//! CoAP messages have some attributes whose size is dynamic:
8//! - The message payload (in http terms: the request/response body)
9//! - the number of options (in http terms: headers)
10//! - the value of an option (in http terms: header value)
11//!
12//! `Message` does not require an allocator and has no opinions about what kind of collection
13//! it uses internally to store these values.
14//!
15//! It solves this problem by being generic over the collections it needs and uses an `Array` trait
16//! to capture its idea of what makes a collection useful.
17//!
18//! This means that you may use a provided implementation (for `Vec` or `tinyvec::ArrayVec`)
19//! or provide your own collection (see the [custom collections example](https://github.com/clov-coffee/kwap/blob/main/kwap_msg/examples/custom_collections.rs))
20//!
21//! ```rust
22//! //! Note: both of these type aliases are exported by `kwap_msg` for convenience.
23//!
24//! use tinyvec::ArrayVec;
25//! use kwap_msg::{Message, Opt};
26//!
27//! // Message Payload byte buffer
28//! // |
29//! // | Option Value byte buffer
30//! // | |
31//! // | | Array of options in the message
32//! // vvvvvvv vvvvvvv vvvvvvvvvvvvvvvvv
33//! type VecMessage = Message<Vec<u8>, Vec<u8>, Vec<Opt<Vec<u8>>>>;
34//!
35//! // Used like: `ArrayVecMessage<1024, 256, 16>`; a message that can store a payload up to 1024 bytes, and up to 16 options each with up to a 256 byte value.
36//! type ArrayVecMessage<
37//! const PAYLOAD_SIZE: usize,
38//! const OPT_SIZE: usize,
39//! const NUM_OPTS: usize,
40//! > = Message<
41//! ArrayVec<[u8; PAYLOAD_SIZE]>,
42//! ArrayVec<[u8; OPT_SIZE]>,
43//! ArrayVec<[Opt<ArrayVec<[u8; OPT_SIZE]>>; NUM_OPTS]>,
44//! >;
45//! ```
46//!
47//! It may look a little ugly, but a core goal of `kwap` is to be platform- and alloc-agnostic.
48//!
49//! ## Performance
50//! This crate uses `criterion` to measure performance of the heaped & heapless implementations in this crate as well as `coap_lite::Packet`.
51//!
52//! In general, `kwap_msg::VecMessage` performs identically to coap_lite (+/- 5%), and both are **much** faster than `kwap_msg::ArrayVecMessage`.
53//!
54//! Benchmarks:
55//! ### Serializing to bytes
56//! <details><summary><b>Click to expand chart</b></summary>
57//!
58//! 
59//! </details>
60//!
61//! ### Deserializing from bytes
62//! <details><summary><b>Click to expand chart</b></summary>
63//!
64//! 
65//! </details>
66
67#![doc(html_root_url = "https://docs.rs/kwap-msg/0.6.1")]
68#![cfg_attr(not(feature = "std"), no_std)]
69#![cfg_attr(not(test), forbid(missing_debug_implementations, unreachable_pub))]
70#![cfg_attr(not(test), deny(unsafe_code, missing_copy_implementations))]
71#![cfg_attr(any(docsrs, feature = "docs"), feature(doc_cfg))]
72#![deny(missing_docs)]
73
74#[cfg(feature = "alloc")]
75extern crate alloc as std_alloc;
76
77#[doc(hidden)]
78pub mod code;
79#[doc(hidden)]
80pub mod from_bytes;
81#[doc(hidden)]
82pub mod opt;
83#[doc(hidden)]
84pub mod to_bytes;
85
86#[doc(inline)]
87pub use code::*;
88#[doc(inline)]
89pub use from_bytes::{MessageParseError, OptParseError, TryFromBytes};
90use kwap_common::{Array, GetSize};
91use kwap_macros::rfc_7252_doc;
92#[doc(inline)]
93pub use opt::*;
94#[cfg(feature = "alloc")]
95use std_alloc::vec::Vec;
96use tinyvec::ArrayVec;
97#[doc(inline)]
98pub use to_bytes::TryIntoBytes;
99
100#[doc = rfc_7252_doc!("5.5")]
101#[derive(Clone, Debug, PartialEq, PartialOrd)]
102pub struct Payload<C: Array<Item = u8>>(pub C);
103
104/// Message that uses Vec byte buffers
105#[cfg(feature = "alloc")]
106#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
107pub type VecMessage = Message<Vec<u8>, Vec<u8>, Vec<Opt<Vec<u8>>>>;
108
109/// Message that uses static fixed-capacity stack-allocating byte buffers
110pub type ArrayVecMessage<const PAYLOAD_CAP: usize, const N_OPTS: usize, const OPT_CAP: usize> =
111 Message<ArrayVec<[u8; PAYLOAD_CAP]>,
112 ArrayVec<[u8; OPT_CAP]>,
113 ArrayVec<[Opt<ArrayVec<[u8; OPT_CAP]>>; N_OPTS]>>;
114
115/// # `Message` struct
116/// Low-level representation of a message that has been parsed from the raw binary format.
117///
118/// Note that `Message` is generic over 3 [`Array`]s:
119/// - `PayloadC`: the byte buffer used to store the message's [`Payload`]
120/// - `OptC`: byte buffer used to store [`Opt`]ion values ([`OptValue`])
121/// - `Opts`: collection of [`Opt`]ions in the message
122///
123/// Messages support both serializing to bytes and from bytes, by using the provided [`TryFromBytes`] and [`TryIntoBytes`] traits.
124///
125/// <details>
126/// <summary><b>RFC7252 - CoAP Messaging Model</b></summary>
127#[doc = concat!("\n#", rfc_7252_doc!("2.1"))]
128/// </details>
129/// <details>
130/// <summary><b>RFC7252 - CoAP Message Binary Format</b></summary>
131#[doc = concat!("\n#", rfc_7252_doc!("3"))]
132/// </details>
133///
134/// ```
135/// use kwap_msg::TryFromBytes;
136/// use kwap_msg::*;
137/// # // version token len code (2.05 Content)
138/// # // | | /
139/// # // | type | / message ID
140/// # // | | | | |
141/// # // vv vv vvvv vvvvvvvv vvvvvvvvvvvvvvvv
142/// # let header: [u8; 4] = 0b_01_00_0001_01000101_0000000000000001u32.to_be_bytes();
143/// # let token: [u8; 1] = [254u8];
144/// # let content_format: &[u8] = b"application/json";
145/// # let options: [&[u8]; 2] = [&[0b_1100_1101u8, 0b00000011u8], content_format];
146/// # let payload: [&[u8]; 2] = [&[0b_11111111u8], b"hello, world!"];
147/// let packet: Vec<u8> = /* bytes! */
148/// # [header.as_ref(), token.as_ref(), options.concat().as_ref(), payload.concat().as_ref()].concat();
149///
150/// // `VecMessage` uses `Vec` as the backing structure for byte buffers
151/// let msg = VecMessage::try_from_bytes(packet.clone()).unwrap();
152/// # let opt = Opt {
153/// # delta: OptDelta(12),
154/// # value: OptValue(content_format.iter().map(|u| *u).collect()),
155/// # };
156/// let mut opts_expected = /* create expected options */
157/// # Vec::new();
158/// # opts_expected.push(opt);
159///
160/// let expected = VecMessage {
161/// id: Id(1),
162/// ty: Type::Con,
163/// ver: Version(1),
164/// token: Token(tinyvec::array_vec!([u8; 8] => 254)),
165/// opts: opts_expected,
166/// code: Code {class: 2, detail: 5},
167/// payload: Payload(b"hello, world!".to_vec()),
168/// };
169///
170/// assert_eq!(msg, expected);
171/// ```
172#[derive(Clone, PartialEq, PartialOrd, Debug)]
173pub struct Message<PayloadC: Array<Item = u8>,
174 OptC: Array<Item = u8> + 'static,
175 Opts: Array<Item = Opt<OptC>>> {
176 /// see [`Id`] for details
177 pub id: Id,
178 /// see [`Type`] for details
179 pub ty: Type,
180 /// see [`Version`] for details
181 pub ver: Version,
182 /// see [`Token`] for details
183 pub token: Token,
184 /// see [`Code`] for details
185 pub code: Code,
186 /// see [`opt::Opt`] for details
187 pub opts: Opts,
188 /// see [`Payload`]
189 pub payload: Payload<PayloadC>,
190}
191
192impl<PayloadC: Array<Item = u8>, OptC: Array<Item = u8> + 'static, Opts: Array<Item = Opt<OptC>>>
193 Message<PayloadC, OptC, Opts>
194{
195 /// Create a new message that ACKs this one.
196 ///
197 /// This needs an [`Id`] to assign to the newly created message.
198 ///
199 /// ```
200 /// // we are a server
201 ///
202 /// use std::net::SocketAddr;
203 ///
204 /// use kwap_msg::{Id, VecMessage as Message};
205 ///
206 /// fn server_get_request() -> Option<(SocketAddr, Message)> {
207 /// // Servery sockety things...
208 /// # use std::net::{Ipv4Addr, ToSocketAddrs};
209 /// # use kwap_msg::{Type, Code, Token, Version, Payload};
210 /// # let addr = (Ipv4Addr::new(0, 0, 0, 0), 1234);
211 /// # let addr = addr.to_socket_addrs().unwrap().next().unwrap();
212 /// # let msg = Message { code: Code::new(0, 0),
213 /// # id: Id(1),
214 /// # ty: Type::Con,
215 /// # ver: Version(1),
216 /// # token: Token(tinyvec::array_vec!([u8; 8] => 254)),
217 /// # opts: vec![],
218 /// # payload: Payload(vec![]) };
219 /// # Some((addr, msg))
220 /// }
221 ///
222 /// fn server_send_msg(addr: SocketAddr, msg: Message) -> Result<(), ()> {
223 /// // Message sendy bits...
224 /// # Ok(())
225 /// }
226 ///
227 /// let (addr, req) = server_get_request().unwrap();
228 /// let ack_id = Id(req.id.0 + 1);
229 /// let ack = req.ack(ack_id);
230 ///
231 /// server_send_msg(addr, ack).unwrap();
232 /// ```
233 pub fn ack(&self, id: Id) -> Self {
234 Self { id,
235 token: self.token,
236 ver: Default::default(),
237 ty: Type::Ack,
238 code: Code::new(0, 0),
239 payload: Payload(Default::default()),
240 opts: Default::default() }
241 }
242}
243
244impl<P: Array<Item = u8>, O: Array<Item = u8>, Os: Array<Item = Opt<O>>> GetSize
245 for Message<P, O, Os>
246{
247 fn get_size(&self) -> usize {
248 let header_size = 4;
249 let payload_marker_size = 1;
250 let payload_size = self.payload.0.get_size();
251 let token_size = self.token.0.len();
252 let opts_size: usize = self.opts.iter().map(|o| o.get_size()).sum();
253
254 header_size + payload_marker_size + payload_size + token_size + opts_size
255 }
256
257 fn max_size(&self) -> Option<usize> {
258 None
259 }
260}
261
262/// Struct representing the first byte of a message.
263///
264/// ```text
265/// CoAP version
266/// |
267/// | Message type (request, response, empty)
268/// | |
269/// | | Length of token, in bytes. (4-bit integer)
270/// | | |
271/// vv vv vvvv
272/// 01 00 0000
273/// ```
274#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
275pub(crate) struct Byte1 {
276 pub(crate) ver: Version,
277 pub(crate) ty: Type,
278 pub(crate) tkl: u8,
279}
280
281/// # Message ID
282///
283/// 16-bit unsigned integer in network byte order. Used to
284/// detect message duplication and to match messages of type
285/// Acknowledgement/Reset to messages of type Confirmable/Non-
286/// confirmable. The rules for generating a Message ID and matching
287/// messages are defined in RFC7252 Section 4
288///
289/// For a little more context and the difference between [`Id`] and [`Token`], see [`Token`].
290///
291/// See [RFC7252 - Message Details](https://datatracker.ietf.org/doc/html/rfc7252#section-3) for context
292#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
293pub struct Id(pub u16);
294
295/// Indicates if this message is of
296/// type Confirmable (0), Non-confirmable (1), Acknowledgement (2), or Reset (3).
297///
298/// See [RFC7252 - Message Details](https://datatracker.ietf.org/doc/html/rfc7252#section-3) for context
299#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Debug)]
300pub enum Type {
301 /// Some messages do not require an acknowledgement. This is
302 /// particularly true for messages that are repeated regularly for
303 /// application requirements, such as repeated readings from a sensor.
304 Non,
305 /// Some messages require an acknowledgement. These messages are
306 /// called "Confirmable". When no packets are lost, each Confirmable
307 /// message elicits exactly one return message of type Acknowledgement
308 /// or type Reset.
309 Con,
310 /// An Acknowledgement message acknowledges that a specific
311 /// Confirmable message arrived. By itself, an Acknowledgement
312 /// message does not indicate success or failure of any request
313 /// encapsulated in the Confirmable message, but the Acknowledgement
314 /// message may also carry a Piggybacked Response.
315 Ack,
316 /// A Reset message indicates that a specific message (Confirmable or
317 /// Non-confirmable) was received, but some context is missing to
318 /// properly process it. This condition is usually caused when the
319 /// receiving node has rebooted and has forgotten some state that
320 /// would be required to interpret the message. Provoking a Reset
321 /// message (e.g., by sending an Empty Confirmable message) is also
322 /// useful as an inexpensive check of the liveness of an endpoint
323 /// ("CoAP ping").
324 Reset,
325}
326
327/// Version of the CoAP protocol that the message adheres to.
328///
329/// Right now, this will always be 1, but may support additional values in the future.
330///
331/// See [RFC7252 - Message Details](https://datatracker.ietf.org/doc/html/rfc7252#section-3) for context
332#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
333pub struct Version(pub u8);
334
335impl Default for Version {
336 fn default() -> Self {
337 Version(1)
338 }
339}
340#[doc = rfc_7252_doc!("5.3.1")]
341#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
342pub struct Token(pub tinyvec::ArrayVec<[u8; 8]>);
343
344impl Token {
345 /// Take an arbitrary-length sequence of bytes and turn it into an opaque message token
346 ///
347 /// Currently uses the BLAKE2 hashing algorithm, but this may change in the future.
348 ///
349 /// ```
350 /// use kwap_msg::Token;
351 ///
352 /// let my_token = Token::opaque(&[0, 1, 2]);
353 /// ```
354 pub fn opaque(data: &[u8]) -> Token {
355 use blake2::digest::consts::U8;
356 use blake2::{Blake2b, Digest};
357
358 let mut digest = Blake2b::<U8>::new();
359 digest.update(data);
360 Token(Into::<[u8; 8]>::into(digest.finalize()).into())
361 }
362}
363
364#[cfg(test)]
365pub(crate) fn test_msg() -> (VecMessage, Vec<u8>) {
366 let header: [u8; 4] = 0b0100_0001_0100_0101_0000_0000_0000_0001_u32.to_be_bytes();
367 let token: [u8; 1] = [254u8];
368 let content_format: &[u8] = b"application/json";
369 let options: [&[u8]; 2] = [&[0b_1100_1101u8, 0b00000011u8], content_format];
370 let payload: [&[u8]; 2] = [&[0b1111_1111_u8], b"hello, world!"];
371 let bytes = [header.as_ref(),
372 token.as_ref(),
373 options.concat().as_ref(),
374 payload.concat().as_ref()].concat();
375
376 let mut opts = Vec::new();
377 let opt = Opt { delta: OptDelta(12),
378 value: OptValue(content_format.to_vec()) };
379 opts.push(opt);
380
381 let msg = VecMessage { id: Id(1),
382 ty: Type::Con,
383 ver: Version(1),
384 token: Token(tinyvec::array_vec!([u8; 8] => 254)),
385 opts,
386 code: Code { class: 2,
387 detail: 5 },
388 payload: Payload(b"hello, world!".to_vec()) };
389 (msg, bytes)
390}
391
392#[cfg(test)]
393pub(crate) mod tests {
394 #[macro_export]
395 macro_rules! assert_eqb {
396 ($actual:expr, $expected:expr) => {
397 if $actual != $expected {
398 panic!("expected {:08b} to equal {:08b}", $actual, $expected)
399 }
400 };
401 }
402
403 #[macro_export]
404 macro_rules! assert_eqb_iter {
405 ($actual:expr, $expected:expr) => {
406 if $actual.iter().ne($expected.iter()) {
407 panic!("expected {:?} to equal {:?}",
408 $actual.into_iter()
409 .map(|b| format!("{:08b}", b))
410 .collect::<Vec<_>>(),
411 $expected.into_iter()
412 .map(|b| format!("{:08b}", b))
413 .collect::<Vec<_>>())
414 }
415 };
416 }
417}