From 69fe0b738ac3d0e5d682513dbfa0b837c9b04e3a Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Thu, 9 Dec 2021 11:59:50 +0000 Subject: [PATCH] draft-ietf-core-echo-request-tag: Add in support 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 Echo option, then the libcoap client retransmits the request adding in the received option. If the server responds normally with an Echo option, then the next request from the client (added by libcoap) will contain that request option. It is the responsibility of the server application to add in the Echo option unless this is triggered by OSCORE. --- README.md | 2 + doc/main.md | 2 + include/coap3/block.h | 2 +- include/coap3/coap_block_internal.h | 3 +- include/coap3/coap_session_internal.h | 4 + include/coap3/pdu.h | 4 + man/coap.txt.in | 2 + man/coap_block.txt.in | 2 +- man/coap_pdu_setup.txt.in | 2 + src/block.c | 144 +++++++++++++++++++++++++- src/coap_debug.c | 11 +- src/net.c | 14 ++- src/pdu.c | 44 +++++--- 13 files changed, 205 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 712d66e8d5..daf6ef1246 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ The following RFCs are supported * RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option +* draft-ietf-core-echo-request-tag-14: 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..242cdb63e6 100644 --- a/doc/main.md +++ b/doc/main.md @@ -36,6 +36,8 @@ The following RFCs are supported * RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option +* draft-ietf-core-echo-request-tag-14: 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 3ff0eaf63a..4b9a588183 100644 --- a/include/coap3/block.h +++ b/include/coap3/block.h @@ -213,7 +213,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 RTAG 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 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_session_internal.h b/include/coap3/coap_session_internal.h index 72fd0f9728..91d3ad1d2f 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -134,6 +134,10 @@ struct coap_session_t { sesison */ uint8_t block_mode; /**< Zero or more COAP_BLOCK_ or'd options */ uint64_t tx_token; /**< Next token number to use */ + uint64_t tx_rtag; /**< Next Request-Tag number to use */ + uint8_t echo[8]; /**< Echo value to send with next request */ + uint32_t echo_len; /**< Echo value length */ + int echo_send; /**< Send Echo in next request */ }; #if COAP_SERVER_SUPPORT diff --git a/include/coap3/pdu.h b/include/coap3/pdu.h index 5bcf2a3da5..61dc56ebbd 100644 --- a/include/coap3/pdu.h +++ b/include/coap3/pdu.h @@ -130,6 +130,10 @@ typedef enum coap_request_t { #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 */ +/* selected option types from draft-ietf-core-echo-request-tag */ +#define COAP_OPTION_ECHO 252 /* _N__E_U, opaque, 0-40 B, RFC???? */ +#define COAP_OPTION_RTAG 292 /* ___RE_U, opaque, 0-8 B, RFC???? */ + #define COAP_MAX_OPT 65535 /**< the highest option number we know */ /* CoAP result codes (HTTP-Code / 100 * 40 + HTTP-Code % 100) */ diff --git a/man/coap.txt.in b/man/coap.txt.in index d109a8751c..40bcf5e212 100644 --- a/man/coap.txt.in +++ b/man/coap.txt.in @@ -68,6 +68,8 @@ See "RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option" +"draft-ietf-core-echo-request-tag-14: 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 a30c87de0c..102928fc02 100644 --- a/man/coap_block.txt.in +++ b/man/coap_block.txt.in @@ -165,7 +165,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 RTAG if the data does not fit in a single PDU. When the block has been acknowledged by the peer, the library will then send the next block of data until all the data has been transmitted. This function must only be called once per _pdu_. The _data_ passed to the diff --git a/man/coap_pdu_setup.txt.in b/man/coap_pdu_setup.txt.in index 308ad128cf..b77d77b304 100644 --- a/man/coap_pdu_setup.txt.in +++ b/man/coap_pdu_setup.txt.in @@ -301,7 +301,9 @@ COAP_OPTION_SIZE2 28 /* __N_E_U, uint, 0-4 B, RFC7959 */ COAP_OPTION_PROXY_URI 35 /* CU-___U, String, 1-1034 B, RFC7252 */ COAP_OPTION_PROXY_SCHEME 39 /* CU-___U, String, 1-255 B, RFC7252 */ COAP_OPTION_SIZE1 60 /* __N_E_U, uint, 0-4 B, RFC7252 */ +COAP_OPTION_ECHO 252 /* _N__E_U, opaque, 0-40 B, RFC???? */ COAP_OPTION_NORESPONSE 258 /* _U-_E_U, uint, 0-1 B, RFC7967 */ +COAP_OPTION_RTAG 292 /* ___RE_U, opaque, 0-8 B, RFC???? */ ---- See FURTHER INFORMATION as to how to get the latest list. diff --git a/src/block.c b/src/block.c index 0563fbfe73..619d2107c2 100644 --- a/src/block.c +++ b/src/block.c @@ -490,6 +490,11 @@ coap_add_data_large_internal(coap_session_t *session, coap_encode_var_safe(buf, sizeof(buf), (unsigned int)length), buf); + coap_update_option(pdu, + COAP_OPTION_RTAG, + coap_encode_var_safe8(buf, sizeof(buf), + ++session->tx_rtag), + buf); } else { /* @@ -1294,12 +1299,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; } @@ -1341,6 +1358,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 = rtag_length; + memcpy(p->rtag, rtag, rtag_length); + p->rtag_set = 1; + } p->body_data = NULL; LL_PREPEND(session->lg_srcv, p); } @@ -1470,6 +1492,94 @@ 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 = 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: + session->echo_len = coap_opt_length(opt); + session->echo_send = 1; + memcpy(session->echo, coap_opt_value(opt), session->echo_len); + } + } + 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) { + session->echo_len = coap_opt_length(opt); + session->echo_send = 1; + memcpy(session->echo, coap_opt_value(opt), session->echo_len); + } +} + /* * 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 @@ -1485,7 +1595,9 @@ 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, @@ -1529,6 +1641,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 BLOCK ACK @@ -1574,6 +1687,10 @@ coap_handle_response_send_block(coap_session_t *session, coap_pdu_t *rcvd) { 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) { LL_FOREACH(session->lg_crcv, lg_crcv) { @@ -1664,10 +1781,11 @@ coap_block_build_body(coap_binary_t *body_data, size_t length, */ 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}; @@ -1707,6 +1825,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, @@ -1943,6 +2062,11 @@ coap_handle_response_get_block(coap_context_t *context, } } } + 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: /* lg_crcv no longer required - cache it */ @@ -1993,6 +2117,16 @@ 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 1b5aa19559..f7d3ae2495 100644 --- a/src/coap_debug.c +++ b/src/coap_debug.c @@ -356,12 +356,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, "RTag" } }; 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[] = { @@ -634,6 +636,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"); @@ -856,7 +861,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/net.c b/src/net.c index 576193c184..83d9dfa419 100644 --- a/src/net.c +++ b/src/net.c @@ -1248,6 +1248,12 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { } } + if (session->echo_send) { + if (coap_insert_option(pdu, COAP_OPTION_ECHO, session->echo_len, + session->echo)) + session->echo_send = 0; + } + if (!coap_pdu_encode_header(pdu, session->proto)) { goto error; } @@ -2842,7 +2848,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 @@ -2909,8 +2915,8 @@ 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. @@ -2919,7 +2925,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 c472c4c83f..872f36015e 100644 --- a/src/pdu.c +++ b/src/pdu.c @@ -415,6 +415,28 @@ 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) { @@ -446,6 +468,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)) @@ -553,22 +578,7 @@ coap_add_option(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) && @@ -938,7 +948,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: ; }