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

@@ -1,4 +1,5 @@
#include "net_service/app_net.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_http_server.h"
@@ -7,6 +8,7 @@
#include "esp_timer.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "mdns.h"
#include "nfc_engine/nfc_brute.h"
@@ -16,7 +18,6 @@
#include "nvs_flash.h"
#include "pn532_host/pn532_core.h"
#include "cJSON.h"
#include "http_parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -28,13 +29,28 @@ static const char *TAG = "app_net";
/* SoftAP: open network (no password) for lab / fastest join. Change SSID here if you want. */
#define SOFTAP_SSID "PN532-Toolkit"
#define MAX_JSON 4096
#define WS_BROADCAST_BUF 4096
#define STATIC_PATH_MAX 192
static httpd_handle_t s_server;
static bool s_scan = true;
static SemaphoreHandle_t s_nfc_op_mu;
static volatile bool s_scan = true;
static TaskHandle_t s_scan_task;
static void nfc_access_lock(void)
{
if (s_nfc_op_mu) {
xSemaphoreTake(s_nfc_op_mu, portMAX_DELAY);
}
}
static void nfc_access_unlock(void)
{
if (s_nfc_op_mu) {
xSemaphoreGive(s_nfc_op_mu);
}
}
static int hexval(char c)
{
if (c >= '0' && c <= '9') {
@@ -76,6 +92,19 @@ static bool hex_decode_flex(const char *hex, uint8_t *out, size_t out_cap, size_
return hex_to_bin(hex, out, *out_len);
}
static bool copy_json_string(const cJSON *item, char *dst, size_t cap)
{
if (!dst || cap == 0 || !cJSON_IsString(item) || !item->valuestring) {
return false;
}
size_t len = strlen(item->valuestring);
if (len >= cap) {
return false;
}
memcpy(dst, item->valuestring, len + 1);
return true;
}
/** Read full POST body (httpd may return partial data in multiple recv calls). */
static int recv_body_capped(httpd_req_t *req, char *buf, size_t cap)
{
@@ -130,7 +159,6 @@ static char *recv_body_alloc(httpd_req_t *req, size_t max_len, int *out_len)
esp_err_t app_net_broadcast_json(const char *channel, const char *json_text)
{
(void)channel;
if (!s_server || !json_text) {
return ESP_ERR_INVALID_STATE;
}
@@ -157,6 +185,10 @@ esp_err_t app_net_broadcast_json(const char *channel, const char *json_text)
static esp_err_t send_json(httpd_req_t *req, cJSON *j, int status)
{
if (!req || !j) {
cJSON_Delete(j);
return ESP_ERR_INVALID_ARG;
}
char *p = cJSON_PrintUnformatted(j);
cJSON_Delete(j);
if (!p) {
@@ -166,15 +198,65 @@ static esp_err_t send_json(httpd_req_t *req, cJSON *j, int status)
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, OPTIONS");
httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type");
httpd_resp_set_status(req, status == 200 ? "200 OK" : "400 Bad Request");
switch (status) {
case 200:
httpd_resp_set_status(req, "200 OK");
break;
case 400:
httpd_resp_set_status(req, "400 Bad Request");
break;
case 404:
httpd_resp_set_status(req, "404 Not Found");
break;
case 409:
httpd_resp_set_status(req, "409 Conflict");
break;
case 500:
httpd_resp_set_status(req, "500 Internal Server Error");
break;
case 501:
httpd_resp_set_status(req, "501 Not Implemented");
break;
default:
httpd_resp_set_status(req, "200 OK");
break;
}
esp_err_t e = httpd_resp_send(req, p, HTTPD_RESP_USE_STRLEN);
free(p);
return e;
}
static esp_err_t ensure_ultralight_tag_locked(nfc_tag_info_t *tag)
{
if (!tag) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = nfc_poll_passive_target(tag);
if (err != ESP_OK) {
return err;
}
return nfc_tag_is_type2(tag) ? ESP_OK : ESP_ERR_NOT_SUPPORTED;
}
static esp_err_t ensure_mifare_classic_tag_locked(nfc_tag_info_t *tag)
{
if (!tag) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = nfc_poll_passive_target(tag);
if (err != ESP_OK) {
return err;
}
return nfc_tag_is_mifare_classic(tag) ? ESP_OK : ESP_ERR_NOT_SUPPORTED;
}
static esp_err_t api_status(httpd_req_t *req)
{
cJSON *o = cJSON_CreateObject();
if (!o) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "out of memory");
return ESP_ERR_NO_MEM;
}
cJSON_AddStringToObject(o, "app", "pn532_nfc_toolkit");
cJSON_AddNumberToObject(o, "uptimeMs", (double)(esp_timer_get_time() / 1000));
cJSON_AddNumberToObject(o, "freeHeap", (double)esp_get_free_heap_size());
@@ -182,6 +264,7 @@ static esp_err_t api_status(httpd_req_t *req)
esp_wifi_get_mode(&mode);
cJSON_AddNumberToObject(o, "wifiMode", mode);
uint8_t ic = 0, hi = 0, lo = 0;
nfc_access_lock();
if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) {
cJSON *pn = cJSON_CreateObject();
cJSON_AddNumberToObject(pn, "ic", ic);
@@ -189,12 +272,18 @@ static esp_err_t api_status(httpd_req_t *req)
cJSON_AddNumberToObject(pn, "fwLo", lo);
cJSON_AddItemToObject(o, "pn532", pn);
}
nfc_access_unlock();
cJSON_AddBoolToObject(o, "scanning", s_scan);
size_t cap_u = 0;
uint32_t cap_l = 0;
bool cap_f = false;
session_capture_get_status(&cap_u, &cap_l, &cap_f);
cJSON *cap = cJSON_CreateObject();
if (!cap) {
cJSON_Delete(o);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "out of memory");
return ESP_ERR_NO_MEM;
}
cJSON_AddNumberToObject(cap, "usedBytes", (double)cap_u);
cJSON_AddNumberToObject(cap, "maxBytes", (double)session_capture_max());
cJSON_AddNumberToObject(cap, "lines", (double)cap_l);
@@ -226,7 +315,7 @@ static esp_err_t api_session_export(httpd_req_t *req)
}
char *buf = malloc(n);
if (!buf) {
return send_json(req, cJSON_CreateString("out of memory"), 400);
return send_json(req, cJSON_CreateString("out of memory"), 500);
}
size_t got = 0;
session_capture_copy_to(buf, n, &got);
@@ -255,6 +344,11 @@ static esp_err_t api_session_deep(httpd_req_t *req)
if (r < 0) {
return send_json(req, cJSON_CreateString("body required or too large"), 400);
}
if (r == 0) {
cJSON *o = cJSON_CreateObject();
cJSON_AddBoolToObject(o, "deepCapture", session_capture_deep_enabled());
return send_json(req, o, 200);
}
cJSON *j = cJSON_Parse(buf);
if (!j) {
return send_json(req, cJSON_CreateString("bad json"), 400);
@@ -275,20 +369,28 @@ static esp_err_t api_nfc_poll(httpd_req_t *req)
(void)recv_body_capped(req, drain, sizeof(drain));
nfc_tag_info_t tag;
nfc_access_lock();
esp_err_t e = nfc_poll_passive_target(&tag);
if (e == ESP_ERR_NOT_FOUND) {
nfc_access_unlock();
cJSON *o = cJSON_CreateObject();
cJSON_AddBoolToObject(o, "present", false);
return send_json(req, o, 200);
}
if (e != ESP_OK) {
nfc_access_unlock();
cJSON *o = cJSON_CreateObject();
cJSON_AddStringToObject(o, "error", esp_err_to_name(e));
return send_json(req, o, 400);
}
cJSON *tj = nfc_tag_to_json(&tag);
nfc_access_unlock();
if (!tj) {
return send_json(req, cJSON_CreateString("out of memory building tag"), 500);
}
cJSON *o = cJSON_CreateObject();
cJSON_AddBoolToObject(o, "present", true);
cJSON_AddItemToObject(o, "tag", nfc_tag_to_json(&tag));
cJSON_AddItemToObject(o, "tag", tj);
return send_json(req, o, 200);
}
@@ -299,6 +401,11 @@ static esp_err_t api_scan(httpd_req_t *req)
if (r < 0) {
return send_json(req, cJSON_CreateString("no body or too large"), 400);
}
if (r == 0) {
cJSON *o = cJSON_CreateObject();
cJSON_AddBoolToObject(o, "enable", s_scan);
return send_json(req, o, 200);
}
cJSON *j = cJSON_Parse(buf);
if (!j) {
return send_json(req, cJSON_CreateString("bad json"), 400);
@@ -317,7 +424,9 @@ static esp_err_t api_general_status(httpd_req_t *req)
{
uint8_t gs[32];
size_t gl = 0;
nfc_access_lock();
esp_err_t e = pn532_get_general_status(gs, sizeof(gs), &gl);
nfc_access_unlock();
if (e != ESP_OK) {
cJSON *o = cJSON_CreateObject();
cJSON_AddStringToObject(o, "error", esp_err_to_name(e));
@@ -325,6 +434,10 @@ static esp_err_t api_general_status(httpd_req_t *req)
}
cJSON *o = cJSON_CreateObject();
cJSON *arr = cJSON_CreateArray();
if (!arr) {
cJSON_Delete(o);
return send_json(req, cJSON_CreateString("out of memory"), 500);
}
for (size_t i = 0; i < gl; i++) {
cJSON_AddItemToArray(arr, cJSON_CreateNumber(gs[i]));
}
@@ -335,6 +448,7 @@ static esp_err_t api_general_status(httpd_req_t *req)
static esp_err_t api_mifare_read(httpd_req_t *req)
{
char buf[512];
char key_hex[13];
int r = recv_body_capped(req, buf, sizeof(buf));
if (r < 0) {
return send_json(req, cJSON_CreateString("no body or too large"), 400);
@@ -346,30 +460,40 @@ static esp_err_t api_mifare_read(httpd_req_t *req)
cJSON *jbk = cJSON_GetObjectItem(j, "block");
cJSON *jk = cJSON_GetObjectItem(j, "key");
int block = cJSON_IsNumber(jbk) ? (int)cJSON_GetNumberValue(jbk) : -1;
const char *key_hex = cJSON_IsString(jk) ? jk->valuestring : NULL;
cJSON *jb = cJSON_GetObjectItem(j, "keyB");
bool key_b = cJSON_IsTrue(jb);
bool have_key = copy_json_string(jk, key_hex, sizeof(key_hex));
cJSON_Delete(j);
if (block < 0 || !key_hex) {
return send_json(req, cJSON_CreateString("block/key required"), 400);
if (block < 0 || block > 255 || !have_key) {
return send_json(req, cJSON_CreateString("block/key required (block 0-255)"), 400);
}
uint8_t keyb[6];
if (!hex_to_bin(key_hex, keyb, 6)) {
return send_json(req, cJSON_CreateString("key must be 12 hex chars"), 400);
}
nfc_tag_info_t tag;
if (nfc_poll_passive_target(&tag) != ESP_OK) {
nfc_access_lock();
esp_err_t err = ensure_mifare_classic_tag_locked(&tag);
if (err == ESP_ERR_NOT_SUPPORTED) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("tag is not MIFARE Classic"), 400);
}
if (err != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("no tag"), 400);
}
nfc_mifare_key_t k = {.key_b = key_b};
memcpy(k.key, keyb, 6);
if (nfc_mifare_authenticate_block(&tag, (uint8_t)block, &k) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("auth failed"), 400);
}
uint8_t blk[NFC_BLOCK_LEN];
if (nfc_mifare_read_block((uint8_t)block, blk) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("read failed"), 400);
}
nfc_access_unlock();
char hexout[NFC_BLOCK_LEN * 2 + 1];
for (int i = 0; i < NFC_BLOCK_LEN; i++) {
snprintf(hexout + i * 2, 3, "%02X", blk[i]);
@@ -383,6 +507,8 @@ static esp_err_t api_mifare_read(httpd_req_t *req)
static esp_err_t api_mifare_write(httpd_req_t *req)
{
char buf[512];
char key_hex[13];
char data_hex[33];
int r = recv_body_capped(req, buf, sizeof(buf));
if (r < 0) {
return send_json(req, cJSON_CreateString("no body or too large"), 400);
@@ -395,30 +521,40 @@ static esp_err_t api_mifare_write(httpd_req_t *req)
cJSON *jk = cJSON_GetObjectItem(j, "key");
cJSON *jd = cJSON_GetObjectItem(j, "data");
int block = cJSON_IsNumber(jbk) ? (int)cJSON_GetNumberValue(jbk) : -1;
const char *key_hex = cJSON_IsString(jk) ? jk->valuestring : NULL;
const char *data_hex = cJSON_IsString(jd) ? jd->valuestring : NULL;
cJSON *jb = cJSON_GetObjectItem(j, "keyB");
bool key_b = cJSON_IsTrue(jb);
bool have_key = copy_json_string(jk, key_hex, sizeof(key_hex));
bool have_data = copy_json_string(jd, data_hex, sizeof(data_hex));
cJSON_Delete(j);
if (block < 0 || !key_hex || !data_hex) {
return send_json(req, cJSON_CreateString("block/key/data required"), 400);
if (block < 0 || block > 255 || !have_key || !have_data) {
return send_json(req, cJSON_CreateString("block/key/data required (block 0-255)"), 400);
}
uint8_t keyb[6], blk[NFC_BLOCK_LEN];
if (!hex_to_bin(key_hex, keyb, 6) || !hex_to_bin(data_hex, blk, NFC_BLOCK_LEN)) {
return send_json(req, cJSON_CreateString("bad hex"), 400);
}
nfc_tag_info_t tag;
if (nfc_poll_passive_target(&tag) != ESP_OK) {
nfc_access_lock();
esp_err_t err = ensure_mifare_classic_tag_locked(&tag);
if (err == ESP_ERR_NOT_SUPPORTED) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("tag is not MIFARE Classic"), 400);
}
if (err != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("no tag"), 400);
}
nfc_mifare_key_t k = {.key_b = key_b};
memcpy(k.key, keyb, 6);
if (nfc_mifare_authenticate_block(&tag, (uint8_t)block, &k) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("auth failed"), 400);
}
if (nfc_mifare_write_block((uint8_t)block, blk) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("write failed"), 400);
}
nfc_access_unlock();
return send_json(req, cJSON_CreateObject(), 200);
}
@@ -436,13 +572,26 @@ static esp_err_t api_ul_read(httpd_req_t *req)
cJSON *jp = cJSON_GetObjectItem(j, "page");
int page = cJSON_IsNumber(jp) ? (int)cJSON_GetNumberValue(jp) : -1;
cJSON_Delete(j);
if (page < 0) {
return send_json(req, cJSON_CreateString("page required"), 400);
if (page < 0 || page > 255) {
return send_json(req, cJSON_CreateString("page required (0-255)"), 400);
}
uint8_t d[4];
nfc_tag_info_t tag;
nfc_access_lock();
esp_err_t e = ensure_ultralight_tag_locked(&tag);
if (e == ESP_ERR_NOT_SUPPORTED) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("tag is not Ultralight/NTAG"), 400);
}
if (e != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("no tag"), 400);
}
if (nfc_ultralight_read_page((uint8_t)page, d) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("read failed"), 400);
}
nfc_access_unlock();
char hx[9];
snprintf(hx, sizeof hx, "%02X%02X%02X%02X", d[0], d[1], d[2], d[3]);
cJSON *o = cJSON_CreateObject();
@@ -454,6 +603,7 @@ static esp_err_t api_ul_read(httpd_req_t *req)
static esp_err_t api_ul_write(httpd_req_t *req)
{
char buf[128];
char data_hex[9];
int r = recv_body_capped(req, buf, sizeof(buf));
if (r < 0) {
return send_json(req, cJSON_CreateString("no body or too large"), 400);
@@ -465,18 +615,31 @@ static esp_err_t api_ul_write(httpd_req_t *req)
cJSON *jp = cJSON_GetObjectItem(j, "page");
cJSON *jd = cJSON_GetObjectItem(j, "data");
int page = cJSON_IsNumber(jp) ? (int)cJSON_GetNumberValue(jp) : -1;
const char *data_hex = cJSON_IsString(jd) ? jd->valuestring : NULL;
bool have_data = copy_json_string(jd, data_hex, sizeof(data_hex));
cJSON_Delete(j);
if (page < 0 || !data_hex) {
return send_json(req, cJSON_CreateString("page and data (8 hex) required"), 400);
if (page < 0 || page > 255 || !have_data) {
return send_json(req, cJSON_CreateString("page and data (8 hex) required (page 0-255)"), 400);
}
uint8_t d[4];
if (!hex_to_bin(data_hex, d, 4)) {
return send_json(req, cJSON_CreateString("data must be 8 hex chars (4 bytes)"), 400);
}
nfc_tag_info_t tag;
nfc_access_lock();
esp_err_t e = ensure_ultralight_tag_locked(&tag);
if (e == ESP_ERR_NOT_SUPPORTED) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("tag is not Ultralight/NTAG"), 400);
}
if (e != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("no tag"), 400);
}
if (nfc_ultralight_write_page((uint8_t)page, d) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("write failed"), 400);
}
nfc_access_unlock();
cJSON *o = cJSON_CreateObject();
cJSON_AddNumberToObject(o, "page", page);
cJSON_AddBoolToObject(o, "ok", true);
@@ -485,19 +648,8 @@ static esp_err_t api_ul_write(httpd_req_t *req)
static esp_err_t api_ota_stub(httpd_req_t *req)
{
(void)req;
cJSON *o = cJSON_CreateString("Use idf.py app-flash or extend with esp_https_ota + bundle URL");
char *p = cJSON_PrintUnformatted(o);
cJSON_Delete(o);
if (!p) {
return ESP_ERR_NO_MEM;
}
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_status(req, "501 Not Implemented");
esp_err_t e = httpd_resp_send(req, p, HTTPD_RESP_USE_STRLEN);
free(p);
return e;
return send_json(req, o, 501);
}
static esp_err_t api_mifare_dictionary_attack(httpd_req_t *req)
@@ -544,30 +696,23 @@ static esp_err_t api_mifare_dictionary_attack(httpd_req_t *req)
cJSON_Delete(j);
nfc_tag_info_t tag;
nfc_access_lock();
if (nfc_poll_passive_target(&tag) != ESP_OK) {
nfc_access_unlock();
return send_json(req, cJSON_CreateString("no tag present"), 400);
}
int attempts = 0;
cJSON *out = nfc_mifare_dictionary_attack(&tag, s0, s1, extra_n ? extra : NULL, extra_n, variations, &attempts);
cJSON *out = nfc_mifare_dictionary_attack(&tag, s0, s1, extra_n ? extra : NULL, extra_n, variations, NULL);
nfc_access_unlock();
if (!out) {
return send_json(req, cJSON_CreateString("attack failed"), 400);
}
char *p = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
if (!p) {
return send_json(req, cJSON_CreateString("print failed"), 400);
}
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_status(req, "200 OK");
esp_err_t e = httpd_resp_send(req, p, HTTPD_RESP_USE_STRLEN);
free(p);
return e;
return send_json(req, out, 200);
}
static esp_err_t api_nfc_emulate_raw(httpd_req_t *req)
{
char buf[1024];
char hex[521];
int r = recv_body_capped(req, buf, sizeof(buf));
if (r < 0) {
return send_json(req, cJSON_CreateString("no body or too large"), 400);
@@ -577,22 +722,27 @@ static esp_err_t api_nfc_emulate_raw(httpd_req_t *req)
return send_json(req, cJSON_CreateString("bad json"), 400);
}
cJSON *jh = cJSON_GetObjectItem(j, "hex");
const char *hex = cJSON_IsString(jh) ? jh->valuestring : NULL;
bool have_hex = copy_json_string(jh, hex, sizeof(hex));
cJSON_Delete(j);
uint8_t bin[260];
size_t blen = 0;
if (!hex || !hex_decode_flex(hex, bin, sizeof(bin), &blen)) {
if (!have_hex || !hex_decode_flex(hex, bin, sizeof(bin), &blen)) {
return send_json(req, cJSON_CreateString("hex command required"), 400);
}
uint8_t resp[300];
size_t rlen = 0;
nfc_access_lock();
esp_err_t err = pn532_send_cmd(bin, blen, resp, sizeof(resp), &rlen, 800);
nfc_access_unlock();
if (err != ESP_OK) {
cJSON *o = cJSON_CreateObject();
cJSON_AddStringToObject(o, "error", esp_err_to_name(err));
return send_json(req, o, 400);
}
char *rh = calloc(1, rlen * 2 + 1);
if (!rh) {
return send_json(req, cJSON_CreateString("out of memory"), 500);
}
for (size_t i = 0; i < rlen; i++) {
snprintf(rh + i * 2, 3, "%02X", resp[i]);
}
@@ -605,6 +755,7 @@ static esp_err_t api_nfc_emulate_raw(httpd_req_t *req)
static esp_err_t api_raw_pn532(httpd_req_t *req)
{
char buf[1024];
char hex[521];
int r = recv_body_capped(req, buf, sizeof(buf));
if (r < 0) {
return send_json(req, cJSON_CreateString("no body or too large"), 400);
@@ -614,22 +765,27 @@ static esp_err_t api_raw_pn532(httpd_req_t *req)
return send_json(req, cJSON_CreateString("bad json"), 400);
}
cJSON *jf = cJSON_GetObjectItem(j, "frame");
const char *hex = cJSON_IsString(jf) ? jf->valuestring : NULL;
bool have_hex = copy_json_string(jf, hex, sizeof(hex));
cJSON_Delete(j);
size_t blen = 0;
uint8_t bin[260];
if (!hex || !hex_decode_flex(hex, bin, sizeof(bin), &blen)) {
if (!have_hex || !hex_decode_flex(hex, bin, sizeof(bin), &blen)) {
return send_json(req, cJSON_CreateString("frame hex required, even length, max 260B"), 400);
}
uint8_t resp[300];
size_t rlen = 0;
nfc_access_lock();
esp_err_t e = pn532_send_cmd(bin, blen, resp, sizeof(resp), &rlen, 300);
nfc_access_unlock();
if (e != ESP_OK) {
cJSON *o = cJSON_CreateObject();
cJSON_AddStringToObject(o, "error", esp_err_to_name(e));
return send_json(req, o, 400);
}
char *rh = calloc(1, rlen * 2 + 1);
if (!rh) {
return send_json(req, cJSON_CreateString("out of memory"), 500);
}
for (size_t i = 0; i < rlen; i++) {
snprintf(rh + i * 2, 3, "%02X", resp[i]);
}
@@ -652,6 +808,7 @@ static esp_err_t api_cors_preflight(httpd_req_t *req)
static esp_err_t static_any(httpd_req_t *req)
{
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
if (strcmp(req->uri, "/") == 0) {
httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
FILE *f = fopen("/spiffs/index.html", "r");
@@ -662,7 +819,6 @@ static esp_err_t static_any(httpd_req_t *req)
char buf[512];
size_t n;
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
if (httpd_resp_send_chunk(req, buf, n) != ESP_OK) {
fclose(f);
@@ -676,8 +832,12 @@ httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "bad path");
return ESP_FAIL;
}
char path[96];
snprintf(path, sizeof path, "/spiffs%s", req->uri);
char path[STATIC_PATH_MAX];
int path_len = snprintf(path, sizeof path, "/spiffs%s", req->uri);
if (path_len < 0 || path_len >= (int)sizeof(path)) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "path too long");
return ESP_FAIL;
}
struct stat st;
if (stat(path, &st) != 0) {
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "not found");
@@ -690,10 +850,10 @@ httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
}
if (strstr(req->uri, ".js")) {
httpd_resp_set_type(req, "application/javascript");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
} else if (strstr(req->uri, ".css")) {
httpd_resp_set_type(req, "text/css");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
} else if (strstr(req->uri, ".html")) {
httpd_resp_set_type(req, "text/html");
}
char buf[512];
size_t n;
@@ -720,7 +880,7 @@ static esp_err_t ws_handler(httpd_req_t *req)
if (ret != ESP_OK) {
return ret;
}
if (ws.type == HTTPD_WS_TYPE_TEXT && ws.len < sizeof(buf)) {
if (ws.type == HTTPD_WS_TYPE_TEXT && ws.len + 1 < sizeof(buf)) {
buf[ws.len] = 0;
if (strcmp((char *)buf, "ping") == 0) {
ws.type = HTTPD_WS_TYPE_TEXT;
@@ -747,20 +907,24 @@ static void scan_loop_task(void *arg)
continue;
}
nfc_tag_info_t tag;
nfc_access_lock();
esp_err_t e = nfc_poll_passive_target(&tag);
if (e == ESP_OK) {
bool is_new = (last.uid_len != tag.uid_len || memcmp(last.uid, tag.uid, tag.uid_len) != 0);
if (is_new) {
last = tag;
cJSON *j = nfc_tag_to_json(&tag);
char *raw = cJSON_PrintUnformatted(j);
cJSON_Delete(j);
if (raw) {
app_net_broadcast_json("scan", raw);
free(raw);
if (j) {
char *raw = cJSON_PrintUnformatted(j);
cJSON_Delete(j);
if (raw) {
app_net_broadcast_json("scan", raw);
free(raw);
}
}
if (session_capture_deep_enabled() && !session_capture_is_full()) {
cJSON *deep = nfc_tag_deep_profile(&tag);
nfc_access_unlock();
char *line = deep ? cJSON_PrintUnformatted(deep) : NULL;
cJSON_Delete(deep);
if (line) {
@@ -796,9 +960,14 @@ static void scan_loop_task(void *arg)
}
free(line);
}
} else {
nfc_access_unlock();
}
} else {
nfc_access_unlock();
}
} else {
nfc_access_unlock();
if (last.uid_len) {
memset(&last, 0, sizeof(last));
app_net_broadcast_json("scan", "{\"present\":false}");
@@ -812,6 +981,24 @@ void app_net_set_continuous_scan(bool on) { s_scan = on; }
bool app_net_continuous_scan(void) { return s_scan; }
static bool register_uri_checked(httpd_handle_t server, const httpd_uri_t *uri)
{
if (!server || !uri) {
return false;
}
esp_err_t err = httpd_register_uri_handler(server, uri);
if (err != ESP_OK) {
ESP_LOGE(TAG, "register %s %s failed: %s",
uri->method == HTTP_GET ? "GET"
: uri->method == HTTP_POST ? "POST"
: uri->method == HTTP_OPTIONS ? "OPTIONS"
: "?",
uri->uri ? uri->uri : "(null)", esp_err_to_name(err));
return false;
}
return true;
}
static httpd_handle_t start_server(void)
{
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
@@ -824,66 +1011,155 @@ static httpd_handle_t start_server(void)
}
httpd_uri_t u = {.uri = "/*", .method = HTTP_OPTIONS, .handler = api_cors_preflight};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/status", .method = HTTP_GET, .handler = api_status};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/nfc/poll", .method = HTTP_POST, .handler = api_nfc_poll};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/nfc/scan", .method = HTTP_POST, .handler = api_scan};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/pn532/general-status", .method = HTTP_GET, .handler = api_general_status};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/mifare/read-block", .method = HTTP_POST, .handler = api_mifare_read};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/mifare/write-block", .method = HTTP_POST, .handler = api_mifare_write};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/ul/read-page", .method = HTTP_POST, .handler = api_ul_read};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/ul/write-page", .method = HTTP_POST, .handler = api_ul_write};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/raw/pn532", .method = HTTP_POST, .handler = api_raw_pn532};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/ota", .method = HTTP_POST, .handler = api_ota_stub};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/session/export", .method = HTTP_GET, .handler = api_session_export};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/session/clear", .method = HTTP_POST, .handler = api_session_clear};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/session/deep", .method = HTTP_POST, .handler = api_session_deep};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/mifare/dictionary-attack", .method = HTTP_POST,
.handler = api_mifare_dictionary_attack};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/api/nfc/emulate-raw", .method = HTTP_POST, .handler = api_nfc_emulate_raw};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/ws", .method = HTTP_GET, .handler = ws_handler, .is_websocket = true};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
u = (httpd_uri_t){.uri = "/*", .method = HTTP_GET, .handler = static_any};
httpd_register_uri_handler(s, &u);
if (!register_uri_checked(s, &u)) {
httpd_stop(s);
return NULL;
}
return s;
}
esp_err_t app_net_init(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
err = nvs_flash_erase();
if (err != ESP_OK) {
return err;
}
err = nvs_flash_init();
}
if (err != ESP_OK) {
return err;
}
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_ap();
err = esp_netif_init();
if (err != ESP_OK) {
return err;
}
err = esp_event_loop_create_default();
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
return err;
}
esp_netif_t *ap_if = esp_netif_create_default_wifi_ap();
if (!ap_if) {
return ESP_FAIL;
}
s_nfc_op_mu = xSemaphoreCreateMutex();
if (!s_nfc_op_mu) {
return ESP_ERR_NO_MEM;
}
wifi_init_config_t wcfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wcfg));
err = esp_wifi_init(&wcfg);
if (err != ESP_OK) {
return err;
}
wifi_config_t ap = {0};
strncpy((char *)ap.ap.ssid, SOFTAP_SSID, sizeof(ap.ap.ssid));
ap.ap.ssid_len = (uint8_t)strlen(SOFTAP_SSID);
ap.ap.channel = 6;
ap.ap.max_connection = 8;
ap.ap.authmode = WIFI_AUTH_OPEN;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap));
ESP_ERROR_CHECK(esp_wifi_start());
err = esp_wifi_set_mode(WIFI_MODE_AP);
if (err != ESP_OK) {
return err;
}
err = esp_wifi_set_config(WIFI_IF_AP, &ap);
if (err != ESP_OK) {
return err;
}
err = esp_wifi_start();
if (err != ESP_OK) {
return err;
}
esp_vfs_spiffs_conf_t sp = {
.base_path = "/spiffs",
@@ -891,18 +1167,27 @@ esp_err_t app_net_init(void)
.max_files = 16,
.format_if_mount_failed = true,
};
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&sp));
err = esp_vfs_spiffs_register(&sp);
if (err != ESP_OK) {
return err;
}
mdns_init();
mdns_hostname_set("pn532tool");
mdns_instance_name_set("PN532 NFC Toolkit");
mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
err = mdns_init();
if (err == ESP_OK) {
(void)mdns_hostname_set("pn532tool");
(void)mdns_instance_name_set("PN532 NFC Toolkit");
(void)mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
} else {
ESP_LOGW(TAG, "mDNS init failed: %s", esp_err_to_name(err));
}
s_server = start_server();
if (!s_server) {
return ESP_FAIL;
}
xTaskCreate(scan_loop_task, "nfc_scan", 20480, NULL, 5, &s_scan_task);
if (xTaskCreate(scan_loop_task, "nfc_scan", 20480, NULL, 5, &s_scan_task) != pdPASS) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}

