#include "nfc_engine/nfc_deep.h" #include "nfc_engine/nfc_engine.h" #include "pn532_host/pn532_core.h" #include "esp_log.h" #include "esp_timer.h" #include #include static const char *TAG = "nfc_deep"; #define MAX_UL_PAGES 240 #define DEFAULT_KEY_COUNT (sizeof(k_default_keys) / sizeof(k_default_keys[0])) static const uint8_t k_default_keys[][6] = { {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}, {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; static void block_to_hex(const uint8_t blk[NFC_BLOCK_LEN], char *out33) { for (int i = 0; i < NFC_BLOCK_LEN; i++) { snprintf(out33 + i * 2, 3, "%02X", blk[i]); } out33[32] = 0; } static void key_to_hex(const uint8_t k[6], char *out13) { for (int i = 0; i < 6; i++) { snprintf(out13 + i * 2, 3, "%02X", k[i]); } out13[12] = 0; } /** Classic 1K: 16 sectors x 4 blocks. Classic 4K: sectors 0–31 x 4 blocks, sectors 32–39 x 16 blocks. */ static bool mfc_sector_layout(const nfc_tag_info_t *tag, int sector, int *first_block, int *num_blocks, uint8_t *trailer_block) { bool is4k = nfc_tag_is_mifare_classic_4k(tag); if (!is4k) { if (sector < 0 || sector > 15) { return false; } *first_block = sector * 4; *num_blocks = 4; *trailer_block = (uint8_t)(sector * 4 + 3); return true; } if (sector < 0 || sector > 39) { return false; } if (sector <= 31) { *first_block = sector * 4; *num_blocks = 4; *trailer_block = (uint8_t)(sector * 4 + 3); } else { int r = sector - 32; *first_block = 128 + r * 16; *num_blocks = 16; *trailer_block = (uint8_t)(128 + r * 16 + 15); } return true; } static bool try_sector(nfc_tag_info_t *tag, int sector, cJSON *sec_out) { if (!tag || !sec_out) { return false; } int fb = 0; int nb = 0; uint8_t trailer = 0; if (!mfc_sector_layout(tag, sector, &fb, &nb, &trailer)) { return false; } cJSON_AddNumberToObject(sec_out, "sector", sector); cJSON_AddNumberToObject(sec_out, "firstBlock", fb); cJSON_AddNumberToObject(sec_out, "trailerBlock", trailer); cJSON_AddNumberToObject(sec_out, "blockCount", nb); for (size_t ki = 0; ki < DEFAULT_KEY_COUNT; ki++) { nfc_mifare_key_t key; memcpy(key.key, k_default_keys[ki], 6); key.key_b = false; if (nfc_mifare_authenticate_block(tag, trailer, &key) == ESP_OK) { cJSON *blocks = cJSON_CreateArray(); if (!blocks) { return false; } cJSON_AddStringToObject(sec_out, "keyType", "A"); char kh[16]; key_to_hex(key.key, kh); cJSON_AddStringToObject(sec_out, "keyHex", kh); for (int b = 0; b < nb; b++) { uint8_t bn = (uint8_t)(fb + b); uint8_t blk[NFC_BLOCK_LEN]; if (nfc_mifare_read_block(bn, blk) == ESP_OK) { char hx[36]; block_to_hex(blk, hx); cJSON_AddItemToArray(blocks, cJSON_CreateString(hx)); } else { cJSON_AddItemToArray(blocks, cJSON_CreateNull()); } } cJSON_AddItemToObject(sec_out, "blocksHex", blocks); return true; } key.key_b = true; if (nfc_mifare_authenticate_block(tag, trailer, &key) == ESP_OK) { cJSON *blocks = cJSON_CreateArray(); if (!blocks) { return false; } cJSON_AddStringToObject(sec_out, "keyType", "B"); char kh[16]; key_to_hex(key.key, kh); cJSON_AddStringToObject(sec_out, "keyHex", kh); for (int b = 0; b < nb; b++) { uint8_t bn = (uint8_t)(fb + b); uint8_t blk[NFC_BLOCK_LEN]; if (nfc_mifare_read_block(bn, blk) == ESP_OK) { char hx[36]; block_to_hex(blk, hx); cJSON_AddItemToArray(blocks, cJSON_CreateString(hx)); } else { cJSON_AddItemToArray(blocks, cJSON_CreateNull()); } } cJSON_AddItemToObject(sec_out, "blocksHex", blocks); return true; } } cJSON_AddBoolToObject(sec_out, "authFailed", true); return false; } static void add_mifare_classic(nfc_tag_info_t *tag, cJSON *root) { if (!tag || !root) { return; } int sectors = nfc_tag_is_mifare_classic_4k(tag) ? 40 : 16; cJSON *arr = cJSON_CreateArray(); if (!arr) { return; } for (int s = 0; s < sectors; s++) { cJSON *sec = cJSON_CreateObject(); if (!sec) { break; } (void)try_sector(tag, s, sec); cJSON_AddItemToArray(arr, sec); } cJSON_AddItemToObject(root, "mifareClassic", arr); } static void add_ultralight(nfc_tag_info_t *tag, cJSON *root) { if (!root) { return; } (void)tag; cJSON *pages = cJSON_CreateArray(); if (!pages) { return; } uint8_t buf[4]; for (int p = 0; p < MAX_UL_PAGES; p++) { if (nfc_ultralight_read_page((uint8_t)p, buf) != ESP_OK) { break; } char line[12]; snprintf(line, sizeof line, "%02X%02X%02X%02X", buf[0], buf[1], buf[2], buf[3]); cJSON_AddItemToArray(pages, cJSON_CreateString(line)); } cJSON_AddItemToObject(root, "ultralightPagesHex", pages); } cJSON *nfc_tag_deep_profile(nfc_tag_info_t *tag) { if (!tag) { return NULL; } cJSON *o = cJSON_CreateObject(); if (!o) { return NULL; } cJSON_AddNumberToObject(o, "capturedMs", (double)(esp_timer_get_time() / 1000)); cJSON *tag_json = nfc_tag_to_json(tag); if (!tag_json) { cJSON_Delete(o); return NULL; } cJSON_AddItemToObject(o, "tag", tag_json); if (nfc_tag_is_mifare_classic(tag)) { add_mifare_classic(tag, o); } else if (nfc_tag_is_type2(tag)) { add_ultralight(tag, o); } else { cJSON_AddStringToObject( o, "note", "Unknown type from SAK/ATQA — stored inventory + PN532 status only. Use Raw console for ISO14443-4 / other stacks."); } ESP_LOGI(TAG, "deep profile done type_hint=%u", tag->type_hint); return o; }