// *** mf0xAB.cpp, mifare API, Ver. 1.7, Windows ***
// *** based on rd0xAB.cpp, Ver. 1.7, Windows  ***
#pragma message("*** mf0xAB Ver.1.7 ***")
#include <stdlib.h>
#include "mf0xAB.h"

// *** convert uidlen, atqa, sak to card type ***
DLL_API BYTE acq2type(DWORD acq, BYTE atslen, PBYTE ats)
{
    BYTE    csType = SCD_UNKNOWN;

    if (acq == 0x07004400) csType = SCD_UL;         // doub, 0044, 00 MIFARE Ultralight CL2
    else if (acq == 0x04000409) csType = SCD_MINI;  // once, 0004, 09 MIFARE Mini

    else if (acq == 0x04000408) csType = SCD_CL1K;  // once, 0004, 08 MIFARE 1K
    else if (acq == 0x04000488) csType = SCD_CL1K;  // once, 0004, 18 MIFARE 1K
    else if (acq == 0x04000218) csType = SCD_CL4K;  // once, 0002, 18 MIFARE 4K
    // * dup *
    //else if (acq == 0x04000408) csType = SCD_PL2S1; // once, 0004, 08 MIFARE Plus 2K Sec.lev.1
    //else if (acq == 0x04000218) csType = SCD_PL4S1; // once, 0002, 18 MIFARE Plus 4K Sec.lev.1

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

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

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

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

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

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

    else if ((acq&(~0x000F0000)) == 0x04000420) csType = SCD_SM1K;  // once, 0x04, 20 SmartMX with MIFARE 1K emulation
    else if ((acq&(~0x000F0000)) == 0x04000428) csType = SCD_SM2K;  // once, 0x04, 28 SmartMX with MIFARE 2K emulation
    else if ((acq&(~0x000F0000)) == 0x04000468) csType = SCD_SM4K;  // once, 0x04, 68 SmartMX with MIFARE 4K emulation

    else if ((acq&(~0x000F0000)) == 0x04000238) csType = SCD_SM4K;  // once, 0x02, 38 SmartMX with MIFARE 4K emulation
    else if ((acq&(~0x000F0000)) == 0x07004820) csType = SCD_SMMX;  // once, 0x48, 20 SmartMX
    // chk ats record
    if ((atslen) && (ats))
    {
        BYTE    ats_plus_cn[] = { 0x0C, 0x75, 0x77, 0x80, 0x02, 0xC1, 0x05, 0x2F, 0x2F, 0x01 };
        if ((atslen >= sizeof(ats_plus_cn)) &&
            (!memcmp(ats, ats_plus_cn, sizeof(ats_plus_cn))) &&
            (csType == SCD_CL4K)) csType = SCD_PL4S1;   // PL simulate CL
    }
    return csType;
}

// *** DIR-mode initialization ***
DLL_API DWORD mfInit(PCCR pccr, PBYTE pmodel, PBYTE phw, PDWORD psn)
{
    BYTE        buf[6];
    DWORD       err;

    if (!pccr) return LERR_FAIL;
    // read data: model, HW version, NVM size,  SN
    if ((err = link_packet(pccr, CMG_VER, 0, NULL, 0, buf, 6)) != LERR_SUCCESS) goto ferr_exit;
    if (*((PDWORD)&buf[2]) == 0) return LERR_FAIL;
    if (pmodel) *pmodel = buf[2] >> 4;  // set model number
    if (phw) *phw = buf[0];             // set HW version
    if (psn) *psn = (((DWORD)buf[2]) << 24) | (((DWORD)buf[3]) << 16) | (((DWORD)buf[4]) << 8) | ((DWORD)buf[5]);
    if (buf[0] < UDEV_REV) return LERR_FAIL;    // HW version must be >= 2.0
    // set DIR mode & buzzer on without store: mode +3, buzzer +8
    if ((err = link_packet(pccr, CMS_MOD, 3 + 8, NULL, 0, NULL, 0)) != LERR_SUCCESS) goto ferr_exit;
    // carrier enable, phase detector - auto
    if ((err = link_packet(pccr, CMS_ISO, 0x00, NULL, 0, NULL, 0)) != LERR_SUCCESS) goto ferr_exit;
    // final beep, good beep
    err = link_packet(pccr, CMF_BUZ, 0x02, NULL, 0, NULL, 0);
ferr_exit:
    return err;
}

// *** beep ***
DLL_API DWORD mfBeep(PCCR pccr, BYTE tone)
{
    if ((!pccr) || (tone > 5)) return LERR_FAIL;
    return link_packet(pccr, CMF_BUZ, tone, NULL, 0, NULL, 0);
}

