[RFC] CoAP for Zephyr


Vinicius Costa Gomes
 

Hi,

Extracted from:

https://gerrit.zephyrproject.org/r/#/c/2487/

--8<---------------cut here---------------start------------->8---

Quapi[1] - Basic CoAP for Zephyr
################################

CoAP[2] is a widely used protocol for IoT communications, used in the OCF
and LWM2M specifications, for example.

Concepts behind Quapi:
- basic, it will be enough to be compliant with the specification and to
implement concrete use cases from OCF and LWM2M, for example;
- network independence, to make the transition to others network stacks easier,
and testing trivial;
- predicable memory usage, avoiding dynamic memory allocation and keeping
the necessary state tracking mechanisms visible to the user;

The possibility of porting `sol-coap` (from Soletta[3]) to Zephyr was
considered, but the differences in the concurrence model of Soletta
and Zephyr, and the dependence of dynamic memory allocation would make
the porting difficult.

`libcoap` [4] is a pretty complete implementation, but too complicated
because it heavily depends on the network stack API and concepts, and
even if it's possible to make it to not allocate memory, much of it is
not directly exposed to the user.

You can reach the current implementation like this:

git clone http://ab01.bz.intel.com/~vcostago/quapi.git

There a couple of basic unit tests (nothing fancy) and very simple
example, and a few FIXME's :-)

If you don't feel like cloning the repository, here is the API::

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

/* Useless forward declaration */
struct quapi_resource;
struct quapi_packet;
struct quapi_pending;

enum quapi_option_num {
QUAPI_OPTION_IF_MATCH = 1,
QUAPI_OPTION_URI_HOST = 3,
QUAPI_OPTION_ETAG = 4,
QUAPI_OPTION_IF_NONE_MATCH = 5,
QUAPI_OPTION_OBSERVE = 6,
QUAPI_OPTION_URI_PORT = 7,
QUAPI_OPTION_LOCATION_PATH = 8,
QUAPI_OPTION_URI_PATH = 11,
QUAPI_OPTION_CONTENT_FORMAT = 12,
QUAPI_OPTION_MAX_AGE = 14,
QUAPI_OPTION_URI_QUERY = 15,
QUAPI_OPTION_ACCEPT = 17,
QUAPI_OPTION_LOCATION_QUERY = 20,
QUAPI_OPTION_PROXY_URI = 35,
QUAPI_OPTION_PROXY_SCHEME = 39
};

enum quapi_method {
QUAPI_METHOD_GET = 1,
QUAPI_METHOD_POST = 2,
QUAPI_METHOD_PUT = 3,
QUAPI_METHOD_DELETE = 4,
};

#define QUAPI_REQUEST_MASK 0x07

enum quapi_msgtype {
QUAPI_TYPE_CON = 0,
QUAPI_TYPE_NON_CON = 1,
QUAPI_TYPE_ACK = 2,
QUAPI_TYPE_RESET = 3
};

#define quapi_make_response_code(clas, det) ((clas << 5) | (det))

enum quapi_response_code {
QUAPI_RESPONSE_CODE_OK = quapi_make_response_code(2, 0),
QUAPI_RESPONSE_CODE_CREATED = quapi_make_response_code(2, 1),
QUAPI_RESPONSE_CODE_DELETED = quapi_make_response_code(2, 2),
QUAPI_RESPONSE_CODE_VALID = quapi_make_response_code(2, 3),
QUAPI_RESPONSE_CODE_CHANGED = quapi_make_response_code(2, 4),
QUAPI_RESPONSE_CODE_CONTENT = quapi_make_response_code(2, 5),
QUAPI_RESPONSE_CODE_BAD_REQUEST = quapi_make_response_code(4, 0),
QUAPI_RESPONSE_CODE_UNAUTHORIZED = quapi_make_response_code(4, 1),
QUAPI_RESPONSE_CODE_BAD_OPTION = quapi_make_response_code(4, 2),
QUAPI_RESPONSE_CODE_FORBIDDEN = quapi_make_response_code(4, 3),
QUAPI_RESPONSE_CODE_NOT_FOUND = quapi_make_response_code(4, 4),
QUAPI_RESPONSE_CODE_NOT_ALLOWED = quapi_make_response_code(4, 5),
QUAPI_RESPONSE_CODE_NOT_ACCEPTABLE = quapi_make_response_code(4, 6),
QUAPI_RESPONSE_CODE_PRECONDITION_FAILED = quapi_make_response_code(4, 12),
QUAPI_RESPONSE_CODE_REQUEST_TOO_LARGE = quapi_make_response_code(4, 13),
QUAPI_RESPONSE_CODE_INTERNAL_ERROR = quapi_make_response_code(5, 0),
QUAPI_RESPONSE_CODE_NOT_IMPLEMENTED = quapi_make_response_code(5, 1),
QUAPI_RESPONSE_CODE_BAD_GATEWAY = quapi_make_response_code(5, 2),
QUAPI_RESPONSE_CODE_SERVICE_UNAVAILABLE = quapi_make_response_code(5, 3),
QUAPI_RESPONSE_CODE_GATEWAY_TIMEOUT = quapi_make_response_code(5, 4),
QUAPI_RESPONSE_CODE_PROXYING_NOT_SUPPORTED = quapi_make_response_code(5, 5)
};

