/***************************************************************************
                          ctransfer.cpp  -  description
                             -------------------
    begin                : Fri Feb 22 2002
    copyright            : (C) 2002-2003 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

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

#include "ctransfer.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#ifndef WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#else
#include <io.h>
#endif

#include "dclib.h"
#include "dcobject.h"
#include "cmessagehandler.h"
#include "cencrypt.h"
#include "core/cdir.h"
#include "core/cbytearray.h"
#include "cconfig.h"
#include "core/platform.h"
#include "cfilemanager.h"
#include "dclib-ssl-use.h"

#include "core/clogfile.h"

/* in 0.3.7 it was just m_bChunk */
#define CHUNKS_SUPPORTED ( m_MessageSupports.m_bADCGet || m_MessageSupports.m_bXMLBZList || m_MessageSupports.m_bChunk || (m_MessageSupports.m_bZBlock && CConfig::Instance()->GetCompressedTransfers()) )

CTransfer::CTransfer( bool listener )
{
	pCallback = 0;

	pByteArray = new CByteArray();

	m_bListener   = listener;
	eMedium       = eltNONE;
	eSrcDirection = edNONE;
	eDstDirection = edNONE;
	eMode         = estNONE;

	m_nStartPosition = 0;
	m_nEndPosition   = 0;
	m_nSrcLevel      = 0;
	m_nDstLevel      = 0;
	starttime.tv_sec = 0;
	starttime.tv_usec= 0;
	m_nFileSize      = 0;
	m_nTransfered    = 0;
	m_nTransferRate  = 0;
	m_nChunkSize     = 0;

	m_nTransferID = 0;

	m_bIdle = true;
	m_eTransferState = etsNONE;

	m_eTransferType = ettNONE;

	// init transferrate
	memset(&m_AverageTransferRate,0,sizeof(m_AverageTransferRate));
	m_AverageTransferRate.index=0;
	gettimeofday(&m_AverageTransferRate.tv[0],NULL);
	
	m_nReadFileBufferSize = CConfig::Instance()->GetReadFileBufferSize();
	
	m_nZlibStatus = 0;
	m_bDisableXML = false;
}

CTransfer::~CTransfer()
{
	SetCallBackFunction(0);

	TransferMutex.Lock();

	delete pByteArray;
	pByteArray = 0;

	m_File.Close();

	TransferMutex.UnLock();
	
	/* happens whenever not finished transfer is disconnected */
	/* if ( m_nZlibStatus == 1 ) // -1 error status will have already been reported elsewhere
	{
		DPRINTF("CTransfer::~CTransfer: zlib not finished\n");
	} */
}

/** */
int CTransfer::CallBack_SendObject( CDCMessage * DCMessage )
{
	int err;

	if ( pCallback != 0 )
	{
		err = pCallback->notify( this, DCMessage );
	}
	else
	{
		err = DC_CallBack( DCMessage );
	}

	if ( err == -1 )
	{
		DPRINTF("CTransfer: CallBack failed (state)...\n");
		delete DCMessage;
	}

	return err;
}

/** */
int CTransfer::CallBack_SendError( CString msg )
{
	CMessageError * Object = new CMessageError();

	Object->m_sError = msg;

	return CallBack_SendObject(Object);
}

/** */
void CTransfer::SetBuffer( CByteArray *ba )
{
	TransferMutex.Lock();
	pByteArray->Append(ba->Data(),ba->Size());
	TransferMutex.UnLock();
}

/** */
void CTransfer::ClearAndAppendBuffer( const unsigned char * data, const unsigned long size )
{
	TransferMutex.Lock();
	pByteArray->SetSize(0);
	pByteArray->Append( data, size );
	TransferMutex.UnLock();
}

/** */
long CTransfer::GetBuffer( CByteArray *ba )
{
	TransferMutex.Lock();
	ba->Append(pByteArray->Data(),pByteArray->Size());
	long i = pByteArray->Size();
	TransferMutex.UnLock();
	return i;
}

/** */
bool CTransfer::SaveBufferToFile( const CString & filename )
{
	TransferMutex.Lock();
	bool res = pByteArray->SaveToFile( filename );
	TransferMutex.UnLock();
	return res;
}

/** */
void CTransfer::InitTime()
{
	TransferMutex.Lock();
	gettimeofday(&starttime,NULL);
	TransferMutex.UnLock();
}

/** */
ulonglong CTransfer::GetTransferrate()
{
	TransferMutex.Lock();

	ulonglong r;

	if ( (starttime.tv_sec == 0) || m_bIdle )
	{
		r = 0;
	}
	else
	{
		r = GetTraffic();
	}

	TransferMutex.UnLock();

	return r;
}

/** */
ulonglong CTransfer::GetBytesForTransferrate( ulonglong rate )
{
	TransferMutex.Lock();

	if ( starttime.tv_sec == 0 )
	{
		TransferMutex.UnLock();
		return 0;
	}

	ulonglong r = GetTraffic();

	if ( r < rate )
	{
		r = rate;
	}
	else if ( r > rate )
	{
		r = 0;
	}

	TransferMutex.UnLock();

	return r;
}

/** */
void CTransfer::AddTraffic( long n )
{
	int i;
	ulonglong t1,t2;
	struct timeval tp;

	m_nTransfered += n;

	gettimeofday(&tp,NULL);

	t1 = ((tp.tv_sec*1000)+(tp.tv_usec/1000));

	i = m_AverageTransferRate.index;

	t2 = ((m_AverageTransferRate.tv[i].tv_sec*1000)+(m_AverageTransferRate.tv[i].tv_usec/1000));

	if ( (t1-t2) > 1000 )
	{
		i++;

		if ( i >= 10 )
		{
			i = 0;
		}

		m_AverageTransferRate.index = i;
		m_AverageTransferRate.tv[i] = tp;
		m_AverageTransferRate.transfered[i] = 0;
	}

	m_AverageTransferRate.transfered[i] += n;
}

