Fix PN532 transport and NFC tag handling.

Tighten PN532 framing and NFC access locking so scan, read, write, and raw operations behave reliably on hardware, and correct Classic versus Type 2 tag classification so common cards hit the right code paths. Refresh the embedded web assets and launcher files to match the corrected firmware behavior.

Made-with: Cursor
This commit is contained in:
drjones
2026-04-02 18:32:06 -07:00
parent 8968560565
commit 3be2d3f936
19 changed files with 1162 additions and 287 deletions

View File

@@ -3,6 +3,7 @@
#include "esp_log.h"
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
static const char *TAG = "nfc_engine";
@@ -12,20 +13,34 @@ static void hint_type(nfc_tag_info_t *t)
{
switch (t->sak) {
case 0x08:
case 0x00:
t->type_hint = 2;
break;
case 0x09:
case 0x18:
case 0x19:
t->type_hint = 1;
break;
case 0x00:
t->type_hint = 2;
break;
default:
t->type_hint = 0;
break;
}
}
bool nfc_tag_is_mifare_classic(const nfc_tag_info_t *tag)
{
return tag && tag->type_hint == 1;
}
bool nfc_tag_is_mifare_classic_4k(const nfc_tag_info_t *tag)
{
return nfc_tag_is_mifare_classic(tag) && tag->sak == 0x18;
}
bool nfc_tag_is_type2(const nfc_tag_info_t *tag)
{
return tag && tag->type_hint == 2;
}
esp_err_t nfc_engine_init(void)
{
esp_err_t e = pn532_core_init();
@@ -48,6 +63,9 @@ esp_err_t nfc_engine_init(void)
esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out)
{
if (!out) {
return ESP_ERR_INVALID_ARG;
}
memset(out, 0, sizeof(*out));
uint8_t resp[64];
size_t rlen = 0;
@@ -55,23 +73,23 @@ esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out)
if (e != ESP_OK) {
return e;
}
if (rlen < 2 || resp[0] != 0x00) {
if (rlen < 2) {
return ESP_ERR_INVALID_RESPONSE;
}
if (resp[1] < 1) {
if (resp[0] < 1) {
return ESP_ERR_NOT_FOUND;
}
if (rlen < 8) {
if (rlen < 7) {
return ESP_ERR_INVALID_RESPONSE;
}
s_tg = resp[2];
out->atqa = (uint16_t)(((uint16_t)resp[3] << 8) | resp[4]);
out->sak = resp[5];
out->uid_len = resp[6];
if (out->uid_len > NFC_MAX_UID_LEN || (size_t)(7 + out->uid_len) > rlen) {
s_tg = resp[1];
out->atqa = (uint16_t)(((uint16_t)resp[2] << 8) | resp[3]);
out->sak = resp[4];
out->uid_len = resp[5];
if (out->uid_len > NFC_MAX_UID_LEN || (size_t)(6 + out->uid_len) > rlen) {
return ESP_ERR_INVALID_RESPONSE;
}
memcpy(out->uid, resp + 7, out->uid_len);
memcpy(out->uid, resp + 6, out->uid_len);
hint_type(out);
return ESP_OK;
}
@@ -80,7 +98,7 @@ static esp_err_t in_data_tg(const uint8_t *data, size_t data_len, uint8_t *respo
size_t *response_len)
{
uint8_t buf[64];
if (data_len > sizeof(buf) - 3) {
if (!data || !response || !response_len || data_len == 0 || data_len > sizeof(buf) - 2) {
return ESP_ERR_INVALID_SIZE;
}
buf[0] = PN532_CMD_INDATAEXCHANGE;
@@ -92,6 +110,9 @@ static esp_err_t in_data_tg(const uint8_t *data, size_t data_len, uint8_t *respo
esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block_no,
const nfc_mifare_key_t *key)
{
if (!tag || !key) {
return ESP_ERR_INVALID_ARG;
}
uint8_t data[12];
data[0] = key->key_b ? PN532_MIFARE_CMD_AUTH_B : PN532_MIFARE_CMD_AUTH_A;
data[1] = block_no;
@@ -113,7 +134,7 @@ esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block
if (e != ESP_OK) {
return e;
}
if (rlen < 1 || resp[0] != 0x00) {
if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_FAIL;
}
return ESP_OK;
@@ -121,6 +142,9 @@ esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block
esp_err_t nfc_mifare_read_block(uint8_t block_no, uint8_t block[NFC_BLOCK_LEN])
{
if (!block) {
return ESP_ERR_INVALID_ARG;
}
uint8_t d[] = {PN532_MIFARE_CMD_READ, block_no};
uint8_t resp[32];
size_t rlen = 0;
@@ -128,15 +152,18 @@ esp_err_t nfc_mifare_read_block(uint8_t block_no, uint8_t block[NFC_BLOCK_LEN])
if (e != ESP_OK) {
return e;
}
if (rlen < 1 + NFC_BLOCK_LEN || resp[0] != 0x00) {
if (rlen < 2 + NFC_BLOCK_LEN || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
memcpy(block, resp + 1, NFC_BLOCK_LEN);
memcpy(block, resp + 2, NFC_BLOCK_LEN);
return ESP_OK;
}
esp_err_t nfc_mifare_write_block(uint8_t block_no, const uint8_t block[NFC_BLOCK_LEN])
{
if (!block) {
return ESP_ERR_INVALID_ARG;
}
uint8_t d[2 + NFC_BLOCK_LEN];
d[0] = PN532_MIFARE_CMD_WRITE;
d[1] = block_no;
@@ -147,7 +174,7 @@ esp_err_t nfc_mifare_write_block(uint8_t block_no, const uint8_t block[NFC_BLOCK
if (e != ESP_OK) {
return e;
}
if (rlen < 1 || resp[0] != 0x00) {
if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_FAIL;
}
return ESP_OK;
@@ -155,6 +182,9 @@ esp_err_t nfc_mifare_write_block(uint8_t block_no, const uint8_t block[NFC_BLOCK
esp_err_t nfc_ultralight_read_page(uint8_t page, uint8_t data[4])
{
if (!data) {
return ESP_ERR_INVALID_ARG;
}
uint8_t d[] = {PN532_MIFARE_CMD_READ, page};
uint8_t resp[32];
size_t rlen = 0;
@@ -162,15 +192,18 @@ esp_err_t nfc_ultralight_read_page(uint8_t page, uint8_t data[4])
if (e != ESP_OK) {
return e;
}
if (rlen < 1 + 16 || resp[0] != 0x00) {
if (rlen < 2 + 16 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
memcpy(data, resp + 1, 4);
memcpy(data, resp + 2, 4);
return ESP_OK;
}
esp_err_t nfc_ultralight_write_page(uint8_t page, const uint8_t data[4])
{
if (!data) {
return ESP_ERR_INVALID_ARG;
}
uint8_t d[6] = {0xA2, page, data[0], data[1], data[2], data[3]};
uint8_t resp[16];
size_t rlen = 0;
@@ -178,7 +211,7 @@ esp_err_t nfc_ultralight_write_page(uint8_t page, const uint8_t data[4])
if (e != ESP_OK) {
return e;
}
if (rlen < 1 || resp[0] != 0x00) {
if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_FAIL;
}
return ESP_OK;
@@ -186,30 +219,40 @@ esp_err_t nfc_ultralight_write_page(uint8_t page, const uint8_t data[4])
esp_err_t nfc_ul_fast_read(uint8_t start_page, uint8_t *out, size_t out_max, size_t *got)
{
if (!out || !got || out_max == 0) {
return ESP_ERR_INVALID_ARG;
}
*got = 0;
size_t off = 0;
uint8_t d[2] = {0x3A, start_page};
size_t max_pages = MAX((size_t)1, MIN((size_t)16, out_max / 4));
size_t remaining_pages = 256U - (size_t)start_page;
if (max_pages > remaining_pages) {
max_pages = remaining_pages;
}
uint8_t end_page = (uint8_t)(start_page + max_pages - 1);
uint8_t d[3] = {0x3A, start_page, end_page};
uint8_t resp[256];
size_t rlen = 0;
esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen);
if (e != ESP_OK) {
return e;
}
if (rlen < 2 || resp[0] != 0x00) {
if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
size_t payload = rlen - 1;
size_t payload = rlen - 2;
if (payload > out_max) {
payload = out_max;
}
memcpy(out, resp + 1, payload);
off = payload;
*got = off;
memcpy(out, resp + 2, payload);
*got = payload;
return ESP_OK;
}
cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag)
{
if (!tag || tag->uid_len > NFC_MAX_UID_LEN) {
return NULL;
}
cJSON *o = cJSON_CreateObject();
if (!o) {
return NULL;
@@ -233,9 +276,9 @@ cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag)
cJSON_AddStringToObject(o, "sakHex", sk);
const char *guess = "Unknown / use Raw or RATS";
if (tag->type_hint == 1) {
guess = (tag->sak == 0x19) ? "MIFARE Classic 4K" : "MIFARE Classic 1K or compatible";
} else if (tag->type_hint == 2) {
if (nfc_tag_is_mifare_classic(tag)) {
guess = nfc_tag_is_mifare_classic_4k(tag) ? "MIFARE Classic 4K" : "MIFARE Classic 1K or compatible";
} else if (nfc_tag_is_type2(tag)) {
guess = "Ultralight / NTAG / Type 2 family";
}
cJSON_AddStringToObject(o, "typeGuess", guess);