/***************************************************************************
 *   Copyright (C) 2004 by yunfan                                          *
 *   yunfan_zg@163.com                                                     *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
 
#include "evapacket.h"
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "qq_crypt.h"

unsigned char Packet::iniKey[16] = {
			0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
			0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
			};
			
int Packet::qqNum = -1;
bool Packet::mIsUDP = true;
unsigned char *Packet::sessionKey = NULL;
unsigned char *Packet::passwordKey = NULL;
unsigned char *Packet::fileSessionKey = NULL;

Packet::Packet(const short version, const short command, const short sequence)
{
	this->version = version;
	this->command = command;
	this->sequence = sequence;
}

Packet::Packet(unsigned char *buf, int *len)
{
	int pos;
	// if it's not UDP, must be TCP
	if(!mIsUDP)	
		pos=3;
	else
		pos = 1;
	
	short tmp;
	
	memcpy(&tmp, buf+pos, 2);
	version = ntohs(tmp);
	pos+=2;
	
	memcpy(&tmp, buf+pos, 2);
	command = ntohs(tmp);
	pos+=2;
	
	memcpy(&tmp, buf+pos, 2);
	sequence = ntohs(tmp);
	pos+=2;
	
	(*len)=(*len)-pos;
	memcpy(buf, buf+pos, *len);
}

Packet::Packet(const Packet &rhs)
{
	*this = rhs;
}

Packet::~Packet()
{
}

bool Packet::operator ==( const Packet &rhs) const
{
	return (command == rhs.getCommand()) && (sequence == rhs.getSequence());
}

Packet &Packet::operator=(const Packet &rhs)
{
	this->version = rhs.getVersion();
	this->command = rhs.getCommand();
	this->sequence = rhs.getSequence();
	this->mIsUDP = rhs.isUDP();
        return *this;	
}

const int Packet::hashCode() 
{
	return ((int)sequence << 16) | command;
}

void Packet::setSessionKey(const unsigned char *skey)
{
	if(sessionKey==NULL){ 
		sessionKey = (unsigned char *)malloc(QQ_KEY_LENGTH * sizeof(unsigned char));
		if(sessionKey == NULL){
			fprintf(stderr, "Packet->setSessionKey: malloc() failed\n");
		}
	}
	memcpy(sessionKey, skey, QQ_KEY_LENGTH);
	fprintf(stderr,"session key set!\n");
}

void Packet::setPasswordKey(const unsigned char* pkey)
{
	if(passwordKey==NULL){ 
		passwordKey = (unsigned char *)malloc(QQ_KEY_LENGTH * sizeof(unsigned char));
		if(passwordKey == NULL){
			fprintf(stderr, "Packet->setPasswordKey: malloc() failed\n");
		}
	}
	memcpy(passwordKey, pkey, QQ_KEY_LENGTH);
	fprintf(stderr, "password key set!\n");
}

void Packet::setFileSessionKey(const unsigned char* fskey)
{
	if(fileSessionKey==NULL){ 
		fileSessionKey = (unsigned char *)malloc(QQ_KEY_LENGTH * sizeof(unsigned char));
		if(fileSessionKey == NULL){
			fprintf(stderr, "Packet->fileSessionKey: malloc() failed\n");
		}
	}
	memcpy(fileSessionKey, fskey, QQ_KEY_LENGTH);
	fprintf(stderr, "file session key set!\n");
}

// this will be called just right before sending in OutPacket::fill method
void Packet::clearAllKeys()
{
	if(sessionKey!=NULL) free(sessionKey);
	if(passwordKey!=NULL) free(passwordKey);
	if(fileSessionKey!=NULL) free(fileSessionKey);
	// force them to NULL now
	sessionKey = NULL;
	passwordKey = NULL;
	fileSessionKey = NULL;
}

/* =========================================================== */

// OutPacket starts here
short OutPacket::startSequenceNum = 5;   // could be any random number

OutPacket::OutPacket(const short command, const bool needAck) :
	Packet(QQ_CLIENT_VERSION, command, ((startSequenceNum++)%0xffff))
{
	this->mNeedAck = needAck;
	resendCount = 5;
}

OutPacket::OutPacket(const OutPacket &rhs) 
	: Packet(rhs)
{
	mNeedAck = rhs.needAck();
	resendCount = rhs.getResendCount();
}

bool OutPacket::fill(unsigned char *buf, int *len) 
{
	int headerLen = 0, bodyLen=0, encryptedLen = 0;
	unsigned char *bodyBuf;
	unsigned char *encrypted;
	bodyBuf   = (unsigned char *)malloc(3000* sizeof(unsigned char));
	encrypted = (unsigned char *)malloc(MAX_PACKET_SIZE * sizeof(unsigned char));
	
	headerLen = putHead(buf);	
	bodyLen   = putBody(bodyBuf);
/*	printf("before encrypted\n");
	for(int i=0; i<bodyLen; i++){
		if(!(i%8)) printf("\n%d: ",i);
		char t = bodyBuf[i];
		printf("%2x ", (uint8_t)t);
	}
	printf("\n");*/	
	encryptedLen = bodyLen;
	encryptBody(bodyBuf, bodyLen, encrypted, &encryptedLen);
	
	memcpy(buf+headerLen, encrypted, encryptedLen);
	
	buf[ headerLen + encryptedLen ] = QQ_PACKET_TAIL;
	(*len) = headerLen + encryptedLen + 1;
	
	if(!isUDP()) {  
		buf[0]=(unsigned char)((*len)>>8);
		buf[1]=(unsigned char)(0x00ff&(*len));
	}
	free(bodyBuf);
	free(encrypted);
	return true;
}