// *** carrier break ***
DLL_API DWORD mfBrkCarrier(PCCR pccr, WORD time_ms)
{
    DWORD           err;
    BYTE            buf[2];

    if (!pccr) return LERR_FAIL;
    // read phd level in buf[1]
    if ((err = link_packet(pccr, CMG_ISO, 0, NULL, 0, buf, 2)) != LERR_SUCCESS) goto ferr_exit;
    // carrier disable
    if ((err = link_packet(pccr, CMS_ISO, buf[1] | 0x80, NULL, 0, NULL, 0)) != LERR_SUCCESS) goto ferr_exit;
    if (time_ms) Sleep((DWORD)(time_ms));
    // carrier enable, phase detector - auto
    err = link_packet(pccr, CMS_ISO, buf[1] & 0x7F, NULL, 0, NULL, 0);
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if (err != LERR_SUCCESS) err = LERR_IOERR;
    return err;
}

// *** card type detection ***
DLL_API DWORD mfType(PCCR pccr, BYTE timeout, PBYTE ptype, PBYTE puidlen, PBYTE puid, PWORD patqa, PBYTE psak, PBYTE patslen, PBYTE pats)
{
    DWORD           err = LERR_SUCCESS;
    BYTE            buf[33];
    BYTE            uidlen, sak;
    WORD            atqa;
    BYTE            ats[SZ_ATS], atslen;
    BOOL            scf = FALSE, rwf = FALSE, rwtst = FALSE;
    DWORD           cnt;

    if ((!pccr) && (timeout > 7)) return LERR_FAIL;
    // reset vars
    uidlen = 0;                         // reset length of UID
    atqa = 0;                           // reset ATQA
    sak = 0;                            // reset SAK
    atslen = 0;                         // reset ATS length
    memset(ats, 0, sizeof(ats));        // reset ATS data
    if (puidlen)
    {
        if (*puidlen & MFDM_RWC) rwtst = TRUE;
        *puidlen = 0;                   // reset length of UID
    }
    if (puid) memset(puid, 0, SZ_UID);  // reset UID
    if (psak) *psak = 0;                // reset SAK
    if (patqa) *patqa = 0;              // reset ATQA
    if (ptype) *ptype = SCD_UNKNOWN;    // reset card type
    if (patslen) *patslen = 0;          // reset atslen
    if (pats) memset(pats, 0, SZ_ATS);  // reset ATS

    if ((err = mfBrkCarrier(pccr, 100)) != LERR_SUCCESS) goto ferr_exit;

    // *** connect to normal card ***
    for (cnt = 2; cnt != 0; cnt--)
    {
        buf[0] = PICC_REQALL;   // PICC_REQALL - wake up from sleep mode
        if (((err = link_packet(pccr, CMF_CSEL, timeout | 8, buf, 1, buf, SZ_SCCD)) != LERR_SUCCESS) &&
            (err != LERR_FAIL)) goto ferr_exit;
        if (err == LERR_SUCCESS) break;
    }
    if (cnt == 0) goto rwc_check;       // jump to RW-connect if connection failed
    // SELECT successfully completed, fill vars
    uidlen = buf[0] & 0x0F;
    atqa = (((WORD)buf[12]) << 8) | ((WORD)buf[13]);
    sak = buf[11];

    if (buf[0] & 0x0F) scf = TRUE;              // set flag if UID present
    if (puidlen) *puidlen = uidlen;             // set length of UID
    if (puid) memcpy(puid, &buf[1], SZ_UID);    // set UID
    if (psak) *psak = sak;                      // set SAK
    if (patqa) *patqa = atqa;                   // set ATQA

    // *** request for card type ***
    if ((patslen) || (pats))
    {
        // attempts to read ATS
        for (cnt = 3; cnt != 0; cnt--)
        {
            buf[0] = PICC_RATS; buf[1] = 0;         // command identifies the PICC type
            if (((err = link_packet(pccr, CMF_LCMD, 2, buf, 2, buf, 1)) != LERR_SUCCESS) &&
                (err != LERR_FAIL)) goto ferr_exit;
            if (err == LERR_SUCCESS) break;
            if (cnt)
            {
                if ((err = mfBrkCarrier(pccr, 100)) != LERR_SUCCESS) goto ferr_exit;
                if (((err = link_packet(pccr, CMF_CSEL, timeout | 8, buf, 1, buf, SZ_SCCD)) != LERR_SUCCESS) &&
                    (err != LERR_FAIL)) goto ferr_exit;
            }
        }
        if (err == LERR_SUCCESS)
        {
            if (buf[0] == 4)    // check 4-bit response
            {
                // CMG_BUF, the receive buffer reading, L = 2 (length byte + data byte)
                if ((err = link_packet(pccr, CMG_BUF, 2, NULL, 0, buf, 2)) != LERR_SUCCESS) goto ferr_exit;
                ats[0] = buf[1];
                atslen = 1;
            }
            else if (buf[0] >= 24)
            {
                BYTE    atsl = (buf[0] % 8) ? (buf[0] / 8 + 1) : (buf[0] / 8);
                // CMF_CHKCRCA, crc check
                if ((err = link_packet(pccr, CMF_CCRCA, 0, NULL, 0, buf, 1)) != LERR_SUCCESS) goto ferr_exit;
                if (buf[0] & 1) // check crc in buffer (status check)
                {
                    // CMG_BUF, the receive buffer reading, L = 1 + atsl - 2 (byte_length + data_length - 2b_length_of_CRC)
                    if ((err = link_packet(pccr, CMG_BUF, atsl + 1 - 2, NULL, 0, buf, atsl + 1 - 2)) != LERR_SUCCESS) goto ferr_exit;
                    atsl -= 2;
                    memcpy(ats, &buf[1], atsl);
                    atslen = atsl;
                }
            }
            // set ats record
            if (patslen) *patslen = atslen;
            if ((pats) && (atslen)) memcpy(pats, ats, atslen);
        }
    }
    // set card type
    if (ptype) *ptype = acq2type((((DWORD)uidlen) << 24) | (((DWORD)atqa) << 8) | ((DWORD)sak), atslen, ats);   // set card type

    // card off
    buf[0] = PICC_HALT; buf[1] = 0;     // HALT command
    if ((err = link_packet(pccr, CMF_LCMD, 0x02, buf, 2, buf, 1)) == LERR_HARDWARE) goto ferr_exit;
rwc_check:
    // *** connect to RW card ***
    if ((puidlen) && (rwtst))
    {
        for (cnt = 2; cnt != 0; cnt--)
        {
            buf[0] = PICC_REQRW;            // PICC_REQRW - wake up command for RW card
            if (((err = link_packet(pccr, CMF_CSEL, timeout | 8, buf, 1, buf, SZ_SCCD)) != LERR_SUCCESS) &&
                (err != LERR_FAIL)) goto ferr_exit;
            if (err == LERR_SUCCESS) break;
            if ((err = mfBrkCarrier(pccr, 20)) != LERR_SUCCESS) goto ferr_exit;
        }
        if (cnt)
        {
            if (puidlen) *puidlen |= MFDM_RWC;  // b.7 - RW flag
            rwf = TRUE;
            buf[0] = PICC_HALT; buf[1] = 0;     // HALT
            if ((err = link_packet(pccr, CMF_LCMD, 0x02, buf, 2, buf, 1)) == LERR_HARDWARE) goto ferr_exit;
        }
    }
    // errors analysis
    if ((scf == FALSE) && (rwf == FALSE)) err = LERR_NOCARD;
    else err = LERR_SUCCESS;
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if ((err != LERR_SUCCESS) && (err != LERR_NOCARD)) err = LERR_IOERR;
    return err;
}

