From: Alan T. DeKok Date: Sun, 6 Apr 2025 10:23:46 +0000 (-0400) Subject: add cast of float to time_delta X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=36ecc26c8f4f3dc57d8380367527622e5e6bd924;p=thirdparty%2Ffreeradius-server.git add cast of float to time_delta and it turns out that the cast-time_delta test was wrong. Which resulted in discovering that parsing time_delta decimals was wrong. added "cast" function to unit_test_attribute. Added tests for casting to unit tests. update cast-time_delta test to use correct values, and to do delta comparisons for floating points --- diff --git a/src/bin/unit_test_attribute.c b/src/bin/unit_test_attribute.c index d261354f46d..e379a4525fb 100644 --- a/src/bin/unit_test_attribute.c +++ b/src/bin/unit_test_attribute.c @@ -1369,6 +1369,49 @@ static size_t command_calc_nary(command_result_t *result, command_file_ctx_t *cc RETURN_OK(slen); } +/** Perform casting + * + */ +static size_t command_cast(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, *out; + size_t match_len; + fr_type_t type; + char const *p, *value, *end; + size_t slen; + + a = 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); + + out = talloc_zero(cc->tmp_ctx, fr_value_box_t); + + 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_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_box_cast(out, out, type, NULL, a) < 0) { + RETURN_OK_WITH_ERROR(); + } + + 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 * */ @@ -3042,6 +3085,11 @@ static fr_table_ptr_sorted_t commands[] = { .usage = "calc_nary op ... -> ", .description = "Perform calculations on value boxes", }}, + { L("cast "), &(command_entry_t){ + .func = command_cast, + .usage = "cast (type) -> ", + .description = "Perform calculations on value boxes", + }}, { L("cd "), &(command_entry_t){ .func = command_cd, .usage = "cd ", diff --git a/src/lib/util/value.c b/src/lib/util/value.c index e674db3aebc..d9e6de05cfb 100644 --- a/src/lib/util/value.c +++ b/src/lib/util/value.c @@ -3224,6 +3224,161 @@ static inline int fr_value_box_cast_to_integer(TALLOC_CTX *ctx, fr_value_box_t * return 0; } + case FR_TYPE_FLOAT32: + if (src->vb_float32 < (double) fr_value_box_integer_min[dst_type]) { + underflow: + fr_strerror_const("Source value for cast would underflow destination type"); + return -1; + } + + if (src->vb_float32 > (double) fr_value_box_integer_max[dst_type]) { + overflow: + fr_strerror_const("Source value for cast would overflow destination type"); + return -1; + } + + switch (dst_type) { + case FR_TYPE_UINT8: + dst->vb_uint8 = src->vb_float32; + break; + + case FR_TYPE_UINT16: + dst->vb_uint16 = src->vb_float32; + break; + + case FR_TYPE_UINT32: + dst->vb_uint32 = src->vb_float32; + break; + + case FR_TYPE_UINT64: + dst->vb_uint64 = src->vb_float32; + break; + + case FR_TYPE_INT8: + dst->vb_int8 = src->vb_float32; + break; + + case FR_TYPE_INT16: + dst->vb_int16 = src->vb_float32; + break; + + case FR_TYPE_INT32: + dst->vb_int32 = src->vb_float32; + break; + + case FR_TYPE_INT64: + dst->vb_int64 = src->vb_float32; + break; + + case FR_TYPE_SIZE: + dst->vb_size = src->vb_float32; + break; + + case FR_TYPE_DATE: { + int64_t sec, nsec; + + sec = src->vb_float32; + sec *= NSEC; + nsec = ((src->vb_float32 * NSEC) - ((float) sec)); + + dst->vb_date = fr_unix_time_from_nsec(sec + nsec); + } + break; + + case FR_TYPE_TIME_DELTA: { + int64_t sec, nsec; + + sec = src->vb_float32; + sec *= NSEC; + nsec = ((src->vb_float32 * NSEC) - ((float) sec)); + + dst->vb_time_delta = fr_time_delta_from_nsec(sec + nsec); + } + break; + + default: + goto bad_cast; + } + return 0; + + case FR_TYPE_FLOAT64: + if (src->vb_float64 < (double) fr_value_box_integer_min[dst_type]) goto underflow; + + if (src->vb_float64 > (double) fr_value_box_integer_max[dst_type]) goto overflow; + + switch (dst_type) { + case FR_TYPE_UINT8: + dst->vb_uint8 = src->vb_float64; + break; + + case FR_TYPE_UINT16: + dst->vb_uint16 = src->vb_float64; + break; + + case FR_TYPE_UINT32: + dst->vb_uint32 = src->vb_float64; + break; + + case FR_TYPE_UINT64: + dst->vb_uint64 = src->vb_float64; + break; + + case FR_TYPE_INT8: + dst->vb_int8 = src->vb_float64; + break; + + case FR_TYPE_INT16: + dst->vb_int16 = src->vb_float64; + break; + + case FR_TYPE_INT32: + dst->vb_int32 = src->vb_float64; + break; + + case FR_TYPE_INT64: + dst->vb_int64 = src->vb_float64; + break; + + case FR_TYPE_SIZE: + dst->vb_size = src->vb_float64; + break; + + case FR_TYPE_DATE: { + int64_t sec, nsec; + + sec = src->vb_float64; + sec *= NSEC; + nsec = ((src->vb_float64 * NSEC) - ((double) sec)); + + /* + * @todo - respect time res + */ + dst->vb_date = fr_unix_time_from_nsec(sec + nsec); + } + break; + + case FR_TYPE_TIME_DELTA: { + int64_t sec, nsec; + int64_t res = NSEC; + bool fail = false; + + if (dst->enumv) res = fr_time_multiplier_by_res[dst->enumv->flags.flag_time_res]; + + sec = src->vb_float64; + sec *= res; + nsec = ((src->vb_float64 * res) - ((double) sec)); + + dst->vb_time_delta = fr_time_delta_from_integer(&fail, sec + nsec, + dst->enumv ? dst->enumv->flags.flag_time_res : FR_TIME_RES_NSEC); + if (fail) goto overflow; + } + break; + + default: + goto bad_cast; + } + return 0; + default: break; } diff --git a/src/tests/keywords/cast-time_delta b/src/tests/keywords/cast-time_delta index 8b5a22c8a9f..06efbced31b 100644 --- a/src/tests/keywords/cast-time_delta +++ b/src/tests/keywords/cast-time_delta @@ -18,7 +18,21 @@ if (%cast("weeks", 7d) != 1) { test_fail } -if (%cast("weeks", 8d) != 1.864) { +# +# Change of scale doesn't affect the numbers. Internally, the +# time_deltas are stored as nanoseconds since epoch. The scale only +# matters for parsing and printing. +# +if 7d != 1w { + test_fail +} + +# +# 8/7 is about 1.142.... But since the integers are 64-bit, +# they have a lot more precision than the floating point numbers. +# As a result, we look for approximate equality. +# +if ((%cast("weeks", 8d) - 1.14285714w) >= 1s) { test_fail } diff --git a/src/tests/unit/cast.txt b/src/tests/unit/cast.txt new file mode 100644 index 00000000000..63115293469 --- /dev/null +++ b/src/tests/unit/cast.txt @@ -0,0 +1,44 @@ +cast ipaddr 127.0.0.1 -> uint32 +match 2130706433 + +cast ipaddr 127.0.0.1 -> octets +match 0x7f000001 + +cast float64 0.5 -> time_delta +match 0.5 + +cast float64 -0.5 -> time_delta +match -0.5 + +cast float64 6.25 -> time_delta +match 6.25 + +cast float64 1024 -> uint8 +match Source value for cast would overflow destination type + +# +# Input can be qualified with a resolution. Output is seconds by default. +# +cast time_delta 6.25s -> time_delta +match 6.25 + +cast time_delta 1w -> time_delta +match 604800 + +cast time_delta 6.25ms -> time_delta +match 0.00625 + +cast time_delta 6.25us -> time_delta +match 0.00000625 + +cast time_delta 8d -> time_delta +match 691200 + +# +# This is 8/7 of a week, or 8 days +# +cast time_delta 1.14285714w -> time_delta +match 691199.998272 + +count +match 24