Skip to content

Commit

Permalink
add, test, and document &atrr-foo[&attr-num]
Browse files Browse the repository at this point in the history
  • Loading branch information
alandekok committed Sep 1, 2024
1 parent d36dc10 commit 9578a1b
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 62 deletions.
35 changes: 32 additions & 3 deletions doc/antora/modules/reference/pages/unlang/attr.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,51 @@ looks in the input packet list for the named attribute.
.Syntax
[source,unlang]
----
&Attribute-Name[<integer>]
&Attribute-Name[<index>]
----

When an attribute appears multiple times in a list, this syntax allows
you to address the attributes as if they were array entries. The
`<integer>` value defines which attribute to address. The `[0]` value
`<index>` value defines which attribute to address. The `[0]` value
refers to the first attributes, `[1]` refers to the second attribute,
etc.

.Examples
The `<index>` can be an integer (0..1000), as in the example below.

The indexes are limited to 1000, because there are essentially no
protocols which have more than 1000 attributes.

.Integer Array index
[source,unlang]
----
&EAP-Message[1]
&reply.NAS-IP-Address[2]
----

The `<index>` can also be a reference to a numerical attribute, as in the example below.

The reference *must* be to an attribute of numerical data type. Structural data types and `string` or `octets` types are not allowed. If the index is out of bounds (e.g. negative), then the reference fails.

The main utility of attribute indexes is in a xref:unlang/foreach.adoc[foreach] loop.

.Attribute reference as Array index
[source,unlang]
----
uint32 foo
&foo = 2
&EAP-Message[&foo]
----

The `<index>` can also be a special value `n`, which means "the last attribute in the list.

.Last attribute in a list
[source,unlang]
----
&EAP-Message[n]
----

=== Array References in lists

It is sometimes useful to refer to children of a list, without
Expand Down
28 changes: 17 additions & 11 deletions doc/antora/modules/reference/pages/xlat/attribute.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,21 @@ Examples of using references inside of a string:

Returns an integer containing the number of named attributes

`%{Attribute-Name[0]}`::

When an attribute appears multiple times in a list, this syntax allows
you to address the attributes as with array entries. `[0]` refers to
the first attributes, `[1]` refers to the second attribute, etc.

`%{Attribute-Name[*]}`::

Returns a comma-separated string containing all values for the named
attributes.

`%{Attribute-Name[n]}`::

Returns the last attribute in the list.

`%{Attribute-Name[<index>]}`::

