From 2cbe25f4844c82930078bbada753a9b079bf287b Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Mon, 11 Feb 2019 20:32:06 +0100 Subject: add OSMO_STRBUF_PRINTF() We are using macros like this or different workarounds in libmsc. In the course of implementing inter-MSC handover, I am encountering yet another such situation of appending multiple strings to a limited char buffer. Standardize. Add a unit test to utils_test.c. Change-Id: I2497514e26c5e7a5d88985fc7e58343be1a027b2 --- include/osmocom/core/utils.h | 85 +++++++++++++++++++++++++++++++++++++++++++ tests/utils/utils_test.c | 86 ++++++++++++++++++++++++++++++++++++++++++++ tests/utils/utils_test.ok | 17 +++++++++ 3 files changed, 188 insertions(+) diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h index fe360b33..16159d3d 100644 --- a/include/osmocom/core/utils.h +++ b/include/osmocom/core/utils.h @@ -145,4 +145,89 @@ uint32_t osmo_isqrt32(uint32_t x); const char osmo_luhn(const char* in, int in_len); +/*! State for OSMO_STRBUF_APPEND() and OSMO_STRBUF_PRINTF(). See there for examples. */ +struct osmo_strbuf { + /*! Point to the start of a string buffer. */ + char *buf; + /*! Total sizeof() the buffer buf points at. */ + size_t len; + /*! Current writing position in buf (end of the string written so far). */ + char *pos; + /*! After all OSMO_STRBUF_APPEND operations, reflects the total number of characters that would be written had + * buf been large enough. Like snprintf()'s return value, this does not include the terminating nul character. + * Hence, to allocate an adequately sized buffer, add 1 to this number. */ + size_t chars_needed; +}; + +/*! Append a string to a buffer, as printed by an snprintf()-like function and with similar bounds checking. + * Make sure to never write past the end of the buffer, and collect the total size that would be needed. + * + * // an example function implementation to append: write N spaces. + * int print_spaces(char *dst, size_t dst_len, int n) + * { + * int i; + * if (n < 0) + * return -EINVAL; + * for (i = 0; i < n && i < dst_len; i++) + * dst[i] = ' '; + * if (dst_len) + * dst[OSMO_MIN(dst_len - 1, n)] = '\0'; + * // return the n that we would have liked to write if space were available: + * return n; + * } + * + * // append above spaces as well as an snprintf() + * void strbuf_example() + * { + * char buf[23]; + * struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) }; + * + * OSMO_STRBUF_APPEND(sb, print_spaces, 5); + * OSMO_STRBUF_APPEND(sb, snprintf, "The answer is %d but what is the question?", 42); + * OSMO_STRBUF_APPEND(sb, print_spaces, 423423); + * + * printf("%s\n", buf); + * printf("would have needed %zu bytes\n", sb.chars_needed); + * } + * + * \param[inout] STRBUF A struct osmo_strbuf instance. + * \param[in] func A function with a signature of int func(char *dst, size_t dst_len [, args]) with semantics like + * snprintf(). + * \param[in] args Arguments passed to func, if any. + */ +#define OSMO_STRBUF_APPEND(STRBUF, func, args...) do { \ + if (!(STRBUF).pos) \ + (STRBUF).pos = (STRBUF).buf; \ + size_t remain = (STRBUF).buf ? (STRBUF).len - ((STRBUF).pos - (STRBUF).buf) : 0; \ + int l = func((STRBUF).pos, remain, ##args); \ + if (l < 0 || l > remain) \ + (STRBUF).pos = (STRBUF).buf + (STRBUF).len; \ + else \ + (STRBUF).pos += l; \ + if (l > 0) \ + (STRBUF).chars_needed += l; \ + } while(0) + +/*! Shortcut for OSMO_STRBUF_APPEND() invocation using snprintf(). + * + * int strbuf_example2(char *buf, size_t buflen) + * { + * int i; + * struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + * + * OSMO_STRBUF_PRINTF(sb, "T minus"); + * for (i = 10; i; i--) + * OSMO_STRBUF_PRINTF(sb, " %d", i); + * OSMO_STRBUF_PRINTF(sb, " ... Lift off!"); + * + * return sb.chars_needed; + * } + * + * \param[inout] STRBUF A struct osmo_strbuf instance. + * \param[in] fmt Format string passed to snprintf. + * \param[in] args Additional arguments passed to snprintf, if any. + */ +#define OSMO_STRBUF_PRINTF(STRBUF, fmt, args...) \ + OSMO_STRBUF_APPEND(STRBUF, snprintf, fmt, ##args) + /*! @} */ diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c index 822861fb..d592fe04 100644 --- a/tests/utils/utils_test.c +++ b/tests/utils/utils_test.c @@ -33,6 +33,7 @@ #include #include #include +#include static void hexdump_test(void) { @@ -936,6 +937,90 @@ static void osmo_str_tolowupper_test() OSMO_ASSERT(ok); } +/* Copy of the examples from OSMO_STRBUF_APPEND() */ +int print_spaces(char *dst, size_t dst_len, int argument) +{ + int i; + if (argument < 0) + return -EINVAL; + for (i = 0; i < argument && i < dst_len; i++) + dst[i] = ' '; + if (dst_len) + dst[OSMO_MIN(dst_len - 1, argument)] = '\0'; + return argument; +} + +void strbuf_example(char *buf, size_t buflen) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + + OSMO_STRBUF_APPEND(sb, print_spaces, 5); + OSMO_STRBUF_APPEND(sb, snprintf, "The answer is %d but what is the question?", 42); + OSMO_STRBUF_APPEND(sb, print_spaces, 423423); + + printf("%s\n", buf); + printf("would have needed %zu bytes\n", sb.chars_needed); +} + +/* Copy of the examples from OSMO_STRBUF_PRINTF() */ +int strbuf_example2(char *buf, size_t buflen) +{ + int i; + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + + OSMO_STRBUF_PRINTF(sb, "T minus"); + for (i = 10; i; i--) + OSMO_STRBUF_PRINTF(sb, " %d", i); + OSMO_STRBUF_PRINTF(sb, " ... Lift off!"); + + return sb.chars_needed; +} + +int strbuf_cascade(char *buf, size_t buflen) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + + OSMO_STRBUF_APPEND(sb, strbuf_example2); + OSMO_STRBUF_PRINTF(sb, " -- "); + OSMO_STRBUF_APPEND(sb, strbuf_example2); + OSMO_STRBUF_PRINTF(sb, " -- "); + OSMO_STRBUF_APPEND(sb, strbuf_example2); + + return sb.chars_needed; +} + +void strbuf_test() +{ + char buf[256]; + int rc; + printf("\n%s\n", __func__); + + printf("OSMO_STRBUF_APPEND():\n"); + strbuf_example(buf, 23); + + printf("\nOSMO_STRBUF_PRINTF():\n"); + rc = strbuf_example2(buf, 23); + printf("1: (need %d chars, had size=23) %s\n", rc, buf); + + rc = strbuf_example2(buf, rc); + printf("2: (need %d chars, had size=%d) %s\n", rc, rc, buf); + + rc = strbuf_example2(buf, rc + 1); + printf("3: (need %d chars, had size=%d+1) %s\n", rc, rc, buf); + + rc = strbuf_example2(buf, 0); + snprintf(buf, sizeof(buf), "0x2b 0x2b 0x2b..."); + printf("4: (need %d chars, had size=0) %s\n", rc, buf); + + rc = strbuf_example2(NULL, 99); + printf("5: (need %d chars, had NULL buffer)\n", rc); + + printf("\ncascade:\n"); + rc = strbuf_cascade(buf, sizeof(buf)); + printf("(need %d chars)\n%s\n", rc, buf); + rc = strbuf_cascade(buf, 63); + printf("(need %d chars, had size=63) %s\n", rc, buf); +} int main(int argc, char **argv) { @@ -954,5 +1039,6 @@ int main(int argc, char **argv) isqrt_test(); osmo_sockaddr_to_str_and_uint_test(); osmo_str_tolowupper_test(); + strbuf_test(); return 0; } diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok index 8d7ced83..1215ddd1 100644 --- a/tests/utils/utils_test.ok +++ b/tests/utils/utils_test.ok @@ -325,3 +325,20 @@ osmo_str_toupper_buf(28, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@ = 62, "ABCDEFGHIJKLMNOPQRSTUVWXYZA" osmo_str_toupper_buf(28, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()", in-place) = 27, "ABCDEFGHIJKLMNOPQRSTUVWXYZA" + +strbuf_test +OSMO_STRBUF_APPEND(): + The answer is 42 +would have needed 423470 bytes + +OSMO_STRBUF_PRINTF(): +1: (need 42 chars, had size=23) T minus 10 9 8 7 6 5 4 +2: (need 42 chars, had size=42) T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off +3: (need 42 chars, had size=42+1) T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! +4: (need 42 chars, had size=0) 0x2b 0x2b 0x2b... +5: (need 42 chars, had NULL buffer) + +cascade: +(need 134 chars) +T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! -- T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! -- T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! +(need 134 chars, had size=63) T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! -- T minus 10 9 8 7 -- cgit v1.2.3