// $Id: RuleMatcher.cc,v 1.21 2003/09/02 06:54:18 vern Exp $

#include "config.h"

#include "Conn.h"
#include "RuleMatcher.h"
#include "DFA.h"
#include "NetVar.h"
#include "Scope.h"

// FIXME: Things that are not fully implemented/working yet:
//
//		  - "ip-options" always evaluates to false
//		  - offsets for payload patterns are ignored
//			(but simulated by snort2bro by leading dots)
//		  - if a rule contains "PayloadSize" and application
//			specific patterns (like HTTP), but no "payload" patterns,
//			it may fail to match. Work-around: Insert an always
//			matching "payload" pattern (not done in snort2bro yet)
//		  - tcp-state always evaluates to true
//			(implemented but deactivated for comparision to Snort)
//		  - Sometimes, the signature match event is generated after a
//		    connection_finished (or similar) event. Using the default rules.bro,
//		    this means that we will not see the rule id in the connection summary.

uint32 RuleHdrTest::idcounter = 0;

RuleHdrTest::RuleHdrTest(Prot arg_prot, uint32 arg_offset, uint32 arg_size,
				Comp arg_comp, maskedvalue_list* arg_vals)
	{
	prot = arg_prot;
	offset = arg_offset;
	size = arg_size;
	comp = arg_comp;
	vals = arg_vals;
	sibling = 0;
	child = 0;
	pattern_rules = 0;
	pure_rules = 0;
	ruleset = new IntSet;
	id = ++idcounter;
	level = 0;

	for ( int i = 0; i < Rule::TYPES; ++i )
		psets[i].re = 0;

	}

Val* RuleMatcher::BuildRuleStateValue(const Rule* rule,
					const RuleEndpointState* state) const
	{
	RecordVal* val = new RecordVal(signature_state);
	val->Assign(0, new StringVal(rule->ID()));
	val->Assign(1, state->conn->BuildConnVal());
	val->Assign(2, new Val(state->is_orig, TYPE_BOOL));
	val->Assign(3, new Val(state->payload_size, TYPE_COUNT));
	return val;
	}

RuleHdrTest::RuleHdrTest(RuleHdrTest& h)
	{
	prot = h.prot;
	offset = h.offset;
	size = h.size;
	comp = h.comp;

	vals = new maskedvalue_list;
	loop_over_list(*h.vals, i)
		vals->append(new MaskedValue(*(*h.vals)[i]));

	for ( int j = 0; j < Rule::TYPES; ++j )
		{
		psets[j].re = 0;
		loop_over_list(h.psets[j].patterns, k)
			psets[j].patterns.append(copy_string(h.psets[j].patterns[k]));
		psets[j].ids = h.psets[j].ids;
		}

	sibling = 0;
	child = 0;
	pattern_rules = 0;
	pure_rules = 0;
	ruleset = new IntSet;
	id = ++idcounter;
	level = 0;
	}

RuleHdrTest::~RuleHdrTest()
	{
	loop_over_list(*vals, i)
		delete (*vals)[i];
	delete vals;

	for ( int i = 0; i < Rule::TYPES; ++i )
		delete psets[i].re;

	delete ruleset;
	}

bool RuleHdrTest::operator==(const RuleHdrTest& h)
	{
	if ( prot != h.prot || offset != h.offset || size != h.size ||
	     comp != h.comp || vals->length() != h.vals->length() )
		return false;

	loop_over_list(*vals, i)
		if ( (*vals)[i]->val != (*h.vals)[i]->val ||
		     (*vals)[i]->mask != (*h.vals)[i]->mask )
			return false;

	return true;
	}

void RuleHdrTest::PrintDebug()
	{
	static char* str_comp[] = { "<=", ">=", "<", ">", "==", "!=" };
	static char* str_prot[] = { "", "ip", "icmp", "tcp", "udp" };

	fprintf(stderr, "	RuleHdrTest %s[%d:%d] %s",
			str_prot[prot], offset, size, str_comp[comp]);

	loop_over_list(*vals, i)
		fprintf(stderr, " 0x%08x/0x%08x",
				(*vals)[i]->val, (*vals)[i]->mask);

	fprintf(stderr, "\n");
	}

