From: Alan T. DeKok Date: Thu, 18 Nov 2021 17:59:08 +0000 (-0500) Subject: add integer to integer math X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22ef2e7cde5fe64b58f824db6fa672c858d61d31;p=thirdparty%2Ffreeradius-server.git add integer to integer math 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. --- diff --git a/src/lib/util/calc.c b/src/lib/util/calc.c index f73925680cd..90faf441944 100644 --- a/src/lib/util/calc.c +++ b/src/lib/util/calc.c @@ -28,11 +28,163 @@ RCSID("$Id$") #include #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, "")); 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, "")); + 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, "")); + 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, "")); 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, "")); } +done: + if (rcode == 0) dst->tainted = a->tainted | b->tainted; + + fr_value_box_clear(&one); + fr_value_box_clear(&two); + return rcode; } diff --git a/src/tests/unit/calc.txt b/src/tests/unit/calc.txt index d6593dd0aa5..31ca75a74f3 100644 --- a/src/tests/unit/calc.txt +++ b/src/tests/unit/calc.txt @@ -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