nRF51 IoT SDK
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
CoAP client/server

Nordic's nCoAP library supports both the client role, the server role, and a mixed role of the Constrained Application Protocol (CoAP).

Applications can use this library to generate CoAP request messages and to set up servers by setting up CoAP endpoints. The memory management for the requests and responses is handled by the library, as is the token matching on responses due to a request message. This token matching will issue a call to an application-provided function that can handle the request.

Endpoints are composed by linking resources in a linked-list manner, each resource pointing to its children. This way it is possible to dynamically locate a given resource based on the CoAP URI-path options and issue a call to a specific endpoint callback function. The linked list also allows to generate a CoRE Link Format Resource Discovery string according to RFC6690.

Client role

The illustration in Figure 1 shows the flow of sending a request message from a CoAP client.

msc_coap_request_flow
Figure 1. nCoAP request message flow.

First, the message to be sent is created and configured using nCoAP library calls. Next, the message is scheduled for transmission by calling the send function in the library. Scheduling the message for transmission creates a copy of the serialized CoAP message and puts this copy in an internal message queue, along with some metadata that is associated with the message. Information like transport layer socket, token number, and other values is stored with the raw CoAP message. This information will be used to match received response message with the messages in the internal queue. If the message is a confirmable request (CON request), the message will be retransmitted if no response was received within the receive time-out.

Note
Currently, the implementation does not support retransmitting CON messages.

When receiving the response, the nCoAP library will match the token of the response message, compare it against the messages in the transmission queue, issue a callback to the application function that was registered along with the request creation, and return.

The original request message in its serialized format is backed up before it is added to the transmission queue. Therefore, the request can safely be deleted at any time. Deleting a CoAP request will free only the memory allocated during creation of the request, not the serialized copy in the nCoAP message transmission queue. This way, the application can keep the application-created request and reuse the message without having to create the message again. If token and message ID are the only values that change between requests, the request with its options and payload can be reused several times, and new serialized versions of the requests will be generated when calling the send function in the library.

Server role

The illustration in Figure 2 shows the flow of receiving a request message from a remote CoAP client. The CoAP server responds to the request after calling the method callback function of the endpoint resource.

msc_coap_response_flow
Figure 2. nCoAP remote request message flow.

First, the endpoint resources are registered, forming a linked-list hierarchy. The first node that is registered acts as the root for subsequent operations on the endpoint resources. Next, a method callback function is set to the endpoint. (Figure 2 uses an endpoint named "light" for illustration.) This way, the client can do method calls on this resource.

When receiving the remote request, the nCoAP library will resolve the resource that was earlier registered. It will traverse recursively through the hierarchy of resource entities and match the names against the URI-paths in the request message. When the correct resource is found, the library calls the resource method handler function that has been set by the application when registering the resource. Right before the callback is issued, a response message is created by the library. Message type and token ID are copied over to the generated response message and passed to the callback along with the original request. The method handler function can override the response message by setting a new response code, add options, or adding a payload. When the function returns, the constructed response will be transmitted over UDP as a CoAP response message.

Endpoints

All CoAP endpoints in nCoAP are called resources. An endpoint can be anything from a resource that is listed in the well-known URI ".well-known/core" on the CoAP server to an endpoint that controls an LED. The resources must be defined in such a way that they stay in memory during the lifetime of the application.

After coap_init has been called, any number of resources can be added. There is no extra memory needed for a resource besides the memory that is used during its declaration. All declared resources are linked together in a hierarchy using the nCoAP library functions.

Resource Hierarchy

The first resource that is created always forms the root of the hierarchy. The coap_init function clears this information and unassigns the root again. Creating a resource does not allocate any additional memory, but rather copies the provided string into the resource and initializes the resource structure.

After the resource is created, it can be linked to another. If a child is linked to a parent, the parent will form a linked list with children, using a front and a back pointer. Each new child that is added to the same parent will be added in the back. The back pointer of the last sibling that was added is then redirected to the new child.

There is no API for removing a child resource. Therefore, the resource hierarchy must be determined during the design of the application. This resources hierarchy can also serve as basis for generating the .well-known/core string.

The example below demonstrates how to link four resources together, forming a hiearchy of resources: one root with two children, where one of the children has a child (grandchild0).