// *** select card ***
DLL_API DWORD mfSelect(PCCR pccr, BYTE timeout, BYTE rq, PBYTE puidlen, PBYTE puid, PWORD patqa, PBYTE psak)
{
    DWORD       err, cnt;
    BYTE        pml = 0;                // parameters len: if 'not any UID' mode pml=11(uidlen+uid) + 4(SAKD)
    BYTE        buf[SZ_SCCD];

    if ((!pccr) || (timeout > 7)) return LERR_FAIL;
    // validate for forced UID parameters
    if ((puidlen) && (*puidlen & 0x0F))
    {
        if (!puid) return LERR_FAIL;
        buf[0] = *puidlen;              // copy 'forced' uidlen
        memcpy(&buf[1], puid, SZ_UID);  // copy forced' UID
        pml = 1 + SZ_UID;               // set new offset for other parameters
    }
    buf[pml++] = rq;    // wake up command

    for (cnt = 3; cnt != 0; cnt--)
    {
        if (((err = link_packet(pccr, CMF_CSEL, timeout | ((pml == 1) ? 8 : 0),
            buf, pml, buf, SZ_SCCD)) != LERR_SUCCESS) && (err != LERR_FAIL)) goto ferr_exit;
        if (err == LERR_SUCCESS) break;
    }
    if (cnt != 0)
    {
        if (puidlen) *puidlen = buf[0] & 0x0F;      // set length of UID
        if (puid) memcpy(puid, &buf[1], SZ_UID);    // set UID
        if (psak) *psak = buf[11];                  // set SAK
        if (patqa) *patqa = (((WORD)buf[12]) << 8) | ((WORD)buf[13]);   // set ATQA
    }
    else err = LERR_NOCARD;
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if ((err != LERR_SUCCESS) && (err != LERR_NOCARD)) err = LERR_IOERR;
    return err;
}

