From: Arran Cudbard-Bell Date: Wed, 15 Apr 2020 22:50:01 +0000 (-0500) Subject: Add start of new tokenization API X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c959fa15d9fc4d5b2cb2f40fe52706682da9aa0d;p=thirdparty%2Ffreeradius-server.git Add start of new tokenization API --- diff --git a/src/lib/util/all.mk b/src/lib/util/all.mk index 9d26382f6d7..26381301261 100644 --- a/src/lib/util/all.mk +++ b/src/lib/util/all.mk @@ -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 index 00000000000..e51d8cc98aa --- /dev/null +++ b/src/lib/util/libfreeradius-util.mk @@ -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 index 00000000000..779b5b3c19c --- /dev/null +++ b/src/lib/util/sbuff.c @@ -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 + */ +RCSID("$Id$") + +#include +#include + +#include +#include + +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 index 00000000000..39bed2385af --- /dev/null +++ b/src/lib/util/sbuff.h @@ -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 + */ +RCSIDH(sbuff_h, "$Id$") + +# ifdef __cplusplus +extern "C" { +# endif + +#include +#include +#include +#include +#include + +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 index 00000000000..37d4c04c1d2 --- /dev/null +++ b/src/lib/util/sbuff_tests.c @@ -0,0 +1,158 @@ +#include + +#include "sbuff.c" + +//#include + +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 index 00000000000..0f37551bf48 --- /dev/null +++ b/src/lib/util/sbuff_tests.mk @@ -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