# EMG routing and billing plugin for use with EMG Portal database schema
#
# $Change: 46757 $ $DateTime: 2016/07/18 16:21:15 $
#
# Currently the plugin only supports MSISDN prefixes, not HLR lookups.
# However, it can be extended to support HLR lookups fairly easy.
#
# INSTANCES must be set to 1 for plugin, it is not thread-safe
#
# The plugin will:
# Load routes from database on init
# Load prices from database on init
# In the "route" hook first apply route logic and then price logic
#
# Check route for a match where multiple hits will be sorted by priority and entries with same priority will be randomized (simple load-balancing)
#
# Check price for a match
#
# Matching is done in the following order (top priority first):
# 
# Connector in  Route  Username   Prefix
# Match         Match  Match      Match (longest first)    - Price only
# Match         Match  Match      Any                      - Price only
# Match         Any    Match      Match (longest first)
# Match         Any    Match      Any
# Any           Any    Match      Match (longest first)
# Any           Any    Match      Any
# Match         Any    Any        Match (longest first)
# Match         Any    Any        Any
# Any           Any    Any        Match (longest first)
# Any           Any    Any        Any
#
# If no route match, the default_route will be used, if defined.
# If no price match, the default_charge will be used.
#
# * It uses the core EMG schema plus additional schema (emgp_route, emgp_price) from EMG Portal.
# * If more than 10 seconds have passed (MIN_RELOAD_INTERVAL) since last reload it will check for new active prices and reload.
# * Plugin does not handle concurrency and must be configured with INSTANCES=1.
# * Changes to source code of plugin requires EMG restart.
#
# Copyright Infoflex Connect AB, 2011-2014
#
use strict;

use DBI;
use DBI qw(:sql_types);
use Carp::Assert;
use JSON qw(decode_json);
use LWP::Simple;
use POSIX qw(strftime);

# These must be changed to reflect installation
my $dbname = 'emg';
my $dbuser = 'emguser';
my $dbpasswd = 'secret';
my $dbhost = '127.0.0.1';

# Output logging
my $debug = 0;
my $logfile = "/tmp/billing.log";

# Default route, if any
my $default_route;
# This should be set to same value as DEFAULT_CHARGE in EMG server.cfg
my $default_charge = 0.05;
my $default_charge_cost = 0.05;

# Check for new prices at most every 10 seconds
my $MIN_RELOAD_INTERVAL = 10;

# --- NO CHANGES SHOULD BE NECESSARY BELOW THIS LINE ---

my $PREFIX_TYPE_MSISDN = 0;
my $PREFIX_TYPE_HLR = 1;

my $dbh;
my $sth_routes;
my $sth_prices;
my $sth_last_routes_update;
my $sth_last_routes_count;
my $sth_last_prices_update;
my $sth_last_prices_count;
my $last_reload_routes_check = 0;
my $last_reload_prices_check = 0;
my $max_active_prices_timestamp = 0;
my $max_active_routes_timestamp = 0;
my $last_active_prices_count = 0;
my $last_active_routes_count = 0;

my $price;
my $price_msisdn;
my $routes;
my $routes_msisdn;

my $ri_reject;

my $maxlen_msisdn_routes_prefix = -1;
my $maxlen_msisdn_prices_prefix = -1;

my $within_emg = 0;

sub dolog
{
	my($str, $critical) = @_;

	if($within_emg) {
		return unless ($debug || $critical);
		my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime);
		open(OUT, ">>$logfile");
		print OUT "$ts $str";
		close(OUT);
	} else {
		print $str;
	}
}

sub db_connect
{
	dolog("db_connect: enter\n");

	$dbh = DBI->connect("DBI:mysql:$dbname:$dbhost", "$dbuser", "$dbpasswd");
	$dbh->{mysql_auto_reconnect} = 1;
	# Get active routes
	$sth_routes = $dbh->prepare("SELECT r.connector, u.username, r.prefix_type, r.prefix, r.priority, r.route, r.route_emguser, r.valid_destaddr_regexp, r.valid_destaddr_reject_invalid FROM (emgp_route r LEFT JOIN emgp_emgaccount ea ON ea.id = r.emgaccount_id ) LEFT JOIN emguser u ON ea.emguser_id = u.userid WHERE status = 1");
	$sth_last_routes_update = $dbh->prepare('SELECT GREATEST(MAX(UNIX_TIMESTAMP(created)), MAX(UNIX_TIMESTAMP(updated))) FROM emgp_route WHERE status = 1');
	$sth_last_routes_count = $dbh->prepare('SELECT COUNT(*) FROM emgp_route WHERE status = 1');

	# Get active prices
	$sth_prices = $dbh->prepare("SELECT p.id, p.connector, p.connector_out, u.username, p.prefix_type, p.prefix, p.charge, p.charge_cost FROM (emgp_price p LEFT JOIN emgp_emgaccount ea ON ea.id = p.emgaccount_id ) LEFT JOIN emguser u ON ea.emguser_id = u.userid WHERE status = 1");
	$sth_last_prices_update = $dbh->prepare('SELECT GREATEST(MAX(UNIX_TIMESTAMP(created)), MAX(UNIX_TIMESTAMP(updated))) FROM emgp_price WHERE status = 1');
	$sth_last_prices_count = $dbh->prepare('SELECT COUNT(*) FROM emgp_price WHERE status = 1');
}