View File

@@ -29,6 +29,9 @@ typedef struct {
esp_err_t nfc_engine_init(void);
esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out);
bool nfc_tag_is_mifare_classic(const nfc_tag_info_t *tag);
bool nfc_tag_is_mifare_classic_4k(const nfc_tag_info_t *tag);
bool nfc_tag_is_type2(const nfc_tag_info_t *tag);
esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block_no,
const nfc_mifare_key_t *key);

View File

@@ -25,7 +25,6 @@ bool session_capture_append_line(const char *line);
/** Export raw bytes (entire buffer) for HTTP download. */
size_t session_capture_export_size(void);
const char *session_capture_export_ptr(void);
/** Copy up to `cap` bytes (typically cap >= session_capture_export_size()). */
void session_capture_copy_to(char *dst, size_t cap, size_t *out_len);

View File

@@ -36,6 +36,27 @@ static const uint8_t k_builtin[][6] = {
#define MAX_VARIANTS_PER_KEY 14
#define MAX_TRIES_BEFORE_WDT 48
static bool add_sector_hit(cJSON *hits, uint8_t sector, const uint8_t key[6], const char *key_type)
{
if (!hits || !key || !key_type) {
return false;
}
char hx[16];
for (int i = 0; i < 6; i++) {
snprintf(hx + i * 2, 3, "%02X", key[i]);
}
hx[12] = 0;
cJSON *h = cJSON_CreateObject();
if (!h) {
return false;
}
cJSON_AddNumberToObject(h, "sector", sector);
cJSON_AddStringToObject(h, "keyHex", hx);
cJSON_AddStringToObject(h, "keyType", key_type);
cJSON_AddItemToArray(hits, h);
return true;
}
static int push_variant(const uint8_t base[6], int idx, uint8_t out[6])
{
memcpy(out, base, 6);
@@ -76,7 +97,7 @@ static bool try_key_on_trailer(nfc_tag_info_t *tag, uint8_t trailer, const uint8
/** Trailer block for MIFARE Classic sector index (0..15 for 1K, 0..39 for 4K). */
static uint8_t classic_trailer_for_sector(const nfc_tag_info_t *tag, uint8_t sec)
{
if (tag->sak == 0x19) {
if (nfc_tag_is_mifare_classic_4k(tag)) {
if (sec <= 31) {
return (uint8_t)(sec * 4 + 3);
}
@@ -102,7 +123,13 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
return NULL;
}
if (tag->type_hint != 1) {
if (!tag) {
cJSON_Delete(root);
cJSON_Delete(hits);
return NULL;
}
if (!nfc_tag_is_mifare_classic(tag)) {
cJSON_AddStringToObject(root, "error", "not_classic_sak_hint");
cJSON_AddItemToObject(root, "sectorHits", hits);
if (attempts_out) {
@@ -111,13 +138,22 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
return root;
}
uint8_t sector_max = nfc_tag_is_mifare_classic_4k(tag) ? 39 : 15;
if (sector_first > sector_last) {
uint8_t t = sector_first;
sector_first = sector_last;
sector_last = t;
}
if (sector_first > sector_max) {
sector_first = sector_max;
}
if (sector_last > sector_max) {
sector_last = sector_max;
}
for (uint8_t sec = sector_first; sec <= sector_last; sec++) {
for (int sec_i = sector_first; sec_i <= sector_last; sec_i++) {
uint8_t sec = (uint8_t)sec_i;
uint8_t trailer = classic_trailer_for_sector(tag, sec);
if (trailer == 0xFF) {
continue;
@@ -133,30 +169,18 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
}
attempts++;
if (try_key_on_trailer(tag, trailer, trial, false)) {
char hx[16];
for (int i = 0; i < 6; i++) {
snprintf(hx + i * 2, 3, "%02X", trial[i]);
if (!add_sector_hit(hits, sec, trial, "A")) {
cJSON_Delete(root);
return NULL;
}
hx[12] = 0;
cJSON *h = cJSON_CreateObject();
cJSON_AddNumberToObject(h, "sector", sec);
cJSON_AddStringToObject(h, "keyHex", hx);
cJSON_AddStringToObject(h, "keyType", "A");
cJSON_AddItemToArray(hits, h);
got = true;
break;
}
if (try_key_on_trailer(tag, trailer, trial, true)) {
char hx[16];
for (int i = 0; i < 6; i++) {
snprintf(hx + i * 2, 3, "%02X", trial[i]);
if (!add_sector_hit(hits, sec, trial, "B")) {
cJSON_Delete(root);
return NULL;
}
hx[12] = 0;
cJSON *h = cJSON_CreateObject();
cJSON_AddNumberToObject(h, "sector", sec);
cJSON_AddStringToObject(h, "keyHex", hx);
cJSON_AddStringToObject(h, "keyType", "B");
cJSON_AddItemToArray(hits, h);
got = true;
break;
}
@@ -177,30 +201,18 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
}
attempts++;
if (try_key_on_trailer(tag, trailer, trial, false)) {
char hx[16];
for (int i = 0; i < 6; i++) {
snprintf(hx + i * 2, 3, "%02X", trial[i]);
if (!add_sector_hit(hits, sec, trial, "A")) {
cJSON_Delete(root);
return NULL;
}
hx[12] = 0;
cJSON *h = cJSON_CreateObject();
cJSON_AddNumberToObject(h, "sector", sec);
cJSON_AddStringToObject(h, "keyHex", hx);
cJSON_AddStringToObject(h, "keyType", "A");
cJSON_AddItemToArray(hits, h);
got = true;
break;
}
if (try_key_on_trailer(tag, trailer, trial, true)) {
char hx[16];
for (int i = 0; i < 6; i++) {
snprintf(hx + i * 2, 3, "%02X", trial[i]);
if (!add_sector_hit(hits, sec, trial, "B")) {
cJSON_Delete(root);
return NULL;
}
hx[12] = 0;
cJSON *h = cJSON_CreateObject();
cJSON_AddNumberToObject(h, "sector", sec);
cJSON_AddStringToObject(h, "keyHex", hx);
cJSON_AddStringToObject(h, "keyType", "B");
cJSON_AddItemToArray(hits, h);
got = true;
break;
}
@@ -213,6 +225,10 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
if (!got) {
cJSON *h = cJSON_CreateObject();
if (!h) {
cJSON_Delete(root);
return NULL;
}
cJSON_AddNumberToObject(h, "sector", sec);
cJSON_AddBoolToObject(h, "miss", true);
cJSON_AddItemToArray(hits, h);

View File

@@ -9,6 +9,7 @@
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},
@@ -37,7 +38,7 @@ static void key_to_hex(const uint8_t k[6], char *out13)
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);
bool is4k = nfc_tag_is_mifare_classic_4k(tag);
if (!is4k) {
if (sector < 0 || sector > 15) {
return false;
@@ -65,6 +66,9 @@ static bool mfc_sector_layout(const nfc_tag_info_t *tag, int sector, int *first_
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;
@@ -76,16 +80,19 @@ static bool try_sector(nfc_tag_info_t *tag, int sector, cJSON *sec_out)
cJSON_AddNumberToObject(sec_out, "trailerBlock", trailer);
cJSON_AddNumberToObject(sec_out, "blockCount", nb);
for (size_t ki = 0; ki < sizeof(k_default_keys) / 6; ki++) {
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);
cJSON *blocks = cJSON_CreateArray();
for (int b = 0; b < nb; b++) {
uint8_t bn = (uint8_t)(fb + b);
uint8_t blk[NFC_BLOCK_LEN];
@@ -102,11 +109,14 @@ static bool try_sector(nfc_tag_info_t *tag, int sector, cJSON *sec_out)
}
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);
cJSON *blocks = cJSON_CreateArray();
for (int b = 0; b < nb; b++) {
uint8_t bn = (uint8_t)(fb + b);
uint8_t blk[NFC_BLOCK_LEN];
@@ -128,10 +138,19 @@ static bool try_sector(nfc_tag_info_t *tag, int sector, cJSON *sec_out)
static void add_mifare_classic(nfc_tag_info_t *tag, cJSON *root)
{
int sectors = (tag->sak == 0x19) ? 40 : 16;
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);
}
@@ -140,8 +159,14 @@ static void add_mifare_classic(nfc_tag_info_t *tag, cJSON *root)
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) {
@@ -156,26 +181,24 @@ static void add_ultralight(nfc_tag_info_t *tag, cJSON *root)
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_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);
cJSON *tag_json = nfc_tag_to_json(tag);
if (!tag_json) {
cJSON_Delete(o);
return NULL;
}
cJSON_AddItemToObject(o, "tag", tag_json);
if (tag->type_hint == 1) {
if (nfc_tag_is_mifare_classic(tag)) {
add_mifare_classic(tag, o);
} else if (tag->type_hint == 2) {
} else if (nfc_tag_is_type2(tag)) {
add_ultralight(tag, o);
} else {
cJSON_AddStringToObject(

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);

View File

@@ -35,12 +35,40 @@ void session_capture_clear(void)
bool session_capture_is_full(void)
{
return s_full;
bool full = false;
if (s_mu) {
xSemaphoreTake(s_mu, portMAX_DELAY);
}
full = s_full;
if (s_mu) {
xSemaphoreGive(s_mu);
}
return full;
}
bool session_capture_deep_enabled(void) { return s_deep; }
bool session_capture_deep_enabled(void)
{
bool deep = false;
if (s_mu) {
xSemaphoreTake(s_mu, portMAX_DELAY);
}
deep = s_deep;
if (s_mu) {
xSemaphoreGive(s_mu);
}
return deep;
}
void session_capture_set_deep(bool on) { s_deep = on; }
void session_capture_set_deep(bool on)
{
if (s_mu) {
xSemaphoreTake(s_mu, portMAX_DELAY);
}
s_deep = on;
if (s_mu) {
xSemaphoreGive(s_mu);
}
}
size_t session_capture_max(void) { return sizeof(s_buf) - 2; }
@@ -65,7 +93,7 @@ void session_capture_get_status(size_t *used_bytes, uint32_t *line_count, bool *
bool session_capture_append_line(const char *line)
{
if (!line || s_full) {
if (!line) {
return false;
}
size_t l = strlen(line);
@@ -106,8 +134,6 @@ size_t session_capture_export_size(void)
return n;
}
const char *session_capture_export_ptr(void) { return s_buf; }
void session_capture_copy_to(char *dst, size_t cap, size_t *out_len)
{
if (s_mu) {

View File

@@ -1,10 +1,7 @@
#include "pn532_host/pn532_core.h"
#include "pn532_host/pn532_transport.h"
#include "esp_log.h"
#include <string.h>
static const char *TAG = "pn532_core";
esp_err_t pn532_send_cmd(const uint8_t *cmd_and_data, size_t len, uint8_t *response, size_t response_max,
size_t *response_len, int timeout_ms)
{
@@ -21,9 +18,6 @@ esp_err_t pn532_send_cmd(const uint8_t *cmd_and_data, size_t len, uint8_t *respo
if (*response_len < 1) {
return ESP_ERR_INVALID_RESPONSE;
}
if (response[0] != 0x00) {
ESP_LOGW(TAG, "chip status 0x%02x", response[0]);
}
return ESP_OK;
}
@@ -34,6 +28,9 @@ esp_err_t pn532_core_init(void)
esp_err_t pn532_get_firmware_version(uint8_t *ic_ver, uint8_t *fw_ver_hi, uint8_t *fw_ver_lo)
{
if (!ic_ver || !fw_ver_hi || !fw_ver_lo) {
return ESP_ERR_INVALID_ARG;
}
uint8_t cmd = PN532_CMD_GETFIRMWAREVERSION;
uint8_t resp[16];
size_t rlen = 0;
@@ -41,7 +38,7 @@ esp_err_t pn532_get_firmware_version(uint8_t *ic_ver, uint8_t *fw_ver_hi, uint8_
if (e != ESP_OK) {
return e;
}
if (rlen < 4) {
if (rlen < 5 || resp[0] != (uint8_t)(PN532_CMD_GETFIRMWAREVERSION + 1)) {
return ESP_ERR_INVALID_RESPONSE;
}
*ic_ver = resp[1];
@@ -55,13 +52,36 @@ esp_err_t pn532_sam_config_normal(void)
uint8_t buf[] = {PN532_CMD_SAMCONFIGURATION, 0x01, 0x14, 0x01};
uint8_t resp[8];
size_t rlen = 0;
return pn532_send_cmd(buf, sizeof(buf), resp, sizeof(resp), &rlen, 200);
esp_err_t e = pn532_send_cmd(buf, sizeof(buf), resp, sizeof(resp), &rlen, 200);
if (e != ESP_OK) {
return e;
}
return (rlen >= 1 && resp[0] == (uint8_t)(PN532_CMD_SAMCONFIGURATION + 1)) ? ESP_OK
: ESP_ERR_INVALID_RESPONSE;
}
esp_err_t pn532_get_general_status(uint8_t *buf, size_t buf_len, size_t *out_len)
{
if (!buf || !out_len) {
return ESP_ERR_INVALID_ARG;
}
uint8_t cmd = PN532_CMD_GETGENERALSTATUS;
return pn532_send_cmd(&cmd, 1, buf, buf_len, out_len, 200);
uint8_t resp[32];
size_t rlen = 0;
esp_err_t e = pn532_send_cmd(&cmd, 1, resp, sizeof(resp), &rlen, 200);
if (e != ESP_OK) {
return e;
}
if (rlen < 2 || resp[0] != (uint8_t)(PN532_CMD_GETGENERALSTATUS + 1)) {
return ESP_ERR_INVALID_RESPONSE;
}
size_t payload_len = rlen - 1;
if (payload_len > buf_len) {
return ESP_ERR_INVALID_SIZE;
}
memcpy(buf, resp + 1, payload_len);
*out_len = payload_len;
return ESP_OK;
}
esp_err_t pn532_rf_field(bool on)
@@ -69,7 +89,12 @@ esp_err_t pn532_rf_field(bool on)
uint8_t buf[] = {PN532_CMD_RFCONFIGURATION, 0x01, on ? 0x01 : 0x00};
uint8_t resp[8];
size_t rlen = 0;
return pn532_send_cmd(buf, sizeof(buf), resp, sizeof(resp), &rlen, 200);
esp_err_t e = pn532_send_cmd(buf, sizeof(buf), resp, sizeof(resp), &rlen, 200);
if (e != ESP_OK) {
return e;
}
return (rlen >= 1 && resp[0] == (uint8_t)(PN532_CMD_RFCONFIGURATION + 1)) ? ESP_OK
: ESP_ERR_INVALID_RESPONSE;
}
esp_err_t pn532_rf_max_retries(void)
@@ -78,37 +103,90 @@ esp_err_t pn532_rf_max_retries(void)
uint8_t buf[] = {PN532_CMD_RFCONFIGURATION, 0x05, 0xFF, 0xFF, 0xFF};
uint8_t resp[8];
size_t rlen = 0;
return pn532_send_cmd(buf, sizeof(buf), resp, sizeof(resp), &rlen, 200);
esp_err_t e = pn532_send_cmd(buf, sizeof(buf), resp, sizeof(resp), &rlen, 200);
if (e != ESP_OK) {
return e;
}
return (rlen >= 1 && resp[0] == (uint8_t)(PN532_CMD_RFCONFIGURATION + 1)) ? ESP_OK
: ESP_ERR_INVALID_RESPONSE;
}
esp_err_t pn532_in_list_passive_target(uint8_t max_targets, uint8_t baud, uint8_t *response,
size_t response_max, size_t *response_len)
{
if (!response || !response_len) {
return ESP_ERR_INVALID_ARG;
}
uint8_t buf[] = {PN532_CMD_INLISTPASSIVETARGET, max_targets, baud};
return pn532_send_cmd(buf, sizeof(buf), response, response_max, response_len, 500);
uint8_t raw[64];
size_t raw_len = 0;
esp_err_t e = pn532_send_cmd(buf, sizeof(buf), raw, sizeof(raw), &raw_len, 500);
if (e != ESP_OK) {
return e;
}
if (raw_len < 2 || raw[0] != (uint8_t)(PN532_CMD_INLISTPASSIVETARGET + 1)) {
return ESP_ERR_INVALID_RESPONSE;
}
size_t payload_len = raw_len - 1;
if (payload_len > response_max) {
return ESP_ERR_INVALID_SIZE;
}
memcpy(response, raw + 1, payload_len);
*response_len = payload_len;
return ESP_OK;
}
esp_err_t pn532_in_data_exchange(const uint8_t *data, size_t data_len, uint8_t *response,
size_t response_max, size_t *response_len)
{
if (!data || data_len == 0 || data_len > PN532_EEPROM_MAX_CMD_PAYLOAD - 1) {
if (!data || !response || !response_len || data_len == 0 || data_len > PN532_EEPROM_MAX_CMD_PAYLOAD - 2) {
return ESP_ERR_INVALID_ARG;
}
uint8_t buf[PN532_EEPROM_MAX_CMD_PAYLOAD + 1];
uint8_t buf[PN532_EEPROM_MAX_CMD_PAYLOAD];
buf[0] = PN532_CMD_INDATAEXCHANGE;
buf[1] = 0x01; /* logical target 1 */
memcpy(buf + 2, data, data_len);
return pn532_send_cmd(buf, 2 + data_len, response, response_max, response_len, 500);
uint8_t raw[PN532_EEPROM_MAX_CMD_PAYLOAD];
size_t raw_len = 0;
esp_err_t e = pn532_send_cmd(buf, 2 + data_len, raw, sizeof(raw), &raw_len, 500);
if (e != ESP_OK) {
return e;
}
if (raw_len < 2 || raw[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1)) {
return ESP_ERR_INVALID_RESPONSE;
}
size_t payload_len = raw_len - 1;
if (payload_len > response_max) {
return ESP_ERR_INVALID_SIZE;
}
memcpy(response, raw + 1, payload_len);
*response_len = payload_len;
return ESP_OK;
}
esp_err_t pn532_in_communicate_thru(const uint8_t *data, size_t data_len, uint8_t *response,
size_t response_max, size_t *response_len)
{
if (!data || data_len > PN532_EEPROM_MAX_CMD_PAYLOAD - 1) {
if (!data || !response || !response_len || data_len > PN532_EEPROM_MAX_CMD_PAYLOAD - 1) {
return ESP_ERR_INVALID_ARG;
}
uint8_t buf[PN532_EEPROM_MAX_CMD_PAYLOAD + 1];
uint8_t buf[PN532_EEPROM_MAX_CMD_PAYLOAD];
buf[0] = PN532_CMD_INCOMMUNICATETHRU;
memcpy(buf + 1, data, data_len);
return pn532_send_cmd(buf, 1 + data_len, response, response_max, response_len, 500);
uint8_t raw[PN532_EEPROM_MAX_CMD_PAYLOAD];
size_t raw_len = 0;
esp_err_t e = pn532_send_cmd(buf, 1 + data_len, raw, sizeof(raw), &raw_len, 500);
if (e != ESP_OK) {
return e;
}
if (raw_len < 2 || raw[0] != (uint8_t)(PN532_CMD_INCOMMUNICATETHRU + 1)) {
return ESP_ERR_INVALID_RESPONSE;
}
size_t payload_len = raw_len - 1;
if (payload_len > response_max) {
return ESP_ERR_INVALID_SIZE;
}
memcpy(response, raw + 1, payload_len);
*response_len = payload_len;
return ESP_OK;
}

View File

@@ -5,6 +5,7 @@
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "driver/uart.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
@@ -12,6 +13,7 @@
#include <sys/param.h>
#define PN532_HOST_TO_PN532 0xD4
#define PN532_PN532_TO_HOST 0xD5
#define PN532_TXBUF_MAX 264
static const char *TAG = "pn532_xport";
@@ -34,13 +36,20 @@ static esp_err_t spi_wait_ready(int timeout_ms)
uint8_t status = 0;
const int64_t end = esp_timer_get_time() / 1000 + timeout_ms;
while ((esp_timer_get_time() / 1000) < (uint64_t)end) {
spi_transaction_t t = {};
uint8_t tx = 0x02; /* SPIstatus read */
t.length = 8;
t.tx_buffer = &tx;
t.rx_buffer = &status;
gpio_set_level(s_spi_cs_gpio, 0);
esp_err_t e = spi_device_polling_transmit(s_spi, &t);
uint8_t cmd = 0x02; /* Status read */
spi_transaction_t t_cmd = {};
t_cmd.length = 8;
t_cmd.tx_buffer = &cmd;
esp_err_t e = spi_device_polling_transmit(s_spi, &t_cmd);
if (e == ESP_OK) {
uint8_t tx = 0x00;
spi_transaction_t t_status = {};
t_status.length = 8;
t_status.tx_buffer = &tx;
t_status.rx_buffer = &status;
e = spi_device_polling_transmit(s_spi, &t_status);
}
gpio_set_level(s_spi_cs_gpio, 1);
if (e != ESP_OK) {
return e;
@@ -58,7 +67,7 @@ static esp_err_t spi_write_frame(const uint8_t *data, size_t len)
ESP_RETURN_ON_ERROR(spi_wait_ready(200), TAG, "wait before write");
gpio_set_level(s_spi_cs_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(2));
uint8_t hdr = 0x04; /* Data write */
uint8_t hdr = 0x01; /* Data write */
spi_transaction_t t0 = {};
t0.length = 8;
t0.tx_buffer = &hdr;
@@ -126,6 +135,9 @@ static esp_err_t i2c_write_raw(const uint8_t *buf, size_t len)
static esp_err_t i2c_read_raw(uint8_t *buf, size_t len)
{
if (!buf || len == 0) {
return ESP_ERR_INVALID_ARG;
}
i2c_cmd_handle_t c = i2c_cmd_link_create();
i2c_master_start(c);
i2c_master_write_byte(c, (CONFIG_PN532_I2C_ADDR << 1) | I2C_MASTER_READ, true);
@@ -139,6 +151,37 @@ static esp_err_t i2c_read_raw(uint8_t *buf, size_t len)
return e;
}
static esp_err_t i2c_read_frame(uint8_t *buf, size_t len)
{
if (!buf || len == 0) {
return ESP_ERR_INVALID_ARG;
}
uint8_t raw[272];
if (len + 1 > sizeof(raw)) {
return ESP_ERR_INVALID_SIZE;
}
ESP_RETURN_ON_ERROR(i2c_read_raw(raw, len + 1), TAG, "i2c read");
if (raw[0] != 0x01) {
ESP_LOGW(TAG, "unexpected i2c status 0x%02x", raw[0]);
return ESP_ERR_INVALID_RESPONSE;
}
memcpy(buf, raw + 1, len);
return ESP_OK;
}
static esp_err_t i2c_wait_ready(int timeout_ms)
{
int64_t t0 = esp_timer_get_time() / 1000;
while (((esp_timer_get_time() / 1000) - t0) < timeout_ms) {
uint8_t status = 0;
if (i2c_read_raw(&status, 1) == ESP_OK && status == 0x01) {
return ESP_OK;
}
vTaskDelay(pdMS_TO_TICKS(2));
}
return ESP_ERR_TIMEOUT;
}
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
#define PN532_UART ((uart_port_t)CONFIG_PN532_HSU_UART_NUM)
@@ -177,8 +220,8 @@ static esp_err_t read_ack(int timeout_ms)
#if defined(CONFIG_PN532_TRANSPORT_SPI)
ESP_RETURN_ON_ERROR(spi_read_bytes(ack, sizeof(ack)), TAG, "read ack spi");
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
vTaskDelay(pdMS_TO_TICKS(5));
ESP_RETURN_ON_ERROR(i2c_read_raw(ack, sizeof(ack)), TAG, "read ack i2c");
ESP_RETURN_ON_ERROR(i2c_wait_ready(timeout_ms), TAG, "wait ack i2c");
ESP_RETURN_ON_ERROR(i2c_read_frame(ack, sizeof(ack)), TAG, "read ack i2c");
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
ESP_RETURN_ON_ERROR(hsu_read_raw(ack, sizeof(ack), timeout_ms), TAG, "read ack hsu");
#endif
@@ -191,67 +234,75 @@ static esp_err_t read_ack(int timeout_ms)
static esp_err_t read_response_frame(uint8_t *body_out, size_t body_max, size_t *body_len, int timeout_ms)
{
uint8_t hdr[8];
#if defined(CONFIG_PN532_TRANSPORT_SPI)
ESP_RETURN_ON_ERROR(spi_read_bytes(hdr, 6), TAG, "hdr spi");
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
{
int64_t t0 = esp_timer_get_time() / 1000;
bool ok = false;
while (((esp_timer_get_time() / 1000) - t0) < timeout_ms) {
uint8_t peek[1];
if (i2c_read_raw(peek, 1) == ESP_OK && peek[0] == 0x01) {
ok = true;
break;
}
vTaskDelay(pdMS_TO_TICKS(2));
}
if (!ok) {
return ESP_ERR_TIMEOUT;
}
ESP_RETURN_ON_ERROR(i2c_read_raw(hdr, 6), TAG, "hdr i2c");
if (!body_out || !body_len) {
return ESP_ERR_INVALID_ARG;
}
uint8_t hdr[8];
size_t frame_len = 0;
#if defined(CONFIG_PN532_TRANSPORT_SPI)
ESP_RETURN_ON_ERROR(spi_read_bytes(hdr, 5), TAG, "hdr spi");
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
ESP_RETURN_ON_ERROR(i2c_wait_ready(timeout_ms), TAG, "wait frame i2c");
ESP_RETURN_ON_ERROR(i2c_read_frame(hdr, 5), TAG, "hdr i2c");
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
ESP_RETURN_ON_ERROR(hsu_read_raw(hdr, 6, timeout_ms), TAG, "hdr hsu");
ESP_RETURN_ON_ERROR(hsu_read_raw(hdr, 5, timeout_ms), TAG, "hdr hsu");
#endif
if (hdr[0] != 0x00 || hdr[1] != 0x00 || hdr[2] != 0xFF) {
ESP_LOG_BUFFER_HEX_LEVEL(TAG, hdr, 6, ESP_LOG_WARN);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, hdr, 5, ESP_LOG_WARN);
return ESP_ERR_INVALID_RESPONSE;
}
uint16_t L = (uint16_t)(hdr[3] * 256 + hdr[4]);
uint8_t lcs = hdr[5];
if ((uint8_t)((hdr[3] + hdr[4] + lcs) & 0xFF) != 0) {
return ESP_ERR_INVALID_CRC;
if (hdr[3] == 0xFF && hdr[4] == 0xFF) {
#if defined(CONFIG_PN532_TRANSPORT_SPI)
ESP_RETURN_ON_ERROR(spi_read_bytes(hdr + 5, 3), TAG, "hdr ext spi");
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
ESP_RETURN_ON_ERROR(i2c_read_frame(hdr + 5, 3), TAG, "hdr ext i2c");
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
ESP_RETURN_ON_ERROR(hsu_read_raw(hdr + 5, 3, timeout_ms), TAG, "hdr ext hsu");
#endif
frame_len = (size_t)(((uint16_t)hdr[5] << 8) | hdr[6]);
if ((uint8_t)(hdr[5] + hdr[6] + hdr[7]) != 0) {
return ESP_ERR_INVALID_CRC;
}
} else {
frame_len = hdr[3];
if ((uint8_t)(hdr[3] + hdr[4]) != 0) {
return ESP_ERR_INVALID_CRC;
}
}
if (L < 2) {
if (frame_len < 2) {
return ESP_ERR_INVALID_SIZE;
}
/* Read TFI..data (L bytes) + DCS — L includes TFI through last payload byte */
const size_t read_total = (size_t)L + 1;
if (read_total > 270) {
const size_t read_total = frame_len + 2;
if (read_total > 272) {
return ESP_ERR_INVALID_SIZE;
}
uint8_t chunk[272];
#if defined(CONFIG_PN532_TRANSPORT_SPI)
ESP_RETURN_ON_ERROR(spi_read_bytes(chunk, read_total), TAG, "payload spi");
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
ESP_RETURN_ON_ERROR(i2c_read_raw(chunk, read_total), TAG, "payload i2c");
ESP_RETURN_ON_ERROR(i2c_read_frame(chunk, read_total), TAG, "payload i2c");
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
ESP_RETURN_ON_ERROR(hsu_read_raw(chunk, read_total, timeout_ms), TAG, "payload hsu");
#endif
uint8_t tfi = chunk[0];
if (tfi != 0xD5) {
if (chunk[0] != PN532_PN532_TO_HOST) {
return ESP_ERR_INVALID_RESPONSE;
}
uint8_t sum = 0;
for (uint16_t i = 0; i < L; i++) {
for (size_t i = 0; i < frame_len; i++) {
sum += chunk[i];
}
uint8_t dcs = chunk[L];
if ((uint8_t)((sum + dcs) & 0xFF) != 0) {
if ((uint8_t)(sum + chunk[frame_len]) != 0) {
return ESP_ERR_INVALID_CRC;
}
*body_len = (size_t)L - 1;
if (chunk[frame_len + 1] != 0x00) {
return ESP_ERR_INVALID_RESPONSE;
}
*body_len = frame_len - 1;
if (*body_len > body_max) {
return ESP_ERR_INVALID_SIZE;
}
@@ -262,29 +313,41 @@ static esp_err_t read_response_frame(uint8_t *body_out, size_t body_max, size_t
esp_err_t pn532_transport_exchange(const uint8_t *tx_body, size_t tx_body_len, uint8_t *rx_body,
size_t rx_body_max, size_t *rx_body_len, int timeout_ms)
{
if (tx_body_len == 0 || tx_body_len > 255) {
if (!tx_body || !rx_body || !rx_body_len) {
return ESP_ERR_INVALID_ARG;
}
uint16_t L = (uint16_t)(1 + tx_body_len);
uint8_t lcs = (uint8_t)(0x100 - (uint8_t)(((L >> 8) + (L & 0xFF)) & 0xFF));
if (tx_body_len == 0 || tx_body_len > 254) {
return ESP_ERR_INVALID_ARG;
}
size_t frame_len = 1 + tx_body_len;
uint8_t sum = PN532_HOST_TO_PN532;
for (size_t i = 0; i < tx_body_len; i++) {
sum += tx_body[i];
}
dcs = (uint8_t)(256 - sum);
uint8_t dcs = (uint8_t)(0x100 - sum);
uint8_t frame[PN532_TXBUF_MAX];
size_t pos = 0;
frame[pos++] = 0x00;
frame[pos++] = 0x00;
frame[pos++] = 0xFF;
frame[pos++] = (uint8_t)((L >> 8) & 0xFF);
frame[pos++] = (uint8_t)(L & 0xFF);
frame[pos++] = lcs;
if (frame_len <= 254) {
uint8_t lcs = (uint8_t)(0x100 - (uint8_t)frame_len);
frame[pos++] = (uint8_t)frame_len;
frame[pos++] = lcs;
} else {
uint16_t ext_len = (uint16_t)frame_len;
frame[pos++] = 0xFF;
frame[pos++] = 0xFF;
frame[pos++] = (uint8_t)((ext_len >> 8) & 0xFF);
frame[pos++] = (uint8_t)(ext_len & 0xFF);
frame[pos++] = (uint8_t)(0x100 - (uint8_t)(((ext_len >> 8) + (ext_len & 0xFF)) & 0xFF));
}
frame[pos++] = PN532_HOST_TO_PN532;
memcpy(frame + pos, tx_body, tx_body_len);
pos += tx_body_len;
frame[pos++] = dcs;
frame[pos++] = 0x00;
#if defined(CONFIG_PN532_TRANSPORT_SPI)
ESP_RETURN_ON_ERROR(spi_write_frame(frame, pos), TAG, "spi wr");
@@ -354,8 +417,3 @@ esp_err_t pn532_transport_init(void)
#endif
return ESP_OK;
}
</think>
Fixing a typo in `pn532_transport.c` and correcting the DCS checksum calculation.
<toolcallsbegin><toolcallbegin>
Read

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@
href="https://fonts.googleapis.com/css2?family=Audiowide&family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;1,400&family=Orbitron:wght@500;600;700;800&display=swap"
rel="stylesheet"
/>
<script type="module" crossorigin src="/assets/index-EAAhhled.js"></script>
<script type="module" crossorigin src="/assets/index-9oJp152C.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-hoMg1Qkq.css">
</head>
<body class="bg-bubble-950 text-slate-200 antialiased selection:bg-bubble-accent/40 selection:text-bubble-950">

View File

@@ -0,0 +1,31 @@
dependencies:
espressif/led_strip:
component_hash: 28c6509a727ef74925b372ed404772aeedf11cce10b78c3f69b3c66799095e2d
dependencies:
- name: idf
require: private
version: '>=4.4'
source:
registry_url: https://components.espressif.com/
type: service
version: 2.5.5
espressif/mdns:
component_hash: 1ebe3bd675bb9d1c58f52bc0b609b32f74e572b01c328f9e61282040c775495c
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.11.0
idf:
source:
type: idf
version: 5.3.5
direct_dependencies:
- espressif/led_strip
- espressif/mdns
manifest_hash: 846f5d4c8f94373f8485b56cff23f9fbea0309ded1992f9236f5f5ef877a4d37
target: esp32s3
version: 2.0.0

View File

@@ -1,12 +1,11 @@
#!/usr/bin/env bash
# Flash PN532 toolkit. Requires ESP-IDF 5.x in PATH (run export.sh first).
# Compatibility wrapper around the repo-root launcher.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
PORT="${ESPPORT:-${1:-/dev/cu.usbserial-A5069RR4}}"
cd "$ROOT"
if ! command -v idf.py >/dev/null 2>&1; then
echo "idf.py not found. Source your ESP-IDF export.sh, then re-run:" >&2
echo " ESPPORT=$PORT $0" >&2
exit 1
PORT="${ESPPORT:-${1:-}}"
if [ -n "$PORT" ]; then
exec "$ROOT/../start-firmware.sh" --port "$PORT" --flash-only
fi
idf.py -p "$PORT" flash
exec "$ROOT/../start-firmware.sh" --flash-only

View File

@@ -1,3 +1,4 @@
## IDF Component Manager — addressable RGB (DevKitC-1)
dependencies:
espressif/led_strip: "^2.5.5"
espressif/mdns: "^1.10.1"

View File

@@ -9,7 +9,7 @@ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
# CONFIG_SPIRAM_MODE_OCT=y
# CONFIG_SPIRAM_SPEED_80M=y
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_FREERTOS_HZ=1000
@@ -18,7 +18,6 @@ CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
CONFIG_HTTPD_MAX_URI_LEN=512
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
CONFIG_LWIP_LOCAL_IP4_TTL=64
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32