// testapdu.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <winscard.h>

int HexStringToByteArray(const TCHAR *string, BYTE *array, int arraySize);
int CheckResponse(const TCHAR *line);
int CompareResponse(const TCHAR *cmpString, BYTE *rsp, DWORD rspLen);

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
    int readerIndex;
    
    SCARDCONTEXT hSC;
    LONG lReturn;
    DWORD i;
    
    SCARDHANDLE hCard;
    DWORD dwAP;

    // SCardListReaders
    TCHAR **readerName = NULL;
    int numReaders = 0;
    LPTSTR pmszReaders = NULL;
    LPTSTR pReader;
    DWORD cch = 0;

    // SCardTransmit
    const int BUFFER_SIZE = 600;
    BYTE bSendBuffer[BUFFER_SIZE];
    DWORD dwSendBufferLen = 0;
    BYTE bRecvBuffer[BUFFER_SIZE];
    DWORD dwRecvBufferLen = 0;
    //BYTE bCompareBuffer[BUFFER_SIZE];
    DWORD dwCompareBufferLen = 0;
    TCHAR line[2048];
    FILE* cardCommandFile;
    bool commandLoaded;
    
    // SCardStatus
    LPTSTR pszReaderName = NULL;
    DWORD dwReaderLen = 0;
    DWORD dwState;
    DWORD dwProtocol;
    LPBYTE pbAtr = NULL;
    DWORD dwAtrLen = 0;
    
    // SCardGetStatusChange
    SCARD_READERSTATE readerStates[1];
    TCHAR inputChar;
    
    printf("APDU Test for Windows CE\n");
    printf("Copyright (C) 2014 Advanced Card Systems Ltd. All Rights Reserved.\n");
    if (argc < 2)
    {
        printf("Usage: testapdu <filename>\n");
        return 1;
    }
    
    // Establish context
    _tprintf(_T("Establishing the context...\n"));
    lReturn = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hSC);
    if (lReturn != SCARD_S_SUCCESS)
    {
        _tprintf(_T("Error: SCardEstablishContext failed with error 0x%08lx\n"), lReturn);
        return 1;
    }

    // List readers
    _tprintf(_T("Listing the readers...\n"));
    lReturn = SCardListReaders(hSC, NULL, NULL, &cch);
    pmszReaders = (LPTSTR) malloc(sizeof(TCHAR) * cch);
    lReturn = SCardListReaders(hSC, NULL, pmszReaders, &cch);
    if (lReturn == SCARD_S_SUCCESS)
    {
        pReader = pmszReaders;
        while (*pReader != '\0')
        {
            _tprintf(_T("Reader %d: %s\n"), numReaders, pReader);
            pReader = pReader + _tcslen(pReader) + 1;
            numReaders++;
        }

        readerName = (TCHAR **) calloc(numReaders, sizeof(TCHAR *));
        if (readerName == NULL)
        {
            _tprintf(_T("Error: not enough memory\n"));
            exit(1);
        }

        i = 0;
        pReader = pmszReaders;
        while (*pReader != '\0')
        {
            readerName[i] = (TCHAR *) malloc(sizeof(TCHAR) * (_tcslen(pReader) + 1));
            if (readerName[i] == NULL)
            {
                _tprintf(_T("Error: not enough memory\n"));
                exit(1);
            }
            _tcscpy(readerName[i], pReader);
            i++;

            pReader = pReader + _tcslen(pReader) + 1;
        }
    }
    
    if (pmszReaders != NULL)
        free(pmszReaders);
    
    if (numReaders == 0)
    {
        _tprintf(_T("Error: reader not found\n"));
        goto err;
    }
    
    readerIndex = -1;
    while ((readerIndex < 0) || (readerIndex >= numReaders))
    {
        _tprintf(_T("Select reader: "));
        _tscanf(_T("%d"), &readerIndex);
        fflush(stdin);
    }

    // Connect to the reader
    _tprintf(_T("Connecting to %s...\n"), readerName[readerIndex]);
    lReturn = SCardConnect(hSC, readerName[readerIndex], SCARD_SHARE_SHARED,
         SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwAP);
    if (lReturn != SCARD_S_SUCCESS)
    {
        _tprintf(_T("Error: SCardConnect failed with error 0x%08lx\n"), lReturn);
        goto err;
    }

    switch (dwAP)
    {
    case SCARD_PROTOCOL_T0:
        _tprintf(_T("Active protocol T0\n"));
        break;

    case SCARD_PROTOCOL_T1:
        _tprintf(_T("Active protocol T1\n"));
        break;

    default:
        _tprintf(_T("Active protocol unnegotiated or unknown\n"));
        break;
    }
    
    // Get the card status
    _tprintf(_T("Getting the card status...\n"));
    lReturn = SCardStatus(hCard, NULL, &dwReaderLen, &dwState, &dwProtocol, NULL, &dwAtrLen);
    pszReaderName = (LPTSTR) malloc(sizeof(TCHAR) * dwReaderLen);
    pbAtr = (LPBYTE) malloc(sizeof(BYTE) * dwAtrLen);
    lReturn = SCardStatus(hCard, pszReaderName, &dwReaderLen, &dwState, &dwProtocol, pbAtr, &dwAtrLen);
    if (lReturn != SCARD_S_SUCCESS)
    {
        _tprintf(_T("Error: SCardStatus failed with error 0x%08lx\n"), lReturn);
    }
    else
    {
        _tprintf(_T("ATR:\n"));
        for (i = 0; i < dwAtrLen; i++)
        {
            if ((i > 0) && (((i + 1) % 16) == 0))
                _tprintf(_T("%02X\n"), pbAtr[i]);
            else
                _tprintf(_T("%02X "), pbAtr[i]);
        }
        _tprintf(_T("\n"));
    }
    
    if (pszReaderName != NULL)
        free(pszReaderName);
    if (pbAtr != NULL)
        free(pbAtr);

    // Begin the transaction
    _tprintf(_T("Beginning the transaction...\n"));
    lReturn = SCardBeginTransaction(hCard);
    if (lReturn != SCARD_S_SUCCESS)
        _tprintf(_T("Error: SCardBeginTransaction failed with error 0x%08lx\n"), lReturn);

    // Transmit data
    SCARD_IO_REQUEST ioSendPci;
    ioSendPci.dwProtocol = dwAP;
    ioSendPci.cbPciLength = sizeof(SCARD_IO_REQUEST);

    // Open card command file
    _tprintf(_T("Opening %s...\n"), argv[1]);
    cardCommandFile = _tfopen(argv[1], _T("rt"));
    if (cardCommandFile == NULL)
    {
        _tprintf(_T("Error: file not found\n"));
        goto err2;
    }

    while (!feof(cardCommandFile))
    {
        commandLoaded = false;
        while (!feof(cardCommandFile))
        {
            // Load command
            if (_fgetts(line, sizeof(line), cardCommandFile) != NULL)
            {
                // Skip line if comment character is read
                if (line[0] != ';')
                {
                    dwSendBufferLen = HexStringToByteArray(line, bSendBuffer, sizeof(bSendBuffer));
                    if (dwSendBufferLen > 0)
                    {
                        commandLoaded = true;
                        break;
                    }
                }
            }
            else
            {
                if (ferror(cardCommandFile) != 0)
                    _tprintf(_T("Error: cannot load card command from file\n"));
                break;
            }
        }
        if (!commandLoaded)
            break;

        commandLoaded = false;
        while (!feof(cardCommandFile))
        {
            // Load response
            if (_fgetts(line, sizeof(line), cardCommandFile) != NULL)
            {
                // Skip line if comment character is read
                if (line[0] != ';')
                {
                    //dwCompareBufferLen = HexStringToByteArray(line, bCompareBuffer, sizeof(bCompareBuffer));
                    dwCompareBufferLen = CheckResponse(line);
                    if (dwCompareBufferLen > 0)
                    {
                        commandLoaded = true;
                        break;
                    }
                }
            }
            else
            {
                if (ferror(cardCommandFile) != 0)
                    _tprintf(_T("Error: cannot load response from file\n"));
                break;
            }
        }
        if (!commandLoaded)
            break;
            
        _tprintf(_T("Command:\n"));
        for (i = 0; i < dwSendBufferLen; i++)
        {
            if ((i > 0) && (((i + 1) % 16) == 0))
                _tprintf(_T("%02X\n"), bSendBuffer[i]);
            else
                _tprintf(_T("%02X "), bSendBuffer[i]);
        }
        _tprintf(_T("\n"));

        dwRecvBufferLen = sizeof(bRecvBuffer);
        lReturn = SCardTransmit(hCard, &ioSendPci, bSendBuffer, dwSendBufferLen, NULL,
            bRecvBuffer, &dwRecvBufferLen);
        if (lReturn != SCARD_S_SUCCESS)
        {
            _tprintf(_T("Error: SCardTransmit failed with error 0x%08lx\n"), lReturn);
        }
        else
        {
            _tprintf(_T("Response:\n"));
            for (i = 0; i < dwRecvBufferLen; i++)
            {
                if ((i > 0) && (((i + 1) % 16) == 0))
                    _tprintf(_T("%02X\n"), bRecvBuffer[i]);
                else
                    _tprintf(_T("%02X "), bRecvBuffer[i]);
            }
            _tprintf(_T("\n"));
/*
            _tprintf(_T("Expected:\n"));
            for (i = 0; i < dwCompareBufferLen; i++)
            {
                if ((i > 0) && (((i + 1) % 16) == 0))
                    _tprintf(_T("%02X\n"), bCompareBuffer[i]);
                else
                    _tprintf(_T("%02X "), bCompareBuffer[i]);
            }
            _tprintf(_T("\n"));

            // Compare response
            if (dwRecvBufferLen == dwCompareBufferLen)
            {
                if (memcmp(bRecvBuffer, bCompareBuffer, dwRecvBufferLen) == 0)
                    _tprintf(_T("Compare OK\n"));
                else
                    _tprintf(_T("Error: unexpected response\n"));
            }
            else
                _tprintf(_T("Error: unexpected response\n"));
*/
            if (CompareResponse(line, bRecvBuffer, dwRecvBufferLen) == 0)
                _tprintf(_T("Compare OK\n"));
            else
                _tprintf(_T("Error: unexpected response\n"));
        }
    }

    // Close card command file
    fclose(cardCommandFile);

    // End transaction
    _tprintf(_T("Ending the transaction...\n"));
    lReturn = SCardEndTransaction(hCard, SCARD_LEAVE_CARD);
    if (lReturn != SCARD_S_SUCCESS)
        _tprintf(_T("Error: SCardEndTransaction failed with error 0x%08lx\n"), lReturn);