// *** set authorization key ***
DLL_API DWORD mfSetAK(PCCR pccr, BYTE ak, PBYTE key)
{
    if ((!pccr) || (!key)) return LERR_FAIL;
    return link_packet(pccr, CMS_AKEY, ak, key, 6, NULL, 0);
}

// *** data block authorization only then card halted ***
DLL_API DWORD mfAuth(PCCR pccr, BYTE timeout,
    BYTE acm, BYTE ak, BYTE block, PBYTE puidlen, PBYTE puid, PWORD patqa, PBYTE psak)
{
    DWORD       err, cnt;
    BYTE        pml = 0;                // parameters len: if 'not any UID' mode pml=11(uidlen+uid) + 4(SAKD)
    BOOL        auf = FALSE;
    BYTE        txbuf[SZ_SCCD + 1];
    BYTE        rxbuf[SZ_SCCD];

    if ((!pccr) || (timeout > 7)) return LERR_FAIL;
    // validate for forced UID parameters
    if ((puidlen) && (*puidlen & 0x0F))
    {
        if (!puid) return LERR_FAIL;
        txbuf[0] = *puidlen;            // copy 'forced' uidlen
        memcpy(&txbuf[1], puid, SZ_UID);// copy forced' UID
        pml = 1 + SZ_UID;               // set new offset for other parameters
    }
    // reset vars
    if (puidlen) *puidlen = 0;          // reset length of UID
    if (puid) memset(puid, 0, SZ_UID);  // reset UID
    if (psak) *psak = 0;                // reset SAK
    if (patqa) *patqa = 0;              // reset ATQA

    txbuf[pml++] = PICC_REQALL;         // total wake up for normal card

    // authorization
    for (cnt = 3; cnt != 0; cnt--)
    {
        if (((err = link_packet(pccr, CMF_CSEL, timeout | ((pml == 1) ? 8 : 0),
            txbuf, pml, rxbuf, SZ_SCCD)) != LERR_SUCCESS) && (err != LERR_FAIL)) goto ferr_exit;
        if (err == LERR_SUCCESS)
        {
            if (puidlen) *puidlen = rxbuf[0];                                   // set length of UID
            if (puid) memcpy(puid, &rxbuf[1], SZ_UID);                          // set UID
            if (psak) *psak = rxbuf[11];                                        // set SAK
            if (patqa) *patqa = (((WORD)rxbuf[12]) << 8) | ((WORD)rxbuf[13]);   // set ATQA
            // AUTH
            rxbuf[0] = ak; rxbuf[1] = block;    // KS
            if (((err = link_packet(pccr, CMF_CAUT, acm, rxbuf, 2, NULL, 0)) != LERR_SUCCESS) &&
                (err != LERR_FAIL)) goto ferr_exit;
            if (err == LERR_SUCCESS)
            {
                auf = TRUE;
                break;
            }
            else
            {
                // read extended error flags
                if ((err = link_packet(pccr, CMG_ISO, 0, NULL, 0, rxbuf, 2)) != LERR_SUCCESS) goto ferr_exit;
                if (rxbuf[0] & 1)
                {
                    err = LERR_INVALIDKEY; // SELECT complete but no authorization
                    break;
                }
            }
            // carrier break
            if ((err = mfBrkCarrier(pccr, 1)) != LERR_SUCCESS) goto ferr_exit;
        }
        else err = LERR_NOCARD;
    }
    if (auf)
    {
        txbuf[0] = PICC_HALT; txbuf[1] = 0;     // HALT command
        if ((err = link_packet(pccr, CMF_LCMD, 2, txbuf, 2, rxbuf, 1)) == LERR_HARDWARE) goto ferr_exit;
        err = LERR_SUCCESS;
    }
    else if (err != LERR_NOCARD) err = LERR_INVALIDKEY;
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if ((err != LERR_SUCCESS) && (err != LERR_NOCARD) && (err != LERR_INVALIDKEY)) err = LERR_IOERR;
    return err;
}

