diff --git a/Makefile.dep b/Makefile.dep index 4c55e2e275e8de5234096ae957276bdc1e3d158f..e083323e1b0e67d82efda6c7a4107840c9560b60 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -768,6 +768,21 @@ ifneq (,$(filter rdcli_simple,$(USEMODULE))) USEMODULE += fmt endif +ifneq (,$(filter rdcli_standalone,$(USEMODULE))) + USEMODULE += rdcli + USEMODULE += xtimer +endif + +ifneq (,$(filter rdcli,$(USEMODULE))) + USEMODULE += rdcli_common + USEMODULE += core_thread_flags + USEMODULE += gcoap + USEMODULE += fmt + ifneq (,$(filter shell_commands,$(USEMODULE))) + USEMODULE += sock_util + endif +endif + ifneq (,$(filter rdcli_common,$(USEMODULE))) USEMODULE += fmt USEMODULE += gcoap diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index c57bff7d0dda09d8f5dd3226bf75b646eb7231bd..c5e37daaefed1663c5a830e0bec86284fa812465 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -65,6 +65,7 @@ PSEUDOMODULES += pktqueue PSEUDOMODULES += printf_float PSEUDOMODULES += prng PSEUDOMODULES += prng_% +PSEUDOMODULES += rdcli_standalone PSEUDOMODULES += rdcli_simple_standalone PSEUDOMODULES += saul_adc PSEUDOMODULES += saul_default diff --git a/sys/Makefile b/sys/Makefile index 77e8832dc65ddbcea6df64ba3955b7a572c163b9..9d63db742c4f4c5108e8b7cff43d02e15120a2c9 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -133,6 +133,10 @@ endif ifneq (,$(filter rdcli_simple,$(USEMODULE))) DIRS += net/application_layer/rdcli_simple endif +ifneq (,$(filter rdcli,$(USEMODULE))) + DIRS += net/application_layer/rdcli +endif + DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE)))) diff --git a/sys/include/net/rdcli.h b/sys/include/net/rdcli.h new file mode 100644 index 0000000000000000000000000000000000000000..f9b2a049f52e246c46637b0fbae174a04169c7c4 --- /dev/null +++ b/sys/include/net/rdcli.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017-2018 Freie Universität Berlin + * + * 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_rdcli CoRE RD Endpoint Library + * @ingroup net + * @brief Library for using RIOT as CoRE Resource Directory endpoint + * + * This module implements a CoRE Resource Directory endpoint library, that + * allows RIOT nodes to register themselves with resource directories. + * It implements the standard endpoint functionality as defined in + * draft-ietf-core-resource-directory-15. + * @see https://tools.ietf.org/html/draft-ietf-core-resource-directory-15 + * + * @note As the name of this library (`rdcli`) can be misleading in + * context of the RD draft (endpoint vs client), this library + * will most likely undergo a name change in the near future... + * + * # Design Decisions + * - all operations provided by this module are fully synchronous, meaning that + * the functions will block until an operation is successful or will time out + * - the implementation limits the client to be registered with a single RD at + * any point in time + * + * @{ + * + * @file + * @brief CoRE Resource Directory endpoint interface + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef NET_RDCLI_H +#define NET_RDCLI_H + +#include "net/sock/udp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Return values and error codes used by this module + */ +enum { + RDCLI_OK = 0, /**< everything went as expected */ + RDCLI_TIMEOUT = -1, /**< no response from the network */ + RDCLI_ERR = -2, /**< internal error or invalid reply */ + RDCLI_NORD = -3, /**< not connected to an RD */ + RDCLI_OVERFLOW = -4, /**< internal buffers can not handle input */ +}; + +/** + * @brief Discover the registration interface resource of a RD + * + * @param[in] remote remote endpoint of the target RD + * @param[out] regif the registration interface is written to this buffer + * @param[in] maxlen size of @p regif + * + * @return RDCLI_OK on success + * @return RDCLI_TIMEOUT if the discovery request times out + * @return RDCLI_NORD if addressed endpoint is not a RD + * @return RDCLI_ERR on any other internal error + */ +int rdcli_discover_regif(const sock_udp_ep_t *remote, + char *regif, size_t maxlen); + +/** + * @brief Initiate the node registration by sending an empty push + * + * - if registration fails (e.g. timeout), we are not associated with any RD + * anymore (even if we have been before we called rdcli_register) + * + * @note In case a multicast address is given, the @p regif parameter MUST be + * NULL. The first RD responding to the request will be chosen and all + * replies from other RD servers are ignored. + * + * @param[in] remote remote endpoint of the target RD + * @param[in] regif registration interface resource of the RD, it will be + * discovered automatically when set to NULL + * + * @return RDCLI_OK on success + * @return RDCLI_TIMEOUT on registration timeout + * @return RDCLI_NORD if addressed endpoint is not a RD + * @return RDCLI_OVERFLOW if @p regif does not fit into internal buffer + * @return RDCLI_ERR on any other internal error + */ +int rdcli_register(const sock_udp_ep_t *remote, const char *regif); + +/** + * @brief Update our current entry at the RD + * + * @return RDCLI_OK on success + * @return RDCLI_TIMEOUT if the update request times out + * @return RDCLI_ERR on any other internal error + */ +int rdcli_update(void); + +/** + * @brief Unregister from a given RD server + * + * @return RDCLI_OK on success + * @return RDCLI_TIMEOUT if the remove request times out + * @return RDCLI_ERR on any other internal error + */ +int rdcli_remove(void); + +/** + * @brief Dump the current RD connection status to STDIO (for debugging) + */ +void rdcli_dump_status(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_RDCLI_H */ +/** @} */ diff --git a/sys/include/net/rdcli_standalone.h b/sys/include/net/rdcli_standalone.h new file mode 100644 index 0000000000000000000000000000000000000000..a76041b882c0de1a0f57dc1fd50a241618aceb6e --- /dev/null +++ b/sys/include/net/rdcli_standalone.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017-2018 Freie Universität Berlin + * + * 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_rdcli_standalone CoRE RD Standalone Extension + * @ingroup net_rdcli + * @brief Run CoRE Resource Directory client in standalone configuration + * + * This sub-module enables the CoRE RD client to manage is registration state + * with a server autonomously by periodically running the update procedure. This + * is implemented by running a dedicated thread. + * + * @{ + * + * @file + * @brief CoRE Resource Directory client standalone extension + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef NET_RDCLI_STANDALONE_H +#define NET_RDCLI_STANDALONE_H + +#include "net/sock/udp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Possible types of events triggered by the standalone rdcli module + */ +typedef enum { + RDCLI_REGISTERED, + RDCLI_DEREGISTERED, + RDCLI_UPDATED, +} rdcli_standalone_event_t; + +/** + * @brief Callback function signature for RD client state synchronization + * + * The registered callback function is executed in the context of the dedicated + * standalone RD client's thread. + * + * @param[in] t type of event + */ +typedef void(*rdcli_standalone_cb_t)(rdcli_standalone_event_t event); + +/** + * @brief Spawn a new thread that takes care of sending periodic updates to an + * active RD entry + * + * @note This function must only be called once (typically during system + * initialization) + */ +void rdcli_standalone_run(void); + +/** + * @brief Register a callback to be notified about RD client state changes + * + * Only a single callback can be active at any point in time, so setting a new + * callback will override the existing one. + * + * @pre @p cb != NULL + * + * @param[in] cb callback to execute on RD client state changes + */ +void rdcli_standalone_reg_cb(rdcli_standalone_cb_t cb); + +/** + * @brief Signal the rdcli thread about connection status change + * + * @note This function should not be called by a user, but it is called from + * withing the rdcli implementation + * + * @param[in] connected set to true if we are connected to a RD + */ +void rdcli_standalone_signal(bool connected); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_RDCLI_STANDALONE_H */ +/** @} */ diff --git a/sys/net/application_layer/rdcli/Makefile b/sys/net/application_layer/rdcli/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3b2e7e88b2c7863e573a6540af83ffafe93aedcb --- /dev/null +++ b/sys/net/application_layer/rdcli/Makefile @@ -0,0 +1,7 @@ +SRC = rdcli.c + +ifneq (,$(filter rdcli_standalone,$(USEMODULE))) + SRC += rdcli_standalone.c +endif + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/rdcli/rdcli.c b/sys/net/application_layer/rdcli/rdcli.c new file mode 100644 index 0000000000000000000000000000000000000000..0742ebcc1c7e56d02a500f78830e920060a1fb8e --- /dev/null +++ b/sys/net/application_layer/rdcli/rdcli.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2017-2018 Freie Universität Berlin + * + * 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_rdcli + * @{ + * + * @file + * @brief CoRE Resource Directory client implementation + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <string.h> + +#include "fmt.h" +#include "mutex.h" +#include "thread_flags.h" + +#include "net/gcoap.h" +#include "net/ipv6/addr.h" +#include "net/rdcli.h" +#include "net/rdcli_common.h" +#include "net/rdcli_config.h" + +#ifdef MODULE_RDCLI_STANDALONE +#include "net/rdcli_standalone.h" +#endif + +#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_MASK (0x000f) + +#define BUFSIZE (512U) + +static char *_regif_buf; +static size_t _regif_buf_len; + +static char _rd_loc[NANOCOAP_URI_MAX]; +static char _rd_regif[NANOCOAP_URI_MAX]; +static sock_udp_ep_t _rd_remote; + +static mutex_t _mutex = MUTEX_INIT; +static volatile thread_t *_waiter; + +static uint8_t buf[BUFSIZE]; + +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 RDCLI_ERR; + } + else if (flags & FLAG_TIMEOUT) { + return RDCLI_TIMEOUT; + } + else if (flags & FLAG_OVERFLOW) { + return RDCLI_OVERFLOW; + } + else { + return RDCLI_OK; + } +} + +static void _on_register(unsigned req_state, coap_pkt_t* pdu, + sock_udp_ep_t *remote) +{ + thread_flags_t flag = FLAG_ERR; + + if ((req_state == GCOAP_MEMO_RESP) && + (pdu->hdr->code == COAP_CODE_CREATED)) { + /* read the location header and save the RD details on success */ + if (coap_get_location_path(pdu, (uint8_t *)_rd_loc, + sizeof(_rd_loc)) > 0) { + memcpy(&_rd_remote, remote, sizeof(_rd_remote)); + flag = FLAG_SUCCESS; + } + else { + /* reset RD entry */ + flag = FLAG_OVERFLOW; + } + } + else if (req_state == GCOAP_MEMO_TIMEOUT) { + flag = FLAG_TIMEOUT; + } + + thread_flags_set((thread_t *)_waiter, flag); +} + +static void _on_update_remove(unsigned req_state, coap_pkt_t *pdu, uint8_t code) +{ + thread_flags_t flag = FLAG_ERR; + + if ((req_state == GCOAP_MEMO_RESP) && (pdu->hdr->code == code)) { + flag = FLAG_SUCCESS; + } + else if (req_state == GCOAP_MEMO_TIMEOUT) { + flag = FLAG_TIMEOUT; + } + + thread_flags_set((thread_t *)_waiter, flag); +} + +static void _on_update(unsigned req_state, coap_pkt_t *pdu, sock_udp_ep_t *remote) +{ + (void)remote; + _on_update_remove(req_state, pdu, COAP_CODE_CHANGED); +} + +static void _on_remove(unsigned req_state, coap_pkt_t *pdu, sock_udp_ep_t *remote) +{ + (void)remote; + _on_update_remove(req_state, pdu, COAP_CODE_DELETED); +} + +static int _update_remove(unsigned code, gcoap_resp_handler_t handle) +{ + coap_pkt_t pkt; + + if (_rd_loc[0] == 0) { + return RDCLI_NORD; + } + + /* build CoAP request packet */ + int res = gcoap_req_init(&pkt, buf, sizeof(buf), code, _rd_loc); + if (res < 0) { + return RDCLI_ERR; + } + coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + ssize_t pkt_len = gcoap_finish(&pkt, 0, COAP_FORMAT_NONE); + + /* send request */ + gcoap_req_send2(buf, pkt_len, &_rd_remote, handle); + + /* synchronize response */ + return _sync(); +} + +static void _on_discover(unsigned req_state, coap_pkt_t *pdu, + sock_udp_ep_t *remote) +{ + thread_flags_t flag = RDCLI_NORD; + (void)remote; + + if (req_state == GCOAP_MEMO_RESP) { + unsigned ct = coap_get_content_type(pdu); + if (ct != COAP_FORMAT_LINK) { + goto end; + } + if (pdu->payload_len == 0) { + goto end; + } + /* do simplified parsing of registration interface location */ + char *start = (char *)pdu->payload; + char *end; + char *limit = (char *)(pdu->payload + pdu->payload_len); + while ((*start != '<') && (start < limit)) { + start++; + } + if (*start != '<') { + goto end; + } + end = ++start; + while ((*end != '>') && (end < limit)) { + end++; + } + if (*end != '>') { + goto end; + } + /* TODO: verify size of interface resource identifier */ + size_t uri_len = (size_t)(end - start); + if (uri_len >= _regif_buf_len) { + goto end; + } + memcpy(_regif_buf, start, uri_len); + memset((_regif_buf + uri_len), 0, (_regif_buf_len - uri_len)); + flag = FLAG_SUCCESS; + } + else if (req_state == GCOAP_MEMO_TIMEOUT) { + flag = FLAG_TIMEOUT; + } + +end: + thread_flags_set((thread_t *)_waiter, flag); +} + +static int _discover_internal(const sock_udp_ep_t *remote, + char *regif, size_t maxlen) +{ + coap_pkt_t pkt; + + /* save pointer to result buffer */ + _regif_buf = regif; + _regif_buf_len = maxlen; + + /* do URI discovery for the registration interface */ + int res = gcoap_req_init(&pkt, buf, sizeof(buf), COAP_METHOD_GET, + "/.well-known/core"); + if (res < 0) { + return RDCLI_ERR; + } + coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + gcoap_add_qstring(&pkt, "rt", "core.rd"); + size_t pkt_len = gcoap_finish(&pkt, 0, COAP_FORMAT_NONE); + res = gcoap_req_send2(buf, pkt_len, remote, _on_discover); + if (res < 0) { + return RDCLI_ERR; + } + return _sync(); +} + +int rdcli_discover_regif(const sock_udp_ep_t *remote, char *regif, size_t maxlen) +{ + assert(remote && regif); + + _lock(); + int res = _discover_internal(remote, regif, maxlen); + mutex_unlock(&_mutex); + return res; +} + +int rdcli_register(const sock_udp_ep_t *remote, const char *regif) +{ + assert(remote); + + int res; + ssize_t pkt_len; + int retval; + coap_pkt_t pkt; + + _lock(); + + /* if no registration interface is given, we will need to trigger a URI + * discovery for it first (see section 5.2) */ + if (regif == NULL) { + retval = _discover_internal(remote, _rd_regif, sizeof(_rd_regif)); + if (retval != RDCLI_OK) { + goto end; + } + } + else { + if (strlen(_rd_regif) >= sizeof(_rd_regif)) { + retval = RDCLI_OVERFLOW; + goto end; + } + strncpy(_rd_regif, regif, sizeof(_rd_regif)); + } + + /* build and send CoAP POST request to the RD's registration interface */ + res = gcoap_req_init(&pkt, buf, sizeof(buf), COAP_METHOD_POST, _rd_regif); + if (res < 0) { + retval = RDCLI_ERR; + goto end; + } + /* set some packet options and write query string */ + coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + rdcli_common_add_qstring(&pkt); + + /* add the resource description as payload */ + res = gcoap_get_resource_list(pkt.payload, pkt.payload_len, + COAP_FORMAT_LINK); + if (res < 0) { + retval = RDCLI_ERR; + goto end; + } + + /* finish up the packet */ + pkt_len = gcoap_finish(&pkt, res, COAP_FORMAT_LINK); + + /* send out the request */ + res = gcoap_req_send2(buf, pkt_len, remote, _on_register); + if (res < 0) { + retval = RDCLI_ERR; + goto end; + } + retval = _sync(); + +end: + /* if we encountered any error, we mark the client as not connected */ + if (retval != RDCLI_OK) { + _rd_loc[0] = '\0'; + } +#ifdef MODULE_RDCLI_STANDALONE + else { + rdcli_standalone_signal(true); + } +#endif + + mutex_unlock(&_mutex); + return retval; +} + +int rdcli_update(void) +{ + _lock(); + int res = _update_remove(COAP_METHOD_POST, _on_update); + if (res != RDCLI_OK) { + /* in case we are not able to reach the RD, we drop the association */ +#ifdef MODULE_RDCLI_STANDALONE + rdcli_standalone_signal(false); +#endif + _rd_loc[0] = '\0'; + } + mutex_unlock(&_mutex); + return res; +} + +int rdcli_remove(void) +{ + _lock(); + if (_rd_loc[0] == '\0') { + mutex_unlock(&_mutex); + return RDCLI_NORD; + } +#ifdef MODULE_RDCLI_STANDALONE + rdcli_standalone_signal(false); +#endif + _update_remove(COAP_METHOD_DELETE, _on_remove); + /* we actually do not care about the result, we drop the RD local RD entry + * in any case */ + _rd_loc[0] = '\0'; + mutex_unlock(&_mutex); + return RDCLI_OK; +} + +void rdcli_dump_status(void) +{ + puts("CoAP RD connection status:"); + + if (_rd_loc[0] == 0) { + puts(" --- not registered with any RD ---"); + } + else { + /* get address string */ + char addr[IPV6_ADDR_MAX_STR_LEN]; + ipv6_addr_to_str(addr, (ipv6_addr_t *)&_rd_remote.addr, sizeof(addr)); + + printf("RD address: coap://[%s]:%i\n", addr, (int)_rd_remote.port); + printf(" ep name: %s\n", rdcli_common_get_ep()); + printf(" lifetime: %is\n", (int)RDCLI_LT); + printf(" reg if: %s\n", _rd_regif); + printf(" location: %s\n", _rd_loc); + } +} diff --git a/sys/net/application_layer/rdcli/rdcli_standalone.c b/sys/net/application_layer/rdcli/rdcli_standalone.c new file mode 100644 index 0000000000000000000000000000000000000000..73026d90c9794716e231cd96a594879a44f172a0 --- /dev/null +++ b/sys/net/application_layer/rdcli/rdcli_standalone.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017-2018 Freie Universität Berlin + * + * 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_rdcli_simple + * @{ + * + * @file + * @brief Standalone extension for the simple RD registration client + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <stdint.h> + +#include "log.h" +#include "assert.h" +#include "thread.h" +#include "xtimer.h" +#include "net/rdcli.h" +#include "net/rdcli_config.h" +#include "net/rdcli_standalone.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* stack configuration */ +#define STACKSIZE (THREAD_STACKSIZE_DEFAULT) +#define PRIO (THREAD_PRIORITY_MAIN - 1) +#define TNAME "rdcli" + +#define UPDATE_TIMEOUT (0xe537) + +#define TIMEOUT_US ((uint64_t)(RDCLI_UPDATE_INTERVAL * US_PER_SEC)) + +static char _stack[STACKSIZE]; + +static xtimer_t _timer; +static kernel_pid_t _runner_pid; +static msg_t _msg; + +static rdcli_standalone_cb_t _cb = NULL; + +static void _set_timer(void) +{ + xtimer_set_msg64(&_timer, TIMEOUT_US, &_msg, _runner_pid); +} + +static void _notify(rdcli_standalone_event_t event) +{ + if (_cb) { + _cb(event); + } +} + +static void *_reg_runner(void *arg) +{ + (void)arg; + msg_t in; + + /* prepare context and message */ + _runner_pid = thread_getpid(); + _msg.type = UPDATE_TIMEOUT; + + while (1) { + DEBUG("rd stand: waiting for message\n"); + msg_receive(&in); + if (in.type == UPDATE_TIMEOUT) { + if (rdcli_update() == RDCLI_OK) { + DEBUG("rd stand: update ok\n"); + _set_timer(); + _notify(RDCLI_UPDATED); + } + else { + _notify(RDCLI_DEREGISTERED); + } + } + } + + return NULL; /* should never be reached */ +} + +void rdcli_standalone_run(void) +{ + thread_create(_stack, sizeof(_stack), PRIO, 0, _reg_runner, NULL, TNAME); +} + +void rdcli_standalone_signal(bool connected) +{ + /* clear timer in any case */ + xtimer_remove(&_timer); + /* reset the update timer in case a connection was established or updated */ + if (connected) { + _set_timer(); + _notify(RDCLI_REGISTERED); + } else { + _notify(RDCLI_DEREGISTERED); + } +} + +void rdcli_standalone_reg_cb(rdcli_standalone_cb_t cb) +{ + /* Note: we do not allow re-setting the callback (via passing cb := NULL), + * as this would mean additional complexity for synchronizing the + * value of `_cb` to prevent concurrency issues... */ + assert(cb); + _cb = cb; +}