/*
 * This keeps track of card insertion/removal events
 * and updates ATR, protocol, and status information.
 *
 * MUSCLE SmartCard Development ( http://www.linuxnet.com )
 *
 * Copyright (C) 2000
 *  David Corcoran <corcoran@linuxnet.com>
 * Copyright (C) 2004
 *  Ludovic Rousseau <ludovic.rousseau@free.fr>
 *
 * $Id: eventhandler.c,v 1.31 2005/02/22 14:40:26 rousseau Exp $
 */

#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#include "pcsclite.h"
#include "ifdhandler.h"
#include "debuglog.h"
#include "thread_generic.h"
#include "readerfactory.h"
#include "eventhandler.h"
#include "dyn_generic.h"
#include "sys_generic.h"
#include "ifdwrapper.h"
#include "prothandler.h"
#include "strlcpycat.h"

static PREADER_STATE readerStates[PCSCLITE_MAX_READERS_CONTEXTS];

void EHStatusHandlerThread(PREADER_CONTEXT);

LONG EHInitializeEventStructures(void)
{
	int fd, i, pageSize;

	fd = 0;
	i = 0;
	pageSize = 0;

	SYS_RemoveFile(PCSCLITE_PUBSHM_FILE);

	fd = SYS_OpenFile(PCSCLITE_PUBSHM_FILE, O_RDWR | O_CREAT, 00644);
	if (fd < 0)
	{
		Log3(PCSC_LOG_CRITICAL, "Cannot create public shared file %s: %s",
			PCSCLITE_PUBSHM_FILE, strerror(errno));
		exit(1);
	}

	SYS_Chmod(PCSCLITE_PUBSHM_FILE,
		S_IRGRP | S_IREAD | S_IWRITE | S_IROTH);

	pageSize = SYS_GetPageSize();

	/*
	 * Jump to end of file space and allocate zero's 
	 */
	SYS_SeekFile(fd, pageSize * PCSCLITE_MAX_READERS_CONTEXTS);
	SYS_WriteFile(fd, "", 1);

	/*
	 * Allocate each reader structure 
	 */
	for (i = 0; i < PCSCLITE_MAX_READERS_CONTEXTS; i++)
	{
		readerStates[i] = (PREADER_STATE)
			SYS_MemoryMap(sizeof(READER_STATE), fd, (i * pageSize));
		if (readerStates[i] == 0)
		{
			Log3(PCSC_LOG_CRITICAL, "Cannot memory map public shared file %s: %s",
				PCSCLITE_PUBSHM_FILE, strerror(errno));
			exit(1);
		}

		/*
		 * Zero out each value in the struct 
		 */
		memset((readerStates[i])->readerName, 0, MAX_READERNAME);
		memset((readerStates[i])->cardAtr, 0, MAX_ATR_SIZE);
		(readerStates[i])->readerID = 0;
		(readerStates[i])->readerState = 0;
		(readerStates[i])->lockState = 0;
		(readerStates[i])->readerSharing = 0;
		(readerStates[i])->cardAtrLength = 0;
		(readerStates[i])->cardProtocol = SCARD_PROTOCOL_UNSET;
	}

	return SCARD_S_SUCCESS;
}

LONG EHDestroyEventHandler(PREADER_CONTEXT rContext)
{
	if (NULL == rContext->readerState)
	{
		Log1(PCSC_LOG_ERROR, "Thread never started (reader init failed?)");
		return SCARD_S_SUCCESS;
	}

	if ('\0' == rContext->readerState->readerName[0])
	{
		Log1(PCSC_LOG_INFO, "Thread already stomped.");
		return SCARD_S_SUCCESS;
	}

	/*
	 * Set the thread to 0 to exit thread 
	 */
	rContext->dwLockId = 0xFFFF;

	Log1(PCSC_LOG_INFO, "Stomping thread.");

	do
	{
		/*
		 * Wait 0.05 seconds for the child to respond 
		 */
		SYS_USleep(50000);
	}
	while (rContext->dwLockId == 0xFFFF);
	/*
	 * Zero out the public status struct to allow it to be recycled and
	 * used again 
	 */

	memset(rContext->readerState->readerName, 0,
		sizeof(rContext->readerState->readerName));
	memset(rContext->readerState->cardAtr, 0,
		sizeof(rContext->readerState->cardAtr));
	rContext->readerState->readerID = 0;
	rContext->readerState->readerState = 0;
	rContext->readerState->lockState = 0;
	rContext->readerState->readerSharing = 0;
	rContext->readerState->cardAtrLength = 0;
	rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;

	/* Zero the thread */
	rContext->pthThread = 0;

	Log1(PCSC_LOG_INFO, "Thread stomped.");

	return SCARD_S_SUCCESS;
}

