// 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.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.

#ifdef PRECOMP
    #include "xmule-headers.h"
#endif

#include "packets.h"
#include "config.h"                         // HAVE_CRYPTOPP
#include "otherfunctions.h"
#include "otherstructs.h"
#include "CFile.h"
#include "CMemFile.h"

#if !HAVE_CRYPTOPP
    #include "CryptoXMpp.h"
#else
    #include <cryptopp/cryptlib.h>
    #include <cryptopp/rsa.h>
    #define CryptoXMpp CryptoPP
#endif

#ifdef WORDS_BIGENDIAN
    #define LE_BE(X) CryptoXMpp::ByteReverse(X)
#else
    #define LE_BE(X) X
#endif

struct Packet_int
{
    bool bSplitted;
    bool bLastSplitted;
    char head[6];
    char* completebuffer;
    char* tempbuffer;
    bool bPacked;
    int bFromPF;
};

Packet::Packet(const uint8_t protocol)
{
    my = new Packet_int;
    my->bSplitted = false;
    my->bLastSplitted = false;
    size = 0;
    pBuffer = 0;
    my->completebuffer = 0;
    my->tempbuffer = 0;
    prot = protocol;
    my->bPacked = false;
}

Packet::Packet(const char* header)
{
    my = new Packet_int;
    my->bSplitted = false;
    my->bPacked = false;
    my->bLastSplitted = false;
    my->tempbuffer = 0;
    pBuffer = 0;
    my->completebuffer = 0;
    Header_Struct* head = (Header_Struct *)header;
    size = head->packetlength - 1;
    opcode = head->command;
    prot = head->eDonkeyID;
}

// -khaos--+++> Slightly modified for our stats uses...
Packet::Packet(const char* type, const char* pPacketPart, const uint32_t nSize, const bool bLast, const bool bFromPF)
{
    // only used for splitted packets!
    my = new Packet_int;
    my->bFromPF = bFromPF;
    my->bSplitted = true;
    my->bPacked = false;
    my->bLastSplitted = bLast;
    my->tempbuffer = 0;
    pBuffer = 0;
    my->completebuffer = const_cast<char *>(pPacketPart);
    size = nSize - 6;
}

// -khaos--+++> Slightly modified for our stats uses...
//				If my->bFromPF = true then packet was formed from a partfile
//				If my->bFromPF = false then this packet was formed from a complete shared file.
Packet::Packet(const char* type, const int8_t in_opcode, const uint32_t in_size, const uint8_t protocol, const bool bFromPF)
{
    my = new Packet_int;
    my->bFromPF = bFromPF;
    // <-----khaos-
    my->bSplitted = false;
    my->bPacked = false;
    my->bLastSplitted = false;
    my->tempbuffer = 0;

    if (in_size)
    {
        my->completebuffer = new char[in_size + 10];
        pBuffer = my->completebuffer + 6;
        memset(my->completebuffer, 0, in_size + 10);
    }
    else
    {
        pBuffer = 0;
        my->completebuffer = 0;
    }

    opcode = in_opcode;
    size = in_size;
    prot = protocol;
}

Packet::Packet(const char* pPacketPart, const uint32_t nSize, const bool bLast)
{
    // only used for splitted packets!
    my = new Packet_int;
    my->bSplitted = true;
    my->bPacked = false;
    my->bLastSplitted = bLast;
    my->tempbuffer = 0;
    pBuffer = 0;
    my->completebuffer = const_cast<char *>(pPacketPart);
    size = nSize - 6;
}

Packet::Packet(const int8_t in_opcode, const uint32_t in_size, const uint8_t protocol, const unsigned char* buffer)
{
    my = new Packet_int;
    my->bSplitted = false;
    my->bPacked = false;
    my->bLastSplitted = false;
    my->tempbuffer = 0;

    if (in_size)
    {
        my->completebuffer = new char[in_size + 6];
        pBuffer = my->completebuffer + 6;
        memcpy(pBuffer, buffer, in_size);
    }
    else
    {
        pBuffer = 0;
        my->completebuffer = 0;
    }

    opcode = in_opcode;
    size = in_size;
    prot = protocol;
}