RuleEndpointState::~RuleEndpointState()
	{
	loop_over_list(matchers, i)
		{
		delete matchers[i]->state;
		delete matchers[i];
		}
	}

RuleMatcher::RuleMatcher(int arg_RE_level)
	{
	root = new RuleHdrTest(RuleHdrTest::NOPROT, 0, 0, RuleHdrTest::EQ,
				new maskedvalue_list);
	RE_level = arg_RE_level;
	}

RuleMatcher::~RuleMatcher()
	{
#ifdef MATCHER_PRINT_STATS
	DumpStats(stderr);
#endif
	Delete(root);

	loop_over_list(rules, i)
		delete rules[i];
	}

void RuleMatcher::Delete(RuleHdrTest* node)
	{
	RuleHdrTest* next;
	for ( RuleHdrTest* h = node->child; h; h = next )
		{
		next = h->sibling;
		Delete(h);
		}

	delete node;
	}

bool RuleMatcher::ReadFiles(const name_list& files)
	{
	parse_error = false;

	for ( int i = 0; i < files.length(); ++i )
		{
		rules_in = search_for_file( files[i], "sig", 0);
		if ( ! rules_in )
			{
			error("Can't open signature file", files[i]);
			return false;
			}

		rules_line_number = 0;
		current_rule_file = files[i];
		rules_parse();
		}

	if ( parse_error )
		return false;

	BuildRulesTree();

	string_list exprs[Rule::TYPES];
	int_list ids[Rule::TYPES];
	BuildRegEx(root, exprs, ids);

	return ! parse_error;
	}

void RuleMatcher::AddRule(Rule* rule)
	{
	if ( rules_by_id.Lookup(rule->ID()) )
		{
		rules_error("rule defined twice");
		return;
		}

	rules.append(rule);
	rules_by_id.Insert(rule->ID(), rule);
	}

void RuleMatcher::BuildRulesTree()
	{
	loop_over_list(rules, r)
		{
		rules[r]->SortHdrTests();
		InsertRuleIntoTree(rules[r], 0, root, 0);
		}
	}

void RuleMatcher::InsertRuleIntoTree(Rule* r, int testnr,
					RuleHdrTest* dest, int level)
	{
	// Initiliaze the preconditions
	loop_over_list(r->preconds, i)
		{
		Rule::Precond* pc = r->preconds[i];

		Rule* pc_rule = rules_by_id.Lookup(pc->id);
		if ( ! pc_rule )
			{
			rules_error_by_id(r->ID(), "unknown rule referenced");
			return;
			}

		pc->rule = pc_rule;
		pc_rule->dependents.append(r);
		}

	// All tests in tree already?
	if ( testnr >= r->hdr_tests.length() )
		{ // then insert it into the right list of the test
		if ( r->patterns.length() )
			{
			r->next = dest->pattern_rules;
			dest->pattern_rules = r;
			}
		else
			{
			r->next = dest->pure_rules;
			dest->pure_rules = r;
			}

		dest->ruleset->Insert(r->Index());
		return;
		}

	// Look for matching child.
	for ( RuleHdrTest* h = dest->child; h; h = h->sibling )
		if ( *h == *r->hdr_tests[testnr] )
			{
			InsertRuleIntoTree(r, testnr + 1, h, level + 1);
			return;
			}

	// Insert new child.
	RuleHdrTest* newtest = new RuleHdrTest(*r->hdr_tests[testnr]);
	newtest->sibling = dest->child;
	newtest->level = level + 1;
	dest->child = newtest;

	InsertRuleIntoTree(r, testnr + 1, newtest, level + 1);
	}