/** */
ulonglong CTransfer::GetTraffic()
{
	int i;
	struct timeval tp;
	ulonglong t1,t2;
	ulonglong n = 0;

	gettimeofday(&tp,NULL);

	t1 = ((tp.tv_sec*1000)+(tp.tv_usec/1000));

	i = m_AverageTransferRate.index;

	if ( i == 9 )
		i = 0;
	else
	{
		i++;
	}

	t2 = ((m_AverageTransferRate.tv[i].tv_sec*1000)+(m_AverageTransferRate.tv[i].tv_usec/1000));

	if ( (t2 == 0) && (i > 0) )
	{
		i = 0;
	}

	t2 = ((m_AverageTransferRate.tv[i].tv_sec*1000)+(m_AverageTransferRate.tv[i].tv_usec/1000));

	if ( t2 != 0 )
	{
		t1 = t1-t2;

		if ( t1 > 0 )
		{
		for(i=0,t2=0;i<10;i++)
		{
			n += m_AverageTransferRate.transfered[i];
		}

		n = (n*1000)/t1;
		}
	}
	else
	{
		DPRINTF("time is null\n");
	}

	return n;
}

/** */
void CTransfer::ConnectionState( eConnectionState state )
{
	CMessageConnectionState * Object = new CMessageConnectionState();

	Object->m_eState   = state;
	Object->m_sMessage = GetSocketError();

	if ( state == estCONNECTED )
	{
		eMode = estTRANSFERHANDSHAKE;

		if ( sNick.NotEmpty() )
		{
			SendMyNick( sNick, sHubHost );
		}
	}
	else if ( state == estSSLCONNECTED )
	{
		if ( (GetSocketMode() == esmSSLCLIENT) || (GetSocketMode() == esmSSLSERVER) )
		{
			DPRINTF("change to old ssl mode success\n");

			CallBack_SendObject(new CMessageKey());
		}
		else if ( (GetSocketMode() == esmFULLSSLCLIENT) || (GetSocketMode() == esmFULLSSLSERVER) )
		{
			DPRINTF("new ssl mode connected\n");
			eMode = estTRANSFERHANDSHAKE;
			
			if ( sNick.NotEmpty() )
			{
				SendMyNick( sNick, sHubHost );
			}
		}
		else
		{
			DPRINTF("CTransfer::ConnectionState: unknown SSL socket mode %d\n", GetSocketMode());
		}
	}
	else if ( state == estDISCONNECTED )
	{
		eMode = estNONE;
	}

	CallBack_SendObject(Object);
}

/** */
int CTransfer::StartDownload( CString dstfile, ulonglong startposition, ulonglong endposition, ulonglong size, ulonglong chunksize, CString srcfile, CString adcTTH )
{
	if ( (srcfile.IsEmpty()) && (eMedium == eltFILE) )
	{
		DPRINTF("ctransfer: wrong mode (empty file) %d\n",eMedium);
		return -1;
	}

	if ( m_bIdle == false )
	{
		DPRINTF("ctransfer: other transfer is running\n");
		return -1;
	}

	if ( eMode == estTRANSFERUPLOAD )
	{
		DPRINTF("ctransfer: wrong transfer mode\n");
		return -1;
	}

	/* this never happens anymore, fixed in 0.3.18 */
	if (adcTTH.Left(4).ToUpper() == "TTH:")
	{
		DPRINTF("CTransfer::StartDownload: Removed TTH: prefix from TTH\n");
		adcTTH = adcTTH.Mid(4, adcTTH.Length() - 4);
	}
	
	TransferMutex.Lock();
	eMode            = estTRANSFERDOWNLOAD;
	m_nStartPosition = startposition;
	m_nEndPosition   = endposition;
	m_nFileSize      = size;
	sDstFilename     = dstfile;
	sSrcFilename     = srcfile;
	m_nTransfered    = 0;
	m_nChunkSize     = chunksize;
	m_sTTH           = adcTTH;
	TransferMutex.UnLock();

	/** set starttime */
	InitTime();

	if ( eMedium == eltCLIENTVERSION )
	{
		m_bIdle = true;
	}
	else
	{
		if ( GetDstFilename() == DC_USER_FILELIST )
		{
			m_nDataType = 0;
			
			if ( m_MessageSupports.m_bXMLBZList )
			{
				SetSrcFilename(DC_USER_FILELIST_XMLBZ);
				
				if ( m_MessageSupports.m_bADCGet )
				{
					SendADCGet( eAdcFile, CString(), 0, -1, false, sSrcFilename );
				}
				else
				{
					SendUGetBlock( sSrcFilename, startposition );
				}
			}
			else
			{
				if ( m_MessageSupports.m_bBZList )
				{
					SetSrcFilename(DC_USER_FILELIST_BZ);
				}
				else
				{
					SetSrcFilename(DC_USER_FILELIST_HE3);
				}
				
				SendGet( sSrcFilename, startposition+1 );
			}
		}
		else
		{
			// if we don't have a TTH, we cannot do ADCGet
			// we'll try to fallback to other get commands, even though these may not work with DC++ >= 0.696
			if ( m_MessageSupports.m_bADCGet && m_MessageSupports.m_bTTHF && adcTTH.NotEmpty() )
			{
				if ( m_MessageSupports.m_bZLIG && CConfig::Instance()->GetCompressedTransfers() )
				{
					m_nDataType = 1;
					SendADCGet( eAdcFile, adcTTH, startposition, chunksize, true );
				}
				else
				{
					m_nDataType = 0;
					SendADCGet ( eAdcFile, adcTTH, startposition, chunksize, false );
				}
			}
			else if ( m_MessageSupports.m_bXMLBZList )
			{
				if ( m_MessageSupports.m_bZBlock && CConfig::Instance()->GetCompressedTransfers() )
				{
					m_nDataType = 1;
					SendUGetZBlock( sDstFilename, startposition, chunksize );
				}
				else
				{
					m_nDataType = 0;
					SendUGetBlock( sDstFilename, startposition, chunksize );
				}
			}
			else if ( m_MessageSupports.m_bZBlock && CConfig::Instance()->GetCompressedTransfers() )
			{
				m_nDataType = 1;
				SendGetZBlock( sDstFilename, startposition, chunksize );
			}
			else if ( m_MessageSupports.m_bChunk )
			{
				m_nDataType = 0;
				SendGet( sDstFilename, startposition+1, chunksize );
			}
			else
			{
				m_nDataType = 0;
				SendGet( sDstFilename, startposition+1 );
			}
		}
	}

	return 0;
}

