diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 0d04d3cec7c6..e5641ff05f41 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -50,7 +50,9 @@ PSEUDOMODULES += lwip_tcp PSEUDOMODULES += lwip_udp PSEUDOMODULES += lwip_udplite PSEUDOMODULES += mpu_stack_guard -PSEUDOMODULES += nanocoap_% +PSEUDOMODULES += nanocoap_sock +PSEUDOMODULES += nanocoap_opt2 +PSEUDOMODULES += nanocoap_opt2_sort PSEUDOMODULES += netdev_default PSEUDOMODULES += netif PSEUDOMODULES += netstats diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index de156d59279a..9296940f5781 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-17 Kaspar Schleiser + * Copyright (C) 2018 Ken Bannister * * 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 @@ -7,10 +8,52 @@ */ /** - * @defgroup net_nanocoap nanocoap small CoAP library + * @defgroup net_nanocoap Nanocoap, small CoAP library * @ingroup net * @brief Provides CoAP functionality optimized for minimal resource usage * + * nanocoap includes both server-side response generation and client-side + * request generation. + * + * ## Write Options and Payload ## + * + * For both server responses and client requests, CoAP uses an Option mechanism + * to encode message metadata that is not required for each message. For + * example, the resource URI path is required only for a request, and is encoded + * as the Uri-Path option. + * + * nanocoap provides two APIs for writing CoAP options: + * + * A **minimal** API that requires only a reference to the buffer. However, the + * caller must remember and provide the last option number written, as well + * as the buffer position. The minimal API always is available. + * + * A convenient **struct-based** API that uses a coap_pkt_t struct to track each + * option as it is written. Activate this API by use of the `nanocoap_opt2` + * submodule. + * + * For either API, by default the caller *must* write options in order by option + * number (see "CoAP option numbers", below). However, the struct-based API + * supports use of the `nanocoap_opt2_sort` module. This module allows the user + * to enter options in any order, and then sorts them in coap_opt_finish(). + * + * ### Higher-level, struct-based API ### + * + * Before starting, ensure the buffer and CoAP header have been initialized. + * For a request, use `coap_build_hdr()`. For a response, update the header + * attributes from the request buffer as needed. + * + * Use `coap_pkt_init()` to initialize the coap_pkt_t struct, including the + * array of options. + * + * Next, use the `coap_opt_add_xxx()` functions to write each option, like + * `coap_opt_add_uint()`. When all options have been added, use + * `coap_opt_finish()`. + * + * Finally, write any message payload at the coap_pkt_t payload pointer + * attribute. The `payload_len` attribute provides the available length in the + * buffer. + * * @{ * * @file @@ -403,6 +446,7 @@ ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_le ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, size_t token_len, unsigned code, uint16_t id); +#ifdef MODULE_NANOCOAP_OPT2 /** * @brief Initialize a packet struct, to build a message buffer * @@ -418,6 +462,7 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, * @param[in] header_len length of header in buf, including token */ void coap_pkt_init(coap_pkt_t *pkt, uint8_t *buf, size_t len, size_t header_len); +#endif /** * @brief Insert a CoAP option into buffer @@ -528,6 +573,7 @@ size_t coap_put_option_block1(uint8_t *buf, uint16_t lastonum, unsigned blknum, */ size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t lastonum); +#ifdef MODULE_NANOCOAP_OPT2 /** * @brief Encode the given string as option(s) into pkt * @@ -573,6 +619,7 @@ ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value); * @return total number of bytes written to buffer */ ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags); +#endif /* MODULE_NANOCOAP_OPT2 */ /** * @brief Get content type from packet diff --git a/sys/net/application_layer/nanocoap/Makefile b/sys/net/application_layer/nanocoap/Makefile index ff222a69b508..24f155d82ead 100644 --- a/sys/net/application_layer/nanocoap/Makefile +++ b/sys/net/application_layer/nanocoap/Makefile @@ -1,3 +1,11 @@ SRC := nanocoap.c -SUBMODULES := 1 + +ifneq (,$(filter nanocoap_sock,$(USEMODULE))) + SRC += sock.c +endif + +ifneq (,$(filter nanocoap_opt2,$(USEMODULE))) + SRC += opt2_add.c +endif + include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index be3f7482f148..b2503879637d 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-18 Kaspar Schleiser + * Copyright (C) 2018 Ken Bannister * * 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 @@ -14,6 +15,7 @@ * @brief Nanocoap implementation * * @author Kaspar Schleiser + * @author Ken Bannister * * @} */ @@ -31,7 +33,11 @@ static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end); int coap_get_option_uint(coap_pkt_t *pkt, unsigned opt_num, uint32_t *target); static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes); -static size_t _encode_uint(uint32_t *val); +size_t _encode_uint(uint32_t *val); + +#ifdef MODULE_NANOCOAP_OPT2_SORT +extern ssize_t _sort_opts(coap_pkt_t *pkt); +#endif /* http://tools.ietf.org/html/rfc7252#section-3 * 0 1 2 3 @@ -147,7 +153,7 @@ uint8_t *coap_find_option(coap_pkt_t *pkt, unsigned opt_num) return NULL; } -static uint8_t *_parse_option(coap_pkt_t *pkt, uint8_t *pkt_pos, uint16_t *delta, int *opt_len) +uint8_t *_parse_option(coap_pkt_t *pkt, uint8_t *pkt_pos, uint16_t *delta, int *opt_len) { uint8_t *hdr_end = pkt->payload; @@ -383,15 +389,6 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, size_t to return sizeof(coap_hdr_t) + token_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->token = buf + sizeof(coap_hdr_t); - pkt->payload = buf + header_len; - pkt->payload_len = len - header_len; -} - static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end) { uint8_t *pkt_pos = *pkt_pos_ptr; @@ -450,7 +447,7 @@ static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes) return ntohl(res); } -static size_t _encode_uint(uint32_t *val) +size_t _encode_uint(uint32_t *val) { uint8_t *tgt = (uint8_t *)val; size_t size = 0; @@ -497,9 +494,12 @@ static unsigned _put_delta_optlen(uint8_t *buf, unsigned offset, unsigned shift, size_t coap_put_option(uint8_t *buf, uint16_t lastonum, uint16_t onum, uint8_t *odata, size_t olen) { +#ifdef MODULE_NANOCOAP_OPT2_SORT + unsigned delta = (lastonum <= onum) ? (onum - lastonum) : 0; +#else assert(lastonum <= onum); - unsigned delta = (onum - lastonum); +#endif *buf = 0; /* write delta value to option header: 4 upper bits of header (shift 4) + @@ -604,85 +604,6 @@ size_t coap_put_option_uri(uint8_t *buf, uint16_t lastonum, const char *uri, uin return bufpos - buf; } -/* Common functionality for addition of an option */ -static ssize_t _add_opt_pkt(coap_pkt_t *pkt, uint16_t optnum, uint8_t *val, - size_t val_len) -{ - assert(pkt->options_len < NANOCOAP_NOPTS_MAX); - - uint16_t lastonum = (pkt->options_len) - ? pkt->options[pkt->options_len - 1].opt_num : 0; - assert(optnum >= lastonum); - - size_t optlen = coap_put_option(pkt->payload, lastonum, optnum, val, val_len); - assert(pkt->payload_len > optlen); - - 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_string(coap_pkt_t *pkt, uint16_t optnum, const char *string, - char separator) -{ - size_t unread_len = strlen(string); - if (!unread_len) { - return 0; - } - char *uripos = (char *)string; - size_t write_len = 0; - - while (unread_len) { - size_t part_len; - uripos++; - uint8_t *part_start = (uint8_t *)uripos; - - while (unread_len--) { - if ((*uripos == separator) || (*uripos == '\0')) { - break; - } - uripos++; - } - - part_len = (uint8_t *)uripos - part_start; - - if (part_len) { - if (pkt->options_len == NANOCOAP_NOPTS_MAX) { - return -ENOSPC; - } - write_len += _add_opt_pkt(pkt, optnum, part_start, part_len); - } - } - - return write_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_finish(coap_pkt_t *pkt, uint16_t flags) -{ - if (flags & COAP_OPT_FINISH_PAYLOAD) { - assert(pkt->payload_len > 1); - - *pkt->payload++ = 0xFF; - pkt->payload_len--; - } - else { - pkt->payload_len = 0; - } - - return pkt->payload - (uint8_t *)pkt->hdr; -} - ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, uint8_t *buf, \ size_t len, void *context) { diff --git a/sys/net/application_layer/nanocoap/opt2_add.c b/sys/net/application_layer/nanocoap/opt2_add.c new file mode 100644 index 000000000000..be297a8d80b3 --- /dev/null +++ b/sys/net/application_layer/nanocoap/opt2_add.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2018 Ken Bannister + * + * 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 +#include +#include + +#include "net/nanocoap.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifdef MODULE_NANOCOAP_OPT2_SORT +ssize_t _sort_opts(coap_pkt_t *pkt); +#endif + +extern size_t _encode_uint(uint32_t *val); +extern uint8_t *_parse_option(coap_pkt_t *pkt, uint8_t *pkt_pos, uint16_t *delta, + int *opt_len); + +/* Common functionality for addition of an option */ +static ssize_t _add_opt_pkt(coap_pkt_t *pkt, uint16_t optnum, uint8_t *val, + size_t val_len) +{ + assert(pkt->options_len < NANOCOAP_NOPTS_MAX); + + uint16_t lastonum = (pkt->options_len) + ? pkt->options[pkt->options_len - 1].opt_num : 0; + + size_t optlen = coap_put_option(pkt->payload, lastonum, optnum, val, val_len); + assert(pkt->payload_len > optlen); + + 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_string(coap_pkt_t *pkt, uint16_t optnum, const char *string, + char separator) +{ + size_t unread_len = strlen(string); + if (!unread_len) { + return 0; + } + char *uripos = (char *)string; + size_t write_len = 0; + + while (unread_len) { + size_t part_len; + uripos++; + uint8_t *part_start = (uint8_t *)uripos; + + while (unread_len--) { + if ((*uripos == separator) || (*uripos == '\0')) { + break; + } + uripos++; + } + + part_len = (uint8_t *)uripos - part_start; + + if (part_len) { + if (pkt->options_len == NANOCOAP_NOPTS_MAX) { + return -ENOSPC; + } + write_len += _add_opt_pkt(pkt, optnum, part_start, part_len); + } + } + + return write_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_finish(coap_pkt_t *pkt, uint16_t flags) +{ +#ifdef MODULE_NANOCOAP_OPT2_SORT + _sort_opts(pkt); +#endif + if (flags & COAP_OPT_FINISH_PAYLOAD) { + assert(pkt->payload_len > 1); + + *pkt->payload++ = 0xFF; + pkt->payload_len--; + } + else { + pkt->payload_len = 0; + } + + return pkt->payload - (uint8_t *)pkt->hdr; +} + +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->token = buf + sizeof(coap_hdr_t); + pkt->payload = buf + header_len; + pkt->payload_len = len - header_len; +} + +#ifdef MODULE_NANOCOAP_OPT2_SORT +static bool _sorted(coap_pkt_t *pkt) +{ + for (unsigned i = 1; i < pkt->options_len; i++) { + if (pkt->options[i].opt_num < pkt->options[i-1].opt_num) { + return false; + } + } + return true; +} + +/* + * Sort the stored message options by option number. Rewrite the buffer and + * update the options attributes in the pkt struct. + * + * pkt[inout] Packet for sort + * returns 0 on success + */ +ssize_t _sort_opts(coap_pkt_t *pkt) +{ + if (_sorted(pkt)) { + return 0; + } + + uint8_t *options = (uint8_t *)pkt->hdr + coap_get_total_hdr_len(pkt); + /* scratch buffer to hold sorted options, with extra space to handle any + * large option header */ + uint8_t scratch[(pkt->payload - options) + 4]; + uint8_t *scratch_pos = &scratch[0]; + + unsigned sorted = 0; /* count of sorted elements */ + uint8_t *next_pos = options; + + do { + // find next option to write + unsigned i, best_i = sorted; + for (i = sorted+1; i < pkt->options_len; i++) { + if (pkt->options[i].opt_num < pkt->options[best_i].opt_num) { + best_i = i; + } + } + i = best_i; + /* prepare to relocate option memo currently at sorted location */ + coap_optpos_t bubble_opt; + bubble_opt.opt_num = pkt->options[sorted].opt_num; + bubble_opt.offset = pkt->options[sorted].offset; + + /* read selected option */ + uint16_t delta; // unused + int option_len; + uint8_t *val = _parse_option(pkt, (uint8_t *)pkt->hdr + pkt->options[i].offset, + &delta, &option_len); + + /* write option to scratch buffer */ + uint8_t *last_pos = next_pos; + next_pos += coap_put_option(scratch_pos, + sorted ? pkt->options[sorted-1].opt_num : 0, + pkt->options[i].opt_num, val, option_len); + scratch_pos += (next_pos - last_pos); + + /* update pkt->options record and save bubbled option */ + pkt->options[sorted].opt_num = pkt->options[i].opt_num; + pkt->options[sorted].offset = last_pos - (uint8_t *)pkt->hdr; + if (i != sorted) { + pkt->options[i].opt_num = bubble_opt.opt_num; + pkt->options[i].offset = bubble_opt.offset; + } + } while (++sorted < pkt->options_len); + + int len_delta = next_pos - pkt->payload; + DEBUG("sort moves payload by %d\n", len_delta); + + memcpy(options, &scratch[0], next_pos - options); + + pkt->payload += len_delta; + pkt->payload_len -= len_delta; + return 0; +} +#endif /* MODULE_NANOCOAP_OPT2_SORT */ diff --git a/tests/unittests/tests-nanocoap/Makefile.include b/tests/unittests/tests-nanocoap/Makefile.include index cd6b8172a754..f7b10caf7b59 100644 --- a/tests/unittests/tests-nanocoap/Makefile.include +++ b/tests/unittests/tests-nanocoap/Makefile.include @@ -1 +1,3 @@ USEMODULE += nanocoap +USEMODULE += nanocoap_opt2 +USEMODULE += nanocoap_opt2_sort diff --git a/tests/unittests/tests-nanocoap/tests-nanocoap.c b/tests/unittests/tests-nanocoap/tests-nanocoap.c index 107f476ad3e2..8a1a2885f548 100644 --- a/tests/unittests/tests-nanocoap/tests-nanocoap.c +++ b/tests/unittests/tests-nanocoap/tests-nanocoap.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "embUnit.h" @@ -46,6 +47,7 @@ static void test_nanocoap__hdr(void) TEST_ASSERT_EQUAL_STRING((char *)path, (char *)path_tmp); } +#ifdef MODULE_NANOCOAP_OPT2 /* * Client GET request with simple path. Test request generation. * Request /time resource from libcoap example @@ -219,16 +221,120 @@ static void test_nanocoap__get_path_too_long(void) TEST_ASSERT_EQUAL_INT(-ENOSPC, get_len); } +#ifdef MODULE_NANOCOAP_OPT2_SORT +/* + * Builds on put_req test, to test resorting options in option number order. + */ +static void test_nanocoap__put_opt_sort(void) +{ + uint8_t buf[128]; + coap_pkt_t pkt; + uint16_t msgid = 0xABCD; + uint8_t token[2] = {0xDA, 0xEC}; + char path[] = "/ab/cd"; + + size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_PUT, msgid); + coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); + + /* out of order; 12, 11 */ + coap_opt_add_uint(&pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_TEXT); + coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/'); + len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); + + memset(&pkt, 0, sizeof(coap_pkt_t)); + int res = coap_parse(&pkt, &buf[0], len); + TEST_ASSERT_EQUAL_INT(0, res); + + /* value is "/" on failure; implicit path when Uri-Path absent */ + char uri[10] = {0}; + coap_get_uri(&pkt, (uint8_t *)&uri[0]); + TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri); + + TEST_ASSERT_EQUAL_INT(COAP_FORMAT_TEXT, coap_get_content_type(&pkt)); +} + +/* + * Builds on put_opt_sort test, to test resorting options already in order. + */ +static void test_nanocoap__put_opt_sort_presorted(void) +{ + uint8_t buf[128]; + coap_pkt_t pkt; + uint16_t msgid = 0xABCD; + uint8_t token[2] = {0xDA, 0xEC}; + char path[] = "/ab"; + + size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_PUT, msgid); + coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); + + coap_opt_add_uint(&pkt, COAP_OPT_OBSERVE, 0); + coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/'); + coap_opt_add_uint(&pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_TEXT); + len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); + + memset(&pkt, 0, sizeof(coap_pkt_t)); + int res = coap_parse(&pkt, &buf[0], len); + TEST_ASSERT_EQUAL_INT(0, res); + + /* no API to retrieve observe option value */ + + /* value is "/" on failure; implicit path when Uri-Path absent */ + char uri[10] = {0}; + coap_get_uri(&pkt, (uint8_t *)&uri[0]); + TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri); + + TEST_ASSERT_EQUAL_INT(COAP_FORMAT_TEXT, coap_get_content_type(&pkt)); +} + +/* + * Builds on put_opt_sort test, to test no-op case with a single option. + */ +static void test_nanocoap__put_opt_sort_1opt(void) +{ + uint8_t buf[128]; + coap_pkt_t pkt; + uint16_t msgid = 0xABCD; + uint8_t token[2] = {0xDA, 0xEC}; + char path[] = "/ab"; + + size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_PUT, msgid); + coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); + + coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/'); + len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); + + memset(&pkt, 0, sizeof(coap_pkt_t)); + int res = coap_parse(&pkt, &buf[0], len); + TEST_ASSERT_EQUAL_INT(0, res); + + /* value is "/" on failure; implicit path when Uri-Path absent */ + char uri[10] = {0}; + coap_get_uri(&pkt, (uint8_t *)&uri[0]); + TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri); +} +#endif /* MODULE_NANOCOAP_OPT2_SORT */ +#endif /* MODULE_NANOCOAP_OPT2 */ + Test *tests_nanocoap_tests(void) { EMB_UNIT_TESTFIXTURES(fixtures) { new_TestFixture(test_nanocoap__hdr), +#ifdef MODULE_NANOCOAP_OPT2 new_TestFixture(test_nanocoap__get_req), new_TestFixture(test_nanocoap__put_req), new_TestFixture(test_nanocoap__get_multi_path), new_TestFixture(test_nanocoap__get_root_path), new_TestFixture(test_nanocoap__get_max_path), new_TestFixture(test_nanocoap__get_path_too_long), +#ifdef MODULE_NANOCOAP_OPT2_SORT + new_TestFixture(test_nanocoap__put_opt_sort), + new_TestFixture(test_nanocoap__put_opt_sort_presorted), + new_TestFixture(test_nanocoap__put_opt_sort_1opt), +#endif +#endif }; EMB_UNIT_TESTCALLER(nanocoap_tests, NULL, NULL, fixtures);