// This file is a part of the xMule Project.
//
// Copyright (c) 2004 Theodore R. Smith (hopeseekr@xmule.ws / http://www.xmule.ws/)
// DSA-1024 Fingerprint: 10A0 6372 9092 85A2 BB7F 907B CB8B 654B E33B F1ED
//
// Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.xmule-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.

#ifdef PRECOMP
#	include "xmule-headers.h"
#else
#	include "DownloadQueue.h"
#include "NewSockets.h"                     // Needed for NewSocket_SendPacketOP
#include "PartFile.h"                       // Needed for CPartFile - audited 5 Nov 2004
#	include "SearchDlg.h"
#	include "SearchList.h"
#	include "ServerList.h"
#	include "ServerWnd.h"
#	include "UDPSocket.h"
#	include "xmule.h"
#	include "xmuleDlg.h"
#include "xmuleIPV4Address.h"               // xmuleIPV4Address
#endif

#include <DynPrefs/DynPrefs.h>              // Needed for DynamicPreferences::Get

#include <netdb.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//=PTHREAD_CREATE_DETACHED;:
static pthread_attr_t attr;

class UDPSocketEvt: public wxEvtHandler
{
private:
	void HandleSocket(wxSocketEvent& event);
	DECLARE_EVENT_TABLE();
};

BEGIN_EVENT_TABLE(UDPSocketEvt, wxEvtHandler)
	EVT_SOCKET(-1, UDPSocketEvt::HandleSocket)
END_EVENT_TABLE()

void UDPSocketEvt::HandleSocket(wxSocketEvent& event)
{
    CUDPSocket* soc = dynamic_cast<CUDPSocket *>(event.GetSocket());

    if (event.GetSocketEvent() == wxSOCKET_INPUT)
    {
        if (theApp.dynprefs->Get<bool>("enable-serverless") == false)
        {
            soc->OnReceive(0);
        }
    }
}

#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
static wxMutex s_mutexProtectingGetHostByName;

static int convert(struct hostent *host, struct hostent *result, char *buf, int buflen, int *h_errnop)
{
    int len, i;
    if (!buf || !h_errnop) return - 1;
    *h_errnop = h_errno;
    *result = *host;
    result->h_name = (char *) buf;
    /* This is the size. */
    len = strlen(host->h_name) + 1;
    if (len > buflen) return - 1;
    buflen -= len;
    buf += len;
    strcpy((char *) result->h_name, host->h_name);
 /* How many aliases and how big the buffer should be? There
    is always a NULL pointer. */
    for (len = sizeof(char *), i = 0 ; host->h_aliases [i] ; i++)
    {
 /* It should be size of (char *) and the length of string
    plus 1. */
        len += strlen(host->h_aliases [i]) + 1 + sizeof(char *);
    }
    if (len > buflen) return - 1;
    buflen -= len;
    /* This is an array of char * for h_aliases. */
#ifdef NEED_ALIGNED_ACCESS
    {
        int extra;
        extra = 4 - (((unsigned long) buf) &3);
        if (extra != 4)
        {
            if (buflen < extra)
            return - 1;
            buf = (char *) buf + extra;
        }
    }
#endif
    result->h_aliases = (char **) buf;
    buf += (i + 1) *sizeof(char *);
    /* We copy the aliases now. */
    for (i = 0 ; host->h_aliases [i] ; i++)
    {
        result->h_aliases [i] = (char *) buf;
        strcpy(result->h_aliases [i], host->h_aliases [i]);
        buf += strlen(host->h_aliases [i]) + 1;
    }
    /* This is the last one */
    result->h_aliases [i] = NULL;
#if BSD >= 43 || defined(h_addr)
    for (len = sizeof(char *), i = 0 ; host->h_addr_list [i] ; i++)
    {
 /* It should be size of (char *) and the length of string
    plus 1. */
        len += host->h_length + sizeof(char *);
    }
    if (len > buflen) return - 1;
    /* This is an array of char * for h_addr_list. */
#ifdef NEED_ALIGNED_ACCESS
    {
        int extra;
        extra = 4 - (((unsigned long) buf) &0x3);
        if (extra != 4)
        {
            if (buflen < extra)
            return - 1;
            buf = ((char *) buf) + extra;
        }
    }
#endif
    result->h_addr_list = (char **) buf;
    buf += (i + 1) *sizeof(char *);
    /* We copy the h_addr_list now. */
    for (i = 0 ; host->h_addr_list [i] ; i++)
    {
        result->h_addr_list [i] = (char *) buf;
        memcpy(result->h_addr_list [i], host->h_addr_list [i], host->h_length);
        buf += host->h_length;
    }
    /* This is the last one */
    result->h_addr_list [i] = NULL;
#else
len = strlen(host->h_addr) + 1 + sizeof(char *);

if (len > buflen) return - 1;

result->h_addr = (char *) buf;

strcpy(result->h_addr, host->h_addr);

#endif
return 0;

}