err2:
    lReturn = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
    if (lReturn != SCARD_S_SUCCESS)
        _tprintf(_T("Error: SCardDisconnect failed with error 0x%08lx\n"), lReturn);

err:
    //
    // Testing SCardGetStatusChange
    //
    if (numReaders > 0)
    {
        _tprintf(_T("Please insert/remove the card within 10 seconds...\n"));
        memset(readerStates, 0, sizeof(readerStates));
        readerStates[0].szReader = readerName[readerIndex];
        while (1)
        {
            lReturn = SCardGetStatusChange(
                hSC,
                10 * 1000,
                readerStates,
                1);
            if (lReturn != SCARD_S_SUCCESS)
            {
                _tprintf(_T("Error: SCardGetStatusChange failed with error 0x%08lx\n"), lReturn);

                inputChar = ' ';
                while ((inputChar != 'y') && (inputChar != 'Y') &&
                    (inputChar != 'n') && (inputChar != 'N'))
                {
                    _tprintf(_T("Continue (y/n)? "));
                    inputChar = _gettchar();
                    fflush(stdin);
                }

                if ((inputChar == 'n') || (inputChar == 'N'))
                    break;
            }
            else
            {
                if (readerStates[0].dwCurrentState != readerStates[0].dwEventState)
                {
                    _tprintf(_T("Status Change: 0x%08lx -> 0x%08lx\n"),
                        readerStates[0].dwCurrentState,
                        readerStates[0].dwEventState);

                    if (readerStates[0].cbAtr > 0)
                    {
                        _tprintf(_T("ATR:\n"));
                        for (i = 0; i < readerStates[0].cbAtr; i++)
                        {
                            if ((i > 0) && (((i + 1) % 16) == 0))
                                _tprintf(_T("%02X\n"), readerStates[0].rgbAtr[i]);
                            else
                                _tprintf(_T("%02X "), readerStates[0].rgbAtr[i]);
                        }
                        _tprintf(_T("\n"));
                    }

                    readerStates[0].dwCurrentState = readerStates[0].dwEventState;
                }
            }
        }
    }

    // Release context
    _tprintf(_T("Releasing the context...\n"));
    lReturn = SCardReleaseContext(hSC);
    if (lReturn != SCARD_S_SUCCESS)
        _tprintf(_T("Error: SCardReleaseContext failed with error 0x%08lx\n"), lReturn);
        
    // Deallocate reader name
    for (i = 0; i < (DWORD) numReaders; i++)
        free(readerName[i]);
    free(readerName);
	
	return 0;
}