uint32_t err_code = coap_init();
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
static coap_resource_t root;
static coap_resource_t child0;
static coap_resource_t grandchild0;
static coap_resource_t child1;
// Create root.
err_code = coap_resource_create(&root, "/");
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
// Create children.
err_code = coap_resource_create(&child0, "light");
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
err_code = coap_resource_create(&grandchild0, "led0");
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
err_code = coap_resource_create(&child1, "temp");
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
// Add root of all endpoints. The first resource to be
// added will be treated as the root of the resources.
err_code = coap_resource_child_add(&root, &child0);
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
err_code = coap_resource_child_add(&root, &child1);
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
err_code = coap_resource_child_add(&child0, &grandchild0);
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);

The image below shows how the resources are linked together to form a resource hiearchy. This hierarchy can be used to locate endpoints when a remote request is sent to an nCoAP server. The image also shows how the resources in the example code above are conceptually linked together in the linked list.

coap_resource_layout.png
Figure 3. nCoAP resource hierarchy with four registered resources.

Callback

The nCoAP endpoints can have a callback function, which is called whenever a CoAP request is detected that has a URI path matching with the registered resources in nCoAP. If no callback is registered for the URI path, a reply with a "404 (Not Found)" code is returned.

void resource_callback_func(coap_message_t * p_request, coap_message_t * p_response)
{
p_response->header.code = COAP_CODE_205_CONTENT;
uint8_t payload[] = {1};
coap_message_payload_set(p_response, payload, sizeof(payload));
}
...
static coap_resource_t resource;
err_code = coap_resource_create(&resource, "resource");
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);
resource.callback = resource_callback_func;

Permissions

Each endpoint can be configured with different access permissions. Each method can be allowed or not allowed; if no permissions are defined explicitely, the default is to allow all methods on the endpoint.

The example below demonstrates how to set the permissions of the fictive "resource" endpoint. The permission settings allow GET and PUT requests, but deny all other methods. Requests to other methods will be rejected and replied to with a "405 (Method Not Allowed)" code. The endpoint callback function will not be called but handled internally in the nCoAP library.

static coap_resource_t resource;
err_code = coap_resource_create(&resource, "resource");
TEST_ASSERT_EQUAL_UINT32(NRF_SUCCESS, err_code);

.well-known/core

The nCoAP library provides an easy-to-use function for collecting the resource names to be used in a .well-known/core resource endpoint. The memory is provided from the application and must be large enough to hold all name strings of all resources plus the link-format syntax. If the provided buffer does not satisfy the needed size of the generated well-known string, an error code is returned.

The function coap_resource_well_known_generate should be run once during startup of the application, to populate the buffer. Traversing the resource hierarchy is a costly operation if the number of resources is great. Therefore, it is assumed that the resources do not change names during the lifetime of the application.

Note
The nCoAP does not currently support creation and deletion of resources at runtime.

The example below demonstrates how the coap_resource_well_known_generate function can be used. In this example, the well_known_core buffer is used as a global, because it can easily be access by both the main function that creates the resource hiearchy and the callback function for the ".well-known/core" endpoint.

static uint8_t well_known_core[100];
...
uint16_t size = sizeof(well_known_core);
err_code = coap_resource_well_known_generate(well_known_core, &size);
APP_ERROR_CHECK(err_code);

Client requests

Creating a request message

A request can be generated by using the coap_request_create API call. The supplied configuration is used as base for creating the new message. Parts of the message can also be overridden by setting the structure members of coap_message_t after creating the request. The transport layer socket association will be automatically assigned during the creation of the request.

Note
If the id member of coap_message_conf_t is set to 0, an auto-generated message ID will be assigned to the new request message on creation.

The example below demonstrates how to create a Non Confirmable (NON) request.

coap_message_t * request;
coap_message_conf_t message_conf;
// Set the CoAP message type to Non Confirmable message.
message_conf.type = COAP_TYPE_NON;
// Set the CoAP method.
message_conf.code = COAP_CODE_PUT;
// Specify a token for the message.
message_conf.token[0] = 0x2;
message_conf.token[1] = 0x4;
message_conf.token_len = 2;
// Register a callback to be run when response for this message is received.
message_conf.response_callback = response_handle_func;
uint32_t err_code = coap_request_create(&request, &message_conf);
if (err_code == NRF_SUCCESS)
...

Setting the remote address

Before the send function is called, the remote destination must be set in the nCoAP request message. The remote address must be set after creating the message, however, because creating the message wipes the allocated memory.

