#include "cryptoki.h"
#include "SlotList.h"
#include "Pkcs11Exceptions.h"
#include "P15EFUS.h"
#include "Communicator.h"
#include "ObjList.h"
#include "PKCS15UnusedSpace.h"


CP15EFUS::CP15EFUS(void)
{
	// Inicializamos las variables miembro
	m_loaded	= false;
	m_maxDataLen	= 0;
	m_ushFileId	= 0x5033;

}

CP15EFUS::~CP15EFUS(void)
{
	for(unsigned long idx = 0; idx<m_pbListaEFUS.size();idx++)
		delete m_pbListaEFUS[idx];
}

void CP15EFUS::LoadP15File(CCommunicator & comm)
{
	ushort			offset		= 0;
	byteBuffer		bbData;

	//
	// TODO::: Falta limpiar estado antiguo
	//

	// Accedemos al DF, cuya ruta es MF->5015->FileId
	comm.SelectFileByName( MF_NAME );

	comm.SelectFileById( 0x5015 );

	try
	{
		// Utilizamos el SFI
		offset = ( (GetFileId() & 0x001F) | 0x0080 ) << 8;

		while(1)
		{
			if(bbData.size())
				bbData.zeroClear();

			comm.readEF_sequence( 0, bbData, offset );

			// Ya hemos leído una entrada del DF, así que lo decodificamos.
			// Además, excepto para el caso del TokenType, se parseará a una estructura P11.
			if( !DecodeObject( bbData ) )
				throw Pkcs11Exception( CKR_DEVICE_ERROR );

			// Si el objeto se ha decodificado bien lo insertamos en la lista
			InsertFileEFUSlist(bbData);

			if( offset & 0x8000 )			// Quitamos el SFI
				offset &= 0x00ff;

			offset += (ushort)bbData.size();

			// Guardamos el tamaño total leído para controlar después que no quede basura en el DF
			m_maxDataLen = offset;

		}
	}
	catch( Pkcs11Exception & e )
	{

		if( e.getErrId() != CKR_DATA_INVALID )
			throw e;
	}

	m_loaded = true;
}

int CP15EFUS::GetNewID(CCommunicator * comm)
{
	CK_RV			ulResult	= CKR_OK;
	int				iFileID		= 0;
	byteBuffer		answer;
	byteBuffer		bbFileID;

	//
	// TODO::: Falta limpiar estado antiguo
	//

	// Accedemos al FE de identificadores, cuya ruta es MF->60A1
	comm->SelectFileByName( MF_NAME );

	comm->SelectFileById( 0x60A1 );

	try
	{
		// Leemos el contenido actual
		comm->ReadBinary( 0, 4, answer );

		// Obtenemos el identificador del fichero a crear (en 4 bytes)
		CUtil::PathASCII2bin(&answer[0], answer.size(), bbFileID);

		// Incrementamos el identificador del siguiente fichero y lo escribimos
		if(bbFileID[1]<0xFF)
			bbFileID[1]++;
		else
		{
			bbFileID[0]++;
			bbFileID[1] = 0x00;
		}

		iFileID  = bbFileID[0]*0x100;
		iFileID += bbFileID[1];

		// Reutilizamos esta función eliminando la barra inicial
		answer = CUtil::PathBin2ASCII( bbFileID );

		answer = answer.substr(1,4);
		comm->WriteBinary( 0, answer );

	}
	catch( Pkcs11Exception & e )
	{
		if( e.getErrId() != CKR_DATA_INVALID )
			throw e;
	}

	return iFileID;
}

void CP15EFUS::WriteP15File(CCommunicator * comm)
{
	byteBuffer		bbData;
	ushort			offset		= 0;	
	unsigned int	idx			= 0;
	unsigned int	itamEFUS	= 0;
	int iposEFUS				= 0;

	//
	// TODO::: Falta limpiar estado antiguo
	//

	// Accedemos al DF, cuya ruta es MF->5015->FileId
	comm->SelectFileByName( MF_NAME );

	comm->SelectFileById( 0x5015 );

	try
	{
		// Utilizamos el SFI
		offset = ( (GetFileId() & 0x001F) | 0x0080 ) << 8;

		itamEFUS = (unsigned int)m_pbListaEFUS.size();

		while( idx<itamEFUS )
		{
			// Escribimos esa nueva entrada en el EFUS
			comm->writeEF_sequence( 0, *m_pbListaEFUS[idx], offset );

			if( offset & 0x8000 )			// Quitamos el SFI
				offset &= 0x00ff;

			offset += (ushort)m_pbListaEFUS[idx]->size();

			iposEFUS = offset;

			idx++;
		}

		// Limpiamos el resto del fichero hasta el tamaño máximo anterior.
		if(iposEFUS<m_maxDataLen)
		{
			bbData.clear();
			bbData.resize(m_maxDataLen-iposEFUS);
			memset( &bbData[0], 0, bbData.size() );
			comm->WriteBinary( offset, bbData );
		}
		else
			// El tamaño máximo se ha incrementado tras esta escritura, así que lo almacenaremos
			m_maxDataLen = iposEFUS;

	}
	catch( Pkcs11Exception & e )
	{
		if( e.getErrId() != CKR_DATA_INVALID )
			throw e;
	}

	m_loaded = true;

}