// *** send long command and receive answer ***
DLL_API DWORD mfLong(PCCR pccr, PBYTE txbuf, BYTE txlen, PBYTE ans, PBYTE pabitlen)
{
    DWORD       err;
    BYTE        buf[33];
    BYTE        bytelen = 0, bitlen = 0;

    if ((!pccr) || (!txbuf) || (!txlen)) return LERR_FAIL;
    if (pabitlen) *pabitlen = 0;
    memset(buf, 0, sizeof(buf));    // reset buf

    if (((err = link_packet(pccr, CMF_LCMD, txlen, txbuf, txlen, buf, 1)) != LERR_SUCCESS) && (err != LERR_FAIL)) goto ferr_exit;
    if (err == LERR_SUCCESS)
    {
        if (bitlen = buf[0])  // verify answer len
        {
            bytelen = (buf[0] % 8) ? buf[0] / 8 + 1 : buf[0] / 8;
            // CMG_BUF, read data from RX-buffer, L=alen/8 (data + datalen)
            if ((err = link_packet(pccr, CMG_BUF, bytelen + 1, NULL, 0, buf, bytelen + 1)) != LERR_SUCCESS) goto ferr_exit;
        }
        if (pabitlen) *pabitlen = bitlen;
        if ((ans) && (bytelen)) memcpy(ans, &buf[1], bytelen);
    }
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if ((err != LERR_SUCCESS) && (err != LERR_FAIL)) err = LERR_IOERR;
    return err;
}

// local service function
WORD _up_crc(BYTE ch, PWORD pwCrc)
{
    ch = (ch ^ (BYTE)((*pwCrc) & 0x00FF));
    ch = (ch ^ (ch << 4));
    *pwCrc = (*pwCrc >> 8) ^ ((WORD)ch << 8) ^ ((WORD)ch << 3) ^ ((WORD)ch >> 4);
    return(*pwCrc);
}

// *** calculate CRC by buf ***
DLL_API WORD mfCalcCrcA(PBYTE buf, DWORD len)
{
    BYTE    cb;
    WORD    wCrc;
    wCrc = 0x6363; // ITU-V.41
    do
    {
        cb = *buf++;
        _up_crc(cb, &wCrc);
    } while (--len);
    return wCrc;    // low byte tx first; high byte tx second
}

// *** calculate CRC and append buf ***
DLL_API void mfAppendCrcA(PBYTE buf, DWORD len)
{
    *((PWORD)&buf[len]) = mfCalcCrcA(buf, len);
}

BYTE get_bit(PBYTE buf, DWORD idx)
{
    return ((buf[idx / 8] >> (idx % 8)) & 1);
}

void set_bit(PBYTE buf, DWORD idx, BYTE bi)
{
    if (bi) buf[idx / 8] |= (bi << (idx % 8));
}

BYTE addEncPar(DWORD len, PBYTE dtbuf, PBYTE pabuf, PBYTE outbuf)
{
    DWORD       src, dst;

    for (src = 0, dst = 0; src<(len * 8); src++, dst++)
    {
        set_bit(outbuf, dst, get_bit(dtbuf, src));
        if ((src % 8) == 7) set_bit(outbuf, ++dst, pabuf[src / 8]);
    }
    return ((BYTE)(dst & 0xFF));
}

// *** basis function for card IO ***
DLL_API DWORD mfIO(PCCR pccr, PBYTE buf, BYTE len, PBYTE par, PBYTE ans, PBYTE pabitlen, PBYTE anspar)
{
    DWORD       err;
    BYTE        txbuf[33], rxbuf[33], rxpar[33];
    BYTE        iost = 0, bytelen = 0, bitlen = 0;

    if ((!pccr) || (!buf) || (!len) || (!par)) return LERR_FAIL;
    memset(txbuf, 0, sizeof(txbuf));
    memset(rxbuf, 0, sizeof(rxbuf));
    if (pabitlen) *pabitlen = 0;
    // build TX-buffer (with parity bits: 9 bits for data byte)
    txbuf[0] = addEncPar(len, buf, par, &txbuf[1]);
    len = (txbuf[0] % 8) ? txbuf[0] / 8 + 1 : txbuf[0] / 8;
    //  transfer data to TX-buffer
    if ((err = link_packet(pccr, CMS_BUF, len + 1, txbuf, len + 1, NULL, 0)) != LERR_SUCCESS) goto ferr_exit;
    // IO with tag
    if ((err = link_packet(pccr, CMF_IO, 0, NULL, 0, &iost, 1)) != LERR_SUCCESS) goto ferr_exit;
    if (!iost) goto ferr_noanswer;
    // decode RAW-buffer
    if ((err = link_packet(pccr, CMF_D106A, 0, NULL, 0, &iost, 1)) != LERR_SUCCESS) goto ferr_exit;
    if (!iost) goto ferr_noanswer;
    // read data from RX-buffer
    if ((err = link_packet(pccr, CMG_BUF, iost + 1, NULL, 0, rxbuf, iost + 1)) != LERR_SUCCESS) goto ferr_exit;
    if (anspar)
    {
        // read parity bits from RX-buffer
        if ((err = link_packet(pccr, CMF_D106A, 2, NULL, 0, &iost, 1)) != LERR_SUCCESS) goto ferr_exit;
        if (!iost) { rxbuf[0] = 0; goto ferr_noanswer; }
        if ((err = link_packet(pccr, CMG_BUF, iost + 1, NULL, 0, rxpar, iost + 1)) != LERR_SUCCESS) goto ferr_exit;
        if (rxpar[0]) memcpy(anspar, &rxpar[1], (rxpar[0] % 8) ? rxpar[0] / 8 + 1 : rxpar[0] / 8);
    }
ferr_noanswer:
    if (pabitlen) *pabitlen = rxbuf[0];
    if ((ans) && (rxbuf[0])) memcpy(ans, &rxbuf[1], (rxbuf[0] % 8) ? rxbuf[0] / 8 + 1 : rxbuf[0] / 8);
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if ((err != LERR_SUCCESS) && (err != LERR_FAIL)) err = LERR_IOERR;
    return err;
}

