]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add cast of float to time_delta
authorAlan T. DeKok <aland@freeradius.org>
Sun, 6 Apr 2025 10:23:46 +0000 (06:23 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Sun, 6 Apr 2025 10:25:52 +0000 (06:25 -0400)
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

src/bin/unit_test_attribute.c
src/lib/util/value.c
src/tests/keywords/cast-time_delta
src/tests/unit/cast.txt [new file with mode: 0644]

index d261354f46dcf69466315513959737479b5d1490..e379a4525fba2a1f7953d1e0bf88e9fcc6e3638d 100644 (file)
@@ -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 <type1> <value1> <type2> <value2> ... -> <output-type>",
                                        .description = "Perform calculations on value boxes",
                                }},
+       { L("cast "),           &(command_entry_t){
+                                       .func = command_cast,
+                                       .usage = "cast (type) <value> -> <output-type>",
+                                       .description = "Perform calculations on value boxes",
+                               }},
        { L("cd "),             &(command_entry_t){
                                        .func = command_cd,
                                        .usage = "cd <path>",
index e674db3aebc729a04b2ddade7b80c60684a067e0..d9e6de05cfb52e2dde7ee0dc102534491f0db00e 100644 (file)
@@ -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;
        }
index 8b5a22c8a9fb6c807b5ddf0dc34438f0c8f24fa2..06efbced31bae92ba8fd06ea6c085e8c8b03bee6 100644 (file)
@@ -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 (file)
index 0000000..6311529
--- /dev/null
@@ -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