This is an old revision of the document!
Table of Contents
The Select Framework
(written by Michal Matyska)
You might ask, why the select framework was introduced at all? The main reason was that everytime you wanted to check any part (header, etc.) of the incomming message you had to write new module/new function which did that. Moreover this function (due to historic limitations) might get at most two parameters. If you need more you have to use workaround and set attributes which are then checked from this function. So to make the route script more readable and understandable a new element has started to be supported - the select framework.
In the script the select is identified by it's unique name. The intention is
to make the identifier as much understandable as possible, so e.g. if you see
@to.uri.user
it is quite obvious, that you'd get the username from the uri
present in the "To" header. Wherever you see the select identifier in the
script, it is actually its value, which is then used during the route script
process.
To make the select framework speed efficient, the identifier of the select is parsed and fixed on the SER startup. If it is not valid select identifier, error message is dumped and SER does not start at all. But then, during the request route script execution, there is just single function call invocation, which gets the parsed select identifier as parameter if it needs it for any purpose. From the performance point of view there is almost no difference between calling specialized function from any module and calling function which represents the select identifier.
Overview of Operation
What we call select is basically unified notation for calling function which is defined as select function either in SER's core or from any module. The identifier starts with at sign and consists of few text elements denoted by dot optionally adding single integer or string parameter enclosed in square brackets.
You can use selects in the routing script in the expression evaluation, as
right side of attribute assignment and as parameters in some function calls.
(This is limited to functions which support this and the parameter is actually
text with the select identifier, so it must be enclosed in double quotes.)
Xlog formatting also support selects as a element while composing the final
value - the formatting element is %@select.identtifier.as.usual
.
Return value of the select is text string, but be aware, it might be also string with 0 length (empty string) if such value is allowed (e.g. some uri parameter value) or when select function signalizes that expected value was not found in the header or message parsing encountered syntax error. This kind of error has influence on the conditional expression - if there is problem with the select evaluation, the result is false regardless it matches the empty string.
Select Identifiers
Select identifier starts with at sign (@) followed by at least one (at most 30) text elements each separated by dot (.) from the previous one. The identifier elements are case insensitive, so you can e.g. use letter case to emphasize some of them, but lower case is preferred. Some of the select functions accept parameters (like index of the repetead headers, or the header name or authorization realm) - this parameter is expressed in the identifier by enclosing it with square brackets. The index starts counting from 1 (programmers be aware), because it is more natural and you can also express that you want count the headers in the opposite direction and get the last header (using -1 as the index).
Examples of identifiers (syntactically valid):
@the.simplest
@another.with!["parameter"]
@yet.another![1].parameter![2]
@you.can.also.mix.string!["like-this"].and.integer![1].parameters
Nested Selects
You might find some repeating patterns in the SIP requests, like that there is
URI element present in quite a lot of SIP headers. The select framework has
possibility to ease the developers' life reusing the URI parser and select
function already included in the core (or parser of parameter values (either
header or URI), or any other if you are able to think it off) and apply it on
intermediate select result. This so called nested select (see developers' doc
if you are interested) divides the select call to two (or more) separate
function calls, where the first one returns the URI text as temporary result,
which is then passed to the built-in URI select machinery as the value which
should be used for the final result. E.g.
@hf_value!["X-any-header"].nameaddr.uri.user
gets the value of X-any-header
header, assuming it is conforming to the name-addr RFC specification and
passes the header value to the name-addr select-parser, which separates the
username from the URI and returns that as the final value of the select.
So every of these URIs (select's results) can be passed to URI specialized select, which makes individual URI parts accessible.
@request_uri, @dst_uri, @next_hop, @from.uri, @to.uri, @refer_to.uri, @rpid.uri, @contact.uri, @record_route.uri or @hf_value!["any-header"].nameaddr.uri - all these return appropriate URI.
Now if you want just the username, append .user to the select identifier and you get the username. The .user identifier part is everytime same regardless which URI was selected in the first step.
Here is the list of all URI's nested select identifiers:
type
: returns the URI type in normalized (lowercase) text sip, sips, tel, telsuser
pwd
host
port
: all above is selfexplaining
transport
: returns the transport parameter value if present, otherwise the default transport used based on the URI type.hostport
: returns the host:port value from the URI. If the port is not present, the default value based on the URI type is used.params
: the whole parameters part of URI like user=phone;phone-context=+1234params!["parameter-name"]
: just the value of the specified parameter
Selects in Expressions
You can use the select in the conditional expressions like these:
if (@select.value) {...}
the test is true, when the select returns NON-EMPTY string.
if (@select.value!="")
or
if (!strempty(@select.value))
if (@select.value=="string") {...} if ("string"==@select.value) {...}
the result is true, when the select is error-free and its return value is equal to "string". Instead of a constant value, you can use any expression (it could involve avps, pvars or other selects).
if (@select.value!="string") {...} if ("string"!=@select.value) {...}
in this case the test checks whether the two strings are different. If there is error during the select evaluation, the result is also false.
if (@select.value=~"reg.*expr.?") {...}
true, when the string returned by select function matches the regular expression. In this case swapping of the left and right values matter, so
if ("string"=~@select.value) {...}
is true when the "string" matches the regular expression, which is obtained as select's return value. This is what you usually don't want.
You can also use the select in any kind of expression, be it in the right hand of an assignment, in an if, while() or switch().
E.g.:
$attribute=@the.select.you.want
Selects in Parameters
Some functions allow to pass the select's return value as parameter. The function must support that, so it is not possible for every function. The parameter holds the select identifier and the function is responsible to resolve and fixup the indetifier at SER startup and then call the select function to obtain the return value.
If the select's evaluation has stopped with error, then the return value of the function is false and the rest of the function's code is typically skipped over.
System Selects
As the select framework has begun to show its strength, new ideas whatever else could be accessible using select call has emerged. The example of that are system selects which's identifiers begin with @sys like:
* @sys.pid
: returns string representation of current process #
* @sys.now
: unix timestamp or
* @sys.utc
: UTC timestamp if you use different time zones
* @sys.unique
: generates and returns random UUID (type 2)
Selects in Modules
As you can see, the select framework is very powerfull tool, which can be easily extended using the SER's modules concept. E.g. the TLS module, there is lot of TLS information you can get/check in the script, but only when the TLS module is used. So all the TLS related selects are part of the TLS module… if you use the module, then they are available, if you don't use the module, they are not and SER will complain about the wrong select identifier and decline to start.
On the other hand, there is no difference between the module's selects and core's selects, so you don't really need to care where is the source of the select, just make sure you have loaded all neccessary modules.