Add files

This commit is contained in:
GOB
2025-10-28 17:50:43 +09:00
parent 50a9295ace
commit b4827a5ee1
34 changed files with 9589 additions and 0 deletions
+2600
View File
File diff suppressed because it is too large Load Diff
Executable
+11
View File
@@ -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");
}
}
}
+10
View File
@@ -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"
+90
View File
@@ -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");
}
}
}
+31
View File
@@ -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"
]
}
}
+11
View File
@@ -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
View File
@@ -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
+16
View File
@@ -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
+33
View File
@@ -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
+91
View File
@@ -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
+167
View File
@@ -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
+240
View File
@@ -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
};
+197
View File
@@ -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
+259
View File
@@ -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
+201
View File
@@ -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
+125
View File
@@ -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
+73
View File
@@ -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