The example below demonstrates how to set the remote address. In this example, the remote address is set to [2003::4] on port number 5683.

static coap_remote_t remote;
remote.port_number = 5683;
remote.addr[0] = 0x20;
remote.addr[1] = 0x03;
remote.addr[15] = 0x04;
...
uint32_t err_code = coap_request_create(&request, &message_conf);
// Request is successfully created and memory allocation for the new request is OK.
if (err_code == NRF_SUCCESS)
{
...
// Set the remote address information.
coap_message_remote_addr_set(request, &remote);
...
// Send the message.
}

Sending the request

After a new request has been created successfully and the address and port number to the remote have been set, the message can be populated with options and payload.

The example below demonstrates how to send the message:

uint32_t err_code = coap_request_create(&request, &message_conf);
if (err_code == NRF_SUCCESS)
{
...
// Trigger the message to be sent.
err_code = coap_request_send(request);
if (err_code != NRF_SUCCESS)
{
// Skip it or try again.
}
}

Deleting the request

After the message has been scheduled for transmission, it can be cleaned up, so that all its memory allocations are freed. This function must be explicitly called unless the application reuses the created message.

The example below demonstrates how to delete the message:

coap_message_t * request;
coap_message_conf_t message_conf;
...
uint32_t err_code = coap_request_create(&request, &message_conf);
...
// Clean up the memory used by the request message.
err_code = coap_request_delete(request);
APP_ERROR_CHECK(err_code);

Full client example

The code below sends a CoAP NON request to coap://[2004::b1]:5683/light/led0, doing a PUT with a one-byte payload.

void response_handler(coap_message_t * p_response)
{
...
}
coap_message_t * request;
coap_message_conf_t message_conf;
// Set remote address to 2004::b1 to the CoAP server.
remote.addr[0] = 0x20;
remote.addr[1] = 0x04;
remote.addr[15] = 0xb1;
// Set port number of the CoAP server.
remote.port_number = 5683;
// Set the CoAP message type to Non Confirmable message.
message_conf.type = COAP_TYPE_NON;
// Set the CoAP method.
message_conf.code = COAP_CODE_PUT;
// Specify a token for the message.
message_conf.token[0] = 0x3;
message_conf.token[1] = 0x4;
message_conf.token_len = 2;
// Register a callback to be run when response for this message is received.
message_conf.response_callback = stock_price_handle;
uint32_t err_code = coap_request_create(&request, &message_conf);
if (err_code == NRF_SUCCESS)
{
// Set remote address and port number to the CoAP server.
coap_message_remote_addr_set(request, &remote);
// Add URI path to the endpoint.
err_code = coap_message_opt_str_add(request, COAP_OPT_URI_PATH, (uint8_t *)"light", 5);
APP_ERROR_CHECK(err_code);
err_code = coap_message_opt_str_add(request, COAP_OPT_URI_PATH, (uint8_t *)"led0", 4);
APP_ERROR_CHECK(err_code);
// Add the PUT payload.
uint8_t payload[] = {1};
err_code = coap_message_payload_set(request, payload, sizeof(payload));
APP_ERROR_CHECK(err_code);
// Trigger the message to be sent.
err_code = coap_request_send(request);
if (err_code != NRF_SUCCESS)
{
// Skip it or try again.
}
// Clean up the memory used by the request message.
err_code = coap_request_delete(request);
APP_ERROR_CHECK(err_code);
}

Configuration parameters

The following configuration parameters should be defined in sdk_config.h.

COAP_DISABLE_LOGS

Disables debug tracing in the module. To enable tracing, this flag must be set to 0 and ENABLE_DEBUG_LOG_SUPPORT must be set to 1.

Description Value
Enable debug trace 0
Disable debug trace 1
Dependencies ENABLE_DEBUG_LOG_SUPPORT

COAP_DISABLE_API_PARAM_CHECK

Disables API parameter checks in the module. Set this define to 1 to disable checks on API parameters in the module.

API parameter checks are added to ensure that the correct parameters are passed to the module. These checks are useful during development phase, but they might be redundant when the application is finalized. Disabling these checks might improve performance.

Description Value
Enable API parameters check 0
Disable API parameters check 1
Dependencies None

COAP_VERSION

Default version number used in CoAP packet.

Restriction Value
Minimum value 0
Maximum value 3
Recommended value 1
Dependencies None

