#include "stdafx.h"
#include "CPing.h"
#include "aplSocketTransport.h"

extern CaplLog* g_log;
#define LogMessageFC( text) if(g_log!=0){g_log->LogMessage(text,true,true,true,true,_T(""),_T("")); }

CRITICAL_SECTION CS_PROTECT_IP_CASH;

CPing::CPing()
{
	bValid = FALSE;
	WSADATA wsaData;			// WSADATA
	int nRet;					// General use return code

	// Dynamically load the ICMP.DLL
	hndlIcmp = LoadLibrary(_T("ICMP.DLL"));
	if (hndlIcmp == NULL)
	{
		//::MessageBox(NULL, _T("Could not load ICMP.DLL"), _T("Error:"), MB_OK);
		TRACE0("Could not load ICMP.DLL");
		return;
	}
	// Retrieve ICMP function pointers
	pIcmpCreateFile  = (HANDLE (WINAPI *)(void))
		GetProcAddress((HMODULE)hndlIcmp,"IcmpCreateFile");// функция принимает однобайтовую строку!
	pIcmpCloseHandle = (BOOL (WINAPI *)(HANDLE))
		GetProcAddress((HMODULE)hndlIcmp,"IcmpCloseHandle");// функция принимает однобайтовую строку!
	pIcmpSendEcho = (DWORD (WINAPI *)
		(HANDLE,DWORD,LPVOID,WORD,PIPINFO,LPVOID,DWORD,DWORD))
		GetProcAddress((HMODULE)hndlIcmp,"IcmpSendEcho");// функция принимает однобайтовую строку!
	// Check all the function pointers
	if (pIcmpCreateFile == NULL		|| 
		pIcmpCloseHandle == NULL	||
		pIcmpSendEcho == NULL)
	{
		//::MessageBox(NULL, _T("Error loading ICMP.DLL"), _T("Error:"), MB_OK);
		TRACE0("Error loading function from ICMP.DLL");
		FreeLibrary((HMODULE)hndlIcmp);
		return;
	}

	// Init WinSock
	nRet = WSAStartup(0x0101, &wsaData );
    if (nRet)
    {
		//::MessageBox(NULL, _T("WSAStartup() error:"), _T("Error:"), MB_OK);
		TRACE0("WSAStartup() error:");
        WSACleanup();
		FreeLibrary((HMODULE)hndlIcmp);
        return;
    }
    // Check WinSock version
    if (0x0101 != wsaData.wVersion)
    {
		//::MessageBox(NULL, _T("No WinSock version 1.1 support found"), _T("Error:"), MB_OK);
		TRACE0("No WinSock version 1.1 support found");
        WSACleanup();
		FreeLibrary((HMODULE)hndlIcmp);
        return;
    }
	bValid = TRUE;
}

CPing::~CPing()
{
    WSACleanup();
	FreeLibrary((HMODULE)hndlIcmp);
}

#define SIZE_TEST_DATA 100

BOOL CPing::Ping(LPCTSTR strHost)
{
	struct in_addr iaDest;		// Internet address structure
    LPHOSTENT pHost;			// Pointer to host entry structure
	DWORD dwtmp;
	DWORD *dwAddress=&dwtmp;			// IP Address
	//IPINFO ipInfo;				// IP Options structure
	//ICMPECHO icmpEcho;			// ICMP Echo reply buffer
	PICMPECHO picmpEcho;

	HANDLE hndlFile;			// Handle for IcmpCreateFile()
	BYTE test_data[SIZE_TEST_DATA];
	WORD size_test_data=SIZE_TEST_DATA;
	memset(test_data,0xF0,size_test_data);
	u_long Status;
	DWORD dwStatus;

    if(!bValid)
	{
		return FALSE;
	}

	// сокетные функции требуют char*
	const char* c_strHost;
#ifdef _UNICODE
	CaplStringAdapter str_ad(strHost);
	c_strHost = (const char*)str_ad;
#else // #ifdef _UNICODE
	c_strHost = strHost;
#endif // #ifdef _UNICODE



	// Lookup destination
    // Use inet_addr() to determine if we're dealing with a name
    // or an address
    iaDest.s_addr = inet_addr(c_strHost);
    if (iaDest.s_addr == INADDR_NONE){
        pHost = gethostbyname(c_strHost);
		if (pHost == NULL){
			return FALSE;
		}
		// Copy the IP address
		dwAddress = (DWORD *)(*pHost->h_addr_list);
    
	}else{
		// Этот вызов пытается преобразовать адрес обратно в имя. Если такого компа нет - тормозит жутко
        // pHost = gethostbyaddr((const char *)&iaDest, sizeof(struct in_addr), AF_INET);
		*dwAddress=iaDest.S_un.S_addr;
	}

	// Get an ICMP echo request handle        
	hndlFile = pIcmpCreateFile();

	
	picmpEcho = (PICMPECHO)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,sizeof(ICMPECHO) + size_test_data);
	picmpEcho->Status = 0;
	picmpEcho->DataSize = size_test_data;
	picmpEcho->pData = test_data; //-V507

	// Set some reasonable default values
	picmpEcho->ipInfo.Ttl = 255;
	picmpEcho->ipInfo.Tos = 0;
	picmpEcho->ipInfo.IPFlags = 0;
	picmpEcho->ipInfo.OptSize = 0;
	picmpEcho->ipInfo.Options = NULL;

	// Reqest an ICMP echo
	dwStatus=pIcmpSendEcho(
		hndlFile,		// Handle from IcmpCreateFile()
		*dwAddress,		// Destination IP address
		test_data,			// Pointer to buffer to send
		size_test_data,				// Size of buffer in BYTEs
		0,//&ipInfo,		// Request options
		picmpEcho,		// Reply buffer
		sizeof(struct tagICMPECHO)+size_test_data,
		1000);			// Time to wait in milliseconds

	iaDest.s_addr = picmpEcho->Source;
	Status = picmpEcho->Status;


	// Close the echo request file handle
	GlobalFree(picmpEcho);
	pIcmpCloseHandle(hndlFile);

	if (dwStatus==0)
	{
		return FALSE;
	}
	 

	return TRUE;
}

