]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add integer to integer math
authorAlan T. DeKok <aland@freeradius.org>
Thu, 18 Nov 2021 17:59:08 +0000 (12:59 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 18 Nov 2021 17:59:08 +0000 (12:59 -0500)
with upcasting of intermediate results to the largest integer
type which can hold the value.  And then finally cast to the
final type when we have the result.

src/lib/util/calc.c
src/tests/unit/calc.txt

index f73925680cd2a6c3b7aab696af6a67f312b373f1..90faf441944fa9f64aa37b0ed7e8222557ce478b 100644 (file)
@@ -28,11 +28,163 @@ 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 swap(_a, _b) do { __typeof__ (_a) _tmp = _a; _a = _b; _b = _tmp; } while (0)
 
 #define OVERFLOW (-3)
 #define INVALID  (-2)
 
+/** Updates type (a,b) -> c
+ *
+ *  Note that we MUST have a less than b here
+ */
+static const fr_type_t upcast[FR_TYPE_FLOAT64 + 1][FR_TYPE_FLOAT64 + 1] = {
+       /*
+        *      Prefix + int --> ipaddr
+        */
+       [FR_TYPE_IPV4_PREFIX] = {
+               [FR_TYPE_UINT8] =   FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_UINT16] =  FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_UINT32] =  FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_UINT64] =  FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_SIZE] =    FR_TYPE_IPV4_ADDR,
+
+               [FR_TYPE_INT8] =    FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_INT16] =   FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_INT32] =   FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_INT64] =   FR_TYPE_IPV4_ADDR,
+
+               [FR_TYPE_FLOAT32] = FR_TYPE_IPV4_ADDR,
+               [FR_TYPE_FLOAT64] = FR_TYPE_IPV4_ADDR,
+       },
+
+       [FR_TYPE_IPV6_PREFIX] = {
+               [FR_TYPE_UINT8] =   FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_UINT16] =  FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_UINT32] =  FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_UINT64] =  FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_SIZE] =    FR_TYPE_IPV6_ADDR,
+
+               [FR_TYPE_INT8] =    FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_INT16] =   FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_INT32] =   FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_INT64] =   FR_TYPE_IPV6_ADDR,
+
+               [FR_TYPE_FLOAT32] = FR_TYPE_IPV6_ADDR,
+               [FR_TYPE_FLOAT64] = FR_TYPE_IPV6_ADDR,
+       },
+
+       /*
+        *      Various ints get cast to the next highest size which
+        *      can hold their values.
+        */
+       [FR_TYPE_UINT8] = {
+               [FR_TYPE_UINT16] = FR_TYPE_UINT16,
+               [FR_TYPE_UINT32] = FR_TYPE_UINT32,
+               [FR_TYPE_UINT64] = FR_TYPE_UINT64,
+               [FR_TYPE_SIZE] =   FR_TYPE_SIZE,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+
+               [FR_TYPE_INT16]  = FR_TYPE_INT16,
+               [FR_TYPE_INT32]  = FR_TYPE_INT32,
+               [FR_TYPE_INT64]  = FR_TYPE_INT64,
+
+               [FR_TYPE_TIME_DELTA] = FR_TYPE_TIME_DELTA,
+               [FR_TYPE_DATE]  = FR_TYPE_DATE,
+       },
+
+       [FR_TYPE_UINT16] = {
+               [FR_TYPE_UINT32] = FR_TYPE_UINT32,
+               [FR_TYPE_UINT64] = FR_TYPE_UINT64,
+               [FR_TYPE_SIZE] =   FR_TYPE_SIZE,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+
+               [FR_TYPE_INT32]  = FR_TYPE_INT32,
+               [FR_TYPE_INT64]  = FR_TYPE_INT64,
+
+               [FR_TYPE_TIME_DELTA]  = FR_TYPE_TIME_DELTA,
+               [FR_TYPE_DATE]  = FR_TYPE_DATE,
+       },
+
+       [FR_TYPE_UINT32] = {
+               [FR_TYPE_UINT64] = FR_TYPE_UINT64,
+               [FR_TYPE_SIZE] =   FR_TYPE_SIZE,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+
+               [FR_TYPE_INT64]  = FR_TYPE_INT64,
+
+               [FR_TYPE_TIME_DELTA]  = FR_TYPE_TIME_DELTA,
+               [FR_TYPE_DATE]  = FR_TYPE_DATE,
+       },
+
+       [FR_TYPE_UINT64] = {
+               [FR_TYPE_SIZE] =   FR_TYPE_SIZE,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+
+               [FR_TYPE_TIME_DELTA]  = FR_TYPE_TIME_DELTA,
+               [FR_TYPE_DATE]  = FR_TYPE_DATE,
+       },
+
+       [FR_TYPE_SIZE] = {
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+       },
+
+       [FR_TYPE_DATE] = {
+               [FR_TYPE_INT16] = FR_TYPE_DATE,
+               [FR_TYPE_INT32] = FR_TYPE_DATE,
+               [FR_TYPE_INT64] = FR_TYPE_DATE,
+               [FR_TYPE_TIME_DELTA]  = FR_TYPE_DATE,
+
+               [FR_TYPE_FLOAT32] = FR_TYPE_DATE,
+               [FR_TYPE_FLOAT64] = FR_TYPE_DATE,
+       },
+
+       /*
+        *      Signed ints
+        */
+       [FR_TYPE_INT8] = {
+               [FR_TYPE_INT16] = FR_TYPE_INT16,
+               [FR_TYPE_INT32] = FR_TYPE_INT32,
+               [FR_TYPE_INT64] = FR_TYPE_INT64,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+       },
+
+       [FR_TYPE_INT16] = {
+               [FR_TYPE_INT32] = FR_TYPE_INT32,
+               [FR_TYPE_INT64] = FR_TYPE_INT64,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+       },
+
+       [FR_TYPE_INT32] = {
+               [FR_TYPE_INT64] = FR_TYPE_INT64,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+       },
+
+       [FR_TYPE_INT64] = {
+               [FR_TYPE_TIME_DELTA] = FR_TYPE_TIME_DELTA,
+               [FR_TYPE_FLOAT32] = FR_TYPE_FLOAT32,
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+       },
+
+       [FR_TYPE_TIME_DELTA] = {
+               [FR_TYPE_FLOAT32] = FR_TYPE_TIME_DELTA,
+               [FR_TYPE_FLOAT64] = FR_TYPE_TIME_DELTA,
+       },
+
+       [FR_TYPE_FLOAT32] = {
+               [FR_TYPE_FLOAT64] = FR_TYPE_FLOAT64,
+       },
+
+};
+
+
 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;