// *** basis function for card IO (bit based)***
DLL_API DWORD mfBitIO(PCCR pccr, PBYTE buf, BYTE blen, PBYTE ans, PBYTE pabitlen, PBYTE anspar)
{
    DWORD       err;
    BYTE        txbuf[33], rxbuf[33], rxpar[33];
    BYTE        iost = 0, bytelen = 0, bitlen = 0;

    if ((!pccr) || (!buf) || (!blen)) return LERR_FAIL;
    memset(txbuf, 0, sizeof(txbuf));
    memset(rxbuf, 0, sizeof(rxbuf));
    if (pabitlen) *pabitlen = 0;
    // build TX-buffer
    txbuf[0] = blen;
    blen = (txbuf[0] % 8) ? txbuf[0] / 8 + 1 : txbuf[0] / 8;
    memcpy(&txbuf[1], buf, blen);
    //  transfer data to TX-buffer
    if ((err = link_packet(pccr, CMS_BUF, blen + 1, txbuf, blen + 1, NULL, 0)) != LERR_SUCCESS) goto ferr_exit;
    // IO with tag
    if ((err = link_packet(pccr, CMF_IO, 0, NULL, 0, &iost, 1)) != LERR_SUCCESS) goto ferr_exit;
    if (!iost) goto ferr_noanswer;
    // decode RAW-buffer
    if ((err = link_packet(pccr, CMF_D106A, 0, NULL, 0, &iost, 1)) != LERR_SUCCESS) goto ferr_exit;
    if (!iost) goto ferr_noanswer;
    // read data from RX-buffer
    if ((err = link_packet(pccr, CMG_BUF, iost + 1, NULL, 0, rxbuf, iost + 1)) != LERR_SUCCESS) goto ferr_exit;
    if (anspar)
    {
        // read parity bits from RX-buffer
        if ((err = link_packet(pccr, CMF_D106A, 2, NULL, 0, &iost, 1)) != LERR_SUCCESS) goto ferr_exit;
        if (!iost) { rxbuf[0] = 0; goto ferr_noanswer; }
        if ((err = link_packet(pccr, CMG_BUF, iost + 1, NULL, 0, rxpar, iost + 1)) != LERR_SUCCESS) goto ferr_exit;
        if (rxpar[0]) memcpy(anspar, &rxpar[1], (rxpar[0] % 8) ? rxpar[0] / 8 + 1 : rxpar[0] / 8);
    }
ferr_noanswer:
    if (pabitlen) *pabitlen = rxbuf[0];
    if ((ans) && (rxbuf[0])) memcpy(ans, &rxbuf[1], (rxbuf[0] % 8) ? rxbuf[0] / 8 + 1 : rxbuf[0] / 8);
ferr_exit:
    if (err == LERR_HARDWARE) err = LERR_NOREADER;
    else if ((err != LERR_SUCCESS) && (err != LERR_FAIL)) err = LERR_IOERR;
    return err;
}