sub get_generic_key
{
	my ($connector, $route, $username) = @_;

	lc("$connector $route $username");
}

sub get_prefix_key
{
	my ($connector, $route, $username, $prefix) = @_;

	lc("$connector $route $username $prefix");
}

sub add_route
{
	my ($connector, $username, $prefix_type, $prefix, $priority, $route, $route_emguser, $valid_destaddr_regexp, $valid_destaddr_reject_invalid) = @_;
	my $prefix_len = length($prefix);

	dolog("add_route: connector=$connector username=$username prefix=$prefix prefix_len=$prefix_len priority=$priority route=$route route=$route_emguser\n");

	my $ri = RI->new;
	$ri->set_prefix_type($prefix_type);
	$ri->set_prefix($prefix);
	$ri->set_priority($priority);
	$ri->set_route($route);
	$ri->set_route_emguser($route_emguser);
	$ri->set_valid_destaddr_regexp($valid_destaddr_regexp);
	$ri->set_valid_destaddr_reject_invalid($valid_destaddr_reject_invalid);

	if($prefix == '*') {
		# Generic entries
		my $key = get_generic_key($connector, '', $username);
		if(!defined($routes->{$key})) {
			$routes->{$key} = ();
		}
		push(@{$routes->{$key}}, $ri);
		dolog("add_route: routes key=$key len=" . scalar(@{$routes->{$key}}). "\n");
	} elsif($prefix_type == $PREFIX_TYPE_MSISDN) {
		# MSISDN entries
		my $key = get_prefix_key($connector, '', $username, $prefix);
		if($prefix_len > $maxlen_msisdn_routes_prefix) {
			$maxlen_msisdn_routes_prefix = $prefix_len;
		}
		if(!defined($routes_msisdn->{$key})) {
			$routes_msisdn->{$key} = ();
		}
		push(@{$routes_msisdn->{$key}}, $ri);
		dolog("add_route: route_msisdn key=$key len=" . scalar(@{$routes_msisdn->{$key}}) . "\n");
	} else {
		# Invalid prefix type
	}
}

sub add_price
{
	my ($connector, $route, $username, $prefix_type, $prefix, $charge, $charge_id, $charge_cost) = @_;
	my $prefix_len = length($prefix);

	dolog("add_price: connector=$connector route=$route username=$username prefix=$prefix prefix_len=$prefix_len charge=$charge charge_id=$charge_id charge_cost=$charge_cost\n");

	my $ps = PI->new;
	$ps->set_prefix_type($prefix_type);
	$ps->set_prefix($prefix);
	$ps->set_charge($charge);
	$ps->set_charge_id($charge_id);
	$ps->set_charge_cost($charge_cost);

	if($prefix == '*') {
		# Generic entries
		my $key = get_generic_key($connector, $route, $username);
		$price->{$key} = $ps;
		dolog("add_price: price key=$key\n");
	} elsif($prefix_type == $PREFIX_TYPE_MSISDN) {
		# MSISDN entries
		my $key = get_prefix_key($connector, $route, $username, $prefix);
		if($prefix_len > $maxlen_msisdn_prices_prefix) {
			$maxlen_msisdn_prices_prefix = $prefix_len;
		}
		$price_msisdn->{$key} = $ps;
		dolog("add_price: price_msisdn key=$key\n");
	} else {
		# Invalid prefix type
	}
}

sub load_routes
{
	$sth_routes->execute;

	$routes = undef;
	$routes_msisdn = undef;
	$maxlen_msisdn_routes_prefix = -1;

	while(my @data = $sth_routes->fetchrow_array) {
		my $connector = @data[0] || '*';
		my $username = @data[1] || '*';
		my $prefix_type = @data[2] || $PREFIX_TYPE_MSISDN;
		my $prefix = @data[3] || '*';
		my $priority = @data[4];
		my $route = @data[5];
		my $route_emguser = @data[6];
		my $valid_destaddr_regexp = @data[7];
		my $valid_destaddr_reject_invalid = @data[8];

		add_route($connector, $username, $prefix_type, $prefix, $priority, $route, $route_emguser,
			$valid_destaddr_regexp, $valid_destaddr_reject_invalid);
	}
	$sth_routes->finish;
}

