1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/net/application_layer/nanocoap/nanocoap.c
Marian Buschsieweke c70075b073
Merge pull request #20950 from maribu/sys/net/nanocoap/coap_get_response_hdr_len
sys/net/nanocoap: add and use coap_get_response_hdr_len()
2024-11-05 13:09:51 +00:00

1504 lines
44 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 implementation
*
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "bitarithm.h"
#include "net/nanocoap.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @name Internally used CoAP packet types
* @{
*/
#define COAP_REQ (0)
#define COAP_RESP (2)
#define COAP_RST (3)
/** @} */
#ifdef MODULE_NANOCOAP_RESOURCES
/**
* @brief CoAP resources XFA
*/
XFA_INIT_CONST(coap_resource_t, coap_resources_xfa);
/**
* @brief Add well-known .core handler
*/
#if CONFIG_NANOCOAP_SERVER_WELL_KNOWN_CORE
NANOCOAP_RESOURCE(well_known_core) COAP_WELL_KNOWN_CORE_DEFAULT_HANDLER;
#endif
/* re-define coap_resources for compatibility with non-XFA version */
#define coap_resources ((const coap_resource_t *)coap_resources_xfa)
#define coap_resources_numof XFA_LEN(coap_resource_t, coap_resources_xfa)
#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) ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
int coap_parse(coap_pkt_t *pkt, uint8_t *buf, size_t len)
{
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;
}
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;
}
} 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);
coap_optpos_t *optpos = pkt->options;
unsigned option_count = 0;
unsigned option_nr = 0;
/* parse options */
while (pkt_pos < pkt_end) {
uint8_t *option_start = pkt_pos;
uint8_t option_byte = *pkt_pos++;
if (option_byte == COAP_PAYLOAD_MARKER) {
pkt->payload = pkt_pos;
pkt->payload_len = buf + len - pkt_pos;
DEBUG("payload len = %u\n", pkt->payload_len);
break;
}
else {
int option_delta = _decode_value(option_byte >> 4, &pkt_pos, 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);
if (option_len < 0) {
DEBUG("bad op len\n");
return -EBADMSG;
}
option_nr += option_delta;
DEBUG("option count=%u nr=%u len=%i\n", option_count, option_nr, option_len);
if (option_delta) {
if (option_count >= CONFIG_NANOCOAP_NOPTS_MAX) {
DEBUG("nanocoap: max nr of options exceeded\n");
return -ENOMEM;
}
/* check if option is critical */
if (option_nr & 1) {
bf_set(pkt->opt_crit, option_count);
}
optpos->opt_num = option_nr;
optpos->offset = (uintptr_t)option_start - (uintptr_t)hdr;
DEBUG("optpos option_nr=%u %u\n", (unsigned)option_nr, (unsigned)optpos->offset);
optpos++;
option_count++;
}
pkt_pos += option_len;
}
}
if (pkt_pos > pkt_end) {
DEBUG("nanocoap: bad packet length\n");
return -EBADMSG;
}
pkt->options_len = option_count;
if (!pkt->payload) {
pkt->payload = pkt_pos;
}
#ifdef MODULE_GCOAP
if (coap_opt_get_uint(pkt, COAP_OPT_OBSERVE, &pkt->observe_value) != 0) {
pkt->observe_value = UINT32_MAX;
}
#endif
DEBUG("coap pkt parsed. code=%u detail=%u payload_len=%u, nopts=%u, 0x%02x\n",
coap_get_code_class(pkt),
coap_get_code_detail(pkt),
pkt->payload_len, option_count, hdr->code);
return 0;
}
int coap_match_path(const coap_resource_t *resource, const uint8_t *uri)
{
assert(resource && uri);
int res;
if (resource->methods & COAP_MATCH_SUBTREE) {
int len = strlen(resource->path);
res = strncmp((char *)uri, resource->path, len);
}
else {
res = strcmp((char *)uri, resource->path);
}
return res;
}
uint8_t *coap_find_option(coap_pkt_t *pkt, unsigned opt_num)
{
const coap_optpos_t *optpos = pkt->options;
unsigned opt_count = pkt->options_len;
while (opt_count--) {
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;
}
optpos++;
}
return NULL;
}
/*
* Parse option attributes
*
* pkt[in] coap_pkt_t for buffer
* pkt_pos[in] first byte of option in buffer
* delta[out] option delta from previous option
* opt_len[out] length of option value
*
* return next byte after option header, usually the option value
* return NULL if initial pkt_pos is payload marker or past options
*/
static uint8_t *_parse_option(const coap_pkt_t *pkt,
uint8_t *pkt_pos, uint16_t *delta, int *opt_len)
{
uint8_t *hdr_end = pkt->payload;
if ((pkt_pos >= hdr_end)
|| (((pkt_pos + 1) == hdr_end) && (*pkt_pos == COAP_PAYLOAD_MARKER))) {
return NULL;
}
uint8_t option_byte = *pkt_pos++;
*delta = _decode_value(option_byte >> 4, &pkt_pos, hdr_end);
*opt_len = _decode_value(option_byte & 0xf, &pkt_pos, hdr_end);
return pkt_pos;
}
ssize_t coap_opt_get_opaque(coap_pkt_t *pkt, unsigned opt_num, uint8_t **value)
{
uint8_t *start = coap_find_option(pkt, opt_num);
if (!start) {
return -ENOENT;
}
uint16_t delta;
int len;
*value = _parse_option(pkt, start, &delta, &len);
if (!*value) {
return -EINVAL;
}
return len;
}
int coap_opt_get_uint(coap_pkt_t *pkt, uint16_t opt_num, uint32_t *target)
{
assert(target);
uint8_t *opt_pos = coap_find_option(pkt, opt_num);
if (opt_pos) {
uint16_t delta;
int option_len = 0;
uint8_t *pkt_pos = _parse_option(pkt, opt_pos, &delta, &option_len);
if (option_len >= 0) {
if (option_len > 4) {
DEBUG("nanocoap: uint option with len > 4 (unsupported).\n");
return -ENOSPC;
}
*target = _decode_uint(pkt_pos, option_len);
return 0;
}
else {
DEBUG("nanocoap: discarding packet with invalid option length.\n");
return -EBADMSG;
}
}
return -ENOENT;
}
uint8_t *coap_iterate_option(coap_pkt_t *pkt, unsigned optnum,
uint8_t **opt_pos, int *opt_len)
{
uint8_t *data_start;
bool first = false;
if (*opt_pos == NULL) {
*opt_pos = coap_find_option(pkt, optnum);
first = true;
if (*opt_pos == NULL) {
return NULL;
}
}
uint16_t delta = 0;
data_start = _parse_option(pkt, *opt_pos, &delta, opt_len);
if (data_start && (first || !delta)) {
*opt_pos = data_start + *opt_len;
return data_start;
}
else {
return NULL;
}
}
static unsigned _get_content_format(coap_pkt_t *pkt, unsigned int opt_num)
{
uint8_t *opt_pos = coap_find_option(pkt, opt_num);
unsigned content_type = COAP_FORMAT_NONE;
if (opt_pos) {
uint16_t delta;
int option_len = 0;
uint8_t *pkt_pos = _parse_option(pkt, opt_pos, &delta, &option_len);
if (option_len == 0) {
content_type = 0;
} else if (option_len == 1) {
content_type = *pkt_pos;
} else if (option_len == 2) {
memcpy(&content_type, pkt_pos, 2);
content_type = ntohs(content_type);
}
}
return content_type;
}
unsigned coap_get_content_type(coap_pkt_t *pkt)
{
return _get_content_format(pkt, COAP_OPT_CONTENT_FORMAT);
}
unsigned coap_get_accept(coap_pkt_t *pkt)
{
return _get_content_format(pkt, COAP_OPT_ACCEPT);
}
ssize_t coap_opt_get_next(const coap_pkt_t *pkt, coap_optpos_t *opt,
uint8_t **value, bool init_opt)
{
if (init_opt) {
opt->opt_num = 0;
opt->offset = coap_get_total_hdr_len(pkt);
}
uint8_t *start = (uint8_t*)pkt->hdr + opt->offset;
/* Find start of option value and value length. */
uint16_t delta;
int len;
start = _parse_option(pkt, start, &delta, &len);
if (!start) {
return -ENOENT;
}
*value = start;
opt->opt_num += delta;
opt->offset = start + len - (uint8_t*)pkt->hdr;
return len;
}
ssize_t coap_opt_get_string(coap_pkt_t *pkt, uint16_t optnum,
uint8_t *target, size_t max_len, char separator)
{
assert(pkt && target && (max_len > 1));
uint8_t *opt_pos = NULL;
int opt_len;
unsigned left = max_len;
while (1) {
uint8_t *part_start = coap_iterate_option(pkt, optnum, &opt_pos, &opt_len);
if (part_start == NULL) {
/* if option was not found still return separator */
if (opt_pos == NULL) {
*target++ = (uint8_t)separator;
--left;
}
break;
}
/* separator and terminating \0 have to fit */
if (left < (unsigned)(opt_len + 2)) {
return -ENOSPC;
}
*target++ = (uint8_t)separator;
memcpy(target, part_start, opt_len);
target += opt_len;
left -= opt_len + 1;
}
*target = '\0';
left--;
return (int)(max_len - left);
}
int coap_get_blockopt(coap_pkt_t *pkt, uint16_t option, uint32_t *blknum, uint8_t *szx)
{
uint8_t *optpos = coap_find_option(pkt, option);
if (!optpos) {
*blknum = 0;
*szx = 0;
return -1;
}
int option_len;
uint16_t delta;
uint8_t *data_start = _parse_option(pkt, optpos, &delta, &option_len);
if (!data_start) {
DEBUG("nanocoap: invalid start data\n");
return -1;
}
if (option_len > 4) {
DEBUG("nanocoap: invalid option length\n");
return -1;
}
uint32_t blkopt = _decode_uint(data_start, option_len);
DEBUG("nanocoap: blkopt len: %i\n", option_len);
DEBUG("nanocoap: blkopt: 0x%08x\n", (unsigned)blkopt);
*blknum = blkopt >> COAP_BLOCKWISE_NUM_OFF;
*szx = blkopt & COAP_BLOCKWISE_SZX_MASK;
return (blkopt & 0x8) ? 1 : 0;
}
bool coap_find_uri_query(coap_pkt_t *pkt, const char *key, const char **value, size_t *len)
{
uint8_t *opt_pos = NULL;
while (1) {
int len_query;
const void *key_data = coap_iterate_option(pkt, COAP_OPT_URI_QUERY,
&opt_pos, &len_query);
if (key_data == NULL) {
return false;
}
const char *separator = memchr(key_data, '=', len_query);
size_t len_key = separator
? (separator - (char *)key_data)
: len_query;
if (memcmp(key, key_data, len_key)) {
continue;
}
if (value == NULL) {
return true;
}
assert(len);
if (separator) {
*value = separator + 1;
*len = len_query - len_key - 1;
} else {
*value = NULL;
*len = 0;
}
return true;
}
return false;
}
bool coap_has_unprocessed_critical_options(const coap_pkt_t *pkt)
{
for (unsigned i = 0; i < sizeof(pkt->opt_crit); ++i){
if (pkt->opt_crit[i]) {
return true;
}
}
return false;
}
ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_len,
coap_request_ctx_t *ctx)
{
assert(ctx);
if (coap_get_code_class(pkt) != COAP_REQ) {
DEBUG("coap_handle_req(): not a request.\n");
return -EBADMSG;
}
if (pkt->hdr->code == 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,
coap_resources, coap_resources_numof);
}
ssize_t coap_subtree_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len,
coap_request_ctx_t *context)
{
assert(context);
coap_resource_subtree_t *subtree = coap_request_ctx_get_context(context);
return coap_tree_handler(pkt, buf, len, context, subtree->resources,
subtree->resources_numof);
}
ssize_t coap_tree_handler(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_len,
coap_request_ctx_t *ctx, const coap_resource_t *resources,
size_t resources_numof)
{
coap_method_flags_t method_flag = coap_method2flag(coap_get_code_detail(pkt));
uint8_t uri[CONFIG_NANOCOAP_URI_MAX];
if (coap_get_uri_path(pkt, uri) <= 0) {
return -EBADMSG;
}
DEBUG("nanocoap: URI path: \"%s\"\n", uri);
for (unsigned i = 0; i < resources_numof; i++) {
const coap_resource_t *resource = &resources[i];
if (!(resource->methods & method_flag)) {
continue;
}
int res = coap_match_path(resource, uri);
if (res != 0) {
continue;
}
ctx->resource = resource;
return resource->handler(pkt, resp_buf, resp_buf_len, ctx);
}
return coap_build_reply(pkt, COAP_CODE_404, resp_buf, resp_buf_len, 0);
}
ssize_t coap_build_reply_header(coap_pkt_t *pkt, unsigned code,
void *buf, size_t len,
int ct,
void **payload, size_t *payload_len_max)
{
uint8_t *bufpos = buf;
uint32_t no_response;
unsigned tkl = coap_get_token_len(pkt);
size_t hdr_len = sizeof(coap_hdr_t) + tkl;
uint8_t type = coap_get_type(pkt) == COAP_TYPE_CON
? COAP_TYPE_ACK
: COAP_TYPE_NON;
if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) {
/* Worst case: 2 byte extended token length field.
* See https://www.rfc-editor.org/rfc/rfc8974#name-extended-token-length-tkl-f
*/
hdr_len += 2;
}
if (hdr_len > len) {
return -ENOBUFS;
}
bufpos += coap_build_hdr(buf, type, coap_get_token(pkt), tkl,
code, ntohs(pkt->hdr->id));
if (coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response) == 0) {
const uint8_t no_response_index = (code >> 5) - 1;
/* If the handler code misbehaved here, we'd face UB otherwise */
assume(no_response_index < 7);
const uint8_t mask = 1 << no_response_index;
if (no_response & mask) {
if (payload) {
*payload = NULL;
*payload_len_max = 0;
payload = NULL;
}
/* no-response requested, only send empty ACK or nothing */
if (type != COAP_TYPE_ACK) {
return 0;
}
}
}
if (IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) {
/* we need to update the header length with the actual one, as we may
* have used less bytes for the extended token length fields as our
* worst case assumption */
hdr_len = bufpos - (uint8_t *)buf;
}
if (payload) {
if (ct >= 0) {
bufpos += coap_put_option_ct(bufpos, 0, ct);
}
*bufpos++ = COAP_PAYLOAD_MARKER;
*payload = bufpos;
hdr_len = bufpos - (uint8_t *)buf;
*payload_len_max = len - hdr_len;
}
/* with the nanoCoAP API we can't detect the overflow before it happens */
assert(hdr_len <= len);
return hdr_len;
}
ssize_t coap_reply_simple(coap_pkt_t *pkt,
unsigned code,
uint8_t *buf, size_t len,
unsigned ct,
const void *payload, size_t payload_len)
{
void *payload_start;
size_t payload_len_max;
ssize_t header_len = coap_build_reply_header(pkt, code, buf, len, ct,
payload ? &payload_start : NULL,
&payload_len_max);
if (payload == NULL || header_len <= 0) {
return header_len;
}
if (payload_len > payload_len_max) {
return -ENOBUFS;
}
memcpy(payload_start, payload, payload_len);
return header_len + payload_len;
}
ssize_t coap_build_empty_ack(coap_pkt_t *pkt, coap_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);
}
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 len = sizeof(coap_hdr_t) + tkl;
if ((len + payload_len) > rlen) {
return -ENOSPC;
}
/* if code is COAP_CODE_EMPTY (zero), assume Reset (RST) type */
unsigned type = COAP_TYPE_RST;
if (code) {
if (coap_get_type(pkt) == COAP_TYPE_CON) {
type = COAP_TYPE_ACK;
}
else {
type = COAP_TYPE_NON;
}
}
uint32_t no_response;
if (coap_opt_get_uint(pkt, COAP_OPT_NO_RESPONSE, &no_response) == 0) {
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;
/* option contains bitmap of disinterest */
if (no_response & mask) {
switch (coap_get_type(pkt)) {
case COAP_TYPE_NON:
/* no response and no ACK */
return 0;
default:
/* There is an immediate ACK response, but it is an empty response */
code = COAP_CODE_EMPTY;
len = sizeof(coap_hdr_t);
tkl = 0;
payload_len = 0;
break;
}
}
}
coap_build_hdr((coap_hdr_t *)rbuf, type, coap_get_token(pkt), tkl, code,
ntohs(pkt->hdr->id));
len += payload_len;
return len;
}
ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, const void *token,
size_t token_len, unsigned code, uint16_t id)
{
assert(!(type & ~0x3));
uint16_t tkl_ext;
uint8_t tkl_ext_len, tkl;
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;
}
else if (token_len > 12 && IS_USED(MODULE_NANOCOAP_TOKEN_EXT)) {
tkl_ext_len = 1;
tkl_ext = token_len - 13;
tkl = 13;
}
else {
tkl = token_len;
tkl_ext_len = 0;
}
memset(hdr, 0, sizeof(coap_hdr_t));
hdr->ver_t_tkl = (COAP_V1 << 6) | (type << 4) | tkl;
hdr->code = code;
hdr->id = htons(id);
if (tkl_ext_len) {
memcpy(hdr + 1, &tkl_ext, tkl_ext_len);
}
/* Some users build a response packet in the same buffer that contained
* the request. In this case, the argument token already points inside
* the target, or more specifically, it is already at the correct place.
* Having `src` and `dest` in `memcpy(dest, src, len)` overlap is
* 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);
if (token_dest != token) {
memcpy(token_dest, token, token_len);
}
return sizeof(coap_hdr_t) + token_len + tkl_ext_len;
}
void coap_pkt_init(coap_pkt_t *pkt, uint8_t *buf, size_t len, size_t header_len)
{
memset(pkt, 0, sizeof(coap_pkt_t));
pkt->hdr = (coap_hdr_t *)buf;
pkt->payload = buf + header_len;
pkt->payload_len = len - header_len;
}
/*
* Decodes a field value in an Option header, either option delta or length.
*
* val[in] Value of a nybble of the first byte of the option
* header. Upper nybble is coded length of delta; lower
* nybble is coded length of value.
* pkt_pos_ptr[in,out] in: commonly, first byte of the option's value;
* otherwise, first byte of extended delta/length header
* out: next byte after the value or extended header
* pkt_end[in] next byte after all options
*
* return field value
* return -ENOSPC if decoded val would extend beyond packet end
* return -EBADMSG if val is 0xF, suggesting the full byte is
* the 0xFF payload marker
*/
static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end)
{
uint8_t *pkt_pos = *pkt_pos_ptr;
size_t left = pkt_end - pkt_pos;
int res;
switch (val) {
case 13:
{
/* An 8-bit unsigned integer follows the initial byte and
indicates the Option Delta minus 13. */
if (left < 1) {
return -ENOSPC;
}
uint8_t delta = *pkt_pos++;
res = delta + 13;
break;
}
case 14:
{
/* A 16-bit unsigned integer in network byte order follows
* the initial byte and indicates the Option Delta minus
* 269. */
if (left < 2) {
return -ENOSPC;
}
uint16_t delta;
uint8_t *_tmp = (uint8_t *)&delta;
*_tmp++ = *pkt_pos++;
*_tmp++ = *pkt_pos++;
res = ntohs(delta) + 269;
break;
}
case 15:
/* Reserved for the Payload Marker. If the field is set to
* this value but the entire byte is not the payload
* marker, this MUST be processed as a message format
* error. */
return -EBADMSG;
default:
res = val;
}
*pkt_pos_ptr = pkt_pos;
return res;
}
static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes)
{
assert(nbytes <= 4);
uint32_t res = 0;
if (nbytes) {
memcpy(((uint8_t *)&res) + (4 - nbytes), pkt_pos, nbytes);
}
return ntohl(res);
}
static size_t _encode_uint(uint32_t *val)
{
uint8_t *tgt = (uint8_t *)val;
size_t size = 0;
/* count number of used bytes */
uint32_t tmp = *val;
while (tmp) {
size++;
tmp >>= 8;
}
/* convert to network byte order */
tmp = htonl(*val);
/* copy bytewise, starting with first actually used byte */
*val = 0;
uint8_t *tmp_u8 = (uint8_t *)&tmp;
tmp_u8 += (4 - size);
for (unsigned n = 0; n < size; n++) {
*tgt++ = *tmp_u8++;
}
return size;
}
/*
* Writes CoAP Option header. Expected to be called twice to write an option:
*
* 1. write delta, using offset 1, shift 4
* 2. write length, using offset n, shift 0, where n is the return value from
* the first invocation of this function
*
* 0 1 2 3 4 5 6 7
* +---------------+---------------+
* | Option Delta | Option Length | 1 byte
* +---------------+---------------+
* / Option Delta / 0-2 bytes
* \ (extended) \
* +-------------------------------+
* / Option Length / 0-2 bytes
* \ (extended) \
* +-------------------------------+
*
* From RFC 7252, Figure 8
*
* param[out] buf addr of byte 0 of header
* param[in] offset offset from buf to write any extended header
* param[in] shift bit shift for byte 0 value
* param[in] value delta/length value to write to header
*
* return offset from byte 0 of next byte to write
*/
static unsigned _put_delta_optlen(uint8_t *buf, unsigned offset, unsigned shift,
unsigned val)
{
if (val < 13) {
*buf |= (val << shift);
}
else if (val < (256 + 13)) {
*buf |= (13 << shift);
buf[offset++] = (val - 13);
}
else {
*buf |= (14 << shift);
uint16_t tmp = (val - 269);
tmp = htons(tmp);
memcpy(buf + offset, &tmp, 2);
offset += 2;
}
return offset;
}
size_t coap_put_option(uint8_t *buf, uint16_t lastonum, uint16_t onum, const void *odata, size_t olen)
{
assert(lastonum <= onum);
unsigned delta = (onum - lastonum);
*buf = 0;
/* write delta value to option header: 4 upper bits of header (shift 4) +
* 1 or 2 optional bytes depending on delta value) */
unsigned n = _put_delta_optlen(buf, 1, 4, delta);
/* write option length to option header: 4 lower bits of header (shift 0) +
* 1 or 2 optional bytes depending of the length of the option */
n = _put_delta_optlen(buf, n, 0, olen);
if (olen) {
memcpy(buf + n, odata, olen);
n += olen;
}
return (size_t)n;
}
static unsigned _size2szx(size_t size)
{
assert(size <= 1024);
/* We must wait to subtract the szx offset of 4 until after the assert below.
* Input should be a power of two, but if not it may have a stray low order
* '1' bit that would invalidate the subtraction. */
unsigned szx = bitarithm_lsb(size);
assert(szx >= 4);
return szx - 4;
}
static unsigned _slicer2blkopt(coap_block_slicer_t *slicer, bool more)
{
size_t blksize = slicer->end - slicer->start;
size_t start = slicer->start;
unsigned blknum = 0;
while (start > 0) {
start -= blksize;
blknum++;
}
return (blknum << 4) | _size2szx(blksize) | (more ? 0x8 : 0);
}
int coap_get_block(coap_pkt_t *pkt, coap_block1_t *block, uint16_t option)
{
block->more = coap_get_blockopt(pkt, option, &block->blknum, &block->szx);
if (block->more >= 0) {
block->offset = block->blknum << (block->szx + 4);
}
else {
block->offset = 0;
}
return (block->more >= 0);
}
size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t lastonum)
{
if (block1->more >= 1) {
return coap_put_option_block1(pkt_pos, lastonum, block1->blknum, block1->szx, block1->more);
}
else {
return 0;
}
}
size_t coap_opt_put_block(uint8_t *buf, uint16_t lastonum, coap_block_slicer_t *slicer,
bool more, uint16_t option)
{
slicer->opt = buf;
return coap_opt_put_uint(buf, lastonum, option, _slicer2blkopt(slicer, more));
}
size_t coap_opt_put_string_with_len(uint8_t *buf, uint16_t lastonum, uint16_t optnum,
const char *string, size_t len, char separator)
{
if (len == 0) {
return 0;
}
uint8_t *bufpos = buf;
char *uripos = (char *)string;
while (len) {
size_t part_len;
if (*uripos == separator) {
uripos++;
}
uint8_t *part_start = (uint8_t *)uripos;
while (len) {
/* must decrement separately from while loop test to ensure
* the value remains non-negative */
len--;
if ((*uripos == separator) || (*uripos == '\0')) {
break;
}
uripos++;
}
part_len = (uint8_t *)uripos - part_start;
/* Creates empty option if part for Uri-Path or Uri-Location contains only *
* a trailing slash, except for root path ("/"). */
if (part_len || ((separator == '/') && (lastonum == optnum))) {
bufpos += coap_put_option(bufpos, lastonum, optnum, part_start, part_len);
lastonum = optnum;
}
}
return bufpos - buf;
}
size_t coap_opt_put_uri_pathquery(uint8_t *buf, uint16_t *lastonum, const char *uri)
{
size_t len;
const char *query = strchr(uri, '?');
uint16_t _lastonum = lastonum ? *lastonum : 0;
if (query) {
len = (query == uri) ? 0 : (query - uri - 1);
} else {
len = strlen(uri);
}
size_t bytes_out = coap_opt_put_string_with_len(buf, _lastonum,
COAP_OPT_URI_PATH,
uri, len, '/');
if (query) {
buf += bytes_out;
bytes_out += coap_opt_put_uri_query(buf, COAP_OPT_URI_PATH, query + 1);
_lastonum = COAP_OPT_URI_QUERY;
}
else if (bytes_out) {
_lastonum = COAP_OPT_URI_PATH;
}
if (lastonum) {
*lastonum = _lastonum;
}
return bytes_out;
}
size_t coap_opt_put_uint(uint8_t *buf, uint16_t lastonum, uint16_t onum,
uint32_t value)
{
unsigned uint_len = _encode_uint(&value);
return coap_put_option(buf, lastonum, onum, (uint8_t *)&value, uint_len);
}
/* Common functionality for addition of an option */
static ssize_t _add_opt_pkt(coap_pkt_t *pkt, uint16_t optnum, const void *val,
size_t val_len)
{
if (pkt->options_len >= CONFIG_NANOCOAP_NOPTS_MAX) {
return -ENOSPC;
}
uint16_t lastonum = (pkt->options_len)
? pkt->options[pkt->options_len - 1].opt_num : 0;
assert(optnum >= lastonum);
/* calculate option length */
uint8_t dummy[3] = { 0 };
size_t optlen = _put_delta_optlen(dummy, 1, 4, optnum - lastonum);
optlen += _put_delta_optlen(dummy, 0, 0, val_len);
optlen += val_len;
if (pkt->payload_len < optlen) {
return -ENOSPC;
}
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_len++;
pkt->payload += optlen;
pkt->payload_len -= optlen;
return optlen;
}
ssize_t coap_opt_add_chars(coap_pkt_t *pkt, uint16_t optnum, const char *chars,
size_t chars_len, char separator)
{
/* chars_len denotes the length of the chars buffer and is
* gradually decremented below while iterating over the buffer */
if (!chars_len) {
return 0;
}
char *uripos = (char *)chars;
char *endpos = ((char *)chars + chars_len);
size_t write_len = 0;
while (chars_len) {
size_t part_len;
if (*uripos == separator) {
uripos++;
}
uint8_t *part_start = (uint8_t *)uripos;
while (chars_len) {
/* must decrement separately from while loop test to ensure
* the value remains non-negative */
chars_len--;
if ((*uripos == separator) || (uripos == endpos)) {
break;
}
uripos++;
}
part_len = (uint8_t *)uripos - part_start;
/* Creates empty option if part for Uri-Path or Uri-Location contains
* only a trailing slash, except for root path ("/"). */
if (part_len || ((separator == '/') && write_len)) {
ssize_t optlen = _add_opt_pkt(pkt, optnum, part_start, part_len);
if (optlen < 0) {
return optlen;
}
write_len += optlen;
}
}
return write_len;
}
ssize_t coap_opt_add_uri_query2(coap_pkt_t *pkt, const char *key, size_t key_len,
const char *val, size_t val_len)
{
assert(pkt);
assert(key);
assert(key_len);
assert(!val_len || (val && val_len));
char qs[CONFIG_NANOCOAP_QS_MAX];
/* length including '=' */
size_t qs_len = key_len + ((val && val_len) ? (val_len + 1) : 0);
/* test if the query string fits */
if (qs_len > CONFIG_NANOCOAP_QS_MAX) {
return -1;
}
memcpy(&qs[0], key, key_len);
if (val && val_len) {
qs[key_len] = '=';
memcpy(&qs[key_len + 1], val, val_len);
}
return _add_opt_pkt(pkt, COAP_OPT_URI_QUERY, (uint8_t *)qs, qs_len);
}
ssize_t coap_opt_add_opaque(coap_pkt_t *pkt, uint16_t optnum, const void *val, size_t val_len)
{
return _add_opt_pkt(pkt, optnum, val, val_len);
}
ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value)
{
uint32_t tmp = value;
unsigned tmp_len = _encode_uint(&tmp);
return _add_opt_pkt(pkt, optnum, (uint8_t *)&tmp, tmp_len);
}
ssize_t coap_opt_add_block(coap_pkt_t *pkt, coap_block_slicer_t *slicer,
bool more, uint16_t option)
{
slicer->opt = pkt->payload;
return coap_opt_add_uint(pkt, option, _slicer2blkopt(slicer, more));
}
ssize_t coap_opt_add_proxy_uri(coap_pkt_t *pkt, const char *uri)
{
assert(pkt);
assert(uri);
return _add_opt_pkt(pkt, COAP_OPT_PROXY_URI, (uint8_t *)uri, strlen(uri));
}
ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags)
{
if (flags & COAP_OPT_FINISH_PAYLOAD) {
if (!pkt->payload_len) {
return -ENOSPC;
}
*pkt->payload++ = COAP_PAYLOAD_MARKER;
pkt->payload_len--;
}
else {
pkt->payload_len = 0;
}
return pkt->payload - (uint8_t *)pkt->hdr;
}
ssize_t coap_opt_remove(coap_pkt_t *pkt, uint16_t opt_num)
{
assert(pkt);
coap_optpos_t *optpos = pkt->options;
coap_optpos_t *prev_opt = NULL;
unsigned opt_count = pkt->options_len;
uint8_t *start_old = NULL; /* First byte that is memmoved: content of next
* option, payload marker or end of message */
uint8_t *start_new = NULL; /* memmove destination: content after rewritten
* next option header, or offset of removed option */
while (opt_count--) {
if ((start_old == NULL) && (optpos->opt_num == opt_num)) {
unsigned new_hdr_len, old_hdr_len;
int option_len;
uint8_t *opt_start;
uint16_t old_delta, new_delta;
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;
break;
}
if (prev_opt == NULL) {
/* this is the first option => new_delta is just opt_num of next option */
new_delta = optpos[1].opt_num;
} else {
new_delta = optpos[1].opt_num - prev_opt->opt_num;
}
prev_opt = optpos;
optpos++;
opt_count--;
/* select start of next option */
opt_start = (uint8_t *)pkt->hdr + 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 = 0;
/* write new_delta value to option header: 4 upper bits of header (shift 4) +
* 1 or 2 optional bytes depending on delta value) */
new_hdr_len = _put_delta_optlen(start_new, 1, 4, new_delta);
/* write option length to option header: 4 lower bits of header (shift 0) +
* 1 or 2 optional bytes depending of the length of the option */
new_hdr_len = _put_delta_optlen(start_new, new_hdr_len, 0, option_len);
start_new += new_hdr_len;
/* account for header length change in next option */
optpos->offset -= (new_hdr_len - old_hdr_len);
}
/* start_old implies start_new */
if (start_old != NULL) {
assert(start_new);
/* adapt options array for removed option */
memcpy(prev_opt, optpos, sizeof(*prev_opt));
prev_opt->offset -= (start_old - start_new);
}
prev_opt = optpos;
optpos++;
}
if (start_old != NULL) {
size_t move_size = (pkt->payload - start_old) + pkt->payload_len;
pkt->options_len--;
if (move_size > 0) {
memmove(start_new, start_old, move_size);
}
pkt->payload -= (start_old - start_new);
}
return (pkt->payload - ((uint8_t *)pkt->hdr)) + pkt->payload_len;
}
ssize_t coap_payload_put_bytes(coap_pkt_t *pkt, const void *data, size_t len)
{
if (pkt->payload_len < len) {
return -ENOSPC;
}
memcpy(pkt->payload, data, len);
coap_payload_advance_bytes(pkt, len);
return len;
}
ssize_t coap_payload_put_char(coap_pkt_t *pkt, char c)
{
if (pkt->payload_len < 1) {
return -ENOSPC;
}
*pkt->payload++ = c;
pkt->payload_len--;
return 1;
}
void coap_block_object_init(coap_block1_t *block, size_t blknum, size_t blksize,
int more)
{
block->szx = _size2szx(blksize);
block->blknum = blknum;
block->more = more;
block->offset = block->blknum << (block->szx + 4);
}
void coap_block_slicer_init(coap_block_slicer_t *slicer, size_t blknum,
size_t blksize)
{
slicer->start = blknum * blksize;
slicer->end = slicer->start + blksize;
slicer->cur = 0;
}
void coap_block2_init(coap_pkt_t *pkt, coap_block_slicer_t *slicer)
{
uint32_t blknum = 0;
uint8_t szx = 0;
/* Retrieve the block2 option from the client request */
if (coap_get_blockopt(pkt, COAP_OPT_BLOCK2, &blknum, &szx) >= 0) {
/* Use the client requested block size if it is smaller than our own
* maximum block size */
if (CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX - 4 < szx) {
szx = CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX - 4;
}
}
coap_block_slicer_init(slicer, blknum, coap_szx2size(szx));
}
bool coap_block_finish(coap_block_slicer_t *slicer, uint16_t option)
{
assert(slicer->opt);
/* The third parameter for _decode_value() points to the end of the header.
* We don't know this position, but we know we can read the option because
* it's already in the buffer. So just point past the option. */
uint8_t *pos = slicer->opt + 1;
uint16_t delta = _decode_value(*slicer->opt >> 4, &pos, slicer->opt + 3);
bool more = slicer->cur > slicer->end;
uint32_t blkopt = _slicer2blkopt(slicer, more);
size_t olen = _encode_uint(&blkopt);
/* ensure that we overwrite the dummy value set by coap_block2_init() */
if (!olen) {
olen = 1;
}
coap_put_option(slicer->opt, option - delta, option, (uint8_t *)&blkopt, olen);
return more;
}
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)
{
/* Check if the generated data filled the requested block */
if (slicer->cur < slicer->start) {
return coap_build_reply(pkt, COAP_CODE_BAD_OPTION, rbuf, rlen, 0);
}
coap_block2_finish(slicer);
return coap_build_reply(pkt, code, rbuf, rlen, payload_len);
}
size_t coap_blockwise_put_char(coap_block_slicer_t *slicer, uint8_t *bufpos, char c)
{
/* Only copy the char if it is within the window */
if ((slicer->start <= slicer->cur) && (slicer->cur < slicer->end)) {
*bufpos = c;
slicer->cur++;
return 1;
}
slicer->cur++;
return 0;
}
size_t coap_blockwise_put_bytes(coap_block_slicer_t *slicer, uint8_t *bufpos,
const void *c, size_t len)
{
size_t str_len = 0; /* Length of the string to copy */
/* Calculate start offset of the supplied string */
size_t str_offset = (slicer->start > slicer->cur)
? slicer->start - slicer->cur
: 0;
/* Check for string before or beyond window */
if ((slicer->cur >= slicer->end) || (str_offset > len)) {
slicer->cur += len;
return 0;
}
/* Check if string is over the end of the window */
if ((slicer->cur + len) >= slicer->end) {
str_len = slicer->end - (slicer->cur + str_offset);
}
else {
str_len = len - str_offset;
}
/* Only copy the relevant part of the string to the buffer */
memcpy(bufpos, (uint8_t *)c + str_offset, str_len);
slicer->cur += len;
return str_len;
}
ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, uint8_t *buf, \
size_t len, coap_request_ctx_t *context)
{
(void)context;
coap_block_slicer_t slicer;
coap_block2_init(pkt, &slicer);
uint8_t *payload = buf + coap_get_response_hdr_len(pkt);
uint8_t *bufpos = payload;
bufpos += coap_put_option_ct(bufpos, 0, COAP_FORMAT_LINK);
bufpos += coap_opt_put_block2(bufpos, COAP_OPT_CONTENT_FORMAT, &slicer, 1);
*bufpos++ = COAP_PAYLOAD_MARKER;
for (unsigned i = 0; i < coap_resources_numof; i++) {
if (i) {
bufpos += coap_blockwise_put_char(&slicer, bufpos, ',');
}
bufpos += coap_blockwise_put_char(&slicer, bufpos, '<');
unsigned url_len = strlen(coap_resources[i].path);
bufpos += coap_blockwise_put_bytes(&slicer, bufpos,
(uint8_t*)coap_resources[i].path, url_len);
bufpos += coap_blockwise_put_char(&slicer, bufpos, '>');
}
unsigned payload_len = bufpos - payload;
return coap_block2_build_reply(pkt, COAP_CODE_205, buf, len, payload_len,
&slicer);
}
unsigned coap_get_len(coap_pkt_t *pkt)
{
unsigned pktlen = sizeof(coap_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)
{
memset(ctx, 0, sizeof(*ctx));
ctx->remote = remote;
}
const char *coap_request_ctx_get_path(const coap_request_ctx_t *ctx)
{
return ctx->resource->path;
}
void *coap_request_ctx_get_context(const coap_request_ctx_t *ctx)
{
return ctx->resource->context;
}
uint32_t coap_request_ctx_get_tl_type(const coap_request_ctx_t *ctx)
{
#ifdef MODULE_GCOAP
return ctx->tl_type;
#else
(void)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
}