//-----------------------------------------------------------------------------
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>, Hagen Fritsch
// Copyright (C) 2011 Gerhard de Koning Gans
// Copyright (C) 2014 Midnitesnake & Andy Davies & Martin Holst Swende
// Copyright (C) 2019 piwi
// Copyright (C) 2020 Iceman
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// High frequency iClass commands
//-----------------------------------------------------------------------------

#include "cmdhficlass.h"
#include <ctype.h>
#include "cliparser.h"
#include "cmdparser.h"    // command_t
#include "commonutil.h"  // ARRAYLEN
#include "cmdtrace.h"
#include "cliparser.h"
#include "util_posix.h"
#include "comms.h"
#include "des.h"
#include "loclass/cipherutils.h"
#include "loclass/cipher.h"
#include "loclass/ikeys.h"
#include "loclass/elite_crack.h"
#include "fileutils.h"
#include "protocols.h"
#include "cardhelper.h"
#include "wiegand_formats.h"
#include "wiegand_formatutils.h"

#define NUM_CSNS 9
#define ICLASS_KEYS_MAX 8
#define ICLASS_AUTH_RETRY 10
#define ICLASS_DECRYPTION_BIN  "iclass_decryptionkey.bin"
static uint8_t empty[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

static int CmdHelp(const char *Cmd);

static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][8] = {
    { 0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78 },
    { 0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00 },
    { 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87 },
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
};

static int cmp_uint32(const void *a, const void *b) {

    const iclass_prekey_t *x = (const iclass_prekey_t *)a;
    const iclass_prekey_t *y = (const iclass_prekey_t *)b;

    uint32_t mx = bytes_to_num((uint8_t *)x->mac, 4);
    uint32_t my = bytes_to_num((uint8_t *)y->mac, 4);

    if (mx < my)
        return -1;
    else
        return mx > my;
}

bool check_known_default(uint8_t *csn, uint8_t *epurse, uint8_t *rmac, uint8_t *tmac, uint8_t *key) {

    iclass_prekey_t *prekey = calloc(ICLASS_KEYS_MAX, sizeof(iclass_prekey_t));
    if (prekey == false) {
        return PM3_EMALLOC;
    }

    uint8_t ccnr[12];
    memcpy(ccnr, epurse, 8);
    memcpy(ccnr + 8, rmac, 4);

    GenerateMacKeyFrom(csn, ccnr, false, false, (uint8_t *)iClass_Key_Table, ICLASS_KEYS_MAX, prekey);
    qsort(prekey, ICLASS_KEYS_MAX, sizeof(iclass_prekey_t), cmp_uint32);

    iclass_prekey_t lookup;
    memcpy(lookup.mac, tmac, 4);

    // binsearch
    iclass_prekey_t *item = (iclass_prekey_t *) bsearch(&lookup, prekey, ICLASS_KEYS_MAX, sizeof(iclass_prekey_t), cmp_uint32);
    if (item != NULL) {
        memcpy(key, item->key, 8);
        return true;
    }
    return false;
}

typedef enum {
    None = 0,
    DES,
    RFU,
    TRIPLEDES
} BLOCK79ENCRYPTION;

static inline uint32_t leadingzeros(uint64_t a) {
#if defined __GNUC__
    return __builtin_clzll(a);
#else
    return 0;
#endif
}
static inline uint32_t countones(uint64_t a) {
#if defined __GNUC__
    return __builtin_popcountll(a);
#else
    return 0;
#endif

}

const char *card_types[] = {
    "PicoPass 16K / 16",                       // 000
    "PicoPass 32K with current book 16K / 16", // 001
    "Unknown Card Type!",                      // 010
    "Unknown Card Type!",                      // 011
    "PicoPass 2K",                             // 100
    "Unknown Card Type!",                      // 101
    "PicoPass 16K / 2",                        // 110
    "PicoPass 32K with current book 16K / 2",  // 111
};

uint8_t card_app2_limit[] = {
    0xff,
    0xff,
    0xff,
    0xff,
    0x1f,
    0xff,
    0xff,
    0xff,
};

static uint8_t isset(uint8_t val, uint8_t mask) {
    return (val & mask);
}

static uint8_t notset(uint8_t val, uint8_t mask) {
    return !(val & mask);
}

uint8_t get_pagemap(const picopass_hdr *hdr) {
    return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
}

static void fuse_config(const picopass_hdr *hdr) {

    uint16_t otp = (hdr->conf.otp[1] << 8 | hdr->conf.otp[0]);

    PrintAndLogEx(INFO, "    Raw: " _YELLOW_("%s"), sprint_hex((uint8_t *)&hdr->conf, 8));
    PrintAndLogEx(INFO, "         " _YELLOW_("%02X") ".....................  app limit", hdr->conf.app_limit);
    PrintAndLogEx(INFO, "            " _YELLOW_("%04X") " ( %5u )......  OTP", otp, otp);
    PrintAndLogEx(INFO, "                  " _YELLOW_("%02X") "............  block write lock", hdr->conf.block_writelock);
    PrintAndLogEx(INFO, "                     " _YELLOW_("%02X") ".........  chip", hdr->conf.chip_config);
    PrintAndLogEx(INFO, "                        " _YELLOW_("%02X") "......  mem", hdr->conf.mem_config);
    PrintAndLogEx(INFO, "                           " _YELLOW_("%02X") "...  EAS", hdr->conf.eas);
    PrintAndLogEx(INFO, "                              " _YELLOW_("%02X") "  fuses", hdr->conf.fuses);

    uint8_t fuses = hdr->conf.fuses;

    PrintAndLogEx(INFO, "  Fuses:");
    if (isset(fuses, FUSE_FPERS))
        PrintAndLogEx(SUCCESS, "    mode......... " _GREEN_("Personalization (programmable)"));
    else
        PrintAndLogEx(SUCCESS, "    mode......... " _YELLOW_("Application (locked)"));

    if (isset(fuses, FUSE_CODING1)) {
        PrintAndLogEx(SUCCESS, "    coding...... RFU");
    } else {
        if (isset(fuses, FUSE_CODING0))
            PrintAndLogEx(SUCCESS, "    coding....... " _YELLOW_("ISO 14443-2 B / 15693"));
        else
            PrintAndLogEx(SUCCESS, "    coding....... " _YELLOW_("ISO 14443-B only"));
    }

    uint8_t pagemap = get_pagemap(hdr);
    switch (pagemap) {
        case 0x0:
            PrintAndLogEx(INFO, "    crypt........ No auth possible. Read only if RA is enabled");
            break;
        case 0x1:
            PrintAndLogEx(SUCCESS, "    crypt........ Non secured page");
            break;
        case 0x2:
            PrintAndLogEx(INFO, "    crypt........ Secured page, keys locked");
            break;
        case 0x03:
            PrintAndLogEx(SUCCESS, "    crypt........ Secured page, " _GREEN_("keys not locked"));
            break;
    }

    if (isset(fuses, FUSE_RA))
        PrintAndLogEx(SUCCESS, "    RA........... Read access enabled (non-secure mode)");
    else
        PrintAndLogEx(INFO, "    RA........... Read access not enabled");
}

static void getMemConfig(uint8_t mem_cfg, uint8_t chip_cfg, uint8_t *app_areas, uint8_t *kb) {
    // How to determine chip type

    // mem-bit 7 = 16K
    // mem-bit 5 = Book
    // mem-bit 4 = 2K
    // chip-bit 4 = Multi App

    uint8_t k16 = isset(mem_cfg, 0x80);
    //uint8_t k2 = isset(mem_cfg, 0x10);
    uint8_t book = isset(mem_cfg, 0x20);

    if (isset(chip_cfg, 0x10) && !k16 && !book) {
        *kb = 2;
        *app_areas = 2;
    } else if (isset(chip_cfg, 0x10) && k16 && !book) {
        *kb = 16;
        *app_areas = 2;
    } else if (notset(chip_cfg, 0x10) && !k16 && !book) {
        *kb = 16;
        *app_areas = 16;
    } else if (isset(chip_cfg, 0x10) && k16 && book) {
        *kb = 32;
        *app_areas = 3;
    } else if (notset(chip_cfg, 0x10) && !k16 && book) {
        *kb = 32;
        *app_areas = 17;
    } else {
        *kb = 32;
        *app_areas = 2;
    }
}

static uint8_t get_mem_config(const picopass_hdr *hdr) {
    // three configuration bits that decides sizes
    uint8_t type = (hdr->conf.chip_config & 0x10) >> 2;
    // 16K bit  0 ==  1==
    type |= (hdr->conf.mem_config & 0x80) >> 6;
    //  BOOK bit 0 ==  1==
    type |= (hdr->conf.mem_config & 0x20) >> 5;
    // 2K
    //type |= (hdr->conf.mem_config & 0x10) >> 5;
    return type;
}

static void mem_app_config(const picopass_hdr *hdr) {
    uint8_t mem = hdr->conf.mem_config;
    uint8_t chip = hdr->conf.chip_config;
    uint8_t kb = 2;
    uint8_t app_areas = 2;

    getMemConfig(mem, chip, &app_areas, &kb);

    uint8_t type = get_mem_config(hdr);
    uint8_t app1_limit = hdr->conf.app_limit - 5; // minus header blocks
    uint8_t app2_limit = card_app2_limit[type];
    uint8_t pagemap = get_pagemap(hdr);

    PrintAndLogEx(INFO, "-------------------------- " _CYAN_("Memory") " --------------------------");

    if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
        PrintAndLogEx(INFO, " %u KBits ( " _YELLOW_("%u") " bytes )", kb, app2_limit * 8);
        PrintAndLogEx(INFO, "    Tag has not App Areas");
        return;
    }

    PrintAndLogEx(INFO, " %u KBits/%u App Areas ( " _YELLOW_("%u") " bytes )", kb, app_areas, (app2_limit + 1) * 8);
    PrintAndLogEx(INFO, "    AA1 blocks %u { 0x06 - 0x%02X (06 - %02d) }", app1_limit, app1_limit + 5, app1_limit + 5);
    PrintAndLogEx(INFO, "    AA2 blocks %u { 0x%02X - 0x%02X (%02d - %02d) }", app2_limit - app1_limit, app1_limit + 5 + 1, app2_limit, app1_limit + 5 + 1, app2_limit);

    PrintAndLogEx(INFO, "------------------------- " _CYAN_("KeyAccess") " ------------------------");
    PrintAndLogEx(INFO, " * Kd, Debit key, AA1    Kc, Credit key, AA2 *");
    uint8_t book = isset(mem, 0x20);
    if (book) {
        PrintAndLogEx(INFO, "    Read A....... debit");
        PrintAndLogEx(INFO, "    Read B....... credit");
        PrintAndLogEx(INFO, "    Write A...... debit");
        PrintAndLogEx(INFO, "    Write B...... credit");
        PrintAndLogEx(INFO, "    Debit........ debit or credit");
        PrintAndLogEx(INFO, "    Credit....... credit");
    } else {
        PrintAndLogEx(INFO, "    Read A....... debit or credit");
        PrintAndLogEx(INFO, "    Read B....... debit or credit");
        PrintAndLogEx(INFO, "    Write A...... credit");
        PrintAndLogEx(INFO, "    Write B...... credit");
        PrintAndLogEx(INFO, "    Debit........ debit or credit");
        PrintAndLogEx(INFO, "    Credit....... credit");
    }
}

static void print_picopass_info(const picopass_hdr *hdr) {
    PrintAndLogEx(INFO, "-------------------- " _CYAN_("card configuration") " --------------------");
    fuse_config(hdr);
    mem_app_config(hdr);
}

