# Definitions of Bro built-in functions.

%%{ // C segment
#include <vector>
#include <algorithm>

using namespace std;

RecordType* ftp_port;
RecordType* bro_resources;
RecordType* matcher_stats;
TableType* var_sizes;

int parse_int(const char*& fmt)
	{
	int k = 0;
	while ( isdigit(*fmt) )
		{
		k = k * 10 + (*fmt - '0');
		++fmt;
		}

	return k;
	}

static TypeTag ok_d_fmt[] = {
	TYPE_BOOL, TYPE_INT, TYPE_COUNT, TYPE_COUNTER, TYPE_PORT,
	TYPE_SUBNET,
	TYPE_ERROR
};
static TypeTag ok_f_fmt[] = {
	TYPE_DOUBLE, TYPE_TIME, TYPE_INTERVAL,
	TYPE_ERROR
};

static int check_fmt_type(TypeTag t, TypeTag ok[])
	{
	for ( int i = 0; ok[i] != TYPE_ERROR; ++i )
		if ( ok[i] == t )
			return 1;

	return 0;
	}

static void do_fmt(const char*& fmt, Val* v, ODesc* d)
	{
	TypeTag t = v->Type()->Tag();
	InternalTypeTag it = v->Type()->InternalType();

	int field_width = -1;
	if ( isdigit(*fmt) )
		field_width = parse_int(fmt);

	int precision = -1;
	if ( *fmt == '.' )
		{
		++fmt;
		precision = parse_int(fmt);
		}

	if ( field_width > 128 || precision > 128 )
		{
		builtin_run_time("excessive field width or precision");
		return;
		}

	ODesc s;

	if ( *fmt == 'A' )
		{
		s.SetStyle(ALTERNATIVE_STYLE);
		++fmt;
		}

	if ( precision >= 0 && *fmt != 'e' && *fmt != 'f' && *fmt != 'g' )
		builtin_run_time("precision specified for non-floating point");

	switch ( *fmt ) {
	case 'D':
		{
		if ( t != TYPE_TIME )
			{
			builtin_run_time("bad type for Date format", v);
			break;
			}

		time_t time = (time_t) v->InternalDouble();

		char buf[256];
		if ( ! strftime(buf, sizeof buf, "%Y-%m-%d-%H:%M:%S",
				localtime(&time)) )
			s.AddSP("<bad time>");
		else
			s.Add(buf);
		}
		break;

	case 'd':
		{
		if ( ! check_fmt_type(t, ok_d_fmt) )
			{
			builtin_run_time("bad type for integer format", v);
			break;
			}

		if ( it == TYPE_INTERNAL_UNSIGNED )
			{
			uint32 u = v->CoerceToUnsigned();

			if ( v->Type()->IsNetworkOrder() )
				{
				if ( v->Type()->Tag() == TYPE_PORT )
					u = v->AsPortVal()->Port();
				else
					u = ntohl(u);
				}
			s.Add(u);
			}
		else
			s.Add(v->CoerceToInt());
		}
		break;

	case 's':
		{
		v->Describe(&s);
		}
		break;

	case 'e':
	case 'f':
	case 'g':
		{
		if ( ! check_fmt_type(t, ok_f_fmt) )
			{
			builtin_run_time("bad type for floating-point format", v);
			break;
			}

		char float_fmt[512];
		char buf[512];
		if ( field_width >= 0 && precision >= 0 )
			sprintf(float_fmt, "%%%d.%d%c", field_width, precision,
				*fmt);
		else if ( precision >= 0 )
			sprintf(float_fmt, "%%.%d%c", precision, *fmt);
		else if ( field_width >= 0 )
			sprintf(float_fmt, "%%%d%c", field_width, *fmt);
		else
			sprintf(float_fmt, "%%%c", *fmt);

		sprintf(buf, float_fmt, v->CoerceToDouble());
		s.Add(buf);
		}
		break;

	default:
		builtin_run_time("bad format");
	}

	if ( field_width > 0 )
		{
		int sl = strlen(s.Description());
		while ( sl++ < field_width )
			d->Add(" ");
		}

	d->Add(s.Description());

	++fmt;
	}

static int next_fmt(const char*& fmt, val_list* args, ODesc* d, int& n)
	{
	const char* fp = fmt;

	// Skip up to next format indicator.
	while ( *fp && *fp != '%' )
		++fp;

	d->AddN(fmt, fp - fmt);

	if ( *fp == '\0' )
		// No more to do.
		return 0;

	fmt = fp + 1;
	if ( *fmt == '%' )
		{
		// "%%" -> '%'
		d->Add("%");
		++fmt;
		return next_fmt(fmt, args, d, n);
		}

	if ( ++n >= args->length() )
		return 0;

	do_fmt(fmt, (*args)[n], d);

	return *fmt != '\0';
	}
%%}

function length%(v: any%): count
	%{
	TableVal* tv = v->Type()->Tag() == TYPE_TABLE ? v->AsTableVal() : 0;

	if ( tv )
		return new Val(tv->Size(), TYPE_COUNT);

	else
		{
		builtin_run_time("length() requires a table/set argument");
		return new Val(0, TYPE_COUNT);
		}
	%}

function clear_table%(v: any%): any
	%{
	if ( v->Type()->Tag() == TYPE_TABLE )
		v->AsTableVal()->RemoveAll();
	else
		builtin_run_time("clear_table() requires a table/set argument");

	return 0;
	%}

function cat%(...%): string
	%{
	ODesc d;
	loop_over_list(@ARG@, i)
		@ARG@[i]->Describe(&d);

	BroString* s = new BroString(1, d.TakeBytes(), d.Len());
	s->SetUseFreeToDelete(1);

	return new StringVal(s);
	%}

function string_cat%(...%): string
	%{
	int n = 0;
	loop_over_list(@ARG@, i)
		n += @ARG@[i]->AsString()->Len();

	u_char* b = new u_char[n+1];
	BroString* s = new BroString(1, b, n);

	loop_over_list(@ARG@, j)
		{
		BroString* s = @ARG@[j]->AsString();
		memcpy(b, s->Bytes(), s->Len());
		b += s->Len();
		}
	*b = 0;

	return new StringVal(s);
	%}