// *** read data block/page ***
// pccr    - pointer to the handle struct (see link_hidopen or link_comopen)
// timeout - operation timeout 0..7, time_in_ms=(timeout*4+3)*25; min=75mS, max=775mS
// mfdm    - data mode: MFDM_BLOCK | MFDM_PAGE (block/page mode), MFDM_NMC | MFDM_RWC (normal/RW card)
// acm     - authorization command (PICC_AUTHENT1A | PICC_AUTHENT1B): no authorization mode when acm=0
//  ak     - authorization key 0..84, RAM-key when ak=0
// start   - start block or page for read 0..255
// len     - data length in blocks or pages
// pdata   - pointer to the data buffer
// puidlen - may be NULL; before call: length of UID in puid for force operation with UID = puid ONLY!
//           puidlen must be NULL or *puidlen must be 0 if need 'any UID' mode
//           after call: the length of the UID card used
// puid    - may be NULL; before call: UID for force operation with card UID = puid ONLY!
//           after call: UID of last used card
// patqa   - may be NULL; after call: ATQA of last used card
// psak    - may be NULL; after call: SAK of last used card
// return  - error code
DLL_API DWORD mfDataRead(PCCR pccr, BYTE timeout, BYTE mfdm, BYTE acm, BYTE ak,
    BYTE start, BYTE len, PBYTE pdata, PBYTE puidlen, PBYTE puid, PWORD patqa, PBYTE psak)
{
    DWORD       err;
    BYTE        pml = 0;                // parameters len: if 'not any UID' mode pml=11(uidlen+uid) + 4(SAKD)
    BOOL        auf = FALSE;            // need authorization flag
    BYTE        buf[78], datalen;

    if ((!pccr) || (!len) || (timeout > 7)) return LERR_FAIL;
    // validate for forced UID parameters
    if ((puidlen) && (*puidlen & 0x0F))
    {
        if (!puid) return LERR_FAIL;
        buf[0] = *puidlen;              // copy 'forced' uidlen
        memcpy(&buf[1], puid, SZ_UID);  // copy forced' UID
        pml = 1 + SZ_UID;               // set new offset for SAKD
    }
    // check for 'not any' UID for normal card
    if (mfdm & MFDM_RWC)
    {
        buf[pml++] = PICC_REQRW;        // +0, wake up command for RW card
        buf[pml++] = 0x00;              // +1, authorization must be off for RW
    }
    else
    {
        buf[pml++] = PICC_REQALL;       // +0, wake up command for normal card
        buf[pml++] = acm;               // +1, authorization command
    }
    buf[pml++] = ak;                    // +2, authorization key number, RAM==0
    buf[pml++] = start;                 // +3, start block/page

    if (mfdm & MFDM_PAGE)
    {
        if (len > 16) return LERR_FAIL; // check datalen in page mode
        datalen = len * SZ_MFPAGE;      // length of data in bytes (page mode, length of page = 4 bytes)
    }
    else
    {
        if (len > 4) return LERR_FAIL;  // check datalen in block mode
        datalen = len * SZ_MFBLK;       // length of data in bytes (block mode, length of block = 16 bytes))
    }
    if (puidlen) *puidlen = 0;                      // reset length of UID
    if (puid) memset(puid, 0, SZ_UID);              // reset UID
    if (psak) *psak = 0;                            // reset SAK
    if (patqa) *patqa = 0;                          // reset ATQA

    // CMG_CPGE | CMG_CBLK, CMD 4pg|ANY|1.5s, UID+ATQA+SAK+datalen
    if (((err = link_packet(pccr, (mfdm & MFDM_PAGE) ? CMG_CPGE : CMG_CBLK,
        ((len - 1) << 4) | timeout | ((pml == 4) ? 8 : 0),
        buf, pml, buf, SZ_SCCD + datalen)) != LERR_SUCCESS) && (err != LERR_FAIL)) goto ferr_exit;
    if (err == LERR_SUCCESS)
    {
        if (puidlen) *puidlen = buf[0];                                 // set length of UID
        if (puid) memcpy(puid, &buf[1], SZ_UID);                        // set UID
        if (psak) *psak = buf[11];                                      // set SAK
        if (patqa) *patqa = (((WORD)buf[12]) << 8) | ((WORD)buf[13]);   // set ATQA
        if (pdata) memcpy(pdata, &buf[SZ_SCCD], (DWORD)datalen);        // set data
    }
    else
    {
        // read extended error flags
        if ((err = link_packet(pccr, CMG_ISO, 0, NULL, 0, buf, 2)) != LERR_SUCCESS) goto ferr_exit;
        if (buf[0] & 0x10) err = LERR_DATAREAD;     // I/O err in read operation (may be read after trailer?)
        else if (buf[0] & 4) err = LERR_DATAREAD;   // can't read data (broken trailer)
        else if (buf[0] & 1) err = LERR_INVALIDKEY; // SELECT complete but no authorization
        else err = LERR_NOCARD; // SELECT failed
    }
ferr_exit:
    return err;
}