static void print_picopass_header(const picopass_hdr *hdr) {
    PrintAndLogEx(INFO, "--------------------------- " _CYAN_("card") " ---------------------------");
    PrintAndLogEx(SUCCESS, "    CSN: " _GREEN_("%s") " uid", sprint_hex(hdr->csn, sizeof(hdr->csn)));
    PrintAndLogEx(SUCCESS, " Config: %s Card configuration", sprint_hex((uint8_t *)&hdr->conf, sizeof(hdr->conf)));
    PrintAndLogEx(SUCCESS, "E-purse: %s Card challenge, CC", sprint_hex(hdr->epurse, sizeof(hdr->epurse)));
    PrintAndLogEx(SUCCESS, "     Kd: %s Debit key, hidden", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
    PrintAndLogEx(SUCCESS, "     Kc: %s Credit key, hidden", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
    PrintAndLogEx(SUCCESS, "    AIA: %s Application Issuer area", sprint_hex(hdr->app_issuer_area, sizeof(hdr->app_issuer_area)));
}

static int CmdHFiClassList(const char *Cmd) {
    char args[128] = {0};
    if (strlen(Cmd) == 0) {
        snprintf(args, sizeof(args), "-t iclass");
    } else {
        strncpy(args, Cmd, sizeof(args) - 1);
    }
    return CmdTraceList(args);
}

static int CmdHFiClassSniff(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass sniff",
                  "Sniff the communication reader and tag",
                  "hf iclass sniff\n"
                  "hf iclass sniff -j -> jam e-purse updates\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("j",  "jam",    "Jam (prevent) e-purse updates"),
        arg_param_end
    };

    CLIExecWithReturn(ctx, Cmd, argtable, true);
    bool jam_epurse_update = arg_get_lit(ctx, 1);
    CLIParserFree(ctx);

    const uint8_t update_epurse_sequence[2] = {0x87, 0x02};

    struct {
        uint8_t jam_search_len;
        uint8_t jam_search_string[2];
    } PACKED payload;

    if (jam_epurse_update) {
        payload.jam_search_len = sizeof(update_epurse_sequence);
        memcpy(payload.jam_search_string, update_epurse_sequence, sizeof(payload.jam_search_string));
    }

    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_ICLASS_SNIFF, (uint8_t *)&payload, sizeof(payload));

    WaitForResponse(CMD_HF_ICLASS_SNIFF, &resp);

    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass list") "` to view captured tracelog");
    PrintAndLogEx(HINT, "Try `" _YELLOW_("trace save -f hf_iclass_mytrace") "` to save tracelog for later analysing");
    return PM3_SUCCESS;
}

static int CmdHFiClassSim(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass sim",
                  "Simulate a iCLASS legacy/standard tag",
                  "hf iclass sim -t 0 --csn 031FEC8AF7FF12E0                -> simulate with specficied CSN\n"
                  "hf iclass sim -t 1                                       -> simulate with default CSN\n"
                  "hf iclass sim -t 2                                       -> execute loclass attack online part\n"
                  "hf iclass eload -f hf-iclass-AA162D30F8FF12F1-dump.bin   -> simulate full iCLASS 2k tag\n"
                  "hf iclass sim -t 3                                       -> simulate full iCLASS 2k tag\n"
                  "hf iclass sim -t 4                                       -> Reader-attack, adapted for KeyRoll mode, gather reader responses to extract elite key");

    void *argtable[] = {
        arg_param_begin,
        arg_int1("t", "type", "<0-4> ", "Simulation type to use"),
        arg_str0(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes to use with sim type 0"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int sim_type = arg_get_int(ctx, 1);

    int csn_len = 0;
    uint8_t csn[8] = {0};
    CLIGetHexWithReturn(ctx, 2, csn, &csn_len);

    if (sim_type == 0 && csn_len > 0) {
        if (csn_len != 8) {
            PrintAndLogEx(ERR, "CSN is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
        PrintAndLogEx(INFO, " simtype: %02x CSN: %s", sim_type, sprint_hex(csn, 8));
    } else if (sim_type == 0 && csn_len == 0) {
        PrintAndLogEx(ERR, "Simtype 0 requires CSN argument (--csn)");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    CLIParserFree(ctx);

    if (sim_type > 4) {
        PrintAndLogEx(ERR, "Undefined simtype %d", sim_type);
        return PM3_EINVARG;
    }

    // remember to change the define NUM_CSNS to match.

    // pre-defined 9 CSN by iceman
    uint8_t csns[8 * NUM_CSNS] = {
        0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0,
        0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0,
        0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0,
        0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0,
        0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0,
        0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0,
        0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0,
        0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0,
        0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0
        //0x04, 0x08, 0x9F, 0x78, 0x6E, 0xFF, 0x12, 0xE0
    };

    /* DUMPFILE FORMAT:
     *
     * <8-byte CSN><8-byte CC><4 byte NR><4 byte MAC>....
     * So, it should wind up as
     * 8 * 24 bytes.
     *
     * The returndata from the pm3 is on the following format
     * <4 byte NR><4 byte MAC>
     * CC are all zeroes, CSN is the same as was sent in
     **/
    uint8_t tries = 0;

    switch (sim_type) {

        case ICLASS_SIM_MODE_READER_ATTACK: {
            PrintAndLogEx(INFO, "Starting iCLASS sim 2 attack (elite mode)");
            PrintAndLogEx(INFO, "press " _YELLOW_("`enter`") " to cancel");
            PacketResponseNG resp;
            clearCommandBuffer();
            SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, NUM_CSNS, 1, csns, 8 * NUM_CSNS);

            while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
                tries++;
                if (kbd_enter_pressed()) {
                    PrintAndLogEx(WARNING, "\naborted via keyboard.");
                    return PM3_EOPABORTED;
                }
                if (tries > 20) {
                    PrintAndLogEx(WARNING, "\ntimeout while waiting for reply.");
                    return PM3_ETIMEOUT;
                }
            }
            uint8_t num_mac  = resp.oldarg[1];
            bool success = (NUM_CSNS == num_mac);
            PrintAndLogEx((success) ? SUCCESS : WARNING, "[%c] %d out of %d MAC obtained [%s]", (success) ? '+' : '!', num_mac, NUM_CSNS, (success) ? "OK" : "FAIL");

            if (num_mac == 0)
                break;

            size_t datalen = NUM_CSNS * 24;
            uint8_t *dump = calloc(datalen, sizeof(uint8_t));
            if (!dump) {
                PrintAndLogEx(WARNING, "Failed to allocate memory");
                return PM3_EMALLOC;
            }

            memset(dump, 0, datalen);//<-- Need zeroes for the EPURSE - field (official)

            uint8_t i = 0;
            for (i = 0 ; i < NUM_CSNS ; i++) {
                //copy CSN
                memcpy(dump + i * 24, csns + i * 8, 8);
                //copy epurse
                memcpy(dump + i * 24 + 8, resp.data.asBytes + i * 16, 8);
                // NR_MAC (eight bytes from the response)  ( 8b csn + 8b epurse == 16)
                memcpy(dump + i * 24 + 16, resp.data.asBytes + i * 16 + 8, 8);
            }
            /** Now, save to dumpfile **/
            saveFile("iclass_mac_attack", ".bin", dump, datalen);
            free(dump);

            PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass loclass -h") "` to recover elite key");
            break;
        }
        case ICLASS_SIM_MODE_READER_ATTACK_KEYROLL: {
            // reader in key roll mode,  when it has two keys it alternates when trying to verify.
            PrintAndLogEx(INFO, "Starting iCLASS sim 4 attack (elite mode, reader in key roll mode)");
            PrintAndLogEx(INFO, "press Enter to cancel");
            PacketResponseNG resp;
            clearCommandBuffer();
            SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, NUM_CSNS, 1, csns, 8 * NUM_CSNS);

            while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
                tries++;
                if (kbd_enter_pressed()) {
                    PrintAndLogEx(WARNING, "\naborted via keyboard.");
                    return PM3_EOPABORTED;
                }
                if (tries > 20) {
                    PrintAndLogEx(WARNING, "\ntimeout while waiting for reply.");
                    return PM3_ETIMEOUT;
                }
            }
            uint8_t num_mac = resp.oldarg[1];
            bool success = ((NUM_CSNS * 2) == num_mac);
            PrintAndLogEx((success) ? SUCCESS : WARNING, "[%c] %d out of %d MAC obtained [%s]", (success) ? '+' : '!', num_mac, NUM_CSNS * 2, (success) ? "OK" : "FAIL");

            if (num_mac == 0)
                break;

            size_t datalen = NUM_CSNS * 24;
            uint8_t *dump = calloc(datalen, sizeof(uint8_t));
            if (!dump) {
                PrintAndLogEx(WARNING, "Failed to allocate memory");
                return PM3_EMALLOC;
            }

#define MAC_ITEM_SIZE 24

            //KEYROLL 1
            //Need zeroes for the CC-field
            memset(dump, 0, datalen);
            for (uint8_t i = 0; i < NUM_CSNS ; i++) {
                // copy CSN
                memcpy(dump + i * MAC_ITEM_SIZE, csns + i * 8, 8); //CSN
                // copy EPURSE
                memcpy(dump + i * MAC_ITEM_SIZE + 8, resp.data.asBytes + i * 16, 8);
                // copy NR_MAC (eight bytes from the response)  ( 8b csn + 8b epurse == 16)
                memcpy(dump + i * MAC_ITEM_SIZE + 16, resp.data.asBytes + i * 16 + 8, 8);
            }
            saveFile("iclass_mac_attack_keyroll_A", ".bin", dump, datalen);

            //KEYROLL 2
            memset(dump, 0, datalen);
            for (uint8_t i = 0; i < NUM_CSNS; i++) {
                uint8_t resp_index = (i + NUM_CSNS) * 16;
                // Copy CSN
                memcpy(dump + i * MAC_ITEM_SIZE, csns + i * 8, 8);
                // copy EPURSE
                memcpy(dump + i * MAC_ITEM_SIZE + 8, resp.data.asBytes + resp_index, 8);
                // copy NR_MAC (eight bytes from the response)  ( 8b csn + 8 epurse == 16)
                memcpy(dump + i * MAC_ITEM_SIZE + 16, resp.data.asBytes + resp_index + 8, 8);
                resp_index++;
            }
            saveFile("iclass_mac_attack_keyroll_B", ".bin", dump, datalen);
            free(dump);

            PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass loclass -h") "` to recover elite key");
            break;
        }
        case ICLASS_SIM_MODE_CSN:
        case ICLASS_SIM_MODE_CSN_DEFAULT:
        case ICLASS_SIM_MODE_FULL:
        default: {
            PrintAndLogEx(INFO, "Starting iCLASS simulation");
            PrintAndLogEx(INFO, "press " _YELLOW_("`button`") " to cancel");
            uint8_t numberOfCSNs = 0;
            clearCommandBuffer();
            SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, numberOfCSNs, 1, csn, 8);

            if (sim_type == ICLASS_SIM_MODE_FULL)
                PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass esave -h") "` to save the emulator memory to file");
            break;
        }
    }
    return PM3_SUCCESS;
}

static int CmdHFiClassInfo(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass info",
                  "Act as a iCLASS reader. Reads / fingerprints a iCLASS tag.",
                  "hf iclass info");

    void *argtable[] = {
        arg_param_begin,
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    CLIParserFree(ctx);
    return info_iclass();
}

int read_iclass_csn(bool loop, bool verbose) {

    uint32_t flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE);
    int res = PM3_SUCCESS;

    do {
        clearCommandBuffer();
        SendCommandMIX(CMD_HF_ICLASS_READER, flags, 0, 0, NULL, 0);
        PacketResponseNG resp;
        if (WaitForResponseTimeout(CMD_ACK, &resp, 2000)) {

            uint8_t status = resp.oldarg[0] & 0xff;

            if (loop) {
                if (status == 0xFF) {
                    continue;
                }
            } else {

                if (status == 0 || status == 0xFF) {
                    if (verbose) PrintAndLogEx(WARNING, "iCLASS / ISO15693 card select failed");
                    res = PM3_EOPABORTED;
                    break;
                }
            }

            picopass_hdr *hdr = (picopass_hdr *)resp.data.asBytes;
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(hdr->csn, sizeof(hdr->csn)));
        }
    } while (loop && kbd_enter_pressed() == false);

    DropField();
    return res;
}