When an attribute appears multiple times in a list, this syntax allows
you to address the attributes as with array entries. `[0]` refers to
the first attributes, `[1]` refers to the second attribute, etc. See the xref:unlang/attr.adoc[attribute reference page for more information.

== Lists and Grouping attributes

There is similar syntax for referencing children of a list, without
Expand All @@ -59,15 +63,17 @@ addressing the children by name.
`%{request.[#]}`::
Returns an integer containing the number of attributes contained in the `request` list. Any list name can be used.

`%{request.[0]}`::

Returns the value of the first attribute in the `request` list.

`%{request.[*]}`::

Returns a comma-separated string containing all values of attributes in the `request` list.

See also xref:unlang/attr.adoc[unlang attribute references].
`%{request.[n]}`::

Returns the last attribute in the list.

`%{request.[0]}`::

Returns the value of the first attribute in the `request` list. See the xref:unlang/attr.adoc[attribute reference page for more information.

Note that the old syntax of `%{request[*]}` will instead refer to
array entries of the `request` list. This is a change from previous
Expand Down
14 changes: 13 additions & 1 deletion src/lib/server/tmpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,22 @@ typedef enum {
TMPL_ATTR_FILTER_TYPE_NONE = 0, //!< No filter present.
TMPL_ATTR_FILTER_TYPE_INDEX, //!< Filter is an index type.
TMPL_ATTR_FILTER_TYPE_CONDITION, //!< Filter is a condition
TMPL_ATTR_FILTER_TYPE_TMPL, //!< Filter is a tmpl
} tmpl_attr_filter_type_t;

typedef struct {
tmpl_attr_filter_type_t _CONST type; //!< Type of filter this is.
int16_t _CONST num; //!< For array references.
xlat_exp_head_t _CONST *cond; //!< xlat condition

/*
* These are "union" because they are disjoint. The "num" field is arguably disjoint, too, but
* there is currently a lot of code in tmpl_tokenize.c which directly references ar->ar_num
* without checking the type.
*/
union {
xlat_exp_head_t _CONST *cond; //!< xlat condition
tmpl_t _CONST *tmpl; //!< tmpl
};
} tmpl_attr_filter_t;

/** An element in a list of nested attribute references
Expand Down Expand Up @@ -507,11 +517,13 @@ FR_DLIST_FUNCS(tmpl_request_list, tmpl_request_t, entry)

#define ar_num filter.num
#define ar_cond filter.cond
#define ar_tmpl filter.tmpl
#define ar_filter_type filter.type

#define ar_filter_is_none(_ar) ((_ar)->ar_filter_type == TMPL_ATTR_FILTER_TYPE_NONE)
#define ar_filter_is_num(_ar) ((_ar)->ar_filter_type == TMPL_ATTR_FILTER_TYPE_INDEX)
#define ar_filter_is_cond(_ar) ((_ar)->ar_filter_type == TMPL_ATTR_FILTER_TYPE_CONDITION)
#define ar_filter_is_tmpl(_ar) ((_ar)->ar_filter_type == TMPL_ATTR_FILTER_TYPE_TMPL)
/** @} */

/** A source or sink of value data.
Expand Down
59 changes: 54 additions & 5 deletions src/lib/server/tmpl_dcursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,58 @@ fr_pair_t *_tmpl_cursor_eval(fr_pair_t *curr, tmpl_dcursor_ctx_t *cc)
tmpl_dcursor_nested_t *ns;
fr_pair_t *iter = curr, *vp;
bool pop = false;
int16_t num = NUM_ALL;

ns = fr_dlist_tail(&cc->nested);
ar = ns->ar;
vp = fr_dcursor_current(&ns->cursor);

fr_assert(!ar || ar_filter_is_none(ar) || ar_filter_is_num(ar)); /* @todo - add evaluation of conditions */
if (!ar) goto all_inst;

if (ar) switch (ar->ar_num) {
/*
* Array indexes can be attribute references. In which case they must be castable to a uint8_t.
*
* i.e. there's likly no point in allowing the array ref to specify "none", or "any", or "count".
*
* Arguably it's useful to specify "all", but why? The main utility of the array reference is to
* index a particular attribute when looping over a list of attributes.
*/
if (ar_filter_is_tmpl(ar)) {
uint8_t ref;

fr_assert(ar_filter_is_tmpl(ar));
fr_assert(tmpl_is_attr(ar->ar_tmpl));

/*
* Can't cast it, we're done.
*/
if (tmpl_expand(&ref, NULL, 0, cc->request, ar->ar_tmpl, NULL, NULL) < 0) {
vp = NULL;
pop = true;
goto done;
}

num = ref;
goto find_num;
}

/*
* @todo - add dynamic evaluation of conditions. But that would work _only_ if the conditions
* aren't blocking, AND we somehow have a way for the conditions to reference a "self" attribute.
*/

/*
* No filter means "first one", unless the "foreach" code called tmpl_attr_rewrite_leaf_num(),
* which rewrites are_
*/
if (ar_filter_is_none(ar)) {
num = 0;
} else {
fr_assert(ar_filter_is_num(ar));
num = ar->ar_num;
}

switch (num) {
/*
* Get the first instance
*/
Expand All @@ -149,6 +193,9 @@ fr_pair_t *_tmpl_cursor_eval(fr_pair_t *curr, tmpl_dcursor_ctx_t *cc)
case NUM_ALL:
case NUM_COUNT:
all_inst:
/*
* @todo - arguably we shouldn't try building things here.
*/
if (!vp) pop = true; /* pop only when we're done */
fr_dcursor_next(&ns->cursor);
break;
Expand All @@ -167,20 +214,21 @@ fr_pair_t *_tmpl_cursor_eval(fr_pair_t *curr, tmpl_dcursor_ctx_t *cc)
* Get the n'th instance
*/
default:
find_num:
{
int16_t i = 0;

while ((i++ < ar->ar_num) && vp) vp = fr_dcursor_next(&ns->cursor);
while ((i++ < num) && vp) vp = fr_dcursor_next(&ns->cursor);
pop = true;
}
break;
} else goto all_inst;
}

/*
* If no pair was found and there is a fill
* callback, call that, depending on the suffix
*/
if (!vp && cc->build && ar) switch (ar->ar_num) {
if (!vp && cc->build && ar) switch (num) {
case NUM_UNSPEC:
case NUM_LAST:
case 0:
Expand All @@ -191,6 +239,7 @@ fr_pair_t *_tmpl_cursor_eval(fr_pair_t *curr, tmpl_dcursor_ctx_t *cc)
break;
}

done:
if (pop) tmpl_cursor_nested_pop(cc);

return vp;
Expand Down
Loading

0 comments on commit 9578a1b

Please sign in to comment.