%%{
int string_array_to_vs(TableVal* tbl, int start, int end, vector<BroString*>& vs)
	{
	vs.clear();
	for ( int i = start; i <= end; ++i )
		{
		Val* ind = new Val(i, TYPE_COUNT);
		Val* v = tbl->Lookup(ind);
		if ( ! v )
			return 0;
		vs.push_back(v->AsString());
#if 0
		char* str = v->AsString()->ExpandedString();
		DEBUG_MSG("string_array[%d] = \"%s\"\n", i, str);
		delete [] str;
#endif
		delete ind;
		}
	return 1;
	}

int vs_to_string_array(vector<BroString*>& vs, TableVal* tbl, int start, int end)
	{
	for ( int i = start, j = 0; i <= end; ++i, ++j )
		{
		Val* ind = new Val(i, TYPE_COUNT);
		tbl->Assign(ind, new StringVal(vs[j]->Len(), (const char *)vs[j]->Bytes()));
		Unref(ind);
		}
	return 1;
	}

BroString* cat_string_array_n(TableVal* tbl, int start, int end)
	{
	vector<BroString*> vs;
	string_array_to_vs(tbl, start, end, vs);
	return concatenate(vs);
	}
%%}

function cat_string_array%(a: string_array%): string
	%{
	TableVal* tbl = a->AsTableVal();
	return new StringVal(cat_string_array_n(tbl, 1, a->AsTable()->Length()));
	%}

function cat_string_array_n%(a: string_array, start: count, end: count%): string
	%{
	TableVal* tbl = a->AsTableVal();
	return new StringVal(cat_string_array_n(tbl, start, end));
	%}

function sort_string_array%(a: string_array%): string_array
	%{
	TableVal* tbl = a->AsTableVal();
	int n = a->AsTable()->Length();

	vector<BroString*> vs;
	string_array_to_vs(tbl, 1, n, vs);

	unsigned int i, j;
	for ( i = 0; i < vs.size(); ++i )
		{
		BroString* x = vs[i];
		for ( j = i; j > 0; --j )
			if ( Bstr_cmp(vs[j-1], x) <= 0 )
				break;
			else
				vs[j] = vs[j-1];
		vs[j] = x;
		}
	// sort(vs.begin(), vs.end(), Bstr_cmp);

	TableVal* b = new TableVal(internal_type("string_array")->AsTableType());
	vs_to_string_array(vs, b, 1, n);
	return b;
	%}

function fmt%(...%): string
	%{
	if ( @ARGC@ == 0 )
		return new StringVal("");

	Val* fmt_v = @ARG@[0];
	if ( fmt_v->Type()->Tag() != TYPE_STRING )
		return bro_cat(@ARGS@);

	const char* fmt = fmt_v->AsString()->CheckString();
	ODesc d;
	int n = 0;

	while ( next_fmt(fmt, @ARGS@, &d, n) )
		;

	if ( n < @ARGC@ - 1 )
		builtin_run_time("too many arguments for format", fmt_v);

	else if ( n >= @ARGC@ )
		builtin_run_time("too few arguments for format", fmt_v);

	BroString* s = new BroString(1, d.TakeBytes(), d.Len());
	s->SetUseFreeToDelete(1);

	return new StringVal(s);
	%}

function edit%(arg_s: string, arg_edit_char: string%): string
	%{
	const char* s = arg_s->CheckString();
	const char* edit_s = arg_edit_char->CheckString();

	if ( strlen(edit_s) != 1 )
		builtin_run_time("not exactly one edit character", @ARG@[1]);

	char edit_c = *edit_s;

	int n = strlen(s) + 1;
	char* new_s = new char[n];
	int ind = 0;

	for ( ; *s; ++s )
		{
		if ( *s == edit_c )
			{ // Delete last character
			if ( --ind < 0 )
				ind = 0;
			}
		else
			new_s[ind++] = *s;
		}

	new_s[ind] = '\0';

	return new StringVal(new BroString(1, byte_vec(new_s), ind));
	%}

function byte_len%(s: string%): count
	%{
	return new Val(s->Len(), TYPE_COUNT);
	%}

function sub_bytes%(s: string, start: count, n: int%): string
	%{
	unsigned int len = s->Len();

	if ( start > len )
		return new StringVal("");

	if ( start > 0 )
		--start;	// make it 0-based

	if ( n < 0 || n > int(len) - int(start) )
		n = len - start;

	byte_vec new_s = new u_char[n + 1];
	const u_char* b = s->Bytes();

	b += start;

	for ( int i = 0; i < n; ++i )
		new_s[i] = b[i];

	new_s[n] = '\0';

	return new StringVal(new BroString(1, new_s, n));
	//### LEAK
	%}

%%{
static int match_prefix(int s_len, const char* s, int t_len, const char* t)
	{
	for ( int i = 0; i < t_len; ++i )
		{
		if ( i >= s_len || s[i] != t[i] )
			return 0;
		}
	return 1;
	}

Val* do_split(BroString* str, RE_Matcher* re, TableVal* other_sep, int incl_sep, int max_num_sep)
	{
	TableVal* a = new TableVal(internal_type("string_array")->AsTableType());
	ListVal* other_strings = 0;

	if ( other_sep && other_sep->Size() > 0 )
		other_strings = other_sep->ConvertToPureList();

	// Currently let us assume that str is NUL-terminated. In
	// future we expect to change this by giving RE_Matcher a
	// const char* segment.

	const char* s = str->CheckString();
	int len = strlen(s);
	const char* end_of_s = s + len;
	int num = 0;
	int num_sep = 0;

	while ( 1 )
		{
		int offset = 0;
		const char* t;

		if ( max_num_sep > 0 && num_sep >= max_num_sep )
			t = end_of_s;
		else
			{
			for ( t = s; t < end_of_s; ++t )
				{
				offset = re->MatchPrefix(t);

				if ( other_strings )
					{
					val_list* vl = other_strings->Vals();
					loop_over_list(*vl, i)
						{
						BroString* sub = (*vl)[i]->AsString();
						if ( sub->Len() > offset &&
						     match_prefix(end_of_s - t, t, sub->Len(), (const char*) (sub->Bytes())) )
							{
							offset = sub->Len();
							}
						}
					}

				if ( offset )
					break;
				}
			}

		Val* ind = new Val(++num, TYPE_COUNT);
		a->Assign(ind, new StringVal(t - s, s));
		Unref(ind);

		if ( t >= end_of_s )
			break;

		++num_sep;

		if ( incl_sep )
			// including the part that matches the pattern
			{
			ind = new Val(++num, TYPE_COUNT);
			a->Assign(ind, new StringVal(offset, t));
			Unref(ind);
			}

		s = t + offset;
		if ( s > end_of_s )
			internal_error("RegMatch in split goes beyond the string");
		}

	if ( other_strings )
		delete other_strings;

	return a;
	}
%%}