Packet::Packet(const int8_t in_opcode, const uint32_t in_size, const uint8_t protocol)
{
    my = new Packet_int;
    my->bSplitted = false;
    my->bPacked = false;
    my->bLastSplitted = false;
    my->tempbuffer = 0;

    if (in_size)
    {
        my->completebuffer = new char[in_size + 10];
        pBuffer = my->completebuffer + 6;
        memset(my->completebuffer, 0, in_size + 10);
    }
    else
    {
        pBuffer = 0;
        my->completebuffer = 0;
    }

    opcode = in_opcode;
    size = in_size;
    prot = protocol;
}

Packet::Packet(CMemFile* datafile, const uint8_t protocol)
{
    my = new Packet_int;
    my->bSplitted = false;
    my->bPacked = false;
    my->bLastSplitted = false;
    size = datafile->GetLength();
    my->completebuffer = new char[datafile->GetLength() + 10];
    pBuffer = my->completebuffer + 6;
    BYTE* tmp = datafile->Detach();;
    memcpy(pBuffer, tmp, size);
    free(tmp);
    my->tempbuffer = 0;
    prot = protocol;
}

Packet::~Packet()
{
    if (my->completebuffer)
    {
        delete[] my->completebuffer;
    }
    else if(pBuffer)
    {
        delete[] pBuffer;
    }
    
    if (my->tempbuffer)
    {
        delete[] my->tempbuffer;
    }
    
    delete my;
}

bool Packet::IsFromPF() const
{
    return my->bFromPF;
}

bool Packet::IsSplitted() const
{
    return my->bSplitted;
}

bool Packet::IsLastSplitted() const
{
    return my->bLastSplitted;
}

char* Packet::GetPacket()
{
    if (my->completebuffer)
    {
        if (!my->bSplitted)
        {
            memcpy(my->completebuffer, GetHeader(), 6);
        }

        return my->completebuffer;
    }
    else
    {
        if (my->tempbuffer)
        {
            delete[] my->tempbuffer;
        }

        my->tempbuffer = new char[size + 10];
        memcpy(my->tempbuffer, GetHeader(), 6);
        memcpy(my->tempbuffer + 6, pBuffer, size);
        return my->tempbuffer;
    }
}

char* Packet::DetachPacket()
{
    if (my->completebuffer)
    {
        if (!my->bSplitted)
        {
            memcpy(my->completebuffer, GetHeader(), 6);
        }
        
        char* result(my->completebuffer);
        my->completebuffer = 0;
        pBuffer = 0;
        return result;
    }
    else
    {
        if (my->tempbuffer)
        {
            delete[] my->tempbuffer;
        }
        
        my->tempbuffer = new char[size + 10];
        memcpy(my->tempbuffer, GetHeader(), 6);
        memcpy(my->tempbuffer + 6, pBuffer, size);
        char* result(my->tempbuffer);
        my->tempbuffer = 0;
        return result;
    }
}

char* Packet::GetHeader()
{
    assert(!my->bSplitted);
    Header_Struct* header((Header_Struct *) my->head);
    header->command = opcode;
    header->eDonkeyID = prot;
    header->packetlength = size + 1;
    return my->head;
}

char* Packet::GetUDPHeader()
{
    assert(!my->bSplitted);
    memset(my->head, 0, 6);
    UDP_Header_Struct* header((UDP_Header_Struct *) my->head);
    header->command = opcode;
    header->eDonkeyID = prot;
    return my->head;
}

