// This file is part of the aMule Project
//
// Copyright (c) 2003-2004 aMule Project ( http://www.amule-project.net )
// 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 "sys.h"
#include <zlib.h>		// Needed for uLongf

#include "packets.h"		// Interface declarations
#include "otherfunctions.h"	// Needed for nstrdup
#include "CMemFile.h"		// Needed for CMemFile
#include "otherstructs.h"	// Needed for Header_Struct

Packet::Packet(uint8 protocol){
	m_bSplitted 		= false;
	m_bLastSplitted 	= false;
	size 				= 0;
	pBuffer 			= NULL;
	completebuffer 	= NULL;
	tempbuffer		= NULL;
	prot 				= protocol;
	m_bPacked 		= false;
}

Packet::Packet(char* header) {
	m_bSplitted = m_bPacked = m_bLastSplitted = false;
	tempbuffer = pBuffer = completebuffer = NULL;
	Header_Struct* head = (Header_Struct*) header;
	size = head->packetlength - 1;
	opcode = head->command;
	prot = head->eDonkeyID;
}

Packet::Packet(char* pPacketPart, uint32 nSize ,bool bLast, bool bFromPF){// only used for splitted packets!
	m_bFromPF = bFromPF;
	m_bSplitted = true;
	m_bPacked = false;
	m_bLastSplitted = bLast;
	tempbuffer = pBuffer = NULL;
	completebuffer = pPacketPart;
	size = nSize-6;
}

Packet::Packet(int8 in_opcode,int32 in_size,uint8 protocol,bool bFromPF) {
	m_bFromPF = bFromPF;
	m_bSplitted = m_bPacked = m_bLastSplitted = false;
	tempbuffer = 0;
	if (in_size) {
		completebuffer = new char[in_size+10];
		pBuffer = completebuffer+6;
		memset(completebuffer,0,in_size+10);
	}
	else {
		pBuffer = completebuffer = NULL;
	}
	opcode = in_opcode;
	size = in_size;
	prot = protocol;
}

Packet::Packet(CMemFile* datafile, uint8 protocol) {
	m_bSplitted = m_bPacked = m_bLastSplitted = false;
	size = datafile->GetLength();
	completebuffer = new char[datafile->GetLength() + 10];
	pBuffer = completebuffer + 6;

	BYTE* tmp = datafile->Detach();
	memcpy(pBuffer,tmp,size);
	free(tmp);

	tempbuffer = NULL;
	prot = protocol;
}

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

char* Packet::GetPacket() {
	if (completebuffer) {
		if (!m_bSplitted) {
			memcpy(completebuffer, GetHeader(), 6);
		}
		return completebuffer;
	} else {
		if (tempbuffer){
			delete[] tempbuffer;
			tempbuffer = NULL; // 'new' may throw an exception
		}
		tempbuffer = new char[size+10];
		memcpy(tempbuffer,GetHeader(),6);
		memcpy(tempbuffer+6,pBuffer,size);
		return tempbuffer;
	}
}

char* Packet::DetachPacket() {
	if (completebuffer) {
		if (!m_bSplitted) {
			memcpy(completebuffer, GetHeader(), 6);
		}
		char* result = completebuffer;
		completebuffer = pBuffer = NULL;
		return result;
	} else{
		if (tempbuffer){
			delete[] tempbuffer;
			tempbuffer = NULL; // 'new' may throw an exception
		}
		tempbuffer = new char[size+10];
		memcpy(tempbuffer,GetHeader(),6);
		memcpy(tempbuffer+6,pBuffer,size);
		char* result = tempbuffer;
		tempbuffer = 0;
		return result;
	}
}

char* Packet::GetHeader() {
	wxASSERT( !m_bSplitted );

	Header_Struct* header = (Header_Struct*) head;
	header->command = opcode;
	header->eDonkeyID =  prot;
	header->packetlength = size + 1;

	return head;
}

char* Packet::GetUDPHeader() {
	wxASSERT( !m_bSplitted );

	memset(head, 0, 6);
	UDP_Header_Struct* header = (UDP_Header_Struct*) head;
	header->command = opcode;
	header->eDonkeyID =  prot;

	return head;
}

void Packet::PackPacket() {
	wxASSERT(!m_bSplitted);

	uLongf newsize = size + 300;
	BYTE* output = new BYTE[newsize];

	uint16 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;
	m_bPacked = true;
}

bool Packet::UnPackPacket(UINT uMaxDecompressedSize) {
	wxASSERT( prot == OP_PACKEDPROT );

	uint32 nNewSize = size * 10 + 300;

	if (nNewSize > uMaxDecompressedSize){
		//ASSERT(0);
		nNewSize = uMaxDecompressedSize;
	}

	BYTE* unpack = new BYTE[nNewSize];
	uLongf unpackedsize = nNewSize;
	uint16 result = uncompress(unpack, &unpackedsize, (BYTE *) pBuffer, size);

	if (result == Z_OK) {
		wxASSERT( completebuffer == NULL );
		wxASSERT( pBuffer != NULL );

		size = unpackedsize;
		delete[] pBuffer;
		pBuffer = (char *) unpack;
		prot = OP_EMULEPROT;
		return true;
	}

	delete[] unpack;
	return false;
}