# Similar to split in awk.

function split%(str: string, re: pattern%): string_array
	%{
	return do_split(str, re, 0, 0, 0);
	%}

# split1(str, pattern, include_separator): table[count] of string
#
# Same as split, except that str is only splitted (if possible) at the
# earliest position and an array of two strings is returned.
# An array of one string is returned when str cannot be splitted.

function split1%(str: string, re: pattern%): string_array
	%{
	return do_split(str, re, 0, 0, 1);
	%}

# Same as split, except that the array returned by split_all also
# includes parts of string that match the pattern in the array.

# For example, split_all("a-b--cd", /(\-)+/) returns {"a", "-", "b",
# "--", "cd"}: odd-indexed elements do not match the pattern
# and even-indexed ones do.

function split_all%(str: string, re: pattern%): string_array
	%{
	return do_split(str, re, 0, 1, 0);
	%}

function split_n%(str: string, re: pattern,
		incl_sep: bool, max_num_sep: count%): string_array
	%{
	return do_split(str, re, 0, incl_sep, max_num_sep);
	%}

function split_complete%(str: string,
		re: pattern, other: string_set,
		incl_sep: bool, max_num_sep: count%): string_array
	%{
	return do_split(str, re, other->AsTableVal(), incl_sep, max_num_sep);
	%}

function strcmp%(s1: string, s2: string%): int
	%{
	return new Val(strcmp(s1->CheckString(), s2->CheckString()), TYPE_INT);
	%}

%%{
int strstr_n(const int big_len, const u_char* big,
		const int little_len, const u_char* little)
	{
	for ( int i = 0; i <= big_len - little_len; ++i )
		{
		if ( ! memcmp(big + i, little, little_len) )
			return i;
		}
	return -1;
	}
%%}

# Returns 0 if $little is not found in $big.
function strstr%(big: string, little: string%): count
	%{
	return new Val(1 + strstr_n(big->Len(), big->Bytes(),
					little->Len(), little->Bytes()),
			TYPE_COUNT);
	%}

# Substitute each (non-overlapping) appearance of $from in $s to $to,
# and return the resulting string.
function subst_string%(s: string, from: string, to: string%): string
	%{
	const int little_len = from->Len();
	if ( little_len == 0 )
		return new StringVal(s);

	int big_len = s->Len();
	u_char* big = s->Bytes();
	data_chunk_t dc;
	vector<data_chunk_t> vs;

	while ( big_len >= little_len )
		{
		int j = strstr_n(big_len, big, little_len, from->Bytes());

		if ( j < 0 )
			break;

		if ( j > 0 )
			{
			dc.length = j; dc.data = (const char*) big;
			vs.push_back(dc);
			}

		dc.length = to->Len();
		dc.data = (const char*) (to->Bytes());
		vs.push_back(dc);

		j += little_len;
		big += j;
		big_len -= j;
		}

	if ( big_len > 0 )
		{
		dc.length = big_len; dc.data = (const char*) big;
		vs.push_back(dc);
		}

	return new StringVal(concatenate(vs));
	%}

function to_int%(str: string%): int
	%{
	const char* s = str->CheckString();
	char* end_s;

	long l = strtol(s, &end_s, 10);
	int i = int(l);

#if 0
	// Not clear we should complain.  For example, is " 205 "
	// a legal conversion?
	if ( s[0] == '\0' || end_s[0] != '\0' )
		builtin_run_time("bad conversion to integer", @ARG@[0]);
#endif

	return new Val(i, TYPE_INT);
	%}

function int_to_count%(n: int%): count
	%{
	if ( n < 0 )
		{
		builtin_run_time("bad conversion to count", @ARG@[0]);
		n = 0;
		}
	return new Val(n, TYPE_COUNT);
	%}

function to_count%(str: string%): count
	%{
	const char* s = str->CheckString();
	char* end_s;

	long l = strtol(s, &end_s, 10);
	uint32 u = uint32(l);

#if 0
	if ( s[0] == '\0' || end_s[0] != '\0' )
		builtin_run_time("bad conversion to count", @ARGS@[0]);
#endif

	return new Val(u, TYPE_COUNT);
	%}

function to_addr%(ip: string%): addr
	%{
	return new AddrVal(ip->ExpandedString());
	%}

function to_net%(a: addr%): net
	%{
#ifdef BROv6
	if ( ! is_v4_addr(a) )
		builtin_run_time("conversion of non-IPv4 address to net", @ARG@[0]);

	uint32 addr = to_v4_addr(a);
#else
	uint32 addr = a;
#endif

	addr = htonl(addr_to_net(ntohl(addr)));

	return new NetVal(addr);
	%}

function net_to_subnet%(a: net%): subnet
	%{
#ifdef BROv6
	if ( ! is_v4_addr(a) )
		{
		builtin_run_time("conversion of non-IPv4 address to subnet", @ARG@[0]);
		return new SubNetVal(uint32(0), 0);
		}

	uint32 addr = to_v4_addr(a);
#else
	uint32 addr = a;
#endif

	switch ( addr_to_class(ntohl(addr)) ) {
	case 'A':
		return new SubNetVal(addr, 8);
	case 'B':
		return new SubNetVal(addr, 16);
	case 'C':
	case 'D':
		return new SubNetVal(addr, 24);

	default:
		return new SubNetVal(addr, 0);
	}
	%}

function mask_addr%(a: addr, top_bits_to_keep: count%): addr
	%{
	return new AddrVal(mask_addr(a, top_bits_to_keep));
	%}

