Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys/net/nanocoap: Add CoAP over TCP support #21048

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/gcoap/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 ---");
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion examples/gcoap/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
static ssize_t _encode_link(const coap_resource_t *resource, char *buf,
size_t maxlen, coap_link_encoder_ctx_t *context);
static ssize_t _stats_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx);
static ssize_t _riot_board_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx);

Check warning on line 67 in examples/gcoap/server.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
#if IS_USED(MODULE_PERIPH_RTC)
static ssize_t _rtc_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx);
#endif
Expand Down Expand Up @@ -92,7 +92,7 @@
NULL
};


Check warning on line 95 in examples/gcoap/server.c

View workflow job for this annotation

GitHub Actions / static-tests

too many consecutive empty lines
/* Adds link format params to resource list */
static ssize_t _encode_link(const coap_resource_t *resource, char *buf,
size_t maxlen, coap_link_encoder_ctx_t *context) {
Expand Down Expand Up @@ -122,7 +122,7 @@
}
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;
Expand All @@ -135,7 +135,7 @@
switch (gcoap_obs_init(&pdu, buf, sizeof(buf), rtc_resource)) {
case GCOAP_OBS_INIT_OK:
len = coap_opt_finish(&pdu, COAP_OPT_FINISH_PAYLOAD);
memcpy(pdu.payload, str_time, strftime(str_time, sizeof(str_time), "%Y-%m-%d %H:%M:%S", &tm_now));

Check warning on line 138 in examples/gcoap/server.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
pdu.payload_len = strlen(str_time);
len += pdu.payload_len;
if (!gcoap_obs_send(buf, len, rtc_resource)) {
Expand All @@ -160,7 +160,7 @@
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
char str_time[20] = "";
memcpy(pdu->payload, str_time, strftime(str_time, sizeof(str_time), "%Y-%m-%d %H:%M:%S", &tm_now));

Check warning on line 163 in examples/gcoap/server.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
pdu->payload_len = strlen(str_time);
resp_len += pdu->payload_len;
return resp_len;
Expand Down Expand Up @@ -209,7 +209,7 @@
return 0;
}

static ssize_t _riot_board_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, coap_request_ctx_t *ctx)

Check warning on line 212 in examples/gcoap/server.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)ctx;
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
Expand Down Expand Up @@ -271,7 +271,7 @@
gcoap_register_listener(&_listener);
#if IS_USED(MODULE_PERIPH_RTC)
static event_periodic_callback_t _ev_pcb_rtc;
event_periodic_callback_init(&_ev_pcb_rtc, ZTIMER_MSEC, EVENT_PRIO_MEDIUM, _rtc_notify_observers, NULL);

Check warning on line 274 in examples/gcoap/server.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
event_periodic_callback_start(&_ev_pcb_rtc, 10 * MS_PER_SEC);
#endif
}
40 changes: 31 additions & 9 deletions examples/nanocoap_server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,26 @@ 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 += nanocoap_resources
USEMODULE += ipv6_addr
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 += xtimer

Expand All @@ -43,7 +51,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)))
Expand All @@ -61,6 +69,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:
Expand Down
99 changes: 99 additions & 0 deletions examples/nanocoap_server/coap+ws2coap+yolo.py
Original file line number Diff line number Diff line change
@@ -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)))
29 changes: 18 additions & 11 deletions examples/nanocoap_server/coap_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
#include <stdio.h>
#include <string.h>

#include "event/callback.h"
#include "event/timeout.h"
#include "event/thread.h"
#include "fmt.h"
#include "net/nanocoap.h"
#include "net/nanocoap_sock.h"
#include "hashes/sha256.h"
#include "kernel_defines.h"

#if MODULE_NANOCOAP_SERVER_SEPARATE
# include "event/thread.h"
# include "event/timeout.h"
# include "event/callback.h"
#endif

/* internal value that can be read/written via CoAP */
static uint8_t internal_value = 0;
Expand All @@ -41,14 +43,14 @@
(uint8_t *)sub_uri, sub_uri_len);
}

static ssize_t _riot_board_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 46 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)context;
return coap_reply_simple(pkt, COAP_CODE_205, buf, len,
COAP_FORMAT_TEXT, (uint8_t*)RIOT_BOARD, strlen(RIOT_BOARD));
}

static ssize_t _riot_block2_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 53 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
(void)context;
coap_block_slicer_t slicer;
Expand Down Expand Up @@ -158,7 +160,7 @@
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);
}
Expand All @@ -167,7 +169,7 @@
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) {
Expand All @@ -177,7 +179,7 @@
.path = "/riot/board", .methods = COAP_GET, .handler = _riot_board_handler
};
NANOCOAP_RESOURCE(value) {
.path = "/riot/value", .methods = COAP_GET | COAP_PUT | COAP_POST, .handler = _riot_value_handler

Check warning on line 182 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
};
NANOCOAP_RESOURCE(ver) {
.path = "/riot/ver", .methods = COAP_GET, .handler = _riot_block2_handler
Expand All @@ -186,8 +188,8 @@
.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)
Expand All @@ -199,12 +201,17 @@
response, sizeof(response));
}

static ssize_t _separate_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, coap_request_ctx_t *context)

Check warning on line 204 in examples/nanocoap_server/coap_handler.c

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
{
static event_timeout_t event_timeout;
static event_callback_t event_timed = EVENT_CALLBACK_INIT(_send_response, &_separate_ctx);

if (event_timeout_is_pending(&event_timeout) && !sock_udp_ep_equal(context->remote, &_separate_ctx.remote)) {
if (event_timeout_is_pending(&event_timeout)) {
if (nanocoap_is_duplicate_in_separate_ctx(&_separate_ctx, pkt, context)) {
/* no need to check transport: Only UDP can have duplicates */
puts("_separate_handler(): duplicate");
return coap_reply_empty_ack(pkt, buf, len);
}
puts("_separate_handler(): response already scheduled");
return coap_build_reply(pkt, COAP_CODE_SERVICE_UNAVAILABLE, buf, len, 0);
}
Expand All @@ -221,13 +228,13 @@
&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 */

/* we can also include the fileserver module */
#ifdef MODULE_NANOCOAP_FILESERVER
Expand Down
28 changes: 27 additions & 1 deletion examples/nanocoap_server/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,23 @@
#include "net/nanocoap_sock.h"
#include "xtimer.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");
Expand All @@ -42,10 +54,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;
Expand Down
3 changes: 3 additions & 0 deletions makefiles/pseudomodules.inc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,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
Expand Down
Loading
Loading