LONG EHSpawnEventHandler(PREADER_CONTEXT rContext)
{
	LONG rv;
	DWORD dwStatus = 0;
	int i;
	UCHAR ucAtr[MAX_ATR_SIZE];
	DWORD dwAtrLen = 0;

	rv = IFDStatusICC(rContext, &dwStatus, ucAtr, &dwAtrLen);
	if (rv != SCARD_S_SUCCESS)
	{
		Log2(PCSC_LOG_ERROR, "Initial Check Failed on %s", rContext->lpcReader);
		return SCARD_F_UNKNOWN_ERROR;
	}

	/*
	 * Find an empty reader slot and insert the new reader 
	 */
	for (i = 0; i < PCSCLITE_MAX_READERS_CONTEXTS; i++)
	{
		if ((readerStates[i])->readerID == 0)
			break;
	}

	if (i == PCSCLITE_MAX_READERS_CONTEXTS)
		return SCARD_F_INTERNAL_ERROR;

	/*
	 * Set all the attributes to this reader 
	 */
	rContext->readerState = readerStates[i];
	strlcpy(rContext->readerState->readerName, rContext->lpcReader,
		sizeof(rContext->readerState->readerName));
	memcpy(rContext->readerState->cardAtr, ucAtr, dwAtrLen);
	rContext->readerState->readerID = i + 100;
	rContext->readerState->readerState = dwStatus;
	rContext->readerState->readerSharing = rContext->dwContexts;
	rContext->readerState->cardAtrLength = dwAtrLen;
	rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;

	rv = SYS_ThreadCreate(&rContext->pthThread, THREAD_ATTR_DETACHED,
		(PCSCLITE_THREAD_FUNCTION( ))EHStatusHandlerThread, (LPVOID) rContext);
	if (rv == 1)
		return SCARD_S_SUCCESS;
	else
		return SCARD_E_NO_MEMORY;
}

