]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
put 'case' statements into an htrie
authorAlan T. DeKok <aland@freeradius.org>
Fri, 16 Apr 2021 19:57:21 +0000 (15:57 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 16 Apr 2021 19:58:36 +0000 (15:58 -0400)
src/lib/unlang/compile.c
src/lib/unlang/switch.c
src/lib/unlang/switch_priv.h
src/tests/keywords/case-duplicate [new file with mode: 0644]

index a8b7fafb72b26b51ea4a3fadb12dc3f22a33a7f6..04ccc192b70787464bd897e298c38d59d419afbf 100644 (file)
@@ -1874,23 +1874,47 @@ static unlang_t *compile_group(unlang_t *parent, unlang_compile_t *unlang_ctx, C
        return compile_section(parent, unlang_ctx, cs, &group);
 }
 
+static int8_t case_cmp(void const *one, void const *two)
+{
+       unlang_case_t const *a = (unlang_case_t const *) one; /* may not be talloc'd! See switch.c */
+       unlang_case_t const *b = (unlang_case_t const *) two; /* may not be talloc'd! */
+
+       return fr_value_box_cmp(tmpl_value(a->vpt), tmpl_value(b->vpt));
+}
+
+static uint32_t case_hash(void const *data)
+{
+       unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */
+
+       return fr_value_box_hash(tmpl_value(a->vpt));
+}
+
+static int case_to_key(uint8_t **out, size_t *outlen, void const *data)
+{
+       unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */
+
+       return fr_value_box_to_key(out, outlen, tmpl_value(a->vpt));
+}
+
 static unlang_t *compile_case(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs);
 
 static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs)
 {
        CONF_ITEM               *ci;
-       fr_token_t              type;
+       fr_token_t              token;
        char const              *name1, *name2;
-       bool                    had_seen_default = false;
 
        unlang_group_t          *g;
-       unlang_switch_t *gext;
+       unlang_switch_t         *gext;
 
        unlang_t                *c;
        ssize_t                 slen;
 
        tmpl_rules_t            t_rules;
 
+       fr_type_t               type;
+       fr_htrie_type_t         htype;
+
        static unlang_ext_t const switch_ext = {
                .type = UNLANG_TYPE_SWITCH,
                .len = sizeof(unlang_switch_t),
@@ -1926,10 +1950,10 @@ static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx,
         *      The 'case' statements need g->vpt filled out to ensure
         *      that the data types match.
         */
-       type = cf_section_name2_quote(cs);
+       token = cf_section_name2_quote(cs);
        slen = tmpl_afrom_substr(gext, &gext->vpt,
                                 &FR_SBUFF_IN(name2, strlen(name2)),
-                                type,
+                                token,
                                 NULL,
                                 &t_rules);
        if (!gext->vpt) {
@@ -1982,6 +2006,33 @@ static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx,
 
        if (!tmpl_is_attr(gext->vpt)) (void) tmpl_cast_set(gext->vpt, FR_TYPE_STRING);
 
+       type = FR_TYPE_STRING;
+       if (gext->vpt->cast) {
+               type = gext->vpt->cast;
+
+       } else if (tmpl_is_attr(gext->vpt)) {
+               type = tmpl_da(gext->vpt)->type;
+       }
+
+       htype = fr_htrie_hint(type);
+       if (htype == FR_HTRIE_INVALID) {
+               cf_log_err(cs, "Invalid data type '%s' used for 'switch' statement",
+                           fr_table_str_by_value(fr_value_box_type_table, type, "???"));
+               talloc_free(g);
+               return NULL;
+       }
+
+       gext->ht = fr_htrie_alloc(gext, htype,
+                                 (fr_hash_t) case_hash,
+                                 (fr_cmp_t) case_cmp,
+                                 (fr_trie_key_t) case_to_key,
+                                 NULL);
+       if (!gext->ht) {
+               cf_log_err(cs, "Failed initializing internal data structures");
+               talloc_free(g);
+               return NULL;
+       }
+
        /*
         *      Walk through the children of the switch section,
         *      ensuring that they're all 'case' statements, and then compiling them.
@@ -1991,6 +2042,7 @@ static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx,
             ci = cf_item_next(cs, ci)) {
                CONF_SECTION *subcs;
                unlang_t *single;
+               unlang_case_t   *case_gext;
 
                if (!cf_item_is_section(ci)) {
                        if (!cf_item_is_pair(ci)) continue;
@@ -2024,13 +2076,11 @@ static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx,
                name2 = cf_section_name2(subcs);
                if (!name2) {
                handle_default:
-                       if (had_seen_default) {
+                       if (gext->default_case) {
                                cf_log_err(ci, "Cannot have two 'default' case statements");
                                talloc_free(g);
                                return NULL;
                        }
-
-                       had_seen_default = true;
                }
 
                /*
@@ -2042,6 +2092,30 @@ static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx,
                        return NULL;
                }
 
+               fr_assert(single->type == UNLANG_TYPE_CASE);
+
+               /*
+                *      Remember the "default" section, and insert the
+                *      non-default "case" into the htrie.
+                */
+               case_gext = unlang_group_to_case(unlang_generic_to_group(single));
+               if (!case_gext->vpt) {
+                       gext->default_case = single;
+
+               } else if (!fr_htrie_insert(gext->ht, single)) {
+                       single = fr_htrie_find(gext->ht, single);
+
+                       /*
+                        *      @todo - look up the key and get the previous one?
+                        */
+                       cf_log_err(ci, "Failed inserting 'case' statement.  Is there a duplicate?");
+
+                       if (single) cf_log_err(unlang_generic_to_group(single)->cs, "Duplicate may be here.");
+
+                       talloc_free(g);
+                       return NULL;
+               }
+
                *g->tail = single;
                g->tail = &single->next;
                g->num_children++;
index 921268b051ae1765270402cbdf12c78c4698be51..daf6bbdc5dcef9626d087ef532d6c863acc37852 100644 (file)
@@ -30,7 +30,7 @@ RCSID("$Id$")
 
 static unlang_action_t unlang_switch(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
 {
-       unlang_t                *this, *found, *null_case;
+       unlang_t                *found;
 
        unlang_group_t          *switch_g;
        unlang_switch_t         *switch_gext;
@@ -40,10 +40,28 @@ static unlang_action_t unlang_switch(rlm_rcode_t *p_result, request_t *request,
 
        fr_pair_t               *vp;
 
+       /*
+        *      Mock up an unlang_cast_t.  Note that these on-stack
+        *      buffers are the reason why case_cmp(), case_hash(),
+        *      and case_to_key() use direct casts, and not the
+        *      "generic to x" functions.
+        */
+       tmpl_t                  case_vpt = (tmpl_t) {
+               .type = TMPL_TYPE_DATA,
+       };
+       unlang_case_t           my_case = (unlang_case_t) {
+               .group = (unlang_group_t) {
+                       .self = (unlang_t) {
+                               .type = UNLANG_TYPE_CASE,
+                       },
+               },
+               .vpt = &case_vpt,
+       };
+
        switch_g = unlang_generic_to_group(frame->instruction);
        switch_gext = unlang_group_to_switch(switch_g);
 
-       null_case = found = NULL;
+       found = NULL;
 
        /*
         *      The attribute doesn't exist.  We can skip
@@ -51,21 +69,7 @@ static unlang_action_t unlang_switch(rlm_rcode_t *p_result, request_t *request,
         */
        if (tmpl_is_attr(switch_gext->vpt)) {
                if (tmpl_find_vp(&vp, request, switch_gext->vpt) < 0) {
-               find_null_case:
-                       for (this = switch_g->children; this; this = this->next) {
-                               unlang_group_t          *case_g;
-                               unlang_case_t   *case_gext;
-
-                               fr_assert(this->type == UNLANG_TYPE_CASE);
-
-                               case_g = unlang_generic_to_group(this);
-                               case_gext = unlang_group_to_case(case_g);
-                               if (case_gext->vpt) continue;
-
-                               found = this;
-                               break;
-                       }
-
+                       found = switch_gext->default_case;
                        goto do_null_case;
                } else {
                        box = &vp->data;
@@ -91,37 +95,18 @@ static unlang_action_t unlang_switch(rlm_rcode_t *p_result, request_t *request,
        }
 
        /*
-        *      Find either the exact matching name, or the
-        *      "case {...}" statement.
+        *      case_gext->vpt.data.literal is an in-line box, so we
+        *      have to make a shallow copy of its contents.
         */
-       for (this = switch_g->children; this; this = this->next) {
-               unlang_group_t  *case_g;
-               unlang_case_t   *case_gext;
-
-               fr_assert(this->type == UNLANG_TYPE_CASE);
-
-               case_g = unlang_generic_to_group(this);
-               case_gext = unlang_group_to_case(case_g);
+       fr_value_box_copy_shallow(request, &case_vpt.data.literal, box);
+       found = fr_htrie_find(switch_gext->ht, &my_case);
+       fr_value_box_clear(&case_vpt.data.literal);
 
-               /*
-                *      Remember the default case
-                */
-               if (!case_gext->vpt) {
-                       if (!null_case) null_case = this;
-                       continue;
-               }
-
-               /*
-                *      We have two values, just compare them.
-                */
-               if (fr_value_box_cmp(box, &case_gext->vpt->data.literal) == 0) {
-                       found = this;
-                       break;
-               }
+       if (!found) {
+       find_null_case:
+               found = switch_gext->default_case;
        }
 
-       if (!found) found = null_case;
-
 do_null_case:
        if (box == tmpl_value(&vpt)) fr_value_box_clear_value(&vpt.data.literal);
 
index 9c868b95b9917c6b0a0bc435ba9d3be2b447602d..4c5cf77524901ddfe56f72a9c5c697e1df8250a2 100644 (file)
@@ -27,10 +27,13 @@ extern "C" {
 #endif
 
 #include <freeradius-devel/server/tmpl.h>
+#include <freeradius-devel/util/htrie.h>
 
 typedef struct {
        unlang_group_t  group;
+       unlang_t        *default_case;
        tmpl_t          *vpt;
+       fr_htrie_t      *ht;
 } unlang_switch_t;
 
 /** Cast a group structure to the switch keyword extension
diff --git a/src/tests/keywords/case-duplicate b/src/tests/keywords/case-duplicate
new file mode 100644 (file)
index 0000000..0aae787
--- /dev/null
@@ -0,0 +1,19 @@
+# PRE: switch
+#
+switch &reply.Filter-Id {
+       case "filter" {
+            ok
+       }
+
+       case "filter" {  # ERROR
+               ok
+       }
+
+       case "fail" {
+               test_fail
+       }
+
+       case {
+               success
+       }
+}