void RuleMatcher::BuildRegEx(RuleHdrTest* hdr_test, string_list* exprs,
				int_list* ids)
	{
	// For each type, get all patterns on this node.
	for ( Rule* r = hdr_test->pattern_rules; r; r = r->next )
		{
		loop_over_list(r->patterns, j)
			{
			Rule::Pattern* p = r->patterns[j];
			exprs[p->type].append(p->pattern);
			ids[p->type].append(p->id);
			}
		}

	// If we're above the RE_level, these patterns will form the regexprs.
	if ( hdr_test->level < RE_level )
		{
		for ( int i = 0; i < Rule::TYPES; ++i )
			{
			if ( exprs[i].length() )
				{
				hdr_test->psets[i].re = new Specific_RE_Matcher(MATCH_EXACTLY, 1);
				hdr_test->psets[i].re->CompileSet(exprs[i], ids[i]);
				hdr_test->psets[i].patterns = exprs[i];
				hdr_test->psets[i].ids = ids[i];
				}
			}
		}

	// Get the patterns on all of our childs.
	for ( RuleHdrTest* h = hdr_test->child; h; h = h->sibling )
		{
		string_list child_exprs[Rule::TYPES];
		int_list child_ids[Rule::TYPES];

		BuildRegEx(h, child_exprs, child_ids);

		for ( int i = 0; i < Rule::TYPES; ++i )
			{
			loop_over_list(child_exprs[i], j)
				{
				exprs[i].append(child_exprs[i][j]);
				ids[i].append(child_ids[i][j]);
				}
			}
		}

	// If we're on the RE_level, all patterns gathered now
	// form the regexprs.
	if ( hdr_test->level == RE_level )
		{
		for ( int i = 0; i < Rule::TYPES; ++i )
			{
			if ( exprs[i].length() )
				{
				hdr_test->psets[i].re = new Specific_RE_Matcher(MATCH_EXACTLY, 1);
				hdr_test->psets[i].re->CompileSet(exprs[i], ids[i]);
				hdr_test->psets[i].patterns = exprs[i];
				hdr_test->psets[i].ids = ids[i];
				}
			}
		}

	// If we're below the RE_level, the regexprs remains empty.
	}

// Get a 8/16/32-bit value from the given position in the packet header
static inline uint32 getval(const u_char* data, int size)
	{
	switch ( size ) {
	case 1:
		return *(uint8*) data;

	case 2:
		return ntohs(*(uint16*) data);

	case 4:
		return ntohl(*(uint32*) data);

	default:
		internal_error("illegal HdrTest size");
	}

	// Should not be reached.
	return 0;
	}


// A line which can be inserted into the macros below for debugging
// fprintf(stderr, "%.8f %08x & %08x %s %08x\n", network_time, v, (mvals)[i]->mask, #op, (mvals)[i]->val);

// Evaluate a value list (matches if at least one value matches).
#define DO_MATCH_OR( mvals, v, op )	\
	{	\
	loop_over_list((mvals), i)	\
		{	\
		if ( ((v) & (mvals)[i]->mask) op (mvals)[i]->val )	\
			goto match;	\
		}	\
	goto no_match;	\
	}

// Evaluate a value list (doesn't match if any value matches).
#define DO_MATCH_NOT_AND( mvals, v, op )	\
	{	\
	loop_over_list((mvals), i)	\
		{	\
		if ( ((v) & (mvals)[i]->mask) op (mvals)[i]->val )	\
			goto no_match;	\
		}	\
	goto match;	\
	}