// Время в часах нахожения адреса в кеше, после которого его надо повторно определять
#define CASHED_IP_TIMELIFE_REQ 12
// Время в часах после последнего запроса адреса в кеше, после которого его можно удалять
#define CASHED_IP_TIMELIFE_DEL 24

struct SCashedAddr
{
	SCashedAddr(DWORD _ip_num)
	{
		ip_num = _ip_num; host_name= Ip2String(ip_num); is_tested = false;
		odt_def=COleDateTime::GetCurrentTime()-COleDateTimeSpan(0, CASHED_IP_TIMELIFE_REQ+1,0,0); // т.е. его надо сразу запускать на тестирование
		odt_req=COleDateTime::GetCurrentTime(); // время последнего запрроса
	}

	DWORD ip_num;
	CString host_name;
	COleDateTime odt_def; // время последнего определения имени
	COleDateTime odt_req; // время последнего обращения за именем
	bool is_tested;		// в очереди на повторное определение имени
};

// Кеш IP-адресов
class CMapIp2Cash :public std::map<DWORD, SCashedAddr*>
{
public:
	CMapIp2Cash(){}
	~CMapIp2Cash()
	{
		CaplEnterCriticalSection ecs(&CS_PROTECT_IP_CASH);
		std::map<DWORD, SCashedAddr*>::iterator it;
		for(it=begin(); it!=end(); it++)
		{
			if(it->second != 0)
				delete it->second;
		}
	}
};

CMapIp2Cash mapBackCashedAddr;
// Список запрашиваемых
CUIntArray requestedIPs;
// Нить запроса к DNS и слежения за просроченными
CWinThread* threadRequesterIPCashe = 0;


