coap_zero/lib.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#![warn(missing_docs)]
8#![cfg_attr(not(test), no_std)]
9
10//! This crate provides a heapless `no_std` implementation for the CoAP protocol.
11//!
12//! It aims to be zero-copy as much as feasible without sacrificing usability too much. The
13//! implemented CoAP endpoint implements automatic retransmissions for reliable message
14//! transmission (for confirmable messages) and message de-duplication. The endpoint automatically
15//! responds to specific messages, e.g. it sends out reset messages when appropriate or retransmits
16//! the last response if a duplicated request is detected.
17//!
18//! The implementation is fixed to NSTART=1, i.e. only a single incoming request and a single
19//! outgoing request may be processed at the same time.
20//!
21//! Currently, only the base CoAP specification is implemented. In the future, it is planned to
22//! also support CoAP Observe which is required for LwM2M, the PATCH, FETCH and iPATCH methods
23//! which significantly improve LwM2M capabilities and Block-Wise transfers which are required for
24//! firmware upgrades in LwM2M.
25//!
26//! # Resources
27//!
28//! - [RFC 7252 - The Constrained Application Protocol (CoAP)](https://www.rfc-editor.org/rfc/rfc7252)
29//! - [RFC 7641 - Observing Resources in the Constrained Application Protocol (CoAP)](https://www.rfc-editor.org/rfc/rfc7641)
30//! - [RFC 7959 - Block-Wise Transfers in the Constrained Application Protocol (CoAP)](https://www.rfc-editor.org/rfc/rfc7959)
31//! - [RFC 8132 - PATCH and FETCH Methods for the Constrained Application Protocol (CoAP)](https://www.rfc-editor.org/rfc/rfc8132.html)
32//!
33//! # Usage
34//!
35//! The main structure is the `coap_zero::endpoint::CoapEndpoint` type. It manages the connection
36//! to another coap endpoint via an `embedded_nal::UdpClientStack` (not owned).
37//! It contains two separate types, `OutgoingCommunication` and `IncomingCommunication`, to handle
38//! the two communication paths separately.
39//!
40//! All operations are driven by internal state machines. Therefore, all calls are non-blocking
41//! and the user has to call the `CoapEndpoint::process` method repeatedly in order to
42//! send/receive CoAP messages. Whenever one of the state machines makes progress, a corresponding
43//! event is returned. Some events _must_ be handled by the user in order to not get stuck in a
44//! specific wait state. For example, an incoming request _must_ be responded to by the user
45//! although this decision may be postponed or may involve sending a reset message.
46//!
47//! If only message parsing is required, the message submodule can be used.
48//!
49//! # Usage Example
50//!
51//! For more detailed examples, refer to the examples in the examples folder.
52//!
53//! ```
54//! # use coap_zero::endpoint::incoming::IncomingEvent;
55//! # use coap_zero::endpoint::outgoing::OutgoingEvent;
56//! # use coap_zero::endpoint::{CoapEndpoint, EndpointEvent, TransmissionParameters};
57//! # use coap_zero::message::codes::RequestCode;
58//! # use core::time::Duration;
59//! # use embedded_nal::SocketAddr;
60//! # use embedded_timers::clock::{Clock, ClockError, Instant};
61//! # use heapless::Vec;
62//! # #[derive(Debug)]
63//! # struct Rng;
64//! # impl embedded_hal::blocking::rng::Read for Rng {
65//! # type Error = std::io::Error;
66//! # fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
67//! # buf[0] = 42;
68//! # Ok(())
69//! # }
70//! # }
71//! # #[derive(Debug)]
72//! # struct StackError;
73//! # struct Socket;
74//! # struct Stack;
75//! # impl embedded_nal::UdpClientStack for Stack {
76//! # type UdpSocket = Socket;
77//! # type Error = StackError;
78//! # fn socket(&mut self) -> Result<Socket, StackError> {
79//! # Ok(Socket)
80//! # }
81//! # fn connect(
82//! # &mut self,
83//! # _socket: &mut Socket,
84//! # _remote: SocketAddr,
85//! # ) -> Result<(), StackError> {
86//! # Ok(())
87//! # }
88//! # fn send(
89//! # &mut self,
90//! # _socket: &mut Socket,
91//! # _buffer: &[u8],
92//! # ) -> Result<(), nb::Error<StackError>> {
93//! # Ok(())
94//! # }
95//! # fn receive(
96//! # &mut self,
97//! # _socket: &mut Socket,
98//! # _buffer: &mut [u8],
99//! # ) -> Result<(usize, SocketAddr), nb::Error<StackError>> {
100//! # Err(nb::Error::WouldBlock)
101//! # }
102//! # fn close(&mut self, _socket: Socket) -> Result<(), StackError> {
103//! # Ok(())
104//! # }
105//! # }
106//! # impl embedded_nal::Dns for Stack {
107//! # type Error = StackError;
108//! # fn get_host_by_name(
109//! # &mut self,
110//! # _hostname: &str,
111//! # _addr_type: embedded_nal::AddrType,
112//! # ) -> nb::Result<embedded_nal::IpAddr, Self::Error> {
113//! # use core::str::FromStr;
114//! # Ok(embedded_nal::IpAddr::from_str("1.1.1.1").unwrap())
115//! # }
116//! # fn get_host_by_address(
117//! # &mut self,
118//! # _addr: embedded_nal::IpAddr,
119//! # ) -> nb::Result<heapless::String<256>, Self::Error> {
120//! # Ok(heapless::String::from("rust-lang.org"))
121//! # }
122//! # }
123//! # #[derive(Debug)]
124//! # struct SystemClock;
125//! # impl Clock for SystemClock {
126//! # fn try_now(&self) -> Result<Instant, ClockError> {
127//! # Ok(Instant::new(0, 0))
128//! # }
129//! # }
130//! static CLOCK: SystemClock = SystemClock;
131//!
132//! let mut stack = Stack;
133//! let mut receive_buffer = [0_u8; coap_zero::DEFAULT_COAP_MESSAGE_SIZE];
134//!
135//! let mut endpoint: CoapEndpoint<'_, Stack, Rng, SystemClock> = CoapEndpoint::try_new(
136//! TransmissionParameters::default(),
137//! Rng,
138//! &CLOCK,
139//! &mut receive_buffer,
140//! )
141//! .unwrap();
142//!
143//! endpoint
144//! .connect_to_url(&mut stack, "coap://coap.me:5683")
145//! .unwrap();
146//!
147//! let outgoing = endpoint.outgoing();
148//! outgoing
149//! .schedule_con(
150//! RequestCode::Get,
151//! outgoing
152//! .parse_options("coap://coap.me:5683/hello", Vec::new())
153//! .unwrap(),
154//! None,
155//! Duration::from_secs(5),
156//! )
157//! .unwrap();
158//!
159//! loop {
160//! let (incoming_event, outgoing_event, endpoint_event) =
161//! endpoint.process(&mut stack).unwrap();
162//!
163//! match incoming_event.unwrap() {
164//! IncomingEvent::Nothing => {
165//! // Whenever nothing else happens, the Nothing event is generated. Ignore it
166//! // silently.
167//! }
168//! IncomingEvent::Request(_confirmable, _message) => {
169//! // Handle request
170//! }
171//! event => println!("Other incoming event: {event:?}"),
172//! }
173//!
174//! match outgoing_event.unwrap() {
175//! OutgoingEvent::Nothing => {}
176//! OutgoingEvent::Success(response) => println!("Request succeeded: {response:?}"),
177//! OutgoingEvent::Timeout
178//! | OutgoingEvent::PiggybackedWrongToken
179//! | OutgoingEvent::ResetReceived => {
180//! println!("Request failed");
181//! }
182//! event => println!("Other outgoing event: {event:?}"),
183//! }
184//!
185//! match endpoint_event {
186//! EndpointEvent::Nothing => {}
187//! EndpointEvent::MsgFormatErr(_err) => {
188//! // A message format error was detected. This is reported as an event instead of
189//! // an error because it is caused by the other endpoint.
190//! println!("endpoint event MsgFormatErr");
191//! }
192//! EndpointEvent::Ping => {
193//! println!("A ping has been received and a response has been sent");
194//! }
195//! EndpointEvent::Unhandled(message) => {
196//! println!("Unhandled message received: {message:?}");
197//! }
198//! }
199//!
200//! # break; // Finish test
201//! }
202//! ```
203//!
204//! # Examples
205//!
206//! Multiple examples are provided. They use [coap.me](https://coap.me/) as coap test server. The lwm2m example shows the usage in the lightweight m2m context using the [Leshan](https://leshan.eclipseprojects.io) public test server.
207//!
208//! - ping - Sends a ping and terminates when the expected reset was received
209//! - simple_get - Sends a CON Get and receives a piggy-backed response
210//! - get_separate_response - Sends a CON Get and received a separate response
211//! - lwm2m - Registers to the leshan server and supplies the time object current time resource. The registration won't update and will be silently closed after 120 seconds.
212//!
213//! # License
214//!
215//! Open Logistics Foundation License\
216//! Version 1.3, January 2023
217//!
218//! See the LICENSE file in the top-level directory.
219//!
220//!
221//! # Contact
222//!
223//! Fraunhofer IML Embedded Rust Group - <embedded-rust@iml.fraunhofer.de>
224
225pub mod endpoint;
226pub mod message;
227
228/// Default for how many options can be send in a CoAP Message
229pub const DEFAULT_MAX_OPTION_COUNT: usize = 8;
230
231/// Default for maximum value size \[Bytes\] of CoAP Options
232pub const DEFAULT_MAX_OPTION_SIZE: usize = 32;
233
234/// Default buffer size \[Bytes\] for a CoAP Message according to <https://www.rfc-editor.org/rfc/rfc7252#section-4.6>
235pub const DEFAULT_COAP_MESSAGE_SIZE: usize = 1152;
236
237#[cfg(test)]
238mod doctests {
239
240 // Required to write `use coap_zero::...` in the test below
241 use crate as coap_zero;
242
243 /// After this test has been fixed, copy it to the main doctest at the top of this file,
244 /// comment all the clutter with `#` and run `cargo readme > README.md` to regenerate the README
245 #[test]
246 fn readme_test() {
247 // The code is squeezed here because empty `#` lines show up in the generated README.md
248 // The upper part of the code is intended to be prefixed with `//! # `, the lower part
249 // should only be prefixed with `//!`.
250
251 use coap_zero::endpoint::incoming::IncomingEvent;
252 use coap_zero::endpoint::outgoing::OutgoingEvent;
253 use coap_zero::endpoint::{CoapEndpoint, EndpointEvent, TransmissionParameters};
254 use coap_zero::message::codes::RequestCode;
255 use core::time::Duration;
256 use embedded_nal::SocketAddr;
257 use embedded_timers::clock::{Clock, ClockError, Instant};
258 use heapless::Vec;
259 #[derive(Debug)]
260 struct Rng;
261 impl embedded_hal::blocking::rng::Read for Rng {
262 type Error = std::io::Error;
263 fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
264 buf[0] = 42;
265 Ok(())
266 }
267 }
268 #[derive(Debug)]
269 struct StackError;
270 struct Socket;
271 struct Stack;
272 impl embedded_nal::UdpClientStack for Stack {
273 type UdpSocket = Socket;
274 type Error = StackError;
275 fn socket(&mut self) -> Result<Socket, StackError> {
276 Ok(Socket)
277 }
278 fn connect(
279 &mut self,
280 _socket: &mut Socket,
281 _remote: SocketAddr,
282 ) -> Result<(), StackError> {
283 Ok(())
284 }
285 fn send(
286 &mut self,
287 _socket: &mut Socket,
288 _buffer: &[u8],
289 ) -> Result<(), nb::Error<StackError>> {
290 Ok(())
291 }
292 fn receive(
293 &mut self,
294 _socket: &mut Socket,
295 _buffer: &mut [u8],
296 ) -> Result<(usize, SocketAddr), nb::Error<StackError>> {
297 Err(nb::Error::WouldBlock)
298 }
299 fn close(&mut self, _socket: Socket) -> Result<(), StackError> {
300 Ok(())
301 }
302 }
303 impl embedded_nal::Dns for Stack {
304 type Error = StackError;
305 fn get_host_by_name(
306 &mut self,
307 _hostname: &str,
308 _addr_type: embedded_nal::AddrType,
309 ) -> nb::Result<embedded_nal::IpAddr, Self::Error> {
310 use core::str::FromStr;
311 Ok(embedded_nal::IpAddr::from_str("1.1.1.1").unwrap())
312 }
313 fn get_host_by_address(
314 &mut self,
315 _addr: embedded_nal::IpAddr,
316 ) -> nb::Result<heapless::String<256>, Self::Error> {
317 Ok(heapless::String::from("rust-lang.org"))
318 }
319 }
320 #[derive(Debug)]
321 struct SystemClock;
322 impl Clock for SystemClock {
323 fn try_now(&self) -> Result<Instant, ClockError> {
324 Ok(Instant::new(0, 0))
325 }
326 }
327
328 static CLOCK: SystemClock = SystemClock;
329
330 let mut stack = Stack;
331 let mut receive_buffer = [0_u8; coap_zero::DEFAULT_COAP_MESSAGE_SIZE];
332
333 let mut endpoint: CoapEndpoint<'_, Stack, Rng, SystemClock> = CoapEndpoint::try_new(
334 TransmissionParameters::default(),
335 Rng,
336 &CLOCK,
337 &mut receive_buffer,
338 )
339 .unwrap();
340
341 endpoint
342 .connect_to_url(&mut stack, "coap://coap.me:5683")
343 .unwrap();
344
345 let outgoing = endpoint.outgoing();
346 outgoing
347 .schedule_con(
348 RequestCode::Get,
349 outgoing
350 .parse_options("coap://coap.me:5683/hello", Vec::new())
351 .unwrap(),
352 None,
353 Duration::from_secs(5),
354 )
355 .unwrap();
356
357 loop {
358 let (incoming_event, outgoing_event, endpoint_event) =
359 endpoint.process(&mut stack).unwrap();
360
361 match incoming_event.unwrap() {
362 IncomingEvent::Nothing => {
363 // Whenever nothing else happens, the Nothing event is generated. Ignore it
364 // silently.
365 }
366 IncomingEvent::Request(_confirmable, _message) => {
367 // Handle request
368 }
369 event => println!("Other incoming event: {event:?}"),
370 }
371
372 match outgoing_event.unwrap() {
373 OutgoingEvent::Nothing => {}
374 OutgoingEvent::Success(response) => println!("Request succeeded: {response:?}"),
375 OutgoingEvent::Timeout
376 | OutgoingEvent::PiggybackedWrongToken
377 | OutgoingEvent::ResetReceived => {
378 println!("Request failed");
379 }
380 event => println!("Other outgoing event: {event:?}"),
381 }
382
383 match endpoint_event {
384 EndpointEvent::Nothing => {}
385 EndpointEvent::MsgFormatErr(_err) => {
386 // A message format error was detected. This is reported as an event instead of
387 // an error because it is caused by the other endpoint.
388 println!("endpoint event MsgFormatErr");
389 }
390 EndpointEvent::Ping => {
391 println!("A ping has been received and a response has been sent");
392 }
393 EndpointEvent::Unhandled(message) => {
394 println!("Unhandled message received: {message:?}");
395 }
396 }
397
398 break; // Finish test
399 }
400 }
401}