RuleEndpointState* RuleMatcher::InitEndpoint(Connection* conn, const IP_Hdr* ip,
				int caplen, RuleEndpointState* opposite)
	{
	int from_orig = addr_eq(ip->SrcAddr(), conn->OrigAddr());

	RuleEndpointState* state = new RuleEndpointState(conn, from_orig, opposite);

	if ( rule_bench == 3 )
		return state;

	rule_hdr_test_list tests;
	tests.append(root);

	loop_over_list(tests, h)
		{
		RuleHdrTest* hdr_test = tests[h];

#ifdef MATCHER_PRINT_DEBUG
		fprintf(stderr, "%.8f HdrTest %d matches (%s%s)\n", network_time, hdr_test->id,
				hdr_test->pattern_rules ? "+" : "-",
				hdr_test->pure_rules ? "+" : "-");
#endif

		// Current HdrTest node matches the packet, so remember it
		// if we have any rules on it.
		if ( hdr_test->pattern_rules || hdr_test->pure_rules )
			state->hdr_tests.append(hdr_test);

		// Evaluate all rules on this node which don't contain
		// any patterns.
		for ( Rule* r = hdr_test->pure_rules; r; r = r->next )
			if ( EvalRuleConditions(r, state, 0, 0, 0) )
				ExecRuleActions(r, state, 0, 0, 0);

		// If we're on or above the RE_level, we may have some
		// pattern matching to do.
		if ( hdr_test->level <= RE_level )
			{
			for ( int i = 0; i < Rule::TYPES; ++i )
				if ( hdr_test->psets[i].re )
					{
					RuleEndpointState::Matcher* m =
						new RuleEndpointState::Matcher;
					m->state = new RE_Match_State(hdr_test->psets[i].re);
					m->type = (Rule::PatternType) i;
					state->matchers.append(m);
					}
			}

		// Get start of transport layer.
		const u_char* transport = ip->Payload();

		// Descend the RuleHdrTest tree further.
		for ( RuleHdrTest* h = hdr_test->child; h; h = h->sibling )
			{
			const u_char* data;

			// Evaluate the header test.
			switch ( h->prot ) {
			case RuleHdrTest::IP:
				data = (const u_char*) ip->IP4_Hdr();
				break;

			case RuleHdrTest::ICMP:
			case RuleHdrTest::TCP:
			case RuleHdrTest::UDP:
				data = transport;
				break;

			default:
				data = 0;
				internal_error("unknown protocol");
			}

			// Sorry for the hidden gotos :-)
			switch ( h->comp ) {
				case RuleHdrTest::EQ:
					DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), ==);

				case RuleHdrTest::NE:
					DO_MATCH_NOT_AND(*h->vals, getval(data + h->offset, h->size), ==);

				case RuleHdrTest::LT:
					DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), <);

				case RuleHdrTest::GT:
					DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), >);

				case RuleHdrTest::LE:
					DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), <=);

				case RuleHdrTest::GE:
					DO_MATCH_OR(*h->vals, getval(data + h->offset, h->size), >=);

				default:
					internal_error("unknown comparision type");
			}

no_match:
			continue;

match:
			tests.append(h);
			}
		}

	// Save some memory.
	state->hdr_tests.resize(0);
	state->matchers.resize(0);
	return state;
	}