struct hostent *
gethostbyname_r(const char *name,
struct hostent *result, char *buffer, int buflen,
int *h_errnop)
{
    struct hostent *host;
    s_mutexProtectingGetHostByName.Lock();
    host = gethostbyname(name);
    if (!host ||
    convert(host, result, buffer, buflen, h_errnop) != 0)
    {
        result = NULL;
    }
    s_mutexProtectingGetHostByName.Unlock();
    return result;
}

#endif /* __FreeBSD__ */

#define TM_DNSDONE 17851

class AsyncDNS: public wxThread
{
    public:
    AsyncDNS();
    virtual ExitCode Entry();
    wxString ipName;
    CUDPSocket *socket;
}

;

AsyncDNS::AsyncDNS(): wxThread(wxTHREAD_DETACHED)
{
}

wxThread::ExitCode AsyncDNS::Entry()
{
    struct hostent ret, *result = NULL;
    int errorno = 0;
    char dataBuf[512] =
    {
        0
        };
#if defined(__linux__)
    gethostbyname_r(ipName.GetData(), &ret, dataBuf, sizeof(dataBuf), &result, &errorno);
#else
    result = gethostbyname_r(ipName.GetData(), &ret, dataBuf, sizeof(dataBuf), &errorno);
#endif
    if (result)
    {
        unsigned long addr = * (unsigned long *) ret.h_addr;
        //new struct sockaddr_in;:
        struct sockaddr_in *newsi = (struct sockaddr_in *) malloc(sizeof(struct sockaddr_in));
        newsi->sin_addr.s_addr = addr;
        wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED, TM_DNSDONE);
        evt.SetClientData(socket);
        evt.SetInt((int) newsi);
        wxPostEvent(theApp.xmuledlg, evt);
    }
}

#if 0
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;
}

;

#endif

IMPLEMENT_DYNAMIC_CLASS(CUDPSocket, wxDatagramSocket)

//static wxIPV4address tmpaddress;

#define ID_SOKETTI 7772
#include <string>
#include <iostream>
using std::cout; using std::endl;
CUDPSocket::CUDPSocket(CServerConnect *in_serverconnect, wxIPV4address &address)
: wxDatagramSocket(address, wxSOCKET_NOWAIT)
{
    //m_hWndResolveMessage = NULL;
    sendbuffer = 0;
    cur_server = 0;
    serverconnect = in_serverconnect;
    printf("*** UDP socket at %d\n", address.Service());

    m_event_handler = new UDPSocketEvt;

    SetEventHandler(*m_event_handler, -1);
    SetNotify(wxSOCKET_INPUT_FLAG);
    Notify(TRUE);
    //DnsTaskHandle = 0;
}

CUDPSocket::~CUDPSocket()
{
    SetNotify(0);
    Notify(FALSE);
    delete m_event_handler;

    if (cur_server)
    {
        delete cur_server;
        cur_server = NULL;
    }

    if (sendbuffer)
    {
        delete[] sendbuffer;
        sendbuffer = NULL;
    }
    //m_udpwnd.DestroyWindow();
}

void CUDPSocket::OnReceive(int nErrorCode)
{
    unsigned char buffer[5000];
    wxIPV4address addr;
    RecvFrom(addr, buffer, 5000);
    wxInt32 length = LastCount();
    wxUint32 fromIP;
    fromIP = GAddress_INET_GetHostAddress(addr.GetAddress());
    if (length != - 1)
    {
        if (buffer[0] == OP_EDONKEYPROT)
        {
            ProcessPacket(buffer + 2, length - 2, buffer[1], fromIP, addr.Service());
        }
        else if(buffer[0] == OP_EMULEPROT)
        {
            ProcessExtPacket(buffer + 2, length - 2, buffer[1], fromIP, addr.Service());
        }
    }
}

