From: Alan T. DeKok Date: Thu, 5 Oct 2023 14:09:12 +0000 (-0400) Subject: add calc n-ary op, for add, or, and, xor, etc. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f214b4473aefa0f704ce16beb1f33951f6def2f0;p=thirdparty%2Ffreeradius-server.git add calc n-ary op, for add, or, and, xor, etc. 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. --- diff --git a/src/bin/unit_test_attribute.c b/src/bin/unit_test_attribute.c index 29c3cd77a6..af58154b98 100644 --- a/src/bin/unit_test_attribute.c +++ b/src/bin/unit_test_attribute.c @@ -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 -> ", .description = "Perform calculations on value boxes", }}, + { L("calc_nary "), &(command_entry_t){ + .func = command_calc_nary, + .usage = "calc_nary op ... -> ", + .description = "Perform calculations on value boxes", + }}, { L("cd "), &(command_entry_t){ .func = command_cd, .usage = "cd ", diff --git a/src/lib/unlang/xlat_expr.c b/src/lib/unlang/xlat_expr.c index 0dc1e1c0bb..26ef625b51 100644 --- a/src/lib/unlang/xlat_expr.c +++ b/src/lib/unlang/xlat_expr.c @@ -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; } diff --git a/src/lib/util/calc.c b/src/lib/util/calc.c index d1ba7c8ba8..a5104091f7 100644 --- a/src/lib/util/calc.c +++ b/src/lib/util/calc.c @@ -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] = { diff --git a/src/lib/util/calc.h b/src/lib/util/calc.h index a28fb0c5e4..5e2ebb782a 100644 --- a/src/lib/util/calc.h +++ b/src/lib/util/calc.h @@ -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 index 0000000000..f4d4da6cff --- /dev/null +++ b/src/tests/unit/calc_nary.txt @@ -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