]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
start of calculation and comparison API
authorAlan T. DeKok <aland@freeradius.org>
Tue, 16 Nov 2021 21:06:04 +0000 (16:06 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 17 Nov 2021 15:18:57 +0000 (10:18 -0500)
src/bin/unit_test_attribute.c
src/lib/util/calc.c [new file with mode: 0644]
src/lib/util/calc.h [new file with mode: 0644]
src/lib/util/libfreeradius-util.mk
src/tests/unit/calc.txt [new file with mode: 0644]

index d2bacd658167fb6090feec03fcf8c7c6d65203b3..97c89728018a3d51caa1925ea0075684d7f9bfb4 100644 (file)
@@ -44,6 +44,7 @@ typedef struct request_s request_t;
 #include <freeradius-devel/unlang/xlat.h>
 #include <freeradius-devel/util/atexit.h>
 #include <freeradius-devel/util/base64.h>
+#include <freeradius-devel/util/calc.h>
 #include <freeradius-devel/util/conf.h>
 #include <freeradius-devel/util/dns.h>
 #include <freeradius-devel/util/file.h>
@@ -1003,9 +1004,14 @@ static size_t parse_typed_value(command_result_t *result, fr_value_box_t *box, c
        fr_skip_whitespace(p);
        *out = p;
 
+       /*
+        *      As a hack, allow most things to be inside
+        *      double-quoted strings.  This is really only for dates,
+        *      which are space-delimited.
+        */
        if (*p == '"'){
                p++;
-               slen = fr_value_box_from_substr(box, box, type, NULL,
+               slen = fr_value_box_from_substr(box, box, FR_TYPE_STRING, NULL,
                                                &FR_SBUFF_IN(p, strlen(p)),
                                                &value_parse_rules_double_quoted,
                                                false);
@@ -1020,6 +1026,12 @@ static size_t parse_typed_value(command_result_t *result, fr_value_box_t *box, c
                p++;
                slen += 2;
 
+               if (type != FR_TYPE_STRING) {
+                       if (fr_value_box_cast_in_place(box, box, type, NULL) < 0) {
+                               RETURN_PARSE_ERROR(0);
+                       }
+               }
+
        } else {
                slen = fr_value_box_from_substr(box, box, type, NULL,
                                                &FR_SBUFF_IN(p, strlen(p)),
@@ -1152,6 +1164,79 @@ static size_t command_normalise_attribute(command_result_t *result, command_file
        RETURN_OK(slen);
 }
 
+static const fr_token_t token2op[UINT8_MAX + 1] = {
+       [ '+' ] = T_OP_ADD,
+       [ '-' ] = T_OP_SUB,
+       [ '^' ] = T_OP_PREPEND,
+       [ '.' ] = T_OP_ADD,
+};
+
+/** Perform calculations
+ *
+ */
+static size_t command_calc(command_result_t *result, command_file_ctx_t *cc,
+                          char *data, UNUSED size_t data_used, char *in, size_t inlen)
+{
+       fr_value_box_t *a, *b, *out;
+       size_t match_len;
+       fr_type_t type;
+       fr_token_t op;
+       char const *p, *value, *end;
+       size_t slen;
+
+       a = talloc_zero(cc->tmp_ctx, fr_value_box_t);
+       b = talloc_zero(cc->tmp_ctx, fr_value_box_t);
+       out = talloc_zero(cc->tmp_ctx, fr_value_box_t);
+
+       p = in;
+       end = in + inlen;
+
+       match_len = parse_typed_value(result, a, &value, p, end - p);
+       if (match_len == 0) return 0; /* errors have already been updated */
+
+       p += match_len;
+       fr_skip_whitespace(p);
+
+       op = fr_table_value_by_longest_prefix(&match_len, fr_tokens_table, p, end - p, T_INVALID);
+       if (op != T_INVALID) {
+               p += match_len;
+       } else {
+               op = token2op[(uint8_t) p[0]];
+               if (op == T_INVALID) RETURN_PARSE_ERROR(0);
+               p++;
+       }
+       fr_skip_whitespace(p);
+
+       match_len = parse_typed_value(result, b, &value, p, end - p);
+       if (match_len == 0) return 0;
+
+       p += match_len;
+       fr_skip_whitespace(p);
+
+       /*
+        *      If there's no output data type, then the code tries to
+        *      figure one out automatically.
+        */
+       if (!*p) {
+               type = FR_TYPE_NULL;
+       } else {
+               if (strncmp(p, "->", 2) != 0) RETURN_PARSE_ERROR(0);
+               p += 2;
+               fr_skip_whitespace(p);
+
+               type = fr_table_value_by_longest_prefix(&match_len, fr_value_box_type_table, p, end - p, FR_TYPE_MAX);
+               if (type == FR_TYPE_MAX) RETURN_PARSE_ERROR(0);
+               fr_value_box_init(out, type, NULL, false);
+       }
+
+       if (fr_value_calc(cc->tmp_ctx, out, type, a, op, b) < 0) RETURN_PARSE_ERROR(0);
+
+       slen = fr_value_box_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), out, NULL);
+       if (slen <= 0) RETURN_OK_WITH_ERROR();
+
+       RETURN_OK(slen);
+}
+
 /** Change the working directory
  *
  */
@@ -2583,6 +2668,11 @@ static fr_table_ptr_sorted_t     commands[] = {
                                        .usage = "attribute <attr> = <value>",
                                        .description = "Parse and reprint an attribute value pair, writing \"ok\" to the data buffer on success"
                                }},
+       { L("calc "),           &(command_entry_t){
+                                       .func = command_calc,
+                                       .usage = "calc <type1> <value1> <operator> <type2> <value2> -> <output-type>",
+                                       .description = "Perform calculations on value boxes",
+                               }},
        { L("cd "),             &(command_entry_t){
                                        .func = command_cd,
                                        .usage = "cd <path>",
diff --git a/src/lib/util/calc.c b/src/lib/util/calc.c
new file mode 100644 (file)
index 0000000..f739256
--- /dev/null
@@ -0,0 +1,470 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ *
+ * @file src/lib/util/calc.c
+ * @brief Functions to perform calculations on leaf values
+ *
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/util/strerror.h>
+#include "calc.h"
+
+#define swap(_a, _b) do { __typeof__ (a) _tmp = _a; _a = _b; _b = _tmp; } while (0)
+
+#define OVERFLOW (-3)
+#define INVALID  (-2)
+
+static int calc_date(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
+{
+       fr_value_box_t one, two;
+       bool overflow;
+
+       fr_assert(dst->type == FR_TYPE_DATE);
+
+       if ((a->type == FR_TYPE_DATE) && (b->type == FR_TYPE_DATE)) {
+               fr_strerror_const("Cannot perform operation on two dates");
+               return -1;
+       }
+
+       fr_assert(!dst->enumv); /* unix time is always seconds */
+
+       /*
+        *      Cast dates to time delta, do the conversions.
+        */
+       if (a->type != FR_TYPE_TIME_DELTA) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_TIME_DELTA, NULL, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != FR_TYPE_TIME_DELTA) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_TIME_DELTA, NULL, b) < 0) return -1;
+               b = &two;
+       }
+
+       switch (op) {
+       case T_OP_ADD:
+               dst->vb_date = fr_unix_time_from_integer(&overflow,
+                                                        fr_time_delta_unwrap(a->vb_time_delta) + fr_time_delta_unwrap(b->vb_time_delta),
+                                                        FR_TIME_RES_NSEC);
+               if (overflow) return OVERFLOW; /* overflow */
+               break;
+
+       case T_OP_SUB:
+               dst->vb_date = fr_unix_time_from_integer(&overflow,
+                                                        fr_time_delta_unwrap(a->vb_time_delta) - fr_time_delta_unwrap(b->vb_time_delta),
+                                                        FR_TIME_RES_NSEC);
+               if (overflow) return OVERFLOW; /* overflow */
+               break;
+
+       default:
+               return INVALID; /* invalid operator */
+       }
+
+       return 0;
+}
+
+static int calc_time_delta(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
+{
+       fr_value_box_t one, two;
+
+       fr_assert(dst->type == FR_TYPE_TIME_DELTA);
+
+       /*
+        *      We can subtract two dates to get a time delta, but we
+        *      cannot add two dates to get a time delta.
+        */
+       if ((a->type == FR_TYPE_DATE) && (b->type == FR_TYPE_DATE)) {
+               if (op != T_OP_SUB) {
+                       fr_strerror_const("Cannot perform operation on two dates");
+                       return -1;
+               }
+       }
+
+       /*
+        *      Unix times are always converted 1-1 to our internal
+        *      TIME_DELTA.
+        */
+       if (a->type == FR_TYPE_DATE) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_TIME_DELTA, NULL, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type == FR_TYPE_DATE) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_TIME_DELTA, NULL, b) < 0) return -1;
+               b = &two;
+       }
+
+       /*
+        *      We cast the inputs based on the destination time resolution.  So "5ms + 5" = "10ms".
+        */
+       if (a->type != FR_TYPE_TIME_DELTA) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_TIME_DELTA, dst->enumv, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != FR_TYPE_TIME_DELTA) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_TIME_DELTA, dst->enumv, b) < 0) return -1;
+               b = &two;
+       }
+
+       switch (op) {
+       case T_OP_ADD:
+               dst->vb_time_delta = fr_time_delta_wrap(fr_time_delta_unwrap(a->vb_time_delta) + fr_time_delta_unwrap(b->vb_time_delta));
+               break;
+
+       case T_OP_SUB:
+               dst->vb_time_delta = fr_time_delta_wrap(fr_time_delta_unwrap(a->vb_time_delta) - fr_time_delta_unwrap(b->vb_time_delta));
+               break;
+
+       default:
+               return INVALID; /* invalid operator */
+       }
+
+       return 0;
+
+}
+
+static int calc_octets(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
+{
+       uint8_t *buf;
+       size_t len;
+       fr_value_box_t one, two;
+
+       fr_assert(dst->type == FR_TYPE_OCTETS);
+
+       if (a->type != FR_TYPE_OCTETS) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_OCTETS, dst->enumv, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != FR_TYPE_OCTETS) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_OCTETS, dst->enumv, b) < 0) return -1;
+               b = &two;
+       }
+
+       len = a->length + b->length;
+
+       switch (op) {
+       case T_OP_PREPEND:      /* dst = b . a */
+               buf = talloc_array(ctx, uint8_t, len);
+               if (!buf) {
+               oom:
+                       fr_strerror_const("Out of memory");
+                       return -1;
+               }
+
+               memcpy(buf, b->vb_octets, b->vb_length);
+               memcpy(buf + b->vb_length, a->vb_octets, a->vb_length);
+
+               fr_value_box_clear_value(dst);
+               fr_value_box_memdup_shallow(dst, dst->enumv, buf, len, a->tainted | b->tainted);
+               break;
+
+       case T_OP_ADD:  /* dst = a . b */
+               buf = talloc_array(ctx, uint8_t, len);
+               if (!buf) goto oom;
+
+               memcpy(buf, a->vb_octets, a->vb_length);
+               memcpy(buf + a->vb_length, b->vb_octets, b->vb_length);
+
+               fr_value_box_clear_value(dst);
+               fr_value_box_memdup_shallow(dst, dst->enumv, buf, len, a->tainted | b->tainted);
+               break;
+
+       default:
+               return INVALID; /* invalid operator */
+       }
+
+       if (a != &one) fr_value_box_clear(&one);
+       if (b != &two) fr_value_box_clear(&two);
+
+       return 0;
+}
+
+static int calc_string(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
+{
+       char *buf;
+       size_t len;
+       fr_value_box_t one, two;
+
+       fr_assert(dst->type == FR_TYPE_STRING);
+
+       if (a->type != FR_TYPE_STRING) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_STRING, dst->enumv, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != FR_TYPE_OCTETS) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_STRING, dst->enumv, b) < 0) return -1;
+               b = &two;
+       }
+
+       len = a->length + b->length;
+
+       switch (op) {
+       case T_OP_PREPEND:      /* dst = b . a */
+               buf = talloc_array(ctx, char, len + 1);
+               if (!buf) {
+               oom:
+                       fr_strerror_const("Out of memory");
+                       return -1;
+               }
+
+               memcpy(buf, b->vb_strvalue, b->vb_length);
+               memcpy(buf + b->vb_length, a->vb_strvalue, a->vb_length);
+               buf[a->vb_length + b->vb_length] = '\0';
+
+               fr_value_box_clear_value(dst);
+               fr_value_box_strdup_shallow(dst, dst->enumv, buf, a->tainted | b->tainted);
+               break;
+
+       case T_OP_ADD:
+               buf = talloc_array(ctx, char, len + 1);
+               if (!buf) goto oom;
+
+               memcpy(buf, a->vb_strvalue, a->vb_length);
+               memcpy(buf + a->vb_length, b->vb_strvalue, b->vb_length);
+               buf[a->vb_length + b->vb_length] = '\0';
+
+               fr_value_box_clear_value(dst);
+               fr_value_box_strdup_shallow(dst, dst->enumv, buf, a->tainted | b->tainted);
+               break;
+
+       default:
+               return INVALID; /* invalid operator */
+       }
+
+       if (a != &one) fr_value_box_clear(&one);
+       if (b != &two) fr_value_box_clear(&two);
+
+       return 0;
+}
+
+static int cast_ipv4_addr(fr_value_box_t *out, fr_value_box_t const *in, fr_dict_attr_t const *enumv)
+{
+       switch (in->type) {
+       default:
+               return -1;
+
+       case FR_TYPE_STRING:
+               if (fr_value_box_from_str(NULL, out, FR_TYPE_IPV4_ADDR, enumv,
+                                         in->vb_strvalue, in->vb_length,
+                                         NULL, in->tainted) < 0) return -1;
+               break;
+
+       case FR_TYPE_IPV6_ADDR:
+               if (fr_value_box_cast(NULL, out, FR_TYPE_IPV4_ADDR, NULL, in) < 0) return -1;
+               break;
+
+       case FR_TYPE_IPV6_PREFIX:
+               if (fr_value_box_cast(NULL, out, FR_TYPE_IPV4_PREFIX, NULL, in) < 0) return -1;
+               break;
+
+       case FR_TYPE_BOOL:
+       case FR_TYPE_INT8:
+       case FR_TYPE_INT16:
+       case FR_TYPE_INT32:
+       case FR_TYPE_INT64:
+       case FR_TYPE_FLOAT32:
+       case FR_TYPE_FLOAT64:
+       case FR_TYPE_UINT8:
+       case FR_TYPE_UINT16:
+       case FR_TYPE_UINT32:
+       case FR_TYPE_UINT64:
+       case FR_TYPE_SIZE:
+               fr_value_box_copy(NULL, out, in);
+               break;
+       }
+
+       return 0;
+}
+
+static int calc_ipv4_addr(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *in1, fr_token_t op, fr_value_box_t const *in2)
+{
+       uint32_t num;
+       fr_value_box_t one, two;
+       fr_value_box_t *a, *b;
+
+       fr_assert(dst->type == FR_TYPE_IPV4_ADDR);
+
+       if (cast_ipv4_addr(&one, in1, dst->enumv) < 0) return -1;
+       a = &one;
+
+       if (cast_ipv4_addr(&two, in2, dst->enumv) < 0) return -1;
+       b = &two;
+
+       switch (op) {
+       case T_OP_ADD:
+               /*
+                *      For simplicity, make sure that the prefix is first.
+                */
+               if (b->type == FR_TYPE_IPV4_PREFIX) swap(a,b);
+
+               /*
+                *      We can only add something to a prefix, and
+                *      that something has to be a number.
+                */
+               if (a->type != FR_TYPE_IPV4_PREFIX) return INVALID;
+
+               switch (b->type) {
+               case FR_TYPE_INT8:
+               case FR_TYPE_INT16:
+               case FR_TYPE_INT64:
+               case FR_TYPE_FLOAT32:
+               case FR_TYPE_FLOAT64:
+                       if (fr_value_box_cast_in_place(ctx, b, FR_TYPE_INT32, NULL) < 0) return -1;
+                       FALL_THROUGH;
+
+               case FR_TYPE_INT32:
+                       if (b->vb_int32 < 0) return OVERFLOW;
+
+                       num = b->vb_int32;
+                       break;
+
+               case FR_TYPE_BOOL:
+               case FR_TYPE_UINT8:
+               case FR_TYPE_UINT16:
+               case FR_TYPE_UINT64:
+               case FR_TYPE_SIZE:
+                       if (fr_value_box_cast_in_place(ctx, b, FR_TYPE_UINT32, NULL) < 0) return -1;
+                       FALL_THROUGH;
+
+               case FR_TYPE_UINT32:
+                       num = b->vb_uint32;
+                       break;
+
+               default:
+                       /*
+                        *      Can't add an IP address to a prefix.
+                        */
+                       return INVALID;
+               }
+
+               /*
+                *      Trying to add a number outside of the given prefix.  That's not allowed.
+                */
+               if (num >= (((uint32_t) 1) << a->vb_ip.prefix)) return OVERFLOW;
+
+               dst->vb_ip.addr.v4.s_addr = a->vb_ip.addr.v4.s_addr + num;
+               dst->vb_ip.prefix = 0;
+               break;
+
+       default:
+               return INVALID;
+       }
+
+       return 0;
+}
+
+typedef int (*fr_binary_op_t)(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b);
+
+static const fr_binary_op_t calc_type[FR_TYPE_MAX] = {
+       [FR_TYPE_OCTETS] = calc_octets,
+       [FR_TYPE_STRING] = calc_string,
+
+       [FR_TYPE_DATE] = calc_date,
+       [FR_TYPE_TIME_DELTA] = calc_time_delta,
+
+       [FR_TYPE_IPV4_ADDR] = calc_ipv4_addr,
+};
+
+int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
+{
+       int rcode;
+
+       if (fr_type_is_structural(dst->type)) {
+               fr_strerror_const("Cannot calculate results for structural types");
+               return -1;
+       }
+
+       switch (op) {
+       case T_OP_CMP_EQ:
+       case T_OP_NE:
+       case T_OP_GE:
+       case T_OP_GT:
+       case T_OP_LE:
+       case T_OP_LT:
+               if ((hint != FR_TYPE_NULL) && (hint != FR_TYPE_BOOL)) {
+                       fr_strerror_const("Invalid destination type for comparison operator");
+                       return -1;
+               }
+
+               fr_value_box_clear_value(dst);
+               fr_value_box_init(dst, FR_TYPE_BOOL, NULL, false);      // just force it...
+
+               rcode = fr_value_box_cmp_op(op, a, b);
+               if (rcode < 0) {
+                       fr_strerror_const("Failed doing comparison");
+                       return -1;
+               }
+
+               dst->vb_bool = (rcode > 0);
+               return 0;
+
+       case T_OP_ADD:
+       case T_OP_SUB:
+       case T_OP_PREPEND:
+               if (hint == FR_TYPE_NULL) {
+                       if (a->type != b->type) {
+                               fr_strerror_const("not yet implemented");
+                               return -1;
+                       }
+
+                       fr_value_box_init(dst, hint, NULL, false);
+
+               } else if ((dst != a) && (dst != b)) {
+                       /*
+                        *      It's OK to use one of the inputs as
+                        *      the output.  But if we don't, ensure
+                        *      that the output value box is
+                        *      initialized.
+                        */
+                       fr_value_box_init(dst, hint, NULL, false);
+               }
+
+               if (!calc_type[dst->type]) {
+                       fr_strerror_printf("No handler has been implemented for leaf type %s",
+                                          fr_table_str_by_value(fr_value_box_type_table, dst->type, "<INVALID>"));
+                       rcode = -1;
+                       break;
+               }
+
+               rcode = calc_type[dst->type](ctx, dst, a, op, b);
+               break;
+
+       default:
+               rcode = INVALID;
+               break;
+       }
+
+       if (rcode == OVERFLOW) {
+               fr_strerror_printf("Value overflows/underflows when calculating answer for %s",
+                                  fr_table_str_by_value(fr_value_box_type_table, dst->type, "<INVALID>"));
+
+       } else if (rcode == INVALID) {
+               fr_strerror_printf("Invalid operator %s for destination type %s",
+                                  fr_tokens[op],
+                                  fr_table_str_by_value(fr_value_box_type_table, dst->type, "<INVALID>"));
+       }
+
+       return rcode;
+}
diff --git a/src/lib/util/calc.h b/src/lib/util/calc.h
new file mode 100644 (file)
index 0000000..c4867e7
--- /dev/null
@@ -0,0 +1,38 @@
+#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
+ */
+
+/**
+ * $Id$
+ *
+ * @file lib/util/edit.h
+ * @brief Structures and prototypes for editing lists.
+ *
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(calc_h, "$Id$")
+
+#include <freeradius-devel/util/value.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b);
+
+#ifdef __cplusplus
+}
+#endif
index b2ca3303dab8dc55c38793321a2d9135a2d481b5..1ccfa4a0a8405c693386da3eb792a7573b4c77e6 100644 (file)
@@ -10,6 +10,7 @@ SOURCES               := \
                   base16.c \
                   base32.c \
                   base64.c \
+                  calc.c \
                   cap.c \
                   cursor.c \
                   dbuff.c \
diff --git a/src/tests/unit/calc.txt b/src/tests/unit/calc.txt
new file mode 100644 (file)
index 0000000..d6593dd
--- /dev/null
@@ -0,0 +1,35 @@
+#
+#  Calculate various thingies.
+#
+
+# string append
+calc string "a" . string "b" -> string
+match ab
+
+# string prepend
+calc string "a" ^ string "b" -> string
+match ba
+
+# time deltas
+calc time_delta 1 + time_delta 2 -> time_delta
+match 3
+
+# dates can be subtracted, but not added.
+calc date "Jan 11 1970 00:00:00 UTC" - date "Jan 1 1970 00:00:00 UTC" -> time_delta
+match 864000
+
+#  One day earlier
+calc date "Jan 11 1970 00:00:00 UTC" -  time_delta 86400 -> date
+match Jan 10 1970 00:00:00 UTC
+
+calc date "Jan 11 1970 00:00:00 UTC" -  time_delta 1d -> date
+match Jan 10 1970 00:00:00 UTC
+
+#
+#  Comparisons don't need output data types
+#
+calc date "Jan 11 1970 00:00:00 UTC" < date "Jan 1 1970 00:00:00 UTC"
+match no
+
+count
+match 14