mirror of
https://github.com/m5stack/M5Unit-NFC.git
synced 2026-05-20 11:48:34 -07:00
Add files
This commit is contained in:
+2600
File diff suppressed because it is too large
Load Diff
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Please execute on repositry root
|
||||
|
||||
## Get version from library.properties
|
||||
## Get git rev of HEAD
|
||||
LIB_VERSION="$(pcregrep -o1 "^\s*version\s*=\s*(\*|\d+(\.\d+){0,3}(\.\*)?)" library.properties)"
|
||||
#echo ${DOXYGEN_PROJECT_NUMBER}
|
||||
DOXYGEN_PROJECT_NUMBER="${LIB_VERSION} git rev:$(git rev-parse --short HEAD)" doxygen docs/Doxyfile
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Detect RFID devices
|
||||
*/
|
||||
#include "main/DetectDevices.cpp"
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Detect NFC devices
|
||||
*/
|
||||
#include <M5Unified.h>
|
||||
#include <M5UnitUnified.h>
|
||||
#include <M5UnitUnifiedNFC.h>
|
||||
#include <M5Utility.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace m5::nfc::a;
|
||||
|
||||
namespace {
|
||||
auto& lcd = M5.Display;
|
||||
m5::unit::UnitUnified Units;
|
||||
m5::unit::CapST25R3916 cap; // ST25R3916 in the HackerCap
|
||||
m5::unit::nfc::NFCLayerA nfc_a{cap};
|
||||
|
||||
} // namespace
|
||||
|
||||
void setup()
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
#if 0
|
||||
//// M5GFX 0.2.15 NG! with HackerCap
|
||||
auto board = M5.getBoard();
|
||||
if (board != lgfx::board_t::board_M5CardputerADV) {
|
||||
M5_LOGE("This is NOT M5Cardputer-ADV %U/%XH", board, board);
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!SPI.bus()) {
|
||||
auto spi_sclk = M5.getPin(m5::pin_name_t::sd_spi_sclk);
|
||||
auto spi_mosi = M5.getPin(m5::pin_name_t::sd_spi_mosi);
|
||||
auto spi_miso = M5.getPin(m5::pin_name_t::sd_spi_miso);
|
||||
M5_LOGI("getPin: %d,%d,%d", spi_sclk, spi_mosi, spi_miso);
|
||||
SPI.begin(spi_sclk, spi_miso, spi_mosi /* SS is shared SD, CC1101, ST25R3916 */);
|
||||
}
|
||||
|
||||
SPISettings settings = {1000000, MSBFIRST, SPI_MODE1};
|
||||
if (!Units.add(cap, SPI, settings) || !Units.begin()) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
|
||||
M5_LOGI("M5UnitUnified has been begun");
|
||||
M5_LOGI("%s", Units.debugInfo().c_str());
|
||||
|
||||
if (lcd.width() < lcd.height()) {
|
||||
lcd.setRotation(1);
|
||||
}
|
||||
lcd.setFont(&fonts::Font0);
|
||||
lcd.fillScreen(0);
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.printf("Please put the devices and click G0");
|
||||
M5.Log.printf("Please put the devices and click G0\n");
|
||||
|
||||
|
||||
// Anntena settings
|
||||
uint8_t v{};
|
||||
cap.readTXDriver(v);
|
||||
M5_LOGI("TXD:%02X", v);
|
||||
cap.writeTXDriver((v & 0x0F) | (10U << 4)); // am_mod 15%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
M5.update();
|
||||
auto touch = M5.Touch.getDetail();
|
||||
Units.update();
|
||||
|
||||
if (M5.BtnA.wasClicked() || touch.wasClicked()) {
|
||||
lcd.fillRect(0, lcd.fontHeight(), lcd.width(), lcd.height() - lcd.fontHeight());
|
||||
std::vector<UID> devices;
|
||||
if (nfc_a.detect(devices)) {
|
||||
lcd.setCursor(0, lcd.fontHeight());
|
||||
M5.Log.printf("Devices: %zu\n", devices.size());
|
||||
lcd.printf("Devices: %zu\n", devices.size());
|
||||
uint32_t idx{};
|
||||
for (auto&& u : devices) {
|
||||
M5.Log.printf("[%2u]:UID:<%s> %s\n", idx, u.uidAsString().c_str(), u.typeAsString().c_str());
|
||||
lcd.printf("[%2u]:UID:<%s> %s\n", idx, u.uidAsString().c_str(), u.typeAsString().c_str());
|
||||
++idx;
|
||||
}
|
||||
} else {
|
||||
M5.Log.printf("No devices\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Dump NFC device
|
||||
*/
|
||||
#include "main/Dump.cpp"
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Dump NFC device
|
||||
*/
|
||||
#include <M5Unified.h>
|
||||
#include <M5UnitUnified.h>
|
||||
#include <M5UnitUnifiedNFC.h>
|
||||
#include <M5Utility.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace m5::nfc::a;
|
||||
|
||||
namespace {
|
||||
auto& lcd = M5.Display;
|
||||
m5::unit::UnitUnified Units;
|
||||
m5::unit::CapST25R3916 cap; // ST25R3916 in the HackerCap
|
||||
m5::unit::nfc::NFCLayerA nfc_a{cap};
|
||||
|
||||
} // namespace
|
||||
|
||||
void setup()
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
#if 0
|
||||
//// M5GFX 0.2.15 NG! with HackerCap
|
||||
auto board = M5.getBoard();
|
||||
if (board != lgfx::board_t::board_M5CardputerADV) {
|
||||
M5_LOGE("This is NOT M5Cardputer-ADV %U/%XH", board, board);
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!SPI.bus()) {
|
||||
auto spi_sclk = M5.getPin(m5::pin_name_t::sd_spi_sclk);
|
||||
auto spi_mosi = M5.getPin(m5::pin_name_t::sd_spi_mosi);
|
||||
auto spi_miso = M5.getPin(m5::pin_name_t::sd_spi_miso);
|
||||
M5_LOGI("getPin: %d,%d,%d", spi_sclk, spi_mosi, spi_miso);
|
||||
SPI.begin(spi_sclk, spi_miso, spi_mosi /* SS is shared SD, CC1101, ST25R3916 */);
|
||||
}
|
||||
|
||||
SPISettings settings = {1000000, MSBFIRST, SPI_MODE1};
|
||||
if (!Units.add(cap, SPI, settings) || !Units.begin()) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
M5_LOGI("M5UnitUnified has been begun");
|
||||
M5_LOGI("%s", Units.debugInfo().c_str());
|
||||
|
||||
if (lcd.width() < lcd.height()) {
|
||||
lcd.setRotation(1);
|
||||
}
|
||||
lcd.setFont(&fonts::Font0);
|
||||
lcd.fillScreen(0);
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.printf("Please put the device and click G0");
|
||||
M5.Log.printf("Please put the device and click G0\n");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
M5.update();
|
||||
auto touch = M5.Touch.getDetail();
|
||||
Units.update();
|
||||
|
||||
if (M5.BtnA.wasClicked() || touch.wasClicked()) {
|
||||
lcd.fillRect(0, lcd.fontHeight(), lcd.width(), lcd.height() - lcd.fontHeight());
|
||||
std::vector<UID> devices;
|
||||
if (nfc_a.detect(devices)) {
|
||||
lcd.setCursor(0, lcd.fontHeight());
|
||||
// If multiple occurrences are detected, only the first one detected
|
||||
auto& top = devices.front();
|
||||
M5.Log.printf("==== Dump %s %s ====\n", top.uidAsString().c_str(), top.typeAsString().c_str());
|
||||
nfc_a.dump(top);
|
||||
} else {
|
||||
M5.Log.printf("No devices\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "M5Unit-NFC",
|
||||
"description": "Library for M5Stack UNIT NFC using M5UnitUnified",
|
||||
"keywords": "M5Stack UNIT NFC,M5UnitUnified",
|
||||
"authors": {
|
||||
"name": "M5Stack",
|
||||
"url": "http://www.m5stack.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/m5stack/M5Unit-NFC.git"
|
||||
},
|
||||
"dependencies":
|
||||
{
|
||||
"m5stack/M5UnitUnified": ">=0.1.0"
|
||||
},
|
||||
"version": "0.0.1",
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"platforms": [
|
||||
"espressif32"
|
||||
],
|
||||
"headers": "M5UnitUnifiedNFC.h",
|
||||
"license": "MIT",
|
||||
"export": {
|
||||
"exclude": [
|
||||
"docs/html"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
name=M5Unit-NFC
|
||||
version=0.0.1
|
||||
author=M5Stack
|
||||
maintainer=M5Stack
|
||||
sentence=Library for M5Stack UNIT NFC using M5UnitUnified
|
||||
paragraph=
|
||||
category=Device Control
|
||||
url=https://github.com/m5stack/M5Unit-NFC.git
|
||||
architectures=esp32
|
||||
includes=M5UnitUnifiedNFC.h
|
||||
depends=M5UnitUnified,M5Utility,M5HAL
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
;-----------------------------------------------------------------------
|
||||
; M5Unit-NFC
|
||||
; For UnitTest and examples (Using M5UnitUnified)
|
||||
;-----------------------------------------------------------------------
|
||||
[platformio]
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra -Wreturn-local-addr -Werror=format -Werror=return-local-addr
|
||||
lib_ldf_mode = deep
|
||||
test_framework = googletest
|
||||
test_build_src = true
|
||||
lib_deps=m5stack/M5Unified
|
||||
m5stack/M5UnitUnified#develop
|
||||
; m5stack/M5Utility#develop
|
||||
|
||||
; --------------------------------
|
||||
[m5base]
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder, time
|
||||
upload_speed = 1500000
|
||||
test_speed = 115200
|
||||
test_ignore= native/*
|
||||
|
||||
[CardputerADV]
|
||||
extends = m5base
|
||||
;platform = espressif32@6.7.0
|
||||
board = esp32-s3-devkitc-1
|
||||
lib_deps = ${env.lib_deps}
|
||||
|
||||
; --------------------------------
|
||||
;Choose framework
|
||||
[arduino_latest]
|
||||
platform = espressif32 @ 6.8.1
|
||||
framework = arduino
|
||||
|
||||
;[esp-idf]
|
||||
;platform = espressif32 @ 6.8.1
|
||||
;framework = espidf
|
||||
|
||||
; --------------------------------
|
||||
;Choose build options
|
||||
[option_release]
|
||||
build_type=release
|
||||
build_flags = ${env.build_flags}
|
||||
-DCORE_DEBUG_LEVEL=3
|
||||
-DLOG_LOCAL_LEVEL=3
|
||||
-DAPP_LOG_LEVEL=3
|
||||
-DM5_LOG_LEVEL=3
|
||||
|
||||
[option_log]
|
||||
build_type=release
|
||||
build_flags = ${env.build_flags}
|
||||
-DCORE_DEBUG_LEVEL=5
|
||||
-DLOG_LOCAL_LEVEL=5
|
||||
-DAPP_LOG_LEVEL=5
|
||||
|
||||
[option_debug]
|
||||
build_type=debug
|
||||
build_flags = ${env.build_flags}
|
||||
-DCORE_DEBUG_LEVEL=5
|
||||
-DLOG_LOCAL_LEVEL=5
|
||||
-DAPP_LOG_LEVEL=5
|
||||
-DDEBUG
|
||||
|
||||
[option_map]
|
||||
build_type=release
|
||||
build_flags = ${env.build_flags}
|
||||
-DCORE_DEBUG_LEVEL=3
|
||||
-DLOG_LOCAL_LEVEL=3
|
||||
-DAPP_LOG_LEVEL=3
|
||||
-DM5_LOG_LEVEL=0
|
||||
-Wl,-Map,output.map
|
||||
|
||||
; Require at leaset C++14 after 1.13.0
|
||||
[test_fw]
|
||||
lib_deps = google/googletest@1.12.1
|
||||
|
||||
; --------------------------------
|
||||
; UnitTest
|
||||
; --------------------------------
|
||||
[env:test_ST25R3916_CardputerADV]
|
||||
extends=CardputerADV, option_release, arduino_latest
|
||||
lib_deps = ${CardputerADV.lib_deps}
|
||||
${test_fw.lib_deps}
|
||||
test_filter= embedded/test_st25r3916
|
||||
|
||||
; --------------------------------
|
||||
;Examples
|
||||
; --------------------------------
|
||||
;CapHacker
|
||||
[env:CapHacker_DetectDevices_CardputerADV_Arduino_latest]
|
||||
extends=CardputerADV, option_release, arduino_latest
|
||||
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/DetectDevices>
|
||||
build_flags = ${option_release.build_flags}
|
||||
-DUSING_CAP_ST25R3916
|
||||
|
||||
[env:CapHacker_Dump_CardputerADV_Arduino_latest]
|
||||
extends=CardputerADV, option_debug, arduino_latest
|
||||
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/Dump>
|
||||
build_flags = ${option_release.build_flags}
|
||||
-DUSING_CAP_ST25R3916
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file M5UnitUnifiedNFC.h
|
||||
*/
|
||||
#ifndef M5_UNIT_UNIFIED_NFC_H
|
||||
#define M5_UNIT_UNIFIED_NFC_H
|
||||
#ifdef __cplusplus
|
||||
#include "M5UnitUnifiedNFC.hpp"
|
||||
#else
|
||||
#error M5Unit-NFC requires a C++ compiler, please change file extension to .cc or .cpp
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file M5UnitUnifiedNFC.hpp
|
||||
@brief Main header of M5Unit-NFC
|
||||
|
||||
@mainpage M5Unit-NFC
|
||||
Library for Unit-NFC using M5UnitUnified.
|
||||
Include NFC-x related definitions.
|
||||
*/
|
||||
#ifndef M5_UNIT_UNIFIED_NFC_HPP
|
||||
#define M5_UNIT_UNIFIED_NFC_HPP
|
||||
|
||||
#include "nfc/nfc.hpp"
|
||||
#include "unit/unit_ST25R3916.hpp"
|
||||
#include "nfc/layer/nfc_layer_a.hpp"
|
||||
|
||||
/*!
|
||||
@namespace m5
|
||||
@brief Top level namespace of M5stack
|
||||
*/
|
||||
namespace m5 {
|
||||
/*!
|
||||
@namespace unit
|
||||
@brief Unit-related namespace
|
||||
*/
|
||||
namespace unit {
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file mifare.cpp
|
||||
@brief Mifare definitions
|
||||
*/
|
||||
#include "mifare.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
namespace a {
|
||||
namespace mifare {
|
||||
|
||||
const Key DEFAULT_CLASSIC_KEY = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
|
||||
namespace classic {
|
||||
bool decode_value_block(int32_t& value, uint8_t& addr, const uint8_t* buf)
|
||||
{
|
||||
if (*((uint32_t*)&buf[0]) == *((uint32_t*)&buf[8]) && *((uint32_t*)&buf[0]) == ~*((uint32_t*)&buf[4]) &&
|
||||
*((uint16_t*)&buf[12]) == *((uint16_t*)&buf[14]) && buf[12] == (uint8_t)~buf[13]) {
|
||||
value = ((int32_t)buf[3]) << 24 | ((int32_t)buf[2]) << 16 | ((int32_t)buf[1]) << 8 | buf[0];
|
||||
addr = buf[12];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* encode_value_block(uint8_t* buf, const int32_t value, const uint8_t addr)
|
||||
{
|
||||
buf[0] = buf[8] = (value >> 0) & 0xFF;
|
||||
buf[1] = buf[9] = (value >> 8) & 0xFF;
|
||||
buf[2] = buf[10] = (value >> 16) & 0xFF;
|
||||
buf[3] = buf[11] = (value >> 24) & 0xFF;
|
||||
|
||||
buf[4] = ~buf[0];
|
||||
buf[5] = ~buf[1];
|
||||
buf[6] = ~buf[2];
|
||||
buf[7] = ~buf[3];
|
||||
|
||||
buf[12] = buf[14] = addr;
|
||||
buf[13] = buf[15] = ~addr;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool encode_access_bits(uint8_t abits[3], const uint8_t p0, const uint8_t p1, const uint8_t p2, const uint8_t p3)
|
||||
{
|
||||
// Valid up to the third bit in each value
|
||||
if ((p0 & 0xF8) | (p1 & 0xF8) | (p2 & 0xF8) | (p3 & 0xF8)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t c1 = ((p3 & 4) << 1) | ((p2 & 4) << 0) | ((p1 & 4) >> 1) | ((p0 & 4) >> 2);
|
||||
uint8_t c2 = ((p3 & 2) << 2) | ((p2 & 2) << 1) | ((p1 & 2) << 0) | ((p0 & 2) >> 1);
|
||||
uint8_t c3 = ((p3 & 1) << 3) | ((p2 & 1) << 2) | ((p1 & 1) << 1) | ((p0 & 1) << 0);
|
||||
|
||||
abits[0] = ((~c2 & 0x0F) << 4) | (~c1 & 0x0F);
|
||||
abits[1] = (c1 << 4) | (~c3 & 0x0F);
|
||||
abits[2] = (c3 << 4) | c2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode_access_bits(uint8_t permissions[4], const uint8_t ab0, const uint8_t ab1, const uint8_t ab2)
|
||||
{
|
||||
// value bits
|
||||
uint8_t c1 = (ab1 >> 4) & 0x0F;
|
||||
uint8_t c2 = ab2 & 0x0F;
|
||||
uint8_t c3 = (ab2 >> 4) & 0x0F;
|
||||
// negated bits
|
||||
uint8_t n1 = (~(ab0 & 0x0F)) & 0x0F;
|
||||
uint8_t n2 = (~((ab0 >> 4) & 0x0F)) & 0x0F;
|
||||
uint8_t n3 = (~(ab1 & 0x0F)) & 0x0F;
|
||||
|
||||
bool valid = (c1 == n1) && (c2 == n2) && (c3 == n3);
|
||||
if (valid || true) {
|
||||
permissions[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0);
|
||||
permissions[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1);
|
||||
permissions[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2);
|
||||
permissions[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
} // namespace classic
|
||||
} // namespace mifare
|
||||
} // namespace a
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file mifare.hpp
|
||||
@brief Mifare definitions
|
||||
*/
|
||||
#ifndef M5_UNIT_UNIFIED_NFC_NFC_NFCA_MIFARE_HPP
|
||||
#define M5_UNIT_UNIFIED_NFC_NFC_NFCA_MIFARE_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
namespace a {
|
||||
/*!
|
||||
@namespace mifare
|
||||
@brief For MIFARE
|
||||
*/
|
||||
namespace mifare {
|
||||
|
||||
/*!
|
||||
@typedef Key
|
||||
@brief MIFARE Key
|
||||
*/
|
||||
using Key = std::array<uint8_t, 6>;
|
||||
|
||||
//! @brief Default key for Mifare classic
|
||||
extern const Key DEFAULT_CLASSIC_KEY;
|
||||
|
||||
/*!
|
||||
@namespace classic
|
||||
@brief For MIFARE classic
|
||||
*/
|
||||
namespace classic {
|
||||
/*!
|
||||
@brief Is this permission treated as a value block?
|
||||
@return True if Value block
|
||||
*/
|
||||
inline constexpr bool is_value_block_permission(const uint8_t permission)
|
||||
{
|
||||
return permission == 0x01 || // value block (Only read and decrement)
|
||||
permission == 0x06; // value block
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Is this block a sector trailer?
|
||||
@return True if sector trailer
|
||||
*/
|
||||
inline constexpr bool is_sector_trailer_block(const uint8_t block)
|
||||
{
|
||||
return (block < 128) ? (block & 0x03) == 0x03 : ((block - 128) & 0x0F) == 0x0F;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Obtains the block address of the sector to which it belongs from the block address
|
||||
@return The sector trailer block address of the block belongs
|
||||
*/
|
||||
inline constexpr uint8_t get_sector_trailer_block(const uint8_t block)
|
||||
{
|
||||
return (block < 128) ? (block | 0x03) : (block | 0x0F);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Obtains the sector to which the block belongs from the block address
|
||||
@return Sector no
|
||||
*/
|
||||
inline constexpr uint8_t get_sector(const uint8_t block)
|
||||
{
|
||||
return (block < 128) ? (block >> 2) : 32 + ((block - 128) >> 4);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Get the offset in the permissions of this block
|
||||
@return Offset (0-3)
|
||||
*/
|
||||
inline uint8_t get_permission_offset(const uint8_t block)
|
||||
{
|
||||
return ((block < 128) ? (block & 0x03) : ((block - 128) & 0x0F) / 5) & 0x03;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Obtains the block address of the sector trailer from sector
|
||||
@param sector Sector no
|
||||
@return The sector trailer block address of the sector
|
||||
*/
|
||||
inline constexpr uint8_t get_sector_trailer_block_from_sector(const uint8_t sector)
|
||||
{
|
||||
return ((sector < 32) ? sector * ((sector < 32) ? 4U : 16U) : 128U + (sector - 32) * ((sector < 32) ? 4U : 16U)) +
|
||||
((sector < 32) ? 4U : 16U) - 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Decode the value of value block
|
||||
@param[out] value Value
|
||||
@param[out] addr Block address
|
||||
@param buf Data of block(16 bytes)
|
||||
@return True if successful
|
||||
*/
|
||||
bool decode_value_block(int32_t& value, uint8_t& addr, const uint8_t buf[16]);
|
||||
/*!
|
||||
@brief Encode the value of value block
|
||||
@param[out] buf Output buffer at least 16 bytes
|
||||
@param value Value
|
||||
@param addr Block address
|
||||
@return Encoded buffer
|
||||
*/
|
||||
const uint8_t* encode_value_block(uint8_t buf[16], const int32_t value, const uint8_t addr);
|
||||
|
||||
/*!
|
||||
@brief Encode accesss bits from permissions
|
||||
@param abits[3] Output buffer at leaset 3 bytes
|
||||
@param p0 permissions for block 0
|
||||
@param p1 permissions for block 1
|
||||
@param p2 permissions for block 2
|
||||
@param p3 permissions for sector tarailer
|
||||
@return True if successful
|
||||
@warning Return values should always be checked
|
||||
@warning Writing incorrect access bits may make the sector inaccessible!
|
||||
*/
|
||||
bool encode_access_bits(uint8_t abits[3], const uint8_t p0, const uint8_t p1, const uint8_t p2, const uint8_t p3);
|
||||
/*!
|
||||
@brief Encode accesss bits from permissions
|
||||
@param abits[3] Output buffer at leaset 3 bytes
|
||||
@param permissions[4] Array of the permissions. [0];block0 ... [3]:sector trailer
|
||||
@return True if successful
|
||||
@warning Return values should always be checked
|
||||
@warning Writing incorrect access bits may make the sector inaccessible!
|
||||
*/
|
||||
inline bool encode_access_bits(uint8_t abits[3], const uint8_t permissions[4])
|
||||
{
|
||||
return encode_access_bits(abits, permissions[0], permissions[1], permissions[2], permissions[3]);
|
||||
}
|
||||
/*!
|
||||
@brief Decode access bits to permissons
|
||||
@param permissions[4] Output buffer at leaset 4 bytes
|
||||
@param ab0 1st byte of the access bits
|
||||
@param ab1 2nd byte of the access bits
|
||||
@param ab2 3rd byte of the access bits
|
||||
@return True if successful
|
||||
@note permissions[0] block 0
|
||||
@note permissions[1] block 1
|
||||
@note permissions[2] block 2
|
||||
@note permissions[3] sector trailer
|
||||
@warning Return values should always be checked
|
||||
*/
|
||||
bool decode_access_bits(uint8_t permissions[4], const uint8_t ab0, const uint8_t ab1, const uint8_t ab2);
|
||||
/*!
|
||||
@brief Decode access bits to permissons
|
||||
@param permissions[4] Output buffer at leaset 4 bytes
|
||||
@param abits[3] Array of the accees bits
|
||||
@return True if successful
|
||||
@warning Return values should always be checked
|
||||
*/
|
||||
inline bool decode_access_bits(uint8_t permissions[4], const uint8_t abits[3])
|
||||
{
|
||||
return decode_access_bits(permissions, abits[0], abits[1], abits[2]);
|
||||
}
|
||||
|
||||
} // namespace classic
|
||||
} // namespace mifare
|
||||
} // namespace a
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
#endif
|
||||
@@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
using MLFSR48 = m5::utility::FibonacciLFSR_Left<48, 5, 6, 7, 9, 13, 19, 21, 23, 24, 29, 31, 33, 34, 36, 38, 39, 43, 48>;
|
||||
class MifareCrypto1 : public MLFSR48 {
|
||||
public:
|
||||
MifareCrypto1() noexcept : MLFSR48(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit MifareCrypto1(const uint64_t key48) noexcept : MLFSR48(0)
|
||||
{
|
||||
init(key48);
|
||||
}
|
||||
|
||||
uint64_t valueLSBFirst() const noexcept
|
||||
{
|
||||
uint64_t v = 0;
|
||||
for (int i = 23; i >= 0; --i) {
|
||||
const int j = (i ^ 3); // nibble 反転
|
||||
v = (v << 1) | static_cast<uint64_t>(_state[2 * j]); // odd列のビット
|
||||
v = (v << 1) | static_cast<uint64_t>(_state[2 * j + 1]); // even列のビット
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void init(const uint64_t key48) noexcept
|
||||
{
|
||||
_state = state_type_t{};
|
||||
// Change the bit order within the byte to LSB first (*1)
|
||||
for (int i = 0; i < 48; ++i) {
|
||||
int byte_index = i >> 3;
|
||||
int bit_index = i & 0x07;
|
||||
int reversed = (byte_index << 3) + (bit_index ^ 7);
|
||||
bool bit = (key48 >> reversed) & 1ULL;
|
||||
_state[i] = bit;
|
||||
}
|
||||
_uid = key48;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
inline void inject(uint32_t uid, uint32_t Nt) noexcept
|
||||
{
|
||||
(void)step32(uid ^ Nt);
|
||||
}
|
||||
|
||||
bool step_with(const bool in, const bool enc = false) noexcept
|
||||
{
|
||||
++_count;
|
||||
|
||||
bool z = filter();
|
||||
#if 1
|
||||
(void)step();
|
||||
const bool ext = in ^ (enc ? z : 0);
|
||||
_state[0] = _state[0] ^ ext;
|
||||
#else
|
||||
static constexpr uint64_t LF_POLY_48 = 0x0000846B50D41170ULL;
|
||||
uint8_t fb = __builtin_parityll((value() & LF_POLY_48) ^ (in & 1) ^ (enc ? (z & 1) : 0));
|
||||
_state <<= 1;
|
||||
_state.set(0, fb);
|
||||
#endif
|
||||
return z;
|
||||
}
|
||||
|
||||
uint8_t step8(const uint8_t in, const bool enc = false) noexcept
|
||||
{
|
||||
uint8_t v{};
|
||||
for (uint_fast8_t i = 0; i < 8; ++i) {
|
||||
v |= step_with((in >> i) & 1, enc) << i;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
uint32_t step32(const uint32_t in, const bool enc = false) noexcept
|
||||
{
|
||||
uint32_t v{};
|
||||
for (uint32_t i = 0; i < 32; ++i) {
|
||||
bool t = step_with((in >> (i ^ 24)) & 1u, enc);
|
||||
v |= t << (24 ^ i);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint8_t oddparity8(uint8_t x) noexcept
|
||||
{
|
||||
return !__builtin_parity(x);
|
||||
}
|
||||
|
||||
uint8_t encrypt(uint8_t buf[8], const uint32_t Nr, const uint32_t Ar) noexcept
|
||||
{
|
||||
uint8_t par{};
|
||||
for (uint_fast8_t i = 0; i < 4; ++i) {
|
||||
uint8_t v = ((Nr >> ((i ^ 0x03) << 3)) & 0xFF);
|
||||
buf[i] = step8(v) ^ v;
|
||||
|
||||
const uint8_t z = filter();
|
||||
// uint8_t aaa = (z ^ oddparity8(v)) & 0x01;
|
||||
// M5_LIB_LOGE("pos[%u]: f:%d P:%u", i, z, aaa);
|
||||
|
||||
// par |= static_cast<uint8_t>((z ^ oddparity8(v)) & 0x01) << (7 - i);
|
||||
par |= static_cast<uint8_t>((z ^ oddparity8(v)) & 0x01) << i;
|
||||
}
|
||||
#if 0
|
||||
for (uint_fast8_t i = 0; i < 4; ++i) {
|
||||
buf[4 + i] = step8(0) ^ ((Ar >> (i << 3)) & 0xFF);
|
||||
}
|
||||
#else
|
||||
for (uint_fast8_t pos = 4; pos < 8; ++pos) {
|
||||
const uint8_t i = pos - 4;
|
||||
const uint8_t nt_byte = static_cast<uint8_t>(Ar >> (i << 3));
|
||||
const uint8_t ks = step8(0x00);
|
||||
buf[pos] = ks ^ nt_byte;
|
||||
|
||||
const uint8_t z = filter();
|
||||
// uint8_t aaa = (z ^ oddparity8(nt_byte)) & 0x01;
|
||||
// M5_LIB_LOGE("pos[%u]: f:%d P:%u", pos, z, aaa);
|
||||
// par |= static_cast<uint8_t>((z ^ oddparity8(nt_byte)) & 0x01) << (7 - pos);
|
||||
par |= static_cast<uint8_t>((z ^ oddparity8(nt_byte)) & 0x01) << pos;
|
||||
}
|
||||
|
||||
#endif
|
||||
M5_LIB_LOGE("MyPar:%02X", par);
|
||||
return par;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void encrypt(std::vector<uint8_t>& out, const uint8_t* tx, const uint16_t len)
|
||||
{
|
||||
out.clear();
|
||||
out.resize(tx_len + ((tx_len + 7) >> 3));
|
||||
|
||||
for (uint_fast16_t i = 0; i < len; ++i) {
|
||||
uint8_t ks = _crypto1.step8(0);
|
||||
enc_tx[i] = tx[i] ^ ks;
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool filter() const noexcept
|
||||
{
|
||||
const state_type_t& s = state();
|
||||
const bool b5 = fb(s[6], s[4], s[2], s[0]);
|
||||
const bool a4 = fa(s[14], s[12], s[10], s[8]);
|
||||
const bool b3 = fb(s[22], s[20], s[18], s[16]);
|
||||
const bool b2 = fb(s[30], s[28], s[26], s[24]);
|
||||
const bool a1 = fa(s[38], s[36], s[34], s[32]);
|
||||
return fc(a1, b2, b3, a4, b5);
|
||||
}
|
||||
|
||||
/*
|
||||
These macros are linearized boolean tables for the output filter functions.
|
||||
E.g. fa(0,1,0,1) is (mf2_f4a >> 0x5)&1
|
||||
const uint32_t mf2_f4a = 0x9E98;
|
||||
const uint32_t mf2_f4b = 0xB48E;
|
||||
const uint32_t mf2_f5c = 0xEC57E80A;
|
||||
|
||||
const uint32_t i5 = ((mf2_f4b >> i4 (x, 7+d, 9+d,11+d,13+d)) & 1)<<0
|
||||
59 | ((mf2_f4a >> i4 (x,15+d,17+d,19+d,21+d)) & 1)<<1
|
||||
60 | ((mf2_f4a >> i4 (x,23+d,25+d,27+d,29+d)) & 1)<<2
|
||||
61 | ((mf2_f4b >> i4 (x,31+d,33+d,35+d,37+d)) & 1)<<3
|
||||
62 | ((mf2_f4a >> i4 (x,39+d,41+d,43+d,45+d)) & 1)<<4;
|
||||
63
|
||||
64 return (mf2_f5c >> i5) & 1;
|
||||
*/
|
||||
|
||||
inline static bool fa(bool a, bool b, bool c, bool d) noexcept
|
||||
{
|
||||
return ((a || b) ^ (a && d)) ^ (c && ((a ^ b) || d));
|
||||
// const uint8_t x = a | (b << 1) | (c << 2) | (d << 3);
|
||||
const uint8_t x = (a << 3) | (b << 2) | (c << 1) | (d << 0);
|
||||
|
||||
// return (0xf22cu >> x) & 1u;
|
||||
return ((0xD9380u >> x) & 16u);
|
||||
// return ((0xB48Eu >> x) & 1u) != 0;
|
||||
// return (0x9E98u >> x) & 1u;
|
||||
}
|
||||
|
||||
inline static bool fb(bool a, bool b, bool c, bool d) noexcept
|
||||
{
|
||||
return ((a && b) || c) ^ ((a ^ b) && (c || d));
|
||||
// const uint8_t x = a | (b << 1) | (c << 2) | (d << 3);
|
||||
const uint8_t x = (a << 3) | (b << 2) | (c << 1) | (d << 0);
|
||||
|
||||
// return ((0xD938u >> x) & 1u) != 0;
|
||||
return ((0xf22c0 >> x) & 16u);
|
||||
// return (0x9E98u >> x) & 1u;
|
||||
// return (0xB48Eu >> x) & 1u;
|
||||
}
|
||||
|
||||
inline static bool fc(bool a, bool b, bool c, bool d, bool e) noexcept
|
||||
{
|
||||
return (a || ((b || e) && (d ^ e))) ^ ((a ^ (b && d)) && ((c ^ d) || (b && e)));
|
||||
// uint8_t x = (unsigned)a << 4 | (unsigned)b << 3 | (unsigned)c << 2 | (unsigned)d << 1 | (unsigned)e;
|
||||
// const uint8_t x = (a << 0) | (b << 1) | (c << 2) | (d << 3) | (e << 4);
|
||||
const uint8_t x = (a << 4) | (b << 3) | (c << 2) | (d << 1) | (e << 0);
|
||||
return ((0xEC57E80Au >> x) & 1u) != 0;
|
||||
}
|
||||
|
||||
//////
|
||||
static inline uint32_t pack_even24(const state_type_t& s)
|
||||
{
|
||||
uint32_t x = 0;
|
||||
for (int k = 0; k < 24; ++k) {
|
||||
x |= (uint32_t(s[2 * k]) << k); // 偶数添字から LSB→MSB に詰める
|
||||
// 0,2,4....
|
||||
// [46][44] .... [2][0]B
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline bool filter_lut(const state_type_t& s)
|
||||
{
|
||||
uint32_t x = pack_even24(s);
|
||||
uint32_t f[5]{};
|
||||
f[0] = 0xf22c0 >> (x & 0xF) & 16; // 0xf22c << 4
|
||||
f[1] = 0x6c9c0 >> ((x >> 4) & 0xF) & 8; // 0xd938 << 3
|
||||
f[2] = 0x3c8b0 >> ((x >> 8) & 0xF) & 4; // 0xf22c << 2
|
||||
f[3] = 0x1e458 >> ((x >> 12) & 0xF) & 2; // 0xf22c << 1
|
||||
f[4] = 0x0d938 >> ((x >> 16) & 0xF) & 1; // 0xd938 << 0
|
||||
uint32_t ff = f[0] | f[1] | f[2] | f[3] | f[4];
|
||||
|
||||
M5_LIB_LOGI("%02X:%02X:%02X:%02X:%02X %u:%u:%u:%u:%u", (x & 0x0F), ((x >> 4) & 0xF), ((x >> 8) & 0xF),
|
||||
((x >> 12) & 0xF), ((x >> 16) & 0xF), (bool)f[0], (bool)f[1], (bool)f[2], (bool)f[3], (bool)f[4]);
|
||||
|
||||
/*
|
||||
M5_LOGI("(%x)%u:(%x)%u:(%x)%u:(%x)%u:(%x)%u %x => %u", //
|
||||
(x & 0x0F), f[0], //
|
||||
((x >> 4) & 0xF), f[1], //
|
||||
((x >> 8) & 0xF), f[2], //
|
||||
((x >> 12) & 0xF), f[3], //
|
||||
((x >> 16) & 0xF), f[4], //
|
||||
ff, ((0xEC57E80Au >> ff) & 1u) != 0);
|
||||
*/
|
||||
return ((0xEC57E80Au >> ff) & 1u) != 0;
|
||||
}
|
||||
|
||||
uint64_t _uid{};
|
||||
uint32_t _count{}; // for debug
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfca.hpp
|
||||
@brief NFC definitions
|
||||
*/
|
||||
#include "nfca.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
namespace {
|
||||
constexpr char name_unknown[] = "Unknown";
|
||||
constexpr char name_classic[] = "MIFARE Classic";
|
||||
constexpr char name_classic_1K[] = "MIFARE Classsic1K";
|
||||
constexpr char name_classic_2K[] = "MIFARE Classsic2K";
|
||||
constexpr char name_classic_4K[] = "MIFARE Classsic4K";
|
||||
constexpr char name_ultra_light[] = "MIFARE Ultralight";
|
||||
constexpr char name_ultra_light_c[] = "MIFARE UltralightC";
|
||||
constexpr char name_plus_2K[] = "MIFARE Plus2K";
|
||||
constexpr char name_plus_4K[] = "MIFARE Plus4K";
|
||||
constexpr char name_desfire_2K[] = "MIFARE DESFire2K";
|
||||
constexpr char name_desfire_4K[] = "MIFARE DESFire4K";
|
||||
constexpr char name_desfire_8K[] = "MIFARE DESFire8K";
|
||||
constexpr char name_ntag203[] = "NTAG 203";
|
||||
constexpr char name_ntag210u[] = "NTAG 210u";
|
||||
constexpr char name_ntag210[] = "NTAG 210";
|
||||
constexpr char name_ntag212[] = "NTAG 212";
|
||||
constexpr char name_ntag213[] = "NTAG 213";
|
||||
constexpr char name_ntag215[] = "NTAG 215";
|
||||
constexpr char name_ntag216[] = "NTAG 216";
|
||||
constexpr char name_iso14443_4[] = "ISO14443-4";
|
||||
constexpr char name_iso18092[] = "ISO18092";
|
||||
constexpr const char* name_table[] = {
|
||||
name_unknown, //
|
||||
name_classic, name_classic_1K, name_classic_2K, name_classic_4K, // Classic
|
||||
name_ultra_light, name_ultra_light_c, // Light
|
||||
name_plus_2K, name_plus_4K, // Plus
|
||||
name_desfire_2K, name_desfire_4K, name_desfire_8K, // DESFire
|
||||
name_ntag203, name_ntag210u, name_ntag210, // NTAG
|
||||
name_ntag212, name_ntag213, name_ntag215, // NTAG
|
||||
name_ntag216, // NTAG
|
||||
name_iso14443_4, name_iso18092, //
|
||||
};
|
||||
|
||||
// included system area
|
||||
constexpr uint16_t max_block_table[] = {
|
||||
0, // Unknown
|
||||
20, 64, 128, 256, // Classic
|
||||
16, 48, // Light
|
||||
128, 256, // Plus
|
||||
0, 0, 0, // DESFire (Not has blocks, File base system)
|
||||
42, 19, 16, 40, 44, 134, 230 // NTAG
|
||||
};
|
||||
|
||||
// [min/max]
|
||||
constexpr uint8_t user_block_table[][2] = {
|
||||
{0, 0},
|
||||
// Classic
|
||||
{1, 19},
|
||||
{1, 63},
|
||||
{0, 127},
|
||||
{0, 255},
|
||||
// Light
|
||||
{4, 15},
|
||||
{4, 39},
|
||||
// Plus
|
||||
{0, 0},
|
||||
{0, 0},
|
||||
// DESFire
|
||||
{0, 0},
|
||||
{0, 0},
|
||||
{0, 0},
|
||||
// NTAG
|
||||
{4, 39}, // 203
|
||||
{4, 15}, // 210u
|
||||
{4, 15}, // 210
|
||||
{4, 35}, // 212
|
||||
{4, 39}, // 213
|
||||
{4, 129}, // 215
|
||||
{4, 225}, // 216
|
||||
};
|
||||
|
||||
constexpr uint8_t max_sector_table[] = {
|
||||
0, //
|
||||
5, 16, 32, 40, // Classic
|
||||
0, 0, // Light
|
||||
32, 40, // Plus
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
namespace a {
|
||||
|
||||
Type get_type(const uint8_t sak)
|
||||
{
|
||||
if (sak & 0x02) {
|
||||
return Type::Unknown;
|
||||
}
|
||||
if (sak & 0x04) {
|
||||
return Type::NotCompleted;
|
||||
}
|
||||
if (sak & 0x20) {
|
||||
return Type::ISO_14443_4;
|
||||
}
|
||||
if (sak & 0x40) {
|
||||
return Type::ISO_18092;
|
||||
}
|
||||
switch (sak) {
|
||||
case 0x00:
|
||||
return Type::MIFARE_UltraLight; // or C or NTAG
|
||||
case 0x01:
|
||||
return Type::MIFARE_DESFire_2K; // or 4K or 8K
|
||||
case 0x08:
|
||||
return Type::MIFARE_Classic_1K;
|
||||
case 0x09:
|
||||
return Type::MIFARE_Classic;
|
||||
case 0x10:
|
||||
return Type::MIFARE_Plus_2K;
|
||||
case 0x11:
|
||||
return Type::MIFARE_Plus_4K;
|
||||
case 0x18:
|
||||
return Type::MIFARE_Classic_4K;
|
||||
case 0x19:
|
||||
return Type::MIFARE_Classic_2K;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return Type::Unknown;
|
||||
}
|
||||
|
||||
uint16_t get_number_of_blocks(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
return max_block_table[idx < m5::stl::size(max_block_table) ? idx : 0];
|
||||
}
|
||||
|
||||
uint8_t get_number_of_sectors(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
return max_sector_table[idx < m5::stl::size(max_sector_table) ? idx : 0];
|
||||
}
|
||||
|
||||
uint8_t get_first_user_block(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
return user_block_table[idx < m5::stl::size(user_block_table) ? idx : 0][0];
|
||||
}
|
||||
|
||||
uint8_t get_last_user_block(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
return user_block_table[idx < m5::stl::size(user_block_table) ? idx : 0][1];
|
||||
}
|
||||
|
||||
uint8_t get_user_block_size(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
auto p = user_block_table[idx < m5::stl::size(user_block_table) ? idx : 0];
|
||||
uint8_t sz = p[1] - p[0];
|
||||
return sz ? (sz + 1) : 0;
|
||||
}
|
||||
|
||||
std::string UID::uidAsString() const
|
||||
{
|
||||
char buf[2 * 10 + 1]{};
|
||||
if (this->size <= 10) {
|
||||
uint8_t left{};
|
||||
for (uint8_t i = 0; i < this->size; ++i) {
|
||||
left += snprintf(buf + left, 3, "%02X", this->uid[i]);
|
||||
}
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string UID::typeAsString() const
|
||||
{
|
||||
const auto idx = m5::stl::to_underlying(this->type);
|
||||
return std::string((this->size && idx <= m5::stl::size(name_table)) ? name_table[idx] : name_unknown);
|
||||
}
|
||||
|
||||
uint8_t calculate_bcc8(const uint8_t* data, const uint32_t len)
|
||||
{
|
||||
uint8_t bcc{};
|
||||
if (data && len) {
|
||||
for (uint32_t i = 0; i < len; ++i) {
|
||||
bcc ^= data[i];
|
||||
}
|
||||
}
|
||||
return bcc;
|
||||
}
|
||||
} // namespace a
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfca.hpp
|
||||
@brief NFC-A definitions
|
||||
*/
|
||||
#ifndef M5_UNIT_UNIFIED_NFC_NFC_NFCA_NFCA_HPP
|
||||
#define M5_UNIT_UNIFIED_NFC_NFC_NFCA_NFCA_HPP
|
||||
|
||||
#include "mifare.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
/*!
|
||||
@namespace a
|
||||
@brief NFC-A definitions
|
||||
*/
|
||||
namespace a {
|
||||
|
||||
/*!
|
||||
@enum Type
|
||||
@brief Type of the PICC device
|
||||
*/
|
||||
enum class Type : uint8_t {
|
||||
Unknown, //!< Unknown type
|
||||
MIFARE_Classic, //!< Also known as MIFARE Standard mini
|
||||
MIFARE_Classic_1K, //!< Also known as MIFARE Standard 1K
|
||||
MIFARE_Classic_2K, //!< Also known as MIFARE Standard 2K
|
||||
MIFARE_Classic_4K, //!< Also known as MIFARE Standard 4K
|
||||
MIFARE_UltraLight, //!< MIFARE Ultralight
|
||||
MIFARE_UltraLightC, //!< MIFARE UltralightC
|
||||
MIFARE_Plus_2K, //!< MIFARE Plus 2K
|
||||
MIFARE_Plus_4K, //!< MIFARE Plus 4K
|
||||
MIFARE_DESFire_2K, //!< MIFARE DESFire 2K
|
||||
MIFARE_DESFire_4K, //!< MIFARE DESFire 4K
|
||||
MIFARE_DESFire_8K, //!< MIFARE DESFire 8K
|
||||
NTAG_203, //!< NATG 203
|
||||
NTAG_210u, //!< NTAG 210μ
|
||||
NTAG_210, //!< NTAG 210
|
||||
NTAG_212, //!< NTAG 212
|
||||
NTAG_213, //!< NTAG 213
|
||||
NTAG_215, //!< NTAG 215
|
||||
NTAG_216, //!< NTAG 216
|
||||
ISO_14443_4, //!< PICC compliant with ISO/IEC 14443-4
|
||||
ISO_18092, //!< PICC compliant with ISO/IEC 18092 (NFC)
|
||||
NotCompleted = 0xFF, //!< SAK indicates UID is not complete
|
||||
};
|
||||
|
||||
//! @brief Is type MIFARE Classic?
|
||||
inline bool is_classic(const Type t)
|
||||
{
|
||||
return t >= Type::MIFARE_Classic && t <= Type::MIFARE_Classic_4K;
|
||||
}
|
||||
|
||||
//! @brief Is type NTAG?
|
||||
inline bool is_ntag(const Type t)
|
||||
{
|
||||
return t >= Type::NTAG_203 && t <= Type::NTAG_216;
|
||||
}
|
||||
|
||||
//! @brief Does the specified type function as NFC?
|
||||
inline bool can_NFC(const Type t)
|
||||
{
|
||||
return t == Type::MIFARE_UltraLight || t == Type::MIFARE_UltraLightC || // Light/C
|
||||
is_ntag(t);
|
||||
}
|
||||
|
||||
//! @brief Has FAST_READ command?
|
||||
inline bool has_fast_read(const Type t)
|
||||
{
|
||||
return t >= Type::NTAG_210 && t <= Type::NTAG_216;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Get type from SAK
|
||||
@param sak SAK
|
||||
@return Type
|
||||
@warning Some types cannot be determined by SAK alone
|
||||
*/
|
||||
Type get_type(const uint8_t sak);
|
||||
|
||||
//! @brief SAK uncompleted?
|
||||
inline bool has_sak_dependent_bit(const uint8_t sak)
|
||||
{
|
||||
return (sak & 0x04);
|
||||
}
|
||||
//! @brief SAK completed? (Complies with ISO/IEC 14443-4)
|
||||
inline bool is_sak_completed_14443_4(const uint8_t sak)
|
||||
{
|
||||
return ((sak & 0x24) == 0x20);
|
||||
}
|
||||
//! @brief SAK completed? (Does not comply with ISO/IEC 14443-4)
|
||||
inline bool is_sak_completed(const uint8_t sak)
|
||||
{
|
||||
return ((sak & 0x24) == 0x00);
|
||||
}
|
||||
|
||||
//! @brief Get the number of the blocks
|
||||
uint16_t get_number_of_blocks(const Type t);
|
||||
//! @brief Get the number of the sectors
|
||||
uint8_t get_number_of_sectors(const Type t);
|
||||
//! @brief Get the first user area block
|
||||
uint8_t get_first_user_block(const Type t);
|
||||
//! @brief Get the last user area block
|
||||
uint8_t get_last_user_block(const Type t);
|
||||
//! @brief Gets the number of the user blocks
|
||||
uint8_t get_user_block_size(const Type t);
|
||||
|
||||
//! @brief Calculate bcc8
|
||||
uint8_t calculate_bcc8(const uint8_t* data, const uint32_t len);
|
||||
|
||||
/*!
|
||||
@struct UID
|
||||
@brief The UID of the PICC
|
||||
*/
|
||||
struct UID {
|
||||
//! @brief PICC type
|
||||
Type type{};
|
||||
//! @brief UID size 4, 7 or 10.
|
||||
uint8_t size{};
|
||||
//! @brief The SAK (Select acknowledge) returned from the PICC after successful selection
|
||||
uint8_t sak{};
|
||||
//! @brief uid data (Valid up to the value of size)
|
||||
uint8_t uid[10]{};
|
||||
//! @brief The number of the blocks or pages
|
||||
uint16_t blocks{};
|
||||
|
||||
//! @brief Valid?
|
||||
inline bool valid() const
|
||||
{
|
||||
return size && type != Type::Unknown && blocks;
|
||||
}
|
||||
//! @brief Is MIFARE classic?
|
||||
inline bool isClassic() const
|
||||
{
|
||||
return valid() && is_classic(type);
|
||||
}
|
||||
//! @brief Is NTAG?
|
||||
inline bool isNTAG() const
|
||||
{
|
||||
return valid() && is_ntag(type);
|
||||
}
|
||||
|
||||
//! @brief Can change NFC?
|
||||
inline bool canNFC() const
|
||||
{
|
||||
return valid() && can_NFC(type);
|
||||
}
|
||||
//! @brief Can use FAST_READ command?
|
||||
inline bool canFastRead() const
|
||||
{
|
||||
return valid() && has_fast_read(type);
|
||||
}
|
||||
//! @brief clear
|
||||
void clear()
|
||||
{
|
||||
size = sak = blocks = 0;
|
||||
type = Type::Unknown;
|
||||
std::memset(uid, 0x00, sizeof(uid));
|
||||
}
|
||||
//! @brief Retrieve the last 4 bytes
|
||||
void tail4(uint8_t buf[4]) const
|
||||
{
|
||||
if (buf) {
|
||||
memcpy(buf, uid + size - 4, 4);
|
||||
}
|
||||
}
|
||||
//! @breif Gets the uid string for debug
|
||||
std::string uidAsString() const;
|
||||
//! @breif Gets the type string for debug
|
||||
std::string typeAsString() const;
|
||||
};
|
||||
|
||||
//! @brief Equal?
|
||||
inline bool operator==(const UID& a, const UID& b)
|
||||
{
|
||||
return (a.size == b.size) && (a.sak == b.sak) && (a.type == b.type) && (a.blocks == b.blocks) &&
|
||||
std::memcmp(a.uid, b.uid, 10) == 0;
|
||||
}
|
||||
//! @brief Not equal?
|
||||
inline bool operator!=(const UID& a, const UID& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/*!
|
||||
@struct ATQA
|
||||
@brief Answer To Request, Type A
|
||||
*/
|
||||
struct ATQA {
|
||||
// single:4 double:7 triple:10 error:0
|
||||
inline uint8_t uidLength() const
|
||||
{
|
||||
uint8_t ulen = ((value[0] >> 6) & 0x03);
|
||||
return (ulen != 0x03) ? 3 + ulen * 3 + 1 : 0;
|
||||
};
|
||||
// Anti-collision bit frame
|
||||
inline uint8_t bitFrame() const
|
||||
{
|
||||
return value[0] & 0x1F;
|
||||
}
|
||||
inline void clear()
|
||||
{
|
||||
value[0] = value[1] = 0;
|
||||
}
|
||||
|
||||
uint8_t value[2]{}; // Litte endian 16bit
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Command
|
||||
@brief ISO-14443-3/4,MIFARE,NTAG commands
|
||||
*/
|
||||
enum class Command : uint8_t {
|
||||
// ISO/IEC 14443-3
|
||||
REQA = 0x26, //!< Reequest
|
||||
WUPA = 0x52, //!< Wake-up
|
||||
HLTA = 0x50, //!< Halt
|
||||
SELECT_CL1 = 0x93, //!< Anticollison/Select CL1
|
||||
SELECT_CL2 = 0x95, //!< Anticollison/Select CL2
|
||||
SELECT_CL3 = 0x97, //!< Anticollison/Select CL3
|
||||
SELCT_CL1_OPT = 0x92, //!< Select cascade level 1 and swich bit rate to fc/64 after receive SAK
|
||||
SELCT_CL2_OPT = 0x94, //!< Select cascade level 2 and swich bit rate to fc/64 after receive SAK
|
||||
SELCT_CL3_OPT = 0x96, //!< Select cascade level 2 and swich bit rate to fc/64 after receive SAK
|
||||
// ISO/IEC 14443-4
|
||||
RATS = 0x0E, //!< Request for Answer to Select
|
||||
// MIFARE
|
||||
AUTH_WITH_KEY_A = 0x60, //!< MIFARE Classic. Authentication with Key A
|
||||
AUTH_WITH_KEY_B = 0x61, //!< MIFARE Classic. Authentication with Key B
|
||||
AUTHENTICATE_1 = 0x1A, //!< MIFARE UltraLight/C. Authentication 1st
|
||||
AUTHENTICATE_2 = 0xAF, //!< MIFARE UltraLight/C. Authentication 2nd
|
||||
READ = 0x30, //!< MIFARE. read
|
||||
WRITE = 0xA0, //!< MIFARE. write
|
||||
DECREMENT = 0xC0, //!< MIFARE Classic. decrement value block
|
||||
INCREMENT = 0xC1, //!< MIFARE Classic. increment value block
|
||||
RESTORE = 0xC2, //!< MIFARE Classic. reads the contents of a value block into the internal Transfer Buffer
|
||||
TRANSFER = 0xB0, //!< MIFARE Classic. writes the contents of the internal Transfer Buffer to a block
|
||||
PERSONALIZE_UID_USAGE = 0x40, //!< MIFARE Classic Personalize UID Usage
|
||||
SET_MOD_TYPE = 0x43, //!< MIFARE Classic SET_MOD_TYPE
|
||||
WRITE_UL = 0xA2, //!< MIFARE UltraLight/C and NTAG write
|
||||
// NTAG
|
||||
GET_VERSION = 0x60, //!< NTAG 21x. Gets the version information
|
||||
FAST_READ = 0x3A, //!< NTAG 21x excluding 210u. Read multiple pages
|
||||
READ_CNT = 0x39, //!< NTAG 213/5/6. Read counter value
|
||||
PWD_AUTH = 0x1B, //!< NTAG 21x excluding 210u. Authentication for protected area
|
||||
READ_SIG = 0x3C, //!< NTAG 21x Read NXP ECC signature
|
||||
WRITE_SIG = 0xA9, //!< NTAG 210u Write custom signature
|
||||
LOCK_SIG = 0xAC, //!< NTAG 210u Lock/Unlock signature
|
||||
};
|
||||
|
||||
} // namespace a
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_a.hpp
|
||||
@brief Common layer for NFC-A Related Units
|
||||
*/
|
||||
#include "nfc_layer_a.hpp"
|
||||
#include <inttypes.h>
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
using namespace m5::nfc::a;
|
||||
using namespace m5::nfc::a::mifare;
|
||||
using namespace m5::nfc::a::mifare::classic;
|
||||
|
||||
namespace {
|
||||
void dump_block(const uint8_t* buf, const int16_t block = -1, const int16_t sector = -1, const uint8_t ab = 0xFF,
|
||||
const bool aberror = false, const bool valueblock = false)
|
||||
{
|
||||
char tmp[128 + 1] = " ";
|
||||
uint32_t left{};
|
||||
// Sector
|
||||
if (sector >= 0) {
|
||||
left = snprintf(tmp, 4, "%02d)", sector);
|
||||
} else {
|
||||
left = 3;
|
||||
}
|
||||
// Block
|
||||
if (block >= 0) {
|
||||
left += snprintf(tmp + left, 7, "[%03d]:", block);
|
||||
} else {
|
||||
strcat(tmp, " ");
|
||||
left += 6;
|
||||
}
|
||||
// Data
|
||||
for (uint8_t i = 0; i < 16; ++i) {
|
||||
left += snprintf(tmp + left, 4, "%02X ", buf[i]);
|
||||
}
|
||||
// Access bits
|
||||
if (ab != 0xFF) {
|
||||
if (!aberror) {
|
||||
left += snprintf(tmp + left, 8, "[%d %d %d]", (ab >> 2) & 1, (ab >> 1) & 1, (ab & 1));
|
||||
} else {
|
||||
strcat(tmp + left, "[ERROR]");
|
||||
left += 7;
|
||||
}
|
||||
}
|
||||
if (valueblock) {
|
||||
int32_t value{};
|
||||
uint8_t addr{};
|
||||
if (decode_value_block(value, addr, buf)) {
|
||||
snprintf(tmp + left, 26, " Addr:%03u Val:%" PRId32 "", addr, value); // PRId32 for compile on NanoC6
|
||||
} else {
|
||||
strcat(tmp + left, "[Illgal value blcok]");
|
||||
}
|
||||
}
|
||||
::puts(tmp);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
namespace nfc {
|
||||
|
||||
// API
|
||||
const m5::nfc::a::UID& NFCLayerA::activatedDevice() const
|
||||
{
|
||||
return _impl->activatedDevice();
|
||||
}
|
||||
|
||||
bool NFCLayerA::detect(std::vector<UID>& devices, const uint32_t timeout_ms)
|
||||
{
|
||||
return _impl->detect(devices, timeout_ms);
|
||||
}
|
||||
|
||||
bool NFCLayerA::activate(const UID& uid)
|
||||
{
|
||||
return _impl->activate(uid);
|
||||
}
|
||||
|
||||
bool NFCLayerA::deactivate()
|
||||
{
|
||||
return _impl->deactivate();
|
||||
}
|
||||
|
||||
bool NFCLayerA::read(uint8_t* rx, uint16_t& rx_len, const uint16_t addr)
|
||||
{
|
||||
return _impl->read(rx, rx_len, addr);
|
||||
}
|
||||
|
||||
bool NFCLayerA::mifare_authenticate(const m5::nfc::a::Command cmd, const UID& uid, const uint8_t block, const Key& key)
|
||||
{
|
||||
return _impl->mifare_authenticate(cmd, uid, block, key);
|
||||
}
|
||||
|
||||
bool NFCLayerA::dump(const m5::nfc::a::UID& uid, const m5::nfc::a::mifare::Key& mkey)
|
||||
{
|
||||
// Activate if uid is not active
|
||||
if (!isActive(uid)) {
|
||||
deactivate();
|
||||
if (!activate(uid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Choose type
|
||||
bool ret{};
|
||||
if (uid.isClassic()) {
|
||||
ret = dump_sector_structure(uid, mkey);
|
||||
} else if (uid.type == Type::MIFARE_UltraLight || uid.isNTAG()) {
|
||||
// return dump_page_structure(uid.blocks);
|
||||
ret = false;
|
||||
}
|
||||
deactivate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool NFCLayerA::dump_sector_structure(const UID& uid, const Key& key)
|
||||
{
|
||||
uint8_t sectors = get_number_of_sectors(uid.type);
|
||||
if (!sectors) {
|
||||
return false;
|
||||
}
|
||||
|
||||
puts(
|
||||
"Sec[Blk]:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F [Access]\n"
|
||||
"-----------------------------------------------------------------");
|
||||
|
||||
bool res{};
|
||||
for (int_fast8_t sector = 0; sector < sectors; ++sector) {
|
||||
auto sblock = get_sector_trailer_block_from_sector(sector);
|
||||
if (mifareAuthenticateA(uid, sblock, key)) {
|
||||
if (!dump_sector(sector)) {
|
||||
M5_LIB_LOGE("Failed to dump:%u", sector);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
M5_LIB_LOGE("Failed to AUTH %u", sblock);
|
||||
return false;
|
||||
}
|
||||
// > Workaround for ST25R3916
|
||||
// TODO : remove it
|
||||
#if 1
|
||||
M5_LIB_LOGE("---------------- deactive");
|
||||
deactivate();
|
||||
M5_LIB_LOGE("---------------- Reactive");
|
||||
activate(uid);
|
||||
M5_LIB_LOGE("---------------- ");
|
||||
// <
|
||||
#endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool NFCLayerA::dump_sector(const uint8_t sector)
|
||||
{
|
||||
// Sector 0~31 has 4 blocks, 32-39 has 16 blocks (4K)
|
||||
const uint8_t blocks = (sector < 32) ? 4U : 16U;
|
||||
const uint8_t base = (sector < 32) ? sector * blocks : 128U + (sector - 32) * blocks;
|
||||
|
||||
uint8_t sbuf[16]{};
|
||||
uint16_t slen{16};
|
||||
uint8_t permissions[4]{}; // [3] is sector trailer
|
||||
const uint8_t saddr = base + blocks - 1; // sector traler
|
||||
|
||||
// Read sector trailer
|
||||
if (!read(sbuf, slen, saddr) || slen != 16) {
|
||||
M5_LIB_LOGE("ERROR READ1 %u", slen);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool error = !decode_access_bits(permissions, sbuf + 6 /* Access bits offset */);
|
||||
// M5_LIB_LOGW(">> S:%u => %u [%u,%u,%u,%u]", sector, saddr, permissions[0], permissions[1], permissions[2],
|
||||
// permissions[3]);
|
||||
|
||||
// Data
|
||||
for (int_fast8_t i = 0; i < blocks - 1; ++i) {
|
||||
uint8_t dbuf[16]{};
|
||||
uint16_t dlen{16};
|
||||
uint8_t daddr = base + i;
|
||||
if (!read(dbuf, dlen, daddr) || dlen != 16) {
|
||||
M5_LIB_LOGE("ERROR READ2");
|
||||
return false;
|
||||
}
|
||||
const uint8_t poffset = (blocks == 4) ? i : i / 5;
|
||||
const uint8_t permission = permissions[poffset];
|
||||
const bool show_permission = (blocks == 4) ? true : (i % 5) == 0;
|
||||
dump_block(dbuf, base + i, (i == 0) ? sector : -1, show_permission ? permission : 0xFF, error,
|
||||
is_value_block_permission(permission));
|
||||
}
|
||||
// Sector trailer
|
||||
dump_block(sbuf, saddr, -1, permissions[3], error);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_a.hpp
|
||||
@brief Common layer for NFC-A Related Units
|
||||
*/
|
||||
#ifndef M5_UNIT_NFC_NFC_LAYER_NFC_LAYER_A_HPP
|
||||
#define M5_UNIT_NFC_NFC_LAYER_NFC_LAYER_A_HPP
|
||||
|
||||
#include "nfc/a/nfca.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace m5 {
|
||||
|
||||
namespace unit {
|
||||
class UnitMFRC522; // M5Unit-RFID
|
||||
class UnitWS1850S; // M5Unit-RFID
|
||||
class UnitST25R3916;
|
||||
class CapST25R3916;
|
||||
|
||||
namespace nfc {
|
||||
|
||||
class NFCLayerA {
|
||||
public:
|
||||
struct Adapter;
|
||||
explicit NFCLayerA(UnitMFRC522& u); // The implementation of this function is located in M5Unit-RFID
|
||||
explicit NFCLayerA(UnitWS1850S& u); // The implementation of this function is located in M5Unit-RFID
|
||||
explicit NFCLayerA(UnitST25R3916& u);
|
||||
explicit NFCLayerA(CapST25R3916& u);
|
||||
|
||||
inline bool isActive(const m5::nfc::a::UID& uid) const
|
||||
{
|
||||
return uid.valid() && activatedDevice() == uid;
|
||||
}
|
||||
|
||||
const m5::nfc::a::UID& activatedDevice() const;
|
||||
|
||||
///@name Detection and activation
|
||||
///@{
|
||||
/*!
|
||||
@brief Detect idle devices
|
||||
@param[out] devices Detected devices
|
||||
@param timeout_ms Timeout (ms)
|
||||
@return True if successful
|
||||
*/
|
||||
bool detect(std::vector<m5::nfc::a::UID>& devices, const uint32_t timeout_at = 10 * 1000U);
|
||||
/*!
|
||||
@brief Activate specific device
|
||||
@param uid UID
|
||||
@return True if successful
|
||||
@note PICC to ACTIVE
|
||||
*/
|
||||
bool activate(const m5::nfc::a::UID& uid);
|
||||
///@}
|
||||
|
||||
///@name For activated device
|
||||
///@{
|
||||
/*!
|
||||
@brief Deactivate specific device
|
||||
@return True if successful
|
||||
@note PICC to HALT
|
||||
*/
|
||||
bool deactivate();
|
||||
/*!
|
||||
@brief Authentication by KeyB
|
||||
*/
|
||||
inline bool mifareAuthenticateA(const m5::nfc::a::UID& uid, const uint8_t block,
|
||||
const m5::nfc::a::mifare::Key& key = m5::nfc::a::mifare::DEFAULT_CLASSIC_KEY)
|
||||
{
|
||||
return mifare_authenticate(m5::nfc::a::Command::AUTH_WITH_KEY_A, uid, block, key);
|
||||
}
|
||||
/*!
|
||||
@brief Authentication by KeyB
|
||||
*/
|
||||
inline bool mifareAuthenticateB(const m5::nfc::a::UID& uid, const uint8_t block,
|
||||
const m5::nfc::a::mifare::Key& key = m5::nfc::a::mifare::DEFAULT_CLASSIC_KEY)
|
||||
{
|
||||
return mifare_authenticate(m5::nfc::a::Command::AUTH_WITH_KEY_B, uid, block, key);
|
||||
}
|
||||
|
||||
bool read(uint8_t* rx, uint16_t& tx_len, const uint16_t addr);
|
||||
// bool write(uint16_t& actual, const uint16_t offset, const uint8_t* buf, const uint16_t len);
|
||||
bool dump(const m5::nfc::a::UID& uid,
|
||||
const m5::nfc::a::mifare::Key& mkey = m5::nfc::a::mifare::DEFAULT_CLASSIC_KEY);
|
||||
///@}
|
||||
|
||||
protected:
|
||||
bool mifare_authenticate(const m5::nfc::a::Command cmd, const m5::nfc::a::UID& uid, const uint8_t block,
|
||||
const m5::nfc::a::mifare::Key& key);
|
||||
bool dump_sector_structure(const m5::nfc::a::UID& uid, const m5::nfc::a::mifare::Key& key);
|
||||
bool dump_sector(const uint8_t sector);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Adapter> _impl;
|
||||
};
|
||||
|
||||
// Impl for units
|
||||
struct NFCLayerA::Adapter {
|
||||
virtual ~Adapter() = default;
|
||||
inline const m5::nfc::a::UID& activatedDevice() const
|
||||
{
|
||||
return _activeUID;
|
||||
}
|
||||
virtual bool detect(std::vector<m5::nfc::a::UID>&, const uint32_t) = 0;
|
||||
virtual bool activate(const m5::nfc::a::UID& uid) = 0;
|
||||
virtual bool deactivate() = 0;
|
||||
virtual bool mifare_authenticate(const m5::nfc::a::Command cmd, const m5::nfc::a::UID& uid, const uint8_t block,
|
||||
const m5::nfc::a::mifare::Key& key) = 0;
|
||||
|
||||
virtual bool read(uint8_t* rx, uint16_t& tx_len, const uint16_t addr) = 0;
|
||||
// virtual bool write(const uint16_t offset, const uint8_t* tx, const uint16_t tx_len) = 0;
|
||||
|
||||
protected:
|
||||
m5::nfc::a::UID _activeUID{};
|
||||
};
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_a_ST25R3916.cpp
|
||||
@brief ST25R3916 adapter for common layer
|
||||
*/
|
||||
#include <nfc/layer/nfc_layer_a.hpp>
|
||||
#include "unit/unit_ST25R3916.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
using namespace m5::unit;
|
||||
using namespace m5::unit::st25r3916;
|
||||
using namespace m5::nfc::a;
|
||||
using namespace m5::nfc::a::mifare;
|
||||
using namespace m5::nfc::a::mifare::classic;
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
namespace nfc {
|
||||
|
||||
//
|
||||
struct AdapterST25R3916 final : NFCLayerA::Adapter {
|
||||
explicit AdapterST25R3916(UnitST25R3916& ref) : _u{ref}
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool detect(std::vector<UID>& devs, const uint32_t timeout_ms) override;
|
||||
virtual bool activate(const UID& uid) override;
|
||||
virtual bool deactivate() override;
|
||||
virtual bool mifare_authenticate(const m5::nfc::a::Command cmd, const UID& uid, const uint8_t block,
|
||||
const Key& key) override;
|
||||
virtual bool read(uint8_t* rx, uint16_t& tx_len, const uint16_t addr) override;
|
||||
|
||||
UnitST25R3916& _u;
|
||||
};
|
||||
|
||||
bool AdapterST25R3916::detect(std::vector<UID>& devs, const uint32_t timeout_ms)
|
||||
{
|
||||
devs.clear();
|
||||
|
||||
auto timeout_at = m5::utility::millis() + timeout_ms;
|
||||
UID uid{};
|
||||
|
||||
uint16_t atqa{};
|
||||
_u.req_wup_device(atqa, true /* REQA */);
|
||||
|
||||
do {
|
||||
uid.clear();
|
||||
// Cascade loop
|
||||
uint8_t lv{1}; // Cascade level 1-3
|
||||
bool completed{};
|
||||
do {
|
||||
if (!_u.select_with_anticollision(completed, uid, lv)) {
|
||||
return false;
|
||||
}
|
||||
} while (!completed && lv++ < 4);
|
||||
|
||||
if (!completed) {
|
||||
return false;
|
||||
}
|
||||
M5_LIB_LOGW("Detect:%s", uid.uidAsString().c_str());
|
||||
|
||||
// Type identification
|
||||
uid.type = _u.identify_mifare_type(uid);
|
||||
uid.blocks = get_number_of_blocks(uid.type);
|
||||
_activeUID = uid;
|
||||
|
||||
// Current activated device to hlt
|
||||
if (!deactivate()) {
|
||||
M5_LIB_LOGE("Failed to deactivate");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO:duplicated check
|
||||
devs.push_back(uid);
|
||||
|
||||
// Exists other devices?
|
||||
if (!_u.req_wup_device(atqa, true /* REQA */)) {
|
||||
break;
|
||||
}
|
||||
} while (m5::utility::millis() <= timeout_at);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdapterST25R3916::activate(const UID& uid)
|
||||
{
|
||||
uint16_t atqa{};
|
||||
_activeUID.clear();
|
||||
_u.req_wup_device(atqa, false /* WUPA*/);
|
||||
if (_u.select(uid)) {
|
||||
M5_LIB_LOGE("Activate:%s", uid.uidAsString().c_str());
|
||||
_activeUID = uid;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AdapterST25R3916::deactivate()
|
||||
{
|
||||
M5_LIB_LOGE("Deactivate:%s", _activeUID.uidAsString().c_str());
|
||||
_activeUID.clear();
|
||||
return _u.hltA();
|
||||
}
|
||||
|
||||
bool AdapterST25R3916::mifare_authenticate(const m5::nfc::a::Command cmd, const UID& uid, const uint8_t block,
|
||||
const Key& key)
|
||||
{
|
||||
return _u.mifare_authenticate(cmd, uid, block, key);
|
||||
}
|
||||
|
||||
bool AdapterST25R3916::read(uint8_t* rx, uint16_t& rx_len, const uint16_t addr)
|
||||
{
|
||||
return _u.read_block(rx, rx_len, addr);
|
||||
}
|
||||
|
||||
//
|
||||
namespace {
|
||||
std::unique_ptr<NFCLayerA::Adapter> make_st25r3916_adapter(UnitST25R3916& u)
|
||||
{
|
||||
return std::unique_ptr<NFCLayerA::Adapter>(new AdapterST25R3916(u));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NFCLayerA::NFCLayerA(UnitST25R3916& u) : _impl(make_st25r3916_adapter(u))
|
||||
{
|
||||
}
|
||||
|
||||
NFCLayerA::NFCLayerA(CapST25R3916& u) : _impl(make_st25r3916_adapter(static_cast<UnitST25R3916&>(u)))
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file ndef.cpp
|
||||
@brief NDEF related
|
||||
*/
|
||||
|
||||
#include "ndef.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr char uri_na[] = "";
|
||||
constexpr char uri_http_www[] = "http://www.";
|
||||
constexpr char uri_https_www[] = "https://www.";
|
||||
constexpr char uri_http[] = "http://";
|
||||
constexpr char uri_https[] = "https://";
|
||||
constexpr char uri_tel[] = "tel:";
|
||||
constexpr char uri_mailto[] = "mailto:";
|
||||
constexpr char uri_ftp_aa[] = "ftp://anonymous:anonymous@";
|
||||
constexpr char uri_ftp_ftp[] = "ftp://ftp.";
|
||||
constexpr char uri_ftps[] = "ftps://";
|
||||
constexpr char uri_sftp[] = "sftp://";
|
||||
constexpr char uri_smb[] = "smb://";
|
||||
constexpr char uri_nfs[] = "nfs://";
|
||||
constexpr char uri_ftp[] = "ftp://";
|
||||
constexpr char uri_dev[] = "dav://";
|
||||
constexpr char uri_news[] = "news:";
|
||||
constexpr char uri_telnet[] = "telnet://";
|
||||
constexpr char uri_imap[] = "imap:";
|
||||
constexpr char uri_rstp[] = "rtsp://";
|
||||
constexpr char uri_urn[] = "urn:";
|
||||
constexpr char uri_pop[] = "pop:";
|
||||
constexpr char uri_sip[] = "sip:";
|
||||
constexpr char uri_sips[] = "sips:";
|
||||
constexpr char uri_tftp[] = "tftp:";
|
||||
constexpr char uri_btspp[] = "btspp://";
|
||||
constexpr char uri_btl2cap[] = "btl2cap://";
|
||||
constexpr char uri_btgoep[] = "btgoep://";
|
||||
constexpr char uri_tcpobex[] = "tcpobex://";
|
||||
constexpr char uri_irdaobex[] = "irdaobex://";
|
||||
constexpr char uri_file[] = "file://";
|
||||
constexpr char uri_urn_epc_id[] = "urn:epc:id:";
|
||||
constexpr char uri_urn_epc_tag[] = "urn:epc:tag:";
|
||||
constexpr char uri_urn_epc_pat[] = "urn:epc:pat:";
|
||||
constexpr char uri_urn_epc_raw[] = "urn:epc:raw:";
|
||||
constexpr char uri_urn_epc[] = "urn:epc:";
|
||||
constexpr char uri_nfc[] = "urn:nfc: ";
|
||||
|
||||
constexpr const char* uri_idc_table[] = {
|
||||
uri_na, uri_http_www, uri_https_www, uri_http, uri_https, uri_tel,
|
||||
uri_mailto, uri_ftp_aa, uri_ftp_ftp, uri_ftps, uri_sftp, uri_smb,
|
||||
uri_nfs, uri_ftp, uri_dev, uri_news, uri_telnet, uri_imap,
|
||||
uri_rstp, uri_urn, uri_pop, uri_sip, uri_sips, uri_tftp,
|
||||
uri_btspp, uri_btl2cap, uri_btgoep, uri_tcpobex, uri_irdaobex, uri_file,
|
||||
uri_urn_epc_id, uri_urn_epc_tag, uri_urn_epc_pat, uri_urn_epc_raw, uri_urn_epc, uri_nfc,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
namespace ndef {
|
||||
|
||||
const char* get_uri_idc_string(const URIProtocol protocol)
|
||||
{
|
||||
auto idx = m5::stl::to_underlying(protocol);
|
||||
return uri_idc_table[idx < m5::stl::size(uri_idc_table) ? idx : 0];
|
||||
}
|
||||
|
||||
} // namespace ndef
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user