int CP15EFUS::DecodeObject( byteBuffer & efusEntry )
{
	// Variables locales
	int nRet = 0;
	asn_dec_rval_t ret_val;

	PKCS15UnusedSpace		* asn1EFUS	   = NULL;

	// Realizamos la decodificación BER de los datos miembros
	ret_val = ber_decode( NULL,
						  &asn_DEF_PKCS15UnusedSpace,
						  (void**)&asn1EFUS,
						  (void*)&efusEntry[0],
						  efusEntry.size() );

	// Si la decodificación ha ido bien, rellenamos los campos de la estructura pInfo
	if( ret_val.code != RC_OK )
		nRet = 0;
	else
		// Fin del parseo PKCS#15
		nRet = 1;

	// Comprobamos si las rutas del EFUS están en BINARIO o en ASCII
	if(asn1EFUS->path.path.size > 4)
		SetPathEncodingType(PATHENC_ASCII);

	if(asn1EFUS)
	{
		// Liberamos la estructura anterior
		asn_DEF_PKCS15UnusedSpace.free_struct( &asn_DEF_PKCS15UnusedSpace, asn1EFUS, 0 );
		asn1EFUS = NULL;
	}

	return nRet;

}

int CP15EFUS::FindValidFile( CCommunicator  * comm, bool isPrivate, int iFileLen, byteBuffer & pbPath )
{
	int iCandidato	= -1;
	int iDifLen		= 0;
	int iBestFit	= 0x800000;
	bool encontrado = false;


	// Antes de realizar cualquier operación comprobamos que el EFUS se haya cargado
	// Si aún no lo hemos leído, leemos el EFUS
	if(!m_loaded)
		LoadP15File(*comm);

	for(unsigned int idx=0; idx<m_pbListaEFUS.size(); idx++)
	{
		// El fichero debe ser del mismo tipo: público/privado
		if( isPrivate != esObjPrivado(*m_pbListaEFUS[idx]) )
			continue;

		// Además debe ser suficientemente grande para albergar el dato
		iDifLen = GetFileLength(*m_pbListaEFUS[idx]);

		iDifLen-=iFileLen;

		if( (iDifLen>0) && (iDifLen < iBestFit) )
		{
			encontrado = true;
			iBestFit	= iDifLen;
			iCandidato	= idx;
		}
	}

	if(!encontrado)
		return -1;

	// Indicaremos la ruta al fichero elemental
	GetBinaryPath(*m_pbListaEFUS[iCandidato], pbPath);

	return iCandidato;
}

//Insertamos el fichero en el vector de entradas del EFUS
void CP15EFUS::InsertFileEFUSlist(byteBuffer & EFUSentry)
{
	byteBuffer * bbData = new byteBuffer(EFUSentry);

	m_pbListaEFUS.push_back(bbData);
}