COAP_PORT_COUNT

Maximum number of client/server ports used by the application. One socket will be created for each port.

Restriction Value
Minimum value 0
Maximum value UDP6_MAX_SOCKET_COUNT
Dependencies UDP6_MAX_SOCKET_COUNT

COAP_SERVER_PORT

Port number used for a CoAP server.

Restriction Value
Minimum value 1
Maximum value 65535
Dependencies None

COAP_CLIENT_PORT

Port number used for a CoAP client.

Restriction Value
Minimum value 1
Maximum value 65535
Dependencies None

COAP_MAX_NUMBER_OF_OPTIONS

Maximum number of options that the nCoAP library can process. If the maximum limit is reached, the library will treat the package as unprocessable.

Restriction Value
Minimum value 1
Maximum value 255
Dependencies None

COAP_MESSAGE_DATA_MAX_SIZE

Maximum size of an nCoAP message excluding the mandatory CoAP header.

Restriction Value
Minimum value 1
Maximum value 65535
Dependencies None

COAP_MESSAGE_QUEUE_SIZE

Maximum number of nCoAP request messages that can be in transmission at the same time (client).

nCoAP uses the Memory Manager that is also used by the underlying transport protocol. Therefore, if you increase this value, you should also increase the number of buffers. Depending on the COAP_MESSAGE_DATA_MAX_SIZE + 4 byte CoAP header, you must increase either MEMORY_MANAGER_SMALL_BLOCK_COUNT or MEMORY_MANAGER_MEDIUM_BLOCK_COUNT to ensure that there are additional buffers for the CoAP message queue. Which macro must be increased depends on the size of the buffer that is sufficient for the CoAP message.

Restriction Value
Minimum value 1
Maximum value 65535
Recommended value 4
Dependencies MEMORY_MANAGER_SMALL_BLOCK_COUNT
MEMORY_MANAGER_MEDIUM_BLOCK_COUNT
MEMORY_MANAGER_SMALL_BLOCK_SIZE
MEMORY_MANAGER_MEDIUM_BLOCK_SIZE

COAP_RESOURCE_MAX_NAME_LEN

Maximum length of the resource name that can be supplied from the application.

Note
One extra byte will be added to the resource name to make sure that the provided string is zero terminated.
Restriction Value
Minimum value 1
Maximum value 65535
Dependencies None

COAP_RESOURCE_MAX_DEPTH

Maximum number of resource depth levels that nCoAP will use. The number is used when adding resources to the resource structure or when traversing the resources for a matching resource name given in a request. Each added level increases the stack usage runtime with 4 bytes.

Restriction Value
Minimum value 1
Maximum value 255
Recommended value 1 - 10
Dependencies None

Specifics and limitations

The following sections describes specifics and limitations to the current implementation.

Implemented features

  • CoAP message types CON, NON, ACK, and RESET.
  • Automatic generation of message ID.
  • Token matching on responses to a local client generated request.
  • Callback on response to a local client generated request.
  • Endpoint creation as resources.
  • Automatic lookup of requested endpoint when receiving a remote request.
  • Endpoint resource function callback to tune response message.
  • Automatic generation of 404 (Not Found) response message if resource is not located among the registered resources.
  • Permission setting on endpoints to select methods to be handled by the endpoint resource callback function.
  • Automatic generation of 405 (Method Not Allowed) response message if located endpoint has permission scheme not allowing the method to be executed on the endpoint resource.
  • CoAP ping (empty message).
  • NON request/reply.
  • CON request/reply (piggybacked).
  • ACK message generated automatically on CON requests.
  • Automatic generation of .well-known/core link-format discovery string.

Limitations

  • Delete message type is not supported.
  • No resource creation on POST and PUT methods.
  • Duplicate message detection is not implemented in the current library.
  • Delayed responses are not supported, only piggybacked responses for CON.
  • CON retransmission is not implemented.
  • Content format in request handling has no effect, and endpoint resources have no implementation handling different content types. Bitmask variables are defined, but not used to enable different content types in a resource, only plain text is used.
  • No multicast support.
  • Not supporting the proxy role.
  • Messages in the transmission queue are not trimmed off the queue if no matching token is received. Re-initialization can be done to flush the queue at the moment.
  • Reset messages are not clearing the original request from the queue on reception.
  • Generating unique tokens is left to the application in the current implementation of the library.
  • COAPS not supported.

References