int HexStringToByteArray(const TCHAR *string, BYTE *array, int arraySize)
{
    int len = 0;
    bool first = true;
    int stringLen = _tcslen(string);
    int i;

    for (i = 0; i < stringLen; i++)
    {
        if ((string[i] >= '0') && (string[i] <= '9'))
        {
            if (first)
            {
                array[len] = (string[i] - '0') << 4;
                first = false;
            }
            else
            {
                array[len] |= (string[i] - '0');
                first = true;
                len++;
            }
        }
        else if ((string[i] >= 'A') && (string[i] <= 'F'))
        {
            if (first)
            {
                array[len] = (string[i] - 'A' + 10) << 4;
                first = false;
            }
            else
            {
                array[len] |= (string[i] - 'A' + 10);
                first = true;
                len++;
            }
        }
        else if ((string[i] >= 'a') && (string[i] <= 'f'))
        {
            if (first)
            {
                array[len] = (string[i] - 'a' + 10) << 4;
                first = false;
            }
            else
            {
                array[len] |= (string[i] - 'a' + 10);
                first = true;
                len++;
            }
        }

        if (len >= arraySize)
            break;
    }

    return len;
}

int CheckResponse(const TCHAR *line)
{
    int count = 0;
    DWORD i;

    for (i = 0; i < _tcslen(line); i++)
    {
        if ((line[i] >= '0') && (line[i] <= '9') ||
            (line[i] >= 'A') && (line[i] <= 'F') ||
            (line[i] >= 'a') && (line[i] <= 'f') ||
            (line[i] == 'X') && (line[i] == 'x'))
        {
            count++;
        }
    }
    
    return count;
}