sub load_prices
{
	$sth_prices->execute;

	$price = undef;
	$price_msisdn = undef;
	$maxlen_msisdn_prices_prefix = -1;

	while(my @data = $sth_prices->fetchrow_array) {
		my $col = 0;
		my $charge_id = @data[$col++];
		my $connector_in = @data[$col++] || '*';
		my $route = @data[$col++] || '*';
		my $username = @data[$col++] || '*';
		my $prefix_type = @data[$col++] || $PREFIX_TYPE_MSISDN;
		my $prefix = @data[$col++] || '*';
		my $charge = @data[$col++];
		my $charge_cost = @data[$col++];

		add_price($connector_in, $route, $username, $prefix_type, $prefix, $charge, $charge_id, $charge_cost);
	}
	$sth_prices->finish;
}

sub reload_routes_if_needed
{
	my $now = time;

	if($now < ($last_reload_routes_check + $MIN_RELOAD_INTERVAL)) {
		dolog("reload_routes_if_needed: not time to check, now=$now last_reload_routes_check=$last_reload_routes_check\n");
		return;
	}
	dolog("reload_routes_if_needed: will check, now=$now last_reload_routes_check=$last_reload_routes_check last_active_routes_count=$last_active_routes_count\n");
	$last_reload_routes_check = $now;

	my $reload_needed = 0;

	$sth_last_routes_update->execute;
	my @row;
	if(@row = $sth_last_routes_update->fetchrow_array()) {
		my $ts = @row[0];
		if($ts > $max_active_routes_timestamp) {
			$reload_needed = 1;
			$max_active_routes_timestamp = $ts;
		}
	}
	$sth_last_routes_count->execute;
	if(@row = $sth_last_routes_count->fetchrow_array()) {
		my $count = @row[0];
		if($count != $last_active_routes_count) {
			$reload_needed = 1;
			$last_active_routes_count = $count;
		}
	}
	if($reload_needed) {
		load_routes;
		dolog("reload_routes_if_needed: reloaded, max_active_routes_timestamp=$max_active_routes_timestamp last_active_routes_count=$last_active_routes_count\n");
	}
	$sth_last_routes_update->finish;
	$sth_last_routes_count->finish;
}

sub reload_prices_if_needed
{
	my $now = time;

	if($now < ($last_reload_prices_check + $MIN_RELOAD_INTERVAL)) {
		dolog("reload_prices_if_needed: not time to check, now=$now last_reload_prices_check=$last_reload_prices_check\n");
		return;
	}
	dolog("reload_prices_if_needed: will check, now=$now last_reload_prices_check=$last_reload_prices_check last_active_prices_count=$last_active_prices_count\n");
	$last_reload_prices_check = $now;

	my $reload_needed = 0;

	$sth_last_prices_update->execute;
	my @row;
	if(@row = $sth_last_prices_update->fetchrow_array()) {
		my $ts = @row[0];
		if($ts > $max_active_prices_timestamp) {
			$reload_needed = 1;
			$max_active_prices_timestamp = $ts;
		}
	}
	$sth_last_prices_count->execute;
	if(@row = $sth_last_prices_count->fetchrow_array()) {
		my $count = @row[0];
		if($count != $last_active_prices_count) {
			$reload_needed = 1;
			$last_active_prices_count = $count;
		}
	}
	if($reload_needed) {
		load_prices;
		dolog("reload_prices_if_needed: reloaded, max_active_prices_timestamp=$max_active_prices_timestamp last_active_prices_count=$last_active_prices_count\n");
	}
	$sth_last_prices_update->finish;
	$sth_last_prices_count->finish;
}

sub create_config
{
	my ($name, $config_param, $dbprofile) = @_;
	dolog("create_config\n");
	# Read db params from supplied db profile, if present
	if(defined($dbprofile)) {
		dolog("create_config: Using db params from DBPROFILE\n");
		$dbname = $dbprofile->{'dbname'};
		$dbuser = $dbprofile->{'username'};
		$dbpasswd = $dbprofile->{'password'};
		$dbhost = $dbprofile->{'host'};
	}
	db_connect;
	reload_routes_if_needed;
	reload_prices_if_needed;

	$ri_reject = RI->new;
}

