Neels Hofmeyr has submitted this change and it was merged. ( 
https://gerrit.osmocom.org/12880 )

Change subject: add OSMO_STRBUF_PRINTF()
......................................................................

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
---
M include/osmocom/core/utils.h
M tests/utils/utils_test.c
M tests/utils/utils_test.ok
3 files changed, 188 insertions(+), 0 deletions(-)

Approvals:
  Harald Welte: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h
index fe360b3..16159d3 100644
--- a/include/osmocom/core/utils.h
+++ b/include/osmocom/core/utils.h
@@ -145,4 +145,89 @@

 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 822861f..d592fe0 100644
--- a/tests/utils/utils_test.c
+++ b/tests/utils/utils_test.c
@@ -33,6 +33,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <errno.h>
+#include <limits.h>

 static void hexdump_test(void)
 {
@@ -936,6 +937,90 @@
        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 @@
        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 8d7ced8..1215ddd 100644
--- a/tests/utils/utils_test.ok
+++ b/tests/utils/utils_test.ok
@@ -325,3 +325,20 @@
                    = 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

--
To view, visit https://gerrit.osmocom.org/12880
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I2497514e26c5e7a5d88985fc7e58343be1a027b2
Gerrit-Change-Number: 12880
Gerrit-PatchSet: 5
Gerrit-Owner: Neels Hofmeyr <[email protected]>
Gerrit-Reviewer: Harald Welte <[email protected]>
Gerrit-Reviewer: Jenkins Builder (1000002)
Gerrit-Reviewer: Neels Hofmeyr <[email protected]>
Gerrit-CC: Max <[email protected]>

Reply via email to