//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "stdafx.h"
#include "emule.h"
#include "packets.h"
#include "UDPSocket.h"

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


CUDPSocketWnd::CUDPSocketWnd(){
}

BEGIN_MESSAGE_MAP(CUDPSocketWnd, CWnd)
	ON_MESSAGE(WM_DNSLOOKUPDONE, OnDNSLookupDone)
END_MESSAGE_MAP()

LRESULT CUDPSocketWnd::OnDNSLookupDone(WPARAM wParam,LPARAM lParam){
	m_pOwner->DnsLookupDone(wParam,lParam);
	return true;
};

CUDPSocket::CUDPSocket(CServerConnect* in_serverconnect){
	m_hWndResolveMessage = NULL;
	sendbuffer = 0;
	cur_server = 0;
	serverconnect = in_serverconnect;
	DnsTaskHandle = 0;
}

CUDPSocket::~CUDPSocket(){
	if (cur_server)
		delete cur_server;
	if (sendbuffer)
		delete[] sendbuffer;
	m_udpwnd.DestroyWindow();
}

bool  CUDPSocket::Create(){
	VERIFY( m_udpwnd.CreateEx(0, AfxRegisterWndClass(0),_T("Emule Socket Wnd"),WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL));
	m_hWndResolveMessage = m_udpwnd.m_hWnd;
	m_udpwnd.m_pOwner = this;
	return CAsyncSocket::Create(0,SOCK_DGRAM,FD_READ);
}

void CUDPSocket::OnReceive(int nErrorCode){
	char buffer[5000];
	CString serverbuffer;
	uint32 port;
	int32 length = ReceiveFrom(buffer,5000,serverbuffer,port);
	if ((uint8)buffer[0] == OP_EDONKEYPROT && length != SOCKET_ERROR)
		ProcessPacket(buffer+2,length-2,buffer[1],serverbuffer.GetBuffer(),port);
	else if (((uint8)buffer[0] == OP_EMULEPROT) && length != SOCKET_ERROR)
		ProcessExtPacket(buffer+2,length-2,buffer[1],serverbuffer.GetBuffer(),port);
	
}

bool CUDPSocket::ProcessPacket(char* packet, int16 size, int8 opcode, char* host, uint16 port){
	try{
		CServer* update;
		update = theApp.serverlist->GetServerByAddress( host, port-4 );
		if( update ){
			update->ResetFailedCount();
			theApp.emuledlg->serverwnd.serverlistctrl.RefreshServer( update );
		}
		switch(opcode){
			case OP_GLOBSEARCHRES:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				theApp.emuledlg->searchwnd.AddUDPResult(theApp.searchlist->ProcessUDPSearchanswer(packet,size));
				break;
			}
			case OP_GLOBFOUNDSORUCES:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				CSafeMemFile* sources = new CSafeMemFile((BYTE*)packet,size);
				uchar fileid[16];
				sources->Read(fileid,16);
				if (CPartFile* file = theApp.downloadqueue->GetFileByID(fileid))
					file->AddSources(sources,inet_addr(host),port);
				else
					theApp.serverlist->CancelUDPBroadcast();
				delete sources;
				break;
			}
 			case OP_GLOBSERVSTATRES:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				if( size < 12 || update == NULL )
					return true;
				uint32 challenge;
				memcpy(&challenge,packet,4);
				if( challenge != update->GetChallenge() )
					return true; 
				uint32 cur_user;
				memcpy(&cur_user,packet+4,4);
				uint32 cur_files;
				memcpy(&cur_files,packet+8,4);
				uint32 cur_maxusers = 0;
				if( size >= 16 ){
					memcpy(&cur_maxusers, packet+12,4);
				}
				if( update ){
					update->SetPing( ::GetTickCount() - update->GetLastPinged() );
					update->SetUserCount( cur_user );
					update->SetFileCount( cur_files );
					update->SetMaxUsers( cur_maxusers );
					theApp.emuledlg->serverwnd.serverlistctrl.RefreshServer( update );
				}
				break;
			}

			default:
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				return false;
		}
		return true;
	}
	catch(...){
		OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_UDP_MISCONF));
		return false;
	}
}

