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}