bool CUDPSocket::ProcessPacket(unsigned char *packet, wxUint16 size, wxUint8 opcode,
wxUint32 hostip, wxUint16 port)
{
	NewSocket_DebugPP(NULL,opcode,size,packet);
    CServer *update;
    update = theApp.serverlist->GetServerByIP(hostip, port - 4);
    if (update)
    {
        update->ResetFailedCount();
        theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
    }
    switch (opcode)
    {
    case OP_GLOBSEARCHRES:
        {
            theApp.xmuledlg->searchwnd->SearchEnd((char *) packet, size, hostip, port - 4);
            theApp.downloadqueue->AddDownDataOverheadOther(size);
            break;
        }
    case OP_GLOBFOUNDSOURCES:
        {
            theApp.downloadqueue->AddDownDataOverheadOther(size);
            CSafeMemFile *data = new CSafeMemFile((BYTE *) packet, size);
            // process all source packets
            int iLeft;
            do
            {
                unsigned char fileid[16];
                data->Read(fileid, 16);
                if (CPartFile *file = theApp.downloadqueue->GetFileByID(fileid))
                file->AddSources(data, hostip, port - 4);
                else
                {
                    // skip sources for that file
                    uint8_t count;
                    data->Read( &count, 1);
                    data->Seek(count * (4 + 2), wxFromStart);
                }
                // check if there is another source packet
                iLeft = (int)(data->GetLength() - data->GetPosition());
                if (iLeft >= 2)
                {
                    uint8_t protocol;
                    data->Read( &protocol, 1);
                    iLeft--;
                    if (protocol != OP_EDONKEYPROT)
                    {
                        data->Seek( - 1, wxFromCurrent);
                        iLeft += 1;
                        break;
                    }
                    uint8_t opcode;
                    data->Read( &opcode, 1);
                    iLeft--;
                    if (opcode != OP_GLOBFOUNDSOURCES)
                    {
                        data->Seek( - 2, wxFromCurrent);
                        iLeft += 2;
                        break;
                    }
                }
            }
            while (iLeft > 0);
            delete data;
            break;
        }
    case OP_GLOBSERVSTATRES:
        {
            // Imported from eMule 0.30c
            theApp.downloadqueue->AddDownDataOverheadOther(size);
            if (size < 12 || update == NULL)
            return true;
#define get_uint32(p)	*((uint32_t*)(p))
            uint32_t challenge = get_uint32(packet);
            if (challenge != update->GetChallenge())
            return true;
            uint32_t cur_user = get_uint32(packet + 4);
            uint32_t cur_files = get_uint32(packet + 8);
            uint32_t cur_maxusers = 0;
            uint32_t cur_softfiles = 0;
            uint32_t cur_hardfiles = 0;
            uint32_t uUDPFlags = 0;
            if (size >= 16)
            {
                cur_maxusers = get_uint32(packet + 12);
            }
            if (size >= 24)
            {
                cur_softfiles = get_uint32(packet + 16);
                cur_hardfiles = get_uint32(packet + 20);
            }
            if (size >= 28)
            {
                uUDPFlags = get_uint32(packet + 24);
            }
            if (update)
            {
                update->SetPing(::GetTickCount() - update->GetLastPinged());
                update->SetUserCount(cur_user);
                update->SetFileCount(cur_files);
                update->SetMaxUsers(cur_maxusers);
                update->SetSoftFiles(cur_softfiles);
                update->SetHardFiles(cur_hardfiles);
                //printf("->> reading Stats from server, flags are %i\n",uUDPFlags);
                update->SetUDPFlags(uUDPFlags);
                theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
            }
            break;
        }
    case OP_SERVER_DESC_RES:
        {
            // Imported from eMule 0.30c
            theApp.downloadqueue->AddDownDataOverheadOther(size);
            if (!update)
            return true;
            CSafeMemFile *srvinfo = new CSafeMemFile((BYTE *) packet, size);
            wxUint16 stringlen;
            char *strName, *strDesc;
            srvinfo->Read( &stringlen, 2);
            strName = (char *) malloc(stringlen + 1);
            srvinfo->Read(strName, stringlen);
            strName[stringlen] = 0;
            srvinfo->Read( &stringlen, 2);
            strDesc = (char *) malloc(stringlen + 1);
            srvinfo->Read(strDesc, stringlen);
            strDesc[stringlen] = 0;
            if (update)
            {
                update->SetDescription(strDesc);
                update->SetListName(strName);
                theApp.xmuledlg->serverwnd->serverlistctrl->RefreshServer(update);
            }
            delete srvinfo;
            free(strName);
            free(strDesc);
            break;
        }
    default:
        printf("UDPSocket::ProcessPacket unknown opcode: %02x\n", opcode);
        theApp.downloadqueue->AddDownDataOverheadOther(size);
        return false;
    }
    return true;
}

