table's provide associative arrays: mappings from one set of values to another. The values being mapped are termed the index (or indices, if they come in groups of more than one) and the results of the mapping the yield.
Tables are quite powerful, and indexing them is very efficient, boiling down to a single hash table lookup. So you should take advantage of them whenever appropriate.
table [ typewhere type] of type
The indices can be of the following scalar types: numeric, temporal, enumerations, string, port, addr, or net. The yield can be of any type. So, for example:
global a: table[count] of string;declares a to be a table indexed by a count value and yielding a string value, similar to a regular array in a language like C. The yield type can also be more complex:
global a: table[count] of table[addr, port] of conn_id;declares a to be a table indexed by count and yielding another table, which itself is indexed by an addr and a port to yield a conn_id record.
This second example illustrates a multi-dimensional table, one indexed not by a single value but by a tuple of values.
[ expr-list ] = exprwhere expr-list is a comma-separated list of expressions corresponding to an index of the table (so, for a table indexed by count, for example, this would be a single expression of type count) and expr is the yield value to assign to that index.
For example,
global a: table[count] of string = { [11] = "eleven", [5] = "five", };initializes the table a to have two elements, one indexed by 11 and yielding the string "eleven" and the other indexed by 5 and yielding the string "five". (Note the comma after the last list element; it is optional, similar to how C allows final commas in declarations.)
You can also group together a set of indices together to initialize them to the same value:
type HostType: enum { DeskTop, Server, Router }; global a: table[addr] of HostType = { [[155.26.27.2, 155.26.27.8, 155.26.27.44]] = Server, };is equivalent to:
type HostType: enum { DeskTop, Server, Router }; global a: table[addr] of HostType = { [155.26.27.2] = Server, [155.26.27.8] = Server, [155.26.27.44] = Server, };This mechanism also applies to hostnames, which can be used in table initializations for any indices of type addr. For example, if www.my-server.com corresponded to the addresses 155.26.27.2 and 155.26.27.44, then the above could be written:
global a: table[addr] of HostType = { [[www.my-server.com, 155.26.27.8]] = Server, };and if it corresponded to all there, then:
global a: table[addr] of HostType = { [www.my-server.com] = Server, };
You can also use multiple index groupings across different indices:
global access_allowed: table[addr, port] of bool = { [www.my-server.com, [21/tcp, 80/tcp]] = T, };is equivalent to:
global access_allowed: table[addr, port] of bool = { [155.26.27.2, 21/tcp] = T, [155.26.27.2, 80/tcp] = T, [155.26.27.8, 21/tcp] = T, [155.26.27.8, 80/tcp] = T, [155.26.27.44, 21/tcp] = T, [155.26.27.44, 80/tcp] = T, };
Fix me: add example of cross-product initialization of sets
&default = exprexpr can have one of two forms. If it's type is the same as the table's yield type, then expr is evaluated and returned. If it's type is a function with arguments whose types correspond left-to-right with the index types of the table, and which returns a type the same as the yield type, then that function is called with the indices that yielded the missing value to compute the default value.
For example:
global a: table[count] of string &default = "nothing special";will return the string "nothing special" anytime a is indexed with a count value that does not appear in a.
A more dynamic example:
function nothing_special(): string { if ( panic_mode ) return "look out!"; else return "nothing special"; } global a: table[count] of string &default = nothing_special;
An example of using a function that computes using the index:
function make_pretty(c: count): string { return fmt("**%d**", c); } global a: table[count] of string &default = make_pretty;
[&create_expire] Specifies that elements in the table should be automatically deleted after a given amount of time has elapsed since they were first entered into the table. Syntax:
&create_expire = exprwhere expr is of type interval.
[&read_expire] The same as &create_expire except the element is deleted when the given amount of time has lapsed since the last time the element was accessed from the table.
[&write_expire] The same as &create_expire except the element is deleted when the given amount of time has lapsed since the last time the element was entered or modified in the table.
[&expire_func] Specifies a function to call when an element is due for expression because of &create_expire, &read_expire, or &write_expire. Syntax:
&expire_func = exprexpr must be a function that takes two arguments: the first one is a table with the same index and yield types as the associated table. The second one is of type any and corresponds to the index(es) of the element being expired. The function must return an interval value. The interval indicates for how much longer the element should remain in the table; returning 0 secs or a negative value instructs Bro to go ahead and delete the element.
Deficiency: The use of an any type here is temporary and will be changing in the future to a general tuple notion.
You specify multiple attributes by listing one after the other, without commas between them:
global a: table[count] of string &default="foo" &write_expire=5sec;Note that you can specify each type of attribute only once. You can, however, specify more than one of &create_expire, &read_expire, and &write_expire. In that case, whenever any of the corresponding timers expires, the element will be deleted.
You can also index arrays using record's, providing the record is comprised of values whose types match that of the table's indices. (Any record fields whose types are themselves records are recursively unpacked to effect this matching.) For example, if we have:
local b: table[addr, port] of conn_id; local c = 131.243.1.10; local d = 80/tcp;then we could index b using b[c, d], but if we had:
local e = [$field1 = c, $field2 = d];we could also index it using a[d]
You can test whether a table holds a given index using the in operator. For example:
[131.243.1.10, 80/tcp] in bor
e in bper the examples above. In addition, if the table has only a single index (not multi-dimensional), then you can omit the []'s:
local active_connections: table[addr] of conn_id; ... if ( 131.243.1.10 in active_connections ) ...
b[131.243.1.10, 80/tcp] = c$id;You can also assign to an entire table. For example, suppose we have the global:
global active_conn_count: table[addr, port] of count;then we could later clear the contents of the table using:
local empty_table: table[addr, port] of count; active_conn_count = empty_table;Here the first statement declares a local variable empty_table with the same type as active_conn_count. Since we don't initialize the table, it starts out empty. Assigning it to active_conn_count then replaces the value of active_conn_count with an empty table. Note: As with record's, assigning table values results in a shallow copy.
In addition to directly accessing an element of a table by specifying its index, you can also loop over all of the indices in a table using the for statement.
delete active_host[c$id];will remove the element in active_host corresponding to the connection identifier c$id (which is a conn_id record). If the element isn't present, nothing happens.