/*
** SellaNMS
** $Id: state.cxx,v 1.19 2005/11/26 06:48:45 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: state.cxx,v 1.19 2005/11/26 06:48:45 sella Exp $";

#include "state.h"
#include "common/util.h"
#include "common/netblock.h"
#include "storage/storagedata.h"
#include "policy_framework/policy_framework.h"
#include "state/state.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <stl/hash_map>
#include <stl/string>
#include <stl/vector>

#define POLICY_TABLE_TRACKING

extern ConfigNode* config;

extern bool interrupt;

State::State():timestamp(0) {
}

State::~State() {
}

RESULT State::execute() {
	RESULT result = SUCCESS;

	time_t next;

   int err;
   pthread_t thread_id = 0;

   TRACE(TRACE_THREAD, "State::execute() starting");

   /* Create the expire thread */
   if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))State::expire_thread, this)) == 0) {
      TRACE(TRACE_INTERNAL, "expire thread created.");
   } else {
      LMSG("failed to create expire thread.");
		interrupt = true;
		result = FAILURE;
	}

	while (!interrupt) {
		/* Update interval. */
		next = time(NULL) + this->interval / MONITOR_STATE_UPDATEINTERVAL_MLT;

		/* Update states */
		if ((result = this->update()) != SUCCESS) {
			LMSG("Failed to update state. Exiting.");

			break;
		}

		while (time(NULL) < next && !interrupt) {
			usleep(USLEEP_LONGLOOP_TIME);
		}
	}

	TRACE(TRACE_INTERNAL, "joining expire thread %lu", thread_id);
	if ((err = pthread_join(thread_id, NULL)) != 0) {
		LMSG("pthread_join returned %d", err);
		result = FAILURE;
	}

	TRACE(TRACE_THREAD, "State::execute() exiting");

	return result;
}