function is_tcp_port%(p: port%): bool
	%{
	return new Val(@ARG@[0]->AsPortVal()->IsTCP(), TYPE_BOOL);
	%}

function reading_live_traffic%(%): bool
	%{
	return new Val(reading_live, TYPE_BOOL);
	%}

function open%(f: string%): file
	%{
	return new Val(new BroFile(f->CheckString(), "w"));
	%}

function open_for_append%(f: string%): file
	%{
	return new Val(new BroFile(f->CheckString(), "a"));
	%}

function close%(f: file%): bool
	%{
	return new Val(f->Close(), TYPE_BOOL);
	%}

function set_buf%(f: file, buffered: bool%): any
	%{
	f->SetBuf(buffered);
	return new Val(0, TYPE_VOID);
	%}

function flush_all%(%): bool
	%{
	return new Val(fflush(0) == 0, TYPE_BOOL);
	%}

function mkdir%(f: string%): bool
	%{
	const char* filename = f->CheckString();
	if ( mkdir(filename, 0777) < 0 && errno != EEXIST )
		{
		builtin_run_time("cannot create directory", @ARG@[0]);
		return new Val(0, TYPE_BOOL);
		}
	else
		return new Val(1, TYPE_BOOL);
	%}

function active_file%(f: file%): bool
	%{
	return new Val(f->File() != 0, TYPE_BOOL);
	%}

function active_connection%(id: conn_id%): bool
	%{
	Connection* c = sessions->FindConnection(id);
	return new Val(c ? 1 : 0, TYPE_BOOL);
	%}

# Note, you *must* first make sure that the connection is active
# (e.g., by calling active_connection()) before invoking this.
function connection_record%(cid: conn_id%): connection
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( c )
		return c->BuildConnVal();
	else
		{
		// Hard to recover from this until we have union types ...
		builtin_run_time("connection id not a known connection (fatal)", cid);
		exit(0);
		return 0;
		}
	%}

function current_time%(%): time
	%{
	return new Val(current_time(), TYPE_TIME);
	%}

function network_time%(%): time
	%{
	return new Val(network_time, TYPE_TIME);
	%}

function getenv%(var: string%): string
	%{
	const char* env_val = getenv(var->CheckString());
	if ( ! env_val )
		env_val = "";	// ###
	return new StringVal(env_val);
	%}

function to_lower%(str: string%): string
	%{
	const char* s = str->CheckString();
	int n = strlen(s) + 1;
	char* lower_s = new char[n];

	char* ls;
	for ( ls = lower_s; *s; ++s )
		{
		if ( isascii(*s) && isupper(*s) )
			*ls++ = tolower(*s);
		else
			*ls++ = *s;
		}

	*ls = '\0';

	return new StringVal(new BroString(1, byte_vec(lower_s), n-1));
	%}

function to_upper%(str: string%): string
	%{
	const char* s = str->CheckString();
	int n = strlen(s) + 1;
	char* upper_s = new char[n];

	char* us;
	for ( us = upper_s; *s; ++s )
		{
		if ( isascii(*s) && islower(*s) )
			*us++ = toupper(*s);
		else
			*us++ = *s;
		}

	*us = '\0';

	return new StringVal(new BroString(1, byte_vec(upper_s), n-1));
	%}

function clean%(str: string%): string
	%{
	char* s = str->ExpandedString();
	return new StringVal(new BroString(1, byte_vec(s), strlen(s)));
	%}

function to_string_literal%(str: string%): string
	%{
	char* s = str->ExpandedString(BroString::BRO_STRING_LITERAL);
	return new StringVal(new BroString(1, byte_vec(s), strlen(s)));
	%}

function exit%(%): int
	%{
	exit(0);
	return 0;
	%}

function system%(str: string%): int
	%{
	const char* s = str->CheckString();
	const char* system_fmt = "(%s) 1>&2 &";	// output to stderr
	char* cmd = new char[strlen(system_fmt) + strlen(s) + 1];

	sprintf(cmd, system_fmt, s);
	int status = system(cmd);
	delete [] cmd;

	return new Val(status, TYPE_INT);
	%}

%%{
static Val* parse_port(const char* line)
	{
	RecordVal* r = new RecordVal(ftp_port);

	int bytes[6];
	if ( line && sscanf(line, "%d,%d,%d,%d,%d,%d",
			&bytes[0], &bytes[1], &bytes[2],
			&bytes[3], &bytes[4], &bytes[5]) == 6 )
		{
		int good = 1;

		for ( int i = 0; i < 6; ++i )
			if ( bytes[i] < 0 || bytes[i] > 255 )
				{
				good = 0;
				break;
				}

		uint32 addr = (bytes[0] << 24) | (bytes[1] << 16) |
				(bytes[2] << 8) | bytes[3];
		uint32 port = (bytes[4] << 8) | bytes[5];

		// Since port is unsigned, no need to check for < 0.
		if ( port > 65535 )
			{
			port = 0;
			good = 0;
			}

		r->Assign(0, new AddrVal(htonl(addr)));
		r->Assign(1, new PortVal(port, 1));
		r->Assign(2, new Val(good, TYPE_BOOL));
		}
	else
		{
		r->Assign(0, new AddrVal(uint32(0)));
		r->Assign(1, new PortVal(0, 1));
		r->Assign(2, new Val(0, TYPE_BOOL));
		}

	return r;
	}
%%}

# Returns true if the given connection exists, false otherwise.
function connection_exists%(c: conn_id%): bool
	%{
	if ( sessions->FindConnection(c) )
		return new Val(1, TYPE_BOOL);
	else
		return new Val(0, TYPE_BOOL);
	%}