/** */
int CTransfer::StartUpload( CString dstfile, ulonglong length, ulonglong pos, ulonglong chunksize, CString srcfile, bool uget, bool adcget, CString adcTTH, bool compress )
{
	if ( srcfile.IsEmpty() && (eMedium == eltFILE) )
	{
		DPRINTF("CTransfer::StartUpload: source file empty\n");
		return -1;
	}

	if ( m_bIdle == false )
	{
		DPRINTF("ctransfer: other transfer is running\n");
		return -1;
	}

	if ( eMode == estTRANSFERDOWNLOAD )
	{
		DPRINTF("ctransfer: wrong transfer mode\n");
		return -1;
	}

	TransferMutex.Lock();
	eMode            = estTRANSFERUPLOAD;
	m_nStartPosition = pos;
	m_nEndPosition   = length;
	m_nFileSize      = length;
	sDstFilename     = dstfile;
	sSrcFilename     = srcfile;
	m_sTTH           = adcTTH;
	TransferMutex.UnLock();
	
	m_nTransfered = 0;
	// if chunksize 0 we use the len
	if ( chunksize == 0 )
		m_nChunkSize = length-pos;
	else
		m_nChunkSize = chunksize;
	m_nPendingSendBytes = 0;

	/** set starttime */
	InitTime();
	
	if ( compress )
	{
		m_nDataType = 2;
	}
	else
	{
		m_nDataType = 0;
	}
	
	if ( adcget )
	{
		if ( GetMedium() == eltTTHL )
		{
			SendADCSnd( eAdcTTHL, adcTTH, pos, m_nChunkSize, compress );
		}
		else if ( GetMedium() == eltLIST )
		{
			SendADCSnd( eAdcList, CString(), pos, m_nChunkSize, compress, dstfile );
		}
		else if ( GetMedium() == eltBUFFER )
		{
			SendADCSnd( eAdcFile, CString(), pos, m_nChunkSize, compress, dstfile );
		}
		else if ( adcTTH.IsEmpty() ) // ADCGet without TTH
		{
			SendADCSnd( eAdcFile, CString(), pos, m_nChunkSize, compress, "/" + dstfile );
		}
		else
		{
			SendADCSnd( eAdcFile, adcTTH, pos, m_nChunkSize, compress );
		}
		
		if ( (eMode != estTRANSFERUPLOAD) || (m_bIdle == false) )
		{
			DPRINTF("Warning: not in uploadmode/idle ADCSND [%d/%d]\n",eMode,m_bIdle);
		}
		else
		{
			DoInitUpload();
		}
	}
	else if ( uget || compress )
	{
		SendSending(m_nChunkSize);
		
		// don't know why this does not work, instead code out has been copied out of HandleMessage()
		//HandleMessage("$Send|", length);
		
		if ( (eMode != estTRANSFERUPLOAD) || (m_bIdle == false) )
		{
			DPRINTF("Warning: not in uploadmode/idle SEND [%d/%d]\n",eMode,m_bIdle);
		}
		else
		{
			DoInitUpload();
		}
	}
	else
	{
		/* according to DC++ 0.306 it is file size and not chunk size */
		SendFileLength(length);
	}

	return 0;
}

/** */
bool CTransfer::DoInitDownload()
{
	CDir dir;
	bool b = false;
	
	if ( eMedium == eltFILE )
	{
		int mode = 0;

		m_File.Close();

		dir.SetPath(CString());

		if ( dir.IsFile(sSrcFilename) == false )
		{
			// create file
			mode |= IO_CREAT;
		}

		mode |= IO_RAW | IO_WRITEONLY;

		if ( m_File.Open( sSrcFilename, mode, MO_IRUSR|MO_IWUSR|MO_IRGRP|MO_IWGRP|MO_IROTH|MO_IWOTH ) == false )
		{
			CallBack_SendError(strerror(errno));
			perror("File open");
		}
		else
		{
			if ( CConfig::Instance()->GetCreateFile() )
			{
				if ( ((mode & IO_CREAT) == IO_CREAT) &&
				     (m_nFileSize > 0) )
				{
					if ( m_File.Seek(m_nFileSize-1,SEEK_SET) )
					{
						if ( m_File.Write("\0",1) == 1 )
						{
							b = true;
						}
					}
				}
			}
			else
			{
				b = true;
			}
				
			if ( b )
			{
				// seek to the startposition
				b = m_File.Seek(m_nStartPosition,SEEK_SET);
			}
			else
			{
				CallBack_SendError(strerror(errno));
				perror("File seek");
				m_File.Close();
			}
		}
	}
	else
	{
		pByteArray->SetSize(0);
		b = true;
	}

	return b;
}