void RuleMatcher::Match(RuleEndpointState* state,
			Rule::PatternType type, const u_char* data, int data_len, bool bol, bool eol)
	{
	// FIXME: There is probably some room for performance improvements
	// in this method.  For example, it *may* help to use an IntSet
	// for 'accepted' (that depends on the average number of matching
	// patterns).

	if ( rule_bench >= 2 )
		return;

	bool newmatch = false;

#ifdef MATCHER_PRINT_DEBUG
	fprintf(stderr, "%.8f Matching %s rules on |", network_time, Rule::TypeToString(type));
	int u;
	for ( u = 0; u < 40 && u < data_len; ++u )
		if ( isprint(data[u]) )
			fputc(data[u], stderr);
		else
			fprintf(stderr, "<%2x>", data[u]);

	fprintf(stderr, "%s|\n", u < data_len ? "..." : "");
#endif

	// Remember size of first non-null data.
	if ( type == Rule::PAYLOAD )
		{
		bol = state->payload_size < 0;

		if ( state->payload_size <= 0 && data_len )
			state->payload_size = data_len;

		else if ( state->payload_size < 0 )
			state->payload_size = 0;
		}

	// Feed data into all relevant matchers.
	loop_over_list(state->matchers, x)
		{
		RuleEndpointState::Matcher* m = state->matchers[x];
		if ( m->type == type &&
		     m->state->Match((const u_char*) data, data_len, bol, eol) )
			newmatch = true;
		}

	// If no new match found, we're already done.
	if ( ! newmatch )
		return;

#ifdef MATCHER_PRINT_DEBUG
	fprintf(stderr, "%.8f New pattern match found\n", network_time);
#endif

	// Build a joined AcceptingSet.
	AcceptingSet accepted;
	int_list matchpos;

	loop_over_list(state->matchers, y)
		{
		RuleEndpointState::Matcher* m = state->matchers[y];
		const AcceptingSet* ac = m->state->Accepted();

		loop_over_list(*ac, k)
			{
			if ( ! accepted.is_member((*ac)[k]) )
				{
				accepted.append((*ac)[k]);
				matchpos.append((*m->state->MatchPositions())[k]);
				}
			}
		}

	// Determine the rules for which all patterns have matched.
	// This code should be fast enough as long as there are only very few
	// matched patterns per connection (which is a plausible assumption).

	rule_list matched;

	loop_over_list(accepted, i)
		{
		Rule* r = Rule::rule_table[accepted[i] - 1];

#ifdef MATCHER_PRINT_DEBUG
		fprintf(stderr, "%.8f Checking rule: %s\n", network_time, r->id);
#endif

		// Check whether all patterns of the rule have matched.
		loop_over_list(r->patterns, j)
			{
			if ( ! accepted.is_member(r->patterns[j]->id) )
				goto next_pattern;

			// See if depth is satisfied.
			if ( (unsigned int) matchpos[i] >
			     r->patterns[j]->offset + r->patterns[j]->depth )
				goto next_pattern;

#ifdef MATCHER_PRINT_DEBUG
			fprintf(stderr, "%.8f All patterns of rule satisfied\n", network_time);
#endif

			// FIXME: How to check for offset ??? ###
			}

		// If not already in the list of matching rules, add it.
		if ( ! matched.is_member(r) )
			matched.append(r);

next_pattern:
		continue;
		}

	// Check which of the matching rules really belong to any of our nodes.

	loop_over_list(matched, j)
		{
		Rule* r = matched[j];

#ifdef MATCHER_PRINT_DEBUG
		fprintf(stderr, "%.8f Accepted rule: %s\n", network_time, r->id);
#endif

		loop_over_list(state->hdr_tests, k)
			{
			RuleHdrTest* h = state->hdr_tests[k];

#ifdef MATCHER_PRINT_DEBUG
			fprintf(stderr, "%.8f Checking for accepted rule on HdrTest %d\n", network_time, h->id);
#endif

			// Skip if rule does not belong to this node.
			if ( ! h->ruleset->Contains(r->Index()) )
				continue;

#ifdef MATCHER_PRINT_DEBUG
			fprintf(stderr, "%.8f On current node\n", network_time);
#endif

			// Skip if rule already fired for this connection.
			if ( state->matched_rules.is_member(r->Index()) )
				continue;

			// Remember that all patterns have matched.
			if ( ! state->matched_by_patterns.is_member(r) )
				state->matched_by_patterns.append(r);

#ifdef MATCHER_PRINT_DEBUG
			fprintf(stderr, "%.8f And has not already fired\n", network_time);
#endif
			// Eval additional conditions.
			if ( ! EvalRuleConditions(r, state, data, data_len, 0) )
				continue;

			// Found a match.
			ExecRuleActions(r, state, data, data_len, 0);
			}
		}
	}

void RuleMatcher::FinishEndpoint(RuleEndpointState* state)
	{
	if ( rule_bench == 3 )
		return;

	// Send EOL to payload matchers.
	Match(state, Rule::PAYLOAD, (const u_char *) "", 0, false, true );

	// Some of the pure rules may match at the end of the connection,
	// although they have not matched at the beginning. So, we have
	// to test the candidates here.

	ExecPureRules(state, 1);

	loop_over_list(state->matched_by_patterns, i)
		ExecRulePurely(state->matched_by_patterns[i], state, 1);
	}

void RuleMatcher::ExecPureRules(RuleEndpointState* state, bool eos)
	{
	loop_over_list(state->hdr_tests, i)
		{
		RuleHdrTest* hdr_test = state->hdr_tests[i];
		for ( Rule* r = hdr_test->pure_rules; r; r = r->next )
			ExecRulePurely(r, state, eos);
		}
	}

bool RuleMatcher::ExecRulePurely(Rule* r, RuleEndpointState* state, bool eos)
	{
	if ( state->matched_rules.is_member(r->Index()) )
		return false;

#ifdef MATCHER_PRINT_DEBUG
	fprintf(stderr, "%.8f Checking rule %s purely\n",
		network_time, r->ID());
#endif
	if ( EvalRuleConditions(r, state, 0, 0, eos) )
		{
#ifdef MATCHER_PRINT_DEBUG
		fprintf(stderr, "%.8f MATCH!\n", network_time);
#endif
		ExecRuleActions(r, state, 0, 0, eos);
		return true;
		}

	return false;
	}

