mirror of
https://github.com/m5stack/M5Unit-NFC.git
synced 2026-05-20 11:48:34 -07:00
Add read/write without encryption
This commit is contained in:
@@ -5,6 +5,6 @@
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Detect NFC-A PICC
|
||||
Detect NFC-F PICC
|
||||
*/
|
||||
#include "main/Detect.cpp"
|
||||
|
||||
@@ -71,10 +71,6 @@ void setup()
|
||||
}
|
||||
lcd.setFont(&fonts::Font0);
|
||||
lcd.fillScreen(0);
|
||||
|
||||
//
|
||||
// cap.dumpRegister();
|
||||
//
|
||||
}
|
||||
|
||||
void loop()
|
||||
@@ -83,13 +79,12 @@ void loop()
|
||||
Units.update();
|
||||
|
||||
std::vector<PICC> piccs;
|
||||
|
||||
// if (nfc_f.detect(piccs)) {
|
||||
if (nfc_f.detect(piccs, 0x0003)) {
|
||||
if (nfc_f.detect(piccs)) {
|
||||
M5.Speaker.tone(3000, 10);
|
||||
M5.Log.printf("%zu PICC\n", piccs.size());
|
||||
for (auto&& picc : piccs) {
|
||||
M5.Log.printf(" %s:%s %02X\n", picc.idmAsString().c_str(), picc.pmmAsString().c_str(), picc.format);
|
||||
M5.Log.printf(" %s:%s %s F:%02X DF:%04X\n", picc.idmAsString().c_str(), picc.pmmAsString().c_str(),
|
||||
picc.typeAsString().c_str(), picc.format, picc.dfc_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-F PICC
|
||||
*/
|
||||
#include "main/Dump.cpp"
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Dump NFC-F PICC
|
||||
*/
|
||||
#include <M5Unified.h>
|
||||
#include <M5UnitUnified.h>
|
||||
#include <M5UnitUnifiedNFC.h>
|
||||
#include <M5Utility.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace m5::nfc;
|
||||
using namespace m5::nfc::f;
|
||||
|
||||
namespace {
|
||||
auto& lcd = M5.Display;
|
||||
m5::unit::UnitUnified Units;
|
||||
m5::unit::CapST25R3916 cap; // ST25R3916 in the HackerCap
|
||||
m5::unit::nfc::NFCLayerF nfc_f{cap};
|
||||
|
||||
} // namespace
|
||||
|
||||
void setup()
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
auto cfg = cap.config();
|
||||
cfg.mode = NFC::F;
|
||||
cap.config(cfg);
|
||||
|
||||
#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 = {10000000, 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 PICC and click G0");
|
||||
M5.Log.printf("Please put the PICC and click G0\n");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
M5.update();
|
||||
Units.update();
|
||||
|
||||
if (M5.BtnA.wasClicked()) {
|
||||
lcd.fillRect(0, lcd.fontHeight(), lcd.width(), lcd.height() - lcd.fontHeight());
|
||||
PICC picc{};
|
||||
if (nfc_f.detect(picc)) {
|
||||
M5.Log.printf("==== Dump %s:%s %s ====\n", picc.idmAsString().c_str(), picc.pmmAsString().c_str(),
|
||||
picc.typeAsString().c_str());
|
||||
nfc_f.dump(picc);
|
||||
} else {
|
||||
M5.Log.printf("PICC NOT exists\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -211,9 +211,9 @@ struct UID {
|
||||
return valid() ? is_user_block(type, block) : false;
|
||||
}
|
||||
|
||||
//! @breif Gets the uid string for debug
|
||||
//! @brief Gets the uid string
|
||||
std::string uidAsString() const;
|
||||
//! @breif Gets the type string for debug
|
||||
//! @brief Gets the type string
|
||||
std::string typeAsString() const;
|
||||
//! @brief clear
|
||||
void clear()
|
||||
|
||||
@@ -12,6 +12,21 @@
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
namespace {
|
||||
constexpr char name_unknown[] = "Unknown";
|
||||
constexpr char name_standard[] = "FeliCa Standard";
|
||||
constexpr char name_lite[] = "FeliCa Lite";
|
||||
constexpr char name_lite_s[] = "FeliCa Lite-S";
|
||||
constexpr char name_plug[] = "FeliCa Plug";
|
||||
constexpr const char* name_table[] = {name_unknown, name_standard, name_lite, name_lite_s, name_plug};
|
||||
|
||||
// Maximum block number (Note that there are gaps in the blocks)
|
||||
constexpr uint16_t max_block_table[] = {0, 0, 0x88, 0xA0, 0};
|
||||
// Maximum number of blocks that can be read simultaneously
|
||||
constexpr uint16_t max_read_block_table[] = {1, 8, 4, 4, 4};
|
||||
|
||||
// [first/last]
|
||||
constexpr uint8_t user_block_table[][2] = {{0XFF, 0XFF}, {0XFF, 0XFF}, {0x00, 0x0D}, {0x00, 0x0D}, {0XFF, 0XFF}};
|
||||
|
||||
std::string to_string(const uint8_t* p, const uint8_t size)
|
||||
{
|
||||
char buf[2 * size + 1]{};
|
||||
@@ -29,6 +44,39 @@ namespace m5 {
|
||||
namespace nfc {
|
||||
namespace f {
|
||||
|
||||
uint16_t get_maximum_block(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
return max_block_table[idx < m5::stl::size(max_block_table) ? idx : 0];
|
||||
}
|
||||
|
||||
uint16_t get_number_of_user_blocks(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;
|
||||
}
|
||||
|
||||
uint16_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];
|
||||
}
|
||||
|
||||
uint16_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_maxumum_read_blocks(const Type t)
|
||||
{
|
||||
uint8_t idx = m5::stl::to_underlying(t);
|
||||
return max_read_block_table[idx < m5::stl::size(max_block_table) ? idx : 0];
|
||||
}
|
||||
|
||||
//
|
||||
std::string PICC::idmAsString() const
|
||||
{
|
||||
return to_string(this->idm.data(), this->idm.size());
|
||||
@@ -39,6 +87,12 @@ std::string PICC::pmmAsString() const
|
||||
return to_string(this->pmm.data(), this->pmm.size());
|
||||
}
|
||||
|
||||
std::string PICC::typeAsString() const
|
||||
{
|
||||
const auto idx = m5::stl::to_underlying(this->type);
|
||||
return std::string((idx <= m5::stl::size(name_table)) ? name_table[idx] : name_unknown);
|
||||
}
|
||||
|
||||
} // namespace f
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
|
||||
+323
-30
@@ -21,58 +21,78 @@ namespace nfc {
|
||||
*/
|
||||
namespace f {
|
||||
|
||||
using IDm = std::array<uint8_t, 8>; //!< Manufacture ID
|
||||
using PMm = std::array<uint8_t, 8>; //!< Manufacture Parameter
|
||||
|
||||
/*!
|
||||
@enum Type
|
||||
@brief Type of the PICC
|
||||
*/
|
||||
enum class Type : uint8_t {
|
||||
Unknown, //!< Unknown type
|
||||
FeliCaStandard,
|
||||
FeliCaLiteS,
|
||||
FelicaPlugin,
|
||||
Unknown, //!< Unknown type
|
||||
FeliCaStandard, //!< Standard
|
||||
FeliCaLite, //!< Lite
|
||||
FeliCaLiteS, //!< Lite-S
|
||||
FeliCaPlug, //!< Plug
|
||||
// FeliCaLink, //!< Link
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Mode
|
||||
@brief NFC-F Mode status
|
||||
*/
|
||||
enum class Mode : uint8_t {
|
||||
Mode0, //!< Power was supplied to the PICC
|
||||
Mode1, //!< Certification for PICC has been completed (Auth1)
|
||||
Mode2, //!< After mutual authentication is complete (Auth2)
|
||||
Mode3, //!< After registering area services or executing system partitioning
|
||||
|
||||
};
|
||||
|
||||
///@name Format bits
|
||||
///@{
|
||||
using Format = uint8_t;
|
||||
constexpr Format format_nfcip1{0x0001};
|
||||
constexpr Format format_dfc{0x0002};
|
||||
constexpr Format format_private{0x0004};
|
||||
constexpr Format format_ndef{0x0008};
|
||||
constexpr Format format_shared{0x0010};
|
||||
constexpr Format format_secure{0x0020};
|
||||
constexpr Format format_nfcip1{0x0001}; //!< Support ISO/IEC18092
|
||||
constexpr Format format_dfc{0x0002}; //!< Has DFC
|
||||
constexpr Format format_private{0x0004}; //!< Has private area
|
||||
constexpr Format format_ndef{0x0008}; //!< Support NDEF
|
||||
constexpr Format format_shared{0x0010}; //!< Has shared area
|
||||
constexpr Format format_secure{0x0020}; //!< FeliCa Secure ID
|
||||
constexpr Format format_felica_plug{0x0040}; //< FeliCa Plug
|
||||
///@}
|
||||
|
||||
using IDm = std::array<uint8_t, 8>; //!< Manufacture ID
|
||||
using PMm = std::array<uint8_t, 8>; //!< Manufacture Parameter
|
||||
|
||||
/*!
|
||||
@enum CommandCode
|
||||
@brief NFC-F Command code
|
||||
*/
|
||||
enum class CommandCode : uint8_t {
|
||||
Polling,
|
||||
RequestService = 0x02,
|
||||
RequestResponse = 0x04,
|
||||
RequestService = 0x02,
|
||||
RequestResponse = 0x04,
|
||||
ReadWithoutEncryption = 0x06,
|
||||
WriteWithoutEncryption = 0x08,
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum ResponseCode
|
||||
@brief NFC-F Response code
|
||||
|
||||
*/
|
||||
enum class ResponseCode : uint8_t {
|
||||
Polling = 0x01,
|
||||
RequestService = 0x03,
|
||||
Polling = 0x01,
|
||||
RequestService = 0x03,
|
||||
RequestResponse = 0x05,
|
||||
ReadWithoutEncryption = 0x07,
|
||||
WriteWithoutEncryption = 0x09,
|
||||
};
|
||||
|
||||
///@name SystemCode
|
||||
///@{
|
||||
constexpr uint16_t system_code_wildcard{0xFFFF}; //!< Wildcard
|
||||
constexpr uint16_t system_code_ndef{0x12FC}; //!< NDEF
|
||||
constexpr uint16_t system_code_secure{0x957A}; //!< FeliCa secure ID
|
||||
constexpr uint16_t system_code_shared{0xFE00}; //!< Shared area
|
||||
constexpr uint16_t system_code_dfc{0x8884}; //!< DFC
|
||||
constexpr uint16_t system_code_wildcard{0xFFFF}; //!< Wildcard
|
||||
constexpr uint16_t system_code_ndef{0x12FC}; //!< NDEF
|
||||
constexpr uint16_t system_code_felica_secure_id{0x957A}; //!< FeliCa secure ID
|
||||
constexpr uint16_t system_code_shared{0xFE00}; //!< Shared area
|
||||
constexpr uint16_t system_code_dfc{0x88B4}; //!< Lite, Lite-S
|
||||
constexpr uint16_t system_code_felica_plug{0xFEE1}; //!< FeliCa Plug
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@@ -102,24 +122,214 @@ inline constexpr uint8_t timeslot_to_slot(const TimeSlot ts)
|
||||
: 0; // Illegal
|
||||
}
|
||||
|
||||
///@name Service attribute
|
||||
///@{
|
||||
// Random service
|
||||
constexpr uint16_t service_random_read_write_auth{0x0008}; //!< Random,Read/write,Authentication required (S)
|
||||
constexpr uint16_t service_random_read_write{0x0009}; //!< Random,Read/write,No authentication required (S, L, LS)
|
||||
constexpr uint16_t service_random_read_auth{0x000A}; //!< Random,Read only,No authentication required (S)
|
||||
constexpr uint16_t service_random_read{0x000B}; //!< Random,Read only,No authentication required (S,LS)
|
||||
// Cyclic service
|
||||
constexpr uint16_t service_cyclic_read_write_auth{0x000C}; //!< Cyclic,Read/write,Authentication required(S)
|
||||
constexpr uint16_t service_cyclic_read_write{0x000D}; //!< Cyclic,Read/write,No athentication required(S)
|
||||
constexpr uint16_t service_cyclic_read_auth{0x000E}; //!< Cyclic,Read only,Authentication required(S)
|
||||
constexpr uint16_t service_cyclic_read{0x000F}; //!< Cyclic,Read only,No authentication required(S)
|
||||
// Parse service
|
||||
constexpr uint16_t service_parse_direct_auth{0x0100};
|
||||
constexpr uint16_t service_parse_direct{0x0101};
|
||||
constexpr uint16_t service_parse_cacheback_auth{0x0102};
|
||||
constexpr uint16_t service_parse_cacheback{0x0103};
|
||||
constexpr uint16_t service_parse_decrement_auth{0x0104};
|
||||
constexpr uint16_t service_parse_decrement{0x0105};
|
||||
constexpr uint16_t service_parse_increment_auth{0x0106};
|
||||
constexpr uint16_t service_parse_increment{0x0107};
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@strutc PICC
|
||||
@brief PICC informationg for NFC-F
|
||||
@struct block_t
|
||||
@brief Block list element
|
||||
*/
|
||||
struct block_t {
|
||||
uint8_t pad{};
|
||||
uint8_t header{}; //!< size:1 access:3 order:4
|
||||
uint16_t number{}; //!< block number (using low byte if 2 byte mode)
|
||||
|
||||
inline block_t() : block_t(0)
|
||||
{
|
||||
}
|
||||
inline constexpr block_t(const uint16_t num, const uint8_t access = 0, const uint8_t order = 0)
|
||||
: header{(uint8_t)(((num > 0xFF) ? 0x00 : 0x80) | ((access & 0x03) << 4) | (order & 0x0F))}, number{num}
|
||||
{
|
||||
}
|
||||
inline constexpr bool is_2byte() const
|
||||
{
|
||||
return (header & 0x80) != 0;
|
||||
}
|
||||
inline constexpr bool is_3byte() const
|
||||
{
|
||||
return (header & 0x80) == 0;
|
||||
}
|
||||
inline constexpr uint8_t access_mode() const
|
||||
{
|
||||
return (header >> 4) & 0x07;
|
||||
}
|
||||
inline constexpr uint8_t ordere() const
|
||||
{
|
||||
return (header & 0x0F);
|
||||
}
|
||||
inline constexpr uint16_t block() const
|
||||
{
|
||||
return number;
|
||||
}
|
||||
inline operator uint16_t() const
|
||||
{
|
||||
return block();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@namespacce lite
|
||||
@brief For FeliCa Lite
|
||||
*/
|
||||
namespace lite {
|
||||
///@name
|
||||
constexpr block_t S_PAD0{0x00};
|
||||
constexpr block_t S_PAD1{0x01};
|
||||
constexpr block_t S_PAD2{0x02};
|
||||
constexpr block_t S_PAD3{0x03};
|
||||
constexpr block_t S_PAD4{0x04};
|
||||
constexpr block_t S_PAD5{0x05};
|
||||
constexpr block_t S_PAD6{0x06};
|
||||
constexpr block_t S_PAD7{0x07};
|
||||
constexpr block_t S_PAD8{0x08};
|
||||
constexpr block_t S_PAD9{0x09};
|
||||
constexpr block_t S_PAD10{0x0A};
|
||||
constexpr block_t S_PAD11{0x0B};
|
||||
constexpr block_t S_PAD12{0X0C};
|
||||
constexpr block_t S_PAD13{0x0D};
|
||||
constexpr block_t REG{0x0E};
|
||||
constexpr block_t RC{0x80};
|
||||
constexpr block_t MAC{0x81};
|
||||
constexpr block_t ID{0x082};
|
||||
constexpr block_t D_ID{0x83};
|
||||
constexpr block_t SER_C{0x84};
|
||||
constexpr block_t SYS_C{0x85};
|
||||
constexpr block_t CKV{0x86};
|
||||
constexpr block_t CK{0x87};
|
||||
constexpr block_t MC{0x88};
|
||||
|
||||
} // namespace lite
|
||||
|
||||
/*!
|
||||
@namespacce lite_s
|
||||
@brief For FeliCa Lite-S
|
||||
*/
|
||||
namespace lite_s {
|
||||
constexpr block_t S_PAD0{0x00};
|
||||
constexpr block_t S_PAD1{0x01};
|
||||
constexpr block_t S_PAD2{0x02};
|
||||
constexpr block_t S_PAD3{0x03};
|
||||
constexpr block_t S_PAD4{0x04};
|
||||
constexpr block_t S_PAD5{0x05};
|
||||
constexpr block_t S_PAD6{0x06};
|
||||
constexpr block_t S_PAD7{0x07};
|
||||
constexpr block_t S_PAD8{0x08};
|
||||
constexpr block_t S_PAD9{0x09};
|
||||
constexpr block_t S_PAD10{0x0A};
|
||||
constexpr block_t S_PAD11{0x0B};
|
||||
constexpr block_t S_PAD12{0X0C};
|
||||
constexpr block_t S_PAD13{0x0D};
|
||||
constexpr block_t REG{0x0E};
|
||||
constexpr block_t RC{0x80};
|
||||
constexpr block_t MAC{0x81};
|
||||
constexpr block_t ID{0x082};
|
||||
constexpr block_t D_ID{0x83};
|
||||
constexpr block_t SER_C{0x84};
|
||||
constexpr block_t SYS_C{0x85};
|
||||
constexpr block_t CKV{0x86};
|
||||
constexpr block_t CK{0x87};
|
||||
constexpr block_t MC{0x88};
|
||||
constexpr block_t WCNT{0x90};
|
||||
constexpr block_t MAC_A{0x91};
|
||||
constexpr block_t STATE{0x92};
|
||||
constexpr block_t CRC_CHECK{0xA0};
|
||||
|
||||
} // namespace lite_s
|
||||
|
||||
//! @brief Gets the maximum block
|
||||
uint16_t get_maximum_block(const Type t);
|
||||
//! @brief Gets the number of user blocks
|
||||
uint16_t get_number_of_user_blocks(const Type t);
|
||||
//!@brief Gets the user area bytes
|
||||
inline uint16_t get_user_area_size(const Type t)
|
||||
{
|
||||
return 16 * get_number_of_user_blocks(t);
|
||||
}
|
||||
//! @brief Gets the first user area block number
|
||||
uint16_t get_first_user_block(const Type t);
|
||||
//! @brief Gets the last user area block number
|
||||
uint16_t get_last_user_block(const Type t);
|
||||
//! @brief Is block user area?
|
||||
inline bool is_user_block(const Type t, const uint16_t block)
|
||||
{
|
||||
return (block >= get_first_user_block(t)) && (block <= get_last_user_block(t));
|
||||
}
|
||||
//! @brief Maximum number of blocks that can be read simultaneously
|
||||
uint8_t get_maxumum_read_blocks(const Type t);
|
||||
|
||||
///@name RequestService
|
||||
///@{
|
||||
constexpr uint16_t NODE_SYSTEM_KEY{0xFFFF}; //!< Retrieving the System Key Version
|
||||
constexpr uint16_t KEY_VERIOSN_NONE{0xFFFF}; //!< No key version exists
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@struct PICC
|
||||
@brief PICC information for NFC-F
|
||||
*/
|
||||
struct PICC {
|
||||
IDm idm{}; //!< Manufacture ID
|
||||
PMm pmm{}; //!< //!< Manufacture Parameter
|
||||
uint16_t request_data{}; //!< Any request data
|
||||
PMm pmm{}; //!< Manufacture Parameter
|
||||
uint16_t request_data{}; //!< Any request data if exists
|
||||
RequestCode request_code{}; //!< Tyepe of the request_data
|
||||
Type type{}; //!< PICC Type
|
||||
Format format{}; //!< Format type group bits
|
||||
uint16_t dfc_format{}; //!< DFC format (ID[8],ID[9] LE) if format include DFC
|
||||
|
||||
//! @breif Gets the IDm string for debug
|
||||
//! @brief Valid?
|
||||
inline bool valid() const
|
||||
{
|
||||
return type != Type::Unknown;
|
||||
}
|
||||
|
||||
//! @brief Total user area size
|
||||
inline uint16_t userAreaSize() const
|
||||
{
|
||||
return valid() ? get_user_area_size(type) : 0;
|
||||
}
|
||||
//! @brief Gets the first user block/page address
|
||||
inline uint16_t firstUserBlock() const
|
||||
{
|
||||
return valid() ? get_first_user_block(type) : 0xFFFF;
|
||||
}
|
||||
inline uint16_t lastUserBlock() const
|
||||
{
|
||||
return valid() ? get_last_user_block(type) : 0xFFFF;
|
||||
}
|
||||
inline bool isUserBlock(const block_t block) const
|
||||
{
|
||||
return is_user_block(type, block);
|
||||
}
|
||||
|
||||
//! @brief Gets the IDm string
|
||||
std::string idmAsString() const;
|
||||
//! @breif Gets the PMm string for debug
|
||||
//! @brief Gets the PMm string
|
||||
std::string pmmAsString() const;
|
||||
//! @brief Gets the type string
|
||||
std::string typeAsString() const;
|
||||
};
|
||||
|
||||
//! @brief Equal?
|
||||
//! @brief Equal? (Only IDm,PMm)
|
||||
inline bool operator==(const PICC& a, const PICC& b)
|
||||
{
|
||||
return a.idm == b.idm && a.pmm == b.pmm;
|
||||
@@ -135,6 +345,89 @@ inline bool operator!=(const PICC& a, const PICC& b)
|
||||
constexpr uint32_t TIMEOUT_POLLING{3};
|
||||
constexpr uint32_t TIMEOUT_POLLING_PICC{2}; // 2 ms per PICC
|
||||
|
||||
/*!
|
||||
@struct REG
|
||||
@brief Subtract Register Block Data
|
||||
*/
|
||||
union REG {
|
||||
uint8_t reg[16]{};
|
||||
struct {
|
||||
uint8_t reg_a[4]; // RegA
|
||||
uint8_t reg_b[4]; // RegB
|
||||
uint8_t reg_c[8]; // RegC
|
||||
} __attribute__((packed));
|
||||
|
||||
REG() {};
|
||||
|
||||
//!@brief Gets the RegA
|
||||
inline uint32_t regA() const
|
||||
{
|
||||
return ((uint32_t)reg_a[3] << 24) | ((uint32_t)reg_a[2] << 16) | ((uint32_t)reg_a[1] << 8) |
|
||||
((uint32_t)reg_a[0]);
|
||||
}
|
||||
//!@brief Gets the RegB
|
||||
inline uint32_t regB() const
|
||||
{
|
||||
return ((uint32_t)reg_b[3] << 24) | ((uint32_t)reg_b[2] << 16) | ((uint32_t)reg_b[1] << 8) |
|
||||
((uint32_t)reg_b[0]);
|
||||
}
|
||||
//!@brief Gets the RegC
|
||||
inline uint64_t regC() const
|
||||
{
|
||||
return ((uint64_t)reg_c[0] << 56) | ((uint64_t)reg_c[1] << 48) | ((uint64_t)reg_c[2] << 40) |
|
||||
((uint64_t)reg_c[3] << 32) | ((uint64_t)reg_c[4] << 24) | ((uint64_t)reg_c[5] << 16) |
|
||||
((uint64_t)reg_c[6] << 8) | ((uint64_t)reg_c[7]);
|
||||
}
|
||||
//!@brief Set the RegA
|
||||
void regA(const uint32_t v)
|
||||
{
|
||||
reg_a[0] = v & 0xFF;
|
||||
reg_a[1] = v >> 8;
|
||||
reg_a[2] = v >> 16;
|
||||
reg_a[3] = v >> 24;
|
||||
}
|
||||
//!@brief Set the RegB
|
||||
void regB(const uint32_t v)
|
||||
{
|
||||
reg_b[0] = v & 0xFF;
|
||||
reg_b[1] = v >> 8;
|
||||
reg_b[2] = v >> 16;
|
||||
reg_b[3] = v >> 24;
|
||||
}
|
||||
//!@brief Set the RegC
|
||||
void regC(const uint64_t v)
|
||||
{
|
||||
reg_c[0] = v & 0xFF;
|
||||
reg_c[1] = v >> 8;
|
||||
reg_c[2] = v >> 16;
|
||||
reg_c[3] = v >> 24;
|
||||
reg_c[4] = v >> 32;
|
||||
reg_c[5] = v >> 40;
|
||||
reg_c[6] = v >> 48;
|
||||
reg_c[7] = v >> 56;
|
||||
}
|
||||
|
||||
inline operator const uint8_t*() const
|
||||
{
|
||||
return reg;
|
||||
}
|
||||
inline operator uint8_t*()
|
||||
{
|
||||
return reg;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
/*!
|
||||
brief Is the new value writable?
|
||||
@param o Old REG value
|
||||
@param New REG value
|
||||
@return true if writable
|
||||
*/
|
||||
inline bool can_write_reg(const REG& o, const REG& n)
|
||||
{
|
||||
return (o.regA() >= n.regA()) && (o.regB() >= n.regB());
|
||||
}
|
||||
|
||||
} // namespace f
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
|
||||
@@ -396,14 +396,16 @@ public:
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@brief Dump all blocks for debug
|
||||
@brief Dump all blocks
|
||||
@param key MIFARE classic key
|
||||
@return True if successful
|
||||
@pre All blocks must be authenticatable using the specified key if MIFARE classic
|
||||
*/
|
||||
bool dump(const m5::nfc::a::mifare::classic::Key& mkey = m5::nfc::a::mifare::classic::DEFAULT_KEY);
|
||||
/*!
|
||||
@brief Dump 1 block
|
||||
@param addr Block address
|
||||
@return True if successful
|
||||
@note The sector to which the block belongs is dumped
|
||||
@pre The block must be authenticated if MIFARE classic
|
||||
*/
|
||||
|
||||
+248
-45
@@ -19,11 +19,10 @@ using namespace m5::nfc::ndef;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t known_system_code_table[] = {
|
||||
system_code_wildcard, system_code_ndef, system_code_secure, system_code_shared, system_code_dfc,
|
||||
};
|
||||
constexpr uint16_t known_system_code_table[] = {system_code_wildcard, system_code_ndef, system_code_felica_secure_id,
|
||||
system_code_shared, system_code_dfc, system_code_felica_plug};
|
||||
|
||||
inline bool exists_system_code(const uint16_t code)
|
||||
inline bool exists_known_system_code(const uint16_t code)
|
||||
{
|
||||
return std::find(std::begin(known_system_code_table), std::end(known_system_code_table), code) !=
|
||||
std::end(known_system_code_table);
|
||||
@@ -34,6 +33,19 @@ inline bool exists_picc(const std::vector<PICC>& v, const PICC& picc)
|
||||
return std::find_if(v.begin(), v.end(), [&picc](const PICC& p) { return p == picc; }) != v.end();
|
||||
}
|
||||
|
||||
void print_block(const uint8_t buf[16], const int16_t block)
|
||||
{
|
||||
char tmp[64 + 1] = " ";
|
||||
uint32_t left{};
|
||||
// Block
|
||||
left += snprintf(tmp + left, 12, "[%04X]:", block);
|
||||
// Data
|
||||
for (uint8_t i = 0; i < 16; ++i) {
|
||||
left += snprintf(tmp + left, 4, "%02X ", buf[i]);
|
||||
}
|
||||
::puts(tmp);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
@@ -46,38 +58,19 @@ bool NFCLayerF::polling(m5::nfc::f::PICC& picc, const uint16_t system_code, cons
|
||||
return _impl->polling(picc, system_code, request_code, time_slot);
|
||||
}
|
||||
|
||||
bool NFCLayerF::detect(m5::nfc::f::PICC& picc, const uint16_t system_code, const uint32_t timeout_ms)
|
||||
bool NFCLayerF::detect(m5::nfc::f::PICC& picc, const uint32_t timeout_ms)
|
||||
{
|
||||
std::vector<PICC> v{};
|
||||
uint16_t codes[1] = {system_code};
|
||||
if (detect_picc(v, codes, 1, TimeSlot::Slot1, timeout_ms)) {
|
||||
if (!exists_system_code(system_code) && picc.format & format_private) {
|
||||
picc = v.front();
|
||||
return true;
|
||||
}
|
||||
std::vector<PICC> piccs;
|
||||
picc = PICC{};
|
||||
if (detect(piccs, TimeSlot::Slot1, timeout_ms)) {
|
||||
picc = piccs.front();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NFCLayerF::detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t system_code, m5::nfc::f::TimeSlot time_slot,
|
||||
const uint32_t timeout_ms)
|
||||
{
|
||||
uint16_t codes[1] = {system_code};
|
||||
if (!detect_picc(piccs, codes, 1, time_slot, timeout_ms)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove items that do not meet the conditions
|
||||
if (!piccs.empty() && !exists_system_code(system_code)) {
|
||||
auto it =
|
||||
std::remove_if(piccs.begin(), piccs.end(), [](PICC& picc) { return (picc.format & format_private) == 0; });
|
||||
piccs.erase(it, piccs.end());
|
||||
}
|
||||
return !piccs.empty();
|
||||
}
|
||||
|
||||
bool NFCLayerF::detect_picc(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* private_code, const uint8_t pc_size,
|
||||
m5::nfc::f::TimeSlot time_slot, const uint32_t timeout_ms)
|
||||
bool NFCLayerF::detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* private_code, const uint8_t pc_size,
|
||||
m5::nfc::f::TimeSlot time_slot, const uint32_t timeout_ms)
|
||||
{
|
||||
uint8_t slots = timeslot_to_slot(time_slot);
|
||||
|
||||
@@ -88,8 +81,10 @@ bool NFCLayerF::detect_picc(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t
|
||||
uint8_t detected{};
|
||||
do {
|
||||
PICC picc1{}, picc2{};
|
||||
Type type{};
|
||||
uint8_t format{};
|
||||
|
||||
// Format identification
|
||||
// 1. Polling wildcard
|
||||
if (!polling(picc1, system_code_wildcard, RequestCode::None, time_slot)) {
|
||||
break;
|
||||
@@ -106,18 +101,28 @@ bool NFCLayerF::detect_picc(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t
|
||||
format |= format_dfc;
|
||||
}
|
||||
|
||||
if ((picc1.idm[0] & 0x0F) == 0x04 && picc1.idm[1] == 0xFE) {
|
||||
type = Type::FeliCaStandard;
|
||||
}
|
||||
|
||||
// 3. Check private area
|
||||
for (uint_fast8_t i = 0; i < pc_size; ++i) {
|
||||
auto code = private_code[i];
|
||||
if (exists_system_code(code)) {
|
||||
M5_LIB_LOGW("Skip %04X (known system code)", code);
|
||||
continue;
|
||||
}
|
||||
if (polling(picc2, code, RequestCode::None, time_slot)) {
|
||||
if (picc1 != picc2) {
|
||||
if (private_code && pc_size) {
|
||||
for (uint_fast8_t i = 0; i < pc_size; ++i) {
|
||||
auto code = private_code[i];
|
||||
if (exists_known_system_code(code)) {
|
||||
M5_LIB_LOGW("Skip %04X (known system code)", code);
|
||||
continue;
|
||||
}
|
||||
format |= format_private;
|
||||
if (polling(picc2, code, RequestCode::None, time_slot)) {
|
||||
if (picc1 != picc2) {
|
||||
continue;
|
||||
}
|
||||
format |= format_private;
|
||||
}
|
||||
}
|
||||
// When private code is specified, PICC that do not meet the conditions are invalid
|
||||
if ((format & format_private) == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,29 +142,227 @@ bool NFCLayerF::detect_picc(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t
|
||||
format |= format_shared;
|
||||
}
|
||||
|
||||
// 6. Check DEF for FeliCa Lite-S
|
||||
// 6. Check DFC
|
||||
if (polling(picc2, system_code_dfc, RequestCode::None, time_slot)) {
|
||||
if (picc1 != picc2) {
|
||||
continue;
|
||||
}
|
||||
// TODO: read lite-S
|
||||
format |= format_shared;
|
||||
format |= format_dfc;
|
||||
}
|
||||
|
||||
// 7. Check secure
|
||||
if (polling(picc2, system_code_secure, RequestCode::None, time_slot)) {
|
||||
if (polling(picc2, system_code_felica_secure_id, RequestCode::None, time_slot)) {
|
||||
if (picc1 != picc2) {
|
||||
continue;
|
||||
}
|
||||
format |= format_secure;
|
||||
}
|
||||
|
||||
// Type identification
|
||||
if (polling(picc2, system_code_felica_plug, RequestCode::None, time_slot)) {
|
||||
if (picc1 != picc2) {
|
||||
continue;
|
||||
}
|
||||
type = Type::FeliCaPlug;
|
||||
} else {
|
||||
// Lite or LiteS?
|
||||
if (format & format_dfc) {
|
||||
uint8_t rbuf[16]{};
|
||||
type = read16(rbuf, picc1, 0xA0 /* CRC_CHECK */) ? Type::FeliCaLiteS : Type::FeliCaLite;
|
||||
if (!read16(rbuf, picc1, 0x82)) { // Read ID(DFC format)
|
||||
continue;
|
||||
}
|
||||
picc1.dfc_format = rbuf[8] | ((uint16_t)rbuf[9] << 8);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (type == Type::Unknown && format) {
|
||||
type = Type::FeliCaStandard;
|
||||
}
|
||||
|
||||
//
|
||||
picc1.format = format;
|
||||
picc1.type = type;
|
||||
|
||||
piccs.push_back(picc1);
|
||||
++detected;
|
||||
} while (detected < slots && m5::utility::millis() <= timeout_at);
|
||||
|
||||
return !piccs.empty();
|
||||
return detected > 0;
|
||||
}
|
||||
|
||||
bool NFCLayerF::requestService(uint16_t& key_version, const m5::nfc::f::PICC& picc, const uint16_t node_code)
|
||||
{
|
||||
uint16_t n[1] = {node_code};
|
||||
uint16_t k[1]{};
|
||||
key_version = KEY_VERIOSN_NONE;
|
||||
if (requestService(k, picc, n, 1)) {
|
||||
key_version = k[0];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NFCLayerF::requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code,
|
||||
const uint8_t node_size)
|
||||
{
|
||||
return _impl->requestService(key_version, picc, node_code, node_size);
|
||||
}
|
||||
|
||||
bool NFCLayerF::read16(uint8_t rx[16], const m5::nfc::f::PICC& picc, const block_t block)
|
||||
{
|
||||
uint16_t rx_len{16};
|
||||
uint16_t sc{service_random_read};
|
||||
return rx && _impl->readWithoutEncryption(rx, rx_len, picc, &sc, 1, &block, 1);
|
||||
}
|
||||
|
||||
bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc, const block_t sblock)
|
||||
{
|
||||
auto rx_len_org = rx_len;
|
||||
rx_len = 0;
|
||||
|
||||
uint16_t sc{service_random_read};
|
||||
uint16_t start = sblock.block();
|
||||
uint16_t blocks = rx_len_org >> 4;
|
||||
uint16_t last = std::min<uint16_t>(picc.lastUserBlock(), start + blocks - 1);
|
||||
if (!rx || !rx_len_org || !picc.isUserBlock(sblock) || !picc.isUserBlock(last)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t batch_size = get_maxumum_read_blocks(picc.type);
|
||||
uint8_t rbuf[16 * batch_size]{};
|
||||
block_t block{start};
|
||||
auto out = rx;
|
||||
uint16_t read_count{};
|
||||
while (block.block() <= last) {
|
||||
uint16_t num = std::min<uint16_t>(last - block.block() + 1, batch_size);
|
||||
|
||||
uint16_t actual = 16 * num;
|
||||
block_t block_list[blocks]{};
|
||||
for (uint_fast16_t i = 0; i < num; ++i) {
|
||||
block_list[i] = block_t(block.block() + i);
|
||||
// M5_LIB_LOGE(" BL[%u]:%02X", i, block_list[i].block());
|
||||
}
|
||||
// M5_LIB_LOGE("rx_len:%u block_list_num:%u", actual, num);
|
||||
|
||||
if (!_impl->readWithoutEncryption(rbuf, actual, picc, &sc, 1, block_list, num) || actual != 16 * num) {
|
||||
return false;
|
||||
}
|
||||
memcpy(out, rbuf, actual);
|
||||
out += actual;
|
||||
read_count += actual;
|
||||
block.number += num;
|
||||
}
|
||||
rx_len = read_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFCLayerF::write16(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t block, const uint8_t tx[16],
|
||||
const uint16_t tx_len)
|
||||
{
|
||||
if (tx && tx_len) {
|
||||
uint16_t sc{service_random_read_write};
|
||||
uint8_t buf[16]{};
|
||||
memcpy(buf, tx, std::min<uint16_t>(16u, tx_len));
|
||||
return _impl->writeWithoutEncryption(picc, &sc, 1, &block, 1, buf, 16);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NFCLayerF::write(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t sblock, const uint8_t* tx,
|
||||
const uint16_t tx_len)
|
||||
{
|
||||
if (!tx || !tx_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t start = sblock.block();
|
||||
uint16_t blocks = (tx_len + 15) >> 4;
|
||||
uint16_t last = std::min<uint16_t>(picc.lastUserBlock(), start + blocks - 1);
|
||||
if (!tx || !tx_len || !picc.isUserBlock(sblock) || !picc.isUserBlock(last)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t written{};
|
||||
for (uint16_t block = start; block <= last; ++block) {
|
||||
const uint16_t wsize = std::min<uint16_t>(tx_len - written, 16);
|
||||
// M5_LIB_LOGE("write:%02X %u", block, wsize);
|
||||
if (!write16(picc, block, tx + written, wsize)) {
|
||||
return false;
|
||||
}
|
||||
written += wsize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFCLayerF::dump(const PICC& picc)
|
||||
{
|
||||
switch (picc.type) {
|
||||
case Type::FeliCaLite:
|
||||
return dump_felica_lite(picc);
|
||||
case Type::FeliCaLiteS:
|
||||
return dump_felica_lite_s(picc);
|
||||
case Type::FeliCaStandard:
|
||||
M5_LIB_LOGE("Not yet");
|
||||
break;
|
||||
default:
|
||||
M5_LIB_LOGE("Not yet");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NFCLayerF::dump(const PICC& picc, const block_t block)
|
||||
{
|
||||
// TODO
|
||||
return dump_block(picc, block);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
bool NFCLayerF::dump_felica_lite(const m5::nfc::f::PICC& picc)
|
||||
{
|
||||
puts(
|
||||
"Block: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"
|
||||
"------------------------------------------------------");
|
||||
|
||||
bool ret{true};
|
||||
for (uint8_t block = 0; block < 0x0F; ++block) {
|
||||
ret &= dump_block(picc, block);
|
||||
}
|
||||
for (uint8_t block = 0x80; block < 0x89; ++block) {
|
||||
ret &= dump_block(picc, block);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool NFCLayerF::dump_felica_lite_s(const m5::nfc::f::PICC& picc)
|
||||
{
|
||||
bool ret{true};
|
||||
ret &= dump_felica_lite(picc);
|
||||
for (uint8_t block = 0x90; block < 0x93; ++block) {
|
||||
// MAC_A(0x91) cannot be read unless written to RC.
|
||||
if (block == 0x91) {
|
||||
printf("[%04X]:MAC_A needs wrtite to RC\n", block);
|
||||
continue;
|
||||
}
|
||||
ret &= dump_block(picc, block);
|
||||
}
|
||||
ret &= dump_block(picc, 0xA0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool NFCLayerF::dump_block(const m5::nfc::f::PICC& picc, m5::nfc::f::block_t block)
|
||||
{
|
||||
uint8_t buf[16]{};
|
||||
|
||||
if (read16(buf, picc, block)) {
|
||||
print_block(buf, block.block());
|
||||
return true;
|
||||
}
|
||||
printf("[%04X]:ERROR\n", block.block());
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
+104
-19
@@ -23,7 +23,6 @@
|
||||
#include <memory>
|
||||
|
||||
namespace m5 {
|
||||
|
||||
namespace unit {
|
||||
class UnitST25R3916;
|
||||
class CapST25R3916;
|
||||
@@ -59,41 +58,118 @@ public:
|
||||
@param[out] picc Detected PICC
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
@warning Misclassification may occur depending on the distance between the reader and the PICC
|
||||
*/
|
||||
inline bool detect(m5::nfc::f::PICC& picc, const uint32_t timeout_ms)
|
||||
{
|
||||
return detect(picc, 0xFFFF /*skip*/, timeout_ms);
|
||||
}
|
||||
/*!
|
||||
@brief Detect single PICC matching the specified system code
|
||||
@param[out] picc Detected PICC
|
||||
@param system_code System code
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
*/
|
||||
bool detect(m5::nfc::f::PICC& picc, const uint16_t system_code, const uint32_t timeout_ms = 100U);
|
||||
bool detect(m5::nfc::f::PICC& picc, const uint32_t timeout_ms = 25);
|
||||
/*!
|
||||
@brief Detect PICCs
|
||||
@param[out] piccs Detected PICCs
|
||||
@param time_slot Maximum number of slots that can be responded
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
@warning Misclassification may occur depending on the distance between the reader and the PICC
|
||||
*/
|
||||
inline bool detect(std::vector<m5::nfc::f::PICC>& piccs,
|
||||
m5::nfc::f::TimeSlot time_slot = m5::nfc::f::TimeSlot::Slot16, const uint32_t timeout_ms = 500U)
|
||||
{
|
||||
return detect_picc(piccs, nullptr, 0, time_slot, timeout_ms);
|
||||
return detect(piccs, nullptr, 0, time_slot, timeout_ms);
|
||||
}
|
||||
/*!
|
||||
@brief Detect PICCs matching the specified system code
|
||||
@param[out] piccs Detected PICCs
|
||||
@param system_code System code
|
||||
@param private_code Private system code
|
||||
@param time_slot Maximum number of slots that can be responded
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
@note If a private code is specified, PICC that does not meet the conditions will not be detected
|
||||
@warning Misclassification may occur depending on the distance between the reader and the PICC
|
||||
*/
|
||||
bool detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t system_code,
|
||||
inline bool detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t private_code,
|
||||
m5::nfc::f::TimeSlot time_slot = m5::nfc::f::TimeSlot::Slot16, const uint32_t timeout_ms = 500U)
|
||||
{
|
||||
return detect(piccs, &private_code, 1, time_slot, timeout_ms);
|
||||
}
|
||||
/*!
|
||||
@brief Detect PICCs matching the specified system code
|
||||
@param[out] piccs Detected PICCs
|
||||
@param private_code Private system code array
|
||||
@param pc_size private_code size
|
||||
@param time_slot Maximum number of slots that can be responded
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
@note If a private code is specified, PICC that does not meet the conditions will not be detected
|
||||
@note private codes are OR matching
|
||||
@warning Misclassification may occur depending on the distance between the reader and the PICC
|
||||
*/
|
||||
bool detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* private_code, const uint8_t pc_size,
|
||||
m5::nfc::f::TimeSlot time_slot = m5::nfc::f::TimeSlot::Slot16, const uint32_t timeout_ms = 500U);
|
||||
///@}
|
||||
|
||||
///@name Request
|
||||
///@{
|
||||
bool requestService(uint16_t& key_version, const m5::nfc::f::PICC& picc, const uint16_t node_code);
|
||||
bool requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code,
|
||||
const uint8_t node_num);
|
||||
///@}
|
||||
|
||||
///@name Random Read/Write
|
||||
///@{
|
||||
/*!
|
||||
@brief Read the 1 block
|
||||
@param[out] rx Output buffer
|
||||
@param picc Target PICC
|
||||
@param block Target block
|
||||
@return True if detected
|
||||
*/
|
||||
bool read16(uint8_t rx[16], const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t block);
|
||||
/*!
|
||||
@brief Read any bytes from user area
|
||||
@details Continue reading only the user area from the first block of the user area until rx_len is satisfied
|
||||
@param[out] rx Buffer
|
||||
@param[in/out] rx_len in:buffer size, out:actual read size
|
||||
@param sblock Block to start reading
|
||||
@return True if successful
|
||||
@warning rx in 16-byte units
|
||||
*/
|
||||
bool read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t sblock);
|
||||
|
||||
/*!
|
||||
@brief Write the 1 block
|
||||
@param picc Target PICC
|
||||
@param block Target block
|
||||
@param tx Buffer
|
||||
@param tx_len Buffer size
|
||||
@return True if detected
|
||||
@warning If the tx_len is less than 16 bytes, the remaining space is filled with 0x00
|
||||
@warning If the tx_len is larger than 16 bytes, only the first 16 bytes will be written
|
||||
*/
|
||||
bool write16(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t block, const uint8_t tx[16],
|
||||
const uint16_t tx_len);
|
||||
/*!
|
||||
@brief Write any bytes to user area
|
||||
@details Continue writing only the user area from the first block of the user area until tx_len is satisfied
|
||||
@param sblock Block to start writing
|
||||
@param tx Buffer
|
||||
@param tx_len Buffer size
|
||||
@return True if successful
|
||||
*/
|
||||
bool write(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t sblock, const uint8_t* tx,
|
||||
const uint16_t tx_len);
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@brief Dump all blocks
|
||||
@return True if successful
|
||||
@note Only the sections that can be read without authentication
|
||||
*/
|
||||
bool dump(const m5::nfc::f::PICC& picc);
|
||||
/*!
|
||||
@brief Dump 1 block
|
||||
@param block block list element
|
||||
@return True if successful
|
||||
@note Only the sections that can be read without authentication
|
||||
*/
|
||||
bool dump(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t block);
|
||||
|
||||
protected:
|
||||
virtual bool read(uint8_t* rx, uint16_t& rx_len, const uint8_t saddr) override;
|
||||
@@ -101,8 +177,9 @@ protected:
|
||||
virtual uint16_t firstUserBlock() const override;
|
||||
virtual uint16_t lastUserBlock() const override;
|
||||
|
||||
bool detect_picc(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* private_system_code, const uint8_t pc_size,
|
||||
m5::nfc::f::TimeSlot time_slot, const uint32_t timeout_ms);
|
||||
bool dump_felica_lite(const m5::nfc::f::PICC& picc);
|
||||
bool dump_felica_lite_s(const m5::nfc::f::PICC& picc);
|
||||
bool dump_block(const m5::nfc::f::PICC& picc, m5::nfc::f::block_t block);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Adapter> _impl;
|
||||
@@ -115,7 +192,15 @@ struct NFCLayerF::Adapter {
|
||||
virtual ~Adapter() = default;
|
||||
|
||||
virtual bool polling(m5::nfc::f::PICC& picc, const uint16_t system_code, const m5::nfc::f::RequestCode request_code,
|
||||
const m5::nfc::f::TimeSlot time_slot) = 0;
|
||||
const m5::nfc::f::TimeSlot time_slot) = 0;
|
||||
virtual bool requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code,
|
||||
const uint8_t node_num) = 0;
|
||||
virtual bool readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
|
||||
const uint16_t* service_code, const uint8_t service_num,
|
||||
const m5::nfc::f::block_t* block_list, const uint8_t block_num) = 0;
|
||||
virtual bool writeWithoutEncryption(const m5::nfc::f::PICC& picc, const uint16_t* service_code,
|
||||
const uint8_t service_num, const m5::nfc::f::block_t* block_list,
|
||||
const uint8_t block_num, const uint8_t* tx, const uint16_t tx_len) = 0;
|
||||
};
|
||||
///@endcond
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@ struct AdapterST25R3916ForF final : NFCLayerF::Adapter {
|
||||
|
||||
virtual bool polling(m5::nfc::f::PICC& picc, const uint16_t system_code, const m5::nfc::f::RequestCode request_code,
|
||||
const m5::nfc::f::TimeSlot time_slot) override;
|
||||
virtual bool requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code,
|
||||
const uint8_t node_num) override;
|
||||
virtual bool readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
|
||||
const uint16_t* service_code, const uint8_t service_num,
|
||||
const block_t* block_list, const uint8_t block_num) override;
|
||||
virtual bool writeWithoutEncryption(const m5::nfc::f::PICC& picc, const uint16_t* service_code,
|
||||
const uint8_t service_num, const m5::nfc::f::block_t* block_list,
|
||||
const uint8_t block_num, const uint8_t* tx, const uint16_t tx_len) override;
|
||||
|
||||
UnitST25R3916& _u;
|
||||
};
|
||||
@@ -38,6 +46,26 @@ bool AdapterST25R3916ForF::polling(m5::nfc::f::PICC& picc, const uint16_t system
|
||||
return _u.nfcfPolling(picc, system_code, request_code, time_slot);
|
||||
}
|
||||
|
||||
bool AdapterST25R3916ForF::requestService(uint16_t key_version[], const m5::nfc::f::PICC& picc,
|
||||
const uint16_t* node_code, const uint8_t node_num)
|
||||
{
|
||||
return _u.nfcfRequestService(key_version, picc, node_code, node_num);
|
||||
}
|
||||
|
||||
bool AdapterST25R3916ForF::readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
|
||||
const uint16_t* service_code, const uint8_t service_num,
|
||||
const block_t* block_list, const uint8_t block_size)
|
||||
{
|
||||
return _u.nfcfReadWithoutEncryption(rx, rx_len, picc, service_code, service_num, block_list, block_size);
|
||||
}
|
||||
|
||||
bool AdapterST25R3916ForF::writeWithoutEncryption(const m5::nfc::f::PICC& picc, const uint16_t* service_code,
|
||||
const uint8_t service_num, const m5::nfc::f::block_t* block_list,
|
||||
const uint8_t block_num, const uint8_t* tx, const uint16_t tx_len)
|
||||
{
|
||||
return _u.nfcfWriteWithoutEncryption(picc, service_code, service_num, block_list, block_num, tx, tx_len);
|
||||
}
|
||||
|
||||
//
|
||||
namespace {
|
||||
std::unique_ptr<NFCLayerF::Adapter> make_st25r3916_adapter(UnitST25R3916& u)
|
||||
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
bool configureNFCMode(const m5::nfc::NFC mode);
|
||||
|
||||
/*!
|
||||
@breif Is the current operating mode the one specified?
|
||||
@brief Is the current operating mode the one specified?
|
||||
@param mode Mode
|
||||
@return True if the current operation is in the specified mode
|
||||
*/
|
||||
@@ -1796,7 +1796,7 @@ public:
|
||||
@param spage Start reading page
|
||||
@param epage End reading page
|
||||
@return True if successful
|
||||
@warning Only PICC supporting the FAST_READ command
|
||||
@warning Only PICC with the FAST_READ command
|
||||
*/
|
||||
bool ntagReadPage(uint8_t* rx, uint16_t& rx_len, const uint8_t spage, const uint8_t epage);
|
||||
///@}
|
||||
@@ -1806,7 +1806,7 @@ public:
|
||||
///@{
|
||||
/*!
|
||||
@brief Transceive
|
||||
@param rx Receive buffer
|
||||
@param[out] rx Receive buffer
|
||||
@param[in/out] rx_len in:Size of receive buffer out:actual read size
|
||||
@param tx Send buffer
|
||||
@param tx_len Size of send buffer
|
||||
@@ -1827,10 +1827,50 @@ public:
|
||||
bool nfcfPolling(m5::nfc::f::PICC& picc, const uint16_t system_code, const m5::nfc::f::RequestCode request_code,
|
||||
const m5::nfc::f::TimeSlot time_slot);
|
||||
|
||||
/*!
|
||||
@brief Requset service
|
||||
@param[out] ky_version Node key version array (at leaset node_size)
|
||||
@param picc PICC
|
||||
@param node_code Node code array
|
||||
@param node_num Number of elements in the node_code array
|
||||
@return True if successful
|
||||
@warning FeliCa Standard only
|
||||
*/
|
||||
bool nfcfRequestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code,
|
||||
const uint8_t node_num);
|
||||
|
||||
bool nfcfRequestService(const m5::nfc::f::PICC& picc);
|
||||
|
||||
|
||||
/*!
|
||||
@brief Read the area that does not require authentication
|
||||
@param[out] rx Buffer(at least 16 bytes * number of blocks)
|
||||
@param[in/out] rx_len in:Bufffer size out:Actual size
|
||||
@param picc PICC
|
||||
@param service_code Service code array
|
||||
@param service_num Number of elements in the service_code array (1-16)
|
||||
@param block_list Block list
|
||||
@param block_num Number of elements in the block_list array (1-8)
|
||||
@return True if successful
|
||||
@note The read unit is 16 bytes
|
||||
@warning The maximum number of blocks read varies by type
|
||||
*/
|
||||
bool nfcfReadWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
|
||||
const uint16_t* service_code, const uint8_t service_num,
|
||||
const m5::nfc::f::block_t* block_list, const uint8_t block_num);
|
||||
/*!
|
||||
@brief Write the area that does not require authentication
|
||||
@param picc PICC
|
||||
@param service_code Service code array
|
||||
@param service_num Number of elements in the service_code array (1-16)
|
||||
@param block_list Block list
|
||||
@param block_num Number of elements in the block_list array
|
||||
@param tx Buffer
|
||||
@param tx_len tx size
|
||||
@return True if successful
|
||||
@note The write unit is 16 bytes
|
||||
@warning The maximum number of blocks write varies by type
|
||||
*/
|
||||
bool nfcfWriteWithoutEncryption(const m5::nfc::f::PICC& picc, const uint16_t* service_code,
|
||||
const uint8_t service_num, const m5::nfc::f::block_t* block_list,
|
||||
const uint8_t block_num, const uint8_t* tx, const uint16_t tx_len);
|
||||
///@}
|
||||
|
||||
// For debug
|
||||
|
||||
@@ -40,7 +40,17 @@ uint8_t val_table[] = {
|
||||
};
|
||||
*/
|
||||
|
||||
uint32_t get_block_list_size(const block_t* block_list, const uint8_t block_num)
|
||||
{
|
||||
uint32_t sz{};
|
||||
if (block_list && block_num) {
|
||||
for (uint_fast16_t i = 0; i < block_num; ++i) {
|
||||
sz += 2 + block_list[i].is_3byte();
|
||||
}
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
@@ -142,7 +152,7 @@ bool UnitST25R3916::nfcfPolling(m5::nfc::f::PICC& picc, const uint16_t system_co
|
||||
|
||||
// m5::utility::log::dump(packet, sizeof(packet), false);
|
||||
|
||||
uint8_t timeout_ms = TIMEOUT_POLLING * TIMEOUT_POLLING_PICC * timeslot_to_slot(time_slot);
|
||||
uint32_t timeout_ms = TIMEOUT_POLLING * TIMEOUT_POLLING_PICC * timeslot_to_slot(time_slot);
|
||||
|
||||
uint8_t rbuf[128]{};
|
||||
uint16_t rx_len{128};
|
||||
@@ -166,24 +176,33 @@ bool UnitST25R3916::nfcfPolling(m5::nfc::f::PICC& picc, const uint16_t system_co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitST25R3916::nfcfRequestService(const m5::nfc::f::PICC& picc)
|
||||
bool UnitST25R3916::nfcfRequestService(uint16_t key_version[], const m5::nfc::f::PICC& picc, const uint16_t* node_code,
|
||||
const uint8_t node_num)
|
||||
{
|
||||
CHECK_MODE();
|
||||
|
||||
std::vector<uint8_t> packet{};
|
||||
uint8_t node_size = 1;
|
||||
packet.resize(1 + 8 + 1 + (2 * node_size));
|
||||
if (!key_version || !node_code || !node_num || picc.type != Type::FeliCaStandard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t timeout_ms = 10;
|
||||
std::vector<uint8_t> packet{};
|
||||
uint32_t timeout_ms = 10; // TODO
|
||||
|
||||
packet.resize(1 + 8 + 1 + (2 * node_num));
|
||||
|
||||
packet[0] = m5::stl::to_underlying(CommandCode::RequestService);
|
||||
memcpy(packet.data() + 1, picc.idm.data(), picc.idm.size());
|
||||
packet[9] = node_size;
|
||||
packet[10] = 0xAA; // todo
|
||||
packet[11] = 0xBB; // toddo
|
||||
packet[9] = node_num;
|
||||
uint8_t* p = packet.data() + 10;
|
||||
for (uint_fast8_t i = 0; i < node_num; ++i) {
|
||||
*p++ = node_code[i] & 0xFF;
|
||||
*p++ = node_code[i] >> 8;
|
||||
}
|
||||
|
||||
uint8_t rbuf[packet.size()]{};
|
||||
uint16_t rx_len{packet.size()};
|
||||
uint16_t rx_len{(uint8_t)packet.size()};
|
||||
|
||||
// m5::utility::log::dump(packet.data(), packet.size(), false);
|
||||
|
||||
if (!nfcfTransceive(rbuf, rx_len, packet.data(), packet.size(), timeout_ms) //
|
||||
|| rx_len < packet.size() || rbuf[1] != m5::stl::to_underlying(ResponseCode::RequestService)) {
|
||||
@@ -193,8 +212,114 @@ bool UnitST25R3916::nfcfRequestService(const m5::nfc::f::PICC& picc)
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::utility::log::dump(rbuf, rx_len, false);
|
||||
// m5::utility::log::dump(rbuf, rx_len, false);
|
||||
|
||||
// TODO
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitST25R3916::nfcfReadWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
|
||||
const uint16_t* service_code, const uint8_t service_num,
|
||||
const block_t* block_list, const uint8_t block_num)
|
||||
{
|
||||
CHECK_MODE();
|
||||
|
||||
auto rx_org_len = rx_len;
|
||||
rx_len = 0;
|
||||
|
||||
if (!rx || !rx_org_len || !service_code || service_num > 16 || !block_num) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> packet{};
|
||||
const uint32_t block_size = get_block_list_size(block_list, block_num);
|
||||
uint32_t timeout_ms = 10; // TODO
|
||||
|
||||
packet.resize(1 + 8 + 1 + (2 * service_num) + 1 + block_size);
|
||||
|
||||
packet[0] = m5::stl::to_underlying(CommandCode::ReadWithoutEncryption);
|
||||
memcpy(packet.data() + 1, picc.idm.data(), picc.idm.size());
|
||||
packet[9] = service_num;
|
||||
uint8_t* p = packet.data() + 10;
|
||||
for (uint_fast8_t i = 0; i < service_num; ++i) {
|
||||
*p++ = service_code[i] & 0xFF;
|
||||
*p++ = service_code[i] >> 8;
|
||||
}
|
||||
*p++ = block_num;
|
||||
for (uint_fast8_t i = 0; i < block_num; ++i) {
|
||||
block_t ble = block_list[i];
|
||||
*p++ = ble.header;
|
||||
if (ble.is_3byte()) {
|
||||
*p++ = ble.block() >> 8;
|
||||
}
|
||||
*p++ = ble.block() & 0xFF;
|
||||
}
|
||||
|
||||
// m5::utility::log::dump(packet.data(), packet.size(), false);
|
||||
|
||||
uint8_t rbuf[1 + 1 + 8 + 1 + 1 + 1 + 16 * block_num]{};
|
||||
uint16_t actual = sizeof(rbuf);
|
||||
if (!nfcfTransceive(rbuf, actual, packet.data(), packet.size(), timeout_ms) || actual < 12 || (rbuf[0] < 11) ||
|
||||
rbuf[1] != m5::stl::to_underlying(ResponseCode::ReadWithoutEncryption) || //
|
||||
(rbuf[10] /*status 1*/ != 0x00) || (rbuf[11] /*status 2*/ != 0x00)) {
|
||||
M5_LIB_LOGE("Failed to readWithoutEncryption %u %u %02X %02X", actual, rbuf[0], rbuf[10], rbuf[11]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// m5::utility::log::dump(rbuf, actual, false);
|
||||
|
||||
// const uint8_t blocks = rbuf[11];
|
||||
rx_len = std::min<uint16_t>(actual - 13, rx_org_len);
|
||||
memcpy(rx, rbuf + 13, rx_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitST25R3916::nfcfWriteWithoutEncryption(const m5::nfc::f::PICC& picc, const uint16_t* service_code,
|
||||
const uint8_t service_num, const m5::nfc::f::block_t* block_list,
|
||||
const uint8_t block_num, const uint8_t* tx, const uint16_t tx_len)
|
||||
{
|
||||
CHECK_MODE();
|
||||
if (!tx || !tx_len || !service_code || service_num > 16 || !block_num) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> packet{};
|
||||
const uint32_t block_size = get_block_list_size(block_list, block_num);
|
||||
uint32_t timeout_ms = 10; // TODO
|
||||
|
||||
packet.resize(1 + 8 + 1 + (2 * service_num) + 1 + block_size + tx_len);
|
||||
|
||||
packet[0] = m5::stl::to_underlying(CommandCode::WriteWithoutEncryption);
|
||||
memcpy(packet.data() + 1, picc.idm.data(), picc.idm.size());
|
||||
packet[9] = service_num;
|
||||
uint8_t* p = packet.data() + 10;
|
||||
for (uint_fast8_t i = 0; i < service_num; ++i) {
|
||||
*p++ = service_code[i] & 0xFF;
|
||||
*p++ = service_code[i] >> 8;
|
||||
}
|
||||
*p++ = block_num;
|
||||
for (uint_fast8_t i = 0; i < block_num; ++i) {
|
||||
block_t ble = block_list[i];
|
||||
*p++ = ble.header;
|
||||
if (ble.is_3byte()) {
|
||||
*p++ = ble.block() >> 8;
|
||||
}
|
||||
*p++ = ble.block() & 0xFF;
|
||||
}
|
||||
memcpy(p, tx, tx_len);
|
||||
|
||||
// m5::utility::log::dump(packet.data(), packet.size(), false);
|
||||
|
||||
uint8_t rbuf[1 + 1 + 8 + 1 + 1]{};
|
||||
uint16_t actual = sizeof(rbuf);
|
||||
if (!nfcfTransceive(rbuf, actual, packet.data(), packet.size(), timeout_ms) || actual < 12 || (rbuf[0] < 11) ||
|
||||
rbuf[1] != m5::stl::to_underlying(ResponseCode::WriteWithoutEncryption) || //
|
||||
(rbuf[10] /*status 1*/ != 0x00) || (rbuf[11] /*status 2*/ != 0x00)) {
|
||||
// m5::utility::log::dump(rbuf, actual, false);
|
||||
M5_LIB_LOGE("Failed to writeWithoutEncryption %u %u %02X %02X", actual, rbuf[0], rbuf[10], rbuf[11]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user