/** */
int CTransfer::HandleMessage( char * c, int )
{
	int p=0;
	eDCMessage type;
	CDCMessage * Object = 0;

	CMessageHandler MessageHandler;

	CString s = c;

	while( (m_bIdle || (eMode == estTRANSFERUPLOAD)) &&
	       ((type=MessageHandler.Parse(&s,p,&Object)) != DC_MESSAGE_PARSE_ERROR) )
	{
//		DPRINTF("transfer: handle message: %d\n",type);

		if ( !Object )
		{
			continue;
		}

		switch (type)
		{
			case DC_MESSAGE_SENDING:
			{
				CMessageSending * msg = (CMessageSending*)Object;
				
				if ( eMode != estTRANSFERDOWNLOAD || (m_bIdle == false) )
				{
					DPRINTF("Warning: not in downloadmode/idle SENDING\n");

					delete Object;
					Object = 0;
				}
				else
				{
					m_nTransfered = 0;

					// update endposition
					if ( m_nEndPosition == 0 )
					{
						m_nEndPosition = msg->m_nLength;
					}

					// fix chunksize
					if ( m_nChunkSize != msg->m_nLength )
					{
						m_nChunkSize = msg->m_nLength;
					}

					// fixme why was this commented out?
					if (m_nFileSize == 0)
					{
						m_nFileSize = msg->m_nLength;
					}

					// start transfer
					if ( DoInitDownload() )
					{
						m_bIdle = false;
					}
				}

				break;
			}
			
			case DC_MESSAGE_ADCSND:
			{
				CMessageADCSnd * msg = (CMessageADCSnd*)Object;
				if ( eMode != estTRANSFERDOWNLOAD || (m_bIdle == false) )
				{
					DPRINTF("Warning: not in downloadmode/idle ADCSND\n");

					delete Object;
					Object = 0;
				}
				else
				{
					m_nTransfered = 0;
					
					if ( m_nEndPosition == 0 )
					{
						m_nEndPosition = msg->m_nSize;
					}
					
					if ( m_nChunkSize != msg->m_nSize )
					{
						if ( m_nChunkSize == 0 )
						{
							m_nChunkSize = msg->m_nSize;
						}
						else
						{
							DPRINTF( "Error: wrong length: Requested=%llu Got=%lld\n", m_nChunkSize, msg->m_nSize );
							CallBack_SendError("Remote client sent wrong data - incorrect length!");
							Disconnect();
							
							delete Object;
							Object = 0;
							
							break;
						}
					}
					
					if ( m_nStartPosition != msg->m_nPos )
					{
						DPRINTF( "Error: wrong start position: Requested=%llu Got=%llu\n", m_nStartPosition, msg->m_nPos );
						CallBack_SendError("Remote client sent wrong data - incorrect start position!");
						Disconnect();
						
						delete Object;
						Object = 0;
						
						break;
					}
					
					if (m_nFileSize == 0)
					{
						m_nFileSize = msg->m_nSize;
					}
					
					// not sure if this is really necessary
					if ( msg->m_bZlib )
					{
						m_nDataType = 1;
					}
					else
					{
						m_nDataType = 0;
					}
					
					if ( DoInitDownload() )
					{
						m_bIdle = false;
					}
				}
				
				break;
			}
			case DC_MESSAGE_FILELENGTH:
			case DC_MESSAGE_LISTLEN:
			{
				CMessageFileLength * msg = (CMessageFileLength*)Object;

				if ( eMode != estTRANSFERDOWNLOAD || (m_bIdle == false) )
				{
					DPRINTF("Warning: not in downloadmode/idle FILELENGTH/LISTLEN\n");

					delete Object;
					Object = 0;
				}
				else
				{
					m_nTransfered = 0;

					// update endposition
					if ( (m_nEndPosition == m_nFileSize) || (m_nEndPosition == 0) )
					{
						m_nEndPosition = msg->m_nFileLength;
					}

					// fix chunksize
					if ( (m_nChunkSize == m_nFileSize) || (m_nChunkSize == 0) )
					{
						m_nChunkSize = msg->m_nFileLength;
					}

					m_nFileSize = msg->m_nFileLength;

					// start transfer
					if ( DoInitDownload() )
					{
						m_bIdle = false;

						SendSend();
					}
				}

				break;
			}

			case DC_MESSAGE_SEND:
			{
				if ( (eMode != estTRANSFERUPLOAD) || (m_bIdle == false) )
				{
					DPRINTF("Warning: not in uploadmode/idle SEND [%d/%d]\n",eMode,m_bIdle);

					delete Object;
					Object = 0;
				}
				else
				{
					DoInitUpload();
				}

				break;
			}

			case DC_MESSAGE_ERROR:
			{
				break;
			}

			case DC_MESSAGE_DIRECTION:
			{
				CMessageDirection * msg = (CMessageDirection*)Object;

				if ( eDstDirection != edNONE )
				{
					// remote will change the direction ... hoho ;-)
					if ( eDstDirection != msg->m_eDirection )
					{
						Disconnect(true);
						DPRINTF("Warning: remote will change his direction (hack) ...\n");
						delete Object;
						Object = 0;
					}
				}
				else
				{
					eDstDirection = msg->m_eDirection;
					m_nDstLevel   = msg->m_nLevel;
				}

				break;
			}

			case DC_MESSAGE_MYNICK:
			{
				CMessageMyNick * msg = (CMessageMyNick*)Object;

				if ( eMode == estTRANSFERHANDSHAKE )
				{
					sDstNick = msg->m_sNick;
#if DCLIB_HAS_SSL == 1			
					SendSSLInfo();
#endif					
					m_nSrcLevel = rand()%0x7FFF;
				}
				else
				{
					DPRINTF("Warning: Nick msg from '%s' only allowed in handshake",msg->m_sNick.Data());

					delete Object;
					Object = 0;
				}

				break;
			}

			case DC_MESSAGE_LOCK:
			{
				CString s;
				CMessageLock * msg = (CMessageLock*)Object;

				// now we need to check some broken clients and later limit support
				// 0.68 had minor version 68, not 680!
				DPRINTF("LOCK %d %d %d\n",msg->m_eClientVersion,msg->m_nVersionMajor,msg->m_nVersionMinor);
				m_bDisableXML = ((msg->m_eClientVersion == eucvDCPP) && (msg->m_nVersionMajor == 0) && (msg->m_nVersionMinor >= 307) && (msg->m_nVersionMinor <= 403));

				if ( eSrcDirection != edNONE )
				{
					// send supports command
					if ( msg->m_bExtProtocol )
					{
						// slots for small files always enabled
						std::vector<CString> supported;
						supported.push_back("MiniSlots");
						
						if ( (CFileManager::Instance()->GetShareBufferSize(esbtXMLBZ) > 0) && ((CConfig::Instance()->GetDisableHashList() == false) || (CConfig::Instance()->GetDisableXMLListWithoutTTH() == false)) )
						{
							supported.push_back("XmlBZList");
						}
						
						// ADCGet needed for compatibility with DC++ >= 0.696
						// TTHF needed for ADCGet (ADCGet by filename did exist, but no longer used by current DC++)
						// TTHL support also needed for compatibility with DC++
						// These stay enabled if just the hash list is disabled, because that only applies to the
						// hashing of new files, files already hashed remain available.
						if ( (CConfig::Instance()->GetDisableHashList() == false) || (CConfig::Instance()->GetDisableADCGetWithoutTTH() == false) )
						{
							supported.push_back("ADCGet");
							supported.push_back("TTHF");
							supported.push_back("TTHL");
						}

						if ( CConfig::Instance()->GetCompressedTransfers() )
						{
							supported.push_back("ZLIG");
						}

						if ( CConfig::Instance()->GetEnableObsoleteExt() )
						{
							// chunk, obsolete dclib extension for segmented downloading
							supported.push_back("CHUNK");
							
							// bzip2 compressed text filelist
							if ( CFileManager::Instance()->GetShareBufferSize(esbtBZ) > 0 )
							{
								supported.push_back("BZList");
							}
							
#if DCLIB_HAS_SSL == 1
							/* Do not add old SSL supports if we are already using new full SSL mode */
							CDir dir;
							if ( CConfig::Instance()->GetOldSSLSupport() &&
								(GetSocketMode() == esmSOCKET) &&
								dir.IsFile(CConfig::Instance()->GetTransferCert(),false) &&
								dir.IsFile(CConfig::Instance()->GetTransferKey(),false)
							   )
							{
								supported.push_back("SSL");
							}
#endif
							if ( CConfig::Instance()->GetCompressedTransfers() )
							{
								supported.push_back("GetZBlock");
							}
						}

						/* It's never empty because MiniSlots is always present */
						SendSupports( supported, CConfig::Instance()->GetShuffleSupports() );
					}

					SendDirection( eSrcDirection, m_nSrcLevel );

					s.Empty();
					CEncrypt::Encrypt(msg->m_sData,s);

					SendKey(s);
				}
				else
				{
					Disconnect(true);
				}

				break;
			}

			case DC_MESSAGE_SUPPORTS:
			{
				CMessageSupports * msg = (CMessageSupports*)Object;
				
				if ( m_bDisableXML )
				{
					// disable xml list for dc++ 0.401
					DPRINTF("CTransfer::HandleMessage: Disabled XML filelist support for DC++ 0.307 to 0.403\n");
					msg->m_bXMLBZList = false;
				}
				
				m_MessageSupports = *msg;

				break;
			}

			case DC_MESSAGE_KEY:
			{
#if DCLIB_HAS_SSL == 1
				// start ssl socket
				if ( m_MessageSupports.m_bSSL &&
				     CConfig::Instance()->GetTransferCert().NotEmpty() &&
				     CConfig::Instance()->GetTransferKey().NotEmpty() &&
				     CConfig::Instance()->GetOldSSLSupport() &&
				     (GetSocketMode() == esmSOCKET) )
				{
					bool b;
					if ( m_bListener )
						b = ChangeSocketMode(esmSSLSERVER, CConfig::Instance()->GetTransferCert(), CConfig::Instance()->GetTransferKey() );
					else
						b = ChangeSocketMode(esmSSLCLIENT, CConfig::Instance()->GetTransferCert(), CConfig::Instance()->GetTransferKey() );
					DPRINTF("change to ssl mode %d\n",b);
					delete Object;
					Object = 0;
				}
#endif

				break;
			}

			case DC_MESSAGE_MAXEDOUT:
			{
				break;
			}

			case DC_MESSAGE_CANCEL:
			{
				if ( (eMode == estTRANSFERUPLOAD) && (m_bIdle == false) )
				{
					if ( eMedium == eltFILE )
					{
						m_File.Close();
					}

					// free buffer
					pByteArray->SetSize(0);

					// stop transfer
					m_bIdle = true;
				}
				else
				{
					DPRINTF("Warning: Cancel msg not in uploadmode/idle SEND [%d/%d]\n",eMode,m_bIdle);

					delete Object;
					Object = 0;
				}

				break;
			}

			default:
			{
				//DPRINTF("ctransfer: unknown message %d\n",type);
			}
		}

		if (Object)
		{
			CallBack_SendObject(Object);
		}
	}

	return p;
}

