// ACS-IBE demo (2-in-1), V1.7, Windows
// _DEMO_MODE_ must be 0 or 1: 0 - IBE-demo, 1 - ACS-demo
#define _DEMO_MODE_         0

#pragma warning (disable:4996)
#include <tchar.h>
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

#include "rd0xAB.h"

#if _DEMO_MODE_ == 0        // demo mode selector
#pragma message("*** IBE demo compiling ***")
#define PRG_NAME            TEXT("ibe-demo.exe")        // program name
#define DEVM_CN             (2 + 8)                     // ACS(0) or IBE(2) mode + buzz_en(+8)
#define OPTS_CN             (4)                         // rwc_en(+4)+sto_en(+2)+polar(+1)
#define TMRS_CN             ((15 << 4) + 9)             // lnk_tout (0..15 << 4) + on_tout(0..15)
#else
#pragma message("*** ACS demo compiling ***")
#define PRG_NAME            TEXT("acs-demo.exe")        // program name
#define DEVM_CN             (0 + 8)                     // ACS(0) or IBE(2) mode + buzz_en(+8)
#define OPTS_CN             (4)                         // rwc_en(+4)+sto_en(+2)+polar(+1)
#define TMRS_CN             ((15 << 4) + 9)             // lnk_tout (0..15 << 4) + on_tout(0..15)
#endif  // endif for demo mode selector

// convert uidlen, atqa, sak to card type (copied from mif0xAB.cpp)
LPTSTR acq2type_verb(DWORD acq)
{
    LPTSTR    csType = TEXT("UNKN");

    if (acq == 0x07004400) csType = TEXT("UL");         // doub, 0044, 00 MIFARE Ultralight CL2
    else if (acq == 0x04000409) csType = TEXT("MINI");  // once, 0004, 09 MIFARE Mini

    else if (acq == 0x04000408) csType = TEXT("CL1K");  // once, 0004, 08 MIFARE 1K
    else if (acq == 0x04000488) csType = TEXT("CL1K");  // once, 0004, 18 MIFARE 1K
    else if (acq == 0x04000218) csType = TEXT("CL4K");  // once, 0002, 18 MIFARE 4K

    else if (acq == 0x07000404) csType = TEXT("CL1K1"); // once, 0004, 04 Any MIFARE CL1
    else if (acq == 0x07000204) csType = TEXT("CL4K1"); // once, 0002, 04 Any MIFARE CL1
    else if (acq == 0x07004404) csType = TEXT("CL1K1"); // once, 0044, 04 Any MIFARE CL1
    else if (acq == 0x07004204) csType = TEXT("CL4K1"); // once, 0042, 04 Any MIFARE CL1

    else if (acq == 0x07004408) csType = TEXT("PL2S1"); // doub, 0044, 08 MIFARE Plus CL2 2K Sec.lev.1
    else if (acq == 0x07004218) csType = TEXT("PL4S1"); // doub, 0042, 18 MIFARE Plus CL2 4K Sec.lev.1

    else if (acq == 0x04000410) csType = TEXT("PL2S2"); // once, 0004, 10 MIFARE Plus 2K Sec.lev.2
    else if (acq == 0x04000211) csType = TEXT("PL4S2"); // once, 0002, 11 MIFARE Plus 4K Sec.lev.2

    else if (acq == 0x07004410) csType = TEXT("PL2S2"); // doub, 0044, 10 MIFARE Plus CL2 2K Sec.lev.2
    else if (acq == 0x07004211) csType = TEXT("PL4S2"); // doub, 0042, 11 MIFARE Plus CL2 4K Sec.lev.2

    else if (acq == 0x04000420) csType = TEXT("PL2S3"); // once, 0004, 20 MIFARE Plus 2K Sec.lev.3
    else if (acq == 0x04000220) csType = TEXT("PL4S3"); // once, 0002, 20 MIFARE Plus 4K Sec.lev.3
    else if (acq == 0x07004420) csType = TEXT("PL2S3"); // doub, 0044, 20 MIFARE Plus 2K Sec.lev.3
    else if (acq == 0x07004220) csType = TEXT("PL4S3"); // doub, 0042, 20 MIFARE Plus 4K Sec.lev.3

    else if (acq == 0x07034420) csType = TEXT("DF2");   // doub, 0344, 20 MIFARE DESFire CL2
    else if (acq == 0x07034460) csType = TEXT("DF2");   // doub, 0344, 60 MIFARE DESFire card emulation
    else if ((acq&(~0xFF0000FF)) == 0x07000024) csType = TEXT("DF1");   // doub, xxxx, 24 MIFARE DESFire CL1

    else if ((acq&(~0x000F0000)) == 0x04000420) csType = TEXT("SM1K");  // once, 0x04, 20 SmartMX with MIFARE 1K emulation
    else if ((acq&(~0x000F0000)) == 0x04000428) csType = TEXT("SM2K");  // once, 0x04, 28 SmartMX | SAM AV2 with MIFARE 2K emulation
    else if ((acq&(~0x000F0000)) == 0x04000468) csType = TEXT("SM4K");  // once, 0x04, 68 SmartMX with MIFARE 4K emulation

    else if ((acq&(~0x000F0000)) == 0x04000238) csType = TEXT("SM4K");  // once, 0x02, 38 SmartMX with MIFARE 4K emulation
    else if ((acq&(~0x000F0000)) == 0x07004820) csType = TEXT("SMMX");  // once, 0x48, 20 SmartMX

    return csType;
}

