#include <winscard.h>

#define CRYPTOKI_EXPORTS

#include "cryptoki.h"
#include "SlotList.h"
#include "Communicator.h"
#include "Pkcs11Exceptions.h"

#include "P15File.h"
#include "P15TokenInfo.h"
#include "ExclusionMutua.h"

#define SLOT_MANUFACTURER_ID "Smart Card Reader/Writer"

extern SCARDCONTEXT g_hContext;
extern CSlotList & g_rSlotList;

extern CK_ULONG ulNumMechanism;

extern CK_ULONG g_ulGlobalFlags;

extern CExclusionMutua g_ExclMutua;

/* Slot and token management */

/* C_GetSlotList obtains a list of slots in the system. */
CK_DEFINE_FUNCTION(CK_RV, C_GetSlotList)
(
  CK_BBOOL       tokenPresent,  /* only slots with tokens? */
  CK_SLOT_ID_PTR pSlotList,     /* receives array of slot IDs */
  CK_ULONG_PTR   pulCount       /* receives number of slots */
)
{
	DBG_TRACE(("C_GetSlotList(%d,0x%08x,0x%08x)...\n",tokenPresent,pSlotList,pulCount));

	CK_RV ulResult = CKR_OK;
	unsigned long ulNumberOfSlots;

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	ulResult = g_ExclMutua.LockMutex();
	if (ulResult != CKR_OK)
		return ulResult;

	if (NULL == g_hContext)
	{
		ulResult = CKR_CRYPTOKI_NOT_INITIALIZED;
	}
	else
	{

		try
		{
			// Actualizamos la lista de slots
			g_rSlotList.Update();

			// Obtenemos el número de slots
			ulNumberOfSlots = g_rSlotList.GetNumberOfSlots(tokenPresent == CK_TRUE);

			// Si pSlotList no es NULL_PTR, además obtenemos la lista de slots
 			if (NULL_PTR != pSlotList)
			{

				// *pulCount contiene el número de slots que se pueden almacenar en pSlotList,
				// y debe ser al menos igual al número de slots obtenido
				if (NULL_PTR == pulCount)
				{
					ulResult = CKR_ARGUMENTS_BAD;
				}
				else if (*pulCount < ulNumberOfSlots)
				{
					ulResult = CKR_BUFFER_TOO_SMALL;
				}
				else
				{
					g_rSlotList.GetSlots(pSlotList, tokenPresent == CK_TRUE);
				}

			}

			// En cualquier caso, devolvemos en *pulCount el número de slots
			*pulCount = ulNumberOfSlots;
		}
		catch (Pkcs11Exception & e)
		{
			ulResult = e.getErrId();
		}
/*		catch (...)
		{
			ulResult = CKR_GENERAL_ERROR;
		}
*/
	}

	DBG_TRACE(("C_GetSlotList()=0x%08x\n",ulResult));

	g_ExclMutua.UnlockMutex();

	return ulResult;
}

/* C_GetSlotInfo obtains information about a particular slot in
 * the system. */