/** */
bool CTransfer::GetEncrypted()
{
	bool res = false;

#if DCLIB_HAS_SSL == 1
	if ( GetSocketMode() == esmSOCKET )
	{
		res = false;
	}
	else
	{
		res = true;
	}
#endif

	return res;
}

/** */
void CTransfer::DataSend()
{
	int l = 0;
	ulonglong len=0;
	ulonglong sendbyte;
	ulonglong freebuf;
	int lo;

	if ( (eMode == estTRANSFERUPLOAD) && (m_bIdle == false) )
	{
		for(lo=0;(lo<5) && (!m_bIdle);lo++)
		{

		sendbyte = 0;

		// check send rate
		if ( m_nTransferRate != 0 )
		{
			sendbyte = GetBytesForTransferrate(m_nTransferRate);

			if ( sendbyte == 0 )
			{
				break;
			}
		}

		freebuf = m_nReadFileBufferSize; //GetFreeSendBufferSize();
		
		// should we test for zblock also?
		if ( !CHUNKS_SUPPORTED || (m_nChunkSize == 0) )
		{
			len = m_nFileSize - m_nStartPosition - m_nTransfered;
		}
		else
		{
			len = m_nChunkSize - m_nTransfered;
		}

		if ( eMedium == eltFILE )
		{
			//DPRINTF("%llu %llu %llu %llu\n",len,lStartPosition,lChunkSize,lCurrentPosition);

			// load data from filehandle and send ...
			if ( m_nFileBufferPos == m_nFileBufferSize )
			{
				if ( len > m_nReadFileBufferSize )
				{
					len = m_nReadFileBufferSize;
				}
				
				if ( m_nDataType == 2 )
				{
					pByteArray->SetSize(0);
					m_nFileBufferSize = 0;
					
					CByteArray readbuffer;
					
					long fileBytesRead = 0;
					
					while ( pByteArray->Size() < ((len+1) / 2) )
					{
						bool more = true;
						int inlen = 0;
						int outlen = 0;
						CByteArray temp;
						
						if ( m_nTransfered + fileBytesRead >= m_nChunkSize )
						{
							/*
							 * there should not be any more data in zlib
							 * because the output buffers should always be
							 * large enough
							 */
							more = false;
							break;
						}
						else
						{
							// limit to reading only up to end of this chunk
							if ( (m_nTransfered + fileBytesRead + len) > m_nChunkSize )
							{
								len = m_nChunkSize - (m_nTransfered + fileBytesRead);
								//printf("Limiting len to %llu\n", len);
							}
							
							readbuffer.SetSize(len);
						
							long read = m_File.Read( (char *)readbuffer.Data(), (long)len );
						
							if ( read == -1 )
							{
								CallBack_SendError(strerror(errno));
								perror("CTransfer::DataSend() read error ! ");
								Disconnect(true);
								return;
							}
							else if ( read == 0 )
							{
								perror("CTransfer::DataSend() no data read ! ");
								Disconnect(true);
								return;
							}
							else
							{
								fileBytesRead += read;
								
								if ( m_nTransfered + fileBytesRead >= m_nChunkSize )
								{
									more = false;
								}
								
								temp.SetSize( m_nReadFileBufferSize * 2 );
							
								inlen = read;
								outlen = temp.Size();
								
								/*
								 * Bad things will happen if the compressed data is over twice the size of
								 * the uncompressed data, fortunately that is very unlikely.
								 */
								m_nZlibStatus = m_Deflater.DeflateBlock( (char *)readbuffer.Data(), &inlen, (char *)temp.Data(), &outlen, more );
								
								if ( ((m_nZlibStatus == 1) && more ) ||
								     ((m_nZlibStatus == 0) && (more == false)) )
								{
									if ( outlen > 0 )
									{
										pByteArray->Append( temp.Data(), outlen );
										m_nFileBufferSize += outlen;
									}
								}
								else
								{
									CString reason;
									if ( m_nZlibStatus == -1 )
									{
										reason = "zlib error";
									}
									else if ( (m_nZlibStatus == 0) && more )
									{
										reason = "had more data, but zlib finished";
									}
									else if ( (m_nZlibStatus == 1) && (more == false) )
									{
										/*
										 * if the compressed data was over twice the size of the uncompressed...
										 */
										reason = "no more data, but zlib not finished";
									}
									CallBack_SendError("Zlib compression failed: " + reason);
									printf("CTransfer::DataSend() deflate failed: %s\n", reason.Data());
									Disconnect(true);
									return;
								}
							}
						}
					}
					
					m_nFileBufferPos = 0;
					len = m_nFileBufferSize;
					
					// must either change sizes or adjust m_nTransfered
					//SetLength( GetLength() + m_nFileBufferSize - fileBytesRead );
					//SetEndPosition( GetEndPosition() + m_nFileBufferSize - fileBytesRead );
					//m_nChunkSize = m_nChunkSize + m_nFileBufferSize - fileBytesRead;
					
					m_nTransfered = m_nTransfered + fileBytesRead - m_nFileBufferSize;
					
					//printf( "Compressed: %ld to %ld\n", fileBytesRead, m_nFileBufferSize );
				}
				else // m_nDataType == 2
				{

				m_nFileBufferSize = m_File.Read( (char *)pByteArray->Data(), (long)len );

				if ( m_nFileBufferSize == -1 )
				{
					CallBack_SendError(strerror(errno));
					perror("CTransfer::DataSend() read error ! ");
					Disconnect(true);
					return;
				}
				else if ( m_nFileBufferSize == 0 )
				{
					perror("CTransfer::DataSend() no data read ! ");
					Disconnect(true);
					return;
				}
				else if ( len > (ulonglong)m_nFileBufferSize )
				{
					perror("CTransfer::DataSend() wrong length calculation ! ");
					len = m_nFileBufferSize;
				}

				m_nFileBufferPos = 0;
				
				} // m_nDataType == 2
			}

			// limit to sendbuffer
			if ( len > (ulonglong)(m_nFileBufferSize-m_nFileBufferPos) )
			{
				len = m_nFileBufferSize-m_nFileBufferPos;
			}
		}
		else if ( (eMedium == eltBUFFER) || (eMedium == eltTTHL) || (eMedium == eltLIST) )
		{
			m_nFileBufferPos = m_nTransfered;
		}

		// limit to transferrate
		if ( sendbyte != 0  )
		{
			if ( len > sendbyte )
			{
				len = sendbyte;
			}
		}

		// limit to free socket sendbuffer
		if ( len > freebuf )
		{
			len = freebuf;
		}

		// fix to pending send bytes
		if ( m_nPendingSendBytes != 0 )
		{
			len = m_nPendingSendBytes;
		}

//		DPRINTF("LEN: %d\n",len);

		/* For compressed buffer transfers, the entire buffer was compress in DoInitUpload*/
		// write data to socket
		l = Write( pByteArray->Data()+m_nFileBufferPos, (long)len, true );

		if ( l > 0 )
		{
			AddTraffic(l);

			if ( m_nPendingSendBytes != 0 )
			{
				m_nPendingSendBytes -= l;
			}
			else
			{
				m_nPendingSendBytes = len-l;
			}

			if ( eMedium == eltFILE )
			{
				m_nFileBufferPos += l;
			}

			// add traffic control
			CSocket::m_Traffic.AddTraffic(ettDATATX,len);
		}
		else if ( l == -1 )
		{
			perror("CTransfer::DataSend() write error ! ");
			Disconnect(true);
			return;
		}
		else if ( l == 0 )
		{
			if ( m_nPendingSendBytes == 0 )
			{
				m_nPendingSendBytes = len;
			}

			break;
		}

		if ( (((m_nStartPosition+m_nTransfered) == m_nFileSize) && (m_nZlibStatus == 0)) ||
		     ( CHUNKS_SUPPORTED &&
		       ((m_nChunkSize == m_nTransfered) && (m_nZlibStatus == 0))
		     )
		   )
		{
			DPRINTF("end found\n");
			if ( eMedium == eltFILE )
			{
				m_File.Close();
			}

			// free buffer
			pByteArray->SetSize(0);

			// stop transfer
			m_bIdle = true;

			// write finished upload message into log file
			if ( CConfig::Instance()->GetLogFile() && CConfig::Instance()->GetLogFinishedUploads() )
			{
				CString logmessage = "Upload: ";
				logmessage += sDstNick;
				logmessage += '@';
				logmessage += sHubHost;
				logmessage += ' ';
				if ( eMedium == eltTTHL )
				{
					logmessage += "TTHL/";
					logmessage += m_sTTH;
				}
				else
				{
					logmessage += sDstFilename;
				}
				logmessage += ' ';
				logmessage += CString::number(m_nStartPosition);
				logmessage += " -> ";
				logmessage += CString::number(m_nStartPosition+m_nTransfered);
				CLogFile::Write(CConfig::Instance()->GetLogFileName(),eltINFO,logmessage);
			}

			// stop loop
			lo = 5;
			
			/* CDownloadManager ignores all CMessageTransfer where m_nTransfered != m_nChunkSize. */
			/* Also, CDownloadManager ignores all the object members and only looks at the CTransfer. */
			CMessageTransfer * cmt = new CMessageTransfer();
			
			cmt->m_eDirection  = edUPLOAD;
			cmt->m_nTransfered = m_nStartPosition+m_nTransfered;
			cmt->m_nLength     = m_nFileSize;
			
			CallBack_SendObject(cmt);
		}

		} // for
	}
}

