#include "stdafx.h"
#include "resource.h"
#include "router.h"
#include "routercfg.h"
#include "ObjectFactory.h"
#include "reghelpers.h"
#include "WanRouterReg.h"
#include "utils.h"

#ifdef NT5
#include "DriverServices.h"
#endif

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static const TCHAR c_WildVarLength = _T('*');
static const TCHAR c_WildOneLength = _T('?');

IMPLEMENT_DYNAMIC(CX25Router, CConfigObject)

CX25Router::CX25Router(HKEY const hKeyRoot, CString const strRegPath, CConfigObject const * const pParent) : CConfigObject(hKeyRoot,strRegPath,pParent)
{
#ifdef NT5
	m_strInstance.Empty();
#endif
	m_nNumRasPorts = 0;
	try
	{
		SetType(CConfigObject::ROUTER);
		// Get protocol parameters
		AffirmThrow(GetParameters());

		// solve the identity crisis
		SetIdentity(DXB_REGVAL_ROUTER_TYPE_WANX);
		// Set our Display Name
		CString szDisplayName;
		AffirmThrow(szDisplayName.LoadString(IDS_X25RouteTableName));
		SetDisplayName(szDisplayName);
		// define abilities
		SetAbility(CConfigObject::DOPROPERTIES);

		HANDLE hProcess = GetCurrentProcess();
		AffirmThrow(DuplicateHandle(hProcess,(HANDLE)hKeyRoot,hProcess,(LPHANDLE)&m_hKeyRoot,0,
									TRUE,DUPLICATE_SAME_ACCESS));
	}
	catch(CAffirmException)
	{
	}
}


CX25Router::~CX25Router()
{
	/*
	// if completely empty, remove our registry keys
	if (CountRoutes() == 0)
		RemoveParameters(TRUE);
	*/
}

// All instances of the Router object write to a common key in the registry. 
// This is done to reflect the fact that there is one and only one instance
// of the Router driver for multiple instances of Dxb running the protocol.
// This also allows for easy updating of routing aliases as X.25 protocols
// are removed or added on multiple adapters.
BOOL CX25Router::SetParameters()
{
	BOOL bSuccess = FALSE;
	try
	{
		
		AffirmThrow(CConfigObject::SetParameters());
		CheckConfig();
		CStringArray Alias, Device, Snid, NumVC;

		POSITION Pos = m_AliasMap.GetStartPosition();

		while (Pos != NULL) 
		{
			CString Key;
			CAlias Value;
			m_AliasMap.GetNextAssoc(Pos, Key, Value);

			Alias.Add(Key);
			Device.Add(Value.m_Device);
			Snid.Add(Value.m_SNID);

			TCHAR buf[16];
			NumVC.Add(_itoa(Value.m_NumPorts, buf, 10));
		}
#ifdef NT5
#pragma message("Need to add CCriticalSection object to keep registry in sync")
// ToDo: Add CriticalSection object to keep from multiple instances creating
// more than one instance of the router. Also need to take care of scenario
// where two instances are started together, both alter the alias table
// and then both update the data
		HKEY hOldKey;
		BOOL bRestartRouter = FALSE;
		if (m_strInstance.IsEmpty())
		{
			CString strInstance;
			AffirmThrow(CreateService(DXB_HDWID_SVC,NULL,NULL,strInstance));
			bRestartRouter = TRUE;  // need to know for later use
			m_strInstance = strInstance;
			// write a signature under Software key
			HKEY hkey;
			AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_CONFIG_DATA,&hkey));
			RegWriteStrValue(hkey,DXB_REGVAL_ROUTER_INSTANCE,strInstance);
			RegCloseKey(hkey);

		}
		else
		{
			hOldKey = m_hKey;     // save the old key
			CString strRegPath(DXB_REGCLASS_VALUE);
			strRegPath += m_strInstance;
			AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,strRegPath,&m_hKey));
		}

#endif

		RegWriteMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_ALIAS, Alias);
		RegWriteMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_DEVICE, Device);
		RegWriteMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_SNID, Snid);
		RegWriteMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_NUMVC, NumVC);

		int TotalRAS = (int)GetTotalRASPorts();
#ifdef NT5
#pragma message ("TotalRAS Ports being set to 255")
		// because of the way NT5.0 works. 
		TotalRAS = 255;
#endif
		RegWriteIntValue(m_hKey,WAN_ROUTE_REGVAL_RASPORTS, TotalRAS);

		CStringArray RouteAlias, RouteAddress;

		for (short i = 0; i < m_RoutingTable.GetSize(); i++) {
			RouteAddress.Add(m_RoutingTable[i].m_Address);
			RouteAlias.Add(m_RoutingTable[i].m_Alias);
			}

		RegWriteMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_ROUTEALIAS, RouteAlias);
		RegWriteMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_ROUTEADDR, RouteAddress);
		RegWriteIntValue(m_hKey,WAN_ROUTE_REGVAL_MAXSENDWINDOW,WAN_ROUTE_REGVAL_MAXSENDWINDOW_DEFAULT);