CK_DEFINE_FUNCTION(CK_RV, C_GetSlotInfo)
(
  CK_SLOT_ID       slotID,  /* the ID of the slot */
  CK_SLOT_INFO_PTR pInfo    /* receives the slot information */
)
{
	DBG_TRACE(("C_GetSlotInfo(0x%08x,0x%08x)...\n",slotID,pInfo));

	CK_RV ulResult = CKR_OK;

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	ulResult = g_ExclMutua.LockMutex();
	if (ulResult != CKR_OK)
		return ulResult;

	if (NULL == g_hContext)
		ulResult = CKR_CRYPTOKI_NOT_INITIALIZED;
	else
		if (NULL_PTR == pInfo)
			ulResult = CKR_ARGUMENTS_BAD;
		else
		{
			try
			{
				// Obtenemos el slot requerido de la lista actual de slots
				CSlot & slot = g_rSlotList.GetSlot(slotID);
				
				// Inicializamos la estructura CK_SLOT_INFO
				memset(pInfo, 0, sizeof(CK_SLOT_INFO));

				// La descripción del slot es el nombre del mismo
				DBG_TRACE(( "C_GetSlotInfo nombreLector (%i) %s\n", slot.GetName().length(), slot.GetName().data()));

				if(slot.GetName().length() <= sizeof(pInfo->slotDescription))
					memcpy_s(pInfo->slotDescription, sizeof(pInfo->slotDescription), slot.GetName().data(), slot.GetName().length());
				else memcpy_s(pInfo->slotDescription, sizeof(pInfo->slotDescription), slot.GetName().data(), sizeof(pInfo->slotDescription));
				
				// Rellenamos el sobrante con espacios
				for (unsigned int i = (unsigned int)slot.GetName().length(); i < sizeof(pInfo->slotDescription); i++)
				{
					pInfo->slotDescription[i] = ' ';
				}

				// El identificador del fabricante tiene un valor fijo
				memcpy_s(pInfo->manufacturerID, sizeof(pInfo->manufacturerID), SLOT_MANUFACTURER_ID, sizeof(pInfo->manufacturerID));

				// Rellenamos el sobrante con espacios
				for (unsigned int i = sizeof(SLOT_MANUFACTURER_ID) - 1; i < sizeof(pInfo->manufacturerID); i++)
				{
					pInfo->manufacturerID[i] = ' ';
				}

				// Presuponemos que todos los slots son hardware y con tarjetas extraíbles
				pInfo->flags = CKF_HW_SLOT + CKF_REMOVABLE_DEVICE;

				// Comprobamos si tiene o no tarjeta
				if (slot.SupportedTokenPresent())
				{
					pInfo->flags += CKF_TOKEN_PRESENT;
				}

				// Rellenamos los campos de versión
				pInfo->hardwareVersion.major = 0;
				pInfo->hardwareVersion.minor = 0;
				pInfo->firmwareVersion.major = 0;
				pInfo->firmwareVersion.minor = 0;

			}
			catch (Pkcs11Exception & e)
			{
				ulResult = e.getErrId();
			}
		}

	DBG_TRACE(("C_GetSlotInfo()=0x%08x\n",ulResult));

	g_ExclMutua.UnlockMutex();

	return ulResult;
}


/* C_GetTokenInfo obtains information about a particular token
 * in the system. */
CK_DEFINE_FUNCTION(CK_RV, C_GetTokenInfo)
(
  CK_SLOT_ID        slotID,  /* ID of the token's slot */
  CK_TOKEN_INFO_PTR pInfo    /* receives the token information */
)
{
	DBG_TRACE(("C_GetTokenInfo(0x%08x,0x%08x)...\n",slotID,pInfo));

	CK_RV ulResult = CKR_OK;

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	if (NULL_PTR == pInfo)
		ulResult = CKR_ARGUMENTS_BAD;

	ulResult = g_ExclMutua.LockMutex();
	if (ulResult != CKR_OK)
		return ulResult;

	try
	{
		// Obtenemos el slot requerido de la lista actual de slots
		CSlot & slot = g_rSlotList.GetSlot(slotID);

		// En el slot ya tenemos leído el TokenInfo así que lo parsearemos
		slot.GetTokenInfo(pInfo);
	}
	catch (Pkcs11Exception & e)
	{
		ulResult = e.getErrId();
	}

	DBG_TRACE(("C_GetTokenInfo()=0x%08x\n",ulResult));

	g_ExclMutua.UnlockMutex();

	return ulResult;
}


/* C_GetMechanismList obtains a list of mechanism types
 * supported by a token. */
CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismList)
(
  CK_SLOT_ID            slotID,          /* ID of token's slot */
  CK_MECHANISM_TYPE_PTR pMechanismList,  /* gets mech. array */
  CK_ULONG_PTR          pulCount         /* gets # of mechs. */
)
{
	DBG_TRACE(("C_GetMechanismList(0x%08x,0x%08x,0x%08x)...\n",slotID,pMechanismList,pulCount));

	CK_RV ulResult = CKR_OK;

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	ulResult = g_ExclMutua.LockMutex();
	if (ulResult != CKR_OK)
		return ulResult;

	try
	{
		// Obtenemos el slot requerido de la lista actual de slots
		CSlot & slot = g_rSlotList.GetSlot(slotID);

		// Devolvemos la lista de mecanismos soportados por el token de ese slot (si lo hay)
		slot.GetMechanismList(pMechanismList, pulCount);

		/**pulCount = ulNumMechanism;

		// Si han reservado memoria devolveremos los mecanismos soportados
		if(pMechanismList)
		{
			for(CK_ULONG i=0; i<ulNumMechanism; i++)
				pMechanismList[i] = g_ListaMecanismos[i].type;
		}*/
	}
	catch (Pkcs11Exception & e)
	{
		ulResult = e.getErrId();
	}

	DBG_TRACE(("C_GetMechanismList()=0x%08x\n",ulResult));

	g_ExclMutua.UnlockMutex();

	return ulResult;
}


