]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add start of new tokenization API
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 15 Apr 2020 22:50:01 +0000 (17:50 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 15 Apr 2020 22:50:01 +0000 (17:50 -0500)
src/lib/util/all.mk
src/lib/util/libfreeradius-util.mk [new file with mode: 0644]
src/lib/util/sbuff.c [new file with mode: 0644]
src/lib/util/sbuff.h [new file with mode: 0644]
src/lib/util/sbuff_tests.c [new file with mode: 0644]
src/lib/util/sbuff_tests.mk [new file with mode: 0644]

index 9d26382f6d7c2dc4c530d40dda4b00ed5ab852c9..263813012616553273f8f9ea53d9da73856c106d 100644 (file)
@@ -1,77 +1,4 @@
-#
-# 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)
diff --git a/src/lib/util/libfreeradius-util.mk b/src/lib/util/libfreeradius-util.mk
new file mode 100644 (file)
index 0000000..e51d8cc
--- /dev/null
@@ -0,0 +1,78 @@
+#
+# 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)
diff --git a/src/lib/util/sbuff.c b/src/lib/util/sbuff.c
new file mode 100644 (file)
index 0000000..779b5b3
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ *   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);
diff --git a/src/lib/util/sbuff.h b/src/lib/util/sbuff.h
new file mode 100644 (file)
index 0000000..39bed23
--- /dev/null
@@ -0,0 +1,218 @@
+#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
diff --git a/src/lib/util/sbuff_tests.c b/src/lib/util/sbuff_tests.c
new file mode 100644 (file)
index 0000000..37d4c04
--- /dev/null
@@ -0,0 +1,158 @@
+#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 }
+};
diff --git a/src/lib/util/sbuff_tests.mk b/src/lib/util/sbuff_tests.mk
new file mode 100644 (file)
index 0000000..0f37551
--- /dev/null
@@ -0,0 +1,8 @@
+TARGET         := sbuff_tests
+
+SOURCES                := sbuff_tests.c
+
+TGT_LDLIBS     := $(LIBS) $(GPERFTOOLS_LIBS)
+TGT_LDFLAGS    := $(LDFLAGS) $(GPERFTOOLS_LDFLAGS)
+
+TGT_PREREQS    += libfreeradius-util.a