/*
 * "$Id: pqibin.cc,v 1.4 2007-02-18 21:46:49 rmf24 Exp $"
 *
 * 3P/PQI network interface for RetroShare.
 *
 * Copyright 2004-2006 by Robert Fernie.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License Version 2 as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 *
 * Please report all bugs and problems to "retroshare@lunamutt.com".
 *
 */

#include "pqi/pqibin.h"
#include "pqi/authssl.h"
#include "util/rsnet.h"
#include "util/rsdir.h"

// #define DEBUG_PQIBIN

BinFileInterface::BinFileInterface(const char *fname, int flags)
	:bin_flags(flags), buf(NULL), hash(NULL), bcount(0)
{
	/* if read + write - open r+ */
	if ((bin_flags & BIN_FLAGS_READABLE) &&
		(bin_flags & BIN_FLAGS_WRITEABLE))
	{
		buf = RsDirUtil::rs_fopen(fname, "rb+");
		/* if the file don't exist */
		if (!buf)
		{
			buf = RsDirUtil::rs_fopen(fname, "wb+");
		}

	}
	else if (bin_flags & BIN_FLAGS_READABLE)
	{
		buf = RsDirUtil::rs_fopen(fname, "rb");
	}
	else if (bin_flags & BIN_FLAGS_WRITEABLE)
	{
		// This is enough to remove old file in Linux...
		// but not in windows.... (what to do)
		buf = RsDirUtil::rs_fopen(fname, "wb");
		fflush(buf); /* this might help windows! */
	}
	else
	{	
		/* not read or write! */
	}

	if (buf)
	{
#ifdef DEBUG_PQIBIN
		std::cerr << "BinFileInterface: " << (void*)this << ": openned file " << fname << std::endl;
#endif
		/* no problem */

		/* go to the end */
		fseek(buf, 0L, SEEK_END);
		/* get size */
		size = ftell(buf);
		/* back to the start */
		fseek(buf, 0L, SEEK_SET);
	}
	else
	{
		size = 0;
	}

	if (bin_flags & BIN_FLAGS_HASH_DATA)
	{
		hash = new pqihash();
	}
}


BinFileInterface::~BinFileInterface()
{
	if (buf)
	{
		fclose(buf);
#ifdef DEBUG_PQIBIN
		std::cerr << "BinFileInterface: " << (void*)this << ": closed file " << std::endl;
#endif
	}

	if (hash)
	{
		delete hash;
	}
}

int BinFileInterface::close()
{
	if (buf)
	{
		fclose(buf);
#ifdef DISTRIB_DEBUG
		std::cerr << "BinFileInterface: " << (void*)this << ": closed file " << std::endl;
#endif
		buf = NULL;
	}
	return 1;
}
		
int	BinFileInterface::senddata(void *data, int len)
{
	if (!buf)
		return -1;

	if (1 != fwrite(data, len, 1, buf))
	{
		return -1;
	}
	if (bin_flags & BIN_FLAGS_HASH_DATA)
	{  
		hash->addData(data, len);
		bcount += len;
	}
	return len;
}

int	BinFileInterface::readdata(void *data, int len)
{
	if (!buf)
		return -1;

	if (1 != fread(data, len, 1, buf))
	{
		return -1;
	}
	if (bin_flags & BIN_FLAGS_HASH_DATA)
	{  
		hash->addData(data, len);
		bcount += len;
	}
	return len;
}

std::string  BinFileInterface::gethash()
{
	std::string hashstr;
	if (bin_flags & BIN_FLAGS_HASH_DATA)
	{  
		hash->Complete(hashstr);
	}
	return hashstr;
}

uint64_t BinFileInterface::bytecount()
{
	if (bin_flags & BIN_FLAGS_HASH_DATA)
	{  
		return bcount;
	}
	return 0;
}

int BinFileInterface::getFileSize()
{
	return size;
}



BinEncryptedFileInterface::BinEncryptedFileInterface(const char* fname, int flags)
	: BinFileInterface(fname, flags), data(NULL), haveData(false), sizeData(0), cpyCount(0)
{
}

BinEncryptedFileInterface::~BinEncryptedFileInterface()
{
	if((sizeData > 0) && data != NULL)
	{
		delete[] data;
	}

}

int BinEncryptedFileInterface::senddata(void* data, int len)
{

	char* encrytedData = NULL;
	int encDataLen = 0;

	// encrypt using own ssl public key
	if(len > 0)
		AuthSSL::getAuthSSL()->encrypt((void*&)encrytedData, encDataLen, data, len, AuthSSL::getAuthSSL()->OwnId());
	else
		return -1;


	if((encDataLen > 0) && (encrytedData != NULL))
	{
		BinFileInterface::senddata(encrytedData, encDataLen);
		delete[] encrytedData;
	}
	else
	{
		return -1;
	}

	return len;
}