#ifdef NT5
		m_hKey = hOldKey;		// restore
		if (bRestartRouter)
		{
			RestartService(m_strInstance);
		}
#endif

		bSuccess = TRUE;
	}
	catch(CAffirmException ae)
	{
	}
	return bSuccess;
}

BOOL CX25Router::GetParameters()
{
	BOOL bSuccess = FALSE;
	try
	{
#ifdef NT5
		HKEY hkey,hOldKey;
		AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_CONFIG_DATA,&hkey));
		RegReadStrValue(hkey,DXB_REGVAL_ROUTER_INSTANCE,m_strInstance);
		RegCloseKey(hkey);

		hOldKey = m_hKey;
		// if the router has been installed.
		if (!m_strInstance.IsEmpty())
		{
			CString strRegPath(DXB_REGCLASS_VALUE);
			strRegPath += m_strInstance;
			AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,strRegPath,&m_hKey));
		}
		// in case the Router service has not been installed,
		// the read will simply return empty data. In this case SetParameters
		// if called, will deal with installing the router.
#endif
		CStringArray Alias, Device, Snid, NumVC;
		RegReadMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_ALIAS,Alias);
		RegReadMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_DEVICE,Device);
		RegReadMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_SNID,Snid);
		RegReadMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_NUMVC,NumVC);


		for (short i = 0; i < Alias.GetSize(); i++)
			AddAlias(Device[i], Snid[i], Alias[i], atoi(NumVC[i]));
	
		CStringArray RouteAlias, RouteAddress;
	
		RegReadMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_ROUTEALIAS,RouteAlias);
		RegReadMultiStrValue(m_hKey,WAN_ROUTE_REGVAL_ROUTEADDR, RouteAddress);

		for (i = 0; i < RouteAlias.GetSize(); i++)
			VERIFY(AddRoute(RouteAddress[i], RouteAlias[i]));
#ifdef NT5
		m_hKey = hOldKey;
#endif
		bSuccess = TRUE;
	}
	catch(CAffirmException ae)
	{
	}
	return bSuccess;
}

BOOL CX25Router::RemoveParameters(BOOL fRemoveKey)
{

    BOOL fSuccess = FALSE;

    try
	{
#ifdef NT5
		// only remove if router is installed and the alias map is empty
		if (!m_strInstance.IsEmpty() && IsAliasMapEmpty())
		{
			DeleteService(m_strInstance);
			m_strInstance.Empty();
			// wipe out the signature under the software key
			HKEY hkey;
			AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_CONFIG_DATA,&hkey));
			RegWriteStrValue(hkey,DXB_REGVAL_ROUTER_INSTANCE,m_strInstance);
			RegCloseKey(hkey);
		}
#endif
		AffirmThrow(CConfigObject::RemoveParameters(TRUE));
		fSuccess = TRUE;
	}
	catch (CAffirmException ae)
	{
	}

	return fSuccess;
}

BOOL CX25Router::DoProperties()
{
	BOOL bSuccess = FALSE;
	try
	{
		CRoutingTable table;
		CAliasMap map;
		// backup our current data set
		table.Copy(m_RoutingTable);
		CopyMap(m_AliasMap,map);
		CPropertySheet sheet(IDS_X25RouteTableName);
		CX25RouterPage routepage(this);
		sheet.AddPage(&routepage);

		if (sheet.DoModal() == IDOK)
		{
			bSuccess = TRUE;
		}
		else
		{
			// restore our working data set
			m_RoutingTable.RemoveAll();
			m_RoutingTable.Copy(table);
			CopyMap(map,m_AliasMap);
		}
	}
	catch(CAffirmException ae)
	{
	}
	return bSuccess;
}

void CX25Router::AddAlias(const CString& Device, const CString& Snid, const CString& Alias, int NumPorts)
{
	CAlias Entry;

	if (!m_AliasMap.Lookup(Alias, Entry)) {
		Entry.m_Device = Device;
		Entry.m_SNID = Snid;

		if (NumPorts > -1)
			Entry.m_NumPorts = NumPorts;

		m_AliasMap.SetAt(Alias, Entry);
		}
}

void CX25Router::ChangeAlias(const CString& oldAlias, const CString& newAlias)
{
	for (short i = 0; i < m_RoutingTable.GetSize(); i++)
		if (m_RoutingTable[i].m_Alias == oldAlias)
			m_RoutingTable[i].m_Alias = newAlias;
			
	CAlias Entry;

	if (m_AliasMap.Lookup(oldAlias, Entry)) {
		m_AliasMap.RemoveKey(oldAlias);
		m_AliasMap.SetAt(newAlias, Entry);
		}
}