// Функция запускается в отдельной нити, выполняет обратный DNS запрос и при успехе заносит в кеш пару IP-имя хоста
// Параллельно очищает кеш от ненужных записей
UINT RequesterIp2Name(LPVOID /*param*/)
{
	CaplStackLogger stack_logger(_T(__FUNCTION__));

	// Запрос значений из DNS
	while(true)
	{
		// Извлечение адреса из очереди п роверка, что для него есть место в кеше
		CaplEnterCriticalSection ecs(&CS_PROTECT_IP_CASH);
		if(requestedIPs.GetSize() == 0)
		{
			break;
		}
		DWORD ip_num = requestedIPs[0];
		requestedIPs.RemoveAt(0);

		std::map<DWORD, SCashedAddr*>::iterator it;
		it = mapBackCashedAddr.find((DWORD)ip_num);
		if(it == mapBackCashedAddr.end() || it->second==0)
		{
			continue;
		}
		ip_num = it->second->ip_num;
		ecs.Leave();

		CString host_name(_T(""));
		// Собственно запрос
		{
			struct in_addr iaDest;		// Internet address structure
			LPHOSTENT pHost;			// Pointer to host entry structure
			DWORD dwtmp;
			DWORD *dwAddress=&dwtmp;			// IP Address
			//	IPINFO ipInfo;				// IP Options structure
			//	ICMPECHO icmpEcho;			// ICMP Echo reply buffer
			//	HANDLE hndlFile;			// Handle for IcmpCreateFile()

			CString ip_str(Ip2String(ip_num, true));

			// Lookup destination
			// Use inet_addr() to determine if we're dealing with a name
			// or an address
#ifdef _UNICODE
			CaplStringAdapter str_ad(ip_str);
			const char* ip_str_c = (const char*)str_ad;
#else // #ifdef _UNICODE
			const char* ip_str_c = ip_str;
#endif // #ifdef _UNICODE
			iaDest.s_addr = inet_addr(ip_str_c);
			if (iaDest.s_addr != INADDR_NONE)
			{
				// Этот вызов пытается преобразовать адрес обратно в имя. Если такого компа нет - тормозит жутко
				pHost = gethostbyaddr((const char *)&iaDest, sizeof(struct in_addr), AF_INET);//UUUU
			}
			else
			{
				host_name = ip_str;
			}
			if(pHost==0)
			{
				host_name = ip_str;
			}
			else
			{
#ifdef _UNICODE
				CaplStringAdapter str_ad(pHost->h_name);
				host_name = (LPCTSTR)str_ad;
#else // #ifdef _UNICODE
				host_name = pHost->h_name;
#endif // #ifdef _UNICODE
			}
		}

		// Вставка результатов запроса в кеш
		ecs.Set(&CS_PROTECT_IP_CASH);
		it = mapBackCashedAddr.find((DWORD)ip_num);
		if(it == mapBackCashedAddr.end() || it->second==0)
		{
			continue;
		}
		it->second->host_name = host_name;
		// Время определения размажем случайным порядком
		it->second->odt_def = COleDateTime::GetCurrentTime()+COleDateTimeSpan(0,0,
			(int)((double)rand() / (RAND_MAX + 1) * 30), (int)((double)rand() / (RAND_MAX + 1) * 60));

		it->second->is_tested = false;
	}

	// Очистка кеша от старых записей
#ifdef _DEBUG
	COleDateTimeSpan odts = COleDateTimeSpan(0,0,1,0);
	//	COleDateTimeSpan odts = COleDateTimeSpan(0,CASHED_IP_TIMELIFE_DEL,0,0);
#else
	COleDateTimeSpan odts = COleDateTimeSpan(0,CASHED_IP_TIMELIFE_DEL,0,0);
#endif
	COleDateTime odt_older = COleDateTime::GetCurrentTime()-odts;

	CaplEnterCriticalSection ecs(&CS_PROTECT_IP_CASH);
	CUIntArray ip4del;
	std::map<DWORD, SCashedAddr*>::iterator it;
	for(it=mapBackCashedAddr.begin(); it!=mapBackCashedAddr.end(); it++)
	{
		if(it->second != 0 && it->second->odt_req > odt_older)
			continue;
		ip4del.Add(it->first);
	}
	for(int i=0; i<ip4del.GetSize(); i++)
	{
		it = mapBackCashedAddr.find(ip4del[i]);
		if(it !=  mapBackCashedAddr.end())
		{
			delete it->second;
			mapBackCashedAddr.erase(it);
		}
	}

	threadRequesterIPCashe = 0;
	return 0;
}

// Ищет IP-адрес в кеше и если находит, возвращает имя компьютера.
// Если не находит, возвращает строковое представление IP и запускает фоновую 
//	нить с обратным запросом к DNS. При получении ответа от DNS заносит ответ в кеш.
// Считает что IP записан в обратном порядке байт (windows, sin_addr.s_addr)
AFX_EXT_API CString Ip2Name(DWORD ip_num)
{
	CString host_name(Ip2String(ip_num));

	if(IsIpLocal(ip_num, true))
	{
		host_name = _T("localhost");
		return host_name;
	}

	SCashedAddr* addr(0);

	// старое время, после которого надо повторно запрашивать имя из DNS
	COleDateTime odt_def_last = COleDateTime::GetCurrentTime() - COleDateTimeSpan(0,CASHED_IP_TIMELIFE_REQ,0,0);

	CaplEnterCriticalSection ecs(&CS_PROTECT_IP_CASH);

	std::map<DWORD, SCashedAddr*>::iterator it  = mapBackCashedAddr.find(ip_num);
	
	if(it != mapBackCashedAddr.end())
		addr = it->second;

	if(addr == 0)
	{
		addr = new SCashedAddr(ip_num);
		mapBackCashedAddr[ip_num] = addr;
	}

	host_name = addr->host_name;
	addr->odt_req = COleDateTime::GetCurrentTime();

	if(addr->odt_def < odt_def_last && !addr->is_tested)
	{
		requestedIPs.Add(ip_num);
		addr->is_tested = true;
	}

	if(requestedIPs.GetSize() > 0 && threadRequesterIPCashe == 0)
	{
		threadRequesterIPCashe = AfxBeginThread((AFX_THREADPROC)RequesterIp2Name,0);
	}

	return host_name;
}