sub check_valid_destaddr
{
	my ($ri_ar, $destaddr) = @_;
	my @ri_out = ();
	foreach my $ri (@$ri_ar) {
		if(!$ri->get_valid_destaddr_regexp) {
			push(@ri_out, $ri);
			next;
		}
		foreach my $regexp (@{$ri->get_valid_destaddr_regexp}) {
			if($destaddr =~ $regexp) {
				push(@ri_out, $ri);
				last;
			}
		}
	}
	if(@ri_out) {
		return \@ri_out;
	}
	dolog("Destaddr $destaddr does not match any route regexp", 1);
	return undef;
}

sub check_route_prefix
{
	my ($cname, $username, $destaddr) = @_;

	for(my $i = $maxlen_msisdn_routes_prefix; $i > 0; $i--) {
		my $key = get_prefix_key($cname, '', $username, substr($destaddr, 0, $i));
		dolog("check_route_prefix: key=$key\n");
		my $ri_in = $routes_msisdn->{$key};
		if(defined($ri_in)) {
			my $ri_out = check_valid_destaddr($ri_in, $destaddr);
			if($ri_out) {
				dolog("check_route_prefix: msisdn match len=$i ri_in=$ri_in\n");
				return $ri_out;
			}
		}
	}

	return undef;
}

sub check_price_prefix
{
	my ($cname, $route, $username, $destaddr) = @_;
	my $ps;

	for(my $i = $maxlen_msisdn_prices_prefix; $i > 0; $i--) {
		my $key = get_prefix_key($cname, $route, $username, substr($destaddr, 0, $i));
		dolog("check_price_prefix: key=$key\n");
		$ps = $price_msisdn->{$key};
		if(defined($ps)) {
			dolog("check_price_prefix: msisdn match len=$i\n");
			last;
		}
	}

	$ps;
}

sub is_queue_full
{
	my ($ri, $connectors) = @_;

	my $route = $ri->get_route;
	my $cinfo = $connectors->{$ri->get_route};
	if(!defined($cinfo)) {
		return 0;
	}
	my $max_queuesize = $cinfo->{'maxqueuesize'};
	if(!defined($max_queuesize)) {
		return 0;
	}
	my $emg_queuesize = $cinfo->{'queuesize'};
	dolog("is_queue_full: max_queuesize=$max_queuesize emg_queuesize=$emg_queuesize\n");
	my $device_queuesize = get_device_queuesize($cinfo);
	dolog("is_queue_full: device_queuesize=$device_queuesize\n");
	if($device_queuesize > 0) {
		dolog("is_queue_full: device_queuesize=$device_queuesize\n");
	}
	if(($device_queuesize + $emg_queuesize) < $max_queuesize) {
		return 0;
	}
	dolog("is_queue_full: yes\n");
	return 1;
}

sub get_device_queuesize
{
	my ($cinfo) = @_;
	my $cnote = $cinfo->{'note'};
	# Hypermedia Gateway?
	if($cnote =~ /^HM:/) {
		$cnote =~ s/^HM://;
		return get_hm_device_queuesize($cnote);
	}
	return 0;
}

