/*
 * clirw.cxx
 *
 * Module for CLI/ANI manipulation.
 *
 * $Id: clirw.cxx,v 1.12 2006/02/20 10:03:23 zvision Exp $
 *
 * Copyright (c) 2005, Michal Zygmuntowicz
 *
 * This work is published under the GNU Public License (GPL)
 * see file COPYING for details.
 * We also explicitely grant the right to link this code
 * with the OpenH323 library.
 */
#if defined(_WIN32) && (_MSC_VER <= 1200)
#pragma warning(disable:4284)
#endif

#include <ptlib.h>
#include <ptlib/ipsock.h>
#include "clirw.h"
#include <h225.h>
#include <h323pdu.h>
#include <q931.h>
#include "sigmsg.h"
#include "stl_supp.h"
#include "h323util.h"
#include "RasTbl.h"
#include "RasPDU.h"
#include "gkauth.h"
#include "Toolkit.h"

namespace {
const char * const CLIRewriteSection = "RewriteCLI";
const char * const ProcessSourceAddress = "ProcessSourceAddress";
const char * const RemoveH323Id = "RemoveH323Id";
const char * const CLIRPolicy = "CLIRPolicy";
const char * const ReservedKeys[] = { ProcessSourceAddress, RemoveH323Id, CLIRPolicy, NULL };
}

CLIRewrite::RewriteRule::RewriteRule()
	: m_matchType(MatchDialedNumber), m_rewriteType(PrefixToNumber),
	m_screeningType(NoScreening), m_manualCLIR(CLIRPassthrough),
	m_CLIRPolicy(IgnoreCLIR)
{
}

PString CLIRewrite::RewriteRule::AsString() const
{
	PString s;

	if (m_manualCLIR == RestrictPresentation)
		s = "restrict";
	else if (m_manualCLIR == AllowPresentation)
		s = "allow";

	if (m_CLIRPolicy != IgnoreCLIR) {
		if (!s)
			s += ",";
		if (m_CLIRPolicy == ForwardCLIR)
			s += "forward";
 		else if (m_CLIRPolicy == ApplyCLIRForTerminals)
			s += "applyforterminals";
		else
			s += "apply";
	}
	if (!s)
		s = "pi=" + s + " ";
	
	if (m_matchType == MatchDialedNumber)
		s += "dialed number: ";
	else if (m_matchType == MatchDestinationNumber)
		s += "destination number: ";
	else
		s += "caller number: ";

	s += m_prefix.empty() ? "any" : m_prefix.c_str();
	if (m_rewriteType == PrefixToNumber)
		s += " = ";
	else if (m_rewriteType == PrefixToPrefix)
		s += " *= ";
	else if (m_rewriteType == NumberToNumber)
		s += " ~= ";
	else
		s += " ?unknown rewrite rule type? ";

	std::vector<std::string>::const_iterator cli = m_cli.begin();
	while (cli != m_cli.end()) {
		s += cli->c_str();
		if (++cli != m_cli.end())
			s += ", ";
	}
	if (m_cli.empty())
		s += (m_screeningType == HideFromTerminals) ? "hide from terminals only" : "hide";

	return s;
}

namespace {
struct RewriteRule_greater : public std::binary_function<CLIRewrite::RewriteRule, CLIRewrite::RewriteRule, bool> {

	bool operator()(const CLIRewrite::RewriteRule &e1, const CLIRewrite::RewriteRule &e2) const
	{
		int diff = e1.m_matchType - e2.m_matchType;
		if (diff != 0)
			return diff < 0;
		 diff = e1.m_prefix.length() - e2.m_prefix.length();
		if (diff != 0)
			return diff > 0;
		return e1.m_prefix.compare(e2.m_prefix) > 0;
	}
};

struct SingleIpRule_greater : public std::binary_function<CLIRewrite::SingleIpRule, CLIRewrite::SingleIpRule, bool> {

