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/examples/rdcli/Makefile b/examples/rdcli/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..cf1007f7ecaae243354cb6609d289958d8a5db32 --- /dev/null +++ b/examples/rdcli/Makefile @@ -0,0 +1,40 @@ +# name of your application +APPLICATION = rdcli + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-mega2560 arduino-uno \ + chronos hifive1 mega-xplained msb-430 msb-430h \ + nucleo-f030r8 nucleo-l053r8 nucleo-f031k6 \ + nucleo-f042k6 nucleo-f303k8 nucleo-f334r8 \ + nucleo-l031k6 stm32f0discovery telosb waspmote-pro \ + wsn430-v1_3b wsn430-v1_4 z1 + +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_ipv6_default +USEMODULE += gnrc_icmpv6_echo + +USEMODULE += rdcli_standalone + +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps +USEMODULE += fmt + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP + +# For debugging and demonstration purposes, we limit the lifetime to 60s +CFLAGS += -DRDCLI_LT=60 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/rdcli/README.md b/examples/rdcli/README.md new file mode 100644 index 0000000000000000000000000000000000000000..315e6087e6488a836545c084e2302c282336f790 --- /dev/null +++ b/examples/rdcli/README.md @@ -0,0 +1,20 @@ +CoRE Resource Directory Client Example +====================================== +This example application demonstrates the usage of RIOT's Resource Directory +(RD) client module, called `rdcli`. This module supports the registration, +update, and removal processes as defined in the +[Resource Directory Draft](https://tools.ietf.org/html/draft-ietf-core-resource-directory-14). + +Usage +===== +The examples includes a shell command that you can use to interact with a given +RD server, called `rdcli`. Simply use that shell command without parameters for +more information on its usage. + +Some connection parameters are configured statically during compile time, +namely the lifetime (`RDCLI_LT`) and the node's endpoint name (`RDCLI_EP`). You +can change these values during command line by overriding these values in the +application's Makefile, e.g. add +``` +CFLAGS += "-DRDCLI_EP=\"MyNewEpName\"" +``` diff --git a/examples/rdcli/main.c b/examples/rdcli/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f60f794a96fcd7eec2bc3628616258480b807dab --- /dev/null +++ b/examples/rdcli/main.c @@ -0,0 +1,108 @@ +/* + * 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 examples + * @{ + * + * @file + * @brief CoRE Resource Directory client (rdcli) example application + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <stdio.h> + +#include "fmt.h" +#include "shell.h" +#include "net/ipv6/addr.h" +#include "net/gcoap.h" +#include "net/rdcli_common.h" +#include "net/rdcli_standalone.h" + +#define MAIN_QUEUE_SIZE (8) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +/* we will use a custom event handler for dumping rdcli_standalone events */ +static void _on_rdcli_event(rdcli_standalone_event_t event) +{ + switch (event) { + case RDCLI_REGISTERED: + puts("rdcli event: now registered with a RD"); + break; + case RDCLI_DEREGISTERED: + puts("rdcli event: dropped client registration"); + break; + case RDCLI_UPDATED: + puts("rdcli event: successfully updated client registration"); + break; + } +} + +/* define some dummy CoAP resources */ +static ssize_t _handler_dummy(coap_pkt_t *pdu, + uint8_t *buf, size_t len, void *ctx) +{ + (void)ctx; + + /* get random data */ + int16_t val = 23; + + gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); + size_t plen = fmt_s16_dec((char *)pdu->payload, val); + return gcoap_finish(pdu, plen, COAP_FORMAT_TEXT); +} + +static ssize_t _handler_info(coap_pkt_t *pdu, + uint8_t *buf, size_t len, void *ctx) +{ + (void)ctx; + + gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT); + size_t slen = sizeof("SOME NODE INFOMRATION"); + memcpy(pdu->payload, "SOME NODE INFOMRATION", slen); + return gcoap_finish(pdu, slen, COAP_FORMAT_TEXT); +} + +static const coap_resource_t _resources[] = { + { "/node/info", COAP_GET, _handler_info, NULL }, + { "/sense/hum", COAP_GET, _handler_dummy, NULL }, + { "/sense/temp", COAP_GET, _handler_dummy, NULL } +}; + +static gcoap_listener_t _listener = { + .resources = (coap_resource_t *)&_resources[0], + .resources_len = sizeof(_resources) / sizeof(_resources[0]), + .next = NULL +}; + +int main(void) +{ + /* we need a message queue for the thread running the shell in order to + * receive potentially fast incoming networking packets */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + + puts("CoRE RD client example!\n"); + + /* setup CoAP resources */ + gcoap_register_listener(&_listener); + + /* register event callback with rdcli_standalone */ + rdcli_standalone_reg_cb(_on_rdcli_event); + + puts("Client information:"); + printf(" ep: %s\n", rdcli_common_get_ep()); + printf(" lt: %is\n", (int)RDCLI_LT); + + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +} 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/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 9abb26f47bbc1eef5ae48f31f0649a74ecca6b96..755f5f0ec1e4b0c37f8ae7711d80cfeebd181981 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -163,6 +163,11 @@ void auto_init(void) extern void rdcli_common_init(void); rdcli_common_init(); #endif +#ifdef MODULE_RDCLI_STANDALONE + DEBUG("Auto init rdcli_standalone\n"); + extern void rdcli_standalone_run(void); + rdcli_standalone_run(); +#endif #ifdef MODULE_RDCLI_SIMPLE_STANDALONE DEBUG("Auto init rdcli_simple module\n"); extern void rdcli_simple_run(void); 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_config.h b/sys/include/net/rdcli_config.h index b63638cd1d074f1d545d245a7cc80b7c6f6a8b36..078261859b0c02bab53a2e7a11ad701ebadf53ae 100644 --- a/sys/include/net/rdcli_config.h +++ b/sys/include/net/rdcli_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Freie Universität Berlin + * 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 @@ -13,7 +13,7 @@ * @{ * * @file - * @brief + * @brief CoRE RD Client static configuration default values * * @author Hauke Petersen <hauke.petersen@fu-berlin.de> */ @@ -42,10 +42,10 @@ extern "C" { #endif /** - * @brief Default client update interval (default is half the lifetime) + * @brief Default client update interval (default is 3/4 the lifetime) */ #ifndef RDCLI_UPDATE_INTERVAL -#define RDCLI_UPDATE_INTERVAL (RDCLI_LT / 2) +#define RDCLI_UPDATE_INTERVAL ((RDCLI_LT / 4) * 3) #endif /** 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; +} diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index 5d1908f2d13e9c4d903f37aab68495b514688e7d..bcffa1c43e50463c236f5b429a336352ce006153 100644 --- a/sys/shell/commands/Makefile +++ b/sys/shell/commands/Makefile @@ -66,6 +66,9 @@ endif ifneq (,$(filter conn_can,$(USEMODULE))) SRC += sc_can.c endif +ifneq (,$(filter rdcli,$(USEMODULE))) + SRC += sc_rdcli.c +endif ifneq (,$(filter periph_rtc,$(FEATURES_PROVIDED))) SRC += sc_rtc.c diff --git a/sys/shell/commands/sc_rdcli.c b/sys/shell/commands/sc_rdcli.c new file mode 100644 index 0000000000000000000000000000000000000000..c67b79016dfcb253b883cb1824981d6bbb563d56 --- /dev/null +++ b/sys/shell/commands/sc_rdcli.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017 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 sys_shell_commands + * @{ + * + * @file + * @brief Shell commands for the rdcli module + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "net/rdcli.h" +#include "net/nanocoap.h" +#include "net/sock/util.h" +#include "net/rdcli_config.h" +#include "net/rdcli_common.h" + +static int make_sock_ep(sock_udp_ep_t *ep, const char *addr) +{ + ep->port = 0; + if (sock_udp_str2ep(ep, addr) < 0) { + return -1; + } + ep->family = AF_INET6; + ep->netif = SOCK_ADDR_ANY_NETIF; + if (ep->port == 0) { + ep->port = RDCLI_SERVER_PORT; + } + return 0; +} + +int _rdcli_handler(int argc, char **argv) +{ + int res; + + if ((argc > 1) && (strcmp(argv[1], "register") == 0)) { + char *regif = NULL; + if (argc < 3) { + printf("usage: %s register <server address> [registration interface]\n", + argv[0]); + return 1; + } + sock_udp_ep_t remote; + if (make_sock_ep(&remote, argv[2]) < 0) { + printf("error: unable to parse address\n"); + return 1; + } + if (argc > 3) { + regif = argv[3]; + } + puts("Registering with RD now, this may take a short while..."); + if (rdcli_register(&remote, regif) != RDCLI_OK) { + puts("error: registration failed"); + } + else { + puts("registration successful\n"); + rdcli_dump_status(); + } + } + else if ((argc > 1) && (strcmp(argv[1], "discover") == 0)) { + if (argc < 3) { + printf("usage: %s discover <server address>\n", argv[0]); + return 1; + } + char regif[NANOCOAP_URI_MAX]; + sock_udp_ep_t remote; + if (make_sock_ep(&remote, argv[2]) < 0) { + printf("error: unable to parse address\n"); + return 1; + } + if (rdcli_discover_regif(&remote, regif, sizeof(regif)) == RDCLI_OK) { + printf("the registration interface is '%s'\n", regif); + } + else { + printf("error: unable to discover registration interface\n"); + } + } + else if ((argc > 1) && (strcmp(argv[1], "update") == 0)) { + res = rdcli_update(); + if (res == RDCLI_OK) { + puts("RD update successful"); + } + else if (res == RDCLI_NORD) { + puts("error: not associated with any RD"); + } + else if (res == RDCLI_TIMEOUT) { + puts("error: unable to reach RD - dropped association"); + } + else { + puts("error: RD update failed"); + } + } + else if ((argc > 1) && (strcmp(argv[1], "remove") == 0)) { + res = rdcli_remove(); + if (res == RDCLI_OK) { + puts("node successfully removed from RD"); + } + else if (res == RDCLI_NORD) { + puts("error: not associated with any RD"); + } + else if (res == RDCLI_TIMEOUT) { + puts("error: unable to reach RD - remove association only locally"); + } + else { + puts("error: unable to remove node from RD"); + } + } + else if ((argc > 1) && (strcmp(argv[1], "info") == 0)) { + rdcli_dump_status(); + } + else { + printf("usage: %s <register|discover|update|remove|info>\n", + argv[0]); + return 1; + } + + return 0; +} diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c index a9871517d3e64da7b5cdbd5642db37827d1f3e27..673ef75eb400870a4890a476a43ed4f133b4c2ed 100644 --- a/sys/shell/commands/shell_commands.c +++ b/sys/shell/commands/shell_commands.c @@ -142,6 +142,10 @@ extern int _ls_handler(int argc, char **argv); extern int _can_handler(int argc, char **argv); #endif +#ifdef MODULE_RDCLI +extern int _rdcli_handler(int argc, char **argv); +#endif + const shell_command_t _shell_command_list[] = { {"reboot", "Reboot the node", _reboot_handler}, #ifdef MODULE_CONFIG @@ -232,6 +236,9 @@ const shell_command_t _shell_command_list[] = { #endif #ifdef MODULE_CONN_CAN {"can", "CAN commands", _can_handler}, +#endif +#ifdef MODULE_RDCLI + {"rdcli", "CoAP RD client commands", _rdcli_handler }, #endif {NULL, NULL, NULL} };