@@ -213,7 +365,7 @@ static int calc_string(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t cons
                a = &one;
        }
 
-       if (b->type != FR_TYPE_OCTETS) {
+       if (b->type != FR_TYPE_STRING) {
                if (fr_value_box_cast(ctx, &two, FR_TYPE_STRING, dst->enumv, b) < 0) return -1;
                b = &two;
        }
@@ -259,18 +411,14 @@ static int calc_string(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t cons
        return 0;
 }
 
-static int cast_ipv4_addr(fr_value_box_t *out, fr_value_box_t const *in, fr_dict_attr_t const *enumv)
+static int cast_ipv4_addr(fr_value_box_t *out, fr_value_box_t const *in)
 {
        switch (in->type) {
        default:
+               fr_strerror_printf("Cannot operate on ipaddr and %s",
+                                  fr_table_str_by_value(fr_value_box_type_table, in->type, "<INVALID>"));
                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;
@@ -279,37 +427,45 @@ static int cast_ipv4_addr(fr_value_box_t *out, fr_value_box_t const *in, fr_dict
                if (fr_value_box_cast(NULL, out, FR_TYPE_IPV4_PREFIX, NULL, in) < 0) return -1;
                break;
 
+               /*
+                *      All of these get mashed to 32-bits.  The cast
+                *      operation will check bounds (both negative and
+                *      positive) on the run-time values.
+                */
        case FR_TYPE_BOOL:
+
+       case FR_TYPE_UINT8:
+       case FR_TYPE_UINT16:
+       case FR_TYPE_UINT32:
+       case FR_TYPE_UINT64:
+
+       case FR_TYPE_SIZE:
+
        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);
+               if (fr_value_box_cast(NULL, out, FR_TYPE_UINT32, NULL, in) < 0) return -1;
                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)
+static int calc_ipv4_addr(UNUSED 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;
+       if (cast_ipv4_addr(&one, in1) < 0) return -1;
        a = &one;
 
-       if (cast_ipv4_addr(&two, in2, dst->enumv) < 0) return -1;
+       if (cast_ipv4_addr(&two, in2) < 0) return -1;
        b = &two;
 
        switch (op) {
@@ -321,51 +477,119 @@ static int calc_ipv4_addr(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t c
 
                /*
                 *      We can only add something to a prefix, and
-                *      that something has to be a number.
+                *      that something has to be a number. The cast
+                *      operation already ensured that the number is
+                *      uint32, and is at least vaguely within the
+                *      allowed range.
                 */
                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;
+               if (b->type != FR_TYPE_UINT32) return INVALID;
 
-               case FR_TYPE_INT32:
-                       if (b->vb_int32 < 0) return OVERFLOW;
+               /*
+                *      Trying to add a number outside of the given prefix.  That's not allowed.
+                */
+               if (b->vb_uint32 >= (((uint32_t) 1) << a->vb_ip.prefix)) return OVERFLOW;
 
-                       num = b->vb_int32;
-                       break;
+               dst->vb_ip.addr.v4.s_addr = a->vb_ip.addr.v4.s_addr + b->vb_uint32;
+               dst->vb_ip.prefix = 0;
+               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;
+       default:
+               return INVALID;
+       }
 
-               case FR_TYPE_UINT32:
-                       num = b->vb_uint32;
-                       break;
+       return 0;
+}
 
-               default:
-                       /*
-                        *      Can't add an IP address to a prefix.
-                        */
-                       return INVALID;
-               }
+static int cast_ipv6_addr(fr_value_box_t *out, fr_value_box_t const *in)
+{
+       switch (in->type) {
+       default:
+               fr_strerror_printf("Cannot operate on ipv6addr and %s",
+                                  fr_table_str_by_value(fr_value_box_type_table, in->type, "<INVALID>"));
+               return -1;
+
+       case FR_TYPE_IPV4_ADDR:
+               if (fr_value_box_cast(NULL, out, FR_TYPE_IPV6_ADDR, NULL, in) < 0) return -1;
+               break;
+
+       case FR_TYPE_IPV4_PREFIX:
+               if (fr_value_box_cast(NULL, out, FR_TYPE_IPV6_PREFIX, NULL, in) < 0) return -1;
+               break;
+
+               /*
+                *      All of these get mashed to 64-bits.  The cast
+                *      operation will check bounds (both negative and
+                *      positive) on the run-time values.
+                */
+       case FR_TYPE_BOOL:
+
+       case FR_TYPE_UINT8:
+       case FR_TYPE_UINT16:
+       case FR_TYPE_UINT32:
+       case FR_TYPE_UINT64:
+
+       case FR_TYPE_SIZE:
+
+       case FR_TYPE_INT8:
+       case FR_TYPE_INT16:
+       case FR_TYPE_INT32:
+       case FR_TYPE_INT64:
+
+       case FR_TYPE_FLOAT32:
+       case FR_TYPE_FLOAT64:
+               if (fr_value_box_cast(NULL, out, FR_TYPE_UINT64, NULL, in) < 0) return -1;
+               break;
+       }
+
+       return 0;
+}
+
+static int calc_ipv6_addr(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *in1, fr_token_t op, fr_value_box_t const *in2)
+{
+       fr_value_box_t one, two;
+       fr_value_box_t *a, *b;
+
+       fr_assert(dst->type == FR_TYPE_IPV6_ADDR);
+
+       if (cast_ipv6_addr(&one, in1) < 0) return -1;
+       a = &one;
+
+       if (cast_ipv6_addr(&two, in2) < 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_IPV6_PREFIX) swap(a,b);
+
+               /*
+                *      We can only add something to a prefix, and
+                *      that something has to be a number. The cast
+                *      operation already ensured that the number is
+                *      uint32, and is at least vaguely within the
+                *      allowed range.
+                */
+               if (a->type != FR_TYPE_IPV6_PREFIX) return INVALID;
+
+               if (b->type != FR_TYPE_UINT64) 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;
+               if (b->vb_uint64 >= (((uint64_t) 1) << a->vb_ip.prefix)) return OVERFLOW;
 
-               dst->vb_ip.addr.v4.s_addr = a->vb_ip.addr.v4.s_addr + num;
+#if 0
+               /*
+                *      @todo - do the masking on stuff
+                */
+               dst->vb_ip.addr.v6.s_addr = a->vb_ip.addr.v6.s_addr + b->uint64;
+#endif
                dst->vb_ip.prefix = 0;
+               dst->vb_ip.scope_id = a->vb_ip.scope_id;
                break;
 
        default:
@@ -375,27 +599,332 @@ static int calc_ipv4_addr(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t c
        return 0;
 }
 
+static int calc_float32(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *in1, fr_token_t op, fr_value_box_t const *in2)
+{
+       fr_value_box_t one, two;
+       fr_value_box_t const *a = in1;
+       fr_value_box_t const *b = in2;
+
+       fr_assert(dst->type == FR_TYPE_FLOAT32);
+
+       /*
+        *      Intermediate calculations are done using increased precision.
+        */
+       if (a->type != FR_TYPE_FLOAT64) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_FLOAT64, NULL, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != FR_TYPE_FLOAT64) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_FLOAT64, NULL, b) < 0) return -1;
+               b = &two;
+       }
+
+       switch (op) {
+       case T_OP_ADD:
+               dst->vb_float32 = a->vb_float64 + b->vb_float64;
+               break;
+
+       case T_OP_SUB:
+               dst->vb_float32 = a->vb_float64 - b->vb_float64;
+               break;
+
+       default:
+               return INVALID;
+       }
+
+       return 0;
+
+}
+
+static int calc_float64(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *in1, fr_token_t op, fr_value_box_t const *in2)
+{
+       fr_value_box_t one, two;
+       fr_value_box_t const *a = in1;
+       fr_value_box_t const *b = in2;
+
+       fr_assert(dst->type == FR_TYPE_FLOAT64);
+
+       if (a->type != FR_TYPE_FLOAT64) {
+               if (fr_value_box_cast(ctx, &one, FR_TYPE_FLOAT64, NULL, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != FR_TYPE_FLOAT64) {
+               if (fr_value_box_cast(ctx, &two, FR_TYPE_FLOAT64, NULL, b) < 0) return -1;
+               b = &two;
+       }
+
+       switch (op) {
+       case T_OP_ADD:
+               dst->vb_float64 = a->vb_float64 + b->vb_float64;
+               break;
+
+       case T_OP_SUB:
+               dst->vb_float64 = a->vb_float64 - b->vb_float64;
+               break;
+
+       default:
+               return INVALID;
+       }
+
+       return 0;
+
+}
+
+#define CALC(_t) static int calc_ ## _t(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *in1, fr_token_t op, fr_value_box_t const *in2) \
+{ \
+       switch (op) { \
+       case T_OP_ADD: \
+               if (!fr_add(&dst->vb_ ## _t, in1->vb_ ## _t, in2->vb_ ## _t)) return OVERFLOW; \
+               break; \
+ \
+       case T_OP_SUB: \
+               if (!fr_sub(&dst->vb_ ## _t, in1->vb_ ## _t, in2->vb_ ## _t)) return OVERFLOW; \
+               break; \
+ \
+       default: \
+               return INVALID; \
+       } \
+ \
+       return 0; \
+}
+
+CALC(uint8)
+CALC(uint16)
+CALC(uint32)
+CALC(uint64)
+
+CALC(int8)
+CALC(int16)
+CALC(int32)
+CALC(int64)
+
 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,
+static const fr_binary_op_t calc_integer_type[FR_TYPE_MAX + 1] = {
+       [FR_TYPE_UINT8] =  calc_uint8,
+       [FR_TYPE_UINT16] = calc_uint16,
+       [FR_TYPE_UINT32] = calc_uint32,
+       [FR_TYPE_UINT64] = calc_uint64,
 
-       [FR_TYPE_DATE] = calc_date,
-       [FR_TYPE_TIME_DELTA] = calc_time_delta,
+       [FR_TYPE_INT8] =  calc_int8,
+       [FR_TYPE_INT16] = calc_int16,
+       [FR_TYPE_INT32] = calc_int32,
+       [FR_TYPE_INT64] = calc_int64,
 
-       [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)
+static int calc_integer(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *in1, fr_token_t op, fr_value_box_t const *in2)
 {
        int rcode;
+       fr_type_t type;
+       fr_value_box_t const *a = in1;
+       fr_value_box_t const *b = in2;
+       fr_value_box_t one, two, out;
+
+       /*
+        *      All of the types are the same.  Just do the work.
+        */
+       if ((dst->type == in1->type) &&
+           (dst->type == in2->type)) {
+               return calc_integer_type[dst->type](ctx, dst, in1, op, in2);
+       }
+
+       /*
+        *      Upcast to the largest type which will handle the
+        *      calculations.
+        */
+       type = dst->type;
+       if (upcast[type][a->type] != FR_TYPE_NULL) {
+               type = upcast[type][a->type];
+
+       } else if (upcast[a->type][type] != FR_TYPE_NULL) {
+               type = upcast[a->type][type];
+       }
 
-       if (fr_type_is_structural(dst->type)) {
-               fr_strerror_const("Cannot calculate results for structural types");
+       if (upcast[type][b->type] != FR_TYPE_NULL) {
+               type = upcast[type][b->type];
+
+       } else if (upcast[b->type][type] != FR_TYPE_NULL) {
+               type = upcast[b->type][type];
+       }
+
+       if (a->type != type) {
+               if (fr_value_box_cast(ctx, &one, type, NULL, a) < 0) return -1;
+               a = &one;
+       }
+
+       if (b->type != type) {
+               if (fr_value_box_cast(ctx, &two, type, NULL, b) < 0) return -1;
+               b = &two;
+       }
+
+       if (!calc_integer_type[type]) {
+               fr_strerror_const("Not yet implemented");
                return -1;
        }
 
+       fr_value_box_init(&out, type, dst->enumv, false);
+       rcode = calc_integer_type[type](ctx, &out, a, op, b);
+       if (rcode < 0) return rcode;
+
+       /*
+        *      Then once we're done, cast the result to the final
+        *      output type.
+        */
+       return fr_value_box_cast(ctx, dst, dst->type, dst->enumv, &out);
+}
+
+static const fr_binary_op_t calc_type[FR_TYPE_MAX] = {
+       [FR_TYPE_OCTETS]        = calc_octets,
+       [FR_TYPE_STRING]        = calc_string,
+
+       [FR_TYPE_IPV4_ADDR]     = calc_ipv4_addr,
+       [FR_TYPE_IPV6_ADDR]     = calc_ipv6_addr,
+
+       [FR_TYPE_UINT8]         = calc_integer,
+       [FR_TYPE_UINT16]        = calc_integer,
+       [FR_TYPE_UINT32]        = calc_integer,
+       [FR_TYPE_UINT64]        = calc_integer,
+
+       [FR_TYPE_SIZE]          = calc_integer,
+
+       [FR_TYPE_INT8]          = calc_integer,
+       [FR_TYPE_INT16]         = calc_integer,
+       [FR_TYPE_INT32]         = calc_integer,
+       [FR_TYPE_INT64]         = calc_integer,
+
+       [FR_TYPE_DATE]          = calc_date,
+       [FR_TYPE_TIME_DELTA]    = calc_time_delta,
+
+       [FR_TYPE_FLOAT32]       = calc_float32,
+       [FR_TYPE_FLOAT64]       = calc_float64,
+};
+
+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 = -1;
+       fr_value_box_t one, two;
+
+       fr_assert(fr_type_is_leaf(a->type));
+       fr_assert(fr_type_is_leaf(b->type));
+       fr_assert((hint == FR_TYPE_NULL) || fr_type_is_leaf(hint));
+
+       fr_value_box_init_null(&one);
+       fr_value_box_init_null(&two);
+
+       /*
+        *      We don't know what the output type should be.  Try to
+        *      guess based on a variety of factors.
+        */
+       if (hint == FR_TYPE_NULL) do {
+                       fr_type_t a_type, b_type;
+
+               switch (op) {
+               case T_OP_PREPEND:
+                       /*
+                        *      Pick the existing type if we have a
+                        *      variable-sized type.  Otherwise, pick
+                        *      octets.
+                        */
+                       if (fr_type_is_variable_size(a->type)) {
+                               hint = a->type;
+
+                       } else if (fr_type_is_variable_size(b->type)) {
+                               hint = b->type;
+
+                       } else {
+                               hint = FR_TYPE_OCTETS;
+                       }
+                       break;
+
+               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:
+                       /*
+                        *      Comparison operators always return
+                        *      "bool".
+                        */
+                       hint = FR_TYPE_BOOL;
+                       break;
+
+               case T_OP_ADD:
+               case T_OP_SUB:
+                       if (a->type == b->type) {
+                               hint = a->type;
+                               break;
+                       }
+
+                       /*
+                        *      Non-comparison operators: Strings of different types always
+                        *      results in octets.
+                        */
+                       if (fr_type_is_variable_size(a->type) && fr_type_is_variable_size(b->type)) {
+                               hint = FR_TYPE_OCTETS;
+                               break;
+                       }
+
+                       /*
+                        *      Nothing else set it.  If the input types are
+                        *      the same, then that must be the output type.
+                        */
+                       if (a->type == b->type) {
+                               hint = a->type;
+                               break;
+                       }
+
+                       /*
+                        *      Try to "up-cast" the types.  This is
+                        *      so that we can take (for example)
+                        *      uint8 + uint16, and have the output as
+                        *      uint16.
+                        */
+                       a_type = a->type;
+                       b_type = b->type;
+                       if (a_type > b_type) swap(a_type, b_type);
+
+                       hint = upcast[a_type][b_type];
+                       if (hint != FR_TYPE_NULL) {
+                               break;
+                       }
+
+                       /*
+                        *      No idea what to do. :(
+                        */
+                       fr_strerror_const("Unable to automatically determine output data type");
+                       goto done;
+
+               default:
+                       return INVALID;
+               }
+       } while (0);
+
+       /*
+        *      If we're doing operations between
+        *      STRING/OCTETS and another type, then cast the
+        *      variable sized type to the fixed size type.
+        *      Doing this casting here makes the rest of the
+        *      code simpler.
+        *
+        *      This isn't always the best thing to do, but it makes
+        *      sense in most situations.  It allows comparisons,
+        *      etc. to operate between strings and integers.
+        */
+       if (!fr_type_is_variable_size(hint)) {
+               if (fr_type_is_variable_size(a->type) && !fr_type_is_variable_size(b->type)) {
+                       if (fr_value_box_cast(NULL, &one, b->type, b->enumv, a) < 0) goto done;
+                       a = &one;
+
+               } else if (!fr_type_is_variable_size(a->type) && fr_type_is_variable_size(b->type)) {
+                       if (fr_value_box_cast(NULL, &two, a->type, a->enumv, b) < 0) goto done;
+                       b = &two;
+               }
+       }
+
        switch (op) {
        case T_OP_CMP_EQ:
        case T_OP_NE:
@@ -403,9 +932,10 @@ int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value
        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;
+               if (hint != FR_TYPE_BOOL) {
+                       fr_strerror_printf("Invalid destination type '%s' for comparison operator",
+                                          fr_table_str_by_value(fr_value_box_type_table, dst->type, "<INVALID>"));
+                       goto done;
                }
 
                fr_value_box_clear_value(dst);
@@ -413,12 +943,12 @@ int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value
 
                rcode = fr_value_box_cmp_op(op, a, b);
                if (rcode < 0) {
-                       fr_strerror_const("Failed doing comparison");
-                       return -1;
+                       goto done;
                }
 
                dst->vb_bool = (rcode > 0);
-               return 0;
+               rcode = 0;
+               break;
 
        case T_OP_ADD:
        case T_OP_SUB:
@@ -426,7 +956,7 @@ int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value
                if (hint == FR_TYPE_NULL) {
                        if (a->type != b->type) {
                                fr_strerror_const("not yet implemented");
-                               return -1;
+                               goto done;
                        }
 
                        fr_value_box_init(dst, hint, NULL, false);
@@ -442,7 +972,7 @@ int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value
                }
 
                if (!calc_type[dst->type]) {
-                       fr_strerror_printf("No handler has been implemented for leaf type %s",
+                       fr_strerror_printf("Cannot perform any operations for destination type %s",
                                           fr_table_str_by_value(fr_value_box_type_table, dst->type, "<INVALID>"));
                        rcode = -1;
                        break;
@@ -466,5 +996,11 @@ int fr_value_calc(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value
                                   fr_table_str_by_value(fr_value_box_type_table, dst->type, "<INVALID>"));
        }
 
+done:
+       if (rcode == 0) dst->tainted = a->tainted | b->tainted;
+
+       fr_value_box_clear(&one);
+       fr_value_box_clear(&two);
+
        return rcode;
 }
index d6593dd0aa5c81bcafab90dcc7fd55b1dcfc047a..31ca75a74f34d1daed9adc23951b77cd8860cbf1 100644 (file)
@@ -31,5 +31,41 @@ match Jan 10 1970 00:00:00 UTC
 calc date "Jan 11 1970 00:00:00 UTC" < date "Jan 1 1970 00:00:00 UTC"
 match no
 
+# we can "fake out" structs by just appending stuff
+calc ipaddr 127.0.0.1 . ipaddr 127.0.0.2 -> octets
+match 0x7f0000017f000002
+
+# this can be cast
+calc string "1" < uint32 2 -> bool
+match yes
+
+# this can't
+calc string "stuff" < uint32 2 -> bool
+match Failed parsing string as type 'uint32'
+
+calc uint8 255 + uint8 255 -> uint8
+match Value overflows/underflows when calculating answer for uint8
+
+calc uint8 127 + uint8 127 -> uint8
+match 254
+
+#
+#  Wildly varying types get intermediate values upcast to the "best"
+#  type which is likely to handle the result.  The final result is
+#  then cast to the output type.
+#
+calc int8 -1 + uint8 14 -> int16
+match 13
+
+calc int32 -1 + uint8 14 -> int16
+match 13
+
+#
+#  Intermediate values are too large for destination, but the
+#  resulting value can fit.
+#
+calc uint32 1000 - uint32 999 -> uint8
+match 1
+
 count
-match 14
+match 30