// *** write data block/page ***
// pccr    - pointer to the handle struct (see link_hidopen or link_comopen)
// timeout - operation timeout 0..7, time_in_ms=(timeout*4+3)*25; min=75mS, max=775mS
// mfdm    - data mode: MFDM_BLOCK | MFDM_PAGE (block/page mode), MFDM_NMC | MFDM_RWC (normal/RW card)
// acm     - authorization command (PICC_AUTHENT1A | PICC_AUTHENT1B): no authorization mode when acm=0
//  ak     - authorization key 0..84, RAM-key when ak=0
// start   - start block or page for read 0..255
// len     - data length in blocks or pages
// pdata   - a pointer to the data buffer, can't be NULL
// puidlen - may be NULL; before call: length of UID in puid for force operation with UID = puid ONLY!
//           puidlen must be NULL or *puidlen must be 0 if need 'any UID' mode
//           after call: the length of the UID card used
// puid    - may be NULL; before call: UID for force operation with card UID = puid ONLY!
//           after call: UID of last used card
// patqa   - may be NULL; after call: ATQA of last used card
// psak    - may be NULL; after call: SAK of last used card
// return  - error code
DLL_API DWORD mfDataWrite(PCCR pccr, BYTE timeout, BYTE mfdm, BYTE acm, BYTE ak,
    BYTE start, BYTE len, PBYTE pdata, PBYTE puidlen, PBYTE puid, PWORD patqa, PBYTE psak)
{
    DWORD       err;
    BYTE        pml = 0;                // parameters len: if 'not any UID' mode pml=11(uidlen+uid) + 4(SAKD)
    BOOL        auf = FALSE;            // need authorization flag
    BYTE        buf[78], datalen;

    if ((!pccr) || (!len) || (timeout > 7)) return LERR_FAIL;
    // validate for forced UID parameters
    if ((puidlen) && (*puidlen & 0x0F))
    {
        if (!puid) return LERR_FAIL;
        buf[0] = *puidlen;              // copy 'forced' uidlen
        memcpy(&buf[1], puid, SZ_UID);  // copy forced' UID
        pml = 1 + SZ_UID;               // set new offset for SAKD
    }
    // check for 'not any' UID for normal card
    if (mfdm & MFDM_RWC)
    {
        buf[pml++] = PICC_REQRW;        // +0, wake up command for RW card
        buf[pml++] = 0x00;              // +1, authorization must be off for RW
    }
    else
    {
        buf[pml++] = PICC_REQALL;       // +0, wake up command for normal card
        buf[pml++] = acm;               // +1, authorization command
    }
    buf[pml++] = ak;                    // +2, authorization key number, RAM==0
    buf[pml++] = start;                 // +3, start block/page

    if (mfdm & MFDM_PAGE)
    {
        if (len > 16) return LERR_FAIL; // check datalen in page mode
        datalen = len * SZ_MFPAGE;      // length of data in bytes (page mode, length of page = 4 bytes)
    }
    else
    {
        if (len > 4) return LERR_FAIL;  // check datalen in block mode
        datalen = len * SZ_MFBLK;       // length of data in bytes (block mode, length of block = 16 bytes))
    }
    memcpy(&buf[pml], pdata, datalen);  // copy data to I/O buffer

    if (puidlen) *puidlen = 0;          // reset length of UID
    if (puid) memset(puid, 0, SZ_UID);  // reset UID
    if (psak) *psak = 0;                // reset SAK
    if (patqa) *patqa = 0;              // reset ATQA

    // CMS_CPGE | CMS_CBLK, CMD 4pg|ANY|1.5s, UID+ATQA+SAK+datalen
    if (((err = link_packet(pccr, (mfdm & MFDM_PAGE) ? CMS_CPGE : CMS_CBLK,
        ((len - 1) << 4) | timeout | ((pml == 4) ? 8 : 0),
        buf, pml + datalen, buf, SZ_SCCD)) != LERR_SUCCESS) && (err != LERR_FAIL)) goto ferr_exit;
    if (err == LERR_SUCCESS)
    {
        if (puidlen) *puidlen = buf[0];             // set length of UID
        if (puid) memcpy(puid, &buf[1], SZ_UID);    // set UID
        if (psak) *psak = buf[11];                  // set SAK
        if (patqa) *patqa = (((WORD)buf[12]) << 8) | ((WORD)buf[13]); // set ATQA
    }
    else
    {
        // read extra error flags
        if ((err = link_packet(pccr, CMG_ISO, 0, NULL, 0, buf, 2)) != LERR_SUCCESS) goto ferr_exit;
        if (buf[0] & 0x40) err = LERR_DATAWRITE;    // I/O err in write operation
        else if (buf[0] & 4) err = LERR_DATAWRITE;  // can't read data (broken trailer)
        else if (buf[0] & 1) err = LERR_INVALIDKEY; // SELECT complete but no authorization
        else err = LERR_NOCARD; // SELECT failed
    }
ferr_exit:
    return err;
}
