1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cord: add RD lookup client

This commit is contained in:
Aiman Ismail 2019-11-12 14:52:18 +01:00 committed by M Aiman Ismail
parent 88279904c5
commit 869052dcd8
5 changed files with 654 additions and 13 deletions

View File

@ -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)))

View File

@ -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

246
sys/include/net/cord/lc.h Normal file
View File

@ -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 <muhammadaimanbin.ismail@haw-hamburg.de>
*/
#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 */
/** @} */

View File

@ -0,0 +1,5 @@
MODULE = cord_lc
SRC = cord_lc.c
include $(RIOTBASE)/Makefile.base

View File

@ -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 <muhammadaimanbin.ismail@haw-hamburg.de>
*
* @}
*/
#include <string.h>
#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;
}