RESULT State::readConfig() {
	RESULT result = SUCCESS;
	string str;
	vector<string> v;
	vector<string>::iterator v_it;
	ConfigNode node;

	config->assignNode("monitor state", node);

	/* How often to start a polling inteval. */
	node.assignValue("interval", interval, MONITOR_STATE_UPDATEINTERVAL_DEF, MONITOR_STATE_UPDATEINTERVAL_MIN, MONITOR_STATE_UPDATEINTERVAL_MAX, MONITOR_STATE_UPDATEINTERVAL_MLT);
	node.assignValue("expire-interval", expireInterval, MONITOR_STATE_EXPIREINTERVAL_DEF, MONITOR_STATE_EXPIREINTERVAL_MIN, MONITOR_STATE_EXPIREINTERVAL_MAX, MONITOR_STATE_EXPIREINTERVAL_MLT);
	node.assignValue("max-delete", maxDelete, MONITOR_STATE_MAXDELETE_DEF, MONITOR_STATE_MAXDELETE_MIN, MONITOR_STATE_MAXDELETE_MAX);

	processElementOnly = node.isNode("process-element-only");

	/* Clear any existing policies. */
	inputPolicy.clear();
	outputPolicy.clear();

	/* Add and compile input policy. */
	if (node.assignValues("policy input", v)) {
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("failed to add/compile input policies");

			return FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (node.assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("failed to add/compile output policies");

			return FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("an output policy is required for this module");

		return FAILURE;
	}

	/* Configure Storage */
	storage.setConfig(*config);

	printConfig();

	return result;
}

void State::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	if (!TRACE_ISSET(TRACE_CONFIG)) {
		return;
	}

	TRACE(TRACE_CONFIG, "interval: %lu (sec)", this->interval / MONITOR_STATE_UPDATEINTERVAL_MLT);
	TRACE(TRACE_CONFIG, "expire-interval: %lu (sec)", this->expireInterval / MONITOR_STATE_EXPIREINTERVAL_MLT);
	TRACE(TRACE_CONFIG, "max-delete: %lu (sec)", this->maxDelete);
	TRACE(TRACE_CONFIG, "process-element-only: %s", this->processElementOnly ? "set" : "unset");

	if (config->assignValues("monitor state policy input", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy input: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy input: none");
	}

	if (config->assignValues("monitor state policy output", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy output: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy output: none");
	}

	return;
}

RESULT State::expire_thread(void* args) {
	RESULT result = SUCCESS;

	time_t next;

	StorageData data;
	variable_binding_t policy_input;
	variable_binding_t policy_output;
	stack<string> ids;

	State *instance = (State*) args;

	const char tables[][10] = { "element", "interface", "ip", (char) NULL };
	const char sql_org[] = "SELECT @X@_attr.id AS id, CONCAT(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(@X@_attr.timestamp)) AS age, @X@_attr.variable AS variable, @X@_attr.value AS value, @X@.id AS referenced FROM @X@_attr LEFT JOIN @X@ ON (@X@_attr.@X@ = @X@.id)";
	char *sql;

	TRACE(TRACE_THREAD, "State::expire_thread() starting main loop");

	/* FIXME: This should probably only select 'xxx-state' and remove the
	   'xxx-state-changes' entry only when it removes a 'xxx-state' entry.
	*/

	while (!interrupt) {
		for (int i = 0; tables[i][0] != (char) NULL && !interrupt; i++) {
			next = time(NULL) + instance->expireInterval / MONITOR_STATE_EXPIREINTERVAL_MLT;

			sql = ereg_replace("@X@", tables[i], sql_org);
			result = instance->storage.query(data, sql);
			SAFE_FREE(sql);

			if (result == FAILURE) {
				LMSG("Failed to query new state changes. SQL: %s", sql);

				return result;
			}

			TRACE(TRACE_DEBUG, "expire starting for table %s_attr with %lu entries", tables[i], data.getNumRow());

			/* Initialize loop. */
			while (data.nextRow() && !interrupt) {
				policy_input["origin"] = "database";
				policy_input["protocol"] = "state";
				policy_input["event"] = "expire";
				policy_input["table"] = tables[i];
				policy_input["table"].append("_attr");

				data.getULong(policy_input["id"], "id");
				data.getString(policy_input["age"], "age");
				data.getString(policy_input["variable"], "variable");
				data.getString(policy_input["state"], "value");
				data.getULong(policy_input["referenced"], "referenced");
				if (policy_input["referenced"].empty()) {
					policy_input["referenced"] = "false";
				} else {
					policy_input["referenced"] = "true";
				}

				instance->processExpirePolicy(policy_input, ids);

				/* Clear old data. */
				policy_input.clear();

				/* Perform a partial delete, if needed. */
				if (ids.size() >= instance->maxDelete) {
					instance->deleteExpired(ids, tables[i]);
				}

				/* Slow loop down. No need to consume CPU. */
				usleep(USLEEP_FASTLOOP_TIME);
			}
			/* Clear data */
			data.clear();

			/* Delete remaining ids. */
			instance->deleteExpired(ids, tables[i]);

			TRACE(TRACE_DEBUG, "expire complete for table %s_attr", tables[i]);
		}

		TRACE(TRACE_DEBUG, "expire iteration complete");
		TRACE(TRACE_TIMER, "expire iteration completed in %d secs", (instance->expireInterval / MONITOR_STATE_EXPIREINTERVAL_MLT) - (next - time(NULL)));

		/* Wait until next run. */
		while (!interrupt && time(NULL) < next) {
			usleep(USLEEP_LONGLOOP_TIME);
		}
	}

	TRACE(TRACE_THREAD, "State::expire_thread() exiting");

	pthread_exit(NULL);
}

RESULT State::deleteExpired(stack<string> &ids, const char* table) {
	string sql;
	StorageData data;

	assert(table != NULL);

	TRACE(TRACE_DEBUG, "Request to delete %lu ids from %s", ids.size(), table);

	if (!ids.empty()) {
		if (storage.queryf(data, "LOCK TABLES %s_attr WRITE, %s READ", table, table) != SUCCESS) {
			LMSG("failed to lock %s table", table);

			return FAILURE;
		}

		/* Build DELETE string. */
		sql = "DELETE FROM %s_attr WHERE ";
		while (!ids.empty()) {
			sql.append("id = " + ids.top() + " OR ");
			ids.pop();
		}
		/* Remove the last ' OR ' */
		sql.erase(sql.size() - 4);

		if (storage.queryf(data, sql.c_str(), table) != SUCCESS) {
			LMSG("failed to delete from %s table", table);

			return FAILURE;
		}

		if (storage.query(data, "UNLOCK TABLES") != SUCCESS) {
			LMSG("failed to unlock tables.");

			return FAILURE;
		}
	}

	return SUCCESS;
}

RESULT State::update() {
	bool running;
	time_t now;

	unsigned long element = 0, interface = 0, ip = 0;
	unsigned long last_element = 0, last_interface = 0, last_ip = 0;

	string sql, variable, value;

	size_t buf_size = 132;
	char buf[buf_size];

	StorageData	data;

	variable_binding_t policy_input;
	hash_map<string, string> tableMap;

	/* Capture start time */
	time(&now);

	/* Restart updateCount */
	updateCount = 0;

	/* Collect all state changes since last check. */
	if (!processElementOnly) {
		/* Select state changes, minus rows with only an element state. */
		sql = "SELECT element.id element_id, interface.id interface_id, ip.id ip_id, element_attr.variable element_attr_variable, element_attr.value element_attr_value, interface_attr.variable interface_attr_variable, interface_attr.value interface_attr_value, ip_attr.variable ip_attr_variable, ip_attr.value ip_attr_value FROM element LEFT JOIN element_attr ON (element_attr.element = element.id AND element_attr.variable LIKE '%%-state') LEFT JOIN interface ON (interface.element = element.id) LEFT JOIN interface_attr ON (interface_attr.interface = interface.id AND interface_attr.variable LIKE '%%-state') LEFT JOIN ip ON (ip.interface = interface.id) LEFT JOIN ip_attr ON (ip_attr.ip = ip.id AND ip_attr.variable LIKE '%%-state') WHERE (element_attr.timestamp > FROM_UNIXTIME(%lu) OR interface_attr.timestamp > FROM_UNIXTIME(%lu) OR ip_attr.timestamp > FROM_UNIXTIME(%lu)) AND interface_attr.variable IS NOT NULL AND ip_attr.variable IS NOT NULL ORDER BY element_id, interface_id, ip_id";
	} else {
		/* Select state changes. */
	sql = "SELECT element.id element_id, interface.id interface_id, ip.id ip_id, element_attr.variable element_attr_variable, element_attr.value element_attr_value, interface_attr.variable interface_attr_variable, interface_attr.value interface_attr_value, ip_attr.variable ip_attr_variable, ip_attr.value ip_attr_value FROM element LEFT JOIN element_attr ON (element_attr.element = element.id AND element_attr.variable LIKE '%%-state') LEFT JOIN interface ON (interface.element = element.id) LEFT JOIN interface_attr ON (interface_attr.interface = interface.id AND interface_attr.variable LIKE '%%-state') LEFT JOIN ip ON (ip.interface = interface.id) LEFT JOIN ip_attr ON (ip_attr.ip = ip.id AND ip_attr.variable LIKE '%%-state') WHERE (element_attr.timestamp > FROM_UNIXTIME(%lu) OR interface_attr.timestamp > FROM_UNIXTIME(%lu) OR ip_attr.timestamp > FROM_UNIXTIME(%lu)) ORDER BY element_id, interface_id, ip_id";
	}

	if (storage.queryf(data, sql.c_str(), this->timestamp, this->timestamp, this->timestamp) == FAILURE) {
		LMSG("Failed to query new state changes. SQL: %s", sql.c_str());

		return FAILURE;
	}

	TRACE(TRACE_DEBUG, "update started with %lu entries to check", data.getNumRow());

	/* Initialize loop. */
	if (running = data.nextRow()) {
		/* Get first ids. */
		data.getULong(element, "element_id");
		data.getULong(interface, "interface_id");
		data.getULong(ip, "ip_id");

		policy_input["origin"] = "database";
		policy_input["protocol"] = "state";
		policy_input["event"] = "update";
	}

	while (running && !interrupt) {
		/* Update last ids. */
		last_element = element;
		last_interface = interface;
		last_ip = ip;

		/* Insert element related data. */
		snprintf(buf, buf_size, "%lu", element);
		policy_input["element-id"] = buf;

		if (element != 0) {
			data.getString(variable, "element_attr_variable");

			if (!variable.empty()) {
				data.getString(value, "element_attr_value");
				policy_input[variable] = value;
				tableMap[variable] = "element";

#ifdef POLICY_TABLE_TRACKING
				snprintf(buf, buf_size, "element-%s", variable.c_str());
				policy_input[buf] = value;
				tableMap[buf] = "element";
#endif
			}
		}

		/* Insert interface related data. */
		snprintf(buf, buf_size, "%lu", interface);
		policy_input["interface-id"] = buf;

		if (interface != 0) {
			data.getString(variable, "interface_attr_variable");

			if (!variable.empty()) {
				data.getString(value, "interface_attr_value");
				policy_input[variable] = value;
				tableMap[variable] = "interface";

#ifdef POLICY_TABLE_TRACKING
				snprintf(buf, buf_size, "interface-%s", variable.c_str());
				policy_input[buf] = value;
				tableMap[buf] = "interface";
#endif
			}
		}

		/* Insert ip related data. */
		snprintf(buf, buf_size, "%lu", ip);
		policy_input["ip-id"] = buf;

		if (ip != 0) {
			data.getString(variable, "ip_attr_variable");

			if (!variable.empty()) {
				data.getString(value, "ip_attr_value");
				policy_input[variable] = value;
				tableMap[variable] = "ip";

#ifdef POLICY_TABLE_TRACKING
				snprintf(buf, buf_size, "ip-%s", variable.c_str());
				policy_input[buf] = value;
				tableMap[buf] = "ip";
#endif
			}
		}

		/* Grab next row. */
		if (running = data.nextRow()) {
			/* Get next set of ids. */
			data.getULong(element, "element_id");
			data.getULong(interface, "interface_id");
			data.getULong(ip, "ip_id");
		} else {
			/* Force last entry to be processed. */
			element = interface = ip = 0;
		}

		/* If this is a new data set (ids don't match), then run data though policy. */
		if (element != last_element || interface != last_interface || ip != last_ip) {
			processPolicy(policy_input, tableMap);

			/* Reinitialize structure for next run. */
			tableMap.clear();
			policy_input.clear();
			policy_input["origin"] = "database";
			policy_input["protocol"] = "state";
			policy_input["event"] = "update";
		}
	}

	/* Update timestamp */
	this->timestamp = now;

	TRACE(TRACE_DEBUG, "update complete - %lu states updated", updateCount);

	return SUCCESS;
}

RESULT State::processExpirePolicy(variable_binding_t& policy_input, stack<string> &ids) {
	RESULT result = SUCCESS;
	ACTION action;

	time_t t;

	variable_binding_t policy_output;

	/* Run input policy over policy_input. */
	t = time(NULL);
	action = this->inputPolicy.evaluate(policy_output, policy_input);
	TRACE(TRACE_TIMER, "inputPolicy.evaluate() ran %lu sec for: id %s", time(NULL) - t, policy_input["id"].c_str());

	if (action == ACTION_REJECT) {
		TRACE(TRACE_DEBUG, "Input policy rejected. Marked for removal. id %s", policy_input["id"].c_str());

		/* Store id for removal. */
		ids.push(policy_input["id"]);

		return result;
	} else if (action == ACTION_FAILURE) {
		LMSG("Input policy evaluation failed. Shutting down. id %s", policy_input["id"].c_str());

		interrupt = true;
		result = FAILURE;

		return result;
	}

	/* Copy data over for output policy. */
	policy_input = policy_output;
	policy_output.clear();

	/* Run output policy over policy_input. */
	t = time(NULL);
	action = this->outputPolicy.evaluate(policy_output, policy_input);
	TRACE(TRACE_TIMER, "outputPolicy.evaluate() ran %lu sec for: id %s", time(NULL) - t, policy_input["id"].c_str());

	if (action == ACTION_REJECT) {
		TRACE(TRACE_DEBUG, "Output policy rejected. Marked for removal. id %s", policy_input["id"].c_str());

		/* Store id for removal. */
		ids.push(policy_input["id"]);

		return result;
	} else if (action == ACTION_FAILURE) {
		LMSG("Output policy evaluation failed. Shutting down. id %s", policy_input["id"].c_str());

		interrupt = true;
		result = FAILURE;

		return result;
	}

	return result;
}

RESULT State::processPolicy(variable_binding_t& policy_input, hash_map<string, string>& tableMap) {
	RESULT result = SUCCESS;
	ACTION action;

	time_t t;

	variable_binding_t policy_output, org_policy_input;

	/* Store an unmodified policy_input for comparison. */
	org_policy_input = policy_input;

	/* Run input policy over policy_input. */
	t = time(NULL);
	action = this->inputPolicy.evaluate(policy_output, policy_input);
	TRACE(TRACE_TIMER, "inputPolicy.evaluate() ran %lu sec for: element %s, interface %s, ip %s", time(NULL) - t, policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

	if (action == ACTION_REJECT) {
		TRACE(TRACE_DEBUG, "Input policy rejected. Proceeding to next message. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		return result;
	} else if (action == ACTION_FAILURE) {
		LMSG("Input policy evaluation failed. Shutting down. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		interrupt = true;
		result = FAILURE;

		return result;
	}

	/* Copy data over for output policy. */
	policy_input = policy_output;
	policy_output.clear();

	/* Run output policy over policy_input. */
	t = time(NULL);
	action = this->outputPolicy.evaluate(policy_output, policy_input);
	TRACE(TRACE_TIMER, "outputPolicy.evaluate() ran %lu sec for: element %s, interface %s, ip %s", time(NULL) - t, policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

	if (action == ACTION_REJECT) {
		TRACE(TRACE_DEBUG, "Output policy rejected. Proceeding to next message. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		return result;
	} else if (action == ACTION_FAILURE) {
		LMSG("Output policy evaluation failed. Shutting down. element %s, interface %s, ip %s", policy_input["element-id"].c_str(), policy_input["interface-id"].c_str(), policy_input["ip-id"].c_str());

		interrupt = true;
		result = FAILURE;

		return result;
	}

	/* Clear tmp policy_input. */
	policy_input.clear();

	/* Deal with state changes. */
	result = processChange(org_policy_input, policy_output, tableMap);

	return result;
}

RESULT State::processChange(variable_binding_t& policy_input, variable_binding_t& policy_output, hash_map<string, string>& tableMap) {
	RESULT result = SUCCESS;

	const short TABLE_UNKNOWN = 0, TABLE_ELEMENT = 1, TABLE_INTERFACE = 2, TABLE_IP = 3;
	short table;
	unsigned long id;

	size_t buf_size = 132;
	char buf[buf_size];

	IPv4State *ipv4_state;
	InterfaceState *interface_state;
	ElementState *element_state;

	/* Syncronize table specific and non-table specific entries. */
	for (variable_binding_t::iterator it = policy_output.begin(); it != policy_output.end(); ++it) {
		/* Don't check non-state entries. */
		if (it->first.size() <= 6 || it->first.substr(it->first.size() - 6, 6).compare("-state")) {
			continue;
		}

		TRACE(TRACE_INTERNAL, "checking %s (sync)", it->first.c_str());

		/* XXX: Should this be != not == ? */
		/* Syncronize data only if policy_output state differs from the policy_input state. */
		if (policy_input.find(it->first) == policy_input.end() || !policy_input[it->first].compare(policy_output[it->first])) {
			TRACE(TRACE_DUMP, "policy_output[%s] = %s - matches/missing input (skip)", it->first.c_str(), policy_output[it->first].c_str());
			continue;
		}

		/* If table specific changed, then set non-table specific to same state. */
		if (!it->first.substr(0, 3).compare("ip-")) {
			policy_output[it->first.substr(3)] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing %s over %s -> value == %s", it->first.c_str(), it->first.substr(3, it->first.size()).c_str(), policy_input[it->first].c_str());
		} else if (!it->first.substr(0, 10).compare("interface-")) {
			policy_output[it->first.substr(10)] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing %s over %s -> value == %s", it->first.c_str(), it->first.substr(10, it->first.size()).c_str(), policy_input[it->first].c_str());
		} else if (!it->first.substr(0, 8).compare("element-")) {
			policy_output[it->first.substr(8)] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing %s over %s -> value == %s", it->first.c_str(), it->first.substr(8, it->first.size()).c_str(), policy_input[it->first].c_str());
		} else if (tableMap.find(it->first) != tableMap.end()) { /* Handle non-table specific case. */
			snprintf(buf, buf_size, "%s-%s", tableMap[it->first].c_str(), it->first.c_str());
			policy_output[buf] = policy_output[it->first];

			TRACE(TRACE_INTERNAL, "syncing %s over %s -> value == %s", it->first.c_str(), buf, policy_input[it->first].c_str());
		}
	}

	/* Update table for each state change. */
	for (variable_binding_t::iterator it = policy_output.begin(); it != policy_output.end(); ++it) {
		/* Don't check non-state entries. */
		if (it->first.size() <= 6 || it->first.substr(it->first.size() - 6, 6).compare("-state")) {
			continue;
		}

		TRACE(TRACE_INTERNAL, "checking %s (update)", it->first.c_str());

		/* Update only if policy_output state differs from the policy_input state. */
		if (policy_input.find(it->first) != policy_input.end() && !policy_input[it->first].compare(policy_output[it->first])) {
			TRACE(TRACE_DUMP, "policy_output[%s] = %s - matches input (skip)", it->first.c_str(), policy_output[it->first].c_str());
			continue;
		}

		TRACE(TRACE_DUMP, "comparing %s => %s (in) to %s (out)", it->first.c_str(), policy_input[it->first].c_str(), policy_output[it->first].c_str());

		/* Update only if this entry doesn't have a specific table entry. */
		if (tableMap.find(it->first) != tableMap.end()) {
			snprintf(buf, buf_size, "%s-%s", tableMap[it->first].c_str(), it->first.c_str());
			if (tableMap.find(buf) != tableMap.end() && !tableMap[it->first].compare(tableMap[buf])) {
				continue;
			}
		}

		if (tableMap.find(it->first) != tableMap.end()) { /* Update existing state entry. */
			if (!tableMap[it->first].compare("ip")) {
				table = TABLE_IP;
			} else if (!tableMap[it->first].compare("interface")) {
				table = TABLE_INTERFACE;
			} else if (!tableMap[it->first].compare("element")) {
				table = TABLE_ELEMENT;
			} else {
				table = TABLE_UNKNOWN;
			}
		} else if (!it->first.substr(0, 3).compare("ip-")) {
			table = TABLE_IP;
		} else if (!it->first.substr(0, 10).compare("interface-")) {
			table = TABLE_INTERFACE;
		} else if (!it->first.substr(0, 8).compare("element-")) {
			table = TABLE_ELEMENT;
		} else {
			if (policy_input.find("ip-id") != policy_input.end()) {
				table = TABLE_IP;
			} else if (policy_input.find("interface-id") != policy_input.end()) {
				table = TABLE_INTERFACE;
			} else if (policy_input.find("element-id") != policy_input.end()) {
				table = TABLE_ELEMENT;
			} else {
				table = TABLE_UNKNOWN;
			}
		}

		switch (table) {
			case TABLE_ELEMENT:
				id = strtoul(policy_input["element-id"].c_str(), NULL, 10);

				TRACE(TRACE_INTERNAL, "updating element_attr %s id %lu to state %s", it->first.c_str(), id, policy_output[it->first].c_str());

				if (!it->first.substr(0, 8).compare("element-")) {
					element_state = new ElementState(&(this->storage), id, it->first.substr(8, it->first.size() - (6 + 8)).c_str());
				} else {
					element_state = new ElementState(&(this->storage), id, it->first.substr(0, it->first.size() - 6).c_str());
				}
				element_state->updateTimestamp(false);
				element_state->set(policy_output[it->first].c_str());

				SAFE_DELETE(element_state);

				updateCount++;
				break;
			case TABLE_INTERFACE:
				id = strtoul(policy_input["interface-id"].c_str(), NULL, 10);

				TRACE(TRACE_INTERNAL, "updating interface_attr %s id %lu to state %s", it->first.c_str(), id, policy_output[it->first].c_str());

				if (!it->first.substr(0, 10).compare("interface-")) {
					interface_state = new InterfaceState(&(this->storage), id, it->first.substr(10, it->first.size() - (6 + 10)).c_str());
				} else {
					interface_state = new InterfaceState(&(this->storage), id, it->first.substr(0, it->first.size() - 6).c_str());
				}
				interface_state->updateTimestamp(false);
				interface_state->set(policy_output[it->first].c_str());

				SAFE_DELETE(interface_state);

				updateCount++;
				break;
			case TABLE_IP:
				id = strtoul(policy_input["ip-id"].c_str(), NULL, 10);

				TRACE(TRACE_INTERNAL, "updating ip_attr %s id %lu to state %s", it->first.c_str(), id, policy_output[it->first].c_str());

				if (!it->first.substr(0, 3).compare("ip-")) {
					ipv4_state = new IPv4State(&(this->storage), id, it->first.substr(3, it->first.size() - (6 + 3)).c_str());
				} else {
					ipv4_state = new IPv4State(&(this->storage), id, it->first.substr(0, it->first.size() - 6).c_str());
				}
				ipv4_state->updateTimestamp(false);
				ipv4_state->set(policy_output[it->first].c_str());

				SAFE_DELETE(ipv4_state);

				updateCount++;
				break;
			default:
				TRACE(TRACE_DEBUG, "unable to determine table for %s state change", it->first.c_str());
		}
   }

	return result;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