bool RuleMatcher::EvalRuleConditions(Rule* r, RuleEndpointState* state,
		const u_char* data, int len, bool eos)
	{
#ifdef MATCHER_PRINT_DEBUG
	fprintf(stderr, "%.8f Evaluating conditions for rule %s\n",
			network_time, r->ID());
#endif
	// Check for other rules which have to match first.
	loop_over_list(r->preconds, i)
		{
		Rule::Precond* pc = r->preconds[i];

		RuleEndpointState* pc_state = state;

		if ( pc->opposite_dir )
			{
			if ( ! state->opposite )
				// No rule matching for other direction yet.
				return false;

			pc_state = state->opposite;
			}

		if ( ! pc->negate )
			{
			if ( ! pc_state->matched_rules.is_member(pc->rule->Index()) )
				// Precond rule has not matched yet.
				return false;
			}
		else
			{
			// Only at eos can we decide about negated conditions.
			if ( ! eos )
				return false;

			if ( pc_state->matched_rules.is_member(pc->rule->Index()) )
				return false;
			}
		}

	loop_over_list(r->conditions, l)
		if ( ! r->conditions[l]->DoMatch(r, state, data, len) )
			return false;

#ifdef MATCHER_PRINT_DEBUG
	fprintf(stderr, "%.8f Conditions met: MATCH! %s\n", network_time, r->ID());
#endif
	return true;
	}

void RuleMatcher::ExecRuleActions(Rule* r, RuleEndpointState* state,
				const u_char* data, int len, bool eos)
	{
	state->matched_rules.append(r->Index());

	loop_over_list(r->actions, i)
		r->actions[i]->DoAction(r, state, data, len);

	// This rule may trigger some other rules; check them.
	loop_over_list(r->dependents, j)
		{
		Rule* dep = (r->dependents)[j];
		ExecRule(dep, state, eos);
		if ( state->opposite )
			ExecRule(dep, state->opposite, eos);
		}
	}

void RuleMatcher::ExecRule(Rule* rule, RuleEndpointState* state, bool eos)
	{
	// Nothing to do if it has alrady matched.
	if ( state->matched_rules.is_member(rule->Index()) )
		return;

	loop_over_list(state->hdr_tests, i)
		{
		RuleHdrTest* h = state->hdr_tests[i];

		// Is it on this HdrTest at all?
		if ( ! h->ruleset->Contains(rule->Index()) )
			continue;

		// Is it a pure rule?
		for ( Rule* r = h->pure_rules; r; r = r->next )
			if ( r == rule )
				{ // found, so let's evaluate it
				ExecRulePurely(rule, state, eos);
				return;
				}

		// It must be a non-pure rule. It can only match right now if
		// all its patterns are satisfied already.
		if ( state->matched_by_patterns.is_member(rule) )
			{ // they are, so let's evaluate it
			ExecRulePurely(rule, state, eos);
			return;
			}
		}
	}

void RuleMatcher::ClearEndpointState(RuleEndpointState* state)
	{
	if ( rule_bench == 3 )
		return;

	ExecPureRules(state, 1);
	state->payload_size = -1;
	state->matched_by_patterns.clear();

	loop_over_list(state->matchers, i)
		state->matchers[i]->state->Clear();
	}

void RuleMatcher::PrintDebug()
	{
	loop_over_list(rules, i)
		rules[i]->PrintDebug();

	fprintf(stderr, "\n---------------\n");

	PrintTreeDebug(root);
	}

static inline void indent(int level)
	{
	for ( int i = level * 2; i; --i )
		fputc(' ', stderr);
	}

void RuleMatcher::PrintTreeDebug(RuleHdrTest* node)
	{
	for ( int i = 0; i < Rule::TYPES; ++i )
		{
		indent(node->level);
		fprintf(stderr, "[%d %s patterns]\n",
				node->psets[i].patterns.length(),
				Rule::TypeToString((Rule::PatternType) i));
		}

	for ( Rule* r = node->pattern_rules; r; r = r->next )
		{
		indent(node->level);
		fprintf(stderr, "Pattern rule %s (%d/%d)\n", r->id, r->idx,
				node->ruleset->Contains(r->Index()));
		}

	for ( Rule* r = node->pure_rules; r; r = r->next )
		{
		indent(node->level);
		fprintf(stderr, "Pure rule %s (%d/%d)\n", r->id, r->idx,
				node->ruleset->Contains(r->Index()));
		}

	for ( RuleHdrTest* h = node->child; h; h = h->sibling )
		{
		indent(node->level);
		fprintf(stderr, "Test %4d\n", h->id);
		PrintTreeDebug(h);
		}
	}

