Use CoAP for lightweight messaging applications, such as for working with simple Internet of Things sensors or other devices.
The Constrained Application Protocol (CoAP) was created for smaller, constrained devices that run on low-powered networks with possibly transient or lossy connectivity. CoAP is similar to HTTP or REST communication in that the messages generally fall into the categories of GET, POST, PUT, and DELETE.
This article covers the basics of CoAP and how to program it in Java server and client applications. A follow-up article will explore advanced topics such as the Observer pattern, device discovery, and cross-protocol proxies.
Introducing CoAP
The CoAP specification is maintained by the Internet Engineering Task Force (IETF). CoAP is very conversational by nature because it is request- and response-driven. Contrast that to the publish/subscribe design of the MQTT protocol.
At a lower level, CoAP messages are sent and received over User Datagram Protocol (UDP), which by nature is unreliable, so a basic reliability scheme is built into CoAP on top of UDP. For added security, messages can be sent using the Datagram Transport Layer Security (DTLS) protocol instead of UDP. In either case, each CoAP message needs to fit into a single UDP/DTLS datagram packet. Further, CoAP supports datagram messaging over IPv4 and IPv6 networks and variants such as 6LoWPAN , as shown in Figure 1.
Figure 1. CoAP messaging network communication layers
Although unicast UDP is used for request- and response-driven CoAP, multicast UDP messaging is used to support CoAP device/sensor discovery. CoAP clients and servers support a special “all CoAP nodes” multicast address, with port 5683, to discover other CoAP servers and their shared resources.
Exploring CoAP
First, this article explores various aspects of the protocol, and then it presents Java code to send and process CoAP messages.
The CoAP message model. All message exchanges in CoAP are like those for HTTP. With CoAP, all interchanges are asynchronous and datagram-based. Optional reliability is built into the message exchange using a timeout and retransmission protocol based on random and increasing back-off timers with eventual timeout. Figure 2 shows CoAP’s two-layer approach to messaging.
Figure 2. The CoAP two-layer messaging approach
CoAP messages include a four-byte fixed-length header. Depending upon the message type, this header is followed by optional header data and the payload. Each message includes a 16-bit message ID used to link requests to their accompanying acknowledgments (ACKs) or error statuses (when applicable). A nonconfirmable (NON) message (that is, one that doesn’t require confirmation or, hence, reliability) is sent from the client to the server with no ACK (see Figure 3).
Figure 3. A NON request message and response
If the recipient is unable to process the message, it may reply with a reset message (RST) to indicate this. An example of when high reliability may not be needed is with telemetry data, because there is a constant stream of updates so missing one update isn’t devastating.
In short, message IDs are unique values that are used to identify duplicate messages and match confirmable messages (CONs) to ACKs. Tokens are unique values used to match requests to responses.
For example, client 1 sends a nonconfirmable GET request for a temperature reading to client 2. A unique message ID (0x101) and token (0x21) are provided. The message ID is useful to detect message duplication (more on that later). For a request/response message exchange, the token must match across all associated messages. Therefore, in this example, the message IDs will be unique for both the request and response messages, but the token (0x21) will be the same for both.
In the case of a NON message, either the request or the response may be lost. For some types of message exchanges, this may be acceptable. For cases where that is not acceptable, CoAP supports CONs, as shown in Figure 4.
In Figure 4, client 1 sends a confirmable GET request for a temperature reading to client 2, providing a unique message ID (0x101) and token (0x21). The token must match throughout the entire message exchange. When client 2 receives the CON request, it sends an ACK message containing the same message ID provided with the request.
Figure 4. A CON request message and response
Later, client 2 sends a CON response message with the requested data and a new message ID, using the same token as in the CON request from client 1. To confirm that it received the data, client 1 sends an ACK to client 2 containing the same message ID as in the response (0x92ab), and the exchange is complete.
Piggybacking. To reduce message traffic and processing overhead, CoAP supports the concept of piggybacking. With this, the response data to a request can be included (piggybacked) on the ACK message; see Figure 5.
Figure 5. CON response content piggybacked onto ACK messages
The CON request is identical to the previous exchange. However, when client 2 sends the ACK, it also includes the response data. When client 1 receives this ACK with data, the CON message exchange is complete.
Handling lost messages. CoAP specifies a way to detect lost UDP messages and then retransmit them. For instance, in any of the CON examples above, if an ACK for the initial CON request was not received, after a timeout period the CON request will be resent with the identical message ID and token, as shown in Figure 6.
Figure 6. Lost CON request message resent later
In the case where the original CON request arrived at client 2 but the CON ACK was lost instead, after a timeout client 1 will resend the original request, as shown in Figure 7.
Figure 7. CON request arrives, but ACK is lost
For a scenario where piggybacking is not used and an explicit CON response is sent, the failure scenario may be slightly different, as Figure 8 shows.
Figure 8. A CON request is sent, and then an ACK is sent. The response arrives, but the final ACK is lost.
In this scenario, the CON request, its ACK, and its CON response are all received. However, client 1’s ACK back to client 2 (which would otherwise complete the exchange) is lost. After a timeout, client 2 assumes its CON response was lost and resends it, which client 1 receives and detects as a duplicate. Client 1 ignores the response but resends the ACK, completing the CON message exchange.
The message retransmission timeout interval. When any CON message is sent, an ACK must be received within a random time interval that is somewhere between the ACK_TIMEOUT and a value calculated as (ACK_TIMEOUT * ACK_RANDOM_FACTOR).
If an ACK is not received in time, the sender retransmits the CON message at exponentially increasing intervals until it receives an ACK (or an RST message) or it runs out of the number of attempts defined by the MAX_RETRANSMIT value. Each time a message is retransmitted, that message’s transmit counter is incremented and its wait time is doubled.
The messaging discussed so far has been viewed from the unicast UDP perspective. However, CoAP supports multicast messaging. In this paradigm, requests can be sent to groups of servers reachable at a UDP multicast address, where all or some of the servers listening may respond to a single request.
CoAP URI and security overview. The CoAP uniform resource identifier (URI) uses coap:// (for communication over UDP) and coaps:// (for communication over DTLS) to locate resources. The resources are organized hierarchically. The format is as follows:
◉ URI = "coap://<host>[:<port>]<path>[?<query>]"
◉ URI = "coaps://<host>[:<port>]<path>[?<query>]"
Here are two examples.
◉ coap://example.com:5683/~sensors/readings.xml
◉ coap://FF05::FD:5683/.well-known/core
CoAP, by default, is not secure and works in what is called NoSec mode, in which no security, encryption, or authentication is performed. However, the specification covers many security points, and here are a few.
◉ Because CoAP is a subset of HTTP/S, many HTTP/S security considerations apply to CoAP also.
◉ For secure communications, CoAP messaging can be performed over DTLS instead of UDP.
◉ CoAP provides support for key-based certificates and access control lists for authentication and authorization.
◉ To avoid URI parsing vulnerabilities, CoAP reduces the scope for parsing and defines a concise range of encodable values with reduced complexity and risk.
◉ CoAP provides guidance on proxies and caching to avoid or at least raise awareness of the risks of “in-the-middle” attacks.
◉ CoAP provides strategies for avoiding request amplification, such as CoAP server slicing and blocking modes.
◉ CoAP detects address spoofing and encourages limited response rates.
CoAP method definitions and Java code
CoAP messaging is analogous to HTTP communication, but it’s not equivalent. For example, both CoAP and HTTP support the same four basic commands—GET, POST, PUT, and DELETE—but the semantics of the commands vary slightly. For instance, many of the success and error codes are different between the two.
CoAP GET. As is the case with REST, this method retrieves a representation of the information that corresponds to the resource within the URI at the time the request is made. As is the case with HTTP, the request can include an Accept option that suggests the preferred content of the response.
The GET response status codes are 2.03 for a valid request, 2.05 for a valid request with content, and 4.05 for “not allowed.” Listing 1 is a CoAP GET from a Java client using the
Californium CoAP library.
Listing 1. Java code to make a CoAP GET for temperature data
public class CoapGetClient {
public static void main(String args[])
throws URISyntaxException {
// make synchronous get call
URI uri = new URI("coap://192.168.1.97:5683/temp");
CoapClient client = new CoapClient(uri);
CoapResponse response = client.get();
if ( response != null ) {
byte[] bytes = response.getPayload();
System.out.println(response.getCode());
System.out.println(response.getOptions());
System.out.println(response.getResponseText());
System.out.println("\nDETAILED RESPONSE:");
System.out.println(Utils.prettyPrint(response));
}
}
}
Thanks to the Californium library implementation, the call to CoapClient.get() is synchronous and will wait to return a code and payload if it’s successful. You can use response.getPayload() to retrieve the payload as a byte array or use the getResponseText() shortcut, as shown in this example. Also shown is the call to retrieve the return code and any optional parameters.
Finally, the Californium library provides a utility class with a prettyPrint() method, which formats the response details as shown in Listing 2. You can download the synchronous CoAP GET application from my GitHub repository.
Listing 2. The output of the CoAP GET request for the current temperature, with complete response details
2.05
{"Content-Format":"text/plain"}
70 degrees F
DETAILED RESPONSE:
==[ CoAP Response ]============================================
MID : 53522
Token : d09b7c5ede
Type : ACK
Status : 2.05 - CONTENT
Options: {"Content-Format":"text/plain"}
RTT : 10 ms
Payload: 12 Bytes
---------------------------------------------------------------
70 degrees F
A return code of 2.05 indicates that the response was sent as part of the CoAP ACK message. Also, the payload in the response to the /temp request was the 70 degrees F text. If an error had occurred instead, the return code would have indicated the error and no payload would have been provided.
Listing 3. An asynchronous CoAP GET request, with a callback to handle the response
class AsynchListener implements CoapHandler {
@Override
public void onLoad(CoapResponse response) {
String content = response.getResponseText();
System.out.println("onLoad: " + content);
}
@Override
public void onError() {
System.err.println("Error");
}
}
// ...
AsynchListener asynchListener = new AsynchListener();
client.get( asynchListener );
After the GET request is made, the response will arrive asynchronously via the onload method within the AsynchListener class, which extends the Californium library’s CoapHandler callback class.
The CoAP server code (see Listing 4) that runs on the temperature device, or the gateway connected to the temperature sensor, handles the GET request.
Listing 4. A CoAP listener that responds to requests for the current temperature
import org.eclipse.californium.core.CoapServer;
...
public class CoapTempServer extends CoapServer {
private static final int COAP_PORT =
Configuration.getStandard().get( CoapConfig.COAP_PORT );
private static final String tempUnti = "F";
float temperature = 70;
public static void main(String[] args) {
try {
CoapTempServer server = new CoapTempServer();
server.start();
}
catch ( Exception e ) {
System.err.println("CoAP server err: " + e.getMessage());
}
}
public CoapTempServer() throws SocketException {
super();
addEndpoints();
add( new TemperatureResource() );
}
private void addEndpoints() {
Configuration config = Configuration.getStandard();
// Add an endpoint listener for each host network interface
for (InetAddress addr :
NetworkInterfacesUtil.getNetworkInterfaces()) {
InetSocketAddress bindToAddress =
new InetSocketAddress(addr, COAP_PORT);
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setInetSocketAddress(bindToAddress);
builder.setConfiguration(config);
addEndpoint( builder.build() );
}
}
class TemperatureResource extends CoapResource {
public HelloWorldResource() {
super("temp"); // set resource URI identifier
getAttributes().setTitle("Server room temperature");
}
@Override
public void handleGET(CoapExchange exchange) {
// get latest temperature reading and return it
temperature = …
exchange.respond(temperature + " degrees " + tempUnit);
}
}
}
The call to addEndpoints in the constructor sets up the socket binding and listening for each host network address. Next, the CoAP resource, implemented in the nested class TemperatureResource, is created and added to the CoAP server. This code gets mapped to the associated URI. Finally, the call to server.start is all it takes to listen for requests, which are routed to the TemperatureResource.handleRequest method.
Listing 5. Adding multiple CoapResource implementations to one CoAP server
public class CoapGetServer extends CoapServer {
// ...
public CoapGetServer() throws SocketException {
super();
addEndpoints();
add(new TemperatureResource());
add(new HumidityResource());
}
class HumidityResource extends CoapResource {
public HumidityResource() {
super("humidity"); // set resource identifier
getAttributes().setTitle("Server room humidity");
}
@Override
public void handleGET(CoapExchange exchange) {
exchange.respond(humidity + " percent");
}
}
class TemperatureResource extends CoapResource {
// ...
}
}
With GET requests, the data flows in one direction. Next, let’s explore how POST requests make things more interesting.
CoAP POST. A CoAP POST request asks the recipient to process the representation (data) enclosed within the request. The actual function performed by the POST is dependent on the target. It typically results in the target resource being updated or a new resource being created if it doesn’t yet exist. If it’s created, the response should include the new URI for it.
Listing 6. A CoAP POST request is like GET but with form data added.
CoapClient client = new CoapClient("coap://192.168.1.10:5683/data");
CoapResponse r1 = client.post("data", MediaTypeRegistry.TEXT_PLAIN);
CoapResponse r2 = client.post( "<data>this is data</data>",
MediaTypeRegistry.APPLICATION_XML );
In this example, requests to the CoAP server resource named data are processed in the handlePOST method (see Listing 7) of the CoapResource interface implementation.
Listing 7. Handling CoAP POST requests
public class MyCoapServer extends CoapResource {
public static void main(String[] args) {
CoapServer server = new CoapServer();
server.add(new MyCoapServer("data"));
server.start();
}
@Override
public void handlePOST(CoapExchange exchange) {
exchange.accept();
int format = exchange.getRequestOptions()
.getContentFormat();
if (format == MediaTypeRegistry.APPLICATION_XML) {
String xml = exchange.getRequestText();
String responseTxt = "Received XML: '" + xml + "'";
System.out.println(responseTxt);
exchange.respond(CREATED, responseTxt);
}
else if (format == MediaTypeRegistry.TEXT_PLAIN) {
// ...
String plain = exchange.getRequestText();
String responseTxt = "Received text: '" + plain + "'";
System.out.println(responseTxt);
exchange.respond(CREATED, responseTxt );
}
else {
// ...
byte[] bytes = exchange.getRequestPayload();
System.out.println("Received bytes: " + bytes);
exchange.respond(CREATED);
}
}
// ...
}
CoAP PUT and DELETE. The PUT method is like POST with a subtle difference: If the resource doesn’t exist, the target has the option to create the resource. The DELETE method requests that the resource identified by the target resource URI be deleted. The DELETE status codes are 2.02 and 4.05.
Source: oracle.com
0 comments:
Post a Comment