diff --git a/CMakeLists.txt b/CMakeLists.txt index 599d35d0..304e0cbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -953,6 +953,8 @@ if(ENABLE_TESTS) ${CMAKE_CURRENT_LIST_DIR}/tests/test_common.h ${CMAKE_CURRENT_LIST_DIR}/tests/test_encode.c ${CMAKE_CURRENT_LIST_DIR}/tests/test_encode.h + ${CMAKE_CURRENT_LIST_DIR}/tests/test_block.c + ${CMAKE_CURRENT_LIST_DIR}/tests/test_block.h ${CMAKE_CURRENT_LIST_DIR}/tests/test_error_response.c ${CMAKE_CURRENT_LIST_DIR}/tests/test_error_response.h ${CMAKE_CURRENT_LIST_DIR}/tests/test_options.c diff --git a/Makefile.am b/Makefile.am index 7446d09f..5309894a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -152,6 +152,7 @@ EXTRA_DIST = \ src/coap_strm_contiki.c \ src/coap_strm_lwip.c \ src/coap_strm_riot.c \ + tests/test_block.h \ tests/test_common.h \ tests/test_error_response.h \ tests/test_encode.h \ diff --git a/src/coap_block.c b/src/coap_block.c index 1bd789ec..321953cc 100644 --- a/src/coap_block.c +++ b/src/coap_block.c @@ -621,7 +621,13 @@ coap_cancel_observe_lkd(coap_session_t *session, coap_binary_t *token, lg_crcv->o_blk_size), buf); } - if (lg_crcv->obs_data) { + if (lg_crcv->obs_data && lg_crcv->obs_data->get_func) { + coap_add_data_large_request_app_lkd(session, pdu, + lg_crcv->obs_data->length, + NULL, + lg_crcv->obs_data->get_func, + lg_crcv->obs_data->app_ptr); + } else if (lg_crcv->obs_data) { coap_add_data_large_request_lkd(session, pdu, lg_crcv->obs_data->length, lg_crcv->obs_data->data, NULL, NULL); @@ -799,6 +805,31 @@ coap_find_lg_xmit_response(const coap_session_t *session, } #endif /* COAP_SERVER_SUPPORT */ +/* + * Add up to length bytes of the body starting at offset, as provided by + * the application's get_func, to pdu. + */ +static int +add_get_func_data(coap_session_t *session, coap_pdu_t *pdu, + coap_get_large_data_t get_func, void *app_ptr, + size_t offset, size_t length) { + uint8_t *l_data; + size_t l_length; + int ret = 1; + + if (length == 0) + return 1; + l_data = coap_malloc_type(COAP_STRING, length); + if (!l_data) { + coap_log_warn("out of memory allocating %" PRIuS " bytes for block data\n", length); + return 0; + } + if (get_func(session, length, offset, l_data, &l_length, app_ptr)) + ret = coap_add_data(pdu, l_length, l_data); + coap_free_type(COAP_STRING, l_data); + return ret; +} + static int coap_add_data_large_internal(coap_session_t *session, const coap_pdu_t *request, @@ -1094,8 +1125,14 @@ coap_add_data_large_internal(coap_session_t *session, rem = chunk; if (chunk > length - block.num * chunk) rem = length - block.num * chunk; - if (!coap_add_data(pdu, rem, &data[block.num * chunk])) - goto fail; + if (get_func) { + if (!add_get_func_data(session, pdu, get_func, app_ptr, + block.num * chunk, rem)) + goto fail; + } else { + if (!coap_add_data(pdu, rem, &data[block.num * chunk])) + goto fail; + } } if (release_func) { coap_lock_callback(release_func(session, app_ptr)); @@ -1329,16 +1366,8 @@ coap_add_data_large_internal(coap_session_t *session, } add_data: if (get_func) { - uint8_t *l_data = coap_malloc_type(COAP_STRING, length); - size_t l_length; - - if (get_func(session, length, 0, l_data, &l_length, app_ptr)) { - if (!coap_add_data(pdu, l_length, l_data)) { - coap_free_type(COAP_STRING, l_data); - goto fail; - } - coap_free_type(COAP_STRING, l_data); - } + if (!add_get_func_data(session, pdu, get_func, app_ptr, 0, length)) + goto fail; } else { if (!coap_add_data(pdu, length, data)) goto fail; @@ -4092,6 +4121,12 @@ check_freshness(coap_session_t *session, coap_pdu_t *rcvd, coap_pdu_t *sent, uint8_t ltoken[8]; size_t ltoken_len; uint64_t token; +#if COAP_CONSTRAINED_STACK + /* Protected by global_lock if needed */ + static uint8_t l_data[1024]; +#else /* ! COAP_CONSTRAINED_STACK */ + uint8_t l_data[1024]; +#endif /* ! COAP_CONSTRAINED_STACK */ if (sent) { if (coap_get_data(sent, &data_len, &data)) @@ -4101,10 +4136,20 @@ check_freshness(coap_session_t *session, coap_pdu_t *rcvd, coap_pdu_t *sent, if (lg_xmit->data_info->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_info->data[offset]; - data_len = (lg_xmit->data_info->length - offset) > blk_size ? blk_size : - lg_xmit->data_info->length - offset; + if (lg_xmit->data_info->get_func) { + assert(blk_size <= sizeof(l_data)); + if (lg_xmit->data_info->get_func(session, blk_size, offset, l_data, + &data_len, + lg_xmit->data_info->app_ptr)) { + have_data = 1; + data = l_data; + } + } else { + have_data = 1; + data = &lg_xmit->data_info->data[offset]; + data_len = (lg_xmit->data_info->length - offset) > blk_size ? blk_size : + lg_xmit->data_info->length - offset; + } } } else { /* lg_crcv */ sent = lg_crcv->sent_pdu; diff --git a/tests/Makefile.am b/tests/Makefile.am index 9c476c42..15afcea2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -18,6 +18,7 @@ noinst_PROGRAMS = \ testdriver_SOURCES = \ testdriver.c \ + test_block.c \ test_error_response.c \ test_encode.c \ test_options.c \ diff --git a/tests/test_block.c b/tests/test_block.c new file mode 100644 index 00000000..f742c365 --- /dev/null +++ b/tests/test_block.c @@ -0,0 +1,223 @@ +/* libcoap unit tests + * + * Copyright (C) 2026 Alejandro Pacheco + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#include "test_common.h" +#include "test_block.h" + +#if COAP_CLIENT_SUPPORT +#include + +#define ReturnIf_CU_ASSERT_PTR_NOT_NULL(value) \ + CU_ASSERT_PTR_NOT_NULL(value); \ + if ((void*)value == NULL) return; + +#if COAP_SERVER_SUPPORT +static size_t large_app_calls; + +static int +large_app_get_data(coap_session_t *s COAP_UNUSED, size_t max, size_t offset, + uint8_t *data, size_t *length, void *app_ptr COAP_UNUSED) { + large_app_calls++; + if (offset >= 4096) + return 0; + *length = max < 4096 - offset ? max : 4096 - offset; + memset(data, 'x', *length); + return 1; +} + +/* A 4.01 with Echo for a request built by coap_add_data_large_request_app() + * must get the re-sent block from the application callback. */ +static void +t_block_echo_get_func(void) { + coap_context_t *l_ctx; + coap_session_t *l_session; + coap_address_t saddr; + coap_pdu_t *pdu; + size_t calls; + uint8_t token = 0x5a; + uint8_t rsp[] = { 0x51, 0x81, 0x12, 0x34, 0x5a, + 0xd8, 0xef, 1, 2, 3, 4, 5, 6, 7, 8 + }; + + coap_address_init(&saddr); + saddr.size = sizeof(struct sockaddr_in6); + saddr.addr.sin6.sin6_family = AF_INET6; + saddr.addr.sin6.sin6_addr = in6addr_loopback; + saddr.addr.sin6.sin6_port = htons(20011); + + l_ctx = coap_new_context(NULL); + ReturnIf_CU_ASSERT_PTR_NOT_NULL(l_ctx); + coap_context_set_block_mode(l_ctx, COAP_BLOCK_USE_LIBCOAP); + /* Something to receive the request so that the re-send succeeds */ + CU_ASSERT_PTR_NOT_NULL(coap_new_endpoint(l_ctx, &saddr, COAP_PROTO_UDP)); + l_session = coap_new_client_session(l_ctx, NULL, &saddr, COAP_PROTO_UDP); + CU_ASSERT_PTR_NOT_NULL(l_session); + if (!l_session) + goto done; + + pdu = coap_pdu_init(COAP_MESSAGE_NON, COAP_REQUEST_CODE_PUT, + coap_new_message_id(l_session), + coap_session_max_pdu_size(l_session)); + CU_ASSERT_PTR_NOT_NULL(pdu); + if (!pdu) + goto done; + coap_add_token(pdu, 1, &token); + CU_ASSERT(coap_add_data_large_request_app(l_session, pdu, 4096, NULL, + large_app_get_data, NULL) == 1); + CU_ASSERT(coap_send(l_session, pdu) != COAP_INVALID_MID); + + calls = large_app_calls; + coap_lock_lock(goto done); + coap_handle_dgram(l_ctx, l_session, rsp, sizeof(rsp)); + coap_lock_unlock(); + CU_ASSERT(large_app_calls > calls); + +done: + coap_free_context(l_ctx); +} + +/* Cancelling an observe whose request body was built by + * coap_add_data_large_request_app() must get the body from the + * application callback again. */ +static void +t_block_cancel_observe_get_func(void) { + coap_context_t *l_ctx; + coap_session_t *l_session; + coap_address_t saddr; + coap_pdu_t *pdu; + coap_binary_t tok; + size_t calls; + uint8_t token = 0x5b; + uint8_t obs = 0; + /* NON 2.05, token 0x5b, Observe 1 */ + uint8_t rsp[] = { 0x51, 0x45, 0x12, 0x35, 0x5b, 0x61, 0x01 }; + + coap_address_init(&saddr); + saddr.size = sizeof(struct sockaddr_in6); + saddr.addr.sin6.sin6_family = AF_INET6; + saddr.addr.sin6.sin6_addr = in6addr_loopback; + saddr.addr.sin6.sin6_port = htons(20012); + + l_ctx = coap_new_context(NULL); + ReturnIf_CU_ASSERT_PTR_NOT_NULL(l_ctx); + coap_context_set_block_mode(l_ctx, COAP_BLOCK_USE_LIBCOAP); + /* Something to receive the requests so that the sends succeed */ + CU_ASSERT_PTR_NOT_NULL(coap_new_endpoint(l_ctx, &saddr, COAP_PROTO_UDP)); + l_session = coap_new_client_session(l_ctx, NULL, &saddr, COAP_PROTO_UDP); + CU_ASSERT_PTR_NOT_NULL(l_session); + if (!l_session) + goto done; + + pdu = coap_pdu_init(COAP_MESSAGE_NON, COAP_REQUEST_CODE_GET, + coap_new_message_id(l_session), + coap_session_max_pdu_size(l_session)); + CU_ASSERT_PTR_NOT_NULL(pdu); + if (!pdu) + goto done; + coap_add_token(pdu, 1, &token); + coap_add_option(pdu, COAP_OPTION_OBSERVE, 0, &obs); + CU_ASSERT(coap_add_data_large_request_app(l_session, pdu, 4096, NULL, + large_app_get_data, NULL) == 1); + CU_ASSERT(coap_send(l_session, pdu) != COAP_INVALID_MID); + + /* Observe is now established */ + coap_lock_lock(goto done); + coap_handle_dgram(l_ctx, l_session, rsp, sizeof(rsp)); + coap_lock_unlock(); + + calls = large_app_calls; + tok.length = 1; + tok.s = &token; + CU_ASSERT(coap_cancel_observe(l_session, &tok, COAP_MESSAGE_NON) == 1); + CU_ASSERT(large_app_calls > calls); + +done: + coap_free_context(l_ctx); +} + +/* A request built by coap_add_data_large_request_app() for a single + * block chosen by the application must get that block from the + * application callback. */ +static void +t_block_large_app_block_num(void) { + coap_context_t *l_ctx; + coap_session_t *l_session; + coap_address_t saddr; + coap_pdu_t *pdu; + size_t calls; + size_t len; + const uint8_t *data; + uint8_t token = 0x5c; + uint8_t buf[4]; + + coap_address_init(&saddr); + saddr.size = sizeof(struct sockaddr_in6); + saddr.addr.sin6.sin6_family = AF_INET6; + saddr.addr.sin6.sin6_addr = in6addr_loopback; + saddr.addr.sin6.sin6_port = htons(20013); + + l_ctx = coap_new_context(NULL); + ReturnIf_CU_ASSERT_PTR_NOT_NULL(l_ctx); + coap_context_set_block_mode(l_ctx, COAP_BLOCK_USE_LIBCOAP); + l_session = coap_new_client_session(l_ctx, NULL, &saddr, COAP_PROTO_UDP); + CU_ASSERT_PTR_NOT_NULL(l_session); + if (!l_session) + goto done; + + pdu = coap_pdu_init(COAP_MESSAGE_NON, COAP_REQUEST_CODE_PUT, + coap_new_message_id(l_session), + coap_session_max_pdu_size(l_session)); + CU_ASSERT_PTR_NOT_NULL(pdu); + if (!pdu) + goto done; + coap_add_token(pdu, 1, &token); + /* Block1 NUM 1, M 1, SZX 6 */ + coap_add_option(pdu, COAP_OPTION_BLOCK1, + coap_encode_var_safe(buf, sizeof(buf), (1 << 4) | (1 << 3) | 6), + buf); + calls = large_app_calls; + CU_ASSERT(coap_add_data_large_request_app(l_session, pdu, 4096, NULL, + large_app_get_data, NULL) == 1); + CU_ASSERT(large_app_calls > calls); + CU_ASSERT(coap_get_data(pdu, &len, &data) && len > 0 && data[0] == 'x'); + coap_delete_pdu(pdu); + +done: + coap_free_context(l_ctx); +} +#endif /* COAP_SERVER_SUPPORT */ + +CU_pSuite +t_init_block_tests(void) { + CU_pSuite suite; + + suite = CU_add_suite("block", NULL, NULL); + if (!suite) { /* signal error */ + fprintf(stderr, "W: cannot add block test suite (%s)\n", + CU_get_error_msg()); + + return NULL; + } + +#define BLOCK_TEST(s,t) \ + if (!CU_ADD_TEST(s,t)) { \ + fprintf(stderr, "W: cannot add block test (%s)\n", \ + CU_get_error_msg()); \ + } + +#if COAP_SERVER_SUPPORT + BLOCK_TEST(suite, t_block_echo_get_func); + BLOCK_TEST(suite, t_block_cancel_observe_get_func); + BLOCK_TEST(suite, t_block_large_app_block_num); +#endif /* COAP_SERVER_SUPPORT */ + + return suite; +} +#endif /* COAP_CLIENT_SUPPORT */ diff --git a/tests/test_block.h b/tests/test_block.h new file mode 100644 index 00000000..c9a007d5 --- /dev/null +++ b/tests/test_block.h @@ -0,0 +1,13 @@ +/* libcoap unit tests + * + * Copyright (C) 2026 Alejandro Pacheco + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#include + +CU_pSuite t_init_block_tests(void); diff --git a/tests/testdriver.c b/tests/testdriver.c index ffa6c865..a7c0bb8f 100644 --- a/tests/testdriver.c +++ b/tests/testdriver.c @@ -14,6 +14,7 @@ #include #include "test_common.h" +#include "test_block.h" #include "test_uri.h" #include "test_encode.h" #include "test_options.h" @@ -47,6 +48,7 @@ main(int argc COAP_UNUSED, char **argv COAP_UNUSED) { #if COAP_CLIENT_SUPPORT t_init_session_tests(); t_init_sendqueue_tests(); + t_init_block_tests(); #endif /* COAP_CLIENT_SUPPORT */ #if COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT t_init_wellknown_tests(); diff --git a/win32/testdriver/testdriver.vcxproj b/win32/testdriver/testdriver.vcxproj index 170877e0..7d29455f 100644 --- a/win32/testdriver/testdriver.vcxproj +++ b/win32/testdriver/testdriver.vcxproj @@ -376,6 +376,7 @@ + @@ -387,6 +388,7 @@ + diff --git a/win32/testdriver/testdriver.vcxproj.filters b/win32/testdriver/testdriver.vcxproj.filters index ff96d278..90c0e65d 100644 --- a/win32/testdriver/testdriver.vcxproj.filters +++ b/win32/testdriver/testdriver.vcxproj.filters @@ -45,6 +45,9 @@ Source Files + + Source Files + @@ -74,5 +77,8 @@ Header Files + + Header Files +