bool CUDPSocket::ProcessExtPacket(char* packet, int16 size, int8 opcode, char* host, uint16 port){
	try{
		switch(opcode){
			/*
			case OP_UDPVERIFYUPREQ:{
				ASSERT (size == 6);
				if (size == 6){
					uint32 checkclientip = 0;
					uint16 checkclientport = 0;
					memcpy(&checkclientip,packet,4);
					memcpy(&checkclientport,packet,2);
					if (theApp.clientlist->VerifyUpload(checkclientip,checkclientport)){
						Packet answer(OP_UDPVERIFYUPA,0,OP_EMULEPROT);
						SendTo(answer.GetUDPHeader(),2,port,host);
					}
				}
				break;
			}*/
			case OP_UDPVERIFYUPA:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				break;
			}
			default:
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				return false;
		}
		return true;
	}
	catch(...){
		OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_UDPEXT));
		return false;
	}
}

void CUDPSocket::AsyncResolveDNS(LPCTSTR lpszHostAddress, UINT nHostPort){
	m_lpszHostAddress = lpszHostAddress;
	m_nHostPort = nHostPort;
	if (DnsTaskHandle)
		WSACancelAsyncRequest(DnsTaskHandle);
	DnsTaskHandle = NULL;
	// see if we have a ip already
	USES_CONVERSION;
	SOCKADDR_IN sockAddr;
	memset(&sockAddr,0,sizeof(sockAddr));
	LPSTR lpszAscii = T2A((LPTSTR)m_lpszHostAddress);
	sockAddr.sin_family = AF_INET;
	sockAddr.sin_addr.s_addr = inet_addr(lpszAscii);
	sockAddr.sin_port = htons((u_short)m_nHostPort);

	// backup for send socket
	m_SaveAddr = sockAddr;

	if (sockAddr.sin_addr.s_addr == INADDR_NONE){
		/* Resolve hostname "hostname" asynchronously */ 
		memset(DnsHostBuffer, 0, sizeof(DnsHostBuffer));

		DnsTaskHandle = WSAAsyncGetHostByName(
			m_hWndResolveMessage,
			WM_DNSLOOKUPDONE,
			lpszHostAddress,
			DnsHostBuffer,
			MAXGETHOSTSTRUCT);

		if (DnsTaskHandle == 0){
			delete[] sendbuffer;
			sendbuffer = 0;
			delete cur_server;
			cur_server = 0;
#ifdef _DEBUG
			AfxMessageBox("LOOKUPERROR DNSTASKHANDLE = 0");
#endif
		}
	}
	else{
		SendBuffer();
	}
}

void CUDPSocket::DnsLookupDone(WPARAM wp, LPARAM lp){
	DnsTaskHandle = NULL;

	/* An asynchronous database routine completed. */
	if (WSAGETASYNCERROR(lp) != 0){
		if (sendbuffer)
			delete[] sendbuffer;
		sendbuffer = 0;
		if (cur_server)
			delete cur_server;
		cur_server = 0;
		return;
	}
	if (m_SaveAddr.sin_addr.s_addr == INADDR_NONE){
		// get the structure length
		int iBufLen = WSAGETASYNCBUFLEN(lp);
		LPHOSTENT lphost = (LPHOSTENT)malloc(iBufLen);
		memcpy(lphost, DnsHostBuffer, iBufLen);
		m_SaveAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
		free(lphost);
		// also reset the receive buffer
		memset(DnsHostBuffer, 0, sizeof(DnsHostBuffer));
	}
	if (cur_server){
		CServer* update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort());
		if (update)
			update->SetID(m_SaveAddr.sin_addr.S_un.S_addr);
		SendBuffer();
	}
}

void CUDPSocket::SendBuffer(){
	if(cur_server && sendbuffer){
		SendTo(sendbuffer,sendblen,(SOCKADDR*)&m_SaveAddr, sizeof(m_SaveAddr));
		delete[] sendbuffer;
		sendbuffer = 0;
		delete cur_server;
		cur_server = 0;
	}
}

void CUDPSocket::SendPacket(Packet* packet,CServer* host){
	cur_server = new CServer(host);
	sendbuffer = new char[packet->size+2];
	memcpy(sendbuffer,packet->GetUDPHeader(),2);
	memcpy(sendbuffer+2,packet->pBuffer,packet->size);
	sendblen = packet->size+2;
	AsyncResolveDNS(cur_server->GetAddress(),cur_server->GetPort()+4);
}
