Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys/string_utils: add string_writer helper #20843

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions sys/include/string_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @author Marian Buschsieweke <[email protected]>
*/

#include <assert.h>
#include <errno.h>
#include <stdint.h>
/* if explicit_bzero() is provided by standard C lib, it may be defined in
Expand All @@ -38,6 +39,67 @@
extern "C" {
#endif

/**
* @brief String Writer structure.
* Helper for writing multiple formatted strings to a buffer
*/
typedef struct {
const char *start; /**< start of the target buffer */
char *position; /**< current write pointer */
size_t capacity; /**< remaining capacity of the buffer */
} string_writer_t;

/**
* @brief Initialize a string writer structure
*
* @param[out] sw String Writer object to initialize
* @param[in] buffer destination buffer
* @param[in] len size of the destination buffer
*/
static inline void string_writer_init(string_writer_t *sw, void *buffer, size_t len)
{
assert(buffer && len);

sw->start = buffer;
sw->position = buffer;
sw->capacity = len;
sw->position[0] = 0;
}

/**
* @brief Get the size of the string contained by the string writer
* @param[in] sw String Writer to query
* @return size of the string
*/
static inline size_t string_writer_len(const string_writer_t *sw)
{
return sw->position - sw->start;
}

/**
* @brief Get the string contained by the string writer
* @param[in] sw String Writer to query
* @return the string assembled by string writer
*/
static inline const char *string_writer_str(const string_writer_t *sw)
{
return sw->start;
}

/**
* @brief Write a formatted string to a buffer
* The string will be truncated if there is not enough space left in
* the destination buffer.
* A terminating `\0` character is always included.
*
* @param[in] sw String Writer to write to
* @param[in] format format string to write
*
* @return number of bytes written on success
* -E2BIG if the string was truncated
*/
int swprintf(string_writer_t *sw, const char *restrict format, ...);
Copy link
Contributor

@derMihai derMihai Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can add

__attribute__ ((format (printf, 2, 3)))

to enable static printf-like compiler checks for the format string and arguments, see https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html.


/* explicit_bzero is provided if:
* - glibc is used as C lib (only with board natvie)
* - newlib is used and __BSD_VISIBILE is set
Expand Down
28 changes: 28 additions & 0 deletions sys/libc/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* @author Benjamin Valentin <[email protected]>
*/

#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include "string_utils.h"

Expand Down Expand Up @@ -50,4 +52,30 @@ const void *memchk(const void *data, uint8_t c, size_t len)
return NULL;
}

int swprintf(string_writer_t *sw, const char *restrict format, ...)
{
va_list args;
Teufelchen1 marked this conversation as resolved.
Show resolved Hide resolved
int res;

/* make sure flash_vsnprintf() is not used */
#if HAS_FLASH_UTILS_ARCH
#undef vsnprintf
#endif

va_start(args, format);
res = vsnprintf(sw->position, sw->capacity, format, args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang should complain here, you can:

#ifdef __clang__
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wformat-nonliteral\"") \
    res = vsnprintf(sw->position, sw->capacity, format, args);
    _Pragma("clang diagnostic pop")
#else
    res = vsnprintf(sw->position, sw->capacity, format, args);
#endif

va_end(args);

if (res <= (int)sw->capacity) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if res == capacity, the output was truncated by one character. This check should therefore be <.

sw->capacity -= res;
sw->position += res;
} else {
sw->position += sw->capacity;
sw->capacity = 0;
res = -E2BIG;
}

return res;
}

/** @} */
36 changes: 36 additions & 0 deletions tests/unittests/tests-libc/tests-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ static void test_libc_strscpy(void)
TEST_ASSERT_EQUAL_INT(strscpy(buffer, "empty", 0), -E2BIG);
}

static void test_libc_swprintf(void)
{
string_writer_t sw;
char buffer[32];
int res;

string_writer_init(&sw, buffer, sizeof(buffer));

/* check that string can be written completely */
res = swprintf(&sw, "Hello World!");
TEST_ASSERT_EQUAL_INT(res, 12);
TEST_ASSERT_EQUAL_INT(string_writer_len(&sw), 12);
TEST_ASSERT_EQUAL_INT(strcmp("Hello World!", string_writer_str(&sw)), 0);

/* check that we can add to the string */
/* Writing 10 characters, returns 10 bytes written */
res = swprintf(&sw, "0123456789");
TEST_ASSERT_EQUAL_INT(res, 10);
benpicco marked this conversation as resolved.
Show resolved Hide resolved
benpicco marked this conversation as resolved.
Show resolved Hide resolved
TEST_ASSERT_EQUAL_INT(strcmp("Hello World!0123456789", string_writer_str(&sw)), 0);

/* The string does not fit completely into the buffer, so it gets truncated */
res = swprintf(&sw, "01234567891");
TEST_ASSERT_EQUAL_INT(res, -E2BIG);
TEST_ASSERT_EQUAL_INT(strcmp("Hello World!0123456789012345678", string_writer_str(&sw)), 0);

/* You can't write to a full buffer */
res = swprintf(&sw, "###");
TEST_ASSERT_EQUAL_INT(res, -E2BIG);

/* check if string was truncated as expected */
TEST_ASSERT_EQUAL_INT(string_writer_len(&sw), 32);
TEST_ASSERT_EQUAL_INT(strcmp("Hello World!0123456789012345678",
string_writer_str(&sw)), 0);
benpicco marked this conversation as resolved.
Show resolved Hide resolved
}

static void test_libc_memchk(void)
{
char buffer[32];
Expand Down Expand Up @@ -101,6 +136,7 @@ Test *tests_libc_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_libc_strscpy),
new_TestFixture(test_libc_swprintf),
new_TestFixture(test_libc_memchk),
new_TestFixture(test_libc_endian),
};
Expand Down
Loading