int CompareResponse(const TCHAR *cmpString, BYTE *rsp, DWORD rspLen)
{
    int ret = 0;
    bool first;
    BYTE byte;
    DWORD len;
    DWORD i;
    DWORD j;

    _tprintf(_T("Expected:\n"));

    len = 0;
    i = 0;
    j = 0;
    first = true;
    while (cmpString[i] != '\0')
    {
        // Convert compare character to byte
        if ((cmpString[i] >= '0') && (cmpString[i] <= '9'))
        {
            if (first)
            {
                byte = (cmpString[i] - '0') << 4;
                first = false;
            }
            else
            {
                byte |= (cmpString[i] - '0');
                first = true;
                
                if ((len > 0) && (((len + 1) % 16) == 0))
                    _tprintf(_T("%02X\n"), byte);
                else
                    _tprintf(_T("%02X "), byte);

                // Increment the length of compare string
                len++;

                if (j >= rspLen)
                    ret = -1;
                else
                {
                    // Compare
                    if (rsp[j] != byte)
                        ret = -1;

                    // Next response byte
                    j++;
                }
            }
        }
        else if ((cmpString[i] >= 'A') && (cmpString[i] <= 'F'))
        {
            if (first)
            {
                byte = (cmpString[i] - 'A' + 10) << 4;
                first = false;
            }
            else
            {
                byte |= (cmpString[i] - 'A' + 10);
                first = true;
                
                if ((len > 0) && (((len + 1) % 16) == 0))
                    _tprintf(_T("%02X\n"), byte);
                else
                    _tprintf(_T("%02X "), byte);

                // Increment the length of compare string
                len++;

                if (j >= rspLen)
                    ret = -1;
                else
                {
                    // Compare
                    if (rsp[j] != byte)
                        ret = -1;

                    // Next response byte
                    j++;
                }
            }
        }
        else if ((cmpString[i] >= 'a') && (cmpString[i] <= 'f'))
        {
            if (first)
            {
                byte = (cmpString[i] - 'a' + 10) << 4;
                first = false;
            }
            else
            {
                byte |= (cmpString[i] - 'a' + 10);
                first = true;
                
                if ((len > 0) && (((len + 1) % 16) == 0))
                    _tprintf(_T("%02X\n"), byte);
                else
                    _tprintf(_T("%02X "), byte);

                // Increment the length of compare string
                len++;

                if (j >= rspLen)
                    ret = -1;
                else
                {
                    // Compare
                    if (rsp[j] != byte)
                        ret = -1;

                    // Next response byte
                    j++;
                }
            }
        }
        else if ((cmpString[i] == 'X') || (cmpString[i] == 'x'))
        {
            if (first)
            {
                byte = 0;
                first = false;
            }
            else
            {
                byte = 0;
                first = true;
                
                if ((len > 0) && (((len + 1) % 16) == 0))
                    _tprintf(_T("XX\n"));
                else
                    _tprintf(_T("XX "), byte);
                    
                // Increment the length of compare string
                len++;

                if (j >= rspLen)
                    ret = -1;
                else
                {
                    // Next response byte
                    j++;
                }
            }
        }

        // Next compare character
        i++;
    }
    
    _tprintf(_T("\n"));

    if (len != rspLen)
        ret = -1;

    return ret;
}