OutPacket &OutPacket::operator=( const OutPacket &rhs)
{
	*((Packet*)this) = (Packet)rhs;
	mNeedAck = rhs.needAck();
	resendCount = rhs.getResendCount();
        return *this;
}

int OutPacket::putBody(unsigned char *buf)
{
	fprintf(stderr, "In OutPacket\n"); 
	buf[0]=0; 
	return 0;
}

int OutPacket::putHead(unsigned char *buf) 
{
	int pos=0;
	if(!isUDP()){
		buf[0]=buf[1]=0;
		pos+=2;
	}
	buf[pos++]=(unsigned char)QQ_PACKET_TAG;
	
	short tmp = htons(version);
	memcpy(buf+pos, &tmp, 2);
	pos+=2; 
	
	tmp = htons(command);
	memcpy(buf+pos, &tmp, 2);
	pos+=2; 

	tmp = htons(sequence);
	memcpy(buf+pos, &tmp, 2);
	pos+=2; 
	
	int id = htonl(getQQ());
	memcpy(buf+pos, &id, 4);
	pos+=4;
		
	return pos;
}

void OutPacket::encryptBody(unsigned char *b, int length, 
                            unsigned char *body, int *bodyLen) 
{
	if(command == QQ_CMD_LOGIN) {  
		*bodyLen = length;
		memcpy(body, b, length);
	} else{
		unsigned char * sessionKey = getSessionKey();
		if(sessionKey == NULL) {     
			fprintf(stderr, "OutPacket->encryptBody: sessionkey not set yet, set encrypted length to 0\n");
			*bodyLen = 0;
			return;
		} 
		qq_crypt(ENCRYPT, b, length, sessionKey, body, bodyLen); 
	}
}

/* =========================================================== */

// InPacket Class starts here

InPacket::InPacket( unsigned char *buf, int len)
	: Packet(buf, &len),
	bodyLength(0)
{
	decryptedBuf = (unsigned char*)malloc(MAX_PACKET_SIZE * sizeof(unsigned char));
	decryptBody(buf, len - QQ_TAIL_LENGTH, decryptedBuf, &bodyLength);
}

InPacket::InPacket(const InPacket &rhs)
	: Packet(rhs)
{
	bodyLength = rhs.getLength();
	decryptedBuf = (unsigned char*)malloc(bodyLength * sizeof(unsigned char));
	memcpy(decryptedBuf, rhs.getBody(), bodyLength);
}

InPacket::~InPacket()
{
	free(decryptedBuf);
}

InPacket &InPacket::operator=( const InPacket &rhs)
{
	*((Packet*)this) = (Packet)rhs;
	bodyLength = rhs.getLength();
	decryptedBuf = (unsigned char*)malloc(bodyLength * sizeof(unsigned char));
	memcpy(decryptedBuf, rhs.getBody(), bodyLength);
        return *this;
}

void InPacket::decryptBody(unsigned char * buf, int len,
			unsigned char *decryptedBody, int *bodyLen)
{
	int ret;
	*bodyLen= MAX_PACKET_SIZE;	
	
	unsigned char * passwordKey = getPasswordKey();
	unsigned char * sessionKey = getSessionKey();
	
	if(passwordKey == NULL) {     
		fprintf(stderr, "InPacket->decryptBody: password key not set yet, set decrypted length to 0\n");
		*bodyLen = 0;
		return;
	}
	if((command != QQ_CMD_LOGIN) && (sessionKey == NULL)) {     
		fprintf(stderr, "InPacket->decryptBody: session key not set yet, set decrypted length to 0\n");
		command = -1;
		*bodyLen = 0;
		return;
	} 
	if(command == QQ_CMD_LOGIN) {  
		ret = qq_crypt(DECRYPT, buf, len, passwordKey, decryptedBody, bodyLen);
		if(ret == 0){  
			*bodyLen= MAX_PACKET_SIZE; 
			ret = qq_crypt(DECRYPT, buf, len, iniKey, decryptedBody, bodyLen);  
		}
	} else {
		ret = qq_crypt(DECRYPT, buf, len, sessionKey, decryptedBody, bodyLen);
		if(ret == 0)
			ret = qq_crypt(DECRYPT, buf, len, passwordKey, decryptedBody, bodyLen);             
		
	}
// 	printf("after decrypted\n");
// 	for(int i=0; i<*bodyLen; i++){
// 		if(!(i%8)) printf("\n%d: ",i);
// 		char t = decryptedBody[i];
// 		printf("%2x ", (uint8_t)t);
// 	}
// 	printf("\n");
}

void InPacket::setInPacket( const InPacket * packet )
{
	version = packet->getVersion();
	command = packet->getCommand();
        sequence = packet->getSequence();
	bodyLength = packet->getLength();
	decryptedBuf = (unsigned char*)malloc(bodyLength * sizeof(unsigned char));
	memcpy(decryptedBuf, packet->getBody(), bodyLength);        
}
