Files
drjones 3be2d3f936 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
2026-04-02 18:32:06 -07:00

299 lines
8.4 KiB
C

#include "nfc_engine/nfc_engine.h"
#include "pn532_host/pn532_core.h"
#include "esp_log.h"
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
static const char *TAG = "nfc_engine";
static uint8_t s_tg = 1;
static void hint_type(nfc_tag_info_t *t)
{
switch (t->sak) {
case 0x08:
case 0x09:
case 0x18:
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();
if (e != ESP_OK) {
return e;
}
e = pn532_sam_config_normal();
if (e != ESP_OK) {
ESP_LOGW(TAG, "SAM config: %s", esp_err_to_name(e));
}
uint8_t ic = 0, hi = 0, lo = 0;
if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) {
ESP_LOGI(TAG, "PN532 fw ic=0x%02x %u.%u", ic, hi, lo);
}
if (pn532_rf_max_retries() != ESP_OK) {
ESP_LOGW(TAG, "RF max retries config failed");
}
return ESP_OK;
}
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;
esp_err_t e = pn532_in_list_passive_target(1, 0x00, resp, sizeof(resp), &rlen);
if (e != ESP_OK) {
return e;
}
if (rlen < 2) {
return ESP_ERR_INVALID_RESPONSE;
}
if (resp[0] < 1) {
return ESP_ERR_NOT_FOUND;
}
if (rlen < 7) {
return ESP_ERR_INVALID_RESPONSE;
}
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 + 6, out->uid_len);
hint_type(out);
return ESP_OK;
}
static esp_err_t in_data_tg(const uint8_t *data, size_t data_len, uint8_t *response, size_t response_max,
size_t *response_len)
{
uint8_t buf[64];
if (!data || !response || !response_len || data_len == 0 || data_len > sizeof(buf) - 2) {
return ESP_ERR_INVALID_SIZE;
}
buf[0] = PN532_CMD_INDATAEXCHANGE;
buf[1] = s_tg;
memcpy(buf + 2, data, data_len);
return pn532_send_cmd(buf, 2 + data_len, response, response_max, response_len, 500);
}
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;
memcpy(data + 2, key->key, 6);
uint8_t uid_use[4];
if (tag->uid_len == 4) {
memcpy(uid_use, tag->uid, 4);
} else if (tag->uid_len >= 7) {
memcpy(uid_use, tag->uid + tag->uid_len - 4, 4);
} else if (tag->uid_len > 4) {
memcpy(uid_use, tag->uid + tag->uid_len - 4, 4);
} else {
return ESP_ERR_INVALID_ARG;
}
memcpy(data + 8, uid_use, 4);
uint8_t resp[32];
size_t rlen = 0;
esp_err_t e = in_data_tg(data, sizeof(data), resp, sizeof(resp), &rlen);
if (e != ESP_OK) {
return e;
}
if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_FAIL;
}
return ESP_OK;
}
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;
esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen);
if (e != ESP_OK) {
return e;
}
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 + 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;
memcpy(d + 2, block, NFC_BLOCK_LEN);
uint8_t resp[16];
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] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_FAIL;
}
return ESP_OK;
}
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;
esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen);
if (e != ESP_OK) {
return e;
}
if (rlen < 2 + 16 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
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;
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] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_FAIL;
}
return ESP_OK;
}
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 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] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
size_t payload = rlen - 2;
if (payload > out_max) {
payload = out_max;
}
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;
}
char uidhex[NFC_MAX_UID_LEN * 2 + 4];
for (int i = 0; i < tag->uid_len; i++) {
snprintf(uidhex + i * 2, 3, "%02X", tag->uid[i]);
}
uidhex[tag->uid_len * 2] = 0;
cJSON_AddStringToObject(o, "uid", uidhex);
cJSON_AddNumberToObject(o, "uidLen", tag->uid_len);
cJSON_AddNumberToObject(o, "atqa", tag->atqa);
cJSON_AddNumberToObject(o, "sak", tag->sak);
cJSON_AddNumberToObject(o, "typeHint", tag->type_hint);
char aq[8];
snprintf(aq, sizeof aq, "%04X", (unsigned)tag->atqa);
cJSON_AddStringToObject(o, "atqaHex", aq);
char sk[8];
snprintf(sk, sizeof sk, "%02X", tag->sak);
cJSON_AddStringToObject(o, "sakHex", sk);
const char *guess = "Unknown / use Raw or RATS";
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);
uint8_t gs[32];
size_t gl = 0;
if (pn532_get_general_status(gs, sizeof(gs), &gl) == ESP_OK && gl > 0) {
cJSON *arr = cJSON_CreateArray();
if (arr) {
for (size_t i = 0; i < gl; i++) {
cJSON_AddItemToArray(arr, cJSON_CreateNumber(gs[i]));
}
cJSON_AddItemToObject(o, "pn532GeneralStatus", arr);
}
}
return o;
}