diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 0e329bca17532f34f540b36893621fc83f32efef..de156d59279a34bd6cc800d95918ee42e3f07c2b 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -17,6 +17,7 @@ * @brief nanocoap API * * @author Kaspar Schleiser <kaspar@schleiser.de> + * @author Ken Bannister <kb2ma@runbox.com> */ #ifndef NET_NANOCOAP_H @@ -224,6 +225,18 @@ extern "C" { #define COAP_BLOCKWISE_SZX_MAX (7) /** @} */ +/** + * @name coap_opt_finish() flag parameter values + * + * Directs packet/buffer updates when user finishes adding options + * @{ + */ +/** @brief no special handling required */ +#define COAP_OPT_FINISH_NONE (0x0000) +/** @brief expect a payload to follow */ +#define COAP_OPT_FINISH_PAYLOAD (0x0001) +/** @} */ + /** * @brief Raw CoAP PDU header structure */ @@ -390,6 +403,22 @@ 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); +/** + * @brief Initialize a packet struct, to build a message buffer + * + * @pre buf CoAP header already initialized + * @post pkt.flags all zeroed + * @post pkt.payload points to first byte after header + * @post pkt.payload_len set to maximum space available for options + payload + * + * @param[out] pkt pkt to initialize + * @param[in] buf buffer to write for pkt, with CoAP header already + * initialized + * @param[in] len length of buf + * @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); + /** * @brief Insert a CoAP option into buffer * @@ -499,6 +528,52 @@ 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); +/** + * @brief Encode the given string as option(s) into pkt + * + * Use separator to split string into multiple options. + * + * @post pkt.payload advanced to first byte after option(s) + * @post pkt.payload_len reduced by option(s) length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] optnum option number to use + * @param[in] string string to encode as option + * @param[in] separator character used in @p string to separate parts + * + * @return number of bytes written to buffer + * @return -ENOSPC if no available options + */ +ssize_t coap_opt_add_string(coap_pkt_t *pkt, uint16_t optnum, const char *string, char separator); + +/** + * @brief Encode the given uint option into pkt + * + * @post pkt.payload advanced to first byte after option + * @post pkt.payload_len reduced by option length + * + * @param[in,out] pkt pkt referencing target buffer + * @param[in] optnum option number to use + * @param[in] value uint to encode + * + * @return number of bytes written to buffer + * @return <0 reserved for error but not implemented yet + */ +ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value); + +/** + * @brief Finalizes options as required and prepares for payload + * + * @post pkt.payload advanced to first available byte after options + * @post pkt.payload_len is maximum bytes available for payload + * + * @param[in,out] pkt pkt to update + * @param[in] flags see COAP_OPT_FINISH... macros + * + * @return total number of bytes written to buffer + */ +ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags); + /** * @brief Get content type from packet * diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c index e72bda605981b250c4304226e063405c8f4d944b..d5b9c0c7218779ffcbc33f99a55aba247eb7a258 100644 --- a/sys/net/application_layer/nanocoap/nanocoap.c +++ b/sys/net/application_layer/nanocoap/nanocoap.c @@ -383,6 +383,15 @@ 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; @@ -595,6 +604,85 @@ 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/tests/unittests/tests-nanocoap/tests-nanocoap.c b/tests/unittests/tests-nanocoap/tests-nanocoap.c index d5694344359259e8e34afd900cd5fad0bc2ccb7f..b62e4ed85e5f4bb1e04aba4060efa4a4cb3fccdf 100644 --- a/tests/unittests/tests-nanocoap/tests-nanocoap.c +++ b/tests/unittests/tests-nanocoap/tests-nanocoap.c @@ -13,6 +13,7 @@ */ #include <errno.h> #include <stdint.h> +#include <stdbool.h> #include "embUnit.h" @@ -45,10 +46,111 @@ static void test_nanocoap__hdr(void) TEST_ASSERT_EQUAL_STRING((char *)path, (char *)path_tmp); } +/* + * Client GET request with simple path. Test request generation. + * Request /time resource from libcoap example + */ +static void test_nanocoap__get_req(void) +{ + uint8_t buf[128]; + coap_pkt_t pkt; + uint16_t msgid = 0xABCD; + uint8_t token[2] = {0xDA, 0xEC}; + char path[] = "/time"; + size_t total_hdr_len = 6; + size_t total_opt_len = 5; + + size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); + TEST_ASSERT_EQUAL_INT(total_hdr_len, len); + + coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); + + TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code(&pkt)); + TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pkt)); + TEST_ASSERT_EQUAL_INT(total_hdr_len, coap_get_total_hdr_len(&pkt)); + TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pkt)); + TEST_ASSERT_EQUAL_INT(122, pkt.payload_len); + + len = coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/'); + TEST_ASSERT_EQUAL_INT(total_opt_len, len); + + char uri[10] = {0}; + coap_get_uri(&pkt, (uint8_t *)&uri[0]); + TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri); + + len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE); + TEST_ASSERT_EQUAL_INT(total_hdr_len + total_opt_len, len); +} + +/* + * Builds on get_req test, to test payload and Content-Format option. + */ +static void test_nanocoap__put_req(void) +{ + uint8_t buf[128]; + coap_pkt_t pkt; + uint16_t msgid = 0xABCD; + uint8_t token[2] = {0xDA, 0xEC}; + char path[] = "/value"; + size_t total_hdr_len = 6; + size_t uri_opt_len = 6; + size_t fmt_opt_len = 1; + + size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_PUT, msgid); + TEST_ASSERT_EQUAL_INT(total_hdr_len, len); + + coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); + + TEST_ASSERT_EQUAL_INT(COAP_METHOD_PUT, coap_get_code(&pkt)); + TEST_ASSERT_EQUAL_INT(122, pkt.payload_len); + + len = coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/'); + TEST_ASSERT_EQUAL_INT(uri_opt_len, len); + + len = coap_opt_add_uint(&pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_TEXT); + TEST_ASSERT_EQUAL_INT(fmt_opt_len, len); + TEST_ASSERT_EQUAL_INT(COAP_FORMAT_TEXT, coap_get_content_type(&pkt)); + + len = coap_opt_finish(&pkt, COAP_OPT_FINISH_PAYLOAD); + TEST_ASSERT_EQUAL_INT(total_hdr_len + uri_opt_len + fmt_opt_len + 1, len); + TEST_ASSERT_EQUAL_INT(0xFF, *(pkt.payload - 1)); + TEST_ASSERT_EQUAL_INT(&buf[0] + 128 - pkt.payload, pkt.payload_len); +} + +/* + * Builds on get_req test, to test path with multiple segments. + */ +static void test_nanocoap__get_multi_path(void) +{ + uint8_t buf[128]; + coap_pkt_t pkt; + uint16_t msgid = 0xABCD; + uint8_t token[2] = {0xDA, 0xEC}; + char path[] = "/ab/cde"; + size_t uri_opt_len = 7; + + size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON, + &token[0], 2, COAP_METHOD_GET, msgid); + + coap_pkt_init(&pkt, &buf[0], sizeof(buf), len); + + len = coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/'); + TEST_ASSERT_EQUAL_INT(uri_opt_len, len); + + char uri[10] = {0}; + coap_get_uri(&pkt, (uint8_t *)&uri[0]); + TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri); +} + Test *tests_nanocoap_tests(void) { EMB_UNIT_TESTFIXTURES(fixtures) { new_TestFixture(test_nanocoap__hdr), + new_TestFixture(test_nanocoap__get_req), + new_TestFixture(test_nanocoap__put_req), + new_TestFixture(test_nanocoap__get_multi_path), }; EMB_UNIT_TESTCALLER(nanocoap_tests, NULL, NULL, fixtures);