bool CP15EFUS::AddFileEFUSlist( CCommunicator  * comm,
								byteBuffer		* filePath,
								int				* fileIndex,
								int				* fileLen)
{
	asn_enc_rval_t ret_val;

	byteBuffer efEncoded;

	unsigned long ulEncodedEFUSLen = 128;
	byte *bEncodedEFUS = new (byte[ulEncodedEFUSLen]);
	memset(bEncodedEFUS, 0x00, ulEncodedEFUSLen);

	// Antes de realizar cualquier operación comprobamos que el EFUS se haya cargado
	// Si aún no lo hemos leído, leemos el EFUS
	if(!m_loaded)
		LoadP15File(*comm);

	// Codificación ASN1
	PKCS15UnusedSpace		* pPKCS15UnusedSpace   = NULL;
	pPKCS15UnusedSpace = (PKCS15UnusedSpace*)calloc(1,sizeof(PKCS15UnusedSpace) );

	// Value--> Path
	if( filePath->size()!=0 )
	{
		byteBuffer		p15FilePath(*filePath);
		if( GetPathEncodingType() == PATHENC_ASCII)
		{
			p15FilePath = CUtil::PathBin2ASCII(p15FilePath);			
		}

		pPKCS15UnusedSpace->path.path.buf = (byte *)malloc( p15FilePath.size() );
		pPKCS15UnusedSpace->path.path.size = p15FilePath.size();

		memcpy( pPKCS15UnusedSpace->path.path.buf, &p15FilePath[0], p15FilePath.size());
	}

	// Value--> index
	if(fileIndex != NULL)
	{
		pPKCS15UnusedSpace->path.index = (INTEGER_t*)calloc(1,sizeof(INTEGER_t));
		pPKCS15UnusedSpace->path.index->buf = (byte *)malloc( 1 );
		pPKCS15UnusedSpace->path.index->size = 1;

		memset( pPKCS15UnusedSpace->path.index->buf, 0, 1);
		memcpy( pPKCS15UnusedSpace->path.index->buf, fileIndex, 1);
	}

	// Value--> length
	if(fileLen != NULL)
	{
		pPKCS15UnusedSpace->path.length = (INTEGER_t*)calloc(1,sizeof(INTEGER_t));
		pPKCS15UnusedSpace->path.length->buf = (byte *)malloc( 2 );
		pPKCS15UnusedSpace->path.length->size = 2;

		memset( pPKCS15UnusedSpace->path.length->buf, 0, 2);
		pPKCS15UnusedSpace->path.length->buf[0] = fileLen[0]>>8;
		pPKCS15UnusedSpace->path.length->buf[1] = fileLen[0]%0x100;
	}

	// Codificamos los atributos del objeto P11 a parsear
	ret_val = der_encode_to_buffer(&asn_DEF_PKCS15UnusedSpace, pPKCS15UnusedSpace, bEncodedEFUS, ulEncodedEFUSLen);

	asn_DEF_PKCS15UnusedSpace.free_struct( &asn_DEF_PKCS15UnusedSpace, pPKCS15UnusedSpace, 0 );

	// Comprobamos el resultado del parseo
	if(ret_val.encoded == -1)
	{
		if(bEncodedEFUS)
			delete bEncodedEFUS;

		return 0;
	}

	// Copiamos la respuesta a devolver
	efEncoded.resize(ret_val.encoded);
	efEncoded.assign(bEncodedEFUS, ret_val.encoded);

	if(bEncodedEFUS)
		delete bEncodedEFUS;

	// Insertamos en el EFUS la nueva entrada
	InsertFileEFUSlist(efEncoded);

	// Realizamos la codificación de cada entrada del EFUS y la escribimos en el FE.
	WriteP15File(comm);

	return true;
}

bool CP15EFUS::DeleteFileEFUSlist( CCommunicator  * comm,
								   int				indexEFUS )
{
	byteBuffer * pbbEFUS = m_pbListaEFUS[indexEFUS];

	m_pbListaEFUS.erase(m_pbListaEFUS.begin()+indexEFUS);

	delete pbbEFUS;

	// Realizamos la codificación de cada entrada del EFUS y la escribimos en el FE.
	WriteP15File(comm);

	return true;
}

//Comprueba si la ruta dada corresponde a un EF Privado.
bool CP15EFUS::esObjPrivado (byteBuffer EFUSentry)
{
	byteBuffer bbAux;

	unsigned int idx=0;
	int iRes = 0;

	byteBuffer bin((byte*)RUTA_PRIVATE_DF, 4);

	while( idx<(EFUSentry.size()-4) )
	{
		bbAux = EFUSentry.substr(idx, 4);
		iRes = memcmp(&bbAux[0], &bin[0], 4);

		if(!iRes)
			return true;
		else
			idx++;
	}

	return false;
}

// Devuelve la ruta contenida en la entrada del EFUS
int CP15EFUS::GetBinaryPath (byteBuffer entradaEFUS, byteBuffer & path)
{
	long tag, len	= 0;
	int offset		= 0;
	int iTamTotal	= -1;

	byteBuffer efusEntry(entradaEFUS);

	// Leemos el TLV del PKCS15UnusedSpace
	offset = CUtil::getTLV( &efusEntry[0], &tag, &len);

	if( tag != 0x30 )
		return iTamTotal;

	// Leemos el TLV SEQUENCE Path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	if( efusEntry[offset] != 0x04 )
		return iTamTotal;

	// Leemos el TLV Path.path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	path.resize(len);

	// Soportamos que en el EFUS la longitud pueda estar en ASCII o en BINARIO,
	// así que si la longitud no viene en 2 bytes, lo convertimos.
	if(len>4)
		CUtil::PathASCII2bin( &efusEntry[offset], len, path );
	else
	{
		memset(path, 0, len);
		memcpy(path, &efusEntry[offset], len);
	}

	return 1;
}