static int CmdHFiClassReader(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass reader",
                  "Act as a iCLASS reader. Look for iCLASS tags until Enter or the pm3 button is pressed",
                  "hf iclass reader -@   -> continuous reader mode"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("@", NULL, "optional - continuous reader mode"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    bool cm = arg_get_lit(ctx, 1);
    CLIParserFree(ctx);

    if (cm) {
        PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
    }

    return read_iclass_csn(cm, true);
}

static int CmdHFiClassELoad(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass eload",
                  "Loads iCLASS tag dump into emulator memory on device",
                  "hf iclass eload -f hf-iclass-AA162D30F8FF12F1-dump.bin\n");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<filename>", "filename of dump"),
        arg_lit0(NULL, "json",              "load JSON type dump"),
        arg_lit0(NULL, "eml",               "load EML type dump"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    if (strlen(filename) == 0) {
        PrintAndLogEx(ERR, "Error: Please specify a filename");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    DumpFileType_t dftype = BIN;

    bool use_json = arg_get_lit(ctx, 2);
    bool use_eml = arg_get_lit(ctx, 3);
    CLIParserFree(ctx);

    if (use_json && use_eml) {
        PrintAndLogEx(ERR, "Error: can't specify both JSON & EML");
        return PM3_EINVARG;
    }

    if (use_json) {
        dftype = JSON;
    } else if (use_eml) {
        dftype = EML;
    }

    size_t bytes_read = 2048;
    uint8_t *dump = calloc(2048, sizeof(uint8_t));
    if (!dump) {
        PrintAndLogEx(ERR, "error, cannot allocate memory ");
        return PM3_EMALLOC;
    }

    int res = 0;

    switch (dftype) {
        case BIN: {
            res = loadFile_safe(filename, ".bin", (void **)&dump, &bytes_read);
            break;
        }
        case EML: {
            res = loadFileEML_safe(filename, (void **)&dump, &bytes_read);
            break;
        }
        case JSON: {
            res = loadFileJSON(filename, dump, 2048, &bytes_read, NULL);
            break;
        }
        case DICTIONARY: {
            PrintAndLogEx(ERR, "Error: Only BIN/JSON/EML formats allowed");
            return PM3_EINVARG;
        }
    }

    if (res != PM3_SUCCESS) {
        free(dump);
        return PM3_EFILE;
    }

    uint8_t *newdump = realloc(dump, bytes_read);
    if (newdump == NULL) {
        free(dump);
        return PM3_EMALLOC;
    } else {
        dump = newdump;
    }

    print_picopass_header((picopass_hdr *) dump);
    print_picopass_info((picopass_hdr *) dump);

    // fast push mode
    conn.block_after_ACK = true;

    //Send to device
    uint32_t bytes_sent = 0;
    uint32_t bytes_remaining  = bytes_read;

    while (bytes_remaining > 0) {
        uint32_t bytes_in_packet = MIN(PM3_CMD_DATA_SIZE, bytes_remaining);
        if (bytes_in_packet == bytes_remaining) {
            // Disable fast mode on last packet
            conn.block_after_ACK = false;
        }
        clearCommandBuffer();
        SendCommandOLD(CMD_HF_ICLASS_EML_MEMSET, bytes_sent, bytes_in_packet, 0, dump + bytes_sent, bytes_in_packet);
        bytes_remaining -= bytes_in_packet;
        bytes_sent += bytes_in_packet;
    }
    free(dump);

    PrintAndLogEx(SUCCESS, "sent %d bytes of data to device emulator memory", bytes_sent);
    return PM3_SUCCESS;
}

static int CmdHFiClassESave(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass esave",
                  "Save emulator memory to file.\n"
                  "if filename is not supplied, CSN will be used.",
                  "hf iclass esave\n"
                  "hf iclass esave -f hf-iclass-dump\n"
                  "hf iclass esave -s 2048 -f hf-iclass-dump");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("f", "file", "<filename>", "filename of dumpfile"),
        arg_int0("s", "size", "<256|2048>", "number of bytes to save (default 256)"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
    char *fnameptr = filename;

    uint16_t bytes = arg_get_int_def(ctx, 2, 256);

    if (bytes > 4096) {
        PrintAndLogEx(WARNING, "Emulator memory is max 4096bytes. Truncating %u to 4096", bytes);
        bytes = 4096;
    }

    CLIParserFree(ctx);

    uint8_t *dump = calloc(bytes, sizeof(uint8_t));
    if (dump == NULL) {
        PrintAndLogEx(WARNING, "Fail, cannot allocate memory");
        return PM3_EMALLOC;
    }

    PrintAndLogEx(INFO, "downloading from emulator memory");
    if (!GetFromDevice(BIG_BUF_EML, dump, bytes, 0, NULL, 0, NULL, 2500, false)) {
        PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
        free(dump);
        return PM3_ETIMEOUT;
    }

    // user supplied filename?
    if (fnlen < 1) {
        fnameptr += snprintf(fnameptr, sizeof(filename), "hf-iclass-");
        FillFileNameByUID(fnameptr, dump, "-dump", 8);
    }

    saveFile(filename, ".bin", dump, bytes);
    saveFileEML(filename, dump, bytes, 8);
    saveFileJSON(filename, jsfIclass, dump, bytes, NULL);
    free(dump);

    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass view") "` to view dump file");
    return PM3_SUCCESS;
}

static int CmdHFiClassEView(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass eview",
                  "Display emulator memory.\n"
                  "Number of bytes to download defaults to 256. Other value is 2048.",
                  "hf iclass eview\n"
                  "hf iclass eview -s 2048\n"
                  "hf iclass eview -s 2048 -v");

    void *argtable[] = {
        arg_param_begin,
        arg_int0("s", "size", "<256|2048>", "number of bytes to save (default 256)"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    uint16_t blocks = 32;
    uint16_t bytes = arg_get_int_def(ctx, 1, 256);
    bool verbose = arg_get_lit(ctx, 2);
    blocks = bytes / 8;

    CLIParserFree(ctx);

    if (bytes > 4096) {
        PrintAndLogEx(WARNING, "Emulator memory is max 4096bytes. Truncating %u to 4096", bytes);
        bytes = 4096;
    }

    if (bytes % 8 != 0) {
        bytes &= 0xFFF8;
        PrintAndLogEx(WARNING, "Number not divided by 8, truncating to %u", bytes);
    }

    uint8_t *dump = calloc(bytes, sizeof(uint8_t));
    if (dump == NULL) {
        PrintAndLogEx(WARNING, "Fail, cannot allocate memory");
        return PM3_EMALLOC;
    }
    memset(dump, 0, bytes);

    PrintAndLogEx(INFO, "downloading from emulator memory");
    if (!GetFromDevice(BIG_BUF_EML, dump, bytes, 0, NULL, 0, NULL, 2500, false)) {
        PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
        free(dump);
        return PM3_ETIMEOUT;
    }

    if (verbose) {
        print_picopass_header((picopass_hdr *) dump);
        print_picopass_info((picopass_hdr *) dump);
    }

    PrintAndLogEx(NORMAL, "");
    printIclassDumpContents(dump, 1, blocks, bytes);
    free(dump);
    return PM3_SUCCESS;
}

static int CmdHFiClassDecrypt(const char *Cmd) {
    CLIParserContext *clictx;
    CLIParserInit(&clictx, "hf iclass decrypt",
                  "3DES decrypt data\n"
                  "This is a naive implementation, it tries to decrypt every block after block 6.\n"
                  "Correct behaviour would be to decrypt only the application areas where the key is valid,\n"
                  "which is defined by the configuration block.\n"
                  "OBS! In order to use this function, the file 'iclass_decryptionkey.bin' must reside\n"
                  "in the resources directory. The file should be 16 bytes binary data",
                  "hf iclass decrypt -f hf-iclass-AA162D30F8FF12F1-dump.bin\n"
                  "hf iclass decrypt -f hf-iclass-AA162D30F8FF12F1-dump.bin -k 000102030405060708090a0b0c0d0e0f\n"
                  "hf iclass decrypt -d 1122334455667788 -k 000102030405060708090a0b0c0d0e0f");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("f", "file", "<filename>", "filename of dumpfile"),
        arg_str0("d", "data", "<hex>", "3DES encrypted data"),
        arg_str0("k", "key", "<hex>", "3DES transport key"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(clictx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(clictx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    size_t decryptedlen = 0;
    uint8_t *decrypted = NULL;
    bool have_file = false;

    if (fnlen > 0) {
        if (loadFile_safe(filename, "", (void **)&decrypted, &decryptedlen) != PM3_SUCCESS) {
            CLIParserFree(clictx);
            return PM3_EINVARG;
        }
        have_file = true;
    }

    int enc_data_len = 0;
    uint8_t enc_data[8] = {0};
    bool have_data = false;

    CLIGetHexWithReturn(clictx, 2, enc_data, &enc_data_len);

    if (enc_data_len > 0) {
        if (enc_data_len != 8) {
            PrintAndLogEx(ERR, "Data must be 8 hex bytes (16 HEX symbols)");
            CLIParserFree(clictx);
            return PM3_EINVARG;
        }
        have_data = true;
    }

    int key_len = 0;
    uint8_t key[16] = {0};
    uint8_t *keyptr = NULL;
    bool have_key = false;

    CLIGetHexWithReturn(clictx, 3, key, &key_len);

    if (key_len > 0) {
        if (key_len != 16) {
            PrintAndLogEx(ERR, "Transport key must be 16 hex bytes (32 HEX characters)");
            CLIParserFree(clictx);
            return PM3_EINVARG;
        }
        have_key = true;
    }

    bool verbose = arg_get_lit(clictx, 4);

    CLIParserFree(clictx);

    size_t keylen = 0;
    uint8_t dec_data[8] = {0};

    bool use_sc = IsCryptoHelperPresent(verbose);

    if (have_key == false && use_sc == false) {
        int res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr, &keylen);
        if (res != PM3_SUCCESS) {
            PrintAndLogEx(INFO, "Couldn't find any decryption methods");
            return PM3_EINVARG;
        }
        memcpy(key, keyptr, sizeof(key));
        free(keyptr);
    }

    // tripledes
    mbedtls_des3_context ctx;
    mbedtls_des3_set2key_dec(&ctx, key);

    if (have_data) {

        if (use_sc) {
            Decrypt(enc_data, dec_data);
        } else {
            mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
        }
        PrintAndLogEx(SUCCESS, "Data: %s", sprint_hex(dec_data, sizeof(dec_data)));
    }

    if (have_file) {

        picopass_hdr *hdr = (picopass_hdr *)decrypted;

        uint8_t mem = hdr->conf.mem_config;
        uint8_t chip = hdr->conf.chip_config;
        uint8_t applimit = hdr->conf.app_limit;
        uint8_t kb = 2;
        uint8_t app_areas = 2;
        getMemConfig(mem, chip, &app_areas, &kb);

        BLOCK79ENCRYPTION aa1_encryption = (decrypted[(6 * 8) + 7] & 0x03);

        uint32_t limit = MIN(applimit, decryptedlen / 8);

        if (decryptedlen / 8 != applimit) {
            PrintAndLogEx(WARNING, "Actual file len " _YELLOW_("%zu") " vs HID app-limit len " _YELLOW_("%u"), decryptedlen, applimit * 8);
            PrintAndLogEx(INFO, "Setting limit to " _GREEN_("%u"), limit * 8);
        }

        //uint8_t numblocks4userid = GetNumberBlocksForUserId(decrypted + (6 * 8));

        for (uint16_t blocknum = 0; blocknum < limit; ++blocknum) {

            uint8_t idx = blocknum * 8;
            memcpy(enc_data, decrypted + idx, 8);

            if (aa1_encryption == RFU || aa1_encryption == None)
                continue;

            // Decrypted block 7,8,9 if configured.
            if (blocknum > 6 && blocknum <= 9 && memcmp(enc_data, empty, 8) != 0) {
                if (use_sc) {
                    Decrypt(enc_data, decrypted + idx);
                } else {
                    mbedtls_des3_crypt_ecb(&ctx, enc_data, decrypted + idx);
                }
            }
        }

        // use the first block (CSN) for filename
        char *fptr = calloc(50, sizeof(uint8_t));
        if (fptr == false) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            free(decrypted);
            return PM3_EMALLOC;
        }

        strcat(fptr, "hf-iclass-");
        FillFileNameByUID(fptr, hdr->csn, "-dump-decrypted", sizeof(hdr->csn));

        saveFile(fptr, ".bin", decrypted, decryptedlen);
        saveFileEML(fptr, decrypted, decryptedlen, 8);
        saveFileJSON(fptr, jsfIclass, decrypted, decryptedlen, NULL);

        printIclassDumpContents(decrypted, 1, (decryptedlen / 8), decryptedlen);

        PrintAndLogEx(NORMAL, "");

        // decode block 6
        if (memcmp(decrypted + (8 * 6), empty, 8) != 0) {
            if (use_sc) {
                DecodeBlock6(decrypted + (8 * 6));
            }
        }

        // decode block 7-8-9
        if (memcmp(decrypted + (8 * 7), empty, 8) != 0) {

            //todo:  remove preamble/sentinal

            uint32_t top = 0, mid, bot;
            mid = bytes_to_num(decrypted + (8 * 7), 4);
            bot = bytes_to_num(decrypted + (8 * 7) + 4, 4);

            PrintAndLogEx(INFO, "Block 7 decoder");

            char hexstr[8 + 1] = {0};
            hex_to_buffer((uint8_t *)hexstr, decrypted + (8 * 7), 8, sizeof(hexstr) - 1, 0, 0, true);

            char binstr[8 * 8 + 1] = {0};
            hextobinstring(binstr, hexstr);
            uint8_t i = 0;
            while (i < strlen(binstr) && binstr[i++] == '0');

            PrintAndLogEx(SUCCESS, "Binary..................... " _GREEN_("%s"), binstr + i);

            PrintAndLogEx(INFO, "Wiegand decode");
            wiegand_message_t packed = initialize_message_object(top, mid, bot);
            HIDTryUnpack(&packed, true);

        } else {
            PrintAndLogEx(INFO, "No credential found.");
        }

        // decode block 9
        if (memcmp(decrypted + (8 * 9), empty, 8) != 0) {

            uint8_t usr_blk_len = GetNumberBlocksForUserId(decrypted + (8 * 6));
            if (usr_blk_len < 3) {

                PrintAndLogEx(NORMAL, "");
                PrintAndLogEx(INFO, "Block 9 decoder");
                uint8_t pinsize = 0;
                if (use_sc) {
                    pinsize = GetPinSize(decrypted + (8 * 6));

                    if (pinsize > 0) {

                        uint64_t pin = bytes_to_num(decrypted + (8 * 9), 5);
                        char tmp[17] = {0};
                        snprintf(tmp, sizeof(tmp), "%."PRIu64, BCD2DEC(pin));
                        PrintAndLogEx(INFO, "PIN........................ " _GREEN_("%.*s"), pinsize, tmp);
                    }
                }
            }
        }

        PrintAndLogEx(INFO, "-----------------------------------------------------------------");

        free(decrypted);
        free(fptr);
    }

    mbedtls_des3_free(&ctx);
    return PM3_SUCCESS;
}

static void iclass_encrypt_block_data(uint8_t *blk_data, uint8_t *key) {
    uint8_t encrypted_data[16];
    uint8_t *encrypted = encrypted_data;
    mbedtls_des3_context ctx;
    mbedtls_des3_set2key_enc(&ctx, key);
    mbedtls_des3_crypt_ecb(&ctx, blk_data, encrypted);
    memcpy(blk_data, encrypted, 8);
    mbedtls_des3_free(&ctx);
}

static int CmdHFiClassEncryptBlk(const char *Cmd) {
    CLIParserContext *clictx;
    CLIParserInit(&clictx, "hf iclass encrypt",
                  "3DES encrypt data\n"
                  "OBS! In order to use this function, the file 'iclass_decryptionkey.bin' must reside\n"
                  "in the resources directory. The file should be 16 hex bytes of binary data",
                  "hf iclass encrypt -d 0102030405060708\n"
                  "hf iclass encrypt -d 0102030405060708 -k 00112233445566778899AABBCCDDEEFF");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("d", "data", "<hex>", "data to encrypt"),
        arg_str0("k", "key", "<hex>", "3DES transport key"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(clictx, Cmd, argtable, false);

    int blk_data_len = 0;
    uint8_t blk_data[8] = {0};

    CLIGetHexWithReturn(clictx, 1, blk_data, &blk_data_len);

    if (blk_data_len != 8) {
        PrintAndLogEx(ERR, "Block data must be 8 hex bytes (16 HEX symbols)");
        CLIParserFree(clictx);
        return PM3_EINVARG;
    }

    int key_len = 0;
    uint8_t key[16] = {0};
    uint8_t *keyptr = NULL;
    bool have_key = false;

    CLIGetHexWithReturn(clictx, 2, key, &key_len);

    if (key_len > 0) {
        if (key_len != 16) {
            PrintAndLogEx(ERR, "Transport key must be 16 hex bytes (32 HEX characters)");
            CLIParserFree(clictx);
            return PM3_EINVARG;
        }
        have_key = true;
    }

    bool verbose = arg_get_lit(clictx, 3);

    CLIParserFree(clictx);

    bool use_sc = IsCryptoHelperPresent(verbose);

    if (have_key == false && use_sc == false) {
        size_t keylen = 0;
        int res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr, &keylen);
        if (res != PM3_SUCCESS) {
            PrintAndLogEx(ERR, "Failed to find the transport key");
            return PM3_EINVARG;
        }

        memcpy(key, keyptr, sizeof(key));
        free(keyptr);
    }

    if (use_sc) {
        Encrypt(blk_data, blk_data);
    } else {
        iclass_encrypt_block_data(blk_data, key);
    }
    PrintAndLogEx(SUCCESS, "encrypted block %s", sprint_hex(blk_data, 8));
    return PM3_SUCCESS;
}

static bool select_only(uint8_t *CSN, uint8_t *CCNR, bool verbose) {

    uint8_t flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE);

    clearCommandBuffer();
    PacketResponseNG resp;
    SendCommandMIX(CMD_HF_ICLASS_READER, flags, 0, 0, NULL, 0);
    if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
        PrintAndLogEx(WARNING, "command execute timeout");
        return false;
    }

    uint8_t isok = resp.oldarg[0] & 0xff;

    // no tag found or button pressed
    if ((isok == 0) || isok == 0xFF) {
        if (verbose) {
            PrintAndLogEx(FAILED, "failed tag-select, aborting...  (%d)", isok);
        }
        return false;
    }

    picopass_hdr *hdr = (picopass_hdr *)resp.data.asBytes;

    if (CSN != NULL)
        memcpy(CSN, hdr->csn, 8);

    if (CCNR != NULL)
        memcpy(CCNR, hdr->epurse, 8);

    if (verbose) {
        PrintAndLogEx(SUCCESS, "CSN     %s", sprint_hex(CSN, 8));
        PrintAndLogEx(SUCCESS, "epurse  %s", sprint_hex(CCNR, 8));
    }
    return true;
}

static int CmdHFiClassDump(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass dump",
                  "Dump all memory from a iCLASS tag",
                  "hf iclass dump -k 001122334455667B\n"
                  "hf iclass dump -k AAAAAAAAAAAAAAAA --credit 001122334455667B\n"
                  "hf iclass dump -k AAAAAAAAAAAAAAAA --elite\n"
                  "hf iclass dump --ki 0\n"
                  "hf iclass dump --ki 0 --ci 2");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("f", "file", "<filename>", "filename to save dump to"),
        arg_str0("k", "key", "<hex>", "debit key or NR/MAC for replay as 8 hex bytes"),
        arg_int0(NULL, "ki", "<dec>", "debit key index to select key from memory 'hf iclass managekeys'"),
        arg_str0(NULL, "credit", "<hex>", "credit key as 8 hex bytes"),
        arg_int0(NULL, "ci", "<dec>", "credit key index to select key from memory 'hf iclass managekeys'"),
        arg_lit0(NULL, "elite", "elite computations applied to key"),
        arg_lit0(NULL, "raw", "raw, the key is interpreted as raw block 3/4"),
        arg_lit0(NULL, "nr", "replay of NR/MAC"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    int key_len = 0;
    uint8_t key[8] = {0};
    bool auth = false;

    CLIGetHexWithReturn(ctx, 2, key, &key_len);

    int deb_key_nr = arg_get_int_def(ctx, 3, -1);

    if (key_len > 0 && deb_key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify debit key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    if (key_len > 0) {
        auth = true;
        if (key_len != 8) {
            PrintAndLogEx(ERR, "Debit key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    if (deb_key_nr >= 0) {
        if (deb_key_nr < ICLASS_KEYS_MAX) {
            auth = true;
            memcpy(key, iClass_Key_Table[deb_key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using AA1 (debit) key[%d] " _GREEN_("%s"), deb_key_nr, sprint_hex(iClass_Key_Table[deb_key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    int credit_key_len = 0;
    uint8_t credit_key[8] = {0};
    bool have_credit_key = false;

    CLIGetHexWithReturn(ctx, 4, credit_key, &credit_key_len);

    int credit_key_nr = arg_get_int_def(ctx, 5, -1);

    if (credit_key_len > 0 && credit_key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify credit key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    if (credit_key_len > 0) {
        auth = true;
        have_credit_key = true;
        if (credit_key_len != 8) {
            PrintAndLogEx(ERR, "Credit key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    if (credit_key_nr >= 0) {
        if (credit_key_nr < ICLASS_KEYS_MAX) {
            auth = true;
            have_credit_key = true;
            memcpy(key, iClass_Key_Table[credit_key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using AA2 (credit) key[%d] " _GREEN_("%s"), credit_key_nr, sprint_hex(iClass_Key_Table[credit_key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    bool elite = arg_get_lit(ctx, 6);
    bool rawkey = arg_get_lit(ctx, 7);
    bool use_replay = arg_get_lit(ctx, 8);

    CLIParserFree(ctx);

    if ((use_replay + rawkey + elite) > 1) {
        PrintAndLogEx(FAILED, "Can not use a combo of 'e', 'r', 'n'");
        return PM3_EINVARG;
    }

    uint8_t app_limit1 = 0, app_limit2 = 0;

    uint32_t flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE);

    //get CSN and config
    PacketResponseNG resp;
    uint8_t tag_data[0x100 * 8];
    memset(tag_data, 0xFF, sizeof(tag_data));

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_ICLASS_READER, flags, 0, 0, NULL, 0);
    if (!WaitForResponseTimeout(CMD_ACK, &resp, 2000)) {
        PrintAndLogEx(WARNING, "command execute timeout");
        DropField();
        return PM3_ESOFT;
    }
    DropField();

    uint8_t readStatus = resp.oldarg[0] & 0xff;
    picopass_hdr *hdr = (picopass_hdr *)resp.data.asBytes;

    if (readStatus == 0) {
        PrintAndLogEx(FAILED, "no tag found");
        DropField();
        return PM3_ESOFT;
    }

    uint8_t pagemap = get_pagemap(hdr);

    if (readStatus & (FLAG_ICLASS_CSN | FLAG_ICLASS_CONF | FLAG_ICLASS_CC)) {

        memcpy(tag_data, hdr, 24);

        uint8_t type = get_mem_config(hdr);

        // tags configured for NON SECURE PAGE,  acts different
        if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {

            PrintAndLogEx(INFO, "Card in non-secure page mode detected");

            app_limit1 = card_app2_limit[type];
            app_limit2 = 0;
        } else {
            app_limit1 = hdr->conf.app_limit;
            app_limit2 = card_app2_limit[type];
        }

    } else {
        PrintAndLogEx(FAILED, "failed to read block 0,1,2");
        DropField();
        return PM3_ESOFT;
    }

    if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
        PrintAndLogEx(INFO, "Dumping all available memory, block 3 - %u (0x%02x)", app_limit1, app_limit1);
        if (auth) {
            PrintAndLogEx(INFO, "No keys needed, ignoring user supplied key");
        }
    } else {
        if (auth == false) {
            PrintAndLogEx(FAILED, "Run command with keys");
            return PM3_ESOFT;
        }
        PrintAndLogEx(INFO, "Card has atleast 2 application areas. AA1 limit %u (0x%02X) AA2 limit %u (0x%02X)", app_limit1, app_limit1, app_limit2, app_limit2);
    }

    iclass_dump_req_t payload = {
        .req.use_raw = rawkey,
        .req.use_elite = elite,
        .req.use_credit_key = false,
        .req.use_replay = use_replay,
        .req.send_reply = true,
        .req.do_auth = auth,
        .end_block = app_limit1,
    };
    memcpy(payload.req.key, key, 8);

    // tags configured for NON SECURE PAGE,  acts different
    if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
        payload.start_block = 3;
        payload.req.do_auth = false;
    } else {
        payload.start_block = 6;
    }

    clearCommandBuffer();
    SendCommandNG(CMD_HF_ICLASS_DUMP, (uint8_t *)&payload, sizeof(payload));

    while (true) {

        PrintAndLogEx(NORMAL, "." NOLF);
        if (kbd_enter_pressed()) {
            PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
            DropField();
            return PM3_EOPABORTED;
        }

        if (WaitForResponseTimeout(CMD_HF_ICLASS_DUMP, &resp, 2000))
            break;
    }

    PrintAndLogEx(NORMAL, "");
    if (resp.status != PM3_SUCCESS) {
        PrintAndLogEx(ERR, "failed to communicate with card");
        return resp.status;
    }

    struct p_resp {
        bool isOK;
        uint16_t block_cnt;
        uint32_t bb_offset;
    } PACKED;
    struct p_resp *packet = (struct p_resp *)resp.data.asBytes;

    if (packet->isOK == false) {
        PrintAndLogEx(WARNING, "read AA1 blocks failed");
        return PM3_ESOFT;
    }

    uint32_t startindex = packet->bb_offset;
    uint32_t blocks_read = packet->block_cnt;

    uint8_t tempbuf[0x100 * 8];

    // response ok - now get bigbuf content of the dump
    if (!GetFromDevice(BIG_BUF, tempbuf, sizeof(tempbuf), startindex, NULL, 0, NULL, 2500, false)) {
        PrintAndLogEx(WARNING, "command execution time out");
        return PM3_ETIMEOUT;
    }

    if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
        // all memory available
        memcpy(tag_data + (8 * 3), tempbuf + (8 * 3), (blocks_read * 8));
    } else {
        // div key KD
        memcpy(tag_data + (8 * 3), tempbuf + (8 * 3), 8);
        // AIA data
        memcpy(tag_data + (8 * 5), tempbuf + (8 * 5), 8);
        // AA1 data
        memcpy(tag_data + (8 * 6), tempbuf + (8 * 6), (blocks_read * 8));
    }

    uint16_t bytes_got = (app_limit1 + 1) * 8;

    // try AA2 Kc, Credit
    bool aa2_success = false;

    if (have_credit_key && pagemap != 0x01) {

        // AA2 authenticate credit key
        memcpy(payload.req.key, credit_key, 8);

        payload.req.use_credit_key = true;
        payload.start_block = app_limit1 + 1;
        payload.end_block = app_limit2;
        payload.req.do_auth = true;

        clearCommandBuffer();
        SendCommandNG(CMD_HF_ICLASS_DUMP, (uint8_t *)&payload, sizeof(payload));

        while (true) {
            PrintAndLogEx(NORMAL, "." NOLF);
            if (kbd_enter_pressed()) {
                PrintAndLogEx(WARNING, "\naborted via keyboard!\n");
                DropField();
                return PM3_EOPABORTED;
            }

            if (WaitForResponseTimeout(CMD_HF_ICLASS_DUMP, &resp, 2000))
                break;
        }
        PrintAndLogEx(NORMAL, "");
        if (resp.status != PM3_SUCCESS) {
            PrintAndLogEx(ERR, "failed to communicate with card");
            goto write_dump;
        }

        packet = (struct p_resp *)resp.data.asBytes;
        if (packet->isOK == false) {
            PrintAndLogEx(WARNING, "failed read block using credit key");
            goto write_dump;
        }

        blocks_read = packet->block_cnt;
        startindex = packet->bb_offset;

        if (blocks_read * 8 > sizeof(tag_data) - bytes_got) {
            PrintAndLogEx(WARNING, "data exceeded buffer size! ");
            blocks_read = (sizeof(tag_data) - bytes_got) / 8;
        }

        // get dumped data from bigbuf
        if (!GetFromDevice(BIG_BUF, tempbuf, sizeof(tempbuf), startindex, NULL, 0, NULL, 2500, false)) {
            PrintAndLogEx(WARNING, "command execution time out");
            goto write_dump;
        }

        // div key KC
        memcpy(tag_data + (8 * 4), tempbuf + (8 * 4), 8);

        // AA2 data
        memcpy(tag_data + (8 * (app_limit1 + 1)), tempbuf + (8 * (app_limit1 + 1)), (blocks_read * 8));

        bytes_got = (blocks_read * 8);

        aa2_success = true;
    }

write_dump:

    if (have_credit_key && pagemap != 0x01 && aa2_success == false)
        PrintAndLogEx(INFO, "Reading AA2 failed. dumping AA1 data to file");

    // print the dump
    printIclassDumpContents(tag_data, 1, (bytes_got / 8), bytes_got);

    // use CSN as filename
    if (filename[0] == 0) {
        strcat(filename, "hf-iclass-");
        FillFileNameByUID(filename, tag_data, "-dump", 8);
    }

    // save the dump to .bin file
    PrintAndLogEx(SUCCESS, "saving dump file - %u blocks read", bytes_got / 8);
    saveFile(filename, ".bin", tag_data, bytes_got);
    saveFileEML(filename, tag_data, bytes_got, 8);
    saveFileJSON(filename, jsfIclass, tag_data, bytes_got, NULL);

    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass decrypt") "` to decrypt dump file");
    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass view") "` to view dump file");
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

static int iclass_write_block(uint8_t blockno, uint8_t *bldata, uint8_t *KEY, bool use_credit_key, bool elite, bool rawkey, bool replay, bool verbose, bool use_secure_pagemode) {

    iclass_writeblock_req_t payload = {
        .req.use_raw = rawkey,
        .req.use_elite = elite,
        .req.use_credit_key = use_credit_key,
        .req.use_replay = replay,
        .req.blockno = blockno,
        .req.send_reply = true,
        .req.do_auth = use_secure_pagemode,
    };
    memcpy(payload.req.key, KEY, 8);
    memcpy(payload.data, bldata, sizeof(payload.data));

    clearCommandBuffer();
    SendCommandNG(CMD_HF_ICLASS_WRITEBL, (uint8_t *)&payload, sizeof(payload));
    PacketResponseNG resp;

    if (WaitForResponseTimeout(CMD_HF_ICLASS_WRITEBL, &resp, 2000) == 0) {
        if (verbose) PrintAndLogEx(WARNING, "Command execute timeout");
        return PM3_ETIMEOUT;
    }

    if (resp.status != PM3_SUCCESS) {
        if (verbose) PrintAndLogEx(ERR, "failed to communicate with card");
        return resp.status;
    }
    return (resp.data.asBytes[0] == 1) ? PM3_SUCCESS : PM3_ESOFT;
}

static int CmdHFiClass_WriteBlock(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass wrbl",
                  "Write data to an iCLASS tag",
                  "hf iclass wrbl -b 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B\n"
                  "hf iclass wrbl -b 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B --credit\n"
                  "hf iclass wrbl -b 10 -d AAAAAAAAAAAAAAAA --ki 0");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
        arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
        arg_int1("b", "block", "<dec>", "The block number to read"),
        arg_str1("d", "data", "<hex>", "data to write as 8 hex bytes"),
        arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
        arg_lit0(NULL, "elite", "elite computations applied to key"),
        arg_lit0(NULL, "raw", "no computations applied to key"),
        arg_lit0(NULL, "nr", "replay of NR/MAC"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int key_len = 0;
    uint8_t key[8] = {0};

    CLIGetHexWithReturn(ctx, 1, key, &key_len);

    int key_nr = arg_get_int_def(ctx, 2, -1);

    if (key_len > 0 && key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    bool auth = false;

    if (key_len > 0) {
        auth = true;
        if (key_len != 8) {
            PrintAndLogEx(ERR, "Key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else if (key_nr >= 0) {
        if (key_nr < ICLASS_KEYS_MAX) {
            auth = true;
            memcpy(key, iClass_Key_Table[key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex(iClass_Key_Table[key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    int blockno = arg_get_int_def(ctx, 3, 0);

    int data_len = 0;
    uint8_t data[8] = {0};
    CLIGetHexWithReturn(ctx, 4, data, &data_len);

    if (data_len != 8) {
        PrintAndLogEx(ERR, "Data must be 8 hex bytes (16 hex symbols)");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    bool use_credit_key = arg_get_lit(ctx, 5);
    bool elite = arg_get_lit(ctx, 6);
    bool rawkey = arg_get_lit(ctx, 7);
    bool use_replay = arg_get_lit(ctx, 8);
    bool verbose = arg_get_lit(ctx, 9);

    CLIParserFree(ctx);

    if ((use_replay + rawkey + elite) > 1) {
        PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw', 'nr'");
        return PM3_EINVARG;
    }

    int isok = iclass_write_block(blockno, data, key, use_credit_key, elite, rawkey, use_replay, verbose, auth);
    switch (isok) {
        case PM3_SUCCESS:
            PrintAndLogEx(SUCCESS, "Wrote block %3d/0x%02X successful", blockno, blockno);
            break;
        case PM3_ETEAROFF:
            if (verbose)
                PrintAndLogEx(INFO, "Writing tear off triggered");
            break;
        default:
            PrintAndLogEx(FAILED, "Writing failed");
            break;
    }
    return isok;
}

static int CmdHFiClassRestore(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass restore",
                  "Restore data from dumpfile onto a iCLASS tag",
                  "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0\n"
                  "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 --ki 0 --elite\n"
                  "hf iclass restore -f hf-iclass-AA162D30F8FF12F1-dump.bin --first 6 --last 18 -k 1122334455667788 --elite\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<filename>", "specify a filename to restore"),
        arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
        arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
        arg_int1(NULL, "first", "<dec>", "The first block number to restore"),
        arg_int1(NULL, "last", "<dec>", "The last block number to restore"),
        arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
        arg_lit0(NULL, "elite", "elite computations applied to key"),
        arg_lit0(NULL, "raw", "no computations applied to key"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    int key_len = 0;
    uint8_t key[8] = {0};

    CLIGetHexWithReturn(ctx, 2, key, &key_len);

    int key_nr = arg_get_int_def(ctx, 3, -1);

    if (key_len > 0 && key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    if (key_len > 0) {
        if (key_len != 8) {
            PrintAndLogEx(ERR, "Key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else if (key_nr >= 0) {
        if (key_nr < ICLASS_KEYS_MAX) {
            memcpy(key, iClass_Key_Table[key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex(iClass_Key_Table[key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else {
        PrintAndLogEx(ERR, "Please specify a key or key index");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    int startblock = arg_get_int_def(ctx, 4, 0);
    int endblock = arg_get_int_def(ctx, 5, 0);

    bool use_credit_key = arg_get_lit(ctx, 6);
    bool elite = arg_get_lit(ctx, 7);
    bool rawkey = arg_get_lit(ctx, 8);
    bool verbose = arg_get_lit(ctx, 9);

    CLIParserFree(ctx);

    if (rawkey + elite > 1) {
        PrintAndLogEx(FAILED, "Can not use both 'e', 'r'");
        return PM3_EINVARG;
    }

    if (startblock < 5) {
        PrintAndLogEx(WARNING, "you cannot write key blocks this way. yet... make your start block > 4");
        return PM3_EINVARG;
    }

    uint32_t payload_size = sizeof(iclass_restore_req_t) + (sizeof(iclass_restore_item_t) * (endblock - startblock + 1));

    if (payload_size > PM3_CMD_DATA_SIZE) {
        PrintAndLogEx(NORMAL, "Trying to write too many blocks at once.  Max: %d", PM3_CMD_DATA_SIZE / 8);
        return PM3_EINVARG;
    }

    uint8_t *dump = NULL;
    size_t bytes_read = 0;
    if (loadFile_safe(filename, "", (void **)&dump, &bytes_read) != PM3_SUCCESS) {
        PrintAndLogEx(FAILED, "File: " _YELLOW_("%s") ": not found or locked.", filename);
        return PM3_EFILE;
    }

    if (bytes_read == 0) {
        PrintAndLogEx(ERR, "file reading error");
        free(dump);
        return PM3_EFILE;
    }

    if (bytes_read < ((endblock - startblock + 1) * 8)) {
        PrintAndLogEx(ERR, "file is smaller than your suggested block range ( " _RED_("0x%02x..0x%02x")" )",
                      startblock, endblock
                     );
        free(dump);
        return PM3_EFILE;
    }

    iclass_restore_req_t *payload = calloc(1, payload_size);
    payload->req.use_raw = rawkey;
    payload->req.use_elite = elite;
    payload->req.use_credit_key = use_credit_key;
    payload->req.use_replay = false;
    payload->req.blockno = startblock;
    payload->req.send_reply = true;
    payload->req.do_auth = true;
    memcpy(payload->req.key, key, 8);

    payload->item_cnt = (endblock - startblock + 1);

    // read data from file from block 6 --- 19
    // we will use this struct [data 8 bytes][MAC 4 bytes] for each block calculate all mac number for each data
    // then copy to usbcommand->asbytes;
    // max is 32 - 6 = 28 block.  28 x 12 bytes gives 336 bytes

    for (uint8_t i = 0; i < payload->item_cnt; i++) {
        payload->blocks[i].blockno = startblock + i;
        memcpy(payload->blocks[i].data, dump + (startblock * 8) + (i * 8), sizeof(payload->blocks[i].data));
    }

    free(dump);

    if (verbose) {
        PrintAndLogEx(INFO, "Preparing to restore block range %02d..%02d", startblock, endblock);

        PrintAndLogEx(INFO, "---------+----------------------");
        PrintAndLogEx(INFO, " block#  | data");
        PrintAndLogEx(INFO, "---------+----------------------");

        for (uint8_t i = 0; i < payload->item_cnt; i++) {
            iclass_restore_item_t item = payload->blocks[i];
            PrintAndLogEx(INFO, "%3d/0x%02X | %s", item.blockno, item.blockno, sprint_hex_inrow(item.data, sizeof(item.data)));
        }
    }

    PrintAndLogEx(INFO, "restore started...");

    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_ICLASS_RESTORE, (uint8_t *)payload, payload_size);

    if (WaitForResponseTimeout(CMD_HF_ICLASS_RESTORE, &resp, 2500) == 0) {
        PrintAndLogEx(WARNING, "command execute timeout");
        DropField();
        free(payload);
        return PM3_ETIMEOUT;
    }

    if (resp.status == PM3_SUCCESS) {
        PrintAndLogEx(SUCCESS, "iCLASS restore " _GREEN_("successful"));
        PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass rdbl ") "` to verify data on card");
    } else {
        PrintAndLogEx(WARNING, "iCLASS restore " _RED_("failed"));
    }

    free(payload);
    return resp.status;
}

static int iclass_read_block(uint8_t *KEY, uint8_t blockno, uint8_t keyType, bool elite, bool rawkey, bool replay, bool verbose, bool auth, uint8_t *out) {

    iclass_auth_req_t payload = {
        .use_raw = rawkey,
        .use_elite = elite,
        .use_credit_key = (keyType == 0x18),
        .use_replay = replay,
        .blockno = blockno,
        .send_reply = true,
        .do_auth = auth,
    };
    memcpy(payload.key, KEY, 8);

    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_ICLASS_READBL, (uint8_t *)&payload, sizeof(payload));

    if (WaitForResponseTimeout(CMD_HF_ICLASS_READBL, &resp, 2000) == false) {
        if (verbose) PrintAndLogEx(WARNING, "Command execute timeout");
        return PM3_ETIMEOUT;
    }

    if (resp.status != PM3_SUCCESS) {
        if (verbose) PrintAndLogEx(ERR, "failed to communicate with card");
        return PM3_EWRONGANSWER;
    }

    // return data.
    iclass_readblock_resp_t *packet = (iclass_readblock_resp_t *)resp.data.asBytes;

    if (packet->isOK == false) {
        if (verbose) PrintAndLogEx(FAILED, "authentication error");
        return PM3_ESOFT;
    }

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(SUCCESS, " block %3d/0x%02X : " _GREEN_("%s"), blockno, blockno, sprint_hex(packet->data, sizeof(packet->data)));
    PrintAndLogEx(NORMAL, "");

    if (out)
        memcpy(out, packet->data, sizeof(packet->data));

    return PM3_SUCCESS;
}

