//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// See LICENSE.txt 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 "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"
#include "cmdsmartcard.h"           // smart select fct
#include "proxendian.h"
#include "iclass_cmd.h"
#include "crypto/asn1utils.h"       // ASN1 decoder
#include "preferences.h"


#define NUM_CSNS               9
#define MAC_ITEM_SIZE          24 // csn(8) + epurse(8) + nr(4) + mac(4) = 24 bytes
#define ICLASS_KEYS_MAX        8
#define ICLASS_AUTH_RETRY      10
#define ICLASS_CFG_BLK_SR_BIT  0xA0 // indicates SIO present when set in block6[0] (legacy tags)
#define ICLASS_DECRYPTION_BIN  "iclass_decryptionkey.bin"

static void print_picopass_info(const picopass_hdr_t *hdr);
void print_picopass_header(const picopass_hdr_t *hdr);

static picopass_hdr_t iclass_last_known_card;
static void iclass_set_last_known_card(picopass_hdr_t *card) {
    memcpy(&iclass_last_known_card, card, sizeof(picopass_hdr_t));
}

static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

static int CmdHelp(const char *Cmd);
static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len);

static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][PICOPASS_BLOCK_SIZE] = {
    { 0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78 },
    { 0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90 },
    { 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87 },
    { 0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 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 * 2, sizeof(iclass_prekey_t));
    if (prekey == NULL) {
        return false;
    }

    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);
    GenerateMacKeyFrom(csn, ccnr, false, true, (uint8_t *)iClass_Key_Table, ICLASS_KEYS_MAX, prekey + ICLASS_KEYS_MAX);
    qsort(prekey, ICLASS_KEYS_MAX * 2, 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 * 2, sizeof(iclass_prekey_t), cmp_uint32);
    if (item != NULL) {
        memcpy(key, item->key, 8);
        free(prekey);
        return true;
    }
    free(prekey);
    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 void iclass_upload_emul(uint8_t *d, uint16_t n, uint16_t offset, uint16_t *bytes_sent) {

    struct p {
        uint16_t offset;
        uint16_t len;
        uint8_t data[];
    } PACKED;

    // fast push mode
    g_conn.block_after_ACK = true;

    //Send to device
    *bytes_sent = 0;
    uint16_t bytes_remaining = n;

    PrintAndLogEx(INFO, "Uploading to emulator memory");
    PrintAndLogEx(INFO, "." NOLF);

    while (bytes_remaining > 0) {
        uint32_t bytes_in_packet = MIN(PM3_CMD_DATA_SIZE - 4, bytes_remaining);
        if (bytes_in_packet == bytes_remaining) {
            // Disable fast mode on last packet
            g_conn.block_after_ACK = false;
        }

        struct p *payload = calloc(4 + bytes_in_packet, sizeof(uint8_t));
        payload->offset = offset + *bytes_sent;
        payload->len = bytes_in_packet;
        memcpy(payload->data, d + *bytes_sent, bytes_in_packet);

        clearCommandBuffer();
        SendCommandNG(CMD_HF_ICLASS_EML_MEMSET, (uint8_t *)payload, 4 + bytes_in_packet);
        free(payload);

        bytes_remaining -= bytes_in_packet;
        *bytes_sent += bytes_in_packet;

        PrintAndLogEx(NORMAL, "." NOLF);
        fflush(stdout);
    }
    PrintAndLogEx(NORMAL, "");
}

static 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
};

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