# For a given connection ID, returns the corresponding "connection" record.
# Generates a run-time error and returns a dummy value if the connection
# doesn't exist.
function lookup_connection%(cid: conn_id%): connection
	%{
	Connection* conn = sessions->FindConnection(cid);
	if ( conn )
		return conn->BuildConnVal();

	builtin_run_time("connection id is not a known connection", cid);

	// Return a dummy connection record.
	RecordVal* c = new RecordVal(connection_type);

	RecordVal* id_val = new RecordVal(conn_id);
	id_val->Assign(0, new AddrVal((unsigned int) 0));
	id_val->Assign(1, new PortVal(ntohs(0), 0));
	id_val->Assign(2, new AddrVal((unsigned int) 0));
	id_val->Assign(3, new PortVal(ntohs(0), 0));
	c->Assign(0, id_val);

	RecordVal* orig_endp = new RecordVal(endpoint);
	orig_endp->Assign(0, new Val(0, TYPE_COUNT));
	orig_endp->Assign(1, new Val(int(0), TYPE_COUNT));

	RecordVal* resp_endp = new RecordVal(endpoint);
	resp_endp->Assign(0, new Val(0, TYPE_COUNT));
	resp_endp->Assign(1, new Val(int(0), TYPE_COUNT));

	c->Assign(1, orig_endp);
	c->Assign(2, resp_endp);

	c->Assign(3, new Val(network_time, TYPE_TIME));
	c->Assign(4, new Val(0.0, TYPE_INTERVAL));
	c->Assign(5, new StringVal(""));	// service
	c->Assign(6, new StringVal(""));	// addl
	c->Assign(7, new Val(0, TYPE_COUNT));	// hot

	return c;
	%}

function skip_further_processing%(cid: conn_id%): bool
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( ! c )
		{
		builtin_run_time("connection id is not a known connection", cid);
		return new Val(0, TYPE_BOOL);
		}

	c->SetSkip(1);
	return new Val(1, TYPE_BOOL);
	%}

function set_record_packets%(cid: conn_id, do_record: bool%): bool
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( ! c )
		{
		builtin_run_time("connection id is not a known connection", cid);
		return new Val(0, TYPE_BOOL);
		}

	c->SetRecordPackets(do_record);
	return new Val(1, TYPE_BOOL);
	%}

function set_contents_file%(cid: conn_id, direction: count, f: file%): bool
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( ! c )
		{
		builtin_run_time("connection id is not a known connection", cid);
		return new Val(0, TYPE_BOOL);
		}

	c->SetContentsFile(direction, f);
	return new Val(1, TYPE_BOOL);
	%}

# Set an individual inactivity timeout for this connection
# (overrides the global inactivity_timeout).  Returns previous
# timeout interval.
function set_inactivity_timeout%(cid: conn_id, timeout: interval%): interval
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( ! c )
		{
		builtin_run_time("connection id is not a known connection", cid);
		return new Val(0, TYPE_INTERVAL);
		}

	double old_timeout = c->InactivityTimeout();
	c->SetInactivityTimeout(timeout);

	return new Val(old_timeout, TYPE_INTERVAL);
	%}

function get_login_state%(cid: conn_id%): count
	%{
	LoginConn* lc;
	Connection* c = sessions->FindConnection(cid);
	if ( ! c || ! (lc = c->AsLoginConn()) )
		{
		builtin_run_time("connection id is not a known login connection", cid);
		return new Val(0, TYPE_COUNT);
		}

	return new Val(int(lc->LoginState()), TYPE_COUNT);
	%}

function set_login_state%(cid: conn_id, new_state: count%): bool
	%{
	LoginConn* lc;
	Connection* c = sessions->FindConnection(cid);
	if ( ! c || ! (lc = c->AsLoginConn()) )
		{
		builtin_run_time("connection id is not a known login connection", cid);
		return new Val(0, TYPE_BOOL);
		}

	lc->SetLoginState(login_state(new_state));
	return new Val(1, TYPE_BOOL);
	%}

function get_orig_seq%(cid: conn_id%): count
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( ! c )
		{
		builtin_run_time("connection id is not a known connection", cid);
		return new Val(0, TYPE_COUNT);
		}

	if ( c->ConnType() != CONN_TCP )
		{
		builtin_run_time("not a TCP connection", cid);
		return new Val(0, TYPE_COUNT);
		}

	TCP_Connection* tc = (TCP_Connection*) c;
	return new Val(tc->OrigSeq(), TYPE_COUNT);
	%}

function get_resp_seq%(cid: conn_id%): count
	%{
	Connection* c = sessions->FindConnection(cid);
	if ( ! c )
		{
		builtin_run_time("connection id not a known connection", cid);
		return new Val(0, TYPE_COUNT);
		}

	if ( c->ConnType() != CONN_TCP )
		{
		builtin_run_time("not a TCP connection", cid);
		return new Val(0, TYPE_COUNT);
		}

	TCP_Connection* tc = (TCP_Connection*) c;
	return new Val(tc->RespSeq(), TYPE_COUNT);
	%}

# These convert addresses <-> n3.n2.n1.n0.in-addr.arpa
function ptr_name_to_addr%(s: string%): addr
	%{
	int a[4];
	uint32 addr;

	if ( sscanf(s->CheckString(),
			"%d.%d.%d.%d.in-addr.arpa",
			a, a+1, a+2, a+3) != 4 )
		{
		builtin_run_time("bad PTR name", @ARG@[0]);
		addr = 0;
		}
	else
		addr = (a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0];

	return new AddrVal(htonl(addr));
	%}

function addr_to_ptr_name%(a: addr%): string
	%{
	// ## Question:
	// uint32 addr = ntohl((*args)[0]->InternalUnsigned());
	uint32 addr;
#ifdef BROv6
	if ( is_v4_addr(a) )
		addr = a[3];
	else
		{
		builtin_run_time("conversion of non-IPv4 address to net", @ARG@[0]);
		addr = 0;
		}
#else
	addr = a;
#endif

	addr = ntohl(addr);
	uint32 a3 = (addr >> 24) & 0xff;
	uint32 a2 = (addr >> 16) & 0xff;
	uint32 a1 = (addr >> 8) & 0xff;
	uint32 a0 = addr & 0xff;

	char buf[256];
	sprintf(buf, "%u.%u.%u.%u.in-addr.arpa", a0, a1, a2, a3);

	return new StringVal(buf);
	%}

# Transforms n0.n1.n2.n3 -> addr.
function parse_dotted_addr%(s: string%): addr
	%{
	return new AddrVal(dotted_to_addr(s->CheckString()));
	%}

