diff --git a/core/utils.c b/core/utils.c index bbede98b..8931eb3f 100644 --- a/core/utils.c +++ b/core/utils.c @@ -940,18 +940,9 @@ int utils_stringCopy(char * buffer, size_t length, const char * str) { - size_t i; - - for (i = 0 ; i < length && str[i] != 0 ; i++) - { - buffer[i] = str[i]; - } - - if (i == length) return -1; - - buffer[i] = 0; + size_t i = strlen(str); // NOSONAR - return (int)i; + return (int)utils_strncpy(buffer, length, str, i); } void utils_copyValue(void * dst, @@ -1155,3 +1146,42 @@ lwm2m_data_type_t utils_depthToDatatype(uri_depth_t depth) return LWM2M_TYPE_UNDEFINED; } + +size_t utils_strnlen(const char *str, size_t max_size) { + if (str == NULL) { + return 0; + } + const char *pos = memchr(str, '\0', max_size); + if (pos == NULL) { + return max_size; + } + + return pos - str; +} + +size_t utils_strncpy(char *dest, const size_t dest_size, const char *src, const size_t src_size) { + if (src == NULL || dest == NULL) { + return 0; + } + + size_t bytes_to_write = dest_size; + size_t actual_src_len = utils_strnlen(src, src_size); + + if (actual_src_len < bytes_to_write) { + bytes_to_write = actual_src_len; + } + memmove(dest, src, bytes_to_write); + if (bytes_to_write < dest_size) { + dest[bytes_to_write] = '\0'; + } + + // Do this always. Just to be sure! + if (dest_size > 0) { + dest[dest_size - 1] = '\0'; + } + + if (dest_size > 0 && bytes_to_write == dest_size) { + return bytes_to_write - 1; // '\0' written to last position + } + return bytes_to_write; +} diff --git a/core/utils.h b/core/utils.h index 9c9efb30..036f2807 100644 --- a/core/utils.h +++ b/core/utils.h @@ -26,6 +26,16 @@ lwm2m_media_type_t utils_convertMediaType(coap_content_type_t type); uint8_t utils_getResponseFormat(uint8_t accept_num, const uint16_t *accept, int numData, const lwm2m_data_t *dataP, bool singleResource, lwm2m_media_type_t *format); int utils_isAltPathValid(const char *altPath); +/** Copy a string. + * + * Use utils_strncpy() if possible! + * This is less safe than utils_strncpy(). + * + * @param buffer The destination buffer + * @param length The max number of bytes to be written to in the destination buffer + * @param str The source string (needs to be NULL-terminated) + * @return The number of bytes written + */ int utils_stringCopy(char *buffer, size_t length, const char *str); size_t utils_intToText(int64_t data, uint8_t *string, size_t length); size_t utils_uintToText(uint64_t data, uint8_t *string, size_t length); @@ -47,5 +57,26 @@ lwm2m_server_t *utils_findBootstrapServer(lwm2m_context_t *contextP, void *fromS #if defined(LWM2M_SERVER_MODE) || defined(LWM2M_BOOTSTRAP_SERVER_MODE) lwm2m_client_t *utils_findClient(lwm2m_context_t *contextP, void *fromSessionH); #endif +/** A safer version of `strlen`. Finds the number of chars (bytes) of the given string up to a given max. length. + * + * The behaviour is undefined if the maximum length value is bigger than the actually reserved memory behind the @a str + * buffer. + * + * @param str The null-terminated string to find the length of. + * @param max_size Max size of string. + * @return The size of the string if it is smaller or equal to max_size. Otherwise max_size. + */ +size_t utils_strnlen(const char *str, size_t max_size); + +/** A safer version of `strncpy`. Copies at most src_size bytes to dest, but checks for available space. + * + * If dest is not NULL and dest_size is not 0, then the destination buffer is always terminated with '\0'. + * + * @param dest The char buffer where to copy the string. + * @param dest_size The size of the destination buffer. + * @param src The source string. + * @param src_size The size of the source string buffer. The effective source string can be shorter. + */ +size_t utils_strncpy(char *dest, const size_t dest_size, const char *src, const size_t src_size); #endif /* WAKAAMA_UTILS_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 98bdfaa8..2ebff1b2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,6 +15,7 @@ set(TEST_SOURCES core_logging_tests.c core_registration_tests.c core_uritests.c + core_utils_tests.c data_senml_cbor_tests.c data_senml_json_tests.c data_tlv_json_lwm2m_data_test.c diff --git a/tests/core_utils_tests.c b/tests/core_utils_tests.c new file mode 100644 index 00000000..1ba15473 --- /dev/null +++ b/tests/core_utils_tests.c @@ -0,0 +1,197 @@ +/******************************************************************************* + * + * Copyright (c) 2025 GARDENA GmbH + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * The Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Lukas Woodtli, GARDENA GmbH - Please refer to git log + * + *******************************************************************************/ +#include "CUnit/Basic.h" +#include "tests.h" +#include "utils.h" + +void test_strnlen_null(void) { + size_t len = utils_strnlen(NULL, 10); + CU_ASSERT_EQUAL(len, 0); +} + +void test_strnlen_0(void) { + const char *string = ""; + size_t len = utils_strnlen(string, 10); + CU_ASSERT_EQUAL(len, 0); +} + +void test_strnlen_1(void) { + const char *string = "a"; + size_t len = utils_strnlen(string, 10); + CU_ASSERT_EQUAL(len, 1); +} + +void test_strnlen_2(void) { + const char *string = "ab"; + size_t len = utils_strnlen(string, 10); + CU_ASSERT_EQUAL(len, 2); +} + +void test_strnlen_max_len_0(void) { + const char *string = "abc"; + size_t len = utils_strnlen(string, 0); + CU_ASSERT_EQUAL(len, 0); +} + +void test_strnlen_max_len_1(void) { + const char *string = "abc"; + size_t len = utils_strnlen(string, 1); + CU_ASSERT_EQUAL(len, 1); +} + +void test_strnlen_max_len_2(void) { + const char *string = "abc"; + size_t len = utils_strnlen(string, 2); + CU_ASSERT_EQUAL(len, 2); +} + +void test_strnlen_max_len_3(void) { + const char *string = "abc"; + size_t len = utils_strnlen(string, 3); + CU_ASSERT_EQUAL(len, 3); +} + +void test_strnlen_max_len_4(void) { + const char *string = "abc"; + size_t len = utils_strnlen(string, 4); + CU_ASSERT_EQUAL(len, 3); +} + +void test_strnlen_max_len_5(void) { + const char *string = "abc"; + size_t len = utils_strnlen(string, 5); + CU_ASSERT_EQUAL(len, 3); +} + +void test_strncpy_null(void) { + char dest[] = {'a', 'b', 'c'}; + size_t len = utils_strncpy(dest, sizeof(dest), NULL, 1); + CU_ASSERT_EQUAL(len, 0); + CU_ASSERT_NSTRING_EQUAL(dest, "abc", sizeof(dest)); + + len = utils_strncpy(NULL, 99, "xyz", 1); + CU_ASSERT_EQUAL(len, 0); + len = utils_strncpy(NULL, 5, NULL, 3); + CU_ASSERT_EQUAL(len, 0); +} + +void test_strncpy_dest_0_src_0(void) { + char dst[] = {'a', 'b', 'c'}; + const char *src = "xyz"; + const size_t len = utils_strncpy(dst, 0, src, 0); + CU_ASSERT_EQUAL(len, 0); + CU_ASSERT_NSTRING_EQUAL(dst, "abc", sizeof(dst)); +} + +void test_strncpy_dest_1_src_0(void) { + char dst[] = {'a', 'b', 'c'}; + const char *src = "xyz"; + const size_t len = utils_strncpy(dst, 1, src, 0); + CU_ASSERT_EQUAL(dst[0], '\0'); + CU_ASSERT_EQUAL(len, 0); + CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 0); +} + +void test_strncpy_dest_sizeof_src_0(void) { + char dst[] = {'a', 'b', 'c'}; + const char *src = "xyz"; + const size_t len = utils_strncpy(dst, sizeof(dst), src, 0); + CU_ASSERT_EQUAL(dst[0], '\0'); + CU_ASSERT_EQUAL(len, 0); + CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 0); +} + +void test_strncpy_dest_sizeof_src_1(void) { + char dst[] = {'a', 'b', 'c'}; + const char *src = "xyz"; + const size_t len = utils_strncpy(dst, sizeof(dst), src, 1); + CU_ASSERT_NSTRING_EQUAL(dst, "x", 1); + CU_ASSERT_EQUAL(dst[1], '\0'); + CU_ASSERT_EQUAL(len, 1); + CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 1); +} + +void test_strncpy_dest_sizeof_src_2(void) { + char dst[] = {'a', 'b', 'c'}; + const char *src = "xyz"; + const size_t len = utils_strncpy(dst, sizeof(dst), src, 2); + CU_ASSERT_NSTRING_EQUAL(dst, "xy", 2); + CU_ASSERT_EQUAL(dst[2], '\0'); + CU_ASSERT_EQUAL(len, 2); + CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 2); +} + +void test_strncpy_dest_sizeof_src_3(void) { + char dst[] = {'a', 'b', 'c'}; + const char *src = "xyz"; + const size_t len = utils_strncpy(dst, sizeof(dst), src, 3); + // only 2 characters and NULL has space in dst + CU_ASSERT_NSTRING_EQUAL(dst, "xy", 2); + CU_ASSERT_EQUAL(dst[2], '\0'); + CU_ASSERT_EQUAL(len, 2); + CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 2); +} + +void test_strncpy_dest_sizeof_src_4(void) { + char dst[] = {'a', 'b', 'c'}; + char src[10]; + memset(src, '\0', sizeof(src)); + src[0] = 'u'; + src[1] = 'v'; + src[2] = 'w'; + const size_t src_len = sizeof(src); + CU_ASSERT_EQUAL(src_len, 10); + const size_t len = utils_strncpy(dst, sizeof(dst), src, src_len); + // only 2 characters and NULL has space in dst + CU_ASSERT_NSTRING_EQUAL(dst, "uv", 2); + CU_ASSERT_EQUAL(dst[2], '\0'); + CU_ASSERT_EQUAL(len, 2); + CU_ASSERT_EQUAL(utils_strnlen(dst, sizeof(dst)), 2); +} + +static struct TestTable table[] = { + {"test_strnlen_null()", test_strnlen_null}, + {"test_strnlen_0()", test_strnlen_0}, + {"test_strnlen_1()", test_strnlen_1}, + {"test_strnlen_2()", test_strnlen_2}, + {"test_strnlen_max_len_0()", test_strnlen_max_len_0}, + {"test_strnlen_max_len_1()", test_strnlen_max_len_1}, + {"test_strnlen_max_len_2()", test_strnlen_max_len_2}, + {"test_strnlen_max_len_3()", test_strnlen_max_len_3}, + {"test_strnlen_max_len_4()", test_strnlen_max_len_4}, + {"test_strnlen_max_len_5()", test_strnlen_max_len_5}, + {"test_strncpy_null()", test_strncpy_null}, + {"test_strncpy_dest_0_src_0()", test_strncpy_dest_0_src_0}, + {"test_strncpy_dest_1_src_0()", test_strncpy_dest_1_src_0}, + {"test_strncpy_dest_sizeof_src_0()", test_strncpy_dest_sizeof_src_0}, + {"test_strncpy_dest_sizeof_src_1()", test_strncpy_dest_sizeof_src_1}, + {"test_strncpy_dest_sizeof_src_3()", test_strncpy_dest_sizeof_src_3}, + {"test_strncpy_dest_sizeof_src_4()", test_strncpy_dest_sizeof_src_4}, + {NULL, NULL}, +}; + +CU_ErrorCode create_utils_suit(void) { + CU_pSuite pSuite = NULL; + + pSuite = CU_add_suite("Suite_utils", NULL, NULL); + if (NULL == pSuite) { + return CU_get_error(); + } + + return add_tests(pSuite, table); +} diff --git a/tests/tests.h b/tests/tests.h index 0c7b40ad..d93ed24d 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -58,4 +58,5 @@ CU_ErrorCode create_logging_test_suit(void); #ifdef LWM2M_SERVER_MODE CU_ErrorCode create_registration_test_suit(void); #endif +CU_ErrorCode create_utils_suit(void); #endif /* TESTS_H_ */ diff --git a/tests/unittests.c b/tests/unittests.c index b09e014f..4d2db813 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -107,6 +107,9 @@ int main(void) { goto exit; #endif + if (CUE_SUCCESS != create_utils_suit()) + goto exit; + CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_basic_show_failures(CU_get_failure_list());