From 0808b9700926c068fbfea0a056553ba726b4b637 Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Wed, 9 Oct 2024 17:53:48 +0100 Subject: [PATCH] coap_net.c: New coap_send_recv() function Supports sending a PDU and waiting for the response PDU for subsequent analysis. Handles multiple block transfers. --- configure.ac | 1 + examples/coap-client.c | 60 ++++- examples/coap-rd.c | 1 + examples/coap-server.c | 1 + include/coap3/coap_net.h | 32 +++ include/coap3/coap_net_internal.h | 29 +++ include/coap3/coap_pdu_internal.h | 5 +- include/coap3/coap_session_internal.h | 5 + libcoap-3.map | 2 + libcoap-3.sym | 2 + man/Makefile.am | 2 +- man/coap_pdu_access.txt.in | 2 +- man/coap_pdu_setup.txt.in | 23 +- man/coap_pdu_transmit.txt.in | 358 ++++++++++++++++++++++++++ src/coap_block.c | 35 ++- src/coap_net.c | 199 +++++++++++++- src/coap_pdu.c | 7 + src/coap_session.c | 12 + 18 files changed, 732 insertions(+), 44 deletions(-) create mode 100644 man/coap_pdu_transmit.txt.in diff --git a/configure.ac b/configure.ac index 97733b5c1d..bb0e10a783 100644 --- a/configure.ac +++ b/configure.ac @@ -1353,6 +1353,7 @@ man/coap_observe.txt man/coap_oscore.txt man/coap_pdu_access.txt man/coap_pdu_setup.txt +man/coap_pdu_transmit.txt man/coap_persist.txt man/coap_proxy.txt man/coap_recovery.txt diff --git a/examples/coap-client.c b/examples/coap-client.c index bfa24c2202..46cdd2b711 100644 --- a/examples/coap-client.c +++ b/examples/coap-client.c @@ -163,6 +163,7 @@ static int quit = 0; static void handle_sigint(int signum COAP_UNUSED) { quit = 1; + coap_send_recv_terminate(); } static int @@ -387,10 +388,10 @@ nack_handler(coap_session_t *session COAP_UNUSED, * Response handler used for coap_send() responses */ static coap_response_t -message_handler(coap_session_t *session COAP_UNUSED, - const coap_pdu_t *sent, - const coap_pdu_t *received, - const coap_mid_t id COAP_UNUSED) { +response_handler(coap_session_t *session COAP_UNUSED, + const coap_pdu_t *sent, + const coap_pdu_t *received, + const coap_mid_t id COAP_UNUSED) { coap_opt_t *block_opt; coap_opt_iterator_t opt_iter; @@ -1672,6 +1673,7 @@ main(int argc, char **argv) { size_t data_len = 0; coap_addr_info_t *info_list = NULL; uint8_t cid_every = 0; + coap_pdu_t *resp_pdu; #ifndef _WIN32 struct sigaction sa; #endif @@ -1924,7 +1926,7 @@ main(int argc, char **argv) { coap_context_set_block_mode(ctx, block_mode); if (csm_max_message_size) coap_context_set_csm_max_message_size(ctx, csm_max_message_size); - coap_register_response_handler(ctx, message_handler); + coap_register_response_handler(ctx, response_handler); coap_register_event_handler(ctx, event_handler); coap_register_nack_handler(ctx, nack_handler); if (the_token.length > COAP_TOKEN_DEFAULT_MAX) @@ -1997,8 +1999,52 @@ main(int argc, char **argv) { if (coap_get_log_level() < COAP_LOG_DEBUG) coap_show_pdu(COAP_LOG_INFO, pdu); - if (coap_send(session, pdu) == COAP_INVALID_MID) { - coap_log_err("cannot send CoAP pdu\n"); + resp_pdu = NULL; + result = coap_send_recv(session, pdu, &resp_pdu, wait_ms); + if (result >= 0) { + if (response_handler(session, pdu, resp_pdu, coap_pdu_get_mid(resp_pdu)) + != COAP_RESPONSE_OK) { + coap_log_err("Response PDU issue\n"); + } + if (result < (int)wait_ms) { + wait_ms -= result; + } else { + wait_ms = 0; + quit = 1; + } + } else { + switch (result) { + case -1: + coap_log_info("coap_send_recv: Invalid timeout value %u\n", wait_ms); + break; + case -2: + coap_log_info("coap_send_recv: Failed to transmit PDU\n"); + break; + case -3: + /* Nack / Event handler already reported issue */ + break; + case -4: + coap_log_info("coap_send_recv: Internal coap_io_process() failed\n"); + break; + case -5: + coap_log_info("coap_send_recv: No response received within the timeout\n"); + break; + case -6: + coap_log_info("coap_send_recv: Terminated by user\n"); + break; + case -7: + coap_log_info("coap_send_recv: Client Mode code not enabled\n"); + break; + default: + coap_log_info("coap_send_recv: Invalid return value %d\n", result); + break; + } + quit = 1; + } + coap_delete_pdu(pdu); + coap_delete_pdu(resp_pdu); + + if (!doing_observe && repeat_count == 1) { quit = 1; } repeat_count--; diff --git a/examples/coap-rd.c b/examples/coap-rd.c index cf18392454..94e7f27a62 100644 --- a/examples/coap-rd.c +++ b/examples/coap-rd.c @@ -132,6 +132,7 @@ static int quit = 0; static void handle_sigint(int signum COAP_UNUSED) { quit = 1; + coap_send_recv_terminate(); } static void diff --git a/examples/coap-server.c b/examples/coap-server.c index 9065841d8c..3d0f8f6f70 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -181,6 +181,7 @@ static int example_data_media_type = COAP_MEDIATYPE_TEXT_PLAIN; static void handle_sigint(int signum COAP_UNUSED) { quit = 1; + coap_send_recv_terminate(); } #ifndef _WIN32 diff --git a/include/coap3/coap_net.h b/include/coap3/coap_net.h index b6d087a64d..3991ebbfe1 100644 --- a/include/coap3/coap_net.h +++ b/include/coap3/coap_net.h @@ -528,6 +528,38 @@ COAP_API coap_mid_t coap_send(coap_session_t *session, coap_pdu_t *pdu); #define coap_send_large(session, pdu) coap_send(session, pdu) +/* + * Send a request PDU and wait for the response PDU. + * + * @param session The CoAP session. + * @param request_pdu The requesting PDU. If this PDU contains the Observe + * option, the unsolocited responses will get handled by the + * defined response handler. This PDU must be freed off by the + * caller after processing. + * @param response_pdu If there is a response, the response PDU is put here. + * This PDU must be freed off by the caller after processing. + * @param timeout_ms Positive maximum number of milliseconds to wait for response + * packet following the request. If there is a large block transfer + * this timeout is for between each request and response. + * + * @return 0 or +ve Time in function in ms after successful transfer (which can be + * bigger than timeout_ms). + * -1 Invalid timeout parameter + * -2 Failed to transmit PDU + * -3 Nack or Event handler invoked, cancelling request + * -4 coap_io_process returned error (fail to re-lock or select()) + * -5 Response not received in the given time + * -6 Terminated by user + * -7 Client mode code not enabled + */ +COAP_API int coap_send_recv(coap_session_t *session, coap_pdu_t *request_pdu, + coap_pdu_t **response_pdu, uint32_t timeout_ms); + +/** + * Terminate any active coap_send_recv() sessions + */ +void coap_send_recv_terminate(void); + /** * Invokes the event handler of @p context for the given @p event and * @p data. diff --git a/include/coap3/coap_net_internal.h b/include/coap3/coap_net_internal.h index 109f75bf77..63f4b10908 100644 --- a/include/coap3/coap_net_internal.h +++ b/include/coap3/coap_net_internal.h @@ -795,6 +795,35 @@ int coap_io_process_with_fds_lkd(coap_context_t *ctx, uint32_t timeout_ms, */ coap_mid_t coap_send_lkd(coap_session_t *session, coap_pdu_t *pdu); +/* + * Send a request PDU and wait for the response PDU. + * + * Note: This function must be called in the locked state. + * + * @param session The CoAP session. + * @param request_pdu The requesting PDU. If this PDU contains the Observe + * option, the unsolocited responses will get handled by the + * defined response handler. This PDU must be freed off by the + * caller after processing. + * @param response_pdu If there is a response, the response PDU is put here. + * This PDU must be freed off by the caller after processing. + * @param timeout_ms Positive maximum number of milliseconds to wait for response + * packet following the request. If there is a large block transfer + * this timeout is for between each request and response. + * + * @return 0 or +ve Time in function in ms after successful transfer (which can be + * bigger than timeout_ms). + * -1 Invalid timeout parameter + * -2 Failed to transmit PDU + * -3 Nack or Event handler invoked, cancelling request + * -4 coap_io_process returned error (fail to re-lock or select()) + * -5 Response not received in the given time + * -6 Terminated by user + * -7 Client mode code not enabled + */ +int coap_send_recv_lkd(coap_session_t *session, coap_pdu_t *request_pdu, + coap_pdu_t **response_pdu, uint32_t timeout_ms); + /** * Sends an error response with code @p code for request @p request to @p dst. * @p opts will be passed to coap_new_error_response() to copy marked options diff --git a/include/coap3/coap_pdu_internal.h b/include/coap3/coap_pdu_internal.h index db6d5ae2ce..255a30aafc 100644 --- a/include/coap3/coap_pdu_internal.h +++ b/include/coap3/coap_pdu_internal.h @@ -145,6 +145,7 @@ struct coap_pdu_t { uint16_t max_opt; /**< highest option number in PDU */ uint32_t e_token_length; /**< length of Token space (includes leading extended bytes */ + unsigned ref; /**< reference count */ coap_bin_const_t actual_token; /**< Actual token in pdu */ size_t alloc_size; /**< allocated storage for token, options and payload */ @@ -165,13 +166,15 @@ struct coap_pdu_t { * to the pdu, and the pbuf stays exclusive to * this pdu. */ #endif - const uint8_t *body_data; /**< Holds ptr to re-assembled data or NULL */ + const uint8_t *body_data; /**< Holds ptr to re-assembled data or NULL. This + does not get released by coap_delete_pdu() */ size_t body_length; /**< Holds body data length */ size_t body_offset; /**< Holds body data offset */ size_t body_total; /**< Holds body data total size */ coap_lg_xmit_t *lg_xmit; /**< Holds ptr to lg_xmit if sending a set of blocks */ coap_session_t *session; /**< Session responsible for PDU or NULL */ + coap_binary_t *data_free; /**< Data to be freed off by coap_delete_pdu() */ }; /** diff --git a/include/coap3/coap_session_internal.h b/include/coap3/coap_session_internal.h index 39686e3c92..8eb4c46149 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -205,6 +205,7 @@ struct coap_session_t { coap_ext_token_check_t */ #if COAP_CLIENT_SUPPORT uint8_t negotiated_cid; /**< Set for a client if CID negotiated */ + uint8_t doing_send_recv; /**< Set if coap_send_recv() active */ #endif /* COAP_CLIENT_SUPPORT */ uint8_t is_dtls13; /**< Set if session is DTLS1.3 */ coap_mid_t remote_test_mid; /**< mid used for checking remote @@ -222,6 +223,10 @@ struct coap_session_t { #if COAP_SERVER_SUPPORT coap_bin_const_t *client_cid; /**< Contains client CID or NULL */ #endif /* COAP_SERVER_SUPPORT */ +#if COAP_CLIENT_SUPPORT + coap_pdu_t *resp_pdu; /**< PDU returned in coap_send_recv() call */ + coap_bin_const_t *req_token; /**< Token in request pdu of coap_send_recv() */ +#endif /* COAP_CLIENT_SUPPORT */ }; #if COAP_SERVER_SUPPORT diff --git a/libcoap-3.map b/libcoap-3.map index 4b498d2262..e2cb16e3e3 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -239,6 +239,8 @@ global: coap_send_ack; coap_send_error; coap_send_message_type; + coap_send_recv; + coap_send_recv_terminate; coap_send_rst; coap_server_is_supported; coap_session_disconnected; diff --git a/libcoap-3.sym b/libcoap-3.sym index ed32463f86..a9642884f2 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -237,6 +237,8 @@ coap_send coap_send_ack coap_send_error coap_send_message_type +coap_send_recv +coap_send_recv_terminate coap_send_rst coap_server_is_supported coap_session_disconnected diff --git a/man/Makefile.am b/man/Makefile.am index 61491dfb99..6ae33d53f5 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -39,6 +39,7 @@ TXT3 = coap_address.txt \ coap_oscore.txt \ coap_pdu_access.txt \ coap_pdu_setup.txt \ + coap_pdu_transmit.txt \ coap_persist.txt \ coap_proxy.txt \ coap_recovery.txt \ @@ -175,7 +176,6 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_pdu_setup.3" > coap_add_option.3 @echo ".so man3/coap_pdu_setup.3" > coap_add_data.3 @echo ".so man3/coap_pdu_setup.3" > coap_add_data_blocked_response.3 - @echo ".so man3/coap_pdu_setup.3" > coap_send.3 @echo ".so man3/coap_pdu_setup.3" > coap_path_into_optlist.3 @echo ".so man3/coap_pdu_setup.3" > coap_split_path.3 @echo ".so man3/coap_pdu_setup.3" > coap_query_into_optlist.3 diff --git a/man/coap_pdu_access.txt.in b/man/coap_pdu_access.txt.in index 0ded9eea23..2e897a572d 100644 --- a/man/coap_pdu_access.txt.in +++ b/man/coap_pdu_access.txt.in @@ -416,7 +416,7 @@ get_pdu_information(coap_pdu_t *pdu) { SEE ALSO -------- -*coap_block*(3) and *coap_pdu_setup*(3) +*coap_block*(3), *coap_pdu_setup*(3) and *coap_pdu_transmit*(3) FURTHER INFORMATION ------------------- diff --git a/man/coap_pdu_setup.txt.in b/man/coap_pdu_setup.txt.in index 1aa38a88df..694f7cd066 100644 --- a/man/coap_pdu_setup.txt.in +++ b/man/coap_pdu_setup.txt.in @@ -26,7 +26,6 @@ coap_add_optlist_pdu, coap_add_option, coap_add_data, coap_add_data_blocked_response, -coap_send, coap_path_into_optlist, coap_split_path, coap_query_into_optlist, @@ -82,8 +81,6 @@ const uint8_t *_data_);* coap_pdu_t *_response_, uint16_t _media_type_, int _maxage_, size_t _length_, const uint8_t *_data_);* -*coap_mid_t coap_send(coap_session_t *_session_, coap_pdu_t *_pdu_);* - *int coap_path_into_optlist(const uint8_t *_path_, size_t _length_, coap_option_num_t _optnum_, coap_optlist_t **_optlist_chain_);* @@ -558,19 +555,6 @@ by this function. *NOTE:* This function has been superseded by *coap_add_data_large_response*(). See *coap_block*(3). -PDU TRANSMIT FUNCTIONS ----------------------- - -*Function: coap_send()* - -The *coap_send*() function is used to initiate the transmission of the _pdu_ -associated with the _session_. The caller must not access or delete _pdu_ -after calling *coap_send*() - even if there is a return error. - -*NOTE:* For request handlers, returning from the request handler will cause -the response PDU to be transmitted as appropriate and there is no need to call -*coap_send*() to do this. - RETURN VALUES ------------- *coap_new_pdu*() and *coap_pdu_init*() return a newly created @@ -591,9 +575,6 @@ encoded (which can be 0 when encoding 0) or 0 on failure. *coap_add_option*() returns the size of option added, or 0 on failure. -*coap_send*() returns the CoAP message ID on success or -COAP_INVALID_MID on failure. - *coap_path_into_optlist*() and *coap_query_into_optlist*() return 1 on success or 0 on failure. @@ -752,8 +733,8 @@ hnd_get_time(coap_resource_t *resource, coap_session_t *session, SEE ALSO -------- -*coap_block*(3), *coap_observe*(3), *coap_oscore*(3), *coap_pdu_access*(3) -and *coap_resource*(3) +*coap_block*(3), *coap_observe*(3), *coap_oscore*(3), *coap_pdu_access*(3), +*coap_pdu_transmit*(3) and *coap_resource*(3) FURTHER INFORMATION ------------------- diff --git a/man/coap_pdu_transmit.txt.in b/man/coap_pdu_transmit.txt.in new file mode 100644 index 0000000000..435d3af353 --- /dev/null +++ b/man/coap_pdu_transmit.txt.in @@ -0,0 +1,358 @@ +// -*- mode:doc; -*- +// vim: set syntax=asciidoc tw=0 + +coap_pdu_transmit(3) +==================== +:doctype: manpage +:man source: coap_pdu_transmit +:man version: @PACKAGE_VERSION@ +:man manual: libcoap Manual + +NAME +---- +coap_pdu_transmit, +coap_send, +coap_send_recv, +coap_send_recv_terminate, +coap_delete_pdu +- Transmitting CoAP PDUs + +SYNOPSIS +-------- +*#include * + +*coap_mid_t coap_send(coap_session_t *_session_, coap_pdu_t *_pdu_);* + +*int coap_send_recv(coap_session_t *_session_, coap_pdu_t *_request_pdu_, +coap_pdu_t **_response_pdu_, uint32_t _timeout_ms_);* + +*void coap_send_recv_terminate(void);* + +*void coap_delete_pdu(coap_pdu_t *_pdu_);* + +For specific (D)TLS library support, link with +*-lcoap-@LIBCOAP_API_VERSION@-notls*, *-lcoap-@LIBCOAP_API_VERSION@-gnutls*, +*-lcoap-@LIBCOAP_API_VERSION@-openssl*, *-lcoap-@LIBCOAP_API_VERSION@-mbedtls*, +*-lcoap-@LIBCOAP_API_VERSION@-wolfssl* +or *-lcoap-@LIBCOAP_API_VERSION@-tinydtls*. Otherwise, link with +*-lcoap-@LIBCOAP_API_VERSION@* to get the default (D)TLS library support. + +DESCRIPTION +----------- +The CoAP PDU should be set up as described in *coap_pdu_setup*(3). This man page +describes how the PDU can be transmitted. + +FUNCTIONS +--------- + +*Function: coap_send()* + +The *coap_send*() function is used to initiate the transmission of the _pdu_ +associated with the _session_. The caller must not access or delete _pdu_ +after calling *coap_send*() - even if there is a return error. + +*NOTE:* For request handlers, returning from the request handler will cause +the response PDU to be transmitted as appropriate and so *coap_send*() must not +be called for the response PDU from within the request handler. + +*Function: coap_send_recv()* + +The *coap_send_recv*() function is used to initiate the transmission of the +_request_pdu_ associated with the _session_ and wait for a response. If there is +a response, _response_pdu_ will get updated with the received response. The request +may cause a block transfer and the response is only returned after all the blocks +have been transferred. + +_timeout_ms_ is set to the positive number of milliseconds to wait +for the response. If this is a blocked transfer, the timeout is used for between each +individual (re-)request and response. For CON type transmissions, this does not need to be +much bigger than (MAX_TRANSMIT_WAIT - MAX_TRANSMIT_SPAN) (default is 48 seconds). + +It is then the responsibility of the caller to analyze _response_pdu_ and following +analysis to release both the _request_pdu_ and _response_pdu_ by calling +*coap_delete_pdu*() for both the _request_pdu_ and _response_pdu_. + +*NOTE:* If *coap_send_recv*() is being used, *coap_send_recv_terminate*() must be +used to terminate the call from, say, a *signal*(2) handler, otherwise +*coap_send_recv*() may not return if there is a lot of traffic for _session_. + +*Function: coap_send_recv_terminate()* + +The *coap_send_recv_terminate*() function is used to terminate any outstanding +sessions currently using the *coap_send_recv*() function. Typically this would +be called within a signal or sigaction handler when the application is to close +down following the receipt of a signal. + +*Function: coap_delete_pdu()* + +The *coap_delete_pdu*() function is used to delete any created _pdu_. _pdu_ can +be NULL. + +*NOTE:* The *coap_send*() will always internally call *coap_delete_pdu*(), even +if there is an error, so *coap_delete*() must not be called after calling +*coap_send*() (or for that matter further code must not reference _pdu_). +However, *coap_send_recv*() does not delete either of _request_pdu_ or +_response_pdu_, so _request_pdu_ must always be deleted, as well as _response_pdu_ +if returned. + +RETURN VALUES +------------- +*coap_send*() returns the CoAP message ID on success or +COAP_INVALID_MID on failure. + +*coap_send_recv*() returns the following values: + +[source, c] +---- + 0 or +ve Time in function in ms after successful transfer (which can be + bigger than timeout_ms) + -1 Invalid timeout parameter + -2 Failed to transmit PDU + -3 Nack or Event handler invoked, cancelling request + -4 coap_io_process returned error (fail to re-lock or select()) + -5 Response not received in the given time + -6 Terminated by user + -7 Client mode code not enabled +---- + +EXAMPLES +-------- +*Setup PDU and Transmit* + +[source, c] +---- +#include + +static int +build_send_pdu(coap_context_t *context, coap_session_t *session, + uint8_t msgtype, uint8_t request_code, const char *path, + const char *query, unsigned char *data, size_t length, + int observe) { + coap_pdu_t *pdu; + uint8_t buf[8]; + size_t buflen; + coap_optlist_t *optlist_chain = NULL; + + /* Remove (void) definition if variable is used */ + (void)context; + + /* Create the pdu with the appropriate options */ + pdu = coap_pdu_init(msgtype, request_code, coap_new_message_id(session), + coap_session_max_pdu_size(session)); + if (!pdu) + return 0; + + /* + * Create unique token for this request for handling unsolicited / + * delayed responses + */ + coap_session_new_token(session, &buflen, buf); + if (!coap_add_token(pdu, buflen, buf)) { + coap_log_debug("cannot add token to request\n"); + goto error; + } + + if (path) { + /* Add in the Uri-Path options */ + if (!coap_path_into_optlist((const uint8_t *)path, strlen(path), + COAP_OPTION_URI_PATH, &optlist_chain)) + goto error; + } + + if (query) { + /* Add in the Uri-Query options */ + if (!coap_query_into_optlist((const uint8_t *)query, strlen(query), + COAP_OPTION_URI_QUERY, &optlist_chain)) + goto error; + } + + if (request_code == COAP_REQUEST_GET && observe) { + /* Indicate that we want to observe this resource */ + if (!coap_insert_optlist(&optlist_chain, + coap_new_optlist(COAP_OPTION_OBSERVE, + coap_encode_var_safe(buf, sizeof(buf), + COAP_OBSERVE_ESTABLISH), buf) + )) + goto error; + } + + /* ... Other code / options etc. ... */ + + /* Add in all the options (after internal sorting) to the pdu */ + if (!coap_add_optlist_pdu(pdu, &optlist_chain)) + goto error; + + if (data && length) { + /* Add in the specified data */ + if (!coap_add_data(pdu, length, data)) + goto error; + } + + if (coap_send(session, pdu) == COAP_INVALID_MID) + goto error; + return 1; + +error: + + if (pdu) + coap_delete_pdu(pdu); + return 0; + +} +---- + +*Setup PDU, then Send and Receive* + +[source, c] +---- +#include + +/* + * SIGINT/SIGTERM handler: terminate any outstanding coap_send_recv(). + * Handle configure using signal(2) or sigaction(2) */ +static void +handle_sigint(int signum COAP_UNUSED) { + coap_send_recv_terminate(); +} + +static int +build_send_recv_pdu(coap_context_t *context, coap_session_t *session, + uint8_t msgtype, uint8_t request_code, const char *path, + const char *query, unsigned char *data, size_t length, + int observe) { + coap_pdu_t *pdu; + coap_pdu_t *resp_pdu = NULL; + uint8_t buf[8]; + size_t buflen; + coap_optlist_t *optlist_chain = NULL; + int result; + uint32_t wait_ms = 60*1000; + int ret = 0; + + /* Remove (void) definition if variable is used */ + (void)context; + + /* Create the pdu with the appropriate options */ + pdu = coap_pdu_init(msgtype, request_code, coap_new_message_id(session), + coap_session_max_pdu_size(session)); + if (!pdu) + return 0; + + /* + * Create unique token for this request for handling unsolicited / + * delayed responses + */ + coap_session_new_token(session, &buflen, buf); + if (!coap_add_token(pdu, buflen, buf)) { + coap_log_debug("cannot add token to request\n"); + goto error; + } + + if (path) { + /* Add in the Uri-Path options */ + if (!coap_path_into_optlist((const uint8_t *)path, strlen(path), + COAP_OPTION_URI_PATH, &optlist_chain)) + goto error; + } + + if (query) { + /* Add in the Uri-Query options */ + if (!coap_query_into_optlist((const uint8_t *)query, strlen(query), + COAP_OPTION_URI_QUERY, &optlist_chain)) + goto error; + } + + if (request_code == COAP_REQUEST_GET && observe) { + /* Indicate that we want to observe this resource */ + if (!coap_insert_optlist(&optlist_chain, + coap_new_optlist(COAP_OPTION_OBSERVE, + coap_encode_var_safe(buf, sizeof(buf), + COAP_OBSERVE_ESTABLISH), buf) + )) + goto error; + } + + /* ... Other code / options etc. ... */ + + /* Add in all the options (after internal sorting) to the pdu */ + if (!coap_add_optlist_pdu(pdu, &optlist_chain)) + goto error; + + if (data && length) { + /* Add in the specified data */ + if (!coap_add_data(pdu, length, data)) + goto error; + } + + result = coap_send_recv(session, pdu, &resp_pdu, wait_ms); + if (result >= 0) { + /* ... Process response PDU ... */ + + if (result < (int)wait_ms) { + wait_ms -= result; + } else { + wait_ms = 0; + } + ret = 1; + } else { + switch (result) { + case -1: + coap_log_err("coap_send_recv: Invalid timeout value %u\n", wait_ms); + break; + case -2: + coap_log_err("coap_send_recv: Failed to transmit PDU\n"); + break; + case -3: + coap_log_err("coap_send_recv: Critical Nack / Event occurred\n"); + break; + case -4: + coap_log_err("coap_send_recv: Internal coap_io_process() failed\n"); + break; + case -5: + coap_log_err("coap_send_recv: No response received within the timeout\n"); + break; + case -6: + coap_log_err("coap_send_recv: Terminated by user\n"); + break; + case -7: + coap_log_err("coap_send_recv: Client Mode code not enabled\n"); + break; + default: + coap_log_err("coap_send_recv: Invalid return value %d\n", result); + break; + } + ret = 0; + } + +error: + + coap_delete_pdu(pdu); + coap_delete_pdu(resp_pdu); + + return ret; +} +---- + +SEE ALSO +-------- +*coap_pdu_setup*(3) and *coap_pdu_access*(3) + +FURTHER INFORMATION +------------------- +See + +"https://rfc-editor.org/rfc/rfc7252[RFC7252: The Constrained Application Protocol (CoAP)]" + +for further information. + +BUGS +---- +Please raise an issue on GitHub at +https://github.com/obgm/libcoap/issues to report any bugs. + +Please raise a Pull Request at https://github.com/obgm/libcoap/pulls +for any fixes. + +AUTHORS +------- +The libcoap project diff --git a/src/coap_block.c b/src/coap_block.c index cf0d27cfe1..983d2ba87d 100644 --- a/src/coap_block.c +++ b/src/coap_block.c @@ -4038,7 +4038,7 @@ coap_handle_response_get_block(coap_context_t *context, rcvd->body_total = size2; #endif /* ! COAP_Q_BLOCK_SUPPORT */ } - if (context->response_handler) { + if (context->response_handler || session->doing_send_recv) { coap_response_t ret; /* need to put back original token into rcvd */ @@ -4054,18 +4054,37 @@ coap_handle_response_get_block(coap_context_t *context, lg_crcv->app_token->s); coap_remove_option(sent, lg_crcv->block_option); } - coap_lock_callback_ret_release(ret, session->context, - context->response_handler(session, sent, rcvd, - rcvd->mid), - /* context is being freed off */ - assert(0)); - if (ret == COAP_RESPONSE_FAIL) { - coap_send_rst_lkd(session, rcvd); + if (!session->doing_send_recv || !session->req_token || + !coap_binary_equal(session->req_token, &rcvd->actual_token)) { + if (context->response_handler) { + coap_lock_callback_ret_release(ret, session->context, + context->response_handler(session, sent, rcvd, + rcvd->mid), + /* context is being freed off */ + assert(0)); + } else { + ret = COAP_RESPONSE_OK; + } + if (ret == COAP_RESPONSE_FAIL && rcvd->type != COAP_MESSAGE_ACK) { + coap_send_rst_lkd(session, rcvd); + session->last_con_handler_res = COAP_RESPONSE_FAIL; + } else { + coap_send_ack_lkd(session, rcvd); + session->last_con_handler_res = COAP_RESPONSE_OK; + } } else { + /* processing coap_send_recv() call */ + session->resp_pdu = rcvd; + rcvd->ref++; + /* Will get freed off when PDU is freed off */ + rcvd->data_free = lg_crcv->body_data; + lg_crcv->body_data = NULL; coap_send_ack_lkd(session, rcvd); + session->last_con_handler_res = COAP_RESPONSE_OK; } } else { coap_send_ack_lkd(session, rcvd); + session->last_con_handler_res = COAP_RESPONSE_OK; } ack_rst_sent = 1; if (lg_crcv->observe_set == 0) { diff --git a/src/coap_net.c b/src/coap_net.c index 42af27a986..cd93e47459 100644 --- a/src/coap_net.c +++ b/src/coap_net.c @@ -1905,6 +1905,150 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { return COAP_INVALID_MID; } +static int send_recv_terminate = 0; + +void +coap_send_recv_terminate(void) { + send_recv_terminate = 1; +} + +COAP_API int +coap_send_recv(coap_session_t *session, coap_pdu_t *request_pdu, + coap_pdu_t **response_pdu, uint32_t timeout_ms) { + int ret; + + coap_lock_lock(session->context, return 0); + ret = coap_send_recv_lkd(session, request_pdu, response_pdu, timeout_ms); + coap_lock_unlock(session->context); + return ret; +} + +/* + * Return 0 or +ve Time in function in ms after successful transfer + * -1 Invalid timeout parameter + * -2 Failed to transmit PDU + * -3 Nack or Event handler invoked, cancelling request + * -4 coap_io_process returned error (fail to re-lock or select()) + * -5 Response not received in the given time + * -6 Terminated by user + * -7 Client mode code not enabled + */ +int +coap_send_recv_lkd(coap_session_t *session, coap_pdu_t *request_pdu, + coap_pdu_t **response_pdu, uint32_t timeout_ms) { +#if COAP_CLIENT_SUPPORT + coap_mid_t mid = COAP_INVALID_MID; + uint32_t rem_timeout = timeout_ms; + uint32_t block_mode = session->block_mode; + int ret = 0; + coap_tick_t now; + coap_tick_t start; + coap_tick_t ticks_so_far; + uint32_t time_so_far_ms; + + coap_ticks(&start); + assert(request_pdu); + + coap_lock_check_locked(session->context); + + session->resp_pdu = NULL; + session->req_token = coap_new_bin_const(request_pdu->actual_token.s, + request_pdu->actual_token.length); + + if (timeout_ms == COAP_IO_NO_WAIT || timeout_ms == COAP_IO_WAIT) { + ret = -1; + goto fail; + } + if (session->state == COAP_SESSION_STATE_NONE) { + ret = -3; + goto fail; + } + + session->block_mode |= COAP_BLOCK_SINGLE_BODY; + session->doing_send_recv = 1; + /* So the user needs to delete the PDU */ + request_pdu->ref++; + mid = coap_send_lkd(session, request_pdu); + if (mid == COAP_INVALID_MID) { + if (!session->doing_send_recv) + ret = -3; + else + ret = -2; + goto fail; + } + + /* Wait for the response to come in */ + while (rem_timeout > 0 && session->doing_send_recv && !session->resp_pdu) { + if (send_recv_terminate) { + ret = -6; + goto fail; + } + ret = coap_io_process_lkd(session->context, rem_timeout); + if (ret < 0) { + ret = -4; + goto fail; + } + /* timeout_ms is for timeout between specific request and response */ + coap_ticks(&now); + ticks_so_far = now - session->last_rx_tx; + time_so_far_ms = (uint32_t)((ticks_so_far * 1000) / COAP_TICKS_PER_SECOND); + if (time_so_far_ms >= timeout_ms) { + rem_timeout = 0; + } else { + rem_timeout = timeout_ms - time_so_far_ms; + } + if (session->state != COAP_SESSION_STATE_ESTABLISHED) { + /* To pick up on (D)TLS setup issues */ + coap_ticks(&now); + ticks_so_far = now - start; + time_so_far_ms = (uint32_t)((ticks_so_far * 1000) / COAP_TICKS_PER_SECOND); + if (time_so_far_ms >= timeout_ms) { + rem_timeout = 0; + } else { + rem_timeout = timeout_ms - time_so_far_ms; + } + } + } + + if (rem_timeout) { + coap_ticks(&now); + ticks_so_far = now - start; + time_so_far_ms = (uint32_t)((ticks_so_far * 1000) / COAP_TICKS_PER_SECOND); + ret = time_so_far_ms; + /* Give PDU to user */ + *response_pdu = session->resp_pdu; + session->resp_pdu = NULL; + if (*response_pdu == NULL) { + ret = -3; + } + } else { + /* If there is a resp_pdu, it will get cleared below */ + ret = -5; + } + +fail: + session->block_mode = block_mode; + session->doing_send_recv = 0; + if (session->resp_pdu && session->resp_pdu->ref) + session->resp_pdu->ref--; + coap_delete_pdu(session->resp_pdu); + session->resp_pdu = NULL; + coap_delete_bin_const(session->req_token); + session->req_token = NULL; + return ret; + +#else /* !COAP_CLIENT_SUPPORT */ + + (void)session; + (void)timeout_ms; + (void)request_pdu; + coap_log_warn("coap_send_recv: Client mode not supported\n"); + *response_pdu = NULL; + return -7; + +#endif /* ! COAP_CLIENT_SUPPORT */ +} + coap_mid_t coap_retransmit(coap_context_t *context, coap_queue_t *node) { if (!context || !node) @@ -2001,6 +2145,9 @@ coap_retransmit(coap_context_t *context, coap_queue_t *node) { if (node->pdu->type == COAP_MESSAGE_CON) { coap_handle_nack(node->session, node->pdu, COAP_NACK_TOO_MANY_RETRIES, node->id); } +#if COAP_CLIENT_SUPPORT + node->session->doing_send_recv = 0; +#endif /* COAP_CLIENT_SUPPORT */ coap_delete_node_lkd(node); return COAP_INVALID_MID; } @@ -3732,7 +3879,14 @@ handle_response(coap_context_t *context, coap_session_t *session, session->doing_first = 0; /* Call application-specific response handler when available. */ - if (context->response_handler) { + if (session->doing_send_recv && session->req_token && + coap_binary_equal(session->req_token, &rcvd->actual_token)) { + /* processing coap_send_recv() call */ + session->resp_pdu = rcvd; + rcvd->ref++; + coap_send_ack_lkd(session, rcvd); + session->last_con_handler_res = COAP_RESPONSE_OK; + } else if (context->response_handler) { coap_response_t ret; coap_lock_callback_ret_release(ret, context, @@ -4336,19 +4490,53 @@ coap_handle_event(coap_context_t *context, coap_event_t event, int coap_handle_event_lkd(coap_context_t *context, coap_event_t event, coap_session_t *session) { + int ret = 0; + coap_log_debug("***EVENT: %s\n", coap_event_name(event)); if (context->handle_event) { - int ret; - coap_lock_callback_ret(ret, context, context->handle_event(session, event)); #if COAP_PROXY_SUPPORT if (event == COAP_EVENT_SERVER_SESSION_DEL) coap_proxy_remove_association(session, 0); #endif /* COAP_PROXY_SUPPORT */ - return ret; +#if COAP_CLIENT_SUPPORT + switch (event) { + case COAP_EVENT_DTLS_CLOSED: + case COAP_EVENT_TCP_CLOSED: + case COAP_EVENT_SESSION_CLOSED: + case COAP_EVENT_OSCORE_DECRYPTION_FAILURE: + case COAP_EVENT_OSCORE_NOT_ENABLED: + case COAP_EVENT_OSCORE_NO_PROTECTED_PAYLOAD: + case COAP_EVENT_OSCORE_NO_SECURITY: + case COAP_EVENT_OSCORE_INTERNAL_ERROR: + case COAP_EVENT_OSCORE_DECODE_ERROR: + case COAP_EVENT_WS_PACKET_SIZE: + case COAP_EVENT_WS_CLOSED: + case COAP_EVENT_BAD_PACKET: + /* Those that are deemed fatal to end sending a request */ + session->doing_send_recv = 0; + break; + case COAP_EVENT_DTLS_CONNECTED: + case COAP_EVENT_DTLS_RENEGOTIATE: + case COAP_EVENT_DTLS_ERROR: + case COAP_EVENT_TCP_CONNECTED: + case COAP_EVENT_TCP_FAILED: + case COAP_EVENT_SESSION_CONNECTED: + case COAP_EVENT_SESSION_FAILED: + case COAP_EVENT_PARTIAL_BLOCK: + case COAP_EVENT_XMIT_BLOCK_FAIL: + case COAP_EVENT_SERVER_SESSION_NEW: + case COAP_EVENT_SERVER_SESSION_DEL: + case COAP_EVENT_MSG_RETRANSMITTED: + case COAP_EVENT_WS_CONNECTED: + case COAP_EVENT_KEEPALIVE_FAILURE: + default: + break; + } +#endif /* COAP_CLIENT_SUPPORT */ } - return 0; + return ret; } COAP_API int @@ -4467,6 +4655,7 @@ coap_startup(void) { resource_uri_wellknown.flags = COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT; resource_uri_wellknown.uri_path = &well_known; #endif /* COAP_SERVER_SUPPORT */ + send_recv_terminate = 0; } void diff --git a/src/coap_pdu.c b/src/coap_pdu.c index 569ac0d70b..f3c9d57924 100644 --- a/src/coap_pdu.c +++ b/src/coap_pdu.c @@ -47,6 +47,7 @@ coap_pdu_clear(coap_pdu_t *pdu, size_t size) { pdu->alloc_size = size; pdu->type = 0; pdu->code = 0; + pdu->ref = 0; pdu->hdr_size = 0; pdu->actual_token.length = 0; pdu->e_token_length = 0; @@ -62,6 +63,7 @@ coap_pdu_clear(coap_pdu_t *pdu, size_t size) { pdu->body_total = 0; pdu->lg_xmit = NULL; pdu->session = NULL; + pdu->data_free = NULL; } #ifdef WITH_LWIP @@ -180,12 +182,17 @@ coap_new_pdu_lkd(coap_pdu_type_t type, coap_pdu_code_t code, void coap_delete_pdu(coap_pdu_t *pdu) { if (pdu != NULL) { + if (pdu->ref) { + pdu->ref--; + return; + } #ifdef WITH_LWIP pbuf_free(pdu->pbuf); #else if (pdu->token != NULL) coap_free_type(COAP_PDU_BUF, pdu->token - pdu->max_hdr_size); #endif + coap_delete_binary(pdu->data_free); coap_free_type(COAP_PDU, pdu); } } diff --git a/src/coap_session.c b/src/coap_session.c index 01ae55eb75..cee303ead0 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -587,6 +587,7 @@ coap_session_free(coap_session_t *session) { if (session->context->sessions) SESSIONS_DELETE(session->context->sessions, session); } + coap_delete_bin_const(session->req_token); #endif /* COAP_CLIENT_SUPPORT */ coap_delete_bin_const(session->last_token); coap_delete_bin_const(session->echo); @@ -920,6 +921,11 @@ coap_handle_nack(coap_session_t *session, coap_update_token(sent, token.length, token.s); } } +#if COAP_CLIENT_SUPPORT + if (reason != COAP_NACK_ICMP_ISSUE) { + session->doing_send_recv = 0; + } +#endif /* COAP_CLIENT_SUPPORT */ } COAP_API void @@ -969,6 +975,9 @@ coap_session_disconnected_lkd(coap_session_t *session, coap_nack_reason_t reason sent_nack = 1; } +#if COAP_CLIENT_SUPPORT + session->doing_send_recv = 0; +#endif /* COAP_CLIENT_SUPPORT */ coap_delete_node_lkd(q); } } @@ -1014,6 +1023,9 @@ coap_session_disconnected_lkd(coap_session_t *session, coap_nack_reason_t reason q->next = NULL; coap_log_debug("** %s: mid=0x%04x: not transmitted after disconnect\n", coap_session_str(session), q->id); +#if COAP_CLIENT_SUPPORT + session->doing_send_recv = 0; +#endif /* COAP_CLIENT_SUPPORT */ coap_delete_node_lkd(q); }