[−][src]Crate async_coap
An experimental, asynchronous implementation of the Constrained Application Protocol (CoAP).
This library provides a flexible, asynchronous
interface for using and serving CoAP resources. You can either use the included datagram-based
back-end or you can write your own back-end by implementing LocalEndpoint
.
By implementing datagram::AsyncDatagramSocket, you can use the provided datagram-based
back-end with whatever datagram-based network layer you might want, be it UDP,
DTLS, or even SMS. An implementation for Rust's standard std::net::UdpSocket
(AllowStdUdpSocket
) is included. A Tokio-based implementation is
forthcoming.
Design
Async-coap works differently than other CoAP libraries, making heavy use of combinators and Futures v0.3.
Simple Unicast
For the most part, CoAP was designed for the typical RESTful paradigm of sending requests and receiving responses. In typical CoAP libraries, you have a message type that you would create, populate with your request, and then pass to a method to send that request somewhere. Once the response had been received, the result would be returned as a single message. Simple. Straightforward.
This similarly straightforward to do with async-coap:
// Create a `remote_endpoint` instance representing the destination // identified by the URI "coap://coap.me/test". This tends to be // more convenient than using `local_endpoint` directly. let mut remote_endpoint = local_endpoint .remote_endpoint_from_uri(uri!("coap://coap.me/test")) .expect("Remote endpoint lookup failed"); // Create a future representing for our request. let future = remote_endpoint.send(CoapRequest::get().emit_any_response()); // Send the request and await the response. let response = future.await.expect("CoAP request failed"); // Print out the response message to standard output. println!("Got response: {}", response);
Block2 Reconstruction
However, there are cases where a single request can result in multiple responses (i.e. Multicast and Observing), as well as cases where a single "logical" request/response can be spread out across many smaller requests and responses (i.e. Block transfer).
Let's take Block2 transfers, for example. Many libraries support Block2 transfers, often by implementing message reconstruction under-the-hood, which can be very convenient. The async-coap way to do it is similarly convenient:
// We can change the path on the above remote_endpoint using // the `clone_using_rel_ref` method: let mut remote_endpoint = remote_endpoint.clone_using_rel_ref(rel_ref!("/large")); // Create a send descriptor that will reconstruct the block2 parts // and return the reconstituted message. let send_descriptor = CoapRequest::get() .block2(None) .emit_successful_collected_response(); let response = remote_endpoint .send(send_descriptor) .await .expect("CoAP request failed"); // Print out the response message to standard output. println!("Reconstructed response: {}", response);
Inspection
The problem with how this is implemented by other CoAP libraries is that it is difficult
or impossible to implement things like progress meters. However, with async-coap, we
can add some feedback to the above example very easily using inspect
:
// Create a send descriptor that will reconstruct the block2 parts // and return the reconstituted message, printing out each individual // message as we go. let send_descriptor = CoapRequest::get() .block2(None) .emit_successful_collected_response() .inspect(|context| { println!("inspect: Got {}", MessageDisplay(context.message())); }); let response = remote_endpoint .send(send_descriptor) .await .expect("CoAP request failed"); // Print out the response message to standard output. println!("Reconstructed response: {}", response);
Multiple Responses
That's all good and well, but what about requests that generate multiple responses, like
multicast requests? For that we use a different
send method: send_as_stream
. Instead of returning a Future
, it returns a Stream
.
This allows us to collect all of the responses:
let mut remote_endpoint = local_endpoint .remote_endpoint_from_uri(uri!("coap://[FF02::FD]/.well-known/core")) .expect("Remote endpoint lookup failed"); // Don't let the remote_endpoint include // a `Uri-Host` host option. remote_endpoint.remove_host_option(); let send_descriptor = CoapRequest::get() .multicast() .accept(ContentFormat::APPLICATION_LINK_FORMAT) .emit_successful_response() .include_socket_addr(); let mut stream = remote_endpoint.send_as_stream(send_descriptor); while let Some((msg, socket_addr)) = stream.next().await.transpose().expect("Error on get") { println!("From {} got {}", socket_addr, msg); }
Future Work
This library is currently in the experimental stage, so there are a lot of additional features and mechanisms that aren't yet implemented. Here is a short list:
- Support for "effortless" serving of observable resources
- Support for Block1 transfers.
- Improved support for observing remote resources.
- Make serving resources easier-to-use.
- OSCORE support.
- Support for supplying alternate transmission parameters.
- Support for burst transmissions for nonconfirmable and multicast requests.
- Make sending asynchronous responses easier.
Support for deeply embedded devices
To the extent possible, the API is designed
to minimize the amount of memory allocation. While it does currently require the alloc
crate,
that requirement will (hopefully) become optional once the Generic Associated Types
feature lands, without significantly influencing how the API works. This will allow for the
same API to be used for deeply embedded, resource-constrained devices as would be used for
other types of non-resource-constrained devices.
Full Example
use std::sync::Arc; use futures::{prelude::*,executor::LocalPool,task::LocalSpawnExt}; use async_coap::prelude::*; use async_coap::datagram::{DatagramLocalEndpoint,AllowStdUdpSocket}; // Create our asynchronous socket. In this case, it is just an // (inefficient) wrapper around the standard rust `UdpSocket`, // but that is quite adequate in this case. let socket = AllowStdUdpSocket::bind("[::]:0").expect("UDP bind failed"); // Create a new local endpoint from the socket we just created, // wrapping it in a `Arc<>` to ensure it can live long enough. let local_endpoint = Arc::new(DatagramLocalEndpoint::new(socket)); // Create a local execution pool for running our local endpoint. let mut pool = LocalPool::new(); // Quick aside: The `Local` in `LocalPool` is completely unrelated // to the `Local` in `LocalEndpoint`: a `LocalEndpoint` refers to // the local side of a CoAP connection. A `LocalPool` is just a // single-threaded execution pool. A `LocalEndpoint` will run // just fine on a `ThreadedPool`. // Add our local endpoint to the pool, so that it // can receive packets. pool.spawner().spawn_local(local_endpoint .clone() .receive_loop_arc(null_receiver!()) .map(|err| panic!("Receive loop terminated: {}", err)) ); // Create a remote endpoint instance to represent the // device we wish to interact with. let remote_endpoint = local_endpoint .remote_endpoint_from_uri(uri!("coap://coap.me")) .unwrap(); // Will only fail if the URI scheme or authority is unrecognizable // Create a future that sends a request to a specific path // on the remote endpoint, collecting any blocks in the response // and returning `Ok(OwnedImmutableMessage)` upon success. let future_result = remote_endpoint.send_to( rel_ref!("large"), CoapRequest::get() // This is a CoAP GET request .accept(ContentFormat::TEXT_PLAIN_UTF8) // We only want plaintext .block2(Some(Default::default())) // Enable block2 processing .emit_successful_collected_response() // Collect all blocks into a single message ); // Wait until we get the result of our request. let result = pool.run_until(future_result); println!("result: {:?}", result);
Additional examples can be found in the module documentation for send descriptors
and the documentation for LocalEndpoint
.
Modules
arc_guard | Arc Guard |
consts | Module defining various CoAP-related constants. |
datagram | Generic, datagram-based CoAP backend, with associated socket abstractions. |
link_format | Mechanisms and constants for encoding and decoding IETF-RFC6690 CoAP link-formats. |
message | Types related to parsing and encoding CoAP messages. |
null | NULL CoAP backend |
option | Types related to interpreting and handling CoAP options. |
send_desc | Send Descriptors |
uri | A limited subset of items from the URI-handling |
Macros
null_receiver | Handler for |
write_options | Helper macro that assists with writing correct implementations of |
Structs
BlockInfo | Type for interpreting |
BlockReconstructor | Tool for reconstructing block-wise messages. |
ContentFormat | A type for representing a CoAP Content Format value. |
ETag | Type for holding the value of an ETag option. |
ReceiveAsStream | A |
SendAsStream | A |
Enums
Error | Type for errors encountered while sending or receiving CoAP requests and responses. |
ResponseStatus | Successful return type from send descriptor handler method that indicates what should happen next. |
Traits
InboundContext | Represents the context for processing an inbound message. |
LocalEndpoint | Trait representing a local (as opposed to remote) CoAP endpoint. Allows for sending and receiving CoAP requests. |
LocalEndpointExt | Extension trait for |
RemoteEndpoint | An object that represents a remote CoAP endpoint with a default, overridable path. |
RemoteEndpointExt | Extension trait which implements additional helper methods. |
RespondableInboundContext | Represents the context for processing an inbound request that can be responded to. |
SocketAddrExt | Extension trait for |
ToSocketAddrs | A flavor of |