void Packet::PackPacket()
{
    assert(!my->bSplitted);
    BYTE* output = new BYTE[size + 300];
    uLongf newsize(size + 300);
    uint16_t result(compress2(output, &newsize, (BYTE *) pBuffer, size, Z_BEST_COMPRESSION));

    if (result != Z_OK || size <= newsize)
    {
        delete[] output;
        return;
    }

    prot = OP_PACKEDPROT;
    memcpy(pBuffer, output, newsize);
    delete[] output;
    my->bPacked = true;
}

bool Packet::UnPackPacket(UINT uMaxDecompressedSize)
{
    assert(prot == OP_PACKEDPROT);
    uint32_t nNewSize(size *10 + 300);

    if (nNewSize > uMaxDecompressedSize)
    {
        nNewSize = uMaxDecompressedSize;
    }

    BYTE* unpack = new BYTE[nNewSize];
    uLongf unpackedsize(nNewSize);
    uint16_t result(uncompress(unpack, &unpackedsize, (BYTE *) pBuffer, size));
    
    if (result == Z_OK)
    {
        assert(my->completebuffer == NULL);
        assert(pBuffer != NULL);
        size = unpackedsize;
        delete[] pBuffer;
        pBuffer = (char *)unpack;
        prot = OP_EMULEPROT;
        return true;
    }

    delete[] unpack;
    return false;
}

CTag::CTag(char* name, uint32_t intvalue)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));
    tag->tagname = nstrdup(name);
    tag->type = TAG_INTEGER;
    tag->intvalue = intvalue;
}

CTag::CTag(int8_t special, uint32_t intvalue)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));
    tag->type = TAG_INTEGER;
    tag->intvalue = intvalue;
    tag->specialtag = special;
}

CTag::CTag(char* name, char* strvalue)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));
    tag->tagname = nstrdup(name);
    tag->type = TAG_STRING;
    tag->stringvalue = nstrdup(strvalue);
}

CTag::CTag(int8_t special, char* strvalue)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));
    tag->type = TAG_STRING;
    tag->stringvalue = nstrdup(strvalue);
    tag->specialtag = special;
}

CTag::CTag(Tag_Struct* in_tag)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));
    memcpy(tag, in_tag, sizeof(Tag_Struct));

    if (in_tag->tagname)
    {
        tag->tagname = nstrdup(in_tag->tagname);
    }

    if (in_tag->stringvalue)
    {
        tag->stringvalue = nstrdup(in_tag->stringvalue);
    }
}

CTag::CTag(CFile* in_data)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));

    if (1 == in_data->Read( &tag->type, 1))
    {
        uint16_t length;

        if (2 == in_data->Read( &length, 2))
        {
	    length = LE_BE(length);
            if (length == 1)
            {
                if (1 != in_data->Read( &tag->specialtag, 1))
                {
                    length = 0;
                }
            }
            else
            {
                tag->tagname = new char[length + 1];
                if (length == in_data->Read(tag->tagname, length))
                {
                    tag->tagname[length] = 0;
                }
                else
                {
                    length = 0;
                }
            }
            if (length)
            {
                if ((TTagType) tag->type == TAG_STRING)
                {
                    if (2 == in_data->Read( &length, 2))
                    {
                        length = LE_BE(length);
                        tag->stringvalue = new char[length + 1];
                        if (length == in_data->Read(tag->stringvalue, length))
                        {
                            tag->stringvalue[length] = 0;
                        }
                        else
                        {
                            delete tag;
                        }
                    }
                    else
                    {
                        delete tag;
                    }
                }
                else if((TTagType) tag->type == TAG_INTEGER)
                {
                    if (4 != in_data->Read( &tag->intvalue, 4))
                    {
                        delete tag;
                    }
                    else
                    {
                        tag->intvalue = LE_BE((CryptoXMpp::word32)tag->intvalue);
                    }
                }
                else
                {
                    delete tag;
                }
            }
            else
            {
                delete tag;
            }
        }
        else
        {
            delete tag;
        }
    }
    else
    {
        delete tag;
    }
}