sub get_hm_device_queuesize
{
	my ($cnote) = @_;
	my ($password, $url) = split(':', $cnote, 2);
	my $queuesize = 0;
	my $json = '';
	eval {
		$json = get("$url?server_password=$password&method=get_q_size");
		unless($json =~ /^METHOD_REPLY/) {
			die "Operation failed";
		}
		$json =~ s/^[^{]+//;
		my $resp = decode_json($json);
		$queuesize = $resp->{total_len};
	};
	if ($@) {
		dolog("get_hm_device_queuesize: json=\"$json\", exception=$@", 1);
	}
	return $queuesize;
}

sub get_a_route
{
	my ($ref, $connectors) = @_;
	if(!defined($ref)) {
		return undef;
	}
	my @ar = @{$ref};

	if(scalar(@ar) == 1) {
		my $ri = @ar[0];
		if(is_queue_full($ri, $connectors)) {
			return 17;
		}
		return $ri;
	}
	foreach(@ar) {
		my $ri = $_;
		$ri->set_random_sort_key;
	}
	@ar = sort { $b->get_sort_key <=> $a->get_sort_key } @ar;
	my $n_queue_full = 0;
	foreach(@ar) {
		my $ri = $_;
		if(!defined($connectors)) {
			return $ri;
		}
		my $cinfo = $connectors->{$ri->get_route};
		# Check if 'BOUND'
		if($cinfo->{'state'} eq '8') {
			# Check queue
			if(is_queue_full($ri, $connectors)) {
				$n_queue_full++;
			} else {
				return $ri;
			}
		}
	}
	if($n_queue_full == scalar(@ar)) {
		return 17;
	}
	return @ar[0];
}

sub do_price_check
{
	my ($cname, $route, $username, $destaddr, $check_msisdn, $match) = @_;
	my $ps;

	if(defined($match->get_charge_id) && defined($match->get_charge_cost_id)) {
		return;
	}

	if($check_msisdn) {
		$ps = check_price_prefix($cname, $route, $username, $destaddr);
	}
	if(defined($ps)) {
		if(!defined($match->get_charge_id) && defined($ps->get_charge)) {
			$match->set_charge($ps->get_charge);
			$match->set_charge_id($ps->get_charge_id);
		}
		if(!defined($match->get_charge_cost_id) && defined($ps->get_charge_cost)) {
			$match->set_charge_cost($ps->get_charge_cost);
			$match->set_charge_cost_id($ps->get_charge_id);
		}
	}

	if(defined($match->get_charge_id) && defined($match->get_charge_cost_id)) {
		return;
	}

	my $key = get_generic_key($cname, $route, $username);
	$ps = $price->{$key};

	if(defined($ps)) {
		if(!defined($match->get_charge_id) && defined($ps->get_charge)) {
			$match->set_charge($ps->get_charge);
			$match->set_charge_id($ps->get_charge_id);
		}
		if(!defined($match->get_charge_cost_id) && defined($ps->get_charge_cost)) {
			$match->set_charge_cost($ps->get_charge_cost);
			$match->set_charge_cost_id($ps->get_charge_id);
		}
	}
}

sub get_ri
{
	my ($cname, $username, $destaddr, $connectors) = @_;

	my $ri;
	my $check_msisdn = 0;
	my $key;

	# Any MSISDN prefix loaded?
	if($maxlen_msisdn_routes_prefix > 0) {
		dolog("route: will check msisdn prefix\n");
		$check_msisdn = 1;
		$ri = get_a_route(check_route_prefix($cname, $username, $destaddr), $connectors);
	}
	# Check generic entries
	if(!defined($ri)) {
		# Match exact connector and exact username
		if($check_msisdn) {
			$ri = get_a_route(check_route_prefix($cname, $username, $destaddr), $connectors);
		}
		if(!defined($ri)) {
			$key = get_generic_key($cname, '', $username);
			$ri = get_a_route($routes->{$key}, $connectors);
		}
	}
	if(!defined($ri)) {
		# Match any connector and exact username
		if($check_msisdn) {
			$ri = get_a_route(check_route_prefix('*', $username, $destaddr), $connectors);
		}
		if(!defined($ri)) {
			$key = get_generic_key('*', '', $username);
			$ri = get_a_route($routes->{$key}, $connectors);
		}
	}
	if(!defined($ri)) {
		# Match exact connector and any username
		if($check_msisdn) {
			$ri = get_a_route(check_route_prefix($cname, '*', $destaddr), $connectors);
		}
		if(!defined($ri)) {
			$key = get_generic_key($cname, '', '*');
			$ri = get_a_route($routes->{$key}, $connectors);
		}
	}

	return ($ri, $check_msisdn);
}

sub route
{
	my ($request, $response) = @_;
	my $ci = ${$request}{'connector'};
	my $qe = ${$request}{'qe'};
	my $connectors = ${$request}{'connectors'};
	my $ret = 1;
	my $cname = $ci->{'name'};
	my $sourceaddr = $qe->{'SOURCEADDR'};
	my $destaddr = $qe->{'DESTADDR'};
	my $username = $qe->{'USERNAME'};
	my $inroute = $qe->{'ROUTE'} || ${$request}{'route'};
	my $inxusername = $qe->{'XUSERNAME'};

	reload_routes_if_needed;
	reload_prices_if_needed;

	dolog("route: connector=$cname username=$username sourceaddr=$sourceaddr destaddr=$destaddr inroute=$inroute inxusername=$inxusername\n");

	my $check_msisdn = 0;

	my $key;
	my $mccmnc;
	my $charge;
	my $charge_cost;

	my $route = $default_route;
	my $xusername;

	if(defined($inroute)) {
		$route = $inroute;
		if(defined($inxusername)) {
			$xusername = $inxusername;
		}
	} else {
		my ($ri, $check_msisdn) = get_ri($cname, $username, $destaddr, $connectors);

		if(!defined($ri)) {
			# Match any connector and any username
			if($check_msisdn) {
				$ri = get_a_route(check_route_prefix('*', '*', $destaddr), $connectors);
			}
			if(!defined($ri)) {
				$key = get_generic_key('*', '', '*');
				$ri = get_a_route($routes->{$key}, $connectors);
			}
		}
	
		if(defined($ri)) {
			if(!ref($ri)) {
				$response->{'errorcode'} = $ri;
				return 1;
			}
			$route = $ri->get_route;
			$xusername = $ri->get_route_emguser;
		}
	}

	if(!defined($qe->{CHARGE})) {
		my $ps;
		my $match = new PI;

		# Any MSISDN prefix loaded?
		if($maxlen_msisdn_prices_prefix > 0) {
			dolog("price: will check msisdn prefix\n");
			$check_msisdn = 1;
		}
		# Check generic entries
		# Match exact connectors and exact username
		do_price_check($cname, $route, $username, $destaddr, $check_msisdn, $match);
		# Match exact connector in and exact username
		do_price_check($cname, '*', $username, $destaddr, $check_msisdn, $match);
		# Match any route and exact username
		do_price_check('*', $route, $username, $destaddr, $check_msisdn, $match);
		# Match any connector and exact username
		do_price_check('*', '*', $username, $destaddr, $check_msisdn, $match);
		# Match exact connector and any username
		do_price_check($cname, '*', '*', $destaddr, $check_msisdn, $match);
		# Match exact connector and any username
		do_price_check('*', $route, '*', $destaddr, $check_msisdn, $match);
		# Match any connector and any username
		do_price_check('*', '*', '*', $destaddr, $check_msisdn, $match);

		$charge = $match->get_charge;
		my $charge_id = $match->get_charge_id;
		$charge_cost = $match->get_charge_cost;
		my $charge_cost_id = $match->get_charge_cost_id;
	
		if(!defined($charge)) {
				$charge = $default_charge;
		}
		if(!defined($charge_cost)) {
				$charge_cost = $default_charge_cost;
		}

		$qe->{CHARGE} = $charge;
		$qe->{CHARGE_PRICE_ID} = $charge_id;
		$qe->{CHARGE_COST} = $charge_cost;
		$qe->{CHARGE_COST_PRICE_ID} = $charge_cost_id;
	}

	dolog("route: connector=$cname username=$username sourceaddr=$sourceaddr destaddr=$destaddr route=$route xusername=$xusername charge=$charge charge_cost=$charge_cost\n");

	$response->{'route'} = $route;

	# Route to specific user?
	if(defined($xusername)) {
		$qe->{XUSERNAME} = $xusername;
	}

	0;
}

# From EMG 5.4.1 environment variable EMG till be set
# to 1 when plugin is run in EMG context
if(1 != ( $ENV{EMG} || 0 )) {
	$last_reload_routes_check = time;
	$last_reload_prices_check = time;

	# When run outside of EMG we can run a small test suite
	#$debug = 1;
	#db_connect;
	#load_prices;
	# We set up "fixtures" here
	# Normal route fixtures
	add_route('c0', 'u0', $PREFIX_TYPE_MSISDN, '46701234567', 1, 'a1', undef, '46.*', 1);
	add_route('c0', 'u0', $PREFIX_TYPE_MSISDN, '*', 1, 'a2');
	add_route('*', 'u0', $PREFIX_TYPE_MSISDN, '46701234567', 1, 'a3');
	add_route('*', 'u0', $PREFIX_TYPE_MSISDN, '*', 1, 'a4');
	add_route('c0', '*', $PREFIX_TYPE_MSISDN, '46701234567', 1, 'a5');
	add_route('c0', '*', $PREFIX_TYPE_MSISDN, '*', 1, 'a6');
	add_route('*', '*', $PREFIX_TYPE_MSISDN, '46701234567', 1, 'a7');
	add_route('*', '*', undef, '*', 1, 'a8');

	# Prio route fixtures
	add_route('c10', '*', $PREFIX_TYPE_MSISDN, '*', 2, 'sec');
	add_route('c10', '*', $PREFIX_TYPE_MSISDN, '*', 10, 'pri');
	add_route('c10', '*', $PREFIX_TYPE_MSISDN, '*', 1, 'last');
	add_route('c11', '*', $PREFIX_TYPE_MSISDN, '*', 5, 'route1');
	add_route('c11', '*', $PREFIX_TYPE_MSISDN, '*', 5, 'route2');
	add_route('c12', 'u4', $PREFIX_TYPE_MSISDN, '*', 5, 'route');

	# Normal price fixtures
	my $i = 0;
	add_price('c1', '*', 'u1', $PREFIX_TYPE_MSISDN, '46701234567', 0.1, $i++, undef);
	add_price('c1', '*', 'u1', $PREFIX_TYPE_MSISDN, '*', 0.2, $i++, 0.2);
	add_price('*', '*', 'u1', $PREFIX_TYPE_MSISDN, '46701234567', 0.3, $i++, 0.3);
	add_price('*', '*', 'u1', $PREFIX_TYPE_MSISDN, '*', 0.4, $i++, undef);
	add_price('c1', '*', '*', $PREFIX_TYPE_MSISDN, '46701234567', 0.5, $i++, 0.5);
	add_price('c1', '*', '*', $PREFIX_TYPE_MSISDN, '*', 0.6, $i++, 0.6);
	add_price('*', '*', '*', $PREFIX_TYPE_MSISDN, '46701234567', 0.7, $i++, 0.7);
	add_price('c2', '*', '*', $PREFIX_TYPE_MSISDN, '46707654321', undef, $i++, 0.65);
	add_price('*', '*', '*', undef, '*', 0.8, $i++, 0.8);

	dolog("maxlen_msisdn_routes_prefix=$maxlen_msisdn_routes_prefix\n");
	dolog("maxlen_msisdn_prices_prefix=$maxlen_msisdn_prices_prefix\n");

	#my $ci = ${$request}{'connector'};
	#my $qe = ${$request}{'qe'};

	my %request;
	my %response;
	my $qe;
	my $ci;

	$ci->{'name'} = 'c0';
	$qe->{'USERNAME'} = 'u0';
	$qe->{'DESTADDR'} = '46701234567';

	$request{'connector'} = $ci;
	$request{'qe'} = $qe;

	# TC-R1
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a1');
	exit;
	# TC-R2
	$ci->{'name'} = 'c1';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a3');
	# TC-R3
	$ci->{'name'} = 'c0';
	$qe->{'USERNAME'} = 'u1';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a5');
	# TC-R4
	$ci->{'name'} = 'c1';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a7');

	$qe->{'DESTADDR'} = '46707654321';

	# TC-R5
	$ci->{'name'} = 'c0';
	$qe->{'USERNAME'} = 'u0';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a2');
	# TC-R6
	$ci->{'name'} = 'c1';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a4');
	# TC-R7
	$ci->{'name'} = 'c0';
	$qe->{'USERNAME'} = 'u1';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a6');
	# TC-R8
	$ci->{'name'} = 'c1';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'a8');

	# TC-PRIO1
	$ci->{'name'} = 'c10';
	$qe->{'USERNAME'} = 'u1';
	$qe->{'DESTADDR'} = '46701234567';
	route(\%request, \%response);
	assert((\%response)->{route} eq 'pri');
	# TC-PRIO2
	my $route1_count = 0;
	my $route2_count = 0;
	my $old_debug = $debug;
	$debug = 0;
	for(my $i = 0; $i < 1000; $i++) {
		$ci->{'name'} = 'c11';
		route(\%request, \%response);
		assert(((\%response)->{route} eq 'route1') || ((\%response)->{route} eq 'route2'));
		if((\%response)->{route} eq 'route1') {
			$route1_count++;
		}
		if((\%response)->{route} eq 'route2') {
			$route2_count++;
		}
	}
	$debug = $old_debug;
	assert($route1_count + $route2_count == 1000);
	assert($route1_count > 400);
	assert($route2_count > 400);
	# TC-PRIO3
	$ci->{'name'} = 'c10';
	$qe->{'USERNAME'} = 'u1';
	$qe->{'DESTADDR'} = '46701234567';
	$request{'connectors'}->{pri}->{state} = 3; # error
	$request{'connectors'}->{sec}->{state} = 8; # bound
	route(\%request, \%response);
	assert((\%response)->{route} eq 'sec');
	# TC-PRIO4
	$request{'connectors'}->{pri}->{state} = 3; # error
	$request{'connectors'}->{sec}->{state} = 3; # error
	route(\%request, \%response);
	assert((\%response)->{route} eq 'pri');

	# TC-QUEUE1 - One connector, queue full
	$ci->{'name'} = 'c12';
	$qe->{'USERNAME'} = 'u4';
	$qe->{'DESTADDR'} = '46701234567';
	$request{'connectors'}->{route}->{state} = 8; # bound
	$request{'connectors'}->{route}->{maxqueuesize} = 10;
	$request{'connectors'}->{route}->{queuesize} = 10;
	route(\%request, \%response);
	assert((\%response)->{errorcode} == 17);
	# TC-QUEUE2 - Two connectors, queue full for one connector
	$ci->{'name'} = 'c11';
	$qe->{'USERNAME'} = 'u1';
	$qe->{'DESTADDR'} = '46701234567';
	$request{'connectors'}->{route1}->{state} = 8; # bound
	$request{'connectors'}->{route2}->{state} = 8; # bound
	$request{'connectors'}->{route1}->{maxqueuesize} = 10;
	$request{'connectors'}->{route1}->{queuesize} = 10;
	route(\%request, \%response);
	assert((\%response)->{route} eq 'route2');
	# TC-QUEUE3 - Two connectors, queue full for all connectors
	$request{'connectors'}->{route2}->{maxqueuesize} = 100;
	$request{'connectors'}->{route2}->{queuesize} = 789;
	route(\%request, \%response);
	assert((\%response)->{errorcode} == 17);

	$ci->{'name'} = 'c1';
	$qe->{'USERNAME'} = 'u1';
	$qe->{'DESTADDR'} = '46701234567';

	# TC-P1
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.1);
	assert($qe->{CHARGE_COST} == 0.2);
	assert((\%response)->{route} eq 'a7');
	# TC-P2
	$ci->{'name'} = 'c2';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.3);
	assert($qe->{CHARGE_COST} == 0.3);
	# TC-P3
	$ci->{'name'} = 'c1';
	$qe->{'USERNAME'} = 'u2';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.5);
	assert($qe->{CHARGE_COST} == 0.5);
	# TC-P4
	$ci->{'name'} = 'c2';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.7);
	assert($qe->{CHARGE_COST} == 0.7);

	$qe->{'DESTADDR'} = '46707654321';

	# TC-P5
	$ci->{'name'} = 'c1';
	$qe->{'USERNAME'} = 'u1';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.2);
	assert($qe->{CHARGE_COST} == 0.2);
	assert((\%response)->{route} eq 'a8');
	# TC-P6
	$ci->{'name'} = 'c2';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.4);
	assert($qe->{CHARGE_COST} == 0.65);
	# TC-P7
	$ci->{'name'} = 'c1';
	$qe->{'USERNAME'} = 'u2';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.6);
	assert($qe->{CHARGE_COST} == 0.6);
	# TC-P8
	$ci->{'name'} = 'c2';
	route(\%request, \%response);
	assert($qe->{CHARGE} == 0.8);
	assert($qe->{CHARGE_COST} == 0.65);

	print "All OK.\n";

} else {
	$within_emg = 1;
}

