diff --git a/Makefile.dep b/Makefile.dep index bc8bb89299..783119f4d3 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -916,9 +916,22 @@ ifneq (,$(filter nimble_%,$(USEMODULE))) USEPKG += nimble endif +ifneq (,$(filter cord_common,$(USEMODULE))) + USEMODULE += fmt + USEMODULE += luid + USEMODULE += gcoap +endif + +ifneq (,$(filter cord_lc cord_ep,$(USEMODULE))) + USEMODULE += core_thread_flags + USEMODULE += cord_common + ifneq (,$(filter shell_commands,$(USEMODULE))) + USEMODULE += sock_util + endif +endif + ifneq (,$(filter cord_epsim,$(USEMODULE))) USEMODULE += cord_common - USEMODULE += gcoap endif ifneq (,$(filter cord_ep_standalone,$(USEMODULE))) @@ -926,18 +939,8 @@ ifneq (,$(filter cord_ep_standalone,$(USEMODULE))) USEMODULE += xtimer endif -ifneq (,$(filter cord_ep,$(USEMODULE))) - USEMODULE += cord_common - USEMODULE += core_thread_flags - USEMODULE += gcoap - ifneq (,$(filter shell_commands,$(USEMODULE))) - USEMODULE += sock_util - endif -endif - -ifneq (,$(filter cord_common,$(USEMODULE))) - USEMODULE += fmt - USEMODULE += luid +ifneq (,$(filter cord_lc,$(USEMODULE))) + USEMODULE += clif endif ifneq (,$(filter tlsf-malloc,$(USEMODULE))) diff --git a/sys/Makefile b/sys/Makefile index f09cef8023..080c08e2d2 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -154,6 +154,9 @@ endif ifneq (,$(filter cord_ep,$(USEMODULE))) DIRS += net/application_layer/cord/ep endif +ifneq (,$(filter cord_lc,$(USEMODULE))) + DIRS += net/application_layer/cord/lc +endif ifneq (,$(filter bluetil_%,$(USEMODULE))) DIRS += net/ble/bluetil endif diff --git a/sys/include/net/cord/lc.h b/sys/include/net/cord/lc.h new file mode 100644 index 0000000000..c348a3bbe6 --- /dev/null +++ b/sys/include/net/cord/lc.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2018 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup net_cord_lc CoRE RD Lookup Client + * @ingroup net_cord + * @brief Library for using RIOT as CoRE Resource Directory (RD) lookup client + * + * This module implements a CoRE Resource Directory lookup client library, that + * allows RIOT nodes to lookup resources, endpoints and groups with resource + * directories. + * It implements the standard lookup functionality as defined in + * draft-ietf-core-resource-directory-23. + * @see https://tools.ietf.org/html/draft-ietf-core-resource-directory-23 + * + * ## Lookup modes + * + * The module defines two types of lookup for interacting with a RD server: + * + * - raw: result of the lookup is returned as is. No `page` or `count` filter + * is applied by default. Use @ref cord_lc_raw() for this mode. + * - pre-parsed: result of the lookup is parsed and returned in a + * @ref cord_lc_res_t or @ref cord_lc_ep_t depending on the type of the + * lookup. The default `count` filter is set to `1` and `page` filter is + * incremented after each successful call and resets to `0` when lookup result + * is empty. Use @ref cord_lc_res() or cord_lc_ep() for this mode. + * + * ## Limitations + * + * Currently, this module cannot do more than a single request concurrently + * and the request is fully synchronous. The client can only connects to one + * RD server at a time. The client will disconnect when a connection to a new + * RD server is made, regardless of whether the connection attempt is successful + * or not. + * + * @{ + * + * @file + * @brief CoRE Resource Directory lookup interface + * + * @author Aiman Ismail + */ + +#ifndef NET_CORD_LC_H +#define NET_CORD_LC_H + +#include "net/sock/udp.h" +#include "net/nanocoap.h" +#include "clif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Return values and error codes used by this module + */ +enum { + CORD_LC_OK = 0, /**< everything went as expected */ + CORD_LC_TIMEOUT = -1, /**< no response from the network */ + CORD_LC_ERR = -2, /**< internal error or invalid reply */ + CORD_LC_OVERFLOW = -3, /**< internal buffers can not handle input */ + CORD_LC_NORSC = -4, /**< lookup interface not found */ +}; + +/** + * @brief RD lookup types + */ +enum { + CORD_LC_RES, /**< Resource lookup type */ + CORD_LC_EP, /**< Endpoint lookup type */ +}; + +/** + * @brief Information about RD server and its lookup interface resources + */ +typedef struct { + const sock_udp_ep_t *remote; /**< Remote endpoint of RD server */ + char *res_lookif; /**< Resource lookup interface */ + char *ep_lookif; /**< Endpoint lookup interface */ + unsigned res_last_page; /**< Page of last resource lookup */ + unsigned ep_last_page; /**< Page of last endpoint lookup */ +} cord_lc_rd_t; + +/** + * @brief Result of lookup + */ +struct cord_lc_result { + clif_t link; /**< Resource link */ + clif_attr_t *attrs; /**< Array of Link Format parameters */ + size_t max_attrs; /**< Max parameters at @p params */ +}; + +typedef struct cord_lc_result cord_lc_res_t; /**< Resource typedef */ +typedef struct cord_lc_result cord_lc_ep_t; /**< Endpoint typedef */ + +/** + * @brief Filters to use for a lookup + */ +typedef struct cord_lc_filter { + clif_attr_t *array; /**< Array of filter(s) */ + size_t len; /**< No. of filters at @p array */ + struct cord_lc_filter *next; /**< Next set of filters */ +} cord_lc_filter_t; + +/** + * @brief Discover the lookup interfaces of a RD server + * + * @param[out] rd Information about RD server at @p remote + * @param[out] buf Buffer to store found lookup interfaces + * @param[in] maxlen Maximum memory available at @p lookif + * @param[in] remote Remote endpoint of RD server + * + * @return bytes used on success + * @return CORD_LC_TIMEOUT if the discovery request times out + * @return CORD_LC_NORSC if no lookup interfaces found for all types + * of lookups + * @return CORD_LC_OVERFLOW if not enough memory available for result + * @return CORD_LC_ERR on any other internal error + */ +int cord_lc_rd_init(cord_lc_rd_t *rd, void *buf, size_t maxlen, + const sock_udp_ep_t *remote); + +/** + * @brief Raw lookup for registered resources/endpoints at a RD server + * + * To specify the number of resources/endpoints to search for, + * `count` and `page` attributes can be used as the filter. + * If the same filter used multiple times with different values, + * only the last filter will be applied. + * + * Content format (e.g. link-format, coral) must be set through + * filters. If none defined, link-format will be used. + * + * @param[in] rd RD server to query + * @param[in] content_format Data format wanted from RD server + * @param[in] lookup_type Lookup type to use + * @param[in] filters Filters for the lookup. Can be NULL. + * @param[out] result Buffer holding the raw response + * @param[in] maxlen Max length of @p result + * + * @return bytes used on success + * @return CORD_LC_NORSC if there is no lookup interface for @p lookup_type + * at @p rd + * @return CORD_LC_TIMEOUT if lookup timed out + * @return CORD_LC_OVERFLOW if not enough memory available for result + * @return CORD_LC_ERR on any other internal error + */ +ssize_t cord_lc_raw(const cord_lc_rd_t *rd, unsigned content_format, + unsigned lookup_type, cord_lc_filter_t *filters, + void *result, size_t maxlen); + +/** + * @brief Get one resource from RD server + * + * Gets only one resource from specified RD server each time called. + * Can be called iteratively to get all the resources on the server. + * If there is no more resource on the server, it will return + * CORD_LC_NORSC. + * + * @param[in] rd RD server to query + * @param[out] result Result link + * @param[in] filters Filters for the lookup + * @param[out] buf Result buffer + * @param[in] maxlen Maximum memory available at @p buf + * @param[in] type Type of resource to query either CORD_LC_EP or + * CORD_LC_RES + * + * @return bytes used on success + * @return CORD_LC_INVALIF if the resource lookup interface at @p rd is invalid + * @return CORD_LC_NORSC if there is no resource (anymore) at @p rd + * @return CORD_LC_TIMEOUT if lookup timed out + * @return CORD_LC_OVERFLOW if not enough memory available for result + * @return CORD_LC_ERR on any other internal error + */ +ssize_t _lookup_result(cord_lc_rd_t *rd, cord_lc_res_t *result, + cord_lc_filter_t *filters, void *buf, size_t maxlen, + unsigned type); + +/** + * @brief Get one resource from RD server + * + * Gets only one resource from specified RD server each time called. + * Can be called iteratively to get all the resources on the server. + * If there is no more resource on the server, it will return + * CORD_LC_NORSC. + * + * @param[in] rd RD server to query + * @param[out] resource Resource link + * @param[in] filters Filters for the lookup + * @param[out] buf Result buffer + * @param[in] maxlen Maximum memory available at @p buf + * + * @return bytes used on success + * @return CORD_LC_INVALIF if the resource lookup interface at @p rd is invalid + * @return CORD_LC_NORSC if there is no resource (anymore) at @p rd + * @return CORD_LC_TIMEOUT if lookup timed out + * @return CORD_LC_OVERFLOW if not enough memory available for result + * @return CORD_LC_ERR on any other internal error + */ +static inline ssize_t cord_lc_res(cord_lc_rd_t *rd, cord_lc_res_t *resource, + cord_lc_filter_t *filters, void *buf, + size_t maxlen) +{ + return _lookup_result(rd, resource, filters, buf, maxlen, CORD_LC_RES); +} + +/** + * @brief Get one endpoint from RD server + * + * Gets only one endpoint from specified RD server each time called. + * Can be called iteratively to get all the endpoints on the server. + * If there is no more endpoint on the server, it will return + * CORD_LC_NORSC. + * + * @param[in] rd RD server to query + * @param[out] endpoint Resource link + * @param[in] filters Filters for the lookup + * @param[out] buf Result buffer + * @param[in] maxlen Maximum memory available at @p buf + * + * @return bytes used on success + * @return CORD_LC_INVALIF if the endpoint lookup interface at @p rd is invalid + * @return CORD_LC_NORSC if there is no endpoints (anymore) at @p rd + * @return CORD_LC_TIMEOUT if lookup timed out + * @return CORD_LC_OVERFLOW if not enough memory available for result + * @return CORD_LC_ERR on any other internal error + */ +static inline ssize_t cord_lc_ep(cord_lc_rd_t *rd, cord_lc_ep_t *endpoint, + cord_lc_filter_t *filters, void *buf, + size_t maxlen) +{ + return _lookup_result(rd, endpoint, filters, buf, maxlen, CORD_LC_EP); +} + +#ifdef __cplusplus +} +#endif + +#endif /* NET_CORD_LC_H */ +/** @} */ diff --git a/sys/net/application_layer/cord/lc/Makefile b/sys/net/application_layer/cord/lc/Makefile new file mode 100644 index 0000000000..ed4441db65 --- /dev/null +++ b/sys/net/application_layer/cord/lc/Makefile @@ -0,0 +1,5 @@ +MODULE = cord_lc + +SRC = cord_lc.c + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/cord/lc/cord_lc.c b/sys/net/application_layer/cord/lc/cord_lc.c new file mode 100644 index 0000000000..7e2bc3c07d --- /dev/null +++ b/sys/net/application_layer/cord/lc/cord_lc.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2017-2018 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_cord_lc + * @{ + * + * @file + * @brief CoRE Resource Directory lookup implementation + * + * @author Aiman Ismail + * + * @} + */ + +#include + +#include "assert.h" +#include "mutex.h" +#include "thread_flags.h" + +#include "net/gcoap.h" +#include "net/cord/lc.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define FLAG_SUCCESS (0x0001) +#define FLAG_TIMEOUT (0x0002) +#define FLAG_ERR (0x0004) +#define FLAG_OVERFLOW (0x0008) +#define FLAG_NORSC (0x0010) +#define FLAG_MASK (0x00ff) + +#define BUFSIZE (CONFIG_GCOAP_PDU_BUF_SIZE) +#define MAX_EXPECTED_ATTRS (6) + +/* Filter to limit number of link returned by RD server to 1 */ +#define SINGLE_COUNT_FILTER { \ + .key = "count", .key_len = strlen("count"), \ + .value = "1", .value_len = strlen("1") \ + } + +/* Filter to continue request at page_str */ +#define PAGE_FILTER(page_str) { \ + .key = "page", .key_len = strlen("page"), \ + .value = page_str, .value_len = strlen(page_str) \ + } + +/* Default filters that will be appended to existing filters for each request. + * Consists of count=1 and page=last_page */ +#define DEFAULT_FILTERS(page_str) { SINGLE_COUNT_FILTER, PAGE_FILTER(page_str) } + +static void _lock(void); +static int _sync(void); +/* callback for _lookup_raw() request */ +static void _on_lookup(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, + const sock_udp_ep_t *remote); +/* callback for _send_rd_init_req() */ +static void _on_rd_init(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, + const sock_udp_ep_t *remote); +static ssize_t _add_filters_to_lookup(coap_pkt_t *pkt, cord_lc_filter_t *filters); +static int _send_rd_init_req(coap_pkt_t *pkt, const sock_udp_ep_t *remote, + void *buf, size_t maxlen); +/* do a lookup and returns payload of the response */ +static ssize_t _lookup_raw(const cord_lc_rd_t *rd, unsigned content_format, + unsigned lookup_type, cord_lc_filter_t *filters, + void *result, size_t maxlen); + +static char *_result_buf; +static size_t _result_buf_len; +static uint8_t reqbuf[CONFIG_GCOAP_PDU_BUF_SIZE] = {0}; + +static mutex_t _mutex = MUTEX_INIT; +static volatile thread_t *_waiter; + +static void _lock(void) +{ + mutex_lock(&_mutex); + _waiter = sched_active_thread; +} + +static int _sync(void) +{ + thread_flags_t flags = thread_flags_wait_any(FLAG_MASK); + + if (flags & FLAG_ERR) { + return CORD_LC_ERR; + } else if (flags & FLAG_TIMEOUT) { + return CORD_LC_TIMEOUT; + } else if (flags & FLAG_OVERFLOW) { + return CORD_LC_OVERFLOW; + } else if (flags & FLAG_NORSC) { + return CORD_LC_NORSC; + } else { + return CORD_LC_OK; + } +} + +static void _on_lookup(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, + const sock_udp_ep_t *remote) +{ + (void)remote; + + thread_flags_t flag = FLAG_ERR; + + if (memo->state == GCOAP_MEMO_RESP) { + unsigned ct = coap_get_content_type(pdu); + if (ct != COAP_FORMAT_LINK) { + DEBUG("cord_lc: unsupported content format: %u\n", ct); + thread_flags_set((thread_t *)_waiter, flag); + } + if (pdu->payload_len == 0) { + flag = FLAG_NORSC; + thread_flags_set((thread_t *)_waiter, flag); + } + if (pdu->payload_len >= _result_buf_len) { + flag = FLAG_OVERFLOW; + thread_flags_set((thread_t *)_waiter, flag); + } + memcpy(_result_buf, pdu->payload, pdu->payload_len); + memset(_result_buf + pdu->payload_len, 0, + _result_buf_len - pdu->payload_len); + _result_buf_len = pdu->payload_len; + flag = FLAG_SUCCESS; + } else if (memo->state == GCOAP_MEMO_TIMEOUT) { + flag = FLAG_TIMEOUT; + } + + thread_flags_set((thread_t *)_waiter, flag); +} + +static ssize_t _add_filters_to_lookup(coap_pkt_t *pkt, cord_lc_filter_t *filters) +{ + cord_lc_filter_t *f = filters; + while (f) { + for (unsigned i = 0; i < f->len; i++) { + clif_attr_t *kv = &(f->array[i]); + if (coap_opt_add_uri_query2(pkt, kv->key, kv->key_len, kv->value, kv->value_len) == -1) { + return CORD_LC_OVERFLOW; + } + } + f = f->next; + } + return CORD_LC_OK; +} + +static ssize_t _lookup_raw(const cord_lc_rd_t *rd, unsigned content_format, + unsigned lookup_type, cord_lc_filter_t *filters, + void *result, size_t maxlen) +{ + assert(rd->remote); + + int res; + int retval; + coap_pkt_t pkt; + ssize_t pkt_len; + + char *lookif = (lookup_type == CORD_LC_RES) ? rd->res_lookif : rd-> ep_lookif; + res = gcoap_req_init(&pkt, reqbuf, sizeof(reqbuf), COAP_METHOD_GET, lookif); + if (res < 0) { + DEBUG("cord_lc: failed gcoap_req_init()\n"); + return CORD_LC_ERR; + } + + /* save pointer to result */ + _result_buf = result; + _result_buf_len = maxlen; + + /* add filters */ + res = _add_filters_to_lookup(&pkt, filters); + if (res != CORD_LC_OK) { + return res; + } + + /* set packet options */ + if (content_format != COAP_FORMAT_LINK) { + DEBUG("cord_lc: unsupported content format\n"); + return CORD_LC_ERR; + } + coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + coap_opt_add_uint(&pkt, COAP_OPT_ACCEPT, content_format); + + pkt_len = gcoap_finish(&pkt, 0, COAP_FORMAT_NONE); + if (pkt_len < 0) { + return CORD_LC_ERR; + } + res = gcoap_req_send(reqbuf, pkt_len, rd->remote, _on_lookup, NULL); + if (res < 0) { + return CORD_LC_ERR; + } + retval = _sync(); + return (retval == CORD_LC_OK) ? (int)_result_buf_len : retval; +} + +static void _on_rd_init(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, + const sock_udp_ep_t *remote) +{ + (void)remote; + + thread_flags_t flag = FLAG_NORSC; + + if (memo->state == GCOAP_MEMO_RESP) { + unsigned ct = coap_get_content_type(pdu); + if (ct != COAP_FORMAT_LINK) { + DEBUG("cord_lc: error payload not in link format: %u\n", ct); + goto end; + } + if (pdu->payload_len == 0) { + DEBUG("cord_lc: error empty payload\n"); + goto end; + } + memcpy(_result_buf, pdu->payload, pdu->payload_len); + _result_buf_len = pdu->payload_len; + _result_buf[_result_buf_len] = '\0'; + flag = FLAG_SUCCESS; + } else if (memo->state == GCOAP_MEMO_TIMEOUT) { + flag = FLAG_TIMEOUT; + } + +end: + if (flag != FLAG_SUCCESS) { + _result_buf = NULL; + _result_buf_len = 0; + } + thread_flags_set((thread_t *)_waiter, flag); +} + +static int _send_rd_init_req(coap_pkt_t *pkt, const sock_udp_ep_t *remote, + void *buf, size_t maxlen) +{ + + int res = gcoap_req_init(pkt, buf, maxlen, COAP_METHOD_GET, "/.well-known/core"); + if (res < 0) { + DEBUG("cord_lc: error gcoap_req_init() %d\n", res); + return CORD_LC_ERR; + } + + coap_hdr_set_type(pkt->hdr, COAP_TYPE_CON); + coap_opt_add_uri_query(pkt, "rt", "core.rd-lookup-*"); + + ssize_t pkt_len = gcoap_finish(pkt, 0, COAP_FORMAT_NONE); + if (pkt_len < 0) { + DEBUG("cord_lc: error gcoap_finish() %zd\n", pkt_len); + return CORD_LC_ERR; + } + + if (!gcoap_req_send(buf, pkt_len, remote, _on_rd_init, NULL)) { + DEBUG("cord_lc: error gcoap_req_send()\n"); + return CORD_LC_ERR; + } + return _sync(); +} + +int cord_lc_rd_init(cord_lc_rd_t *rd, void *buf, size_t maxlen, + const sock_udp_ep_t *remote) +{ + assert(remote); + + coap_pkt_t pkt; + rd->remote = remote; + + _lock(); + memset(buf, '0', maxlen); + _result_buf = buf; + _result_buf_len = maxlen; + + int retval = _send_rd_init_req(&pkt, remote, buf, maxlen); + if (retval != CORD_LC_OK) { + DEBUG("cord_lc: failed to send req %d\n", retval); + goto end; + } + + /* Parse the payload */ + clif_t lookif; + clif_attr_t attrs[MAX_EXPECTED_ATTRS]; + unsigned attrs_used = 0; + size_t parsed_len = 0; + while ((!rd->res_lookif || !rd->ep_lookif) || + (parsed_len != _result_buf_len)) { + + ssize_t ret = clif_decode_link(&lookif, attrs + attrs_used, + ARRAY_SIZE(attrs) - attrs_used, + _result_buf + parsed_len, + _result_buf_len - parsed_len); + if (ret < 0) { + DEBUG("cord_lc: error decoding payload %zd\n", ret); + retval = CORD_LC_ERR; + goto end; + } + + attrs_used += lookif.attrs_len; + parsed_len += ret; + + /* check if we found ep_lookif or res_lookif */ + for (unsigned i = 0; i < lookif.attrs_len; i++) { + clif_attr_t *current_attr = (lookif.attrs + i); + if (current_attr->value == NULL) { + /* skip attributes that have no value */ + continue; + } + + if (!strncmp(current_attr->value, "core.rd-lookup-res", + current_attr->value_len)) { + rd->res_lookif = lookif.target; + rd->res_lookif[lookif.target_len] = '\0'; + } else if (!strncmp(current_attr->value, "core.rd-lookup-ep", + current_attr->value_len)) { + rd->ep_lookif = lookif.target; + rd->ep_lookif[lookif.target_len] = '\0'; + } + } + } + + if (!rd->res_lookif && !rd->ep_lookif) { + DEBUG("cord_lc: no lookup interfaces found\n"); + retval = CORD_LC_NORSC; + } else { + retval = parsed_len; + } +end: + mutex_unlock(&_mutex); + return retval; +} + +ssize_t cord_lc_raw(const cord_lc_rd_t *rd, unsigned content_format, + unsigned lookup_type, cord_lc_filter_t *filters, + void *result, size_t maxlen) +{ + _lock(); + ssize_t retval = _lookup_raw(rd, content_format, lookup_type, filters, + result, maxlen); + mutex_unlock(&_mutex); + return retval; +} + +ssize_t _lookup_result(cord_lc_rd_t *rd, cord_lc_res_t *result, + cord_lc_filter_t *filters, void *buf, size_t maxlen, + unsigned type) +{ + int retval; + + _lock(); + unsigned *page_ptr = (type == CORD_LC_EP) + ? &rd->ep_last_page : &rd->res_last_page; + /* int will always fit in an 12-char array */ + char page_str[12]; + snprintf(page_str, sizeof(page_str), "%u", (*page_ptr)++); + + /* Append given filters to default filters (page, count). + * If same filter are also specified by filters, assume the RD server will + * use the value from last filter in the filter list */ + clif_attr_t default_attrs[] = DEFAULT_FILTERS(page_str); + cord_lc_filter_t *all_filters = &(cord_lc_filter_t) { + .array = default_attrs, + .len = ARRAY_SIZE(default_attrs), + .next = filters, + }; + + retval = _lookup_raw(rd, COAP_FORMAT_LINK, type, all_filters, buf, maxlen); + if (retval < 0) { + if (retval == CORD_LC_NORSC) { + *page_ptr = 0; + } + DEBUG("cord_lc: error ep lookup failed\n"); + mutex_unlock(&_mutex); + return retval; + } + + /* parse the result */ + retval = clif_decode_link(&result->link, result->attrs, result->max_attrs, + buf, retval); + if (retval < 0) { + DEBUG("cord_lc: no endpoint link found\n"); + retval = CORD_LC_ERR; + } + mutex_unlock(&_mutex); + return retval; +}