void EHStatusHandlerThread(PREADER_CONTEXT rContext)
{
	LONG rv;
	LPCTSTR lpcReader;
	DWORD dwStatus, dwReaderSharing;
	DWORD dwCurrentState;
	int pageSize;

	/*
	 * Zero out everything 
	 */
	dwStatus = 0;
	dwReaderSharing = 0;
	dwCurrentState = 0;

	lpcReader = rContext->lpcReader;

	pageSize = SYS_GetPageSize();

	rv = IFDStatusICC(rContext, &dwStatus, rContext->readerState->cardAtr,
			&rContext->readerState->cardAtrLength);
	if (dwStatus & SCARD_PRESENT)
	{
		rv = IFDPowerICC(rContext, IFD_POWER_UP,
			rContext->readerState->cardAtr,
			&rContext->readerState->cardAtrLength);

		/* the protocol is unset after a power on */
		rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;

		if (rv == IFD_SUCCESS)
		{
			dwStatus |= SCARD_PRESENT;
			dwStatus &= ~SCARD_ABSENT;
			dwStatus |= SCARD_POWERED;
			dwStatus |= SCARD_NEGOTIABLE;
			dwStatus &= ~SCARD_SPECIFIC;
			dwStatus &= ~SCARD_SWALLOWED;
			dwStatus &= ~SCARD_UNKNOWN;

			if (rContext->readerState->cardAtrLength > 0)
			{
				LogXxd(PCSC_LOG_INFO, "Card ATR: ",
					rContext->readerState->cardAtr,
					rContext->readerState->cardAtrLength);
			}
			else
				Log1(PCSC_LOG_INFO, "Card ATR: (NULL)");
		}
		else
		{
			dwStatus |= SCARD_PRESENT;
			dwStatus &= ~SCARD_ABSENT;
			dwStatus |= SCARD_SWALLOWED;
			dwStatus &= ~SCARD_POWERED;
			dwStatus &= ~SCARD_NEGOTIABLE;
			dwStatus &= ~SCARD_SPECIFIC;
			dwStatus &= ~SCARD_UNKNOWN;
			Log1(PCSC_LOG_ERROR, "Error powering up card.");
		}

		dwCurrentState = SCARD_PRESENT;
	}
	else
	{
		dwStatus |= SCARD_ABSENT;
		dwStatus &= ~SCARD_PRESENT;
		dwStatus &= ~SCARD_POWERED;
		dwStatus &= ~SCARD_NEGOTIABLE;
		dwStatus &= ~SCARD_SPECIFIC;
		dwStatus &= ~SCARD_SWALLOWED;
		dwStatus &= ~SCARD_UNKNOWN;
		rContext->readerState->cardAtrLength = 0;
		rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;

		dwCurrentState = SCARD_ABSENT;
	}

	/*
	 * Set all the public attributes to this reader 
	 */
	rContext->readerState->readerState = dwStatus;
	rContext->readerState->readerSharing = dwReaderSharing =
		rContext->dwContexts;

	SYS_MMapSynchronize((void *) rContext->readerState, pageSize);

	while (1)
	{
		dwStatus = 0;

		rv = IFDStatusICC(rContext, &dwStatus,
			rContext->readerState->cardAtr,
			&rContext->readerState->cardAtrLength);

		if (rv != SCARD_S_SUCCESS)
		{
			Log2(PCSC_LOG_ERROR, "Error communicating to: %s", lpcReader);

			/*
			 * Set error status on this reader while errors occur 
			 */

			rContext->readerState->readerState &= ~SCARD_ABSENT;
			rContext->readerState->readerState &= ~SCARD_PRESENT;
			rContext->readerState->readerState &= ~SCARD_POWERED;
			rContext->readerState->readerState &= ~SCARD_NEGOTIABLE;
			rContext->readerState->readerState &= ~SCARD_SPECIFIC;
			rContext->readerState->readerState &= ~SCARD_SWALLOWED;
			rContext->readerState->readerState |= SCARD_UNKNOWN;
			rContext->readerState->cardAtrLength = 0;
			rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;

			dwCurrentState = SCARD_UNKNOWN;

			SYS_MMapSynchronize((void *) rContext->readerState, pageSize);

			/*
			 * This code causes race conditions on G4's with USB
			 * insertion 
			 */
			/*
			 * dwErrorCount += 1; SYS_Sleep(1); 
			 */
			/*
			 * After 10 seconds of errors, try to reinitialize the reader
			 * This sometimes helps bring readers out of *crazy* states. 
			 */
			/*
			 * if ( dwErrorCount == 10 ) { RFUnInitializeReader( rContext
			 * ); RFInitializeReader( rContext ); dwErrorCount = 0; } 
			 */

			/*
			 * End of race condition code block 
			 */
		}

		if (dwStatus & SCARD_ABSENT)
		{
			if (dwCurrentState == SCARD_PRESENT ||
				dwCurrentState == SCARD_UNKNOWN)
			{
				/*
				 * Change the status structure 
				 */
				Log2(PCSC_LOG_INFO, "Card Removed From %s", lpcReader);
				/*
				 * Notify the card has been removed 
				 */
				RFSetReaderEventState(rContext, SCARD_REMOVED);

				rContext->readerState->cardAtrLength = 0;
				rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;
				rContext->readerState->readerState |= SCARD_ABSENT;
				rContext->readerState->readerState &= ~SCARD_UNKNOWN;
				rContext->readerState->readerState &= ~SCARD_PRESENT;
				rContext->readerState->readerState &= ~SCARD_POWERED;
				rContext->readerState->readerState &= ~SCARD_NEGOTIABLE;
				rContext->readerState->readerState &= ~SCARD_SWALLOWED;
				rContext->readerState->readerState &= ~SCARD_SPECIFIC;
				dwCurrentState = SCARD_ABSENT;

				SYS_MMapSynchronize((void *) rContext->readerState, pageSize);
			}

		}
		else if (dwStatus & SCARD_PRESENT)
		{
			if (dwCurrentState == SCARD_ABSENT ||
				dwCurrentState == SCARD_UNKNOWN)
			{
				/*
				 * Power and reset the card 
				 */
				SYS_USleep(PCSCLITE_STATUS_WAIT);
				rv = IFDPowerICC(rContext, IFD_POWER_UP,
					rContext->readerState->cardAtr,
					&rContext->readerState->cardAtrLength);

				/* the protocol is unset after a power on */
				rContext->readerState->cardProtocol = SCARD_PROTOCOL_UNSET;

				if (rv == IFD_SUCCESS)
				{
					rContext->readerState->readerState |= SCARD_PRESENT;
					rContext->readerState->readerState &= ~SCARD_ABSENT;
					rContext->readerState->readerState |= SCARD_POWERED;
					rContext->readerState->readerState |= SCARD_NEGOTIABLE;
					rContext->readerState->readerState &= ~SCARD_SPECIFIC;
					rContext->readerState->readerState &= ~SCARD_UNKNOWN;
					rContext->readerState->readerState &= ~SCARD_SWALLOWED;

					/*
					 * Notify the card has been reset 
					 */
					RFSetReaderEventState(rContext, SCARD_RESET); 
				}
				else
				{
					rContext->readerState->readerState |= SCARD_PRESENT;
					rContext->readerState->readerState &= ~SCARD_ABSENT;
					rContext->readerState->readerState |= SCARD_SWALLOWED;
					rContext->readerState->readerState &= ~SCARD_POWERED;
					rContext->readerState->readerState &= ~SCARD_NEGOTIABLE;
					rContext->readerState->readerState &= ~SCARD_SPECIFIC;
					rContext->readerState->readerState &= ~SCARD_UNKNOWN;
					rContext->readerState->cardAtrLength = 0;
				}

				dwCurrentState = SCARD_PRESENT;

				SYS_MMapSynchronize((void *) rContext->readerState, pageSize);

				Log2(PCSC_LOG_INFO, "Card inserted into %s", lpcReader);

				if (rv == IFD_SUCCESS)
				{
					if (rContext->readerState->cardAtrLength > 0)
					{
						LogXxd(PCSC_LOG_INFO, "Card ATR: ",
							rContext->readerState->cardAtr,
							rContext->readerState->cardAtrLength);
					}
					else
						Log1(PCSC_LOG_INFO, "Card ATR: (NULL)");
				}
				else
					Log1(PCSC_LOG_ERROR,"Error powering up card.");
			}
		}

		if (rContext->dwLockId == 0xFFFF)
		{
			/*
			 * Exit and notify the caller 
			 */
			rContext->dwLockId = 0;
			SYS_ThreadDetach(rContext->pthThread);
			SYS_ThreadExit(0);
		}

		/*
		 * Sharing may change w/o an event pass it on 
		 */

		if (dwReaderSharing != rContext->dwContexts)
		{
			dwReaderSharing = rContext->dwContexts;
			rContext->readerState->readerSharing = dwReaderSharing;
			SYS_MMapSynchronize((void *) rContext->readerState, pageSize);
		}

		SYS_USleep(PCSCLITE_STATUS_POLL_RATE);
	}
}

void EHSetSharingEvent(PREADER_CONTEXT rContext, DWORD dwValue)
{
	rContext->readerState->lockState = dwValue;
}