void CX25Router::FindByAlias(const CString& Alias, CString& Device, CString& Snid) const
{
	CAlias Entry;

	if (m_AliasMap.Lookup(Alias, Entry)) {	
		Device = Entry.m_Device;
		Snid = Entry.m_SNID;
		}
}

BOOL CX25Router::FindByAlias(const CString& Alias) const
{
	CAlias Entry;

	return m_AliasMap.Lookup(Alias, Entry);
}

BOOL CX25Router::SetAliasNumPorts(const CString& Alias, DWORD NumPorts)
{
	CAlias Entry;
	
	BOOL found = m_AliasMap.Lookup(Alias, Entry);
	if (found) {
		Entry.m_NumPorts = NumPorts;
		m_AliasMap.SetAt(Alias, Entry);
		if (NumPorts == 0)
			RemoveRoutingAlias(Entry.m_Device, Entry.m_SNID, FALSE);
		}

	return found;
}

void CX25Router::RemoveRoutingAlias(const CString& Device, const CString& Snid, BOOL RemoveEntry)
{
	CString Alias = FindByRealName(Device, Snid);

	for (short i = short(m_RoutingTable.GetUpperBound()); i >= 0; i--)
		if (m_RoutingTable[i].m_Alias == Alias)
			m_RoutingTable.RemoveAt(i);

	if (RemoveEntry)
		m_AliasMap.RemoveKey(Alias);
}

CString CX25Router::FindByRealName(const CString& Device, const CString& Snid) const
{
	CString Alias;
	CAlias Value;
	POSITION pos = m_AliasMap.GetStartPosition();

	while (pos) {
		CString AliasLookup;

		m_AliasMap.GetNextAssoc(pos, AliasLookup, Value);

		if (Value.m_Device == Device && Value.m_SNID == Snid) {
			Alias = AliasLookup;
			break;
			}
		}

	return Alias;
}
BOOL CX25Router::AddRoute(const CString& Address, const CString& Alias)
{
// do not add route if address is already in list
	for (short i = 0; i < m_RoutingTable.GetSize(); i++)
		if (m_RoutingTable[i].m_Address == Address)
			return FALSE;

	CRoute route;
	
	route.m_Address = Address;
	route.m_Alias = Alias;

	m_RoutingTable.Add(route);

	return TRUE;
}

void CX25Router::RemoveRoute(int index)
{
	m_RoutingTable.RemoveAt(index);
}

/*
order in the routing table is, of course, signifcant. The user may need to move
routes up or down such that no routes are hidden(see below)
*/
BOOL CX25Router::MoveUp(int index)
{
	if (index <= 0 || index > m_RoutingTable.GetUpperBound())
		return FALSE;

	CRoute temp = m_RoutingTable[index - 1];
	m_RoutingTable.SetAt(index - 1, m_RoutingTable[index]);
	m_RoutingTable.SetAt(index, temp);

	return TRUE;
}

BOOL CX25Router::MoveDown(int index)
{
	if (index < 0 || index >= m_RoutingTable.GetUpperBound())
		return FALSE;

	CRoute temp = m_RoutingTable[index + 1];
	m_RoutingTable.SetAt(index + 1, m_RoutingTable[index]);
	m_RoutingTable.SetAt(index, temp);

	return TRUE;
}

BOOL Hidden(const CString& wild, const CString& hide)
{
	ASSERT(wild != hide);

	int star = wild.Find(c_WildVarLength);
	int pound = wild.Find(c_WildOneLength);
	int wildlen = wild.GetLength();
	
	if (star > -1 || pound > -1) {
		int length = hide.GetLength();

		if (length >= wildlen - 1 && (star > -1 || length == wildlen))
			if (strncmp(wild, hide, wildlen - 1) == 0)
				return TRUE;
		}

	return FALSE;
}

int RouteCmp(const CRoute& c1, const CRoute& c2)
{
	const CString& strOne = c1.m_Address;
	const CString& strTwo = c2.m_Address;

	if (Hidden(strOne, strTwo))
		return 1;
	else if (Hidden(strTwo, strOne))
		return -1;
	else
		return 0;
}

