Includes ESP-IDF NFC stack (deep capture, 4K Classic geometry, UL write API, open SoftAP), React dashboard with live tag diagnostics, and docs. README updated for APIs and lab Wi-Fi defaults. Made-with: Cursor
189 lines
6.1 KiB
C
189 lines
6.1 KiB
C
#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 <stdio.h>
|
||
#include <string.h>
|
||
|
||
static const char *TAG = "nfc_deep";
|
||
|
||
#define MAX_UL_PAGES 240
|
||
|
||
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 = (tag->sak == 0x19);
|
||
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)
|
||
{
|
||
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 < sizeof(k_default_keys) / 6; 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_AddStringToObject(sec_out, "keyType", "A");
|
||
char kh[16];
|
||
key_to_hex(key.key, kh);
|
||
cJSON_AddStringToObject(sec_out, "keyHex", kh);
|
||
cJSON *blocks = cJSON_CreateArray();
|
||
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_AddStringToObject(sec_out, "keyType", "B");
|
||
char kh[16];
|
||
key_to_hex(key.key, kh);
|
||
cJSON_AddStringToObject(sec_out, "keyHex", kh);
|
||
cJSON *blocks = cJSON_CreateArray();
|
||
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)
|
||
{
|
||
int sectors = (tag->sak == 0x19) ? 40 : 16;
|
||
cJSON *arr = cJSON_CreateArray();
|
||
for (int s = 0; s < sectors; s++) {
|
||
cJSON *sec = cJSON_CreateObject();
|
||
(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)
|
||
{
|
||
(void)tag;
|
||
cJSON *pages = cJSON_CreateArray();
|
||
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)
|
||
{
|
||
cJSON *o = cJSON_CreateObject();
|
||
if (!o) {
|
||
return NULL;
|
||
}
|
||
cJSON_AddNumberToObject(o, "capturedMs", (double)(esp_timer_get_time() / 1000));
|
||
cJSON_AddItemToObject(o, "tag", nfc_tag_to_json(tag));
|
||
|
||
uint8_t gs[32];
|
||
size_t gl = 0;
|
||
if (pn532_get_general_status(gs, sizeof(gs), &gl) == ESP_OK && gl > 0) {
|
||
cJSON *g = cJSON_CreateArray();
|
||
for (size_t i = 0; i < gl; i++) {
|
||
cJSON_AddItemToArray(g, cJSON_CreateNumber(gs[i]));
|
||
}
|
||
cJSON_AddItemToObject(o, "pn532GeneralStatus", g);
|
||
}
|
||
|
||
if (tag->type_hint == 1) {
|
||
add_mifare_classic(tag, o);
|
||
} else if (tag->type_hint == 2) {
|
||
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;
|
||
}
|