function parse_ftp_port%(s: string%): ftp_port
	%{
	return parse_port(s->CheckString());
	%}

function parse_ftp_pasv%(str: string%): ftp_port
	%{
	const char* s = str->CheckString();
	const char* line = strchr(s, '(');
	if ( line )
		++line;	// move past '('
	else if ( (line = strstr(s, "PORT")) )
		line += 5;	// Skip over
	else if ( (line = strchr(s, ',')) )
		{ // Look for comma-separated list.
		while ( --line >= s && isdigit(*line) )
			;	// Back up over preceding digits.
		++line;	// now points to first digit, or beginning of s
		}

	return parse_port(line);
	%}

function fmt_ftp_port%(a: addr, p: port%): string
	%{
#ifdef BROv6
	if ( ! is_v4_addr(a) )
		builtin_run_time("conversion of non-IPv4 address to net", @ARG@[0]);

	uint32 addr = to_v4_addr(a);
#else
	uint32 addr = a;
#endif
	addr = ntohl(addr);
	return new StringVal(fmt("%d,%d,%d,%d,%d,%d",
					addr >> 24, (addr >> 16) & 0xff,
					(addr >> 8) & 0xff, addr & 0xff,
					p >> 8, p & 0xff));
	%}

%%{
#include "HTTP.h"

const char* conn_id_string(Val* c)
	{
	Val* id = (*(c->AsRecord()))[0];
	val_list* vl = id->AsRecord();

	addr_type orig_h = (*vl)[0]->AsAddr();
	uint32 orig_p = (*vl)[1]->AsPortVal()->Port();
	addr_type resp_h = (*vl)[2]->AsAddr();
	uint32 resp_p = (*vl)[3]->AsPortVal()->Port();

	return fmt("%s/%u -> %s/%u\n", dotted_addr(orig_h), orig_p, dotted_addr(resp_h), resp_p);
	}
%%}

# Skip data of the HTTP entity on the connection
function skip_http_entity_data%(c: connection, is_orig: bool%): any
	%{
	HTTP_Conn* hc = (HTTP_Conn*) c;
	// ## Question: how do we check if hc is an HTTP_Conn?
	if ( hc )
		hc->SkipEntityData(is_orig);
	else
		internal_error(fmt("connection %s has run away", conn_id_string(@ARG@[0])));
	return 0;
	%}

# Unescape all characters in the URI, i.e. decode every %xx group.
#
# Note that unescaping reserved characters may cause loss of
# information (see below).
#
# RFC 2396: A URI is always in an "escaped" form, since escaping or
# unescaping a completed URI might change its semantics.  Normally,
# the only time escape encodings can safely be made is when the URI
# is being created from its component parts.

function unescape_URI%(URI: string%): string
	%{
	const u_char* line = URI->Bytes();
	const u_char* const line_end = line + URI->Len();

	return new StringVal(unescape_URI(line, line_end, 0));
	%}

%%{
#include "SMTP.h"
%%}

# Skip smtp_data till next mail
function skip_smtp_data%(c: connection%): any
	%{
	SMTP_Conn* sc = (SMTP_Conn*) c;
	if ( sc )
		sc->SkipData();
	else
		internal_error(fmt("connection %s has run away", conn_id_string(@ARG@[0])));
	return 0;
	%}

%%{
static double start_time = 0.0;
%%}

function resource_usage%(%): bro_resources
	%{
	struct rusage r;

	if ( getrusage(RUSAGE_SELF, &r) < 0 )
		internal_error("getrusage() failed in bro_resource_usage()");

	double elapsed_time = current_time() - start_time;

	double user_time =
		double(r.ru_utime.tv_sec) + double(r.ru_utime.tv_usec) / 1e6;
	double system_time =
		double(r.ru_stime.tv_sec) + double(r.ru_stime.tv_usec) / 1e6;

	RecordVal* res = new RecordVal(bro_resources);

	res->Assign(0, new IntervalVal(elapsed_time, Seconds));
	res->Assign(1, new IntervalVal(user_time, Seconds));
	res->Assign(2, new IntervalVal(system_time, Seconds));

	res->Assign(3, new Val(unsigned(r.ru_maxrss), TYPE_COUNT));
	res->Assign(4, new Val(unsigned(r.ru_minflt), TYPE_COUNT));
	res->Assign(5, new Val(unsigned(r.ru_majflt), TYPE_COUNT));
	res->Assign(6, new Val(unsigned(r.ru_nswap), TYPE_COUNT));
	res->Assign(7, new Val(unsigned(r.ru_inblock), TYPE_COUNT));
	res->Assign(8, new Val(unsigned(r.ru_oublock), TYPE_COUNT));
	res->Assign(9, new Val(unsigned(r.ru_nivcsw), TYPE_COUNT));

	if ( sessions )
		{
		SessionStats s;
		sessions->GetStats(s);

		res->Assign(10, new Val(unsigned(s.num_TCP_conns), TYPE_COUNT));
		res->Assign(11, new Val(unsigned(s.num_UDP_conns), TYPE_COUNT));
		res->Assign(12, new Val(unsigned(s.num_ICMP_conns), TYPE_COUNT));
		res->Assign(13, new Val(unsigned(s.num_fragments), TYPE_COUNT));
		res->Assign(14, new Val(unsigned(s.num_timers), TYPE_COUNT));

		res->Assign(15, new Val(unsigned(s.max_TCP_conns), TYPE_COUNT));
		res->Assign(16, new Val(unsigned(s.max_UDP_conns), TYPE_COUNT));
		res->Assign(17, new Val(unsigned(s.max_ICMP_conns), TYPE_COUNT));
		res->Assign(18, new Val(unsigned(s.max_fragments), TYPE_COUNT));
		res->Assign(19, new Val(unsigned(s.max_timers), TYPE_COUNT));
		}

	else
		{
		for ( int i = 10; i <= 19; ++i )
			res->Assign(i, new Val(unsigned(0), TYPE_COUNT));
		}

	return res;
	%}

