1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-18 19:13:51 +01:00
Marian Buschsieweke ab116ec71c
sys/net/nanocoap: Fix sending bogus separate responses
When module `nanocoap_server_separate` is not used, the functions to
send separate responses are still provided, just in a broken version:
They will send the separate replies from a different endpoint than the
request was received at (even on machines with only one IP address, as
also the source port is randomized).

This changes the behavior to only provide the functions for separate
response when the do work, so that others will detect an invalid
configuration at compile time rather than at run time.

The documentation is duly updated.
2024-12-13 08:40:43 +01:00

1151 lines
33 KiB
C

/*
* Copyright (C) 2016-18 Kaspar Schleiser <kaspar@schleiser.de>
* 2018 Inria
* 2018 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup net_nanocoap
* @{
*
* @file
* @brief nanoCoAP sock helpers
*
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* @}
*/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include "atomic_utils.h"
#include "net/credman.h"
#include "net/nanocoap.h"
#include "net/nanocoap_sock.h"
#include "net/sock/util.h"
#include "net/sock/udp.h"
#include "net/iana/portrange.h"
#include "random.h"
#include "sys/uio.h"
#include "timex.h"
#include "ztimer.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @brief Size of the buffer used for the DTLS handshake
*
* This size was found suitable for DTLS using a simple PSK in mode AES_128_CCM_8.
* DTLS places no restriction on its handshake package size therefore this might need change,
* if mode or key-size change especially if certificates instead of PSK are used.
*/
#ifndef CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE
#define CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE (160)
#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 */
STATE_WAIT_RESPONSE, /**< waiting for a response */
};
typedef struct {
coap_blockwise_cb_t callback;
void *arg;
uint32_t blknum;
bool more;
#if CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN
uint8_t token[4];
#endif
} _block_ctx_t;
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)
uint8_t buf[CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE];
sock->type = COAP_SOCKET_TYPE_DTLS;
return sock_dtls_establish_session(&sock->udp, &sock->dtls, &sock->dtls_session,
tag, local, remote, buf, sizeof(buf));
#else
(void)sock;
(void)local;
(void)remote;
(void)tag;
return -ENOTSUP;
#endif
}
static int _get_error(const coap_pkt_t *pkt)
{
switch (coap_get_code_class(pkt)) {
case COAP_CLASS_CLIENT_FAILURE:
return -ENXIO;
case COAP_CLASS_SERVER_FAILURE:
return -ENETRESET;
default:
return 0;
}
}
static inline nanocoap_socket_type_t _get_type(nanocoap_sock_t *sock)
{
#if IS_USED(MODULE_NANOCOAP_DTLS)
return sock->type;
#else
(void)sock;
return COAP_SOCKET_TYPE_UDP;
#endif
}
static int _sock_sendv(nanocoap_sock_t *sock, const iolist_t *snips)
{
switch (_get_type(sock)) {
case COAP_SOCKET_TYPE_UDP:
return sock_udp_sendv(&sock->udp, snips, NULL);
#if IS_USED(MODULE_NANOCOAP_DTLS)
case COAP_SOCKET_TYPE_DTLS:
return sock_dtls_sendv(&sock->dtls, &sock->dtls_session, snips,
CONFIG_SOCK_DTLS_TIMEOUT_MS);
#endif
default:
assert(0);
return -EINVAL;
}
}
static int _sock_recv_buf(nanocoap_sock_t *sock, void **data, void **ctx, uint32_t timeout)
{
switch (_get_type(sock)) {
case COAP_SOCKET_TYPE_UDP:
return sock_udp_recv_buf(&sock->udp, data, ctx, timeout, NULL);
#if IS_USED(MODULE_NANOCOAP_DTLS)
case COAP_SOCKET_TYPE_DTLS:
return sock_dtls_recv_buf(&sock->dtls, &sock->dtls_session, data, ctx, timeout);
#endif
default:
assert(0);
return -EINVAL;
}
}
static int _send_ack(nanocoap_sock_t *sock, coap_pkt_t *pkt)
{
coap_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));
return _sock_sendv(sock, &snip);
}
static bool _id_or_token_missmatch(const coap_pkt_t *pkt, unsigned id,
const void *token, size_t token_len)
{
switch (coap_get_type(pkt)) {
case COAP_TYPE_RST:
case COAP_TYPE_ACK:
/* message ID only has to match for RST and ACK */
if (coap_get_id(pkt) != id) {
return true;
}
/* falls through */
default:
/* token has to match if message is not empty */
if (pkt->hdr->code != 0) {
if (coap_get_token_len(pkt) != token_len) {
return true;
}
return memcmp(coap_get_token(pkt), token, token_len);
}
else {
/* but only RST and ACK may be empty */
return coap_get_type(pkt) != COAP_TYPE_RST &&
coap_get_type(pkt) != COAP_TYPE_ACK;
}
}
}
static uint32_t _deadline_from_interval(uint32_t interval)
{
return US_PER_MS * ztimer_now(ZTIMER_MSEC) + interval;
}
static uint32_t _deadline_left_us(uint32_t deadline)
{
uint32_t now = ztimer_now(ZTIMER_MSEC) * US_PER_MS;
if (now > deadline) {
return 0;
}
return deadline - now;
}
ssize_t nanocoap_sock_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);
void *payload, *ctx = NULL;
const uint8_t *token = coap_get_token(pkt);
uint8_t token_len = coap_get_token_len(pkt);
uint8_t state = STATE_REQUEST_SEND;
/* random timeout, deadline for receive retries */
uint32_t timeout = random_uint32_range((uint32_t)CONFIG_COAP_ACK_TIMEOUT_MS * US_PER_MS,
(uint32_t)CONFIG_COAP_ACK_TIMEOUT_MS * CONFIG_COAP_RANDOM_FACTOR_1000);
uint32_t deadline = _deadline_from_interval(timeout);
/* check if we expect a reply */
const bool confirmable = coap_get_type(pkt) == COAP_TYPE_CON;
/* add 1 for initial transmit, retry only when CONfirmable */
unsigned tries_left = confirmable * CONFIG_COAP_MAX_RETRANSMIT + 1;
/* Create the first payload snip from the request buffer */
iolist_t head = {
.iol_next = pkt->snips,
.iol_base = pkt->hdr,
.iol_len = coap_get_total_len(pkt),
};
while (1) {
switch (state) {
case STATE_REQUEST_SEND:
assert(tries_left > 0);
--tries_left;
DEBUG("nanocoap: send %u bytes (%u tries left)\n",
(unsigned)iolist_size(&head), tries_left);
res = _sock_sendv(sock, &head);
if (res <= 0) {
DEBUG("nanocoap: error sending coap request, %" PRIdSIZE "\n", res);
goto release;
}
/* no response needed and no response handler given */
if (!confirmable && !cb) {
res = 0;
goto release;
}
/* ctx must have been released at this point */
assert(ctx == NULL);
state = STATE_WAIT_RESPONSE;
/* fall-through */
case STATE_WAIT_RESPONSE: /* waiting for response, no empty ACK received */
case STATE_STOP_RETRANSMIT: /* waiting for response, empty ACK received */
if (ctx == NULL) {
DEBUG("nanocoap: waiting for response (timeout: %"PRIu32" µs)\n",
_deadline_left_us(deadline));
}
const void *old_ctx = ctx;
tmp = _sock_recv_buf(sock, &payload, &ctx, _deadline_left_us(deadline));
/* sock_udp_recv_buf() is supposed to return multiple packet fragments
* when called multiple times with the same context.
* In practise, this is not implemented and it will always return a pointer
* to the whole packet on the first call and NULL on the second call, which
* releases the packet.
* This assertion will trigger should the behavior change in the future.
*/
if (old_ctx) {
assert(tmp == 0 && ctx == NULL);
}
if (tmp == 0) {
/* no more data */
/* sock_udp_recv_buf() needs to be called in a loop until ctx is NULL again
* to release the buffer */
continue;
}
res = tmp;
if (res == -ETIMEDOUT) {
if (tries_left == 0) {
DEBUG("nanocoap: maximum retries reached\n");
goto release;
}
state = STATE_REQUEST_SEND;
DEBUG("nanocoap: timeout waiting for response\n");
timeout *= 2;
deadline = _deadline_from_interval(timeout);
continue;
}
if (res < 0) {
DEBUG("nanocoap: error receiving CoAP response, %" PRIdSIZE "\n", res);
goto release;
}
/* parse response */
if (coap_parse(pkt, payload, res) < 0) {
DEBUG("nanocoap: error parsing packet\n");
continue;
}
else if (_id_or_token_missmatch(pkt, id, token, token_len)) {
DEBUG("nanocoap: ID mismatch, got %u want %u\n", coap_get_id(pkt), id);
continue;
}
DEBUG("nanocoap: response code=%i\n", coap_get_code_decimal(pkt));
switch (coap_get_type(pkt)) {
case COAP_TYPE_ACK:
if (coap_get_code_raw(pkt) == COAP_CODE_EMPTY) {
/* empty ACK, wait for separate response */
state = STATE_STOP_RETRANSMIT;
deadline = _deadline_from_interval(CONFIG_COAP_SEPARATE_RESPONSE_TIMEOUT_MS
* US_PER_MS);
tries_left = 0; /* stop retransmissions */
DEBUG("nanocoap: wait for separate response\n");
continue;
}
/* fall-through */
case COAP_TYPE_RST:
case COAP_TYPE_CON:
case COAP_TYPE_NON:
if (coap_get_type(pkt) == COAP_TYPE_RST) {
/* think about whether cb should be called on RST as well */
res = -EBADMSG;
goto release;
}
if (coap_get_type(pkt) == COAP_TYPE_CON) {
_send_ack(sock, pkt);
}
if (cb) {
res = cb(arg, pkt);
}
else {
res = _get_error(pkt);
}
goto release;
}
}
}
release:
while (ctx) {
/* make sure ctx is really deleted in all cases */
_sock_recv_buf(sock, &payload, &ctx, 0);
}
return res;
}
static int _request_cb(void *arg, coap_pkt_t *pkt)
{
struct iovec *buf = arg;
size_t pkt_len = coap_get_total_len(pkt);
int res = _get_error(pkt);
if (res) {
return res;
}
if (pkt_len > buf->iov_len) {
return -ENOBUFS;
}
memcpy(buf->iov_base, pkt->hdr, pkt_len);
pkt->hdr = buf->iov_base;
pkt->payload = (uint8_t*)pkt->hdr + (pkt_len - pkt->payload_len);
return pkt_len;
}
ssize_t nanocoap_sock_request(nanocoap_sock_t *sock, coap_pkt_t *pkt, size_t len)
{
struct iovec buf = {
.iov_base = pkt->hdr,
.iov_len = len,
};
return nanocoap_sock_request_cb(sock, pkt, _request_cb, &buf);
}
static int _get_put_cb(void *arg, coap_pkt_t *pkt)
{
struct iovec *buf = arg;
int res = _get_error(pkt);
if (res) {
return res;
}
if (pkt->payload_len > buf->iov_len) {
return -ENOBUFS;
}
memcpy(buf->iov_base, pkt->payload, pkt->payload_len);
return pkt->payload_len;
}
static ssize_t _sock_get(nanocoap_sock_t *sock, const char *path,
uint8_t type,
void *response, size_t max_len)
{
/* buffer for CoAP header */
uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
uint8_t *pktpos = buffer;
coap_pkt_t pkt = {
.hdr = (void *)pktpos,
};
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));
pktpos += coap_opt_put_uri_pathquery(pktpos, NULL, path);
pkt.payload = pktpos;
pkt.payload_len = 0;
return nanocoap_sock_request_cb(sock, &pkt, _get_put_cb, &ctx);
}
ssize_t nanocoap_sock_get(nanocoap_sock_t *sock, const char *path, void *buf, size_t len)
{
return _sock_get(sock, path, COAP_TYPE_CON, buf, len);
}
ssize_t _sock_put_post(nanocoap_sock_t *sock, const char *path, unsigned code,
uint8_t type, const void *request, size_t len,
void *response, size_t max_len)
{
/* buffer for CoAP header */
uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
uint8_t *pktpos = buffer;
iolist_t payload = {
.iol_base = (void *)request,
.iol_len = len,
};
coap_pkt_t pkt = {
.hdr = (void *)buffer,
.snips = &payload,
};
struct iovec ctx = {
.iov_base = response,
.iov_len = max_len,
};
uint16_t lastonum = 0;
pktpos += coap_build_hdr(pkt.hdr, type, NULL, 0, code, nanocoap_sock_next_msg_id(sock));
pktpos += coap_opt_put_uri_pathquery(pktpos, &lastonum, path);
if (response == NULL && type == COAP_TYPE_NON) {
/* all responses (2.xx, 4.xx and 5.xx) are ignored */
pktpos += coap_opt_put_uint(pktpos, lastonum,
COAP_OPT_NO_RESPONSE, 26);
}
if (len) {
/* set payload marker */
*pktpos++ = 0xFF;
}
pkt.payload = pktpos;
pkt.payload_len = 0;
return nanocoap_sock_request_cb(sock, &pkt, response ? _get_put_cb : NULL, &ctx);
}
ssize_t nanocoap_sock_put(nanocoap_sock_t *sock, const char *path,
const void *request, size_t len,
void *response, size_t len_max)
{
return _sock_put_post(sock, path, COAP_METHOD_PUT, COAP_TYPE_CON, request, len,
response, len_max);
}
ssize_t nanocoap_sock_post(nanocoap_sock_t *sock, const char *path,
const void *request, size_t len,
void *response, size_t len_max)
{
return _sock_put_post(sock, path, COAP_METHOD_POST, COAP_TYPE_CON, request, len,
response, 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)
{
return _sock_put_post(sock, path, COAP_METHOD_FETCH, COAP_TYPE_CON, request, len,
response, 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)
{
return _sock_put_post(sock, path, COAP_METHOD_PUT, COAP_TYPE_NON, request, len,
response, 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)
{
return _sock_put_post(sock, path, COAP_METHOD_POST, COAP_TYPE_NON, request, len,
response, 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)
{
return _sock_put_post(sock, path, COAP_METHOD_FETCH, COAP_TYPE_NON, request, len,
response, len_max);
}
ssize_t nanocoap_sock_get_non(nanocoap_sock_t *sock, const char *path,
void *response, size_t len_max)
{
return _sock_get(sock, path, COAP_TYPE_NON, response, len_max);
}
static ssize_t _sock_put_post_url(const char *url, unsigned code,
const void *request, size_t len,
void *response, size_t len_max)
{
nanocoap_sock_t sock;
int res = nanocoap_sock_url_connect(url, &sock);
if (res) {
return res;
}
res = _sock_put_post(&sock, sock_urlpath(url), code, COAP_TYPE_CON,
request, len, response, len_max);
nanocoap_sock_close(&sock);
return res;
}
ssize_t nanocoap_sock_put_url(const char *url,
const void *request, size_t len,
void *response, size_t len_max)
{
return _sock_put_post_url(url, COAP_METHOD_PUT, request, len, response, len_max);
}
ssize_t nanocoap_sock_post_url(const char *url,
const void *request, size_t len,
void *response, size_t len_max)
{
return _sock_put_post_url(url, COAP_METHOD_POST, request, len, response, len_max);
}
ssize_t nanocoap_sock_fetch_url(const char *url,
const void *request, size_t len,
void *response, size_t len_max)
{
return _sock_put_post_url(url, COAP_METHOD_FETCH, request, len, response, len_max);
}
ssize_t nanocoap_sock_delete(nanocoap_sock_t *sock, const char *path)
{
/* buffer for CoAP header */
uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
uint8_t *pktpos = buffer;
coap_pkt_t pkt = {
.hdr = (void *)pktpos,
};
pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_DELETE,
nanocoap_sock_next_msg_id(sock));
pktpos += coap_opt_put_uri_pathquery(pktpos, NULL, path);
pkt.payload = pktpos;
return nanocoap_sock_request_cb(sock, &pkt, NULL, NULL);
}
ssize_t nanocoap_sock_delete_url(const char *url)
{
nanocoap_sock_t sock;
int res = nanocoap_sock_url_connect(url, &sock);
if (res) {
return res;
}
res = nanocoap_sock_delete(&sock, sock_urlpath(url));
nanocoap_sock_close(&sock);
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)
{
int res;
nanocoap_sock_t sock;
res = nanocoap_sock_connect(&sock, local, remote);
if (res) {
return res;
}
res = nanocoap_sock_request(&sock, pkt, len);
nanocoap_sock_close(&sock);
return res;
}
static int _block_cb(void *arg, coap_pkt_t *pkt)
{
_block_ctx_t *ctx = arg;
coap_block1_t block2;
int res = _get_error(pkt);
if (res) {
return res;
}
/* response was not block-wise */
if (!coap_get_block2(pkt, &block2)) {
block2.offset = 0;
block2.more = false;
}
DEBUG("nanocoap: got block %"PRIu32" (offset %u)\n",
block2.blknum, (unsigned)block2.offset);
if (block2.blknum != ctx->blknum) {
return -EAGAIN;
}
ctx->more = block2.more;
return ctx->callback(ctx->arg, block2.offset, pkt->payload, pkt->payload_len, block2.more);
}
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,
};
uint16_t lastonum = 0;
void *token = NULL;
size_t token_len = 0;
#if CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN
/* HACK: go-coap always expects a token */
/* see https://github.com/plgd-dev/go-coap/issues/512 */
token = ctx->token;
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));
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);
pkt.payload = buf;
pkt.payload_len = 0;
return nanocoap_sock_request_cb(sock, &pkt, _block_cb, ctx);
}
int nanocoap_sock_block_request(coap_block_request_t *req,
const void *data, size_t len, bool more,
coap_request_cb_t callback, void *arg)
{
/* clip the payload at the block size */
if (len > coap_szx2size(req->blksize)) {
len = coap_szx2size(req->blksize);
more = true;
}
int res;
uint8_t buf[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
iolist_t snip = {
.iol_base = (void *)data,
.iol_len = len,
};
coap_pkt_t pkt = {
.hdr = (void *)buf,
.snips = &snip,
};
uint8_t *pktpos = (void *)pkt.hdr;
uint16_t lastonum = 0;
pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, req->method,
nanocoap_sock_next_msg_id(req->sock));
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));
if (len) {
/* set payload marker */
*pktpos++ = 0xFF;
}
pkt.payload = pktpos;
pkt.payload_len = 0;
res = nanocoap_sock_request_cb(req->sock, &pkt, callback, arg);
if (res < 0) {
return res;
}
++req->blknum;
return len;
}
int nanocoap_sock_get_blockwise(nanocoap_sock_t *sock, const char *path,
coap_blksize_t blksize,
coap_blockwise_cb_t callback, void *arg)
{
uint8_t buf[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
_block_ctx_t ctx = {
.callback = callback,
.arg = arg,
.more = true,
};
#if CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN
random_bytes(ctx.token, sizeof(ctx.token));
#endif
uint8_t retries = CONFIG_COAP_MAX_RETRANSMIT;
while (ctx.more) {
DEBUG("nanocoap: fetching block %"PRIu32"\n", ctx.blknum);
int res = _fetch_block(sock, buf, sizeof(buf), path, blksize, &ctx);
if (res == -EAGAIN) {
if (--retries) {
continue;
}
res = -EBADMSG;
}
if (res < 0) {
DEBUG("nanocoap: error fetching block %"PRIu32": %d\n", ctx.blknum, res);
return res;
}
ctx.blknum += 1;
retries = CONFIG_COAP_MAX_RETRANSMIT;
}
return 0;
}
typedef struct {
uint8_t *ptr;
size_t len;
size_t offset;
size_t res;
} _buf_slice_t;
static int _2buf_slice(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
{
_buf_slice_t *ctx = arg;
if (offset + len < ctx->offset) {
return 0;
}
if (offset > ctx->offset + ctx->len) {
return 0;
}
if (!ctx->len) {
return 0;
}
offset = ctx->offset - offset;
len = MIN(len - offset, ctx->len);
memcpy(ctx->ptr, buf + offset, len);
ctx->len -= len;
ctx->ptr += len;
ctx->offset += len;
ctx->res += len;
DEBUG("nanocoap: got %"PRIuSIZE" bytes, %"PRIuSIZE" bytes left (offset: %"PRIuSIZE")\n",
len, ctx->len, offset);
if (!more) {
ctx->len = 0;
}
return 0;
}
static unsigned _num_blks(size_t offset, size_t len, coap_blksize_t szx)
{
uint16_t mask = coap_szx2size(szx) - 1;
uint8_t shift = szx + 4;
size_t end = offset + len;
unsigned num_blks = ((end >> shift) + !!(end & mask))
- ((offset >> shift) + !!(offset & mask));
return num_blks;
}
int nanocoap_sock_get_slice(nanocoap_sock_t *sock, const char *path,
coap_blksize_t blksize, size_t offset,
void *dst, size_t len)
{
uint8_t buf[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
/* try to find optimal blocksize */
unsigned num_blocks = _num_blks(offset, len, blksize);
for (uint8_t szx = 0; szx < blksize; ++szx) {
if (_num_blks(offset, len, szx) <= num_blocks) {
blksize = szx;
break;
}
}
_buf_slice_t dst_ctx = {
.ptr = dst,
.len = len,
.offset = offset,
};
_block_ctx_t ctx = {
.callback = _2buf_slice,
.arg = &dst_ctx,
.blknum = offset >> (blksize + 4),
.more = true,
};
#if CONFIG_NANOCOAP_SOCK_BLOCK_TOKEN
random_bytes(ctx.token, sizeof(ctx.token));
#endif
uint8_t retries = CONFIG_COAP_MAX_RETRANSMIT;
while (dst_ctx.len) {
DEBUG("nanocoap: fetching block %"PRIu32"\n", ctx.blknum);
int res = _fetch_block(sock, buf, sizeof(buf), path, blksize, &ctx);
if (res == -EAGAIN) {
if (--retries) {
continue;
}
res = -EBADMSG;
}
if (res < 0) {
DEBUG("nanocoap: error fetching block %"PRIu32": %d\n", ctx.blknum, res);
return res;
}
ctx.blknum += 1;
retries = CONFIG_COAP_MAX_RETRANSMIT;
}
return dst_ctx.res;
}
int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock)
{
char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN];
sock_udp_ep_t remote;
bool is_coaps = false;
if (IS_USED(MODULE_NANOCOAP_DTLS) && !strncmp(url, "coaps://", 8)) {
DEBUG("nanocoap: CoAPS URL detected\n");
is_coaps = true;
}
if (!is_coaps && strncmp(url, "coap://", 7)) {
DEBUG("nanocoap: URL doesn't start with \"coap://\"\n");
return -EINVAL;
}
if (sock_urlsplit(url, hostport, NULL) < 0) {
DEBUG("nanocoap: invalid URL\n");
return -EINVAL;
}
if (sock_udp_name2ep(&remote, hostport) < 0) {
DEBUG("nanocoap: invalid URL\n");
return -EINVAL;
}
if (!remote.port) {
remote.port = is_coaps ? COAPS_PORT : COAP_PORT;
}
if (is_coaps) {
#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;
}
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;
}
#else
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);
}
}
int nanocoap_get_blockwise_url(const char *url,
coap_blksize_t blksize,
coap_blockwise_cb_t callback, void *arg)
{
nanocoap_sock_t sock;
int res = nanocoap_sock_url_connect(url, &sock);
if (res) {
return res;
}
res = nanocoap_sock_get_blockwise(&sock, sock_urlpath(url), blksize, callback, arg);
nanocoap_sock_close(&sock);
return res;
}
typedef struct {
uint8_t *ptr;
size_t len;
} _buf_t;
static int _2buf(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
{
_buf_t *dst = arg;
if (offset + len > dst->len) {
return -ENOBUFS;
}
memcpy(dst->ptr + offset, buf, len);
if (!more) {
dst->len = offset + len;
}
return 0;
}
ssize_t nanocoap_get_blockwise_url_to_buf(const char *url,
coap_blksize_t blksize,
void *buf, size_t len)
{
_buf_t _buf = { .ptr = buf, .len = len };
int res = nanocoap_get_blockwise_url(url, blksize, _2buf, &_buf);
return (res < 0) ? (ssize_t)res : (ssize_t)_buf.len;
}
ssize_t nanocoap_get_blockwise_to_buf(nanocoap_sock_t *sock, const char *path,
coap_blksize_t blksize,
void *buf, size_t len)
{
_buf_t _buf = { .ptr = buf, .len = len };
int res = nanocoap_sock_get_blockwise(sock, path, blksize, _2buf, &_buf);
return (res < 0) ? (ssize_t)res : (ssize_t)_buf.len;
}
int nanocoap_server(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,
};
if (!local->port) {
local->port = COAP_PORT;
}
ssize_t res = sock_udp_create(&sock, local, NULL, 0);
if (res != 0) {
return -1;
}
while (1) {
sock_udp_aux_rx_t *aux_in_ptr = NULL;
#ifdef MODULE_SOCK_AUX_LOCAL
sock_udp_aux_rx_t aux_in = {
.flags = SOCK_AUX_GET_LOCAL,
};
aux_in_ptr = &aux_in;
#endif
res = sock_udp_recv_aux(&sock, buf, bufsize, SOCK_NO_TIMEOUT,
&remote, aux_in_ptr);
if (res <= 0) {
DEBUG("nanocoap: error receiving UDP packet %" PRIdSIZE "\n", res);
continue;
}
coap_pkt_t pkt;
if (coap_parse(&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
/* 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 = aux_in.local,
};
if (!sock_udp_ep_is_multicast(&aux_in.local)) {
aux_out_ptr = &aux_out;
}
ctx.local = &aux_in.local;
#endif
if ((res = coap_handle_req(&pkt, buf, bufsize, &ctx)) <= 0) {
DEBUG("nanocoap: error handling request %" PRIdSIZE "\n", res);
continue;
}
sock_udp_send_aux(&sock, buf, res, &remote, aux_out_ptr);
}
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);
}
#if MODULE_NANOCOAP_SERVER_SEPARATE
int nanocoap_server_prepare_separate(nanocoap_server_response_ctx_t *ctx,
coap_pkt_t *pkt, const coap_request_ctx_t *req)
{
size_t tkl = coap_get_token_len(pkt);
if (tkl > sizeof(ctx->token)) {
DEBUG_PUTS("nanocoap: token too long for separate response ctx");
/* Legacy code may not check the return value. To still have somewhat
* sane behavior, we ask for no response for any response class.
* Getting no reply is certainly not ideal, but better than one without
* a matching token. */
memset(ctx, 0, sizeof(*ctx));
ctx->no_response = 0xff;
return -EOVERFLOW;
}
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;
return 0;
}
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];
assert(type != COAP_TYPE_ACK);
assert(type != COAP_TYPE_CON); /* TODO: add support */
const uint8_t no_response_index = (code >> 5) - 1;
/* If the handler code misbehaved here, we'd face UB otherwise */
assert(no_response_index < 7);
const uint8_t mask = 1 << no_response_index;
if (ctx->no_response & mask) {
return 0;
}
iolist_t data = {
.iol_base = (void *)payload,
.iol_len = len,
};
iolist_t head = {
.iol_next = &data,
.iol_base = rbuf,
};
head.iol_len = coap_build_hdr((coap_hdr_t *)rbuf, type,
ctx->token, ctx->tkl,
code, random_uint32());
if (len) {
rbuf[head.iol_len++] = 0xFF;
}
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,
};
if (!sock_udp_ep_is_multicast(&ctx->local)) {
aux_out_ptr = &aux_out;
}
return sock_udp_sendv_aux(NULL, &head, &ctx->remote, aux_out_ptr);
}
#endif