	bool operator()(const CLIRewrite::SingleIpRule &e1, const CLIRewrite::SingleIpRule &e2) const 
	{
		int diff;
		if (e1.first.IsAny()) {
			if (!e2.first.IsAny())
				return false;
		} else {
			if (e2.first.IsAny())
				return true;
			diff = e1.first.Compare(e2.first);
			if (diff != 0)
				return diff > 0;
		}
		return false;
	}
};

struct DoubleIpRule_greater : public std::binary_function<CLIRewrite::DoubleIpRule, CLIRewrite::DoubleIpRule, bool> {

	bool operator()(const CLIRewrite::DoubleIpRule &e1, const CLIRewrite::DoubleIpRule &e2) const 
	{
		if (e1.first.IsAny()) {
			if (!e2.first.IsAny())
				return false;
		} else {
			if (e2.first.IsAny())
				return true;
			int diff = e1.first.Compare(e2.first);
			if (diff != 0)
				return diff > 0;
		}
		return false;
	}
};
} /* namespace */

CLIRewrite::CLIRewrite()
	: m_processSourceAddress(true), m_removeH323Id(false),
	m_CLIRPolicy(RewriteRule::IgnoreCLIR)
{
	PConfig *cfg = GkConfig();

	unsigned inboundRules = 0, outboundRules = 0;
	SingleIpRules::iterator siprule = m_inboundRules.end();
	DoubleIpRules::iterator diprule = m_outboundRules.end();
	
	const PStringToString kv = cfg->GetAllKeyValues(CLIRewriteSection);
	for (PINDEX i = 0; i < kv.GetSize(); i++) {
		PString key = kv.GetKeyAt(i);

		unsigned j = 0;
		while (ReservedKeys[j] != NULL)
			if(key == ReservedKeys[j++])
				break;
		if (ReservedKeys[j] != NULL)
			continue;

		if (key[0] == '%') {
			const PINDEX sepIndex = key.Find('%', 1);
			if (sepIndex != P_MAX_INDEX)
				key = key.Mid(sepIndex + 1).Trim();
		}

		// on Unix multiple entries for the same key are concatenated
		// to one string and separated by a new line
		PStringArray dataLines = kv.GetDataAt(i).Tokenise("\n", FALSE);
		for (PINDEX d = 0; d < dataLines.GetSize(); d++) {
			PString data = dataLines[d];
			RewriteRules *rules = NULL;
			bool newsiprule = false, newdiprule = false, inbound = false;
			NetworkAddress addr;

			// check the rule type (inbound/outbound)		
			if (key.Find("in:") == 0) {
				const PString ip = key.Mid(3).Trim();
				inbound = true;
			
				if (!(ip == "*" || ip == "any"))
					addr = NetworkAddress(ip);

				// check if the IP is already on the list
				siprule = m_inboundRules.begin();
				while (siprule != m_inboundRules.end()) {
					if (siprule->first.m_address.IsAny() || addr.m_address.IsAny()) {
						if (siprule->first.m_address.IsAny() && addr.m_address.IsAny())
							break;
					} else if (siprule->first == addr)
						break;
					++siprule;
				}

				// append the new IP to the list	
				if (siprule == m_inboundRules.end()) {
					m_inboundRules.resize(m_inboundRules.size() + 1);
					siprule = m_inboundRules.end() - 1;
					siprule->first = addr;
					newsiprule = true;
				}
	
				rules = &(siprule->second);
			} else if (key.Find("out:") == 0) {
				const PString ip = key.Mid(4).Trim();
				inbound = false;

				if (!(ip == "*" || ip == "any"))
					addr = NetworkAddress(ip);
				
				// check if the IP is already on the list
				diprule = m_outboundRules.begin();
				while (diprule != m_outboundRules.end()) {
					if (diprule->first.m_address.IsAny() || addr.m_address.IsAny()) {
						if (diprule->first.m_address.IsAny() && addr.m_address.IsAny())
							break;
					} else if (diprule->first == addr)
						break;
					++diprule;
				}
	
				// append the new IP to the list	
				if (diprule == m_outboundRules.end()) {
					m_outboundRules.resize(m_outboundRules.size() + 1);
					diprule = m_outboundRules.end() - 1;
					diprule->first = addr;
					newdiprule = true;
				}
				
				// separate and extract callee IP address, if present
				// no address means wildcard "match any"
				addr = NetworkAddress();
				
				PINDEX sepIndex = data.Find('=');
				if (sepIndex != P_MAX_INDEX) {
					PString lhs = data.Left(sepIndex).Trim();
					sepIndex = lhs.FindOneOf(" \t");
					if (sepIndex != P_MAX_INDEX) {
						lhs = lhs.Left(sepIndex).Trim();
						data = data.Mid(sepIndex + 1);
						if (!(lhs == "*" || lhs == "any"))
							addr = NetworkAddress(lhs);
					}
				}
				// check if the address is already on the list
				siprule = diprule->second.begin();
				while (siprule != diprule->second.end()) {
					if (siprule->first.m_address.IsAny() || addr.m_address.IsAny()) {
						if (siprule->first.m_address.IsAny() && addr.m_address.IsAny())
							break;
					} else if (siprule->first == addr)
						break;
					siprule++;
				}
	
				// append the new callee address, if not found
				if (siprule == diprule->second.end()) {
					diprule->second.resize(diprule->second.size() + 1);
					siprule = diprule->second.end() - 1;
					siprule->first = addr;
					newsiprule = true;
				}
				
				rules = &(siprule->second);
			} else {
				PTRACE(1, "CLIRW\tUknown CLI rewrite rule: " << key << '=' 
					<< kv.GetDataAt(i)
					);
				continue;
			}

			// process CLIR options
			int manualCLIR = RewriteRule::CLIRPassthrough;
			int CLIRPolicy = RewriteRule::IgnoreCLIR;
			
			data = data.Trim();
			if (data.Find("pi=") == 0) {
				data = data.Mid(3);
				PINDEX sepIndex = data.FindOneOf(" \t");
				if (sepIndex != P_MAX_INDEX) {
					PString lhs = data.Left(sepIndex).Trim();
					data = data.Mid(sepIndex + 1).Trim();
					PStringArray tokens = lhs.Tokenise(",;", FALSE);
					for (PINDEX i = 0; i < tokens.GetSize(); ++i)
						if (tokens[i] *= "allow")
							manualCLIR = RewriteRule::AllowPresentation;
						else if (tokens[i] *= "restrict")
							manualCLIR = RewriteRule::RestrictPresentation;
						else if (tokens[i] *= "forward")
							CLIRPolicy = RewriteRule::ForwardCLIR;
						else if (tokens[i] *= "apply")
							CLIRPolicy = RewriteRule::AlwaysApplyCLIR;
						else if (tokens[i] *= "applyforterminals")
							CLIRPolicy = RewriteRule::ApplyCLIRForTerminals;
						else
							PTRACE(1, "CLIRW\tInvalid CLI rewrite rule syntax: " << key << '=' 
								<< kv.GetDataAt(i) << ", unreconized pi option '" << tokens[i]
								<< "'"
								);
				}
			}
			// process CLI/ANI rewrite rule
			
			const PINDEX sepIndex = data.Find('=');
			if (sepIndex == P_MAX_INDEX) {
				PTRACE(1, "CLIRW\tInvalid CLI rewrite rule syntax: " << key << '=' 
					<< kv.GetDataAt(i)
					);
				if (newsiprule)
					if (inbound)
						m_inboundRules.erase(siprule);
					else
						diprule->second.erase(siprule);
				if (newdiprule)
					m_outboundRules.erase(diprule);
				continue;
			}
	
			// extract match condition
			
			PINDEX keyIndex = 0;
			int matchType = inbound ? RewriteRule::MatchDialedNumber : RewriteRule::MatchDestinationNumber;
			if (!inbound && data.Find("cno:") == 0) {
				matchType = RewriteRule::MatchDestinationNumber;
				keyIndex += 4;
			} else if (data.Find("dno:") == 0) {
				matchType = RewriteRule::MatchDialedNumber;
				keyIndex += 4;
			} else if (data.Find("cli:") == 0) {
				matchType = RewriteRule::MatchCallerNumber;
				keyIndex += 4;
			}
	
			// extract prefix to be matched
			PINDEX keyChars = sepIndex - keyIndex;
			int rewriteType = RewriteRule::PrefixToNumber;
			if (sepIndex > keyIndex)
				if (data[sepIndex-1] == '*') {
					rewriteType = RewriteRule::PrefixToPrefix;
					--keyChars;
				} else if (data[sepIndex-1] == '~') {
					rewriteType = RewriteRule::NumberToNumber;
					--keyChars;
				}
					
			if (rewriteType == RewriteRule::PrefixToPrefix && matchType != RewriteRule::MatchCallerNumber) {
				PTRACE(1, "CLIRW\tInvalid CLI rewrite rule syntax - cannot perform "
					"*= rewrite on non 'cli:' rules: " << key << '=' 
					<< kv.GetDataAt(i)
					);
				if (newsiprule)
					if (inbound)
						m_inboundRules.erase(siprule);
					else
						diprule->second.erase(siprule);
				if (newdiprule)
					m_outboundRules.erase(diprule);
				continue;
			}
			
			std::string prefix((const char*)(data.Mid(keyIndex, keyChars).Trim()));
			if (prefix == "any")
				prefix.erase();

			// check if the rule already exists			
			RewriteRules::iterator rule = rules->begin();
			while (rule != rules->end())
				if (rule->m_matchType == matchType && rule->m_rewriteType == rewriteType
						&& rule->m_prefix.compare(prefix) == 0)
					break;
				else
					++rule;
	
			if (rule == rules->end()) {
				rules->resize(rules->size() + 1);
				rule = rules->end() - 1;
			}
	
			rule->m_screeningType = RewriteRule::NoScreening;
			rule->m_matchType = matchType;
			rule->m_rewriteType = rewriteType;
			rule->m_prefix = prefix;		
			rule->m_manualCLIR = manualCLIR;
			rule->m_CLIRPolicy = CLIRPolicy;
			
			// get RHS of the rewrite rule, multiple targets will be selected
			// in random order
			PStringArray clis = data.Mid(sepIndex + 1).Tokenise(", ", FALSE);
			if (clis.GetSize() < 1)
				rule->m_screeningType = RewriteRule::AlwaysHide;
			else if (clis[0] *= PString("hide"))
				rule->m_screeningType = RewriteRule::AlwaysHide;
			else if (clis[0] *= PString("hidefromterminals"))
				rule->m_screeningType = RewriteRule::HideFromTerminals;
			
			if (rule->m_screeningType == RewriteRule::NoScreening) {
				rule->m_cli.resize(clis.GetSize());
				for (PINDEX j = 0; j < clis.GetSize(); j++)
					rule->m_cli[j] = clis[j];
			} else
				rule->m_cli.clear();

			if (inbound)
				inboundRules++;
			else
				outboundRules++;
		} /* for (d) */
	} /* for (i) */

	// sort rules by IP network mask length	
	std::stable_sort(m_inboundRules.begin(), m_inboundRules.end(), SingleIpRule_greater());
	std::stable_sort(m_outboundRules.begin(), m_outboundRules.end(), DoubleIpRule_greater());

	siprule = m_inboundRules.begin();
	while (siprule != m_inboundRules.end()) {
		std::stable_sort(siprule->second.begin(), siprule->second.end(), RewriteRule_greater());
		++siprule;
	}

	diprule = m_outboundRules.begin();	
	while (diprule != m_outboundRules.end()) {
		std::stable_sort(diprule->second.begin(), diprule->second.end(), SingleIpRule_greater());
		siprule = diprule->second.begin();
		while (siprule != diprule->second.end()) {
			std::stable_sort(siprule->second.begin(), siprule->second.end(), RewriteRule_greater());
			++siprule;
		}
		++diprule;
	}
	
	PTRACE(5, "CLIRW\t" << inboundRules << " inbound rules loaded");
#if PTRACING
	if (PTrace::CanTrace(6)) {
		ostream &strm = PTrace::Begin(6, __FILE__, __LINE__);
		strm << "Inbound CLI rewrite rules:" << endl;
		for (unsigned i = 0; i < m_inboundRules.size(); i++) {
			strm << "\tsrc " << m_inboundRules[i].first.AsString() << ":" << endl;
			for (unsigned j = 0; j < m_inboundRules[i].second.size(); j++)
				strm << "\t\t" << m_inboundRules[i].second[j].AsString() << endl;
		}
		PTrace::End(strm);
	}
#endif

	PTRACE(5, "CLIRW\t" << outboundRules << " outbound rules loaded");
#if PTRACING
	if (PTrace::CanTrace(6)) {
		ostream &strm = PTrace::Begin(6, __FILE__, __LINE__);
		strm << "Outbound CLI rewrite rules:" << endl;
		for (unsigned i = 0; i < m_outboundRules.size(); i++)
			for (unsigned j = 0; j < m_outboundRules[i].second.size(); j++) {
				strm << "\tsrc " << m_outboundRules[i].first.AsString() << " dst " 
					<< m_outboundRules[i].second[j].first.AsString() << ":" << endl;
				for (unsigned k = 0; k < m_outboundRules[i].second[j].second.size(); k++)
					strm << "\t\t" << m_outboundRules[i].second[j].second[k].AsString() << endl;
			}
		PTrace::End(strm);
	}
#endif

	m_processSourceAddress = Toolkit::AsBool(
		cfg->GetString(CLIRewriteSection, ProcessSourceAddress, "1")
		);
	m_removeH323Id = Toolkit::AsBool(
		cfg->GetString(CLIRewriteSection, RemoveH323Id, "1")
		);
	
	const PString clirPolicy = cfg->GetString(CLIRewriteSection, CLIRPolicy,
		PString::Empty()
		);
	if (clirPolicy *= "applyforterminals")
		m_CLIRPolicy = RewriteRule::ApplyCLIRForTerminals;
	else if (clirPolicy *= "apply")
		m_CLIRPolicy = RewriteRule::AlwaysApplyCLIR;
	else if (clirPolicy *= "forward")
		m_CLIRPolicy = RewriteRule::ForwardCLIR;
	else if (clirPolicy.IsEmpty())
		m_CLIRPolicy = RewriteRule::IgnoreCLIR;
	else
		PTRACE(1, "CLIRW\tSyntax error in the config - an unrecognized "
			"CLIRPolicy value: '" << clirPolicy << "'"
			);
}