function get_matcher_stats%(%): matcher_stats
	%{
	RuleMatcher::Stats s;
	memset(&s, 0, sizeof(s));

	if ( rule_matcher )
		rule_matcher->GetStats(&s);

	RecordVal* r = new RecordVal(matcher_stats);
	r->Assign(0, new Val(s.matchers, TYPE_COUNT));
	r->Assign(1, new Val(s.states, TYPE_COUNT));
	r->Assign(2, new Val(s.computed, TYPE_COUNT));
	r->Assign(3, new Val(s.mem, TYPE_COUNT));
	r->Assign(4, new Val(s.hits, TYPE_COUNT));
	r->Assign(5, new Val(s.misses, TYPE_COUNT));

	return r;
	%}

function val_size%(v: any%): count
	%{
	return new Val(v->Size(), TYPE_COUNT);
	%}

function global_sizes%(%): var_sizes
	%{
	TableVal* sizes = new TableVal(var_sizes);
	PDict(ID)* globals = global_scope()->Vars();
	IterCookie* c = globals->InitForIteration();

	ID* id;
	while ( (id = globals->NextEntry(c)) )
		if ( id->HasVal() )
			{
			Val* id_name = new StringVal(id->Name());
			Val* id_size = new Val(id->ID_Val()->Size(), TYPE_COUNT);
			sizes->Assign(id_name, id_size);
			Unref(id_name);
			}

	return sizes;
	%}

%%{
#include "TCP_Rewriter.h"

// Trace rewriting functions.
class PacketDumper;
extern PacketDumper* transformed_pkt_dump;
extern void commit_trace(Val* conn_val, int commit, int future);
%%}

# True if we're rewriting (anonymizing), false otherwise.
function rewriting_trace%(%): bool
	%{
	return new Val(transformed_pkt_dump != 0, TYPE_BOOL);
	%}

%%{
#include "Anon.h"
%%}

# Preserve prefix as original one in anonymization.
function preserve_prefix%(a: addr, width: count%): any
	%{
	AnonymizeIPAddr* ip_anon = ip_anonymizer[PREFIX_PRESERVING_A50];
	if ( ip_anon )
		{
#ifdef BROv6
		if ( ! is_v4_addr(a) )
			builtin_run_time("preserve_prefix() not supported for IPv6 addresses");
		else
			ip_anon->PreservePrefix(a[3], width);
#else
		ip_anon->PreservePrefix(a, width);
#endif
		}


	return 0;
	%}

function preserve_subnet%(a: subnet%): any
	%{
	DEBUG_MSG(fmt("%s/%d\n", dotted_addr(a->AsAddr()), a->Width()));
	AnonymizeIPAddr* ip_anon = ip_anonymizer[PREFIX_PRESERVING_A50];
	if ( ip_anon )
		{
#ifdef BROv6
		if ( ! is_v4_addr(a->AsAddr()) )
			builtin_run_time("preserve_subnet() not supported for IPv6 addresses");
		else
			ip_anon->PreservePrefix(a->AsAddr()[3], a->Width());
#else
		ip_anon->PreservePrefix(a->AsAddr(), a->Width());
#endif
		}

	return 0;
	%}

function preserve_net%(a: net%): any
	%{
#ifdef BROv6
	builtin_run_time("preserve_net() not supported with --enable-BROv6");
#else
	AnonymizeIPAddr* ip_anon = ip_anonymizer[PREFIX_PRESERVING_A50];
	if ( ip_anon )
		ip_anon->PreserveNet(a);
#endif

	return 0;
	%}

# Anonymize given IP address.
function anonymize_addr%(a: addr, method: IPAddrAnonymization%): addr
	%{
	int anon_method = method->InternalInt();
	if ( anon_method < 0 || anon_method >= NUM_ADDR_ANONYMIZATION_METHODS )
		builtin_run_time("anonymize_addr(): invalid ip addr anonymization method");

	if ( anon_method == KEEP_ORIG_ADDR )
		return new AddrVal(a);

	AnonymizeIPAddr* ip_anon = ip_anonymizer[anon_method];

	if ( ip_anon )
		{
#ifdef BROv6
		if ( ! is_v4_addr(a) )
			{
			builtin_run_time("anonymize_addr() not supported for IPv6 addresses");
			return 0;
			}
		else
			return new AddrVal(ip_anon->Anonymize(a[3]));
#else
		return new AddrVal(ip_anon->Anonymize(a));
#endif
		}
	else
		{
		builtin_run_time("IP anonymizer is not initialized");
		return 0;
		}
	%}

# Return reassembly type.
function get_contents_type%(c: connection%): count
	%{
	if ( c->ConnType() == CONN_TCP )
		{
		TCP_Connection* tc = (TCP_Connection*) c;

		if ( tc->Orig()->IsTCP_EndpointContents() &&
		     tc->Resp()->IsTCP_EndpointContents() )
			return new Val(TCP_CONTENTS_BOTH, TYPE_COUNT);

		if ( tc->Orig()->IsTCP_EndpointContents() )
			return new Val(TCP_CONTENTS_ORIG, TYPE_COUNT);

		if ( tc->Resp()->IsTCP_EndpointContents() )
			return new Val(TCP_CONTENTS_RESP, TYPE_COUNT);
		}

	return new Val(TCP_CONTENTS_NONE, TYPE_COUNT);
	%}


%%{
static void hash_md5(val_list& vlist, unsigned char digest[16])
	{
	md5_state_s h;

	md5_init(&h);
	loop_over_list(vlist, i)
		{
		Val* v = vlist[i];
		if ( v->Type()->Tag() == TYPE_STRING )
			{
			BroString* str = v->AsString();
			md5_append(&h, str->Bytes(), str->Len());
			}
		else
			{
			ODesc d(DESC_BINARY);
			v->Describe(&d);
			md5_append(&h, (const md5_byte_t *) d.Bytes(), d.Len());
			}
		}
	md5_finish(&h, digest);
	}

static void hmac_md5(val_list& vlist, unsigned char digest[16])
	{
	hash_md5(vlist, digest);
	for ( int i = 0; i < 16; ++i )
		digest[i] = digest[i] ^ shared_hmac_md5_key[i];
	hash_md5(16, digest, digest);
	}
%%}

function md5_hash%(...%): string
	%{
	unsigned char digest[16];
	hash_md5(@ARG@, digest);
	return new StringVal(md5_digest_print(digest));
	%}