void verbose_err(DWORD err, LPTSTR szErr)
{
    if (err == LERR_SUCCESS) lstrcpy(szErr, TEXT("Success!"));
    else if (err == LERR_FAIL) lstrcpy(szErr, TEXT("Command execution error!"));
    else if (err == LERR_HARDWARE) lstrcpy(szErr, TEXT("Hardware error!"));
    else if (err == LERR_TIMEOUT_TX) lstrcpy(szErr, TEXT("Communication error: TX timeout!"));
    else if (err == LERR_TIMEOUT_RX) lstrcpy(szErr, TEXT("Communication error: RX timeout!"));
    else if (err == LERR_NOTSYNC) lstrcpy(szErr, TEXT("Communication error: synchronization lost!"));
    else if (err == LERR_NOREADER) lstrcpy(szErr, TEXT("Can't find reader!"));
    else if (err == LERR_NOCARD) lstrcpy(szErr, TEXT("Can't find card!"));
    else if (err == LERR_INVALIDCARD) lstrcpy(szErr, TEXT("Invalid card type!"));
    else if (err == LERR_INVALIDKEY) lstrcpy(szErr, TEXT("Invalid auth key or card type!"));
    else if (err == LERR_IOERR) lstrcpy(szErr, TEXT("I/O error!"));
    else if (err == LERR_DATAREAD) lstrcpy(szErr, TEXT("Can't read data from card!"));
    else if (err == LERR_DATAWRITE) lstrcpy(szErr, TEXT("Can't write data to card!"));
    else if (err == LERR_BUSY) lstrcpy(szErr, TEXT("Device busy error!"));
    else lstrcpy(szErr, TEXT("Unknown error!"));
}

void usage(void)
{
    _tprintf(TEXT("Usage for USB: %s\n"), PRG_NAME);
    _tprintf(TEXT("Usage for COM: %s COM10 9600\n"), PRG_NAME);
}