static int CmdHFiClass_ReadBlock(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass rdbl",
                  "Read a iCLASS block from tag",
                  "hf iclass rdbl -b 6 -k 0011223344556677\n"
                  "hf iclass rdbl -b 27 -k 0011223344556677 --credit\n"
                  "hf iclass rdbl -b 10 --ki 0");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
        arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
        arg_int1("b", "block", "<dec>", "The block number to read"),
        arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
        arg_lit0(NULL, "elite", "elite computations applied to key"),
        arg_lit0(NULL, "raw", "no computations applied to key"),
        arg_lit0(NULL, "nr", "replay of NR/MAC"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int key_len = 0;
    uint8_t key[8] = {0};

    CLIGetHexWithReturn(ctx, 1, key, &key_len);

    int key_nr = arg_get_int_def(ctx, 2, -1);

    if (key_len > 0 && key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    bool auth = false;

    if (key_len > 0) {
        auth = true;
        if (key_len != 8) {
            PrintAndLogEx(ERR, "Key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else if (key_nr >= 0) {
        if (key_nr < ICLASS_KEYS_MAX) {
            auth = true;
            memcpy(key, iClass_Key_Table[key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex(iClass_Key_Table[key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    int blockno = arg_get_int_def(ctx, 3, 0);

    uint8_t keyType = 0x88; //debit key
    if (arg_get_lit(ctx, 4)) {
        PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key");
        keyType = 0x18; //credit key
    }

    bool elite = arg_get_lit(ctx, 5);
    bool rawkey = arg_get_lit(ctx, 6);
    bool use_replay = arg_get_lit(ctx, 7);
    bool verbose = arg_get_lit(ctx, 8);

    CLIParserFree(ctx);

    if ((use_replay + rawkey + elite) > 1) {
        PrintAndLogEx(ERR, "Can not use a combo of 'e', 'r', 'n'");
        return PM3_EINVARG;
    }

    if (verbose) {
        if (key_len > 0)
            PrintAndLogEx(SUCCESS, "Using key %s", sprint_hex(key, 8));
    }

    if (auth == false && verbose) {
        PrintAndLogEx(WARNING, "warning: no authentication used with read. Typical for cards configured into `non-secure page`");

    }

    uint8_t data[8] = {0};
    int res = iclass_read_block(key, blockno, keyType, elite, rawkey, use_replay, verbose, auth, data);
    if (res != PM3_SUCCESS)
        return res;

    if (blockno < 6 || blockno > 7)
        return PM3_SUCCESS;

    if (memcmp(data, empty, 8) == 0)
        return PM3_SUCCESS;

    bool use_sc = IsCryptoHelperPresent(verbose);
    if (use_sc == false)
        return PM3_SUCCESS;

    // crypto helper available.
    PrintAndLogEx(INFO, "----------------------------- " _CYAN_("cardhelper") " -----------------------------");

    switch (blockno) {
        case 6: {
            DecodeBlock6(data);
            break;
        }
        case 7: {

            uint8_t dec_data[8];

            uint64_t a = bytes_to_num(data, 8);
            bool starts = (leadingzeros(a) < 12);
            bool ones = (countones(a) > 16 && countones(a) < 48);

            if (starts && ones) {
                PrintAndLogEx(INFO, "data looks encrypted, False Positives " _YELLOW_("ARE") " possible");
                Decrypt(data, dec_data);
                PrintAndLogEx(SUCCESS, "decrypted : " _GREEN_("%s"), sprint_hex(dec_data, sizeof(dec_data)));
            } else {
                memcpy(dec_data, data, sizeof(dec_data));
                PrintAndLogEx(INFO, "data looks unencrypted, trying to decode");
            }

            if (memcmp(dec_data, empty, 8) != 0) {

                //todo:  remove preamble/sentinal

                uint32_t top = 0, mid, bot;
                mid = bytes_to_num(dec_data, 4);
                bot = bytes_to_num(dec_data + 4, 4);

                char hexstr[16 + 1] = {0};
                hex_to_buffer((uint8_t *)hexstr, dec_data, 8, sizeof(hexstr) - 1, 0, 0, true);
                char binstr[64 + 1] = {0};
                hextobinstring(binstr, hexstr);
                uint8_t i = 0;
                while (i < strlen(binstr) && binstr[i++] == '0');

                i &= 0x3C;
                PrintAndLogEx(SUCCESS, "      bin : %s", binstr + i);
                PrintAndLogEx(INFO, "");
                PrintAndLogEx(INFO, "------------------------------ " _CYAN_("wiegand") " -------------------------------");
                wiegand_message_t packed = initialize_message_object(top, mid, bot);
                HIDTryUnpack(&packed, true);
            } else {
                PrintAndLogEx(INFO, "no credential found");
            }
            break;
        }
    }
    PrintAndLogEx(INFO, "----------------------------------------------------------------------");
    return PM3_SUCCESS;
}

static int CmdHFiClass_loclass(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass loclass",
                  "Execute the offline part of loclass attack\n"
                  "  An iclass dumpfile is assumed to consist of an arbitrary number of\n"
                  "  malicious CSNs, and their protocol responses\n"
                  "  The binary format of the file is expected to be as follows: \n"
                  "  <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>\n"
                  "  <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>\n"
                  "  <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>\n"
                  "   ... totalling N*24 bytes",
                  "hf iclass loclass -f iclass-dump.bin\n"
                  "hf iclass loclass --test");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("f", "file", "<filename>", "filename of Bruteforce iclass dumpfile"),
        arg_lit0(NULL, "test",              "Perform self-test"),
        arg_lit0(NULL, "long",              "Perform self-test, including long ones"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    bool test = arg_get_lit(ctx, 2);
    bool longtest = arg_get_lit(ctx, 3);

    CLIParserFree(ctx);

    if (test || longtest) {
        int errors = testCipherUtils();
        errors += testMAC();
        errors += doKeyTests();
        errors += testElite(longtest);

        if (errors != PM3_SUCCESS)
            PrintAndLogEx(ERR, "There were errors!!!");

        return PM3_ESOFT;
    }

    return bruteforceFileNoKeys(filename);
}

void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t endblock, size_t filesize) {

    picopass_hdr *hdr = (picopass_hdr *)iclass_dump;
//    picopass_ns_hdr *ns_hdr = (picopass_ns_hdr *)iclass_dump;
//    uint8_t pagemap = get_pagemap(hdr);
//    if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { }

    uint8_t lock = hdr->conf.block_writelock;

    // is chip in ReadOnly (RO)
    bool ro = ((lock & 0x80) == 0);

    uint8_t maxmemcount;
    uint8_t filemaxblock = filesize / 8;
    uint8_t mem_config = iclass_dump[13];

    if (mem_config & 0x80)
        maxmemcount = 255;
    else
        maxmemcount = 31;

    if (startblock == 0)
        startblock = 6;

    if ((endblock > maxmemcount) || (endblock == 0))
        endblock = maxmemcount;

    // remember endblock needs to relate to zero-index arrays.
    if (endblock > filemaxblock - 1)
        endblock = filemaxblock - 1;

    /*
    PrintAndLogEx(INFO, "startblock: %u, endblock: %u, filesize: %zu, maxmemcount: %u, filemaxblock: %u"
        , startblock
        , endblock
        , filesize
        , maxmemcount
        , filemaxblock
    );
    */
    uint8_t pagemap = get_pagemap(hdr);



    int i = startblock;

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, " block#  | data                    | ascii    |lck| info");
    PrintAndLogEx(INFO, "---------+-------------------------+----------+---+--------------");
    PrintAndLogEx(INFO, "  0/0x00 | " _GREEN_("%s") " |   | CSN ", sprint_hex_ascii(iclass_dump, 8));

    if (i != 1)
        PrintAndLogEx(INFO, "....");

    while (i <= endblock) {
        uint8_t *blk = iclass_dump + (i * 8);

        bool bl_lock = false;
        if (ro == false) {
            switch (i) {
                case 12: {
                    bl_lock = ((lock & 0x40) == 0);
                    break;
                }
                case 11: {
                    bl_lock = ((lock & 0x20) == 0);
                    break;
                }
                case 10: {
                    bl_lock = ((lock & 0x10) == 0);
                    break;
                }
                case 9: {
                    bl_lock = ((lock & 0x08) == 0);
                    break;
                }
                case 8: {
                    bl_lock = ((lock & 0x04) == 0);
                    break;
                }
                case 7: {
                    bl_lock = ((lock & 0x02) == 0);
                    break;
                }
                case 6: {
                    bl_lock = ((lock & 0x01) == 0);
                    break;
                }
            }
        } else {
            bl_lock = true;
        }

        const char *lockstr = (bl_lock) ? _RED_("x") : " ";

        if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
            const char *info_nonks[] = {"CSN", "Config", "AIA", "User"};
            const char *s = info_nonks[3];
            if (i < 3) {
                s = info_nonks[i];
            }

            PrintAndLogEx(INFO, "%3d/0x%02X | %s | %s | %s ", i, i, sprint_hex_ascii(blk, 8), lockstr, s);
        } else {
            const char *info_ks[] = {"CSN", "Config", "E-purse", "Debit", "Credit", "AIA", "User"};
            const char *s = info_ks[6];
            if (i < 6) {
                s = info_ks[i];
            }
            PrintAndLogEx(INFO, "%3d/0x%02X | %s | %s | %s ", i, i, sprint_hex_ascii(blk, 8), lockstr, s);
        }
        i++;
    }
    PrintAndLogEx(INFO, "---------+-------------------------+----------+---+--------------");
    PrintAndLogEx(NORMAL, "");
}

static int CmdHFiClassView(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass view",
                  "Print a iCLASS tag dump file",
                  "hf iclass view -f hf-iclass-AA162D30F8FF12F1-dump.bin\n"
                  "hf iclass view --first 1 -f hf-iclass-AA162D30F8FF12F1-dump.bin\n");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<filename>",  "filename of dump"),
        arg_int0(NULL, "first", "<dec>", "Begin printing from this block (default block 6)"),
        arg_int0(NULL, "last", "<dec>", "End printing at this block (default 0, ALL)"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE];
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    int startblock = arg_get_int_def(ctx, 2, 0);
    int endblock = arg_get_int_def(ctx, 3, 0);
    bool verbose = arg_get_lit(ctx, 4);

    CLIParserFree(ctx);

    uint8_t *dump = NULL;
    size_t bytes_read = 0;
    if (loadFile_safe(filename, "", (void **)&dump, &bytes_read) != PM3_SUCCESS) {
        PrintAndLogEx(FAILED, "File: " _YELLOW_("%s") ": not found or locked.", filename);
        return PM3_EFILE;
    }

    if (verbose) {
        PrintAndLogEx(INFO, "File: " _YELLOW_("%s"), filename);
        PrintAndLogEx(INFO, "File size %zu bytes, file blocks %d (0x%x)", bytes_read, (uint16_t)(bytes_read >> 3), (uint16_t)(bytes_read >> 3));
        PrintAndLogEx(INFO, "Printing blocks from: " _YELLOW_("%02d") " to: " _YELLOW_("%02d"), (startblock == 0) ? 6 : startblock, endblock);
    }

    PrintAndLogEx(NORMAL, "");
    print_picopass_header((picopass_hdr *) dump);
    print_picopass_info((picopass_hdr *) dump);
    printIclassDumpContents(dump, startblock, endblock, bytes_read);
    free(dump);
    return PM3_SUCCESS;
}

void HFiClassCalcDivKey(uint8_t *CSN, uint8_t *KEY, uint8_t *div_key, bool elite) {
    if (elite) {
        uint8_t keytable[128] = {0};
        uint8_t key_index[8] = {0};
        uint8_t key_sel[8] = { 0 };
        uint8_t key_sel_p[8] = { 0 };
        hash2(KEY, keytable);
        hash1(CSN, key_index);
        for (uint8_t i = 0; i < 8 ; i++)
            key_sel[i] = keytable[key_index[i]];

        //Permute from iclass format to standard format
        permutekey_rev(key_sel, key_sel_p);
        diversifyKey(CSN, key_sel_p, div_key);
    } else {
        diversifyKey(CSN, KEY, div_key);
    }
}

//when told CSN, oldkey, newkey, if new key is elite (elite), and if old key was elite (oldElite)
//calculate and return xor_div_key (ready for a key write command)
//print all div_keys if verbose
static void HFiClassCalcNewKey(uint8_t *CSN, uint8_t *OLDKEY, uint8_t *NEWKEY, uint8_t *xor_div_key, bool elite, bool oldElite, bool verbose) {
    uint8_t old_div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    uint8_t new_div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    //get old div key
    HFiClassCalcDivKey(CSN, OLDKEY, old_div_key, oldElite);
    //get new div key
    HFiClassCalcDivKey(CSN, NEWKEY, new_div_key, elite);

    for (uint8_t i = 0; i < ARRAYLEN(old_div_key); i++) {
        xor_div_key[i] = old_div_key[i] ^ new_div_key[i];
    }
    if (verbose) {
        PrintAndLogEx(SUCCESS, "Old div key : %s", sprint_hex(old_div_key, 8));
        PrintAndLogEx(SUCCESS, "New div key : %s", sprint_hex(new_div_key, 8));
        PrintAndLogEx(SUCCESS, "Xor div key : " _YELLOW_("%s") "\n", sprint_hex(xor_div_key, 8));
    }
}

static int CmdHFiClassCalcNewKey(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass calcnewkey",
                  "Calculate new keys for updating (blocks 3 & 4)",
                  "hf iclass calcnewkey --old 1122334455667788 --new 2233445566778899 --csn deadbeafdeadbeaf --elite2 -> e key to e key given csn\n"
                  "hf iclass calcnewkey --old 1122334455667788 --new 2233445566778899 --elite                         -> std key to e key read csn\n"
                  "hf iclass calcnewkey --old 1122334455667788 --new 2233445566778899                                 -> std to std read csn");

    void *argtable[] = {
        arg_param_begin,
        arg_str0(NULL, "old", "<hex>", "Specify key as 8 hex bytes"),
        arg_int0(NULL, "oki", "<dec>", "Old key index to select key from memory 'hf iclass managekeys'"),
        arg_str0(NULL, "new", "<hex>", "Specify key as 8 hex bytes"),
        arg_int0(NULL, "nki", "<dec>", "New key index to select key from memory 'hf iclass managekeys'"),
        arg_str0(NULL, "csn", "<hex>", "Specify a Card Serial Number (CSN) to diversify the key (if omitted will attempt to read a CSN)"),
        arg_lit0(NULL, "elite", "Elite computations applied to new key"),
        arg_lit0(NULL, "elite2", "Elite computations applied to both old and new key"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int old_key_len = 0;
    uint8_t old_key[8] = {0};
    CLIGetHexWithReturn(ctx, 1, old_key, &old_key_len);

    int old_key_nr = arg_get_int_def(ctx, 2, -1);

    if (old_key_len > 0 && old_key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify old key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    if (old_key_len > 0) {
        if (old_key_len != 8) {
            PrintAndLogEx(ERR, "Old key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else if (old_key_nr >= 0) {
        if (old_key_nr < ICLASS_KEYS_MAX) {
            memcpy(old_key, iClass_Key_Table[old_key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using old key[%d] " _GREEN_("%s"), old_key_nr, sprint_hex(iClass_Key_Table[old_key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else {
        PrintAndLogEx(ERR, "Please specify an old key or old key index");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    int new_key_len = 0;
    uint8_t new_key[8] = {0};
    CLIGetHexWithReturn(ctx, 3, new_key, &new_key_len);

    int new_key_nr = arg_get_int_def(ctx, 4, -1);

    if (new_key_len > 0 && new_key_nr >= 0) {
        PrintAndLogEx(ERR, "Please specify new key or index, not both");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    if (new_key_len > 0) {
        if (new_key_len != 8) {
            PrintAndLogEx(ERR, "New key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else if (new_key_nr >= 0) {
        if (new_key_nr < ICLASS_KEYS_MAX) {
            memcpy(new_key, iClass_Key_Table[new_key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using new key[%d] " _GREEN_("%s"), new_key_nr, sprint_hex(iClass_Key_Table[new_key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    } else {
        PrintAndLogEx(ERR, "Please specify an new key or old key index");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    int csn_len = 0;
    uint8_t csn[8] = {0};
    CLIGetHexWithReturn(ctx, 5, csn, &csn_len);
    bool givenCSN = false;

    if (csn_len > 0) {
        givenCSN = true;
        if (csn_len != 8) {
            PrintAndLogEx(ERR, "CSN is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    bool elite = arg_get_lit(ctx, 6);
    bool old_elite = false;

    if (arg_get_lit(ctx, 7)) {
        elite = true;
        old_elite = true;
    }

    CLIParserFree(ctx);

    uint8_t xor_div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    if (givenCSN == false) {
        uint8_t CCNR[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        if (select_only(csn, CCNR, true) == false) {
            DropField();
            return PM3_ESOFT;
        }
    }

    HFiClassCalcNewKey(csn, old_key, new_key, xor_div_key, elite, old_elite, true);

    return PM3_SUCCESS;
}

static int loadKeys(char *filename) {

    uint8_t *dump = NULL;
    size_t bytes_read = 0;
    if (loadFile_safe(filename, "", (void **)&dump, &bytes_read) != PM3_SUCCESS) {
        PrintAndLogEx(FAILED, "File: " _YELLOW_("%s") ": not found or locked.", filename);
        return PM3_EFILE;
    }

    if (bytes_read > ICLASS_KEYS_MAX * 8) {
        PrintAndLogEx(WARNING, "File is too long to load - bytes: %zu", bytes_read);
        free(dump);
        return PM3_EFILE;
    }
    uint8_t i = 0;
    for (; i < bytes_read / 8; i++)
        memcpy(iClass_Key_Table[i], dump + (i * 8), 8);

    free(dump);
    PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%2d") " keys from %s", i, filename);
    return PM3_SUCCESS;
}

static int saveKeys(char *filename) {
    FILE *f;
    f = fopen(filename, "wb");
    if (!f) {
        PrintAndLogEx(FAILED, "File: " _YELLOW_("%s") ": not found or locked.", filename);
        return PM3_EFILE;
    }
    for (uint8_t i = 0; i < ICLASS_KEYS_MAX; i++) {
        if (fwrite(iClass_Key_Table[i], 8, 1, f) != 1) {
            PrintAndLogEx(WARNING, "save key failed to write to file:" _YELLOW_("%s"), filename);
            break;
        }
    }
    fclose(f);
    return PM3_SUCCESS;
}

static int printKeys(void) {
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "idx| key");
    PrintAndLogEx(INFO, "---+------------------------");
    for (uint8_t i = 0; i < ICLASS_KEYS_MAX; i++) {
        if (memcmp(iClass_Key_Table[i], "\x00\x00\x00\x00\x00\x00\x00\x00", 8) == 0)
            PrintAndLogEx(INFO, " %u |", i);
        else
            PrintAndLogEx(INFO, " %u | " _YELLOW_("%s"), i, sprint_hex(iClass_Key_Table[i], 8));
    }
    PrintAndLogEx(INFO, "---+------------------------");
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

static int CmdHFiClassManageKeys(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass managekeys",
                  "Manage iCLASS Keys in client memory",
                  "hf iclass managekeys --ki 0 -k 1122334455667788 -> set key 1122334455667788 at index 0\n"
                  "hf iclass managekeys -f mykeys.bin --save       -> save key file\n"
                  "hf iclass managekeys -f mykeys.bin --load       -> load key file\n"
                  "hf iclass managekeys -p                         -> print keys");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("f", "file", "<filename>", "Specify a filename to use with load or save operations"),
        arg_str0("k", "key", "<hex>", "Access key as 8 hex bytes"),
        arg_int0(NULL, "ki", "<dec>", "Specify key index to set key in memory"),
        arg_lit0(NULL, "save", "Save keys in memory to file specified by filename"),
        arg_lit0(NULL, "load", "Load keys to memory from file specified by filename"),
        arg_lit0("p", "print", "Print keys loaded into memory"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    int key_len = 0;
    uint8_t key[8] = {0};
    CLIGetHexWithReturn(ctx, 2, key, &key_len);
    uint8_t operation = 0;

    if (key_len > 0) {
        operation += 3;
        if (key_len != 8) {
            PrintAndLogEx(ERR, "Key is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    int key_nr = arg_get_int_def(ctx, 3, -1);

    if (key_nr >= 0) {
        if (key_nr < ICLASS_KEYS_MAX) {
            PrintAndLogEx(SUCCESS, "Current key[%d] " _YELLOW_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key index is out-of-range");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    if (arg_get_lit(ctx, 4)) {  //save
        operation += 6;
    }
    if (arg_get_lit(ctx, 5)) {  //load
        operation += 5;
    }
    if (arg_get_lit(ctx, 6)) {  //print
        operation += 4;
    }

    CLIParserFree(ctx);

    if (operation == 0) {
        PrintAndLogEx(ERR, "No operation specified (load, save, or print)\n");
        return PM3_EINVARG;
    }
    if (operation > 6) {
        PrintAndLogEx(ERR, "Too many operations specified\n");
        return PM3_EINVARG;
    }
    if (operation > 4 && fnlen == 0) {
        PrintAndLogEx(ERR, "You must enter a filename when loading or saving\n");
        return PM3_EINVARG;
    }
    if (key_len > 0 && key_nr == -1) {
        PrintAndLogEx(ERR, "Please specify key index when specifying key");
        return PM3_EINVARG;
    }

    switch (operation) {
        case 3:
            memcpy(iClass_Key_Table[key_nr], key, 8);
            PrintAndLogEx(SUCCESS, "    New key[%d] " _GREEN_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8));
            return PM3_SUCCESS;
        case 4:
            return printKeys();
        case 5:
            return loadKeys(filename);
        case 6:
            return saveKeys(filename);
    }
    return PM3_SUCCESS;
}

static void add_key(uint8_t *key) {

    uint8_t i;
    for (i = 0; i < ICLASS_KEYS_MAX; i++) {

        if (memcmp(iClass_Key_Table[i], key, 8) == 0) {
            PrintAndLogEx(SUCCESS, "Key already at keyslot " _GREEN_("%d"), i);
            break;
        }

        if (memcmp(iClass_Key_Table[i], "\x00\x00\x00\x00\x00\x00\x00\x00", 8) == 0) {
            memcpy(iClass_Key_Table[i], key, 8);
            PrintAndLogEx(SUCCESS, "Added key to keyslot " _GREEN_("%d"), i);
            break;
        }
    }

    if (i == ICLASS_KEYS_MAX) {
        PrintAndLogEx(INFO, "Couldn't find an empty keyslot");
    } else {
        PrintAndLogEx(HINT, "Try " _YELLOW_("`hf iclass managekeys -p`") " to view keys");
    }
}


/*
static int iclass_chk_keys(uint8_t *keyBlock, uint32_t keycount) {

    iclass_premac_t *pre = calloc(keycount, sizeof(iclass_premac_t));
    if (pre == NULL) {
        return PM3_EMALLOC;
    }

    // max 42 keys inside USB_COMMAND.  512/4 = 103 mac
    uint32_t chunksize = keycount > (PM3_CMD_DATA_SIZE / 4) ? (PM3_CMD_DATA_SIZE / 4) : keycount;
    bool lastChunk = false;

    // fast push mode
    conn.block_after_ACK = true;

    // keep track of position of found key
    uint8_t found_offset = 0;
    uint32_t key_offset = 0;
    // main keychunk loop
    for (key_offset = 0; key_offset < keycount; key_offset += chunksize) {

        uint64_t t2 = msclock();
        uint8_t timeout = 0;

        if (kbd_enter_pressed()) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(WARNING, "Aborted via keyboard!");
            goto out;
        }

        uint32_t keys = ((keycount - key_offset)  > chunksize) ? chunksize : keycount - key_offset;

        // last chunk?
        if (keys == keycount - key_offset) {
            lastChunk = true;
            // Disable fast mode on last command
            conn.block_after_ACK = false;
        }
        uint32_t flags = lastChunk << 8;
        // bit 16
        //   - 1 indicates credit key
        //   - 0 indicates debit key (default)
        flags |= (use_credit_key << 16);

        clearCommandBuffer();
        SendCommandOLD(CMD_HF_ICLASS_CHKKEYS, flags, keys, 0, pre + key_offset, 4 * keys);
        PacketResponseNG resp;

        bool looped = false;
        while (!WaitForResponseTimeout(CMD_HF_ICLASS_CHKKEYS, &resp, 2000)) {
            timeout++;
            PrintAndLogEx(NORMAL, "." NOLF);
            if (timeout > 120) {
                PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
                goto out;
            }
            looped = true;
        }

        if (looped)
            PrintAndLogEx(NORMAL, "");

        found_offset = resp.oldarg[1] & 0xFF;
        uint8_t isOK = resp.oldarg[0] & 0xFF;

        t2 = msclock() - t2;
        switch (isOK) {
            case 1: {
                found_key = true;
                PrintAndLogEx(NORMAL, "");
                PrintAndLogEx(SUCCESS, "Found valid key " _GREEN_("%s")
                              , sprint_hex(keyBlock + (key_offset + found_offset) * 8, 8)
                             );
                break;
            }
            case 0: {
                PrintAndLogEx(INPLACE, "Chunk [%d/%d]", key_offset, keycount);
                break;
            }
            case 99: {
            }
            default: {
                break;
            }
        }

        // both keys found.
        if (found_key) {
            break;
        }

    }
    return PM3_SUCCESS;
}
*/

static int CmdHFiClassCheckKeys(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass chk",
                  "Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag",
                  "hf iclass chk -f iclass_default_keys.dic\n"
                  "hf iclass chk -f iclass_default_keys.dic --elite");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<filename>", "Dictionary file with default iclass keys"),
        arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
        arg_lit0(NULL, "elite", "elite computations applied to key"),
        arg_lit0(NULL, "raw", "no computations applied to key (raw)"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    bool use_credit_key = arg_get_lit(ctx, 2);
    bool use_elite = arg_get_lit(ctx, 3);
    bool use_raw = arg_get_lit(ctx, 4);

    CLIParserFree(ctx);

    uint8_t CSN[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    uint8_t CCNR[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    bool found_key = false;
    //bool found_credit = false;
    bool got_csn = false;

    uint64_t t1 = msclock();

    uint8_t *keyBlock = NULL;
    uint32_t keycount = 0;

    // load keys
    int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount);
    if (res != PM3_SUCCESS || keycount == 0) {
        free(keyBlock);
        return res;
    }

    // Get CSN / UID and CCNR
    PrintAndLogEx(SUCCESS, "Reading tag CSN / CCNR...");
    for (uint8_t i = 0; i < ICLASS_AUTH_RETRY && !got_csn; i++) {
        got_csn = select_only(CSN, CCNR, false);
        if (got_csn == false)
            PrintAndLogEx(WARNING, "one more try");
    }

    if (got_csn == false) {
        PrintAndLogEx(WARNING, "Tried 10 times. Can't select card, aborting...");
        free(keyBlock);
        DropField();
        return PM3_ESOFT;
    }

    iclass_premac_t *pre = calloc(keycount, sizeof(iclass_premac_t));
    if (pre == NULL) {
        return PM3_EMALLOC;
    }


    PrintAndLogEx(SUCCESS, "    CSN: " _GREEN_("%s"), sprint_hex(CSN, sizeof(CSN)));
    PrintAndLogEx(SUCCESS, "   CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));

    PrintAndLogEx(SUCCESS, "Generating diversified keys %s", (use_elite || use_raw) ? NOLF : "");
    if (use_elite)
        PrintAndLogEx(NORMAL, "using " _YELLOW_("elite algo"));
    if (use_raw)
        PrintAndLogEx(NORMAL, "using " _YELLOW_("raw mode"));

    GenerateMacFrom(CSN, CCNR, use_raw, use_elite, keyBlock, keycount, pre);

    PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", (use_credit_key) ? "CREDIT" : "DEBIT");


    // max 42 keys inside USB_COMMAND.  512/4 = 103 mac
    uint32_t chunksize = keycount > (PM3_CMD_DATA_SIZE / 4) ? (PM3_CMD_DATA_SIZE / 4) : keycount;
    bool lastChunk = false;

    // fast push mode
    conn.block_after_ACK = true;

    // keep track of position of found key
    uint8_t found_offset = 0;
    uint32_t key_offset = 0;
    // main keychunk loop
    for (key_offset = 0; key_offset < keycount; key_offset += chunksize) {

        uint64_t t2 = msclock();
        uint8_t timeout = 0;

        if (kbd_enter_pressed()) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(WARNING, "Aborted via keyboard!");
            goto out;
        }

        uint32_t keys = ((keycount - key_offset)  > chunksize) ? chunksize : keycount - key_offset;

        // last chunk?
        if (keys == keycount - key_offset) {
            lastChunk = true;
            // Disable fast mode on last command
            conn.block_after_ACK = false;
        }
        uint32_t flags = lastChunk << 8;
        // bit 16
        //   - 1 indicates credit key
        //   - 0 indicates debit key (default)
        flags |= (use_credit_key << 16);

        clearCommandBuffer();
        SendCommandOLD(CMD_HF_ICLASS_CHKKEYS, flags, keys, 0, pre + key_offset, 4 * keys);
        PacketResponseNG resp;

        bool looped = false;
        while (!WaitForResponseTimeout(CMD_HF_ICLASS_CHKKEYS, &resp, 2000)) {
            timeout++;
            PrintAndLogEx(NORMAL, "." NOLF);
            if (timeout > 120) {
                PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
                goto out;
            }
            looped = true;
        }

        if (looped)
            PrintAndLogEx(NORMAL, "");

        found_offset = resp.oldarg[1] & 0xFF;
        uint8_t isOK = resp.oldarg[0] & 0xFF;

        t2 = msclock() - t2;
        switch (isOK) {
            case 1: {
                found_key = true;
                PrintAndLogEx(NORMAL, "");
                PrintAndLogEx(SUCCESS, "Found valid key " _GREEN_("%s")
                              , sprint_hex(keyBlock + (key_offset + found_offset) * 8, 8)
                             );
                break;
            }
            case 0: {
                PrintAndLogEx(INPLACE, "Chunk [%d/%d]", key_offset, keycount);
                break;
            }
            case 99: {
            }
            default: {
                break;
            }
        }

        // both keys found.
        if (found_key) {
            break;
        }
    }

out:
    t1 = msclock() - t1;

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(SUCCESS, "time in iclass chk " _YELLOW_("%.3f") " seconds", (float)t1 / 1000.0);
    DropField();

    if (found_key) {
        uint8_t *key = keyBlock + (key_offset + found_offset) * 8;
        add_key(key);
    }

    free(pre);
    free(keyBlock);
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}


// this method tries to identify in which configuration mode a iCLASS / iCLASS SE reader is in.
// Standard or Elite / HighSecurity mode.  It uses a default key dictionary list in order to work.
static int CmdHFiClassLookUp(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass lookup",
                  "Lookup keys takes some sniffed trace data and tries to verify what key was used against a dictionary file",
                  "hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic\n"
                  "hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<filename>", "Dictionary file with default iclass keys"),
        arg_str1(NULL, "csn", "<hex>", "Specify CSN as 8 hex bytes"),
        arg_str1(NULL, "epurse", "<hex>", "Specify ePurse as 8 hex bytes"),
        arg_str1(NULL, "macs", "<hex>", "MACs"),
        arg_lit0(NULL, "raw", "no computations applied to key"),
        arg_lit0(NULL, "elite", "Elite computations applied to key"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    int csn_len = 0;
    uint8_t csn[8] = {0};
    CLIGetHexWithReturn(ctx, 2, csn, &csn_len);

    if (csn_len > 0) {
        if (csn_len != 8) {
            PrintAndLogEx(ERR, "CSN is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    int epurse_len = 0;
    uint8_t epurse[8] = {0};
    CLIGetHexWithReturn(ctx, 3, epurse, &epurse_len);

    if (epurse_len > 0) {
        if (epurse_len != 8) {
            PrintAndLogEx(ERR, "ePurse is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    int macs_len = 0;
    uint8_t macs[8] = {0};
    CLIGetHexWithReturn(ctx, 4, macs, &macs_len);

    if (macs_len > 0) {
        if (macs_len != 8) {
            PrintAndLogEx(ERR, "MAC is incorrect length");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    bool use_elite = arg_get_lit(ctx, 5);
    bool use_raw = arg_get_lit(ctx, 6);

    CLIParserFree(ctx);

    uint8_t CCNR[12];
    uint8_t MAC_TAG[4] = { 0, 0, 0, 0 };

    // stupid copy.. CCNR is a combo of epurse and reader nonce
    memcpy(CCNR, epurse, 8);
    memcpy(CCNR + 8, macs, 4);
    memcpy(MAC_TAG, macs + 4, 4);

    PrintAndLogEx(SUCCESS, "    CSN: " _GREEN_("%s"), sprint_hex(csn, sizeof(csn)));
    PrintAndLogEx(SUCCESS, " Epurse: %s", sprint_hex(epurse, sizeof(epurse)));
    PrintAndLogEx(SUCCESS, "   MACS: %s", sprint_hex(macs, sizeof(macs)));
    PrintAndLogEx(SUCCESS, "   CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
    PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));

    // run time
    uint64_t t1 = msclock();

    uint8_t *keyBlock = NULL;
    uint32_t keycount = 0;

    // load keys
    int res = loadFileDICTIONARY_safe(filename, (void **)&keyBlock, 8, &keycount);
    if (res != PM3_SUCCESS || keycount == 0) {
        free(keyBlock);
        return res;
    }

    //iclass_prekey_t
    iclass_prekey_t *prekey = calloc(keycount, sizeof(iclass_prekey_t));
    if (!prekey) {
        free(keyBlock);
        return PM3_EMALLOC;
    }

    PrintAndLogEx(SUCCESS, "Generating diversified keys...");
    GenerateMacKeyFrom(csn, CCNR, use_raw, use_elite, keyBlock, keycount, prekey);

    if (use_elite)
        PrintAndLogEx(SUCCESS, "Using " _YELLOW_("elite algo"));
    if (use_raw)
        PrintAndLogEx(SUCCESS, "Using " _YELLOW_("raw mode"));

    PrintAndLogEx(SUCCESS, "Sorting...");

    // sort mac list.
    qsort(prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32);

    PrintAndLogEx(SUCCESS, "Searching for " _YELLOW_("%s") " key...", "DEBIT");
    iclass_prekey_t *item;
    iclass_prekey_t lookup;
    memcpy(lookup.mac, MAC_TAG, 4);

    // binsearch
    item = (iclass_prekey_t *) bsearch(&lookup, prekey, keycount, sizeof(iclass_prekey_t), cmp_uint32);

    if (item != NULL) {
        PrintAndLogEx(SUCCESS, "Found valid key " _GREEN_("%s"), sprint_hex(item->key, 8));
        add_key(item->key);
    }

    t1 = msclock() - t1;
    PrintAndLogEx(SUCCESS, "time in iclass lookup " _YELLOW_("%.3f") " seconds", (float)t1 / 1000.0);

    free(prekey);
    free(keyBlock);
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

typedef struct {
    uint8_t thread_idx;
    uint8_t use_raw;
    uint8_t use_elite;
    uint32_t keycnt;
    uint8_t csn[8];
    uint8_t cc_nr[12];
    uint8_t *keys;
    union {
        iclass_premac_t *premac;
        iclass_prekey_t *prekey;
    } list;
} PACKED iclass_thread_arg_t;

static size_t iclass_tc = 1;

static void *bf_generate_mac(void *thread_arg) {

    iclass_thread_arg_t *targ = (iclass_thread_arg_t *)thread_arg;
    const uint8_t idx = targ->thread_idx;
    const uint8_t use_raw = targ->use_raw;
    const uint8_t use_elite = targ->use_elite;
    const uint32_t keycnt = targ->keycnt;

    uint8_t *keys = targ->keys;
    iclass_premac_t *list = targ->list.premac;

    uint8_t csn[8];
    uint8_t cc_nr[12];
    memcpy(csn, targ->csn, sizeof(csn));
    memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));

    uint8_t key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    uint8_t div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    for (uint32_t i = idx; i < keycnt; i += iclass_tc) {

        memcpy(key, keys + 8 * i, 8);

        if (use_raw)
            memcpy(div_key, key, 8);
        else
            HFiClassCalcDivKey(csn, key, div_key, use_elite);

        doMAC(cc_nr, div_key, list[i].mac);
    }
    return NULL;
}

// precalc diversified keys and their MAC
void GenerateMacFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_premac_t *list) {

    iclass_tc = num_CPUs();
    pthread_t threads[iclass_tc];
    iclass_thread_arg_t args[iclass_tc];
    // init thread arguments
    for (uint8_t i = 0; i < iclass_tc; i++) {
        args[i].thread_idx = i;
        args[i].use_raw = use_raw;
        args[i].use_elite = use_elite;
        args[i].keycnt = keycnt;
        args[i].keys = keys;
        args[i].list.premac = list;

        memcpy(args[i].csn, CSN, sizeof(args[i].csn));
        memcpy(args[i].cc_nr, CCNR, sizeof(args[i].cc_nr));
    }

    for (int i = 0; i < iclass_tc; i++) {
        int res = pthread_create(&threads[i], NULL, bf_generate_mac, (void *)&args[i]);
        if (res) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
            return;
        }
    }

    for (int i = 0; i < iclass_tc; i++)
        pthread_join(threads[i], NULL);
}

static void *bf_generate_mackey(void *thread_arg) {

    iclass_thread_arg_t *targ = (iclass_thread_arg_t *)thread_arg;
    const uint8_t idx = targ->thread_idx;
    const uint8_t use_raw = targ->use_raw;
    const uint8_t use_elite = targ->use_elite;
    const uint32_t keycnt = targ->keycnt;

    uint8_t *keys = targ->keys;
    iclass_prekey_t *list = targ->list.prekey;

    uint8_t csn[8];
    uint8_t cc_nr[12];
    memcpy(csn, targ->csn, sizeof(csn));
    memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));

    uint8_t div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    for (uint32_t i = idx; i < keycnt; i += iclass_tc) {

        memcpy(list[i].key, keys + 8 * i, 8);

        if (use_raw)
            memcpy(div_key, list[i].key, 8);
        else
            HFiClassCalcDivKey(csn, list[i].key, div_key, use_elite);

        doMAC(cc_nr, div_key, list[i].mac);
    }
    return NULL;
}

void GenerateMacKeyFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_prekey_t *list) {

    iclass_tc = num_CPUs();
    pthread_t threads[iclass_tc];
    iclass_thread_arg_t args[iclass_tc];
    // init thread arguments
    for (uint8_t i = 0; i < iclass_tc; i++) {
        args[i].thread_idx = i;
        args[i].use_raw = use_raw;
        args[i].use_elite = use_elite;
        args[i].keycnt = keycnt;
        args[i].keys = keys;
        args[i].list.prekey = list;

        memcpy(args[i].csn, CSN, sizeof(args[i].csn));
        memcpy(args[i].cc_nr, CCNR, sizeof(args[i].cc_nr));
    }

    for (int i = 0; i < iclass_tc; i++) {
        int res = pthread_create(&threads[i], NULL, bf_generate_mackey, (void *)&args[i]);
        if (res) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
            return;
        }
    }

    for (int i = 0; i < iclass_tc; i++)
        pthread_join(threads[i], NULL);

    PrintAndLogEx(NORMAL, "");
}

// print diversified keys
void PrintPreCalcMac(uint8_t *keys, uint32_t keycnt, iclass_premac_t *pre_list) {

    iclass_prekey_t *b = calloc(keycnt, sizeof(iclass_prekey_t));
    if (!b)
        return;

    for (uint32_t i = 0; i < keycnt; i++) {
        memcpy(b[i].key, keys + 8 * i, 8);
        memcpy(b[i].mac, pre_list[i].mac, 4);
    }
    PrintPreCalc(b, keycnt);
    free(b);
}

void PrintPreCalc(iclass_prekey_t *list, uint32_t itemcnt) {
    PrintAndLogEx(NORMAL, "-----+------------------+---------");
    PrintAndLogEx(NORMAL, "#key | key              | mac");
    PrintAndLogEx(NORMAL, "-----+------------------+---------");
    for (int i = 0; i < itemcnt; i++) {

        if (i < 10) {
            PrintAndLogEx(NORMAL, "[%2d] | %016" PRIx64 " | %08" PRIx64, i, bytes_to_num(list[i].key, 8), bytes_to_num(list[i].mac, 4));
        } else if (i == 10) {
            PrintAndLogEx(SUCCESS, "... skip printing the rest");
        }
    }
}

static void permute(uint8_t *data, uint8_t len, uint8_t *output) {
#define KEY_SIZE 8

    if (len > KEY_SIZE) {
        for (uint8_t m = 0; m < len; m += KEY_SIZE) {
            permute(data + m, KEY_SIZE, output + m);
        }
        return;
    }
    if (len != KEY_SIZE) {
        PrintAndLogEx(WARNING, "wrong key size\n");
        return;
    }
    for (uint8_t i = 0; i < KEY_SIZE; ++i) {
        uint8_t p = 0;
        uint8_t mask = 0x80 >> i;
        for (uint8_t j = 0; j < KEY_SIZE; ++j) {
            p >>= 1;
            if (data[j] & mask)
                p |= 0x80;
        }
        output[i] = p;
    }
}
static void permute_rev(uint8_t *data, uint8_t len, uint8_t *output) {
    permute(data, len, output);
    permute(output, len, data);
    permute(data, len, output);
}
static void simple_crc(uint8_t *data, uint8_t len, uint8_t *output) {
    uint8_t crc = 0;
    for (uint8_t i = 0; i < len; ++i) {
        // seventh byte contains the crc.
        if ((i & 0x7) == 0x7) {
            output[i] = crc ^ 0xFF;
            crc = 0;
        } else {
            output[i] = data[i];
            crc ^= data[i];
        }
    }
}
// DES doesn't use the MSB.
static void shave(uint8_t *data, uint8_t len) {
    for (uint8_t i = 0; i < len; ++i)
        data[i] &= 0xFE;
}
static void generate_rev(uint8_t *data, uint8_t len) {
    uint8_t *key = calloc(len, sizeof(uint8_t));
    PrintAndLogEx(SUCCESS, "input permuted key | %s \n", sprint_hex(data, len));
    permute_rev(data, len, key);
    PrintAndLogEx(SUCCESS, "    unpermuted key | %s \n", sprint_hex(key, len));
    shave(key, len);
    PrintAndLogEx(SUCCESS, "               key | %s \n", sprint_hex(key, len));
    free(key);
}
static void generate(uint8_t *data, uint8_t len) {
    uint8_t *key = calloc(len, sizeof(uint8_t));
    uint8_t *pkey = calloc(len, sizeof(uint8_t));
    PrintAndLogEx(SUCCESS, "   input key | %s \n", sprint_hex(data, len));
    permute(data, len, pkey);
    PrintAndLogEx(SUCCESS, "permuted key | %s \n", sprint_hex(pkey, len));
    simple_crc(pkey, len, key);
    PrintAndLogEx(SUCCESS, "  CRC'ed key | %s \n", sprint_hex(key, len));
    free(key);
    free(pkey);
}

static int CmdHFiClassPermuteKey(const char *Cmd) {

    uint8_t key[8] = {0};
    uint8_t data[16] = {0};
    int len = 0;

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass permutekey",
                  "Permute function from 'heart of darkness' paper.",
                  "hf iclass permutekey --reverse --key 0123456789abcdef\n"
                  "hf iclass permutekey --key ff55330f0055330f\n");

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("r", "reverse", "reverse permuted key"),
        arg_str1(NULL, "key", "<hex>", "input key, 8 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);
    bool isReverse = arg_get_lit(ctx, 1);
    CLIGetHexWithReturn(ctx, 2, data, &len);
    CLIParserFree(ctx);

    memcpy(key, data, 8);

    if (isReverse) {
        generate_rev(data, len);
        uint8_t key_std_format[8] = {0};
        permutekey_rev(key, key_std_format);
        PrintAndLogEx(SUCCESS, "Standard NIST format key " _YELLOW_("%s") " \n", sprint_hex(key_std_format, 8));
    } else {
        generate(data, len);
        uint8_t key_iclass_format[8] = {0};
        permutekey(key, key_iclass_format);
        PrintAndLogEx(SUCCESS, "HID permuted iCLASS format: %s \n", sprint_hex(key_iclass_format, 8));
    }
    return PM3_SUCCESS;
}

static int CmdHFiClassEncode(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass encode",
                  "Encode binary wiegand to block 7",
                  "hf iclass encode --bin 10001111100000001010100011 --ki 0            -> FC 31 CN 337\n"
                  "hf iclass encode --bin 10001111100000001010100011 --ki 0 --elite    -> FC 31 CN 337,  writing w elite key"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str1(NULL, "bin", "<bin>", "Binary string i.e 0001001001"),
        arg_int1(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
        arg_lit0(NULL, "credit", "key is assumed to be the credit key"),
        arg_lit0(NULL, "elite", "elite computations applied to key"),
        arg_lit0(NULL, "raw", "no computations applied to key"),
        arg_str0(NULL, "enckey", "<hex>", "3DES transport key, 16 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int bin_len = 63;
    uint8_t bin[70] = {0};
    CLIGetStrWithReturn(ctx, 1, bin, &bin_len);

    int key_nr = arg_get_int_def(ctx, 2, -1);
    bool auth = false;

    uint8_t key[8] = {0};
    if (key_nr >= 0) {
        if (key_nr < ICLASS_KEYS_MAX) {
            auth = true;
            memcpy(key, iClass_Key_Table[key_nr], 8);
            PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex(iClass_Key_Table[key_nr], 8));
        } else {
            PrintAndLogEx(ERR, "Key number is invalid");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }

    bool use_credit_key = arg_get_lit(ctx, 3);
    bool elite = arg_get_lit(ctx, 4);
    bool rawkey = arg_get_lit(ctx, 5);

    int enc_key_len = 0;
    uint8_t enc_key[16] = {0};
    uint8_t *enckeyptr = NULL;
    bool have_enc_key = false;
    bool use_sc = false;
    CLIGetHexWithReturn(ctx, 6, enc_key, &enc_key_len);

    CLIParserFree(ctx);

    if ((rawkey + elite) > 1) {
        PrintAndLogEx(ERR, "Can not use a combo of 'elite', 'raw'");
        return PM3_EINVARG;
    }

    if (enc_key_len > 0) {
        if (enc_key_len != 16) {
            PrintAndLogEx(ERR, "Transport key must be 16 hex bytes (32 HEX characters)");
            return PM3_EINVARG;
        }
        have_enc_key = true;
    }

    if (bin_len > 127) {
        PrintAndLogEx(ERR, "Binary wiegand string must be less than 128 bits");
        return PM3_EINVARG;
    }

    if (have_enc_key == false) {
        use_sc = IsCryptoHelperPresent(false);
        if (use_sc == false) {
            size_t keylen = 0;
            int res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&enckeyptr, &keylen);
            if (res != PM3_SUCCESS) {
                PrintAndLogEx(ERR, "Failed to find the transport key");
                return PM3_EINVARG;
            }
            if (keylen != 16) {
                PrintAndLogEx(ERR, "Failed to load transport key from file");
                return PM3_EINVARG;
            }
            memcpy(enc_key, enckeyptr, sizeof(enc_key));
            free(enckeyptr);
        }
    }

    uint8_t credential[] = {
        0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xE0, 0x17,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    };

    uint8_t data[8];
    memset(data, 0, sizeof(data));
    BitstreamOut bout = {data, 0, 0 };

    for (int i = 0; i < 64 - bin_len - 1; i++) {
        pushBit(&bout, 0);
    }
    // add binary sentinel bit.
    pushBit(&bout, 1);

    // convert binary string to hex bytes
    for (int i = 0; i < bin_len; i++) {
        char c = bin[i];
        if (c == '1')
            pushBit(&bout, 1);
        else if (c == '0')
            pushBit(&bout, 0);
        else {
            PrintAndLogEx(WARNING, "Ignoring '%c'", c);
        }
    }
    memcpy(credential + 8, data, sizeof(data));

    // encrypt with transport key
    if (use_sc) {
        Encrypt(credential + 8, credential + 8);
        Encrypt(credential + 16, credential + 16);
        Encrypt(credential + 24, credential + 24);
    } else {
        iclass_encrypt_block_data(credential + 8, enc_key);
        iclass_encrypt_block_data(credential + 16, enc_key);
        iclass_encrypt_block_data(credential + 24, enc_key);
    }

    int isok = PM3_SUCCESS;
    // write
    for (uint8_t i = 0; i < 4; i++) {
        isok = iclass_write_block(6 + i, credential + (i * 8), key, use_credit_key, elite, rawkey, false, false, auth);
        switch (isok) {
            case PM3_SUCCESS:
                PrintAndLogEx(SUCCESS, "Write block %d/0x0%x ( " _GREEN_("ok") " )  --> " _YELLOW_("%s"), 6 + i, 6 + i, sprint_hex_inrow(credential + (i * 8), 8));
                break;
            default:
                PrintAndLogEx(SUCCESS, "Write block %d/0x0%x ( " _RED_("fail") " )", 6 + i, 6 + i);
                break;
        }
    }
    return isok;
}

/*
static int CmdHFiClassAutopwn(const char *Cmd) {

    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass autopwn",
                  "Tries to check keys, if found,  dump card and save file",
                  "hf iclass autopwn\n");

    void *argtable[] = {
        arg_param_begin,
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    CLIParserFree(ctx);

    // Check keys.

    // dump

    PrintAndLogEx(INFO, "to be implemented");
    return PM3_SUCCESS;
}
*/

static command_t CommandTable[] = {
    {"-----------", CmdHelp,                    AlwaysAvailable, "--------------------- " _CYAN_("operations") " ---------------------"},
    {"help",        CmdHelp,                    AlwaysAvailable, "    This help"},
//    {"clone",       CmdHFiClassClone,           IfPm3Iclass,     "[*] Create a HID credential to Picopass / iCLASS tag"},
    {"dump",        CmdHFiClassDump,            IfPm3Iclass,     "[*] Dump Picopass / iCLASS tag to file"},
    {"info",        CmdHFiClassInfo,            AlwaysAvailable, "    Tag information"},
    {"list",        CmdHFiClassList,            AlwaysAvailable, "    List iclass history"},
    {"rdbl",        CmdHFiClass_ReadBlock,      IfPm3Iclass,     "[*] Read Picopass / iCLASS block"},
    {"reader",      CmdHFiClassReader,          IfPm3Iclass,     "    Act like an Picopass / iCLASS reader"},
    {"restore",     CmdHFiClassRestore,        IfPm3Iclass,      "[*] Restore a dump file onto a Picopass / iCLASS tag"},
    {"sniff",       CmdHFiClassSniff,           IfPm3Iclass,     "    Eavesdrop Picopass / iCLASS communication"},
    {"wrbl",        CmdHFiClass_WriteBlock,     IfPm3Iclass,     "[*] Write Picopass / iCLASS block"},

    {"-----------", CmdHelp,                    AlwaysAvailable, "--------------------- " _CYAN_("recovery") " ---------------------"},
//    {"autopwn",     CmdHFiClassAutopwn,         IfPm3Iclass,     "[*] Automatic key recovery tool for iCLASS"},
    {"chk",         CmdHFiClassCheckKeys,       IfPm3Iclass,     "[*] Check keys"},
    {"loclass",     CmdHFiClass_loclass,        AlwaysAvailable, "[*] Use loclass to perform bruteforce reader attack"},
    {"lookup",      CmdHFiClassLookUp,          AlwaysAvailable, "[*] Uses authentication trace to check for key in dictionary file"},
    {"-----------", CmdHelp,                    AlwaysAvailable, "--------------------- " _CYAN_("simulation") " ---------------------"},
    {"sim",         CmdHFiClassSim,             IfPm3Iclass,     "[*] Simulate iCLASS tag"},
    {"eload",       CmdHFiClassELoad,           IfPm3Iclass,     "[*] Load Picopass / iCLASS dump file into emulator memory"},
    {"esave",       CmdHFiClassESave,           IfPm3Iclass,     "[*] Save emulator memory to file"},
    {"eview",       CmdHFiClassEView,           IfPm3Iclass,     "[.] View emulator memory"},

    {"-----------", CmdHelp,                    AlwaysAvailable, "--------------------- " _CYAN_("utils") " ---------------------"},
    {"calcnewkey",  CmdHFiClassCalcNewKey,      AlwaysAvailable, "[*] Calc diversified keys (blocks 3 & 4) to write new keys"},
    {"encode",      CmdHFiClassEncode,          AlwaysAvailable, "[*] Encode binary wiegand to block 7"},
    {"encrypt",     CmdHFiClassEncryptBlk,      AlwaysAvailable, "[*] Encrypt given block data"},
    {"decrypt",     CmdHFiClassDecrypt,         AlwaysAvailable, "[*] Decrypt given block data or tag dump file" },
    {"managekeys",  CmdHFiClassManageKeys,      AlwaysAvailable, "[*] Manage keys to use with iclass commands"},
    {"permutekey",  CmdHFiClassPermuteKey,      IfPm3Iclass,     "    Permute function from 'heart of darkness' paper"},
    {"view",        CmdHFiClassView,            AlwaysAvailable, "[*] Display content from tag dump file"},
    {NULL, NULL, NULL, NULL}
};

static int CmdHelp(const char *Cmd) {
    (void)Cmd; // Cmd is not used so far
    CmdsHelp(CommandTable);
    return PM3_SUCCESS;
}

int CmdHFiClass(const char *Cmd) {
    clearCommandBuffer();
    return CmdsParse(CommandTable, Cmd);
}

//static void test_credential_type(void) {
// need AA1 key
// Block 5 -> tells if its a legacy or SIO,  also tells which key to use.

// tech   | blocks used           | desc                              | num of payloads
// -------+-----------------------+-----------------------------------+------
// legacy | 6,7,8,9               | AA!, Access control payload       | 1
// SE     | 6,7,8,9,10,11,12      | AA1, Secure identity object (SIO) | 1
// SR     | 6,7,8,9,              | AA1, Access control payload       | 2
//        | 10,11,12,13,14,15,16  | AA1, Secure identity object (SIO) |
// SEOS   |                       |                                   |
//}

int info_iclass(void) {

    uint32_t flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE);

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_ICLASS_READER, flags, 0, 0, NULL, 0);
    PacketResponseNG resp;

    if (WaitForResponseTimeout(CMD_ACK, &resp, 2000)) {

        uint8_t readStatus = resp.oldarg[0] & 0xff;

        // no tag found or button pressed
        if (readStatus == 0 || readStatus == 0xFF) {
            DropField();
            return PM3_EOPABORTED;
        }

        picopass_hdr *hdr = (picopass_hdr *)resp.data.asBytes;
        picopass_ns_hdr *ns_hdr = (picopass_ns_hdr *)resp.data.asBytes;

        PrintAndLogEx(NORMAL, "");
        PrintAndLogEx(INFO, "--------------------- " _CYAN_("Tag Information") " ----------------------");
        PrintAndLogEx(INFO, "------------------------------------------------------------");

        if (readStatus & FLAG_ICLASS_CSN) {
            PrintAndLogEx(SUCCESS, "    CSN: " _GREEN_("%s") " uid", sprint_hex(hdr->csn, sizeof(hdr->csn)));
        }

        if (readStatus & FLAG_ICLASS_CONF) {
            PrintAndLogEx(SUCCESS, " Config: %s card configuration", sprint_hex((uint8_t *)&hdr->conf, sizeof(hdr->conf)));
        }

        // page mapping.  If fuse0|1 == 0x01, card is in non-secure mode, with CSN, CONF, AIA as top 3 blocks.
        // page9 in http://www.proxmark.org/files/Documents/13.56%20MHz%20-%20iClass/DS%20Picopass%202KS%20V1-0.pdf
        uint8_t pagemap = get_pagemap(hdr);
        if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
            PrintAndLogEx(SUCCESS, "    AIA: %s application issuer area", sprint_hex(ns_hdr->app_issuer_area, sizeof(ns_hdr->app_issuer_area)));
        } else {

            if (readStatus & FLAG_ICLASS_CC) {
                PrintAndLogEx(SUCCESS, "E-purse: %s Card challenge, CC", sprint_hex(hdr->epurse, sizeof(hdr->epurse)));
            }

            PrintAndLogEx(SUCCESS, "     Kd: %s debit key, hidden", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
            PrintAndLogEx(SUCCESS, "     Kc: %s credit key, hidden", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));

            if (readStatus & FLAG_ICLASS_AIA) {
                PrintAndLogEx(SUCCESS, "    AIA: %s application issuer area", sprint_hex(hdr->app_issuer_area, sizeof(hdr->app_issuer_area)));
            }
        }

        if (readStatus & FLAG_ICLASS_CONF) {
            print_picopass_info(hdr);
        }

        PrintAndLogEx(INFO, "------------------------ " _CYAN_("Fingerprint") " -----------------------");

        uint8_t aia[8];
        if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
            memcpy(aia, ns_hdr->app_issuer_area, sizeof(aia));
        } else {
            memcpy(aia, hdr->app_issuer_area, sizeof(aia));
        }

        // if CSN ends with FF12E0, it's inside HID CSN range.
        bool isHidRange = (memcmp(hdr->csn + 5, "\xFF\x12\xE0", 3) == 0);

        bool legacy = (memcmp(aia, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
        bool se_enabled = (memcmp(aia, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);

        if (isHidRange) {
            PrintAndLogEx(SUCCESS, "    CSN.......... " _YELLOW_("HID range"));
            if (legacy)
                PrintAndLogEx(SUCCESS, "    Credential... " _GREEN_("iCLASS legacy"));
            if (se_enabled)
                PrintAndLogEx(SUCCESS, "    Credential... " _GREEN_("iCLASS SE"));
        } else {
            PrintAndLogEx(SUCCESS, "    CSN.......... " _YELLOW_("outside HID range"));
        }

        uint8_t cardtype = get_mem_config(hdr);
        PrintAndLogEx(SUCCESS, "    Card type.... " _GREEN_("%s"), card_types[cardtype]);
    }
    DropField();
    return PM3_SUCCESS;
}