static iclass_config_card_item_t iclass_config_types[13] =  {
    {"Audio/Visual #1 - Beep ON, LED Off, Flash GREEN on read", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x00, 0xA8, 0x8F, 0xA7, 0x80, 0xA9, 0x01}},
    {"Audio/Visual #2 - Beep ON, LED RED, Host must flash GREEN", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x18, 0xAC, 0x00, 0xA8, 0x1F, 0xA7, 0x80, 0xA9, 0x01}},
    {"Audio/Visual #3 - Beep ON, LED Off, Host must flash RED and/or GREEN", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x00, 0xA8, 0x0F, 0xA9, 0x03, 0xA7, 0x80}},
    {"Keypad Output #1 - Buffer ONE key (8 bit Dorado)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    {"Keypad Output #2 - Buffer ONE to FIVE keys (standard 26 bit)", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAE, 0x0B, 0xAF, 0xFF, 0xAD, 0x15, 0xB3, 0x03}},
    {"Keypad Output #3 - Local PIN verify", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAD, 0x6D, 0xB3, 0x03, 0x00, 0x00, 0x00, 0x00}},
    {"Mifare CSN #1 - 32 bit reverse output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x01, 0xA7, 0x80, 0xA8, 0x9F, 0xA9, 0x01}},
    {"Mifare CSN #2 - 16 bit output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x02, 0xA7, 0x80, 0xA8, 0x9F, 0xA9, 0x01}},
    {"Mifare CSN #3 - 34 bit output", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x18, 0xAC, 0x03, 0xA7, 0x80, 0xA8, 0x9F, 0xA9, 0x01}},
    {"Keyroll DISABLE - Set ELITE Key and DISABLE Keyrolling", {0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0xBF, 0x18, 0xBF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
    {"Keyroll ENABLE - Set ELITE Key and ENABLE Keyrolling", {0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0xBF, 0x18, 0xBF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
    {"Reset READER - Reset READER to defaults", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    {"Reset ENROLLER - Reset ENROLLER to defaults", {0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF}}
};

static bool check_config_card(const iclass_config_card_item_t *o) {
    if (o == NULL || strlen(o->desc) == 0) {
        PrintAndLogEx(INFO, "No data available");
        PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass config -l") "` to download from cardhelper");
        return false;
    }
    return true;
}

static int load_config_cards(void) {
    PrintAndLogEx(INFO, "detecting cardhelper...");
    if (IsCardHelperPresent(false) == false) {
        PrintAndLogEx(FAILED, "failed to detect cardhelper");
        return PM3_ENODATA;
    }

    for (int i = 0; i < ARRAYLEN(iclass_config_types); ++i) {

        PrintAndLogEx(INPLACE, "loading setting %i", i);
        iclass_config_card_item_t *ret = &iclass_config_types[i];

        uint8_t desc[70] = {0};
        if (GetConfigCardStrByIdx(i, desc) == PM3_SUCCESS) {
            memcpy(ret->desc, desc, sizeof(desc));
        }

        uint8_t blocks[16] = {0};
        if (GetConfigCardByIdx(i, blocks) == PM3_SUCCESS) {
            memcpy(ret->data, blocks, sizeof(blocks));
        }
    }
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass configcard -p") "` to list all");
    return PM3_SUCCESS;
}

static const iclass_config_card_item_t *get_config_card_item(int idx) {
    if (idx > -1 && idx < 14) {
        return &iclass_config_types[idx];
    }
    return &iclass_config_types[13];
}

static void print_config_cards(void) {
    if (check_config_card(&iclass_config_types[0])) {
        PrintAndLogEx(INFO, "---- " _CYAN_("Config cards available") " ------------");
        for (int i = 0; i < ARRAYLEN(iclass_config_types)   ; ++i) {
            PrintAndLogEx(INFO, "%2d, %s", i, iclass_config_types[i].desc);
        }
        PrintAndLogEx(NORMAL, "");
    }
}

static void print_config_card(const iclass_config_card_item_t *o) {
    if (check_config_card(o)) {
        PrintAndLogEx(INFO, "description... " _YELLOW_("%s"), o->desc);
        PrintAndLogEx(INFO, "data.......... " _YELLOW_("%s"), sprint_hex_inrow(o->data, sizeof(o->data)));
    }
}

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 generate_config_card(const iclass_config_card_item_t *o,  uint8_t *key, bool got_kr) {
    if (check_config_card(o) == false) {
        return PM3_EINVARG;
    }

    // generated config card header
    picopass_hdr_t configcard;
    memset(&configcard, 0xFF, sizeof(picopass_hdr_t));
    memcpy(configcard.csn, "\x41\x87\x66\x00\xFB\xFF\x12\xE0", 8);
    memcpy(&configcard.conf, "\xFF\xFF\xFF\xFF\xF9\xFF\xFF\xBC", 8);
    memcpy(&configcard.epurse, "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 8);
    // defaulting to known AA1 key
    HFiClassCalcDivKey(configcard.csn, iClass_Key_Table[0], configcard.key_d, false);

    // reference
    picopass_hdr_t *cc = &configcard;

    // get header from card
    PrintAndLogEx(INFO, "trying to read a card..");
    int res = read_iclass_csn(false, false, false);
    if (res == PM3_SUCCESS) {
        cc = &iclass_last_known_card;
        // calc diversified key for selected card
        HFiClassCalcDivKey(cc->csn, iClass_Key_Table[0], cc->key_d, false);
    } else {
        PrintAndLogEx(FAILED, "failed to read a card");
        PrintAndLogEx(INFO, "falling back to default config card");
    }

    // generate dump file
    uint8_t app1_limit = cc->conf.app_limit;
    uint8_t old_limit = app1_limit;
    uint16_t tot_bytes = (app1_limit + 1) * 8;

    PrintAndLogEx(INFO, " APP1 limit: %u", app1_limit);
    PrintAndLogEx(INFO, "total bytes: %u", tot_bytes);
    // normal size
    uint8_t *data = calloc(1, tot_bytes);
    if (data == NULL) {
        PrintAndLogEx(FAILED, "failed to allocate memory");
        return PM3_EMALLOC;
    }

    memcpy(data, cc, sizeof(picopass_hdr_t));

    print_picopass_header(cc);

    // Keyrolling configuration cards are special.
    if (strstr(o->desc, "Keyroll") != NULL) {

        if (got_kr == false) {
            PrintAndLogEx(ERR, "please specify KEYROLL key!");
            free(data);
            return PM3_EINVARG;
        }

        if (app1_limit < 0x16) {
            // if card wasn't large enough before,  adapt to new size
            PrintAndLogEx(WARNING, "Adapting applimit1 for KEY rolling..");

            app1_limit = 0x16;
            cc->conf.app_limit = 0x16;
            tot_bytes = (app1_limit + 1) * 8;

            uint8_t *p = realloc(data, tot_bytes);
            if (p == NULL) {
                PrintAndLogEx(FAILED, "failed to allocate memory");
                free(data);
                return PM3_EMALLOC;
            }
            data = p;
        }

        memset(data + sizeof(picopass_hdr_t), 0xFF,  tot_bytes - sizeof(picopass_hdr_t));

        bool old = GetFlushAfterWrite();
        SetFlushAfterWrite(true);

        // KEYROLL need to encrypt
        uint8_t key_en[16] = {0};
        uint8_t *keyptr_en = NULL;
        if (IsCardHelperPresent(false) == false) {
            size_t keylen = 0;
            int res_key = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr_en, &keylen);
            if (res_key != PM3_SUCCESS) {
                PrintAndLogEx(ERR, "Failed to find iclass_decryptionkey.bin");
                free(data);
                return PM3_EINVARG;
            }

            if (keylen != 16) {
                PrintAndLogEx(ERR, "Failed to load transport key from file");
                free(keyptr_en);
                free(data);
                return PM3_EINVARG;
            }
            memcpy(key_en, keyptr_en, sizeof(key_en));
            free(keyptr_en);
        }

        PrintAndLogEx(INFO, "Setting up encryption... " NOLF);
        uint8_t ffs[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
        if (IsCardHelperPresent(false) != false) {
            if (Encrypt(ffs, ffs) == false) {
                PrintAndLogEx(WARNING, "failed to encrypt FF");
            } else {
                PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
            }
        } else {
            iclass_encrypt_block_data(ffs, key_en);
            PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
        }

        // local key copy
        PrintAndLogEx(INFO, "Encrypting local key... " NOLF);
        uint8_t lkey[8];
        memcpy(lkey, key, sizeof(lkey));
        uint8_t enckey1[8];
        if (IsCardHelperPresent(false) != false) {
            if (Encrypt(lkey, enckey1) == false) {
                PrintAndLogEx(WARNING, "failed to encrypt key1");
            } else {
                PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
            }
        } else {
            iclass_encrypt_block_data(lkey, key_en);
            PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
        }

        PrintAndLogEx(INFO, "Copy data... " NOLF);
        memcpy(data, cc, sizeof(picopass_hdr_t));
        memcpy(data + (6 * 8), o->data, sizeof(o->data));

        // encrypted keyroll key 0D
        if (IsCardHelperPresent(false) != false) {
            memcpy(data + (0x0D * 8), enckey1, sizeof(enckey1));
        } else {
            memcpy(data + (0x0D * 8), lkey, sizeof(enckey1));
        }
        // encrypted 0xFF
        for (uint8_t i = 0x0E; i < 0x14; i++) {
            memcpy(data + (i * 8), ffs, sizeof(ffs));
        }
        PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");

        // encrypted partial keyroll key 14
        PrintAndLogEx(INFO, "Setting encrypted partial key14... " NOLF);
        uint8_t foo[8] = {0x15};
        memcpy(foo + 1, key, 7);
        uint8_t enckey2[8];
        if (IsCardHelperPresent(false) != false) {
            if (Encrypt(foo, enckey2) == false) {
                PrintAndLogEx(WARNING, "failed to encrypt partial 1");
            } else {
                PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
                memcpy(data + (0x14 * 8), enckey2, sizeof(enckey2));
            }
        } else {
            iclass_encrypt_block_data(foo, key_en);
            PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
            memcpy(data + (0x14 * 8), foo, sizeof(enckey2));
        }

        // encrypted partial keyroll key 15
        PrintAndLogEx(INFO, "Setting encrypted partial key15... " NOLF);
        memset(foo, 0xFF, sizeof(foo));
        foo[0] = key[7];
        if (IsCardHelperPresent(false) != false) {
            if (Encrypt(foo, enckey2) == false) {
                PrintAndLogEx(WARNING, "failed to encrypt partial 2");
            } else {
                PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
                memcpy(data + (0x15 * 8), enckey2, sizeof(enckey2));
            }
        } else {
            iclass_encrypt_block_data(foo, key_en);
            PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");
            memcpy(data + (0x15 * 8), foo, sizeof(enckey2));
        }

        // encrypted 0xFF
        PrintAndLogEx(INFO, "Setting 0xFF's... " NOLF);
        for (uint16_t i = 0x16; i < (app1_limit + 1); i++) {
            memcpy(data + (i * 8), ffs, sizeof(ffs));
        }

        PrintAndLogEx(NORMAL, "( " _GREEN_("ok") " )");

        // revert potential modified app1_limit
        cc->conf.app_limit = old_limit;

        SetFlushAfterWrite(old);
    } else {
        memcpy(data, cc, sizeof(picopass_hdr_t));
        memcpy(data + (6 * 8), o->data, sizeof(o->data));
    }

    //Send to device
    PrintAndLogEx(INFO, "Uploading to device... ");
    uint16_t bytes_sent = 0;
    iclass_upload_emul(data, tot_bytes, 0, &bytes_sent);
    free(data);

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(SUCCESS, "sent " _YELLOW_("%u") " bytes of data to device emulator memory", bytes_sent);
    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass eview") "` to view dump file");
    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass sim -t 3") "` to start simulating config card");
    return PM3_SUCCESS;
}

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_t *hdr) {
    return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3;
}

static void fuse_config(const picopass_hdr_t *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") " ( %3u ).............  app limit", hdr->conf.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");

    if (notset(fuses, FUSE_FPROD0) && isset(fuses, FUSE_FPROD1)) {
        PrintAndLogEx(INFO, "    PROD0/1...... Default production fuses");
    }
}

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

    // mem-bit 7 = 16K
    // mem-bit 5 = Book
    // mem-bit 4 = 2K
    // chip-bit 4 = Multi App
    *books = 1;
    *pages = 1;

    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;
        *pages = 8;
    } else if (isset(chip_cfg, 0x10) && k16 && book) {
        *kb = 32;
        *app_areas = 3;
        *books = 2;
    } else if (notset(chip_cfg, 0x10) && !k16 && book) {
        *kb = 32;
        *app_areas = 17;
        *pages = 8;
        *books = 2;
    } else {
        *kb = 32;
        *app_areas = 2;
    }
}

static uint8_t get_mem_config(const picopass_hdr_t *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_t *hdr) {
    uint8_t mem = hdr->conf.mem_config;
    uint8_t chip = hdr->conf.chip_config;
    uint8_t kb = 2;
    uint8_t app_areas = 2;
    uint8_t books = 1;
    uint8_t pages = 1;

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

    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) * books * pages);

    PrintAndLogEx(INFO, "    %u books / %u pages"
                  , books
                  , pages
                 );
    PrintAndLogEx(INFO, " First book / first page configuration");
    PrintAndLogEx(INFO, "    Config | 0 - 5 ( 0x00 - 0x05 ) - 6 blocks ");
    PrintAndLogEx(INFO, "    AA1    | 6 - %2d ( 0x06 - 0x%02X ) - %u blocks", app1_limit + 5, app1_limit + 5, app1_limit);
    if (app1_limit + 5 < app2_limit) {
        PrintAndLogEx(INFO, "    AA2    | %2d - %2d ( 0x%02X - 0x%02X ) - %u blocks", app1_limit + 5 + 1, app2_limit, app1_limit + 5 + 1, app2_limit, app2_limit - app1_limit);
    }
    /*
    [=]  32 KBits/3 App Areas ( 2048 bytes )
    [=]     AA1 blocks 250 { 0x06 - 0xFF (06 - 255) }
    [=]     AA2 blocks 5 { 0x100 - 0xFF (256 - 255) }
    */

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

void print_picopass_info(const picopass_hdr_t *hdr) {
    PrintAndLogEx(INFO, "-------------------- " _CYAN_("Card configuration") " --------------------");
    fuse_config(hdr);
    mem_app_config(hdr);
}

void print_picopass_header(const picopass_hdr_t *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)));

    if (memcmp(hdr->key_d, zeros, sizeof(zeros)) && memcmp(hdr->key_d, empty, sizeof(empty))) {
        PrintAndLogEx(SUCCESS, "     Kd... " _YELLOW_("%s") " debit key", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
    } else {
        PrintAndLogEx(SUCCESS, "     Kd... %s debit key ( hidden )", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
    }

    if (memcmp(hdr->key_c, zeros, sizeof(zeros)) && memcmp(hdr->key_c, empty, sizeof(empty))) {
        PrintAndLogEx(SUCCESS, "     Kc... " _YELLOW_("%s") " credit key", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
    } else {
        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) {
    return CmdTraceListAlias(Cmd, "hf iclass", "iclass -c");
}

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);

    if (jam_epurse_update) {
        PrintAndLogEx(INFO, "Sniff with jam of iCLASS e-purse updates...");
    }

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

    memset(&payload, 0, sizeof(payload));

    if (jam_epurse_update) {
        const uint8_t update_epurse_sequence[2] = {0x87, 0x02};
        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(NORMAL, "");
    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");
    if (jam_epurse_update) {
        PrintAndLogEx(HINT, "Verify if the jam worked by comparing value in trace and block 2");
    }
    PrintAndLogEx(NORMAL, "");
    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 specified 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 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_def(ctx, 1, 3);

    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[NUM_CSNS * PICOPASS_BLOCK_SIZE] = {
        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 " _GREEN_("<Enter>") " to abort");
            PacketResponseNG resp;
            clearCommandBuffer();
            SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, NUM_CSNS, 1, csns, NUM_CSNS * PICOPASS_BLOCK_SIZE);

            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 * MAC_ITEM_SIZE;
            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 * MAC_ITEM_SIZE), csns + i * 8, 8);
                //copy epurse
                memcpy(dump + (i * MAC_ITEM_SIZE) + 8, resp.data.asBytes + i * 16, 8);
                // 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);
            }
            /** Now, save to dumpfile **/
            saveFile("iclass_mac_attack", ".bin", dump, datalen);
            free(dump);

            PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass loclass -f iclass_mac_attack.bin") "` 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 " _GREEN_("<Enter>") " to abort");
            PacketResponseNG resp;
            clearCommandBuffer();
            SendCommandMIX(CMD_HF_ICLASS_SIMULATE, sim_type, NUM_CSNS, 1, csns, NUM_CSNS * PICOPASS_BLOCK_SIZE);

            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 * MAC_ITEM_SIZE;
            uint8_t *dump = calloc(datalen, sizeof(uint8_t));
            if (!dump) {
                PrintAndLogEx(WARNING, "Failed to allocate memory");
                return PM3_EMALLOC;
            }

            //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 -f iclass_mac_attack_keyroll_A.bin") "` to recover elite key");
            PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass loclass -f iclass_mac_attack_keyroll_B.bin") "` 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 " _GREEN_("`pm3 button`") " to abort");
            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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    bool shallow_mod = arg_get_lit(ctx, 1);
    CLIParserFree(ctx);
    return info_iclass(shallow_mod);
}

int read_iclass_csn(bool loop, bool verbose, bool shallow_mod) {

    iclass_card_select_t payload = {
        .flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE)
    };

    if (shallow_mod) {
        payload.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
    }

    int res = PM3_SUCCESS;

    do {
        clearCommandBuffer();
        PacketResponseNG resp;
        SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload, sizeof(iclass_card_select_t));

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

            iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
            if (loop) {
                if (resp.status == PM3_ERFTRANS) {
                    continue;
                }
            } else {

                if (r->status == FLAG_ICLASS_NULL || resp.status == PM3_ERFTRANS) {
                    if (verbose) PrintAndLogEx(WARNING, "iCLASS / Picopass card select failed ( %d , %d)", r->status, resp.status);
                    res = PM3_EOPABORTED;
                    break;
                }
            }

            picopass_hdr_t *card = calloc(1, sizeof(picopass_hdr_t));
            if (card) {
                memcpy(card, &r->header.hdr, sizeof(picopass_hdr_t));
                if (loop == false) {
                    PrintAndLogEx(NORMAL, "");
                }
                PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn)));
                iclass_set_last_known_card(card);
                free(card);
                res = PM3_SUCCESS;
            } else {
                PrintAndLogEx(FAILED, "failed to allocate memory");
                res = PM3_EMALLOC;
            }
        }
    } 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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    bool cm = arg_get_lit(ctx, 1);
    bool shallow_mod = arg_get_lit(ctx, 2);
    CLIParserFree(ctx);

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

    return read_iclass_csn(cm, false, shallow_mod);
}

static int CmdHFiClassELoad(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass eload",
                  "Load emulator memory with data from (bin/json) iCLASS dump file",
                  "hf iclass eload -f hf-iclass-AA162D30F8FF12F1-dump.json\n"
                  "hf iclass eload -f hf-iclass-AA162D30F8FF12F1-dump.bin -m\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<fn>", "Specify a filename for dump file"),
        arg_lit0("m", "mem",  "use RDV4 spiffs"),
        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);

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

    bool use_spiffs = arg_get_lit(ctx, 2);
    bool verbose = arg_get_lit(ctx, 3);
    CLIParserFree(ctx);

    // use RDV4 spiffs
    if (use_spiffs && IfPm3Flash() == false) {
        PrintAndLogEx(WARNING, "Device not compiled to support spiffs");
        return PM3_EINVARG;
    }

    if (use_spiffs) {

        if (fnlen > 32) {
            PrintAndLogEx(WARNING, "filename too long for spiffs, expected 32, got %u", fnlen);
            return PM3_EINVARG;
        }

        clearCommandBuffer();
        SendCommandNG(CMD_SPIFFS_ELOAD, (uint8_t *)filename, fnlen);
        PacketResponseNG resp;
        if (WaitForResponseTimeout(CMD_SPIFFS_ELOAD, &resp, 2000) == false) {
            PrintAndLogEx(WARNING, "timeout while waiting for reply.");
            return PM3_ETIMEOUT;
        }

        if (resp.status != PM3_SUCCESS) {
            PrintAndLogEx(FAILED, "Loading file from spiffs to emulatore memory failed");
            return PM3_EFLASH;
        }

        PrintAndLogEx(SUCCESS, "File transfered from spiffs to device emulator memory");
        return PM3_SUCCESS;
    }

    // read dump file
    uint8_t *dump = NULL;
    size_t bytes_read = 2048;
    int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
    if (res != PM3_SUCCESS) {
        return res;
    }

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

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

    PrintAndLogEx(NORMAL, "");

    //Send to device
    uint16_t bytes_sent = 0;
    iclass_upload_emul(dump, bytes_read, 0, &bytes_sent);
    free(dump);
    PrintAndLogEx(SUCCESS, "uploaded " _YELLOW_("%d") " bytes to emulator memory", bytes_sent);
    PrintAndLogEx(HINT, "You are ready to simulate. See " _YELLOW_("`hf iclass sim -h`"));
    PrintAndLogEx(INFO, "Done!");
    return PM3_SUCCESS;
}

static int CmdHFiClassESave(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass esave",
                  "Save emulator memory to file (bin/json)\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", "<fn>", "Specify a filename for dump file"),
        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);
    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) {
        char *fptr = filename;
        fptr += snprintf(fptr, sizeof(filename), "hf-iclass-");
        FillFileNameByUID(fptr, dump, "-dump", 8);
    }

    pm3_save_dump(filename, dump, bytes, jsfIclass);
    free(dump);

    PrintAndLogEx(HINT, "Try `" _YELLOW_("hf iclass view -f") "` 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_lit0("z", "dense", "dense dump output style"),
        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);
    bool dense_output = g_session.dense_output || arg_get_lit(ctx, 3);
    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_t *) dump);
        print_picopass_info((picopass_hdr_t *) dump);
    }

    PrintAndLogEx(NORMAL, "");
    printIclassDumpContents(dump, 1, blocks, bytes, dense_output);

    if (verbose) {
        print_iclass_sio(dump, bytes);
    }

    free(dump);
    return PM3_SUCCESS;
}

static int CmdHFiClassESetBlk(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass esetblk",
                  "Sets an individual block in emulator memory.",
                  "hf iclass esetblk --blk 7 -d 0000000000000000");

    void *argtable[] = {
        arg_param_begin,
        arg_int1(NULL, "blk", "<dec>", "block number"),
        arg_str0("d", "data", "<hex>", "bytes to write, 8 hex bytes"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int blk = arg_get_int_def(ctx, 1, 0);

    if (blk > 255 || blk < 0) {
        PrintAndLogEx(WARNING, "block number must be between 0 and 255. Got " _RED_("%i"), blk);
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    uint8_t data[PICOPASS_BLOCK_SIZE] = {0x00};
    int datalen = 0;
    int res = CLIParamHexToBuf(arg_get_str(ctx, 2), data, sizeof(data), &datalen);
    CLIParserFree(ctx);

    if (res) {
        PrintAndLogEx(FAILED, "Error parsing bytes");
        return PM3_EINVARG;
    }

    if (datalen != PICOPASS_BLOCK_SIZE) {
        PrintAndLogEx(WARNING, "block data must include 8 HEX bytes. Got " _RED_("%i"), datalen);
        return PM3_EINVARG;
    }

    uint16_t bytes_sent = 0;
    iclass_upload_emul(data, sizeof(data), blk * PICOPASS_BLOCK_SIZE, &bytes_sent);

    return PM3_SUCCESS;
}

static bool iclass_detect_new_pacs(uint8_t *d) {
    uint8_t n = 0;
    while (n++ < (PICOPASS_BLOCK_SIZE / 2)) {
        if (d[n] && d[n + 1] == 0xA6) {
            return true;
        }
    }
    return false;
}

// block 7 decoder for PACS
static int iclass_decode_credentials_new_pacs(uint8_t *d) {

    uint8_t offset = 0;
    while (d[offset] == 0 && (offset < PICOPASS_BLOCK_SIZE / 2)) {
        offset++;
    }

    uint8_t pad = d[offset];

    PrintAndLogEx(INFO, "%u , %u", offset, pad);

    char *binstr = (char *)calloc((PICOPASS_BLOCK_SIZE * 8) + 1, sizeof(uint8_t));
    if (binstr == NULL) {
        return PM3_EMALLOC;
    }

    uint8_t n = PICOPASS_BLOCK_SIZE - offset - 2;
    bytes_2_binstr(binstr, d + offset + 2, n);

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(SUCCESS, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(d + offset + 2, n));
    PrintAndLogEx(SUCCESS, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));

    binstr[strlen(binstr) - pad] = '\0';
    PrintAndLogEx(SUCCESS, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));

    size_t hexlen = 0;
    uint8_t hex[16] = {0};
    binstr_2_bytes(hex, &hexlen, binstr);
    PrintAndLogEx(SUCCESS, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen));

    uint32_t top = 0, mid = 0, bot = 0;
    if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) {
        PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
        free(binstr);
        return PM3_EINVARG;
    }

    free(binstr);

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

    return PM3_SUCCESS;
}

static void iclass_decode_credentials(uint8_t *data) {
    picopass_hdr_t *hdr = (picopass_hdr_t *)data;
    if (memcmp(hdr->app_issuer_area, empty, PICOPASS_BLOCK_SIZE)) {
        // Not a Legacy or SR card, nothing to do here.
        return;
    }

    BLOCK79ENCRYPTION encryption = (data[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03);

    uint8_t *b7 = data + (PICOPASS_BLOCK_SIZE * 7);

    bool has_new_pacs = iclass_detect_new_pacs(b7);
    bool has_values = (memcmp(b7, empty, PICOPASS_BLOCK_SIZE) != 0) && (memcmp(b7, zeros, PICOPASS_BLOCK_SIZE) != 0);
    if (has_values && encryption == None) {

        // todo:  remove preamble/sentinel
        PrintAndLogEx(INFO, "Block 7 decoder");

        if (has_new_pacs) {
            iclass_decode_credentials_new_pacs(b7);
        } else {
            char hexstr[16 + 1] = {0};
            hex_to_buffer((uint8_t *)hexstr, b7, PICOPASS_BLOCK_SIZE, sizeof(hexstr) - 1, 0, 0, true);

            uint32_t top = 0, mid = 0, bot = 0;
            hexstring_to_u96(&top, &mid, &bot, hexstr);

            char binstr[64 + 1];
            hextobinstring(binstr, hexstr);
            char *pbin = binstr;
            while (strlen(pbin) && *(++pbin) == '0');

            PrintAndLogEx(SUCCESS, "Binary..................... " _GREEN_("%s"), pbin);

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

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

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"
                  "\nOBS!\n"
                  "In order to use this function, the file `iclass_decryptionkey.bin` must reside\n"
                  "in the resources directory. The file must be 16 bytes binary data\n"
                  "or...\n"
                  "make sure your cardhelper is placed in the sim module",
                  "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", "<fn>", "Specify a filename for dump file"),
        arg_str0("d", "data", "<hex>", "3DES encrypted data"),
        arg_str0("k", "key", "<hex>", "3DES transport key"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_lit0(NULL, "d6", "decode as block 6"),
        arg_lit0("z", "dense", "dense dump output style"),
        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);

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

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

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

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

    bool verbose = arg_get_lit(clictx, 4);
    bool use_decode6 = arg_get_lit(clictx, 5);
    bool dense_output = g_session.dense_output || arg_get_lit(clictx, 6);
    CLIParserFree(clictx);

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

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

    size_t decryptedlen = 2048;
    uint8_t *decrypted = NULL;
    bool have_file = false;
    int res = PM3_SUCCESS;

    // if user supplied dump file,  time to load it
    if (fnlen > 0) {

        // read dump file
        res = pm3_load_dump(filename, (void **)&decrypted, &decryptedlen, 2048);
        if (res != PM3_SUCCESS) {
            return res;
        }

        have_file = true;
    }

    // load transport key
    bool use_sc = false;
    if (have_key == false) {
        use_sc = IsCardHelperPresent(verbose);
        if (use_sc == false) {
            size_t keylen = 0;
            res = loadFile_safe(ICLASS_DECRYPTION_BIN, "", (void **)&keyptr, &keylen);
            if (res != PM3_SUCCESS) {
                PrintAndLogEx(INFO, "Couldn't find any decryption methods");
                free(decrypted);
                return PM3_EINVARG;
            }

            if (keylen != 16) {
                PrintAndLogEx(ERR, "Failed to load transport key from file");
                free(keyptr);
                free(decrypted);
                return PM3_EINVARG;
            }
            memcpy(key, keyptr, sizeof(key));
            free(keyptr);
        }
    }

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

    // decrypt user supplied data
    if (have_data) {

        uint8_t dec_data[8] = {0};
        if (use_sc) {
            Decrypt(enc_data, dec_data);
        } else {
            mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
        }

        PrintAndLogEx(SUCCESS, "encrypted... %s", sprint_hex_inrow(enc_data, sizeof(enc_data)));
        PrintAndLogEx(SUCCESS, "plain....... " _YELLOW_("%s"), sprint_hex_inrow(dec_data, sizeof(dec_data)));

        if (use_sc && use_decode6)
            DecodeBlock6(dec_data);
    }

    // decrypt dump file data
    if (have_file) {

        picopass_hdr_t *hdr = (picopass_hdr_t *)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;
        uint8_t books = 1;
        uint8_t pages = 1;
        getMemConfig(mem, chip, &app_areas, &kb, &books, &pages);

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

        uint8_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));

        bool decrypted_block789 = false;
        for (uint8_t blocknum = 0; blocknum < limit; ++blocknum) {

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

            switch (aa1_encryption) {
                // Right now, only 3DES is supported
                case TRIPLEDES:
                    // Decrypt 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);
                        }
                        decrypted_block789 = true;
                    }
                    break;
                case DES:
                case RFU:
                case None:
                // Nothing to do for None anyway...
                default:
                    continue;
            }

            if (decrypted_block789) {
                // Set the 2 last bits of block6 to 0 to mark the data as decrypted
                decrypted[(6 * 8) + 7] &= 0xFC;
            }
        }

        // 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));

        pm3_save_dump(fptr, decrypted, decryptedlen, jsfIclass);

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

        if (verbose) {
            print_iclass_sio(decrypted, decryptedlen);
        }

        PrintAndLogEx(NORMAL, "");

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

        // decode block 7-8-9
        iclass_decode_credentials(decrypted);

        // decode block 9
        has_values = (memcmp(decrypted + (8 * 9), empty, 8) != 0) && (memcmp(decrypted + (8 * 9), zeros, 8) != 0);
        if (has_values && use_sc) {
            uint8_t usr_blk_len = GetNumberBlocksForUserId(decrypted + (8 * 6));
            if (usr_blk_len < 3) {
                PrintAndLogEx(NORMAL, "");
                PrintAndLogEx(INFO, "Block 9 decoder");

                uint8_t 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 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 = false;
    if (have_key == false) {
        use_sc = IsCardHelperPresent(verbose);
        if (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 any encryption methods");
                return PM3_EINVARG;
            }

            if (keylen != 16) {
                PrintAndLogEx(ERR, "Failed to load transport key from file");
                free(keyptr);
                return PM3_EINVARG;
            }
            memcpy(key, keyptr, sizeof(key));
            free(keyptr);
        }
    }


    PrintAndLogEx(SUCCESS, "plain....... %s", sprint_hex_inrow(blk_data, sizeof(blk_data)));

    if (use_sc) {
        Encrypt(blk_data, blk_data);
    } else {
        iclass_encrypt_block_data(blk_data, key);
    }

    PrintAndLogEx(SUCCESS, "encrypted... " _YELLOW_("%s"), sprint_hex_inrow(blk_data, sizeof(blk_data)));
    return PM3_SUCCESS;
}

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

    iclass_card_select_t payload = {
        .flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE)
    };

    if (shallow_mod) {
        payload.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
    }

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

    if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
        PrintAndLogEx(WARNING, "command execute timeout");
        return false;
    }

    iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
    picopass_hdr_t *hdr = &r->header.hdr;

    // no tag found or button pressed
    if (r->status == FLAG_ICLASS_NULL || resp.status == PM3_ERFTRANS) {
        if (verbose) {
            PrintAndLogEx(FAILED, "failed tag-select, aborting...  (%d)", r->status);
        }
        return false;
    }

    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", "<fn>", "save filename"),
        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_lit0("z", "dense", "dense dump output style"),
        arg_lit0(NULL, "force", "force unsecure card read"),
        arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        arg_lit0(NULL, "ns", "no save to file"),
        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(credit_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);
    bool dense_output = g_session.dense_output || arg_get_lit(ctx, 9);
    bool force = arg_get_lit(ctx, 10);
    bool shallow_mod = arg_get_lit(ctx, 11);
    bool nosave = arg_get_lit(ctx, 12);

    CLIParserFree(ctx);

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

    uint8_t app_limit1 = 0, app_limit2 = 0;

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

    iclass_card_select_t payload_rdr = {
        .flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE)
    };

    if (shallow_mod) {
        payload_rdr.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
    }

    clearCommandBuffer();
    PacketResponseNG resp;
    SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload_rdr, sizeof(iclass_card_select_t));

    if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
        PrintAndLogEx(WARNING, "command execute timeout");
        DropField();
        return PM3_ESOFT;
    }
    DropField();

    if (resp.status == PM3_ERFTRANS) {
        PrintAndLogEx(FAILED, "no tag found");
        DropField();
        return PM3_ESOFT;
    }

    iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;
    if (r->status == FLAG_ICLASS_NULL) {
        PrintAndLogEx(FAILED, "failed to read block 0,1,2");
        return PM3_ESOFT;
    }

    picopass_hdr_t *hdr = &r->header.hdr;
    uint8_t pagemap = get_pagemap(hdr);

    if (r->status & (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 if (hdr->conf.app_limit >= hdr->conf.mem_config) {
            PrintAndLogEx(WARNING, "AA1 config is >= card size, using card size as AA1 limit");
            app_limit1 = card_app2_limit[type];
        } else {
            app_limit1 = hdr->conf.app_limit;
            app_limit2 = card_app2_limit[type];
        }
    }

    //
    if (force) {
        pagemap = PICOPASS_NON_SECURE_PAGEMODE;
        PrintAndLogEx(INFO, "Forcing NON SECURE PAGE dumping");
    }

    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;
        }

        if (app_limit2 != 0) {
            PrintAndLogEx(INFO, "Card has at least 2 application areas. AA1 limit %u (0x%02X) AA2 limit %u (0x%02X)", app_limit1, app_limit1, app_limit2, app_limit2);
        } else {
            PrintAndLogEx(INFO, "Card has 1 application area. AA1 limit %u (0x%02X)", app_limit1, app_limit1);
        }
    }

    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,
        .req.shallow_mod = shallow_mod,
        .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 = 5;
    }

    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) {
        // div key KD
        memcpy(tag_data + (PICOPASS_BLOCK_SIZE * 3),
               tempbuf + (PICOPASS_BLOCK_SIZE * 3), PICOPASS_BLOCK_SIZE);
    }
    // all memory available
    memcpy(tag_data + (PICOPASS_BLOCK_SIZE * payload.start_block),
           tempbuf + (PICOPASS_BLOCK_SIZE * payload.start_block),
           blocks_read * PICOPASS_BLOCK_SIZE);

    uint16_t bytes_got = (app_limit1 + 1) * 8;

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

    if (have_credit_key && pagemap != PICOPASS_NON_SECURE_PAGEMODE && app_limit2 != 0) {

        // 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 + (PICOPASS_BLOCK_SIZE * 4), tempbuf + (PICOPASS_BLOCK_SIZE * 4), PICOPASS_BLOCK_SIZE);

        // AA2 data
        memcpy(tag_data + (PICOPASS_BLOCK_SIZE * payload.start_block),
               tempbuf + (PICOPASS_BLOCK_SIZE * payload.start_block),
               blocks_read * PICOPASS_BLOCK_SIZE);

        bytes_got += (blocks_read * PICOPASS_BLOCK_SIZE);

        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, dense_output);

    if (nosave) {
        PrintAndLogEx(INFO, "Called with no save option");
        PrintAndLogEx(NORMAL, "");
        return PM3_SUCCESS;
    }

    // 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);

    pm3_save_dump(filename, tag_data, bytes_got, jsfIclass);

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

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

    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,
        .req.shallow_mod = shallow_mod,
    };
    memcpy(payload.req.key, KEY, 8);
    memcpy(payload.data, bldata, sizeof(payload.data));

    if (replay) {
        memcpy(payload.mac, macdata, sizeof(payload.mac));
    }

    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 --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B\n"
                  "hf iclass wrbl --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B --credit\n"
                  "hf iclass wrbl --blk 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(NULL, "blk", "<dec>", "block number"),
        arg_str1("d", "data", "<hex>", "data to write as 8 hex bytes"),
        arg_str0("m", "mac", "<hex>", "replay mac data (4 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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        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;
    }

    int mac_len = 0;
    uint8_t mac[4] = {0};
    CLIGetHexWithReturn(ctx, 5, mac, &mac_len);

    if (mac_len) {
        if (mac_len != 4) {
            PrintAndLogEx(ERR, "MAC must be 4 hex bytes (8 hex symbols)");
            CLIParserFree(ctx);
            return PM3_EINVARG;
        }
    }


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

    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, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod);
    switch (isok) {
        case PM3_SUCCESS:
            PrintAndLogEx(SUCCESS, "Wrote block " _YELLOW_("%d") " / " _YELLOW_("0x%02X") " ( " _GREEN_("ok") " )", blockno, blockno);
            break;
        case PM3_ETEAROFF:
            if (verbose)
                PrintAndLogEx(INFO, "Writing tear off triggered");
            break;
        default:
            PrintAndLogEx(FAILED, "Writing failed");
            break;
    }
    PrintAndLogEx(NORMAL, "");
    return isok;
}

static int CmdHFiClassCreditEpurse(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass creditepurse",
                  "Credit the epurse on an iCLASS tag. The provided key must be the credit key.\n"
                  "The first two bytes of the epurse are the debit value (big endian) and may be any value except FFFF.\n"
                  "The remaining two bytes of the epurse are the credit value and must be smaller than the previous value.",
                  "hf iclass creditepurse -d FEFFFFFF -k 001122334455667B\n"
                  "hf iclass creditepurse -d FEFFFFFF --ki 0");

    void *argtable[] = {
        arg_param_begin,
        arg_str0("k", "key", "<hex>", "Credit  key as 8 hex bytes"),
        arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
        arg_str1("d", "data", "<hex>", "data to write as 8 hex bytes"),
        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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        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;
    }

    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, "Key or key number must be provided");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    int blockno = 2;

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

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

    bool elite = arg_get_lit(ctx, 4);
    bool rawkey = arg_get_lit(ctx, 5);
    bool verbose = arg_get_lit(ctx, 6);
    bool shallow_mod = arg_get_lit(ctx, 7);

    CLIParserFree(ctx);

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

    iclass_credit_epurse_t payload = {
        .req.use_raw = rawkey,
        .req.use_elite = elite,
        .req.use_credit_key = true,
        .req.use_replay = false,
        .req.blockno = blockno,
        .req.send_reply = true,
        .req.do_auth = true,
        .req.shallow_mod = shallow_mod,
    };
    memcpy(payload.req.key, key, 8);
    memcpy(payload.epurse, data, sizeof(payload.epurse));

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

    int isok;
    if (WaitForResponseTimeout(CMD_HF_ICLASS_CREDIT_EPURSE, &resp, 2000) == 0) {
        if (verbose) PrintAndLogEx(WARNING, "Command execute timeout");
        isok = PM3_ETIMEOUT;
    } else if (resp.status != PM3_SUCCESS) {
        if (verbose) PrintAndLogEx(ERR, "failed to communicate with card");
        isok = resp.status;
    } else {
        isok = (resp.data.asBytes[0] == 1) ? PM3_SUCCESS : PM3_ESOFT;
    }

    switch (isok) {
        case PM3_SUCCESS:
            PrintAndLogEx(SUCCESS, "Credited epurse successfully");
            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 (bin/eml/json) 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", "<fn>", "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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        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);
    bool shallow_mod = arg_get_lit(ctx, 10);

    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;
    }

    // read dump file
    uint8_t *dump = NULL;
    size_t bytes_read = 2048;
    int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
    if (res != PM3_SUCCESS) {
        return res;
    }

    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;
    payload->req.shallow_mod = shallow_mod;
    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, bool shallow_mod, 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,
        .shallow_mod = shallow_mod,
    };
    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 --blk 6 -k 0011223344556677\n"
                  "hf iclass rdbl --blk 27 -k 0011223344556677 --credit\n"
                  "hf iclass rdbl --blk 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(NULL, "blk", "<dec>", "Block number"),
        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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        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);
    bool shallow_mod = 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;
    }

    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, shallow_mod, 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 = IsCardHelperPresent(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[PICOPASS_BLOCK_SIZE];

            uint64_t a = bytes_to_num(data, PICOPASS_BLOCK_SIZE);
            bool starts = (leadingzeros(a) < 12);
            bool ones = (bitcount64(a) > 16 && bitcount64(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");
            }

            bool has_new_pacs = iclass_detect_new_pacs(dec_data);
            bool has_values = (memcmp(dec_data, empty, PICOPASS_BLOCK_SIZE) != 0) && (memcmp(dec_data, zeros, PICOPASS_BLOCK_SIZE) != 0);

            if (has_values) {

                if (has_new_pacs) {
                    iclass_decode_credentials_new_pacs(dec_data);
                } else {
                    //todo:  remove preamble/sentinel
                    uint32_t top = 0, mid = 0, bot = 0;

                    char hexstr[16 + 1] = {0};
                    hex_to_buffer((uint8_t *)hexstr, dec_data, PICOPASS_BLOCK_SIZE, sizeof(hexstr) - 1, 0, 0, true);
                    hexstring_to_u96(&top, &mid, &bot, hexstr);

                    char binstr[64 + 1];
                    hextobinstring(binstr, hexstr);
                    char *pbin = binstr;
                    while (strlen(pbin) && *(++pbin) == '0');

                    PrintAndLogEx(SUCCESS, "      bin : %s", pbin);
                    PrintAndLogEx(INFO, "");
                    PrintAndLogEx(INFO, "------------------------------ " _CYAN_("Wiegand") " -------------------------------");
                    wiegand_message_t packed = initialize_message_object(top, mid, bot, 0);
                    HIDTryUnpack(&packed);
                }
            } 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", "<fn>", "filename with nr/mac data from `hf iclass sim -t 2` "),
        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);
}

static void detect_credential(uint8_t *iclass_dump, size_t dump_len, bool *is_legacy, bool *is_se, bool *is_sr, uint8_t **sio_start_ptr, size_t *sio_length) {
    *is_legacy = false;
    *is_sr = false;
    *is_se = false;
    if (sio_start_ptr != NULL) {
        *sio_start_ptr = NULL;
    }
    if (sio_length != NULL) {
        *sio_length = 0;
    }

    if (dump_len < sizeof(picopass_hdr_t)) {
        // Can't really do anything with a dump that doesn't include the header
        return;
    }

    picopass_hdr_t *hdr = (picopass_hdr_t *)iclass_dump;

    if (!memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
        // Legacy AIA
        *is_legacy = true;

        if (dump_len < 11 * PICOPASS_BLOCK_SIZE) {
            // Can't reliably detect if the card is SR without checking
            // blocks 6 and 10
            return;
        }

        // SR bit set in legacy config block
        if ((iclass_dump[6 * PICOPASS_BLOCK_SIZE] & ICLASS_CFG_BLK_SR_BIT) == ICLASS_CFG_BLK_SR_BIT) {
            // If the card is blank (all FF's) then we'll reach here too, so check for an empty block 10
            // to avoid false positivies
            if (memcmp(iclass_dump + (10 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
                *is_sr = true;
                if (sio_start_ptr != NULL) {
                    // SR SIO starts at block 10
                    *sio_start_ptr = iclass_dump + (10 * PICOPASS_BLOCK_SIZE);
                }
            }
        }
    } else if (!memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\x00\x06\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
        // SE AIA
        *is_se = true;

        if (sio_start_ptr != NULL) {
            // SE SIO starts at block 6
            *sio_start_ptr = iclass_dump + (6 * PICOPASS_BLOCK_SIZE);
        }
    }

    if (sio_length == NULL || sio_start_ptr == NULL || *sio_start_ptr == NULL) {
        // No need to calculate length
        return;
    }

    uint8_t *sio_start = *sio_start_ptr;

    if (sio_start[0] != 0x30) {
        // SIOs always start with a SEQUENCE(P), if this is missing then bail
        return;
    }

    if (sio_start[1] >= 0x80 || sio_start[1] == 0x00) {
        // We only support definite short form lengths
        return;
    }

    // Length of bytes within the SEQUENCE, plus tag and length bytes for the SEQUENCE tag
    *sio_length = sio_start[1] + 2;
}

// print ASN1 decoded array in TLV view
static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len) {
    bool is_legacy, is_se, is_sr;
    uint8_t *sio_start;
    size_t sio_length;
    detect_credential(iclass_dump, dump_len, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length);

    if (sio_start == NULL) {
        return;
    }

    if (dump_len < sio_length + (sio_start - iclass_dump)) {
        // SIO length exceeds the size of the dump we have, bail
        return;
    }

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "---------------------------- " _CYAN_("SIO - RAW") " ----------------------------");
    print_hex_noascii_break(sio_start, sio_length, 32);
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - ASN1 TLV") " --------------------------");
    asn1_print(sio_start, sio_length, "  ");
    PrintAndLogEx(NORMAL, "");
}

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

    picopass_hdr_t *hdr = (picopass_hdr_t *)iclass_dump;
//    picopass_ns_hdr_t *ns_hdr = (picopass_ns_hdr_t *)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;

    uint8_t pagemap = get_pagemap(hdr);

    if (startblock == 0) {
        if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) {
            startblock = 3;
        } else {
            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
    );
    */

    bool is_legacy, is_se, is_sr;
    uint8_t *sio_start;
    size_t sio_length;
    detect_credential(iclass_dump, endblock * 8, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length);

    bool is_legacy_decrypted = is_legacy && (iclass_dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03) == 0x00;

    int sio_start_block = 0, sio_end_block = 0;
    if (sio_start && sio_length > 0) {
        sio_start_block = (sio_start - iclass_dump) / PICOPASS_BLOCK_SIZE;
        sio_end_block = sio_start_block + ((sio_length + PICOPASS_BLOCK_SIZE - 1) / PICOPASS_BLOCK_SIZE) - 1;
    }

    int i = startblock;
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, "--------------------------- " _CYAN_("Tag memory") " ----------------------------");
    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(INFO, " block#  | data                    | ascii    |lck| info");
    PrintAndLogEx(INFO, "---------+-------------------------+----------+---+----------------");
    PrintAndLogEx(INFO, "  0/0x00 | " _GREEN_("%s") "| " _GREEN_("%s") " |   | CSN "
                  , sprint_hex(iclass_dump, 8)
                  , sprint_ascii(iclass_dump, 8)
                 );

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

    bool in_repeated_block = false;
    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") : " ";

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

            regular_print_block = true;
        } else {
            const char *info_ks[] = {"CSN", "Config", "E-purse", "Debit", "Credit", "AIA", "User", "User AA2"};

            if (i >= 6 && i <= 9 && is_legacy) {
                // legacy credential
                PrintAndLogEx(INFO, "%3d/0x%02X | " _YELLOW_("%s") "| " _YELLOW_("%s") " | %s | User / %s "
                              , i
                              , i
                              , sprint_hex(blk, 8)
                              , sprint_ascii(blk, 8)
                              , lockstr
                              , i == 6 ? "HID CFG" : (is_legacy_decrypted ? "Cred" : "Enc Cred")
                             );
            } else if (sio_start_block != 0 && i >= sio_start_block && i <= sio_end_block) {
                // SIO credential
                PrintAndLogEx(INFO, "%3d/0x%02X | " _CYAN_("%s") "| " _CYAN_("%s") " | %s | User / SIO / %s"
                              , i
                              , i
                              , sprint_hex(blk, 8)
                              , sprint_ascii(blk, 8)
                              , lockstr
                              , is_se ? "SE" : "SR"
                             );
            } else {
                if (i < 6) {
                    block_info = info_ks[i];
                } else if (i > hdr->conf.app_limit) {
                    block_info = info_ks[7];
                } else {
                    block_info = info_ks[6];
                }

                regular_print_block = true;
            }
        }

        if (regular_print_block) {
            // suppress repeating blocks, truncate as such that the first and last block with the same data is shown
            // but the blocks in between are replaced with a single line of "......" if dense_output is enabled
            if (dense_output && i > 6 && i < (endblock - 1) && !in_repeated_block && !memcmp(blk, blk - 8, 8) &&
                    !memcmp(blk, blk + 8, 8) && !memcmp(blk, blk + 16, 8)) {
                // we're in a user block that isn't the first user block nor last two user blocks,
                // and the current block data is the same as the previous and next two block
                in_repeated_block = true;
                PrintAndLogEx(INFO, "  ......");
            } else if (in_repeated_block && (memcmp(blk, blk + 8, 8) || i == endblock)) {
                // in a repeating block, but the next block doesn't match anymore, or we're at the end block
                in_repeated_block = false;
            }

            if (in_repeated_block == false) {
                PrintAndLogEx(INFO,
                              "%3d/0x%02X | %s | %s | %s",
                              i,
                              i,
                              sprint_hex_ascii(blk, 8),
                              lockstr,
                              block_info);
            }
        }

        i++;
    }
    PrintAndLogEx(INFO, "---------+-------------------------+----------+---+----------------");
    if (is_legacy)
        PrintAndLogEx(HINT, _YELLOW_("yellow") " = legacy credential");

    if (is_se)
        PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SE credential");

    if (is_sr)
        PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SR credential");

    PrintAndLogEx(NORMAL, "");
}

static int CmdHFiClassView(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass view",
                  "Print a iCLASS tag dump file (bin/eml/json)",
                  "hf iclass view -f hf-iclass-AA162D30F8FF12F1-dump.bin\n"
                  "hf iclass view --first 1 -f hf-iclass-AA162D30F8FF12F1-dump.bin\n\n"
                  "If --first is not specified it will default to the first user block\n"
                  "which is block 6 for secured chips or block 3 for non-secured chips");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<fn>",  "Specify a filename for dump file"),
        arg_int0(NULL, "first", "<dec>", "Begin printing from this block (default first user block)"),
        arg_int0(NULL, "last", "<dec>", "End printing at this block (default 0, ALL)"),
        arg_lit0("v", "verbose", "verbose output"),
        arg_lit0("z", "dense", "dense dump output style"),
        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);
    bool dense_output = g_session.dense_output || arg_get_lit(ctx, 5);

    CLIParserFree(ctx);

    // read dump file
    uint8_t *dump = NULL;
    size_t bytes_read = 2048;
    int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, 2048);
    if (res != PM3_SUCCESS) {
        return res;
    }

    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_t *) dump);
    print_picopass_info((picopass_hdr_t *) dump);
    printIclassDumpContents(dump, startblock, endblock, bytes_read, dense_output);
    iclass_decode_credentials(dump);

    if (verbose) {
        print_iclass_sio(dump, 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_lit0(NULL, "oldelite", "Elite computations applied only to old 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;
    }

    if (arg_get_lit(ctx, 8)) {
        elite = false;
        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) == false) {
            DropField();
            return PM3_ESOFT;
        }
    }

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

    return PM3_SUCCESS;
}

static int iclass_load_keys(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 *  PICOPASS_BLOCK_SIZE) {
        PrintAndLogEx(WARNING, "File is too long to load - bytes: %zu", bytes_read);
        free(dump);
        return PM3_EFILE;
    }
    size_t i = 0;
    for (; i < bytes_read / PICOPASS_BLOCK_SIZE; i++) {
        memcpy(iClass_Key_Table[i], dump + (i * PICOPASS_BLOCK_SIZE), PICOPASS_BLOCK_SIZE);
    }

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

static int iclass_print_keys(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], zeros, sizeof(zeros)) == 0)
            PrintAndLogEx(INFO, " %u |", i);
        else
            PrintAndLogEx(INFO, " %u | " _YELLOW_("%s"), i, sprint_hex(iClass_Key_Table[i], PICOPASS_BLOCK_SIZE));
    }
    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", "<fn>", "Specify a filename for load / 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 iclass_print_keys();
        case 5:
            return iclass_load_keys(filename);
        case 6: {
            bool isOK = saveFile(filename, ".bin", iClass_Key_Table, sizeof(iClass_Key_Table));
            if (isOK == false) {
                return PM3_EFILE;
            }
        }
    }
    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 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_elite_keys.dic --elite");

    void *argtable[] = {
        arg_param_begin,
        arg_str1("f", "file", "<fn>", "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_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        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);
    bool shallow_mod = arg_get_lit(ctx, 5);

    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};

    uint64_t t1 = msclock();

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

    // limit size of keys that can be held in memory
    if (keycount > 100000) {
        PrintAndLogEx(FAILED, "File contains more than 100 000 keys, aborting...");
        free(keyBlock);
        return PM3_EFILE;
    }

    // Get CSN / UID and CCNR
    PrintAndLogEx(SUCCESS, "Reading tag CSN / CCNR...");

    bool got_csn = false;
    for (uint8_t i = 0; i < ICLASS_AUTH_RETRY; i++) {
        got_csn = select_only(CSN, CCNR, false, shallow_mod);
        if (got_csn == false)
            PrintAndLogEx(WARNING, "one more try");
        else
            break;
    }

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

    // allocate memory for the pre calculated macs
    iclass_premac_t *pre = calloc(keycount, sizeof(iclass_premac_t));
    if (pre == NULL) {
        PrintAndLogEx(WARNING, "failed to allocate memory");
        return PM3_EMALLOC;
    }

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

    PrintAndLogEx(INFO, "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");

    // USB_COMMAND.  512/4 = 103 mac
    uint32_t max_chunk_size = 0;
    if (keycount > ((PM3_CMD_DATA_SIZE - sizeof(iclass_chk_t)) / 4))
        max_chunk_size = (PM3_CMD_DATA_SIZE - sizeof(iclass_chk_t)) / 4;
    else
        max_chunk_size = keycount;

    // fast push mode
    g_conn.block_after_ACK = true;

    // keep track of position of found key
    uint32_t chunk_offset = 0;
    uint8_t found_offset = 0;
    bool found_key = false;

    // We have
    //  - a list of keys.
    //  - a list of precalculated macs that corresponds to the key list
    // We send a chunk of macs to the device each time

    // main keychunk loop
    for (chunk_offset = 0; chunk_offset < keycount; chunk_offset += max_chunk_size) {

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

        uint32_t curr_chunk_cnt = keycount - chunk_offset;
        if ((keycount - chunk_offset)  > max_chunk_size) {
            curr_chunk_cnt = max_chunk_size;
        }

        // last chunk?
        if (curr_chunk_cnt == keycount - chunk_offset) {
            // Disable fast mode on last command
            g_conn.block_after_ACK = false;
        }

        uint32_t tmp_plen = sizeof(iclass_chk_t) + (4 * curr_chunk_cnt);
        iclass_chk_t *packet = calloc(tmp_plen,  sizeof(uint8_t));
        if (packet == NULL) {
            PrintAndLogEx(WARNING, "failed to allocate memory");
            break;
        }
        packet->use_credit_key = use_credit_key;
        packet->count = curr_chunk_cnt;
        packet->shallow_mod = shallow_mod;
        // copy chunk of pre calculated macs to packet
        memcpy(packet->items, (pre + chunk_offset), (4 * curr_chunk_cnt));

        clearCommandBuffer();
        SendCommandNG(CMD_HF_ICLASS_CHKKEYS, (uint8_t *)packet, tmp_plen);
        free(packet);

        bool looped = false;
        uint8_t timeout = 0;

        PacketResponseNG resp;
        while (WaitForResponseTimeout(CMD_HF_ICLASS_CHKKEYS, &resp, 2000) == false) {
            timeout++;
            PrintAndLogEx(NORMAL, "." NOLF);
            if (timeout > 10) {
                PrintAndLogEx(WARNING, "\ncommand execute timeout, aborting...");
                goto out;
            }
            looped = true;
        }

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

        if (resp.status == PM3_SUCCESS) {
            found_offset = resp.data.asBytes[0];
            found_key = true;
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(SUCCESS,
                          "Found valid key " _GREEN_("%s")
                          , sprint_hex(keyBlock + (chunk_offset + found_offset) * 8, 8)
                         );
            break;
        } else {
            PrintAndLogEx(INPLACE, "Chunk [%03d/%d]", chunk_offset, keycount);
            fflush(stdout);
        }
    }

out:
    t1 = msclock() - t1;

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

    if (found_key) {
        uint8_t *key = keyBlock + (chunk_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",
                  "This command take sniffed trace data and try to recovery a iCLASS Standard or iCLASS Elite key.",
                  "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", "<fn>", "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, "elite", "Elite computations applied to key"),
        arg_lit0(NULL, "raw", "no 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(INFO, "Generating diversified keys...");
    GenerateMacKeyFrom(csn, CCNR, use_raw, use_elite, keyBlock, keycount, prekey);

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

    PrintAndLogEx(INFO, "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 pthread_mutex_t generator_mutex = PTHREAD_MUTEX_INITIALIZER;
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);

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

        doMAC(cc_nr, div_key, list[i].mac);
        pthread_mutex_unlock(&generator_mutex);
    }
    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) {

    pthread_mutex_init(&generator_mutex, NULL);

    iclass_tc = num_CPUs();
    pthread_t threads[iclass_tc];
    iclass_thread_arg_t args[iclass_tc];
    // init thread arguments
    for (size_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);

        pthread_mutex_lock(&generator_mutex);
        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);
        pthread_mutex_unlock(&generator_mutex);
    }
    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) {

    pthread_mutex_init(&generator_mutex, NULL);
    iclass_tc = num_CPUs();
    pthread_t threads[iclass_tc];
    iclass_thread_arg_t args[iclass_tc];
    // init thread arguments
    for (size_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 (size_t 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);

}

// 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(const 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,8,9\n"
                  "Use either --bin or --wiegand/--fc/--cn",
                  "hf iclass encode --bin 10001111100000001010100011 --ki 0            -> FC 31 CN 337 (H10301)\n"
                  "hf iclass encode -w H10301 --fc 31 --cn 337 --ki 0                  -> FC 31 CN 337 (H10301)\n"
                  "hf iclass encode --bin 10001111100000001010100011 --ki 0 --elite    -> FC 31 CN 337 (H10301), writing w elite key"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0(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_u64_0(NULL, "fc", "<dec>", "facility code"),
        arg_u64_0(NULL, "cn", "<dec>", "card number"),
        arg_str0("w",   "wiegand", "<format>", "see " _YELLOW_("`wiegand list`") " for available formats"),
        arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"),
        arg_lit0("v", NULL, "verbose (print encoded blocks)"),
        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);

    wiegand_card_t card;
    memset(&card, 0, sizeof(wiegand_card_t));
    card.FacilityCode = arg_get_u32_def(ctx, 7, 0);
    card.CardNumber = arg_get_u32_def(ctx, 8, 0);

    char format[16] = {0};
    int format_len = 0;
    CLIParamStrToBuf(arg_get_str(ctx, 9), (uint8_t *)format, sizeof(format), &format_len);

    bool shallow_mod = arg_get_lit(ctx, 10);
    bool verbose = arg_get_lit(ctx, 11);

    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 (bin_len == 0 && card.FacilityCode == 0 && card.CardNumber == 0) {
        PrintAndLogEx(ERR, "Must provide either --cn/--fc or --bin");
        return PM3_EINVARG;
    }

    if (have_enc_key == false) {
        use_sc = IsCardHelperPresent(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");
                free(enckeyptr);
                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_t 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);
        }
    }

    if (bin_len) {
        memcpy(credential + 8, data, sizeof(data));
    } else {
        wiegand_message_t packed;
        memset(&packed, 0, sizeof(wiegand_message_t));

        int format_idx = HIDFindCardFormat(format);
        if (format_idx == -1) {
            PrintAndLogEx(WARNING, "Unknown format: " _YELLOW_("%s"), format);
            return PM3_EINVARG;
        }

        if (HIDPack(format_idx, &card, &packed, false) == false) {
            PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format.");
            return PM3_ESOFT;
        }

        // iceman: only for formats w length smaller than 37.
        // Needs a check.

        // increase length to allow setting bit just above real data
        packed.Length++;
        // Set sentinel bit
        set_bit_by_position(&packed, true, 0);

#ifdef HOST_LITTLE_ENDIAN
        packed.Mid = BSWAP_32(packed.Mid);
        packed.Bot = BSWAP_32(packed.Bot);
#endif

        memcpy(credential + 8, &packed.Mid, sizeof(packed.Mid));
        memcpy(credential + 12, &packed.Bot, sizeof(packed.Bot));
    }

    // 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);
    }

    if (verbose) {
        for (uint8_t i = 0; i < 4; i++) {
            PrintAndLogEx(INFO, "Block %d/0x0%x -> " _YELLOW_("%s"), 6 + i, 6 + i, sprint_hex_inrow(credential + (i * 8), 8));
        }
    }

    if (!g_session.pm3_present) {
        PrintAndLogEx(ERR, "Device offline\n");
        return PM3_EFAILED;
    }

    int isok = PM3_SUCCESS;
    // write
    for (uint8_t i = 0; i < 4; i++) {
        isok = iclass_write_block(6 + i, credential + (i * 8), NULL, key, use_credit_key, elite, rawkey, false, false, auth, shallow_mod);
        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(INFO, "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 int CmdHFiClassConfigCard(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass configcard",
                  "Manage reader configuration card via Cardhelper or internal database,\n"
                  "The generated config card will be uploaded to device emulator memory.\n"
                  "You can start simulating `hf iclass sim -t 3` or use the emul commands",
                  "hf iclass configcard -l           --> download config card settings from cardhelper\n"
                  "hf iclass configcard -p           --> print all config cards in the database\n"
                  "hf iclass configcard --ci 1       --> view config card setting in slot 1\n"
                  "hf iclass configcard -g --ci 0    --> generate config file from slot 0"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_int0(NULL, "ci", "<dec>", "use config slot at index"),
        arg_int0(NULL, "ki", "<dec>", "Key index to select key from memory 'hf iclass managekeys'"),
        arg_lit0("g", NULL, "generate card dump file"),
        arg_lit0("l", NULL, "load available cards"),
        arg_lit0("p", NULL, "print available cards"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int ccidx = arg_get_int_def(ctx, 1, -1);
    int kidx = arg_get_int_def(ctx, 2, -1);
    bool do_generate = arg_get_lit(ctx, 3);
    bool do_load = arg_get_lit(ctx, 4);
    bool do_print = arg_get_lit(ctx, 5);
    CLIParserFree(ctx);

    bool got_kr = false;
    uint8_t key[8] = {0};
    if (kidx >= 0) {
        if (kidx < ICLASS_KEYS_MAX) {
            got_kr = true;
            memcpy(key, iClass_Key_Table[kidx], 8);
            PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), kidx, sprint_hex(iClass_Key_Table[kidx], 8));
        } else {
            PrintAndLogEx(ERR, "--ki number is invalid");
            return PM3_EINVARG;
        }
    }

    if (do_load) {
        if (load_config_cards() != PM3_SUCCESS) {
            PrintAndLogEx(INFO, "failed to load, check your cardhelper");
        }
    }

    if (do_print) {
        print_config_cards();
    }

    if (ccidx > -1 && ccidx < ARRAYLEN(iclass_config_types)) {
        const iclass_config_card_item_t *item = get_config_card_item(ccidx);
        print_config_card(item);
    } else {
        PrintAndLogEx(ERR, "Please specify a valid configuration number!");
    }

    if (do_generate && (ccidx > -1 && ccidx < ARRAYLEN(iclass_config_types))) {
        const iclass_config_card_item_t *item = get_config_card_item(ccidx);
        if (strstr(item->desc, "Keyroll") != NULL) {
            if (got_kr == false) {
                PrintAndLogEx(ERR, "please specify KEYROLL key!");
                return PM3_EINVARG;
            }
        }
        generate_config_card(item, key, got_kr);
    }

    return PM3_SUCCESS;
}

static int CmdHFiClassSAM(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf iclass sam",
                  "Extract PACS via a HID SAM\n",
                  "hf iclass sam\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v", "verbose", "verbose output"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);
    bool verbose = arg_get_lit(ctx, 1);
    CLIParserFree(ctx);

    if (IsHIDSamPresent(verbose) == false) {
        return PM3_ESOFT;
    }

    clearCommandBuffer();
    SendCommandNG(CMD_HF_SAM_PICOPASS, NULL, 0);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_SAM_PICOPASS, &resp, 4000) == false) {
        PrintAndLogEx(WARNING, "SAM timeout");
        return PM3_ETIMEOUT;
    }

    switch (resp.status) {
        case PM3_SUCCESS:
            break;
        case PM3_ENOPACS:
            PrintAndLogEx(SUCCESS, "No PACS data found. Card empty?");
            return resp.status;
        default:
            PrintAndLogEx(WARNING, "SAM select failed");
            return resp.status;
    }

    // CSN, config, epurse, NR/MAC, AIA
    // PACS
    // first byte skip
    // second byte length
    // third padded
    // fourth ..
    uint8_t *d = resp.data.asBytes;
    uint8_t n = d[1] - 1;  // skip length byte
    uint8_t pad = d[2];
    char *binstr = (char *)calloc((n * 8) + 1, sizeof(uint8_t));
    if (binstr == NULL) {
        return PM3_EMALLOC;
    }

    bytes_2_binstr(binstr, d + 3, n);

    PrintAndLogEx(NORMAL, "");
    PrintAndLogEx(SUCCESS, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(d + 2, resp.length - 2));
    PrintAndLogEx(SUCCESS, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));

    binstr[strlen(binstr) - pad] = '\0';
    PrintAndLogEx(SUCCESS, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));

    size_t hexlen = 0;
    uint8_t hex[16] = {0};
    binstr_2_bytes(hex, &hexlen, binstr);
    PrintAndLogEx(SUCCESS, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen));

    uint32_t top = 0, mid = 0, bot = 0;
    if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) {
        PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
        free(binstr);
        return PM3_EINVARG;
    }

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

    PrintAndLogEx(NORMAL, "");

    if (strlen(binstr) >= 26 && verbose) {

        // iCLASS Legacy
        PrintAndLogEx(INFO, "Clone to " _YELLOW_("iCLASS Legacy"));
        PrintAndLogEx(SUCCESS, "    hf iclass encode --ki 0 --bin %s", binstr);
        PrintAndLogEx(NORMAL, "");

        // HID Prox II
        PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("HID Prox II"));
        PrintAndLogEx(SUCCESS, "    lf hid clone -w H10301 --bin %s", binstr);
        PrintAndLogEx(NORMAL, "");

        // MIFARE Classic
        char mfcbin[28] = {0};
        mfcbin[0] = '1';
        memcpy(mfcbin + 1, binstr, strlen(binstr));
        binstr_2_bytes(hex, &hexlen, mfcbin);

        PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic") " (Pm3 simulation)");
        PrintAndLogEx(SUCCESS, "    hf mf eclr;");
        PrintAndLogEx(SUCCESS, "    hf mf esetblk --blk 0 -d 049DBA42A23E80884400C82000000000;");
        PrintAndLogEx(SUCCESS, "    hf mf esetblk --blk 1 -d 1B014D48000000000000000000000000;");
        PrintAndLogEx(SUCCESS, "    hf mf esetblk --blk 3 -d A0A1A2A3A4A5787788C189ECA97F8C2A;");
        PrintAndLogEx(SUCCESS, "    hf mf esetblk --blk 5 -d 020000000000000000000000%s;", sprint_hex_inrow(hex, hexlen));
        PrintAndLogEx(SUCCESS, "    hf mf esetblk --blk 7 -d 484944204953787788AA204752454154;");
        PrintAndLogEx(SUCCESS, "    hf mf sim --1k -i;");
        PrintAndLogEx(NORMAL, "");

        PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic 1K"));
        PrintAndLogEx(SUCCESS, "    hf mf encodehid --bin %s", binstr);
        PrintAndLogEx(NORMAL, "");
    }
    free(binstr);

    return PM3_SUCCESS;
}

static command_t CommandTable[] = {
    {"help",        CmdHelp,                    AlwaysAvailable, "This help"},
    {"list",        CmdHFiClassList,            AlwaysAvailable, "List iclass history"},
//    {"-----------", CmdHelp,                    AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"},
    {"-----------", CmdHelp,                    IfPm3Iclass,     "------------------- " _CYAN_("Operations") " -------------------"},
//    {"clone",       CmdHFiClassClone,           IfPm3Iclass,     "Create a HID credential to Picopass / iCLASS tag"},
    {"dump",        CmdHFiClassDump,            IfPm3Iclass,     "Dump Picopass / iCLASS tag to file"},
    {"info",        CmdHFiClassInfo,            IfPm3Iclass,     "Tag information"},
    {"rdbl",        CmdHFiClass_ReadBlock,      IfPm3Iclass,     "Read Picopass / iCLASS block"},
    {"reader",      CmdHFiClassReader,          IfPm3Iclass,     "Act like a Picopass / iCLASS reader"},
    {"restore",     CmdHFiClassRestore,         IfPm3Iclass,      "Restore a dump file onto a Picopass / iCLASS tag"},
    {"sniff",       CmdHFiClassSniff,           IfPm3Iclass,     "Eavesdrop Picopass / iCLASS communication"},
    {"view",        CmdHFiClassView,            AlwaysAvailable, "Display content from tag dump file"},
    {"wrbl",        CmdHFiClass_WriteBlock,     IfPm3Iclass,     "Write Picopass / iCLASS block"},
    {"creditepurse", CmdHFiClassCreditEpurse,   IfPm3Iclass,     "Credit epurse value"},
    {"-----------", 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,                    IfPm3Iclass,     "-------------------- " _CYAN_("Simulation") " -------------------"},
    {"sim",         CmdHFiClassSim,             IfPm3Iclass,     "Simulate iCLASS tag"},
    {"eload",       CmdHFiClassELoad,           IfPm3Iclass,     "Upload file into emulator memory"},
    {"esave",       CmdHFiClassESave,           IfPm3Iclass,     "Save emulator memory to file"},
    {"esetblk",     CmdHFiClassESetBlk,         IfPm3Iclass,     "Set emulator memory block data"},
    {"eview",       CmdHFiClassEView,           IfPm3Iclass,     "View emulator memory"},
    {"-----------", CmdHelp,                    AlwaysAvailable, "---------------------- " _CYAN_("Utils") " ----------------------"},
    {"configcard",  CmdHFiClassConfigCard,      IfPm3Iclass,     "Reader configuration card"},
    {"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,      AlwaysAvailable, "Permute function from 'heart of darkness' paper"},
    {"-----------", CmdHelp,                    IfPm3Smartcard,  "----------------------- " _CYAN_("SAM") " -----------------------"},
    {"sam",         CmdHFiClassSAM,             IfPm3Smartcard,  "SAM tests"},
    {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   |                       |                                   |
// MFC SIO|                       |                                   |
// DESFIRE|                       |                                   |
//}

int info_iclass(bool shallow_mod) {

    iclass_card_select_t payload = {
        .flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE)
    };

    if (shallow_mod) {
        payload.flags |= FLAG_ICLASS_READER_SHALLOW_MOD;
    }

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

    if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) {
        DropField();
        return PM3_ETIMEOUT;
    }
    DropField();

    iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes;

    // no tag found or button pressed
    if (r->status == FLAG_ICLASS_NULL || resp.status == PM3_ERFTRANS) {
        return PM3_EOPABORTED;
    }

    picopass_hdr_t *hdr = &r->header.hdr;
    picopass_ns_hdr_t *ns_hdr = &r->header.ns_hdr;

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

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

    if ((r->status & FLAG_ICLASS_CONF) == 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 ((r->status & FLAG_ICLASS_CC) == FLAG_ICLASS_CC) {
            PrintAndLogEx(SUCCESS, "E-purse: %s Card challenge, CC", sprint_hex(hdr->epurse, sizeof(hdr->epurse)));
        }

        if (memcmp(hdr->key_d, zeros, sizeof(zeros))) {
            PrintAndLogEx(SUCCESS, "     Kd: " _YELLOW_("%s") " debit key", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
        } else {
            PrintAndLogEx(SUCCESS, "     Kd: %s debit key ( hidden )", sprint_hex(hdr->key_d, sizeof(hdr->key_d)));
        }

        if (memcmp(hdr->key_c, zeros, sizeof(zeros))) {
            PrintAndLogEx(SUCCESS, "     Kc: " _YELLOW_("%s") " credit key", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
        } else {
            PrintAndLogEx(SUCCESS, "     Kc: %s credit key ( hidden )", sprint_hex(hdr->key_c, sizeof(hdr->key_c)));
        }


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

    if ((r->status & FLAG_ICLASS_CONF) == 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 starts with E012FFF (big endian), it's inside HID CSN range.
    bool is_hid_range = (hdr->csn[4] & 0xF0) == 0xF0 && (memcmp(hdr->csn + 5, "\xFF\x12\xE0", 3) == 0);

    if (is_hid_range) {
        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);

        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]);

    return PM3_SUCCESS;
}