# Route information object
package RI;

sub new {
	my $class = shift;

	my $objref = {
		_errorcode => 0
	};

	bless $objref, $class;
}

sub set_prefix_type { $_[0]->{_prefix_type} = $_[1] }
sub get_prefix_type { return $_[0]->{_prefix_type} }

sub set_prefix { $_[0]->{_prefix} = $_[1] }
sub get_prefix { return $_[0]->{_prefix} }

sub set_priority { $_[0]->{_priority} = $_[1] }
sub get_priority { return $_[0]->{_priority} }

sub set_route { $_[0]->{_route} = $_[1] }
sub get_route { return $_[0]->{_route} }

sub set_route_emguser { $_[0]->{_route_emguser} = $_[1] }
sub get_route_emguser { return $_[0]->{_route_emguser} }

sub set_valid_destaddr_regexp {
	return if(!$_[1]);
	my @regexps_in = split(/\|/, $_[1]);
	my @regexps = ();
	foreach my $regexp (@regexps_in) {
			push(@regexps, qr/$regexp/);
	}
	$_[0]->{_valid_destaddr_regexp } = \@regexps;
}
sub get_valid_destaddr_regexp  { return $_[0]->{_valid_destaddr_regexp } }

sub set_valid_destaddr_reject_invalid { $_[0]->{_valid_destaddr_reject_invalid} = $_[1] }
sub get_valid_destaddr_reject_invalid { return $_[0]->{_valid_destaddr_reject_invalid} }

