]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add calc n-ary op, for add, or, and, xor, etc.
authorAlan T. DeKok <aland@freeradius.org>
Thu, 5 Oct 2023 14:09:12 +0000 (10:09 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 5 Oct 2023 19:20:19 +0000 (15:20 -0400)
This should make expressions a bit more efficient, as there will
be fewer intermediate nodes, and more work will be done at parse
time.

The expr wrappers aren't done, so that needs adding.  We will have
to do some cleanups of the resolve, etc. code for that to be best.

The logical operators || and && aren't implemented in calc.c,
because they are short-circuit operators, and calc.c gets a fully
evaluated list of value-boxes.

That being said, it's likely not much work to add short-circuit
operators to the calc.c code.

src/bin/unit_test_attribute.c
src/lib/unlang/xlat_expr.c
src/lib/util/calc.c
src/lib/util/calc.h
src/tests/unit/calc_nary.txt [new file with mode: 0644]

index 29c3cd77a661004b0e923056911c83527bba5c25..af58154b98184e78e38ea168da3279d313d4877b 100644 (file)
@@ -1268,6 +1268,70 @@ static size_t command_calc(command_result_t *result, command_file_ctx_t *cc,
        RETURN_OK(slen);
 }
 
+/** Perform calculations on multi-valued ops
+ *
+ */
+static size_t command_calc_nary(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 *group, *a, *out;
+       size_t match_len;
+       fr_type_t type;
+       fr_token_t op;
+       char const *p, *value, *end;
+       size_t slen;
+
+       group = talloc_zero(cc->tmp_ctx, fr_value_box_t);
+       fr_value_box_init(group, FR_TYPE_GROUP, NULL, false);
+
+       p = in;
+       end = in + inlen;
+
+       /*
+        *      Multi-valued operations
+        */
+       op = token2op[(uint8_t) p[0]];
+       if (op == T_INVALID) {
+               fr_strerror_printf("Unknown operator '%c'", p[0]);
+               RETURN_PARSE_ERROR(0);
+       }
+       p++;
+
+       while (p < end) {
+               fr_skip_whitespace(p);
+
+               a = talloc_zero(group, fr_value_box_t);
+
+               match_len = parse_typed_value(result, a, &value, p, end - p);
+               if (match_len == 0) return 0; /* errors have already been updated */
+
+               fr_value_box_list_insert_tail(&group->vb_group, a);
+
+               p += match_len;
+
+               if (strncmp(p, "->", 2) == 0) break;
+       }
+
+       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);
+
+
+       if (fr_value_calc_nary_op(cc->tmp_ctx, out, type, op, group) < 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
  *
  */
@@ -2870,6 +2934,11 @@ static fr_table_ptr_sorted_t     commands[] = {
                                        .usage = "calc <type1> <value1> <operator> <type2> <value2> -> <output-type>",
                                        .description = "Perform calculations on value boxes",
                                }},
+       { L("calc_nary "), &(command_entry_t){
+                                       .func = command_calc_nary,
+                                       .usage = "calc_nary op <type1> <value1> <type2> <value2> ... -> <output-type>",
+                                       .description = "Perform calculations on value boxes",
+                               }},
        { L("cd "),             &(command_entry_t){
                                        .func = command_cd,
                                        .usage = "cd <path>",
index 0dc1e1c0bb39421c3618c5e471888f42e54ab59d..26ef625b51f517a8c7b37007f032fac2f7097c38 100644 (file)
@@ -348,6 +348,23 @@ flags:
        return 0;
 }
 
+static void fr_value_box_init_zero(fr_value_box_t *vb, fr_type_t type)
+{
+       switch (type) {
+       case FR_TYPE_STRING:
+               fr_value_box_strdup_shallow(vb, NULL, "", false);
+               break;
+
+       case FR_TYPE_OCTETS:
+               fr_value_box_memdup_shallow(vb, NULL, (void const *) "", 0, false);
+               break;
+
+       default:
+               fr_value_box_init(vb, type, NULL, false);
+               break;
+       }
+}
+
 static xlat_arg_parser_t const binary_op_xlat_args[] = {
        { .required = false, .type = FR_TYPE_VOID },
        { .required = false, .type = FR_TYPE_VOID },
@@ -406,20 +423,7 @@ static xlat_action_t xlat_binary_op(TALLOC_CTX *ctx, fr_dcursor_t *out,
                        }
 
                        a = &one;
-
-                       switch (b->type) {
-                       case FR_TYPE_STRING:
-                               fr_value_box_strdup_shallow(a, NULL, "", false);
-                               break;
-
-                       case FR_TYPE_OCTETS:
-                               fr_value_box_memdup_shallow(a, NULL, (void const *) "", 0, false);
-                               break;
-
-                       default:
-                               fr_value_box_init(a, b->type, NULL, false);
-                               break;
-                       }
+                       fr_value_box_init_zero(a, b->type);
                }
 
                if (!b) {
@@ -429,20 +433,7 @@ static xlat_action_t xlat_binary_op(TALLOC_CTX *ctx, fr_dcursor_t *out,
                        }
 
                        b = &two;
-
-                       switch (a->type) {
-                       case FR_TYPE_STRING:
-                               fr_value_box_strdup_shallow(b, NULL, "", false);
-                               break;
-
-                       case FR_TYPE_OCTETS:
-                               fr_value_box_memdup_shallow(b, NULL, (void const *) "", 0, false);
-                               break;
-
-                       default:
-                               fr_value_box_init(b, a->type, NULL, false);
-                               break;
-                       }
+                       fr_value_box_init_zero(b, a->type);
                }
 
                rcode = fr_value_calc_binary_op(dst, dst, default_type, a, op, b);
@@ -842,7 +833,7 @@ typedef struct {
        fr_value_box_list_t     list;
 } xlat_logical_rctx_t;
 
-static fr_slen_t xlat_expr_print_logical(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, fr_sbuff_escape_rules_t const *e_rules)
+static fr_slen_t xlat_expr_print_nary(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, fr_sbuff_escape_rules_t const *e_rules)
 {
        size_t  at_in = fr_sbuff_used_total(out);
        xlat_logical_inst_t *inst = instance;
@@ -1667,7 +1658,7 @@ do { \
        if (unlikely((xlat = xlat_func_register(NULL, STRINGIFY(_name), xlat_func_ ## _func_name, FR_TYPE_VOID)) == NULL)) return -1; \
        xlat_func_async_instantiate_set(xlat, xlat_instantiate_ ## _func_name, xlat_ ## _func_name ## _inst_t, NULL, NULL); \
        xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_PURE | XLAT_FUNC_FLAG_INTERNAL); \
-       xlat_func_print_set(xlat, xlat_expr_print_ ## _func_name); \
+       xlat_func_print_set(xlat, xlat_expr_print_nary); \
        xlat_purify_func_set(xlat, xlat_expr_logical_purify); \
        xlat->token = _op; \
 } while (0)
@@ -1792,6 +1783,14 @@ static const bool logical_ops[T_TOKEN_LAST] = {
        [T_LOR] = true,
 };
 
+/*
+ *     These operators can take multiple arguments.
+ */
+static const bool multivalue_ops[T_TOKEN_LAST] = {
+       [T_LAND] = true,
+       [T_LOR] = true,
+};
+
 /*
  *     Allow for BEDMAS ordering.  Gross ordering is first number,
  *     fine ordering is second number.  Unused operators are assigned as zero.
@@ -2705,7 +2704,9 @@ redo:
                        fr_sbuff_set(&our_in, &m_rhs);
                        FR_SBUFF_ERROR_RETURN(&our_in);
                }
+       }
 
+       if (multivalue_ops[op]) {
                if ((lhs->type == XLAT_FUNC) && (lhs->call.func->token == op)) {
                        xlat_func_append_arg(lhs, rhs, cond);
 
@@ -2714,7 +2715,7 @@ redo:
                        goto redo;
                }
 
-               if (reparse_rcode(head, &lhs, true) < 0) goto fail_lhs;
+               if (logical_ops[op]) if (reparse_rcode(head, &lhs, true) < 0) goto fail_lhs;
                goto purify;
        }
 
index d1ba7c8ba86edb6e0c39678ced8668b0fc87414a..a5104091f7e9536fdb2226c74ee4885bc1794ff9 100644 (file)
@@ -2091,6 +2091,145 @@ done:
        return handle_result(hint, op, rcode);
 }
 
+/** Calculate DST = OP { A, B, C, ... }
+ *
+ *  The result is written to DST only *after* it has been calculated.
+ *  So it's safe to pass DST as one of the inputs.  DST should already
+ *  exist.
+ */
+int fr_value_calc_nary_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t type, fr_token_t op, fr_value_box_t const *group)
+{
+       fr_value_box_t out, *vb;
+       fr_binary_op_t calc;
+
+       if (group->type != FR_TYPE_GROUP) {
+               fr_strerror_const("Invalid type passed to multivalue calculation");
+               return -1;
+       }
+
+       if (fr_type_is_structural(type)) {
+       invalid_type:
+               fr_strerror_printf("Invalid operation %s for data type %s", fr_tokens[op], fr_type_to_str(type));
+               return -1;
+       }
+
+       if (type == FR_TYPE_STRING) {
+               fr_sbuff_t *sbuff;
+               bool secret = false;
+               bool tainted = false;
+
+               if (op != T_ADD) goto invalid_type;
+
+               FR_SBUFF_TALLOC_THREAD_LOCAL(&sbuff, 1024, (1 << 16));
+
+               if (fr_value_box_list_concat_as_string(&tainted, &secret, sbuff, UNCONST(fr_value_box_list_t *, &group->vb_group), NULL, 0, NULL, FR_VALUE_BOX_LIST_NONE, false) < 0) return -1;
+
+               if (fr_value_box_bstrndup(ctx, dst, NULL, fr_sbuff_start(sbuff), fr_sbuff_used(sbuff), tainted) < 0) return -1;
+
+               fr_value_box_set_secret(dst, secret);
+
+               return 0;               
+       }
+
+       if (type == FR_TYPE_OCTETS) {
+               fr_dbuff_t *dbuff;
+               bool secret = false;
+               bool tainted = false;
+
+               if (op != T_ADD) goto invalid_type;
+
+               FR_DBUFF_TALLOC_THREAD_LOCAL(&dbuff, 1024, (1 << 16));
+
+               if (fr_value_box_list_concat_as_octets(&tainted, &secret, dbuff, UNCONST(fr_value_box_list_t *, &group->vb_group), NULL, 0, FR_VALUE_BOX_LIST_NONE, false) < 0) return -1;
+
+               if (fr_value_box_memdup(ctx, dst, NULL, fr_dbuff_start(dbuff), fr_dbuff_used(dbuff), tainted) < 0) return -1;
+
+               fr_value_box_set_secret(dst, secret);
+
+               return 0;               
+       }
+
+       /*
+        *      Can't add or multiply booleans.
+        */
+       if ((type == FR_TYPE_BOOL) && !((op == T_AND) || (op == T_OR) || (op == T_XOR))) goto unsupported;
+
+       switch (op) {
+       case T_ADD:
+       case T_MUL:
+       case T_AND:
+       case T_OR:
+       case T_XOR:
+               break;
+
+       default:
+               goto invalid_type;
+       }
+
+       /*
+        *      Strings and octets are different.
+        */
+       if (!fr_type_is_numeric(type)) {
+       unsupported:
+               fr_strerror_printf("Not yet supported operation %s for data type %s", fr_tokens[op], fr_type_to_str(type));
+               return -1;
+       }
+
+       switch (type) {
+       case FR_TYPE_UINT8:
+       case FR_TYPE_UINT16:
+       case FR_TYPE_UINT32:
+       case FR_TYPE_UINT64:
+               calc = calc_uint64;
+               break;
+
+       case FR_TYPE_INT8:
+       case FR_TYPE_INT16:
+       case FR_TYPE_INT32:
+       case FR_TYPE_INT64:
+               calc = calc_int64;
+               break;
+
+       case FR_TYPE_FLOAT32:
+               calc = calc_float32;
+               break;
+
+       case FR_TYPE_FLOAT64:
+               calc = calc_float64;
+               break;
+
+       default:
+               goto unsupported;
+       }
+
+       vb = fr_value_box_list_head(&group->vb_group);
+       if (!vb) {
+               fr_strerror_printf("Empty input is invalid");
+               return -1;
+       }
+
+       if (fr_value_box_cast(ctx, &out, type, NULL, vb) < 0) return -1;
+
+       while ((vb = fr_value_box_list_next(&group->vb_group, vb)) != NULL) {
+               int rcode;
+               fr_value_box_t box;
+
+               if (vb->type == type) {
+                       rcode = calc(ctx, &out, &out, op, vb);
+                       if (rcode < 0) return rcode;
+
+               } else {
+                       if (fr_value_box_cast(ctx, &box, type, NULL, vb) < 0) return -1;
+
+                       rcode = calc(ctx, &out, &out, op, &box);
+                       if (rcode < 0) return rcode;
+               }
+       }
+
+       return fr_value_box_copy(ctx, dst, &out);
+}
+
+
 #define T(_x) [T_OP_ ## _x ## _EQ] = T_ ## _x
 
 static const fr_token_t assignment2op[T_TOKEN_LAST] = {
index a28fb0c5e42fb13bf15c889bf43dd04133d84a7a..5e2ebb782a62d217d5e7f9d5b7f5fa70d57a7944 100644 (file)
@@ -38,6 +38,8 @@ int fr_value_calc_assignment_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_token_t
 
 int fr_value_calc_unary_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_token_t op, fr_value_box_t const *src) CC_HINT(nonnull(1));
 
+int fr_value_calc_nary_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t type, fr_token_t op, fr_value_box_t const *group) CC_HINT(nonnull(2,5));
+
 int fr_value_calc_list_op(TALLOC_CTX *ctx, fr_value_box_t *box, fr_token_t op, fr_value_box_list_t const *list);
 
 int fr_value_calc_list_cmp(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_list_t const *list1, fr_token_t op, fr_value_box_list_t const *list2) CC_HINT(nonnull);
diff --git a/src/tests/unit/calc_nary.txt b/src/tests/unit/calc_nary.txt
new file mode 100644 (file)
index 0000000..f4d4da6
--- /dev/null
@@ -0,0 +1,35 @@
+#
+#  Calculate multi-value operations
+#
+calc_nary + uint8 1 uint16 2 uint32 3 -> uint32
+match 6
+
+calc_nary & uint8 255 uint16 192 uint32 127 -> uint8
+match 64
+
+calc_nary + string "foo " string "bar " string "baz" -> string
+match foo bar baz
+
+calc_nary + string "foo " octets 0x7e4a5a string "baz" -> string
+match foo ~JZbaz
+
+calc_nary + ipaddr 127.0.0.1 uint32 45 string "baz" -> string
+match 127.0.0.145baz
+
+calc_nary + octets 0xabcdef -> octets
+match 0xabcdef
+
+calc_nary + octets 0xabcdef octets 0x11223344 -> octets
+match 0xabcdef11223344
+
+calc_nary + octets 0xabcdef string "a" -> octets
+match 0xabcdef61
+
+calc_nary + octets 0xabcdef ipaddr 127.0.0.1 -> octets
+match 0xabcdef7f000001
+
+calc_nary + octets 0xabcdef ipaddr 127.0.0.1 string "foo" -> octets
+match 0xabcdef7f000001666f6f
+
+count
+match 20