function md5_hmac%(...%): string
	%{
	unsigned char digest[16];
	hmac_md5(@ARG@, digest);
	return new StringVal(md5_digest_print(digest));
	%}

# The following functions are attempts to convert strings into
# patterns at run-time. These attempts were later *abandoned* because
# NFA and DFA cannot be cleanly deallocated.

function merge_pattern%(p1: pattern, p2: pattern%): pattern
	%{
	if ( reading_live )
		{
		builtin_run_time("should not call merge_pattern while reading live traffic");
		return 0;
		}

	RE_Matcher* re = new RE_Matcher();
	re->AddPat(p1->PatternText());
	re->AddPat(p2->PatternText());
	re->Compile();
	return new PatternVal(re);
	%}

%%{
char* to_pat_str(int sn, const char* ss)
	{
	const char special_re_char[] = "^$-:\"\\/|*+?.(){}[]";

	char* pat = new char[sn * 4 + 1];
	int pat_len = 0;

	for ( int i = 0; i < sn; ++i )
		{
		if ( ! strchr(special_re_char, ss[i]) )
			pat[pat_len++] = ss[i];
		else
			{
			pat[pat_len++] = '\\';
			pat[pat_len++] = ss[i];
			}
		}
	pat[pat_len] = '\0';
	return pat;
	}
%%}

function convert_for_pattern%(s: string%): string
	%{
	char* t = to_pat_str(s->Len(), (const char*)(s->Bytes()));
	StringVal* ret = new StringVal(t);
	delete[] t;
	return ret;
	%}

function string_to_pattern%(s: string, convert: bool%): pattern
	%{
	if ( reading_live )
		{
		builtin_run_time("should not call merge_pattern while reading live traffic");
		return 0;
		}

	const char* ss = (const char*) (s->Bytes());
	int sn = s->Len();
	char* pat;

	if ( convert )
		pat = to_pat_str(sn, ss);
	else
		{
		pat = new char[sn+1];
		memcpy(pat, ss, sn);
		pat[sn] = '\0';
		}

	RE_Matcher* re = new RE_Matcher(pat);
	delete[] pat;
	re->Compile();
	return new PatternVal(re);
	%}

# Make printable version of string.
function escape_string%(s: string%): string
	%{
	char* escstr = s->ExpandedString();
	Val* val = new StringVal(escstr);
	delete [] escstr;
	return val;
	%}

# Precompile a pcap filter.
function precompile_pcap_filter%(id: PcapFilterID, s: string%): bool
	%{
	bool success = true;

	loop_over_list(pkt_srcs, i)
		{
		pkt_srcs[i]->ClearErrorMsg();

		if ( ! pkt_srcs[i]->PrecompileFilter(id->ForceAsInt(),
							s->CheckString()) )
			{
			run_time( "precompile_pcap_filter: %s",
				pkt_srcs[i]->ErrorMsg() );
			success = false;
			}
		}

	return new Val(success, TYPE_BOOL);
	%}


# Install precompiled pcap filter.
function install_pcap_filter%(id: PcapFilterID%): bool
	%{
	// Don't allow the script-level to change the filter when
	// the user has specified one on the command line.
	if ( user_pcap_filter )
		return new Val(0, TYPE_BOOL);

	bool success = true;

	loop_over_list(pkt_srcs, i)
		{
		pkt_srcs[i]->ClearErrorMsg();

		if ( ! pkt_srcs[i]->SetFilter(id->ForceAsInt()) )
			success = false;
		}

	return new Val(success, TYPE_BOOL);
	%}

# If last pcap function failed, returns a descriptive error message
function pcap_error%(%): string
	%{
	loop_over_list(pkt_srcs, i)
		{
		const char* err = pkt_srcs[i]->ErrorMsg();
		if ( *err )
			return new StringVal(err);
		}

	return new StringVal("no error");
	%}

# Install filter to drop packets from a source (addr/subnet) with a given
# probabilty (0..100) if none of the given TCP flags is set.
function install_src_addr_filter%(ip: addr, tcp_flags: count, prob: count%) : bool
	%{
	sessions->GetPacketFilter()->AddSrc(ip, tcp_flags, prob);
	return 0;
	%}

function install_src_net_filter%(snet: subnet, tcp_flags: count, prob: count%) : bool
	%{
	sessions->GetPacketFilter()->AddSrc(snet, tcp_flags, prob);
	return new Val(1, TYPE_BOOL);
	%}

# Remove the filter for the source.
function uninstall_src_addr_filter%(ip: addr%) : bool
	%{
	return new Val(sessions->GetPacketFilter()->RemoveSrc(ip), TYPE_BOOL);
	%}

function uninstall_src_net_filter%(snet: subnet%) : bool
	%{
	return new Val(sessions->GetPacketFilter()->RemoveSrc(snet), TYPE_BOOL);
	%}

# Same for destination.
function install_dst_addr_filter%(ip: addr, tcp_flags: count, prob: count%) : bool
	%{
	sessions->GetPacketFilter()->AddDst(ip, tcp_flags, prob);
	return new Val(1, TYPE_BOOL);
	%}

function install_dst_net_filter%(snet: subnet, tcp_flags: count, prob: count%) : bool
	%{
	sessions->GetPacketFilter()->AddDst(snet, tcp_flags, prob);
	return new Val(1, TYPE_BOOL);
	%}

function uninstall_dst_addr_filter%(ip: addr%) : bool
	%{
	return new Val(sessions->GetPacketFilter()->RemoveDst(ip), TYPE_BOOL);
	%}

function uninstall_dst_net_filter%(snet: subnet%) : bool
	%{
	return new Val(sessions->GetPacketFilter()->RemoveDst(snet), TYPE_BOOL);
	%}


function checkpoint_state%(%) : bool
	%{
	return new Val(persistence_serializer->WriteAll("bro.state"), TYPE_BOOL);
	%}

function capture_events%(filename: string%) : bool
	%{
 	if ( ! event_serializer )
		event_serializer = new FileSerializer();
	else
		event_serializer->Close();

	const char* f = (const char*) filename->CheckString();
	return new Val(event_serializer->Open(f), TYPE_BOOL);
	%}