/** */
void CTransfer::DataTimeout()
{
	if ( (eMode == estTRANSFERDOWNLOAD) && (m_bIdle == false) )
	{
		DPRINTF("CTransfer: download timeout ...\n");

		// send string ...
		SendString("|");
	}
	else if ( eMode == estTRANSFERHANDSHAKE )
	{
		DPRINTF("CTransfer: handshake timeout ...\n");

		// handshake timeout
		Disconnect(true);
	}
	else if ( m_bIdle )
	{
		// send string ...
		SendString("|");
	}
}

/** */
void CTransfer::Notify()
{
}

/** */
void CTransfer::DataAvailable( const char * buffer, int len )
{
	int i = 0, r, p = 0;

	while ( i < len )
	{
		if ( (eMode == estTRANSFERDOWNLOAD) && (m_bIdle == false) )
		{
			while ( (i < len) && (!m_bIdle) )
			{
				if ( eMedium == eltFILE )
				{
					r = HandleFileTransfer(buffer+i,len-i);
				}
				else if ( (eMedium == eltBUFFER) || (eMedium == eltTTHL) || (eMedium == eltLIST) )
				{
					r = HandleBufferTransfer(buffer+i,len-i);
				}
				else
				{
					r = -1;
				}

				if ( r >= 0 )
				{
					i += r;
				}
				else
				{
					break;
				}

				// add traffic control
				CSocket::m_Traffic.AddTraffic(ettDATARX,r);

				// set transfer to idle on finished chunk if remote
				// support chunks or if fileend reached
				if ( ((m_nChunkSize == m_nTransfered) && (m_nZlibStatus == 0)) &&
				    ( CHUNKS_SUPPORTED ||
				     ((m_nStartPosition+m_nChunkSize) == m_nFileSize) ) )
				{
					m_bIdle = true;
					
					// send transfer message does NOT go here, moving it here
					// breaks multi download from original nmdc client microdc 0.11.0
					// which is handled by a single $Send| getting multiple chunks
				}
				
				// however, we can put the test m_nChunkSize == m_nTransfered, to
				// avoid sending transfer messages which will just be ignored
				if ( m_nChunkSize == m_nTransfered )
				{
					// send transfer message to handle new chunk-end
					CMessageTransfer * Object = new CMessageTransfer();
					Object->m_eDirection  = edDOWNLOAD;
					Object->m_nTransfered = m_nTransfered;
					Object->m_nLength     = m_nFileSize;
					CallBack_SendObject(Object);
					
					// check end - only finish if zlib has finished (m_nZlibStatus is always 0 when not using compressed transfers)
					// yes we just checked m_nChunkSize == m_nTransfered
					// but after CallBack_SendObject() m_nTransfered may have been set back to zero
					if ( (m_nChunkSize == m_nTransfered) && (m_nZlibStatus == 0) )
					{
						// if endposition not filesize and remote don't support chunks we must disconnect
						if ( ((m_nStartPosition+m_nChunkSize) != m_nFileSize) && !CHUNKS_SUPPORTED )
						{
							Disconnect();
						}

						m_bIdle = true;

						m_File.Close();
					}
				}
			}
		}
		else
		{
			/*
			 * if there is some problem, resulting in trying to
			 * parse file data as NMDC command strings, then the problem
			 * is elsewhere, and determining when the end of the
			 * segment has been reached needs to be fixed
			 */
			r = HandleControlTransfer(buffer+i,len-i);
			
			// add traffic control
			CSocket::m_Traffic.AddTraffic(ettCONTROLRX,r);
			
			i += r;
		}

		if ( i == p )
		{
			DPRINTF("WARNING: unknown data ! [%d %d]\n",i,len);
			break;
		}
		else
		{
			p = i;
		}
	}
}