int BinEncryptedFileInterface::readdata(void* data, int len)
{
	// to respect the inherited behavior of BinInterface
	// the whole file is read and decryped and store to be read by subsequent calls
	char* encryptedData = NULL;
	int encrypDataLen = 0;


	if(!haveData) // read whole data for first call, or first call after close()
	{

		encrypDataLen = BinFileInterface::getFileSize();
		encryptedData = new char[encrypDataLen];

		// make sure assign was successful
		if(encryptedData == NULL)
			return -1;


		if(-1 == BinFileInterface::readdata(encryptedData, encrypDataLen))
		{
			delete[] encryptedData;
			return -1;
		}

		if((encrypDataLen > 0) && (encryptedData != NULL))
		{
				if(!AuthSSL::getAuthSSL()->decrypt((void*&)(this->data), sizeData, encryptedData, encrypDataLen))
				{
					delete[] encryptedData;
					return -1;
				}

				haveData = true;
				delete[] encryptedData;
		}


		if(len <= sizeData)
		{
			memcpy(data, this->data, len);
			cpyCount += len;
		}
		else
		{
			std::cerr << "BinEncryptedFileInterface::readData(): Error, Asking for more data than present" << std::endl;
			return -1;
		}
	}
	else
	{

		if((cpyCount + len) <= sizeData)
		{
			memcpy(data, (void *) ((this->data) + cpyCount), len);
			cpyCount += len;
		}
		else
		{
			std::cerr << "BinEncryptedFileInterface::readData(): Error, Asking for more data than present" << std::endl;
			return -1;
		}
	}

	return len;
}

int BinEncryptedFileInterface::close()
{
	if(data != NULL)
	{
		delete[] data;
		sizeData = 0;
		haveData = false;
		cpyCount = 0;
	}

	return BinFileInterface::close();
}

uint64_t BinEncryptedFileInterface::bytecount()
{
	return cpyCount;
}

bool BinEncryptedFileInterface::moretoread()
{
	if(haveData)
		return (cpyCount < sizeData);
	else
		return cpyCount < getFileSize();
}

BinMemInterface::BinMemInterface(int defsize, int flags)
	:bin_flags(flags), buf(NULL), size(defsize), 
	recvsize(0), readloc(0), hash(NULL), bcount(0)
	{
		buf = malloc(defsize);
		if (bin_flags & BIN_FLAGS_HASH_DATA)
		{
			hash = new pqihash();
		}
	}


BinMemInterface::BinMemInterface(const void *data, const int defsize, int flags)
	:bin_flags(flags), buf(NULL), size(defsize), 
	recvsize(0), readloc(0), hash(NULL), bcount(0)
	{
		buf = malloc(defsize);
		if (bin_flags & BIN_FLAGS_HASH_DATA)
		{
			hash = new pqihash();
		}

		/* just remove the const 
		 * *BAD* but senddata don't change it anyway 
		 */
		senddata((void *) data, defsize);
	}

BinMemInterface::~BinMemInterface() 
{ 
	if (buf)
		free(buf);

	if (hash)
		delete hash;
	return; 
}

int BinMemInterface::close()
{
	if (buf)
	{
		free(buf);
		buf = NULL;
	}
	size = 0;
	recvsize = 0;
	readloc = 0;
	return 1;
}

	/* some fns to mess with the memory */
int	BinMemInterface::fseek(int loc)
	{
		if (loc <= recvsize)
		{
			readloc = loc;
			return 1;
		}
		return 0;
	}


int	BinMemInterface::senddata(void *data, const int len)
	{
		if(recvsize + len > size)
		{
			/* resize? */
			return -1;
		}
		memcpy((char *) buf + recvsize, data, len);
		if (bin_flags & BIN_FLAGS_HASH_DATA)
		{  
			hash->addData(data, len);
			bcount += len;
		}
		recvsize += len;
		return len;
	}

		
int	BinMemInterface::readdata(void *data, int len)
	{
		if(readloc + len > recvsize)
		{
			/* no more stuff? */
			return -1;
		}
		memcpy(data, (char *) buf + readloc, len);
		if (bin_flags & BIN_FLAGS_HASH_DATA)
		{  
			hash->addData(data, len);
			bcount += len;
		}
		readloc += len;
		return len;
	}



std::string  BinMemInterface::gethash()
	{
		std::string hashstr;
		if (bin_flags & BIN_FLAGS_HASH_DATA)
		{  
			hash->Complete(hashstr);
		}
		return hashstr;
	}


uint64_t BinMemInterface::bytecount()
{
	if (bin_flags & BIN_FLAGS_HASH_DATA)
	{  
		return bcount;
	}
	return 0;
}
	
bool    BinMemInterface::writetofile(const char *fname)
{
	FILE *fd = RsDirUtil::rs_fopen(fname, "wb");
	if (!fd)
	{
		return false;
	}

	if (1 != fwrite(buf, recvsize, 1, fd))
	{
		fclose(fd);
		return false;
	}

	fclose(fd);

	return true;
}