/* C_GetMechanismInfo obtains information about a particular
 * mechanism possibly supported by a token. */
CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismInfo)
(
  CK_SLOT_ID            slotID,  /* ID of the token's slot */
  CK_MECHANISM_TYPE     type,    /* type of mechanism */
  CK_MECHANISM_INFO_PTR pInfo    /* receives mechanism info */
)
{
	DBG_TRACE(("C_GetMechanismInfo(0x%08x,0x%08x,0x%08x)...\n",slotID,type,pInfo));

	CK_RV ulResult = CKR_OK;

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	ulResult = g_ExclMutua.LockMutex();
	if (ulResult != CKR_OK)
		return ulResult;

	try
	{
		if (pInfo == NULL)
			throw Pkcs11Exception(CKR_HOST_MEMORY);

		// Obtenemos el slot requerido de la lista actual de slots
		CSlot & slot = g_rSlotList.GetSlot(slotID);

		// Intentamos crear un objeto de ese tipo de mecanismo para este token concreto.
		CMech* pMech = slot.newMechanism(type);

		if(pMech == NULL)
			throw Pkcs11Exception(CKR_MECHANISM_INVALID);

		// Si hemos encontrado ese mecanismo devolveremos sus datos
		pInfo->flags		= pMech->m_MechInfo.flags;
		pInfo->ulMaxKeySize	= pMech->m_MechInfo.ulMaxKeySize;
		pInfo->ulMinKeySize = pMech->m_MechInfo.ulMinKeySize;

		// Liberamos el objeto mecanismo
		if(pMech)
		{
			delete(pMech);
			pMech = NULL;
		}
	}
	catch (Pkcs11Exception & e)
	{
		ulResult = e.getErrId();
	}

	DBG_TRACE(("C_GetMechanismInfo()=0x%08x\n", ulResult));

	g_ExclMutua.UnlockMutex();

	return ulResult;
}


/* C_InitToken initializes a token. */
CK_DEFINE_FUNCTION(CK_RV, C_InitToken)
/* pLabel changed from CK_CHAR_PTR to CK_UTF8CHAR_PTR for v2.10 */
(
  CK_SLOT_ID      slotID,    /* ID of the token's slot */
  CK_UTF8CHAR_PTR pPin,      /* the SO's initial PIN */
  CK_ULONG        ulPinLen,  /* length in bytes of the PIN */
  CK_UTF8CHAR_PTR pLabel     /* 32-byte token label (blank padded) */
)
{
	DBG_TRACE(("C_InitToken(0x%08x,0x%08x,%d,0x%08x)...\n",slotID,pPin,ulPinLen,pLabel));

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	DBG_TRACE(("C_InitToken()=0x%08x\n",CKR_FUNCTION_NOT_SUPPORTED));
	return CKR_FUNCTION_NOT_SUPPORTED;
}


/* C_InitPIN initializes the normal user's PIN. */
CK_DEFINE_FUNCTION(CK_RV, C_InitPIN)
(
  CK_SESSION_HANDLE hSession,  /* the session's handle */
  CK_UTF8CHAR_PTR   pPin,      /* the normal user's PIN */
  CK_ULONG          ulPinLen   /* length in bytes of the PIN */
)
{
	DBG_TRACE(("C_InitPIN(0x%08x,0x%08x,%d)...\n",hSession,pPin,ulPinLen));

	if (!(g_ulGlobalFlags & FLAG_LIBRERIA_INICIALIZADA))
		return (CKR_CRYPTOKI_NOT_INITIALIZED);

	DBG_TRACE(("C_InitPIN()=0x%08x\n",CKR_FUNCTION_NOT_SUPPORTED));
	return CKR_FUNCTION_NOT_SUPPORTED;
}

