diff --git a/core/utils.c b/core/utils.c index cd49b81f..411f321f 100644 --- a/core/utils.c +++ b/core/utils.c @@ -1167,3 +1167,30 @@ size_t utils_strnlen(const char *str, size_t 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 feea9083..ac88f752 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 mex. size of 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); @@ -57,4 +67,15 @@ lwm2m_client_t *utils_findClient(lwm2m_context_t *contextP, void *fromSessionH); */ 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 the destination buffer is always terminated with a '\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/core_utils_tests.c b/tests/core_utils_tests.c index 4e37db97..81e367ba 100644 --- a/tests/core_utils_tests.c +++ b/tests/core_utils_tests.c @@ -78,6 +78,93 @@ void test_strnlen_max_len_5(void) { 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}, @@ -89,6 +176,13 @@ static struct TestTable table[] = { {"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}, };