-#
-# Makefile
-#
-# Version: $Id$
-#
-TARGET := libfreeradius-util.a
+SUBMAKEFILES := \
+ libfreeradius-util.mk \
+ sbuff_tests.mk
-SOURCES := \
- ascend.c \
- base64.c \
- cursor.c \
- debug.c \
- dict_print.c \
- dict_tokenize.c \
- dict_unknown.c \
- dict_util.c \
- dict_validate.c \
- dl.c \
- dns.c \
- event.c \
- fopencookie.c \
- fifo.c \
- file.c \
- fring.c \
- getaddrinfo.c \
- hash.c \
- heap.c \
- hmac_md5.c \
- hmac_sha1.c \
- inet.c \
- isaac.c \
- log.c \
- md4.c \
- md5.c \
- misc.c \
- missing.c \
- net.c \
- packet.c \
- pair_cursor.c \
- pair.c \
- pair_legacy.c \
- pair_tokenize.c \
- pcap.c \
- print.c \
- proto.c \
- rand.c \
- rbtree.c \
- retry.c \
- regex.c \
- sha1.c \
- snprintf.c \
- socket.c \
- strerror.c \
- strlcat.c \
- strlcpy.c \
- struct.c \
- syserror.c \
- table.c \
- talloc.c \
- thread_local.c \
- token.c \
- time.c \
- timeval.c \
- trie.c \
- udp.c \
- udpfromto.c \
- value.c \
- version.c
-
-HEADERS := $(subst src/lib/,,$(wildcard src/lib/util/*.h))
-
-SRC_CFLAGS := -D_LIBRADIUS -DNO_ASSERT -I$(top_builddir)/src
-
-# System libraries discovered by our top level configure script, links things
-# like pthread and the regexp libraries.
-TGT_LDLIBS := $(LIBS) $(PCAP_LIBS)
-TGT_LDFLAGS := $(LDFLAGS) $(PCAP_LDFLAGS)
--- /dev/null
+#
+# Makefile
+#
+# Version: $Id$
+#
+TARGET := libfreeradius-util.a
+
+SOURCES := \
+ ascend.c \
+ base64.c \
+ cursor.c \
+ debug.c \
+ dict_print.c \
+ dict_tokenize.c \
+ dict_unknown.c \
+ dict_util.c \
+ dict_validate.c \
+ dl.c \
+ dns.c \
+ event.c \
+ fifo.c \
+ file.c \
+ fopencookie.c \
+ fring.c \
+ getaddrinfo.c \
+ hash.c \
+ heap.c \
+ hmac_md5.c \
+ hmac_sha1.c \
+ inet.c \
+ isaac.c \
+ log.c \
+ md4.c \
+ md5.c \
+ misc.c \
+ missing.c \
+ net.c \
+ packet.c \
+ pair_cursor.c \
+ pair_legacy.c \
+ pair_tokenize.c \
+ pair.c \
+ pcap.c \
+ print.c \
+ proto.c \
+ rand.c \
+ rbtree.c \
+ regex.c \
+ retry.c \
+ sbuff.c \
+ sha1.c \
+ snprintf.c \
+ socket.c \
+ strerror.c \
+ strlcat.c \
+ strlcpy.c \
+ struct.c \
+ syserror.c \
+ table.c \
+ talloc.c \
+ thread_local.c \
+ time.c \
+ timeval.c \
+ token.c \
+ trie.c \
+ udp.c \
+ udpfromto.c \
+ value.c \
+ version.c
+
+HEADERS := $(subst src/lib/,,$(wildcard src/lib/util/*.h))
+
+SRC_CFLAGS := -D_LIBRADIUS -DNO_ASSERT -I$(top_builddir)/src
+
+# System libraries discovered by our top level configure script, links things
+# like pthread and the regexp libraries.
+TGT_LDLIBS := $(LIBS) $(PCAP_LIBS)
+TGT_LDFLAGS := $(LDFLAGS) $(PCAP_LDFLAGS)
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/** A generic string buffer structure for string printing and parsing
+ *
+ * @file src/lib/util/sbuff.c
+ *
+ * @copyright 2020 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/util/sbuff.h>
+#include <freeradius-devel/util/print.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+static_assert(sizeof(long long) >= sizeof(int64_t), "long long must be as wide or wider than an int64_t");
+static_assert(sizeof(unsigned long long) >= sizeof(uint64_t), "long long must be as wide or wider than an uint64_t");
+
+/** Wind position to first instance of specified multibyte utf8 char
+ *
+ * Only use this function if the search char could be multibyte,
+ * as there's a large performance penalty.
+ *
+ * @param[in,out] in Sbuff to search in.
+ * @param[in] chr to search for.
+ * @return
+ * - 0, no instances found.
+ * - >0 the offset at which the first occurrence of the multi-byte chr was found.
+ */
+size_t fr_sbuff_strchr_utf8(fr_sbuff_t *in, char *chr)
+{
+ char const *found;
+ char const *p = in->p;
+
+ found = fr_utf8_strchr(NULL, p, in->end - in->p, chr);
+ if (!found) return 0;
+
+ in->p = found;
+
+ return found - p;
+}
+
+/** Wind position to first instance of specified char
+ *
+ * @param[in,out] in Sbuff to search in.
+ * @param[in] c to search for.
+ * @return
+ * - 0, no instances found.
+ * - >0 the offset at which the first occurrence of the char was found.
+ */
+size_t fr_sbuff_strchr(fr_sbuff_t *in, char c)
+{
+ char const *found;
+ char const *p = in->p;
+
+ found = memchr(in->p, c, in->end - in->p);
+ if (!found) return 0;
+
+ in->p = found;
+
+ return found - p;
+}
+
+/** Wind position to the first instance of the specified needle
+ *
+ * @param[in,out] in sbuff to search in.
+ * @param[in] needle to search for.
+ * @param[in] len Length of the needle. -1 to use strlen.
+ * @return
+ * - 0, no instances found.
+ * - >0 the offset at which the first occurrence of the needle was found.
+ */
+size_t fr_sbuff_strstr(fr_sbuff_t *in, char const *needle, ssize_t len)
+{
+ char const *found;
+ char const *p = in->p;
+
+ if (len < 0) len = strlen(needle);
+
+ found = memmem(in->p, in->end - in->p, needle, len );
+ if (!found) return 0;
+
+ in->p = found;
+
+ return found - p;
+}
+
+/** Copy n bytes from the sbuff to another buffer
+ *
+ * Will fail if output buffer is too small, or insufficient data is available in sbuff.
+ *
+ * @param[out] out Where to copy to.
+ * @param[in] outlen Size of output buffer.
+ * @param[in] in Where to copy from. Will copy len bytes from current position in buffer.
+ * @param[in] len How many bytes to copy. If 0 the entire buffer will be copied.
+ * @return
+ * - 0 if insufficient bytes are available in the sbuff.
+ * - <0 the number of additional bytes we'd need in the output buffer as a negative value.
+ * - >0 the number of bytes copied to out.
+ */
+ssize_t fr_sbuff_strncpy_exact(char *out, size_t outlen, fr_sbuff_t *in, size_t len)
+{
+ if (len == SIZE_MAX) len = in->end - in->p;
+ if (unlikely(outlen == 0)) return -(len + 1);
+
+ outlen--; /* Account the \0 byte */
+
+ if (len > outlen) return outlen - len; /* Return how many bytes we'd need */
+ if ((in->p + len) > in->end) return 0; /* Copying off the end of sbuff */
+
+ memcpy(out, in->p, len);
+ out[len] = '\0';
+
+ in->p += len;
+
+ return len;
+}
+
+/** Copy as many bytes as possible from the sbuff to another buffer
+ *
+ * Copy size is limited by available data in sbuff and output buffer length.
+ *
+ * @param[out] out Where to copy to.
+ * @param[in] outlen Size of output buffer.
+ * @param[in] in Where to copy from. Will copy len bytes from current position in buffer.
+ * @param[in] len How many bytes to copy. If 0 the entire buffer will be copied.
+ */
+size_t fr_sbuff_strncpy(char *out, size_t outlen, fr_sbuff_t *in, size_t len)
+{
+ if (unlikely(outlen == 0)) return 0;
+
+ outlen--; /* Account the \0 byte */
+
+ if (len == SIZE_MAX) len = in->end - in->p;
+ if (len > outlen) len = outlen;
+ if ((in->p + len) > in->end) len = (in->end - in->p);
+
+ memcpy(out, in->p, len);
+ out[len] = '\0';
+
+ in->p += len;
+
+ return len;
+}
+
+/** Used to define a number parsing functions for singed integers
+ *
+ * @param[in] _type Output type.
+ * @param[in] _min value.
+ * @param[in] _max value.
+ */
+#define PARSE_INT_DEF(_type, _min, _max) \
+size_t fr_sbuff_parse_##_type(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in) \
+{ \
+ char buff[sizeof(STRINGIFY(_min)) + 1]; \
+ char *end; \
+ size_t len; \
+ long long num; \
+ len = fr_sbuff_strncpy(buff, sizeof(buff), FR_SBUFF_NO_ADVANCE(in), sizeof(STRINGIFY(_min))); \
+ if ((len == 0) && err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
+ num = strtoll(buff, &end, 10); \
+ if (end == buff) { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
+ return 0; \
+ } \
+ if ((num > (_max)) || ((errno == EINVAL) && (num == LLONG_MAX))) { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_INTEGER_OVERFLOW; \
+ *out = (_type)(_max); \
+ } else if (num < (_min) || ((errno == EINVAL) && (num == LLONG_MIN))) { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_INTEGER_UNDERFLOW; \
+ *out = (_type)(_min); \
+ } else { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
+ *out = (_type)(num); \
+ } \
+ return end - buff; \
+}
+
+PARSE_INT_DEF(int8_t, INT8_MIN, INT8_MAX);
+PARSE_INT_DEF(int16_t, INT16_MIN, INT16_MAX);
+PARSE_INT_DEF(int32_t, INT32_MIN, INT32_MAX);
+PARSE_INT_DEF(int64_t, INT64_MIN, INT64_MAX);
+
+/** Used to define a number parsing functions for singed integers
+ *
+ * @param[in] _type Output type.
+ * @param[in] _max value.
+ */
+#define PARSE_UINT_DEF(_type, _max) \
+size_t fr_sbuff_parse_##_type(fr_sbuff_parse_error_t *err, _type *out, fr_sbuff_t *in) \
+{ \
+ char buff[sizeof(STRINGIFY(_max)) + 1]; \
+ char *end; \
+ size_t len; \
+ unsigned long long num; \
+ len = fr_sbuff_strncpy(buff, sizeof(buff), FR_SBUFF_NO_ADVANCE(in), sizeof(STRINGIFY(_max))); \
+ if ((len == 0) && err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
+ num = strtoull(buff, &end, 10); \
+ if (end == buff) { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
+ return 0; \
+ } \
+ if ((num > (_max)) || ((errno == EINVAL) && (num == ULLONG_MAX))) { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_INTEGER_OVERFLOW; \
+ *out = (_type)(_max); \
+ } else { \
+ if (err) *err = FR_SBUFF_PARSE_ERROR_NOT_FOUND; \
+ *out = (_type)(num); \
+ } \
+ return end - buff; \
+}
+
+PARSE_UINT_DEF(uint8_t, UINT8_MAX);
+PARSE_UINT_DEF(uint16_t, UINT16_MAX);
+PARSE_UINT_DEF(uint32_t, UINT32_MAX);
+PARSE_UINT_DEF(uint64_t, UINT64_MAX);
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/** A generic string buffer structure for string printing and parsing
+ *
+ * Because doing manual length checks is error prone and a waste of everyone's time.
+ *
+ * @file src/lib/util/sbuff.h
+ *
+ * @copyright 2020 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+RCSIDH(sbuff_h, "$Id$")
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <errno.h>
+
+typedef struct {
+ union {
+ char const *start; //!< Immutable start pointer.
+ char *start_m; //!< Mutable start pointer.
+ };
+
+ union {
+ char const *end; //!< Immutable end pointer.
+ char *end_m; //!< Mutable end pointer.
+ };
+
+ union {
+ char const *p; //!< Immutable position pointer.
+ char *p_m; //!< Mutable position pointer.
+ };
+
+ bool is_const; //!< Can't be modified.
+ bool is_extendable; //!< Dynamically allocated talloc buffer.
+} fr_sbuff_t;
+
+typedef enum {
+ FR_SBUFF_PARSE_OK = 0, //!< No error.
+ FR_SBUFF_PARSE_ERROR_NOT_FOUND = -1, //!< String does not contain a token
+ ///< matching the output type.
+ FR_SBUFF_PARSE_ERROR_INTEGER_OVERFLOW = -2, //!< Integer type would overflow.
+ FR_SBUFF_PARSE_ERROR_INTEGER_UNDERFLOW = -3 //!< Integer type would underflow.
+} fr_sbuff_parse_error_t;
+
+/** @name Sbuff position manipulation
+ * @{
+ */
+/** Prevent an sbuff being advanced as it's passed into a parsing function
+ *
+ * @param[in] _sbuff to make an ephemeral copy of.
+ */
+#define FR_SBUFF_NO_ADVANCE(_sbuff) (fr_sbuff_t[]){ *(_sbuff) }
+
+/** Reset the current position of the sbuff to the start of the string
+ *
+ */
+#define fr_sbuff_start(_sbuff) ((_sbuff)->p) = ((_sbuff)->start)
+
+/** Reset the current position of the sbuff to the end of the string
+ *
+ */
+#define fr_sbuff_end(_sbuff) ((_sbuff)->p) = ((_sbuff)->end)
+
+size_t fr_sbuff_strchr_utf8(fr_sbuff_t *in, char *chr);
+
+size_t fr_sbuff_strchr(fr_sbuff_t *in, char c);
+
+size_t fr_sbuff_strstr(fr_sbuff_t *in, char const *needle, ssize_t len);
+/** @} */
+
+/** @name Copy data out of an sbuff
+ * @{
+ */
+ssize_t fr_sbuff_strncpy_exact(char *out, size_t outlen, fr_sbuff_t *in, size_t len);
+
+size_t fr_sbuff_strncpy(char *out, size_t outlen, fr_sbuff_t *in, size_t len);
+/** @} */
+
+/** @name Look for a token in a particular format, parse it, and write it to the output pointer
+ *
+ * These functions should not be called directly. #fr_sbuff_parse should be used instead
+ * so that if the output variable type changes, the parse rules are automatically changed.
+ * @{
+ */
+size_t fr_sbuff_parse_int8_t(fr_sbuff_parse_error_t *err, int8_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_int16_t(fr_sbuff_parse_error_t *err, int16_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_int32_t(fr_sbuff_parse_error_t *err, int32_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_int64_t(fr_sbuff_parse_error_t *err, int64_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_uint8_t(fr_sbuff_parse_error_t *err, uint8_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_uint16_t(fr_sbuff_parse_error_t *err, uint16_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_uint32_t(fr_sbuff_parse_error_t *err, uint32_t *out, fr_sbuff_t *in);
+
+size_t fr_sbuff_parse_uint64_t(fr_sbuff_parse_error_t *err, uint64_t *out, fr_sbuff_t *in);
+
+
+/** Parse a value based on the output type
+ *
+ * @param[out] _err If not NULL a value describing the parse error
+ * will be written to err.
+ * @param[out] _out Pointer to an integer type.
+ * @param[in] _in Sbuff to parse integer from.
+ * @return The number of bytes parsed (even on error).
+ */
+#define fr_sbuff_parse(_err, _out, _in) \
+ _Generic((_out), \
+ int8_t * : fr_sbuff_parse_int8_t(_err, _out, _in), \
+ int16_t * : fr_sbuff_parse_int16_t(_err, _out, _in), \
+ int32_t * : fr_sbuff_parse_int32_t(_err, _out, _in), \
+ int64_t * : fr_sbuff_parse_int64_t(_err, _out, _in), \
+ uint8_t * : fr_sbuff_parse_uint8_t(_err, _out, _in), \
+ uint16_t * : fr_sbuff_parse_uint16_t(_err, _out, _in), \
+ uint32_t * : fr_sbuff_parse_uint32_t(_err, _out, _in), \
+ uint64_t * : fr_sbuff_parse_uint64_t(_err, _out, _in) \
+ )
+/** @} */
+
+static inline void _fr_sbuff_parse_init(fr_sbuff_t *out, char const *start, char const *end, bool is_const)
+{
+ if (unlikely(end < start)) end = start; /* Could be an assert? */
+
+ out->p = out->start = start;
+ out->end = end;
+ out->is_const = is_const;
+ out->is_extendable = false;
+}
+
+/** Initialise an sbuff for binary safe string parsing
+ *
+ * @param[out] _out Pointer to buffer to parse
+ * @param[in] _start Start of the buffer to parse.
+ * @param[in] _len_or_end Either an end pointer or the length
+ * of the buffer we're parsing.
+ */
+#define fr_sbuff_parse_init(_out, _start, _len_or_end) \
+_Generic((_len_or_end), \
+ size_t : _fr_sbuff_parse_init(_out, _start, (char const *)(_start) + (size_t)(_len_or_end), true), \
+ char * : _fr_sbuff_parse_init(_out, _start, (char const *)(_len_or_end), false), \
+ char const * : _fr_sbuff_parse_init(_out, _start, (char const *)(_len_or_end), true) \
+)
+
+/** Initialise an sbuff for a stack allocated buffer
+ *
+ * Usually used for printing to a buffer
+ *
+ * @param[out] _out Pointer to sbuff to initialise.
+ * @param[in] _buff Char buffer to wrap.
+ */
+#define fr_sbuff_print_init(_out, _buff) _fr_sbuff_init(_out, _buff, sizeof(_buff), false);
+
+/** Initialise an sbuff for a talloced buffer
+ *
+ * Usually used for printing to a buffer of variable length
+ *
+ * @param[out] _out Pointer to sbuff to initialise.
+ * @param[in] _buff Talloced char buffer to wrap.
+ */
+#define fr_sbuff_print_talloc_init(_out, _buff) \
+do { \
+ _fr_sbuff_print_init(_out, _buff, talloc_array_length(_buff) - 1, true); \
+ (_out)->is_extendable = true; \
+} while (0)
+
+/** Initialise an sbuff and alloc a talloc buffer
+ *
+ * Usually used for printing to a buffer of variable length
+ *
+ * @param[out] _out Pointer to sbuff to initialise.
+ * @param[in] _ctx Talloc ctx to allocate buffer in.
+ * @param[in] _len Length of buffer to initialise, excluding '\0'.
+ */
+#define fr_sbuff_aprint_talloc_init(_out, _ctx, _len) \
+do { \
+ char *_buff; \
+ MEM(_buff = talloc_array(_ctx, char, (_len) + 1)); \
+ _fr_sbuff_print_init(_out, _buff, (_len) + 1, true); \
+ (_out)->is_extendable = true; \
+} while (0)
+
+static inline void _fr_sbuff_print_init(fr_sbuff_t *out, char *start, char *end, bool extendable)
+{
+ if (unlikely((end - 1) < start)) end = start; /* Could be an assert? */
+
+ out->p_m = out->start_m = start;
+ out->end_m = (end - 1); /* Always leave room for \0 byte */
+ out->is_const = false;
+ out->is_extendable = extendable;
+}
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+#include <freeradius-devel/util/acutest.h>
+
+#include "sbuff.c"
+
+//#include <gperftools/profiler.h>
+
+static void test_parse_init(void)
+{
+ char const *in = "i am a test string";
+ fr_sbuff_t sbuff;
+
+ TEST_CASE("Parse init with size");
+ fr_sbuff_parse_init(&sbuff, in, strlen(in));
+
+ TEST_CHECK(sbuff.start == in);
+ TEST_CHECK(sbuff.p == in);
+ TEST_CHECK(sbuff.end == in + strlen(in));
+
+ TEST_CASE("Parse init with end");
+ fr_sbuff_parse_init(&sbuff, in, in + strlen(in));
+
+ TEST_CHECK(sbuff.start == in);
+ TEST_CHECK(sbuff.p == in);
+ TEST_CHECK(sbuff.end == in + strlen(in));
+
+ TEST_CASE("Parse init with const end");
+ fr_sbuff_parse_init(&sbuff, in, (char const *)(in + strlen(in)));
+
+ TEST_CHECK(sbuff.start == in);
+ TEST_CHECK(sbuff.p == in);
+ TEST_CHECK(sbuff.end == in + strlen(in));
+}
+
+static void test_strncpy_exact(void)
+{
+ char const *in = "i am a test string";
+ char const *in_long = "i am a longer test string";
+ char out[18 + 1];
+ fr_sbuff_t sbuff;
+ ssize_t slen;
+
+ fr_sbuff_parse_init(&sbuff, in, strlen(in));
+
+ TEST_CASE("Copy 5 bytes to out");
+ slen = fr_sbuff_strncpy_exact(out, sizeof(out), &sbuff, 5);
+ TEST_CHECK(slen == 5);
+ TEST_CHECK(strcmp(out, "i am ") == 0);
+ TEST_CHECK(strcmp(sbuff.p, "a test string") == 0);
+
+ TEST_CASE("Copy 13 bytes to out");
+ slen = fr_sbuff_strncpy_exact(out, sizeof(out), &sbuff, 13);
+ TEST_CHECK(slen == 13);
+ TEST_CHECK(strcmp(out, "a test string") == 0);
+ TEST_CHECK(strcmp(sbuff.p, "") == 0);
+ TEST_CHECK(sbuff.p == sbuff.end);
+
+ TEST_CASE("Copy would overrun input");
+ slen = fr_sbuff_strncpy_exact(out, sizeof(out), &sbuff, 1);
+ TEST_CHECK(slen == 0);
+ TEST_CHECK(sbuff.p == sbuff.end);
+
+ TEST_CASE("Copy would overrun output (and SIZE_MAX special value)");
+ fr_sbuff_parse_init(&sbuff, in_long, strlen(in_long));
+
+ slen = fr_sbuff_strncpy_exact(out, sizeof(out), &sbuff, SIZE_MAX);
+ TEST_CHECK(slen == -7);
+ TEST_CHECK(sbuff.p == sbuff.start);
+
+ TEST_CASE("Zero length output buffer");
+ fr_sbuff_start(&sbuff);
+ out[0] = 'a';
+ slen = fr_sbuff_strncpy_exact(out, 0, &sbuff, SIZE_MAX);
+ TEST_CHECK(slen == -26);
+ TEST_CHECK(out[0] == 'a'); /* Must not write \0 */
+ TEST_CHECK(sbuff.p == sbuff.start);
+}
+
+static void test_strncpy(void)
+{
+ char const *in = "i am a test string";
+ char const *in_long = "i am a longer test string";
+ char out[18 + 1];
+ fr_sbuff_t sbuff;
+ ssize_t slen;
+
+ fr_sbuff_parse_init(&sbuff, in, strlen(in));
+
+ TEST_CASE("Copy 5 bytes to out");
+ slen = fr_sbuff_strncpy(out, sizeof(out), &sbuff, 5);
+ TEST_CHECK(slen == 5);
+ TEST_CHECK(strcmp(out, "i am ") == 0);
+ TEST_CHECK(strcmp(sbuff.p, "a test string") == 0);
+
+ TEST_CASE("Copy 13 bytes to out");
+ slen = fr_sbuff_strncpy(out, sizeof(out), &sbuff, 13);
+ TEST_CHECK(slen == 13);
+ TEST_CHECK(strcmp(out, "a test string") == 0);
+ TEST_CHECK(strcmp(sbuff.p, "") == 0);
+ TEST_CHECK(sbuff.p == sbuff.end);
+
+ TEST_CASE("Copy would overrun input");
+ slen = fr_sbuff_strncpy(out, sizeof(out), &sbuff, 1);
+ TEST_CHECK(slen == 0);
+ TEST_CHECK(sbuff.p == sbuff.end);
+
+ TEST_CASE("Copy would overrun output (and SIZE_MAX special value)");
+ fr_sbuff_parse_init(&sbuff, in_long, strlen(in_long));
+
+ slen = fr_sbuff_strncpy(out, sizeof(out), &sbuff, SIZE_MAX);
+ TEST_CHECK(slen == 18);
+ TEST_CHECK(strcmp(out, "i am a longer test") == 0);
+
+ TEST_CASE("Zero length output buffer");
+ fr_sbuff_start(&sbuff);
+ out[0] = 'a';
+ slen = fr_sbuff_strncpy(out, 0, &sbuff, SIZE_MAX);
+ TEST_CHECK(slen == 0);
+ TEST_CHECK(out[0] == 'a'); /* Must not write \0 */
+ TEST_CHECK(sbuff.p == sbuff.start);
+}
+
+/*
+static void test_sbuff_parse_num(void)
+{
+ char const *uint8_str_overflow = "256";
+ char const *uint64_str_overflow = "18446744073709551616";
+ char const *number_str =
+}
+*/
+
+static void test_no_advance(void)
+{
+ char const *in = "i am a test string";
+ char out[18 + 1];
+ fr_sbuff_t sbuff;
+ ssize_t slen;
+
+ fr_sbuff_parse_init(&sbuff, in, strlen(in));
+
+ TEST_CASE("Copy 5 bytes to out - no advance");
+ TEST_CHECK(sbuff.p == sbuff.start);
+ slen = fr_sbuff_strncpy_exact(out, sizeof(out), FR_SBUFF_NO_ADVANCE(&sbuff), 5);
+ TEST_CHECK(slen == 5);
+ TEST_CHECK(strcmp(out, "i am ") == 0);
+ TEST_CHECK(sbuff.p == sbuff.start);
+}
+
+TEST_LIST = {
+ /*
+ * Basic tests
+ */
+ { "fr_sbuff_parse_init", test_parse_init },
+ { "fr_sbuff_strncpy_exact", test_strncpy_exact },
+ { "fr_sbuff_strncpy", test_strncpy },
+ { "no-advance", test_no_advance },
+
+ { NULL }
+};
--- /dev/null
+TARGET := sbuff_tests
+
+SOURCES := sbuff_tests.c
+
+TGT_LDLIBS := $(LIBS) $(GPERFTOOLS_LIBS)
+TGT_LDFLAGS := $(LDFLAGS) $(GPERFTOOLS_LDFLAGS)
+
+TGT_PREREQS += libfreeradius-util.a