Compare commits
2 Commits
main
...
docs/readm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2da1515f8d | ||
|
|
63db10d400 |
@@ -1,17 +0,0 @@
|
|||||||
# EditorConfig is awesome: https://editorconfig.org
|
|
||||||
<!-- stewardship-standard: editorconfig-v1 -->
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.{md,markdown}]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[Makefile]
|
|
||||||
indent_style = tab
|
|
||||||
27
.gitattributes
vendored
27
.gitattributes
vendored
@@ -1,27 +0,0 @@
|
|||||||
# stewardship-standard: gitattributes-v1
|
|
||||||
* text=auto eol=lf
|
|
||||||
|
|
||||||
*.md text eol=lf
|
|
||||||
*.txt text eol=lf
|
|
||||||
*.json text eol=lf
|
|
||||||
*.yml text eol=lf
|
|
||||||
*.yaml text eol=lf
|
|
||||||
*.sh text eol=lf
|
|
||||||
*.py text eol=lf
|
|
||||||
*.js text eol=lf
|
|
||||||
*.ts text eol=lf
|
|
||||||
*.c text eol=lf
|
|
||||||
*.cpp text eol=lf
|
|
||||||
*.h text eol=lf
|
|
||||||
*.hpp text eol=lf
|
|
||||||
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.webp binary
|
|
||||||
*.pdf binary
|
|
||||||
*.zip binary
|
|
||||||
*.bin binary
|
|
||||||
*.elf binary
|
|
||||||
*.uf2 binary
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Bug Report
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Describe the problem and expected behavior.
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
- Repo version/commit:
|
|
||||||
- OS/toolchain/board/service:
|
|
||||||
- Relevant configuration with secrets removed:
|
|
||||||
|
|
||||||
## Reproduction
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
## Logs
|
|
||||||
|
|
||||||
Paste only sanitized logs. Remove credentials, tokens, personal data, captures, dumps, and target identifiers.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Documentation Task
|
|
||||||
|
|
||||||
## Page Or Section
|
|
||||||
|
|
||||||
Name the README/wiki section that needs work.
|
|
||||||
|
|
||||||
## Change Needed
|
|
||||||
|
|
||||||
Describe what should be clearer, corrected, or added.
|
|
||||||
|
|
||||||
## Source Of Truth
|
|
||||||
|
|
||||||
Link to code, hardware notes, upstream docs, release notes, or maintainer decision.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Release Checklist
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Describe what is being released and why.
|
|
||||||
|
|
||||||
## Checks
|
|
||||||
|
|
||||||
- [ ] README and wiki are current.
|
|
||||||
- [ ] Changelog entry exists.
|
|
||||||
- [ ] License/provenance is clear.
|
|
||||||
- [ ] No secrets or private data are included.
|
|
||||||
- [ ] Firmware/binary artifacts include SHA256 hashes and target details.
|
|
||||||
- [ ] Build or smoke-check result is recorded.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Pull Request Checklist
|
|
||||||
|
|
||||||
- [ ] Scope is clear and limited.
|
|
||||||
- [ ] README/wiki updates are included when behavior, setup, hardware, or release process changes.
|
|
||||||
- [ ] No secrets, tokens, private data, dumps, captures, or generated dependency folders are committed.
|
|
||||||
- [ ] Build/test/smoke-check result is documented.
|
|
||||||
- [ ] License or upstream provenance is preserved.
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,7 +5,3 @@ firmware/managed_components/
|
|||||||
web/node_modules/
|
web/node_modules/
|
||||||
web/dist/
|
web/dist/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
._*
|
|
||||||
**/._*
|
|
||||||
.cache/
|
|
||||||
**/.cache/clangd/
|
|
||||||
|
|||||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,13 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All meaningful changes to this repository should be recorded here.
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
- Add future changes here before tagging or publishing release artifacts.
|
|
||||||
|
|
||||||
## 2026-05-20 - Gitea Stewardship Import
|
|
||||||
|
|
||||||
- Verified README and wiki coverage.
|
|
||||||
- Added standard stewardship documentation where missing.
|
|
||||||
- Established security, contribution, release, and provenance expectations.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
* @drjones
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
## Maintainer Expectations
|
|
||||||
|
|
||||||
Keep changes small, reviewable, and tied to a clear project purpose. Do not mix source changes with generated build output or dependency caches.
|
|
||||||
|
|
||||||
## Before Committing
|
|
||||||
|
|
||||||
- Run the relevant build, lint, or smoke test when the project provides one.
|
|
||||||
- Check that no credentials, `.env` files, tokens, private keys, captures, dumps, or personal data are staged.
|
|
||||||
- Keep firmware binaries, large archives, and generated artifacts out of Git unless the repo explicitly documents otherwise.
|
|
||||||
- Preserve upstream licenses and attribution for third-party code.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Update README and wiki pages when setup, hardware, architecture, environment variables, or release behavior changes.
|
|
||||||
|
|
||||||
## Safety
|
|
||||||
|
|
||||||
Only submit work intended for authorized environments. Project documentation should make scope and safe operation clearer, never weaker.
|
|
||||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2026 sudo-jones-cmd
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# License Status
|
|
||||||
|
|
||||||
This repository has not been assigned a blanket license by the stewardship pass.
|
|
||||||
|
|
||||||
## Current Rule
|
|
||||||
|
|
||||||
- Existing upstream licenses must be preserved.
|
|
||||||
- Third-party code must retain attribution and license files.
|
|
||||||
- Original private work remains all rights reserved until an explicit license is selected.
|
|
||||||
- Do not assume MIT, Apache, GPL, or public-domain status unless a license file in this repository says so.
|
|
||||||
|
|
||||||
## Next Step
|
|
||||||
|
|
||||||
Classify ownership and dependencies before publishing releases or accepting external contributions.
|
|
||||||
27
README.md
27
README.md
@@ -13,7 +13,7 @@ ESP32-S3 + PN532 + a serious web UI — no desktop app, no dongle software, no m
|
|||||||
|
|
||||||
3. **RAM session buffer with a hard stop + phone download** — Captures live in **on-chip RAM** (**48 KB** buffer, NDJSON lines). When the buffer is full, **RF polling pauses** so you never lose data to silent overflow. You tap **Download NDJSON**, get one file with every profile, then **Clear** to resume. That workflow is built for field audits, bench sessions, and anything where “I need the dump on my phone, now” matters.
|
3. **RAM session buffer with a hard stop + phone download** — Captures live in **on-chip RAM** (**48 KB** buffer, NDJSON lines). When the buffer is full, **RF polling pauses** so you never lose data to silent overflow. You tap **Download NDJSON**, get one file with every profile, then **Clear** to resume. That workflow is built for field audits, bench sessions, and anything where “I need the dump on my phone, now” matters.
|
||||||
|
|
||||||
4. **Self-hosted on the device** — Default **SoftAP**: SSID **`PN532-Toolkit`**, password **`nfc-toolkit`** (WPA2). **mDNS** hostname **`pn532tool.local`** (HTTP port **80**). No cloud, no account, no telemetry.
|
4. **Self-hosted on the device** — Default **SoftAP**: SSID **`PN532-Toolkit`**, **open network** (no password, lab default). **mDNS** hostname **`pn532tool.local`** (HTTP port **80**). No cloud, no account, no telemetry.
|
||||||
|
|
||||||
5. **Honest architecture** — **ESP-IDF**, explicit components (`pn532_host`, `nfc_engine`, `net_service`), **Vite/React** UI embedded in **SPIFFS**. You extend it like real firmware, not a black-box sketch. PN532 talks **SPI, I2C, or UART (HSU)** — pins and bus are **menuconfig**, not guesses.
|
5. **Honest architecture** — **ESP-IDF**, explicit components (`pn532_host`, `nfc_engine`, `net_service`), **Vite/React** UI embedded in **SPIFFS**. You extend it like real firmware, not a black-box sketch. PN532 talks **SPI, I2C, or UART (HSU)** — pins and bus are **menuconfig**, not guesses.
|
||||||
|
|
||||||
@@ -38,9 +38,12 @@ This stack is **not** a Proxmark replacement (no LF, no raw carrier manipulation
|
|||||||
| **Session RAM** | **~48 KB** NDJSON capture buffer; mutex-protected; **full** flag stops polling until you clear. |
|
| **Session RAM** | **~48 KB** NDJSON capture buffer; mutex-protected; **full** flag stops polling until you clear. |
|
||||||
| **HTTP API** | JSON in/out on documented routes; **CORS** headers on API responses; **`OPTIONS /*`** preflight for cross-origin clients. POST bodies are read **completely** (chunked TCP-safe). |
|
| **HTTP API** | JSON in/out on documented routes; **CORS** headers on API responses; **`OPTIONS /*`** preflight for cross-origin clients. POST bodies are read **completely** (chunked TCP-safe). |
|
||||||
| **WebSocket** | **`/ws`**: JSON envelopes `{"channel":"…","payload":…}` — **`scan`** (inventory / `present:false` when tag leaves), **`capture`** (`recorded`, `bufferFull`, etc.), **`pong`** for keepalive. |
|
| **WebSocket** | **`/ws`**: JSON envelopes `{"channel":"…","payload":…}` — **`scan`** (inventory / `present:false` when tag leaves), **`capture`** (`recorded`, `bufferFull`, etc.), **`pong`** for keepalive. |
|
||||||
| **OTA endpoint** | **`POST /api/ota`** returns **501** with a stub message — there is **no** in-field OTA in this repo yet; use **`idf.py flash`** (or the included scripts). |
|
| **OTA endpoint** | **`POST /api/ota`** now performs a real **HTTPS OTA** with the ESP-IDF certificate bundle; successful updates reboot into the new slot and rollback support is enabled. |
|
||||||
| **Onboard RGB (DevKit-style)** | Optional **boot-time** shutdown of a **WS2812/SK6812** on **GPIO 48** (Kconfig) so the addressable LED isn’t left on a random color — **not** on all boards; power LEDs are hardware. |
|
| **Onboard RGB (DevKit-style)** | Optional **boot-time** shutdown of a **WS2812/SK6812** on **GPIO 48** (Kconfig) so the addressable LED isn’t left on a random color — **not** on all boards; power LEDs are hardware. |
|
||||||
| **Status** | **`/api/status`**: uptime, free heap, Wi‑Fi mode, **PN532 firmware version** (when reachable), scan flag, session stats. |
|
| **Status** | **`/api/status`**: uptime, free heap, Wi‑Fi mode, **PN532 firmware version** (when reachable), scan flag, `targetActive`, and session stats. |
|
||||||
|
| **Probe API** | **`POST /api/nfc/probe`** builds a richer capability profile for the present tag, including structured-clone suitability and Type 2 `GET_VERSION` when supported. |
|
||||||
|
| **Structured clone flow** | **`POST /api/clone/capture`** normalizes MIFARE Classic and Type 2 tags into JSON snapshots; **`POST /api/clone/program`** writes supported snapshots back to a destination tag with safe defaults. |
|
||||||
|
| **Target mode** | **`POST /api/nfc/target/*`** provides `status`, `start`, `recv`, `send`, and `stop` around **`TgInitAsTarget` / `TgGetData` / `TgSetData`**, with a raw-parameter escape hatch when you need exact bytes. |
|
||||||
|
|
||||||
### Web UI (each page)
|
### Web UI (each page)
|
||||||
|
|
||||||
@@ -89,16 +92,24 @@ This stack is **not** a Proxmark replacement (no LF, no raw carrier manipulation
|
|||||||
| Method | Path | Purpose |
|
| Method | Path | Purpose |
|
||||||
|--------|------|---------|
|
|--------|------|---------|
|
||||||
| `POST` | `/api/nfc/poll` | One-shot poll; body may be `{}` — returns `present`, optional `tag` |
|
| `POST` | `/api/nfc/poll` | One-shot poll; body may be `{}` — returns `present`, optional `tag` |
|
||||||
|
| `POST` | `/api/nfc/probe` | Rich capability probe for the current tag; adds clone/program hints and Type 2 `GET_VERSION` when available |
|
||||||
| `POST` | `/api/nfc/scan` | Body `{"enable": true}` or `false` — continuous background scan |
|
| `POST` | `/api/nfc/scan` | Body `{"enable": true}` or `false` — continuous background scan |
|
||||||
| `GET` | `/api/pn532/general-status` | Raw PN532 **general status** byte array |
|
| `GET` | `/api/pn532/general-status` | Raw PN532 **general status** byte array |
|
||||||
| `POST` | `/api/mifare/read-block` | `block`, `key` (12 hex), `keyB` |
|
| `POST` | `/api/mifare/read-block` | `block`, `key` (12 hex), `keyB` |
|
||||||
| `POST` | `/api/mifare/write-block` | `block`, `key`, `keyB`, `data` (32 hex) |
|
| `POST` | `/api/mifare/write-block` | `block`, `key`, `keyB`, `data` (32 hex) |
|
||||||
| `POST` | `/api/ul/read-page` | `page` |
|
| `POST` | `/api/ul/read-page` | `page` |
|
||||||
| `POST` | `/api/ul/write-page` | `page`, `data` (8 hex) |
|
| `POST` | `/api/ul/write-page` | `page`, `data` (8 hex) |
|
||||||
| `POST` | `/api/raw/pn532` | `frame` — hex command bytes |
|
| `POST` | `/api/raw/pn532` | `frame` — PN532 command bytes (not a full transport frame) |
|
||||||
| `POST` | `/api/mifare/dictionary-attack` | `readerType` (`classic1k` / `classic4k`), `variations`, optional `keysHex[]`, optional `sectorFirst` / `sectorLast` |
|
| `POST` | `/api/mifare/dictionary-attack` | `readerType` (`classic1k` / `classic4k`), `variations`, optional `keysHex[]`, optional `sectorFirst` / `sectorLast` |
|
||||||
|
| `POST` | `/api/clone/capture` | Optional `mode` (`auto` / `classic` / `type2`), optional `keysHex[]`, `variations`, `maxPages` — returns a structured snapshot for supported tags |
|
||||||
|
| `POST` | `/api/clone/program` | `snapshot` plus optional `includeTrailers` / `includeLockPages` — programs a supported destination tag with safe skips by default |
|
||||||
|
| `POST` | `/api/nfc/target/status` | Returns whether PN532 target mode is active |
|
||||||
|
| `POST` | `/api/nfc/target/start` | Start target mode from semantic JSON fields or `rawParamsHex` |
|
||||||
|
| `POST` | `/api/nfc/target/recv` | Wait for initiator bytes and return status + payload hex |
|
||||||
|
| `POST` | `/api/nfc/target/send` | Send target response bytes back to the initiator |
|
||||||
|
| `POST` | `/api/nfc/target/stop` | Return PN532 to normal SAM mode |
|
||||||
| `POST` | `/api/nfc/emulate-raw` | `hex` — raw command bytes (e.g. **0x8C** sequences) |
|
| `POST` | `/api/nfc/emulate-raw` | `hex` — raw command bytes (e.g. **0x8C** sequences) |
|
||||||
| `POST` | `/api/ota` | **Not implemented** — returns **501** |
|
| `POST` | `/api/ota` | `url` (HTTPS), optional `reboot` — performs a real OTA update via `esp_https_ota` |
|
||||||
|
|
||||||
**WebSocket:** `GET /ws` — JSON text frames as above; send **`ping`**, receive **`pong`**.
|
**WebSocket:** `GET /ws` — JSON text frames as above; send **`ping`**, receive **`pong`**.
|
||||||
|
|
||||||
@@ -112,7 +123,7 @@ The SPA keeps the **last 5 000** WebSocket events in **sessionStorage**. **Dow
|
|||||||
|
|
||||||
### Card emulation (PN532 target mode)
|
### Card emulation (PN532 target mode)
|
||||||
|
|
||||||
**Emulate** sends raw **`TgInitAsTarget` (0x8C)** payloads via **`POST /api/nfc/emulate-raw`**. You own the bytes (see NXP **UM0701**). This is **experimental**; a bad sequence can leave the front end wedged until **power cycle**.
|
**Emulate** now has two layers: a higher-level **`/api/nfc/target/*`** subsystem for `TgInitAsTarget` / `TgGetData` / `TgSetData`, plus **`POST /api/nfc/emulate-raw`** when you want to drive the exact byte stream yourself. The structured path is safer; raw mode is still experimental and bad sequences can wedge the front end until re-init or power cycle.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -145,6 +156,10 @@ idf.py menuconfig # PN532 Host: SPI / I2C / UART + pins; Board indicators (RGB
|
|||||||
idf.py build flash monitor
|
idf.py build flash monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Common red PN532 module notes
|
||||||
|
|
||||||
|
The common red Elechouse-style PN532 board usually selects its bus with two onboard switches: **HSU = OFF/OFF**, **I2C = ON/OFF**, **SPI = OFF/ON**. HSU and I2C usually share header pins, and many boards ship in **HSU** by default, so a “dead” SPI/I2C setup is often just a switch mismatch rather than bad firmware.
|
||||||
|
|
||||||
From `firmware/`, with IDF already in your environment, you can also **`./flash.sh`** (uses **`ESPPORT`** or the first argument as the serial device).
|
From `firmware/`, with IDF already in your environment, you can also **`./flash.sh`** (uses **`ESPPORT`** or the first argument as the serial device).
|
||||||
|
|
||||||
See [docs/FLASHING.md](docs/FLASHING.md) and [docs/PINOUT.md](docs/PINOUT.md). Limitations and scope notes: [docs/LIMITATIONS.md](docs/LIMITATIONS.md).
|
See [docs/FLASHING.md](docs/FLASHING.md) and [docs/PINOUT.md](docs/PINOUT.md). Limitations and scope notes: [docs/LIMITATIONS.md](docs/LIMITATIONS.md).
|
||||||
|
|||||||
19
SECURITY.md
19
SECURITY.md
@@ -1,19 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This repository is maintained for authorized, lawful work only. Do not use code, firmware, payloads, scripts, or documentation from this project against systems, accounts, devices, networks, cards, readers, or services you do not own or do not have explicit permission to test.
|
|
||||||
|
|
||||||
## Reporting
|
|
||||||
|
|
||||||
Report security concerns privately to the maintainer. Do not open public issues containing live credentials, tokens, private captures, card data, target identifiers, exploit chains, or sensitive logs.
|
|
||||||
|
|
||||||
## Secrets And Data
|
|
||||||
|
|
||||||
- Do not commit `.env` files, API keys, Wi-Fi credentials, session cookies, private keys, dumps, captures, or personal data.
|
|
||||||
- Firmware binaries and captured artifacts must include provenance notes and SHA256 hashes before release.
|
|
||||||
- Generated dependency folders and build output belong outside Git unless there is a documented reason.
|
|
||||||
|
|
||||||
## Maintainer Rule
|
|
||||||
|
|
||||||
If a change increases misuse risk, narrows safety boundaries, or weakens provenance, it must be rejected or quarantined until documented.
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
The PN532 is a **hosted NFC controller**, not a low-level RF lab instrument. This project exposes PN532 features honestly:
|
The PN532 is a **hosted NFC controller**, not a low-level RF lab instrument. This project exposes PN532 features honestly:
|
||||||
|
|
||||||
- **ISO14443-B**: reader support exists with **chip-level caveats** (anticollision / stack-dependent behavior). No promises of full mobile/PICC coverage.
|
- **ISO14443-B**: reader support exists with **chip-level caveats** (anticollision / stack-dependent behavior). No promises of full mobile/PICC coverage.
|
||||||
- **Card emulation / TG modes**: PN532 firmware supports target commands; real-world mimicry depends on timing, UID size, and reader expectations — expose experimentally, not as “propable MIFARE magic.”
|
- **Card emulation / TG modes**: PN532 firmware supports target commands and this repo now exposes a structured target-mode API, but real-world mimicry still depends on timing, UID size, ATS/general bytes, and reader expectations. Treat it as a controllable subsystem, not magic full-card impersonation.
|
||||||
- **Key recovery**: on-device “brute force” at Proxmark scale is **infeasible**. The UI provides **dictionary / manual** key workflows.
|
- **Key recovery**: on-device “brute force” at Proxmark scale is **infeasible**. The UI provides **dictionary / manual** key workflows.
|
||||||
- **Signal / RF metrics**: diagnostics use **PN532 status / timings / retries**, not calibrated dBm.
|
- **Signal / RF metrics**: diagnostics use **PN532 status / timings / retries**, not calibrated dBm.
|
||||||
- **OTA via UI**: `POST /api/ota` is a **stub** (`501`) — ship OTA with `esp_https_ota` + signed images when you need production updates.
|
- **OTA via UI**: `POST /api/ota` now performs real HTTPS OTA with the ESP-IDF certificate bundle. For production fleets, add your own release signing, manifest control, and hardware validation gates before broad rollout.
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
# Maintenance
|
|
||||||
<!-- stewardship-standard: maintenance-v1 -->
|
|
||||||
|
|
||||||
## Stewardship Rules
|
|
||||||
|
|
||||||
- Keep generated files, build outputs, copied SDKs, and raw firmware binaries out of Git unless they are the source of truth.
|
|
||||||
- Keep credentials, tokens, dumps, private messages, session stores, and local machine paths out of commits.
|
|
||||||
- Prefer small commits with clear intent and a matching issue or release note.
|
|
||||||
- Preserve upstream attribution when code is copied, forked, or adapted.
|
|
||||||
|
|
||||||
## Routine Checks
|
|
||||||
|
|
||||||
- README still describes what the project does.
|
|
||||||
- Setup instructions still work.
|
|
||||||
- Security policy is accurate for the current risk level.
|
|
||||||
- Changelog records user-visible changes.
|
|
||||||
- License status is explicit.
|
|
||||||
|
|
||||||
## Hardware Gate
|
|
||||||
|
|
||||||
- Confirm exact board, module, and peripheral versions.
|
|
||||||
- Verify flashing and recovery steps on physical hardware.
|
|
||||||
- Attach firmware binaries through releases only, with SHA256 checksums.
|
|
||||||
@@ -20,3 +20,13 @@ The firmware Kconfig ships **example** GPIOs:
|
|||||||
## Transport selection
|
## Transport selection
|
||||||
|
|
||||||
Start with the bus your breakout is jumpered for — **I2C is often simplest** on ESP32 for bring-up; SPI may require **lower clock** initially (e.g. **100 kHz**).
|
Start with the bus your breakout is jumpered for — **I2C is often simplest** on ESP32 for bring-up; SPI may require **lower clock** initially (e.g. **100 kHz**).
|
||||||
|
|
||||||
|
## Common red module switch matrix
|
||||||
|
|
||||||
|
For the common Elechouse-style red PN532 board, the onboard two-position switch matrix is typically:
|
||||||
|
|
||||||
|
- **HSU**: `OFF/OFF`
|
||||||
|
- **I2C**: `ON/OFF`
|
||||||
|
- **SPI**: `OFF/ON`
|
||||||
|
|
||||||
|
Many of these boards boot in **HSU** by default, and the **I2C** and **HSU** labels often refer to the same physical header pins from opposite sides of the PCB. If the bus looks dead, check the switches before changing firmware.
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# Project Handoff
|
|
||||||
<!-- stewardship-standard: project-handoff-v1 -->
|
|
||||||
|
|
||||||
## What This Repo Needs From A Maintainer
|
|
||||||
|
|
||||||
- A one-paragraph project summary in README.md.
|
|
||||||
- Confirmed setup instructions.
|
|
||||||
- Confirmed license status.
|
|
||||||
- Confirmed provenance for imported code and binaries.
|
|
||||||
- A known-good verification command, test, build, flash, or demo path.
|
|
||||||
|
|
||||||
## Current Stewardship State
|
|
||||||
|
|
||||||
This repo has baseline governance files, wiki pages, issue templates, labels, milestones, and a readiness issue. The next maintainer should replace generic stewardship notes with project-specific facts.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Provenance Checklist
|
|
||||||
<!-- stewardship-standard: provenance-checklist-v1 -->
|
|
||||||
|
|
||||||
Use this before claiming ownership or publishing artifacts.
|
|
||||||
|
|
||||||
- [ ] Identify original upstream source, if any.
|
|
||||||
- [ ] Record fork URL, commit, tag, or archive source.
|
|
||||||
- [ ] Preserve third-party notices and license files.
|
|
||||||
- [ ] Separate local patches from imported code where practical.
|
|
||||||
- [ ] Record binary build inputs, toolchain versions, and source commit.
|
|
||||||
- [ ] Publish checksums for release assets.
|
|
||||||
- [ ] Mark unknown-origin content as blocked until resolved.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Release Process
|
|
||||||
<!-- stewardship-standard: release-process-v1 -->
|
|
||||||
|
|
||||||
## Before Tagging
|
|
||||||
|
|
||||||
- Confirm the default branch builds, runs, or flashes as documented.
|
|
||||||
- Confirm no secrets, private data, generated dependency trees, or raw binaries are accidentally committed.
|
|
||||||
- Confirm license and upstream provenance are documented.
|
|
||||||
- Update CHANGELOG.md.
|
|
||||||
- Attach binaries only as release assets with SHA256 checksums and source commit references.
|
|
||||||
|
|
||||||
## Release Notes
|
|
||||||
|
|
||||||
Include:
|
|
||||||
|
|
||||||
- Purpose of the release.
|
|
||||||
- Commit hash or tag.
|
|
||||||
- Build environment.
|
|
||||||
- Known limitations.
|
|
||||||
- Verification performed.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Roadmap
|
|
||||||
<!-- stewardship-standard: roadmap-v1 -->
|
|
||||||
|
|
||||||
## Now
|
|
||||||
|
|
||||||
- Confirm the project purpose in the README.
|
|
||||||
- Confirm build, run, or flash instructions on a clean machine.
|
|
||||||
- Classify license status and upstream provenance.
|
|
||||||
- Close the stewardship readiness checklist issue.
|
|
||||||
|
|
||||||
## Next
|
|
||||||
|
|
||||||
- Add project-specific tests or verification steps.
|
|
||||||
- Publish the first verified release only after provenance and security review.
|
|
||||||
- Replace placeholder wiki notes with project-specific architecture or hardware details.
|
|
||||||
|
|
||||||
## Later
|
|
||||||
|
|
||||||
- Add examples, screenshots, wiring diagrams, or demo media where useful.
|
|
||||||
- Decide whether duplicate or experimental branches should be archived.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Security Review
|
|
||||||
<!-- stewardship-standard: security-review-v1 -->
|
|
||||||
|
|
||||||
## Required Checks
|
|
||||||
|
|
||||||
- [ ] No credentials, tokens, cookies, API keys, private keys, or session files.
|
|
||||||
- [ ] No private user data, dumps, card data, logs, or captures that should not be stored.
|
|
||||||
- [ ] No copied dependency trees where package managers or SDK installers should be used instead.
|
|
||||||
- [ ] No unexplained binaries in source history.
|
|
||||||
- [ ] Risky behavior is documented and scoped to authorized lab use.
|
|
||||||
|
|
||||||
## Release Gate
|
|
||||||
|
|
||||||
A release is blocked until the checklist is complete or a maintainer explicitly records why the item does not apply.
|
|
||||||
BIN
firmware/.cache/clangd/index/app_net.c.8976D557EC300024.idx
Normal file
BIN
firmware/.cache/clangd/index/app_net.c.8976D557EC300024.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/app_net.h.379D685E6D39744F.idx
Normal file
BIN
firmware/.cache/clangd/index/app_net.h.379D685E6D39744F.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
firmware/.cache/clangd/index/jobs.c.32B64D759D53BD2C.idx
Normal file
BIN
firmware/.cache/clangd/index/jobs.c.32B64D759D53BD2C.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/jobs.h.D44D4CC819830F66.idx
Normal file
BIN
firmware/.cache/clangd/index/jobs.h.D44D4CC819830F66.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/led_strip.h.E0D4D2D09150BA23.idx
Normal file
BIN
firmware/.cache/clangd/index/led_strip.h.E0D4D2D09150BA23.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
firmware/.cache/clangd/index/main.c.B516995A6C0A4F7B.idx
Normal file
BIN
firmware/.cache/clangd/index/main.c.B516995A6C0A4F7B.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns.h.F79EBFE9494D08D5.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns.h.F79EBFE9494D08D5.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_browser.c.844EE27ECDD370B3.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_browser.c.844EE27ECDD370B3.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_browser.h.4C9C0DA24E273588.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_browser.h.4C9C0DA24E273588.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_console.c.4A45177451EC0E51.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_console.c.4A45177451EC0E51.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_debug.c.AA7109521C3B6657.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_debug.c.AA7109521C3B6657.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_debug.h.1967329A83A6D539.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_debug.h.1967329A83A6D539.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_netif.c.15DF3D448C6EF1FA.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_netif.c.15DF3D448C6EF1FA.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_netif.h.5B3BB947FC74DB09.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_netif.h.5B3BB947FC74DB09.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_pcb.c.7AEB4688ECE9D08A.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_pcb.c.7AEB4688ECE9D08A.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_pcb.h.95AD3ECE0DDA239D.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_pcb.h.95AD3ECE0DDA239D.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_private.h.F18457B0D1445EE2.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_private.h.F18457B0D1445EE2.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_querier.c.0578019256BDEEA7.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_querier.c.0578019256BDEEA7.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_querier.h.2D0ECFD0DB6BD816.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_querier.h.2D0ECFD0DB6BD816.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_receive.c.B5E60DD8BC9F0846.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_receive.c.B5E60DD8BC9F0846.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_receive.h.7D5DBD82E2E64FE4.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_receive.h.7D5DBD82E2E64FE4.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_send.c.59BD33003D102D61.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_send.c.59BD33003D102D61.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_send.h.4F6F65E6B9467994.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_send.h.4F6F65E6B9467994.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_service.c.3ED53626DAC5EF3D.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_service.c.3ED53626DAC5EF3D.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_service.h.C2CF3A06E06F9BB7.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_service.h.C2CF3A06E06F9BB7.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_utils.c.65695015EB907823.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_utils.c.65695015EB907823.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/mdns_utils.h.F6BBFFB10960D7CE.idx
Normal file
BIN
firmware/.cache/clangd/index/mdns_utils.h.F6BBFFB10960D7CE.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/nfc_brute.c.5CE5CA4075A21387.idx
Normal file
BIN
firmware/.cache/clangd/index/nfc_brute.c.5CE5CA4075A21387.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/nfc_brute.h.357ACE396DF621D3.idx
Normal file
BIN
firmware/.cache/clangd/index/nfc_brute.h.357ACE396DF621D3.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/nfc_deep.c.84695751986FA510.idx
Normal file
BIN
firmware/.cache/clangd/index/nfc_deep.c.84695751986FA510.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/nfc_deep.h.CD94CBA950807F37.idx
Normal file
BIN
firmware/.cache/clangd/index/nfc_deep.h.CD94CBA950807F37.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/nfc_engine.c.DF9FC1E131D8D184.idx
Normal file
BIN
firmware/.cache/clangd/index/nfc_engine.c.DF9FC1E131D8D184.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/nfc_engine.h.364F51431207B3F8.idx
Normal file
BIN
firmware/.cache/clangd/index/nfc_engine.h.364F51431207B3F8.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/pn532_core.c.980577ACBAE216BC.idx
Normal file
BIN
firmware/.cache/clangd/index/pn532_core.c.980577ACBAE216BC.idx
Normal file
Binary file not shown.
BIN
firmware/.cache/clangd/index/pn532_core.h.9A8DA7CCA3043FA0.idx
Normal file
BIN
firmware/.cache/clangd/index/pn532_core.h.9A8DA7CCA3043FA0.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,5 +2,5 @@ idf_component_register(
|
|||||||
SRCS "app_net.c"
|
SRCS "app_net.c"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
REQUIRES esp_http_server http_parser esp_wifi esp_netif nvs_flash mdns esp_timer
|
REQUIRES esp_http_server http_parser esp_wifi esp_netif nvs_flash mdns esp_timer
|
||||||
json spiffs vfs freertos nfc_engine pn532_host
|
json spiffs vfs freertos nfc_engine pn532_host esp_https_ota app_update mbedtls
|
||||||
)
|
)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,12 +26,26 @@ typedef struct {
|
|||||||
bool key_b;
|
bool key_b;
|
||||||
} nfc_mifare_key_t;
|
} nfc_mifare_key_t;
|
||||||
|
|
||||||
|
/** Legacy: full init including transport — aborts on failure via caller's ESP_ERROR_CHECK. */
|
||||||
esp_err_t nfc_engine_init(void);
|
esp_err_t nfc_engine_init(void);
|
||||||
|
|
||||||
|
/** Non-fatal first-boot init. Returns true if PN532 is present and configured. */
|
||||||
|
bool nfc_engine_try_init(void);
|
||||||
|
|
||||||
|
/** Soft re-attach after transport is already open — skips bus re-init, tries chip commands.
|
||||||
|
* Call from a background retry loop; always safe to call even if already ready. */
|
||||||
|
bool nfc_engine_try_reattach(void);
|
||||||
|
|
||||||
|
/** Returns true when the PN532 was successfully initialised (or re-attached). */
|
||||||
|
bool nfc_engine_is_ready(void);
|
||||||
|
|
||||||
esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out);
|
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(const nfc_tag_info_t *tag);
|
||||||
bool nfc_tag_is_mifare_classic_4k(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);
|
bool nfc_tag_is_type2(const nfc_tag_info_t *tag);
|
||||||
|
int nfc_mifare_sector_count(const nfc_tag_info_t *tag);
|
||||||
|
bool nfc_mifare_sector_layout(const nfc_tag_info_t *tag, int sector, int *first_block, int *num_blocks,
|
||||||
|
uint8_t *trailer_block);
|
||||||
|
|
||||||
esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block_no,
|
esp_err_t nfc_mifare_authenticate_block(const nfc_tag_info_t *tag, uint8_t block_no,
|
||||||
const nfc_mifare_key_t *key);
|
const nfc_mifare_key_t *key);
|
||||||
@@ -42,6 +56,7 @@ esp_err_t nfc_ultralight_read_page(uint8_t page, uint8_t data[4]);
|
|||||||
esp_err_t nfc_ultralight_write_page(uint8_t page, const uint8_t data[4]);
|
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);
|
esp_err_t nfc_ul_fast_read(uint8_t start_page, uint8_t *out, size_t out_max, size_t *got);
|
||||||
|
esp_err_t nfc_type2_get_version(uint8_t version[8]);
|
||||||
|
|
||||||
/** Build JSON snapshot of last seen tag + optional blocks (caller frees cJSON). */
|
/** Build JSON snapshot of last seen tag + optional blocks (caller frees cJSON). */
|
||||||
cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag);
|
cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag);
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
|
|||||||
attempts++;
|
attempts++;
|
||||||
if (try_key_on_trailer(tag, trailer, trial, false)) {
|
if (try_key_on_trailer(tag, trailer, trial, false)) {
|
||||||
if (!add_sector_hit(hits, sec, trial, "A")) {
|
if (!add_sector_hit(hits, sec, trial, "A")) {
|
||||||
|
cJSON_Delete(hits);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -178,6 +179,7 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
|
|||||||
}
|
}
|
||||||
if (try_key_on_trailer(tag, trailer, trial, true)) {
|
if (try_key_on_trailer(tag, trailer, trial, true)) {
|
||||||
if (!add_sector_hit(hits, sec, trial, "B")) {
|
if (!add_sector_hit(hits, sec, trial, "B")) {
|
||||||
|
cJSON_Delete(hits);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -202,6 +204,7 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
|
|||||||
attempts++;
|
attempts++;
|
||||||
if (try_key_on_trailer(tag, trailer, trial, false)) {
|
if (try_key_on_trailer(tag, trailer, trial, false)) {
|
||||||
if (!add_sector_hit(hits, sec, trial, "A")) {
|
if (!add_sector_hit(hits, sec, trial, "A")) {
|
||||||
|
cJSON_Delete(hits);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -210,6 +213,7 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
|
|||||||
}
|
}
|
||||||
if (try_key_on_trailer(tag, trailer, trial, true)) {
|
if (try_key_on_trailer(tag, trailer, trial, true)) {
|
||||||
if (!add_sector_hit(hits, sec, trial, "B")) {
|
if (!add_sector_hit(hits, sec, trial, "B")) {
|
||||||
|
cJSON_Delete(hits);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -226,6 +230,7 @@ cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, u
|
|||||||
if (!got) {
|
if (!got) {
|
||||||
cJSON *h = cJSON_CreateObject();
|
cJSON *h = cJSON_CreateObject();
|
||||||
if (!h) {
|
if (!h) {
|
||||||
|
cJSON_Delete(hits);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
static const char *TAG = "nfc_engine";
|
static const char *TAG = "nfc_engine";
|
||||||
|
|
||||||
static uint8_t s_tg = 1;
|
static uint8_t s_tg = 1;
|
||||||
|
static volatile bool s_pn532_ready = false;
|
||||||
|
|
||||||
|
bool nfc_engine_is_ready(void) { return s_pn532_ready; }
|
||||||
|
|
||||||
static void hint_type(nfc_tag_info_t *t)
|
static void hint_type(nfc_tag_info_t *t)
|
||||||
{
|
{
|
||||||
@@ -18,7 +21,12 @@ static void hint_type(nfc_tag_info_t *t)
|
|||||||
t->type_hint = 1;
|
t->type_hint = 1;
|
||||||
break;
|
break;
|
||||||
case 0x00:
|
case 0x00:
|
||||||
t->type_hint = 2;
|
/* Typical Type 2 inventory tuple is ATQA 0x0044 and 7-byte UID. */
|
||||||
|
if ((t->atqa == 0x4400 || t->atqa == 0x0044) && t->uid_len == 7) {
|
||||||
|
t->type_hint = 2;
|
||||||
|
} else {
|
||||||
|
t->type_hint = 0;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
t->type_hint = 0;
|
t->type_hint = 0;
|
||||||
@@ -41,6 +49,45 @@ bool nfc_tag_is_type2(const nfc_tag_info_t *tag)
|
|||||||
return tag && tag->type_hint == 2;
|
return tag && tag->type_hint == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int nfc_mifare_sector_count(const nfc_tag_info_t *tag)
|
||||||
|
{
|
||||||
|
if (!nfc_tag_is_mifare_classic(tag)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return nfc_tag_is_mifare_classic_4k(tag) ? 40 : 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_mifare_sector_layout(const nfc_tag_info_t *tag, int sector, int *first_block, int *num_blocks,
|
||||||
|
uint8_t *trailer_block)
|
||||||
|
{
|
||||||
|
if (!nfc_tag_is_mifare_classic(tag) || !first_block || !num_blocks || !trailer_block) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!nfc_tag_is_mifare_classic_4k(tag)) {
|
||||||
|
if (sector < 0 || sector > 15) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*first_block = sector * 4;
|
||||||
|
*num_blocks = 4;
|
||||||
|
*trailer_block = (uint8_t)(sector * 4 + 3);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sector < 0 || sector > 39) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sector <= 31) {
|
||||||
|
*first_block = sector * 4;
|
||||||
|
*num_blocks = 4;
|
||||||
|
*trailer_block = (uint8_t)(sector * 4 + 3);
|
||||||
|
} else {
|
||||||
|
int r = sector - 32;
|
||||||
|
*first_block = 128 + r * 16;
|
||||||
|
*num_blocks = 16;
|
||||||
|
*trailer_block = (uint8_t)(128 + r * 16 + 15);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t nfc_engine_init(void)
|
esp_err_t nfc_engine_init(void)
|
||||||
{
|
{
|
||||||
esp_err_t e = pn532_core_init();
|
esp_err_t e = pn532_core_init();
|
||||||
@@ -58,9 +105,51 @@ esp_err_t nfc_engine_init(void)
|
|||||||
if (pn532_rf_max_retries() != ESP_OK) {
|
if (pn532_rf_max_retries() != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "RF max retries config failed");
|
ESP_LOGW(TAG, "RF max retries config failed");
|
||||||
}
|
}
|
||||||
|
s_pn532_ready = true;
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool nfc_engine_try_init(void)
|
||||||
|
{
|
||||||
|
esp_err_t e = pn532_core_init();
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "PN532 not found (%s) — AP running, will retry every 5 s", esp_err_to_name(e));
|
||||||
|
s_pn532_ready = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
e = pn532_sam_config_normal();
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "SAM config: %s", esp_err_to_name(e));
|
||||||
|
}
|
||||||
|
uint8_t ic = 0, hi = 0, lo = 0;
|
||||||
|
if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "PN532 fw ic=0x%02x %u.%u", ic, hi, lo);
|
||||||
|
}
|
||||||
|
if (pn532_rf_max_retries() != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "RF max retries config failed");
|
||||||
|
}
|
||||||
|
s_pn532_ready = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nfc_engine_try_reattach(void)
|
||||||
|
{
|
||||||
|
/* Transport already open — just ping the chip and re-apply configuration. */
|
||||||
|
esp_err_t e = pn532_sam_config_normal();
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t ic = 0, hi = 0, lo = 0;
|
||||||
|
if (pn532_get_firmware_version(&ic, &hi, &lo) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "PN532 reattached ic=0x%02x %u.%u", ic, hi, lo);
|
||||||
|
}
|
||||||
|
if (pn532_rf_max_retries() != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "RF max retries config failed");
|
||||||
|
}
|
||||||
|
s_pn532_ready = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out)
|
esp_err_t nfc_poll_passive_target(nfc_tag_info_t *out)
|
||||||
{
|
{
|
||||||
if (!out) {
|
if (!out) {
|
||||||
@@ -248,6 +337,25 @@ esp_err_t nfc_ul_fast_read(uint8_t start_page, uint8_t *out, size_t out_max, siz
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t nfc_type2_get_version(uint8_t version[8])
|
||||||
|
{
|
||||||
|
if (!version) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
uint8_t d[] = {0x60};
|
||||||
|
uint8_t resp[32];
|
||||||
|
size_t rlen = 0;
|
||||||
|
esp_err_t e = in_data_tg(d, sizeof(d), resp, sizeof(resp), &rlen);
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if (rlen < 2 + 8 || resp[0] != (uint8_t)(PN532_CMD_INDATAEXCHANGE + 1) || resp[1] != 0x00) {
|
||||||
|
return ESP_ERR_INVALID_RESPONSE;
|
||||||
|
}
|
||||||
|
memcpy(version, resp + 2, 8);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag)
|
cJSON *nfc_tag_to_json(const nfc_tag_info_t *tag)
|
||||||
{
|
{
|
||||||
if (!tag || tag->uid_len > NFC_MAX_UID_LEN) {
|
if (!tag || tag->uid_len > NFC_MAX_UID_LEN) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "nfc_engine/session_capture.h"
|
#include "nfc_engine/session_capture.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/semphr.h"
|
#include "freertos/semphr.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define SESSION_CAPTURE_BYTES (48 * 1024)
|
#define SESSION_CAPTURE_BYTES (48 * 1024)
|
||||||
@@ -15,6 +16,10 @@ static SemaphoreHandle_t s_mu;
|
|||||||
void session_capture_init(void)
|
void session_capture_init(void)
|
||||||
{
|
{
|
||||||
s_mu = xSemaphoreCreateMutex();
|
s_mu = xSemaphoreCreateMutex();
|
||||||
|
if (!s_mu) {
|
||||||
|
ESP_LOGE("session_capture", "mutex create failed — aborting");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
session_capture_clear();
|
session_capture_clear();
|
||||||
s_deep = false;
|
s_deep = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,20 @@ esp_err_t pn532_in_communicate_thru(const uint8_t *data, size_t data_len,
|
|||||||
uint8_t *response, size_t response_max,
|
uint8_t *response, size_t response_max,
|
||||||
size_t *response_len);
|
size_t *response_len);
|
||||||
|
|
||||||
|
/** Enter PN532 target mode using TgInitAsTarget parameters (mode + target descriptors + optional GT). */
|
||||||
|
esp_err_t pn532_tg_init_as_target(const uint8_t *params, size_t params_len,
|
||||||
|
uint8_t *response, size_t response_max,
|
||||||
|
size_t *response_len, int timeout_ms);
|
||||||
|
|
||||||
|
/** Receive bytes from the initiator while PN532 is in target mode. */
|
||||||
|
esp_err_t pn532_tg_get_data(uint8_t *response, size_t response_max,
|
||||||
|
size_t *response_len, int timeout_ms);
|
||||||
|
|
||||||
|
/** Send bytes back to the initiator while PN532 is in target mode. */
|
||||||
|
esp_err_t pn532_tg_set_data(const uint8_t *data, size_t data_len,
|
||||||
|
uint8_t *response, size_t response_max,
|
||||||
|
size_t *response_len, int timeout_ms);
|
||||||
|
|
||||||
/** RF field on/off via RFConfiguration (0x32) item 0x01, RF field */
|
/** RF field on/off via RFConfiguration (0x32) item 0x01, RF field */
|
||||||
esp_err_t pn532_rf_field(bool on);
|
esp_err_t pn532_rf_field(bool on);
|
||||||
|
|
||||||
|
|||||||
@@ -190,3 +190,81 @@ esp_err_t pn532_in_communicate_thru(const uint8_t *data, size_t data_len, uint8_
|
|||||||
*response_len = payload_len;
|
*response_len = payload_len;
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t pn532_tg_init_as_target(const uint8_t *params, size_t params_len, uint8_t *response,
|
||||||
|
size_t response_max, size_t *response_len, int timeout_ms)
|
||||||
|
{
|
||||||
|
if (!params || !response || !response_len || params_len == 0 || params_len > PN532_EEPROM_MAX_CMD_PAYLOAD - 1) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
uint8_t buf[PN532_EEPROM_MAX_CMD_PAYLOAD];
|
||||||
|
buf[0] = PN532_CMD_TGINITASTARGET;
|
||||||
|
memcpy(buf + 1, params, params_len);
|
||||||
|
uint8_t raw[PN532_EEPROM_MAX_CMD_PAYLOAD];
|
||||||
|
size_t raw_len = 0;
|
||||||
|
esp_err_t e = pn532_send_cmd(buf, 1 + params_len, raw, sizeof(raw), &raw_len, timeout_ms);
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if (raw_len < 2 || raw[0] != (uint8_t)(PN532_CMD_TGINITASTARGET + 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_tg_get_data(uint8_t *response, size_t response_max, size_t *response_len, int timeout_ms)
|
||||||
|
{
|
||||||
|
if (!response || !response_len) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
uint8_t cmd = PN532_CMD_TGGETDATA;
|
||||||
|
uint8_t raw[PN532_EEPROM_MAX_CMD_PAYLOAD];
|
||||||
|
size_t raw_len = 0;
|
||||||
|
esp_err_t e = pn532_send_cmd(&cmd, 1, raw, sizeof(raw), &raw_len, timeout_ms);
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if (raw_len < 2 || raw[0] != (uint8_t)(PN532_CMD_TGGETDATA + 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_tg_set_data(const uint8_t *data, size_t data_len, uint8_t *response, size_t response_max,
|
||||||
|
size_t *response_len, int timeout_ms)
|
||||||
|
{
|
||||||
|
if (!data || !response || !response_len || data_len == 0 || data_len > PN532_EEPROM_MAX_CMD_PAYLOAD - 1) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
uint8_t buf[PN532_EEPROM_MAX_CMD_PAYLOAD];
|
||||||
|
buf[0] = PN532_CMD_TGSETDATA;
|
||||||
|
memcpy(buf + 1, data, data_len);
|
||||||
|
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, timeout_ms);
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if (raw_len < 2 || raw[0] != (uint8_t)(PN532_CMD_TGSETDATA + 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ esp_err_t pn532_transport_exchange(const uint8_t *tx_body, size_t tx_body_len, u
|
|||||||
#if defined(CONFIG_PN532_TRANSPORT_SPI)
|
#if defined(CONFIG_PN532_TRANSPORT_SPI)
|
||||||
ESP_RETURN_ON_ERROR(spi_write_frame(frame, pos), TAG, "spi wr");
|
ESP_RETURN_ON_ERROR(spi_write_frame(frame, pos), TAG, "spi wr");
|
||||||
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
|
#elif defined(CONFIG_PN532_TRANSPORT_I2C)
|
||||||
i2c_wakeup();
|
ESP_RETURN_ON_ERROR(i2c_wakeup(), TAG, "i2c wake");
|
||||||
ESP_RETURN_ON_ERROR(i2c_write_raw(frame, pos), TAG, "i2c wr");
|
ESP_RETURN_ON_ERROR(i2c_write_raw(frame, pos), TAG, "i2c wr");
|
||||||
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
|
#elif defined(CONFIG_PN532_TRANSPORT_HSU)
|
||||||
ESP_RETURN_ON_ERROR(hsu_write_raw(frame, pos), TAG, "hsu wr");
|
ESP_RETURN_ON_ERROR(hsu_write_raw(frame, pos), TAG, "hsu wr");
|
||||||
|
|||||||
1
firmware/data/assets/index-Bb0xTRCn.css
Normal file
1
firmware/data/assets/index-Bb0xTRCn.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
|||||||
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"
|
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"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<script type="module" crossorigin src="/assets/index-9oJp152C.js"></script>
|
<script type="module" crossorigin src="/assets/index-BmIGATlK.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-hoMg1Qkq.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Bb0xTRCn.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-bubble-950 text-slate-200 antialiased selection:bg-bubble-accent/40 selection:text-bubble-950">
|
<body class="bg-bubble-950 text-slate-200 antialiased selection:bg-bubble-accent/40 selection:text-bubble-950">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
#include "board_rgb_off.h"
|
#include "board_rgb_off.h"
|
||||||
#include "nfc_engine/nfc_engine.h"
|
#include "nfc_engine/nfc_engine.h"
|
||||||
#include "nfc_engine/session_capture.h"
|
#include "nfc_engine/session_capture.h"
|
||||||
@@ -8,10 +9,15 @@ static const char *TAG = "main";
|
|||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
|
(void)esp_ota_mark_app_valid_cancel_rollback();
|
||||||
board_rgb_led_quiet();
|
board_rgb_led_quiet();
|
||||||
ESP_LOGI(TAG, "PN532 NFC Toolkit starting");
|
ESP_LOGI(TAG, "PN532 NFC Toolkit starting");
|
||||||
ESP_ERROR_CHECK(nfc_engine_init());
|
nfc_engine_try_init(); /* non-fatal: logs warning if PN532 absent, AP starts regardless */
|
||||||
session_capture_init();
|
session_capture_init();
|
||||||
ESP_ERROR_CHECK(app_net_init());
|
ESP_ERROR_CHECK(app_net_init());
|
||||||
ESP_LOGI(TAG, "Open AP SSID PN532-Toolkit — http://192.168.4.1");
|
if (nfc_engine_is_ready()) {
|
||||||
|
ESP_LOGI(TAG, "PN532 ready · AP SSID PN532-Toolkit → http://192.168.4.1");
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "PN532 not found at boot — AP running, retrying · http://192.168.4.1");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,3 +31,6 @@ CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE=y
|
|||||||
|
|
||||||
# mDNS
|
# mDNS
|
||||||
CONFIG_MDNS_MAX_SERVICES=10
|
CONFIG_MDNS_MAX_SERVICES=10
|
||||||
|
|
||||||
|
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||||
|
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { NavLink, Route, Routes } from "react-router-dom";
|
|||||||
import BrowserLogBar from "./BrowserLogBar";
|
import BrowserLogBar from "./BrowserLogBar";
|
||||||
import FlashBackdrop from "./FlashBackdrop";
|
import FlashBackdrop from "./FlashBackdrop";
|
||||||
import ScanCashFlourish from "./ScanCashFlourish";
|
import ScanCashFlourish from "./ScanCashFlourish";
|
||||||
import { NfcWsProvider } from "./NfcWsContext";
|
import { NfcWsProvider, useNfcWs } from "./NfcWsContext";
|
||||||
import { ToastHost } from "./toast";
|
import { ToastHost } from "./toast";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import ReadAnalyze from "./pages/ReadAnalyze";
|
import ReadAnalyze from "./pages/ReadAnalyze";
|
||||||
@@ -17,6 +17,29 @@ import Brute from "./pages/Brute";
|
|||||||
import Emulate from "./pages/Emulate";
|
import Emulate from "./pages/Emulate";
|
||||||
import KeyLab from "./pages/KeyLab";
|
import KeyLab from "./pages/KeyLab";
|
||||||
|
|
||||||
|
function HeaderBadge() {
|
||||||
|
const { wsOk, pn532Connected } = useNfcWs();
|
||||||
|
return (
|
||||||
|
<span className="hidden font-mono text-[9px] text-bubble-mint/40 md:inline lg:text-[10px]">
|
||||||
|
<span className={wsOk ? "text-bubble-accent/90" : "animate-pulse text-bubble-rose/60"}>●</span>{" "}
|
||||||
|
RF_STACK{" "}
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
!wsOk
|
||||||
|
? "animate-pulse text-bubble-rose/80"
|
||||||
|
: pn532Connected
|
||||||
|
? "font-bold text-bubble-mint"
|
||||||
|
: "animate-pulse text-amber-400"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!wsOk ? "WAIT" : pn532Connected ? "LIVE" : "NO RF"}
|
||||||
|
</span>
|
||||||
|
<span className="mx-1.5 text-bubble-mint/25">│</span>
|
||||||
|
<span className="text-bubble-mint/50">ws://stream</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const nav = [
|
const nav = [
|
||||||
["/", "Dash"],
|
["/", "Dash"],
|
||||||
["/capture", "Read-all"],
|
["/capture", "Read-all"],
|
||||||
@@ -70,12 +93,7 @@ export default function App() {
|
|||||||
MAXIMAL
|
MAXIMAL
|
||||||
</span>
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<span className="hidden font-mono text-[9px] text-bubble-mint/40 md:inline lg:text-[10px]">
|
<HeaderBadge />
|
||||||
<span className="text-bubble-accent/90">●</span> RF_STACK{" "}
|
|
||||||
<span className="text-bubble-rose/80">LIVE</span>
|
|
||||||
<span className="mx-1.5 text-bubble-mint/25">│</span>
|
|
||||||
<span className="text-bubble-mint/50">ws://stream</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<nav className="flex max-w-full flex-wrap justify-end gap-1 text-[10px] font-mono sm:gap-1.5 sm:text-[11px]">
|
<nav className="flex max-w-full flex-wrap justify-end gap-1 text-[10px] font-mono sm:gap-1.5 sm:text-[11px]">
|
||||||
{nav.map(([to, label]) => (
|
{nav.map(([to, label]) => (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import type { Tag } from "./api";
|
import { apiGet, type Status, type Tag } from "./api";
|
||||||
import { useToolkitWs } from "./useWebSocket";
|
import { useToolkitWs } from "./useWebSocket";
|
||||||
|
|
||||||
export type BrowserLogEntry = {
|
export type BrowserLogEntry = {
|
||||||
@@ -12,6 +12,7 @@ export type CashVariant = "tag" | "vault";
|
|||||||
|
|
||||||
type NfcCtx = {
|
type NfcCtx = {
|
||||||
wsOk: boolean;
|
wsOk: boolean;
|
||||||
|
pn532Connected: boolean;
|
||||||
lastTag: Tag | null;
|
lastTag: Tag | null;
|
||||||
tagPresent: boolean;
|
tagPresent: boolean;
|
||||||
log: BrowserLogEntry[];
|
log: BrowserLogEntry[];
|
||||||
@@ -89,6 +90,7 @@ function isCaptureRecorded(payload: unknown): boolean {
|
|||||||
export function NfcWsProvider({ children }: { children: React.ReactNode }) {
|
export function NfcWsProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [lastTag, setLastTag] = useState<Tag | null>(null);
|
const [lastTag, setLastTag] = useState<Tag | null>(null);
|
||||||
const [tagPresent, setTagPresent] = useState(false);
|
const [tagPresent, setTagPresent] = useState(false);
|
||||||
|
const [pn532Connected, setPn532Connected] = useState(false);
|
||||||
const [log, setLog] = useState<BrowserLogEntry[]>(() => loadPersisted());
|
const [log, setLog] = useState<BrowserLogEntry[]>(() => loadPersisted());
|
||||||
const [cashWave, setCashWave] = useState(0);
|
const [cashWave, setCashWave] = useState(0);
|
||||||
const [cashVariant, setCashVariant] = useState<CashVariant>("tag");
|
const [cashVariant, setCashVariant] = useState<CashVariant>("tag");
|
||||||
@@ -121,6 +123,11 @@ export function NfcWsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
} else if (ch === "capture" && isCaptureRecorded(o.payload)) {
|
} else if (ch === "capture" && isCaptureRecorded(o.payload)) {
|
||||||
bump("vault");
|
bump("vault");
|
||||||
|
} else if (ch === "pn532") {
|
||||||
|
const p = o.payload as { connected?: boolean } | undefined;
|
||||||
|
if (typeof p?.connected === "boolean") {
|
||||||
|
setPn532Connected(p.connected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
@@ -131,6 +138,19 @@ export function NfcWsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const wsOk = useToolkitWs(onMsg);
|
const wsOk = useToolkitWs(onMsg);
|
||||||
|
|
||||||
|
/* Fetch initial PN532 state as soon as WS connects (avoids waiting for first broadcast). */
|
||||||
|
useEffect(() => {
|
||||||
|
if (wsOk) {
|
||||||
|
apiGet<Status>("/api/status")
|
||||||
|
.then((s) => {
|
||||||
|
if (typeof s.pn532Connected === "boolean") {
|
||||||
|
setPn532Connected(s.pn532Connected);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => { /* silently ignore — badge will update on next WS event */ });
|
||||||
|
}
|
||||||
|
}, [wsOk]);
|
||||||
|
|
||||||
const applyScanPoll = useCallback(
|
const applyScanPoll = useCallback(
|
||||||
(present: boolean, tag?: Tag) => {
|
(present: boolean, tag?: Tag) => {
|
||||||
if (!present) {
|
if (!present) {
|
||||||
@@ -165,6 +185,7 @@ export function NfcWsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const v = useMemo(
|
const v = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
wsOk,
|
wsOk,
|
||||||
|
pn532Connected,
|
||||||
lastTag,
|
lastTag,
|
||||||
tagPresent,
|
tagPresent,
|
||||||
log,
|
log,
|
||||||
@@ -174,7 +195,7 @@ export function NfcWsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
exportBrowserLog,
|
exportBrowserLog,
|
||||||
applyScanPoll,
|
applyScanPoll,
|
||||||
}),
|
}),
|
||||||
[wsOk, lastTag, tagPresent, log, cashWave, cashVariant, clearBrowserLog, exportBrowserLog, applyScanPoll],
|
[wsOk, pn532Connected, lastTag, tagPresent, log, cashWave, cashVariant, clearBrowserLog, exportBrowserLog, applyScanPoll],
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Ctx.Provider value={v}>{children}</Ctx.Provider>;
|
return <Ctx.Provider value={v}>{children}</Ctx.Provider>;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export type Status = {
|
|||||||
uptimeMs: number;
|
uptimeMs: number;
|
||||||
freeHeap: number;
|
freeHeap: number;
|
||||||
wifiMode: number;
|
wifiMode: number;
|
||||||
|
pn532Connected: boolean;
|
||||||
pn532?: { ic: number; fwHi: number; fwLo: number };
|
pn532?: { ic: number; fwHi: number; fwLo: number };
|
||||||
scanning: boolean;
|
scanning: boolean;
|
||||||
session?: SessionInfo;
|
session?: SessionInfo;
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ export default function Brute() {
|
|||||||
const keysHex = extraKeys
|
const keysHex = extraKeys
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.map((l) => l.replace(/\s/g, "").toUpperCase())
|
.map((l) => l.replace(/\s/g, "").toUpperCase())
|
||||||
.filter((l) => l.length === 12);
|
.filter((l) => l.length === 12 && /^[0-9A-F]+$/.test(l));
|
||||||
|
const invalidCount = extraKeys
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((l) => l.replace(/\s/g, ""))
|
||||||
|
.filter((l) => l.length > 0 && (l.length !== 12 || !/^[0-9A-Fa-f]+$/.test(l))).length;
|
||||||
|
if (invalidCount > 0) {
|
||||||
|
toast(`${invalidCount} line(s) not valid 12-hex keys — skipped, firmware built-ins still run`);
|
||||||
|
}
|
||||||
const body = {
|
const body = {
|
||||||
readerType: reader,
|
readerType: reader,
|
||||||
variations,
|
variations,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export default function Capture() {
|
|||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
apiGet<Status>("/api/status")
|
apiGet<Status>("/api/status")
|
||||||
.then(setSt)
|
.then(setSt)
|
||||||
.catch(() => {});
|
.catch(() => toast("Status unreachable", "err"));
|
||||||
}, []);
|
}, [toast]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh();
|
refresh();
|
||||||
|
|||||||
@@ -152,9 +152,22 @@ export default function Dashboard() {
|
|||||||
</li>
|
</li>
|
||||||
<li className="flex justify-between">
|
<li className="flex justify-between">
|
||||||
<span className="text-slate-500">PN532</span>
|
<span className="text-slate-500">PN532</span>
|
||||||
<span className="text-bubble-accent">
|
{st.pn532Connected ? (
|
||||||
{st.pn532 ? `IC${st.pn532.ic} v${st.pn532.fwHi}.${st.pn532.fwLo}` : "n/a"}
|
<span className="flex items-center gap-2">
|
||||||
</span>
|
{st.pn532 && (
|
||||||
|
<span className="text-[10px] text-slate-500">
|
||||||
|
IC{st.pn532.ic} v{st.pn532.fwHi}.{st.pn532.fwLo}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="rounded px-1.5 py-0.5 bg-bubble-mint/20 text-bubble-mint font-bold text-[10px] tracking-widest">
|
||||||
|
READY
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="animate-pulse rounded px-1.5 py-0.5 bg-amber-500/20 text-amber-400 font-bold text-[10px] tracking-widest">
|
||||||
|
SEARCHING
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -11,13 +11,26 @@ export default function Emulate() {
|
|||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
const send = async () => {
|
const send = async () => {
|
||||||
|
const cleanHex = hex.replace(/\s/g, "");
|
||||||
|
if (!cleanHex) {
|
||||||
|
toast("Hex payload is empty", "err");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cleanHex.length % 2 !== 0) {
|
||||||
|
toast("Hex must have an even number of characters", "err");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^[0-9A-Fa-f]+$/.test(cleanHex)) {
|
||||||
|
toast("Hex must contain only 0-9 A-F characters", "err");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
setOut("");
|
setOut("");
|
||||||
try {
|
try {
|
||||||
const r = await fetch(apiUrl("/api/nfc/emulate-raw"), {
|
const r = await fetch(apiUrl("/api/nfc/emulate-raw"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ hex: hex.replace(/\s/g, "") }),
|
body: JSON.stringify({ hex: cleanHex }),
|
||||||
});
|
});
|
||||||
const t = await r.text();
|
const t = await r.text();
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user