Commit 8dcd98b5f0 for strongswan.org
commit 8dcd98b5f04915d84588744e7a5fceb4e9da831a
Author: Tobias Brunner <tobias@strongswan.org>
Date: Fri Sep 18 09:46:33 2026 +0200
string: Add helpers to parse fixed-size integers from strings
We currently call strto[u]l[l]() in various locations, some with proper
validation/range checks, most without. These functions are meant to
replace those calls.
Also added a helper to determine the base from the string as we
generally only want to use 10 and 16 (i.e. no base 8 and rarely base 2).
diff --git a/src/libstrongswan/tests/suites/test_utils.c b/src/libstrongswan/tests/suites/test_utils.c
index 7cda4acecd..7da9dc8b76 100644
--- a/src/libstrongswan/tests/suites/test_utils.c
+++ b/src/libstrongswan/tests/suites/test_utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2015 Tobias Brunner
+ * Copyright (C) 2013-2026 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@@ -126,6 +126,227 @@ START_TEST(test_timeval_add_ms)
}
END_TEST
+/*******************************************************************************
+ * [u]int64_from_string
+ */
+
+static struct {
+ char *s;
+ int b;
+ char e;
+ bool v;
+ uint64_t i;
+} ufs_data[] = {
+ {NULL, 0, '\0', FALSE, 0},
+ {"", 0, '\0', FALSE, 0},
+ {" ", 0, '\0', FALSE, 0},
+ {"-", 0, '\0', FALSE, 0},
+ {"+", 0, '\0', FALSE, 0},
+ {"a", 0, '\0', FALSE, 0},
+ {"0", 0, '\0', TRUE, 0},
+ {"-0", 0, '\0', FALSE, 0},
+ {"5", 0, '\0', TRUE, 5},
+ {"5", 2, '\0', FALSE, 0},
+ {" 5", 0, '\0', TRUE, 5},
+ {"\t5", 0, '\0', TRUE, 5},
+ {"+5", 0, '\0', TRUE, 5},
+ {"-5", 0, '\0', FALSE, 0},
+ {" -5", 0, '\0', FALSE, 0},
+ {"5s", 0, 's', TRUE, 5},
+ {"010", 0, '\0', TRUE, 8},
+ {"010", 2, '\0', TRUE, 2},
+ {"010", 10, '\0', TRUE, 10},
+ {"0x10", 0, '\0', TRUE, 16},
+ {"0x10", 10, 'x', TRUE, 0},
+ {"0x10", 16, '\0', TRUE, 16},
+ {"0x10s", 0, 's', TRUE, 16},
+ {"18446744073709551615", 0, '\0', TRUE, UINT64_MAX},
+ {"18446744073709551615s", 0, 's', TRUE, UINT64_MAX},
+ {"18446744073709551616", 0, '\0', FALSE, 0},
+ {"18446744073709551616s", 0, '\0', FALSE, 0},
+};
+
+START_TEST(test_uint64_from_string)
+{
+ char *end;
+ uint64_t val = 42;
+
+ ck_assert(!uint64_from_string(ufs_data[_i].s, NULL, ufs_data[_i].b, NULL));
+ ck_assert(!uint64_from_string(ufs_data[_i].s, &end, ufs_data[_i].b, NULL));
+ ck_assert(uint64_from_string(ufs_data[_i].s, NULL, ufs_data[_i].b,
+ &val) == ufs_data[_i].v);
+ if (ufs_data[_i].v)
+ {
+ ck_assert_int_eq(val, ufs_data[_i].i);
+ }
+ else
+ {
+ ck_assert_int_eq(val, 42);
+ }
+ val = 42;
+ ck_assert(uint64_from_string(ufs_data[_i].s, &end, ufs_data[_i].b,
+ &val) == ufs_data[_i].v);
+ if (ufs_data[_i].v)
+ {
+ ck_assert_int_eq(val, ufs_data[_i].i);
+ ck_assert_int_eq(ufs_data[_i].e, *end);
+ }
+ else
+ {
+ ck_assert_int_eq(val, 42);
+ }
+}
+END_TEST
+
+START_TEST(test_uintx_from_string)
+{
+ char *end;
+ uint32_t val32 = 0;
+ uint16_t val16 = 0;
+ uint8_t val8 = 0;
+
+ /* the main parsing functionality is already tested above, so only check
+ * range enforcement for smaller integer types */
+ ck_assert(!uint32_from_string("4294967295", NULL, 0, NULL));
+ ck_assert(!uint32_from_string("4294967295", &end, 0, NULL));
+ ck_assert(uint32_from_string("4294967295", NULL, 0, &val32));
+ ck_assert(uint32_from_string("4294967295", &end, 0, &val32));
+ ck_assert_int_eq(val32, UINT32_MAX);
+ ck_assert(!uint32_from_string("4294967296", &end, 0, &val32));
+
+ ck_assert(!uint16_from_string("65535", NULL, 0, NULL));
+ ck_assert(!uint16_from_string("65535", &end, 0, NULL));
+ ck_assert(uint16_from_string("65535", NULL, 0, &val16));
+ ck_assert(uint16_from_string("65535", &end, 0, &val16));
+ ck_assert_int_eq(val16, UINT16_MAX);
+ ck_assert(!uint16_from_string("65536", &end, 0, &val16));
+
+ ck_assert(!uint8_from_string("255", NULL, 0, NULL));
+ ck_assert(!uint8_from_string("255", &end, 0, NULL));
+ ck_assert(uint8_from_string("255", NULL, 0, &val8));
+ ck_assert(uint8_from_string("255", &end, 0, &val8));
+ ck_assert_int_eq(val8, UINT8_MAX);
+ ck_assert(!uint8_from_string("256", &end, 0, &val8));
+}
+END_TEST
+
+
+static struct {
+ char *s;
+ int b;
+ char e;
+ bool v;
+ int64_t i;
+} ifs_data[] = {
+ {NULL, 0, '\0', FALSE, 0},
+ {"", 0, '\0', FALSE, 0},
+ {" ", 0, '\0', FALSE, 0},
+ {"-", 0, '\0', FALSE, 0},
+ {"+", 0, '\0', FALSE, 0},
+ {"a", 0, '\0', FALSE, 0},
+ {"0", 0, '\0', TRUE, 0},
+ {"-0", 0, '\0', TRUE, 0},
+ {"5", 0, '\0', TRUE, 5},
+ {"5", 2, '\0', FALSE, 0},
+ {" 5", 0, '\0', TRUE, 5},
+ {"\t5", 0, '\0', TRUE, 5},
+ {"+5", 0, '\0', TRUE, 5},
+ {"-5", 0, '\0', TRUE, -5},
+ {" -5", 0, '\0', TRUE, -5},
+ {"5s", 0, 's', TRUE, 5},
+ {"010", 0, '\0', TRUE, 8},
+ {"010", 2, '\0', TRUE, 2},
+ {"010", 10, '\0', TRUE, 10},
+ {"0x10", 0, '\0', TRUE, 16},
+ {"0x10", 10, 'x', TRUE, 0},
+ {"0x10", 16, '\0', TRUE, 16},
+ {"-0x10", 16, '\0', TRUE, -16},
+ {"0x10s", 0, 's', TRUE, 16},
+ {"-0x10s", 0, 's', TRUE, -16},
+ {"9223372036854775807", 0, '\0', TRUE, INT64_MAX},
+ {"9223372036854775807s", 0, 's', TRUE, INT64_MAX},
+ {"9223372036854775808", 0, '\0', FALSE, 0},
+ {"9223372036854775808s", 0, '\0', FALSE, 0},
+ {"-9223372036854775808", 0, '\0', TRUE, INT64_MIN},
+ {"-9223372036854775808s", 0, 's', TRUE, INT64_MIN},
+ {"-9223372036854775809", 0, '\0', FALSE, 0},
+ {"-9223372036854775809s", 0, '\0', FALSE, 0},
+};
+
+START_TEST(test_int64_from_string)
+{
+ char *end;
+ int64_t val = 42;
+
+ ck_assert(!int64_from_string(ifs_data[_i].s, NULL, ifs_data[_i].b, NULL));
+ ck_assert(!int64_from_string(ifs_data[_i].s, &end, ifs_data[_i].b, NULL));
+ ck_assert(int64_from_string(ifs_data[_i].s, NULL, ifs_data[_i].b,
+ &val) == ifs_data[_i].v);
+ if (ifs_data[_i].v)
+ {
+ ck_assert_int_eq(val, ifs_data[_i].i);
+ }
+ else
+ {
+ ck_assert_int_eq(val, 42);
+ }
+ val = 42;
+ ck_assert(int64_from_string(ifs_data[_i].s, &end, ifs_data[_i].b,
+ &val) == ifs_data[_i].v);
+ if (ifs_data[_i].v)
+ {
+ ck_assert_int_eq(val, ifs_data[_i].i);
+ ck_assert_int_eq(ifs_data[_i].e, *end);
+ }
+ else
+ {
+ ck_assert_int_eq(val, 42);
+ }
+}
+END_TEST
+
+START_TEST(test_intx_from_string)
+{
+ char *end;
+ int32_t val32 = 0;
+
+ /* the main parsing functionality is already tested above, so only check
+ * range enforcement for smaller integer types */
+ ck_assert(!int32_from_string("2147483647", NULL, 0, NULL));
+ ck_assert(!int32_from_string("2147483647", &end, 0, NULL));
+ ck_assert(int32_from_string("2147483647", NULL, 0, &val32));
+ ck_assert(int32_from_string("2147483647", &end, 0, &val32));
+ ck_assert_int_eq(val32, INT32_MAX);
+ ck_assert(!int32_from_string("2147483648", &end, 0, &val32));
+
+ ck_assert(int32_from_string("-2147483648", &end, 0, &val32));
+ ck_assert_int_eq(val32, INT32_MIN);
+ ck_assert(!int32_from_string("-2147483649", &end, 0, &val32));
+}
+END_TEST
+
+/*******************************************************************************
+ * base_from_string
+ */
+
+START_TEST(test_base_from_string)
+{
+ ck_assert_int_eq(base_from_string(NULL), 10);
+ ck_assert_int_eq(base_from_string(""), 10);
+ ck_assert_int_eq(base_from_string(" "), 10);
+ ck_assert_int_eq(base_from_string("0"), 10);
+ ck_assert_int_eq(base_from_string(" 0"), 10);
+ ck_assert_int_eq(base_from_string("0x"), 16);
+ ck_assert_int_eq(base_from_string("-0x"), 16);
+ ck_assert_int_eq(base_from_string("+0x"), 16);
+ ck_assert_int_eq(base_from_string(" -0x"), 16);
+ ck_assert_int_eq(base_from_string(" 0x"), 16);
+ ck_assert_int_eq(base_from_string("0x2"), 16);
+ ck_assert_int_eq(base_from_string("\t0x2"), 16);
+ ck_assert_int_eq(base_from_string("\t -0x2"), 16);
+}
+END_TEST
+
/*******************************************************************************
* timespan_from_string
*/
@@ -1343,6 +1564,17 @@ Suite *utils_suite_create()
tcase_add_test(tc, test_timeval_add_ms);
suite_add_tcase(s, tc);
+ tc = tcase_create("[u]int64_from_string");
+ tcase_add_loop_test(tc, test_uint64_from_string, 0, countof(ufs_data));
+ tcase_add_test(tc, test_uintx_from_string);
+ tcase_add_loop_test(tc, test_int64_from_string, 0, countof(ifs_data));
+ tcase_add_test(tc, test_intx_from_string);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("base_from_string");
+ tcase_add_test(tc, test_base_from_string);
+ suite_add_tcase(s, tc);
+
tc = tcase_create("timespan_from_string");
tcase_add_loop_test(tc, test_timespan_from_string, 0, countof(ts_data));
suite_add_tcase(s, tc);
diff --git a/src/libstrongswan/utils/utils/string.c b/src/libstrongswan/utils/utils/string.c
index 20c76ca332..4ed4fcd8e5 100644
--- a/src/libstrongswan/utils/utils/string.c
+++ b/src/libstrongswan/utils/utils/string.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2014 Tobias Brunner
+ * Copyright (C) 2008-2026 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -15,9 +15,11 @@
* for more details.
*/
+#include <errno.h>
+
#include <utils/utils.h>
-/**
+/*
* Described in header.
*/
char* translate(char *str, const char *from, const char *to)
@@ -39,7 +41,7 @@ char* translate(char *str, const char *from, const char *to)
return str;
}
-/**
+/*
* Described in header.
*/
char* strreplace(const char *str, const char *search, const char *replace)
@@ -90,3 +92,65 @@ char* strreplace(const char *str, const char *search, const char *replace)
strcpy(dst, pos);
return res;
}
+
+/*
+ * Described in header
+ */
+bool uint64_from_string(const char *str, char **end, int base, uint64_t *out)
+{
+ const char *startptr = str;
+ char *endptr;
+ unsigned long long val;
+
+ if (!str || !out)
+ {
+ return FALSE;
+ }
+ /* explicitly reject '-' prefixes and whitespace-only strings */
+ while (isspace((u_char)*startptr))
+ {
+ startptr++;
+ }
+ if (!*startptr || *startptr == '-')
+ {
+ return FALSE;
+ }
+ errno = 0;
+ val = strtoull(startptr, &endptr, base);
+ if (endptr == startptr || errno)
+ {
+ return FALSE;
+ }
+ *out = val;
+ if (end)
+ {
+ *end = endptr;
+ }
+ return TRUE;
+}
+
+/*
+ * Described in header
+ */
+bool int64_from_string(const char *str, char **end, int base, int64_t *out)
+{
+ char *endptr;
+ long long val;
+
+ if (!str || !out)
+ {
+ return FALSE;
+ }
+ errno = 0;
+ val = strtoll(str, &endptr, base);
+ if (endptr == str || errno)
+ {
+ return FALSE;
+ }
+ *out = val;
+ if (end)
+ {
+ *end = endptr;
+ }
+ return TRUE;
+}
diff --git a/src/libstrongswan/utils/utils/string.h b/src/libstrongswan/utils/utils/string.h
index 6c91cae0c5..a55b4438d0 100644
--- a/src/libstrongswan/utils/utils/string.h
+++ b/src/libstrongswan/utils/utils/string.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2014 Tobias Brunner
+ * Copyright (C) 2008-2026 Tobias Brunner
* Copyright (C) 2008 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -23,6 +23,8 @@
#ifndef STRING_H_
#define STRING_H_
+#include <ctype.h>
+
/**
* Helper function that compares two strings for equality
*/
@@ -101,4 +103,164 @@ char *translate(char *str, const char *from, const char *to);
*/
char *strreplace(const char *str, const char *search, const char *replace);
+/**
+ * Parse an unsigned 64-bit integer from a string, wrapping strtoull().
+ *
+ * It rejects the following: empty/no-digit input, a leading '-' (which
+ * strtoull() would wrap modulo 2^64) and out-of-range values.
+ *
+ * Only the prefix is parsed, \p end (if given) points to the first unconsumed
+ * character so callers can scan suffixes. To ensure a plain number,
+ * additionally check <tt>*end == '\0'</tt>.
+ *
+ * Prefer base 10 (with the settings_t-style explicit 0x-check for hex) because
+ * base 0 additionally enables octal, which silently reinterprets zero-padded
+ * input ("010" => 8).
+ *
+ * @param str string to parse (NULL-safe)
+ * @param end first unconsumed character on success (optional)
+ * @param base numeric base, same as strtoull()
+ * @param[out] out parsed value on success, unchanged otherwise (required)
+ * @return TRUE if at least one digit converted without range error
+ */
+bool uint64_from_string(const char *str, char **end, int base, uint64_t *out);
+
+/**
+ * Parse an unsigned 32-bit integer from a string, wrapping strtoull().
+ *
+ * @copydetails uint64_from_string
+ */
+static inline bool uint32_from_string(const char *str, char **end, int base,
+ uint32_t *out)
+{
+ char *endptr;
+ uint64_t val;
+
+ if (out && uint64_from_string(str, &endptr, base, &val) &&
+ val <= UINT32_MAX)
+ {
+ if (end)
+ {
+ *end = endptr;
+ }
+ *out = val;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Parse an unsigned 16-bit integer from a string, wrapping strtoull().
+ *
+ * @copydetails uint64_from_string
+ */
+static inline bool uint16_from_string(const char *str, char **end, int base,
+ uint16_t *out)
+{
+ char *endptr;
+ uint64_t val;
+
+ if (out && uint64_from_string(str, &endptr, base, &val) &&
+ val <= UINT16_MAX)
+ {
+ if (end)
+ {
+ *end = endptr;
+ }
+ *out = val;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Parse an unsigned 8-bit integer from a string, wrapping strtoull().
+ *
+ * @copydetails uint64_from_string
+ */
+static inline bool uint8_from_string(const char *str, char **end, int base,
+ uint8_t *out)
+{
+ char *endptr;
+ uint64_t val;
+
+ if (out && uint64_from_string(str, &endptr, base, &val) && val <= UINT8_MAX)
+ {
+ if (end)
+ {
+ *end = endptr;
+ }
+ *out = val;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Parse a signed 64-bit integer from a string, wrapping strtoll().
+ *
+ * It rejects the following: empty/no-digit input and out-of-range values.
+ * A leading -/+ is accepted as the value's sign.
+ *
+ * Only the prefix is parsed, \p end (if given) points to the first unconsumed
+ * character so callers can scan suffixes. To ensure a plain number,
+ * additionally check <tt>*end == '\0'</tt>.
+ *
+ * Prefer base 10 (with the settings_t-style explicit 0x-check for hex) because
+ * base 0 additionally enables octal, which silently reinterprets zero-padded
+ * input ("010" => 8).
+ *
+ * @param str string to parse (NULL-safe)
+ * @param end first unconsumed character on success (optional)
+ * @param base numeric base, same as strtoll()
+ * @param[out] out parsed value on success, unchanged otherwise (required)
+ * @return TRUE if at least one digit converted without range error
+ */
+bool int64_from_string(const char *str, char **end, int base, int64_t *out);
+
+/**
+ * Parse a signed 32-bit integer from a string, wrapping strtoll().
+ *
+ * @copydetails int64_from_string
+ */
+static inline bool int32_from_string(const char *str, char **end, int base,
+ int32_t *out)
+{
+ char *endptr;
+ int64_t val;
+
+ if (out && int64_from_string(str, &endptr, base, &val) &&
+ val <= INT32_MAX && val >= INT32_MIN)
+ {
+ if (end)
+ {
+ *end = endptr;
+ }
+ *out = val;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Determine the base when parsing integer strings.
+ *
+ * We generally only want to parse integers in base 10 and 16 (with 0x prefix).
+ *
+ * @param str string to parse (NULL-safe)
+ * @return base determined based on the given string, defaults to 10
+ */
+static inline int base_from_string(const char *str)
+{
+ while (str && isspace((u_char)*str))
+ {
+ str++;
+ }
+ if (str && (*str == '-' || *str == '+'))
+ {
+ str++;
+ }
+ return strcasepfx(str, "0x") ? 16 : 10;
+}
+
#endif /** STRING_H_ @} */