diff --git a/dist/tools/coap-yolo/README.md b/dist/tools/coap-yolo/README.md new file mode 100644 index 000000000000..62db1ba564e5 --- /dev/null +++ b/dist/tools/coap-yolo/README.md @@ -0,0 +1,17 @@ +CoAP over YOLO Utils +==================== + +CoAP over YOLO is using the CoAP over WebSocket serialization but sending the +messages over UDP. The CoAP over WebSocket format depends on a reliable, +order-preserving, duplication-free message transport; which UDP clearly is not. +Hence the name YOLO. + +However, if the RIOT note is connected to a Linux host with a single reliable, +order-preserving and duplication-free link, this should work. + +This folder contains WebSocket to UDP proxy that forwards messages received +on the WebSocket to a UDP endpoint specified by the command line, and forwards +any replies received via UDP back to the WebSocket. This allows +exposing a CoAP over YOLO as a CoAP over WebSocket. With this you can use any +CoAP over WebSocket implementation such as e.g. `coap-client` from +[libcoap](https://libcoap.net/) to connect to CoAP over YOLO. diff --git a/dist/tools/coap-yolo/coap+ws2coap+yolo.py b/dist/tools/coap-yolo/coap+ws2coap+yolo.py new file mode 100755 index 000000000000..e5c42793c9b7 --- /dev/null +++ b/dist/tools/coap-yolo/coap+ws2coap+yolo.py @@ -0,0 +1,99 @@ +#!/usr/bin/python3 +""" +Bridge that translates CoAP over YOLO to CoAP over WebSocket. +""" + +import aiohttp +import aiohttp.web +import argparse +import asyncio +import sys + +udp_ep = None +udp_transport = None +ws = None + +class ForwardFromUdpProtocol: + """ + Forward received UDP datagrams via the currently connected WebSocket + """ + def connection_made(self, transport): + pass + + def datagram_received(self, data, addr): + global ws + if ws is not None: + asyncio.ensure_future(ws.send_bytes(data), loop=asyncio.get_event_loop()) + + +async def websocket_handler(request): + """ + Forward received WebSocket messages to the (statically) configured UDP + destination endpoint + """ + global udp_transport + global udp_ep + global ws + if ws is not None: + print("Someone already is connected") + return + ws = aiohttp.web.WebSocketResponse(protocols=("coap")) + print("WebSocket connection opened") + await ws.prepare(request) + + async for msg in ws: + if msg.type == aiohttp.WSMsgType.BINARY: + udp_transport.sendto(msg.data, udp_ep) + elif msg.type == aiohttp.WSMsgType.CLOSED: + udp_transport.sendto(b'', udp_ep) + ws = None + return + else: + print(f"Warning: Got unexpected WebSocket Message {msg}") + + udp_transport.sendto(b'', udp_ep) + ws = None + print("WebSocket connection closed") + + +async def ws2yolo(_udp_ep, ws_ep, udp_local_ep): + """ + Run a WebSocket 2 CoAP over YOLO bridge with the given endpoints + """ + global udp_transport + global udp_ep + udp_ep = _udp_ep + loop = asyncio.get_running_loop() + udp_transport, protocol = await loop.create_datagram_endpoint( + ForwardFromUdpProtocol, + local_addr=udp_local_ep) + + app = aiohttp.web.Application() + app.router.add_route('GET', '/.well-known/coap', websocket_handler) + runner = aiohttp.web.AppRunner(app) + await runner.setup() + site = aiohttp.web.TCPSite(runner) + await site.start() + await asyncio.Event().wait() + + +if __name__ == "__main__": + DESCRIPTION = "Forward WebSocket messages via UDP" + parser = argparse.ArgumentParser(description=DESCRIPTION) + parser.add_argument("--udp-host", default="::1", type=str, + help="UDP host to forward to") + parser.add_argument("--udp-port", default=1337, type=int, + help="UDP port to forward to") + parser.add_argument("--local-host", default=None, type=str, + help="UDP host to forward from") + parser.add_argument("--local-port", default=0, type=int, + help="UDP port to forward from") + parser.add_argument("--ws-host", default="::1", type=str, + help="WebSocket host to listen at") + parser.add_argument("--ws-port", default=8080, type=int, + help="WebSocket port to listen at") + + args = parser.parse_args() + asyncio.run(ws2yolo((args.udp_host, args.udp_port), + (args.ws_host, args.ws_port), + (args.local_host, args.local_port))) diff --git a/examples/gcoap/client.c b/examples/gcoap/client.c index a2b31ff04082..f603321729c1 100644 --- a/examples/gcoap/client.c +++ b/examples/gcoap/client.c @@ -133,16 +133,16 @@ static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t* pdu, uri_parser_result_t urip; uri_parser_process(&urip, _last_req_uri, strlen(_last_req_uri)); if (*_proxy_uri) { - gcoap_req_init(pdu, (uint8_t *)pdu->hdr, CONFIG_GCOAP_PDU_BUF_SIZE, + gcoap_req_init(pdu, pdu->buf, CONFIG_GCOAP_PDU_BUF_SIZE, COAP_METHOD_GET, NULL); } else { - gcoap_req_init(pdu, (uint8_t *)pdu->hdr, CONFIG_GCOAP_PDU_BUF_SIZE, + gcoap_req_init(pdu, pdu->buf, CONFIG_GCOAP_PDU_BUF_SIZE, COAP_METHOD_GET, urip.path); } if (msg_type == COAP_TYPE_ACK) { - coap_hdr_set_type(pdu->hdr, COAP_TYPE_CON); + coap_pkt_set_type(pdu, COAP_TYPE_CON); } block.blknum++; coap_opt_add_block2_control(pdu, &block); @@ -153,7 +153,7 @@ static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t* pdu, int len = coap_opt_finish(pdu, COAP_OPT_FINISH_NONE); gcoap_socket_type_t tl = _get_tl(*_proxy_uri ? _proxy_uri : _last_req_uri); - _send((uint8_t *)pdu->hdr, len, remote, memo->context, tl); + _send(pdu->buf, len, remote, memo->context, tl); } else { puts("--- blockwise complete ---"); @@ -340,7 +340,7 @@ int gcoap_cli_cmd(int argc, char **argv) } } - coap_hdr_set_type(pdu.hdr, msg_type); + coap_pkt_set_type(&pdu, msg_type); size_t paylen = 0; if (apos < argc) { diff --git a/examples/gcoap/server.c b/examples/gcoap/server.c index b91ff5e419f6..cfe289db90ac 100644 --- a/examples/gcoap/server.c +++ b/examples/gcoap/server.c @@ -122,7 +122,7 @@ static void _rtc_notify_observers(void *arg) } size_t len; char str_time[20] = ""; - uint8_t buf[sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1 + sizeof(str_time)]; + uint8_t buf[sizeof(coap_udp_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1 + sizeof(str_time)]; coap_pkt_t pdu; const coap_resource_t *rtc_resource = NULL; const gcoap_listener_t *listener = NULL; diff --git a/examples/nanocoap_reverse_proxy/Makefile b/examples/nanocoap_reverse_proxy/Makefile new file mode 100644 index 000000000000..60b100a116af --- /dev/null +++ b/examples/nanocoap_reverse_proxy/Makefile @@ -0,0 +1,61 @@ +# name of your application +APPLICATION = nanocoap_reverse_proxy + +# 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)/../.. + +NETWORK_STACK ?= gnrc + +# 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 += sock_udp +USEMODULE += ipv6_addr + +ifeq ($(NETWORK_STACK),gnrc) + USEMODULE += auto_init_gnrc_netif + # Specify the mandatory networking modules for IPv6 + USEMODULE += gnrc_ipv6_default + # Additional networking modules that can be dropped if not needed + USEMODULE += gnrc_icmpv6_echo +endif +ifeq ($(NETWORK_STACK),lwip) + USEMODULE += auto_init_lwip_netif + USEMODULE += lwip_ipv6 lwip_ipv6_autoconfig +endif + +# Comment this out to enable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +#DEVELHELP = 1 + +# Enable fileserver and TCP for boards with plenty of memory +HIGH_MEMORY_BOARDS := native native64 same54-xpro mcb2388 + +USEMODULE += event_thread +USEMODULE += nanocoap_proxy +USEMODULE += nanocoap_server_ws +USEMODULE += nanocoap_ws_udp_yolo +USEMODULE += ztimer_usec + +# async TCP is not supported on GNRC yet +ifeq ($(NETWORK_STACK),lwip) + USEMODULE += nanocoap_server_tcp +endif + +# if nanocaop_server_tcp is used: This app makes use of event_thread +# to run the TCP server +ifneq (,$(filter nanocoap_server_tcp,$(USEMODULE))) + USEMODULE += event_thread +endif + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include + +# Set a custom channel if needed +include $(RIOTMAKE)/default-radio-settings.inc.mk diff --git a/examples/nanocoap_reverse_proxy/Makefile.ci b/examples/nanocoap_reverse_proxy/Makefile.ci new file mode 100644 index 000000000000..d8e75b093a38 --- /dev/null +++ b/examples/nanocoap_reverse_proxy/Makefile.ci @@ -0,0 +1,35 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + atmega8 \ + bluepill-stm32f030c8 \ + i-nucleo-lrwan1 \ + msb-430 \ + msb-430h \ + nucleo-c031c6 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + olimex-msp430-h1611 \ + olimex-msp430-h2618 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32g0316-disco \ + stm32l0538-disco \ + telosb \ + weact-g030f6 \ + z1 \ + # diff --git a/examples/nanocoap_reverse_proxy/main.c b/examples/nanocoap_reverse_proxy/main.c new file mode 100644 index 000000000000..8752503bd2f4 --- /dev/null +++ b/examples/nanocoap_reverse_proxy/main.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 Kaspar Schleiser + * + * 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 CoAP example server application (using nanocoap) + * + * @author Kaspar Schleiser + * @} + */ + +#include + +#include "net/nanocoap_sock.h" +#include "time_units.h" +#include "ztimer.h" + +#if MODULE_NANOCOAP_SERVER_TCP +# include "event/thread.h" +#endif + +#define COAP_INBUF_SIZE (256U) + +#define MAIN_QUEUE_SIZE (8) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +#if MODULE_NANOCOAP_SERVER_TCP +static nanocoap_tcp_server_ctx_t tcp_ctx; +#endif + +#if MODULE_NANOCOAP_SERVER_WS && MODULE_NANOCOAP_WS_UDP_YOLO +static coap_ws_over_udp_yolo_init_arg_t _ws_ctx; +#endif + +int main(void) +{ + puts("RIOT nanocoap example application"); + + /* nanocoap_server uses gnrc sock which uses gnrc which needs a msg queue */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + + puts("Waiting for address autoconfiguration..."); + ztimer_sleep(ZTIMER_USEC, 3 * US_PER_SEC); + + /* print network addresses */ + printf("{\"IPv6 addresses\": [\""); + netifs_print_ipv6("\", \""); + puts("\"]}"); + +#if MODULE_NANOCOAP_SERVER_TCP + nanocoap_server_tcp(&tcp_ctx, EVENT_PRIO_MEDIUM, NULL); + printf("CoAP+TCP on PORT %u\n", (unsigned)tcp_ctx.local.port); +#endif + +#if MODULE_NANOCOAP_SERVER_WS && MODULE_NANOCOAP_WS_UDP_YOLO + sock_udp_ep_t local_ws = { .port = 1337, .family = AF_INET6 }; + nanocoap_server_ws(&coap_ws_over_udp_yolo, &_ws_ctx, &local_ws, sizeof(local_ws)); + printf("CoAP+YOLO on PORT %u\n", (unsigned)local_ws.port); +#endif + +#if MODULE_NANOCOAP_UDP + /* initialize nanocoap server instance */ + uint8_t buf[COAP_INBUF_SIZE]; + sock_udp_ep_t local = { .port=COAP_PORT, .family=AF_INET6 }; + printf("CoAP (UDP) on PORT %u\n", (unsigned)local.port); + nanocoap_server_udp(&local, buf, sizeof(buf)); +#endif + + /* should be never reached */ + return 0; +} diff --git a/examples/nanocoap_reverse_proxy/reverse_proxy.c b/examples/nanocoap_reverse_proxy/reverse_proxy.c new file mode 100644 index 000000000000..69b2f5cce9a1 --- /dev/null +++ b/examples/nanocoap_reverse_proxy/reverse_proxy.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 ML!PA Consulting GmbH + * + * 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. + */ + +#include "event/thread.h" +#include "net/nanocoap.h" +#include "net/nanocoap_proxy.h" + +/* set up reverse proxies per supported transport */ +#if MODULE_NANOCOAP_WS_UDP_YOLO +static nanocoap_rproxy_ctx_t _yolo_proxy = { + .evq = EVENT_PRIO_MEDIUM, + .scheme = "coap+yolo://" +}; + +NANOCOAP_RESOURCE(yolo_proxy) { + .path = "/yolo", + .methods = COAP_GET | COAP_PUT | COAP_POST | COAP_DELETE | COAP_MATCH_SUBTREE, + .handler= nanocoap_rproxy_handler, + .context = &_yolo_proxy, +}; +#endif + +#if MODULE_NANOCOAP_UDP +static nanocoap_rproxy_ctx_t _udp_proxy = { + .evq = EVENT_PRIO_MEDIUM, + .scheme = "coap://" +}; + +NANOCOAP_RESOURCE(udp_proxy) { + .path = "/udp", + .methods = COAP_GET | COAP_PUT | COAP_POST | COAP_DELETE | COAP_MATCH_SUBTREE, + .handler= nanocoap_rproxy_handler, + .context = &_udp_proxy, +}; +#endif + +#if MODULE_NANOCOAP_TCP +static nanocoap_rproxy_ctx_t _tcp_proxy = { + .evq = EVENT_PRIO_MEDIUM, + .scheme = "coap://" +}; + +NANOCOAP_RESOURCE(tcp_proxy) { + .path = "/tcp", + .methods = COAP_GET | COAP_PUT | COAP_POST | COAP_DELETE | COAP_MATCH_SUBTREE, + .handler= _proxy_resource_handler, + .context = &_tcp_proxy, +}; +#endif diff --git a/examples/nanocoap_server/Makefile b/examples/nanocoap_server/Makefile index b944748b8464..01690959a814 100644 --- a/examples/nanocoap_server/Makefile +++ b/examples/nanocoap_server/Makefile @@ -7,18 +7,28 @@ BOARD ?= native # This has to be the absolute path to the RIOT base directory: RIOTBASE ?= $(CURDIR)/../.. +NETWORK_STACK ?= gnrc + # 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 -# Specify the mandatory networking modules for IPv6 and UDP -USEMODULE += gnrc_ipv6_default USEMODULE += sock_udp -# Additional networking modules that can be dropped if not needed -USEMODULE += gnrc_icmpv6_echo - -USEMODULE += nanocoap_sock +USEMODULE += ipv6_addr USEMODULE += nanocoap_resources +USEMODULE += nanocoap_sock +USEMODULE += nanocoap_udp + +ifeq ($(NETWORK_STACK),gnrc) + USEMODULE += auto_init_gnrc_netif + # Specify the mandatory networking modules for IPv6 + USEMODULE += gnrc_ipv6_default + # Additional networking modules that can be dropped if not needed + USEMODULE += gnrc_icmpv6_echo +endif +ifeq ($(NETWORK_STACK),lwip) + USEMODULE += auto_init_lwip_netif + USEMODULE += lwip_ipv6 lwip_ipv6_autoconfig +endif USEMODULE += ztimer_msec @@ -43,7 +53,7 @@ ifneq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS))) USEMODULE += prng_minstd endif -# Enable fileserver for boards with plenty of memory +# Enable fileserver and TCP for boards with plenty of memory HIGH_MEMORY_BOARDS := native native64 same54-xpro mcb2388 ifneq (,$(filter $(BOARD),$(HIGH_MEMORY_BOARDS))) @@ -63,6 +73,20 @@ ifneq (,$(filter $(BOARD),$(HIGH_MEMORY_BOARDS))) ifneq (,$(filter native native64,$(BOARD))) USEMODULE += vfs_auto_format endif + + USEMODULE += nanocoap_server_ws + USEMODULE += nanocoap_ws_udp_yolo + + # async TCP is not supported on GNRC yet + ifeq ($(NETWORK_STACK),lwip) + USEMODULE += nanocoap_server_tcp + endif +endif + +# if nanocaop_server_tcp is used: This app makes use of event_thread +# to run the TCP server +ifneq (,$(filter nanocoap_server_tcp,$(USEMODULE))) + USEMODULE += event_thread endif # Change this to 0 show compiler invocation lines by default: diff --git a/examples/nanocoap_server/coap_handler.c b/examples/nanocoap_server/coap_handler.c index 86dd39993997..327f5fb5a45a 100644 --- a/examples/nanocoap_server/coap_handler.c +++ b/examples/nanocoap_server/coap_handler.c @@ -15,9 +15,9 @@ #include "event/thread.h" #include "event/timeout.h" #include "fmt.h" +#include "hashes/sha256.h" #include "net/nanocoap.h" #include "net/nanocoap_sock.h" -#include "hashes/sha256.h" /* internal value that can be read/written via CoAP */ static uint8_t internal_value = 0; @@ -158,7 +158,7 @@ ssize_t _sha256_handler(coap_pkt_t* pkt, uint8_t *buf, size_t len, coap_request_ return reply_len; } - uint8_t *pkt_pos = (uint8_t*)pkt->hdr + reply_len; + uint8_t *pkt_pos = pkt->buf + reply_len; if (blockwise) { pkt_pos += coap_opt_put_block1_control(pkt_pos, 0, &block1); } @@ -167,7 +167,7 @@ ssize_t _sha256_handler(coap_pkt_t* pkt, uint8_t *buf, size_t len, coap_request_ pkt_pos += fmt_bytes_hex((char *)pkt_pos, digest, sizeof(digest)); } - return pkt_pos - (uint8_t*)pkt->hdr; + return pkt_pos - pkt->buf; } NANOCOAP_RESOURCE(echo) { @@ -186,8 +186,8 @@ NANOCOAP_RESOURCE(sha256) { .path = "/sha256", .methods = COAP_POST, .handler = _sha256_handler }; -/* separate response requires an event thread to execute it */ -#ifdef MODULE_EVENT_THREAD +/* separate response is an optional feature */ +#ifdef MODULE_NANOCOAP_SERVER_SEPARATE static nanocoap_server_response_ctx_t _separate_ctx; static void _send_response(void *ctx) @@ -226,13 +226,13 @@ static ssize_t _separate_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap &event_timed.super); event_timeout_set(&event_timeout, 1 * MS_PER_SEC); - return coap_build_empty_ack(pkt, (void *)buf); + return coap_reply_empty_ack(pkt, buf, len); } NANOCOAP_RESOURCE(separate) { .path = "/separate", .methods = COAP_GET, .handler = _separate_handler, }; -#endif /* MODULE_EVENT_THREAD */ +#endif /* MODULE_NANOCOAP_SERVER_SEPARATE */ #ifdef MODULE_NANOCOAP_SERVER_OBSERVE static ssize_t _time_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context) diff --git a/examples/nanocoap_server/main.c b/examples/nanocoap_server/main.c index 72dba3209f49..9114a7dbc667 100644 --- a/examples/nanocoap_server/main.c +++ b/examples/nanocoap_server/main.c @@ -22,11 +22,23 @@ #include "net/nanocoap_sock.h" #include "ztimer.h" +#if MODULE_NANOCOAP_SERVER_TCP +# include "event/thread.h" +#endif + #define COAP_INBUF_SIZE (256U) #define MAIN_QUEUE_SIZE (8) static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; +#if MODULE_NANOCOAP_SERVER_TCP +static nanocoap_tcp_server_ctx_t tcp_ctx; +#endif + +#if MODULE_NANOCOAP_SERVER_WS && MODULE_NANOCOAP_WS_UDP_YOLO +static coap_ws_over_udp_yolo_init_arg_t _ws_ctx; +#endif + extern void setup_observe_event(void); int main(void) @@ -48,10 +60,24 @@ int main(void) netifs_print_ipv6("\", \""); puts("\"]}"); +#if MODULE_NANOCOAP_SERVER_TCP + nanocoap_server_tcp(&tcp_ctx, EVENT_PRIO_MEDIUM, NULL); + printf("CoAP+TCP on PORT %u\n", (unsigned)tcp_ctx.local.port); +#endif + +#if MODULE_NANOCOAP_SERVER_WS && MODULE_NANOCOAP_WS_UDP_YOLO + sock_udp_ep_t local_ws = { .port = 1337, .family = AF_INET6 }; + nanocoap_server_ws(&coap_ws_over_udp_yolo, &_ws_ctx, &local_ws, sizeof(local_ws)); + printf("CoAP+YOLO on PORT %u\n", (unsigned)local_ws.port); +#endif + +#if MODULE_NANOCOAP_UDP /* initialize nanocoap server instance */ uint8_t buf[COAP_INBUF_SIZE]; sock_udp_ep_t local = { .port=COAP_PORT, .family=AF_INET6 }; - nanocoap_server(&local, buf, sizeof(buf)); + printf("CoAP (UDP) on PORT %u\n", (unsigned)local.port); + nanocoap_server_udp(&local, buf, sizeof(buf)); +#endif /* should be never reached */ return 0; diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index d185707e1069..c220b1b03fb6 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -312,6 +312,9 @@ PSEUDOMODULES += nanocoap_% PSEUDOMODULES += nanocoap_fileserver_callback PSEUDOMODULES += nanocoap_fileserver_delete PSEUDOMODULES += nanocoap_fileserver_put +PSEUDOMODULES += nanocoap_token_ext +PSEUDOMODULES += nanocoap_tcp +PSEUDOMODULES += nanocoap_udp PSEUDOMODULES += netdev_default PSEUDOMODULES += netdev_ieee802154_% PSEUDOMODULES += netdev_ieee802154_rx_timestamp diff --git a/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c b/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c index 0865ec183a71..84767fb2ce0d 100644 --- a/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c +++ b/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c @@ -24,6 +24,9 @@ #include "lwip/api.h" #include "lwip/opt.h" +#define ENABLE_DEBUG 0 +#include "debug.h" + static inline void _tcp_sock_init(sock_tcp_t *sock, struct netconn *conn, sock_tcp_queue_t *queue) { @@ -293,6 +296,9 @@ int sock_tcp_accept(sock_tcp_queue_t *queue, sock_tcp_t **sock, ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len, uint32_t timeout) { + DEBUG("sock_tcp_read(sock, data, max_len=%u, timeout=%" PRIu32 ")\n", + (unsigned)max_len, timeout); + struct pbuf *buf; ssize_t recvd = 0; ssize_t res = 0; @@ -322,6 +328,7 @@ ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len, if ((timeout == 0) && !mbox_avail(&sock->base.conn->recvmbox.mbox)) { mutex_unlock(&sock->mutex); + DEBUG_PUTS("sock_tcp_read(): -EAGAIN"); return -EAGAIN; } @@ -333,6 +340,7 @@ ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len, else { err_t err; if ((err = netconn_recv_tcp_pbuf(sock->base.conn, &buf)) < 0) { + DEBUG("sock_tcp_read(): %d", (int)err); switch (err) { case ERR_ABRT: res = -ECONNABORTED; @@ -400,6 +408,17 @@ ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len, #endif netconn_set_nonblocking(sock->base.conn, false); mutex_unlock(&sock->mutex); + + DEBUG("sock_tcp_read(): %d\n", (int)res); + if (ENABLE_DEBUG && (res > 0)) { + DEBUG(" "); + unsigned bytes_to_print = (res > 8) ? 8 : res; + for (unsigned i = 0; i < bytes_to_print; i++) { + DEBUG(" %02X", (unsigned)((uint8_t *)data)[i]); + } + DEBUG_PUTS((res > 8) ? "..." : ""); + } + return res; } @@ -408,6 +427,16 @@ ssize_t sock_tcp_write(sock_tcp_t *sock, const void *data, size_t len) struct netconn *conn; int res = 0; + DEBUG("sock_tcp_write(sock, data, %u)\n", (unsigned)len); + if (ENABLE_DEBUG) { + DEBUG(" "); + unsigned bytes_to_print = (len > 8) ? 8 : len; + for (unsigned i = 0; i < bytes_to_print; i++) { + DEBUG(" %02X", (unsigned)((uint8_t *)data)[i]); + } + DEBUG_PUTS((len > 8) ? "..." : ""); + } + assert(sock != NULL); assert((len == 0) || (data != NULL)); /* (len != 0) => (data != NULL) */ mutex_lock(&sock->mutex); @@ -423,6 +452,8 @@ ssize_t sock_tcp_write(sock_tcp_t *sock, const void *data, size_t len) NULL) so we can leave the mutex */ res = lwip_sock_send(conn, data, len, 0, NULL, NETCONN_TCP); + DEBUG("sock_tcp_write(): %d\n", (int)res); + return res; } diff --git a/sys/Makefile.dep b/sys/Makefile.dep index db8cef9c2a27..ba274f690d98 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -114,6 +114,7 @@ ifneq (,$(filter dhcpv6_relay,$(USEMODULE))) DEFAULT_MODULE += auto_init_dhcpv6_relay USEMODULE += event USEMODULE += sock_async_event + USEMODULE += sock_async USEMODULE += sock_udp endif @@ -525,6 +526,11 @@ ifneq (,$(filter nanocoap_dtls,$(USEMODULE))) USEPKG += tinydtls endif +ifneq (,$(filter nanocoap_proxy,$(USEMODULE))) + USEMODULE += nanocoap_server_separate + USEMODULE += nanocoap_sock +endif + ifneq (,$(filter nanocoap_server_auto_init,$(USEMODULE))) USEMODULE += nanocoap_server endif @@ -538,13 +544,35 @@ ifneq (,$(filter nanocoap_server_separate,$(USEMODULE))) USEMODULE += sock_aux_local endif +ifneq (,$(filter nanocoap_server_tcp,$(USEMODULE))) + USEMODULE += nanocoap_server + USEMODULE += nanocoap_tcp + USEMODULE += sock_async_event +endif + +ifneq (,$(filter nanocoap_server_ws,$(USEMODULE))) + USEMODULE += nanocoap_server + USEMODULE += nanocoap_ws + USEMODULE += event +endif + ifneq (,$(filter nanocoap_server,$(USEMODULE))) USEMODULE += nanocoap_resources USEMODULE += nanocoap_sock endif -ifneq (,$(filter nanocoap_sock,$(USEMODULE))) +ifneq (,$(filter nanocoap_ws_udp_yolo,$(USEMODULE))) USEMODULE += sock_udp + USEMODULE += sock_async +endif + +ifneq (,$(filter nanocoap_sock,$(USEMODULE))) + ifneq (,$(filter nanocoap_udp,$(USEMODULE))) + USEMODULE += sock_udp + endif + ifneq (,$(filter nanocoap_tcp,$(USEMODULE))) + USEMODULE += sock_tcp + endif USEMODULE += sock_util USEMODULE += ztimer_msec endif @@ -573,6 +601,13 @@ ifneq (,$(filter nanocoap_%,$(USEMODULE))) USEMODULE += nanocoap endif +ifneq (,$(filter nanocoap,$(USEMODULE))) + # default to UDP transport if none is selected + ifeq (,$(filter nanocoap_udp nanocoap_tcp nanocoap_dtls,$(USEMODULE))) + USEMODULE += nanocoap_udp + endif +endif + ifneq (,$(filter skald_%,$(USEMODULE))) USEMODULE += skald endif diff --git a/sys/include/net/coap.h b/sys/include/net/coap.h index 797d4d7f8a73..da8b26e60d6c 100644 --- a/sys/include/net/coap.h +++ b/sys/include/net/coap.h @@ -237,6 +237,37 @@ typedef enum { #define COAP_CODE_PROXYING_NOT_SUPPORTED ((5 << 5) | 5) /** @} */ +/** + * @name CoAP Signaling Codes + * + * See [RFC 8323 Section 5](https://www.rfc-editor.org/rfc/rfc8323#section-5) + * @{ + */ +#define COAP_CLASS_SIGNAL (7) +#define COAP_CODE_SIGNAL_CSM ((7 << 5) | 1) +#define COAP_CODE_SIGNAL_PING ((7 << 5) | 2) +#define COAP_CODE_SIGNAL_PONG ((7 << 5) | 3) +#define COAP_CODE_SIGNAL_RELEASE ((7 << 5) | 4) +#define COAP_CODE_SIGNAL_ABORT ((7 << 5) | 5) +/** @} */ + +/** + * @name CoAP Signaling Option Numbers + * + * This Option Numbers only apply to CoAP messages with code + * @ref COAP_CODE_SIGNAL_CSM. See + * [RFC 8323 Section 11.2](https://www.rfc-editor.org/rfc/rfc8323#section-11.2) + * + * @note This option numbers may collide with the numeric values of normal + * CoAP Options. This is by design; the CSM Signalling Messages use + * a distinctive CoAP Option Number space. + * @{ + */ +#define COAP_SIGNAL_CSM_OPT_MAX_MESSAGE_SIZE 2 /**< Signals the max message size */ +#define COAP_SIGNAL_CSM_OPT_BLOCK_WISE_TRANSFER 4 /**< zero-length flag to indicate support for block-wise transfers */ +#define COAP_SIGNAL_CSM_OPT_EXTENDED_TOKEN_LENGTH 6 /**< Signals the length of token willing to process */ +/** @} */ + /** * @name Content-Format option codes * @anchor net_coap_format @@ -540,6 +571,7 @@ typedef enum { * @brief Marks the boundary between header and payload */ #define COAP_PAYLOAD_MARKER (0xFF) +#define COAP_PAYLOAD_MARKER_SIZE (1U) /**< Size of the payload marker */ /** * @defgroup net_coap_conf CoAP compile configurations diff --git a/sys/include/net/gcoap.h b/sys/include/net/gcoap.h index 2dd8de986927..a487573e54e9 100644 --- a/sys/include/net/gcoap.h +++ b/sys/include/net/gcoap.h @@ -401,14 +401,15 @@ #include "event/callback.h" #include "event/timeout.h" -#include "net/ipv6/addr.h" #include "net/sock/udp.h" +#include "net/nanocoap.h" + #if IS_USED(MODULE_GCOAP_DTLS) -#include "net/sock/dtls.h" +# include "net/sock/dtls.h" +#endif +#if IS_USED(MODULE_NANOCOAP_CACHE) +# include "net/nanocoap/cache.h" #endif -#include "net/nanocoap.h" -#include "net/nanocoap/cache.h" -#include "timex.h" #ifdef __cplusplus extern "C" { @@ -480,7 +481,7 @@ extern "C" { /** * @brief Maximum length in bytes for a header, including the token */ -#define GCOAP_HEADER_MAXLEN (sizeof(coap_hdr_t) + GCOAP_TOKENLEN_MAX) +#define GCOAP_HEADER_MAXLEN (sizeof(coap_udp_hdr_t) + GCOAP_TOKENLEN_MAX) /** * @ingroup net_gcoap_conf @@ -1217,22 +1218,37 @@ sock_dtls_t *gcoap_get_sock_dtls(void); #endif /** - * @brief Get the header of a request from a @ref gcoap_request_memo_t + * @brief Get the buffer from a @ref gcoap_request_memo_t * * @param[in] memo A request memo. Must not be NULL. * - * @return The request header for the given request memo. + * @return The buffer storing the message */ -static inline coap_hdr_t *gcoap_request_memo_get_hdr(const gcoap_request_memo_t *memo) +static inline uint8_t *gcoap_request_memo_get_buf(gcoap_request_memo_t *memo) { if (memo->send_limit == GCOAP_SEND_LIMIT_NON) { - return (coap_hdr_t *)&memo->msg.hdr_buf[0]; + return &memo->msg.hdr_buf[0]; } else { - return (coap_hdr_t *)memo->msg.data.pdu_buf; + return memo->msg.data.pdu_buf; } } +/** + * @brief Get the header of a request from a @ref gcoap_request_memo_t + * + * @param[in] memo A request memo. Must not be NULL. + * + * @return The request header for the given request memo. + * + * @deprecated Use @ref gcoap_request_memo_get_buf instead + */ +static inline coap_udp_hdr_t *gcoap_request_memo_get_hdr(const gcoap_request_memo_t *memo) +{ + gcoap_request_memo_t *evil_cast_is_evil = (gcoap_request_memo_t *)memo; + return (coap_udp_hdr_t *)gcoap_request_memo_get_buf(evil_cast_is_evil); +} + #ifdef __cplusplus } #endif diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 4ae8107736e3..cb772a92d10f 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -20,7 +20,8 @@ * * nanocoap includes the core structs to store message information. It also * provides helper functions for use before sending and after receiving a - * message, such as coap_parse() to read an incoming message. + * message, such as coap_parse_udp() / coap_parse_tcp() / coap_parse_ws() to + * read an incoming message. * * ## Application APIs * @@ -78,7 +79,6 @@ #define NET_NANOCOAP_H #include -#include #include #include #include @@ -91,19 +91,27 @@ #include "byteorder.h" #include "iolist.h" #include "macros/utils.h" -#include "net/coap.h" #include "modules.h" +#include "net/coap.h" +#include "net/nanocoap_ws.h" #else #include "coap.h" #include #endif +/* work around to get unit tests compiling without pulling in a network stack */ #if defined(MODULE_SOCK_UDP) || defined(DOXYGEN) -#include "net/sock/udp.h" +# include "net/sock/udp.h" #else typedef void sock_udp_ep_t; #endif +#if MODULE_SOCK_TCP || defined(DOXYGEN) +# include "net/sock/tcp.h" +#elif !MODULE_GNRC_SOCK +typedef void sock_tcp_ep_t; +#endif + #if defined(MODULE_NANOCOAP_RESOURCES) #include "xfa.h" #endif @@ -195,14 +203,85 @@ extern "C" { #define COAP_OPT_FINISH_PAYLOAD (0x0001) /** @} */ +#ifndef MODULE_NANOCOAP_UDP +# define MODULE_NANOCOAP_UDP 0 +#endif + +#ifndef MODULE_NANOCOAP_DTLS +# define MODULE_NANOCOAP_DTLS 0 +#endif + +#ifndef MODULE_NANOCOAP_TCP +# define MODULE_NANOCOAP_TCP 0 +# define NANOCOAP_EXPOSE_DEPRECATED_API 0 +#endif + +#ifndef MODULE_NANOCOAP_WS +# define MODULE_NANOCOAP_WS 0 +# define NANOCOAP_EXPOSE_DEPRECATED_API 0 +#endif + +#ifndef NANOCOAP_EXPOSE_DEPRECATED_API +# define NANOCOAP_EXPOSE_DEPRECATED_API 1 +#endif + +/** + * @brief Number of transports enabled at compile-time + */ +#define NANOCOAP_ENABLED_TRANSPORTS \ + (MODULE_NANOCOAP_UDP + MODULE_NANOCOAP_DTLS + MODULE_NANOCOAP_TCP + MODULE_NANOCOAP_WS) + +/** + * + * @brief When building TCP packets, we build a tentative header that is + * replaced by the correct header before sending. + * + * The reason is that the length of the options + payload is not known in + * advance, so we have to finalize the header later on. + * + * We have: + * Len | TKL = 1 Byte + * Extended Length = 2 Byte (0-4 Byte, we assume 2 is the worst case on MCUs) + * Code = 1 Byte + * ------------------------- + * Total = 4 Byte + */ +#define COAP_TCP_TENTATIVE_HEADER_SIZE 4 + /** - * @brief Raw CoAP PDU header structure + * @brief Size of the CoAP over WebSocket header (excluding the Token and + * extended TKL field) + */ +#define COAP_WS_HEADER_SIZE 2 + +/** + * @brief Raw CoAP over UDP PDU header structure */ typedef struct __attribute__((packed)) { uint8_t ver_t_tkl; /**< version, token, token length */ uint8_t code; /**< CoAP code (e.g.m 205) */ uint16_t id; /**< Req/resp ID */ -} coap_hdr_t; +} coap_udp_hdr_t; + +#if NANOCOAP_EXPOSE_DEPRECATED_API +/** + * @brief Alias for @ref coap_udp_hdr_t for backward compatibility + * + * @deprecated Avoid using low level types in your application, but rather use + * the zero overhead wrappers and work on @ref coap_pkt_t instead. + */ +typedef coap_udp_hdr_t coap_hdr_t; +#endif + +/** + * @brief Enumeration of CoAP transports supported by nanocoap + */ +typedef enum { + COAP_TRANSPORT_UDP, /**< CoAP over UDP (see RFC 7252) */ + COAP_TRANSPORT_DTLS, /**< CoAP over DTLS (see RFC 7252) */ + COAP_TRANSPORT_TCP, /**< CoAP over TCP (see RFC 8323) */ + COAP_TRANSPORT_WS, /**< CoAP over WebSocket (see RFC 8323) */ +} coap_transport_t; /** * @brief CoAP option array entry @@ -215,9 +294,10 @@ typedef struct { /** * @brief CoAP PDU parsing context structure * - * When this struct is used to assemble the header, @p payload is used as the - * write pointer and @p payload_len contains the number of free bytes left in - * then packet buffer pointed to by @ref coap_pkt_t::hdr + * When this struct is used to assemble the header, @ref coap_pkt_t::payload is + * used as the write pointer and @ref coap_pkt_t::payload_len contains the + * number of free bytes left in then packet buffer pointed to by @ref + * coap_pkt_t::buf * * When the header was written, @p payload must not be changed, it must remain * pointing to the end of the header. @@ -230,13 +310,44 @@ typedef struct { * (or header byte) in the original CoAP packet buffer. */ typedef struct { - coap_hdr_t *hdr; /**< pointer to raw packet */ + union { + /** + * @brief pointer to the beginning of the buffer holding the pkt + * + * In other words: Pointer to the first byte of the header. + */ + uint8_t *buf; +#if NANOCOAP_EXPOSE_DEPRECATED_API + /** + * @brief Deprecated alias for @ref coap_pkt_t::buf + * + * @warning This alias for @ref coap_pkt_t::buf is not available if a + * non-UDP transport for nanocoap is used, as this has the + * assumption baked in that the beginning of the message + * buffer holds a UDP style CoAP header. + * @deprecated Use @ref coap_pkt_t::buf to access the underlying buffer. + * Use helpers such as @ref coap_get_code_raw to parse the + * contents in a transport agnostic way. + */ + coap_udp_hdr_t *hdr; +#endif + }; uint8_t *payload; /**< pointer to end of the header */ iolist_t *snips; /**< payload snips (optional)*/ uint16_t payload_len; /**< length of payload */ uint16_t options_len; /**< length of options array */ + /** + * @brief Offset and length of the token + * + * This stores the position and length of the CoAP Token. Use the accessors + * @ref coap_get_token_len and @ref coap_get_token and treat the value as + * opaque */ + uint16_t token_pos_and_len; coap_optpos_t options[CONFIG_NANOCOAP_NOPTS_MAX]; /**< option offset array */ BITFIELD(opt_crit, CONFIG_NANOCOAP_NOPTS_MAX); /**< unhandled critical option */ +#if NANOCOAP_ENABLED_TRANSPORTS > 1 || DOXYGEN + coap_transport_t transport; /**< transport this packet is using */ +#endif #ifdef MODULE_GCOAP uint32_t observe_value; /**< observe value */ #endif @@ -323,13 +434,120 @@ typedef const struct { const size_t resources_numof; /**< number of entries in array */ } coap_resource_subtree_t; +/** + * @name Internal helpers + * @{ + */ +/** + * @brief Get the extended token length field from the token length value + * @param[in] tkl The value of the token length field + * @return The length of the extended token length field + * @warning Internal function, use @ref coap_pkt_tkl_ext_len instead. + * @note Yes, this function does have a really long name. Another + * reason to use @ref coap_pkt_tkl_ext_len instead. + */ +static inline uint8_t coap_get_ext_tkl_field_len_from_tkl_val(uint8_t tkl) +{ + if (!IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + return 0; + } + + switch (tkl & 0xf) { + case 13: + return 1; + case 14: + return 2; + case 15: + assert(0); + /* fall-through */ + default: + return 0; + } + +} + +/** + * @brief Get the extended token length field from the token length value + * @param[in] token_length Length of the tokent o encode + * @return The length of the extended token length field + * @warning Internal function, use @ref coap_pkt_tkl_ext_len instead. + * @note Yes, this function does have a really long name. Another + * reason to use @ref coap_pkt_tkl_ext_len instead. + */ +static inline uint8_t coap_get_ext_tkl_field_len_from_token_length(size_t token_length) +{ + if (!IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + return 0; + } + + /* Regarding use unnamed magic numbers 13 and 269: + * - If token length is < 13 it fits into TKL field (4 bit) + * - If token length is < 269 it fits into 8-bit extended TKL field + * - Otherwise token length goes into 16-bit extended TKL field. + * + * (Not using named constants here, as RFC 8974 also has no names for those + * magic numbers.) + * + * See: https://www.rfc-editor.org/rfc/rfc8974#name-extended-token-length-tkl-f + */ + if (token_length < 13) { + return 0; + } + + if (token_length < 269) { + return 1; + } + + return 2; +} + +/** + * @brief Get the length of the packet length field of a CoAP over TCP packet + * @param[in] pkt Packet to get the length of the length field of + * @pre `coap_get_transport(pkt) == COAP_TRANSPORT_TCP` + * @retval 0 No extended length field present + * @retval 1 An 8 bit extended length field is present + * @retval 2 A 16 bit extended length field is present + * @retval 4 A 32 bit extended length field is present + */ +static inline uint8_t coap_tcp_get_ext_len(const coap_pkt_t *pkt) +{ + switch (pkt->buf[0] >> 4) { + default: + /* no extended length field */ + return 0; + case 13: + /* 8-bit extended length field */ + return 1; + case 14: + /* 16-bit extended length field */ + return 2; + case 15: + /* 32-bit extended length field */ + return 4; + } +} +/** @} */ + /** * @brief Initialize CoAP request context * * @param[in] ctx Pointer to the request context to initialize * @param[in] remote Remote endpoint that made the request + * + * @note This function is only provided when module `nanocoap_udp` is used + */ +void coap_request_ctx_init_udp(coap_request_ctx_t *ctx, sock_udp_ep_t *remote); + +/** + * @brief Alias for @ref coap_request_ctx_init + * + * @deprecated Use @ref coap_request_ctx_init_udp instead */ -void coap_request_ctx_init(coap_request_ctx_t *ctx, sock_udp_ep_t *remote); +static inline void coap_request_ctx_init(coap_request_ctx_t *ctx, sock_udp_ep_t *remote) +{ + return coap_request_ctx_init_udp(ctx, remote); +} /** * @brief CoAP resource request handler context @@ -337,10 +555,28 @@ void coap_request_ctx_init(coap_request_ctx_t *ctx, sock_udp_ep_t *remote); */ struct _coap_request_ctx { const coap_resource_t *resource; /**< resource of the request */ - sock_udp_ep_t *remote; /**< remote endpoint of the request */ -#if defined(MODULE_SOCK_AUX_LOCAL) || DOXYGEN - sock_udp_ep_t *local; /**< local endpoint of the request */ + union { +#if MODULE_NANOCOAP_UDP || MODULE_NANOCOAP_DTLS || DOXYGEN + struct { + union { + sock_udp_ep_t *remote_udp; /**< remote UDP endpoint of the request */ + sock_udp_ep_t *remote; /**< deprecated alias for request_udp */ + }; +# if defined(MODULE_SOCK_AUX_LOCAL) || DOXYGEN + union { + sock_udp_ep_t *local_udp; /**< local UDP endpoint of the request */ + sock_udp_ep_t *local; /**< deprecated alias for local_udp */ + }; +# endif + }; +#endif +#if MODULE_NANOCOAP_TCP + sock_tcp_t *sock_tcp; /**< TCP socket the request was received on */ #endif +#if MODULE_NANOCOAP_WS + coap_ws_conn_t *conn_ws; /**< WebSocket connection the request was received over */ +#endif + }; #if defined(MODULE_GCOAP) || DOXYGEN /** * @brief transport the packet was received over @@ -353,9 +589,44 @@ struct _coap_request_ctx { }; /* forward declarations */ -static inline uint8_t *coap_hdr_data_ptr(const coap_hdr_t *hdr); -static inline size_t coap_hdr_get_token_len(const coap_hdr_t *hdr); -static inline const void * coap_hdr_get_token(const coap_hdr_t *hdr); +static inline uint8_t *coap_hdr_data_ptr(const coap_udp_hdr_t *hdr); +static inline size_t coap_hdr_get_token_len(const coap_udp_hdr_t *hdr); +static inline const void *coap_hdr_get_token(const coap_udp_hdr_t *hdr); + +/** + * @brief Retrieve the transport of a CoAP packet + * @param[in] pkt The packet to retrieve the transport of + * @return The transport the packet is using + */ +static inline coap_transport_t coap_get_transport(const coap_pkt_t *pkt) +{ + (void)pkt; +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + return pkt->transport; +#elif MODULE_NANOCOAP_DTLS + return COAP_TRANSPORT_DTLS; +#elif MODULE_NANOCOAP_TCP + return COAP_TRANSPORT_TCP; +#elif MODULE_NANOCOAP_WS + return COAP_TRANSPORT_WS; +#else + return COAP_TRANSPORT_UDP; +#endif +} + +/** + * @brief Set the transport of a CoAP packet + * @param[in] pkt The packet to retrieve the transport of + * @return The transport the packet is using + */ +static inline void coap_set_transport(coap_pkt_t *pkt, coap_transport_t transport) +{ + (void)pkt; + (void)transport; +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + pkt->transport = transport; +#endif +} /** * @brief Get resource path associated with a CoAP request @@ -456,6 +727,33 @@ extern const unsigned coap_resources_numof; * Includes message ID, code, type, token, CoAP version */ /**@{*/ +/** + * @brief Get the CoAP header of a CoAP over UDP (or CoAP over DTLS) packet + * @param[in] pkt The packet to get the header of + * @return A pointer to the header in the packet + * @retval NULL The packet is not using UDP/DTLS as transport + */ +static inline coap_udp_hdr_t *coap_get_udp_hdr(coap_pkt_t *pkt) +{ + if ((unsigned)coap_get_transport(pkt) <= (unsigned)COAP_TRANSPORT_DTLS) { + return (coap_udp_hdr_t *)pkt->buf; + } + + return NULL; +} + +/** + * @brief Same as @ref coap_get_udp_hdr but for `const` memory + */ +static inline const coap_udp_hdr_t *coap_get_udp_hdr_const(const coap_pkt_t *pkt) +{ + if ((unsigned)coap_get_transport(pkt) <= (unsigned)COAP_TRANSPORT_DTLS) { + return (const coap_udp_hdr_t *)pkt->buf; + } + + return NULL; +} + /** * @brief Encode given code class and code detail to raw code * @@ -469,6 +767,25 @@ static inline uint8_t coap_code(unsigned cls, unsigned detail) return (cls << 5) | detail; } +/** + * @brief Get a message's raw code (class + detail) + * + * @param[in] pkt CoAP packet + * + * @returns raw message code + */ +static inline unsigned coap_get_code_raw(const coap_pkt_t *pkt) +{ + const coap_udp_hdr_t *hdr = coap_get_udp_hdr_const(pkt); + + if (hdr) { + return coap_get_udp_hdr_const(pkt)->code; + } + + /* CoAP over TCP / WebSocket */ + return pkt->buf[1 + coap_tcp_get_ext_len(pkt)]; +} + /** * @brief Get a message's code class (3 most significant bits of code) * @@ -478,7 +795,7 @@ static inline uint8_t coap_code(unsigned cls, unsigned detail) */ static inline unsigned coap_get_code_class(const coap_pkt_t *pkt) { - return pkt->hdr->code >> 5; + return coap_get_code_raw(pkt) >> 5; } /** @@ -490,7 +807,7 @@ static inline unsigned coap_get_code_class(const coap_pkt_t *pkt) */ static inline unsigned coap_get_code_detail(const coap_pkt_t *pkt) { - return pkt->hdr->code & 0x1f; + return coap_get_code_raw(pkt) & 0x1f; } /** @@ -505,18 +822,6 @@ static inline unsigned coap_get_code_decimal(const coap_pkt_t *pkt) return (coap_get_code_class(pkt) * 100) + coap_get_code_detail(pkt); } -/** - * @brief Get a message's raw code (class + detail) - * - * @param[in] pkt CoAP packet - * - * @returns raw message code - */ -static inline unsigned coap_get_code_raw(const coap_pkt_t *pkt) -{ - return (unsigned)pkt->hdr->code; -} - /** * @brief Get a request's method type * @@ -526,19 +831,51 @@ static inline unsigned coap_get_code_raw(const coap_pkt_t *pkt) */ static inline coap_method_t coap_get_method(const coap_pkt_t *pkt) { - return pkt->hdr->code; + return coap_get_code_raw(pkt); } /** * @brief Get the message ID of the given CoAP packet * - * @param[in] pkt CoAP packet + * @param[in] pkt CoAP packet to get the ID of * * @returns message ID */ static inline unsigned coap_get_id(const coap_pkt_t *pkt) { - return ntohs(pkt->hdr->id); + switch (coap_get_transport(pkt)) { + default: + case COAP_TRANSPORT_UDP: + case COAP_TRANSPORT_DTLS: + return ntohs(coap_get_udp_hdr_const(pkt)->id); + case COAP_TRANSPORT_TCP: + case COAP_TRANSPORT_WS: + /* there are no message IDs in CoAP over TCP/WS, they are inherently + * reliable */ + return 0; + } +} + +/** + * @brief Set the message ID of the given CoAP packet + * + * @param[out] pkt CoAP packet to write the ID to + * @param[in] id Message ID to write (in host byte order) + */ +static inline void coap_set_id(coap_pkt_t *pkt, uint16_t id) +{ + switch (coap_get_transport(pkt)) { + default: + case COAP_TRANSPORT_UDP: + case COAP_TRANSPORT_DTLS: + coap_get_udp_hdr(pkt)->id = ntohs(id); + break; + case COAP_TRANSPORT_TCP: + case COAP_TRANSPORT_WS: + /* there are no message IDs in CoAP over TCP/WS, they are inherently + * reliable */ + return; + } } /** @@ -553,7 +890,7 @@ static inline unsigned coap_get_id(const coap_pkt_t *pkt) */ static inline unsigned coap_get_token_len(const coap_pkt_t *pkt) { - return coap_hdr_get_token_len(pkt->hdr); + return pkt->token_pos_and_len & 0xfff; } /** @@ -565,7 +902,9 @@ static inline unsigned coap_get_token_len(const coap_pkt_t *pkt) */ static inline void *coap_get_token(const coap_pkt_t *pkt) { - return coap_hdr_data_ptr(pkt->hdr); + size_t offset = pkt->token_pos_and_len >> 12U; + assert(offset > 0); + return pkt->buf + offset; } /** @@ -579,7 +918,7 @@ static inline void *coap_get_token(const coap_pkt_t *pkt) */ static inline unsigned coap_get_total_len(const coap_pkt_t *pkt) { - return (uintptr_t)pkt->payload - (uintptr_t)pkt->hdr + pkt->payload_len; + return (uintptr_t)pkt->payload - (uintptr_t)pkt->buf + pkt->payload_len; } /** @@ -594,7 +933,14 @@ static inline unsigned coap_get_total_len(const coap_pkt_t *pkt) */ static inline unsigned coap_get_type(const coap_pkt_t *pkt) { - return (pkt->hdr->ver_t_tkl & 0x30) >> 4; + const coap_udp_hdr_t *hdr = coap_get_udp_hdr_const(pkt); + if (hdr) { + return (hdr->ver_t_tkl & 0x30) >> 4; + } + + /* CoAP over TCP/WebSockets do not need to be confirmed: TCP + * is reliable anyway. */ + return COAP_TYPE_NON; } /** @@ -602,40 +948,49 @@ static inline unsigned coap_get_type(const coap_pkt_t *pkt) * * @param[in] pkt CoAP packet * - * @returns CoAP version number + * @return CoAP version number */ static inline unsigned coap_get_ver(const coap_pkt_t *pkt) { - return (pkt->hdr->ver_t_tkl & 0x60) >> 6; + const coap_udp_hdr_t *hdr = coap_get_udp_hdr_const(pkt); + if (hdr) { + return (pkt->buf[0] & 0x60) >> 6; + } + + return COAP_V1; } /** * @brief Get the size of the extended Token length field * (RFC 8974) * + * @deprecated Use @ref coap_pkt_tkl_ext_len instead. + * * @note This requires the `nanocoap_token_ext` module to be enabled * * @param[in] hdr CoAP header * * @returns number of bytes used for extended token length */ -static inline uint8_t coap_hdr_tkl_ext_len(const coap_hdr_t *hdr) +static inline uint8_t coap_hdr_tkl_ext_len(const coap_udp_hdr_t *hdr) { - if (!IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { - return 0; - } + return coap_get_ext_tkl_field_len_from_tkl_val(hdr->ver_t_tkl & 0xf); +} - switch (hdr->ver_t_tkl & 0xf) { - case 13: - return 1; - case 14: - return 2; - case 15: - assert(0); - /* fall-through */ - default: - return 0; - } +/** + * @brief Get the size of the extended Token length field + * (RFC 8974) + * + * @note This requires the `nanocoap_token_ext` module to be enabled + * + * @param[in] pkt CoAP packet + * + * @returns number of bytes used for extended token length + */ +static inline uint8_t coap_pkt_tkl_ext_len(const coap_pkt_t *pkt) +{ + /* for all supported transports TKL is the second nimble of the header */ + return coap_get_ext_tkl_field_len_from_tkl_val(pkt->buf[0] & 0x0f); } /** @@ -643,15 +998,17 @@ static inline uint8_t coap_hdr_tkl_ext_len(const coap_hdr_t *hdr) * * @param[in] hdr Header of CoAP packet in contiguous memory * + * @deprecated Use coap_get_token() instead + * * @returns pointer to first byte after the header */ -static inline uint8_t *coap_hdr_data_ptr(const coap_hdr_t *hdr) +static inline uint8_t *coap_hdr_data_ptr(const coap_udp_hdr_t *hdr) { - return ((uint8_t *)hdr) + sizeof(coap_hdr_t) + coap_hdr_tkl_ext_len(hdr); + return ((uint8_t *)hdr) + sizeof(coap_udp_hdr_t) + coap_hdr_tkl_ext_len(hdr); } /** - * @brief Get the total header length (4-byte header + token length) + * @brief Get the total header length * * @param[in] pkt CoAP packet * @@ -659,8 +1016,12 @@ static inline uint8_t *coap_hdr_data_ptr(const coap_hdr_t *hdr) */ static inline unsigned coap_get_total_hdr_len(const coap_pkt_t *pkt) { - return sizeof(coap_hdr_t) + coap_hdr_tkl_ext_len(pkt->hdr) + - coap_get_token_len(pkt); + /* The token is the last field of the header for all header formats. + * Hence, the offset of the end of the token field is the length of + * the total header */ + size_t token_offset = pkt->token_pos_and_len >> 12U; + size_t token_len = pkt->token_pos_and_len & 0xfff; + return token_offset + token_len; } /** @@ -676,7 +1037,23 @@ static inline unsigned coap_get_total_hdr_len(const coap_pkt_t *pkt) */ static inline unsigned coap_get_response_hdr_len(const coap_pkt_t *pkt) { - return coap_get_total_hdr_len(pkt); + switch (coap_get_transport(pkt)) { + default: + case COAP_TRANSPORT_UDP: + case COAP_TRANSPORT_DTLS: + case COAP_TRANSPORT_WS: + /* header length depends only on token length, which matches between + * request and response */ + return coap_get_total_hdr_len(pkt); + case COAP_TRANSPORT_TCP: + /* header length also depends on payload length, which may differ + * between request and response */ + break; + } + + size_t token_len = pkt->token_pos_and_len & 0xfff; + return COAP_TCP_TENTATIVE_HEADER_SIZE + token_len + + coap_get_ext_tkl_field_len_from_token_length(token_len); } /** @@ -684,8 +1061,10 @@ static inline unsigned coap_get_response_hdr_len(const coap_pkt_t *pkt) * * @param[out] hdr CoAP header to write to * @param[in] code raw message code + * + * @deprecated Use @ref coap_pkt_set_code instead */ -static inline void coap_hdr_set_code(coap_hdr_t *hdr, uint8_t code) +static inline void coap_hdr_set_code(coap_udp_hdr_t *hdr, uint8_t code) { hdr->code = code; } @@ -698,7 +1077,20 @@ static inline void coap_hdr_set_code(coap_hdr_t *hdr, uint8_t code) */ static inline void coap_pkt_set_code(coap_pkt_t *pkt, uint8_t code) { - coap_hdr_set_code(pkt->hdr, code); + switch (coap_get_transport(pkt)) { + case COAP_TRANSPORT_UDP: + case COAP_TRANSPORT_DTLS: + coap_hdr_set_code(coap_get_udp_hdr(pkt), code); + break; + case COAP_TRANSPORT_TCP: + /* the position of the message code is not fixed + * when using CoAP over TCP */ + pkt->buf[2 + coap_tcp_get_ext_len(pkt)] = code; + break; + case COAP_TRANSPORT_WS: + pkt->buf[2] = code; + break; + } } /** @@ -708,8 +1100,10 @@ static inline void coap_pkt_set_code(coap_pkt_t *pkt, uint8_t code) * * @param[out] hdr CoAP header to write * @param[in] type message type as integer value [0-3] + * + * @deprecated Use @ref coap_pkt_set_type instead */ -static inline void coap_hdr_set_type(coap_hdr_t *hdr, unsigned type) +static inline void coap_hdr_set_type(coap_udp_hdr_t *hdr, unsigned type) { /* assert correct range of type */ assert(!(type & ~0x3)); @@ -733,7 +1127,7 @@ static inline void coap_hdr_set_type(coap_hdr_t *hdr, unsigned type) * @ref coap_get_token instead. In the TX path the token was * added by us, so we really should know. */ -static inline size_t coap_hdr_get_token_len(const coap_hdr_t *hdr) +static inline size_t coap_hdr_get_token_len(const coap_udp_hdr_t *hdr) { const uint8_t *buf = (const void *)hdr; /* Regarding use unnamed magic numbers 13 and 269: @@ -750,9 +1144,9 @@ static inline size_t coap_hdr_get_token_len(const coap_hdr_t *hdr) case 0: return hdr->ver_t_tkl & 0xf; case 1: - return buf[sizeof(coap_hdr_t)] + 13; + return buf[sizeof(coap_udp_hdr_t)] + 13; case 2: - return byteorder_bebuftohs(buf + sizeof(coap_hdr_t)) + 269; + return byteorder_bebuftohs(buf + sizeof(coap_udp_hdr_t)) + 269; } return 0; @@ -773,7 +1167,7 @@ static inline size_t coap_hdr_get_token_len(const coap_hdr_t *hdr) * @ref coap_get_token instead. In the TX path the token was * added by us, so we really should know. */ -static inline const void * coap_hdr_get_token(const coap_hdr_t *hdr) +static inline const void * coap_hdr_get_token(const coap_udp_hdr_t *hdr) { uint8_t *token = (void *)hdr; token += sizeof(*hdr) + coap_hdr_tkl_ext_len(hdr); @@ -794,10 +1188,47 @@ static inline const void * coap_hdr_get_token(const coap_hdr_t *hdr) * was created by us (e.g. using @ref coap_build_hdr which returns * the header size), so we really should know already. */ -static inline size_t coap_hdr_len(const coap_hdr_t *hdr) +static inline size_t coap_hdr_len(const coap_udp_hdr_t *hdr) { return sizeof(*hdr) + coap_hdr_tkl_ext_len(hdr) + coap_hdr_get_token_len(hdr); } + +/** + * @brief Set the message type for the given CoAP packet + * + * @pre (type := [0-3]) + * + * @param[out] pkt CoAP packet to write to + * @param[in] type message type as integer value [0-3] + */ +static inline void coap_pkt_set_type(coap_pkt_t *pkt, unsigned type) +{ + coap_udp_hdr_t *hdr = coap_get_udp_hdr(pkt); + + /* only set type on UDP/DTLS, as TCP/WebSocket do not + * support message types */ + if (hdr) { + coap_hdr_set_type(hdr, type); + } +} + +/** + * @brief Set the message token length for the given CoAP packet + * + * @pre `tkl <= 8` + * + * @param[out] pkt CoAP packet to write to + * @param[in] tkl Token length to write + * + * @warning This function is internal, no out of tree users please. + */ +static inline void coap_pkt_set_tkl(coap_pkt_t *pkt, uint8_t tkl) +{ + /* TKL is in the same position for UDP/DTLS/TCP/WebSocket, so + * we do not need to dispatch */ + pkt->buf[0] &= 0xf0; + pkt->buf[0] |= (tkl & 0x0f); +} /**@}*/ /** @@ -2048,6 +2479,163 @@ ssize_t coap_block2_build_reply(coap_pkt_t *pkt, unsigned code, uint8_t *rbuf, unsigned rlen, unsigned payload_len, coap_block_slicer_t *slicer); +/** + * @brief Build a CoAP over UDP packet + * + * @param[out] pkt Destination packet (may be NULL) + * @param[out] buf Destination buffer to write to + * @param[in] buf_len Length of @p buf in bytes + * @param[in] type CoAP packet type (e.g., COAP_TYPE_CON, ...) + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * @param[in] id CoAP request id + * + * @returns length of resulting header + */ +ssize_t coap_build_udp(coap_pkt_t *pkt, void *buf, size_t buf_len, uint8_t type, + const void *token, size_t token_len, uint8_t code, uint16_t id); + +/** + * @brief Build a CoAP over UDP header + * + * @param[out] buf Destination buffer to write to + * @param[in] buf_len Length of @p buf in bytes + * @param[in] type CoAP packet type (e.g., COAP_TYPE_CON, ...) + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * @param[in] id CoAP request id + * + * @returns length of resulting header + * + * @note You probably want to use @ref coap_build_udp instead. + */ +static inline ssize_t coap_build_udp_hdr(void *buf, size_t buf_len, uint8_t type, const void *token, + size_t token_len, uint8_t code, uint16_t id) +{ + return coap_build_udp(NULL, buf, buf_len, type, token, token_len, code, id); +} + +/** + * @brief Build a CoAP over TCP packet + * + * @param[out] pkt Destination packet to write to + * @param[out] buf Destination buffer to write to + * @param[in] buf_len Length of @p buf in bytes + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * + * @return Length of the header written into the packet + * + * @warning This will not write a standard compliant CoAP over TCP header yet. + * The final standard compliant header will be generated in-place + * just before sending. + * + * @details Since we do not know the size of the resulting packet at this + * point, the content of the "Len" and "Extended Length" fields + * are not known. Worse: Even the size of the "Extended Length" + * field is unknown. A custom header format is used as place holder + * for the final CoAP over TCP header, that needs to be generated + * in-place just before sending. The custom header will allocate + * enough memory to allow for a 16-bit Extended Length field. We + * that packets larger than ~ 64 KiB will not be send here. + */ +ssize_t coap_build_tcp(coap_pkt_t *pkt, void *buf, size_t buf_len, const void *token, + size_t token_len, uint8_t code); + +/** + * @brief Build a CoAP over TCP packet + * + * @param[out] buf Destination buffer to write to + * @param[in] buf_len Length of @p buf in bytes + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * + * @return length of resulting header + * + * @note You probably want to call @ref coap_build_tcp instead. All warnings + * and info apply here as well. + */ +static inline ssize_t coap_build_tcp_hdr(void *buf, size_t buf_len, + const void *token, size_t token_len, + uint8_t code) +{ + return coap_build_tcp(NULL, buf, buf_len, token, token_len, code); +} + +/** + * @brief Build a CoAP over WebSocket packet + * + * @param[out] pkt Destination packet to write to + * @param[out] buf Destination buffer to write to + * @param[in] buf_len Length of @p buf in bytes + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * + * @return Length of the header written into the packet + */ +ssize_t coap_build_ws(coap_pkt_t *pkt, void *buf, size_t buf_len, const void *token, + size_t token_len, uint8_t code); + +/** + * @brief Build a CoAP over WebSocket packet + * + * @param[out] buf Destination buffer to write to + * @param[in] buf_len Length of @p buf in bytes + * @param[in] token token + * @param[in] token_len length of @p token + * @param[in] code CoAP code (e.g., COAP_CODE_204, ...) + * + * @return length of resulting header + * + * @note You probably want to call @ref coap_build_ws instead. + */ +static inline ssize_t coap_build_ws_hdr(void *buf, size_t buf_len, + const void *token, size_t token_len, + uint8_t code) +{ + return coap_build_ws(NULL, buf, buf_len, token, token_len, code); +} + +/** + * @brief Finalize a CoAP over TCP packet + * + * This converts the tentative CoAP over TCP header without the length info + * into the final CoAP over TCP header. + */ +void coap_finalize_tcp(coap_pkt_t *pkt); + +/** + * @brief Finalize a CoAP over TCP tentative header + * + * This rewrites the CoAP over TCP header with the final one. + * + * @param[in,out] hdr The tentative header to rewrite + * @param[in] data_len The length of the packet without the header + * (counting options, payload marker, and payload) + * @return Number of bytes the header shrank (from the top). + */ +size_t coap_finalize_tcp_header(uint8_t *hdr, size_t data_len); + +/** + * @brief Finalize a CoAP over TCP tentative header + * + * This is more costly than @ref coap_finalize_tcp_header, as the length of the + * data part is parsed from the buffer + * + * @param[in,out] buf Buffer containing CoAP packet with tentative + * header + * @param[in] buf_len Length of the whole packet (header + options + + * payload marker (if any) + payload (if any)) + * in @p buf + * @return Number of bytes the header shrank (from the top) + */ +size_t coap_finalize_tcp_header_in_buf(uint8_t *buf, size_t buf_len); + /** * @brief Builds a CoAP header * @@ -2066,9 +2654,22 @@ ssize_t coap_block2_build_reply(coap_pkt_t *pkt, unsigned code, * the request). * * @returns length of resulting header + * + * @deprecated Use @ref coap_build_udp_hdr instead */ -ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, - size_t token_len, unsigned code, uint16_t id); +static inline ssize_t coap_build_hdr(coap_udp_hdr_t *hdr, unsigned type, const void *token, + size_t token_len, unsigned code, uint16_t id) +{ + size_t fingers_crossed_size = sizeof(*hdr) + token_len; + + if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + /* With RFC 8974, we have an additional extended TKL + * field of 0-4 bytes in length */ + fingers_crossed_size += 4; + } + + return coap_build_udp_hdr(hdr, fingers_crossed_size, type, token, token_len, code, id); +} /** * @brief Build reply to CoAP request @@ -2077,29 +2678,67 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, * will create the reply packet header based on parameters from the request * (e.g., id, token). * - * Passing a non-zero @p payload_len will ensure the payload fits into the - * buffer along with the header. For this validation, payload_len must include - * any options, the payload marker, as well as the payload proper. - * - * @param[in] pkt packet to reply to - * @param[in] code reply code (e.g., COAP_CODE_204) - * @param[out] rbuf buffer to write reply to - * @param[in] rlen size of @p rbuf - * @param[in] payload_len length of payload - * - * @returns size of reply packet on success - * - * Note that this size can be severely shortened if due to a No-Response option there - * is only an empty ACK to be sent back. The caller may just continue populating the - * payload (the space was checked to suffice), but may also skip that needless step - * if the returned length is less than the requested payload length. - * - * @returns 0 if no response should be sent due to a No-Response option in the request - * @returns <0 on error - * @returns -ENOSPC if @p rbuf too small + * Passing a non-zero @p max_data_len will ensure the remaining data fits into + * the buffer along with the header. For this validation, @p max_data_len must + * include any CoAP Options, the payload marker, as well as the payload proper. + * + * @param[in] pkt packet to reply to + * @param[in] code reply code (e.g., COAP_CODE_204) + * @param[out] rbuf buffer to write reply to + * @param[in] rlen size of @p rbuf + * @param[in] max_data_len Length of additional CoAP options, the payload marker and payload + * + * @note The size returned can be severely shortened if due to a + * No-Response option there is only an empty ACK to be sent back. + * The caller may just continue populating the payload (the space + * was checked to suffice), but may also skip that needless step + * if the returned length is less than the requested @p + * max_data_length. + * + * @return @p max_data_len + size of the header written in bytes + * + * @retval 0 No response should be sent due to a No-Response option in the request + * @retval -ENOSPC @p rbuf too small + * @retval <0 other error + * + * Usage: + * + * ```C + * static ssize_t _foo_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, + * coap_request_ctx_t *context) + * { + * static const char *payload = "Hello World"; + * const payload_len = strlen(payload); + * // Worst case estimation of the data to add: + * size_t max_data_len = COAP_OPT_FOO_MAX_LEN + COAP_OPT_BAR_MAX_LEN + * + COAP_PAYLOAD_MARKER_SIZE + payload_len; + * ssize_t hdr_len = coap_build_reply(pkt, COAP_CODE_CONTENT, buf, len, max_data_len); + * + * if (hdr_len < 0) { + * return hdr_len; // pass through error + * } + * + * if (hdr_len == 0) { + * return 0; // no-response option present and matching + * } + * + * // This step is needed due to an API design flaw + * hdr_len -= max_data_len; + * + * uint8_t *pos = buf + hdr_len; + * uint16_t lastonum = 0; + * pos += coap_opt_put_uint(buf, lastonum, COAP_OPT_FOO, 42); + * lastonum = COAP_OPT_FOO; + * pos += coap_opt_put_uint(buf, lastonum, COAP_OPT_BAR, 1337); + * *pos++ = COAP_PAYLOAD_MARKER; + * memcpy(pos, payload, payload_len); + * pos += payload_len; + * return (uintptr_t)pos - (uintptr_t)buf; + * } + * ``` */ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, - uint8_t *rbuf, unsigned rlen, unsigned payload_len); + uint8_t *rbuf, unsigned rlen, unsigned max_data_len); /** * @brief Build empty reply to CoAP request @@ -2115,7 +2754,7 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, * @returns size of reply packet on success * @returns -ENOSPC if @p rbuf too small */ -ssize_t coap_build_empty_ack(coap_pkt_t *pkt, coap_hdr_t *ack); +ssize_t coap_build_empty_ack(const coap_pkt_t *pkt, coap_udp_hdr_t *ack); /** * @brief Handle incoming CoAP request @@ -2188,25 +2827,87 @@ static inline coap_method_flags_t coap_method2flag(unsigned code) } /** - * @brief Parse a CoAP PDU + * @brief Parse a CoAP PDU in UDP / DTLS format * * This function parses a raw CoAP PDU from @p buf with size @p len and fills * the structure pointed to by @p pkt. * @p pkt must point to a preallocated coap_pkt_t structure. * + * @warning For CoAP over TCP @p buf may contain incomplete and multiple + * packets. This function expects a CoAP packet at the beginning + * of @p buf and will consume at most one packet. If the buffer + * contains less than at least one full CoAP over TCP packet, it + * will return `-EGAIN` and leave the buffer unchanged. Callers + * should fill up the buffer more and retry. + * * @param[out] pkt structure to parse into * @param[in] buf pointer to raw packet data * @param[in] len length of packet at @p buf * - * @returns 0 on success - * @returns <0 on error + * @return Number of bytes parsed (may not match @p len on stream + * transports) + * @retval <0 error + * @retval -EAGAIN @p buf does not contain a complete CoAP over TCP + * packet, read more data and retry + */ +ssize_t coap_parse_udp(coap_pkt_t *pkt, uint8_t *buf, size_t len); + +/** + * @brief Alias for @ref coap_parse_udp + * + * @deprecated Use @ref coap_parse_udp instead + */ +static inline ssize_t coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) +{ + return coap_parse_udp(pkt, buf, len); +} + +/** + * @brief Parse a CoAP PDU in TCP format + * + * This function parses a raw CoAP PDU from @p buf with size @p len and fills + * the structure pointed to by @p pkt. + * @p pkt must point to a preallocated coap_pkt_t structure. + * + * @warning For CoAP over TCP @p buf may contain incomplete and multiple + * packets. This function expects a CoAP packet at the beginning + * of @p buf and will consume at most one packet. If the buffer + * contains less than at least one full CoAP over TCP packet, it + * will return `-EGAIN` and leave the buffer unchanged. Callers + * should fill up the buffer more and retry. + * + * @param[out] pkt structure to parse into + * @param[in] buf pointer to raw packet data + * @param[in] len length of packet at @p buf + * + * @return Number of bytes parsed (may not match @p len on stream + * transports) + * @retval <0 error + * @retval -EAGAIN @p buf does not contain a complete CoAP over TCP + * packet, read more data and retry + */ +ssize_t coap_parse_tcp(coap_pkt_t *pkt, uint8_t *buf, size_t len); + +/** + * @brief Parse a CoAP PDU in WebSocket format + * + * This function parses a raw CoAP PDU from @p buf with size @p len and fills + * the structure pointed to by @p pkt. + * @p pkt must point to a preallocated coap_pkt_t structure. + * + * @param[out] pkt structure to parse into + * @param[in] buf pointer to raw packet data + * @param[in] len length of packet at @p buf + * + * @return Number of bytes parsed (will match @p len here) + * @retval <0 error */ -int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len); +ssize_t coap_parse_ws(coap_pkt_t *pkt, uint8_t *buf, size_t len); /** * @brief Initialize a packet struct, to build a message buffer * - * @pre buf CoAP header already initialized + * @pre buf UDP CoAP header already initialized * @post pkt.flags all zeroed * @post pkt.payload points to first byte after header * @post pkt.payload_len set to maximum space available for options + payload @@ -2216,6 +2917,9 @@ int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len); * initialized * @param[in] len length of buf * @param[in] header_len length of header in buf, including token + * + * @deprecated Use @ref coap_pkt_udp instead, which will initialize both header and + * pkt. */ void coap_pkt_init(coap_pkt_t *pkt, uint8_t *buf, size_t len, size_t header_len); @@ -2333,6 +3037,38 @@ ssize_t coap_reply_simple(coap_pkt_t *pkt, unsigned ct, const void *payload, size_t payload_len); +/** + * @brief Create an empty ACK reply (as e.g. needed when preparing a + * separate response) + * + * @param[in] pkt packet to reply to + * @param[out] buf buffer to write reply to + * @param[in] len size of @p buf in bytes + * + * @return The number of bytes written + * @retval 0 No ACK needed (e.g. because CoAP over TCP; this is NO error) + * @retval <0 Error + * @retval >0 ACK written + */ +static inline ssize_t coap_reply_empty_ack(coap_pkt_t *pkt, + uint8_t *buf, size_t len) +{ + switch (coap_get_transport(pkt)) { + case COAP_TRANSPORT_TCP: + case COAP_TRANSPORT_WS: + return 0; + case COAP_TRANSPORT_UDP: + if (len < sizeof(coap_udp_hdr_t)) { + return -EOVERFLOW; + } + return coap_build_empty_ack(pkt, (coap_udp_hdr_t *)buf); + default: + break; + } + + return -ENOTSUP; +} + /** * @brief Reference to the default .well-known/core handler defined by the * application diff --git a/sys/include/net/nanocoap_proxy.h b/sys/include/net/nanocoap_proxy.h new file mode 100644 index 000000000000..99272f2cb2e5 --- /dev/null +++ b/sys/include/net/nanocoap_proxy.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2025 ML!PA Consulting GmbH + * + * 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. + */ +#ifndef NET_NANOCOAP_PROXY_H +#define NET_NANOCOAP_PROXY_H + +/** + * @defgroup net_nanocoap_proxy nanocoap proxy implementation + * @ingroup net + * @brief A trivial reverse proxy on top of nanocoap + * + * @{ + * + * @file + * @brief nanocoap reverse proxy + * + * @author Marian Buschsieweke + */ + +#include +#include + +#include "bitfield.h" +#include "event.h" +#include "net/nanocoap.h" +#include "net/nanocoap_sock.h" + +#ifdef __cplusplus +extern "C" { +#endif +#ifndef CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS +/** + * @brief Number of requests to handle in parallel + * + * @note The forwards will currently be forwarded one at a time, but a higher + * value will allow accepting more requests that come in roughly at the + * same time. + */ +# define CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS 1 +#endif + +/** + * @brief Canonical name of the NanoCoAP reverse proxy context + */ +typedef struct nanocoap_rproxy_ctx nanocoap_rproxy_ctx_t; + +/** + * @brief Context for a forwarded request/reply + */ +typedef struct { + event_t ev; /**< Event used to execute the forwarding */ + nanocoap_rproxy_ctx_t *proxy; /**< The proxy this belongs to */ + /** + * @brief The name of the endpoint to forward to + * + * @note This is needed to translate e.g. Location-Path options in the + * reply into the corresponding proxy-path + * + * @details `54 == strlen("[0000:0000:0000:0000:0000:ffff:192.168.100.228]:65535") + 1` + */ + char ep[54]; + nanocoap_sock_t client; /**< Client socket to use to send this request */ + nanocoap_server_response_ctx_t response_ctx; /**< response ctx to use to reply */ + coap_pkt_t req; /**< Request for forward */ + char buf[256]; /**< Buffer used to store the request to forward */ +} nanocoap_rproxy_forward_ctx_t; + +/** + * @brief Contents of @ref nanocoap_rproxy_ctx_t + */ +struct nanocoap_rproxy_ctx { + event_queue_t *evq; /**< Event queue that handles the forwards */ + /** + * @brief Request forwarding contexts + */ + nanocoap_rproxy_forward_ctx_t forwards[CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS]; + /** + * @brief Bookkeeping for @ref nanocoap_rproxy_ctx_t::forwards + */ + BITFIELD(forwards_used, CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS); + /** + * @brief URI-Scheme to use when forwarding + */ + char scheme[16]; +}; + +/** + * @brief nanocoap resource handler implementing the proxy + * + * Usage: + * + * ```C + * static nanocoap_rproxy_ctx_t _udp_proxy = { + * .evq = EVENT_PRIO_MEDIUM, + * .scheme = "coap://" + * }; + * + * NANOCOAP_RESOURCE(udp_proxy) { + * .path = "/udp", + * .methods = COAP_GET | COAP_PUT | COAP_POST | COAP_DELETE | COAP_MATCH_SUBTREE, + * .handler= nanocoap_rproxy_handler, + * .context = &_udp_proxy, + * }; + * ``` + * + * @param[in] pkt Received packet to forward + * @param[out] buf Buffer to write the response to + * @param[in] len Length of @p buf in bytes + * @param[in] ctx Request ctx + */ +ssize_t nanocoap_rproxy_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, + coap_request_ctx_t *ctx); + +#ifdef __cplusplus +} +#endif +/** @} */ +#endif /* NET_NANOCOAP_PROXY_H */ diff --git a/sys/include/net/nanocoap_sock.h b/sys/include/net/nanocoap_sock.h index d74c629e046e..24f366d12475 100644 --- a/sys/include/net/nanocoap_sock.h +++ b/sys/include/net/nanocoap_sock.h @@ -7,6 +7,8 @@ * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ +#ifndef NET_NANOCOAP_SOCK_H +#define NET_NANOCOAP_SOCK_H /** * @defgroup net_nanosock nanoCoAP Sock @@ -135,19 +137,24 @@ * @author Kaspar Schleiser */ -#ifndef NET_NANOCOAP_SOCK_H -#define NET_NANOCOAP_SOCK_H - #include #include -#include "random.h" #include "net/nanocoap.h" +#include "net/nanocoap_ws.h" #include "net/sock/udp.h" #include "net/sock/util.h" -#if IS_USED(MODULE_NANOCOAP_DTLS) -#include "net/credman.h" -#include "net/sock/dtls.h" +#include "xfa.h" + +#if MODULE_NANOCOAP_UDP +# include "random.h" +#endif +#if MODULE_NANOCOAP_TCP +# include "net/sock/tcp.h" +#endif +#if MODULE_NANOCOAP_DTLS +# include "net/credman.h" +# include "net/sock/dtls.h" #endif #ifdef __cplusplus @@ -159,7 +166,7 @@ extern "C" { * Tag together with the credential type (PSK) needs to be unique */ #ifndef CONFIG_NANOCOAP_SOCK_DTLS_TAG -#define CONFIG_NANOCOAP_SOCK_DTLS_TAG (0xc0ab) +# define CONFIG_NANOCOAP_SOCK_DTLS_TAG (0xc0ab) #endif /** @@ -167,15 +174,15 @@ extern "C" { * Used both for RX and TX, needs to hold payload block + header */ #ifndef CONFIG_NANOCOAP_SERVER_BUF_SIZE -#define CONFIG_NANOCOAP_SERVER_BUF_SIZE ((1 << (CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT + 3)) \ - + CONFIG_NANOCOAP_URI_MAX + 16) +# define CONFIG_NANOCOAP_SERVER_BUF_SIZE \ + ((1 << (CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT + 3)) + CONFIG_NANOCOAP_URI_MAX + 16) #endif /** * @brief CoAP server thread stack size */ #ifndef CONFIG_NANOCOAP_SERVER_STACK_SIZE -#define CONFIG_NANOCOAP_SERVER_STACK_SIZE THREAD_STACKSIZE_DEFAULT +# define CONFIG_NANOCOAP_SERVER_STACK_SIZE THREAD_STACKSIZE_DEFAULT #endif /** @@ -187,54 +194,220 @@ extern "C" { * See https://github.com/plgd-dev/go-coap/issues/512 */ #ifndef CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN -#define CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN (0) +# define CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN (0) +#endif + +#ifndef CONFIG_NANOCOAP_SERVER_TCP_MAX_CLIENTS +/** + * @brief Maximum number of clients the TCP server can handle concurrently + */ +# define CONFIG_NANOCOAP_SERVER_TCP_MAX_CLIENTS 4 #endif /** * @brief NanoCoAP socket types + * + * @details This can be casted to @ref coap_transport_t */ typedef enum { - COAP_SOCKET_TYPE_UDP, /**< transport is plain UDP */ - COAP_SOCKET_TYPE_DTLS, /**< transport is DTLS */ + COAP_SOCKET_TYPE_UDP = COAP_TRANSPORT_UDP, /**< transport is plain UDP */ + COAP_SOCKET_TYPE_DTLS = COAP_TRANSPORT_DTLS, /**< transport is DTLS */ + COAP_SOCKET_TYPE_TCP = COAP_TRANSPORT_TCP, /**< transport is TCP */ + COAP_SOCKET_TYPE_WS = COAP_TRANSPORT_WS, /**< transport is WebSocket */ } nanocoap_socket_type_t; /** * @brief NanoCoAP socket struct */ typedef struct { - sock_udp_t udp; /**< UDP socket */ -#if IS_USED(MODULE_NANOCOAP_DTLS) || defined(DOXYGEN) - sock_dtls_t dtls; /**< DTLS socket */ - sock_dtls_session_t dtls_session; /**< Session object for the stored socket. - Used for exchanging a session between - functions. */ - nanocoap_socket_type_t type; /**< Socket type (UDP, DTLS) */ + union { +#if MODULE_NANOCOAP_UDP || MODULE_NANOCOAP_DTLS || DOXYGEN + struct { + sock_udp_t udp; /**< UDP socket */ +# if MODULE_NANOCOAP_DTLS || DOXYGEN + sock_dtls_t dtls; /**< DTLS socket */ + /** + * @brief Session object for the stored socket. + * Used for exchanging a session between + * functions. + */ + sock_dtls_session_t dtls_session; +# endif + uint16_t msg_id; /**< next CoAP message ID */ + }; +#endif +#if MODULE_NANOCOAP_TCP + struct { +# if MODULE_SOCK_TCP + sock_tcp_t tcp; /**< TCP socket */ +# endif + void *tcp_buf; /**< recv buffer for TCP socket + * (output of malloc()) */ + uint16_t tcp_buf_fill; /**< number of bytes in + * @ref nanocoap_sock_t::tcp_buf */ + }; +#endif +#if MODULE_NANOCOAP_WS + struct { + coap_ws_conn_t *ws_conn; /**< WebSocket connection */ + coap_request_cb_t ws_cb; /**< request callback to call */ + void *ws_cb_arg; /**< arg for the request callback */ + mutex_t ws_sync; /**< unlock this on when a reply is + ready */ + ssize_t ws_retval; /**< return value to pass along */ + }; +#endif + }; +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + nanocoap_socket_type_t type; /**< Socket type (UDP, DTLS, TCP) */ #endif - uint16_t msg_id; /**< next CoAP message ID */ } nanocoap_sock_t; /** * @brief Blockwise request helper struct */ typedef struct { - nanocoap_sock_t *sock; /**< socket used for the request */ - const char *path; /**< path on the server */ - uint32_t blknum; /**< current block number */ - coap_method_t method; /**< request method (GET, POST, PUT) */ - uint8_t blksize; /**< CoAP blocksize exponent */ + nanocoap_sock_t *sock; /**< socket used for the request */ + const char *path; /**< path on the server */ + uint32_t blknum; /**< current block number */ + coap_method_t method; /**< request method (GET, POST, PUT) */ + uint8_t blksize; /**< CoAP blocksize exponent */ } coap_block_request_t; /** * @brief Context from CoAP request for separate response */ typedef struct { - sock_udp_ep_t remote; /**< remote to send response to */ - sock_udp_ep_t local; /**< local from which to send response */ - uint8_t token[COAP_TOKEN_LENGTH_MAX]; /**< request token */ - uint8_t tkl; /**< request token length */ - uint8_t no_response; /**< no-response bitmap */ + union{ +#if MODULE_NANOCOAP_UDP || MODULE_NANOCOAP_DTLS || DOXYGEN + struct { + sock_udp_ep_t remote_udp; /**< UDP endpoint to send response to */ + sock_udp_ep_t local_udp; /**< UDP endpoint from which to send response */ + }; +#endif +#if MODULE_NANOCOAP_SERVER_TCP || DOXYGEN + sock_tcp_t *sock_tcp; /**< TCP socket to send the response over */ +#endif +#if MODULE_NANOCOAP_WS || DOXYGEN + coap_ws_conn_t *conn_ws; /**< WebSocket connection to send the response over */ +#endif + }; + uint8_t token[COAP_TOKEN_LENGTH_MAX]; /**< request token */ + uint8_t tkl; /**< request token length */ + uint8_t no_response; /**< no-response bitmap */ +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + coap_transport_t transport; /**< transport to use */ +#endif } nanocoap_server_response_ctx_t; +#if MODULE_NANOCOAP_SERVER_TCP || DOXYGEN +/** + * @brief Context to pass to a NanoCoAP TCP server + * + * @warning All members are for internal use, do not access them. + */ +typedef struct { + sock_udp_ep_t local; /**< Local endpoint to listen at */ + sock_tcp_queue_t queue; /**< TCP queue to use */ + /** + * @brief TCP sockets to use + * + * This limits the number of concurrent connections (in other words the + * number of clients to serve). + */ + sock_tcp_t socks[CONFIG_NANOCOAP_SERVER_TCP_MAX_CLIENTS]; + /** + * @brief Number of bytes in the per client RX buf + */ + uint16_t rx_fill[CONFIG_NANOCOAP_SERVER_TCP_MAX_CLIENTS]; + /** + * @brief Receive buffer, one per client + */ + uint8_t rx_bufs[CONFIG_NANOCOAP_SERVER_TCP_MAX_CLIENTS * CONFIG_NANOCOAP_SERVER_BUF_SIZE]; + /** + * @brief Send buffer, shared between all connections + */ + uint8_t tx_buf[CONFIG_NANOCOAP_SERVER_BUF_SIZE]; + event_queue_t *evq; /**< event queue to handle TCP connections in */ +} nanocoap_tcp_server_ctx_t; +#endif + +/** + * @brief Signature of an URL based socket connector + * @param[in] url URL to connect to + * @param[out] sock Socket to initialize + * @retval -ENOTSUP URL scheme not matching (keep on trying) + * @return Success or error code when connection + */ +typedef int (*nanocoap_sock_url_connect_handler_t)(const char *url, nanocoap_sock_t *sock); + +/** + * @brief Cross file array of custom URL connectors. + * + * @details @ref nanocoap_sock_url_connect will call them in order before + * trying the builtin URL schemes. If one of the custom connectors + * does not return `-ENOTSUP`, @ref nanocoap_sock_url_connect will + * assume the URL to be handled pass through the return value. + */ +XFA_USE_CONST(nanocoap_sock_url_connect_handler_t, nanocoap_sock_url_connect_handlers); + +#define _NANOCOAP_SOCK_URL_CONNECT_HANDLER(func, prio) \ + XFA_CONST(nanocoap_sock_url_connect_handler_t, nanocoap_sock_url_connect_handlers, prio) \ + CONCAT(nanocoap_sock_url_connect_handler_, func) = func +/** + * @brief Add a new URL connect handler + * @param[in] Function implementing the URL connect handler + * @param[in] Numeric priority (low numeric value --> higher priority) for + * ordering. + */ +#define NANOCOAP_SOCK_URL_CONNECT_HANDLER(func, prio) \ + _NANOCOAP_SOCK_URL_CONNECT_HANDLER(func, prio) + +/** + * @brief Get the type of a given nanocoap socket + * @param[in] sock Socket to get the type of + * + * @retval COAP_SOCKET_TYPE_UDP CoAP over UDP socket + * @retval COAP_SOCKET_TYPE_DTLS CoAP over DTLS socket + * @retval COAP_SOCKET_TYPE_TCP CoAP over TCP socket + */ +static inline nanocoap_socket_type_t nanocoap_sock_get_type(const nanocoap_sock_t *sock) +{ + (void)sock; +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + return sock->type; +#elif MODULE_NANOCOAP_UDP + return COAP_SOCKET_TYPE_UDP; +#elif MODULE_NANOCOAP_DTLS + return COAP_SOCKET_TYPE_DTLS; +#elif MODULE_NANOCOAP_TCP + return COAP_SOCKET_TYPE_TCP; +#elif MODULE_NANOCOAP_WS + return COAP_SOCKET_TYPE_WS; +#else +# error "nanocoap: no transport enabled" +#endif +} + +/** + * @brief Set the nanocoap socket type + * @param[in] sock Socket to set the type of + * @param[in] type The socket type to set + * + * @warning This function is internal. Calls outside of nanocoap's sock + * implementation *will* break things. + * + */ +static inline void nanocoap_sock_set_type(nanocoap_sock_t *sock, + nanocoap_socket_type_t type) +{ + (void)sock; + (void)type; +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + sock->type = type; +#endif +} + /** * @brief Prepare the context for a separate response * @@ -273,6 +446,25 @@ int nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx, bool nanocoap_server_is_remote_in_response_ctx(const nanocoap_server_response_ctx_t *ctx, const coap_request_ctx_t *req); +/** + * @brief Get the transport of the given separate response context + * @param[in] ctx Context to get the transport of + * @return The transport the response will be send on + */ +static inline coap_transport_t nanocoap_server_response_ctx_transport(const nanocoap_server_response_ctx_t *ctx) +{ + (void)ctx; +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + return ctx->transport; +#elif MODULE_NANOCOAP_UDP + return COAP_TRANSPORT_UDP; +#elif MODULE_NANOCOAP_DTLS + return COAP_TRANSPORT_DTLS; +#elif MODULE_NANOCOAP_TCP + return COAP_TRANSPORT_TCP; +#endif +} + /** * @brief Build and send a separate response to a CoAP request * @@ -286,6 +478,9 @@ bool nanocoap_server_is_remote_in_response_ctx(const nanocoap_server_response_ct * @warning This function is only available when using the module * `nanocoap_server_separate` * + * @pre The transport used is UDP/DTLS. (There are not separate responses + * in CoAP over TCP / WebSocket.) + * * @param[in] ctx Context information for the CoAP response * @param[in] code CoAP response code * @param[in] type Response type, may be `COAP_TYPE_NON` @@ -455,11 +650,16 @@ void nanocoap_notify_observers_simple(const coap_resource_t *res, uint32_t obs, */ static inline uint16_t nanocoap_sock_next_msg_id(nanocoap_sock_t *sock) { +#if MODULE_NANOCOAP_UDP || MODULE_NANOCOAP_DTLS return sock->msg_id++; +#else + (void)sock; + return 0; +#endif } /** - * @brief Start a nanocoap server instance + * @brief Start a nanocoap server instance listening on UDP * * This function only returns if there's an error binding to @p local, or if * receiving of UDP packets fails. @@ -470,7 +670,54 @@ static inline uint16_t nanocoap_sock_next_msg_id(nanocoap_sock_t *sock) * * @returns -1 on error */ -int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize); +int nanocoap_server_udp(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize); + +#if MODULE_NANOCOAP_SERVER_TCP +/** + * @brief Start a nanocoap server instance listening on TCP + * + * This function only returns if there's an error binding to @p local, or if + * listening for TCP connection fails. + * + * @param[in,out] ctx Context to operate on + * @param[in] evq Event queue to operate on + * @param[in] local Local endpoint to listen on or `NULL`. + * + * @retval 0 Success, server set up and connections will be accepted and + * handled from the event thread + * @retval <0 Error setting up server + */ +int nanocoap_server_tcp(nanocoap_tcp_server_ctx_t *ctx, + event_queue_t *evq, + const sock_tcp_ep_t *local); +#endif + +#if MODULE_NANOCOAP_SERVER_WS +/** + * @brief Setup a nanocoap server instance listening on WebSockets + * + * @param[in] transport WebSocket transport to set up a WebSocket handle on + * @param[in,out] transport_arg Argument to pass to the transport when opening the handle + * @param[in] local_ep Local endpoint to listen on + * @param[in] local_ep_len Length of @p local_ep in bytes + * + * @retval 0 Success, server set up and connections will be accepted and + * handled from the event thread + * @retval <0 Error setting up server + */ +int nanocoap_server_ws(const coap_ws_transport_t *transport, void *transport_arg, + const void *local_ep, size_t local_ep_len); +#endif + +/** + * @brief Alias of @ref nanocoap_server_udp + * + * @deprecated Call @ref nanocoap_server_udp directly. + */ +static inline int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) +{ + return nanocoap_server_udp(local, buf, bufsize); +} /** * @brief Create and start the nanoCoAP server thread @@ -482,33 +729,73 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize); * * @return pid of the server thread */ -kernel_pid_t nanocoap_server_start(const sock_udp_ep_t *local); +kernel_pid_t nanocoap_server_start_udp(const sock_udp_ep_t *local); + +/** + * @brief Alias for @ref nanocoap_server_start_udp + * + * @deprecated Use @ref nanocoap_server_start_udp instead + */ +static inline kernel_pid_t nanocoap_server_start(const sock_udp_ep_t *local) +{ + return nanocoap_server_start_udp(local); +} /** - * @brief Create a CoAP client socket + * @brief Create a CoAP over UDP client socket * * @param[out] sock CoAP UDP socket * @param[in] local Local UDP endpoint, may be NULL * @param[in] remote remote UDP endpoint * + * @note Requires module `nanocoap_udp` + * * @returns 0 on success * @returns <0 on error */ -static inline int nanocoap_sock_connect(nanocoap_sock_t *sock, - const sock_udp_ep_t *local, - const sock_udp_ep_t *remote) +static inline int nanocoap_sock_udp_connect(nanocoap_sock_t *sock, const sock_udp_ep_t *local, + const sock_udp_ep_t *remote) { -#if IS_USED(MODULE_NANOCOAP_DTLS) - sock->type = COAP_SOCKET_TYPE_UDP; -#endif +#if MODULE_NANOCOAP_UDP + nanocoap_sock_set_type(sock, COAP_SOCKET_TYPE_UDP); sock->msg_id = random_uint32(); return sock_udp_create(&sock->udp, local, remote, 0); +#else + (void)sock; + (void)local; + (void)remote; + /* We could hide this function altogether if module nanocoap_udp is not + * used. But that would mandate more use of the preprocessor. Instead, + * we allow this function to be called in dead branches and fail at link + * time when this is actually used but UDP support not enabled */ + extern void nanocoap_sock_udp_connect_called_but_module_nanocoap_udp_not_used(void); + nanocoap_sock_udp_connect_called_but_module_nanocoap_udp_not_used(); + return -ENOTSUP; +#endif +} + +/** + * @brief Create a CoAP over UDP client socket + * + * @param[out] sock CoAP UDP socket + * @param[in] local Local UDP endpoint, may be NULL + * @param[in] remote remote UDP endpoint + * + * @deprecated Use @ref nanocoap_sock_connect_udp instead + * + * @returns 0 on success + * @returns <0 on error + */ +static inline int nanocoap_sock_connect(nanocoap_sock_t *sock, const sock_udp_ep_t *local, + const sock_udp_ep_t *remote) +{ + return nanocoap_sock_udp_connect(sock, local, remote); } #if IS_USED(MODULE_NANOCOAP_DTLS) || DOXYGEN /** - * @brief Create a DTLS secured CoAP client socket + * @brief Create a CoAP over DTLS client socket * * @param[out] sock CoAP UDP socket * @param[in] local Local UDP endpoint, may be NULL @@ -523,6 +810,42 @@ int nanocoap_sock_dtls_connect(nanocoap_sock_t *sock, sock_udp_ep_t *local, const sock_udp_ep_t *remote, credman_tag_t tag); #endif +/** + * @brief Create a CoAP over TCP client socket + * + * @param[out] sock CoAP TCP socket + * @param[in] local_port local TCP port, may be `0` + * @param[in] remote remote TCP endpoint + * + * @note Requires module `nanocoap_tcp` + * + * @returns 0 on success + * @returns <0 on error + */ +int nanocoap_sock_tcp_connect(nanocoap_sock_t *sock, + uint16_t local_port, const sock_tcp_ep_t *remote); + +/** + * @brief Create a CoAP over WebSocket client socket + * + * @param[out] sock CoAP WebSocket socket + * @param[in] transport WebSocket transport to use + * @param[in,out] arg Argument to pass to @p transport when opening a + * WebSocket handle + * @param[in] local_ep Local WebSocket endpoint + * @param[in] remote_ep Remote WebSocket endpoint + * @param[in] ep_len Length of @p remote_ep and @p local_ep in bytes + * + * @note Requires module `nanocoap_ws` + * + * @returns 0 on success + * @returns <0 on error + */ +int nanocoap_sock_ws_connect(nanocoap_sock_t *sock, + const coap_ws_transport_t *transport, void *arg, + const void *local_ep, const void *remote_ep, + size_t ep_len); + /** * @brief Create a CoAP client socket by URL * @@ -539,16 +862,33 @@ int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock); * * @param[in] sock CoAP UDP socket */ -static inline void nanocoap_sock_close(nanocoap_sock_t *sock) -{ -#if IS_USED(MODULE_NANOCOAP_DTLS) - if (sock->type == COAP_SOCKET_TYPE_DTLS) { - sock_dtls_session_destroy(&sock->dtls, &sock->dtls_session); - sock_dtls_close(&sock->dtls); - } -#endif - sock_udp_close(&sock->udp); -} +void nanocoap_sock_close(nanocoap_sock_t *sock); + +/** + * @brief Build a CoAP packet for sending it over the given socket + * + * @note This will pick the CoAP format suitable for the transport + * given in @p sock via @ref nanocoap_sock_request_cb + * + * @param[in] sock Socket the packet is inteded for + * @param[out] pkt Write the packet metadata here + * (may be `NULL` to only write the content) + * @param[out] buf Write the packet content here + * @param[in] buf_len Size of @p buf in bytes + * @param[in] type CoAP packet type (ignored, unless transport is UDP/DTLS) + * @param[in] token CoAP Token to use + * @param[in] token_len Length of @p token in bytes + * @param[in] code Code (e.g. @ref COAP_METHOD_GET etc.) + * + * @return The number of bytes written into @p buf on success + * @retval -EOVERFLOW Required more than @p buf_len bytes in the buffer + * @retval -EINVAL Called with invalid arguments + */ +ssize_t nanocoap_sock_build_pkt(nanocoap_sock_t *sock, coap_pkt_t *pkt, + void *buf, size_t buf_len, + uint8_t type, + const void *token, size_t token_len, + uint8_t code); /** * @brief Simple synchronous CoAP (confirmable) GET @@ -561,8 +901,7 @@ static inline void nanocoap_sock_close(nanocoap_sock_t *sock) * @returns length of response payload on success * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_get(nanocoap_sock_t *sock, const char *path, void *buf, - size_t len); +ssize_t nanocoap_sock_get(nanocoap_sock_t *sock, const char *path, void *buf, size_t len); /** * @brief Simple non-confirmable GET @@ -575,8 +914,8 @@ ssize_t nanocoap_sock_get(nanocoap_sock_t *sock, const char *path, void *buf, * @returns length of response payload on success * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_get_non(nanocoap_sock_t *sock, const char *path, - void *response, size_t len_max); +ssize_t nanocoap_sock_get_non(nanocoap_sock_t *sock, const char *path, void *response, + size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) PUT @@ -591,8 +930,7 @@ ssize_t nanocoap_sock_get_non(nanocoap_sock_t *sock, const char *path, * @returns length of response payload on success * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_put(nanocoap_sock_t *sock, const char *path, - const void *request, size_t len, +ssize_t nanocoap_sock_put(nanocoap_sock_t *sock, const char *path, const void *request, size_t len, void *response, size_t len_max); /** @@ -610,9 +948,8 @@ ssize_t nanocoap_sock_put(nanocoap_sock_t *sock, const char *path, * independently of success (because no response is requested in that case) * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_put_non(nanocoap_sock_t *sock, const char *path, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_put_non(nanocoap_sock_t *sock, const char *path, const void *request, + size_t len, void *response, size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) PUT to URL @@ -626,9 +963,8 @@ ssize_t nanocoap_sock_put_non(nanocoap_sock_t *sock, const char *path, * @returns length of response payload on success * @returns <0 on error */ -ssize_t nanocoap_sock_put_url(const char *url, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_put_url(const char *url, const void *request, size_t len, void *response, + size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) POST @@ -643,8 +979,7 @@ ssize_t nanocoap_sock_put_url(const char *url, * @returns length of response payload on success * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_post(nanocoap_sock_t *sock, const char *path, - const void *request, size_t len, +ssize_t nanocoap_sock_post(nanocoap_sock_t *sock, const char *path, const void *request, size_t len, void *response, size_t len_max); /** @@ -662,9 +997,8 @@ ssize_t nanocoap_sock_post(nanocoap_sock_t *sock, const char *path, * independently of success (because no response is requested in that case) * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_post_non(nanocoap_sock_t *sock, const char *path, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_post_non(nanocoap_sock_t *sock, const char *path, const void *request, + size_t len, void *response, size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) POST to URL @@ -678,9 +1012,8 @@ ssize_t nanocoap_sock_post_non(nanocoap_sock_t *sock, const char *path, * @returns length of response payload on success * @returns <0 on error */ -ssize_t nanocoap_sock_post_url(const char *url, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_post_url(const char *url, const void *request, size_t len, void *response, + size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) FETCH @@ -696,9 +1029,8 @@ ssize_t nanocoap_sock_post_url(const char *url, * @returns length of response payload on success * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_fetch(nanocoap_sock_t *sock, const char *path, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_fetch(nanocoap_sock_t *sock, const char *path, const void *request, + size_t len, void *response, size_t len_max); /** * @brief Simple non-confirmable FETCH @@ -716,9 +1048,8 @@ ssize_t nanocoap_sock_fetch(nanocoap_sock_t *sock, const char *path, * independently of success (because no response is requested in that case) * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_sock_fetch_non(nanocoap_sock_t *sock, const char *path, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_fetch_non(nanocoap_sock_t *sock, const char *path, const void *request, + size_t len, void *response, size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) FETCH to URL @@ -733,9 +1064,8 @@ ssize_t nanocoap_sock_fetch_non(nanocoap_sock_t *sock, const char *path, * @returns length of response payload on success * @returns <0 on error */ -ssize_t nanocoap_sock_fetch_url(const char *url, - const void *request, size_t len, - void *response, size_t len_max); +ssize_t nanocoap_sock_fetch_url(const char *url, const void *request, size_t len, void *response, + size_t len_max); /** * @brief Simple synchronous CoAP (confirmable) DELETE @@ -774,8 +1104,7 @@ ssize_t nanocoap_sock_delete_url(const char *url); * @returns -1 if failed to fetch the url content * @returns 0 on success */ -int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path, - coap_blksize_t blksize, +int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path, coap_blksize_t blksize, coap_blockwise_cb_t callback, void *arg); /** @@ -793,9 +1122,8 @@ int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path, * @returns -EINVAL if an invalid url is provided * @returns size of the response payload on success */ -int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path, - coap_blksize_t blksize, size_t offset, - void *dst, size_t len); +int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path, coap_blksize_t blksize, + size_t offset, void *dst, size_t len); /** * @brief Performs a blockwise coap get request to the specified url. @@ -814,8 +1142,7 @@ int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path, * @returns -1 if failed to fetch the url content * @returns 0 on success */ -int nanocoap_get_blockwise_url(const char *url, - coap_blksize_t blksize, +int nanocoap_get_blockwise_url(const char *url, coap_blksize_t blksize, coap_blockwise_cb_t callback, void *arg); /** @@ -837,9 +1164,8 @@ int nanocoap_get_blockwise_url(const char *url, * @returns -ENOBUFS if the provided buffer was too small * @returns size of the response payload on success */ -ssize_t nanocoap_get_blockwise_url_to_buf(const char *url, - coap_blksize_t blksize, - void *buf, size_t len); +ssize_t nanocoap_get_blockwise_url_to_buf(const char *url, coap_blksize_t blksize, void *buf, + size_t len); /** * @brief Performs a blockwise CoAP GET request, store the response @@ -861,8 +1187,7 @@ ssize_t nanocoap_get_blockwise_url_to_buf(const char *url, * @returns size of the response payload on success */ ssize_t nanocoap_get_blockwise_to_buf(nanocoap_sock_t *sock, const char *path, - coap_blksize_t blksize, - void *buf, size_t len); + coap_blksize_t blksize, void *buf, size_t len); /** * @brief Simple synchronous CoAP request @@ -901,8 +1226,8 @@ ssize_t nanocoap_sock_request(nanocoap_sock_t *sock, coap_pkt_t *pkt, size_t len * @returns any error on @see sock_udp_recv_buf or @see sock_dtls_recv_buf * @returns any return value of @p cb for a matching response */ -ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, - coap_request_cb_t cb, void *arg); +ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, coap_request_cb_t cb, + void *arg); /** * @brief Simple synchronous CoAP request @@ -917,8 +1242,32 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, * @returns length of response on success * @returns @see nanocoap_sock_request_cb on error */ -ssize_t nanocoap_request(coap_pkt_t *pkt, const sock_udp_ep_t *local, - const sock_udp_ep_t *remote, size_t len); +ssize_t nanocoap_request_udp(coap_pkt_t *pkt, + const sock_udp_ep_t *local, const sock_udp_ep_t *remote, + size_t len); + +/** + * @brief Alias for @ref nanocoap_request_udp + * + * @param[in,out] pkt Packet struct containing the request. Is reused for + * the response + * @param[in] local Local UDP endpoint, may be NULL + * @param[in] remote remote UDP endpoint + * @param[in] len Total length of the buffer associated with the + * request + * + * @deprecated Use @ref nanocoap_request_udp instead + * + * @returns length of response on success + * @returns @see nanocoap_sock_request_cb on error + */ +static inline ssize_t nanocoap_request(coap_pkt_t *pkt, + const sock_udp_ep_t *local, + const sock_udp_ep_t *remote, + size_t len) +{ + return nanocoap_request_udp(pkt, local, remote, len); +} /** * @brief Initialize block request context by URL and connect a socket @@ -933,10 +1282,8 @@ ssize_t nanocoap_request(coap_pkt_t *pkt, const sock_udp_ep_t *local, * @retval <0 Error (see @ref nanocoap_sock_url_connect for details) */ static inline int nanocoap_block_request_connect_url(coap_block_request_t *ctx, - nanocoap_sock_t *sock, - const char *url, - coap_method_t method, - coap_blksize_t blksize) + nanocoap_sock_t *sock, const char *url, + coap_method_t method, coap_blksize_t blksize) { ctx->sock = sock; ctx->path = sock_urlpath(url); @@ -966,11 +1313,68 @@ static inline int nanocoap_block_request_connect_url(coap_block_request_t *ctx, * @return Number of payload bytes written on success * Negative error on failure */ -int nanocoap_sock_block_request(coap_block_request_t *ctx, - const void *data, size_t len, bool more, +int nanocoap_sock_block_request(coap_block_request_t *ctx, const void *data, size_t len, bool more, coap_request_cb_t cb, void *arg); + +/** + * @brief Send a CoAP-over-TCP CSM message + * + * @warning This is an internal function. It's API may change as needed without + * deprecation. + * + * @param[in] sock TCP socket to send the CSM message over + * @param[out] buf Message buffer to assemble the CSM message in before + * sending it out + * @param[in] buf_size Size of @p buf in bytes. This will also be reported + * to the other side as maximum size for messages + * to receive. + * + * This will send a CSM message that indicates messages up at most @p buf_size + * bytes will be processed (assuming @p buf is also used as receive buffer). + * It will also indicate support for block-wise transfer. If and only if + * module `nanocoap_token_ext` is used, support for RFC 8974 Extended Tokens + * will be indicated. + */ +ssize_t nanocoap_send_csm_message(sock_tcp_t *sock, void *_buf, size_t buf_size); + +/** + * @brief Send a CoAP-over-WebSocket CSM message + * + * @warning This is an internal function. It's API may change as needed without + * deprecation. + * + * @param[in,out] conn Connection to send the date over + * @param[out] buf Message buffer to assemble the CSM message in before + * sending it out + * @param[in] buf_size Size of @p buf in bytes. This will also be reported + * to the other side as maximum size for messages + * to receive. + * + * @retval 0 Success + * @retval <0 Error + * + * This will send a CSM message that indicates messages up at most @p buf_size + * bytes will be processed (assuming @p buf is also used as receive buffer). + * It will also indicate support for block-wise transfer. If and only if + * module `nanocoap_token_ext` is used, support for RFC 8974 Extended Tokens + * will be indicated. + */ +int nanocoap_send_csm_message_ws(coap_ws_conn_t *conn, void *buf, size_t buf_size); + +/** + * @brief Send a CoAP-over-TCP/CoAP-over-WebSocket Abort Signaling message + * + * @warning This is an internal function. It's API may change as needed without + * deprecation. + * + * @param[in] sock CoAP TCP/WS socket to send the CSM message over + * + * @retval 0 Success + * @retval <0 Negative errno code indicating the rror + */ +int nanocoap_send_abort_signal(nanocoap_sock_t *sock); #ifdef __cplusplus } #endif -#endif /* NET_NANOCOAP_SOCK_H */ /** @} */ +#endif /* NET_NANOCOAP_SOCK_H */ diff --git a/sys/include/net/nanocoap_ws.h b/sys/include/net/nanocoap_ws.h new file mode 100644 index 000000000000..347f427bb1ad --- /dev/null +++ b/sys/include/net/nanocoap_ws.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * 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. + */ +#ifndef NET_NANOCOAP_WS_H +#define NET_NANOCOAP_WS_H + +/** + * @defgroup net_nanocoap_ws nanoCoAP WebSocket Interface + * @ingroup net_nanocoap + * @brief An interface for using nanoCoAP over + * WebSocket(-compatible) transports + * + * This is a generic interface to provide a reliable, order preserving, + * duplication free, connection oriented, message based transport. This can + * very well be a WebSocket implementation, but is intentionally open for + * "abuse". + * + * @note Using CoAP over WebSocket serialization over transports other than + * WebSockets is not backed by any standard. There will be absolutely + * no interoperability when doing so. + * + * @{ + * + * @file + * @brief WebSocket interface for nanocoap + * + * @author Marian Buschsieweke + */ + +#include +#include + +#include "iolist.h" + +#if defined(MODULE_SOCK_UDP) || defined(DOXYGEN) +# include "net/sock/udp.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_NANOCOAP_WS_TX_BUF_SIZE +# define CONFIG_NANOCOAP_WS_TX_BUF_SIZE 128 +#endif + +/** + * @brief Canonical name of @ref struct coap_ws_conn + */ +typedef struct coap_ws_conn coap_ws_conn_t; + +/** + * @brief Canonical name of @ref struct coap_ws_transport + */ +typedef struct coap_ws_transport coap_ws_transport_t; + +/** + * @brief Canonical name of @ref struct coap_ws_handle + */ +typedef struct coap_ws_handle coap_ws_handle_t; + +typedef struct { + /** + * @brief Function to call when a request to create a connection request + * is available + * @param[in,out] handle Handle a new connection can be accepted on + * @param[in,out] conn New connection + * + * @note The called function may write to @ref coap_ws_conn_t::arg of + * @p conn + * + * @retval true Accept new connection + * @retval false Reject new connection + */ + bool (*conn_accept_cb)(coap_ws_handle_t *handle, coap_ws_conn_t *conn); + /** + * @brief Function to call when a message was received + * @param[in,out] conn Connection the message was received over + * @param[in,out] msg Message that was received + * @param[in] msg_len Length of @p msg in bytes + */ + void (*conn_recv_cb)(coap_ws_conn_t *conn, void *msg, size_t msg_len); + /** + * @brief Function to call to reclaim a connection + * + * @retval true Also clean up the handle this callback returns + * @retval false Keep the handle + * + * The user of the connection may still use @p conn during the callback, + * but must drop any reference to @p conn before returning. + */ + bool (*conn_reclaim_cb)(coap_ws_conn_t *conn); +} coap_ws_cbs_t; + +/** + * @brief A CoAP over WebSocket connection + * + * The canonical name is @ref coap_ws_conn_t + */ +struct coap_ws_conn { + /** + * @brief The handle this connection belongs to + */ + coap_ws_handle_t * handle; + /** + * @brief Pointer used internally by NanoCoAP + */ + void *arg; +}; + +/** + * @brief A CoAP over WebSocket handle + * + * The canonical name is @ref coap_ws_handle_t + * + * A WebSocket handle is used to create and accept connections on. + */ +struct coap_ws_handle { + const coap_ws_transport_t *transport; /**< The transport this belongs to */ + uint8_t tx_buf[CONFIG_NANOCOAP_WS_TX_BUF_SIZE]; /**< Buffer to use for assembling outgoing messages in */ +}; + +/** + * @brief A CoAP over WebSocket transport + * + * The canonical name is @ref coap_ws_transport_t + */ +struct coap_ws_transport { + /** + * @brief Open a WebSocket handle + * @param[in] cbs Callbacks to call on events + * @param[in,out] arg Argument (content is opaque to NanoCoAP) + * @param[in] ep Endpoint to listen on + * @param[in] el_len Length of @p ep in bytes + * @return The new handle + * @retval NULL Error (e.g. out of resources) + */ + coap_ws_handle_t * (*open_handle)(const coap_ws_cbs_t *cbs, void *arg, + const void *ep, size_t ep_len); + /** + * @brief Close a WebSocket handle + * @param[in,out] handle The WebSocket handle to close + * + * @note The implementation is expected to reclaim any still open + * connection belonging to this handle + */ + void (*close_handle)(coap_ws_handle_t *handle); + /** + * @brief Establish a new connection + * @param[in,out] handle The handle to open a connection over + * @param[in] ep Endpoint to connect to + * @param[in] ep_len Size of @p ep in bytes + * @param[in] arg Store this into @ref coap_ws_conn_t::arg + * + * @post @p arg is stored into the returned connection before this + * function returns and before any event callback are called + * + * @return Pointer to the new connection + * @retval NULL Connection failed + */ + coap_ws_conn_t * (*connect)(coap_ws_handle_t *handle, + const void *ep, size_t ep_len, void *arg); + /** + * @brief Close a WebSocket connection + * @param[in,out] conn Connection to close + * + * @note This will triger a call to @ref coap_ws_cbs_t::conn_reclaim_cb + */ + void (*close)(coap_ws_conn_t *conn); + /** + * @brief Get the remote endpoint of the given connection + * @param[in] conn The connection to get the remote endpoint of + * @param[out] ep Store a pointer to the endpoint representation in here + * + * @return Size of the endpoint + */ + size_t (*remote_ep)(const coap_ws_conn_t *conn, const void **ep); + /** + * @brief Send a single message over the given connection + * @param[in,out] conn Connection to send a message over + * @param[in] iol Message to send + * + * @note The message to send needs to contain the concatenation of all + * data chunks in @p iol. + * @retval 0 Success + * @retval <0 Error (failed to send (part of) the message) + */ + int (*sendv)(coap_ws_conn_t *conn, const iolist_t *iol); +}; + +#if defined(MODULE_SOCK_UDP) || defined(DOXYGEN) +typedef struct { + coap_ws_conn_t conn; + sock_udp_ep_t remote; +} coap_ws_over_udp_yolo_conn_t; + +typedef struct coap_ws_over_udp_yolo_init_arg { + coap_ws_handle_t handle; + const coap_ws_cbs_t *cbs; + sock_udp_t sock; + coap_ws_over_udp_yolo_conn_t conns[4]; + void (*cleanup)(struct coap_ws_over_udp_yolo_init_arg *arg); +} coap_ws_over_udp_yolo_init_arg_t; + +/** + * @brief Providing a WebSocket like interface using UDP, YOLO! + * + * @warning A WebSocket interface needs to provide a reliable, ordered, + * duplication free, connection based transfer of messages. UDP + * is unreliable, unordered, connection-less message transfer prone + * to duplicates. But ... YOLO! + * + * @note This will only be provided with module `nanocoap_ws_udp_yolo`. + * + * This can be good enough for testing between two machines with a direct + * link-layer connection between and the link layer implementing retransmissions + * and acknowledgements. + * + * Using it for anything but testing has a high chance of foot-shooting. + */ +extern const coap_ws_transport_t coap_ws_over_udp_yolo; +#endif + +#ifdef __cplusplus +} +#endif +/** @} */ +#endif /* NET_NANOCOAP_WS_H */ diff --git a/sys/net/application_layer/cord/ep/cord_ep.c b/sys/net/application_layer/cord/ep/cord_ep.c index 42e47a77c9be..58a6fdd76dd6 100644 --- a/sys/net/application_layer/cord/ep/cord_ep.c +++ b/sys/net/application_layer/cord/ep/cord_ep.c @@ -18,6 +18,7 @@ * @} */ +#include #include #include "mutex.h" @@ -28,15 +29,11 @@ #include "net/ipv6/addr.h" #include "net/cord/ep.h" #include "net/cord/common.h" -#include "net/cord/config.h" #ifdef MODULE_CORD_EP_STANDALONE #include "net/cord/ep_standalone.h" #endif -#define ENABLE_DEBUG 0 -#include "debug.h" - #define FLAG_SUCCESS (0x0001) #define FLAG_TIMEOUT (0x0002) #define FLAG_ERR (0x0004) @@ -87,7 +84,7 @@ static void _on_register(const gcoap_request_memo_t *memo, coap_pkt_t* pdu, thread_flags_t flag = FLAG_ERR; if ((memo->state == GCOAP_MEMO_RESP) && - (pdu->hdr->code == COAP_CODE_CREATED)) { + (coap_get_code_raw(pdu) == 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) { @@ -110,7 +107,7 @@ 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)) { + if ((req_state == GCOAP_MEMO_RESP) && (coap_get_code_raw(pdu) == code)) { flag = FLAG_SUCCESS; } else if (req_state == GCOAP_MEMO_TIMEOUT) { @@ -147,7 +144,7 @@ static int _update_remove(unsigned code, gcoap_resp_handler_t handle) if (res < 0) { return CORD_EP_ERR; } - coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + coap_pkt_set_type(&pkt, COAP_TYPE_CON); ssize_t pkt_len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); /* send request */ @@ -223,7 +220,7 @@ static int _discover_internal(const sock_udp_ep_t *remote, if (res < 0) { return CORD_EP_ERR; } - coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + coap_pkt_set_type(&pkt, COAP_TYPE_CON); coap_opt_add_uri_query(&pkt, "rt", "core.rd"); size_t pkt_len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); res = gcoap_req_send(buf, pkt_len, remote, NULL, _on_discover, NULL, GCOAP_SOCKET_TYPE_UNDEF); @@ -277,7 +274,7 @@ int cord_ep_register(const sock_udp_ep_t *remote, const char *regif) goto end; } /* set some packet options and write query string */ - coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + coap_pkt_set_type(&pkt, COAP_TYPE_CON); coap_opt_add_uint(&pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_LINK); res = cord_common_add_qstring(&pkt); if (res < 0) { diff --git a/sys/net/application_layer/cord/epsim/cord_epsim.c b/sys/net/application_layer/cord/epsim/cord_epsim.c index 0f24d4b91ad7..742d0f247148 100644 --- a/sys/net/application_layer/cord/epsim/cord_epsim.c +++ b/sys/net/application_layer/cord/epsim/cord_epsim.c @@ -23,9 +23,7 @@ #include "assert.h" #include "net/gcoap.h" #include "net/cord/epsim.h" -#include "net/cord/config.h" #include "net/cord/common.h" -#include "net/ipv6/addr.h" #define BUFSIZE (128U) @@ -59,7 +57,7 @@ int cord_epsim_register(const sock_udp_ep_t *rd_ep) return CORD_EPSIM_ERROR; } /* make packet confirmable */ - coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + coap_pkt_set_type(&pkt, COAP_TYPE_CON); /* add Uri-Query options */ if (cord_common_add_qstring(&pkt) < 0) { return CORD_EPSIM_ERROR; diff --git a/sys/net/application_layer/cord/lc/cord_lc.c b/sys/net/application_layer/cord/lc/cord_lc.c index 72d8fd9f0ed3..2a103a28de93 100644 --- a/sys/net/application_layer/cord/lc/cord_lc.c +++ b/sys/net/application_layer/cord/lc/cord_lc.c @@ -183,7 +183,7 @@ static ssize_t _lookup_raw(const cord_lc_rd_t *rd, unsigned content_format, DEBUG("cord_lc: unsupported content format\n"); return CORD_LC_ERR; } - coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON); + coap_pkt_set_type(&pkt, COAP_TYPE_CON); coap_opt_add_uint(&pkt, COAP_OPT_ACCEPT, content_format); pkt_len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); @@ -251,7 +251,7 @@ static int _send_rd_init_req(coap_pkt_t *pkt, const sock_udp_ep_t *remote, return CORD_LC_ERR; } - coap_hdr_set_type(pkt->hdr, COAP_TYPE_CON); + coap_pkt_set_type(pkt, COAP_TYPE_CON); coap_opt_add_uri_query(pkt, "rt", "core.rd-lookup-*"); ssize_t pkt_len = coap_opt_finish(pkt, COAP_OPT_FINISH_NONE); diff --git a/sys/net/application_layer/gcoap/dns.c b/sys/net/application_layer/gcoap/dns.c index 7273ac119308..886c4b4b0b79 100644 --- a/sys/net/application_layer/gcoap/dns.c +++ b/sys/net/application_layer/gcoap/dns.c @@ -17,22 +17,17 @@ #include #include -#include "fmt.h" -#include "log.h" #include "mutex.h" #include "net/credman.h" #include "net/gcoap.h" -#include "net/af.h" #include "net/dns/cache.h" #include "net/ipv4/addr.h" #include "net/ipv6/addr.h" #include "net/sock/dns.h" #include "net/sock/udp.h" -#include "net/sock/util.h" #include "random.h" #include "string_utils.h" #include "uri_parser.h" -#include "ut_process.h" #include "net/gcoap/dns.h" @@ -493,7 +488,7 @@ static ssize_t _req_init(coap_pkt_t *pdu, uri_parser_result_t *uri_comp, bool co gcoap_req_init_path_buffer(pdu, pdu->payload, pdu->payload_len, COAP_METHOD_FETCH, uri_comp->path, uri_comp->path_len); if (con) { - coap_hdr_set_type(pdu->hdr, COAP_TYPE_CON); + coap_pkt_set_type(pdu, COAP_TYPE_CON); } if (coap_opt_add_format(pdu, COAP_FORMAT_DNS_MESSAGE) < 0) { @@ -545,7 +540,7 @@ static int _do_block(coap_pkt_t *pdu, const sock_udp_ep_t *remote, coap_block1_finish(&slicer); - if ((len = _send(pdu->hdr, len, remote, slicer.start == 0, context, tl_type)) <= 0) { + if ((len = _send(pdu->buf, len, remote, slicer.start == 0, context, tl_type)) <= 0) { DEBUG("gcoap_dns: msg send failed: %" PRIdSIZE "\n", len); return len; } @@ -583,7 +578,7 @@ static ssize_t _req(_req_ctx_t *context) } len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD); memcpy(pdu->payload, context->dns_buf, context->dns_buf_len); - return _send(pdu->hdr, len + context->dns_buf_len, &_remote, true, context, tl_type); + return _send(pdu->buf, len + context->dns_buf_len, &_remote, true, context, tl_type); } } @@ -698,7 +693,7 @@ static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, unsigned msg_type = coap_get_type(pdu); int len; - pdu->payload = (uint8_t *)pdu->hdr; + pdu->payload = pdu->buf; pdu->payload_len = CONFIG_GCOAP_DNS_PDU_BUF_SIZE; tl_type = _req_init(pdu, &_uri_comp, msg_type == COAP_TYPE_ACK); block.blknum++; @@ -712,7 +707,7 @@ static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, goto unlock; } len = coap_opt_finish(pdu, COAP_OPT_FINISH_NONE); - if ((len = _send((uint8_t *)pdu->hdr, len, remote, false, context, tl_type)) <= 0) { + if ((len = _send(pdu->buf, len, remote, false, context, tl_type)) <= 0) { DEBUG("gcoap_dns: Unable to request next block: %d\n", len); context->res = len; goto unlock; diff --git a/sys/net/application_layer/gcoap/forward_proxy.c b/sys/net/application_layer/gcoap/forward_proxy.c index afe811f6c6ec..78e46273bff5 100644 --- a/sys/net/application_layer/gcoap/forward_proxy.c +++ b/sys/net/application_layer/gcoap/forward_proxy.c @@ -18,15 +18,17 @@ #include #include "event.h" -#include "kernel_defines.h" #include "net/gcoap.h" #include "net/gcoap/forward_proxy.h" #include "uri_parser.h" -#include "net/nanocoap/cache.h" #include "ztimer.h" #include "forward_proxy_internal.h" +#if MODULE_NANOCOAP_CACHE +# include "net/nanocoap/cache.h" +#endif + #define ENABLE_DEBUG 0 #include "debug.h" @@ -243,7 +245,7 @@ static ssize_t _dispatch_msg(const void *buf, size_t len, sock_udp_ep_t *remote, static void _send_empty_ack(event_t *event) { - coap_hdr_t buf; + uint8_t buf[sizeof(coap_udp_hdr_t)]; client_ep_t *cep = container_of(event, client_ep_t, event); if (_cep_get_response_type(cep) != COAP_TYPE_ACK) { @@ -251,17 +253,15 @@ static void _send_empty_ack(event_t *event) } _cep_set_response_type(cep, COAP_TYPE_CON); - /* Flipping byte order as unlike in the other places where mid is - * used, coap_build_hdr would actually flip it back */ - coap_build_hdr(&buf, COAP_TYPE_ACK, NULL, 0, 0, ntohs(cep->mid)); + coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_ACK, NULL, 0, 0, cep->mid); _dispatch_msg(&buf, sizeof(buf), &cep->ep, &cep->proxy_ep); } static void _set_response_type(coap_pkt_t *pdu, uint8_t resp_type) { - coap_hdr_set_type(pdu->hdr, resp_type); + coap_pkt_set_type(pdu, resp_type); if (resp_type == COAP_TYPE_CON) { - pdu->hdr->id = htons(gcoap_next_msg_id()); + coap_set_id(pdu, gcoap_next_msg_id()); } } @@ -316,18 +316,18 @@ static void _forward_resp_handler(const gcoap_request_memo_t *memo, /* the response was truncated, so there should be enough space * to allocate an empty error message instead (with a potential Observe option) if not, * _listen_buf is _way_ too short ;-) */ - assert(buf_len >= (sizeof(*pdu->hdr) + 4U)); - gcoap_resp_init(pdu, (uint8_t *)pdu->hdr, buf_len, COAP_CODE_INTERNAL_SERVER_ERROR); + assert(buf_len >= (sizeof(coap_udp_hdr_t) + 4U)); + gcoap_resp_init(pdu, pdu->buf, buf_len, COAP_CODE_INTERNAL_SERVER_ERROR); coap_opt_finish(pdu, COAP_OPT_FINISH_NONE); _set_response_type(pdu, _cep_get_response_type(cep)); } else if (memo->state == GCOAP_MEMO_TIMEOUT) { /* send RST */ - gcoap_resp_init(pdu, (uint8_t *)pdu->hdr, buf_len, COAP_CODE_EMPTY); + gcoap_resp_init(pdu, pdu->buf, buf_len, COAP_CODE_EMPTY); coap_opt_finish(pdu, COAP_OPT_FINISH_NONE); } /* don't use buf_len here, in case the above `gcoap_resp_init`s changed `pdu` */ - _dispatch_msg(pdu->hdr, coap_get_total_len(pdu), &cep->ep, &cep->proxy_ep); + _dispatch_msg(pdu->buf, coap_get_total_len(pdu), &cep->ep, &cep->proxy_ep); _free_client_ep(cep); } @@ -418,7 +418,7 @@ static int _gcoap_forward_proxy_copy_options(coap_pkt_t *pkt, int gcoap_forward_proxy_req_send(client_ep_t *cep) { int len; - if ((len = gcoap_req_send((uint8_t *)cep->pdu.hdr, coap_get_total_len(&cep->pdu), + if ((len = gcoap_req_send(cep->pdu.buf, coap_get_total_len(&cep->pdu), &cep->server_ep, NULL, _forward_resp_handler, cep, GCOAP_SOCKET_TYPE_UNDEF)) <= 0) { DEBUG("gcoap_forward_proxy_req_send(): gcoap_req_send failed %d\n", len); @@ -457,15 +457,9 @@ static int _gcoap_forward_proxy_via_coap(coap_pkt_t *client_pkt, unsigned token_len = coap_get_token_len(client_pkt); coap_pkt_init(&client_ep->pdu, proxy_req_buf, CONFIG_GCOAP_PDU_BUF_SIZE, - sizeof(coap_hdr_t) + token_len); + sizeof(coap_udp_hdr_t) + token_len); - client_ep->pdu.hdr->ver_t_tkl = client_pkt->hdr->ver_t_tkl; - client_ep->pdu.hdr->code = client_pkt->hdr->code; - client_ep->pdu.hdr->id = client_pkt->hdr->id; - - if (token_len) { - memcpy(coap_get_token(&client_ep->pdu), coap_get_token(client_pkt), token_len); - } + memcpy(client_ep->pdu.buf, client_pkt->buf, coap_get_total_hdr_len(client_pkt)); /* copy all options from client_pkt to pkt */ len = _gcoap_forward_proxy_copy_options(&client_ep->pdu, client_pkt, client_ep, urip); @@ -502,7 +496,7 @@ int gcoap_forward_proxy_request_process(coap_pkt_t *pkt, return -ENOMEM; } - cep->mid = pkt->hdr->id; + cep->mid = coap_get_id(pkt); _cep_set_response_type( cep, (coap_get_type(pkt) == COAP_TYPE_CON) ? COAP_TYPE_ACK : COAP_TYPE_NON diff --git a/sys/net/application_layer/gcoap/gcoap.c b/sys/net/application_layer/gcoap/gcoap.c index aa2841f98caf..ba9e8c1254d9 100644 --- a/sys/net/application_layer/gcoap/gcoap.c +++ b/sys/net/application_layer/gcoap/gcoap.c @@ -72,7 +72,7 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, size_t len, sock_udp_ep_t *remote, sock_udp_aux_tx_t *aux); static void _expire_request(gcoap_request_memo_t *memo); static gcoap_request_memo_t* _find_req_memo_by_mid(const sock_udp_ep_t *remote, - uint16_t mid); + const coap_pkt_t *pkt); static gcoap_request_memo_t* _find_req_memo_by_token(const sock_udp_ep_t *remote, const uint8_t *token, size_t tkl); static gcoap_request_memo_t* _find_req_memo_by_pdu_token(const coap_pkt_t *src_pdu, @@ -420,7 +420,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ */ int8_t messagelayer_emptyresponse_type = NO_IMMEDIATE_REPLY; - ssize_t res = coap_parse(&pdu, buf, len); + ssize_t res = coap_parse_udp(&pdu, buf, len); if (res < 0) { DEBUG("gcoap: parse failure: %" PRIdSIZE "\n", res); /* If a response, can't clear memo, but it will timeout later. @@ -436,7 +436,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ if (coap_get_type(&pdu) == COAP_TYPE_RST) { DEBUG("gcoap: received RST, expiring potentially existing memo\n"); - memo = _find_req_memo_by_mid(remote, pdu.hdr->id); + memo = _find_req_memo_by_mid(remote, &pdu); if (memo) { event_timeout_clear(&memo->resp_evt_tmout); _expire_request(memo); @@ -458,7 +458,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ messagelayer_emptyresponse_type = COAP_TYPE_RST; DEBUG("gcoap: Answering empty CON request with RST\n"); } else if (coap_get_type(&pdu) == COAP_TYPE_ACK) { - memo = _find_req_memo_by_mid(remote, pdu.hdr->id); + memo = _find_req_memo_by_mid(remote, &pdu); if ((memo != NULL) && (memo->send_limit != GCOAP_SEND_LIMIT_NON)) { DEBUG("gcoap: empty ACK processed, stopping retransmissions\n"); _cease_retransmission(memo); @@ -515,7 +515,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ if (IS_USED(MODULE_NANOCOAP_CACHE)) { nanocoap_cache_entry_t *ce = NULL; - if ((pdu.hdr->code == COAP_CODE_VALID) && + if ((coap_get_code_raw(&pdu) == COAP_CODE_VALID) && (ce = _cache_lookup_memo(memo))) { /* update max_age from response and send cached response */ uint32_t max_age = 60; @@ -524,7 +524,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ ce->max_age = ztimer_now(ZTIMER_SEC) + max_age; /* copy all options and possible payload from the cached response * to the new response */ - assert((uint8_t *)pdu.hdr == &_listen_buf[0]); + assert((uint8_t *)pdu.buf == &_listen_buf[0]); if (_cache_build_response(ce, &pdu, _listen_buf, sizeof(_listen_buf)) < 0) { memo->state = GCOAP_MEMO_ERR; @@ -534,7 +534,7 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ } } /* TODO: resend request if VALID but no cache entry? */ - else if ((pdu.hdr->code != COAP_CODE_VALID)) { + else if ((coap_get_code_raw(&pdu) != COAP_CODE_VALID)) { _cache_process(memo, &pdu); } } @@ -584,16 +584,11 @@ static void _process_coap_pdu(gcoap_socket_t *sock, sock_udp_ep_t *remote, sock_ } if (messagelayer_emptyresponse_type != NO_IMMEDIATE_REPLY) { - coap_hdr_set_type(pdu.hdr, (uint8_t)messagelayer_emptyresponse_type); + coap_pkt_set_type(&pdu, messagelayer_emptyresponse_type); coap_pkt_set_code(&pdu, COAP_CODE_EMPTY); - /* Set the token length to 0, preserving the CoAP version as it was and - * the empty message type that was just set. - * - * FIXME: Introduce an internal function to set or truncate the token - * */ - pdu.hdr->ver_t_tkl &= 0xf0; + coap_pkt_set_tkl(&pdu, 0); - ssize_t bytes = _tl_send(sock, buf, sizeof(coap_hdr_t), remote, aux); + ssize_t bytes = _tl_send(sock, buf, sizeof(coap_udp_hdr_t), remote, aux); if (bytes <= 0) { DEBUG("gcoap: empty response failed: %" PRIdSIZE "\n", bytes); } @@ -801,8 +796,8 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, coap_request_ctx_t ctx = { .resource = resource, .tl_type = (uint32_t)sock->type, - .remote = remote, - .local = aux ? &aux->local : NULL, + .remote_udp = remote, + .local_udp = aux ? &aux->local : NULL, }; pdu_len = resource->handler(pdu, buf, len, &ctx); @@ -951,7 +946,7 @@ static gcoap_request_memo_t* _find_req_memo_by_token(const sock_udp_ep_t *remote } gcoap_request_memo_t *memo = &_coap_state.open_reqs[i]; - coap_hdr_t *hdr = gcoap_request_memo_get_hdr(memo); + coap_udp_hdr_t *hdr = gcoap_request_memo_get_hdr(memo); /* verbose debug to catch bugs with request/response matching */ #if SOCK_HAS_IPV4 @@ -1025,13 +1020,15 @@ static gcoap_request_memo_t* _find_req_memo_by_pdu_token( * Finds the memo for an outstanding request within the _coap_state.open_reqs * array. Matches on remote endpoint and message ID. * - * remote[in] Remote endpoint to match - * mid[in] Message ID to match + * @param[in] remote Remote endpoint to match + * @param[in] pkt Packet containing the message ID to search for * * return Registered request memo, or NULL if not found */ -static gcoap_request_memo_t* _find_req_memo_by_mid(const sock_udp_ep_t *remote, uint16_t mid) +static gcoap_request_memo_t* _find_req_memo_by_mid(const sock_udp_ep_t *remote, const coap_pkt_t *pkt) { + /* mid is in network byte order */ + uint16_t mid = coap_get_udp_hdr_const(pkt)->id; for (int i = 0; i < CONFIG_GCOAP_REQ_WAITING_MAX; i++) { if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED) { continue; @@ -1059,7 +1056,7 @@ static void _expire_request(gcoap_request_memo_t *memo) memset(&req, 0, sizeof(req)); /* 0 means there is an observe option value */ coap_clear_observe(&req); - req.hdr = gcoap_request_memo_get_hdr(memo); + req.buf = gcoap_request_memo_get_buf(memo); memo->resp_handler(memo, &req, NULL); } _memo_clear_resend_buffer(memo); @@ -1406,9 +1403,8 @@ static void _cache_process(gcoap_request_memo_t *memo, } coap_pkt_t req; - req.hdr = gcoap_request_memo_get_hdr(memo); - size_t pdu_len = pdu->payload_len + - (pdu->payload - (uint8_t *)pdu->hdr); + req.buf = gcoap_request_memo_get_buf(memo); + size_t pdu_len = pdu->payload_len + (pdu->payload - pdu->buf); #if IS_USED(MODULE_NANOCOAP_CACHE) nanocoap_cache_entry_t *ce; /* cache_key in memo is pre-processor guarded so we need to as well */ @@ -1432,7 +1428,7 @@ static ssize_t _cache_build_response(nanocoap_cache_entry_t *ce, coap_pkt_t *pdu } /* Use the same code from the cached content. Use other header * fields from the incoming request */ - gcoap_resp_init(pdu, buf, len, ce->response_pkt.hdr->code); + gcoap_resp_init(pdu, buf, len, coap_get_code_raw(&ce->response_pkt)); /* copy all options and possible payload from the cached response * to the new response */ unsigned header_len_req = coap_get_total_hdr_len(pdu); @@ -1445,15 +1441,15 @@ static ssize_t _cache_build_response(nanocoap_cache_entry_t *ce, coap_pkt_t *pdu (ce->response_buf + header_len_cached), opt_payload_len); /* parse into pdu including all options and payload pointers etc */ - coap_parse(pdu, buf, header_len_req + opt_payload_len); + coap_parse_udp(pdu, buf, header_len_req + opt_payload_len); return ce->response_len; } static void _copy_hdr_from_req_memo(coap_pkt_t *pdu, gcoap_request_memo_t *memo) { - const coap_hdr_t *hdr = gcoap_request_memo_get_hdr(memo); + const coap_udp_hdr_t *hdr = gcoap_request_memo_get_hdr(memo); size_t hdr_len = coap_hdr_len(hdr); - memcpy(pdu->hdr, hdr, hdr_len); + memcpy(pdu->buf, hdr, hdr_len); } static void _receive_from_cache_cb(void *ctx) @@ -1469,7 +1465,7 @@ static void _receive_from_cache_cb(void *ctx) if (memo->resp_handler) { /* copy header from request so gcoap_resp_init in _cache_build_response works correctly */ - coap_pkt_t pdu = { .hdr = (coap_hdr_t *)_listen_buf }; + coap_pkt_t pdu = { .buf = _listen_buf }; _copy_hdr_from_req_memo(&pdu, memo); if (_cache_build_response(ce, &pdu, _listen_buf, sizeof(_listen_buf)) >= 0) { memo->state = (ce->truncated) ? GCOAP_MEMO_RESP_TRUNC : GCOAP_MEMO_RESP; @@ -1536,7 +1532,7 @@ static ssize_t _cache_check(const uint8_t *buf, size_t len, coap_pkt_t req; nanocoap_cache_entry_t *ce = NULL; /* XXX cast to const might cause problems here :-/ */ - ssize_t res = coap_parse(&req, (uint8_t *)buf, len); + ssize_t res = coap_parse_udp(&req, (uint8_t *)buf, len); if (res < 0) { DEBUG("gcoap: parse failure for cache lookup: %" PRIdSIZE "\n", res); @@ -1691,30 +1687,29 @@ int gcoap_req_init_path_buffer(coap_pkt_t *pdu, uint8_t *buf, size_t len, { assert((path == NULL) || (path[0] == '/')); - pdu->hdr = (coap_hdr_t *)buf; - /* generate token */ uint16_t msgid = gcoap_next_msg_id(); ssize_t res; if (code) { -#if CONFIG_GCOAP_TOKENLEN - uint8_t token[CONFIG_GCOAP_TOKENLEN]; - for (size_t i = 0; i < CONFIG_GCOAP_TOKENLEN; i += 4) { - uint32_t rand = random_uint32(); - memcpy(&token[i], - &rand, - (CONFIG_GCOAP_TOKENLEN - i >= 4) ? 4 : CONFIG_GCOAP_TOKENLEN - i); - } - res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &token[0], - CONFIG_GCOAP_TOKENLEN, code, msgid); -#else - res = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, NULL, - CONFIG_GCOAP_TOKENLEN, code, msgid); -#endif + if (CONFIG_GCOAP_TOKENLEN > 0) { + uint8_t token[CONFIG_GCOAP_TOKENLEN]; + for (size_t i = 0; i < CONFIG_GCOAP_TOKENLEN; i += 4) { + uint32_t rand = random_uint32(); + memcpy(&token[i], + &rand, + (CONFIG_GCOAP_TOKENLEN - i >= 4) ? 4 : CONFIG_GCOAP_TOKENLEN - i); + } + res = coap_build_udp_hdr(buf, len, COAP_TYPE_NON, &token[0], + CONFIG_GCOAP_TOKENLEN, code, msgid); + } + else { + res = coap_build_udp_hdr(buf, len, COAP_TYPE_NON, NULL, + CONFIG_GCOAP_TOKENLEN, code, msgid); + } } else { /* ping request */ - res = coap_build_hdr(pdu->hdr, COAP_TYPE_CON, NULL, 0, code, msgid); + res = coap_build_udp_hdr(buf, len, COAP_TYPE_CON, NULL, 0, code, msgid); } coap_pkt_init(pdu, buf, len, res); @@ -1950,10 +1945,10 @@ int gcoap_obs_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, return GCOAP_OBS_INIT_UNUSED; } - pdu->hdr = (coap_hdr_t *)buf; + pdu->buf = buf; uint16_t msgid = gcoap_next_msg_id(); - ssize_t hdrlen = coap_build_hdr(pdu->hdr, COAP_TYPE_NON, &memo->token[0], - memo->token_len, COAP_CODE_CONTENT, msgid); + ssize_t hdrlen = coap_build_udp_hdr(buf, len, COAP_TYPE_NON, &memo->token[0], + memo->token_len, COAP_CODE_CONTENT, msgid); if (hdrlen <= 0) { /* reason for negative hdrlen is not defined, so we also are vague */ diff --git a/sys/net/application_layer/nanocoap/cache.c b/sys/net/application_layer/nanocoap/cache.c index 6cff2552a6c7..17ebb55c9de4 100644 --- a/sys/net/application_layer/nanocoap/cache.c +++ b/sys/net/application_layer/nanocoap/cache.c @@ -139,12 +139,12 @@ void nanocoap_cache_key_generate(const coap_pkt_t *req, uint8_t *cache_key) sha256_init(&ctx); _cache_key_digest_opts(req, &ctx, !(IS_USED(MODULE_GCOAP_FORWARD_PROXY)), true); - switch (req->hdr->code) { - case COAP_METHOD_FETCH: - sha256_update(&ctx, req->payload, req->payload_len); - break; - default: - break; + switch (coap_get_code_raw(req)) { + case COAP_METHOD_FETCH: + sha256_update(&ctx, req->payload, req->payload_len); + break; + default: + break; } sha256_final(&ctx, cache_key); } @@ -201,14 +201,15 @@ nanocoap_cache_entry_t *nanocoap_cache_process(const uint8_t *cache_key, unsigne nanocoap_cache_entry_t *ce; ce = nanocoap_cache_key_lookup(cache_key); + uint8_t code = coap_get_code_raw(resp); /* This response is not cacheable. */ - if (resp->hdr->code == COAP_CODE_CREATED) { + if (code == COAP_CODE_CREATED) { /* NO OP */ } /* This response is not cacheable. However, a cache MUST mark any stored response for the deleted resource as not fresh. */ - else if (resp->hdr->code == COAP_CODE_DELETED) { + else if (code == COAP_CODE_DELETED) { if (ce) { /* set max_age to now(), so that the cache is considered * stale immdiately */ @@ -226,7 +227,7 @@ nanocoap_cache_entry_t *nanocoap_cache_process(const uint8_t *cache_key, unsigne response received. (Unsafe options may trigger similar option-specific processing as defined by the option.) */ - else if (resp->hdr->code == COAP_CODE_VALID) { + else if (code == COAP_CODE_VALID) { if (ce) { /* refresh max_age() */ uint32_t max_age = 60; @@ -238,7 +239,7 @@ nanocoap_cache_entry_t *nanocoap_cache_process(const uint8_t *cache_key, unsigne /* This response is not cacheable. However, a cache MUST mark any stored response for the changed resource as not fresh. */ - else if (resp->hdr->code == COAP_CODE_CHANGED) { + else if (code == COAP_CODE_CHANGED) { if (ce) { /* set max_age to now(), so that the cache is considered * stale immdiately */ @@ -249,7 +250,7 @@ nanocoap_cache_entry_t *nanocoap_cache_process(const uint8_t *cache_key, unsigne to determine freshness and (if present) the ETag Option for validation. */ - else if (resp->hdr->code == COAP_CODE_CONTENT) { + else if (code == COAP_CODE_CONTENT) { if ((ce = nanocoap_cache_add_by_key(cache_key, request_method, resp, resp_len)) == NULL) { /* no space left in the cache? */ @@ -309,9 +310,9 @@ nanocoap_cache_entry_t *nanocoap_cache_add_by_key(const uint8_t *cache_key, memcpy(ce->cache_key, cache_key, CONFIG_NANOCOAP_CACHE_KEY_LENGTH); memcpy(&ce->response_pkt, resp, sizeof(coap_pkt_t)); - memcpy(&ce->response_buf, resp->hdr, resp_len); - ce->response_pkt.hdr = (coap_hdr_t *) ce->response_buf; - ce->response_pkt.payload = ce->response_buf + (resp->payload - ((uint8_t *)resp->hdr)); + memcpy(&ce->response_buf, resp->buf, resp_len); + ce->response_pkt.buf = ce->response_buf; + ce->response_pkt.payload = ce->response_buf + (resp->payload - resp->buf); ce->response_len = resp_len; ce->request_method = request_method; diff --git a/sys/net/application_layer/nanocoap/fileserver.c b/sys/net/application_layer/nanocoap/fileserver.c index 6a4e61ae0748..98dc6e763f1e 100644 --- a/sys/net/application_layer/nanocoap/fileserver.c +++ b/sys/net/application_layer/nanocoap/fileserver.c @@ -21,11 +21,13 @@ #include #include -#include "kernel_defines.h" #include "checksum/fletcher32.h" #include "net/nanocoap/fileserver.h" #include "vfs.h" -#include "vfs_util.h" + +#if MODULE_VFS_UTIL +# include "vfs_util.h" +#endif #define ENABLE_DEBUG 0 #include "debug.h" @@ -518,16 +520,15 @@ static ssize_t _get_directory(coap_pkt_t *pdu, uint8_t *buf, size_t len, vfs_closedir(&dir); coap_block2_finish(&slicer); - return (uintptr_t)buf - (uintptr_t)pdu->hdr; + return (uintptr_t)buf - (uintptr_t)pdu->buf; } #if IS_USED(MODULE_NANOCOAP_FILESERVER_PUT) static ssize_t _put_directory(coap_pkt_t *pdu, uint8_t *buf, size_t len, struct requestdata *request) { - int err; vfs_DIR dir; - if ((err = vfs_opendir(&dir, request->namebuf)) == 0) { + if (vfs_opendir(&dir, request->namebuf) == 0) { vfs_closedir(&dir); if (request->options.exists.if_match && request->options.if_match_len) { return _error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED); @@ -538,6 +539,7 @@ static ssize_t _put_directory(coap_pkt_t *pdu, uint8_t *buf, size_t len, if (request->options.exists.if_match) { return _error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED); } + int err; if ((err = vfs_mkdir(request->namebuf, 0777)) < 0) { return _error_handler(pdu, buf, len, err); } diff --git a/sys/net/application_layer/nanocoap/fs.c b/sys/net/application_layer/nanocoap/fs.c index 090aceaeb23e..8f8637e5e348 100644 --- a/sys/net/application_layer/nanocoap/fs.c +++ b/sys/net/application_layer/nanocoap/fs.c @@ -175,18 +175,20 @@ static int _query_server(nanocoap_sock_t *sock, const char *path, uint8_t *buf = _buf; coap_pkt_t pkt = { - .hdr = (void *)buf, + .buf = buf, }; uint16_t lastonum = 0; - buf += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, - nanocoap_sock_next_msg_id(sock)); + ssize_t hdr_len = coap_build_udp_hdr(_buf, sizeof(_buf), COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, + nanocoap_sock_next_msg_id(sock)); + assume(hdr_len > 0); + buf += hdr_len; buf += coap_opt_put_uri_pathquery(buf, &lastonum, path); buf += coap_opt_put_uint(buf, lastonum, COAP_OPT_BLOCK2, 0); buf += coap_opt_put_uint(buf, COAP_OPT_BLOCK2, COAP_OPT_SIZE2, 0); - assert((uintptr_t)buf - (uintptr_t)pkt.hdr < sizeof(_buf)); + assert((uintptr_t)buf - (uintptr_t)_buf < sizeof(_buf)); pkt.payload = buf; pkt.payload_len = 0; diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index 1d218bbb945a..2f2552dc167f 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -60,79 +60,97 @@ NANOCOAP_RESOURCE(well_known_core) COAP_WELL_KNOWN_CORE_DEFAULT_HANDLER; #define coap_resources_numof XFA_LEN(coap_resource_t, coap_resources_xfa) #endif +#if !MODULE_NANOCOAP_UDP && \ + !MODULE_NANOCOAP_DTLS && \ + !MODULE_NANOCOAP_TCP && \ + !MODULE_NANOCOAP_WS +# error "At least one nanocoap transport needs to be selected" +#endif + static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end); static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes); static size_t _encode_uint(uint32_t *val); -/* http://tools.ietf.org/html/rfc7252#section-3 - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |Ver| T | TKL | Code | Message ID | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Token (if any, TKL bytes) ... - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Options (if any) ... - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |1 1 1 1 1 1 1 1| Payload (if any) ... - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/** + * @brief Decode the CoAP Token position and length and encode as needed + * for @ref coap_pkt_t::token_pos_and_len + * + * @param[in] buf Message buffer (first byte of CoAP UDP/TCP/WebSocket header) + * @param[in] pos Position of the token when no extended token length field is + * present (e.g. 4 for UDP) + * + * @return The position and length of the Token + * @retval 0 Invalid message */ -int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) +static uint16_t _decode_token_pos_and_len(const uint8_t *buf, size_t buf_len, uint8_t token_pos) { - coap_hdr_t *hdr = (coap_hdr_t *)buf; - pkt->hdr = hdr; - - uint8_t *pkt_pos = coap_hdr_data_ptr(hdr); - uint8_t *pkt_end = buf + len; - - pkt->payload = NULL; - pkt->payload_len = 0; - memset(pkt->opt_crit, 0, sizeof(pkt->opt_crit)); - pkt->snips = NULL; - - if (len < sizeof(coap_hdr_t)) { - DEBUG("msg too short\n"); - return -EBADMSG; - } - else if ((coap_get_code_raw(pkt) == 0) && (len > sizeof(coap_hdr_t))) { - DEBUG("empty msg too long\n"); - return -EBADMSG; + uint8_t tkl = buf[0] & 0xf; + uint16_t token_length = tkl; + + /* Both token_length and token_pos may not be final yet. But if we go past + * this line, we at least have enough data to safely decode them without + * overflowing buf. */ + if (buf_len < token_length + token_pos) { + DEBUG_PUTS("nanocoap: token longer than packet."); + return 0; } if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { - if ((pkt->hdr->ver_t_tkl & 0xf) == 15) { - DEBUG("nanocoap: token length is reserved value 15," - "invalid for extended token length field.\n"); - return -EBADMSG; + switch (tkl) { + case 15: + DEBUG_PUTS("nanocoap: token length is reserved value 15," + "invalid for extended token length field."); + return 0; + case 14: + token_length = byteorder_bebuftohs(buf + token_pos) + 269; + if (token_length > 0xfff) { + DEBUG_PUTS("nanocoap: CoAP Tokens >= 4 KiB not implemented"); + return 0; + } + token_pos += 2; + break; + case 13: + token_length = buf[token_pos] + 13; + token_pos += 1; + break; + } + + /* testing again: token_length + token_pos may now be larger */ + if (buf_len < token_length + token_pos) { + DEBUG_PUTS("nanocoap: token longer than packet."); + return 0; } - } else if (coap_get_token_len(pkt) > COAP_TOKEN_LENGTH_MAX) { - DEBUG("nanocoap: token length invalid\n"); - return -EBADMSG; } - /* pkt_pos range is validated after options parsing loop below */ - pkt_pos += coap_get_token_len(pkt); + return (token_pos << 12) | token_length; +} + +static ssize_t _parse_options(coap_pkt_t *pkt, size_t len) +{ coap_optpos_t *optpos = pkt->options; unsigned option_count = 0; unsigned option_nr = 0; + /* Packet end may be before buf + len in case of CoAP over TCP. + * But pkt->payload_len is pointing to the end no matter what. */ + uint8_t *pkt_end = pkt->payload + pkt->payload_len; + /* parse options */ - while (pkt_pos < pkt_end) { - uint8_t *option_start = pkt_pos; - uint8_t option_byte = *pkt_pos++; + while (pkt->payload < pkt_end) { + uint8_t *option_start = pkt->payload; + uint8_t option_byte = *pkt->payload++; if (option_byte == COAP_PAYLOAD_MARKER) { - pkt->payload = pkt_pos; - pkt->payload_len = buf + len - pkt_pos; + pkt->payload_len = pkt->buf + len - pkt->payload; DEBUG("payload len = %u\n", pkt->payload_len); break; } else { - int option_delta = _decode_value(option_byte >> 4, &pkt_pos, pkt_end); + int option_delta = _decode_value(option_byte >> 4, &pkt->payload, pkt_end); if (option_delta < 0) { DEBUG("bad op delta\n"); return -EBADMSG; } - int option_len = _decode_value(option_byte & 0xf, &pkt_pos, pkt_end); + int option_len = _decode_value(option_byte & 0xf, &pkt->payload, pkt_end); if (option_len < 0) { DEBUG("bad op len\n"); return -EBADMSG; @@ -151,24 +169,21 @@ int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) bf_set(pkt->opt_crit, option_count); } optpos->opt_num = option_nr; - optpos->offset = (uintptr_t)option_start - (uintptr_t)hdr; + optpos->offset = (uintptr_t)option_start - (uintptr_t)pkt->buf; DEBUG("optpos option_nr=%u %u\n", (unsigned)option_nr, (unsigned)optpos->offset); optpos++; option_count++; } - pkt_pos += option_len; + pkt->payload += option_len; } } - if (pkt_pos > pkt_end) { - DEBUG("nanocoap: bad packet length\n"); - return -EBADMSG; - } + pkt->payload_len = (uintptr_t)pkt_end - (uintptr_t)pkt->payload; pkt->options_len = option_count; if (!pkt->payload) { - pkt->payload = pkt_pos; + pkt->payload = pkt->payload; } #ifdef MODULE_GCOAP @@ -177,12 +192,174 @@ int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len) } #endif - DEBUG("coap pkt parsed. code=%u detail=%u payload_len=%u, nopts=%u, 0x%02x\n", + DEBUG("coap pkt parsed. code=%u.%02u payload_len=%u, nopts=%u\n", coap_get_code_class(pkt), coap_get_code_detail(pkt), - pkt->payload_len, option_count, hdr->code); + pkt->payload_len, option_count); - return 0; + return len; +} + +/* http://tools.ietf.org/html/rfc7252#section-3 + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Ver| T | TKL | Code | Message ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Token (if any, TKL bytes) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1 1 1 1 1 1 1 1| Payload (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +ssize_t coap_parse_udp(coap_pkt_t *pkt, uint8_t *buf, size_t len) +{ + *pkt = (coap_pkt_t){ +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + .transport = COAP_TRANSPORT_UDP, +#endif + .buf = buf + }; + + if (len < sizeof(coap_udp_hdr_t)) { + DEBUG("msg too short\n"); + return -EBADMSG; + } + else if ((coap_get_code_raw(pkt) == 0) && (len > sizeof(coap_udp_hdr_t))) { + DEBUG("empty msg too long\n"); + return -EBADMSG; + } + + pkt->token_pos_and_len = _decode_token_pos_and_len(buf, len, sizeof(coap_udp_hdr_t)); + if (!pkt->token_pos_and_len) { + return -EBADMSG; + } + + pkt->payload = buf + coap_get_total_hdr_len(pkt); + pkt->payload_len = len - coap_get_total_hdr_len(pkt); + + return _parse_options(pkt, len); +} + +/* https://www.rfc-editor.org/rfc/rfc8323#section-3.2 Figure 4 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Len | TKL | Extended Length (if any, as chosen by Len) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Code | Token (if any, TKL bytes) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1 1 1 1 1 1 1 1| Payload (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +ssize_t coap_parse_tcp(coap_pkt_t *pkt, uint8_t *buf, size_t len) +{ + *pkt = (coap_pkt_t){ +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + .transport = COAP_TRANSPORT_TCP, +#endif + .buf = buf + }; + size_t hdr_len = 2; + + /* Check if the current chunk of the TCP stream already contains a full + * CoAP packet. We return -EAGAIN if the packet is incomplete */ + if (len < hdr_len) { + return -EAGAIN; + } + + size_t pkt_data_len = buf[0] >> 4; + + /* For the special cases pkt_data_len == 13/14/15, pkt_data_len is not + * actually final. But if go past this point we have at least 13 bytes of + * the packet, which is enough to get the final length */ + if (hdr_len + pkt_data_len > len) { + return -EAGAIN; + } + + + switch (pkt_data_len) { + case 13: + pkt_data_len = buf[1] + 13; + hdr_len += sizeof(uint8_t); + break; + case 14: + pkt_data_len = byteorder_bebuftohs(&buf[1]) + 269; + hdr_len += sizeof(uint16_t); + break; + case 15: + pkt_data_len = byteorder_bebuftohl(&buf[1]) + 65805; + hdr_len += sizeof(uint32_t); + break; + } + + uint8_t token_pos = hdr_len; + hdr_len += (buf[0] & 0xf); + + if (len < pkt_data_len + hdr_len) { + return -EAGAIN; + } + + pkt->token_pos_and_len = _decode_token_pos_and_len(buf, len, token_pos); + if (!pkt->token_pos_and_len) { + return -EBADMSG; + } + + if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + /* hdr_len may have grown due to RFC 8974 extended token length */ + hdr_len = coap_get_total_hdr_len(pkt); + + if (len < pkt_data_len + hdr_len) { + return -EAGAIN; + } + } + + pkt->payload = buf + hdr_len; + pkt->payload_len = pkt_data_len; + + return _parse_options(pkt, hdr_len + pkt_data_len); +} + + +/* https://www.rfc-editor.org/rfc/rfc8323#section-3.2 Figure 10 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Len=0 | TKL | Code | Token (TKL bytes) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1 1 1 1 1 1 1 1| Payload (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +ssize_t coap_parse_ws(coap_pkt_t *pkt, uint8_t *buf, size_t len) +{ + *pkt = (coap_pkt_t){ +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + .transport = COAP_TRANSPORT_WS, +#endif + .buf = buf + }; + + if (len < COAP_WS_HEADER_SIZE) { + return -EBADMSG; + } + + pkt->token_pos_and_len = _decode_token_pos_and_len(buf, len, COAP_WS_HEADER_SIZE); + if (!pkt->token_pos_and_len) { + return -EBADMSG; + } + + size_t hdr_len = coap_get_total_hdr_len(pkt); + pkt->payload = buf + hdr_len; + pkt->payload_len = len - hdr_len; + + return _parse_options(pkt, len); } int coap_match_path(const coap_resource_t *resource, const uint8_t *uri) @@ -209,7 +386,7 @@ uint8_t *coap_find_option(coap_pkt_t *pkt, unsigned opt_num) if (optpos->opt_num == opt_num) { unsigned idx = index_of(pkt->options, optpos); bf_unset(pkt->opt_crit, idx); - return (uint8_t*)pkt->hdr + optpos->offset; + return pkt->buf + optpos->offset; } optpos++; } @@ -352,7 +529,7 @@ ssize_t coap_opt_get_next(const coap_pkt_t *pkt, coap_optpos_t *opt, opt->opt_num = 0; opt->offset = coap_get_total_hdr_len(pkt); } - uint8_t *start = (uint8_t*)pkt->hdr + opt->offset; + uint8_t *start = pkt->buf + opt->offset; /* Find start of option value and value length. */ uint16_t delta; @@ -365,7 +542,7 @@ ssize_t coap_opt_get_next(const coap_pkt_t *pkt, coap_optpos_t *opt, *value = start; opt->opt_num += delta; - opt->offset = start + len - (uint8_t*)pkt->hdr; + opt->offset = start + len - pkt->buf; return len; } @@ -505,7 +682,7 @@ ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_le return -EBADMSG; } - if (pkt->hdr->code == 0) { + if (coap_get_code_raw(pkt) == 0) { return coap_build_reply(pkt, COAP_CODE_EMPTY, resp_buf, resp_buf_len, 0); } return coap_tree_handler(pkt, resp_buf, resp_buf_len, ctx, @@ -559,7 +736,11 @@ ssize_t coap_build_reply_header(coap_pkt_t *pkt, unsigned code, uint8_t *bufpos = buf; uint32_t no_response; unsigned tkl = coap_get_token_len(pkt); - size_t hdr_len = sizeof(coap_hdr_t) + tkl; + static_assert(sizeof(coap_udp_hdr_t) == COAP_TCP_TENTATIVE_HEADER_SIZE, + "Implementation assumption that CoAP over UDP header and " + "tentative CoAP over TCP header are of the same size does " + "not hold, code needs fixing"); + size_t hdr_len = sizeof(coap_udp_hdr_t) + tkl; uint8_t type = coap_get_type(pkt) == COAP_TYPE_CON ? COAP_TYPE_ACK : COAP_TYPE_NON; @@ -575,8 +756,20 @@ ssize_t coap_build_reply_header(coap_pkt_t *pkt, unsigned code, return -ENOBUFS; } - bufpos += coap_build_hdr(buf, type, coap_get_token(pkt), tkl, - code, ntohs(pkt->hdr->id)); + switch (coap_get_transport(pkt)) { + default: + case COAP_TRANSPORT_DTLS: + case COAP_TRANSPORT_UDP: + bufpos += coap_build_udp_hdr(buf, len, type, coap_get_token(pkt), tkl, + code, coap_get_id(pkt)); + break; + case COAP_TRANSPORT_TCP: + bufpos += coap_build_tcp_hdr(buf, len, coap_get_token(pkt), tkl, code); + break; + case COAP_TRANSPORT_WS: + bufpos += coap_build_ws_hdr(buf, len, coap_get_token(pkt), tkl, code); + break; + } if (coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response) == 0) { const uint8_t no_response_index = (code >> 5) - 1; @@ -646,22 +839,21 @@ ssize_t coap_reply_simple(coap_pkt_t *pkt, return header_len + payload_len; } -ssize_t coap_build_empty_ack(coap_pkt_t *pkt, coap_hdr_t *ack) +ssize_t coap_build_empty_ack(const coap_pkt_t *pkt, coap_udp_hdr_t *ack) { if (coap_get_type(pkt) != COAP_TYPE_CON) { return 0; } - coap_build_hdr(ack, COAP_TYPE_ACK, NULL, 0, - COAP_CODE_EMPTY, ntohs(pkt->hdr->id)); - - return sizeof(*ack); + return coap_build_udp_hdr(ack, sizeof(*ack), COAP_TYPE_ACK, NULL, 0, + COAP_CODE_EMPTY, coap_get_id(pkt)); } ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, uint8_t *rbuf, unsigned rlen, unsigned payload_len) { unsigned tkl = coap_get_token_len(pkt); + unsigned hdr_len; unsigned type = COAP_TYPE_NON; if (!code) { @@ -673,9 +865,23 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, else if (coap_get_type(pkt) == COAP_TYPE_CON) { type = COAP_TYPE_ACK; } - unsigned len = sizeof(coap_hdr_t) + tkl; - if ((len + payload_len) > rlen) { + switch (coap_get_transport(pkt)) { + default: + case COAP_TRANSPORT_UDP: + case COAP_TRANSPORT_DTLS: + hdr_len = sizeof(coap_udp_hdr_t); + break; + case COAP_TRANSPORT_TCP: + hdr_len = COAP_TCP_TENTATIVE_HEADER_SIZE; + break; + case COAP_TRANSPORT_WS: + hdr_len = COAP_WS_HEADER_SIZE; + } + + hdr_len += tkl + coap_pkt_tkl_ext_len(pkt); + + if ((hdr_len + payload_len) > rlen) { return -ENOSPC; } @@ -690,6 +896,10 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, /* option contains bitmap of disinterest */ if (no_response & mask) { + if (coap_get_transport(pkt) == COAP_TRANSPORT_TCP) { + return 0; + } + switch (coap_get_type(pkt)) { case COAP_TYPE_NON: /* no response and no ACK */ @@ -697,7 +907,7 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, default: /* There is an immediate ACK response, but it is an empty response */ code = COAP_CODE_EMPTY; - len = sizeof(coap_hdr_t); + hdr_len = sizeof(coap_udp_hdr_t); tkl = 0; payload_len = 0; break; @@ -705,43 +915,72 @@ ssize_t coap_build_reply(coap_pkt_t *pkt, unsigned code, } } - coap_build_hdr((coap_hdr_t *)rbuf, type, coap_get_token(pkt), tkl, code, - ntohs(pkt->hdr->id)); - len += payload_len; + switch (coap_get_transport(pkt)) { + case COAP_TRANSPORT_UDP: + case COAP_TRANSPORT_DTLS: + coap_build_udp_hdr(rbuf, rlen, type, coap_get_token(pkt), tkl, code, coap_get_id(pkt)); + break; + case COAP_TRANSPORT_TCP: + coap_build_tcp_hdr(rbuf, rlen, coap_get_token(pkt), tkl, code); + break; + case COAP_TRANSPORT_WS: + coap_build_ws_hdr(rbuf, rlen, coap_get_token(pkt), tkl, code); + break; + } - return len; + return hdr_len + payload_len; } -ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, - size_t token_len, unsigned code, uint16_t id) +static size_t _tkl_ext_len(uint8_t *tkl, uint8_t tkl_ext[2], size_t token_len) { - assert(!(type & ~0x3)); - - uint16_t tkl_ext; - uint8_t tkl_ext_len, tkl; + if (!IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + assume(token_len <= 8); + *tkl = token_len; + return 0; + } - if (token_len > 268 && IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { - tkl_ext_len = 2; - tkl_ext = htons(token_len - 269); /* 269 = 255 + 14 */ - tkl = 14; + if (token_len > 268) { + *tkl = 14; + uint16_t tmp = htons(token_len - 269);/* 269 = 255 + 14 */ + tkl_ext[0] = tmp >> 8; + tkl_ext[1] = tmp; + return 2; } - else if (token_len > 12 && IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { - tkl_ext_len = 1; - tkl_ext = token_len - 13; - tkl = 13; + + if (token_len > 12) { + *tkl = 13; + tkl_ext[0] = token_len - 13; + return 1; } - else { - tkl = token_len; - tkl_ext_len = 0; + + *tkl = token_len; + return 0; +} + +ssize_t coap_build_udp(coap_pkt_t *pkt, void *buf, size_t buf_len, uint8_t type, + const void *token, size_t token_len, uint8_t code, uint16_t id) +{ + assert(!(type & ~0x3)); + + uint8_t tkl_ext[2]; + uint8_t tkl; + size_t tkl_ext_len = _tkl_ext_len(&tkl, tkl_ext, token_len); + + size_t hdr_len = sizeof(coap_udp_hdr_t) + token_len + tkl_ext_len; + if (buf_len < hdr_len) { + return -EOVERFLOW; } - memset(hdr, 0, sizeof(coap_hdr_t)); + memset(buf, 0, sizeof(coap_udp_hdr_t)); + coap_udp_hdr_t *hdr = buf; hdr->ver_t_tkl = (COAP_V1 << 6) | (type << 4) | tkl; hdr->code = code; hdr->id = htons(id); + uint16_t token_offset = sizeof(coap_udp_hdr_t); if (tkl_ext_len) { - memcpy(hdr + 1, &tkl_ext, tkl_ext_len); + memcpy(buf + token_offset, tkl_ext, tkl_ext_len); + token_offset += tkl_ext_len; } /* Some users build a response packet in the same buffer that contained @@ -751,20 +990,182 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token, * undefined behavior, so have to treat this explicitly. We could use * memmove(), but we know that either `src` and `dest` do not overlap * at all, or fully. So we can be a bit more efficient here. */ - void *token_dest = coap_hdr_data_ptr(hdr); + void *token_dest = buf + token_offset; if (token_dest != token) { memcpy(token_dest, token, token_len); } - return sizeof(coap_hdr_t) + token_len + tkl_ext_len; + if (pkt) { + memset(pkt, 0, sizeof(*pkt)); + pkt->buf = buf; + pkt->payload = buf + hdr_len; + pkt->payload_len = buf_len - hdr_len; + coap_set_transport(pkt, COAP_TRANSPORT_UDP); + pkt->token_pos_and_len = (token_offset << 12) | token_len; + } + + return hdr_len; +} + +ssize_t coap_build_tcp(coap_pkt_t *pkt, void *_buf, size_t buf_len, + const void *token, size_t token_len, uint8_t code) +{ + uint8_t *buf = _buf; + + static const size_t min_hdr_size = COAP_TCP_TENTATIVE_HEADER_SIZE; + uint8_t tkl_ext[2]; + uint8_t tkl; + size_t tkl_ext_len = _tkl_ext_len(&tkl, tkl_ext, token_len); + size_t hdr_size = min_hdr_size + tkl_ext_len + token_len; + + if (buf_len < hdr_size) { + return -EOVERFLOW; + } + + /* We copy the token first and use memmove, as caller may reuse the same + * buffer for the reply that was used for the request. Since CoAP over TCP + * has a dynamic header size (depending on the length of the message), the + * header of the reply might very well be larger than the one of the request. + * Worse: Our tentative header uses a worst case estimation and is almost + * always larger than the header of the request. So we need to move data + * with a bit of care here. */ + uint16_t token_offset = COAP_TCP_TENTATIVE_HEADER_SIZE + tkl_ext_len; + memmove(buf + token_offset, token, token_len); + + buf[0] = (14U << 4) | tkl; + buf[1] = 0; + buf[2] = 0; + buf[3] = code; + memcpy(buf + token_offset, tkl_ext, tkl_ext_len); + token_offset += tkl_ext_len; + + if (pkt) { + memset(pkt, 0, sizeof(*pkt)); + pkt->buf = buf; + pkt->payload = buf + hdr_size; + pkt->payload_len = buf_len - hdr_size; + coap_set_transport(pkt, COAP_TRANSPORT_TCP); + pkt->token_pos_and_len = (token_offset << 12) | token_len; + } + + return hdr_size; +} + +void coap_finalize_tcp(coap_pkt_t *pkt) +{ + assume(pkt); + size_t data_len = coap_get_total_len(pkt) - coap_get_total_hdr_len(pkt); + uint16_t shrunk = coap_finalize_tcp_header(pkt->buf, data_len); + pkt->buf += shrunk; + uint16_t token_pos = pkt->token_pos_and_len >> 12; + pkt->token_pos_and_len &= 0x0fff; + token_pos -= shrunk; + pkt->token_pos_and_len |= token_pos << 12; +} + +size_t coap_finalize_tcp_header(uint8_t *hdr, size_t data_len) +{ +#if SIZE_MAX > 65805 + assume(data_len < 65805); +#endif + + /* Not using named constants, as RFC 8323 also uses the magic numbers + * without names. + * + * - If data_len < 13, the four bit Len field is used. + * - If 13 <= data_len < 269, the four-bit Len field is set to 13 + * and data_len - 13 is stored into the 8-bit extended Len field. + * - If 269 <= data_len < 65805, the four-bit Len field is set to 14 and + * data_len - 269 is stored into the 16-bit extended Len field. + */ + if (data_len >= 269) { + byteorder_htobebufs(hdr + 1, data_len - 269); + return 0; + } + + if (data_len >= 13) { + hdr[1] = (13U << 4) | (hdr[0] & 0xf); + hdr[2] = data_len - 13; + return 1; + } + + hdr[2] = (data_len << 4) | (hdr[0] & 0xf); + return 2; +} + +size_t coap_finalize_tcp_header_in_buf(uint8_t *buf, size_t buf_len) +{ + uint16_t tmp = _decode_token_pos_and_len(buf, buf_len, COAP_TCP_TENTATIVE_HEADER_SIZE); + size_t token_offset = tmp >> 12U; + size_t token_len = tmp & 0xfff; + size_t hdr_len = token_offset + token_len; + + if (buf_len < hdr_len) { + assert(0); + return buf_len; + } + + size_t data_len = buf_len - hdr_len; + return coap_finalize_tcp_header(buf, data_len); +} + +ssize_t coap_build_ws(coap_pkt_t *pkt, void *_buf, size_t buf_len, + const void *token, size_t token_len, uint8_t code) +{ + uint8_t *buf = _buf; + + static const size_t min_hdr_size = COAP_WS_HEADER_SIZE; + uint8_t tkl_ext[2]; + uint8_t tkl; + size_t tkl_ext_len = _tkl_ext_len(&tkl, tkl_ext, token_len); + size_t hdr_size = min_hdr_size + tkl_ext_len + token_len; + + if (buf_len < hdr_size) { + return -EOVERFLOW; + } + + /* We copy the token first and use memmove, as caller may reuse the same + * buffer for the reply that was used for the request. Since CoAP over TCP + * has a dynamic header size (depending on the length of the message), the + * header of the reply might very well be larger than the one of the request. + * Worse: Our tentative header uses a worst case estimation and is almost + * always larger than the header of the request. So we need to move data + * with a bit of care here. */ + uint16_t token_offset = COAP_WS_HEADER_SIZE + tkl_ext_len; + memmove(buf + token_offset, token, token_len); + + buf[0] = tkl; + buf[1] = code; + memcpy(buf + token_offset, tkl_ext, tkl_ext_len); + token_offset += tkl_ext_len; + + if (pkt) { + memset(pkt, 0, sizeof(*pkt)); + pkt->buf = buf; + pkt->payload = buf + hdr_size; + pkt->payload_len = buf_len - hdr_size; + coap_set_transport(pkt, COAP_TRANSPORT_WS); + pkt->token_pos_and_len = (token_offset << 12) | token_len; + } + + return hdr_size; } void coap_pkt_init(coap_pkt_t *pkt, uint8_t *buf, size_t len, size_t header_len) { + assert((buf[0] >> 6) == COAP_V1); memset(pkt, 0, sizeof(coap_pkt_t)); - pkt->hdr = (coap_hdr_t *)buf; + pkt->buf = buf; pkt->payload = buf + header_len; pkt->payload_len = len - header_len; + coap_set_transport(pkt, COAP_TRANSPORT_UDP); + + /* Parsing the CoAP header we just generated is a bit stupid. The + * API did not anticipate CoAP over TCP/WebSocket and + * RFC 8974 extend token length, which both cause the token field + * to no longer be at a fixed offset. */ + pkt->token_pos_and_len = _decode_token_pos_and_len(buf, len, + sizeof(coap_udp_hdr_t)); } /* @@ -1095,7 +1496,7 @@ static ssize_t _add_opt_pkt(coap_pkt_t *pkt, uint16_t optnum, const void *val, coap_put_option(pkt->payload, lastonum, optnum, val, val_len); pkt->options[pkt->options_len].opt_num = optnum; - pkt->options[pkt->options_len].offset = pkt->payload - (uint8_t *)pkt->hdr; + pkt->options[pkt->options_len].offset = pkt->payload - pkt->buf; pkt->options_len++; pkt->payload += optlen; pkt->payload_len -= optlen; @@ -1217,7 +1618,7 @@ ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags) pkt->payload_len = 0; } - return pkt->payload - (uint8_t *)pkt->hdr; + return pkt->payload - pkt->buf; } ssize_t coap_opt_remove(coap_pkt_t *pkt, uint16_t opt_num) @@ -1241,7 +1642,7 @@ ssize_t coap_opt_remove(coap_pkt_t *pkt, uint16_t opt_num) if (opt_count == 0) { /* this is the last option => use payload / end pointer as old start */ start_old = (pkt->payload_len) ? pkt->payload - 1 : pkt->payload; - start_new = (uint8_t *)pkt->hdr + optpos->offset; + start_new = pkt->buf + optpos->offset; break; } @@ -1255,12 +1656,12 @@ ssize_t coap_opt_remove(coap_pkt_t *pkt, uint16_t opt_num) optpos++; opt_count--; /* select start of next option */ - opt_start = (uint8_t *)pkt->hdr + optpos->offset; + opt_start = pkt->buf + optpos->offset; start_old = _parse_option(pkt, opt_start, &old_delta, &option_len); old_hdr_len = start_old - opt_start; /* select start of to be deleted option and set delta/length of next option */ - start_new = (uint8_t *)pkt->hdr + prev_opt->offset; + start_new = pkt->buf + prev_opt->offset; *start_new = 0; /* write new_delta value to option header: 4 upper bits of header (shift 4) + * 1 or 2 optional bytes depending on delta value) */ @@ -1292,7 +1693,7 @@ ssize_t coap_opt_remove(coap_pkt_t *pkt, uint16_t opt_num) } pkt->payload -= (start_old - start_new); } - return (pkt->payload - ((uint8_t *)pkt->hdr)) + pkt->payload_len; + return pkt->payload - pkt->buf + pkt->payload_len; } ssize_t coap_payload_put_bytes(coap_pkt_t *pkt, const void *data, size_t len) @@ -1460,19 +1861,36 @@ ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, uint8_t *buf, \ unsigned coap_get_len(coap_pkt_t *pkt) { - unsigned pktlen = sizeof(coap_hdr_t) + coap_get_token_len(pkt); + unsigned pktlen = sizeof(coap_udp_hdr_t) + coap_get_token_len(pkt); if (pkt->payload) { pktlen += pkt->payload_len + 1; } return pktlen; } -void coap_request_ctx_init(coap_request_ctx_t *ctx, sock_udp_ep_t *remote) +#if MODULE_NANOCOAP_UDP +void coap_request_ctx_init_udp(coap_request_ctx_t *ctx, sock_udp_ep_t *remote) { memset(ctx, 0, sizeof(*ctx)); - ctx->remote = remote; + ctx->remote_udp = remote; } +const sock_udp_ep_t *coap_request_ctx_get_remote_udp(const coap_request_ctx_t *ctx) +{ + return ctx->remote_udp; +} + +const sock_udp_ep_t *coap_request_ctx_get_local_udp(const coap_request_ctx_t *ctx) +{ +#if defined(MODULE_SOCK_AUX_LOCAL) + return ctx->local_udp; +#else + (void)ctx; + return NULL; +#endif +} +#endif + const char *coap_request_ctx_get_path(const coap_request_ctx_t *ctx) { return ctx->resource->path; @@ -1492,18 +1910,3 @@ uint32_t coap_request_ctx_get_tl_type(const coap_request_ctx_t *ctx) return 0; #endif } - -const sock_udp_ep_t *coap_request_ctx_get_remote_udp(const coap_request_ctx_t *ctx) -{ - return ctx->remote; -} - -const sock_udp_ep_t *coap_request_ctx_get_local_udp(const coap_request_ctx_t *ctx) -{ -#if defined(MODULE_SOCK_AUX_LOCAL) - return ctx->local; -#else - (void)ctx; - return NULL; -#endif -} diff --git a/sys/net/application_layer/nanocoap/proxy.c b/sys/net/application_layer/nanocoap/proxy.c new file mode 100644 index 000000000000..b6b334a34c30 --- /dev/null +++ b/sys/net/application_layer/nanocoap/proxy.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2025 ML!PA Consulting GmbH + * + * 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_nanocoap_proxy + * @{ + * + * @file + * @brief A trivial reverse proxy on top of nanocoap + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#include "bitfield.h" +#include "event.h" +#include "event/thread.h" +#include "fmt.h" +#include "log.h" +#include "net/nanocoap.h" +#include "net/nanocoap_proxy.h" + +#define ENABLE_DEBUG 1 +#include "debug.h" + +static int _request_done_cb(void *arg, coap_pkt_t *pkt) +{ + nanocoap_rproxy_forward_ctx_t *conn = arg; + for (unsigned i = 0; i < pkt->options_len; i++) { + if (pkt->options[i].opt_num & 0x1) { + DEBUG_PUTS("[reverse proxy] No option handling for reply, but critical option detected"); + const char *msg = "opt in reply"; + return nanocoap_server_send_separate(&conn->response_ctx, + COAP_CODE_INTERNAL_SERVER_ERROR, + COAP_TYPE_NON, + msg, strlen(msg)); + } + } + + DEBUG_PUTS("[reverse proxy] Forwarding reply"); + return nanocoap_server_send_separate(&conn->response_ctx, + coap_get_code_raw(pkt), + COAP_TYPE_NON, + pkt->payload, pkt->payload_len); +} + +static void _disconnect(nanocoap_rproxy_forward_ctx_t *conn) +{ + nanocoap_sock_close(&conn->client); + bf_unset(conn->proxy->forwards_used, index_of(conn->proxy->forwards, conn)); + +} + +static void _forward_request_handler(event_t *ev) +{ + nanocoap_rproxy_forward_ctx_t *conn = container_of(ev, nanocoap_rproxy_forward_ctx_t, ev); + DEBUG_PUTS("[reverse proxy] Forwarding request ..."); + ssize_t err = nanocoap_sock_request_cb(&conn->client, &conn->req, _request_done_cb, conn); + DEBUG("[reverse proxy] Forwarded request: %" PRIdSIZE "\n", err); + if (err < 0) { + char errnum[8]; + nanocoap_server_send_separate(&conn->response_ctx, + COAP_CODE_INTERNAL_SERVER_ERROR, + COAP_TYPE_NON, errnum, + fmt_s32_dec(errnum, err)); + } + _disconnect(conn); +} + +static bool _is_duplicate(nanocoap_rproxy_ctx_t *ctx, const coap_request_ctx_t *req) +{ + for (unsigned i = 0; i < CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS; i++) { + if (bf_isset(ctx->forwards_used, i)) { + if (nanocoap_server_is_remote_in_response_ctx(&ctx->forwards[i].response_ctx, req)) { + return true; + } + } + } + + return false; +} + +static int _connect(nanocoap_rproxy_forward_ctx_t **dest, nanocoap_rproxy_ctx_t *ctx, + const char *ep, size_t ep_len) +{ + if (ep_len >= sizeof(ctx->forwards[0].ep)) { + return -EINVAL; + } + + unsigned idx_free = CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS; + for (unsigned i = 0; i < CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS; i++) { + if (!bf_isset(ctx->forwards_used, i)) { + idx_free = i; + break; + } + } + + if (idx_free >= CONFIG_NANOCOAP_RPROXY_PARALLEL_FORWARDS) { + return -EAGAIN; + } + + char uri[CONFIG_NANOCOAP_URI_MAX]; + size_t scheme_len = strlen(ctx->scheme); + if (scheme_len + ep_len + 1 > sizeof(uri)) { + /* not enough space in uri to write scheme + ep + terminating zero byte */ + return -EOVERFLOW; + } + char *pos = uri; + memcpy(pos, ctx->scheme, scheme_len); + pos += scheme_len; + memcpy(pos, ep, ep_len); + pos += ep_len; + *pos = '\0'; + + nanocoap_rproxy_forward_ctx_t *conn = &ctx->forwards[idx_free]; + + int err = nanocoap_sock_url_connect(uri, &conn->client); + if (err) { + LOG_WARNING("Reverse proxy: Failed to connect to \"%s\"\n", uri); + return -EHOSTUNREACH; + } + + DEBUG("[reverse proxy] Connected to \"%s\"\n", uri); + + bf_set(ctx->forwards_used, idx_free); + memcpy(conn->ep, ep, ep_len); + conn->ep[ep_len] = '\0'; + conn->proxy = ctx; + + *dest = conn; + return 0; +} + +ssize_t nanocoap_rproxy_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, + coap_request_ctx_t *ctx) +{ + nanocoap_rproxy_ctx_t *proxy = coap_request_ctx_get_context(ctx); + + if (_is_duplicate(proxy, ctx)) { + DEBUG_PUTS("[reverse proxy] Got duplicate --> empty ACK (if needed)"); + return coap_reply_empty_ack(pkt, buf, len); + } + + char uri[CONFIG_NANOCOAP_URI_MAX] = ""; + coap_get_uri_path(pkt, (void *)uri); + + const char *my_path = coap_request_ctx_get_path(ctx); + const char *ep = uri + strlen(my_path); + /* If this does not hold the subtree matching should not have happened */ + assume(strlen(uri) >= strlen(my_path)); + + if (*ep != '/') { + DEBUG_PUTS("[reverse proxy] No endpoint specified --> 4.04"); + return coap_reply_simple(pkt, COAP_CODE_404, buf, len, COAP_FORMAT_NONE, + NULL, 0); + } + ep++; + const char *uri_remainder = NULL; + const char *ep_end = strchr(ep, '/'); + if (ep_end) { + uri_remainder = ep_end + 1; + } + else { + ep_end = ep + strlen(ep); + } + + nanocoap_rproxy_forward_ctx_t *conn = NULL; + { + int err = _connect(&conn, proxy, ep, (uintptr_t)ep_end - (uintptr_t)ep); + const char *msg_invalid_ep = "invalid ep"; + switch (err) { + case 0: + /* no error */ + break; + case -EAGAIN: + DEBUG_PUTS("[reverse proxy] No free slot for connection --> 4.29"); + return coap_reply_simple(pkt, COAP_CODE_TOO_MANY_REQUESTS, buf, len, + COAP_FORMAT_NONE, NULL, 0); + case -EINVAL: + DEBUG_PUTS("[reverse proxy] Invalid EP --> 4.00"); + return coap_reply_simple(pkt, COAP_CODE_BAD_REQUEST, buf, len, + COAP_FORMAT_NONE, msg_invalid_ep, strlen(msg_invalid_ep)); + case -EOVERFLOW: + DEBUG_PUTS("[reverse proxy] URI buffer too small to connect --> 5.00"); + return coap_reply_simple(pkt, COAP_CODE_BAD_REQUEST, buf, len, + COAP_FORMAT_NONE, NULL, 0); + case -EHOSTUNREACH: + DEBUG_PUTS("[reverse proxy] Failed to connect --> 4.04"); + return coap_reply_simple(pkt, COAP_CODE_404, buf, len, + COAP_FORMAT_NONE, NULL, 0); + default: + DEBUG("[reverse proxy] Unhandled error %d in _connect --> 5.00\n", err); + return coap_reply_simple(pkt, COAP_CODE_BAD_REQUEST, buf, len, + COAP_FORMAT_NONE, NULL, 0); + } + } + + if (nanocoap_server_prepare_separate(&conn->response_ctx, pkt, ctx)) { + DEBUG_PUTS("[reverse proxy] Failed to prepare response context --> RST"); + /* Send a RST message: We don't support extended tokens here */ + return coap_build_reply(pkt, 0, buf, len, 0); + } + + ssize_t hdr_len = nanocoap_sock_build_pkt(&conn->client, &conn->req, + buf, len, COAP_TYPE_CON, + coap_get_token(pkt), coap_get_token_len(pkt), + coap_get_code_raw(pkt)); + if (hdr_len < 0) { + DEBUG("[reverse proxy] Failed to build req to forward: %" PRIdSIZE " --> 5.00", hdr_len); + _disconnect(conn); + return coap_reply_simple(pkt, COAP_CODE_INTERNAL_SERVER_ERROR, + buf, len, COAP_FORMAT_NONE, NULL, 0); + } + + uint8_t *pktpos = buf + hdr_len; + uint8_t *pktend = buf + len; + uint16_t lastonum = 0; + for (unsigned i = 0; i < pkt->options_len; i++) { + bool forward_option = false; + uint16_t onum = pkt->options[i].opt_num; + switch (onum) { + /* convert URI-path: trim off prefix uses to identify target endpoint */ + case COAP_OPT_URI_PATH: + if (uri_remainder) { + /* CoAP option header can be 3 bytes: Even if Uri-Path (11) + * would be the first option (Option Delta would be 11), it + * fits into 4 bits. Option Length can be extended (+2 bytes + * in worst case */ + if (pktpos + 3 + strlen(uri_remainder) > pktend) { + /* URI-Path (potentially) overflows buffer */ + DEBUG_PUTS("[reverse proxy] Buffer too small for URI --> 5.00"); + _disconnect(conn); + return coap_reply_simple(pkt, COAP_CODE_INTERNAL_SERVER_ERROR, + buf, len, COAP_FORMAT_NONE, NULL, 0); + } + pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, uri_remainder); + } + break; + case COAP_OPT_BLOCK1: + case COAP_OPT_BLOCK2: + /* Block1 and Block2 are critical, so we cannot silently elide them. + * The are also unsafe to forward, so normally we would give up + * rather than forwarding them as opaque. But, yolo */ + forward_option = true; + break; + default: + if (onum & 0x2) { + /* Option is not safe for forwarding. If it is critical, we + * give up. Otherwise we just drop the option and continue. */ + if (onum & 0x1) { + /* option is critical, we cannot just elide it */ + DEBUG("[reverse proxy] Option %u not handled --> 4.02\n", (unsigned)onum); + _disconnect(conn); + char str_onum[8]; + return coap_reply_simple(pkt, COAP_CODE_BAD_OPTION, + buf, len, COAP_FORMAT_NONE, + str_onum, fmt_u32_dec(str_onum, onum)); + + } + } + else { + forward_option = true; + } + break; + } + + if (forward_option) { + /* option is safe for forwarding, we copy-paste it */ + coap_optpos_t pos = pkt->options[i]; + uint8_t *optval; + size_t optlen = coap_opt_get_next(pkt, &pos, &optval, false); + /* worst case option header is 5 bytes + option length */ + if (pktpos + 5 + optlen > pktend) { + /* option (potentially) overflows buffer */ + _disconnect(conn); + DEBUG_PUTS("[reverse proxy] Buffer too small for CoAP Option --> 5.00"); + return coap_reply_simple(pkt, COAP_CODE_INTERNAL_SERVER_ERROR, + buf, len, COAP_FORMAT_NONE, NULL, 0); + } + pktpos += coap_put_option(pktpos, lastonum, onum, optval, optlen); + lastonum = onum; + } + } + + conn->req.payload = pktpos; + conn->req.payload_len = pkt->payload_len; + if (pkt->payload_len) { + if (pktpos + 1 + pkt->payload_len > pktend) { + _disconnect(conn); + DEBUG_PUTS("[reverse proxy] Buffer too small for payload --> 5.00"); + return coap_reply_simple(pkt, COAP_CODE_INTERNAL_SERVER_ERROR, + buf, len, COAP_FORMAT_NONE, NULL, 0); + } + *pktpos++ = 0xff; /* payload marker */ + conn->req.payload = pktpos; + memcpy(pktpos, pkt->payload, pkt->payload_len); + } + + assume(conn->req.payload + conn->req.payload_len <= pktend); + conn->ev.handler = _forward_request_handler; + event_post(proxy->evq, &conn->ev); + DEBUG_PUTS("[reverse proxy] Request to forward queued --> ACK (if needed)"); + return coap_reply_empty_ack(pkt, buf, len); +} diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c index 06a852a3188c..803a6117c327 100644 --- a/sys/net/application_layer/nanocoap/sock.c +++ b/sys/net/application_layer/nanocoap/sock.c @@ -35,6 +35,14 @@ #include "sys/uio.h" /* IWYU pragma: keep (exports struct iovec) */ #include "ztimer.h" +#if MODULE_NANOCOAP_TCP +# include "net/sock/tcp.h" +#endif + +#if MODULE_NANOCOAP_SERVER_TCP +# include "net/sock/async/event.h" +#endif + #define ENABLE_DEBUG 0 #include "debug.h" @@ -53,6 +61,18 @@ # define CONFIG_NANOCOAP_MAX_OBSERVERS 4 #endif +#ifndef CONFIG_NANOCOAP_SERVER_TCP_TIMEOUT_US +# define CONFIG_NANOCOAP_SERVER_TCP_TIMEOUT_US 100000 +#endif + +#ifndef CONFIG_NANOCOAP_CLIENT_TCP_TIMEOUT_US +# define CONFIG_NANOCOAP_CLIENT_TCP_TIMEOUT_US CONFIG_NANOCOAP_SERVER_TCP_TIMEOUT_US +#endif + +#ifndef CONFIG_NANOCOAP_TCP_SOCKET_BUF_SIZE +# define CONFIG_NANOCOAP_TCP_SOCKET_BUF_SIZE 128 +#endif + enum { STATE_REQUEST_SEND, /**< request was just sent or will be sent again */ STATE_STOP_RETRANSMIT, /**< stop retransmissions due to a matching empty ACK */ @@ -98,13 +118,36 @@ static _observer_t _observer_pool[CONFIG_NANOCOAP_MAX_OBSERVERS]; static mutex_t _observer_pool_lock; #endif +XFA_INIT_CONST(nanocoap_sock_url_connect_handler_t, nanocoap_sock_url_connect_handlers); + +ssize_t nanocoap_sock_build_pkt(nanocoap_sock_t *sock, coap_pkt_t *pkt, + void *buf, size_t buf_len, + uint8_t type, + const void *token, size_t token_len, + uint8_t code) +{ + assert(sock && buf && (token || (token_len == 0))); + + switch (nanocoap_sock_get_type(sock)) { + default: + case COAP_SOCKET_TYPE_UDP: + case COAP_SOCKET_TYPE_DTLS: + return coap_build_udp(pkt, buf, buf_len, type, token, token_len, code, + nanocoap_sock_next_msg_id(sock)); + case COAP_SOCKET_TYPE_TCP: + return coap_build_tcp(pkt, buf, buf_len, token, token_len, code); + case COAP_SOCKET_TYPE_WS: + return coap_build_ws(pkt, buf, buf_len, token, token_len, code); + } +} + int nanocoap_sock_dtls_connect(nanocoap_sock_t *sock, sock_udp_ep_t *local, const sock_udp_ep_t *remote, credman_tag_t tag) { -#if IS_USED(MODULE_NANOCOAP_DTLS) +#if MODULE_NANOCOAP_DTLS uint8_t buf[CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE]; - sock->type = COAP_SOCKET_TYPE_DTLS; + nanocoap_sock_set_type(sock, COAP_SOCKET_TYPE_DTLS); return sock_dtls_establish_session(&sock->udp, &sock->dtls, &sock->dtls_session, tag, local, remote, buf, sizeof(buf)); #else @@ -112,6 +155,8 @@ int nanocoap_sock_dtls_connect(nanocoap_sock_t *sock, sock_udp_ep_t *local, (void)local; (void)remote; (void)tag; + extern void nanocoap_sock_dtls_connect_called_but_module_nanocoap_dtls_not_used(void); + nanocoap_sock_dtls_connect_called_but_module_nanocoap_dtls_not_used(); return -ENOTSUP; #endif } @@ -128,25 +173,49 @@ static int _get_error(const coap_pkt_t *pkt) } } -static inline nanocoap_socket_type_t _get_type(nanocoap_sock_t *sock) +#if MODULE_NANOCOAP_TCP +/* TODO: Implement sock_tcp_writev() to avoid sending the CoAP header and + * payload in separate TCP segments even if they would fit in a single one. */ +static int _tcp_writev(sock_tcp_t *sock, const iolist_t *iol) { -#if IS_USED(MODULE_NANOCOAP_DTLS) - return sock->type; -#else - (void)sock; - return COAP_SOCKET_TYPE_UDP; -#endif + while (iol) { + const uint8_t *data = iol->iol_base; + size_t len = iol->iol_len; + while (len) { + ssize_t tmp = sock_tcp_write(sock, data, len); + if (tmp < 0) { + return tmp; + } + data += tmp; + len -= tmp; + } + + iol = iol->iol_next; + } + + return 0; } +#endif static int _sock_sendv(nanocoap_sock_t *sock, const iolist_t *snips) { - switch (_get_type(sock)) { + switch (nanocoap_sock_get_type(sock)) { +#if MODULE_NANOCOAP_UDP case COAP_SOCKET_TYPE_UDP: return sock_udp_sendv(&sock->udp, snips, NULL); -#if IS_USED(MODULE_NANOCOAP_DTLS) +#endif +#if MODULE_NANOCOAP_DTLS case COAP_SOCKET_TYPE_DTLS: return sock_dtls_sendv(&sock->dtls, &sock->dtls_session, snips, CONFIG_SOCK_DTLS_TIMEOUT_MS); +#endif +#if MODULE_NANOCOAP_TCP + case COAP_SOCKET_TYPE_TCP: + return _tcp_writev(&sock->tcp, snips); +#endif +#if MODULE_NANOCOAP_WS + case COAP_SOCKET_TYPE_WS: + return sock->ws_conn->handle->transport->sendv(sock->ws_conn, snips); #endif default: assert(0); @@ -156,13 +225,24 @@ static int _sock_sendv(nanocoap_sock_t *sock, const iolist_t *snips) static int _sock_recv_buf(nanocoap_sock_t *sock, void **data, void **ctx, uint32_t timeout) { - switch (_get_type(sock)) { + (void)sock; + (void)data; + (void)ctx; + (void)timeout; + switch (nanocoap_sock_get_type(sock)) { +#if MODULE_NANOCOAP_UDP case COAP_SOCKET_TYPE_UDP: return sock_udp_recv_buf(&sock->udp, data, ctx, timeout, NULL); -#if IS_USED(MODULE_NANOCOAP_DTLS) +#endif +#if MODULE_NANOCOAP_DTLS case COAP_SOCKET_TYPE_DTLS: return sock_dtls_recv_buf(&sock->dtls, &sock->dtls_session, data, ctx, timeout); #endif + case COAP_SOCKET_TYPE_TCP: + /* this API does not work with TCP: There is no sock_tcp_recv_buf() + * and even if there were: We have no guarantee that CoAP messages + * align with TCP segments: A single segment could contains multiple + * CoAP messages or a partial CoAP message. */ default: assert(0); return -EINVAL; @@ -171,15 +251,21 @@ static int _sock_recv_buf(nanocoap_sock_t *sock, void **data, void **ctx, uint32 static int _send_ack(nanocoap_sock_t *sock, coap_pkt_t *pkt) { - coap_hdr_t ack; + switch (nanocoap_sock_get_type(sock)) { + default: + break; + case COAP_SOCKET_TYPE_TCP: + return -ENOTSUP; + } + + coap_udp_hdr_t ack; const iolist_t snip = { .iol_base = &ack, .iol_len = sizeof(ack), }; - coap_build_hdr(&ack, COAP_TYPE_ACK, NULL, 0, - COAP_CODE_EMPTY, ntohs(pkt->hdr->id)); + coap_build_empty_ack(pkt, &ack); return _sock_sendv(sock, &snip); } @@ -197,7 +283,7 @@ static bool _id_or_token_missmatch(const coap_pkt_t *pkt, unsigned id, /* falls through */ default: /* token has to match if message is not empty */ - if (pkt->hdr->code != 0) { + if (coap_get_code_raw(pkt) != 0) { if (coap_get_token_len(pkt) != token_len) { return true; } @@ -232,8 +318,8 @@ static void _sock_flush(nanocoap_sock_t *sock) while (_sock_recv_buf(sock, &payload, &ctx, 0) > 0 || ctx) {} } -ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, - coap_request_cb_t cb, void *arg) +static ssize_t nanocoap_sock_udp_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, + coap_request_cb_t cb, void *arg) { ssize_t tmp, res = 0; const unsigned id = coap_get_id(pkt); @@ -256,7 +342,7 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, /* Create the first payload snip from the request buffer */ iolist_t head = { .iol_next = pkt->snips, - .iol_base = pkt->hdr, + .iol_base = pkt->buf, .iol_len = coap_get_total_len(pkt), }; @@ -330,7 +416,7 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, } /* parse response */ - if (coap_parse(pkt, payload, res) < 0) { + if (coap_parse_udp(pkt, payload, res) < 0) { DEBUG("nanocoap: error parsing packet\n"); continue; } @@ -382,6 +468,372 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, return res; } +MAYBE_UNUSED +static int _tcp_ws_handle_csm(const coap_pkt_t *pkt) +{ + /* We don't implement any CSM Option. But we need to fail when stumbling + * upon a critical option */ + if (coap_has_unprocessed_critical_options(pkt)) { + return -ENOTSUP; + } + + return 0; +} + +int nanocoap_send_abort_signal(nanocoap_sock_t *sock) +{ + uint8_t buf[8]; + ssize_t retval; + switch (nanocoap_sock_get_type(sock)) { +#if MODULE_NANOCOAP_TCP + case COAP_SOCKET_TYPE_TCP: + retval = coap_build_tcp_hdr(buf, sizeof(buf), NULL, 0, COAP_CODE_SIGNAL_ABORT); + break; +#endif +#if MODULE_NANOCOAP_WS + case COAP_SOCKET_TYPE_WS: + retval = coap_build_ws_hdr(buf, sizeof(buf), NULL, 0, COAP_CODE_SIGNAL_ABORT); + break; +#endif + default: + return -ENOTSUP; + } + + if (retval < 0) { + return retval; + } + + iolist_t msg = { + .iol_base = buf, + .iol_len = retval, + .iol_next = NULL, + }; + (void)msg; + + switch (nanocoap_sock_get_type(sock)) { +#if MODULE_NANOCOAP_TCP + case COAP_SOCKET_TYPE_TCP: + { + /* data_len = length of packet after after CoAP Token */ + const size_t data_len = 0; + size_t shrunk = coap_finalize_tcp_header(buf, data_len); + msg.iol_base = (void *)((uintptr_t)msg.iol_base + shrunk); + msg.iol_len -= shrunk; + return _tcp_writev(&sock->tcp, &msg); + } +#endif +#if MODULE_NANOCOAP_WS + case COAP_SOCKET_TYPE_WS: + return sock->ws_conn->handle->transport->sendv(sock->ws_conn, &msg); +#endif + default: + break; + } + return -ENOTSUP; +} + +#if MODULE_NANOCOAP_TCP +static int _tcp_send(nanocoap_sock_t *sock, coap_pkt_t *pkt) +{ + coap_finalize_tcp(pkt); + const uint8_t *ptr = pkt->buf; + size_t remaining = coap_get_total_len(pkt); + ssize_t res; + while (remaining) { + res = sock_tcp_write(&sock->tcp, ptr, remaining); + if (res < 0) { + return res; + } + + if (res == 0) { + return -EOF; + } + + ptr += res; + remaining -= res; + } + + return 0; +} + +static ssize_t nanocoap_sock_tcp_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, + coap_request_cb_t cb, void *arg) +{ + ssize_t res = 0; + assume(sock && pkt && sock->tcp_buf); + + int err = _tcp_send(sock, pkt); + if (err) { + return err; + } + + /* We may still have unparsed data in the TCP buf. E.g. we must be prepared + * to receive CSM messages at any point in time */ + uint8_t *end = sock->tcp_buf + CONFIG_NANOCOAP_TCP_SOCKET_BUF_SIZE; + + bool response_handled = false; + while (!response_handled) { + uint8_t *pos = sock->tcp_buf + sock->tcp_buf_fill; + if (pos >= end) { + return -ENOBUFS; + } + + res = coap_parse_tcp(pkt, sock->tcp_buf, sock->tcp_buf_fill); + if (res == -EAGAIN) { + /* no full CoAP message in buf yet, receive more */ + res = sock_tcp_read(&sock->tcp, pos, (uintptr_t)end - (uintptr_t)pos, + CONFIG_NANOCOAP_CLIENT_TCP_TIMEOUT_US); + if (res < 0) { + return res; + } + if (res == 0) { + return -EOF; + } + + sock->tcp_buf_fill += res; + continue; + } + + /* parsing message failed, pass on error */ + if (res < 0) { + return res; + } + + if (coap_get_code_class(pkt) != COAP_CLASS_SIGNAL) { + cb(arg, pkt); + response_handled = true; + } + + /* We cannot have more bytes parsed than there are in the buffer. */ + assume((size_t)res <= sock->tcp_buf_fill); + sock->tcp_buf_fill = sock->tcp_buf_fill - (size_t)res; + if (sock->tcp_buf_fill) { + /* There are still bytes in the receive buffer that have not yet + * been parsed. Move them to the beginning of the buf */ + memmove(sock->tcp_buf, sock->tcp_buf + res, sock->tcp_buf_fill); + } + } + + if (res < 0) { + return res; + } + + return _get_error(pkt); +} +#endif + +#if MODULE_NANOCOAP_WS +int nanocoap_send_csm_message_ws(coap_ws_conn_t *conn, void *_buf, size_t buf_size) +{ + assert((buf_size >= 16) && (buf_size <= UINT16_MAX)); + uint8_t *buf = _buf; + ssize_t pos = coap_build_ws_hdr(buf, buf_size, NULL, 0, COAP_CODE_SIGNAL_CSM); + if (pos < 0) { + return pos; + } + + pos += coap_opt_put_uint(buf + pos, 0, COAP_SIGNAL_CSM_OPT_MAX_MESSAGE_SIZE, + buf_size); + pos += coap_put_option(buf + pos, COAP_SIGNAL_CSM_OPT_MAX_MESSAGE_SIZE, + COAP_SIGNAL_CSM_OPT_BLOCK_WISE_TRANSFER, NULL, 0); + /* Indicate support for extended token length, if selected. Allow up to + * half of the buffer to be used for the token. */ + if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + pos += coap_opt_put_uint(buf + pos, + COAP_SIGNAL_CSM_OPT_BLOCK_WISE_TRANSFER, + COAP_SIGNAL_CSM_OPT_EXTENDED_TOKEN_LENGTH, + buf_size / 2); + } + + DEBUG("nanocoap_ws: sending %u B of CSM\n", (unsigned)pos); + iolist_t out = { + .iol_base = buf, + .iol_len = pos, + .iol_next = NULL, + }; + + return conn->handle->transport->sendv(conn, &out); +} + +static void _client_ws_conn_recv_cb(coap_ws_conn_t *conn, void *msg, size_t msg_len) +{ + nanocoap_sock_t *sock = conn->arg; + coap_pkt_t pkt; + + { + ssize_t retval = coap_parse_ws(&pkt, msg, msg_len); + if (retval < 0) { + DEBUG("nanocoap_ws: failed to parse reply: %" PRIdSIZE " \n", retval); + conn->handle->transport->close(conn); + sock->ws_retval = -EBADMSG; + mutex_unlock(&sock->ws_sync); + return; + } + } + + if (coap_get_code_raw(&pkt) == COAP_CODE_SIGNAL_CSM) { + DEBUG_PUTS("nanocoap_ws: Got CSM signal message"); + int retval = _tcp_ws_handle_csm(&pkt); + if (retval) { + DEBUG("nanocoap_ws: Failed to handle CSM message: %d\n", retval); + conn->handle->transport->close(conn); + sock->ws_retval = -ECONNABORTED; + mutex_unlock(&sock->ws_sync); + } + return; + } + + unsigned irq_state = irq_disable(); + coap_request_cb_t cb = sock->ws_cb; + void *arg = sock->ws_cb_arg; + sock->ws_cb = NULL; + sock->ws_cb_arg = NULL; + sock->ws_retval = msg_len; + int err = _get_error(&pkt); + if (err) { + sock->ws_retval = err; + } + irq_restore(irq_state); + + if (cb) { + cb(arg, &pkt); + mutex_unlock(&sock->ws_sync); + return; + } + + DEBUG_PUTS("nanocoap_ws: Unexpected response"); +} + +static bool _client_ws_conn_reclaim_cb(coap_ws_conn_t *conn) +{ + (void)conn; + /* drop handle */ + return true; +} + +int nanocoap_sock_ws_connect(nanocoap_sock_t *sock, + const coap_ws_transport_t *transport, void *arg, + const void *remote_ep, const void *local_ep, size_t ep_len) +{ + assert(sock && transport && (remote_ep || !ep_len)); + *sock = (nanocoap_sock_t) { + .ws_sync = MUTEX_INIT_LOCKED, + }; + nanocoap_sock_set_type(sock, COAP_SOCKET_TYPE_WS); + static const coap_ws_cbs_t cbs = { + .conn_recv_cb = _client_ws_conn_recv_cb, + .conn_reclaim_cb = _client_ws_conn_reclaim_cb, + }; + coap_ws_handle_t *handle = transport->open_handle(&cbs, arg, local_ep, ep_len); + if (!handle) { + DEBUG_PUTS("nanocoap_sock_ws_connect: failted to open handle"); + return -ENETDOWN; + } + + sock->ws_conn = transport->connect(handle, remote_ep, ep_len, sock); + if (!sock->ws_conn) { + transport->close_handle(handle); + DEBUG_PUTS("nanocoap_sock_ws_connect: failed to open connection"); + return -ECONNREFUSED; + } + + int retval = nanocoap_send_csm_message_ws(sock->ws_conn, handle->tx_buf, + sizeof(handle->tx_buf)); + if (retval) { + DEBUG("nanocoap_sock_ws_connect: failed to send csm: %d\n", retval); + /* all connections corresponding to the handle are implicitly closed + * by the transport */ + transport->close_handle(handle); + return retval; + } + + return 0; +} + +struct sock_and_ztimer { + nanocoap_sock_t *sock; + ztimer_t timer; +}; + +static void _sock_recv_timeout_cb(void *_arg) +{ + struct sock_and_ztimer *arg = _arg; + mutex_unlock(&arg->sock->ws_sync); +} + +static ssize_t nanocoap_sock_ws_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, + coap_request_cb_t cb, void *arg) +{ + assume(sock && pkt); + DEBUG("nanocoap_ws: sending request over conn %p\n", (void *)sock->ws_conn); + + iolist_t iol = { + .iol_base = pkt->buf, + .iol_len = coap_get_total_len(pkt), + .iol_next = NULL, + }; + + unsigned irq_state = irq_disable(); + sock->ws_cb = cb; + sock->ws_cb_arg = arg; + sock->ws_retval = -ETIMEDOUT; + irq_restore(irq_state); + + /* make sure we block on the next call to mutex_lock() */ + (void)mutex_trylock(&sock->ws_sync); + + ssize_t err = sock->ws_conn->handle->transport->sendv(sock->ws_conn, &iol); + if (err) { + irq_state = irq_disable(); + sock->ws_cb = NULL; + sock->ws_cb_arg = NULL; + irq_restore(irq_state); + DEBUG("nanocoap_ws: sending request failed: %" PRIdSIZE "\n", err); + return err; + } + + struct sock_and_ztimer saz = { + .sock = sock, + .timer = { + .callback = _sock_recv_timeout_cb, + .arg = &saz, + }, + }; + if (IS_USED(MODULE_ZTIMER_MSEC)) { + ztimer_set(ZTIMER_MSEC, &saz.timer, 5 * MS_PER_SEC); + } + else { + ztimer_set(ZTIMER_USEC, &saz.timer, 5 * US_PER_SEC); + } + + mutex_lock(&sock->ws_sync); + ztimer_remove(IS_USED(MODULE_ZTIMER_MSEC) ? ZTIMER_MSEC : ZTIMER_USEC, &saz.timer); + err = sock->ws_retval; + DEBUG("nanocoap_sock_ws_request_cb returns %" PRIdSIZE " for conn %p\n", + err, (void *)sock->ws_conn); + return err; +} +#endif + +ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, + coap_request_cb_t cb, void *arg) +{ + switch (nanocoap_sock_get_type(sock)) { + default: + case COAP_SOCKET_TYPE_UDP: + case COAP_SOCKET_TYPE_DTLS: + return nanocoap_sock_udp_request_cb(sock, pkt, cb, arg); +#if MODULE_NANOCOAP_TCP + case COAP_SOCKET_TYPE_TCP: + return nanocoap_sock_tcp_request_cb(sock, pkt, cb, arg); +#endif +#if MODULE_NANOCOAP_WS + case COAP_SOCKET_TYPE_WS: + return nanocoap_sock_ws_request_cb(sock, pkt, cb, arg); +#endif + } +} + static int _request_cb(void *arg, coap_pkt_t *pkt) { struct iovec *buf = arg; @@ -396,10 +848,10 @@ static int _request_cb(void *arg, coap_pkt_t *pkt) return -ENOBUFS; } - memcpy(buf->iov_base, pkt->hdr, pkt_len); + memcpy(buf->iov_base, pkt->buf, pkt_len); - pkt->hdr = buf->iov_base; - pkt->payload = (uint8_t*)pkt->hdr + (pkt_len - pkt->payload_len); + pkt->buf = buf->iov_base; + pkt->payload = pkt->buf + (pkt_len - pkt->payload_len); return pkt_len; } @@ -407,7 +859,7 @@ static int _request_cb(void *arg, coap_pkt_t *pkt) ssize_t nanocoap_sock_request(nanocoap_sock_t *sock, coap_pkt_t *pkt, size_t len) { struct iovec buf = { - .iov_base = pkt->hdr, + .iov_base = pkt->buf, .iov_len = len, }; return nanocoap_sock_request_cb(sock, pkt, _request_cb, &buf); @@ -439,17 +891,17 @@ static ssize_t _sock_get(nanocoap_sock_t *sock, const char *path, uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX]; uint8_t *pktpos = buffer; - coap_pkt_t pkt = { - .hdr = (void *)pktpos, - }; + coap_pkt_t pkt; struct iovec ctx = { .iov_base = response, .iov_len = max_len, }; - pktpos += coap_build_hdr(pkt.hdr, type, NULL, 0, COAP_METHOD_GET, - nanocoap_sock_next_msg_id(sock)); + ssize_t hdr_len = nanocoap_sock_build_pkt(sock, &pkt, buffer, sizeof(buffer), + type, NULL, 0, COAP_METHOD_GET); + assume(hdr_len > 0); + pktpos += hdr_len; pktpos += coap_opt_put_uri_pathquery(pktpos, NULL, path); pkt.payload = pktpos; @@ -477,7 +929,7 @@ ssize_t _sock_put_post(nanocoap_sock_t *sock, const char *path, unsigned code, }; coap_pkt_t pkt = { - .hdr = (void *)buffer, + .buf = buffer, .snips = &payload, }; @@ -487,7 +939,10 @@ ssize_t _sock_put_post(nanocoap_sock_t *sock, const char *path, unsigned code, }; uint16_t lastonum = 0; - pktpos += coap_build_hdr(pkt.hdr, type, NULL, 0, code, nanocoap_sock_next_msg_id(sock)); + ssize_t hdr_len = nanocoap_sock_build_pkt(sock, NULL, buffer, sizeof(buffer), + type, NULL, 0, code); + assume(hdr_len > 0); + pktpos += hdr_len; pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, path); if (response == NULL && type == COAP_TYPE_NON) { @@ -605,12 +1060,13 @@ ssize_t nanocoap_sock_delete(nanocoap_sock_t *sock, const char *path) uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX]; uint8_t *pktpos = buffer; - coap_pkt_t pkt = { - .hdr = (void *)pktpos, - }; + coap_pkt_t pkt; - pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_DELETE, - nanocoap_sock_next_msg_id(sock)); + ssize_t hdr_len = nanocoap_sock_build_pkt(sock, &pkt, buffer, sizeof(buffer), + COAP_TYPE_CON, NULL, 0, + COAP_METHOD_DELETE); + assume(hdr_len > 0); + pktpos += hdr_len; pktpos += coap_opt_put_uri_pathquery(pktpos, NULL, path); pkt.payload = pktpos; @@ -632,13 +1088,13 @@ ssize_t nanocoap_sock_delete_url(const char *url) return res; } -ssize_t nanocoap_request(coap_pkt_t *pkt, const sock_udp_ep_t *local, - const sock_udp_ep_t *remote, size_t len) +ssize_t nanocoap_request_udp(coap_pkt_t *pkt, const sock_udp_ep_t *local, + const sock_udp_ep_t *remote, size_t len) { int res; nanocoap_sock_t sock; - res = nanocoap_sock_connect(&sock, local, remote); + res = nanocoap_sock_udp_connect(&sock, local, remote); if (res) { return res; } @@ -680,9 +1136,7 @@ static int _fetch_block(nanocoap_sock_t *sock, uint8_t *buf, size_t len, const char *path, coap_blksize_t blksize, _block_ctx_t *ctx) { - coap_pkt_t pkt = { - .hdr = (void *)buf, - }; + coap_pkt_t pkt; uint16_t lastonum = 0; void *token = NULL; @@ -695,13 +1149,14 @@ static int _fetch_block(nanocoap_sock_t *sock, uint8_t *buf, size_t len, token_len = sizeof(ctx->token); #endif - buf += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, token, token_len, COAP_METHOD_GET, - nanocoap_sock_next_msg_id(sock)); + ssize_t hdr_len = nanocoap_sock_build_pkt(sock, &pkt, buf, len, COAP_TYPE_CON, + token, token_len, COAP_METHOD_GET); + assume(hdr_len > 0); + buf += hdr_len; buf += coap_opt_put_uri_pathquery(buf, &lastonum, path); buf += coap_opt_put_uint(buf, lastonum, COAP_OPT_BLOCK2, (ctx->blknum << 4) | blksize); - (void)len; - assert((uintptr_t)buf - (uintptr_t)pkt.hdr < len); + assume((uintptr_t)buf - (uintptr_t)pkt.buf < len); pkt.payload = buf; pkt.payload_len = 0; @@ -726,16 +1181,15 @@ int nanocoap_sock_block_request(coap_block_request_t *req, .iol_len = len, }; - coap_pkt_t pkt = { - .hdr = (void *)buf, - .snips = &snip, - }; - - uint8_t *pktpos = (void *)pkt.hdr; + coap_pkt_t pkt; + uint8_t *pktpos = buf; uint16_t lastonum = 0; - pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, req->method, - nanocoap_sock_next_msg_id(req->sock)); + ssize_t hdr_size = nanocoap_sock_build_pkt(req->sock, &pkt, buf, sizeof(buf), + COAP_TYPE_CON, NULL, 0, + req->method); + assume(hdr_size > 0); + pktpos += hdr_size; pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, req->path); pktpos += coap_opt_put_uint(pktpos, lastonum, COAP_OPT_BLOCK1, (req->blknum << 4) | req->blksize | (more ? 0x8 : 0)); @@ -746,6 +1200,7 @@ int nanocoap_sock_block_request(coap_block_request_t *req, pkt.payload = pktpos; pkt.payload_len = 0; + pkt.snips = &snip; res = nanocoap_sock_request_cb(req->sock, &pkt, callback, arg); if (res < 0) { @@ -906,19 +1361,33 @@ int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path, int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock) { + size_t connectors_numof = XFA_LEN(nanocoap_sock_url_connect_handler_t, nanocoap_sock_url_connect_handlers); + for (size_t i = 0; i < connectors_numof; i++) { + int retval = nanocoap_sock_url_connect_handlers[i](url, sock); + if (retval != -ENOTSUP) { + return retval; + } + } + char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN]; - sock_udp_ep_t remote; + union { + sock_udp_ep_t udp; + sock_tcp_ep_t tcp; + } remote; - bool is_coaps = false; + nanocoap_socket_type_t type = COAP_SOCKET_TYPE_UDP; if (IS_USED(MODULE_NANOCOAP_DTLS) && !strncmp(url, "coaps://", 8)) { - DEBUG("nanocoap: CoAPS URL detected\n"); - is_coaps = true; + DEBUG_PUTS("nanocoap: CoAP over DTLS URL detected"); + type = COAP_SOCKET_TYPE_DTLS; } - - if (!is_coaps && strncmp(url, "coap://", 7)) { - DEBUG("nanocoap: URL doesn't start with \"coap://\"\n"); - return -EINVAL; + else if (IS_USED(MODULE_NANOCOAP_TCP) && !strncmp(url, "coap+tcp://", 11)) { + DEBUG_PUTS("nanocoap: CoAP over TCP URL detected"); + type = COAP_SOCKET_TYPE_TCP; + } + else if (strncmp(url, "coap://", 7)) { + DEBUG_PUTS("nanocoap: protocol scheme not recognized/supported"); + return -ENOTSUP; } if (sock_urlsplit(url, hostport, NULL) < 0) { @@ -926,37 +1395,57 @@ int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock) return -EINVAL; } - if (sock_udp_name2ep(&remote, hostport) < 0) { - DEBUG("nanocoap: invalid URL\n"); - return -EINVAL; + if (type == COAP_SOCKET_TYPE_TCP) { + if (sock_tcp_name2ep(&remote.tcp, hostport) < 0) { + DEBUG("nanocoap: invalid URL\n"); + return -EINVAL; + } + + if (!remote.tcp.port) { + remote.tcp.port = COAP_PORT; + } } + else { + if (sock_udp_name2ep(&remote.udp, hostport) < 0) { + DEBUG("nanocoap: invalid URL\n"); + return -EINVAL; + } - if (!remote.port) { - remote.port = is_coaps ? COAPS_PORT : COAP_PORT; + if (!remote.udp.port) { + remote.udp.port = (type == COAP_SOCKET_TYPE_DTLS) ? COAPS_PORT + : COAP_PORT; + } } - if (is_coaps) { + switch (type) { + default: + case COAP_SOCKET_TYPE_UDP: + return nanocoap_sock_udp_connect(sock, NULL, &remote.udp); + case COAP_SOCKET_TYPE_DTLS: + { #if SOCK_HAS_IPV6 - /* tinydtls wants the interface to match */ - if (!remote.netif && sock_udp_ep_is_v6(&remote) && - ipv6_addr_is_link_local((ipv6_addr_t *)remote.addr.ipv6)) { - netif_t *iface = netif_iter(NULL); - if (iface == NULL) { - return -ENODEV; + /* tinydtls wants the interface to match */ + if (!remote.udp.netif && sock_udp_ep_is_v6(&remote.udp) && + ipv6_addr_is_link_local((ipv6_addr_t *)remote.udp.addr.ipv6)) { + netif_t *iface = netif_iter(NULL); + if (iface == NULL) { + return -ENODEV; + } + remote.udp.netif = netif_get_id(iface); } - remote.netif = netif_get_id(iface); - } - sock_udp_ep_t local = SOCK_IPV6_EP_ANY; - if (!sock_udp_ep_is_v6(&remote)) { - local.family = AF_INET; - } + sock_udp_ep_t local = SOCK_IPV6_EP_ANY; + if (!sock_udp_ep_is_v6(&remote.udp)) { + local.family = AF_INET; + } #else - sock_udp_ep_t local = SOCK_IPV4_EP_ANY; + sock_udp_ep_t local = SOCK_IPV4_EP_ANY; #endif - return nanocoap_sock_dtls_connect(sock, &local, &remote, CONFIG_NANOCOAP_SOCK_DTLS_TAG); - } else { - return nanocoap_sock_connect(sock, NULL, &remote); + return nanocoap_sock_dtls_connect(sock, &local, &remote.udp, CONFIG_NANOCOAP_SOCK_DTLS_TAG); + } + break; + case COAP_SOCKET_TYPE_TCP: + return nanocoap_sock_tcp_connect(sock, 0, &remote.tcp); } } @@ -976,6 +1465,40 @@ int nanocoap_get_blockwise_url(const char *url, return res; } +void nanocoap_sock_close(nanocoap_sock_t *sock) +{ + switch (nanocoap_sock_get_type(sock)) { +#if MODULE_NANOCOAP_DTLS + case COAP_SOCKET_TYPE_DTLS: + sock_dtls_session_destroy(&sock->dtls, &sock->dtls_session); + sock_dtls_close(&sock->dtls); + break; +#endif +#if MODULE_NANOCOAP_UDP + case COAP_SOCKET_TYPE_UDP: + sock_udp_close(&sock->udp); + break; +#endif +#if MODULE_NANOCOAP_TCP + case COAP_SOCKET_TYPE_TCP: + nanocoap_send_abort_signal(sock); + sock_tcp_disconnect(&sock->tcp); + free(sock->tcp_buf); + sock->tcp_buf = NULL; + sock->tcp_buf_fill = 0; + break; +#endif +#if MODULE_NANOCOAP_WS + case COAP_SOCKET_TYPE_WS: + nanocoap_send_abort_signal(sock); + sock->ws_conn->handle->transport->close(sock->ws_conn); + break; +#endif + default: + assert(0); + } +} + typedef struct { uint8_t *ptr; size_t len; @@ -1019,12 +1542,438 @@ ssize_t nanocoap_get_blockwise_to_buf(nanocoap_sock_t *sock, const char *path, return (res < 0) ? (ssize_t)res : (ssize_t)_buf.len; } -int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) +static kernel_pid_t _coap_server_pid; +static void *_nanocoap_server_thread(void *local) +{ + static uint8_t buf[CONFIG_NANOCOAP_SERVER_BUF_SIZE]; + + nanocoap_server(local, buf, sizeof(buf)); + + return NULL; +} + +kernel_pid_t nanocoap_server_start_udp(const sock_udp_ep_t *local) +{ + static char stack[CONFIG_NANOCOAP_SERVER_STACK_SIZE]; + + if (_coap_server_pid) { + return _coap_server_pid; + } + _coap_server_pid = thread_create(stack, sizeof(stack), THREAD_PRIORITY_MAIN - 1, + 0, _nanocoap_server_thread, + (void *)local, "nanoCoAP server"); + return _coap_server_pid; +} + +void auto_init_nanocoap_server(void) +{ + sock_udp_ep_t local = { + .port = COAP_PORT, + .family = AF_INET6, + }; + + nanocoap_server_start_udp(&local); +} + +#if MODULE_NANOCOAP_TCP +int nanocoap_sock_tcp_connect(nanocoap_sock_t *sock, + uint16_t local_port, const sock_tcp_ep_t *remote) +{ + memset(sock, 0, sizeof(*sock)); + if (NULL == (sock->tcp_buf = malloc(CONFIG_NANOCOAP_TCP_SOCKET_BUF_SIZE))) { + return -ENOMEM; + } + + nanocoap_sock_set_type(sock, COAP_SOCKET_TYPE_TCP); + + int retval = sock_tcp_connect(&sock->tcp, remote, local_port, 0); + + if (retval) { + free(sock->tcp_buf); + return retval; + } + + ssize_t len = nanocoap_send_csm_message(&sock->tcp, sock->tcp_buf, + CONFIG_NANOCOAP_TCP_SOCKET_BUF_SIZE); + + if (len < 0) { + free(sock->tcp_buf); + sock_tcp_disconnect(&sock->tcp); + return len; + } + + return 0; +} + +ssize_t nanocoap_send_csm_message(sock_tcp_t *sock, void *_buf, size_t buf_size) +{ + assert((buf_size >= 16) && (buf_size <= UINT16_MAX)); + uint8_t *buf = _buf; + ssize_t pos = coap_build_tcp_hdr(buf, buf_size, NULL, 0, COAP_CODE_SIGNAL_CSM); + if (pos < 0) { + return pos; + } + + pos += coap_opt_put_uint(buf + pos, 0, COAP_SIGNAL_CSM_OPT_MAX_MESSAGE_SIZE, + buf_size); + pos += coap_put_option(buf + pos, COAP_SIGNAL_CSM_OPT_MAX_MESSAGE_SIZE, + COAP_SIGNAL_CSM_OPT_BLOCK_WISE_TRANSFER, NULL, 0); + /* Indicate support for extended token length, if selected. Allow up to + * half of the buffer to be used for the token. */ + if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) { + pos += coap_opt_put_uint(buf + pos, + COAP_SIGNAL_CSM_OPT_BLOCK_WISE_TRANSFER, + COAP_SIGNAL_CSM_OPT_EXTENDED_TOKEN_LENGTH, + buf_size / 2); + } + + size_t shrunk = coap_finalize_tcp_header_in_buf(buf, pos); + buf += shrunk; + pos -= shrunk; + DEBUG("nanocoap: sending %u B of CSM\n", (unsigned)pos); + return sock_tcp_write(sock, buf, pos); +} +#endif + +#if MODULE_NANOCOAP_SERVER_TCP +static void _server_tcp_data_cb(sock_tcp_t *sock, sock_async_flags_t flags, + void *arg) +{ + nanocoap_tcp_server_ctx_t *ctx = arg; + unsigned idx = index_of(ctx->socks, sock); + + if (flags & SOCK_ASYNC_CONN_FIN) { + DEBUG("nanocoap_tcp: TCP socket #%u closed. " + "Event flags = %x\n", + idx, (unsigned)flags); + sock_tcp_disconnect(sock); + return; + } + + if (!(flags & (SOCK_ASYNC_MSG_RECV | SOCK_ASYNC_MSG_SENT))) { + DEBUG("nanocoap_tcp: Unhandled event(s) on TCP socket #%u. " + "Event flags = %x\n", + idx, (unsigned)flags); + return; + } + + uint16_t fill = ctx->rx_fill[idx]; + const size_t bufsize = CONFIG_NANOCOAP_SERVER_BUF_SIZE; + uint8_t *rx_buf = &ctx->rx_bufs[idx * bufsize]; + uint8_t *tx_buf = ctx->tx_buf; + ssize_t len = sock_tcp_read(sock, rx_buf + fill, bufsize - fill, 0); + + if (len <= 0) { + if (len == -EAGAIN) { + DEBUG_PUTS("nanocoap_tcp: Got data ready event, but no data ready."); + return; + } + + DEBUG("nanocoap_tcp: Failed to read data for socket #%u: %" PRIdSIZE "\n", + idx, len); + + sock_tcp_disconnect(sock); + return; + } + + DEBUG("nanocoap_tcp: Got %" PRIdSIZE " B on socket #%u\n", len, idx); + + ctx->rx_fill[idx] = fill += len; + + /* a single segment may contain more than one CoAP message */ + while (fill) { + coap_pkt_t pkt; + len = coap_parse_tcp(&pkt, rx_buf, fill); + + if (len == -EAGAIN) { + DEBUG("nanocoap_tcp: CoAP message on socket #%u not yet complete\n", + idx); + return; + } + + ssize_t reply_len = 0; + int res; + + switch (coap_get_code_raw(&pkt)) { + case COAP_CODE_EMPTY: + /* > In CoAP over reliable transports, Empty messages + * > (Code 0.00) can always be sent and MUST be ignored by the + * > recipient. */ + break; + case COAP_CODE_SIGNAL_CSM: + res = _tcp_ws_handle_csm(&pkt); + if (res < 0) { + DEBUG("nanocoap_tcp: failed to handle CSM of client #%u: %d\n", + idx, res); + sock_tcp_disconnect(sock); + return; + } + break; + case COAP_CODE_SIGNAL_PING: + reply_len = coap_reply_simple(&pkt, COAP_CODE_SIGNAL_PONG, + tx_buf, bufsize, 0, NULL, 0); + break; + case COAP_CODE_SIGNAL_PONG: + /* We probably did not send a ping as server, but let's just + * ignore the pong anyway */ + break; + case COAP_CODE_SIGNAL_ABORT: + DEBUG_PUTS("nanocoap_tcp: got abort signal, closing sock"); + sock_tcp_disconnect(sock); + return; + default: + { + sock_tcp_ep_t remote; + sock_tcp_get_remote(sock, &remote); + coap_request_ctx_t req = { + .sock_tcp = sock, + }; + reply_len = coap_handle_req(&pkt, tx_buf, bufsize, &req); + } + } + + if (reply_len) { + if (reply_len < 0) { + DEBUG("nanocoap_tcp: error handling request of client #%u: %" + PRIdSIZE "\n", idx, reply_len); + sock_tcp_disconnect(sock); + return; + } + size_t shrunk = coap_finalize_tcp_header_in_buf(tx_buf, reply_len); + tx_buf += shrunk; + reply_len -= shrunk; + ssize_t outlen = sock_tcp_write(sock, tx_buf, reply_len); + + if (outlen != reply_len) { + DEBUG("nanocoap_tcp: failed to respond to client #%u: %" PRIdSIZE "\n", + idx, outlen); + sock_tcp_disconnect(sock); + return; + } + + DEBUG("nanocoap_tcp: replied %" PRIdSIZE " B to client #%u\n", + outlen, idx); + } + + fill -= len; + + /* if there is still unparsed data, move it to the front of the buf */ + if (fill) { + DEBUG("nanocoap_tcp: Still %u B of unparsed data in buf, " + "shuffling bytes around\n", + (unsigned)fill); + memmove(rx_buf, rx_buf + len, fill); + } + } + + ctx->rx_fill[idx] = fill; +} + +static void _server_tcp_listen_cb(sock_tcp_queue_t *queue, + sock_async_flags_t flags, void *arg) +{ + nanocoap_tcp_server_ctx_t *ctx = arg; + + if (!(flags & SOCK_ASYNC_CONN_RECV)) { + DEBUG("nanocoap_tcp: Unhandled event(s) on listen socket. " + "Event flags = %x\n", + (unsigned)flags); + return; + } + + sock_tcp_t *client; + int res = sock_tcp_accept(queue, &client, 0); + if (res != 0) { + DEBUG("nanocoap_tcp: sock_tcp_accept failed with %d\n", res); + return; + } + + DEBUG("nanocoap_tcp: accepted connection\n"); + + ssize_t len; + if ((len = nanocoap_send_csm_message(client, ctx->tx_buf, sizeof(ctx->tx_buf))) < 0) { + /* Silence linter: len is used with ENABLE_DEBUG == 1 */ + (void)len; + DEBUG("nanocoap_tcp: error sending CSM message: %" PRIdSIZE + " --> dropping just accepted connection\n", + len); + sock_tcp_disconnect(client); + return; + } + + sock_tcp_event_init(client, ctx->evq, _server_tcp_data_cb, ctx); + + unsigned idx = index_of(ctx->socks, client); + ctx->rx_fill[idx] = 0; + DEBUG("nanocoap_tcp: new client %u\n", idx); +} + +int nanocoap_server_tcp(nanocoap_tcp_server_ctx_t *ctx, + event_queue_t *evq, + const sock_tcp_ep_t *local) + +{ + assume(ctx && evq); + memset(ctx, 0, sizeof(*ctx)); + + ctx->evq = evq; + + if (local) { + memcpy(&ctx->local, local, sizeof(ctx->local)); + if (!ctx->local.port) { + ctx->local.port = COAP_PORT; + } + } + else { + ctx->local = (sock_tcp_ep_t){ .port = COAP_PORT, .family = AF_INET6 }; + } + + + int res = sock_tcp_listen(&ctx->queue, &ctx->local, ctx->socks, + ARRAY_SIZE(ctx->socks), 0); + if (res != 0) { + return res; + } + + sock_tcp_queue_event_init(&ctx->queue, evq, _server_tcp_listen_cb, ctx); + + return 0; +} +#endif + +#if MODULE_NANOCOAP_SERVER_WS +static bool _server_ws_conn_accept_cb(coap_ws_handle_t *handle, coap_ws_conn_t *conn) +{ + int retval = nanocoap_send_csm_message_ws(conn, handle->tx_buf, sizeof(handle->tx_buf)); + if (retval) { + DEBUG("nanocoap_server_ws: sending CSM failed: %d\n", retval); + handle->transport->close(conn); + return true; + } + + DEBUG("nanocoap_server_ws: new conn %p on handle %p\n", + (void *)conn, (void *)handle); + + return true; +} + +static void _server_ws_conn_recv_cb(coap_ws_conn_t *conn, void *msg, size_t msg_len) +{ + coap_pkt_t pkt; + ssize_t retval = coap_parse_ws(&pkt, msg, msg_len); + if (retval < 0) { + DEBUG("nanocoa_server_ws: failed to parse CoAP message on connection " + "%p. Closing connection.\n", + (void *)conn); + conn->handle->transport->close(conn); + return; + } + + int res; + ssize_t reply_len = 0; + uint8_t *buf = conn->handle->tx_buf; + const size_t buf_size = sizeof(conn->handle->tx_buf); + switch (coap_get_code_raw(&pkt)) { + case COAP_CODE_EMPTY: + /* > In CoAP over reliable transports, Empty messages + * > (Code 0.00) can always be sent and MUST be ignored by the + * > recipient. */ + break; + case COAP_CODE_SIGNAL_CSM: + res = _tcp_ws_handle_csm(&pkt); + if (res < 0) { + DEBUG("nanocoap_server_ws: failed to handle CSM on connection %p: %d\n", + (void *)conn, res); + conn->handle->transport->close(conn); + return; + } + + /* Hack for CoAP over YOLO: "Connection" may be replaced without us + * noticing, so we just reply with a CSM as if this was the initial + * handshanke. This is fine, as a CSM message is to be expected at + * any point in time. */ + res = nanocoap_send_csm_message_ws(conn, conn->handle->tx_buf, + sizeof(conn->handle->tx_buf)); + if (res < 0) { + DEBUG("nanocoap_server_ws: failed to reply CSM on connection %p: %d\n", + (void *)conn, res); + conn->handle->transport->close(conn); + return; + } + break; + case COAP_CODE_SIGNAL_PING: + reply_len = coap_reply_simple(&pkt, COAP_CODE_SIGNAL_PONG, + buf, buf_size, 0, NULL, 0); + break; + case COAP_CODE_SIGNAL_PONG: + /* We probably did not send a ping as server, but let's just + * ignore the pong anyway */ + break; + case COAP_CODE_SIGNAL_ABORT: + DEBUG_PUTS("nanocoap_server_ws: got abort signal, closing connection"); + conn->handle->transport->close(conn); + return; + default: + { + coap_request_ctx_t req = { + .conn_ws = conn, + }; + reply_len = coap_handle_req(&pkt, buf, buf_size, &req); + } + } + + if (reply_len) { + if (reply_len < 0) { + DEBUG("nanocoap_server_ws: error handling request from conn %p: %" + PRIdSIZE "\n", (void *)conn, reply_len); + conn->handle->transport->close(conn); + return; + } + + iolist_t iol = { + .iol_base = buf, + .iol_len = reply_len, + .iol_next = NULL, + }; + res = conn->handle->transport->sendv(conn, &iol); + + if (res) { + DEBUG("nanocoap_server_ws: failed to send reply to conn %p: %d\n", + (void *)conn, res); + conn->handle->transport->close(conn); + } + } +} + +int nanocoap_server_ws(const coap_ws_transport_t *transport, void *transport_arg, + const void *local_ep, size_t local_ep_len) +{ + assert(transport); + static const coap_ws_cbs_t _cbs = { + .conn_accept_cb = _server_ws_conn_accept_cb, + .conn_recv_cb = _server_ws_conn_recv_cb, + }; + coap_ws_handle_t *handle = transport->open_handle(&_cbs, transport_arg, + local_ep, local_ep_len); + if (!handle) { + DEBUG("nanocoap_server_ws: failed to open handle for transport %p\n", + (void *)transport); + return -ENETDOWN; + } + + DEBUG("nanocoap_server_ws: set up with handle %p\n", (void *)handle); + + return 0; +} +#endif + +#if MODULE_NANOCOAP_UDP +int nanocoap_server_udp(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) { sock_udp_t sock; sock_udp_ep_t remote; coap_request_ctx_t ctx = { - .remote = &remote, + .remote_udp = &remote, }; if (!local->port) { @@ -1039,12 +1988,12 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) while (1) { sock_udp_aux_rx_t *aux_in_ptr = NULL; -#ifdef MODULE_SOCK_AUX_LOCAL +# ifdef MODULE_SOCK_AUX_LOCAL sock_udp_aux_rx_t aux_in = { .flags = SOCK_AUX_GET_LOCAL, }; aux_in_ptr = &aux_in; -#endif +# endif res = sock_udp_recv_aux(&sock, buf, bufsize, SOCK_NO_TIMEOUT, &remote, aux_in_ptr); @@ -1053,13 +2002,13 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) continue; } coap_pkt_t pkt; - if (coap_parse(&pkt, (uint8_t *)buf, res) < 0) { + if (coap_parse_udp(&pkt, (uint8_t *)buf, res) < 0) { DEBUG("nanocoap: error parsing packet\n"); continue; } sock_udp_aux_tx_t *aux_out_ptr = NULL; -#ifdef MODULE_SOCK_AUX_LOCAL +# ifdef MODULE_SOCK_AUX_LOCAL /* make sure we reply with the same address that the request was * destined for -- except in the multicast case */ sock_udp_aux_tx_t aux_out = { @@ -1069,8 +2018,8 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) if (!sock_udp_ep_is_multicast(&aux_in.local)) { aux_out_ptr = &aux_out; } - ctx.local = &aux_in.local; -#endif + ctx.local_udp = &aux_in.local; +# endif if ((res = coap_handle_req(&pkt, buf, bufsize, &ctx)) <= 0) { DEBUG("nanocoap: error handling request %" PRIdSIZE "\n", res); continue; @@ -1081,39 +2030,7 @@ int nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize) return 0; } - -static kernel_pid_t _coap_server_pid; -static void *_nanocoap_server_thread(void *local) -{ - static uint8_t buf[CONFIG_NANOCOAP_SERVER_BUF_SIZE]; - - nanocoap_server(local, buf, sizeof(buf)); - - return NULL; -} - -kernel_pid_t nanocoap_server_start(const sock_udp_ep_t *local) -{ - static char stack[CONFIG_NANOCOAP_SERVER_STACK_SIZE]; - - if (_coap_server_pid) { - return _coap_server_pid; - } - _coap_server_pid = thread_create(stack, sizeof(stack), THREAD_PRIORITY_MAIN - 1, - 0, _nanocoap_server_thread, - (void *)local, "nanoCoAP server"); - return _coap_server_pid; -} - -void auto_init_nanocoap_server(void) -{ - sock_udp_ep_t local = { - .port = COAP_PORT, - .family = AF_INET6, - }; - - nanocoap_server_start(&local); -} +#endif #if MODULE_NANOCOAP_SERVER_SEPARATE int nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx, @@ -1132,20 +2049,56 @@ int nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx, } ctx->tkl = tkl; memcpy(ctx->token, coap_get_token(pkt), tkl); - memcpy(&ctx->remote, req->remote, sizeof(ctx->remote)); - assert(req->local); - memcpy(&ctx->local, req->local, sizeof(ctx->local)); uint32_t no_response = 0; coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response); ctx->no_response = no_response; + switch (coap_get_transport(pkt)) { +#ifdef MODULE_NANOCOAP_UDP + case COAP_TRANSPORT_UDP: + memcpy(&ctx->remote_udp, req->remote_udp, sizeof(ctx->remote_udp)); + assert(req->local); + memcpy(&ctx->local_udp, req->local_udp, sizeof(ctx->local_udp)); + break; +#endif +#ifdef MODULE_NANOCOAP_SERVER_TCP + case COAP_TRANSPORT_TCP: + ctx->sock_tcp = req->sock_tcp; + break; +#endif +#if MODULE_NANOCOAP_WS + case COAP_TRANSPORT_WS: + ctx->conn_ws = req->conn_ws; + break; +#endif + default: + return -ENOTSUP; + } + +#if NANOCOAP_ENABLED_TRANSPORTS > 1 + ctx->transport = coap_get_transport(pkt); +#endif + return 0; } bool nanocoap_server_is_remote_in_response_ctx(const nanocoap_server_response_ctx_t *ctx, const coap_request_ctx_t *req) { - return sock_udp_ep_equal(&ctx->remote, req->remote); + switch (nanocoap_server_response_ctx_transport(ctx)) { +#if MODULE_NANOCOAP_UDP + case COAP_TRANSPORT_UDP: + return sock_udp_ep_equal(&ctx->remote_udp, req->remote_udp); +#endif +#if MODULE_NANOCOAP_TCP + case COAP_TRANSPORT_TCP: + return ctx->sock_tcp == req->sock_tcp; +#endif + default: + (void)ctx; + (void)req; + return false; + } } ssize_t nanocoap_server_build_separate(const nanocoap_server_response_ctx_t *ctx, @@ -1155,9 +2108,6 @@ ssize_t nanocoap_server_build_separate(const nanocoap_server_response_ctx_t *ctx { assert(type != COAP_TYPE_ACK); assert(type != COAP_TYPE_CON); /* TODO: add support */ - if ((sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1) > buf_len) { - return -EOVERFLOW; - } const uint8_t no_response_index = (code >> 5) - 1; /* If the handler code misbehaved here, we'd face UB otherwise */ @@ -1168,23 +2118,34 @@ ssize_t nanocoap_server_build_separate(const nanocoap_server_response_ctx_t *ctx return -ECANCELED; } - return coap_build_hdr(buf, type, ctx->token, ctx->tkl, code, msg_id); + switch (nanocoap_server_response_ctx_transport(ctx)) { + case COAP_TRANSPORT_UDP: + return coap_build_udp_hdr(buf, buf_len, type, ctx->token, ctx->tkl, + code, msg_id); + case COAP_TRANSPORT_TCP: + return coap_build_tcp_hdr(buf, buf_len, ctx->token, ctx->tkl, code); + case COAP_TRANSPORT_WS: + return coap_build_ws_hdr(buf, buf_len, ctx->token, ctx->tkl, code); + default: + return -ENOTSUP; + } } -int nanocoap_server_sendv_separate(const nanocoap_server_response_ctx_t *ctx, - const iolist_t *reply) +# if MODULE_NANOCOAP_UDP +static int _nanocoap_server_sendv_separate_udp(const nanocoap_server_response_ctx_t *ctx, + const iolist_t *reply) { sock_udp_aux_tx_t *aux_out_ptr = NULL; /* make sure we reply with the same address that the request was * destined for -- except in the multicast case */ sock_udp_aux_tx_t aux_out = { .flags = SOCK_AUX_SET_LOCAL, - .local = ctx->local, + .local = ctx->local_udp, }; - if (!sock_udp_ep_is_multicast(&ctx->local)) { + if (!sock_udp_ep_is_multicast(&ctx->local_udp)) { aux_out_ptr = &aux_out; } - ssize_t retval = sock_udp_sendv_aux(NULL, reply, &ctx->remote, aux_out_ptr); + ssize_t retval = sock_udp_sendv_aux(NULL, reply, &ctx->remote_udp, aux_out_ptr); if (retval < 0) { return retval; @@ -1192,12 +2153,34 @@ int nanocoap_server_sendv_separate(const nanocoap_server_response_ctx_t *ctx, return 0; } +# endif + +int nanocoap_server_sendv_separate(const nanocoap_server_response_ctx_t *ctx, + const iolist_t *reply) +{ + switch (nanocoap_server_response_ctx_transport(ctx)) { +# if MODULE_NANOCOAP_UDP + case COAP_TRANSPORT_UDP: + return _nanocoap_server_sendv_separate_udp(ctx, reply); +# endif +# if MODULE_NANOCOAP_SERVER_TCP + case COAP_TRANSPORT_TCP: + return _tcp_writev(ctx->sock_tcp, reply); +# endif +# if MODULE_NANOCOAP_SERVER_WS + case COAP_TRANSPORT_WS: + return ctx->conn_ws->handle->transport->sendv(ctx->conn_ws, reply); +# endif + default: + return -ENOTSUP; + } +} int nanocoap_server_send_separate(const nanocoap_server_response_ctx_t *ctx, unsigned code, unsigned type, const void *payload, size_t len) { - uint8_t rbuf[sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1]; + uint8_t rbuf[sizeof(coap_udp_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1]; ssize_t hdr_len = nanocoap_server_build_separate(ctx, rbuf, sizeof(rbuf), code, type, random_uint32()); @@ -1226,6 +2209,23 @@ int nanocoap_server_send_separate(const nanocoap_server_response_ctx_t *ctx, #endif #if MODULE_NANOCOAP_SERVER_OBSERVE +static bool _response_ctx_is_ep_equal(const nanocoap_server_response_ctx_t *resp_ctx, + const coap_request_ctx_t *req_ctx) +{ + switch (nanocoap_server_response_ctx_transport(resp_ctx)) { +#if MODULE_NANOCOAP_UDP + case COAP_TRANSPORT_UDP: + return sock_udp_ep_equal(&resp_ctx->remote_udp, req_ctx->remote_udp); +#endif +#if MODULE_NANOCOAP_SERVER_TCP + case COAP_TRANSPORT_TCP: + return resp_ctx->sock_tcp == req_ctx->sock_tcp; +#endif + default: + return false; + } +} + int nanocoap_register_observer(const coap_request_ctx_t *req_ctx, coap_pkt_t *req_pkt) { mutex_lock(&_observer_pool_lock); @@ -1238,8 +2238,7 @@ int nanocoap_register_observer(const coap_request_ctx_t *req_ctx, coap_pkt_t *re free = &_observer_pool[i]; } if ((_observer_pool[i].resource == resource) - && sock_udp_ep_equal(&_observer_pool[i].response.remote, - coap_request_ctx_get_remote_udp(req_ctx))) + && _response_ctx_is_ep_equal(&_observer_pool[i].response, req_ctx)) { /* Deviation from the standard: Subscribing twice makes no * sense with our CoAP implementation, so either this is a @@ -1284,14 +2283,15 @@ void nanocoap_unregister_observer(const coap_request_ctx_t *req_ctx, && (_observer_pool[i].response.tkl == coap_get_token_len(req_pkt)) && !memcmp(_observer_pool[i].response.token, coap_get_token(req_pkt), _observer_pool[i].response.tkl) - && sock_udp_ep_equal(&_observer_pool[i].response.remote, coap_request_ctx_get_remote_udp(req_ctx))) { - DEBUG("nanocoap: observer at index %" PRIuSIZE " unregistered\n", i); + && _response_ctx_is_ep_equal(&_observer_pool[i].response, req_ctx)) { + DEBUG("nanocoap: observer at index %" PRIuSIZE " unregistred\n", i); _observer_pool[i].resource = NULL; } } mutex_unlock(&_observer_pool_lock); } +# if MODULE_NANOCOAP_UDP void nanocoap_unregister_observer_due_to_reset(const sock_udp_ep_t *ep, uint16_t msg_id) { @@ -1299,7 +2299,7 @@ void nanocoap_unregister_observer_due_to_reset(const sock_udp_ep_t *ep, for (size_t i = 0; i < CONFIG_NANOCOAP_MAX_OBSERVERS; i++) { if ((_observer_pool[i].resource != NULL) && (_observer_pool[i].msg_id == msg_id) - && sock_udp_ep_equal(&_observer_pool[i].response.remote, ep)) { + && sock_udp_ep_equal(&_observer_pool[i].response.remote_udp, ep)) { DEBUG("nanocoap: observer at index %" PRIuSIZE " unregistered due to RST\n", i); _observer_pool[i].resource = NULL; return; @@ -1307,13 +2307,14 @@ void nanocoap_unregister_observer_due_to_reset(const sock_udp_ep_t *ep, } mutex_unlock(&_observer_pool_lock); } +# endif void nanocoap_notify_observers(const coap_resource_t *res, const iolist_t *iol) { mutex_lock(&_observer_pool_lock); for (size_t i = 0; i < CONFIG_NANOCOAP_MAX_OBSERVERS; i++) { if (_observer_pool[i].resource == res) { - uint8_t rbuf[sizeof(coap_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1]; + uint8_t rbuf[sizeof(coap_udp_hdr_t) + COAP_TOKEN_LENGTH_MAX + 1]; ssize_t hdr_len = nanocoap_server_build_separate(&_observer_pool[i].response, rbuf, sizeof(rbuf), COAP_CODE_CONTENT, COAP_TYPE_NON, diff --git a/sys/net/application_layer/nanocoap/ws.c b/sys/net/application_layer/nanocoap/ws.c new file mode 100644 index 000000000000..84b2f974af0c --- /dev/null +++ b/sys/net/application_layer/nanocoap/ws.c @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * 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_nanocoap + * @{ + * + * @file + * @brief nanoCoAP WebSocket transports + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#include "bitfield.h" +#include "net/nanocoap_sock.h" +#include "net/nanocoap_ws.h" +#include "net/sock/async.h" +#include "net/sock/udp.h" +#include "net/sock/util.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if MODULE_NANOCOAP_WS_UDP_YOLO +static coap_ws_over_udp_yolo_init_arg_t _yolo_url_connect_handles[2]; +static BITFIELD(_yolo_url_connect_handles_used, ARRAY_SIZE(_yolo_url_connect_handles)); + +static coap_ws_over_udp_yolo_conn_t * _yolo_new_conn(coap_ws_over_udp_yolo_init_arg_t *yolo) +{ + for (unsigned i = 0; i < ARRAY_SIZE(yolo->conns); i++) { + if (yolo->conns[i].conn.handle == NULL) { + yolo->conns[i].conn.handle = &yolo->handle; + return &yolo->conns[i]; + } + } + + return NULL; +} + +static void _yolo_sock_udp_cb(sock_udp_t *sock, sock_async_flags_t flags, + void *arg) +{ + if (!(flags & SOCK_ASYNC_MSG_RECV)) { + return; + } + coap_ws_over_udp_yolo_init_arg_t *yolo = arg; + + void *data, *buf_ctx = NULL; + + sock_udp_ep_t remote; + ssize_t retval = sock_udp_recv_buf(sock, &data, &buf_ctx, 0, &remote); + DEBUG("ws_yolo: sock_udp_recv_buf() --> data = %p, buf_ctx = %p\n", + data, buf_ctx); + if (retval < 0) { + DEBUG("ws_yolo: recv failed: %" PRIdSIZE "\n", retval); + return; + } + + unsigned idx; + for (idx = 0; idx < ARRAY_SIZE(yolo->conns); idx++) { + if (sock_udp_ep_equal(&remote, &yolo->conns[idx].remote)) { + yolo->cbs->conn_recv_cb(&yolo->conns[idx].conn, data, retval); + break; + } + } + + if (idx >= ARRAY_SIZE(yolo->conns)) { + /* message from unknown endpoint --> new connection */ + if (yolo->cbs->conn_accept_cb) { + unsigned irq_state = irq_disable(); + coap_ws_over_udp_yolo_conn_t *conn = _yolo_new_conn(yolo); + memcpy(&conn->remote, &remote, sizeof(conn->remote)); + irq_restore(irq_state); + if (conn) { + if (yolo->cbs->conn_accept_cb(&yolo->handle, &conn->conn)) { + yolo->cbs->conn_recv_cb(&conn->conn, data, retval); + DEBUG("ws_yolo: Accepted connection #%u on handle %p\n", + (unsigned)index_of(yolo->conns, conn), (void *)&yolo->handle); + } + else { + /* connection not accepted, closing it again */ + irq_state = irq_disable(); + memset(conn, 0, sizeof(*conn)); + irq_restore(irq_state); + } + } + } + + } + + /* free network stack owned buffer */ + retval = sock_udp_recv_buf(sock, &data, &buf_ctx, 0, NULL); + assert((retval == 0) && (data == NULL)); +} + +static coap_ws_handle_t * _yolo_open_handle(const coap_ws_cbs_t *cbs, void *arg, + const void *ep, size_t ep_len) +{ + if (ep_len != sizeof(sock_udp_ep_t)) { + DEBUG_PUTS("ws_yolo: Failed to create handle: invalid listen endpoint"); + return NULL; + } + coap_ws_over_udp_yolo_init_arg_t *yolo = arg; + memset(yolo, 0, sizeof(*yolo)); + int retval = sock_udp_create(&yolo->sock, ep, NULL, 0); + if (retval) { + DEBUG("ws_yolo: Failed to create handle: sock_udp_create() --> %d\n", + retval); + return NULL; + } + yolo->cbs = cbs; + yolo->handle.transport = &coap_ws_over_udp_yolo; + sock_udp_set_cb(&yolo->sock, _yolo_sock_udp_cb, arg); + return &yolo->handle; +} + +static void _yolo_close_handle(coap_ws_handle_t *handle) +{ + coap_ws_over_udp_yolo_init_arg_t *yolo = container_of(handle, coap_ws_over_udp_yolo_init_arg_t, handle); + + /* The memory of the connections handed to NanoCoAP are still owned by + * NanoCoAP. Reclaim them to get ownership back */ + if (yolo->cbs->conn_reclaim_cb) { + for (unsigned i = 0; i < ARRAY_SIZE(yolo->conns); i++) { + if (yolo->conns[i].conn.handle != NULL) { + yolo->cbs->conn_reclaim_cb(&yolo->conns[i].conn); + } + } + } + + sock_udp_close(&yolo->sock); + + if (yolo->cleanup) { + yolo->cleanup(yolo); + } + DEBUG("ws_yolo: Closed handle %p\n", (void *)handle); +} + +static coap_ws_conn_t * _yolo_connect(coap_ws_handle_t *handle, + const void *ep, size_t ep_len, void *arg) +{ + coap_ws_over_udp_yolo_init_arg_t *yolo = container_of(handle, coap_ws_over_udp_yolo_init_arg_t, handle); + if (ep_len != sizeof(sock_udp_ep_t)) { + DEBUG_PUTS("ws_yolo: Endpoint not a UPD endpint"); + return NULL; + } + + unsigned irq_state = irq_disable(); + coap_ws_over_udp_yolo_conn_t *conn = _yolo_new_conn(yolo); + if (!conn) { + irq_restore(irq_state); + DEBUG_PUTS("ws_yolo: No unused connection to hand out"); + return NULL; + } + + conn->conn.arg = arg; + memcpy(&conn->remote, ep, sizeof(conn->remote)); + irq_restore(irq_state); + DEBUG("ws_yolo: Created connection #%u on handle %p\n", + (unsigned)index_of(yolo->conns, conn), (void *)&yolo->handle); + return &conn->conn; +} + +static void _yolo_close(coap_ws_conn_t *conn) +{ + assert(conn->handle && (conn->handle->transport == &coap_ws_over_udp_yolo)); + coap_ws_over_udp_yolo_conn_t *yolo_conn = container_of(conn, coap_ws_over_udp_yolo_conn_t, conn); + coap_ws_over_udp_yolo_init_arg_t *yolo = container_of(conn->handle, coap_ws_over_udp_yolo_init_arg_t, handle); + + DEBUG("ws_yolo: Closing connection #%u on handle %p\n", + (unsigned)index_of(yolo->conns, yolo_conn), (void *)&yolo->handle); + + bool clean_handle = false; + if (yolo->cbs->conn_reclaim_cb) { + clean_handle = yolo->cbs->conn_reclaim_cb(conn); + } + + unsigned irq_state = irq_disable(); + memset(yolo_conn, 0, sizeof(*yolo_conn)); + irq_restore(irq_state); + + if (clean_handle) { + _yolo_close_handle(&yolo->handle); + } +} + +static size_t _yolo_remote_ep(const coap_ws_conn_t *conn, const void **ep) +{ + assert(conn->handle && (conn->handle->transport == &coap_ws_over_udp_yolo)); + assert(ep); + coap_ws_over_udp_yolo_conn_t *yolo_conn = container_of(conn, coap_ws_over_udp_yolo_conn_t, conn); + *ep = &yolo_conn->remote; + return sizeof(yolo_conn->remote); +} + +static int _yolo_sendv(coap_ws_conn_t *conn, const iolist_t *iol) +{ + assert(conn->handle && (conn->handle->transport == &coap_ws_over_udp_yolo)); + coap_ws_over_udp_yolo_conn_t *yolo_conn = container_of(conn, coap_ws_over_udp_yolo_conn_t, conn); + coap_ws_over_udp_yolo_init_arg_t *yolo = container_of(conn->handle, coap_ws_over_udp_yolo_init_arg_t, handle); + ssize_t retval = sock_udp_sendv(&yolo->sock, iol, &yolo_conn->remote); + DEBUG("ws_yolo: Send to connection #%u on handle %p: %" PRIdSIZE "\n", + (unsigned)index_of(yolo->conns, yolo_conn), (void *)&yolo->handle, + retval); + + return (retval >= 0) ? 0 : (int)retval; +} + +const coap_ws_transport_t coap_ws_over_udp_yolo = { + .open_handle = _yolo_open_handle, + .close_handle = _yolo_close_handle, + .connect = _yolo_connect, + .close = _yolo_close, + .remote_ep = _yolo_remote_ep, + .sendv = _yolo_sendv, +}; + +static void _handle_cleanup(coap_ws_over_udp_yolo_init_arg_t *yolo) +{ + size_t idx = index_of(_yolo_url_connect_handles, yolo); + bf_unset_atomic(_yolo_url_connect_handles_used, idx); + DEBUG("ws_yolo: Clean up handle for url_connect #%u\n", (unsigned)idx); +} + +static int _yolo_url_connect(const char *url, nanocoap_sock_t *sock) +{ + if (strncmp(url, "coap+yolo://", 12)) { + return -ENOTSUP; + } + + coap_ws_over_udp_yolo_init_arg_t *arg = NULL; + + unsigned irq_state = irq_disable(); + size_t idx; + for (idx = 0; idx < ARRAY_SIZE(_yolo_url_connect_handles); idx++) { + if (!bf_isset(_yolo_url_connect_handles_used, idx)) { + bf_set(_yolo_url_connect_handles_used, idx); + arg = &_yolo_url_connect_handles[idx]; + break; + } + } + irq_restore(irq_state); + if (!arg) { + DEBUG_PUTS("ws_yolo: URL connect failed: out of handles"); + return -ENOMEM; + } + + char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN]; + if (sock_urlsplit(url, hostport, NULL)) { + bf_unset_atomic(_yolo_url_connect_handles_used, idx); + DEBUG_PUTS("ws_yolo: URL connect failed: no host/port in URL"); + return -EINVAL; + } + + sock_udp_ep_t remote; + if (sock_udp_name2ep(&remote, hostport)) { + bf_unset_atomic(_yolo_url_connect_handles_used, idx); + DEBUG_PUTS("ws_yolo: URL connect failed: invalid host/port"); + return -EINVAL; + } + + sock_udp_ep_t local = { + .family = remote.family + }; + + if (remote.port == 0) { + remote.port = 1337; + } + + int retval = nanocoap_sock_ws_connect(sock, &coap_ws_over_udp_yolo, arg, + &remote, &local, sizeof(remote)); + if (retval) { + DEBUG("ws_yolo: nanocoap_sock_ws_connect() returned %d\n", retval); + bf_unset_atomic(_yolo_url_connect_handles_used, idx); + return retval; + } + + arg->cleanup = _handle_cleanup; + return 0; +} + +NANOCOAP_SOCK_URL_CONNECT_HANDLER(_yolo_url_connect, 10); +#endif diff --git a/tests/net/nanocoap_cli/Makefile b/tests/net/nanocoap_cli/Makefile index 442ba186994c..20dd4922c32b 100644 --- a/tests/net/nanocoap_cli/Makefile +++ b/tests/net/nanocoap_cli/Makefile @@ -38,6 +38,7 @@ LOW_MEMORY_BOARDS := \ # Don't enable VFS, DTLS functions on small boards ifeq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS))) USEMODULE += nanocoap_dtls + USEMODULE += nanocoap_udp USEMODULE += prng_sha256prng USEMODULE += nanocoap_vfs @@ -48,9 +49,12 @@ ifeq (,$(filter $(BOARD),$(LOW_MEMORY_BOARDS))) # DTLS and VFS operations require more stack on the main thread CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(3*THREAD_STACKSIZE_DEFAULT\) - # always enable auto-format for native + # always enable auto-format and TCP support for native ifneq (,$(filter native native64,$(BOARD))) USEMODULE += vfs_auto_format + USEMODULE += nanocoap_tcp + USEMODULE += nanocoap_ws + USEMODULE += nanocoap_ws_udp_yolo endif endif @@ -59,3 +63,7 @@ USEMODULE += od USEMODULE += shell include $(RIOTBASE)/Makefile.include + +# A 2 minutes timeout on e.g. shell command `url get coap+tcp://...` is not +# acceptable. We reduce this to 2 seconds here. +CFLAGS += -DCONFIG_GNRC_TCP_CONNECTION_TIMEOUT_DURATION_MS=2000 diff --git a/tests/net/nanocoap_cli/nanocli_client.c b/tests/net/nanocoap_cli/nanocli_client.c index a9df7ab54765..2fdeecee0776 100644 --- a/tests/net/nanocoap_cli/nanocli_client.c +++ b/tests/net/nanocoap_cli/nanocli_client.c @@ -78,7 +78,7 @@ static ssize_t _send(coap_pkt_t *pkt, size_t len, return 0; } - return nanocoap_request(pkt, NULL, &remote, len); + return nanocoap_request_udp(pkt, NULL, &remote, len); } #if MODULE_NANOCOAP_TOKEN_EXT @@ -93,8 +93,7 @@ static int _cmd_client(int argc, char **argv) { /* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */ const char *method_codes[] = {"get", "post", "put"}; - unsigned buflen = 128; - uint8_t buf[buflen]; + uint8_t buf[128]; coap_pkt_t pkt; size_t len; @@ -113,14 +112,14 @@ static int _cmd_client(int argc, char **argv) goto end; } - pkt.hdr = (coap_hdr_t *)buf; + pkt.buf = buf; /* parse options */ if (argc == 5 || argc == 6) { - ssize_t hdrlen = coap_build_hdr(pkt.hdr, COAP_TYPE_CON, - _client_token, _client_token_len, - code_pos+1, 1); - coap_pkt_init(&pkt, &buf[0], buflen, hdrlen); + ssize_t hdrlen = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_CON, + _client_token, _client_token_len, + code_pos + 1, 1); + coap_pkt_init(&pkt, &buf[0], sizeof(buf), hdrlen); coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, argv[4], '/'); if (argc == 6) { coap_opt_add_uint(&pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_TEXT); @@ -137,7 +136,7 @@ static int _cmd_client(int argc, char **argv) printf("nanocli: sending msg ID %u, %" PRIuSIZE " bytes\n", coap_get_id(&pkt), len); - ssize_t res = _send(&pkt, buflen, argv[2], argv[3]); + ssize_t res = _send(&pkt, sizeof(buf), argv[2], argv[3]); if (res < 0) { printf("nanocli: msg send failed: %" PRIdSIZE "\n", res); } diff --git a/tests/net/nanocoap_cli/nanocli_server.c b/tests/net/nanocoap_cli/nanocli_server.c index af063d959cda..9ff990b80adb 100644 --- a/tests/net/nanocoap_cli/nanocli_server.c +++ b/tests/net/nanocoap_cli/nanocli_server.c @@ -33,7 +33,7 @@ * Customized implementation of nanocoap_server() to ignore a count of * requests. Allows testing confirmable messaging. */ -static int _nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize, +static int _nanocoap_server_udp(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize, int ignore_count) { sock_udp_t sock; @@ -62,10 +62,10 @@ static int _nanocoap_server(sock_udp_ep_t *local, uint8_t *buf, size_t bufsize, else { coap_pkt_t pkt; coap_request_ctx_t ctx = { - .remote = &remote, + .remote_udp = &remote, }; - if (coap_parse(&pkt, (uint8_t *)buf, res) < 0) { + if (coap_parse_udp(&pkt, (uint8_t *)buf, res) < 0) { DEBUG("nanocoap: error parsing packet\n"); continue; } @@ -85,7 +85,7 @@ static void _start_server(uint16_t port, int ignore_count) { uint8_t buf[128]; sock_udp_ep_t local = { .port=port, .family=AF_INET6 }; - _nanocoap_server(&local, buf, sizeof(buf), ignore_count); + _nanocoap_server_udp(&local, buf, sizeof(buf), ignore_count); } static int _cmd_server(int argc, char **argv) diff --git a/tests/net/nanocoap_cli/request_handlers.c b/tests/net/nanocoap_cli/request_handlers.c index 56b78162da52..cc13b68c2582 100644 --- a/tests/net/nanocoap_cli/request_handlers.c +++ b/tests/net/nanocoap_cli/request_handlers.c @@ -25,7 +25,6 @@ #include "fmt.h" #include "net/nanocoap.h" -#include "kernel_defines.h" #define _MAX_PAYLOAD_LEN (16) diff --git a/tests/pkg/edhoc_c/initiator.c b/tests/pkg/edhoc_c/initiator.c index c0597ef3751f..9e8301a7eb8e 100644 --- a/tests/pkg/edhoc_c/initiator.c +++ b/tests/pkg/edhoc_c/initiator.c @@ -20,9 +20,7 @@ #include #include "net/gnrc/netif.h" -#include "net/ipv6.h" #include "net/nanocoap_sock.h" -#include "shell.h" #include "edhoc/edhoc.h" #include "edhoc_keys.h" @@ -33,9 +31,6 @@ #include "tinycrypt/sha256.h" #endif -#define ENABLE_DEBUG 0 -#include "debug.h" - #define COAP_BUF_SIZE (256U) #if IS_ACTIVE(CONFIG_INITIATOR) @@ -114,11 +109,9 @@ static ssize_t _build_coap_pkt(coap_pkt_t *pkt, uint8_t *buf, ssize_t buflen, uint8_t token[2] = { 0xDA, 0xEC }; ssize_t len = 0; - /* set pkt buffer */ - pkt->hdr = (coap_hdr_t *)buf; /* build header, confirmed message always post */ - ssize_t hdrlen = coap_build_hdr(pkt->hdr, COAP_TYPE_CON, token, - sizeof(token), COAP_METHOD_POST, 1); + ssize_t hdrlen = coap_build_udp_hdr(buf, buflen, COAP_TYPE_CON, token, + sizeof(token), COAP_METHOD_POST, 1); coap_pkt_init(pkt, buf, buflen, hdrlen); coap_opt_add_string(pkt, COAP_OPT_URI_PATH, "/.well-known/edhoc", '/'); diff --git a/tests/riotboot_flashwrite/coap_handler.c b/tests/riotboot_flashwrite/coap_handler.c index 12a09de83916..e721ffa87c6c 100644 --- a/tests/riotboot_flashwrite/coap_handler.c +++ b/tests/riotboot_flashwrite/coap_handler.c @@ -8,7 +8,6 @@ #include #include -#include #include "net/nanocoap.h" #include "riotboot/flashwrite.h" @@ -72,10 +71,10 @@ ssize_t _flashwrite_handler(coap_pkt_t* pkt, uint8_t *buf, size_t len, coap_requ return reply_len; } - uint8_t *pkt_pos = (uint8_t*)pkt->hdr + reply_len; + uint8_t *pkt_pos = pkt->buf + reply_len; pkt_pos += coap_put_block1_ok(pkt_pos, &block1, 0); - return pkt_pos - (uint8_t*)pkt->hdr; + return pkt_pos - pkt->buf; } NANOCOAP_RESOURCE(flashwrite) { diff --git a/tests/unittests/tests-gcoap/tests-gcoap.c b/tests/unittests/tests-gcoap/tests-gcoap.c index a043847ae6d7..f963f7dde445 100644 --- a/tests/unittests/tests-gcoap/tests-gcoap.c +++ b/tests/unittests/tests-gcoap/tests-gcoap.c @@ -111,7 +111,7 @@ static void test_gcoap__client_get_resp(void) { uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; coap_pkt_t pdu; - int res; + ssize_t res; size_t hdr_fixed_len = 4; char exp_payload[] = "Oct 22 10:46:48"; size_t exp_tokenlen = 2; @@ -124,9 +124,9 @@ static void test_gcoap__client_get_resp(void) }; memcpy(buf, pdu_data, sizeof(pdu_data)); - res = coap_parse(&pdu, &buf[0], sizeof(pdu_data)); + res = coap_parse_udp(&pdu, &buf[0], sizeof(pdu_data)); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(sizeof(pdu_data), res); TEST_ASSERT_EQUAL_INT(COAP_CLASS_SUCCESS, coap_get_code_class(&pdu)); TEST_ASSERT_EQUAL_INT(exp_tokenlen, coap_get_token_len(&pdu)); TEST_ASSERT_EQUAL_INT(hdr_fixed_len + exp_tokenlen, coap_get_total_hdr_len(&pdu)); @@ -155,7 +155,7 @@ static void test_gcoap__client_put_req(void) len = coap_opt_finish(&pdu, COAP_OPT_FINISH_PAYLOAD); memcpy(pdu.payload, payload, 1); - coap_parse(&pdu, buf, len + 1); + coap_parse_udp(&pdu, buf, len + 1); TEST_ASSERT_EQUAL_INT(COAP_METHOD_PUT, coap_get_code_decimal(&pdu)); TEST_ASSERT_EQUAL_INT(1, pdu.payload_len); @@ -201,9 +201,9 @@ static void test_gcoap__client_get_path_defer(void) len = coap_opt_finish(&pdu, COAP_OPT_FINISH_NONE); TEST_ASSERT_EQUAL_INT(len, - sizeof(coap_hdr_t) + CONFIG_GCOAP_TOKENLEN + ETAG_SLACK + optlen); + sizeof(coap_udp_hdr_t) + CONFIG_GCOAP_TOKENLEN + ETAG_SLACK + optlen); - coap_parse(&pdu, buf, len); + coap_parse_udp(&pdu, buf, len); char uri[CONFIG_NANOCOAP_URI_MAX] = {0}; coap_get_uri_path(&pdu, (uint8_t *)&uri[0]); @@ -237,7 +237,7 @@ static void test_gcoap__client_ping(void) * Request from libcoap example for gcoap_cli /cli/stats resource * Include 2-byte token and Uri-Host option. */ -static int _read_cli_stats_req(coap_pkt_t *pdu, uint8_t *buf) +static ssize_t _read_cli_stats_req(coap_pkt_t *pdu, uint8_t *buf) { uint8_t pdu_data[] = { 0x52, 0x01, 0x20, 0xb6, 0x35, 0x61, 0x3d, 0x10, @@ -249,7 +249,7 @@ static int _read_cli_stats_req(coap_pkt_t *pdu, uint8_t *buf) }; memcpy(buf, pdu_data, sizeof(pdu_data)); - return coap_parse(pdu, buf, sizeof(pdu_data)); + return coap_parse_udp(pdu, buf, sizeof(pdu_data)); } /* Server GET request success case. Validate request example. */ @@ -258,9 +258,9 @@ static void test_gcoap__server_get_req(void) uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; coap_pkt_t pdu; - int res = _read_cli_stats_req(&pdu, &buf[0]); + ssize_t res = _read_cli_stats_req(&pdu, &buf[0]); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code_decimal(&pdu)); TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pdu)); TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pdu)); @@ -313,7 +313,7 @@ static void test_gcoap__server_get_resp(void) * Confirmable request from libcoap example for gcoap_cli /cli/stats resource. * Include 2-byte token. */ -static int _read_cli_stats_req_con(coap_pkt_t *pdu, uint8_t *buf) +static ssize_t _read_cli_stats_req_con(coap_pkt_t *pdu, uint8_t *buf) { uint8_t pdu_data[] = { 0x42, 0x01, 0x8e, 0x03, 0x35, 0x61, 0xb3, 0x63, @@ -321,7 +321,7 @@ static int _read_cli_stats_req_con(coap_pkt_t *pdu, uint8_t *buf) }; memcpy(buf, pdu_data, sizeof(pdu_data)); - return coap_parse(pdu, buf, sizeof(pdu_data)); + return coap_parse_udp(pdu, buf, sizeof(pdu_data)); } /* Server CON GET request success case. Validate request is confirmable. */ @@ -330,9 +330,9 @@ static void test_gcoap__server_con_req(void) uint8_t buf[CONFIG_GCOAP_PDU_BUF_SIZE]; coap_pkt_t pdu; - int res = _read_cli_stats_req_con(&pdu, &buf[0]); + ssize_t res = _read_cli_stats_req_con(&pdu, &buf[0]); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code_decimal(&pdu)); TEST_ASSERT_EQUAL_INT(COAP_TYPE_CON, coap_get_type(&pdu)); } diff --git a/tests/unittests/tests-nanocoap/Makefile.include b/tests/unittests/tests-nanocoap/Makefile.include index 2b94b4ef2eff..231bd8c9b757 100644 --- a/tests/unittests/tests-nanocoap/Makefile.include +++ b/tests/unittests/tests-nanocoap/Makefile.include @@ -1,2 +1,4 @@ USEMODULE += nanocoap +USEMODULE += nanocoap_udp +USEMODULE += nanocoap_tcp USEMODULE += nanocoap_token_ext diff --git a/tests/unittests/tests-nanocoap/tests-nanocoap.c b/tests/unittests/tests-nanocoap/tests-nanocoap.c index b6493b7b762c..facf1a03de73 100644 --- a/tests/unittests/tests-nanocoap/tests-nanocoap.c +++ b/tests/unittests/tests-nanocoap/tests-nanocoap.c @@ -21,7 +21,6 @@ #include "net/nanocoap.h" -#include "unittests-constants.h" #include "tests-nanocoap.h" #define _BUF_SIZE (128U) @@ -38,13 +37,13 @@ static void test_nanocoap__hdr(void) unsigned char path_tmp[64] = {0}; uint8_t *pktpos = &buf[0]; - pktpos += coap_build_hdr((coap_hdr_t *)pktpos, COAP_TYPE_CON, NULL, 0, + pktpos += coap_build_udp_hdr(pktpos, sizeof(buf), COAP_TYPE_CON, NULL, 0, COAP_METHOD_GET, msgid); pktpos += coap_opt_put_location_path(pktpos, 0, loc_path); pktpos += coap_opt_put_uri_path(pktpos, COAP_OPT_LOCATION_PATH, path); coap_pkt_t pkt; - coap_parse(&pkt, &buf[0], pktpos - &buf[0]); + TEST_ASSERT_EQUAL_INT(pktpos - &buf[0], coap_parse_udp(&pkt, &buf[0], pktpos - &buf[0])); TEST_ASSERT_EQUAL_INT(msgid, coap_get_id(&pkt)); @@ -70,12 +69,12 @@ static void test_nanocoap__hdr_2(void) uint8_t *pktpos = &buf[0]; uint16_t lastonum = 0; - pktpos += coap_build_hdr((coap_hdr_t *)pktpos, COAP_TYPE_CON, NULL, 0, - COAP_METHOD_GET, msgid); + pktpos += coap_build_udp_hdr(pktpos, sizeof(buf), COAP_TYPE_CON, NULL, 0, + COAP_METHOD_GET, msgid); pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, path); coap_pkt_t pkt; - coap_parse(&pkt, &buf[0], pktpos - &buf[0]); + TEST_ASSERT_EQUAL_INT(pktpos - &buf[0], coap_parse_udp(&pkt, &buf[0], pktpos - &buf[0])); TEST_ASSERT_EQUAL_INT(msgid, coap_get_id(&pkt)); @@ -102,8 +101,8 @@ static void test_nanocoap__get_req(void) size_t total_hdr_len = 6; size_t total_opt_len = 5; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); TEST_ASSERT_EQUAL_INT(total_hdr_len, len); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); @@ -140,8 +139,8 @@ static void test_nanocoap__put_req(void) size_t fmt_opt_len = 1; size_t accept_opt_len = 2; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_PUT, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_PUT, msgid); TEST_ASSERT_EQUAL_INT(total_hdr_len, len); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); @@ -178,8 +177,8 @@ static void test_nanocoap__get_multi_path(void) char path[] = "/ab/cde"; size_t uri_opt_len = 7; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -204,8 +203,8 @@ static void test_nanocoap__get_path_trailing_slash(void) char path[] = "/time/"; size_t uri_opt_len = 6; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -229,8 +228,8 @@ static void test_nanocoap__get_root_path(void) uint8_t token[2] = {0xDA, 0xEC}; char path[] = "/"; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -253,8 +252,8 @@ static void test_nanocoap__get_max_path(void) /* includes extra byte for option length > 12 */ size_t uri_opt_len = 64; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -281,8 +280,8 @@ static void test_nanocoap__get_path_too_long(void) /* includes extra byte for option length > 12 */ size_t uri_opt_len = 65; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -309,8 +308,8 @@ static void test_nanocoap__get_query(void) char qs[] = "ab=cde"; size_t query_opt_len = 7; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -352,8 +351,8 @@ static void test_nanocoap__get_multi_query(void) char key2[] = "f"; char qs[] = "ab=cde&f"; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -409,8 +408,8 @@ static void test_nanocoap__add_uri_query2(void) char qs3[] = "a=do&bcd&bcd"; size_t query3_opt_len = 4; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -467,8 +466,8 @@ static void test_nanocoap__option_add_buffer_max(void) size_t uri_opt_len = 64; /* option hdr 2, option value 62 */ - ssize_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + ssize_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -492,8 +491,8 @@ static void __test_option_remove(uint16_t stride) uint16_t msgid = 0xABCD; uint8_t token[2] = {0xDA, 0xEC}; - ssize_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + ssize_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); /* shrink buffer to attempt overfill */ coap_pkt_init(&pkt, &buf[0], sizeof(buf) - 1, len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -653,8 +652,8 @@ static void test_nanocoap__option_remove_no_payload(void) uint16_t msgid = 0xABCD; uint8_t token[2] = {0xDA, 0xEC}; - ssize_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + ssize_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); /* shrink buffer to attempt overfill */ coap_pkt_init(&pkt, &buf[0], sizeof(buf) - 1, len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -684,7 +683,7 @@ static void test_nanocoap__option_remove_no_payload(void) * Includes 2-byte token; non-confirmable. * Generated with libcoap. */ -static int _read_riot_value_req(coap_pkt_t *pkt, uint8_t *buf) +static ssize_t _read_riot_value_req(coap_pkt_t *pkt, uint8_t *buf) { uint8_t pkt_data[] = { 0x52, 0x01, 0x9e, 0x6b, 0x35, 0x61, 0xb4, 0x72, @@ -693,7 +692,7 @@ static int _read_riot_value_req(coap_pkt_t *pkt, uint8_t *buf) }; memcpy(buf, pkt_data, sizeof(pkt_data)); - return coap_parse(pkt, buf, sizeof(pkt_data)); + return coap_parse_udp(pkt, buf, sizeof(pkt_data)); } /* Server GET request success case. */ @@ -703,9 +702,9 @@ static void test_nanocoap__server_get_req(void) coap_pkt_t pkt; char path[] = "/riot/value"; - int res = _read_riot_value_req(&pkt, &buf[0]); + ssize_t res = _read_riot_value_req(&pkt, &buf[0]); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code_decimal(&pkt)); TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pkt)); TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pkt)); @@ -724,12 +723,12 @@ static void test_nanocoap__server_reply_simple(void) coap_pkt_t pkt; char *payload = "0"; - int res = _read_riot_value_req(&pkt, &buf[0]); + ssize_t res = _read_riot_value_req(&pkt, &buf[0]); coap_reply_simple(&pkt, COAP_CODE_CONTENT, buf, _BUF_SIZE, COAP_FORMAT_TEXT, (uint8_t *)payload, 1); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); TEST_ASSERT_EQUAL_INT(COAP_CODE_CONTENT, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pkt)); TEST_ASSERT_EQUAL_INT(4 + 2, coap_get_total_hdr_len(&pkt)); @@ -742,7 +741,7 @@ static void test_nanocoap__server_reply_simple(void) * Includes 2-byte token; confirmable. * Generated with libcoap. */ -static int _read_riot_value_req_con(coap_pkt_t *pkt, uint8_t *buf) +static ssize_t _read_riot_value_req_con(coap_pkt_t *pkt, uint8_t *buf) { uint8_t pkt_data[] = { 0x42, 0x01, 0xbe, 0x16, 0x35, 0x61, 0xb4, 0x72, @@ -751,7 +750,7 @@ static int _read_riot_value_req_con(coap_pkt_t *pkt, uint8_t *buf) }; memcpy(buf, pkt_data, sizeof(pkt_data)); - return coap_parse(pkt, buf, sizeof(pkt_data)); + return coap_parse_udp(pkt, buf, sizeof(pkt_data)); } /* Builds on test_nanocoap__server_get_req to test confirmable request. */ @@ -760,9 +759,9 @@ static void test_nanocoap__server_get_req_con(void) uint8_t buf[_BUF_SIZE]; coap_pkt_t pkt; - int res = _read_riot_value_req_con(&pkt, &buf[0]); + ssize_t res = _read_riot_value_req_con(&pkt, &buf[0]); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code_decimal(&pkt)); TEST_ASSERT_EQUAL_INT(COAP_TYPE_CON, coap_get_type(&pkt)); } @@ -786,9 +785,9 @@ static void test_nanocoap__server_option_count_overflow_check(void) { /* this test passes a forged CoAP packet containing 42 options (provided by * @nmeum in #10753, 42 is a random number which just needs to be higher - * than CONFIG_NANOCOAP_NOPTS_MAX) to coap_parse(). The used coap_pkt_t is part + * than CONFIG_NANOCOAP_NOPTS_MAX) to coap_parse_udp(). The used coap_pkt_t is part * of a struct, followed by an array of 42 coap_option_t. The array is - * cleared before the call to coap_parse(). If the overflow protection is + * cleared before the call to coap_parse_udp(). If the overflow protection is * working, the array must still be clear after parsing the packet, and the * proper error code (-ENOMEM) is returned. Otherwise, the parsing wrote * past scratch.pkt, thus the array is not zeroed anymore. @@ -814,11 +813,11 @@ static void test_nanocoap__server_option_count_overflow_check(void) memset(&scratch, 0, sizeof(scratch)); - int res = coap_parse(&scratch.pkt, pkt_data, sizeof(pkt_data)); + ssize_t res = coap_parse_udp(&scratch.pkt, pkt_data, sizeof(pkt_data)); /* check if any byte of the guard_data array is non-zero */ int dirty = 0; - volatile uint8_t *pos = scratch.guard_data; + uint8_t *pos = scratch.guard_data; for (size_t i = 0; i < sizeof(scratch.guard_data); i++) { if (*pos) { dirty = 1; @@ -831,7 +830,7 @@ static void test_nanocoap__server_option_count_overflow_check(void) } /* - * Verifies that coap_parse() recognizes inclusion of too many options. + * Verifies that coap_parse_udp() recognizes inclusion of too many options. */ static void test_nanocoap__server_option_count_overflow(void) { @@ -856,13 +855,13 @@ static void test_nanocoap__server_option_count_overflow(void) } /* don't read final two bytes, where overflow option will be added later */ - int res = coap_parse(&pkt, buf, sizeof(buf) - 2); - TEST_ASSERT_EQUAL_INT(0, res); + ssize_t res = coap_parse_udp(&pkt, buf, sizeof(buf) - 2); + TEST_ASSERT_EQUAL_INT(sizeof(buf) - 2, res); /* add option to overflow */ memcpy(&buf[base_len+i], fill_opt, 2); - res = coap_parse(&pkt, buf, sizeof(buf)); + res = coap_parse_udp(&pkt, buf, sizeof(buf)); TEST_ASSERT(res < 0); } @@ -877,7 +876,7 @@ static void test_nanocoap__server_option_count_overflow(void) * Uri-Query: lt=60 * Payload: (absent if omit_payload) */ -static int _read_rd_post_req(coap_pkt_t *pkt, bool omit_payload) +static ssize_t _read_rd_post_req(coap_pkt_t *pkt, bool omit_payload) { static uint8_t pkt_data[] = { 0x42, 0x02, 0x20, 0x92, 0xb9, 0x27, 0xbd, 0x04, @@ -892,7 +891,7 @@ static int _read_rd_post_req(coap_pkt_t *pkt, bool omit_payload) }; size_t len = omit_payload ? 59 : sizeof(pkt_data); - return coap_parse(pkt, pkt_data, len); + return coap_parse_udp(pkt, pkt_data, len); } /* @@ -901,8 +900,8 @@ static int _read_rd_post_req(coap_pkt_t *pkt, bool omit_payload) static void test_nanocoap__options_iterate(void) { coap_pkt_t pkt; - int res = _read_rd_post_req(&pkt, true); - TEST_ASSERT_EQUAL_INT(0, res); + ssize_t res = _read_rd_post_req(&pkt, true); + TEST_ASSERT(res > 0); /* read all options */ coap_optpos_t opt = {0, 0}; @@ -925,7 +924,7 @@ static void test_nanocoap__options_iterate(void) /* test with no payload to verify end of options handling */ memset(&pkt, 0, sizeof(pkt)); res = _read_rd_post_req(&pkt, false); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); for (int i = 0; i < 5; i++) { ssize_t optlen = coap_opt_get_next(&pkt, &opt, &value, !i); @@ -946,15 +945,15 @@ static void test_nanocoap__options_iterate(void) static void test_nanocoap__options_get_opaque(void) { coap_pkt_t pkt; - int res = _read_rd_post_req(&pkt, true); - TEST_ASSERT_EQUAL_INT(0, res); + ssize_t res = _read_rd_post_req(&pkt, true); + TEST_ASSERT(res > 0); /* read Uri-Query options */ uint8_t *value; ssize_t optlen = coap_opt_get_opaque(&pkt, COAP_OPT_URI_QUERY, &value); TEST_ASSERT_EQUAL_INT(24, optlen); - coap_optpos_t opt = {0, value + optlen - (uint8_t *)pkt.hdr}; + coap_optpos_t opt = {0, value + optlen - pkt.buf}; optlen = coap_opt_get_next(&pkt, &opt, &value, false); TEST_ASSERT_EQUAL_INT(0, opt.opt_num); @@ -977,9 +976,9 @@ static void test_nanocoap__empty(void) uint16_t msgid = 0xABCD; coap_pkt_t pkt; - int res = coap_parse(&pkt, pkt_data, 4); + ssize_t res = coap_parse_udp(&pkt, pkt_data, 4); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT(res > 0); TEST_ASSERT_EQUAL_INT(0, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(msgid, coap_get_id(&pkt)); TEST_ASSERT_EQUAL_INT(0, coap_get_token_len(&pkt)); @@ -987,12 +986,12 @@ static void test_nanocoap__empty(void) /* too short */ memset(&pkt, 0, sizeof(coap_pkt_t)); - res = coap_parse(&pkt, pkt_data, 3); + res = coap_parse_udp(&pkt, pkt_data, 3); TEST_ASSERT_EQUAL_INT(-EBADMSG, res); /* too long */ memset(&pkt, 0, sizeof(coap_pkt_t)); - res = coap_parse(&pkt, pkt_data, 5); + res = coap_parse_udp(&pkt, pkt_data, 5); TEST_ASSERT_EQUAL_INT(-EBADMSG, res); } @@ -1011,8 +1010,8 @@ static void test_nanocoap__add_path_unterminated_string(void) /* some random non-zero character at the end of /time */ path[path_len] = 'Z'; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -1037,8 +1036,8 @@ static void test_nanocoap__add_get_proxy_uri(void) uint8_t token[2] = {0xDA, 0xEC}; char proxy_uri[60] = "coap://[2001:db8::1]:5683/.well-known/core"; - size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + size_t len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); TEST_ASSERT_EQUAL_INT(len, coap_get_total_hdr_len(&pkt)); @@ -1056,7 +1055,7 @@ static void test_nanocoap__add_get_proxy_uri(void) } /* - * Verifies that coap_parse() recognizes token length bigger than allowed. + * Verifies that coap_parse_udp() recognizes token length bigger than allowed. */ static void test_nanocoap__token_length_over_limit(void) { @@ -1076,28 +1075,28 @@ static void test_nanocoap__token_length_over_limit(void) coap_pkt_t pkt; /* Valid packet (TKL = 8) */ - int res = coap_parse(&pkt, buf_valid, sizeof(buf_valid)); + ssize_t res = coap_parse_udp(&pkt, buf_valid, sizeof(buf_valid)); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(sizeof(buf_valid), res); TEST_ASSERT_EQUAL_INT(1, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(msgid, coap_get_id(&pkt)); TEST_ASSERT_EQUAL_INT(8, coap_get_token_len(&pkt)); TEST_ASSERT_EQUAL_INT(0, pkt.payload_len); /* Invalid packet (TKL = 15) */ - res = coap_parse(&pkt, buf_invalid, sizeof(buf_invalid)); + res = coap_parse_udp(&pkt, buf_invalid, sizeof(buf_invalid)); TEST_ASSERT_EQUAL_INT(-EBADMSG, res); } /* - * Verifies that coap_parse() recognizes 8 bit extended token length + * Verifies that coap_parse_udp() recognizes 8 bit extended token length */ static void test_nanocoap__token_length_ext_16(void) { const char *token = "0123456789ABCDEF"; uint8_t buf[32]; - coap_hdr_t *hdr = (void *)buf; + coap_udp_hdr_t *hdr = (void *)buf; /* build a request with an overlong token (that mandates the use of * an 8 bit extended token length field) */ @@ -1107,9 +1106,9 @@ static void test_nanocoap__token_length_ext_16(void) /* parse the packet build, and verify it parses back as expected */ coap_pkt_t pkt; - int res = coap_parse(&pkt, buf, 21); + ssize_t res = coap_parse_udp(&pkt, buf, 21); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(21, res); TEST_ASSERT_EQUAL_INT(21, coap_get_total_hdr_len(&pkt)); TEST_ASSERT_EQUAL_INT(COAP_METHOD_DELETE, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(23, coap_get_id(&pkt)); @@ -1124,8 +1123,8 @@ static void test_nanocoap__token_length_ext_16(void) ssize_t len = coap_build_reply_header(&pkt, COAP_CODE_DELETED, rbuf, sizeof(rbuf), 0, NULL, NULL); TEST_ASSERT_EQUAL_INT(21, len); - res = coap_parse(&pkt, rbuf, 21); - TEST_ASSERT_EQUAL_INT(0, res); + res = coap_parse_udp(&pkt, rbuf, 21); + TEST_ASSERT_EQUAL_INT(21, res); TEST_ASSERT_EQUAL_INT(21, coap_get_total_hdr_len(&pkt)); TEST_ASSERT_EQUAL_INT(COAP_CODE_DELETED, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(23, coap_get_id(&pkt)); @@ -1136,7 +1135,7 @@ static void test_nanocoap__token_length_ext_16(void) } /* - * Verifies that coap_parse() recognizes 16 bit extended token length + * Verifies that coap_parse_udp() recognizes 16 bit extended token length */ static void test_nanocoap__token_length_ext_269(void) { @@ -1146,7 +1145,7 @@ static void test_nanocoap__token_length_ext_269(void) "et justo duo dolores et ea rebum. Stet clita kasd gubergren," "no sea takimata sanctus est Lore."; uint8_t buf[280]; - coap_hdr_t *hdr = (void *)buf; + coap_udp_hdr_t *hdr = (void *)buf; /* build a request with an overlong token (that mandates the use of * an 16 bit extended token length field) */ @@ -1156,9 +1155,9 @@ static void test_nanocoap__token_length_ext_269(void) /* parse the packet build, and verify it parses back as expected */ coap_pkt_t pkt; - int res = coap_parse(&pkt, buf, 275); + ssize_t res = coap_parse_udp(&pkt, buf, 275); - TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(275, res); TEST_ASSERT_EQUAL_INT(275, coap_get_total_hdr_len(&pkt)); TEST_ASSERT_EQUAL_INT(COAP_METHOD_DELETE, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(23, coap_get_id(&pkt)); @@ -1174,8 +1173,8 @@ static void test_nanocoap__token_length_ext_269(void) sizeof(rbuf), 0, NULL, NULL); TEST_ASSERT_EQUAL_INT(275, len); - res = coap_parse(&pkt, rbuf, 275); - TEST_ASSERT_EQUAL_INT(0, res); + res = coap_parse_udp(&pkt, rbuf, 275); + TEST_ASSERT_EQUAL_INT(275, res); TEST_ASSERT_EQUAL_INT(275, coap_get_total_hdr_len(&pkt)); TEST_ASSERT_EQUAL_INT(COAP_CODE_DELETED, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(23, coap_get_id(&pkt)); @@ -1208,7 +1207,7 @@ static void test_nanocoap___rst_message(void) /* now check that parsing it back works */ coap_pkt_t pkt; - TEST_ASSERT_EQUAL_INT(0, coap_parse(&pkt, buf, sizeof(rst_expected))); + TEST_ASSERT_EQUAL_INT(sizeof(rst_expected), coap_parse_udp(&pkt, buf, sizeof(rst_expected))); TEST_ASSERT_EQUAL_INT(COAP_TYPE_RST, coap_get_type(&pkt)); TEST_ASSERT_EQUAL_INT(0, coap_get_code_raw(&pkt)); TEST_ASSERT_EQUAL_INT(0, coap_get_token_len(&pkt)); @@ -1221,12 +1220,220 @@ static void test_nanocoap___rst_message(void) 0xde, 0xed, 0xbe, 0xef, /* Token = 0xdeadbeef */ }; memset(buf, 0x55, sizeof(buf)); - TEST_ASSERT_EQUAL_INT(0, coap_parse(&pkt, con_request, sizeof(con_request))); + TEST_ASSERT_EQUAL_INT(sizeof(con_request), coap_parse_udp(&pkt, con_request, sizeof(con_request))); TEST_ASSERT_EQUAL_INT(sizeof(rst_expected), coap_build_reply(&pkt, 0, buf, sizeof(buf), 0)); TEST_ASSERT(0 == memcmp(rst_expected, buf, sizeof(rst_expected))); TEST_ASSERT_EQUAL_INT(0x55, buf[sizeof(rst_expected)]); } +/* Test building and parsing a simple TCP header */ +static void test_nanocoap__tcp_basic(void) +{ + ssize_t res; + size_t shrunk; + coap_pkt_t pkt; + uint8_t buf[32]; + const uint8_t token[] = { 0xde, 0xad, 0xbe, 0xef }; + const uint8_t expected[] = { + 0x04, /* Len = 0, TKL = 4 */ + COAP_METHOD_GET, /* Code = 0.01 GET */ + 0xde, 0xad, 0xbe, 0xef, /* Token = deadbeef */ + }; + + res = coap_build_tcp_hdr(buf, sizeof(buf), token, sizeof(token), COAP_METHOD_GET); + TEST_ASSERT((ssize_t)sizeof(expected) <= res); + shrunk = coap_finalize_tcp_header_in_buf(buf, res); + TEST_ASSERT_EQUAL_INT(sizeof(expected), (size_t)res - shrunk); + TEST_ASSERT(memcmp(expected, buf + shrunk, sizeof(expected)) == 0); + + /* same, but with coap_pkt_t initialized */ + res = coap_build_tcp(&pkt, buf, sizeof(buf), token, sizeof(token), COAP_METHOD_GET); + TEST_ASSERT((ssize_t)sizeof(expected) <= res); + TEST_ASSERT(buf == pkt.buf); + TEST_ASSERT_EQUAL_INT((uintptr_t)buf + sizeof(buf) - (uintptr_t)pkt.payload, pkt.payload_len); + /* we add no payload and finalize */ + pkt.payload_len = 0; + coap_finalize_tcp(&pkt); + TEST_ASSERT(buf <= pkt.buf); + TEST_ASSERT_EQUAL_INT(sizeof(expected), (uintptr_t)pkt.payload - (uintptr_t)pkt.buf); + TEST_ASSERT(memcmp(expected, pkt.buf, sizeof(expected)) == 0); + + /* now verify that the coap_pkt_t is correctly initialized and that the + * getters work */ + TEST_ASSERT(pkt.payload == pkt.buf + sizeof(expected)); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT(0 == memcmp(token, coap_get_token(&pkt), sizeof(token))); + TEST_ASSERT_EQUAL_INT(COAP_TRANSPORT_TCP, coap_get_transport(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_method(&pkt)); +} + +/* Test building and parsing a tiny TCP header */ +static void test_nanocoap__tcp_tiny(void) +{ + ssize_t res; + coap_pkt_t pkt; + uint8_t msg[] = { + 0x00, /* Len = 0, TKL = 0 */ + COAP_CODE_404, /* Code = 4.04 Not Found*/ + }; + + res = coap_parse_tcp(&pkt, msg, sizeof(msg)); + /* now verify that the coap_pkt_t is correctly initialized and that the + * getters work */ + TEST_ASSERT_EQUAL_INT(sizeof(msg), res); + TEST_ASSERT(pkt.payload == pkt.buf + sizeof(msg)); + TEST_ASSERT_EQUAL_INT(0, coap_get_token_len(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_TRANSPORT_TCP, coap_get_transport(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_CODE_404, coap_get_method(&pkt)); +} + +/* Test request/response exchange with CoAP over TCP. Doing so while reusing + * the request buffer for the response, as often done in our code base. */ +static void test_nanocoap__tcp_request_response(void) +{ + ssize_t res; + size_t shrunk; + coap_pkt_t pkt; + uint8_t buf[64]; + const uint8_t token[] = { 0x13, 0x37, 0x42 }; + const char path[] = "/some/path"; + char parsed_path[sizeof(path)]; + + /* build request */ + res = coap_build_tcp_hdr(buf, sizeof(buf), token, sizeof(token), COAP_METHOD_GET); + TEST_ASSERT(res > 0); + res += coap_opt_put_uri_path(buf + res, 0, path); + shrunk = coap_finalize_tcp_header_in_buf(buf, res); + + /* parse request */ + memset(&pkt, 0, sizeof(pkt)); + TEST_ASSERT_EQUAL_INT(res - shrunk, coap_parse_tcp(&pkt, buf + shrunk, res - shrunk)); + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_method(&pkt)); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT(0 == memcmp(token, coap_get_token(&pkt), sizeof(token))); + res = coap_get_uri_path(&pkt, (void *)parsed_path); + TEST_ASSERT_EQUAL_INT(sizeof(path), res); + TEST_ASSERT(0 == memcmp(path, parsed_path, sizeof(parsed_path))); + + /* build reply */ + const char *pld = "test"; + const size_t pld_len = strlen(pld); + res = coap_reply_simple(&pkt, COAP_CODE_CONTENT, + buf, sizeof(buf), + COAP_FORMAT_TEXT, pld, pld_len); + TEST_ASSERT(res > 0); + shrunk = coap_finalize_tcp_header_in_buf(buf, res); + /* parse reply */ + memset(&pkt, 0, sizeof(pkt)); + TEST_ASSERT_EQUAL_INT(res - shrunk, coap_parse_tcp(&pkt, buf + shrunk, res - shrunk)); + TEST_ASSERT_EQUAL_INT(COAP_CODE_CONTENT, coap_get_code_raw(&pkt)); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT(0 == memcmp(token, coap_get_token(&pkt), sizeof(token))); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT_EQUAL_INT(pld_len, pkt.payload_len); + TEST_ASSERT(0 == memcmp(pld, pkt.payload, pld_len)); +} + +/* Test building and parsing a simple CoAP over WS header */ +static void test_nanocoap__ws_basic(void) +{ + ssize_t res; + coap_pkt_t pkt; + uint8_t buf[32]; + const uint8_t token[] = { 0xde, 0xad, 0xbe, 0xef }; + const uint8_t expected[] = { + 0x04, /* Len = 0, TKL = 4 */ + COAP_METHOD_GET, /* Code = 0.01 GET */ + 0xde, 0xad, 0xbe, 0xef, /* Token = deadbeef */ + }; + + res = coap_build_ws_hdr(buf, sizeof(buf), token, sizeof(token), COAP_METHOD_GET); + TEST_ASSERT((ssize_t)sizeof(expected) == res); + TEST_ASSERT(memcmp(expected, buf, sizeof(expected)) == 0); + + /* same, but with coap_pkt_t initialized */ + res = coap_build_ws(&pkt, buf, sizeof(buf), token, sizeof(token), COAP_METHOD_GET); + TEST_ASSERT((ssize_t)sizeof(expected) == res); + TEST_ASSERT(buf == pkt.buf); + TEST_ASSERT_EQUAL_INT((uintptr_t)buf + sizeof(buf) - (uintptr_t)pkt.payload, pkt.payload_len); + TEST_ASSERT(memcmp(expected, pkt.buf, sizeof(expected)) == 0); + /* we add no payload */ + pkt.payload_len = 0; + + /* now verify that the coap_pkt_t is correctly initialized and that the + * getters work */ + TEST_ASSERT(pkt.payload == pkt.buf + sizeof(expected)); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT(0 == memcmp(token, coap_get_token(&pkt), sizeof(token))); + TEST_ASSERT_EQUAL_INT(COAP_TRANSPORT_WS, coap_get_transport(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_method(&pkt)); +} + +/* Test building and parsing a tiny CoAP over WS header */ +static void test_nanocoap__ws_tiny(void) +{ + ssize_t res; + coap_pkt_t pkt; + uint8_t msg[] = { + 0x00, /* Len = 0, TKL = 0 */ + COAP_CODE_404, /* Code = 4.04 Not Found*/ + }; + + res = coap_parse_ws(&pkt, msg, sizeof(msg)); + /* now verify that the coap_pkt_t is correctly initialized and that the + * getters work */ + TEST_ASSERT_EQUAL_INT(sizeof(msg), res); + TEST_ASSERT(pkt.payload == pkt.buf + sizeof(msg)); + TEST_ASSERT_EQUAL_INT(0, coap_get_token_len(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_TRANSPORT_WS, coap_get_transport(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_CODE_404, coap_get_method(&pkt)); +} + +/* Test request/response exchange with CoAP over WebSocket. Doing so while + * reusing the request buffer for the response, as often done in our code + * base. */ +static void test_nanocoap__ws_request_response(void) +{ + ssize_t res; + coap_pkt_t pkt; + uint8_t buf[64]; + const uint8_t token[] = { 0x13, 0x37, 0x42 }; + const char path[] = "/some/path"; + char parsed_path[sizeof(path)]; + + /* build request */ + res = coap_build_ws_hdr(buf, sizeof(buf), token, sizeof(token), COAP_METHOD_GET); + TEST_ASSERT(res > 0); + res += coap_opt_put_uri_path(buf + res, 0, path); + + /* parse request */ + memset(&pkt, 0, sizeof(pkt)); + TEST_ASSERT_EQUAL_INT(res, coap_parse_ws(&pkt, buf, res)); + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_method(&pkt)); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT(0 == memcmp(token, coap_get_token(&pkt), sizeof(token))); + res = coap_get_uri_path(&pkt, (void *)parsed_path); + TEST_ASSERT_EQUAL_INT(sizeof(path), res); + TEST_ASSERT(0 == memcmp(path, parsed_path, sizeof(parsed_path))); + + /* build reply */ + const char *pld = "test"; + const size_t pld_len = strlen(pld); + res = coap_reply_simple(&pkt, COAP_CODE_CONTENT, + buf, sizeof(buf), + COAP_FORMAT_TEXT, pld, pld_len); + TEST_ASSERT(res > 0); + /* parse reply */ + memset(&pkt, 0, sizeof(pkt)); + TEST_ASSERT_EQUAL_INT(res, coap_parse_ws(&pkt, buf, res)); + TEST_ASSERT_EQUAL_INT(COAP_CODE_CONTENT, coap_get_code_raw(&pkt)); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT(0 == memcmp(token, coap_get_token(&pkt), sizeof(token))); + TEST_ASSERT_EQUAL_INT(sizeof(token), coap_get_token_len(&pkt)); + TEST_ASSERT_EQUAL_INT(pld_len, pkt.payload_len); + TEST_ASSERT(0 == memcmp(pld, pkt.payload, pld_len)); +} + Test *tests_nanocoap_tests(void) { EMB_UNIT_TESTFIXTURES(fixtures) { @@ -1266,6 +1473,12 @@ Test *tests_nanocoap_tests(void) new_TestFixture(test_nanocoap__token_length_ext_16), new_TestFixture(test_nanocoap__token_length_ext_269), new_TestFixture(test_nanocoap___rst_message), + new_TestFixture(test_nanocoap__tcp_basic), + new_TestFixture(test_nanocoap__tcp_tiny), + new_TestFixture(test_nanocoap__tcp_request_response), + new_TestFixture(test_nanocoap__ws_basic), + new_TestFixture(test_nanocoap__ws_tiny), + new_TestFixture(test_nanocoap__ws_request_response), }; EMB_UNIT_TESTCALLER(nanocoap_tests, NULL, NULL, fixtures); diff --git a/tests/unittests/tests-nanocoap_cache/tests-nanocoap_cache.c b/tests/unittests/tests-nanocoap_cache/tests-nanocoap_cache.c index c8d96b7c6e2e..49c27ba7671c 100644 --- a/tests/unittests/tests-nanocoap_cache/tests-nanocoap_cache.c +++ b/tests/unittests/tests-nanocoap_cache/tests-nanocoap_cache.c @@ -11,7 +11,6 @@ * * @file */ -#include #include #include #include @@ -22,7 +21,6 @@ #include "ztimer.h" #include "hashes/sha256.h" -#include "unittests-constants.h" #include "tests-nanocoap_cache.h" #define _BUF_SIZE (128U) @@ -41,15 +39,15 @@ static void test_nanocoap_cache__cachekey(void) size_t len; /* 1. packet */ - len = coap_build_hdr((coap_hdr_t *)&buf1[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf1, sizeof(buf1), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt1, &buf1[0], sizeof(buf1), len); coap_opt_add_string(&pkt1, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_finish(&pkt1, COAP_OPT_FINISH_NONE); /* 2. packet */ - len = coap_build_hdr((coap_hdr_t *)&buf2[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf2, sizeof(buf2), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt2, &buf2[0], sizeof(buf2), len); coap_opt_add_string(&pkt2, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_finish(&pkt2, COAP_OPT_FINISH_NONE); @@ -61,8 +59,8 @@ static void test_nanocoap_cache__cachekey(void) TEST_ASSERT_EQUAL_INT(0, nanocoap_cache_key_compare(digest1, digest2)); /* 3. packet */ - len = coap_build_hdr((coap_hdr_t *)&buf2[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf2, sizeof(buf2), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt2, &buf2[0], sizeof(buf2), len); coap_opt_add_string(&pkt2, COAP_OPT_URI_PATH, &path2[0], '/'); coap_opt_finish(&pkt2, COAP_OPT_FINISH_NONE); @@ -95,8 +93,8 @@ static void test_nanocoap_cache__cachekey_blockwise(void) }; /* 1. packet */ - len = coap_build_hdr((coap_hdr_t *)&buf1[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf1, sizeof(buf1), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt1, &buf1[0], sizeof(buf1), len); coap_opt_add_string(&pkt1, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_add_block1_control(&pkt1, &blockopt); @@ -107,8 +105,8 @@ static void test_nanocoap_cache__cachekey_blockwise(void) blockopt.blknum = 2; /* 2. packet */ - len = coap_build_hdr((coap_hdr_t *)&buf2[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf2, sizeof(buf2), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&pkt2, &buf2[0], sizeof(buf2), len); coap_opt_add_string(&pkt2, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_add_block1_control(&pkt1, &blockopt); @@ -160,15 +158,15 @@ static void test_nanocoap_cache__add(void) snprintf(path, sizeof(path), "/path_%u", i); /* request */ - len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&req, &buf[0], sizeof(buf), len); coap_opt_add_string(&req, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_finish(&req, COAP_OPT_FINISH_NONE); /* response */ - len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON, - &token[0], 2, COAP_CODE_205, msgid); + len = coap_build_udp_hdr(rbuf, sizeof(rbuf), COAP_TYPE_NON, + &token[0], 2, COAP_CODE_205, msgid); coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len); coap_opt_finish(&resp, COAP_OPT_FINISH_NONE); @@ -219,15 +217,15 @@ static void test_nanocoap_cache__del(void) TEST_ASSERT_EQUAL_INT(0, nanocoap_cache_used_count()); /* request */ - len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&req, &buf[0], sizeof(buf), len); coap_opt_add_string(&req, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_finish(&req, COAP_OPT_FINISH_NONE); /* response */ - len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON, - &token[0], 2, COAP_CODE_205, msgid); + len = coap_build_udp_hdr(rbuf, sizeof(rbuf), COAP_TYPE_NON, + &token[0], 2, COAP_CODE_205, msgid); coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len); coap_opt_finish(&resp, COAP_OPT_FINISH_NONE); @@ -265,15 +263,15 @@ static void test_nanocoap_cache__max_age(void) nanocoap_cache_init(); /* request */ - len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, - &token[0], 2, COAP_METHOD_GET, msgid); + len = coap_build_udp_hdr(buf, sizeof(buf), COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); coap_pkt_init(&req, &buf[0], sizeof(buf), len); coap_opt_add_string(&req, COAP_OPT_URI_PATH, &path[0], '/'); coap_opt_finish(&req, COAP_OPT_FINISH_NONE); /* response with max-age 30 sec */ - len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON, - &token[0], 2, COAP_CODE_205, msgid); + len = coap_build_udp_hdr(rbuf, sizeof(rbuf), COAP_TYPE_NON, + &token[0], 2, COAP_CODE_205, msgid); coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len); coap_opt_add_uint(&resp, COAP_OPT_MAX_AGE, 30); coap_opt_finish(&resp, COAP_OPT_FINISH_NONE); @@ -291,8 +289,8 @@ static void test_nanocoap_cache__max_age(void) nanocoap_cache_del(c); /* response with max-age 60 sec (default, if option is missing) */ - len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON, - &token[0], 2, COAP_CODE_205, msgid); + len = coap_build_udp_hdr(rbuf, sizeof(rbuf), COAP_TYPE_NON, + &token[0], 2, COAP_CODE_205, msgid); coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len); coap_opt_finish(&resp, COAP_OPT_FINISH_NONE);