void CLIRewrite::InRewrite(
	SetupMsg &msg /// Q.931 Setup message to be rewritten
	)
{
	PIPSocket::Address addr;
	msg.GetPeerAddr(addr);

	// find a rule that matches caller's IP	
	SingleIpRules::const_iterator i = m_inboundRules.begin();
	while (i != m_inboundRules.end())
		if (i->first.IsAny() || (addr << i->first))
			break;
		else
			i++;

	if (i == m_inboundRules.end())
		return;

	Rewrite(msg, *i, true, NULL);
}
		
void CLIRewrite::OutRewrite(
	SetupMsg &msg, /// Q.931 Setup message to be rewritten
	SetupAuthData &authData, /// additional data
	const PIPSocket::Address& destAddr /// destination address
	)
{
	PIPSocket::Address addr;
	msg.GetPeerAddr(addr);
	
	// find a rule that matches caller's IP	
	DoubleIpRules::const_iterator diprule = m_outboundRules.begin();
	while (diprule != m_outboundRules.end())
		if (diprule->first.IsAny() || (addr << diprule->first))
			break;
		else
			diprule++;

	if (diprule == m_outboundRules.end())
		return;

	// now find a rule that also matches callee's IP	
	SingleIpRules::const_iterator siprule = diprule->second.begin();
	while (siprule != diprule->second.end())
		if (siprule->first.IsAny() || (destAddr << siprule->first))
			break;
		else
			siprule++;

	if (siprule == diprule->second.end())
		return;

	Rewrite(msg, *siprule, false, &authData);
}