bool    BinMemInterface::readfromfile(const char *fname)
{
	FILE *fd = RsDirUtil::rs_fopen(fname, "rb");
	if (!fd)
	{
		return false;
	}

	/* get size */
	::fseek(fd, 0L, SEEK_END);
	int fsize = ftell(fd);

	if (fsize > size)
	{
		/* not enough room */
		std::cerr << "BinMemInterface::readfromfile() not enough room";
		std::cerr << std::endl;

		fclose(fd);
		return false;
	}

	::fseek(fd, 0L, SEEK_SET);
	if (1 != fread(buf, fsize, 1, fd))
	{
		/* not enough room */
		std::cerr << "BinMemInterface::readfromfile() failed fread";
		std::cerr << std::endl;

		fclose(fd);
		return false;
	}

	recvsize = fsize;
	readloc = 0;
	fclose(fd);

	return true;
}


/**************************************************************************/


void printNetBinID(std::ostream &out, std::string id, uint32_t t)
{
	out << "NetBinId(" << id << ",";
	if (t == PQI_CONNECT_TCP)
	{
		out << "TCP)";
	}
	else
	{
		out << "UDP)";
	}
}


/************************** NetBinDummy ******************************
 * A test framework, 
 *
 */

#include "pqi/pqiperson.h"

const uint32_t DEFAULT_DUMMY_DELTA 	= 5;

NetBinDummy::NetBinDummy(PQInterface *parent, std::string id, uint32_t t)
       :NetBinInterface(parent, id), type(t), dummyConnected(false), 
        toConnect(false), connectDelta(DEFAULT_DUMMY_DELTA)
{ 
	return; 
}

	// Net Interface
int NetBinDummy::connect(struct sockaddr_in raddr)
{
	std::cerr << "NetBinDummy::connect(";
	std::cerr << rs_inet_ntoa(raddr.sin_addr) << ":";
	std::cerr << htons(raddr.sin_port);
	std::cerr << ") ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl; 

	std::cerr << "NetBinDummy::dummyConnect = true!";
	std::cerr << std::endl; 

	if (type == PQI_CONNECT_TCP)
	{
		std::cerr << "NetBinDummy:: Not connecting TCP";
		std::cerr << std::endl; 
		if (parent())
		{
			parent()->notifyEvent(this, CONNECT_FAILED);
		}
	}
	else if (!dummyConnected)
	{
		toConnect = true;
		connectTS = time(NULL) + connectDelta;
		std::cerr << "NetBinDummy::toConnect = true, connect in: ";
		std::cerr << connectDelta << " secs";
		std::cerr << std::endl; 
	}
	else
	{
		std::cerr << "NetBinDummy:: Already Connected!";
		std::cerr << std::endl; 
	}

	return 1;
}

int NetBinDummy::listen()
{
	std::cerr << "NetBinDummy::connect() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return 1;
}

int NetBinDummy::stoplistening()
{
	std::cerr << "NetBinDummy::stoplistening() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return 1;
}

int NetBinDummy::disconnect()
{
	std::cerr << "NetBinDummy::disconnect() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	std::cerr << "NetBinDummy::dummyConnect = false!";
	std::cerr << std::endl; 
	dummyConnected = false;

	if (parent())
	{
		parent()->notifyEvent(this, CONNECT_FAILED);
	}

	return 1;
}

int NetBinDummy::reset()
{
	std::cerr << "NetBinDummy::reset() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	disconnect();

	return 1;
}


         // Bin Interface.
int     NetBinDummy::tick()
{

	if (toConnect)
	{
		if (connectTS < time(NULL))
		{
			std::cerr << "NetBinDummy::tick() dummyConnected = true ";
			printNetBinID(std::cerr, PeerId(), type);
			std::cerr << std::endl;
			dummyConnected = true;
			toConnect = false;
			if (parent())
				parent()->notifyEvent(this, CONNECT_SUCCESS);
		}
		else
		{
			std::cerr << "NetBinDummy::tick() toConnect ";
			printNetBinID(std::cerr, PeerId(), type);
			std::cerr << std::endl;
		}
	}
	return 0;
}

int     NetBinDummy::senddata(void */*data*/, int len)
{
	std::cerr << "NetBinDummy::senddata() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	if (dummyConnected)
		return len;
	return 0;
}

int     NetBinDummy::readdata(void */*data*/, int /*len*/)
{
	std::cerr << "NetBinDummy::readdata() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return 0;
}

int     NetBinDummy::netstatus()
{
	std::cerr << "NetBinDummy::netstatus() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return 1;
}

int     NetBinDummy::isactive()
{
	std::cerr << "NetBinDummy::isactive() ";
	printNetBinID(std::cerr, PeerId(), type);
	if (dummyConnected)
		std::cerr << " true ";
	else
		std::cerr << " false ";
	std::cerr << std::endl;

	return dummyConnected;
}

bool    NetBinDummy::moretoread()
{
	std::cerr << "NetBinDummy::moretoread() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return false;
}

bool    NetBinDummy::cansend()
{
	std::cerr << "NetBinDummy::cansend() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return dummyConnected;
}

int	NetBinDummy::close()
{
	std::cerr << "NetBinDummy::close() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return 1;
}

std::string NetBinDummy::gethash()
{
	std::cerr << "NetBinDummy::gethash() ";
	printNetBinID(std::cerr, PeerId(), type);
	std::cerr << std::endl;

	return std::string("");
}