void RuleMatcher::GetStats(Stats* stats, RuleHdrTest* hdr_test)
	{
	if ( ! hdr_test )
		{
		stats->matchers = 0;
		stats->states = 0;
		stats->computed = 0;
		stats->mem = 0;
		stats->hits = 0;
		stats->misses = 0;
		hdr_test = root;
		}

	DFA_State_Cache::Stats cstats;

	for ( int i = 0; i < Rule::TYPES; ++i )
		if ( hdr_test->psets[i].re )
			{
			++stats->matchers;
			hdr_test->psets[i].re->DFA()->Cache()->GetStats(&cstats);

			stats->states += cstats.states;
			stats->computed += cstats.computed;
			stats->mem += cstats.mem;
			stats->hits += cstats.hits;
			stats->misses += cstats.misses;
			}

	for ( RuleHdrTest* h = hdr_test->child; h; h = h->sibling )
		GetStats(stats, h);
	}

void RuleMatcher::DumpStats(FILE* f)
	{
	Stats stats;
	GetStats(&stats);

	fprintf(f, "Computed states = %d; Classes = ??; Computed trans. = %d; Matchers = %d; Mem = %d\n",
		stats.states, stats.computed, stats.matchers, stats.mem);
	fprintf(f, "DFA cache hits = %d; misses = %d\n",
			stats.hits, stats.misses);
	}


static Val* get_bro_val(const char* label)
	{
	ID* id = lookup_ID(label, GLOBAL_MODULE_NAME, false);
	if ( ! id )
		{
		rules_error("Unknown script-level identifier");
		return 0;
		}

	return id->ID_Val();
	}


// Converts an atomic Val and appends it to the list
static bool val_to_maskedval(Val* v, maskedvalue_list* append_to)
	{
	MaskedValue* mval = new MaskedValue;

	switch ( v->Type()->Tag() ) {
		case TYPE_BOOL:
		case TYPE_COUNT:
		case TYPE_ENUM:
		case TYPE_INT:
		case TYPE_PORT:
			mval->val = v->CoerceToUnsigned();
			mval->mask = 0xffffffff;
			break;

		case TYPE_SUBNET:
#ifdef BROv6
			rules_error("subnets for BROv6 not supported");
			mval->val = 0;
			mval->mask = 0;
#else
			mval->val = ntohl(v->AsSubNet()->net);
			mval->mask = ntohl(v->AsSubNetVal()->Mask());
#endif
			break;

		default:
			rules_error("Wrong type of identifier");
			return false;
	}

	append_to->append(mval);

	return true;
	}

void id_to_maskedvallist(const char* id, maskedvalue_list* append_to)
	{
	Val* v = get_bro_val(id);
	if ( ! v )
		return;

	if ( v->Type()->Tag() == TYPE_TABLE )
		{
		val_list* vals = v->AsTableVal()->ConvertToPureList()->Vals();
		loop_over_list(*vals, i )
			if ( ! val_to_maskedval((*vals)[i], append_to) )
				return;
		}

	else
		val_to_maskedval(v, append_to);
	}

char* id_to_str(const char* id)
	{
	BroString* src;
	char* dst;

	Val* v = get_bro_val(id);
	if ( ! v )
		goto error;

	if ( v->Type()->Tag() != TYPE_STRING )
		{
		rules_error("Identifier must refer to string");
		goto error;
		}

	src = v->AsString();
	dst = new char[src->Len()+1];
	memcpy(dst, src->Bytes(), src->Len());
	*(dst+src->Len()) = '\0';
	return dst;

error:
	char* dummy = new char[16];
	strcpy(dummy, "<error>");
	return dummy;
	}

uint32 id_to_uint(const char* id)
	{
	Val* v = get_bro_val(id);
	if ( ! v )
		return 0;

	TypeTag t = v->Type()->Tag();

	if ( t == TYPE_BOOL || t == TYPE_COUNT || t == TYPE_ENUM ||
	     t == TYPE_INT || t == TYPE_PORT )
		return v->CoerceToUnsigned();

	rules_error("Identifier must refer to integer");
	return 0;
	}
