Files
nfc-pn532-warlord/firmware/components/nfc_engine/nfc_engine.c
drjones 63db10d400 fw: robust HTTP body reads, CORS preflight, LED off at boot; update docs
- app_net.c: replace bare httpd_req_recv with recv_body_capped/alloc
  helpers (TCP-safe, full-body reads); add OPTIONS/* CORS preflight
  handler; bump WS broadcast buffer to 2048; add CORS Allow-Methods
- CMakeLists (net_service): add http_parser dep for HTTP_OPTIONS
- nfc_engine/pn532_core: add nfc_access_lock/unlock mutex, board-RGB
  quiet helper, UL type detection, general-status improvements
- pn532_transport: minor cleanup
- main.c: call board_rgb_led_quiet() at boot to kill onboard LED
- sdkconfig.defaults: add board RGB Kconfig defaults
- README, docs/LIMITATIONS, docs/PINOUT: expand and correct

Made-with: Cursor
2026-04-07 21:23:04 -07:00

362 lines
10 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:
/* Typical Type 2 inventory tuple is ATQA 0x0044 and 7-byte UID. */
if ((t->atqa == 0x4400 || t->atqa == 0x0044) && t->uid_len == 7) {
t->type_hint = 2;
} else {
t->type_hint = 0;
}
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;
}
int nfc_mifare_sector_count(const nfc_tag_info_t *tag)
{
if (!nfc_tag_is_mifare_classic(tag)) {
return 0;
}
return nfc_tag_is_mifare_classic_4k(tag) ? 40 : 16;
}
bool nfc_mifare_sector_layout(const nfc_tag_info_t *tag, int sector, int *first_block, int *num_blocks,
uint8_t *trailer_block)
{
if (!nfc_tag_is_mifare_classic(tag) || !first_block || !num_blocks || !trailer_block) {
return false;
}
if (!nfc_tag_is_mifare_classic_4k(tag)) {
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;
}
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;
}
esp_err_t nfc_type2_get_version(uint8_t version[8])
{
if (!version) {
return ESP_ERR_INVALID_ARG;
}
uint8_t d[] = {0x60};
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 + 8 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
memcpy(version, resp + 2, 8);
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;
}