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),
* 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) {
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.
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;
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;
}
/*
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++;
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;
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
*/
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;
}
/*
- * 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);