[][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 async-coap-uri crate.

Macros

null_receiver

Handler for LocalEndpoint::receive that does nothing and lets the underlying LocalEndpoint implementation decide how best to respond (if at all).

write_options

Helper macro that assists with writing correct implementations of SendDesc::write_options.

Structs

BlockInfo

Type for interpreting block1 and block2 option values.

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 Stream that is created by LocalEndpointExt::receive_as_stream.

SendAsStream

A Stream that is created by LocalEndpointExt::send_as_stream, RemoteEndpointExt::send_as_stream, and RemoteEndpointExt::send_to_as_stream.

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 LocalEndpoint which implements additional helper methods.

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 SocketAddr types that allows the local endpoint get the information it needs.

ToSocketAddrs

A flavor of std::net::ToSocketAddrs that allows the implementation of SocketAddr to be replaced.