// *** main ***
int __cdecl _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
    CCR             ccr;
    DWORD           port = 0, cbr = 0;
    DWORD           err = LERR_SUCCESS;
    BYTE            dm = 0, opt = 0, tmr = 0;

    TCHAR           szLocal[32];
    BYTE            buf[14], key;
    DWORD           cnt;
    bool            brkf = false;

    memset(&ccr, 0, sizeof(ccr));
    _tprintf(TEXT("ACS/IBE demo, COM/USB, V%s.\n--\n"), RDRAPI_VER);
    // Parse command line arguments
    if ((argc != 1) && (argc != 3))
    {
        usage();
        goto main_ferr;
    }
    if (argc == 3)
    {
        if ((lstrlen(argv[1]) >= 4) &&
            (((argv[1][0] == 'C') && (argv[1][1] == 'O') && (argv[1][2] == 'M')) ||
            ((argv[1][0] == 'c') && (argv[1][1] == 'o') && (argv[1][2] == 'm'))))
        {
            _stscanf_s(&argv[1][3], TEXT("%u"), &port);
            _stscanf_s(argv[2], TEXT("%u"), &cbr);
        }
    }
    if ((port == 0) && (argc > 1))
    {
        _tprintf(TEXT("Invalid parameters!\n\n"));
        usage();
        goto main_ferr;
    }

    // if port != 0 then try open COM else try open USB
    if (port)
    {
        _tprintf(TEXT("COM%u, CBR=%u. "), port, cbr);
        if ((err = link_comopen(port, cbr, &ccr))) goto main_ferr;
    }
    else
    {
        _tprintf(TEXT("USB. "));
        if ((err = link_hidopen(0, NULL, &ccr)))
        {
             err = LERR_NOREADER;
             goto main_ferr;
        }
    }
    // read device description: HW, NVMSZ, model, SN
    if ((err = link_packet(&ccr, CMG_VER, 0, NULL, 0, buf, 6)) != LERR_SUCCESS)
    {
         if ((port) && (err == LERR_HARDWARE)) err = LERR_NOREADER;
         goto main_ferr;
    }
    _tprintf(TEXT("Reader "));
    if ((buf[2] & 0xF0) == 0x10) _tprintf(TEXT("RD-01A"));
    else if ((buf[2] & 0xF0) > 0x10) _tprintf(TEXT("RD-%02uAB"), buf[2] / 16);
    else _tprintf(TEXT("???"));

    _tprintf(TEXT(", SN:%08X, HW:%u.%u, %uKb.\n"),
        _byteswap_ulong(*((PDWORD)&buf[2])), buf[0] >> 4, buf[0] & 15, ((DWORD)buf[1] + 1) / 2);
    if (buf[0] < 0x20)
    {
        _tprintf(TEXT(" ERROR: HW version must be >= 2.0!\n"));
        goto main_ferr;
    }
    _tprintf(TEXT("--\n"));
    // temporary set DIR mode, buzzer on
    if ((err = link_packet(&ccr, CMS_MOD, 3 + 8, NULL, 0, NULL, 0)) != LERR_SUCCESS) goto main_ferr;

    // *** ACS/IBE mode settings ***
    if ((DEVM_CN & 3) == 0) lstrcpy(szLocal, TEXT("ACS"));
    else if ((DEVM_CN & 3) == 2) lstrcpy(szLocal, TEXT("IBE"));
    else _stprintf(szLocal, TEXT("%u - invalid!"), DEVM_CN & 3);
    _tprintf(TEXT("Mode .............. %s.\n"), szLocal);
    if (((DEVM_CN & 3) != 0) && ((DEVM_CN & 3) != 2)) goto main_ferr;
    _tprintf(TEXT("Buzzer ............ %s.\n"), (DEVM_CN & 8) ? TEXT("enable") : TEXT("disable"));
    _tprintf(TEXT("RW-card ........... %s.\n"), (OPTS_CN & 4) ? TEXT("enable") : TEXT("disable"));
    _tprintf(TEXT("Polarity .......... %s.\n"), (OPTS_CN & 1) ? TEXT("inverted") : TEXT("direct"));
    _tprintf(TEXT("Ack timeout ....... %.1fs.\n"), (float)((TMRS_CN & 0xF0) | 15) * 25 / 1000);             // time = tmr_nibble * 16 * 25mS
    _tprintf(TEXT("Unlock time ....... %.1fs.\n\n"), (float)(((TMRS_CN & 0x0F) << 4) | 15) * 50 / 1000);    // time = tmr_nibble * 16 * 50mS

    _tprintf(TEXT("--\nSetup ACS/IBE mode... "));
    // *** set if difference only ***
    // options
    if ((err = link_packet(&ccr, CMG_OPT, 0x80, NULL, 0, &opt, 1)) != LERR_SUCCESS) goto main_ferr;
    if ((opt != OPTS_CN) && ((err = link_packet(&ccr, CMS_OPT, OPTS_CN, NULL, 0, NULL, 0)) != LERR_SUCCESS)) goto main_ferr;
    // timers
    if ((err = link_packet(&ccr, CMG_TMR, 0, NULL, 0, &tmr, 1)) != LERR_SUCCESS) goto main_ferr;
    if ((tmr != TMRS_CN) && ((err = link_packet(&ccr, CMS_TMR, TMRS_CN, NULL, 0, NULL, 0)) != LERR_SUCCESS)) goto main_ferr;
    // device mode
    if ((err = link_packet(&ccr, CMG_MOD, 0x80, NULL, 0, &dm, 1)) != LERR_SUCCESS) goto main_ferr;
    if ((err = link_packet(&ccr, CMS_MOD, DEVM_CN | ((dm != DEVM_CN) ? 0x80 : 0), NULL, 0, NULL, 0)) != LERR_SUCCESS) goto main_ferr;
    // final beep :-)
    if ((err = link_packet(&ccr, CMF_BUZ, 0x02, NULL, 0, NULL, 0)) != LERR_SUCCESS) goto main_ferr;
    _tprintf(TEXT("Ok.\n"));

    // *** main loop ***
    _tprintf(TEXT("--\nPress 1 for lock, 2 for unlock, 3 for default action (int. db in ACS-mode).\nPress Esc for exit.\n\nWait for card...\n"));
    while (!brkf)
    {
        DWORD           stoptick;
        SYSTEMTIME      ltm;

        if (_kbhit())
        {
            if ((key = (BYTE)_getch()) == 0) key = (BYTE)_getch();
            if (key == 0x1B)
            {
                _tprintf(TEXT("^Esc pressed!\n"));
                brkf = true;
                break;
            }
        }
        // wait for unlock request from reader: length = 14b{1b(uidlen) + 10b(uid) + 1b(sak) + 2b(atqa)}
        if ((err = link_rqwait(&ccr, buf, 14)) == LERR_HARDWARE) goto main_ferr;
        if ((err != LERR_SUCCESS) || ((buf[0] & 0x0F) > 10)) continue;  // uidlen <= 10, b.7 - RW-flag

        // build type string
        lstrcpy(szLocal, acq2type_verb( ((DWORD)(buf[0] & 0x0F) << 24) |                // uidlen
            (((DWORD)buf[12]) << 16) | (((DWORD)buf[13]) << 8) | ((DWORD)buf[11])));    // ATQA + SAK
        if (buf[0] & MFDM_RWC) lstrcat(szLocal, TEXT("-RW"));
        // print request message
        GetLocalTime(&ltm);
        _tprintf(TEXT("[%2u/%02u/%02u %2u:%02u:%02u] %8s"),
            ltm.wDay, ltm.wMonth, ltm.wYear % 100,
            ltm.wHour, ltm.wMinute, ltm.wSecond,
            szLocal);   // print time, card type
        // build UID string
        for (szLocal[0] = 0, cnt = 0; cnt < (DWORD)(buf[0] & 0x0F); cnt++)
            _stprintf(&szLocal[lstrlen(szLocal)], TEXT("%02X"), buf[cnt + 1]);
        _tprintf(TEXT(", UID:%14s. Unlock? (1|2|3): "), szLocal);   // print UID
        fflush(stdout);
        // loop for getting acknowledge from user
        key = 0;
        stoptick = GetTickCount() + (TMRS_CN & 0xF0) * 25;  // count timeout time
        while (GetTickCount() < stoptick)
        {
            if (_kbhit())
            {
                if ((key = (BYTE)_getch()) == 0) key = (BYTE)_getch();
                if ((key == '1') || (key == '2') || (key == '3') || (key == 0x1B)) break;
            }
        }
        if (key == 0x1B) brkf = true;
        if ((key != '1') && (key != '2') && (key != '3')) _tprintf(TEXT("none.\n"));
        else
        {
            if (key == '1') _tprintf(TEXT("lock"));
            else if (key == '2') _tprintf(TEXT("unlk"));
            else _tprintf(TEXT("dflt"));
            _tprintf(TEXT("..."));
            key = (key & 3) - 1;
            // *** sending ack ***
            // 0 - no, 1 - yes, 2 - by reader db
            if ((err = link_rqack(&ccr, key)) == LERR_HARDWARE) goto main_ferr;
            if (err == LERR_SUCCESS) _tprintf(TEXT(" Ok.\n"));
            else _tprintf(TEXT(" Fail.\n"));
        }
    }
main_ferr:
    link_close(&ccr);
    if ((err) && (!brkf))
    {
        verbose_err(err, szLocal);
        _tprintf(TEXT("%s\n"), szLocal);
    }
    return 0;
}
