coap_block.c: Use get_func wherever a request body is added

For a request set up with coap_add_data_large_request_app(), the data
is provided by the application's get_func and there is no data pointer.
Three places still took the body from that NULL pointer, so
coap_add_data() was passed a NULL pointer:

- check_freshness(), re-sending the next block with the Echo option
  from a 4.01 response.
- coap_cancel_observe_lkd(), re-adding the observe request's body to
  the cancel request.
- coap_add_data_large_internal(), when the application sets a Block1
  option with a non-zero block number to send a single block.

Use get_func in all of these, as the other places that send a block of
an lg_xmit already do. The single block and the whole-body cases now
share add_get_func_data(), which also checks the allocation and no
longer leaks the buffer when get_func fails.

Add unit tests.
This commit is contained in:
Alejandro Pacheco authored and Jon Shallow committed 2026-10-16 14:14:20 +01:00
1 parent 85ed6ebb93
commit 92fc71e908
9 files changed
+312 -17

No files matched your search

+2
View File
@@ -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
+1
View File
@@ -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 \
+62 -17
View File
@@ -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;
+1
View File
@@ -18,6 +18,7 @@ noinst_PROGRAMS = \
testdriver_SOURCES = \
testdriver.c \
test_block.c \
test_error_response.c \
test_encode.c \
test_options.c \
+223
View File
@@ -0,0 +1,223 @@
/* libcoap unit tests
*
* Copyright (C) 2026 Alejandro Pacheco <alepacheco99@gmail.com>
*
* 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 <stdio.h>
#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 */
+13
View File
@@ -0,0 +1,13 @@
/* libcoap unit tests
*
* Copyright (C) 2026 Alejandro Pacheco <alepacheco99@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*
* This file is part of the CoAP library libcoap. Please see
* README for terms of use.
*/
#include <CUnit/CUnit.h>
CU_pSuite t_init_block_tests(void);
+2
View File
@@ -14,6 +14,7 @@
#include <CUnit/Basic.h>
#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();
+2
View File
@@ -376,6 +376,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\tests\testdriver.c" />
<ClCompile Include="..\..\tests\test_block.c" />
<ClCompile Include="..\..\tests\test_error_response.c" />
<ClCompile Include="..\..\tests\test_options.c" />
<ClCompile Include="..\..\tests\test_oscore.c" />
@@ -387,6 +388,7 @@
<ClCompile Include="..\..\tests\test_wellknown.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\tests\test_block.h" />
<ClInclude Include="..\..\tests\test_error_response.h" />
<ClInclude Include="..\..\tests\test_options.h" />
<ClInclude Include="..\..\tests\test_oscore.h" />
@@ -45,6 +45,9 @@
<ClCompile Include="..\..\tests\test_tls.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\tests\test_block.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\tests\test_wellknown.h">
@@ -74,5 +77,8 @@
<ClInclude Include="..\..\tests\test_tls.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\tests\test_block.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>