diff --git a/examples/libcoap-client/Kconfig b/examples/libcoap-client/Kconfig new file mode 100644 index 0000000000000..9c5ed2c3790bf --- /dev/null +++ b/examples/libcoap-client/Kconfig @@ -0,0 +1,13 @@ +if USEMODULE_LIBCOAP +config LIBCOAP_CLIENT_URI + string "CoAP URI to connect to" + default "coap://[fe80::405:5aff:fe15:9b7f]/.well-known/core" +config LIBCOAP_USE_PSK + string "Secret to use for PSK communications" + default "secretPSK" + depends on USEMODULE_TINYDTLS +config LIBCOAP_USE_PSK_ID + string "User ID to use for PSK communications" + default "user_abc" + depends on USEMODULE_TINYDTLS +endif # USEMODULE_LIBCOAP diff --git a/examples/libcoap-client/Makefile b/examples/libcoap-client/Makefile new file mode 100644 index 0000000000000..813a521c12bec --- /dev/null +++ b/examples/libcoap-client/Makefile @@ -0,0 +1,67 @@ +# name of your application +APPLICATION = libcoap-client + +# 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)/../.. + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += netdev_default +USEMODULE += auto_init_gnrc_netif + +# Activate ICMPv6 error messages +USEMODULE += gnrc_icmpv6_error + +# Specify the mandatory networking module for a IPv6 routing node +USEMODULE += gnrc_ipv6_router_default + +# Add a routing protocol +USEMODULE += gnrc_rpl +USEMODULE += auto_init_gnrc_rpl + +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo + +# Specify the mandatory networking modules for IPv6 and UDP +USEMODULE += gnrc_ipv6_default +USEMODULE += memarray +USEMODULE += ipv4_addr + +# a cryptographically secure implementation of PRNG is needed for tinydtls +# Uncomment the following 3 lines for tinydtls support +CFLAGS += -DWITH_RIOT_SOCK +USEPKG += tinydtls +USEMODULE += prng_sha1prng + +# libcoap support +USEPKG += libcoap + +# Configure if DNS is required +# USEMODULE += sock_dns + +# Support 64 bit ticks +USEMODULE += ztimer64_xtimer_compat + +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_cmds_default +USEMODULE += ps +USEMODULE += netstats_l2 +USEMODULE += netstats_ipv6 +USEMODULE += netstats_rpl + +# libcoap needs some space +CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(3*THREAD_STACKSIZE_DEFAULT\) + +# 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: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +# QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/libcoap-client/Makefile.ci b/examples/libcoap-client/Makefile.ci new file mode 100644 index 0000000000000..9e746742194b9 --- /dev/null +++ b/examples/libcoap-client/Makefile.ci @@ -0,0 +1,70 @@ +BOARD_INSUFFICIENT_MEMORY := \ + airfy-beacon \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega256rfr2-xpro \ + atmega328p \ + atmega328p-xplained-mini \ + atmega8 \ + atxmega-a3bu-xplained \ + b-l072z-lrwan1 \ + blackpill-stm32f103c8 \ + blackpill-stm32f103cb \ + bluepill-stm32f030c8 \ + bluepill-stm32f103c8 \ + bluepill-stm32f103cb \ + calliope-mini \ + cc2650-launchpad \ + cc2650stk \ + derfmega128 \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + im880b \ + lsn50 \ + maple-mini \ + microbit \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nrf51dk \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f103rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-l073rz \ + olimex-msp430-h1611 \ + olimex-msp430-h2618 \ + opencm904 \ + samd10-xmini \ + saml10-xpro \ + saml11-xpro \ + samr21-xpro \ + slstk3400a \ + spark-core \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32f7508-dk \ + stm32g0316-disco \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + telosb \ + waspmote-pro \ + yunjia-nrf51822 \ + z1 \ + zigduino \ + # diff --git a/examples/libcoap-client/README.md b/examples/libcoap-client/README.md new file mode 100644 index 0000000000000..eaa7456ed4e4b --- /dev/null +++ b/examples/libcoap-client/README.md @@ -0,0 +1,24 @@ +# libcoap client example + +This example shows how to configure a client to use libcoap + +## Fast configuration (Between RIOT instances): + +Preparing the logical interfaces: + + sudo ./../../dist/tools/tapsetup/tapsetup --create 2 + +## Client invocation +For the client: + + PORT=tap0 make term + coapc coap://[ip6-address]/some/path + +The IP address to connect to needs to be that as returned by libcoap_server, +or that of the tap0 interface, etc. + +## Handling the static memory allocation + +libcoap for RIOT is using the `sys/memarray` module and therefore there +are certain limits. Said resources are defined in `libcoap/src/coap_mem.c`, +but can be overwritten at compile time. diff --git a/examples/libcoap-client/app.config b/examples/libcoap-client/app.config new file mode 100644 index 0000000000000..a76e237c501d5 --- /dev/null +++ b/examples/libcoap-client/app.config @@ -0,0 +1,10 @@ +CONFIG_LIBCOAP_CLIENT_SUPPORT=y +CONFIG_LIBCOAP_CLIENT_URI="coap://[fe80::405:5aff:fe15:9b7f]/.well-known/core" + +CONFIG_LIBCOAP_USE_PSK="secretPSK" +CONFIG_LIBCOAP_USE_PSK_ID="user_abc" + +CONFIG_KCONFIG_USEPKG_LIBCOAP=y +# Logging levels are defined in pkg/libcoap using Kconfig + +CONFIG_KCONFIG_USEPKG_TINYDTLS=y diff --git a/examples/libcoap-client/client-coap.c b/examples/libcoap-client/client-coap.c new file mode 100644 index 0000000000000..f6b8b456832a8 --- /dev/null +++ b/examples/libcoap-client/client-coap.c @@ -0,0 +1,260 @@ +/* + * client-coap.c -- RIOT client example + * + * Copyright (C) 2023-2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#include "coap_config.h" +#include +#include +#include +#include +#include "client-coap.h" +#include "macros/utils.h" +#include "net/utils.h" +#include +#include +#include + +#ifdef CONFIG_LIBCOAP_CLIENT_URI +#define COAP_CLIENT_URI CONFIG_LIBCOAP_CLIENT_URI +#else /* ! CONFIG_LIBCOAP_CLIENT_URI */ +#define COAP_CLIENT_URI "coap://[fe80::405:5aff:fe15:9b7f]/.well-known/core" +#endif /* ! CONFIG_LIBCOAP_CLIENT_URI */ + +#ifdef CONFIG_LIBCOAP_USE_PSK +#define COAP_USE_PSK CONFIG_LIBCOAP_USE_PSK +#else /* ! CONFIG_LIBCOAP_USE_PSK */ +#define COAP_USE_PSK NULL +#endif /* ! CONFIG_LIBCOAP_USE_PSK */ + +#ifdef CONFIG_LIBCOAP_USE_PSK_ID +#define COAP_USE_PSK_ID CONFIG_LIBCOAP_USE_PSK_ID +#else /* ! CONFIG_LIBCOAP_USE_PSK_ID */ +#define COAP_USE_PSK_ID NULL +#endif /* ! CONFIG_LIBCOAP_USE_PSK_ID */ + +static coap_context_t *main_coap_context = NULL; +static coap_optlist_t *optlist = NULL; + +static int quit = 0; + +static coap_response_t +message_handler(coap_session_t *session, + const coap_pdu_t *sent, + const coap_pdu_t *received, + const coap_mid_t id) +{ + const uint8_t *data; + size_t len; + size_t offset; + size_t total; + + (void)session; + (void)sent; + (void)id; + if (coap_get_data_large(received, &len, &data, &offset, &total)) { + printf("%*.*s", (int)len, (int)len, (const char *)data); + if (len + offset == total) { + printf("\n"); + quit = 1; + } + } + return COAP_RESPONSE_OK; +} + +static void +nack_handler(coap_session_t *session COAP_UNUSED, + const coap_pdu_t *sent COAP_UNUSED, + const coap_nack_reason_t reason, + const coap_mid_t id COAP_UNUSED) +{ + + switch (reason) { + case COAP_NACK_TOO_MANY_RETRIES: + case COAP_NACK_NOT_DELIVERABLE: + case COAP_NACK_RST: + case COAP_NACK_TLS_FAILED: + case COAP_NACK_TLS_LAYER_FAILED: + case COAP_NACK_WS_LAYER_FAILED: + case COAP_NACK_WS_FAILED: + coap_log_err("cannot send CoAP pdu\n"); + quit = 1; + break; + case COAP_NACK_ICMP_ISSUE: + case COAP_NACK_BAD_RESPONSE: + default: + break; + } + return; +} + +static int +resolve_address(const char *host, const char *service, coap_address_t *dst, + int scheme_hint_bits) +{ + uint16_t port = service ? atoi(service) : 0; + int ret = 0; + coap_str_const_t str_host; + coap_addr_info_t *addr_info; + + str_host.s = (const uint8_t *)host; + str_host.length = strlen(host); + addr_info = coap_resolve_address_info(&str_host, port, port, port, port, + AF_UNSPEC, scheme_hint_bits, + COAP_RESOLVE_TYPE_REMOTE); + if (addr_info) { + ret = 1; + *dst = addr_info->addr; + } + + coap_free_address_info(addr_info); + return ret; +} + +void +client_coap_init(int argc, char **argv) +{ + coap_session_t *session = NULL; + coap_pdu_t *pdu; + coap_address_t dst; + coap_mid_t mid; + int len; + coap_uri_t uri; + char portbuf[8]; + +#define BUFSIZE 100 + unsigned char buf[BUFSIZE]; + int res; + const char *coap_uri = COAP_CLIENT_URI; + + if (argc > 1) { + coap_uri = argv[1]; + } + + /* Initialize libcoap library */ + coap_startup(); + + coap_set_log_level(COAP_MAX_LOGGING_LEVEL); + + /* Parse the URI */ + len = coap_split_uri((const unsigned char *)coap_uri, strlen(coap_uri), &uri); + if (len != 0) { + coap_log_warn("Failed to parse uri %s\n", coap_uri); + goto fail; + } + + snprintf(portbuf, sizeof(portbuf), "%d", uri.port); + snprintf((char *)buf, sizeof(buf), "%*.*s", (int)uri.host.length, + (int)uri.host.length, (const char *)uri.host.s); + /* resolve destination address where packet should be sent */ + len = resolve_address((const char *)buf, portbuf, &dst, 1 << uri.scheme); + if (len <= 0) { + coap_log_warn("Failed to resolve address %*.*s\n", (int)uri.host.length, + (int)uri.host.length, (const char *)uri.host.s); + goto fail; + } + + main_coap_context = coap_new_context(NULL); + if (!main_coap_context) { + coap_log_warn("Failed to initialize context\n"); + goto fail; + } + + coap_context_set_block_mode(main_coap_context, COAP_BLOCK_USE_LIBCOAP); + + if (uri.scheme == COAP_URI_SCHEME_COAP) { + session = coap_new_client_session(main_coap_context, NULL, &dst, + COAP_PROTO_UDP); + } + else if (uri.scheme == COAP_URI_SCHEME_COAP_TCP) { + session = coap_new_client_session(main_coap_context, NULL, &dst, + COAP_PROTO_TCP); +#if defined (COAP_USE_PSK) && defined(COAP_USE_PSK_ID) + } + else { + static coap_dtls_cpsk_t dtls_psk; + static char client_sni[256]; + + memset(client_sni, 0, sizeof(client_sni)); + memset(&dtls_psk, 0, sizeof(dtls_psk)); + dtls_psk.version = COAP_DTLS_CPSK_SETUP_VERSION; + if (uri.host.length) { + memcpy(client_sni, uri.host.s, + MIN(uri.host.length, sizeof(client_sni) - 1)); + } + else { + memcpy(client_sni, "localhost", 9); + } + dtls_psk.client_sni = client_sni; + dtls_psk.psk_info.identity.s = (const uint8_t *)COAP_USE_PSK_ID; + dtls_psk.psk_info.identity.length = strlen(COAP_USE_PSK_ID); + dtls_psk.psk_info.key.s = (const uint8_t *)COAP_USE_PSK; + dtls_psk.psk_info.key.length = strlen(COAP_USE_PSK); + + session = coap_new_client_session_psk2(main_coap_context, NULL, &dst, + COAP_PROTO_DTLS, &dtls_psk); +#else /* ! COAP_USE_PSK && ! COAP_USE_PSK_ID */ + coap_log_err("CONFIG_LIBCOAP_USE_PSK and CONFIG_LIBCOAP_USE_PSK_ID not defined\n"); + goto fail; +#endif /* ! COAP_USE_PSK && ! COAP_USE_PSK_ID */ + } + + if (!session) { + coap_log_warn("Failed to create session\n"); + goto fail; + } + + coap_register_response_handler(main_coap_context, message_handler); + coap_register_nack_handler(main_coap_context, nack_handler); + + /* construct CoAP message */ + pdu = coap_pdu_init(coap_is_mcast(&dst) ? COAP_MESSAGE_NON : COAP_MESSAGE_CON, + COAP_REQUEST_CODE_GET, + coap_new_message_id(session), + coap_session_max_pdu_size(session)); + if (!pdu) { + coap_log_warn("Failed to create PDU\n"); + goto fail; + } + + len = coap_uri_into_options(&uri, &dst, &optlist, 1, buf, sizeof(buf)); + if (len) { + coap_log_warn("Failed to create options\n"); + goto fail; + } + + /* Add option list (which will be sorted) to the PDU */ + if (optlist) { + res = coap_add_optlist_pdu(pdu, &optlist); + if (res != 1) { + coap_log_warn("Failed to add options to PDU\n"); + goto fail; + } + } + + /* and send the PDU */ + mid = coap_send(session, pdu); + if (mid == COAP_INVALID_MID) { + coap_log_warn("Failed to send PDU\n"); + goto fail; + } + while (!quit) { + coap_io_process(main_coap_context, 1000); + } +fail: + /* Clean up library usage so client can be run again */ + quit = 0; + coap_delete_optlist(optlist); + optlist = NULL; + coap_session_release(session); + session = NULL; + coap_free_context(main_coap_context); + main_coap_context = NULL; + coap_cleanup(); +} diff --git a/examples/libcoap-client/client-coap.h b/examples/libcoap-client/client-coap.h new file mode 100644 index 0000000000000..083e949a6352e --- /dev/null +++ b/examples/libcoap-client/client-coap.h @@ -0,0 +1,29 @@ +/* + * client-coap.h -- RIOT client example + * + * Copyright (C) 2023-2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef CLIENT_COAP_H +#define CLIENT_COAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "coap_config.h" +#include + +/* Start up the CoAP Client */ +void client_coap_init(int argc, char **argv); + +#ifdef __cplusplus +} +#endif + +#endif /* CLIENT_COAP_H */ diff --git a/examples/libcoap-client/main.c b/examples/libcoap-client/main.c new file mode 100644 index 0000000000000..23dacae51fb0e --- /dev/null +++ b/examples/libcoap-client/main.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * 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 Example application for libcoap client + * + * @author Raul Fuentes <> + * + * @} + */ + +#include + +#include "shell.h" +#include "msg.h" + +#include "coap3/coap.h" + +#define MAIN_QUEUE_SIZE (8) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +extern int client_coap_init(int argc, char **argv); + +static const shell_command_t shell_commands[] = { + { "coapc", "Start a libcoap client", client_coap_init }, + { NULL, NULL, 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("RIOT libcoap client testing implementation"); + + /* start shell */ + puts("All up, running the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +} diff --git a/examples/libcoap-server/Kconfig b/examples/libcoap-server/Kconfig new file mode 100644 index 0000000000000..5ade9943e73cb --- /dev/null +++ b/examples/libcoap-server/Kconfig @@ -0,0 +1,15 @@ +if USEMODULE_LIBCOAP +config LIBCOAP_USE_PSK + string "Secret to use for PSK communications" + default "secretPSK" + depends on USEMODULE_TINYDTLS +config LIBCOAP_CLIENT_SUPPORT + bool "Set to y if ongoing proxy support is required" + default n +if LIBCOAP_CLIENT_SUPPORT +config LIBCOAP_USE_PSK_ID + string "User ID to use for ongoing PSK communications" + default "user_abc" + depends on USEMODULE_TINYDTLS +endif # LIBCOAP_CLIENT_SUPPORT +endif # USEMODULE_LIBCOAP diff --git a/examples/libcoap-server/Makefile b/examples/libcoap-server/Makefile new file mode 100644 index 0000000000000..954d91deb1714 --- /dev/null +++ b/examples/libcoap-server/Makefile @@ -0,0 +1,69 @@ +# name of your application +APPLICATION = libcoap-server + +# 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)/../.. + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += netdev_default +USEMODULE += auto_init_gnrc_netif + +# Activate ICMPv6 error messages +# USEMODULE += gnrc_icmpv6_error + +# Specify the mandatory networking module for a IPv6 routing node +USEMODULE += gnrc_ipv6_router_default + +# Add a routing protocol +USEMODULE += gnrc_rpl +USEMODULE += auto_init_gnrc_rpl + +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo +USEMODULE += shell_cmd_gnrc_udp + +# Specify the mandatory networking modules for IPv6 and UDP +USEMODULE += gnrc_ipv6_default +USEMODULE += memarray +USEMODULE += ipv4_addr + +# a cryptographically secure implementation of PRNG is needed for tinydtls +# Uncomment the following 3 lines for tinydtls support +CFLAGS += -DWITH_RIOT_SOCK +USEPKG += tinydtls +USEMODULE += prng_sha1prng + +# libcoap support +USEPKG += libcoap + +USEMODULE += sock_udp +# Configure if DNS is required +USEMODULE += sock_dns + +# USEMODULE += xtimer +USEMODULE += ztimer64_xtimer_compat + +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_cmds_default +USEMODULE += ps +USEMODULE += netstats_l2 +USEMODULE += netstats_ipv6 +USEMODULE += netstats_rpl + +# libcoap needs some space +CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(3*THREAD_STACKSIZE_DEFAULT\) + +# 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: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/libcoap-server/Makefile.ci b/examples/libcoap-server/Makefile.ci new file mode 100644 index 0000000000000..9e746742194b9 --- /dev/null +++ b/examples/libcoap-server/Makefile.ci @@ -0,0 +1,70 @@ +BOARD_INSUFFICIENT_MEMORY := \ + airfy-beacon \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega256rfr2-xpro \ + atmega328p \ + atmega328p-xplained-mini \ + atmega8 \ + atxmega-a3bu-xplained \ + b-l072z-lrwan1 \ + blackpill-stm32f103c8 \ + blackpill-stm32f103cb \ + bluepill-stm32f030c8 \ + bluepill-stm32f103c8 \ + bluepill-stm32f103cb \ + calliope-mini \ + cc2650-launchpad \ + cc2650stk \ + derfmega128 \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + im880b \ + lsn50 \ + maple-mini \ + microbit \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nrf51dk \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f103rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-l073rz \ + olimex-msp430-h1611 \ + olimex-msp430-h2618 \ + opencm904 \ + samd10-xmini \ + saml10-xpro \ + saml11-xpro \ + samr21-xpro \ + slstk3400a \ + spark-core \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32f7508-dk \ + stm32g0316-disco \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + telosb \ + waspmote-pro \ + yunjia-nrf51822 \ + z1 \ + zigduino \ + # diff --git a/examples/libcoap-server/README.md b/examples/libcoap-server/README.md new file mode 100644 index 0000000000000..ed6458c608629 --- /dev/null +++ b/examples/libcoap-server/README.md @@ -0,0 +1,25 @@ +# libcoap server example + +This example shows how to configure a server to use libcoap + +## Fast configuration (Between RIOT instances): + +Preparing the logical interfaces: + + sudo ./../../dist/tools/tapsetup/tapsetup --create 2 + +## Server invocation +For the server: + + PORT=tap0 make term + coaps {stop|start} + +The server supports requests for + coap://[ip]/time?ticks + coap://[ip]/.well-known/core + +## Handling the static memory allocation + +libcoap for RIOT is using the `sys/memarray` module and therefore there +are certain limits. Said resources are defined in `libcoap/src/coap_mem.c`, +but can be overwritten at compile time. diff --git a/examples/libcoap-server/app.config b/examples/libcoap-server/app.config new file mode 100644 index 0000000000000..66fe9bd31c960 --- /dev/null +++ b/examples/libcoap-server/app.config @@ -0,0 +1,10 @@ +CONFIG_LIBCOAP_SERVER_SUPPORT=y + +CONFIG_LIBCOAP_USE_PSK="secretPSK" + +CONFIG_KCONFIG_USEPKG_LIBCOAP=y +# Logging levels are defined in pkg/libcoap using Kconfig + +CONFIG_KCONFIG_USEPKG_TINYDTLS=y +CONFIG_DTLS_PEER_MAX=2 +CONFIG_DTLS_HANDSHAKE_MAX=2 diff --git a/examples/libcoap-server/main.c b/examples/libcoap-server/main.c new file mode 100644 index 0000000000000..a9db1ffc60e31 --- /dev/null +++ b/examples/libcoap-server/main.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * 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 Example application for libcoap server + * + * @author Raul Fuentes <> + * + * @} + */ + +#include + +#include "shell.h" +#include "msg.h" + +#include "coap3/coap.h" + +#define MAIN_QUEUE_SIZE (8) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +extern int server_coap_init(int argc, char **argv); + +static const shell_command_t shell_commands[] = { + { "coaps", "Start a libcoap server", server_coap_init }, + { NULL, NULL, 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("RIOT libcoap server testing implementation"); + + /* start shell */ + puts("All up, running the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +} diff --git a/examples/libcoap-server/server-coap.c b/examples/libcoap-server/server-coap.c new file mode 100644 index 0000000000000..03902638b1f95 --- /dev/null +++ b/examples/libcoap-server/server-coap.c @@ -0,0 +1,303 @@ +/* + * server-coap.c -- RIOT example + * + * Copyright (C) 2023-2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#include "coap_config.h" +#include +#include "server-coap.h" +#include +#include "macros/utils.h" + +#ifdef CONFIG_LIBCOAP_USE_PSK +#define COAP_USE_PSK CONFIG_LIBCOAP_USE_PSK +#else /* CONFIG_LIBCOAP_USE_PSK */ +#define COAP_USE_PSK NULL +#endif /* CONFIG_LIBCOAP_USE_PSK */ + +static volatile int running = 0; +static int quit; + +coap_context_t *main_coap_context; + +static coap_time_t clock_offset; +/* changeable clock base (see handle_put_time()) */ +static coap_time_t my_clock_base = 0; +static coap_resource_t *time_resource = NULL; /* just for testing */ + +static void +hnd_get_time(coap_resource_t *resource, coap_session_t *session, + const coap_pdu_t *request, const coap_string_t *query, + coap_pdu_t *response) +{ + unsigned char buf[40]; + size_t len; + coap_tick_t now; + coap_tick_t t; + + (void)resource; + (void)session; + (void)request; + /* FIXME: return time, e.g. in human-readable by default and ticks + * when query ?ticks is given. */ + + /* if my_clock_base was deleted, we pretend to have no such resource */ + coap_pdu_set_code(response, my_clock_base ? COAP_RESPONSE_CODE_CONTENT : + COAP_RESPONSE_CODE_NOT_FOUND); + if (my_clock_base) { + coap_add_option(response, COAP_OPTION_CONTENT_FORMAT, + coap_encode_var_safe(buf, sizeof(buf), + COAP_MEDIATYPE_TEXT_PLAIN), + buf); + } + + coap_add_option(response, COAP_OPTION_MAXAGE, + coap_encode_var_safe(buf, sizeof(buf), 0x01), buf); + + if (my_clock_base) { + + /* calculate current time */ + coap_ticks(&t); + now = my_clock_base + (t / COAP_TICKS_PER_SECOND); + + if (query != NULL + && coap_string_equal(query, coap_make_str_const("ticks"))) { + /* output ticks */ + len = snprintf((char *)buf, sizeof(buf), "%u", (unsigned int)now); + coap_add_data(response, len, buf); + } + } +} + +static void +init_coap_resources(coap_context_t *ctx) +{ + coap_resource_t *r; + +#if 0 + r = coap_resource_init(NULL, 0, 0); + coap_register_handler(r, COAP_REQUEST_GET, hnd_get_index); + + coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"General Info\""), 0); + coap_add_resource(ctx, r); +#endif + /* store clock base to use in /time */ + my_clock_base = clock_offset; + + r = coap_resource_init(coap_make_str_const("time"), 0); + if (!r) { + goto error; + } + + coap_resource_set_get_observable(r, 1); + time_resource = r; + coap_register_handler(r, COAP_REQUEST_GET, hnd_get_time); +#if 0 + coap_register_handler(r, COAP_REQUEST_PUT, hnd_put_time); + coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_time); +#endif + coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + /* coap_add_attr(r, coap_make_str_const("title"), + coap_make_str_const("\"Internal Clock\""), 0); */ + coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0); + coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0); + + coap_add_resource(ctx, r); +#if 0 + if (coap_async_is_supported()) { + r = coap_resource_init(coap_make_str_const("async"), 0); + coap_register_handler(r, COAP_REQUEST_GET, hnd_get_async); + + coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_resource(ctx, r); + } +#endif + + return; +error: + coap_log_crit("cannot create resource\n"); +} + +static int +init_coap_context_endpoints(const char *use_psk) +{ + coap_address_t listenaddress; + gnrc_netif_t *netif = gnrc_netif_iter(NULL); + ipv6_addr_t addr; + char addr_str[INET6_ADDRSTRLEN + 8]; + int scheme_hint_bits = 1 << COAP_URI_SCHEME_COAP; + coap_addr_info_t *info = NULL; + coap_addr_info_t *info_list = NULL; + coap_str_const_t local; + int have_ep = 0; + + /* Get the first address on the interface */ + if (gnrc_netif_ipv6_addrs_get(netif, &addr, sizeof(addr)) < 0) { + puts("Unable to get first address of the interface"); + return 0; + } + + coap_address_init(&listenaddress); + listenaddress.addr.sin6.sin6_family = AF_INET6; + memcpy(&listenaddress.addr.sin6.sin6_addr, &addr, + sizeof(listenaddress.addr.sin6.sin6_addr)); + coap_print_ip_addr(&listenaddress, addr_str, sizeof(addr_str)); + coap_log_info("Server IP [%s]\n", addr_str); + + main_coap_context = coap_new_context(NULL); + if (!main_coap_context) { + return 0; + } + + if (use_psk && coap_dtls_is_supported()) { + coap_dtls_spsk_t setup_data; + + /* Need PSK set up before setting up endpoints */ + memset(&setup_data, 0, sizeof(setup_data)); + setup_data.version = COAP_DTLS_SPSK_SETUP_VERSION; + setup_data.psk_info.key.s = (const uint8_t *)use_psk; + setup_data.psk_info.key.length = strlen(use_psk); + coap_context_set_psk2(main_coap_context, &setup_data); + scheme_hint_bits |= 1 << COAP_URI_SCHEME_COAPS; + } + + local.s = (uint8_t *)addr_str; + local.length = strlen(addr_str); + info_list = coap_resolve_address_info(&local, COAP_DEFAULT_PORT, + COAPS_DEFAULT_PORT, + 0, 0, + 0, + scheme_hint_bits, + COAP_RESOLVE_TYPE_REMOTE); + for (info = info_list; info != NULL; info = info->next) { + coap_endpoint_t *ep; + + ep = coap_new_endpoint(main_coap_context, &info->addr, info->proto); + if (!ep) { + coap_log_warn("cannot create endpoint for proto %u\n", + info->proto); + } + else { + have_ep = 1; + } + } + coap_free_address_info(info_list); + if (!have_ep) { + return 0; + } + + return 1; +} + +void * +server_coap_run(void *arg) +{ + (void)arg; + + /* Initialize libcoap library */ + coap_startup(); + + coap_set_log_level(COAP_MAX_LOGGING_LEVEL); + + if (!init_coap_context_endpoints(COAP_USE_PSK)) { + goto fail; + } + + /* Limit the number of idle sessions to save RAM */ + coap_context_set_max_idle_sessions(main_coap_context, 2); + clock_offset = 1; /* Need a non-zero value */ + init_coap_resources(main_coap_context); + + coap_log_info("libcoap server ready\n"); + /* Keep on processing ... */ + while (quit == 0) { + coap_io_process(main_coap_context, 1000); + } +fail: + /* Clean up library usage so client can be run again */ + coap_free_context(main_coap_context); + main_coap_context = NULL; + coap_cleanup(); + running = 0; + quit = 0; + coap_log_info("libcoap server stopped\n"); + return NULL; +} + +static char server_stack[THREAD_STACKSIZE_MAIN + + THREAD_EXTRA_STACKSIZE_PRINTF]; + +static +void +start_server(void) +{ + kernel_pid_t server_pid; + + /* Only one instance of the server */ + if (running) { + puts("Error: server already running"); + return; + } + + /* The server is initialized */ + server_pid = thread_create(server_stack, + sizeof(server_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + server_coap_run, NULL, "libcoap_server"); + + /* Uncommon but better be sure */ + if (server_pid == EINVAL) { + puts("ERROR: Thread invalid"); + return; + } + + if (server_pid == EOVERFLOW) { + puts("ERROR: Thread overflow!"); + return; + } + + running = 1; + return; +} + +static +void +stop_server(void) +{ + /* check if server is running at all */ + if (running == 0) { + puts("Error: libcoap server is not running"); + return; + } + + quit = 1; + + puts("Stopping server..."); +} + +void +server_coap_init(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: %s start|stop\n", argv[0]); + return; + } + if (strcmp(argv[1], "start") == 0) { + start_server(); + } + else if (strcmp(argv[1], "stop") == 0) { + stop_server(); + } + else { + printf("Error: invalid command. Usage: %s start|stop\n", argv[0]); + } + return; +} diff --git a/examples/libcoap-server/server-coap.h b/examples/libcoap-server/server-coap.h new file mode 100644 index 0000000000000..fb9802f5d90c9 --- /dev/null +++ b/examples/libcoap-server/server-coap.h @@ -0,0 +1,29 @@ +/* + * server-coap.h -- RIOT client example + * + * Copyright (C) 2023-2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef SERVER_COAP_H +#define SERVER_COAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "coap_config.h" +#include + +/* Start up the CoAP Server */ +void server_coap_init(int argc, char **argv); + +#ifdef __cplusplus +} +#endif + +#endif /* SERVER_COAP_H */ diff --git a/pkg/Kconfig b/pkg/Kconfig index 4feea70743faa..7a6fad5f16d33 100644 --- a/pkg/Kconfig +++ b/pkg/Kconfig @@ -38,6 +38,7 @@ rsource "jerryscript/Kconfig" rsource "jsmn/Kconfig" rsource "libb2/Kconfig" rsource "libbase58/Kconfig" +rsource "libcoap/Kconfig" rsource "libcose/Kconfig" rsource "libfixmath/Kconfig" rsource "libhydrogen/Kconfig" diff --git a/pkg/libcoap/Kconfig b/pkg/libcoap/Kconfig new file mode 100644 index 0000000000000..fe807941ecffe --- /dev/null +++ b/pkg/libcoap/Kconfig @@ -0,0 +1,285 @@ +# Copyright (c) 2019 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. +# +menuconfig KCONFIG_USEPKG_LIBCOAP + bool "Configure libcoap" + depends on USEPKG_LIBCOAP + help + Configure libcoap package via Kconfig. + +if KCONFIG_USEPKG_LIBCOAP + +choice LIBCOAP_DEBUG_LEVEL + bool "Set CoAP debugging level" + default LIBCOAP_LOG_WARNING + help + Set CoAP debugging level + + config LIBCOAP_LOG_EMERG + bool "Emergency" + config LIBCOAP_LOG_ALERT + bool "Alert" + config LIBCOAP_LOG_CRIT + bool "Critical" + config LIBCOAP_LOG_ERROR + bool "Error" + config LIBCOAP_LOG_WARNING + bool "Warning" + config LIBCOAP_LOG_NOTICE + bool "Notice" + config LIBCOAP_LOG_INFO + bool "Info" + config LIBCOAP_LOG_DEBUG + bool "Debug" + config LIBCOAP_LOG_OSCORE + bool "OSCORE" +endchoice + +config LIBCOAP_MAX_LOGGING_LEVEL + int + default 0 if LIBCOAP_LOG_EMERG + default 1 if LIBCOAP_LOG_ALERT + default 2 if LIBCOAP_LOG_CRIT + default 3 if LIBCOAP_LOG_ERROR + default 4 if LIBCOAP_LOG_WARNING + default 5 if LIBCOAP_LOG_NOTICE + default 6 if LIBCOAP_LOG_INFO + default 7 if LIBCOAP_LOG_DEBUG + default 8 if LIBCOAP_LOG_OSCORE + +config LIBCOAP_IPV4_SUPPORT + bool "Enable IPv4 support within CoAP" + default n + help + Enable IPv4 functionality for CoAP. + + If this option is disabled, redundant CoAP IPv4 code is removed. + +config LIBCOAP_IPV6_SUPPORT + bool "Enable IPv6 support within CoAP" + default y + help + Enable IPv6 functionality for CoAP. + + If this option is disabled, redundant CoAP IPv6 code is removed. + +config LIBCOAP_TCP_SUPPORT + bool "Enable TCP support within CoAP" + default n + help + Enable TCP functionality for CoAP. This is required if TLS sessions + are to be used. Note that RIOT TCP support also needs to be enabled. + + If this option is disabled, redundant CoAP TCP code is removed. + +config LIBCOAP_OSCORE_SUPPORT + bool "Enable OSCORE support within CoAP" + default n + help + Enable OSCORE functionality for CoAP. + + If this option is disabled, redundant CoAP OSCORE code is removed. + +config LIBCOAP_OBSERVE_PERSIST + bool "Enable Observe persist support within CoAP" + default n + help + Enable Observe persist functionality for CoAP. + + If this option is disabled, redundant CoAP Observe persist code is removed. + +config LIBCOAP_WS_SOCKET + bool "Enable WebSocket support within CoAP" + default n + help + Enable WebSocket functionality for CoAP. + + If this option is disabled, redundant CoAP WebSocket code is removed. + +config LIBCOAP_Q_BLOCK_SUPPORT + bool "Enable Q-Block (RFC9177) support within CoAP" + default n + help + Enable Q-Block (RFC9177) functionality for CoAP. + + If this option is disabled, redundant CoAP Q-Block code is removed. + +config LIBCOAP_ASYNC_SUPPORT + bool "Enable separate responses support within CoAP" + default y + help + Enable async separate responses functionality for CoAP. + + If this option is disabled, redundent CoAP async separate responses code is removed. + +config LIBCOAP_THREAD_SAFE + bool "Enable thread safe support within CoAP" + default n + help + Enable thread safe support within CoAP. + + If this option is disabled, libcoap is not thread safe, + +config LIBCOAP_THREAD_RECURSIVE_CHECK + bool "Enable thread recursive lock detection if thread safe support is enabled" + depends on LIBCOAP_THREAD_SAFE + default n + help + Enable thread recursive lock detection if thread safe support is enabled. + + If this option is disabled, there is no multi thread recursive detection. + +config LIBCOAP_CLIENT_SUPPORT + bool "Enable Client functionality within CoAP" + default n + help + Enable client functionality (ability to make requests and receive + responses) for CoAP. If the server is going to act as a proxy, then + this needs to be enabled to support the ongoing session going to + the next hop. + + If this option is disabled, redundant CoAP client only code is + removed. + If both this option and LIBCOAP_SERVER_SUPPORT are disabled, then + both are automatically enabled for backwards compatability. + +if LIBCOAP_CLIENT_SUPPORT + +config LIBCOAP_MAX_LG_CRCVS + int "Max number of client large receives supported" + default 1 + help + The maximum number of supported client large receives. + +endif # LIBCOAP_CLIENT_SUPPORT + +config LIBCOAP_SERVER_SUPPORT + bool "Enable Server functionality within CoAP" + default n + help + Enable server functionality (ability to receive requests and send + responses) for CoAP. + + If this option is disabled, redundant CoAP server only code is + removed. + If both this option and LIBCOAP_CLIENT_SUPPORT are disabled, then + both are automatically enabled for backwards compatability. + +if LIBCOAP_SERVER_SUPPORT + +config LIBCOAP_MAX_ENDPOINTS + int "Max number of endpoints supported" + default 4 if LIBCOAP_TCP_SUPPORT + default 2 + help + The maximum number of supported endpoints. + +config LIBCOAP_MAX_RESOURCES + int "Max number of resources supported" + default 8 + help + The maximum number of supported resources. + +config LIBCOAP_MAX_ATTRIBUTE_SIZE + int "Max size of attribute memory allocation" + default 16 + help + The maximum size of a supported attribute. + +config LIBCOAP_MAX_ATTRIBUTES + int "Max number of resource attributes supported" + default 32 + help + The maximum number of supported resource attributes. + +config LIBCOAP_MAX_CACHE_KEYS + int "Max number of cache keys supported" + default 2 + help + The maximum number of supported cache keys. + +config LIBCOAP_MAX_CACHE_ENTRIES + int "Max number of cache entries supported" + default 2 + help + The maximum number of supported cache entries. + +config LIBCOAP_MAX_LG_SRCVS + int "Max number of server large receives supported" + default 2 + help + The maximum number of supported server large receives. + +endif # LIBCOAP_SERVER_SUPPORT + +config LIBCOAP_MAX_LG_XMITS + int "Max number of large transmits supported" + default 2 if LIBCOAP_SERVER_SUPPORT + default 1 + help + The maximum number of supported large transmits. + +config LIBCOAP_MAX_STRING_SIZE + int "Max size of string memory allocation" + default 64 + help + The maximum size of a supported string. + +config LIBCOAP_MAX_STRINGS + int "Max number of strings supported" + default 16 + help + The maximum number of supported strings. + +config LIBCOAP_MAX_PACKETS + int "Max number of packets supported" + default 4 + help + The maximum number of supported packets. + +config LIBCOAP_MAX_NODES + int "Max number of nodes supported" + default 4 + help + The maximum number of supported nodes. + +config LIBCOAP_MAX_CONTEXTS + int "Max number of contexts supported" + default 1 + help + The maximum number of supported contexts. + +config LIBCOAP_MAX_PDUS + int "Max number of PDUs supported" + default 4 + help + The maximum number of supported PDUs. + +config LIBCOAP_MAX_DTLS_SESSIONS + int "Max number of DTLS sessions supported" + default 2 + help + The maximum number of supported DTLS sessions. + +config LIBCOAP_MAX_SESSIONS + int "Max number of sessions supported" + default 4 + help + The maximum number of supported sessions. + +config LIBCOAP_MAX_OPTION_SIZE + int "Max size of option memory allocation" + default 16 + help + The maximum size of a supported option. + +config LIBCOAP_MAX_OPTIONS + int "Max number of options supported" + default 16 + help + The maximum number of supported options. + +endif # KCONFIG_USEPKG_LIBCOAP diff --git a/pkg/libcoap/Makefile b/pkg/libcoap/Makefile new file mode 100644 index 0000000000000..370ed49d586d0 --- /dev/null +++ b/pkg/libcoap/Makefile @@ -0,0 +1,15 @@ +PKG_NAME=libcoap +PKG_URL=https://github.com/obgm/libcoap +PKG_VERSION=a733325b16e9070c03b0a664a6dbe01ffda7de65 +PKG_LICENSE=BSD-2-Clause + +LIBCOAP_BUILD_DIR=$(BINDIR)/pkg/$(PKG_NAME) +LIBCOAP_SOURCE_DIR=$(RIOTBASE)/build/pkg/$(PKG_NAME) +LIBCOAP_INCLUDE_DIR=$(RIOTBASE)/build/pkg/$(PKG_NAME)/include/coap3 + +include $(RIOTBASE)/pkg/pkg.mk + +all: + @cp $(LIBCOAP_SOURCE_DIR)/coap_config.h.riot $(LIBCOAP_SOURCE_DIR)/coap_config.h + @cp $(LIBCOAP_INCLUDE_DIR)/coap.h.riot $(LIBCOAP_INCLUDE_DIR)/coap.h + "$(MAKE)" -C $(LIBCOAP_SOURCE_DIR)/src -f $(CURDIR)/Makefile.libcoap diff --git a/pkg/libcoap/Makefile.dep b/pkg/libcoap/Makefile.dep new file mode 100644 index 0000000000000..da74092fbec76 --- /dev/null +++ b/pkg/libcoap/Makefile.dep @@ -0,0 +1,10 @@ +USEMODULE += posix_headers +USEMODULE += posix_inet +USEMODULE += sock_udp +USEMODULE += sock_tcp +USEMODULE += sock_aux_local +USEMODULE += gnrc_sock_async + +ifneq (,$(filter libcoap,$(USEPKG))) + USEMODULE += libcoap +endif diff --git a/pkg/libcoap/Makefile.include b/pkg/libcoap/Makefile.include new file mode 100644 index 0000000000000..a51fce2d70273 --- /dev/null +++ b/pkg/libcoap/Makefile.include @@ -0,0 +1,2 @@ +INCLUDES += -I$(PKGDIRBASE)/libcoap \ + -I$(PKGDIRBASE)/libcoap/include diff --git a/pkg/libcoap/Makefile.libcoap b/pkg/libcoap/Makefile.libcoap new file mode 100644 index 0000000000000..e560ef9e21633 --- /dev/null +++ b/pkg/libcoap/Makefile.libcoap @@ -0,0 +1,42 @@ +MODULE := libcoap + +SRC := coap_address.c \ + coap_asn1.c \ + coap_async.c \ + coap_block.c \ + coap_cache.c \ + coap_debug.c \ + coap_dtls.c \ + coap_encode.c \ + coap_event.c \ + coap_gnutls.c\ + coap_hashkey.c \ + coap_io.c \ + coap_io_riot.c\ + coap_layers.c\ + coap_mbedtls.c\ + coap_mem.c \ + coap_net.c \ + coap_netif.c \ + coap_notls.c \ + coap_openssl.c\ + coap_option.c \ + coap_oscore.c \ + coap_pdu.c \ + coap_prng.c \ + coap_resource.c \ + coap_session.c \ + coap_sha1.c \ + coap_str.c \ + coap_subscribe.c \ + coap_tcp.c \ + coap_threadsafe.c \ + coap_tinydtls.c \ + coap_uri.c \ + coap_ws.c + +ifneq (,$(filter libcoap_tinydtls,$(USEMODULE))) +CFLAGS += -DCOAP_WITH_LIBTINYDTLS +endif + +include $(RIOTBASE)/Makefile.base diff --git a/tests/pkg/libcoap/Kconfig b/tests/pkg/libcoap/Kconfig new file mode 100644 index 0000000000000..5173037ef8fcd --- /dev/null +++ b/tests/pkg/libcoap/Kconfig @@ -0,0 +1,10 @@ +if USEMODULE_LIBCOAP +config LIBCOAP_USE_PSK + string "Secret to use for PSK communications" + default "secretPSK" + depends on USEMODULE_TINYDTLS +config LIBCOAP_USE_PSK_ID + string "Identifier (user) to use for PSK communications" + default "test_user" + depends on USEMODULE_TINYDTLS +endif # USEMODULE_LIBCOAP diff --git a/tests/pkg/libcoap/Makefile b/tests/pkg/libcoap/Makefile new file mode 100644 index 0000000000000..67022acfc5b79 --- /dev/null +++ b/tests/pkg/libcoap/Makefile @@ -0,0 +1,35 @@ +# Include common testing definitions +include ../Makefile.pkg_common + +# This test depends on tap device setup not being set +PORT= + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += netdev_default +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_netif_single + +# Additional networking modules that can be dropped if not needed +USEMODULE += netutils + +# Specify the mandatory networking modules for IPv6 and UDP +USEMODULE += gnrc_ipv6_default + +USEMODULE += memarray + +# a cryptographically secure implementation of PRNG is needed for tinydtls +CFLAGS += -DWITH_RIOT_SOCK +CFLAGS += -DDTLS_PEER_MAX=2 -DDTLS_HANDSHAKE_MAX=2 -DNETQ_MAXCNT=5 +USEPKG += tinydtls +USEMODULE += prng_sha1prng + +# libcoap support +USEPKG += libcoap + +USEMODULE += ztimer64_xtimer_compat + +# libcoap needs some space +CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(3*THREAD_STACKSIZE_DEFAULT\) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/pkg/libcoap/Makefile.ci b/tests/pkg/libcoap/Makefile.ci new file mode 100644 index 0000000000000..9e746742194b9 --- /dev/null +++ b/tests/pkg/libcoap/Makefile.ci @@ -0,0 +1,70 @@ +BOARD_INSUFFICIENT_MEMORY := \ + airfy-beacon \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega256rfr2-xpro \ + atmega328p \ + atmega328p-xplained-mini \ + atmega8 \ + atxmega-a3bu-xplained \ + b-l072z-lrwan1 \ + blackpill-stm32f103c8 \ + blackpill-stm32f103cb \ + bluepill-stm32f030c8 \ + bluepill-stm32f103c8 \ + bluepill-stm32f103cb \ + calliope-mini \ + cc2650-launchpad \ + cc2650stk \ + derfmega128 \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + im880b \ + lsn50 \ + maple-mini \ + microbit \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nrf51dk \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f103rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-l073rz \ + olimex-msp430-h1611 \ + olimex-msp430-h2618 \ + opencm904 \ + samd10-xmini \ + saml10-xpro \ + saml11-xpro \ + samr21-xpro \ + slstk3400a \ + spark-core \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32f7508-dk \ + stm32g0316-disco \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + telosb \ + waspmote-pro \ + yunjia-nrf51822 \ + z1 \ + zigduino \ + # diff --git a/tests/pkg/libcoap/README.md b/tests/pkg/libcoap/README.md new file mode 100644 index 0000000000000..0599020ef4cee --- /dev/null +++ b/tests/pkg/libcoap/README.md @@ -0,0 +1,7 @@ +# libcoap test example + +This test is to check that the libcoap client and server logic talk to +each other. + +THe client request generates a CoAP request, which is sent to the server +who responds back to the client. diff --git a/tests/pkg/libcoap/app.config b/tests/pkg/libcoap/app.config new file mode 100644 index 0000000000000..484fa5d3026ac --- /dev/null +++ b/tests/pkg/libcoap/app.config @@ -0,0 +1,13 @@ +CONFIG_LIBCOAP_SERVER_SUPPORT=y +CONFIG_LIBCOAP_CLIENT_SUPPORT=y + +CONFIG_LIBCOAP_USE_PSK="secretPSK" +CONFIG_LIBCOAP_USE_PSK_ID="test_user" + +CONFIG_KCONFIG_USEPKG_LIBCOAP=y +# Logging levels are defined in pkg/libcoap using Kconfig +CONFIG_LIBCOAP_LOG_ALERT=y + +CONFIG_KCONFIG_USEPKG_TINYDTLS=y +CONFIG_DTLS_PEER_MAX=2 +CONFIG_DTLS_HANDSHAKE_MAX=2 diff --git a/tests/pkg/libcoap/libcoap-test.c b/tests/pkg/libcoap/libcoap-test.c new file mode 100644 index 0000000000000..a25d25f0678ce --- /dev/null +++ b/tests/pkg/libcoap/libcoap-test.c @@ -0,0 +1,328 @@ +/* + * libcoap-test.c -- RIOT example + * + * Copyright (C) 2023-2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#include "coap_config.h" +#include +#include "libcoap-test.h" +#include +#include "macros/utils.h" + +#ifdef CONFIG_LIBCOAP_USE_PSK +#define COAP_USE_PSK CONFIG_LIBCOAP_USE_PSK +#else /* CONFIG_LIBCOAP_USE_PSK */ +#define COAP_USE_PSK NULL +#endif /* CONFIG_LIBCOAP_USE_PSK */ + +#ifdef CONFIG_LIBCOAP_USE_PSK_ID +#define COAP_USE_PSK_ID CONFIG_LIBCOAP_USE_PSK_ID +#else /* CONFIG_LIBCOAP_USE_PSK_ID */ +#define COAP_USE_PSK_ID NULL +#endif /* CONFIG_LIBCOAP_USE_PSK_ID */ + +static int quit; + +#define INDEX "This is a DTLS loopback test client/server made with libcoap\n" + +static void +hnd_get_index(coap_resource_t *resource, coap_session_t *session, + const coap_pdu_t *request, const coap_string_t *query, + coap_pdu_t *response) +{ + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 0x7fff, 0, strlen(INDEX), + (const uint8_t *)INDEX, NULL, NULL); +} + +static void +init_coap_resources(coap_context_t *ctx) +{ + coap_resource_t *r; + + r = coap_resource_init(NULL, 0); + if (r == NULL) { + goto error; + } + coap_register_handler(r, COAP_REQUEST_GET, hnd_get_index); + + coap_add_resource(ctx, r); + + return; +error: + coap_log_crit("cannot create resource\n"); +} + +static int +init_coap_endpoints(coap_context_t *ctx, const char *use_psk) +{ + char addr_str[INET6_ADDRSTRLEN + 8]; + int scheme_hint_bits = 1 << COAP_URI_SCHEME_COAP; + coap_addr_info_t *info = NULL; + coap_addr_info_t *info_list = NULL; + coap_str_const_t local; + int have_ep = 0; + + if (use_psk && coap_dtls_is_supported()) { + coap_dtls_spsk_t setup_data; + + /* Need PSK set up before setting up endpoints */ + memset(&setup_data, 0, sizeof(setup_data)); + setup_data.version = COAP_DTLS_SPSK_SETUP_VERSION; + setup_data.psk_info.key.s = (const uint8_t *)use_psk; + setup_data.psk_info.key.length = strlen(use_psk); + coap_context_set_psk2(ctx, &setup_data); + scheme_hint_bits |= 1 << COAP_URI_SCHEME_COAPS; + } + + local.s = (uint8_t *)"::1"; + local.length = strlen(addr_str); + info_list = coap_resolve_address_info(&local, COAP_DEFAULT_PORT, + COAPS_DEFAULT_PORT, + 0, 0, + 0, + scheme_hint_bits, + COAP_RESOLVE_TYPE_REMOTE); + for (info = info_list; info != NULL; info = info->next) { + coap_endpoint_t *ep; + + ep = coap_new_endpoint(ctx, &info->addr, info->proto); + if (!ep) { + coap_log_warn("cannot create endpoint for proto %u\n", + info->proto); + } + else { + have_ep = 1; + } + } + coap_free_address_info(info_list); + if (!have_ep) { + return 0; + } + + return 1; +} + +static int +coap_server_init(coap_context_t *ctx) +{ + if (!init_coap_endpoints(ctx, COAP_USE_PSK)) { + return 0; + } + + init_coap_resources(ctx); + return 1; +} + +static int +resolve_address(const char *host, const char *service, coap_address_t *dst, + int scheme_hint_bits) +{ + uint16_t port = service ? atoi(service) : 0; + int ret = 0; + coap_str_const_t str_host; + coap_addr_info_t *addr_info; + + str_host.s = (const uint8_t *)host; + str_host.length = strlen(host); + addr_info = coap_resolve_address_info(&str_host, port, port, port, port, + AF_UNSPEC, scheme_hint_bits, + COAP_RESOLVE_TYPE_REMOTE); + if (addr_info) { + ret = 1; + *dst = addr_info->addr; + } + + coap_free_address_info(addr_info); + return ret; +} + +static coap_response_t +message_handler(coap_session_t *session, + const coap_pdu_t *sent, + const coap_pdu_t *received, + const coap_mid_t id) +{ + const uint8_t *data; + size_t len; + size_t offset; + size_t total; + + (void)session; + (void)sent; + (void)id; + if (coap_get_data_large(received, &len, &data, &offset, &total)) { + printf("%*.*s", (int)len, (int)len, (const char *)data); + if (len + offset == total) { + printf("\n"); + quit = 1; + } + } + return COAP_RESPONSE_OK; +} + +static int +coap_client_init(coap_context_t *ctx) +{ + coap_session_t *session = NULL; + coap_pdu_t *pdu; + coap_address_t dst; + coap_mid_t mid; + int len; + coap_uri_t uri; + char portbuf[8]; + coap_optlist_t *optlist = NULL; + +#define BUFSIZE 100 + unsigned char buf[BUFSIZE]; + int res; + const char *coap_uri = "coaps://[::1]"; + + /* Parse the URI */ + len = coap_split_uri((const unsigned char *)coap_uri, strlen(coap_uri), &uri); + if (len != 0) { + coap_log_warn("Failed to parse uri %s\n", coap_uri); + goto fail; + } + + snprintf(portbuf, sizeof(portbuf), "%d", uri.port); + snprintf((char *)buf, sizeof(buf), "%*.*s", (int)uri.host.length, + (int)uri.host.length, (const char *)uri.host.s); + /* resolve destination address where packet should be sent */ + len = resolve_address((const char *)buf, portbuf, &dst, 1 << uri.scheme); + if (len <= 0) { + coap_log_warn("Failed to resolve address %*.*s\n", (int)uri.host.length, + (int)uri.host.length, (const char *)uri.host.s); + goto fail; + } + + + if (uri.scheme == COAP_URI_SCHEME_COAP) { + session = coap_new_client_session(ctx, NULL, &dst, + COAP_PROTO_UDP); + } + else if (uri.scheme == COAP_URI_SCHEME_COAP_TCP) { + goto fail; + } + else if (uri.scheme == COAP_URI_SCHEME_COAPS_TCP) { + goto fail; + } + else if (uri.scheme == COAP_URI_SCHEME_COAPS) { +#if defined (COAP_USE_PSK) && defined(COAP_USE_PSK_ID) + static coap_dtls_cpsk_t dtls_psk; + static char client_sni[256]; + + memset(client_sni, 0, sizeof(client_sni)); + memset(&dtls_psk, 0, sizeof(dtls_psk)); + dtls_psk.version = COAP_DTLS_CPSK_SETUP_VERSION; + if (uri.host.length) { + memcpy(client_sni, uri.host.s, + MIN(uri.host.length, sizeof(client_sni) - 1)); + } + else { + memcpy(client_sni, "localhost", 9); + } + dtls_psk.client_sni = client_sni; + dtls_psk.psk_info.identity.s = (const uint8_t *)COAP_USE_PSK_ID; + dtls_psk.psk_info.identity.length = strlen(COAP_USE_PSK_ID); + dtls_psk.psk_info.key.s = (const uint8_t *)COAP_USE_PSK; + dtls_psk.psk_info.key.length = strlen(COAP_USE_PSK); + + session = coap_new_client_session_psk2(ctx, NULL, &dst, + COAP_PROTO_DTLS, &dtls_psk); +#else /* ! COAP_USE_PSK && ! COAP_USE_PSK_ID */ + coap_log_err("CONFIG_LIBCOAP_USE_PSK and CONFIG_LIBCOAP_USE_PSK_ID not defined\n"); + goto fail; +#endif /* ! COAP_USE_PSK && ! COAP_USE_PSK_ID */ + } + + if (!session) { + coap_log_warn("Failed to create session\n"); + goto fail; + } + + coap_register_response_handler(ctx, message_handler); + + /* construct CoAP message */ + pdu = coap_pdu_init(COAP_MESSAGE_CON, + COAP_REQUEST_CODE_GET, + coap_new_message_id(session), + coap_session_max_pdu_size(session)); + if (!pdu) { + coap_log_warn("Failed to create PDU\n"); + goto fail; + } + + len = coap_uri_into_options(&uri, &dst, &optlist, 1, buf, sizeof(buf)); + if (len) { + coap_log_warn("Failed to create options\n"); + goto fail; + } + + /* Add option list (which will be sorted) to the PDU */ + if (optlist) { + res = coap_add_optlist_pdu(pdu, &optlist); + if (res != 1) { + coap_log_warn("Failed to add options to PDU\n"); + goto fail; + } + } + + /* and send the PDU */ + mid = coap_send(session, pdu); + if (mid == COAP_INVALID_MID) { + coap_log_warn("Failed to send PDU\n"); + goto fail; + } + coap_delete_optlist(optlist); + return 1; + +fail: + coap_delete_optlist(optlist); + return 0; +} + +void +libcoap_test_run(void) +{ + coap_context_t *coap_context; + + /* Initialize libcoap library */ + coap_startup(); + + coap_set_log_level(COAP_MAX_LOGGING_LEVEL); + + coap_context = coap_new_context(NULL); + if (!coap_context) { + goto fail; + } + coap_context_set_block_mode(coap_context, COAP_BLOCK_USE_LIBCOAP); + + /* Set up server logic */ + coap_server_init(coap_context); + coap_log_info("libcoap test server ready\n"); + + /* Set up and initiate client logic */ + coap_client_init(coap_context); + coap_log_info("libcoap test client started\n"); + + /* Keep on processing until response is back in ... */ + while (quit == 0) { + coap_io_process(coap_context, 1000); + } +fail: + /* Clean up library usage so client can be run again */ + coap_free_context(coap_context); + + coap_cleanup(); + coap_log_info("libcoap test stopped\n"); + return; +} diff --git a/tests/pkg/libcoap/libcoap-test.h b/tests/pkg/libcoap/libcoap-test.h new file mode 100644 index 0000000000000..baa5217272a9e --- /dev/null +++ b/tests/pkg/libcoap/libcoap-test.h @@ -0,0 +1,29 @@ +/* + * libcoap-test.h -- RIOT client example + * + * Copyright (C) 2023-2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef LIBCOAP_TEST_H +#define LIBCOAP_TEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "coap_config.h" +#include + +/* Start up the CoAP Server */ +void libcoap_test_run(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBCOAP_TEST_H */ diff --git a/tests/pkg/libcoap/main.c b/tests/pkg/libcoap/main.c new file mode 100644 index 0000000000000..48b8254c01cf1 --- /dev/null +++ b/tests/pkg/libcoap/main.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * 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 Example test application for libcoap + * + * @author Raul Fuentes <> + * + * @} + */ + +#include + +#include "shell.h" +#include "msg.h" + +#include "libcoap-test.h" +#include "coap3/coap.h" + +#define MAIN_QUEUE_SIZE (8) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +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); + + libcoap_test_run(); + + return 0; +} diff --git a/tests/pkg/libcoap/tests/01-run.py b/tests/pkg/libcoap/tests/01-run.py new file mode 100755 index 0000000000000..57ff7858cd3c1 --- /dev/null +++ b/tests/pkg/libcoap/tests/01-run.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg +# +# 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. + +import sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("This is a DTLS loopback test client/server made with libcoap\r\n") + + +if __name__ == "__main__": + sys.exit(run(testfunc))