// Devuelve EN BINARIO la ruta contenida en la entrada del EFUS
byteBuffer *CP15EFUS::GetFilePath ( byteBuffer entradaEFUS )
{
	long tag, len	= 0;
	int offset		= 0;

	byteBuffer efusEntry(entradaEFUS);

	byteBuffer *bbPath = NULL;

	// Leemos el TLV del PKCS15UnusedSpace
	offset = CUtil::getTLV( &efusEntry[0], &tag, &len);

	if( tag != 0x30 )
		return NULL;

	// Leemos el TLV SEQUENCE Path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	if( efusEntry[offset] != 0x04 )
		return NULL;

	// Leemos el TLV Path.path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	bbPath = new(byteBuffer);

	if(len>4)
		CUtil::PathASCII2bin( &efusEntry[offset], len, *bbPath );
	else
		bbPath->assign( &efusEntry[offset], len);

	return bbPath;
}


// Devuelve el tamaño del fichero anotado en el EFUS
int CP15EFUS::GetFileLength (byteBuffer entradaEFUS)
{
	long tag, len	= 0;
	int offset		= 0;
	int iTamTotal	= -1;

	byteBuffer efusEntry(entradaEFUS);

	// Leemos el TLV del PKCS15UnusedSpace
	offset = CUtil::getTLV( &efusEntry[0], &tag, &len);

	if( tag != 0x30 )
		return iTamTotal;

	// Leemos el TLV SEQUENCE Path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	if( efusEntry[offset] != 0x04 )
		return iTamTotal;

	// Leemos el TLV Path.path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);
	offset+= len;

	if( efusEntry[offset] != 0x02 )
		return iTamTotal;

	// Leemos el TLV Path.index
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);
	offset+= len;

	// Deberíamos haber alcanzado el TLV del Path.length
	if( efusEntry[offset] != 0x80 )
		return iTamTotal;

	if(efusEntry[offset+1]>1)
	{
		iTamTotal = efusEntry[offset+2]*0x100;
		iTamTotal += efusEntry[offset+3];
	}
	else
		iTamTotal = efusEntry[offset+2];

	return iTamTotal;
}

// Devuelve el índice del fichero anotado en el EFUS
int CP15EFUS::GetFileIndex (byteBuffer entradaEFUS)
{
	long tag, len	= 0;
	int offset		= 0;
	int iIndex		= -1;

	byteBuffer efusEntry(entradaEFUS);

	// Leemos el TLV del PKCS15UnusedSpace
	offset = CUtil::getTLV( &efusEntry[0], &tag, &len);

	if( tag != 0x30 )
		return iIndex;

	// Leemos el TLV SEQUENCE Path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	if( efusEntry[offset] != 0x04 )
		return iIndex;

	// Leemos el TLV Path.path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);
	offset+= len;

	if( efusEntry[offset] != 0x02 )
		return iIndex;

	// Leemos el TLV Path.index
	iIndex=efusEntry[offset+2];

	return iIndex;
}

// Devuelve la ruta contenida en la entrada del EFUS
byteBuffer *CP15EFUS::GetAuthID ( byteBuffer entradaEFUS )
{
	long tag, len	= 0;
	int offset		= 0;
	int iTamTotal	= 0;

	byteBuffer efusEntry(entradaEFUS);

	byteBuffer *bbPath = NULL;

	// Leemos el TLV del PKCS15UnusedSpace
	offset = CUtil::getTLV( &efusEntry[0], &tag, &len);

	// Guardamos el tamaño total de la entrada
	iTamTotal = len;

	if( tag != 0x30 )
		return NULL;

	// Leemos el TLV SEQUENCE Path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	if( efusEntry[offset] != 0x04 )
		return NULL;

	// Leemos el TLV Path.path
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);
	offset+= len;

	if( efusEntry[offset] != 0x02 )
		return NULL;

	// Leemos el TLV Path.index
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);
	offset+= len;

	if( efusEntry[offset] != 0x80 )
		return NULL;

	// Leemos el TLV Path.lenght
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	// Si hemos llegado al final entonces la entrada no conenía authId
	if( offset == iTamTotal )
		return NULL;

	// Leemos el TLV AuthID, que aquí ya sí debería existir
	offset+= len;
	offset+= CUtil::getTLV( &efusEntry[offset], &tag, &len);

	bbPath = new(byteBuffer);
	bbPath->assign( &efusEntry[offset], len);

	return bbPath;
}

