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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user