diff --git a/README.md b/README.md index c1069ed4de..fb96095136 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,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 dcd965d264..110654cfc0 100644 --- a/doc/main.md +++ b/doc/main.md @@ -38,6 +38,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 fd7e6468ae..383a8549ca 100644 --- a/include/coap3/block.h +++ b/include/coap3/block.h @@ -2,6 +2,7 @@ * block.h -- block transfer * * Copyright (C) 2010-2012,2014-2015 Olaf Bergmann + * Copyright (C) 2022 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * @@ -24,7 +25,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) * @{ */ @@ -239,7 +240,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. * @@ -287,7 +288,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 @@ -295,7 +296,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_*() @@ -332,13 +333,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 @@ -346,7 +347,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_*() @@ -361,7 +362,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 027e0c041a..f3c5874325 100644 --- a/include/coap3/coap_block_internal.h +++ b/include/coap3/coap_block_internal.h @@ -2,8 +2,8 @@ * coap_block_internal.h -- Structures, Enums & Functions that are not * exposed to application programming * - * Copyright (C) 2010-2021 Olaf Bergmann - * Copyright (C) 2021 Jon Shallow + * Copyright (C) 2010-2022 Olaf Bergmann + * Copyright (C) 2021-2022 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * @@ -191,7 +191,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..1e1cd67407 100644 --- a/include/coap3/coap_cache.h +++ b/include/coap3/coap_cache.h @@ -1,6 +1,6 @@ /* coap_cache.h -- Caching of CoAP requests * -* Copyright (C) 2020 Olaf Bergmann +* Copyright (C) 2020-2022 Olaf Bergmann * * SPDX-License-Identifier: BSD-2-Clause * @@ -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 4cf9104bc8..f001e05d9c 100644 --- a/include/coap3/coap_event.h +++ b/include/coap3/coap_event.h @@ -2,6 +2,7 @@ * coap_event.h -- libcoap Event API * * Copyright (C) 2016 Olaf Bergmann + * Copyright (C) 2021-2022 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * @@ -54,7 +55,7 @@ typedef enum coap_event_t { COAP_EVENT_SESSION_FAILED = 0x2003, /** - * (Q-)BLOCK receive errors + * (Q-)Block receive errors */ COAP_EVENT_PARTIAL_BLOCK = 0x3001, diff --git a/include/coap3/coap_session_internal.h b/include/coap3/coap_session_internal.h index d25fbe8814..56675c2e27 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -2,7 +2,7 @@ * coap_session_internal.h -- Structures, Enums & Functions that are not * exposed to application programming * - * Copyright (C) 2010-2019 Olaf Bergmann + * Copyright (C) 2010-2022 Olaf Bergmann * * SPDX-License-Identifier: BSD-2-Clause * @@ -145,7 +145,9 @@ struct coap_session_t { uint8_t doing_first; /**< Set if doing client's first request */ uint8_t proxy_session; /**< Set if this is an ongoing proxy session */ uint8_t delay_recursive; /**< Set if in coap_client_delay_first() */ + 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 08a0e2d778..21551d52db 100644 --- a/include/coap3/pdu.h +++ b/include/coap3/pdu.h @@ -2,6 +2,7 @@ * pdu.h -- CoAP message structure * * Copyright (C) 2010-2014 Olaf Bergmann + * Copyright (C) 2021-2022 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * @@ -130,7 +131,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 6f42472535..527a9c0b50 100644 --- a/man/coap.txt.in +++ b/man/coap.txt.in @@ -70,6 +70,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 859ecfd0cd..71e96c10c7 100644 --- a/src/block.c +++ b/src/block.c @@ -1,6 +1,6 @@ /* block.c -- block transfer * - * Copyright (C) 2010--2012,2015-2019 Olaf Bergmann and others + * Copyright (C) 2010--2012,2015-2022 Olaf Bergmann and others * * SPDX-License-Identifier: BSD-2-Clause * @@ -520,6 +520,8 @@ 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; @@ -604,8 +606,12 @@ 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 @@ -674,6 +680,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) { @@ -958,8 +966,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)); @@ -1000,12 +1006,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); @@ -1101,7 +1101,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 @@ -1395,7 +1395,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 @@ -1437,12 +1437,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; } @@ -1484,6 +1496,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); } @@ -1571,8 +1588,15 @@ coap_handle_request_put_block(coap_context_t *context, h(resource, session, pdu, query, response); /* Check if lg_xmit generated and update PDU code if so */ coap_check_code_lg_xmit(session, response, resource, query, pdu->code); - /* Last chunk - lg_srcv no longer needed */ - goto free_lg_srcv; + /* Check to see if the server is doing a 4.01 + Echo response */ + if (response->code == COAP_RESPONSE_CODE(401) && + coap_check_option(response, COAP_OPTION_ECHO, &opt_iter)) { + /* Need to keep lg_srcv around for client's response */ + goto skip_app_handler; + } else { + /* Last chunk - lg_srcv no longer needed */ + goto free_lg_srcv; + } } else { /* No need to update body_data and body_length as a single PDU */ @@ -1632,6 +1656,90 @@ 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 @@ -1647,7 +1755,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, @@ -1655,7 +1764,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; } @@ -1700,6 +1812,7 @@ 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 Block1 ACK @@ -1752,6 +1865,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) { @@ -1836,17 +1952,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_b_t block; @@ -1887,6 +2003,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, @@ -2138,6 +2255,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: @@ -2190,6 +2311,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 20be4a415a..dda64b1e7f 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[] = { @@ -655,6 +657,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"); diff --git a/src/coap_session.c b/src/coap_session.c index 3f93317871..0936ae89cb 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -1,6 +1,7 @@ /* coap_session.c -- Session management for libcoap * * Copyright (C) 2017 Jean-Claue Michelou + * Copyright (C) 2022 Jon Shallow * * SPDX-License-Identifier: BSD-2-Clause * @@ -209,8 +210,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 c52bec3588..69e8b377e9 100644 --- a/src/net.c +++ b/src/net.c @@ -1,6 +1,6 @@ /* net.c -- CoAP context inteface * - * Copyright (C) 2010--2019 Olaf Bergmann and others + * Copyright (C) 2010--2022 Olaf Bergmann and others * * SPDX-License-Identifier: BSD-2-Clause * @@ -1336,6 +1336,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; } @@ -2890,7 +2898,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 @@ -2937,8 +2945,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. @@ -2948,7 +2955,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 6d37e9131d..7e3fa320b8 100644 --- a/src/pdu.c +++ b/src/pdu.c @@ -418,6 +418,50 @@ coap_remove_option(coap_pdu_t *pdu, coap_option_num_t number) { return 1; } +static int +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; + /* Protest at the known non-repeatable options and ignore them */ + case COAP_OPTION_URI_HOST: + case COAP_OPTION_IF_NONE_MATCH: + case COAP_OPTION_OBSERVE: + case COAP_OPTION_URI_PORT: + case COAP_OPTION_OSCORE: + case COAP_OPTION_CONTENT_FORMAT: + case COAP_OPTION_MAXAGE: + case COAP_OPTION_HOP_LIMIT: + case COAP_OPTION_ACCEPT: + case COAP_OPTION_BLOCK2: + case COAP_OPTION_BLOCK1: + case COAP_OPTION_SIZE2: + case COAP_OPTION_PROXY_URI: + case COAP_OPTION_PROXY_SCHEME: + case COAP_OPTION_SIZE1: + case COAP_OPTION_ECHO: + case COAP_OPTION_NORESPONSE: + coap_log(LOG_INFO, + "Option number %d is not defined as repeatable - dropped\n", + number); + return 0; + 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; + } + return 1; +} + size_t coap_insert_option(coap_pdu_t *pdu, coap_option_num_t number, size_t len, const uint8_t *data) { @@ -449,6 +493,10 @@ 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) { + if (!check_repeatable(number)) + return 0; + } if (!coap_pdu_check_resize(pdu, pdu->used_size + shift - shrink)) @@ -561,43 +609,8 @@ 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; - /* Protest at the known non-repeatable options and ignore them */ - case COAP_OPTION_URI_HOST: - case COAP_OPTION_IF_NONE_MATCH: - case COAP_OPTION_OBSERVE: - case COAP_OPTION_URI_PORT: - case COAP_OPTION_OSCORE: - case COAP_OPTION_CONTENT_FORMAT: - case COAP_OPTION_MAXAGE: - case COAP_OPTION_HOP_LIMIT: - case COAP_OPTION_ACCEPT: - case COAP_OPTION_BLOCK2: - case COAP_OPTION_BLOCK1: - case COAP_OPTION_SIZE2: - case COAP_OPTION_PROXY_URI: - case COAP_OPTION_PROXY_SCHEME: - case COAP_OPTION_SIZE1: - case COAP_OPTION_NORESPONSE: - coap_log(LOG_INFO, - "Option number %d is not defined as repeatable - dropped\n", - number); + if (!check_repeatable(number)) return 0; - 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; - } } if (COAP_PDU_IS_REQUEST(pdu) && @@ -968,7 +981,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: ; }