void CLIRewrite::Rewrite(
	SetupMsg &msg, /// Q.931 Setup message to be rewritten
	const SingleIpRule &ipRule,
	bool inbound,
	SetupAuthData *authData
	) const
{
	unsigned plan = Q931::ISDNPlan, type = Q931::UnknownType;
	unsigned presentation = (unsigned)-1, screening = (unsigned)-1;
	PString cli, dno, cno;

	// get ANI/CLI	
	msg.GetQ931().GetCallingPartyNumber(cli, &plan, &type, &presentation, &screening, (unsigned)-1, (unsigned)-1);
	if (cli.IsEmpty() && msg.GetUUIEBody().HasOptionalField(H225_Setup_UUIE::e_sourceAddress))
		cli = GetBestAliasAddressString(msg.GetUUIEBody().m_sourceAddress, true,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	// get dialed number
	if (inbound) {
		msg.GetQ931().GetCalledPartyNumber(dno);
		if (dno.IsEmpty() && msg.GetUUIEBody().HasOptionalField(H225_Setup_UUIE::e_destinationAddress))
			dno = GetBestAliasAddressString(msg.GetUUIEBody().m_destinationAddress, true,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);
	} else if (authData != NULL)
		dno = authData->m_dialedNumber;

	// get destination number
	if (!inbound) {
		msg.GetQ931().GetCalledPartyNumber(cno);
		if (cno.IsEmpty() && msg.GetUUIEBody().HasOptionalField(H225_Setup_UUIE::e_destinationAddress))
			cno = GetBestAliasAddressString(msg.GetUUIEBody().m_destinationAddress, true,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);
	}

	// find ANI/CLI condition/prefix match
	PString newcli;
	RewriteRules::const_iterator rule = ipRule.second.begin();
	while (rule != ipRule.second.end()) {
		if (!rule->m_prefix.empty()) {
			int matchLen = 0;
			const char *number = NULL;
			if (rule->m_matchType == RewriteRule::MatchCallerNumber)
				number = cli;
			else if (rule->m_matchType == RewriteRule::MatchDialedNumber)
				number = dno;
			else if (!inbound && rule->m_matchType == RewriteRule::MatchDestinationNumber)
				number = cno;
			if (number != NULL) {
				matchLen = MatchPrefix(number, rule->m_prefix.c_str());
				if (matchLen > 0 && rule->m_rewriteType == RewriteRule::NumberToNumber
						&& strlen(number) != (unsigned)matchLen)
					matchLen = 0;
			}
			if (matchLen <= 0) {
				++rule;
				continue;
			}
		}
		if (rule->m_screeningType != RewriteRule::NoScreening)
			break;
		if (!rule->m_cli.empty()) {
			// get the new ANI/CLI
			newcli = rule->m_cli[rand() % rule->m_cli.size()].c_str();
			// if this is a number range, choose the new ANI/CLI from the range
			const PINDEX sepIndex = newcli.Find('-');
			if (sepIndex != P_MAX_INDEX) {
				PString lowStr(newcli.Left(sepIndex).Trim());
				PString highStr(newcli.Mid(sepIndex + 1).Trim());
				PUInt64 low = lowStr.AsUnsigned64();
				PUInt64 high = highStr.AsUnsigned64();
				PUInt64 diff = (low < high) ? (high - low) : (low - high);

				int numLeadingZeros1 = 0;
				while (numLeadingZeros1 < lowStr.GetLength()
						&& lowStr[numLeadingZeros1] == '0')
					++numLeadingZeros1;
						
				int numLeadingZeros2 = 0;
				while (numLeadingZeros2 < highStr.GetLength()
						&& highStr[numLeadingZeros2] == '0')
					++numLeadingZeros2;
				
				if (diff >= RAND_MAX)
					diff = PUInt64(rand());
				else
					diff = PUInt64(rand() % ((unsigned)diff + 1));
					
				diff = (low < high) ? (low + diff) : (high + diff);
				newcli = PString(diff);

				if (lowStr.GetLength() == highStr.GetLength() && (numLeadingZeros1 > 0 || numLeadingZeros2 > 0)) {
					while (newcli.GetLength() < highStr.GetLength())
						newcli = PString("0") + newcli;
				}

				PTRACE(5, "CLIRW\t" << (inbound ? "Inbound" : "Outbound")
					<< " CLI range rewrite target is '" << newcli << "' selected by the rule "
					<< rule->AsString()
					);
			}
			if (rule->m_rewriteType == RewriteRule::PrefixToPrefix
					&& rule->m_matchType == RewriteRule::MatchCallerNumber)
				newcli = RewriteString(cli, rule->m_prefix.c_str(), newcli);
			
			PTRACE(5, "CLIRW\t" << (inbound ? "Inbound" : "Outbound")
				<< " CLI rewrite to '" << newcli << "' by the rule " << rule->AsString()
				);
			break;
		}
		++rule;
	}

	bool isTerminal = false;	
	if (authData && authData->m_call) {
		endptr callee = authData->m_call->GetCalledParty();
		if (callee && callee->GetEndpointType().HasOptionalField(H225_EndpointType::e_terminal))
			isTerminal = true;
	}

	if (rule->m_manualCLIR == RewriteRule::RestrictPresentation) {
		presentation = 1;
		PTRACE(5, "CLIRW\tCLIR forced to 'restricted' by the " << (inbound ? "inbound" : "outbound")
			<< " rule " << rule->AsString()
			);
	} else if (rule->m_manualCLIR == RewriteRule::AllowPresentation) {
		presentation = 0;
		PTRACE(5, "CLIRW\tCLIR forced to 'allowed' by the " << (inbound ? "inbound" : "outbound")
			<< " rule " << rule->AsString()
			);
	}
	
	int clirPolicy = rule->m_CLIRPolicy;
	if (clirPolicy == RewriteRule::IgnoreCLIR)
		clirPolicy = m_CLIRPolicy;

	int screeningType = rule->m_screeningType;
	if (rule->m_CLIRPolicy == RewriteRule::AlwaysApplyCLIR
			|| (rule->m_CLIRPolicy == RewriteRule::ApplyCLIRForTerminals
				&& isTerminal)) {
		if (presentation == 1)
			screeningType = RewriteRule::AlwaysHide;
		else if (presentation == (unsigned)-1)
			if (msg.GetUUIEBody().HasOptionalField(H225_Setup_UUIE::e_presentationIndicator)
					&& msg.GetUUIEBody().m_presentationIndicator.GetTag() == H225_PresentationIndicator::e_presentationRestricted)
			screeningType = RewriteRule::AlwaysHide;
	}
	
	if (screeningType == RewriteRule::AlwaysHide) {
		presentation = 1;
		screening = 3;
		newcli = "";
		plan = Q931::UnknownPlan;
		type = Q931::UnknownType;
		if (msg.GetQ931().HasIE((Q931::InformationElementCodes)0x6d)) // calling party subaddress IE
			msg.GetQ931().RemoveIE((Q931::InformationElementCodes)0x6d);
		if (msg.GetQ931().HasIE(Q931::DisplayIE))
			msg.GetQ931().RemoveIE(Q931::DisplayIE);
		PTRACE(5, "CLIRW\tCLI hidden by the " << (inbound ? "inbound" : "outbound")
			<< " rule " << rule->AsString()
			);
	} else if (screeningType == RewriteRule::HideFromTerminals) {
		presentation = 1;
		msg.GetQ931().GetCallingPartyNumber(newcli);
		if (isTerminal) {
			isTerminal = true;
			screening = 3;
			newcli = "";
			plan = Q931::UnknownPlan;
			type = Q931::UnknownType;
			if (msg.GetQ931().HasIE((Q931::InformationElementCodes)0x6d)) // calling party subaddress IE
				msg.GetQ931().RemoveIE((Q931::InformationElementCodes)0x6d);
			if (msg.GetQ931().HasIE(Q931::DisplayIE))
				msg.GetQ931().RemoveIE(Q931::DisplayIE);
			PTRACE(5, "CLIRW\tCLI hidden by the " << (inbound ? "inbound" : "outbound")
				<< " rule " << rule->AsString()
				);
		}
	} else if (newcli.IsEmpty())
		return;

	if (presentation != (unsigned)-1 && screening == (unsigned)-1)
		screening = 0;

	msg.GetQ931().SetCallingPartyNumber(newcli, plan, type, presentation, screening);
	msg.SetChanged();
	if (m_processSourceAddress && msg.GetUUIEBody().HasOptionalField(H225_Setup_UUIE::e_sourceAddress)) {
		H225_ArrayOf_AliasAddress &sourceAddress = msg.GetUUIEBody().m_sourceAddress;
		PINDEX aliasIndex = 0;
		if (m_removeH323Id)
			sourceAddress.SetSize(1);
		else {
			while (aliasIndex < sourceAddress.GetSize())
				if (sourceAddress[aliasIndex].GetTag() == H225_AliasAddress::e_h323_ID
						|| sourceAddress[aliasIndex].GetTag() == H225_AliasAddress::e_url_ID
						|| sourceAddress[aliasIndex].GetTag() == H225_AliasAddress::e_email_ID) {
					aliasIndex++;
					continue;
				} else
					sourceAddress.RemoveAt(aliasIndex);
			sourceAddress.SetSize(sourceAddress.GetSize() + 1);
		}
		
		if (presentation != (unsigned)-1) {
			msg.GetUUIEBody().IncludeOptionalField(H225_Setup_UUIE::e_presentationIndicator);
			msg.GetUUIEBody().m_presentationIndicator.SetTag(presentation);
			msg.GetUUIEBody().IncludeOptionalField(H225_Setup_UUIE::e_screeningIndicator);
			msg.GetUUIEBody().m_screeningIndicator.SetValue(screening);
		}
		
		if (screeningType == RewriteRule::AlwaysHide) {
			msg.GetUUIEBody().IncludeOptionalField(H225_Setup_UUIE::e_presentationIndicator);
			msg.GetUUIEBody().m_presentationIndicator.SetTag(H225_PresentationIndicator::e_presentationRestricted);
			msg.GetUUIEBody().IncludeOptionalField(H225_Setup_UUIE::e_screeningIndicator);
			msg.GetUUIEBody().m_screeningIndicator.SetValue(H225_ScreeningIndicator::e_networkProvided);
			msg.GetUUIEBody().RemoveOptionalField(H225_Setup_UUIE::e_sourceAddress);
		} else if (screeningType == RewriteRule::HideFromTerminals) {
			msg.GetUUIEBody().IncludeOptionalField(H225_Setup_UUIE::e_presentationIndicator);
			msg.GetUUIEBody().m_presentationIndicator.SetTag(H225_PresentationIndicator::e_presentationRestricted);
			if (isTerminal && !newcli) {
				msg.GetUUIEBody().IncludeOptionalField(H225_Setup_UUIE::e_screeningIndicator);
				msg.GetUUIEBody().m_screeningIndicator.SetValue(H225_ScreeningIndicator::e_networkProvided);
				H323SetAliasAddress(newcli, sourceAddress[sourceAddress.GetSize() - 1]);
			} else
				msg.GetUUIEBody().RemoveOptionalField(H225_Setup_UUIE::e_sourceAddress);
		} else
			H323SetAliasAddress(newcli, sourceAddress[sourceAddress.GetSize() - 1]);
		msg.SetUUIEChanged();
	}
}


syntax highlighted by Code2HTML, v. 0.9.1