/*
When the user adds routes with wildcards they may leave them in an order where
a less wildcarded address is below a more wildcarded address thus hidding the less
wildcarded address, i.e. the route will never be seen and used. This routine will
ask the user if they want to move hidden address up so that no address is hidden.

Believe it or not a simple bubble sort is just what the doctor ordered. Bubble sort
will sort and only move up routes that need to be moved and leave the list as
close to the original order as possible.
*/
int CX25Router::FindHiddenRoutes()
{
	BOOL warned = FALSE;

	for (short i = 0; i < m_RoutingTable.GetSize() - 1; i++)
		for (short j = i + 1; j < m_RoutingTable.GetSize(); j++)
			if (RouteCmp(m_RoutingTable[i], m_RoutingTable[j]) == 1) {

				if (!warned) {
					int result = AfxMessageBox(IDS_RouteHidden, MB_YESNOCANCEL);

					if (result == IDYES)
						warned = TRUE;
					else
						return result;
					}

				CRoute Entry = m_RoutingTable[j];

				m_RoutingTable.RemoveAt(j);
				m_RoutingTable.InsertAt(i, Entry);
				i = -1;
				break;
				}
	
	return IDOK;	
}

/*
checks for a valid configuration. In this case if the user has an alias with 
circuits and the routing table is empty we ask the user if they want to add
a wildcarded route. Without this route added no outbound calls can be routed
anywhere and tus will not work
*/
BOOL CX25Router::CheckConfig()
{
	if (m_AliasMap.GetCount() == 1 && m_RoutingTable.GetSize() == 0) {
		POSITION pos = m_AliasMap.GetStartPosition();
		CAlias Entry;
		CString Alias;

		m_AliasMap.GetNextAssoc(pos, Alias, Entry);

		if (Entry.m_NumPorts > 0)
			VERIFY(AddRoute(c_WildVarLength, Alias));
		}

	return TRUE;
}

DWORD CX25Router::GetTotalRASPorts() const
{
	DWORD totalPorts = 0;
	CString Alias;
	CAlias Value;
	POSITION pos = m_AliasMap.GetStartPosition();

	while (pos) {
		m_AliasMap.GetNextAssoc(pos, Alias, Value);
		totalPorts += Value.m_NumPorts;
		}

	return totalPorts;
}


// valid address are valid X.25 address with an optional ? or * at the end
BOOL CX25Router::ValidateRouteAddress(const CString& Address)
{
	if (Address.GetLength() == 0 || Address.GetLength() > 17)
		return FALSE;

	for (int i = 0; i < Address.GetLength() - 1; i++)
		if (!isdigit(Address[i]))
			return FALSE;

	TCHAR Last = Address[Address.GetLength() - 1];

	if (!isdigit(Last) && Last != c_WildVarLength && Last != c_WildOneLength)
		return FALSE;
	
	return TRUE;
}



BOOL CX25Router::AddRoute(int SelCount, int *SelItems)
{
	BOOL result = FALSE;

	CRouteDlg dlg;
	dlg.m_AliasMap = &m_AliasMap;

	while (TRUE)
	{
		if (dlg.DoModal() == IDOK)
		{
			if (ValidateRouteAddress(dlg.m_Address))
			{
				result = AddRoute(dlg.m_Address, dlg.m_Alias); 

				if (!result)
				{
					CString Error;

					Error.Format(IDS_DuplicateRoute, dlg.m_Address);
					AfxMessageBox(Error);
				}

				break;
			}
		}
		else
		{
			result = FALSE;
			break;
		}

		AfxMessageBox(IDS_BadRouteAddress);
	}

	return result;
}

BOOL CX25Router::ChangeRoute(int SelCount, int *SelItems)
{
	BOOL result = FALSE;

	if (SelCount == 1) {
		CRoute *route = GetRoute(SelItems[0]);
		CRouteDlg dlg;
				
		dlg.m_AliasMap = &m_AliasMap;
		dlg.m_Address = route->m_Address;
		dlg.m_Alias = route->m_Alias;
		while (TRUE) {
			if (dlg.DoModal() == IDOK) {
				if (ValidateRouteAddress(dlg.m_Address)) {
					route->m_Address = dlg.m_Address;
					route->m_Alias = dlg.m_Alias;
					result = TRUE;
					break;
					}
				}
			else {
				result = FALSE;
				break;
				}
			AfxMessageBox(IDS_BadRouteAddress);
			}
		}

	return result;
}

BOOL CX25Router::RemoveRoute(int SelCount, int *SelItems)
{
	for (SelCount--; SelCount >= 0; SelCount--)
		RemoveRoute(SelItems[SelCount]);

	return TRUE;
}

// later replace with overridden = operator, this function simply copies
// entries of one map into another.
void CX25Router::CopyMap(const CAliasMap& src,CAliasMap& dst)
{
	if (src.IsEmpty())
		return;
	dst.RemoveAll();
	POSITION pos = src.GetStartPosition();
	do
	{
		CString key;
		CAlias Value;
		src.GetNextAssoc(pos,key,Value);
		dst.SetAt(key,Value);
	}while(pos);

}


