From fce94876245e1fe972d48b42a35db2da1eaea8d7 Mon Sep 17 00:00:00 2001 From: GOB Date: Mon, 1 Dec 2025 17:03:08 +0900 Subject: [PATCH] Add read/write without encryption --- examples/UnitUnified/NFCF/Detect/Detect.ino | 2 +- .../UnitUnified/NFCF/Detect/main/Detect.cpp | 11 +- examples/UnitUnified/NFCF/Dump/Dump.ino | 10 + examples/UnitUnified/NFCF/Dump/main/Dump.cpp | 92 +++++ src/nfc/a/nfca.hpp | 4 +- src/nfc/f/nfcf.cpp | 54 +++ src/nfc/f/nfcf.hpp | 353 ++++++++++++++++-- src/nfc/layer/nfc_layer_a.hpp | 4 +- src/nfc/layer/nfc_layer_f.cpp | 293 ++++++++++++--- src/nfc/layer/nfc_layer_f.hpp | 123 +++++- src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp | 28 ++ src/unit/unit_ST25R3916.hpp | 52 ++- src/unit/unit_ST25R3916_nfcf.cpp | 147 +++++++- 13 files changed, 1050 insertions(+), 123 deletions(-) create mode 100644 examples/UnitUnified/NFCF/Dump/Dump.ino create mode 100644 examples/UnitUnified/NFCF/Dump/main/Dump.cpp diff --git a/examples/UnitUnified/NFCF/Detect/Detect.ino b/examples/UnitUnified/NFCF/Detect/Detect.ino index 8d44340..749a852 100644 --- a/examples/UnitUnified/NFCF/Detect/Detect.ino +++ b/examples/UnitUnified/NFCF/Detect/Detect.ino @@ -5,6 +5,6 @@ */ /* Example using M5UnitUnified for M5Cardputer-ADV with HackerCap - Detect NFC-A PICC + Detect NFC-F PICC */ #include "main/Detect.cpp" diff --git a/examples/UnitUnified/NFCF/Detect/main/Detect.cpp b/examples/UnitUnified/NFCF/Detect/main/Detect.cpp index 2d2142a..ae7233f 100644 --- a/examples/UnitUnified/NFCF/Detect/main/Detect.cpp +++ b/examples/UnitUnified/NFCF/Detect/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 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); } } } diff --git a/examples/UnitUnified/NFCF/Dump/Dump.ino b/examples/UnitUnified/NFCF/Dump/Dump.ino new file mode 100644 index 0000000..416208c --- /dev/null +++ b/examples/UnitUnified/NFCF/Dump/Dump.ino @@ -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" diff --git a/examples/UnitUnified/NFCF/Dump/main/Dump.cpp b/examples/UnitUnified/NFCF/Dump/main/Dump.cpp new file mode 100644 index 0000000..74103f5 --- /dev/null +++ b/examples/UnitUnified/NFCF/Dump/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 +#include +#include +#include +#include + +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"); + } + } +} diff --git a/src/nfc/a/nfca.hpp b/src/nfc/a/nfca.hpp index 05f9ee4..a5b0986 100644 --- a/src/nfc/a/nfca.hpp +++ b/src/nfc/a/nfca.hpp @@ -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() diff --git a/src/nfc/f/nfcf.cpp b/src/nfc/f/nfcf.cpp index 6f79f9d..e7f392c 100644 --- a/src/nfc/f/nfcf.cpp +++ b/src/nfc/f/nfcf.cpp @@ -12,6 +12,21 @@ #include 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 diff --git a/src/nfc/f/nfcf.hpp b/src/nfc/f/nfcf.hpp index e0fcb7c..bbd44cb 100644 --- a/src/nfc/f/nfcf.hpp +++ b/src/nfc/f/nfcf.hpp @@ -21,58 +21,78 @@ namespace nfc { */ namespace f { +using IDm = std::array; //!< Manufacture ID +using PMm = std::array; //!< 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; //!< Manufacture ID -using PMm = std::array; //!< 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 diff --git a/src/nfc/layer/nfc_layer_a.hpp b/src/nfc/layer/nfc_layer_a.hpp index e6aff17..a518937 100644 --- a/src/nfc/layer/nfc_layer_a.hpp +++ b/src/nfc/layer/nfc_layer_a.hpp @@ -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 */ diff --git a/src/nfc/layer/nfc_layer_f.cpp b/src/nfc/layer/nfc_layer_f.cpp index 83ed5c1..a354dad 100644 --- a/src/nfc/layer/nfc_layer_f.cpp +++ b/src/nfc/layer/nfc_layer_f.cpp @@ -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& 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 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 piccs; + picc = PICC{}; + if (detect(piccs, TimeSlot::Slot1, timeout_ms)) { + picc = piccs.front(); + return true; } return false; } -bool NFCLayerF::detect(std::vector& 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& 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& 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& 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& 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& 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(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(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(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(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(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; } // diff --git a/src/nfc/layer/nfc_layer_f.hpp b/src/nfc/layer/nfc_layer_f.hpp index e1b945e..f3a17db 100644 --- a/src/nfc/layer/nfc_layer_f.hpp +++ b/src/nfc/layer/nfc_layer_f.hpp @@ -23,7 +23,6 @@ #include 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& 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& piccs, const uint16_t system_code, + inline bool detect(std::vector& 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& 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& 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 _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 diff --git a/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp b/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp index 0a250f3..0fdf4d3 100644 --- a/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp +++ b/src/nfc/layer/unit/nfc_layer_f_ST25R3916.cpp @@ -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 make_st25r3916_adapter(UnitST25R3916& u) diff --git a/src/unit/unit_ST25R3916.hpp b/src/unit/unit_ST25R3916.hpp index 387b57e..eccafab 100644 --- a/src/unit/unit_ST25R3916.hpp +++ b/src/unit/unit_ST25R3916.hpp @@ -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 diff --git a/src/unit/unit_ST25R3916_nfcf.cpp b/src/unit/unit_ST25R3916_nfcf.cpp index 4f412aa..8dc0af9 100644 --- a/src/unit/unit_ST25R3916_nfcf.cpp +++ b/src/unit/unit_ST25R3916_nfcf.cpp @@ -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 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 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 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(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 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; }