CTag::CTag(char* name, uint32 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 special, uint32 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 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) ) {
		throw CInvalidPacket("short packet reading tag data type");
	}

	uint16 length;

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

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

	if ((TTagType) tag->type == TAG_STRING) {
		if ( 2 != in_data->Read(&length, 2) ) {
			throw CInvalidPacket("short packet reading string data length");
		}
		
		tag->stringvalue = new char[length+1];

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

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

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

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

	uint16 length;

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

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

		if ( length != in_data->Read(tag->tagname, length) ) {
			throw CInvalidPacket("short packet reading tagname");
		}
		tag->tagname[length] = 0;
	}

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

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

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

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

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 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");
	}

/*  FIX ____ FIX ____  FIX ____ FIX ____  XXX FIX ____ FIX ____  FIX ____ FIX ____  FIX ____ FIX ____
	else if (tag.type == 4) { // FLOAT (used by Hybrid 0.48)
		in_data->Read(&tag.floatvalue, 4);
	}
	else if (tag.type == 1) { // HASH (never seen)
		//TRACE("CTag::CTag(CFile*); Reading *unverified* HASH tag\n");
		in_data->Seek(16, CFile::current);
	}
	else if (tag.type == 5) { // bool (never seen; propably 1 bit)
		// NOTE: This is preventive code, it was never tested
		//TRACE("CTag::CTag(CFile*); Reading *unverified* bool tag\n");
		in_data->Seek(1, CFile::current);
	}
	else if (tag.type == 6) { // bool Array (never seen; propably <numbits> <bits>)
		// NOTE: This is preventive code, it was never tested
		//TRACE("CTag::CTag(CFile*); Reading *unverified* bool Array tag\n");
		uint16 len;

		in_data->Read(&len, 2);
		in_data->Seek((len+7) / 8, CFile::current);
	}
	else if (tag.type == 7) { // BLOB (never seen; propably <len> <byte>)
		// NOTE: This is preventive code, it was never tested
		//TRACE("CTag::CTag(CFile*); Reading *unverified* BLOB tag\n");
		uint16 len;

		in_data->Read(&len, 2);
		in_data->Seek(len, CFile::current);
	}
*/

  	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){
	file->Write(&tag->type, 1);

	if (tag->tagname) {
		uint16 taglen = (uint16) strlen(tag->tagname);

		file->Write(&taglen, 2);
		file->Write(tag->tagname, taglen);
	} else {
		uint16 taglen = 1;

		file->Write(&taglen, 2);
		file->Write(&tag->specialtag, taglen);
	}

	if ((TTagType) tag->type == TAG_STRING) {
		uint16 len = (uint16) strlen(tag->stringvalue);

		file->Write(&len, 2);
		file->Write(tag->stringvalue, len);
	} else if ((TTagType) tag->type == TAG_INTEGER) {
		file->Write(&tag->intvalue, 4);
	}
	
	return true;
}

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

	if (tag->tagname) {
		uint16 taglen = (uint16) strlen(tag->tagname);

		file->Write(&taglen, 2);
		file->Write(tag->tagname, taglen);
	} else {
		uint16 taglen = 1;

		file->Write(&taglen, 2);
		file->Write(&tag->specialtag, taglen);
	}

	if ((TTagType) tag->type == TAG_STRING) {
		uint16 len = (uint16) strlen(tag->stringvalue);

		file->Write(&len, 2);
		file->Write(tag->stringvalue, len);
	} else if ((TTagType) tag->type == TAG_INTEGER) {
		file->Write(&tag->intvalue, 4);
	}

	return true;
}

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

	if (tag->tagname && (!tag->specialtag)) {
		uint16 taglen = (uint16) strlen(tag->tagname);

		fwrite(&taglen, 2, 1, file);
		fwrite(tag->tagname, taglen, 1, file);
	} else {
		uint16 taglen = 1;

		fwrite(&taglen, 2, 1, file);
		fwrite(&tag->specialtag, taglen, 1, file);
	}

	if ((TTagType) tag->type == TAG_STRING) {
		uint16 len = (uint16) strlen(tag->stringvalue);

		fwrite(&len, 2, 1, file);
		fwrite(tag->stringvalue, len, 1, file);
	} else if ((TTagType) tag->type == TAG_INTEGER) {
		fwrite(&tag->intvalue, 4, 1, file);
	}
	
	return ferror(file);
}

void CTag::DumpToStdout() const
{
	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;
	}
}

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

CInvalidPacket::CInvalidPacket(const char* reason)
{
	// that was _bad_ practice unless length is guaranteed - Unleashed
	// strcpy(m_acWhat, "(NULL)");

	strncpy(m_acWhat, reason ? reason : "(NULL)", sizeof(m_acWhat));
	m_acWhat[sizeof(m_acWhat) - 1] = '\0';
}

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