From 7033555d2978b8d4d5e16d43cfbfe1b1781c418f Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Mon, 15 Jun 2020 09:43:36 +0100 Subject: [PATCH] RFC8768: Add in support for CoAP Option Hop Limit loop detection All this is handled by libcoap with coap-client having the ability to define an initial Hop Count value for testing. No other application support is required. Incoming requests are checked for the Hop Limit option - if found, the value is decremented in the PDU before passing it up to the application. If it decrements to 0, a 5.08 response is generated. coap_add_option() checks if this is a Proxy option for a request. If so, and Hop Limit option does not exist, then the Hop Limit option is inserted into the PDU with the default value (16). coap_send() checks if it is a 5.08 response and if so, prepends the diagnostic data with the IP address of the transmitting interface. If the IP address already exists, then the PDU is dropped as the return path for the 5.08 response is looping. An alternative method for return path looping check is also used by initially setting, then testing and then decrementing the Hop Limit value as response packets pass back through the proxies. If a proxy does not copy the Hop Limit options when returning the 5.08 response, then the IP address duplication is used. Makefile.am: Hide the coap_insert_option(), coap_update_option and coap_pdu_check_resize() function from applications. README.md: Add in RFC8768 is supported by libcoap. examples/client.c: Add in the -H hoplimit option. include/coap2/pdu.h: Add in the default Hop-Limit: option COAP_OPTION_HOP_LIMIT. Re-organize the COAP_OPTION_ descriptions to include the C, U, N, R, E, I and U flags. Expose the internal coap_pdu_check_resize() function so src/net.c can use it. Define the two new internal functions coap_insert_option() and coap_update_option() and expose them so that the test suites can use them. man/coap-client.txt.in: Document the new -H hoplimit option. man/coap.txt.in: Include the new RFC8768. man/coap_pdu_setup.txt.in: Update documentation for the CoAP options. src/coap_debug.c: Add in support for printing out COAP_OPTION_HOP_LIMIT. src/net.c: Make sure that COAP_OPTION_HOP_LIMIT is not returned in an error response. Decrement received Hop-limit in handle_request() and generate 5.08 response if appropriate. Update coap_send() to prepend IP in 5.08 response. Support COAP_OPTION_HOP_LIMIT being used in the 5.08 response message. src/pdu.c: Report on CoAP options that are repeated, but not defined as repeatable. Define new (5.08) Hop Limit Reached error response. Add in new internal coap_insert_option() function. This now means that callers of coap_add_option() no longer have to have the options in the correct order. Add in new internal coap_update_option() function that updates the value of an option. Make coap_pdu_check_resize() non-static. tests/test_pdu.c: Check that options are getting inserted in the correct order when using coap_insert_option(), even though they are provided in a random order. Test the coap_update_option() function. --- Makefile.am | 3 + README.md | 3 + examples/client.c | 65 ++++++++++---- include/coap2/pdu.h | 123 ++++++++++++++++++-------- man/coap-client.txt.in | 9 +- man/coap.txt.in | 2 + man/coap_pdu_setup.txt.in | 68 ++++++++++----- src/coap_debug.c | 5 +- src/net.c | 176 ++++++++++++++++++++++++++++++++++++-- src/pdu.c | 172 ++++++++++++++++++++++++++++++++++++- tests/test_pdu.c | 157 +++++++++++++++++++++++++++++++++- 11 files changed, 694 insertions(+), 89 deletions(-) diff --git a/Makefile.am b/Makefile.am index 6a22eedaff..c1bde83bc4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -174,9 +174,11 @@ coap_dtls_session_update_mtu \ coap_dtls_shutdown \ coap_dtls_startup \ coap_epoll_ctl_mod \ +coap_insert_option \ coap_io_do_events \ coap_mfree_endpoint \ coap_packet_extract_pbuf \ +coap_pdu_check_resize \ coap_pdu_from_pbuf \ coap_session_mfree \ coap_session_new_dtls_session \ @@ -199,6 +201,7 @@ coap_tls_new_client_session \ coap_tls_new_server_session \ coap_tls_read \ coap_tls_write \ +coap_update_option \ " # This helper is called by libcoap-$(LIBCOAP_API_VERSION).{map,sym} to see if diff --git a/README.md b/README.md index dd93af8d4b..e4e696c77c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ The following RFCs are supported * RFC8132: PATCH and FETCH Methods for the Constrained Application Protocol (CoAP) * RFC8323: CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets + [No WebSockets support] + +* RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option There is (D)TLS support for the following libraries diff --git a/examples/client.c b/examples/client.c index f5b99859ee..95571bd3c9 100644 --- a/examples/client.c +++ b/examples/client.c @@ -251,15 +251,16 @@ clear_obs(coap_context_t *ctx, coap_session_t *session) { for (option = optlist; option; option = option->next ) { switch (option->number) { - case COAP_OPTION_URI_PORT : - case COAP_OPTION_URI_PATH : - case COAP_OPTION_URI_QUERY : + case COAP_OPTION_URI_PORT : /* 7 */ + case COAP_OPTION_URI_PATH : /* 11 */ + case COAP_OPTION_URI_QUERY : /* 15 */ + case COAP_OPTION_HOP_LIMIT : /* 16 */ if (!coap_add_option(pdu, option->number, option->length, option->data)) { goto error; } break; - default: + default: ; } } @@ -453,13 +454,14 @@ message_handler(struct coap_context_t *ctx, /* create pdu with request for next block */ pdu = coap_new_request(ctx, session, method, NULL, NULL, 0); /* first, create bare PDU w/o any option */ if ( pdu ) { - /* add URI components from optlist */ + /* add URI components from optlist in ascending order */ for (option = optlist; option; option = option->next ) { switch (option->number) { - case COAP_OPTION_URI_HOST : - case COAP_OPTION_URI_PORT : - case COAP_OPTION_URI_PATH : - case COAP_OPTION_URI_QUERY : + case COAP_OPTION_URI_HOST : /* 3 */ + case COAP_OPTION_URI_PORT : /* 7 */ + case COAP_OPTION_URI_PATH : /* 11 */ + case COAP_OPTION_URI_QUERY : /* 15 */ + case COAP_OPTION_HOP_LIMIT : /* 16 */ coap_add_option(pdu, option->number, option->length, option->data); break; @@ -550,11 +552,12 @@ message_handler(struct coap_context_t *ctx, /* add URI components from optlist */ for (option = optlist; option; option = option->next ) { switch (option->number) { - case COAP_OPTION_URI_HOST : - case COAP_OPTION_URI_PORT : - case COAP_OPTION_URI_PATH : - case COAP_OPTION_CONTENT_FORMAT : - case COAP_OPTION_URI_QUERY : + case COAP_OPTION_URI_HOST : /* 3 */ + case COAP_OPTION_URI_PORT : /* 7 */ + case COAP_OPTION_URI_PATH : /* 11 */ + case COAP_OPTION_CONTENT_FORMAT : /* 12 */ + case COAP_OPTION_URI_QUERY : /* 15 */ + case COAP_OPTION_HOP_LIMIT : /* 16 */ coap_add_option(pdu, option->number, option->length, option->data); break; @@ -641,8 +644,8 @@ usage( const char *program, const char *version) { "%s\n\n" "Usage: %s [-a addr] [-b [num,]size] [-e text] [-f file] [-l loss]\n" "\t\t[-m method] [-o file] [-p port] [-r] [-s duration] [-t type]\n" - "\t\t[-v num] [-A type] [-B seconds] [-K interval] [-N] [-O num,text]\n" - "\t\t[-P addr[:port]] [-T token] [-U]\n" + "\t\t[-v num] [-A type] [-B seconds] [-H hoplimit] [-K interval] [-N]\n" + "\t\t[-O num,text] [-P addr[:port]] [-T token] [-U]\n" "\t\t[[-h match_hint_file] [-k key] [-u user]]\n" "\t\t[[-c certfile] [-j keyfile] [-C cafile] [-J pkcs11_pin]\n" "\t\t[-R root_cafile] [-S match_pki_sni_file]] URI\n" @@ -674,6 +677,9 @@ usage( const char *program, const char *version) { "\t-A type\t\tAccepted media type\n" "\t-B seconds\tBreak operation after waiting given seconds\n" "\t \t\t(default is %d)\n" + "\t-H hoplimit\tSet the Hop Limit count to hoplimit for proxies. Must\n" + "\t \t\thave a value between 1 and 255 inclusive.\n" + "\t \t\tDefault is '16'\n" "\t-K interval\tsend a ping after interval seconds of inactivity\n" "\t-N \t\tSend NON-confirmable message\n" "\t-O num,text\tAdd option num with contents text to request. If the\n" @@ -722,7 +728,8 @@ usage( const char *program, const char *version) { "\t \t\t'trusted' for the verification.\n" "\t \t\tAlternatively, this can point to a directory containing\n" "\t \t\ta set of CA PEM files\n" - "\n" + ); + fprintf( stderr, "Examples:\n" "\tcoap-client -m get coap://[::1]/\n" "\tcoap-client -m get coap://[::1]/.well-known/core\n" @@ -788,6 +795,24 @@ cmdline_content_type(char *arg, uint16_t key) { } } +static int +cmdline_hop_limit(char *arg) { + coap_optlist_t *node; + uint32_t value; + uint8_t buf[4]; + + value = strtol(arg, NULL, 10); + if (value < 1 || value > 255) { + return 0; + } + node = coap_new_optlist(COAP_OPTION_HOP_LIMIT, coap_encode_var_safe(buf, sizeof(buf), value), buf); + if (node) { + coap_insert_optlist(&optlist, node); + } + return 1; +} + + static uint16_t get_default_port(const coap_uri_t *u) { return coap_uri_scheme_is_secure(u) ? COAPS_DEFAULT_PORT : COAP_DEFAULT_PORT; @@ -1533,7 +1558,7 @@ main(int argc, char **argv) { struct sigaction sa; #endif - while ((opt = getopt(argc, argv, "a:b:c:e:f:h:j:k:l:m:o:p:rs:t:u:v:A:B:C:J:K:NO:P:R:T:U")) != -1) { + while ((opt = getopt(argc, argv, "a:b:c:e:f:h:j:k:l:m:o:p:rs:t:u:v:A:B:C:J:K:H:NO:P:R:T:U")) != -1) { switch (opt) { case 'a': strncpy(node_str, optarg, NI_MAXHOST - 1); @@ -1643,6 +1668,10 @@ main(int argc, char **argv) { exit(1); } break; + case 'H': + if (!cmdline_hop_limit(optarg)) + fprintf(stderr, "Hop Limit has to be > 0 and < 256\n"); + break; default: usage( argv[0], LIBCOAP_PACKAGE_VERSION ); exit( 1 ); diff --git a/include/coap2/pdu.h b/include/coap2/pdu.h index 2487bc3178..6f808e50aa 100644 --- a/include/coap2/pdu.h +++ b/include/coap2/pdu.h @@ -32,6 +32,10 @@ struct coap_session_t; #define COAP_DEFAULT_MTU 1152 #endif /* COAP_DEFAULT_MTU */ +#ifndef COAP_DEFAULT_HOP_LIMIT +#define COAP_DEFAULT_HOP_LIMIT 16 +#endif /* COAP_DEFAULT_HOP_LIMIT */ + /* TCP Message format constants, do not modify */ #define COAP_MESSAGE_SIZE_OFFSET_TCP8 13 #define COAP_MESSAGE_SIZE_OFFSET_TCP16 269 /* 13 + 256 */ @@ -87,41 +91,60 @@ typedef enum coap_request_t { } coap_request_t; /* - * CoAP option types (be sure to update coap_option_check_critical() when - * adding options + * CoAP option types (be sure to update coap_option_check_critical() and + * coap_add_option() when adding options */ -#define COAP_OPTION_IF_MATCH 1 /* C, opaque, 0-8 B, (none) */ -#define COAP_OPTION_URI_HOST 3 /* C, String, 1-255 B, destination address */ -#define COAP_OPTION_ETAG 4 /* E, opaque, 1-8 B, (none) */ -#define COAP_OPTION_IF_NONE_MATCH 5 /* empty, 0 B, (none) */ -#define COAP_OPTION_URI_PORT 7 /* C, uint, 0-2 B, destination port */ -#define COAP_OPTION_LOCATION_PATH 8 /* E, String, 0-255 B, - */ -#define COAP_OPTION_URI_PATH 11 /* C, String, 0-255 B, (none) */ -#define COAP_OPTION_CONTENT_FORMAT 12 /* E, uint, 0-2 B, (none) */ -#define COAP_OPTION_CONTENT_TYPE COAP_OPTION_CONTENT_FORMAT -#define COAP_OPTION_MAXAGE 14 /* E, uint, 0--4 B, 60 Seconds */ -#define COAP_OPTION_URI_QUERY 15 /* C, String, 1-255 B, (none) */ -#define COAP_OPTION_ACCEPT 17 /* C, uint, 0-2 B, (none) */ -#define COAP_OPTION_LOCATION_QUERY 20 /* E, String, 0-255 B, (none) */ -#define COAP_OPTION_SIZE2 28 /* E, uint, 0-4 B, (none) */ -#define COAP_OPTION_PROXY_URI 35 /* C, String, 1-1034 B, (none) */ -#define COAP_OPTION_PROXY_SCHEME 39 /* C, String, 1-255 B, (none) */ -#define COAP_OPTION_SIZE1 60 /* E, uint, 0-4 B, (none) */ - -/* option types from RFC 7641 */ - -#define COAP_OPTION_OBSERVE 6 /* E, empty/uint, 0 B/0-3 B, (none) */ -#define COAP_OPTION_SUBSCRIPTION COAP_OPTION_OBSERVE - -/* selected option types from RFC 7959 */ - -#define COAP_OPTION_BLOCK2 23 /* C, uint, 0--3 B, (none) */ -#define COAP_OPTION_BLOCK1 27 /* C, uint, 0--3 B, (none) */ - -/* selected option types from RFC 7967 */ +/* + * The C, U, and N flags indicate the properties + * Critical, Unsafe, and NoCacheKey, respectively. + * If U is set, then N has no meaning as per + * https://tools.ietf.org/html/rfc7252#section-5.10 + * and is set to a -. + * + * Separately, R is for the options that can be repeated + * + * The least significant byte of the option is set as followed + * as per https://tools.ietf.org/html/rfc7252#section-5.4.6 + * + * 0 1 2 3 4 5 6 7 + * --+---+---+---+---+---+---+---+ + * | NoCacheKey| U | C | + * --+---+---+---+---+---+---+---+ + * + * https://tools.ietf.org/html/rfc8613#section-4 goes on to define E, I and U + * properties Encrypted and Integrity Protected, Integrity Protected Only and + * Unprotected respectively. Integretity Protected Only is not currently used. + * + * An Option is tagged with CUNREIU with any of the letters replaced with _ if + * not set, or - for N if U is set (see above) for aiding understanding of the + * Option. + */ -#define COAP_OPTION_NORESPONSE 258 /* N, uint, 0--1 B, 0 */ +#define COAP_OPTION_IF_MATCH 1 /* C__RE__, opaque, 0-8 B, RFC7252 */ +#define COAP_OPTION_URI_HOST 3 /* CU-___U, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_ETAG 4 /* ___RE__, opaque, 1-8 B, RFC7252 */ +#define COAP_OPTION_IF_NONE_MATCH 5 /* C___E__, empty, 0 B, RFC7252 */ +#define COAP_OPTION_OBSERVE 6 /* _U-_E_U, empty/uint,0/0-3 B, RFC7641 */ +#define COAP_OPTION_URI_PORT 7 /* CU-___U, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_LOCATION_PATH 8 /* ___RE__, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_OSCORE 9 /* C_____U, *, 0-255 B, RFC8613 */ +#define COAP_OPTION_URI_PATH 11 /* CU-RE__, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_CONTENT_FORMAT 12 /* ____E__, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_CONTENT_TYPE COAP_OPTION_CONTENT_FORMAT +/* COAP_OPTION_MAXAGE default 60 seconds if not set */ +#define COAP_OPTION_MAXAGE 14 /* _U-_E_U, uint, 0-4 B, RFC7252 */ +#define COAP_OPTION_URI_QUERY 15 /* CU-RE__, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_HOP_LIMIT 16 /* ______U, uint, 1 B, RFC8768 */ +#define COAP_OPTION_ACCEPT 17 /* C___E__, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_LOCATION_QUERY 20 /* ___RE__, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_BLOCK2 23 /* CU-_E_U, uint, 0-3 B, RFC7959 */ +#define COAP_OPTION_BLOCK1 27 /* CU-_E_U, uint, 0-3 B, RFC7959 */ +#define COAP_OPTION_SIZE2 28 /* __N_E_U, uint, 0-4 B, RFC7959 */ +#define COAP_OPTION_PROXY_URI 35 /* CU-___U, String, 1-1034 B, RFC7252 */ +#define COAP_OPTION_PROXY_SCHEME 39 /* CU-___U, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_SIZE1 60 /* __N_E_U, uint, 0-4 B, RFC7252 */ +#define COAP_OPTION_NORESPONSE 258 /* _U-_E_U, uint, 0-1 B, RFC7967 */ #define COAP_MAX_OPT 65535 /**< the highest option number we know */ @@ -273,7 +296,7 @@ typedef struct coap_pdu_t { uint8_t type; /**< message type */ uint8_t code; /**< request method (value 1--31) or response code (value 64-255) */ uint8_t max_hdr_size; /**< space reserved for protocol-specific header */ - uint8_t hdr_size; /**< actaul size used for protocol-specific header */ + uint8_t hdr_size; /**< actual size used for protocol-specific header */ uint8_t token_length; /**< length of Token */ uint16_t tid; /**< transaction id, if any, in regular host byte order */ uint16_t max_delta; /**< highest option number */ @@ -359,6 +382,19 @@ coap_pdu_init(uint8_t type, uint8_t code, uint16_t tid, size_t size); */ int coap_pdu_resize(coap_pdu_t *pdu, size_t new_size); +/** + * Dynamically grows the size of @p pdu to @p new_size if needed. The new size + * must not exceed the PDU's configure maximum size. On success, this + * function returns 1, otherwise 0. + * + * Internal use only. + * + * @param pdu The PDU to resize. + * @param new_size The new size in bytes. + * @return 1 if the operation succeeded, 0 otherwise. + */ +int coap_pdu_check_resize(coap_pdu_t *pdu, size_t new_size); + /** * Clears any contents from @p pdu and resets @c used_size, * and @c data pointers. @c max_size is set to @p size, any @@ -489,6 +525,25 @@ uint8_t *coap_add_option_later(coap_pdu_t *pdu, uint16_t type, size_t len); + +/** + * Inserts option of given type in the @p pdu with the appropriate data. + * The option will be inserted in the appropriate place in the options in the pdu. + * + * Internal use only. + */ +size_t coap_insert_option(coap_pdu_t *pdu, uint16_t type, + size_t len, const uint8_t *data); + +/** + * Updates existing first option of given type in the @p pdu with the new data. + * + * Internal use only. + */ +int coap_update_option(coap_pdu_t *pdu, + uint16_t type, + size_t len, + const uint8_t *data); /** * Adds given data to the pdu that is passed as first parameter. Note that the * PDU's data is destroyed by coap_add_option(). coap_add_data() must be called @@ -500,7 +555,7 @@ int coap_add_data(coap_pdu_t *pdu, /** * Adds given data to the pdu that is passed as first parameter but does not - * copyt it. Note that the PDU's data is destroyed by coap_add_option(). + * copy it. Note that the PDU's data is destroyed by coap_add_option(). * coap_add_data() must be have been called once for this PDU, otherwise the * result is undefined. * The actual data must be copied at the returned location. diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in index 9f1dcc07fe..ffa18a63b4 100644 --- a/man/coap-client.txt.in +++ b/man/coap-client.txt.in @@ -16,8 +16,9 @@ SYNOPSIS -------- *coap-client* [*-a* addr] [*-b* [num,]size] [*-e* text] [*-f* file] [*-l* loss] [*-m* method] [*-o* file] [*-p* port] [*-r*] [*-s duration*] - [*-t* type] [*-v* num] [*-A* type] [*-B* seconds] [*-K* interval] - [*-N*] [*-O* num,text] [*-P* addr[:port]] [*-T* token] [*-U*] + [*-t* type] [*-v* num] [*-A* type] [*-B* seconds] + [*-H* hoplimit] [*-K* interval] [*-N*] [*-O* num,text] + [*-P* addr[:port]] [*-T* token] [*-U*] [[*-h* match_hint_file] [*-k* key] [*-u* user]] [[*-c* certfile] [*-j* keyfile] [*-C* cafile] [*-J* pkcs11_pin] [*-R* root_cafile]] URI @@ -109,6 +110,10 @@ OPTIONS - General *-B* seconds:: Break operation after waiting given seconds (default is 90). +*-H* hoplimit:: + Set the Hop Limit count to hoplimit for proxies. Must have a value between + 1 and 255 inclusive. Default is '16'. + *-K* interval:: Send a ping after interval seconds of inactivity. If not specified (or 0), keep-alive is disabled (default). diff --git a/man/coap.txt.in b/man/coap.txt.in index 34f33179a9..cb31b87a60 100644 --- a/man/coap.txt.in +++ b/man/coap.txt.in @@ -61,6 +61,8 @@ See "RFC8323: CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets" +"RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option" + for further information. BUGS diff --git a/man/coap_pdu_setup.txt.in b/man/coap_pdu_setup.txt.in index d35422795a..159094845c 100644 --- a/man/coap_pdu_setup.txt.in +++ b/man/coap_pdu_setup.txt.in @@ -149,26 +149,54 @@ of the data of the option, and _data_ points to the content of the option. The following is the current list of options with their numeric value ---- -#define COAP_OPTION_IF_MATCH 1 /* opaque, 0-8 B */ -#define COAP_OPTION_URI_HOST 3 /* String, 1-255 B */ -#define COAP_OPTION_ETAG 4 /* opaque, 1-8 B */ -#define COAP_OPTION_IF_NONE_MATCH 5 /* empty, 0 B */ -#define COAP_OPTION_OBSERVE 6 /* empty/uint, 0 B/0-3 B */ -#define COAP_OPTION_URI_PORT 7 /* uint, 0-2 B */ -#define COAP_OPTION_LOCATION_PATH 8 /* String, 0-255 B */ -#define COAP_OPTION_URI_PATH 11 /* String, 0-255 B */ -#define COAP_OPTION_CONTENT_FORMAT 12 /* uint, 0-2 B */ -#define COAP_OPTION_MAXAGE 14 /* uint, 0-4 B, default 60 Seconds */ -#define COAP_OPTION_URI_QUERY 15 /* String, 1-255 B */ -#define COAP_OPTION_ACCEPT 17 /* uint, 0-2 B */ -#define COAP_OPTION_LOCATION_QUERY 20 /* String, 0-255 B */ -#define COAP_OPTION_BLOCK2 23 /* uint, 0-3 B */ -#define COAP_OPTION_BLOCK1 27 /* uint, 0-3 B */ -#define COAP_OPTION_SIZE2 28 /* uint, 0-4 B */ -#define COAP_OPTION_PROXY_URI 35 /* String, 1-1034 B */ -#define COAP_OPTION_PROXY_SCHEME 39 /* String, 1-255 B */ -#define COAP_OPTION_SIZE1 60 /* uint, 0-4 B */ -#define COAP_OPTION_NORESPONSE 258 /* uint, 0-1 B */ +/* + * The C, U, and N flags indicate the properties + * Critical, Unsafe, and NoCacheKey, respectively. + * If U is set, then N has no meaning as per + * https://tools.ietf.org/html/rfc7252#section-5.10 + * and is set to a -. + * Separately, R is for the options that can be repeated + * + * The least significant byte of the option is set as followed + * as per https://tools.ietf.org/html/rfc7252#section-5.4.6 + * + * 0 1 2 3 4 5 6 7 + * --+---+---+---+---+---+---+---+ + * | NoCacheKey| U | C | + * --+---+---+---+---+---+---+---+ + * + * https://tools.ietf.org/html/rfc8613#section-4 goes on to define E, I and U + * properties Encrypted and Integrity Protected, Integrity Protected Only and + * Unprotected respectively. Integretity Protected Only is not currently used. + * + * An Option is tagged with CUNREIU with any of the letters replaced with _ if + * not set, or - for N if U is set (see above) for aiding understanding of the + * Option. + */ + +#define COAP_OPTION_IF_MATCH 1 /* C__RE__, opaque, 0-8 B, RFC7252 */ +#define COAP_OPTION_URI_HOST 3 /* CU-___U, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_ETAG 4 /* ___RE__, opaque, 1-8 B, RFC7252 */ +#define COAP_OPTION_IF_NONE_MATCH 5 /* C___E__, empty, 0 B, RFC7252 */ +#define COAP_OPTION_OBSERVE 6 /* _U-_E_U, empty/uint, 0 B/0-3 B, RFC7641 */ +#define COAP_OPTION_URI_PORT 7 /* CU-___U, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_LOCATION_PATH 8 /* ___RE__, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_OSCORE 9 /* C_____U, *, 0-255 B, RFC8613 */ +#define COAP_OPTION_URI_PATH 11 /* CU-RE__, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_CONTENT_FORMAT 12 /* ____E__, uint, 0-2 B, RFC7252 */ +/* COAP_OPTION_MAXAGE default 60 seconds if not set */ +#define COAP_OPTION_MAXAGE 14 /* _U-_E_U, uint, 0-4 B, RFC7252 */ +#define COAP_OPTION_URI_QUERY 15 /* CU-RE__, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_HOP_LIMIT 16 /* ______U, uint, 1 B, RFC8768 */ +#define COAP_OPTION_ACCEPT 17 /* C___E__, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_LOCATION_QUERY 20 /* ___RE__, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_BLOCK2 23 /* CU-_E_U, uint, 0-3 B, RFC7959 */ +#define COAP_OPTION_BLOCK1 27 /* CU-_E_U, uint, 0-3 B, RFC7959 */ +#define COAP_OPTION_SIZE2 28 /* __N_E_U, uint, 0-4 B, RFC7959 */ +#define COAP_OPTION_PROXY_URI 35 /* CU-___U, String, 1-1034 B, RFC7252 */ +#define COAP_OPTION_PROXY_SCHEME 39 /* CU-___U, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_SIZE1 60 /* __N_E_U, uint, 0-4 B, RFC7252 */ +#define COAP_OPTION_NORESPONSE 258 /* _U-_E_U, uint, 0-1 B, RFC7967 */ ---- See FURTHER INFORMATION as to how to get the latest list. diff --git a/src/coap_debug.c b/src/coap_debug.c index 547ced1a1b..95faaeb96b 100644 --- a/src/coap_debug.c +++ b/src/coap_debug.c @@ -316,14 +316,15 @@ msg_option_string(uint8_t code, uint16_t option_type) { { COAP_OPTION_CONTENT_FORMAT, "Content-Format" }, { COAP_OPTION_MAXAGE, "Max-Age" }, { COAP_OPTION_URI_QUERY, "Uri-Query" }, + { COAP_OPTION_HOP_LIMIT, "Hop-Limit" }, { COAP_OPTION_ACCEPT, "Accept" }, { COAP_OPTION_LOCATION_QUERY, "Location-Query" }, { COAP_OPTION_BLOCK2, "Block2" }, { COAP_OPTION_BLOCK1, "Block1" }, + { COAP_OPTION_SIZE2, "Size2" }, { COAP_OPTION_PROXY_URI, "Proxy-Uri" }, { COAP_OPTION_PROXY_SCHEME, "Proxy-Scheme" }, { COAP_OPTION_SIZE1, "Size1" }, - { COAP_OPTION_SIZE2, "Size2" }, { COAP_OPTION_NORESPONSE, "No-Response" } }; @@ -571,6 +572,7 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { case COAP_OPTION_OBSERVE: case COAP_OPTION_SIZE1: case COAP_OPTION_SIZE2: + case COAP_OPTION_HOP_LIMIT: /* show values as unsigned decimal value */ buf_len = snprintf((char *)buf, sizeof(buf), "%u", coap_decode_var_bytes(coap_opt_value(option), @@ -584,6 +586,7 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { opt_iter.type == COAP_OPTION_URI_HOST || opt_iter.type == COAP_OPTION_LOCATION_PATH || opt_iter.type == COAP_OPTION_LOCATION_QUERY || + opt_iter.type == COAP_OPTION_PROXY_SCHEME || opt_iter.type == COAP_OPTION_URI_QUERY) { encode = 0; } else { diff --git a/src/net.c b/src/net.c index 5a3fbf1a38..262c7397da 100644 --- a/src/net.c +++ b/src/net.c @@ -51,6 +51,10 @@ #include #endif +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 40 +#endif + #ifndef min #define min(a,b) ((a) < (b) ? (a) : (b)) #endif @@ -999,6 +1003,110 @@ coap_send(coap_session_t *session, coap_pdu_t *pdu) { uint8_t r; ssize_t bytes_written; + if (pdu->code == COAP_RESPONSE_CODE(508)) { + /* + * Need to prepend our IP identifier to the data as per + * https://www.rfc-editor.org/rfc/rfc8768.html#section-4 + */ + char addr_str[INET6_ADDRSTRLEN + 8 + 1]; + coap_opt_iterator_t opt_iter; + coap_opt_t *opt; + size_t hop_limit; + + addr_str[sizeof(addr_str)-1] = '\000'; + if (coap_print_addr(&session->addr_info.local, (uint8_t*)addr_str, + sizeof(addr_str) - 1)) { + char *cp; + int len; + + if (addr_str[0] == '[') { + cp = strchr(addr_str, ']'); + if (cp) *cp = '\000'; + if (memcmp(&addr_str[1], "::ffff:", 7) == 0) { + /* IPv4 embedded into IPv6 */ + cp = &addr_str[8]; + } + else { + cp = &addr_str[1]; + } + } + else { + cp = strchr(addr_str, ':'); + if (cp) *cp = '\000'; + cp = addr_str; + } + len = strlen(cp); + + /* See if Hop Limit option is being used in return path */ + opt = coap_check_option(pdu, COAP_OPTION_HOP_LIMIT, &opt_iter); + if (opt) { + uint8_t buf[4]; + + hop_limit = + coap_decode_var_bytes (coap_opt_value (opt), coap_opt_length (opt)); + if (hop_limit == 1) { + coap_log(LOG_WARNING, "Proxy loop detected '%s'\n", + (char*)pdu->data); + coap_delete_pdu(pdu); + return (coap_tid_t)COAP_DROPPED_RESPONSE; + } + else if (hop_limit < 1 || hop_limit > 255) { + /* Something is bad - need to drop this pdu (TODO or delete option) */ + coap_log(LOG_WARNING, "Proxy return has bad hop limit count '%zu'\n", + hop_limit); + coap_delete_pdu(pdu); + return (coap_tid_t)COAP_DROPPED_RESPONSE; + } + hop_limit--; + coap_update_option(pdu, COAP_OPTION_HOP_LIMIT, + coap_encode_var_safe8(buf, sizeof(buf), hop_limit), + buf); + } + + /* Need to check that we are not seeing this proxy in the return loop */ + if (pdu->data && opt == NULL) { + if (pdu->used_size + 1 <= pdu->max_size) { + char *a_match; + size_t data_len = pdu->used_size - (pdu->data - pdu->token); + pdu->data[data_len] = '\000'; + a_match = strstr((char*)pdu->data, cp); + if (a_match && (a_match == (char*)pdu->data || a_match[-1] == ' ') && + ((size_t)(a_match - (char*)pdu->data + len) == data_len || + a_match[len] == ' ')) { + coap_log(LOG_WARNING, "Proxy loop detected '%s'\n", + (char*)pdu->data); + coap_delete_pdu(pdu); + return (coap_tid_t)COAP_DROPPED_RESPONSE; + } + } + } + if (pdu->used_size + len + 1 <= pdu->max_size) { + size_t old_size = pdu->used_size; + if (coap_pdu_resize(pdu, pdu->used_size + len + 1)) { + if (pdu->data == NULL) { + /* + * Set Hop Limit to max for return path. If this libcoap is in + * a proxy loop path, it will always decrement hop limit in code + * above and hence timeout / drop the response as appropriate + */ + hop_limit = 255; + coap_insert_option(pdu, COAP_OPTION_HOP_LIMIT, 1, + (uint8_t *)&hop_limit); + coap_add_data(pdu, len, (uint8_t*)cp); + } + else { + /* prepend with space separator, leaving hop limit "as is" */ + memmove(pdu->data + len + 1, pdu->data, + old_size - (pdu->data - pdu->token)); + memcpy(pdu->data, cp, len); + pdu->data[len] = ' '; + pdu->used_size += len + 1; + } + } + } + } + } + if (!coap_pdu_encode_header(pdu, session->proto)) { goto error; } @@ -1801,11 +1909,22 @@ coap_new_error_response(coap_pdu_t *request, unsigned char code, uint16_t opt_type = 0; /* used for calculating delta-storage */ #if COAP_ERROR_PHRASE_LENGTH > 0 - const char *phrase = coap_response_phrase(code); + const char *phrase; + if (code != COAP_RESPONSE_CODE(508)) { + phrase = coap_response_phrase(code); - /* Need some more space for the error phrase and payload start marker */ - if (phrase) - size += strlen(phrase) + 1; + /* Need some more space for the error phrase and payload start marker */ + if (phrase) + size += strlen(phrase) + 1; + } + else { + /* + * Need space for IP for 5.08 response which is filled in in coap_send() + * https://www.rfc-editor.org/rfc/rfc8768.html#section-4 + */ + phrase = NULL; + size += INET6_ADDRSTRLEN; + } #endif assert(request); @@ -1819,6 +1938,7 @@ coap_new_error_response(coap_pdu_t *request, unsigned char code, * request. We always need the Token, for 4.02 the unknown critical * options must be included as well. */ coap_option_clrb(opts, COAP_OPTION_CONTENT_TYPE); /* we do not want this */ + coap_option_clrb(opts, COAP_OPTION_HOP_LIMIT); /* we do not want this */ coap_option_iterator_init(request, &opt_iter, opts); @@ -2161,15 +2281,46 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu coap_method_handler_t h = NULL; coap_pdu_t *response = NULL; coap_opt_filter_t opt_filter; - coap_resource_t *resource; + coap_resource_t *resource = NULL; /* The respond field indicates whether a response must be treated * specially due to a No-Response option that declares disinterest * or interest in a specific response class. DEFAULT indicates that * No-Response has not been specified. */ enum respond_t respond = RESPONSE_DEFAULT; + coap_opt_iterator_t opt_iter; + coap_opt_t *opt; + int skip_hop_limit_check = 0; + int resp; coap_option_filter_clear(opt_filter); + /* If !skip_hop_limit_check test placeholder for Proxy-Uri: code */ + if (!skip_hop_limit_check) { + opt = coap_check_option(pdu, COAP_OPTION_HOP_LIMIT, &opt_iter); + if (opt) { + size_t hop_limit; + uint8_t buf[4]; + + hop_limit = + coap_decode_var_bytes (coap_opt_value (opt), coap_opt_length (opt)); + if (hop_limit == 1) { + /* coap_send() will fill in the IP address for us */ + resp = 508; + goto fail_response; + } + else if (hop_limit < 1 || hop_limit > 255) { + /* Need to return a 4.00 RFC8768 Section 3 */ + coap_log(LOG_INFO, "Invalid Hop Limit\n"); + resp = 400; + goto fail_response; + } + hop_limit--; + coap_update_option(pdu, COAP_OPTION_HOP_LIMIT, + coap_encode_var_safe8(buf, sizeof(buf), hop_limit), + buf); + } + } + /* try to find the resource from the request URI */ coap_string_t *uri_path = coap_get_uri_path(pdu); if (!uri_path) @@ -2285,7 +2436,6 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu if response == NULL */ if (coap_add_token(response, pdu->token_length, pdu->token)) { coap_binary_t token = { pdu->token_length, pdu->token }; - coap_opt_iterator_t opt_iter; coap_opt_t *observe = NULL; int observe_action = COAP_OBSERVE_CANCEL; coap_string_t *query = coap_get_query(pdu); @@ -2391,6 +2541,17 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu assert(response == NULL); coap_delete_string(uri_path); + return; + +fail_response: + response = + coap_new_error_response(pdu, COAP_RESPONSE_CODE(resp), + opt_filter); + if (response) { + if (coap_send(session, response) == COAP_INVALID_TID) + coap_log(LOG_WARNING, "cannot send response for transaction %u\n", + pdu->tid); + } } static void @@ -2462,9 +2623,6 @@ coap_dispatch(coap_context_t *context, coap_session_t *session, int is_ping_rst; if (LOG_DEBUG <= coap_get_log_level()) { -#ifndef INET6_ADDRSTRLEN -#define INET6_ADDRSTRLEN 40 -#endif /* FIXME: get debug to work again ** unsigned char addr[INET6_ADDRSTRLEN+8], localaddr[INET6_ADDRSTRLEN+8]; if (coap_print_addr(remote, addr, INET6_ADDRSTRLEN+8) && diff --git a/src/pdu.c b/src/pdu.c index 4fe88352dd..181f4818c7 100644 --- a/src/pdu.c +++ b/src/pdu.c @@ -170,7 +170,7 @@ coap_pdu_resize(coap_pdu_t *pdu, size_t new_size) { return 1; } -static int +int coap_pdu_check_resize(coap_pdu_t *pdu, size_t size) { if (size > pdu->alloc_size) { size_t new_size = max(256, pdu->alloc_size * 2); @@ -210,19 +210,182 @@ coap_add_token(coap_pdu_t *pdu, size_t len, const uint8_t *data) { return 1; } +size_t +coap_insert_option(coap_pdu_t *pdu, uint16_t type, size_t len, + const uint8_t *data) { + coap_opt_iterator_t opt_iter; + coap_opt_t *option; + uint16_t prev_type = 0; + size_t shift; + size_t opt_delta; + coap_option_t decode; + size_t shrink = 0; + + if (type >= pdu->max_delta) + return coap_add_option(pdu, type, len, data); + + /* Need to locate where in current options to insert this one */ + coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL); + while ((option = coap_option_next(&opt_iter))) { + if (opt_iter.type > type) { + /* Found where to insert */ + break; + } + prev_type = opt_iter.type; + } + assert(option != NULL); + /* size of option inc header to insert */ + shift = coap_opt_encode_size(type - prev_type, len); + + /* size of next option (header may shrink in size as delta changes */ + if (!coap_opt_parse(option, opt_iter.length + 5, &decode)) + return 0; + opt_delta = opt_iter.type - type; + + if (!coap_pdu_check_resize(pdu, + pdu->used_size + shift - shrink)) + return 0; + + /* Possible a re-size took place with a realloc() */ + /* Need to locate where in current options to insert this one */ + coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL); + while ((option = coap_option_next(&opt_iter))) { + if (opt_iter.type > type) { + /* Found where to insert */ + break; + } + } + assert(option != NULL); + + if (decode.delta <= 12) { + /* can simply patch in the new delta of next option */ + option[0] = (option[0] & 0x0f) + (opt_delta << 4); + } + else if (decode.delta <= 269 && opt_delta <= 12) { + /* option header is going to shrink by one byte */ + option[1] = (option[0] & 0x0f) + (opt_delta << 4); + shrink = 1; + } + else if (decode.delta <= 269 && opt_delta <= 269) { + /* can simply patch in the new delta of next option */ + option[1] = opt_delta - 13; + } + else if (opt_delta <= 12) { + /* option header is going to shrink by two bytes */ + option[2] = (option[0] & 0x0f) + (opt_delta << 4); + shrink = 2; + } + else if (opt_delta <= 269) { + /* option header is going to shrink by one bytes */ + option[1] = (option[0] & 0x0f) + 0xd0; + option[2] = opt_delta - 13; + shrink = 1; + } + else { + /* can simply patch in the new delta of next option */ + option[1] = (opt_delta - 269) >> 8; + option[2] = (opt_delta - 269) & 0xff; + } + + memmove(&option[shift], &option[shrink], + pdu->used_size - (option - pdu->token) - shrink); + if (!coap_opt_encode(option, pdu->alloc_size - pdu->used_size, + type - prev_type, data, len)) + return 0; + + pdu->used_size += shift - shrink; + if (pdu->data) + pdu->data += shift - shrink; + return shift; +} + +int +coap_update_option(coap_pdu_t *pdu, uint16_t type, size_t len, + const uint8_t *data) { + coap_opt_iterator_t opt_iter; + coap_opt_t *option; + coap_option_t decode; + size_t new_length = 0; + size_t old_length = 0; + + option = coap_check_option(pdu, type, &opt_iter); + if (!option) + return coap_insert_option(pdu, type, len, data); + + old_length = coap_opt_parse(option, (size_t)-1, &decode); + if (old_length == 0) + return 0; + new_length = coap_opt_encode_size(decode.delta, len); + + if (new_length > old_length) { + if (!coap_pdu_check_resize(pdu, + pdu->used_size + new_length - old_length)) + return 0; + /* Possible a re-size took place with a realloc() */ + option = coap_check_option(pdu, type, &opt_iter); + } + + if (new_length != old_length) + memmove(&option[new_length], &option[old_length], + pdu->used_size - (option - pdu->token) - old_length); + + if (!coap_opt_encode(option, new_length, + decode.delta, data, len)) + return 0; + + pdu->used_size += new_length - old_length; + if (pdu->data) + pdu->data += new_length - old_length; + return 1; +} + /* FIXME: de-duplicate code with coap_add_option_later */ size_t -coap_add_option(coap_pdu_t *pdu, uint16_t type, size_t len, const uint8_t *data) { +coap_add_option(coap_pdu_t *pdu, uint16_t type, size_t len, + const uint8_t *data) { size_t optsize; coap_opt_t *opt; assert(pdu); pdu->data = NULL; + if (type == pdu->max_delta) { + /* Validate that the option is repeatable */ + switch (type) { + /* Ignore list of genuine repeatable */ + case COAP_OPTION_IF_MATCH: + case COAP_OPTION_ETAG: + case COAP_OPTION_LOCATION_PATH: + case COAP_OPTION_URI_PATH: + case COAP_OPTION_URI_QUERY: + case COAP_OPTION_LOCATION_QUERY: + break; + default: + coap_log(LOG_INFO, "Option %d is not defined as repeatable\n", type); + /* Accepting it after warning as there may be user defineable options */ + break; + } + } + + if (COAP_PDU_IS_REQUEST(pdu) && + (type == COAP_OPTION_PROXY_URI || type == COAP_OPTION_PROXY_SCHEME)) { + /* + * Need to check whether there is a hop-limit option. If not, it needs + * to be inserted by default (RFC 8768). + */ + coap_opt_iterator_t opt_iter; + + if (coap_check_option(pdu, COAP_OPTION_HOP_LIMIT, &opt_iter) == NULL) { + size_t hop_limit = COAP_OPTION_HOP_LIMIT; + + coap_insert_option(pdu, COAP_OPTION_HOP_LIMIT, 1, (uint8_t *)&hop_limit); + } + } + if (type < pdu->max_delta) { - coap_log(LOG_WARNING, + coap_log(LOG_DEBUG, "coap_add_option: options are not in correct order\n"); - return 0; + return coap_insert_option(pdu, type, len, data); } if (!coap_pdu_check_resize(pdu, @@ -363,6 +526,7 @@ error_desc_t coap_error[] = { { COAP_RESPONSE_CODE(503), "Service Unavailable" }, { COAP_RESPONSE_CODE(504), "Gateway Timeout" }, { COAP_RESPONSE_CODE(505), "Proxying Not Supported" }, + { COAP_RESPONSE_CODE(508), "Hop Limit Reached" }, { 0, NULL } /* end marker */ }; diff --git a/tests/test_pdu.c b/tests/test_pdu.c index 094b38a000..c715c34276 100644 --- a/tests/test_pdu.c +++ b/tests/test_pdu.c @@ -754,7 +754,6 @@ t_encode_pdu13(void) { coap_delete_optlist(optlist); } - static void t_encode_pdu14(void) { coap_optlist_t *optlist = NULL; @@ -790,6 +789,159 @@ t_encode_pdu14(void) { coap_delete_optlist(optlist); } +/* Check inserting options with random types get put into the PDU in the + right order */ +static void +t_encode_pdu15(void) { + size_t n; + uint16_t opt_num[] = { 300, 13, 10, 7, 11, 268, 269, 12, 8, 9 }; + uint8_t opt_val[] = { 59, 56, 53, 50, 54, 57, 58, 55, 51, 52 }; + uint8_t opt_srt[] = { 50, 51, 52, 53, 54, 55, 56, 57, 58, 59 }; + coap_opt_iterator_t oi; + coap_opt_t *option; + + coap_pdu_clear(pdu, pdu->max_size); /* clear PDU */ + + CU_ASSERT(pdu->data == NULL); + + for (n = 0; n < (sizeof(opt_num)/sizeof(opt_num[0])); n++) { + coap_insert_option(pdu, opt_num[n], + sizeof(opt_val[n]), &opt_val[n]); + } + + /* Check options in pdu are in right order */ + coap_option_iterator_init(pdu, &oi, COAP_OPT_ALL); + for (n = 0; n < (sizeof(opt_num)/sizeof(opt_num[0])); n++) { + option = coap_option_next(&oi); + CU_ASSERT(oi.bad == 0); + CU_ASSERT(option != NULL); + CU_ASSERT(coap_opt_length(option) == 1); + CU_ASSERT(*coap_opt_value(option) == opt_srt[n]); + } + option = coap_option_next(&oi); + CU_ASSERT(oi.bad == 1); + CU_ASSERT(option == NULL); +} + +/* Check changing value of options works */ +static void +t_encode_pdu16(void) { + size_t n; + uint16_t opt_num[] = { 300, 10, 7 }; + uint8_t opt_val[] = { 53, 51, 50 }; + uint8_t data[] = { 'd', 'a', 't', 'a' }; + uint8_t data1[] = { 0x71, 0x32, 0x31, 0x33, 0xe1, 0x00, 0x15, 0x35, + 0xff, 0x64, 0x61, 0x74, 0x61 }; + uint8_t data2[] = { 0x71, 0x32, 0x33, 0x01, 0x23, 0x45, 0xe1, 0x00, + 0x15, 0x35, 0xff, 0x64, 0x61, 0x74, 0x61 }; + uint8_t data3[] = { 0x70, 0x31, 0x33, 0xe1, 0x00, 0x15, 0x35, 0xff, + 0x64, 0x61, 0x74, 0x61 }; + uint8_t data4[] = { 0x71, 0x32, 0x31, 0x33, 0xe4, 0x00, 0x15, 0x06, + 0x54, 0x32, 0x10, 0xff, 0x64, 0x61, 0x74, 0x61 }; + int new_val; + unsigned char buf[4]; + + coap_pdu_clear(pdu, pdu->max_size); /* clear PDU */ + + CU_ASSERT(pdu->data == NULL); + + for (n = 0; n < (sizeof(opt_num)/sizeof(opt_num[0])); n++) { + coap_add_option(pdu, opt_num[n], + sizeof(opt_val[n]), &opt_val[n]); + } + coap_add_data(pdu, sizeof(data), data); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); + /* Now update an option in the middle */ + new_val = 0x12345; + coap_update_option(pdu, 10, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data2, pdu->used_size) == 0); + /* Shrink it back again */ + new_val = 51; + coap_update_option(pdu, 10, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); + /* Now update an option at the start */ + new_val = 0; + coap_update_option(pdu, 7, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data3, pdu->used_size) == 0); + /* put it back again */ + new_val = 50; + coap_update_option(pdu, 7, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); + /* Now update an option at the end */ + new_val = 0x6543210; + coap_update_option(pdu, 300, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data4, pdu->used_size) == 0); + /* put it back again */ + new_val = 53; + coap_update_option(pdu, 300, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); +} + +/* Same as t_encode_pdu16, but without any data, but with a token */ +static void +t_encode_pdu17(void) { + size_t n; + uint8_t token[] = { 't' }; + uint16_t opt_num[] = { 300, 10, 7 }; + uint8_t opt_val[] = { 53, 51, 50 }; + uint8_t data1[] = { 0x74, 0x71, 0x32, 0x31, 0x33, 0xe1, 0x00, 0x15, + 0x35 }; + uint8_t data2[] = { 0x74, 0x71, 0x32, 0x33, 0x01, 0x23, 0x45, 0xe1, + 0x00, 0x15, 0x35 }; + uint8_t data3[] = { 0x74, 0x70, 0x31, 0x33, 0xe1, 0x00, 0x15, 0x35 }; + uint8_t data4[] = { 0x74, 0x71, 0x32, 0x31, 0x33, 0xe4, 0x00, 0x15, + 0x06, 0x54, 0x32, 0x10 }; + int new_val; + unsigned char buf[4]; + + coap_pdu_clear(pdu, pdu->max_size); /* clear PDU */ + + CU_ASSERT(pdu->data == NULL); + + coap_add_token(pdu, sizeof(token), token); + for (n = 0; n < (sizeof(opt_num)/sizeof(opt_num[0])); n++) { + coap_add_option(pdu, opt_num[n], + sizeof(opt_val[n]), &opt_val[n]); + } + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); + /* Now update an option in the middle */ + new_val = 0x12345; + coap_update_option(pdu, 10, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data2, pdu->used_size) == 0); + /* Shrink it back again */ + new_val = 51; + coap_update_option(pdu, 10, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); + /* Now update an option at the start */ + new_val = 0; + coap_update_option(pdu, 7, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data3, pdu->used_size) == 0); + /* put it back again */ + new_val = 50; + coap_update_option(pdu, 7, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); + /* Now update an option at the end */ + new_val = 0x6543210; + coap_update_option(pdu, 300, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data4, pdu->used_size) == 0); + /* put it back again */ + new_val = 53; + coap_update_option(pdu, 300, + coap_encode_var_safe(buf, sizeof(buf), new_val), buf); + CU_ASSERT(memcmp(pdu->token, data1, pdu->used_size) == 0); +} + static int t_pdu_tests_create(void) { pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); @@ -859,6 +1011,9 @@ t_init_pdu_tests(void) { PDU_ENCODER_TEST(suite[1], t_encode_pdu12); PDU_ENCODER_TEST(suite[1], t_encode_pdu13); PDU_ENCODER_TEST(suite[1], t_encode_pdu14); + PDU_ENCODER_TEST(suite[1], t_encode_pdu15); + PDU_ENCODER_TEST(suite[1], t_encode_pdu16); + PDU_ENCODER_TEST(suite[1], t_encode_pdu17); } else /* signal error */ fprintf(stderr, "W: cannot add pdu parser test suite (%s)\n",