CTag::CTag(CMemFile* in_data)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));

    if (1 == in_data->Read( &tag->type, 1))
    {
        uint16_t length;

        if (2 == in_data->Read( &length, 2))
        {
	    length = LE_BE(length);
            if (length == 1)
            {
                if (1 != in_data->Read( &tag->specialtag, 1))
                {
                    length = 0;
                }
            }
            else
            {
                tag->tagname = new char[length + 1];

                if (length == in_data->Read(tag->tagname, length))
                {
                    tag->tagname[length] = 0;
                }
                else
                {
                    length = 0;
                }
            }
            if (length)
            {
                if ((TTagType) tag->type == TAG_STRING)
                {
                    if (2 == in_data->Read( &length, 2))
                    {
                        length = LE_BE(length);
                        tag->stringvalue = new char[length + 1];

                        if (length == in_data->Read(tag->stringvalue, length))
                        {
                            tag->stringvalue[length] = 0;
                        }
                        else
                        {
                            delete tag;
                        }
                    }
                    else
                    {
                        delete tag;
                    }
                }
                else if((TTagType) tag->type == TAG_INTEGER)
                {
                    if (4 != in_data->Read( &tag->intvalue, 4))
                    {
                        delete tag;
                    }
                    else
                    {
                        tag->intvalue = LE_BE((CryptoXMpp::word32)tag->intvalue);
                    }
                }
                else
                {
                    delete tag;
                }
            }
            else
            {
                delete tag;
            }
        }
        else
        {
            delete tag;
        }
    }
    else
    {
        delete tag;
    }
}

CTag::CTag(FILE* in_data)
{
    tag = new Tag_Struct;
    memset(tag, 0, sizeof(Tag_Struct));

    if (1 != fread( &tag->type, 1, 1, in_data))
    {
        throw CInvalidPacket("short packet reading tag data type");
    }

    uint16_t length;

    if (1 != fread( &length, 2, 1, in_data))
    {
        throw CInvalidPacket("short packet reading tagname length");
    }

    if (length == 1)
    {
        if (1 != fread( &tag->specialtag, 1, 1, in_data))
        {
            throw CInvalidPacket("short packet reading tag id");
        }
    }
    else
    {
        tag->tagname = new char[length + 1];

        if (1 != fread(tag->tagname, length, 1, in_data))
        {
            throw CInvalidPacket("short packet reading tagname");
        }

        tag->tagname[length] = 0;
    }

    if ((TTagType) tag->type == TAG_STRING)
    {
        if (1 != fread( &length, 2, 1, in_data))
        {
            throw CInvalidPacket("short packet reading string data length");
        }

        tag->stringvalue = new char[length + 1];

        if (1 != fread(tag->stringvalue, length, 1, in_data))
        {
            throw CInvalidPacket("short packet reading string data");
        }

        tag->stringvalue[length] = 0;
    }
    else if((TTagType) tag->type == TAG_INTEGER)
    {
        if (1 != fread( &tag->intvalue, 4, 1, in_data))
        {
            throw CInvalidPacket("short packet reading integer data");
        }
    }
    else
    {
        throw CInvalidPacket("unknown tag data type");
    }
}

CTag::~CTag()
{
    if (tag->tagname)
    {
        delete[] tag->tagname;
    }

    if (tag->stringvalue)
    {
        delete[] tag->stringvalue;
    }
    
    delete tag;
}

bool CTag::WriteTagToFile(CFile* file) const
{
    file->Write( &tag->type, 1);

    if (tag->tagname)
    {
        uint16_t taglen = (uint16_t)strlen(tag->tagname);
        uint16_t w_taglen = LE_BE(taglen);
        file->Write(&w_taglen, 2);
        file->Write(tag->tagname, taglen);
    }
    else
    {
        uint16_t taglen = 1;
        uint16_t w_taglen = LE_BE((uint16_t)1);
        file->Write(&taglen, 2);
        file->Write(&tag->specialtag, taglen);
    }

    if ((TTagType) tag->type == TAG_STRING)
    {
        uint16_t len = (uint16_t) strlen(tag->stringvalue);
        uint16_t w_len = LE_BE(len);
        file->Write(&w_len, 2);
        file->Write(tag->stringvalue, len);
    }
    else if ((TTagType) tag->type == TAG_INTEGER)
    {
        uint32_t w_intvalue = LE_BE((CryptoXMpp::word32)tag->intvalue);
        file->Write(&w_intvalue, 4);
    }

    return true;
}