/** */
int CTransfer::HandleFileTransfer( const char * buffer, int len )
{
	int i,i1;
	ulonglong l;
	CString serr;
	bool berr=false;
	CByteArray zbuffer;
	int outlen;
	int inlen;
	
	// decompress buffer ...
	if ( m_nDataType == 1 )
	{
		zbuffer.SetSize(1024*100);
		outlen = 1024*100;
		inlen  = len;
		m_nZlibStatus = m_ZLib.InflateZBlock( buffer, &inlen, (char*)zbuffer.Data(), &outlen );
		if ( m_nZlibStatus == -1 )
		{
			DPRINTF("HandleFileTransfer: inflate failed!\n");
			
			// close file
			m_File.Close();

			CallBack_SendError("Zlib decompression failed");

			// disconnect on error
			Disconnect();
			
			return -1;
		}
		
		len = outlen;
		buffer = (const char*)zbuffer.Data();
	}

	// check endposition
	if ( (len+m_nTransfered) > m_nChunkSize )
	{
		l = m_nChunkSize-m_nTransfered;
	}
	else
	{
		l = len;
	}

	i1 = 0;

	// write data
	if ( (m_File.IsOpen()) && (l > 0) )
	{
		while( i1 != l )
		{
			i = m_File.Write( buffer, l );

			if ( i != -1 )
			{
				i1 += i;
			}
			else
			{
				i1 -= m_File.GetBufferPos();

				DPRINTF("CTransfer::HandleFileTransfer: write failed with %d pending bytes\n",m_File.GetBufferPos());
				berr = true;
				serr = strerror(errno);
				break;
			}
		}
	}
	else
	{
		// TODO: error message
	}

	AddTraffic(i1);

	// handle error
	if ( berr )
	{
		i1 = -1;

		// close file
		m_File.Close();

		CallBack_SendError(serr);

		// disconnect on error
		Disconnect();
	}
	else
	{
		if ( m_nDataType == 1 )
		{
			i1 = inlen;
		}
	}

	return i1;
}

