From fcec0f5aaf5c9818e291dc6535500e7d06b52eed Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Thu, 9 Dec 2021 11:59:50 +0000 Subject: [PATCH] RFC9175: Add in support for the Echo and Request-Tag options A unique Request-Tag is added to any PUT/POST/FETCH request that contains a large body. The server is then able to differentiate between concurrent sending of large data using the same session. If a server responds with a 4.01 and includes an Echo option, then the libcoap client retransmits the request adding in the received Echo option with the same value. If the server responds normally with an Echo option, then the next request from the client (internally added by libcoap) will contain that Echo option. It is the responsibility of the server application to add in the Echo option if required unless this is triggered by OSCORE. --- README.md | 4 +- doc/main.md | 4 +- include/coap3/block.h | 18 +-- include/coap3/coap_block_internal.h | 3 +- include/coap3/coap_cache.h | 4 +- include/coap3/coap_event.h | 2 +- include/coap3/coap_session_internal.h | 2 + include/coap3/pdu.h | 2 + man/coap.txt.in | 2 + man/coap_block.txt.in | 18 +-- src/block.c | 179 ++++++++++++++++++++++---- src/coap_debug.c | 11 +- src/coap_session.c | 3 +- src/net.c | 19 ++- src/pdu.c | 43 ++++--- 15 files changed, 236 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 712d66e8d5..78fe633d1c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Static Analysis](https://scan.coverity.com/projects/10970/badge.svg?flat=1)](https://scan.coverity.com/projects/obgm-libcoap) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libcoap.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:libcoap) -Copyright (C) 2010—2021 by Olaf Bergmann and others +Copyright (C) 2010—2022 by Olaf Bergmann and others ABOUT LIBCOAP ============= @@ -51,6 +51,8 @@ The following RFCs are supported * RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option +* RFC9175: CoAP: Echo, Request-Tag, and Token Processing + There is (D)TLS support for the following libraries * OpenSSL (Minimum version 1.1.0) [PKI, PSK and PKCS11] diff --git a/doc/main.md b/doc/main.md index 3ffe3d1b94..f08a6a135b 100644 --- a/doc/main.md +++ b/doc/main.md @@ -4,7 +4,7 @@ libcoap {#mainpage} A C implementation of the Constrained Application Protocol (RFC 7252) ===================================================================== -Copyright (C) 2010--2021 by Olaf Bergmann and others +Copyright (C) 2010--2022 by Olaf Bergmann and others About libcoap ============= @@ -36,6 +36,8 @@ The following RFCs are supported * RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option +* RFC9175: CoAP: Echo, Request-Tag, and Token Processing + There is (D)TLS support for the following libraries * OpenSSL (Minimum version 1.1.0) [PKI, PSK and PKCS11] diff --git a/include/coap3/block.h b/include/coap3/block.h index c093c1cd78..f96619fa6f 100644 --- a/include/coap3/block.h +++ b/include/coap3/block.h @@ -24,7 +24,7 @@ /** * @ingroup application_api * @defgroup block Block Transfer - * API for handling PDUs using CoAP BLOCK options (RFC7959) + * API for handling PDUs using CoAP Block options (RFC7959) * @{ */ @@ -167,7 +167,7 @@ coap_block_build_body(coap_binary_t *body_data, size_t length, /** * Adds the appropriate part of @p data to the @p response pdu. If blocks are * required, then the appropriate block will be added to the PDU and sent. - * Adds a ETAG option that is the hash of the entire data if the data is to be + * Adds a ETag option that is the hash of the entire data if the data is to be * split into blocks * Used by a request handler. * @@ -215,7 +215,7 @@ typedef void (*coap_release_large_data_t)(coap_session_t *session, * Used for a client request. * * If the data spans multiple PDUs, then the data will get transmitted using - * BLOCK1 option with the addition of the SIZE1 option. + * Block1 option with the addition of the Size1 and Request-Tag options. * The underlying library will handle the transmission of the individual blocks. * Once the body of data has been transmitted (or a failure occurred), then * @p release_func (if not NULL) will get called so the application can @@ -223,7 +223,7 @@ typedef void (*coap_release_large_data_t)(coap_session_t *session, * the application not to change the contents of @p data until the data * transfer has completed. * - * There is no need for the application to include the BLOCK1 option in the + * There is no need for the application to include the Block1 option in the * @p pdu. * * coap_add_data_large_request() (or the alternative coap_add_data_large_*() @@ -260,13 +260,13 @@ int coap_add_data_large_request(coap_session_t *session, * * If all the data can be transmitted in a single PDU, this is functionally * the same as coap_add_data() except @p release_func (if not NULL) will get - * invoked after data transmission. The MEDIA_TYPE, MAXAGE and ETAG options may - * be added in as appropriate. + * invoked after data transmission. The Content-Format, Max-Age and ETag + * options may be added in as appropriate. * * Used by a server request handler to create the response. * * If the data spans multiple PDUs, then the data will get transmitted using - * BLOCK2 (response) option with the addition of the SIZE2 and ETAG + * Block2 (response) option with the addition of the Size2 and ETag * options. The underlying library will handle the transmission of the * individual blocks. Once the body of data has been transmitted (or a * failure occurred), then @p release_func (if not NULL) will get called so the @@ -274,7 +274,7 @@ int coap_add_data_large_request(coap_session_t *session, * responsibility of the application not to change the contents of @p data * until the data transfer has completed. * - * There is no need for the application to include the BLOCK2 option in the + * There is no need for the application to include the Block2 option in the * @p pdu. * * coap_add_data_large_response() (or the alternative coap_add_data_large_*() @@ -289,7 +289,7 @@ int coap_add_data_large_request(coap_session_t *session, * @param request The requesting pdu. * @param response The response pdu. * @param query The query taken from the (original) requesting pdu. - * @param media_type The format of the data. + * @param media_type The content format of the data. * @param maxage The maxmimum life of the data. If @c -1, then there * is no maxage. * @param etag ETag to use if not 0. diff --git a/include/coap3/coap_block_internal.h b/include/coap3/coap_block_internal.h index 26a0933418..9ed7a5a29f 100644 --- a/include/coap3/coap_block_internal.h +++ b/include/coap3/coap_block_internal.h @@ -189,7 +189,8 @@ int coap_handle_request_put_block(coap_context_t *context, #endif /* COAP_SERVER_SUPPORT */ #if COAP_CLIENT_SUPPORT -int coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *rcvd); +int coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *sent, + coap_pdu_t *rcvd); int coap_handle_response_get_block(coap_context_t *context, coap_session_t *session, diff --git a/include/coap3/coap_cache.h b/include/coap3/coap_cache.h index 92407e4fd4..0e403f5b9f 100644 --- a/include/coap3/coap_cache.h +++ b/include/coap3/coap_cache.h @@ -50,7 +50,7 @@ typedef enum coap_cache_record_pdu_t { * for an explanation of CoAP cache keys. * * Specific CoAP options can be removed from the cache-key. Examples of - * this are the BLOCK1 and BLOCK2 options - which make no real sense including + * this are the Block1 and Block2 options - which make no real sense including * them in a client or server environment, but should be included in a proxy * caching environment where things are cached on a per block basis. * This is done globally by calling the coap_cache_ignore_options() @@ -78,7 +78,7 @@ coap_cache_key_t *coap_cache_derive_key(const coap_session_t *session, * for an explanation of CoAP cache keys. * * Specific CoAP options can be removed from the cache-key. Examples of - * this are the BLOCK1 and BLOCK2 options - which make no real sense including + * this are the Block1 and Block2 options - which make no real sense including * them in a client or server environment, but should be included in a proxy * caching environment where things are cached on a per block basis. * This is done individually by specifying @p cache_ignore_count and diff --git a/include/coap3/coap_event.h b/include/coap3/coap_event.h index 81c96c64f6..540866d82c 100644 --- a/include/coap3/coap_event.h +++ b/include/coap3/coap_event.h @@ -54,7 +54,7 @@ typedef enum coap_event_t { COAP_EVENT_SESSION_FAILED = 0x2003, /** - * (Q-)BLOCK receive errors + * (Q-)Block receive errors */ COAP_EVENT_PARTIAL_BLOCK = 0x3001 } coap_event_t; diff --git a/include/coap3/coap_session_internal.h b/include/coap3/coap_session_internal.h index 93bdbae1b3..df7ab51c1f 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -134,7 +134,9 @@ struct coap_session_t { sesison */ uint8_t block_mode; /**< Zero or more COAP_BLOCK_ or'd options */ uint8_t proxy_session; /**< Set if this is an ongoing proxy session */ + uint32_t tx_rtag; /**< Next Request-Tag number to use */ uint64_t tx_token; /**< Next token number to use */ + coap_bin_const_t *echo; /**< Echo value to send with next request */ }; #if COAP_SERVER_SUPPORT diff --git a/include/coap3/pdu.h b/include/coap3/pdu.h index c6737b6d18..7e23ca94db 100644 --- a/include/coap3/pdu.h +++ b/include/coap3/pdu.h @@ -128,7 +128,9 @@ typedef enum coap_request_t { #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_ECHO 252 /* _N__E_U, opaque, 0-40 B, RFC9175 */ #define COAP_OPTION_NORESPONSE 258 /* _U-_E_U, uint, 0-1 B, RFC7967 */ +#define COAP_OPTION_RTAG 292 /* ___RE_U, opaque, 0-8 B, RFC9175 */ #define COAP_MAX_OPT 65535 /**< the highest option number we know */ diff --git a/man/coap.txt.in b/man/coap.txt.in index d109a8751c..963310c17a 100644 --- a/man/coap.txt.in +++ b/man/coap.txt.in @@ -68,6 +68,8 @@ See "RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option" +"RFC9175: CoAP: Echo, Request-Tag, and Token Processing" + for further information. BUGS diff --git a/man/coap_block.txt.in b/man/coap_block.txt.in index 44e1a8ee17..43568c07c3 100644 --- a/man/coap_block.txt.in +++ b/man/coap_block.txt.in @@ -99,7 +99,7 @@ In RAM constrained environments, option 2 may be the preferred method. This man page focuses on getting libcoap to do all the work, not how to do it all in the application. -However, if the client supplies a BLOCK1 or BLOCK2 Option in the PDU where the +However, if the client supplies a Block1 or Block2 Option in the PDU where the block number is not 0, this is assumed to be a random access request and any other blocks will not be requested by libcoap even if instructed otherwise. @@ -167,7 +167,7 @@ If COAP_BLOCK_USE_LIBCOAP is set, then any PDUs presented to the application handlers will get the tokens set back to the initiating token so that requests can be matched with responses even if different tokens had to be used for the series of packet interchanges. Furthermore, if COAP_BLOCK_SINGLE_BODY is set, -then the PDU that presents the entire body will have any BLOCKx option removed. +then the PDU that presents the entire body will have any BlockX option removed. *NOTE:* COAP_BLOCK_USE_LIBCOAP must be set if libcoap is to do all the block tracking and requesting, otherwise the application will have to do all @@ -180,7 +180,7 @@ but supports the transmission of data that has a body size that is potentially larger than can be fitted into a single client request PDU. The specified payload _data_ of length _length_ is associated with the _session_ with the first block of data added to the PDU _pdu_ along with the appropriate CoAP -options such as BLOCK1, and SIZE1 if the data does not fit in +options such as Block1, Size1 and Request-Tag if the data does not fit in a single PDU. When the block receipt has been acknowledged by the peer, the library @@ -216,7 +216,7 @@ NULL, the callback function is called once the final block of _data_ has been transmitted. The user-defined parameter _app_ptr_ is the same value that was passed to *coap_add_data_large_response*(). -It also adds in the appropriate CoAP options such as BLOCK2, SIZE2 and ETAG to +It also adds in the appropriate CoAP options such as Block2, Size2 and ETag to handle block-wise transfer if the data does not fit in a single PDU. _resource_, _query_, _session_, _request_, and _response_ are the same @@ -224,7 +224,7 @@ parameters as in the called resource handler that invokes *coap_add_data_large_response*(). If _etag_ is 0, then a unique ETag value will be generated, else is the ETag value to use. The _media_type_ is for the format of the _data_ and _maxage_ defines the -lifetime of the response. If _maxage_ is set to -1, then the MAXAGE option +lifetime of the response. If _maxage_ is set to -1, then the Max-Age option does not get included (which indicates the default value of 60 seconds according to RFC 7252). @@ -444,10 +444,10 @@ const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { * Define the format - COAP_MEDIATYPE_TEXT_PLAIN - to add in * Define how long this response is valid for (secs) - 1 - to add in. * - * OBSERVE Option added internally if needed within the function - * BLOCK2 Option added internally if output too large - * SIZE2 Option added internally - * ETAG Option added internally + * Observe Option added internally if needed within the function + * Block2 Option added internally if output too large + * Size2 Option added internally + * ETag Option added internally */ coap_add_data_large_response(resource, session, request, response, query, COAP_MEDIATYPE_TEXT_PLAIN, 1, 0, diff --git a/src/block.c b/src/block.c index baba96b5da..e9ddef660e 100644 --- a/src/block.c +++ b/src/block.c @@ -226,7 +226,7 @@ coap_add_data_blocked_response(const coap_pdu_t *request, } /* - * BLOCK2 not requested + * Block2 not requested */ if (!coap_add_data(response, length, data)) { /* @@ -411,11 +411,13 @@ coap_add_data_large_internal(coap_session_t *session, } avail = pdu->max_size - pdu->used_size - pdu->hdr_size; + /* There may be a response with Echo option */ + avail -= coap_opt_encode_size(COAP_OPTION_ECHO, 40); /* May need token of length 8, so account for this */ avail -= (pdu->token_length < 8) ? 8 - pdu->token_length : 0; blk_size = coap_flsll((long long)avail) - 4 - 1; - /* see if BLOCKx defined - if so update blk_size as given by app */ + /* see if BlockX defined - if so update blk_size as given by app */ if (coap_get_block(pdu, option, &block)) { if (block.szx < blk_size) blk_size = block.szx; @@ -494,11 +496,15 @@ coap_add_data_large_internal(coap_session_t *session, coap_encode_var_safe(buf, sizeof(buf), (unsigned int)length), buf); - } - else { + coap_update_option(pdu, + COAP_OPTION_RTAG, + coap_encode_var_safe8(buf, sizeof(buf), + ++session->tx_rtag), + buf); + } else { /* - * resource+query match is used for BLOCK2 large body transmissions - * token match is used for BLOCK1 large body transmissions + * resource+query match is used for Block2 large body transmissions + * token match is used for Block1 large body transmissions */ lg_xmit->b.b2.resource = resource; if (query) { @@ -561,6 +567,8 @@ coap_add_data_large_internal(coap_session_t *session, /* Check we still have space after adding in some options */ avail = pdu->max_size - pdu->used_size - pdu->hdr_size; + /* There may be a response with Echo option */ + avail -= coap_opt_encode_size(COAP_OPTION_ECHO, 40); /* May need token of length 8, so account for this */ avail -= (pdu->token_length < 8) ? 8 - pdu->token_length : 0; if (avail < (ssize_t)chunk) { @@ -830,8 +838,6 @@ coap_block_check_lg_srcv_timeouts(coap_session_t *session, coap_tick_t now) { coap_lg_crcv_t * coap_block_new_lg_crcv(coap_session_t *session, coap_pdu_t *pdu) { coap_lg_crcv_t *lg_crcv; - uint8_t buf[8]; - size_t length; uint64_t state_token = STATE_TOKEN_FULL(++session->tx_token, 1); lg_crcv = coap_malloc_type(COAP_LG_CRCV, sizeof(coap_lg_crcv_t)); @@ -872,12 +878,6 @@ coap_block_new_lg_crcv(coap_session_t *session, coap_pdu_t *pdu) { /* Need to set up a base token for actual communications if retries needed */ lg_crcv->retry_counter = 1; lg_crcv->state_token = state_token; - length = coap_encode_var_safe8(buf, sizeof(lg_crcv->state_token), - lg_crcv->state_token); - if (!coap_update_token(pdu, length, buf)) { - coap_block_delete_lg_crcv(session, lg_crcv); - return NULL; - } /* In case it is there - must not be in continuing request PDUs */ coap_remove_option(&lg_crcv->pdu, COAP_OPTION_BLOCK1); @@ -973,7 +973,7 @@ add_block_send(uint32_t num, uint32_t *out_blocks, * * This is set up using coap_add_data_large_response() * - * Server is sending a large data response to GET / observe (BLOCK2) + * Server is sending a large data response to GET / observe (Block2) * * Return: 0 Call application handler * 1 Do not call application handler - just send the built response @@ -1108,7 +1108,7 @@ coap_handle_request_send_block(coap_session_t *session, /* * Copy the options across and then fix the block option * - * Need to drop Observe option if BLOCK2 and block.num != 0 + * Need to drop Observe option if Block2 and block.num != 0 */ coap_option_iterator_init(&p->pdu, &opt_iter, COAP_OPT_ALL); while ((option = coap_option_next(&opt_iter))) { @@ -1250,7 +1250,7 @@ update_received_blocks(coap_rblock_t *rec_blocks, uint32_t block_num) { /* * Need to check if this is a large PUT / POST using multiple blocks * - * Server receiving PUT/POST etc. of a large amount of data (BLOCK1) + * Server receiving PUT/POST etc. of a large amount of data (Block1) * * Return: 0 Call application handler * 1 Do not call application handler - just send the built response @@ -1292,12 +1292,24 @@ coap_handle_request_put_block(coap_context_t *context, uint16_t fmt = fmt_opt ? coap_decode_var_bytes(coap_opt_value(fmt_opt), coap_opt_length(fmt_opt)) : COAP_MEDIATYPE_TEXT_PLAIN; + coap_opt_t *rtag_opt = coap_check_option(pdu, + COAP_OPTION_RTAG, + &opt_iter); + size_t rtag_length = rtag_opt ? coap_opt_length(rtag_opt) : 0; + const uint8_t *rtag = rtag_opt ? coap_opt_value(rtag_opt) : NULL; total = size_opt ? coap_decode_var_bytes(coap_opt_value(size_opt), coap_opt_length(size_opt)) : 0; offset = block.num << (block.szx + 4); LL_FOREACH(session->lg_srcv, p) { + if (rtag_opt || p->rtag_set == 1) { + if (!(rtag_opt && p->rtag_set == 1)) + continue; + if (p->rtag_length != rtag_length || + memcmp(p->rtag, rtag, rtag_length) != 0) + continue; + } if (resource == p->resource) { break; } @@ -1339,6 +1351,11 @@ coap_handle_request_put_block(coap_context_t *context, memcpy(p->observe, coap_opt_value(observe), p->observe_length); p->observe_set = 1; } + if (rtag_opt) { + p->rtag_length = (uint8_t)rtag_length; + memcpy(p->rtag, rtag, rtag_length); + p->rtag_set = 1; + } p->body_data = NULL; LL_PREPEND(session->lg_srcv, p); } @@ -1391,7 +1408,7 @@ coap_handle_request_put_block(coap_context_t *context, } /* - * Remove the BLOCK1 option as passing all of the data to + * Remove the Block1 option as passing all of the data to * application layer. Add back in observe option if appropriate. * Adjust all other information. */ @@ -1474,12 +1491,96 @@ coap_handle_request_put_block(coap_context_t *context, #endif /* COAP_SERVER_SUPPORT */ #if COAP_CLIENT_SUPPORT +static int +check_freshness(coap_session_t *session, coap_pdu_t *rcvd, coap_pdu_t *sent, + coap_lg_xmit_t *lg_xmit, coap_lg_crcv_t *lg_crcv) +{ + /* Check for ECHO option for freshness */ + coap_opt_iterator_t opt_iter; + coap_opt_t *opt = coap_check_option(rcvd, COAP_OPTION_ECHO, &opt_iter); + + if (opt) { + if (sent || lg_xmit || lg_crcv) { + /* Need to retransmit original request with ECHO added */ + coap_pdu_t *echo_pdu; + coap_mid_t mid; + const uint8_t *data; + size_t data_len; + int have_data = 0; + uint8_t ltoken[8]; + size_t ltoken_len; + uint64_t token; + + if (sent) { + if (coap_get_data(sent, &data_len, &data)) + have_data = 1; + } else if (lg_xmit) { + sent = &lg_xmit->pdu; + if (lg_xmit->length) { + size_t blk_size = (size_t)1 << (lg_xmit->blk_size + 4); + size_t offset = (lg_xmit->last_block + 1) * blk_size; + have_data = 1; + data = &lg_xmit->data[offset]; + data_len = (lg_xmit->length - offset) > blk_size ? blk_size : + lg_xmit->length - offset; + } + } else /* lg_crcv */ { + sent = &lg_crcv->pdu; + if (coap_get_data(sent, &data_len, &data)) + have_data = 1; + } + if (lg_xmit) { + token = STATE_TOKEN_FULL(lg_xmit->b.b1.state_token, + ++lg_xmit->b.b1.count); + } else { + token = STATE_TOKEN_FULL(lg_crcv->state_token, + ++lg_crcv->retry_counter); + } + ltoken_len = coap_encode_var_safe8(ltoken, sizeof(token), token); + echo_pdu = coap_pdu_duplicate(sent, session, ltoken_len, ltoken, NULL); + if (!echo_pdu) + return 0; + if (!coap_insert_option(echo_pdu, COAP_OPTION_ECHO, + coap_opt_length(opt), coap_opt_value(opt))) + goto no_sent; + if (have_data) { + coap_add_data(echo_pdu, data_len, data); + } + + mid = coap_send_internal(session, echo_pdu); + if (mid == COAP_INVALID_MID) + goto no_sent; + return 1; + } else { + /* Need to save ECHO value to add to next reansmission */ +no_sent: + coap_delete_bin_const(session->echo); + session->echo = coap_new_bin_const(coap_opt_value(opt), + coap_opt_length(opt)); + } + } + return 0; +} + +static void +track_echo(coap_session_t *session, coap_pdu_t *rcvd) +{ + coap_opt_iterator_t opt_iter; + coap_opt_t *opt = coap_check_option(rcvd, COAP_OPTION_ECHO, &opt_iter); + + if (opt) { + coap_delete_bin_const(session->echo); + session->echo = coap_new_bin_const(coap_opt_value(opt), + coap_opt_length(opt)); + } +} + /* * Need to see if this is a response to a large body request transfer. If so, * need to initiate the request containing the next block and not trouble the * application. Note that Token must unique per request/response. * - * Client receives large data acknowledgement from server (BLOCK1) + * Client receives large data acknowledgement from server (Block1) * * This is set up using coap_add_data_large_request() * @@ -1489,7 +1590,8 @@ coap_handle_request_put_block(coap_context_t *context, * 1 Do not call application handler - just send the built response */ int -coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *rcvd) { +coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *sent, + coap_pdu_t *rcvd) { coap_lg_xmit_t *p; coap_lg_xmit_t *q; uint64_t token_match = STATE_TOKEN_BASE(coap_decode_var_bytes8(rcvd->token, @@ -1497,7 +1599,10 @@ coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *rcvd) { LL_FOREACH_SAFE(session->lg_xmit, p, q) { if (!COAP_PDU_IS_REQUEST(&p->pdu) || - token_match != STATE_TOKEN_BASE(p->b.b1.state_token)) { + (token_match != STATE_TOKEN_BASE(p->b.b1.state_token) && + token_match != + STATE_TOKEN_BASE(coap_decode_var_bytes8(p->b.b1.app_token->s, + p->b.b1.app_token->length)))) { /* try out the next one */ continue; } @@ -1533,12 +1638,13 @@ coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *rcvd) { (p->offset + chunk) % ((size_t)1 << (block.szx + 4))); } } + track_echo(session, rcvd); if (p->last_block == (int)block.num) { /* - * Duplicate BLOCK ACK + * Duplicate Block ACK * * RFCs not clear here, but on a lossy connection, there could - * be multiple BLOCK ACKs, causing the client to retransmit the + * be multiple Block ACKs, causing the client to retransmit the * same block multiple times, or the server retransmitting the * same ACK. * @@ -1577,6 +1683,9 @@ coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *rcvd) { goto fail_body; return 1; } + } else if (rcvd->code == COAP_RESPONSE_CODE(401)) { + if (check_freshness(session, rcvd, sent, p, NULL)) + return 1; } fail_body: if (session->lg_crcv) { @@ -1661,17 +1770,17 @@ coap_block_build_body(coap_binary_t *body_data, size_t length, * application. Note that Token must unique per request/response. * * This is set up using coap_send() - * Client receives large data from server (BLOCK2) + * Client receives large data from server (Block2) * * Return: 0 Call application handler * 1 Do not call application handler - just sent the next request */ int coap_handle_response_get_block(coap_context_t *context, - coap_session_t *session, - coap_pdu_t *sent, - coap_pdu_t *rcvd, - coap_recurse_t recursive) { + coap_session_t *session, + coap_pdu_t *sent, + coap_pdu_t *rcvd, + coap_recurse_t recursive) { coap_lg_crcv_t *p; int app_has_response = 0; coap_block_t block = {0, 0, 0}; @@ -1711,6 +1820,7 @@ coap_handle_response_get_block(coap_context_t *context, have_block = 1; block_opt = COAP_OPTION_BLOCK2; } + track_echo(session, rcvd); if (have_block) { coap_opt_t *fmt_opt = coap_check_option(rcvd, COAP_OPTION_CONTENT_FORMAT, @@ -1946,6 +2056,10 @@ coap_handle_response_get_block(coap_context_t *context, goto call_app_handler; } } + } else if (rcvd->code == COAP_RESPONSE_CODE(401)) { + if (check_freshness(session, rcvd, sent, NULL, p)) + goto skip_app_handler; + goto fail_resp; } if (!block.m && !p->observe_set) { fail_resp: @@ -1997,6 +2111,15 @@ coap_handle_response_get_block(coap_context_t *context, coap_block_delete_lg_crcv(session, lg_crcv); } } + track_echo(session, rcvd); + } else if (rcvd->code == COAP_RESPONSE_CODE(401)) { + coap_lg_crcv_t *lg_crcv = coap_block_new_lg_crcv(session, sent); + + if (lg_crcv) { + LL_PREPEND(session->lg_crcv, lg_crcv); + return coap_handle_response_get_block(context, session, sent, rcvd, + COAP_RECURSE_NO); + } } } return app_has_response; diff --git a/src/coap_debug.c b/src/coap_debug.c index 1b9c922c37..452813cdae 100644 --- a/src/coap_debug.c +++ b/src/coap_debug.c @@ -364,12 +364,14 @@ msg_option_string(uint8_t code, uint16_t option_type) { { COAP_OPTION_PROXY_URI, "Proxy-Uri" }, { COAP_OPTION_PROXY_SCHEME, "Proxy-Scheme" }, { COAP_OPTION_SIZE1, "Size1" }, - { COAP_OPTION_NORESPONSE, "No-Response" } + { COAP_OPTION_ECHO, "Echo" }, + { COAP_OPTION_NORESPONSE, "No-Response" }, + { COAP_OPTION_RTAG, "Request-Tag" } }; static struct option_desc_t options_csm[] = { { COAP_SIGNALING_OPTION_MAX_MESSAGE_SIZE, "Max-Message-Size" }, - { COAP_SIGNALING_OPTION_BLOCK_WISE_TRANSFER, "Block-wise-Transfer" } + { COAP_SIGNALING_OPTION_BLOCK_WISE_TRANSFER, "Block-Wise-Transfer" } }; static struct option_desc_t options_pingpong[] = { @@ -642,6 +644,9 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { case COAP_OPTION_IF_MATCH: case COAP_OPTION_ETAG: + case COAP_OPTION_ECHO: + case COAP_OPTION_NORESPONSE: + case COAP_OPTION_RTAG: opt_len = coap_opt_length(option); opt_val = coap_opt_value(option); snprintf((char *)buf, sizeof(buf), "0x"); @@ -864,7 +869,7 @@ char *coap_string_tls_support(char *buffer, size_t bufsize) if (have_dtls == 0 && have_tls == 0) { snprintf(buffer, bufsize, "(No DTLS or TLS support)"); return buffer; - } + } switch (tls_version->type) { case COAP_TLS_LIBRARY_NOTLS: snprintf(buffer, bufsize, "(No DTLS or TLS support)"); diff --git a/src/coap_session.c b/src/coap_session.c index fa33d4f635..2245a0911d 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -160,8 +160,9 @@ coap_make_session(coap_proto_t proto, coap_session_type_t type, session->dtls_event = -1; session->last_ping_mid = COAP_INVALID_MID; - /* initialize message id */ + /* Randomly initialize */ coap_prng((unsigned char *)&session->tx_mid, sizeof(session->tx_mid)); + coap_prng((unsigned char *)&session->tx_rtag, sizeof(session->tx_rtag)); return session; } diff --git a/src/net.c b/src/net.c index fdeee390fa..3d4a294bac 100644 --- a/src/net.c +++ b/src/net.c @@ -1264,6 +1264,14 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { } } + if (session->echo) { + if (!coap_insert_option(pdu, COAP_OPTION_ECHO, session->echo->length, + session->echo->s)) + goto error; + coap_delete_bin_const(session->echo); + session->echo = NULL; + } + if (!coap_pdu_encode_header(pdu, session->proto)) { goto error; } @@ -1280,11 +1288,11 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { */ if (coap_check_option(pdu, COAP_OPTION_BLOCK1, &opt_iter) != NULL) { coap_log(LOG_DEBUG, - "Remote end did not indicate CSM support for BLOCK1 enabled\n"); + "Remote end did not indicate CSM support for Block1 enabled\n"); } if (coap_check_option(pdu, COAP_OPTION_BLOCK2, &opt_iter) != NULL) { coap_log(LOG_DEBUG, - "Remote end did not indicate CSM support for BLOCK2 enabled\n"); + "Remote end did not indicate CSM support for Block2 enabled\n"); } } #endif /* !COAP_DISABLE_TCP */ @@ -2876,7 +2884,7 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu if (observe) coap_delete_observer(resource, session, &token); if (added_block) - coap_remove_option(pdu, COAP_OPTION_BLOCK1); + coap_remove_option(response, COAP_OPTION_BLOCK1); } /* If original request contained a token, and the registered @@ -2943,8 +2951,7 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu #if COAP_CLIENT_SUPPORT static void handle_response(coap_context_t *context, coap_session_t *session, - coap_pdu_t *sent, coap_pdu_t *rcvd) { - + coap_pdu_t *sent, coap_pdu_t *rcvd) { /* In a lossy context, the ACK of a separate response may have * been lost, so we need to stop retransmitting requests with the * same token. @@ -2954,7 +2961,7 @@ handle_response(coap_context_t *context, coap_session_t *session, if (session->block_mode & COAP_BLOCK_USE_LIBCOAP) { /* See if need to send next block to server */ - if (coap_handle_response_send_block(session, rcvd)) { + if (coap_handle_response_send_block(session, sent, rcvd)) { /* Next block transmitted, no need to inform app */ coap_send_ack(session, rcvd); return; diff --git a/src/pdu.c b/src/pdu.c index 1344f054f7..bc1022bd00 100644 --- a/src/pdu.c +++ b/src/pdu.c @@ -418,6 +418,27 @@ coap_remove_option(coap_pdu_t *pdu, coap_option_num_t number) { return 1; } +static void +check_repeatable(coap_option_num_t number) { + /* Validate that the option is repeatable */ + switch (number) { + /* 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: + case COAP_OPTION_RTAG: + break; + default: + coap_log(LOG_INFO, "Option number %d is not defined as repeatable\n", + number); + /* Accepting it after warning as there may be user defineable options */ + break; + } +} + size_t coap_insert_option(coap_pdu_t *pdu, coap_option_num_t number, size_t len, const uint8_t *data) { @@ -449,6 +470,9 @@ coap_insert_option(coap_pdu_t *pdu, coap_option_num_t number, size_t len, if (!coap_opt_parse(option, pdu->used_size - (option - pdu->token), &decode)) return 0; opt_delta = opt_iter.number - number; + if (opt_delta == 0) { + check_repeatable(number); + } if (!coap_pdu_check_resize(pdu, pdu->used_size + shift - shrink)) @@ -561,22 +585,7 @@ coap_add_option_internal(coap_pdu_t *pdu, coap_option_num_t number, size_t len, assert(pdu); if (number == pdu->max_opt) { - /* Validate that the option is repeatable */ - switch (number) { - /* 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 number %d is not defined as repeatable\n", - number); - /* Accepting it after warning as there may be user defineable options */ - break; - } + check_repeatable(number); } if (COAP_PDU_IS_REQUEST(pdu) && @@ -947,7 +956,9 @@ coap_pdu_parse_opt_base(coap_pdu_t *pdu, uint16_t len) { case COAP_OPTION_PROXY_URI: if (len < 1 || len > 1034) res = 0; break; case COAP_OPTION_PROXY_SCHEME: if (len < 1 || len > 255) res = 0; break; case COAP_OPTION_SIZE1: if (len > 4) res = 0; break; + case COAP_OPTION_ECHO: if (len > 40) res = 0; break; case COAP_OPTION_NORESPONSE: if (len > 1) res = 0; break; + case COAP_OPTION_RTAG: if (len > 8) res = 0; break; default: ; }