sub set_errorcode { $_[0]->{_errorcode} = $_[1] }
sub get_errorcode { return $_[0]->{_errorcode} }

sub set_random_sort_key { $_[0]->{_sort_key} = $_[0]->{_priority} * 10000 + int(rand(1000)) }
sub get_sort_key { return $_[0]->{_sort_key} }

# Price information object
package PI;

sub new {
	my $class = shift;

	my $objref = {
	};

	bless $objref, $class;
}

sub set_prefix_type { $_[0]->{_set_prefix_type} = $_[1] }
sub get_prefix_type { return $_[0]->{_set_prefix_type} }

sub set_prefix { $_[0]->{_set_prefix} = $_[1] }
sub get_prefix { return $_[0]->{_set_prefix} }

sub set_charge { $_[0]->{_set_charge} = $_[1] }
sub get_charge { return $_[0]->{_set_charge} }

sub set_charge_id { $_[0]->{_set_charge_id} = $_[1] }
sub get_charge_id { return $_[0]->{_set_charge_id} }

sub set_charge_cost { $_[0]->{_set_charge_cost} = $_[1] }
sub get_charge_cost { return $_[0]->{_set_charge_cost} }

sub set_charge_cost_id { $_[0]->{_set_charge_cost_id} = $_[1] }
sub get_charge_cost_id { return $_[0]->{_set_charge_cost_id} }