#define QUAPI_CODE_EMPTY (0)

typedef int (*quapi_method_t)(const struct quapi_resource *resource,
struct quapi_packet *request,
const void *addr);

struct quapi_resource {
quapi_method_t get, post, put, del;
const char **path;
void *user_data;
};

struct quapi_packet {
uint8_t *buf;
uint8_t *start;
uint16_t len;
uint16_t used;
};

typedef int (*quapi_reply_t)(const struct quapi_packet *response,
struct quapi_pending *pending);

struct quapi_pending {
struct quapi_packet request;
uint16_t timeout;
quapi_reply_t reply;
void *user_data;
};

struct quapi_option {
void *value;
uint16_t len;
};

/* buf must be valid while pkt is used */
int quapi_packet_parse(struct quapi_packet *pkt, uint8_t *buf, size_t len);

/* buf must be valid while pkt is used */
int quapi_packet_init(struct quapi_packet *pkt,
uint8_t *buf, size_t len);

int quapi_pending_init(struct quapi_pending *pending,
const struct quapi_packet *request);

struct quapi_pending *quapi_pending_next_unused(
struct quapi_pending *pendings);

/*
* After a response is received, clear all pending retransmissions related to
* that response.
*/
struct quapi_pending *quapi_pending_received(
const struct quapi_packet *response,
struct quapi_pending *pendings);

/*
* Returns the next pending about to expire, pending->timeout informs how many
* ms to next expiration.
*/
struct quapi_pending *quapi_pending_next_to_expire(
struct quapi_pending *pendings);

/*
* After a request is sent, user may want to cycle the pending retransmission
* so the timeout is updated. Returns false if this is the last
* retransmission.
*/
bool quapi_pending_cycle(struct quapi_pending *pending);

/*
* When a request is received, call the appropriate methods of the
* matching resources.
*/
int quapi_handle_request(struct quapi_packet *pkt,
const struct quapi_resource *resources,
const void *from);

/*
* Same logic as the first implementation of sol-coap, returns a pointer to
* the start of the payload, and how much memory is available (to the
* payload), it will also insert he COAP_MARKER (0xFF).
*/
uint8_t *quapi_packet_get_payload(struct quapi_packet *pkt, uint16_t *len);

int quapi_packet_set_used(struct quapi_packet *pkt, uint16_t len);

int quapi_add_option(struct quapi_packet *pkt, uint16_t code,
const void *value, uint16_t len);

int quapi_find_options(const struct quapi_packet *pkt, uint16_t code,
struct quapi_option *options, uint16_t veclen);

/* This stuff isn't interesting */
uint8_t quapi_header_get_version(const struct quapi_packet *pkt);

uint8_t quapi_header_get_type(const struct quapi_packet *pkt);

const uint8_t *quapi_header_get_token(const struct quapi_packet *pkt,
uint8_t *len);

uint8_t quapi_header_get_code(const struct quapi_packet *pkt);

uint16_t quapi_header_get_id(const struct quapi_packet *pkt);

void quapi_header_set_version(struct quapi_packet *pkt, uint8_t ver);

void quapi_header_set_type(struct quapi_packet *pkt, uint8_t type);

int quapi_header_set_token(struct quapi_packet *pkt, const uint8_t *token,
uint8_t tokenlen);

void quapi_header_set_code(struct quapi_packet *pkt, uint8_t code);

void quapi_header_set_id(struct quapi_packet *pkt, uint16_t id);


Another thing that I would appreciate is to not focus on the names of
the functions (at first), instead focusing on the primitives provided.
The lack of documentation is a feature for now :-)

One thing that I couldn't decide is how to expose the "observe"[5]
functionality without at least the abstraction of a network
address. Suggestions are welcome.


[1] The name is a placeholder, the only reason it was chosen was that
it sounds like 'coap' (for portuguese speakers), but I would like that
this kind of discussion would happen after the consensus is reached
that this approach has value :-)

[2] https://tools.ietf.org/html/rfc7252

[3] https://github.com/solettaproject/soletta

[4] https://libcoap.net/

[5] https://tools.ietf.org/html/rfc7641


Cheers,
--
Vinicius

Join devel@lists.zephyrproject.org to automatically receive all group messages.