diff --git a/examples/gcoap/gcoap_cli.c b/examples/gcoap/gcoap_cli.c index 4645c61f3a..c3d9ee2b62 100644 --- a/examples/gcoap/gcoap_cli.c +++ b/examples/gcoap/gcoap_cli.c @@ -29,6 +29,8 @@ #define ENABLE_DEBUG (0) #include "debug.h" +static ssize_t _encode_link(const coap_resource_t *resource, char *buf, + size_t maxlen, coap_link_encoder_ctx_t *context); static void _resp_handler(unsigned req_state, coap_pkt_t* pdu, sock_udp_ep_t *remote); static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, void *ctx); @@ -40,15 +42,39 @@ static const coap_resource_t _resources[] = { { "/riot/board", COAP_GET, _riot_board_handler, NULL }, }; +static const char *_link_params[] = { + ";ct=0;rt=\"count\"", + NULL +}; + static gcoap_listener_t _listener = { &_resources[0], sizeof(_resources) / sizeof(_resources[0]), + _encode_link, NULL }; /* Counts requests sent by CLI. */ static uint16_t req_count = 0; +/* Adds link format params to resource list */ +static ssize_t _encode_link(const coap_resource_t *resource, char *buf, + size_t maxlen, coap_link_encoder_ctx_t *context) { + ssize_t res = gcoap_encode_link(resource, buf, maxlen, context); + if (res > 0) { + if (_link_params[context->link_pos] + && (strlen(_link_params[context->link_pos]) < (maxlen - res))) { + if (buf) { + memcpy(buf+res, _link_params[context->link_pos], + strlen(_link_params[context->link_pos])); + } + return res + strlen(_link_params[context->link_pos]); + } + } + + return res; +} + /* * Response callback. */ diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index 0099119117..1767e83a7e 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -50,7 +50,8 @@ * [nanocoap](group__net__nanocoap.html) documentation. * * gcoap itself defines a resource for `/.well-known/core` discovery, which - * lists all of the registered paths. + * lists all of the registered paths. See the _Resource list creation_ section + * below for more. * * ### Creating a response ### * @@ -82,6 +83,16 @@ * If no payload, call only gcoap_response() to write the full response. If you * need to add Options, follow the first three steps in the list above instead. * + * ### Resource list creation ### + * + * gcoap allows customization of the function that provides the list of registered + * resources for `/.well-known/core` and CoRE Resource Directory registration. + * By default gcoap provides gcoap_encode_link(), which lists only the target + * path for each link. However, an application may specify a custom function in + * the gcoap_listener_t it registers with gcoap. For example, this function may + * add parameters to provide more information about the resource, as described + * in RFC 6690. See the gcoap example for use of a custom encoder function. + * * ## Client Operation ## * * Client operation includes two phases: creating and sending a request, and @@ -449,6 +460,38 @@ extern "C" { #define GCOAP_RESEND_BUFS_MAX (1) #endif +/** + * @name Bitwise positional flags for encoding resource links + * @{ + */ +#define COAP_LINK_FLAG_INIT_RESLIST (1) /**< initialize as for first resource + * in a list */ +/** @} */ + +/** + * @brief Context information required to write a resource link + */ +typedef struct { + unsigned content_format; /**< link format */ + size_t link_pos; /**< position of link within listener */ + uint16_t flags; /**< encoder switches; see GCOAP_LINK_FLAG_* + constants */ +} coap_link_encoder_ctx_t; + +/** + * @brief Handler function to write a resource link + * + * @param[in] resource Resource for link + * @param[out] buf Buffer on which to write; may be null + * @param[in] maxlen Remaining length for @p buf + * @param[in] context Contextual information on what/how to write + * + * @return count of bytes written to @p buf (or writable if @p buf is null) + * @return -1 on error + */ +typedef ssize_t (*gcoap_link_encoder_t)(const coap_resource_t *resource, char *buf, + size_t maxlen, coap_link_encoder_ctx_t *context); + /** * @brief A modular collection of resources for a server */ @@ -456,6 +499,7 @@ typedef struct gcoap_listener { const coap_resource_t *resources; /**< First element in the array of * resources; must order alphabetically */ size_t resources_len; /**< Length of array */ + gcoap_link_encoder_t link_encoder; /**< Writes a link for a resource */ struct gcoap_listener *next; /**< Next listener in list */ } gcoap_listener_t; @@ -715,6 +759,22 @@ uint8_t gcoap_op_state(void); */ int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf); +/** + * @brief Writes a resource in CoRE Link Format to a provided buffer. + * + * This default implementation only writes the resource path. + * + * @param[in] resource resource to write + * @param[out] buf output buffer to write link into, may be null + * @param[in] maxlen length of @p buf, ignored if @p buf is NULL + * @param[in] context other parameters that affect how link is written + * + * @return count of bytes written to @p buf (or writable if @p buf is null) + * @return -1 on error + */ +ssize_t gcoap_encode_link(const coap_resource_t *resource, char *buf, + size_t maxlen, coap_link_encoder_ctx_t *context); + /** * @brief Adds a single Uri-Query option to a CoAP request * diff --git a/sys/net/application_layer/gcoap/gcoap.c b/sys/net/application_layer/gcoap/gcoap.c index 7441e6f562..216b6ec215 100644 --- a/sys/net/application_layer/gcoap/gcoap.c +++ b/sys/net/application_layer/gcoap/gcoap.c @@ -63,6 +63,7 @@ const coap_resource_t _default_resources[] = { static gcoap_listener_t _default_listener = { &_default_resources[0], sizeof(_default_resources) / sizeof(_default_resources[0]), + NULL, NULL }; @@ -633,6 +634,9 @@ void gcoap_register_listener(gcoap_listener_t *listener) } listener->next = NULL; + if (!listener->link_encoder) { + listener->link_encoder = gcoap_encode_link; + } _last->next = listener; } @@ -900,7 +904,6 @@ uint8_t gcoap_op_state(void) int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf) { - (void)cf; /* only used in the assert below. */ assert(cf == COAP_FORMAT_LINK); /* skip the first listener, gcoap itself (we skip /.well-known/core) */ @@ -909,30 +912,36 @@ int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf) char *out = (char *)buf; size_t pos = 0; + coap_link_encoder_ctx_t ctx; + ctx.content_format = cf; + /* indicate initial link for the list */ + ctx.flags = COAP_LINK_FLAG_INIT_RESLIST; + /* write payload */ while (listener) { - const coap_resource_t *resource = listener->resources; + if (!listener->link_encoder) { + continue; + } + ctx.link_pos = 0; - for (unsigned i = 0; i < listener->resources_len; i++) { - size_t path_len = strlen(resource->path); + for (; ctx.link_pos < listener->resources_len; ctx.link_pos++) { + ssize_t res; if (out) { - /* only add new resources if there is space in the buffer */ - if ((pos + path_len + 3) > maxlen) { - break; - } - if (pos) { - out[pos++] = ','; - } - out[pos++] = '<'; - memcpy(&out[pos], resource->path, path_len); - pos += path_len; - out[pos++] = '>'; + res = listener->link_encoder(&listener->resources[ctx.link_pos], + &out[pos], maxlen - pos, &ctx); } else { - pos += (pos) ? 3 : 2; - pos += path_len; + res = listener->link_encoder(&listener->resources[ctx.link_pos], + NULL, 0, &ctx); + } + + if (res > 0) { + pos += res; + ctx.flags &= ~COAP_LINK_FLAG_INIT_RESLIST; + } + else { + break; } - ++resource; } listener = listener->next; @@ -941,6 +950,31 @@ int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf) return (int)pos; } +ssize_t gcoap_encode_link(const coap_resource_t *resource, char *buf, + size_t maxlen, coap_link_encoder_ctx_t *context) +{ + size_t path_len = strlen(resource->path); + /* count target separators and any link separator */ + size_t exp_size = path_len + 2 + + ((context->flags & COAP_LINK_FLAG_INIT_RESLIST) ? 0 : 1); + + if (buf) { + unsigned pos = 0; + if (exp_size > maxlen) { + return -1; + } + + if (!(context->flags & COAP_LINK_FLAG_INIT_RESLIST)) { + buf[pos++] = ','; + } + buf[pos++] = '<'; + memcpy(&buf[pos], resource->path, path_len); + buf[pos+path_len] = '>'; + } + + return exp_size; +} + int gcoap_add_qstring(coap_pkt_t *pdu, const char *key, const char *val) { char qs[NANOCOAP_QS_MAX]; diff --git a/tests/unittests/tests-gcoap/tests-gcoap.c b/tests/unittests/tests-gcoap/tests-gcoap.c index 981186aecc..19aabbf22a 100644 --- a/tests/unittests/tests-gcoap/tests-gcoap.c +++ b/tests/unittests/tests-gcoap/tests-gcoap.c @@ -38,12 +38,14 @@ static const coap_resource_t resources_second[] = { static gcoap_listener_t listener = { .resources = &resources[0], .resources_len = (sizeof(resources) / sizeof(resources[0])), + .link_encoder = NULL, .next = NULL }; static gcoap_listener_t listener_second = { .resources = &resources_second[0], .resources_len = (sizeof(resources_second) / sizeof(resources_second[0])), + .link_encoder = NULL, .next = NULL };