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//! ![chart](https://raw.githubusercontent.com/clov-coffee/kwap/main/kwap_msg/docs/from_bytes.svg)
59//! </details>
60//!
61//! ### Deserializing from bytes
62//! <details><summary><b>Click to expand chart</b></summary>
63//!
64//! ![chart](https://raw.githubusercontent.com/clov-coffee/kwap/main/kwap_msg/docs/to_bytes.svg)
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}