bool CTag::WriteTagToFile(CMemFile* file) const
{
    file->Write( &tag->type, 1);

    if (tag->tagname)
    {
        uint16_t taglen = (uint16_t) strlen(tag->tagname);
        uint16_t w_taglen = LE_BE(taglen);
        file->Write(&w_taglen, 2);
        file->Write(tag->tagname, taglen);
    }
    else
    {
        uint16_t taglen = 1;
        uint16_t w_taglen = LE_BE((uint16_t)1);
        file->Write(&w_taglen, 2);
        file->Write(&tag->specialtag, taglen);
    }

    if ((TTagType) tag->type == TAG_STRING)
    {
        uint16_t len = (uint16_t) strlen(tag->stringvalue);
        uint16_t w_len = LE_BE(len);
        file->Write(&w_len, 2);
        file->Write(tag->stringvalue, len);
    }
    else if ((TTagType) tag->type == TAG_INTEGER)
    {
        uint32_t w_intvalue = LE_BE((CryptoXMpp::word32)tag->intvalue);
        file->Write(&w_intvalue, 4);
    }

    return true;
}

bool CTag::WriteTagToFile(FILE* file) const
{
    fputc(tag->type, file);

    if (tag->tagname && (!tag->specialtag))
    {
        uint16_t taglen = (uint16_t) strlen(tag->tagname);
        uint16_t w_taglen = LE_BE(taglen);
        fwrite(&w_taglen, 2, 1, file);
        fwrite(tag->tagname, taglen, 1, file);
    }
    else
    {
        uint16_t taglen = 1;
        uint16_t w_taglen = LE_BE((uint16_t)1);
        fwrite(&w_taglen, 2, 1, file);
        fwrite(&tag->specialtag, taglen, 1, file);
    }

    if ((TTagType) tag->type == TAG_STRING)
    {
        uint16_t len = (uint16_t) strlen(tag->stringvalue);
        uint16_t w_len = LE_BE(len);
        fwrite(&w_len, 2, 1, file);
        fwrite(tag->stringvalue, len, 1, file);
    }
    else if ((TTagType) tag->type == TAG_INTEGER)
    {
        uint32_t w_intvalue = LE_BE((CryptoXMpp::word32)tag->intvalue);
        fwrite(&w_intvalue, 4, 1, file);
    }

    return ferror(file);
}

void CTag::DumpToStdout() const
{
#if defined(__DEBUG__)
    if (tag->tagname)
    {
        printf("name='%s'", tag->tagname);
    }
    else
    {
        printf("special=%u", (unsigned int) tag->specialtag);
    }

    switch ((TTagType) tag->type)
    {
        case TAG_STRING:
            printf(" string='%s'\n", tag->stringvalue);
            break;
        case TAG_INTEGER:
            printf(" integer=%u\n", (unsigned int) tag->intvalue);
            break;
        default:
            printf(" unknown type %u\n", (unsigned int) tag->type);
            break;
    }
#endif
}

TTagType CTag::GetType() const
{
    return(TTagType) tag->type;
}

CInvalidPacket::CInvalidPacket(const char* reason)
{
    if (reason)
    {
        strncpy(m_acWhat, reason, sizeof m_acWhat);
        m_acWhat[sizeof m_acWhat - 1] = 0;
    }
    else
    {
        strcpy(m_acWhat, "(NULL)");
    }
}

const char* CInvalidPacket::what() const throw()
{
    return m_acWhat;
}