bool CUDPSocket::ProcessExtPacket(unsigned char *packet, wxUint16 size, wxUint8 opcode, wxUint32 hostip, wxUint16 port)
{
	NewSocket_DebugPP(NULL,opcode,size,packet);
        switch (opcode)
        {
 /*
 case OP_UDPVERIFYUPREQ:{
 wxASSERT (size == 6);
 if (size == 6){
 uint32_t checkclientip = 0;
 uint16_t 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,hostip);
 }
 }
 break;
    }*/
        case OP_UDPVERIFYUPA:
            {
                theApp.downloadqueue->AddDownDataOverheadOther(size);
                break;
            }
        default:
            theApp.downloadqueue->AddDownDataOverheadOther(size);
            return false;
        }
        return true;
}

void CUDPSocket::DnsLookupDone(struct sockaddr_in *inaddr)
{
    /* An asynchronous database routine completed. */
    if (inaddr == NULL)
    {
    /*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)
    {
        m_SaveAddr.sin_addr.s_addr = inaddr->sin_addr.s_addr;
    }
    if (cur_server)
    {
        CServer *update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(), cur_server->GetPort());
        if (update)
        update->SetID(m_SaveAddr.sin_addr.s_addr);
        SendBuffer();
    }
    if (inaddr)
    {
        free(inaddr);
    }
}

void CUDPSocket::SendBuffer()
{
    if (cur_server &&sendbuffer)
    {
        if (Ok())
        {
            xmuleIPV4Address addr;
            long tmp = m_SaveAddr.sin_addr.s_addr;

            #if wxCHECK_VERSION(2, 5, 2)
                addr.Hostname(ntohl(m_SaveAddr.sin_addr.s_addr));
            #else
                addr.Hostname(tmp);
            #endif
            addr.Service(m_SaveAddr.sin_port);

            if (this->Ok())
            {
                SendTo(addr, sendbuffer, sendblen);
                //only for debugging:
                //printf("CUDPSocket::SendBuffer to %s:%d\n",(const char *) addr->IPAddress(),addr->Service());
            }
        }

        delete[] sendbuffer;
        delete cur_server;
        sendbuffer = NULL;
        cur_server = NULL;
    }
}

void CUDPSocket::SendPacket(Packet *packet, CServer *host)
{
//printf("CUDPSocket::SendPacket opcode=0x%02x size=%u\n",packet->opcode,packet->size);
    if (Ok())
    {
        wxIPV4address *addr = new wxIPV4address();
        cur_server = new CServer(host);
        if (GAddress_INET_SetPort(addr->GetAddress(), cur_server->GetPort() + 4) == GSOCK_NOERROR)
        {
            if (GAddress_INET_SetHostAddress(addr->GetAddress(), inet_addr(cur_server->GetAddress())) == GSOCK_NOERROR)
            {
                sendblen = packet->size + 2;
                sendbuffer = new char[sendblen];
#if defined(__DEBUG__)
                //printf("CUDPSocket::SendPacket to %s:%d 0x%02x,%d\n", 
		//(const char *) addr->IPAddress(), addr->Service(), packet->opcode, packet->size);
#endif
                memcpy(sendbuffer, packet->GetUDPHeader(), 2);
                memcpy(sendbuffer + 2, packet->pBuffer, packet->size);
                SendTo( *addr, sendbuffer, sendblen);
            }
        }
        delete addr;
        delete[] sendbuffer;
        delete cur_server;
    }
    sendbuffer = NULL;
    cur_server = NULL;
}