/** */
int CTransfer::HandleBufferTransfer( const char * buffer, int len )
{
	int i;
	CByteArray zbuffer;
	int outlen;
	int inlen;
	
	// decompress buffer ...
	if ( m_nDataType == 1 )
	{
		zbuffer.SetSize(1024*100);
		outlen = 1024*100;
		inlen  = len;
		m_nZlibStatus = m_ZLib.InflateZBlock( buffer, &inlen, (char*)zbuffer.Data(), &outlen );
		if ( m_nZlibStatus == -1 )
		{
			DPRINTF("HandleBufferTransfer: inflate failed!\n");
			CallBack_SendError("Zlib decompression failed");

			// disconnect on error
			Disconnect();
			
			return -1;
		}
		
		len = outlen;
		buffer = (const char*)zbuffer.Data();
	}

	// check endposition
	if ( (len+m_nTransfered) > m_nChunkSize )
	{
		i = m_nChunkSize-m_nTransfered;
	}
	else
	{
		i = len;
	}

	pByteArray->Append((const unsigned char*)buffer,i);

	AddTraffic(i);

	if ( m_nDataType == 1 )
	{
		i = inlen;
	}

	return i;
}

/** */
int CTransfer::HandleControlTransfer( const char * buffer, int len )
{
	CString s;
	int i,p = 0;

	// search first '|'
	for(i=0;i<len;i++)
	{
		if ( buffer[i] == '|' )
		{
			s = sBuffer;
			s += CString().Set(buffer,i+1);
			break;
		}
	}
	
	if ( i == len )
		i = -1;
	i++;

	if ( i > 0 )
	{
		p = HandleMessage( s.Data(), i );

		// some clients send data after filelength (e.g. nmdc 2.02)
		if ( (p < s.Length()) && (!m_bIdle) && (eMode == estTRANSFERDOWNLOAD) )
		{
			DPRINTF("WARNING: transfer->client send unwanted data ! [%d %d %d]\n",i,p,len);
		}
	}

	if ( (i == s.Length()) || ((!m_bIdle) && (eMode == estTRANSFERDOWNLOAD)) )
	{
		sBuffer.Empty();
	}
	else
	{
		sBuffer = s.Mid( i, s.Length()-i );
		p = len;
	}

	return p;
}

/** Code copied out of HandleMessage() so it can be called directly from another place */
void CTransfer::DoInitUpload()
{
	m_nTransfered = 0;
	// init filehandle
	if ( eMedium == eltFILE )
	{
		m_nFileBufferPos    = m_nReadFileBufferSize;
		m_nFileBufferSize   = m_nReadFileBufferSize;
		m_nPendingSendBytes = 0;
		pByteArray->SetSize(m_nReadFileBufferSize);
		m_File.Close();
		
		if ( m_File.Open( sSrcFilename, IO_RAW | IO_READONLY ) == false )
		{
			CallBack_SendError( CString("(File open) ") + strerror(errno) );
			perror("File open");
		}
		else
		{
			// seek to the startposition
			if ( m_File.Seek(m_nStartPosition,SEEK_SET) == false )
			{
				CallBack_SendError( CString("(File seek) ") + strerror(errno) );
				perror("File seek");
				m_File.Close();
			}
		}
		// check for error
		if ( !m_File.IsOpen() )
		{
			Disconnect(true);
		}
		else
		{
			// start transfer
			m_bIdle = false;
		}
	}
	else
	{
		// compress buffer if necessary
		if ( m_nDataType == 2 )
		{
			m_nFileBufferPos = 0;
			CByteArray * compressed = new CByteArray();
			unsigned long outlen = CDeflater::OneShotDeflate( (const char*)pByteArray->Data(), pByteArray->Size(), compressed );
			if ( outlen > 0 )
			{
				//printf("Compressed buffer from %ld to %ld\n", pByteArray->Size(), outlen );
				pByteArray->SetSize(0);
				pByteArray->Append( compressed->Data(), outlen );
				
				// hack
				SetEndPosition(outlen);
				SetLength(outlen);
				m_nChunkSize = outlen;
			}
			else
			{
				printf("CTransfer::DoInitUpload: Compress buffer failed!\n");
			}
			delete compressed;
		}
		
		// start transfer
		m_bIdle = false;
	}
	
	if ( dclibVerbose() > 0 )
	{
		if ( eMedium == eltTTHL )
		{
			printf("start upload ... TTHL/%s %lld/%lld\n",m_sTTH.Data(),m_nStartPosition,m_nEndPosition);
		}
		else
		{
			printf("start upload ...'%s' %lld/%lld\n",sSrcFilename.Data(),m_nStartPosition,m_nChunkSize);
		}
	}
}

/** */
void CTransfer::SendSSLInfo()
{
	if ( GetSocketMode() != esmSOCKET )
	{
		CMessageLog * log1 = new CMessageLog();
		log1->sMessage  = GetSSLVersion();
		log1->sMessage += " connection using ";
		log1->sMessage += GetSSLCipher();
		
		CallBack_SendObject(log1);
		
#if 0
		/* nobody cares and they're all auto generated self signed certs */
		CMessageLog * log2 = new CMessageLog();
		log2->sMessage = VerifyPeerCertificate();
		
		CallBack_SendObject(log2);
#endif
	}
}

/** */
bool CTransfer::SupportsChunks() const
{
	return CHUNKS_SUPPORTED;
}
