diff --git a/examples/UnitUnified/NFCF/NDEF/NDEF.ino b/examples/UnitUnified/NFCF/NDEF/NDEF.ino new file mode 100644 index 0000000..64f5794 --- /dev/null +++ b/examples/UnitUnified/NFCF/NDEF/NDEF.ino @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/* + Example using M5UnitUnified for M5Cardputer-ADV with HackerCap + NDEF example +*/ +#include "main/NDEF.cpp" diff --git a/examples/UnitUnified/NFCF/NDEF/main/NDEF.cpp b/examples/UnitUnified/NFCF/NDEF/main/NDEF.cpp new file mode 100644 index 0000000..dbf726b --- /dev/null +++ b/examples/UnitUnified/NFCF/NDEF/main/NDEF.cpp @@ -0,0 +1,182 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/* + Example using M5UnitUnified for M5Cardputer-ADV with HackerCap + NDEF example +*/ +#include +#include +#include +#include +#include + +using namespace m5::nfc; +using namespace m5::nfc::f; +using namespace m5::nfc::ndef; + +namespace { +auto& lcd = M5.Display; +m5::unit::UnitUnified Units; +m5::unit::CapST25R3916 cap; // ST25R3916 in the HackerCap +m5::unit::nfc::NFCLayerF nfc_f{cap}; + +void read_ndef(const PICC& picc) +{ + TLV msg; + // Read NDEF message TLV + if (!nfc_f.ndefRead(msg)) { + M5_LOGE("Failed to read"); + return; + } + + // If it does not exist, a Null TLV is returned + if (msg.isMessageTLV()) { + lcd.setCursor(0, lcd.fontHeight()); + M5.Log.printf("==== MDEF Message %zu records ====\n", msg.records().size()); + for (auto&& r : msg.records()) { + switch (r.tnf()) { + case TNF::Wellknown: { + auto s = r.payloadAsString().c_str(); + M5.Log.printf("SZ:%3u TNF:%u T:%s [%s]\n", r.payloadSize(), r.tnf(), r.type(), s); + lcd.printf("T:%s [%s]\n", r.type(), s); + } break; + default: + M5.Log.printf("SZ:%3u TNF:%u T:%s\n", r.payloadSize(), r.tnf(), r.type()); + lcd.printf("T:%s\n", r.type()); + if (strcmp(r.type(), "image/png") == 0) { + lcd.drawPng(r.payload(), r.payloadSize(), lcd.width() >> 1, lcd.height() >> 1); + } + break; + } + } + } else { + M5.Log.printf("NDEF Message TLV is NOT exists\n"); + } +} + +void write_ndef(const PICC& picc) +{ + TLV msg{}; // Message as default + Record r[4] = {}; // Wellknown as default + + // Change format to support NDEF + if (!nfc_f.writeSupportNDEF(true)) { + M5_LOGE("Failed to writeSupportNDEF"); + return; + } + + // URI record + r[0].setURIPayload("m5stack.com/", URIProtocol::HTTPS); + + // Text record with langage type + const char* en_data = "Hello M5Stack"; + r[1].setTextPayload(en_data, "en"); + const char* ja_data = "こんにちは M5Stack"; + r[2].setTextPayload(ja_data, "ja"); + const char* zh_data = "你好 M5Stack"; + r[3].setTextPayload(zh_data, "zh"); + + uint32_t max_user_size = picc.userAreaSize() - 1 /* terminator TLV */; + for (auto&& rr : r) { + msg.push_back(rr); + if (msg.required() > max_user_size) { + msg.pop_back(); + break; + } + } + + if (!nfc_f.ndefWrite(msg)) { + M5_LOGE("Failed to write"); + return; + } + nfc_f.dump(); +} + +} // 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(); + bool clicked = M5.BtnA.wasClicked(); // For read + bool held = M5.BtnA.wasHold(); // For write + + if (clicked || held) { + PICC picc{}; + if (nfc_f.detect(picc)) { + if (nfc_f.activate(picc)) { + 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); + + if (clicked) { + M5.Speaker.tone(2000, 30); + lcd.fillScreen(TFT_BLUE); + read_ndef(picc); + } else if (held) { + M5.Speaker.tone(4000, 30); + lcd.fillScreen(TFT_YELLOW); + write_ndef(picc); + } + M5.Log.printf("Please remove the PICC from the reader\n"); + nfc_f.deactivate(); + } + lcd.setCursor(0, 0); + lcd.printf("Please put the PICC and click/hold G0"); + M5.Log.printf("Please put the PICC and click/hold G0\n"); + } else { + M5.Log.printf("PICC NOT exists\n"); + } + } +} diff --git a/platformio.ini b/platformio.ini index cd54312..18f5cf9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -150,4 +150,19 @@ build_flags = ${option_release.build_flags} ${CardputerADV.build_flags} +[env:CapHacker_NFCF_Dump_CardputerADV_Arduino_latest] +extends=CardputerADV, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCF/Dump> +build_flags = ${option_release.build_flags} + ${CardputerADV.build_flags} + +[env:CapHacker_NFCF_NDEF_CardputerADV_Arduino_latest] +extends=CardputerADV, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCF/NDEF> +build_flags = ${option_release.build_flags} + ${CardputerADV.build_flags} + + + + diff --git a/src/nfc/a/nfca.cpp b/src/nfc/a/nfca.cpp index 6eb0bff..26b304c 100644 --- a/src/nfc/a/nfca.cpp +++ b/src/nfc/a/nfca.cpp @@ -10,6 +10,8 @@ #include "nfca.hpp" #include +using namespace m5::nfc; + namespace { constexpr char name_unknown[] = "Unknown"; constexpr char name_classic_mini[] = "MIFARE Classic Mini"; @@ -105,6 +107,17 @@ constexpr uint16_t user_area_size_table[] = { 0, 0, }; +constexpr NFCForumTag nfc_forum_tag_table[] = { + NFCForumTag::None, // + NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, // Classic + NFCForumTag::Type2, NFCForumTag::Type2, // UltraLight + NFCForumTag::None, NFCForumTag::None, // Plus + NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, // DESFire + NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // NTAG + NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // NTAG + NFCForumTag::None, NFCForumTag::None, // +}; + } // namespace using namespace m5::nfc::a::mifare; @@ -243,6 +256,13 @@ Type version_to_type(const uint8_t info[10]) return Type::Unknown; } +m5::nfc::NFCForumTag get_nfc_forum_tag_type(const Type t) +{ + uint8_t idx = m5::stl::to_underlying(t); + return nfc_forum_tag_table[idx < m5::stl::size(max_block_table) ? idx : 0]; +} + +// std::string PICC::uidAsString() const { char buf[2 * 10 + 1]{}; diff --git a/src/nfc/a/nfca.hpp b/src/nfc/a/nfca.hpp index ac49bde..df1626c 100644 --- a/src/nfc/a/nfca.hpp +++ b/src/nfc/a/nfca.hpp @@ -10,7 +10,9 @@ #ifndef M5_UNIT_UNIFIED_NFC_NFC_A_NFCA_HPP #define M5_UNIT_UNIFIED_NFC_NFC_A_NFCA_HPP +#include "nfc/nfc.hpp" #include "mifare.hpp" +#include #include namespace m5 { @@ -50,6 +52,9 @@ enum class Type : uint8_t { NotCompleted = 0xFF, //!< SAK indicates UID is not complete }; +//! @brief Get NFC Forum Tag Type from PICC type +m5::nfc::NFCForumTag get_nfc_forum_tag_type(const Type t); + //! @brief Is type MIFARE Classic? inline bool is_mifare_classic(const Type t) { @@ -104,10 +109,7 @@ inline bool is_sak_completed(const uint8_t sak) @warning This is a preliminary diagnosis, a more accurate diagnosis is required */ Type sak_to_type(const uint8_t sak); - -/*! - @brief Inferring the type from GterVersionL3 - */ +//! @brief Inferring the type from GetVersionL3 Type version_to_type(const uint8_t info[10]); //! @brief Gets the number of blocks @@ -130,6 +132,7 @@ uint16_t get_first_user_block(const Type t); uint16_t get_last_user_block(const Type t); //! @brief Is block user area? bool is_user_block(const Type t, const uint16_t block); + //! @brief Calculate bcc8 uint8_t calculate_bcc8(const uint8_t* data, const uint32_t len); @@ -170,7 +173,7 @@ struct PICC { { return valid() && is_ntag(type); } - //! @brief Supports NFC tag? + //! @brief Supports NFC? inline bool supportsNFC() const { return valid() && supports_NFC(type); @@ -199,6 +202,12 @@ struct PICC { { return valid() ? get_last_user_block(type) : 0xFFFF; } + //! @brief Is user block? + inline bool isUserBlock(const uint8_t block) const + { + return valid() ? is_user_block(type, block) : false; + } + //! @brief Retrieve the last 4 bytes void tail4(uint8_t buf[4]) const { @@ -206,9 +215,11 @@ struct PICC { memcpy(buf, uid + size - 4, 4); } } - inline bool isUserBlock(const uint8_t block) const + + //! @brief NFC ForumTag + inline NFCForumTag nfcForumTagType() const { - return valid() ? is_user_block(type, block) : false; + return get_nfc_forum_tag_type(type); } //! @brief Gets the uid string diff --git a/src/nfc/f/nfcf.cpp b/src/nfc/f/nfcf.cpp index e7f392c..30f758d 100644 --- a/src/nfc/f/nfcf.cpp +++ b/src/nfc/f/nfcf.cpp @@ -22,7 +22,9 @@ constexpr const char* name_table[] = {name_unknown, name_standard, name_lite, na // 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}; +constexpr uint16_t max_read_block_table[] = {0, 8, 4, 4, 4}; +// Maximum number of blocks that can be write simultaneously +constexpr uint16_t max_write_block_table[] = {0, 1, 1, 1, 1}; // [first/last] constexpr uint8_t user_block_table[][2] = {{0XFF, 0XFF}, {0XFF, 0XFF}, {0x00, 0x0D}, {0x00, 0x0D}, {0XFF, 0XFF}}; @@ -76,6 +78,12 @@ uint8_t get_maxumum_read_blocks(const Type t) return max_read_block_table[idx < m5::stl::size(max_block_table) ? idx : 0]; } +uint8_t get_maxumum_write_blocks(const Type t) +{ + uint8_t idx = m5::stl::to_underlying(t); + return max_write_block_table[idx < m5::stl::size(max_block_table) ? idx : 0]; +} + // std::string PICC::idmAsString() const { diff --git a/src/nfc/f/nfcf.hpp b/src/nfc/f/nfcf.hpp index 1e8b13b..605f776 100644 --- a/src/nfc/f/nfcf.hpp +++ b/src/nfc/f/nfcf.hpp @@ -10,7 +10,9 @@ #ifndef M5_UNIT_UNIFIED_NFC_NFC_F_NFCF_HPP #define M5_UNIT_UNIFIED_NFC_NFC_F_NFCF_HPP +#include "nfc/nfc.hpp" #include +#include #include namespace m5 { @@ -37,6 +39,12 @@ enum class Type : uint8_t { // FeliCaLink, //!< Link }; +//! @brief Get NFC Forum Tag Type from PICC type +inline m5::nfc::NFCForumTag get_nfc_forum_tag_type(const Type t) +{ + return (t != Type::Unknown) ? NFCForumTag::Type3 : NFCForumTag::None; +} + /*! @enum Mode @brief NFC-F Mode status @@ -276,6 +284,8 @@ inline bool is_user_block(const Type t, const uint16_t block) } //! @brief Maximum number of blocks that can be read simultaneously uint8_t get_maxumum_read_blocks(const Type t); +//! @brief Maximum number of blocks that can be write simultaneously +uint8_t get_maxumum_write_blocks(const Type t); ///@name RequestService ///@{ @@ -307,20 +317,45 @@ struct PICC { { return valid() ? get_user_area_size(type) : 0; } - //! @brief Gets the first user block/page address + //! @brief Gets the first user block inline uint16_t firstUserBlock() const { return valid() ? get_first_user_block(type) : 0xFFFF; } + //! @brief Gets the last user block inline uint16_t lastUserBlock() const { return valid() ? get_last_user_block(type) : 0xFFFF; } + //! @brief Is user block? inline bool isUserBlock(const block_t block) const { return is_user_block(type, block); } + //! @brief Maximum number of blocks that can be read simultaneously + uint8_t maximumReadBlocks() const + { + return get_maxumum_read_blocks(type); + } + //! @brief Maximum number of blocks that can be write simultaneously + uint8_t maximumWriteBlocks() const + { + return get_maxumum_write_blocks(type); + } + + //! @brief Check format + bool check_format(const Format f) + { + return (format & f) != 0; + } + + //! @brief NFC ForumTag + inline NFCForumTag nfcForumTagType() const + { + return get_nfc_forum_tag_type(type); + } + //! @brief Gets the IDm string std::string idmAsString() const; //! @brief Gets the PMm string @@ -332,7 +367,7 @@ struct PICC { //! @brief Equal? (Only IDm,PMm) inline bool operator==(const PICC& a, const PICC& b) { - return a.idm == b.idm && a.pmm == b.pmm; + return a.valid() && b.valid() && a.idm == b.idm && a.pmm == b.pmm && a.type == b.type; } //! @brief Not equal? inline bool operator!=(const PICC& a, const PICC& b) diff --git a/src/nfc/layer/ndef_layer.cpp b/src/nfc/layer/ndef_layer.cpp index bfa125e..8134023 100644 --- a/src/nfc/layer/ndef_layer.cpp +++ b/src/nfc/layer/ndef_layer.cpp @@ -8,6 +8,10 @@ @brief Common layer for NDEF related */ #include "ndef_layer.hpp" +#include "nfc/a/nfca.hpp" +#include "nfc/b/nfcb.hpp" +#include "nfc/f/nfcf.hpp" +#include "nfc/v/nfcv.hpp" #include "nfc/ndef/ndef.hpp" #include "nfc/ndef/ndef_tlv.hpp" #include "nfc/ndef/ndef_record.hpp" @@ -44,10 +48,53 @@ bool NDEFLayer::isValidFormat(bool& valid) return true; } -bool NDEFLayer::read(std::vector& tlvs, const m5::nfc::ndef::TagBits tagBits) +bool NDEFLayer::read(const m5::nfc::NFCForumTag ftag, std::vector& tlvs, + const m5::nfc::ndef::TagBits tagBits) { tlvs.clear(); + switch (ftag) { + case NFCForumTag::Type1: + case NFCForumTag::Type2: + case NFCForumTag::Type5: + return read_with_tlv(tlvs, tagBits); + case NFCForumTag::Type3: + case NFCForumTag::Type4: { + tlvs.clear(); + TLV tlv; + if (read_without_tlv(tlv)) { + tlvs.emplace_back(tlv); + return true; + } + } break; + default: + break; + } + return false; +} + +bool NDEFLayer::write(const m5::nfc::NFCForumTag ftag, const std::vector& tlvs, const bool keep) +{ + if (!tlvs.empty()) { + switch (ftag) { + case NFCForumTag::Type1: + case NFCForumTag::Type2: + case NFCForumTag::Type5: + return write_with_tlv(tlvs, keep); + case NFCForumTag::Type3: + case NFCForumTag::Type4: + return write_without_tlv(tlvs.front()); + default: + break; + } + } + return false; +} + +// +bool NDEFLayer::read_with_tlv(std::vector& tlvs, const m5::nfc::ndef::TagBits tagBits) +{ + bool ret{}; uint8_t* buf{}; uint16_t block = _interface.firstUserBlock(); uint16_t last_block = _interface.lastUserBlock(); @@ -87,14 +134,15 @@ bool NDEFLayer::read(std::vector& tlvs, const m5::nfc::ndef: } } while (!tlv.isTerminatorTLV() && !tlv.isNullTLV()); + ret = true; } skip: free(buf); - return true; + return ret; } -bool NDEFLayer::write(const std::vector& tlvs, const bool keep) +bool NDEFLayer::write_with_tlv(const std::vector& tlvs, const bool keep) { bool ret{}; const uint32_t user_bytes = @@ -110,7 +158,7 @@ bool NDEFLayer::write(const std::vector& tlvs, const bool ke Since there is an NTAG containing information such as LockControl starting from the beginning of the user area, skip it and write */ - if (keep && read(tmp)) { + if (keep && read_with_tlv(tmp, tagBitsAll)) { // Remove Null,NDEF,and Terminator (Keep Lock,Memory,Proprietary) auto it = std::remove_if(tmp.begin(), tmp.end(), [](const TLV& m) { // return m.tag() == Tag::Null || m.tag() == Tag::Message || m.tag() == Tag::Terminator; @@ -173,6 +221,148 @@ skip: return ret; } +bool NDEFLayer::read_without_tlv(m5::nfc::ndef::TLV& tlv) +{ + tlv = TLV{Tag::Null}; + + TLV tmp{}; // Message as default + bool ret{}; + uint16_t block = _interface.firstUserBlock(); + uint16_t last_block = _interface.lastUserBlock(); + if (block == 0xFFFF || last_block == 0xFFFF) { + M5_LIB_LOGE("ERROR"); + return false; + } + + // Attribute block + AttributeBlock ab{}; + uint16_t actual{16}; + if (!_interface.read(ab.block, actual, block) || actual != 16 || ab.check_sum() != ab.calculate_check_sum()) { + M5_LIB_LOGE("Failed to read AB actual:%u sum:%04X/%04X", actual, ab.check_sum(), ab.calculate_check_sum()); + return false; + } + + M5_LIB_LOGD("AB:%02X %u/%u/%u %02X/%02X %u %04X/%04X", ab.version(), ab.max_block_to_read(), + ab.max_block_to_write(), ab.blocks_for_ndef_storage(), ab.write_flag(), ab.access_flag(), + ab.current_ndef_message_length(), ab.check_sum(), ab.calculate_check_sum()); + + if (!ab.valid()) { + return false; + } + + // NDEF Records + uint16_t buf_size = ((ab.current_ndef_message_length() + 15) >> 4) << 4; + uint8_t* buf = static_cast(malloc(buf_size)); + + if (!buf) { + M5_LIB_LOGE("Failed to allocate memory %u", buf_size); + return false; + } + actual = buf_size; + if (!_interface.read(buf, actual, block + 1) || actual != buf_size) { + M5_LIB_LOGE("Failed to read %u/%u", actual, buf_size); + goto skip; + } + + { + uint16_t decoded{}; + uint16_t idx{}; + while (decoded < ab.current_ndef_message_length()) { + Record r{}; + auto len = r.decode(buf + decoded, actual - decoded); + if (!len) { + M5_LIB_LOGE("Failed to decode %u", idx); + goto skip; + } + tmp.push_back(r); + decoded += len; + ++idx; + } + ret = true; + tlv = tmp; + } + +skip: + free(buf); + return ret; +} + +bool NDEFLayer::write_without_tlv(const m5::nfc::ndef::TLV& tlv) +{ + bool ret{}; + + if (!tlv.isMessageTLV()) { + return false; + } + + uint16_t first_block = _interface.firstUserBlock(); + uint16_t last_block = _interface.lastUserBlock(); + if (first_block == 0xFFFF || last_block == 0xFFFF) { + return false; + } + + uint16_t user_area_size = (last_block - first_block + 1) * _interface.userBlockUnitSize(); + uint32_t record_size = std::accumulate(tlv.records().begin(), tlv.records().end(), 0U, + [](uint32_t acc, const Record& r) { return acc + r.required(); }); + if (record_size + 16 > user_area_size) { + M5_LIB_LOGE("Not enough user area %u/%u", record_size + 16, user_area_size); + return false; + } + + // Encode + uint8_t* buf = static_cast(malloc(record_size)); + if (!buf) { + M5_LIB_LOGE("Failed to allocate memory %u", record_size); + return false; + } + + uint32_t idx{}; + uint32_t encoded{}; + for (auto&& r : tlv.records()) { + auto len = r.encode(buf + encoded, record_size - encoded); + if (!len) { + M5_LIB_LOGE("Failed to encode %u", idx); + goto skip; + } + encoded += len; + ++idx; + } + + // Write + { + AttributeBlock ab{}; + ab.max_block_to_read(_interface.maximumReadBlocks()); + ab.max_block_to_write(_interface.maximumWriteBlocks()); + ab.blocks_for_ndef_storage(last_block - first_block + 1 -1); + ab.write_flag(AttributeBlock::WriteFlag::InProgress); + ab.access_flag(AttributeBlock::AccessFlag::ReadWrite); + ab.current_ndef_message_length(record_size); + ab.update_check_sum(); + + // 1) Write AB (In progress) + if (!_interface.write(first_block, ab.block, sizeof(ab.block))) { + goto skip; + } + + // 2) Write records + if (!_interface.write(first_block + 1, buf, record_size)) { + goto skip; + } + + // 3) Write AB again (Done) + ab.write_flag(AttributeBlock::WriteFlag::Done); + ab.update_check_sum(); + if (!_interface.write(first_block, ab.block, sizeof(ab.block))) { + goto skip; + } + ret = true; + } + +skip: + free(buf); + return ret; +} + #if 0 bool NDEFLayer::readTLVSize(uint32_t& size, const TagBits tagBits) { diff --git a/src/nfc/layer/ndef_layer.hpp b/src/nfc/layer/ndef_layer.hpp index 0f7bf42..01b1baa 100644 --- a/src/nfc/layer/ndef_layer.hpp +++ b/src/nfc/layer/ndef_layer.hpp @@ -11,6 +11,7 @@ #define M5_UNIT_NFC_NFC_LAYER_NDEFC_LAYER_HPP #include "nfc_layer.hpp" +#include "nfc/ndef/ndef.hpp" #include namespace m5 { @@ -31,13 +32,19 @@ public: { } bool isValidFormat(bool& valid); - bool read(std::vector& tlvs, + bool read(const m5::nfc::NFCForumTag ftag, std::vector& tlvs, const m5::nfc::ndef::TagBits tagBits = m5::nfc::ndef::tagBitsMessage); - bool write(const std::vector& tlvs, const bool keep = true); + bool write(const m5::nfc::NFCForumTag ftag, const std::vector& tlvs, const bool keep = true); // bool readTLVSize(uint32_t& size, const m5::nfc::ndef::TagBits tagBits = m5::nfc::ndef::tagBitsAll); protected: + bool read_with_tlv(std::vector& tlvs, const m5::nfc::ndef::TagBits tagBits); + bool write_with_tlv(const std::vector& tlvs, const bool keep); + + bool read_without_tlv(m5::nfc::ndef::TLV& tlv); + bool write_without_tlv(const m5::nfc::ndef::TLV& tlv); + // bool calculate_ndef_size(uint32_t& size, const uint8_t* p, const uint8_t* end, const uint8_t targetTagBit); private: diff --git a/src/nfc/layer/nfc_layer.hpp b/src/nfc/layer/nfc_layer.hpp index 254a7c1..d3c860f 100644 --- a/src/nfc/layer/nfc_layer.hpp +++ b/src/nfc/layer/nfc_layer.hpp @@ -24,7 +24,15 @@ public: virtual uint16_t lastUserBlock() const = 0; virtual uint16_t userBlockUnitSize() const = 0; -private: + // For NFC-F AttributeBlock + virtual uint8_t maximumReadBlocks() const + { + return 0; + } + virtual uint8_t maximumWriteBlocks() const + { + return 0; + } }; } // namespace nfc diff --git a/src/nfc/layer/nfc_layer_a.cpp b/src/nfc/layer/nfc_layer_a.cpp index 9cac450..7d9c98d 100644 --- a/src/nfc/layer/nfc_layer_a.cpp +++ b/src/nfc/layer/nfc_layer_a.cpp @@ -669,18 +669,18 @@ bool NFCLayerA::ndefRead(m5::nfc::ndef::TLV& msg) bool NFCLayerA::ndefRead(std::vector& tlvs, const m5::nfc::ndef::TagBits tagBits) { - return _activePICC.supportsNFC() && _ndef.read(tlvs, tagBits); + return _activePICC.supportsNFC() && _ndef.read(_activePICC.nfcForumTagType(), tlvs, tagBits); } bool NFCLayerA::ndefWrite(const m5::nfc::ndef::TLV& msg) { std::vector tlvs = {msg}; - return msg.isMessageTLV() && _activePICC.supportsNFC() && _ndef.write(tlvs); + return msg.isMessageTLV() && _activePICC.supportsNFC() && _ndef.write(_activePICC.nfcForumTagType(), tlvs); } bool NFCLayerA::ndefWrite(const std::vector& tlvs) { - return _activePICC.supportsNFC() && _ndef.write(tlvs, false); + return _activePICC.supportsNFC() && _ndef.write(_activePICC.nfcForumTagType(), tlvs, false); } // diff --git a/src/nfc/layer/nfc_layer_a.hpp b/src/nfc/layer/nfc_layer_a.hpp index 9ca6f91..99b5634 100644 --- a/src/nfc/layer/nfc_layer_a.hpp +++ b/src/nfc/layer/nfc_layer_a.hpp @@ -105,7 +105,7 @@ public: */ bool select(m5::nfc::a::PICC& picc); /*! - @brief Activate a specific PICC by PICC (anti-collision against the given PICC) + @brief Activate a specific PICC (anti-collision against the given PICC) @param picc PICC @return True if successful @pre PICC is READY state @@ -215,6 +215,7 @@ public: const m5::nfc::a::mifare::classic::Key& key = m5::nfc::a::mifare::classic::DEFAULT_KEY); ///@} + ///@note For activated PICC ///@name For MIFARE classic ///@{ /*! @@ -358,6 +359,7 @@ public: bool mifareUltralightChangeFormatToNTAG(); ///@} + ///@note For activated PICC ///@name For NDEF ///@{ /*! @@ -404,6 +406,9 @@ public: bool ndefWrite(const std::vector& tlvs); ///@} + ///@note For activated PICC + ///@note Dump + ///@{ /*! @brief Dump all blocks @param key MIFARE classic key diff --git a/src/nfc/layer/nfc_layer_f.cpp b/src/nfc/layer/nfc_layer_f.cpp index d2a3cf6..5e8daf3 100644 --- a/src/nfc/layer/nfc_layer_f.cpp +++ b/src/nfc/layer/nfc_layer_f.cpp @@ -46,6 +46,11 @@ void print_block(const uint8_t buf[16], const int16_t block) ::puts(tmp); } +inline bool is_same_idm_and_pmm(const PICC& a, const PICC& b) +{ + return a.idm == b.idm && a.pmm == b.pmm; +} + } // namespace namespace m5 { @@ -94,6 +99,8 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri continue; } + _activePICC = picc1; + // 2. Check IDm for NFCIP-1 Transport Protocol / DFC if (picc1.idm[0] == 0x01 && picc1.idm[1] == 0xFE) { format |= format_nfcip1; @@ -114,7 +121,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri continue; } if (polling(picc2, code, RequestCode::None, time_slot)) { - if (picc1 != picc2) { + if (!is_same_idm_and_pmm(picc1, picc2)) { continue; } format |= format_private; @@ -128,7 +135,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // 4. Check NDEF if (polling(picc2, system_code_ndef, RequestCode::None, time_slot)) { - if (picc1 != picc2) { + if (!is_same_idm_and_pmm(picc1, picc2)) { continue; } format |= format_ndef; @@ -136,7 +143,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // 5. Check shared area if (polling(picc2, system_code_shared, RequestCode::None, time_slot)) { - if (picc1 != picc2) { + if (!is_same_idm_and_pmm(picc1, picc2)) { continue; } format |= format_shared; @@ -144,7 +151,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // 6. Check DFC if (polling(picc2, system_code_dfc, RequestCode::None, time_slot)) { - if (picc1 != picc2) { + if (!is_same_idm_and_pmm(picc1, picc2)) { continue; } format |= format_dfc; @@ -152,7 +159,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // 7. Check secure if (polling(picc2, system_code_felica_secure_id, RequestCode::None, time_slot)) { - if (picc1 != picc2) { + if (!is_same_idm_and_pmm(picc1, picc2)) { continue; } format |= format_secure; @@ -160,7 +167,7 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // Type identification if (polling(picc2, system_code_felica_plug, RequestCode::None, time_slot)) { - if (picc1 != picc2) { + if (!is_same_idm_and_pmm(picc1, picc2)) { continue; } type = Type::FeliCaPlug; @@ -168,8 +175,8 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri // 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) + type = read_16(rbuf, 0xA0 /* CRC_CHECK */, false) ? Type::FeliCaLiteS : Type::FeliCaLite; + if (!read_16(rbuf, 0x82, false)) { // Read ID(DFC format) continue; } picc1.dfc_format = rbuf[8] | ((uint16_t)rbuf[9] << 8); @@ -185,39 +192,51 @@ bool NFCLayerF::detect(std::vector& piccs, const uint16_t* pri picc1.format = format; picc1.type = type; - piccs.push_back(picc1); - ++detected; + if (picc1.type != Type::Unknown) { + piccs.push_back(picc1); + ++detected; + } + deactivate(); } while (detected < slots && m5::utility::millis() <= timeout_at); - + deactivate(); return detected > 0; } -bool NFCLayerF::requestService(uint16_t& key_version, const m5::nfc::f::PICC& picc, const uint16_t node_code) +bool NFCLayerF::activate(const m5::nfc::f::PICC& picc) { - 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]; + if (picc.valid()) { + _activePICC = picc; 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) +bool NFCLayerF::deactivate() { - return _impl->requestService(key_version, picc, node_code, node_size); + _activePICC = PICC{}; + return true; } -bool NFCLayerF::read16(uint8_t rx[16], const m5::nfc::f::PICC& picc, const block_t block) +bool NFCLayerF::requestService(uint16_t& key_version, const uint16_t node_code) +{ + key_version = KEY_VERIOSN_NONE; + return requestService(&key_version, &node_code, 1); +} + +bool NFCLayerF::requestService(uint16_t key_version[], const uint16_t* node_code, const uint8_t node_size) +{ + return _activePICC.valid() && _impl->requestService(key_version, _activePICC, node_code, node_size); +} + +bool NFCLayerF::read_16(uint8_t rx[16], const block_t block, const bool check_valid) { uint16_t rx_len{16}; uint16_t sc{service_random_read}; - return rx && _impl->readWithoutEncryption(rx, rx_len, picc, &sc, 1, &block, 1); + return rx && (check_valid ? _activePICC.valid() : true) && + _impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, &block, 1); } -bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc, const block_t sblock) +bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const block_t sblock) { auto rx_len_org = rx_len; rx_len = 0; @@ -225,12 +244,13 @@ bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc 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)) { + uint16_t last = std::min(_activePICC.lastUserBlock(), start + blocks - 1); + if (!_activePICC.valid() || !rx || !rx_len_org || !_activePICC.isUserBlock(sblock) || + !_activePICC.isUserBlock(last)) { return false; } - const uint16_t batch_size = get_maxumum_read_blocks(picc.type); + const uint16_t batch_size = get_maxumum_read_blocks(_activePICC.type); uint8_t rbuf[16 * batch_size]{}; block_t block{start}; auto out = rx; @@ -246,7 +266,7 @@ bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc } // 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) { + if (!_impl->readWithoutEncryption(rbuf, actual, _activePICC, &sc, 1, block_list, num) || actual != 16 * num) { return false; } memcpy(out, rbuf, actual); @@ -258,29 +278,23 @@ bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc 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) +bool NFCLayerF::write16(const m5::nfc::f::block_t block, const uint8_t tx[16], const uint16_t tx_len) { - if (tx && tx_len) { + if (_activePICC.valid() && 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 _impl->writeWithoutEncryption(_activePICC, &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) +bool NFCLayerF::write(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)) { + uint16_t last = std::min(_activePICC.lastUserBlock(), start + blocks - 1); + if (!_activePICC.valid() || !tx || !tx_len || !_activePICC.isUserBlock(sblock) || !_activePICC.isUserBlock(last)) { return false; } @@ -288,7 +302,7 @@ bool NFCLayerF::write(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t sb 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)) { + if (!write16(block, tx + written, wsize)) { return false; } written += wsize; @@ -296,32 +310,89 @@ bool NFCLayerF::write(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t sb return true; } -bool NFCLayerF::dump(const PICC& picc) +bool NFCLayerF::ndefIsValidFormat(bool& valid) { - switch (picc.type) { + valid = false; + /* TODO NDEF*/ + return _ndef.isValidFormat(valid); +} + +bool NFCLayerF::ndefRead(m5::nfc::ndef::TLV& msg) +{ + msg = TLV(Tag::Null); + + std::vector tlvs{}; + if (ndefRead(tlvs, tagBitsMessage)) { + msg = !tlvs.empty() ? tlvs.front() : TLV(Tag::Null); + return true; + } + return false; +} + +bool NFCLayerF::ndefRead(std::vector& tlvs, const m5::nfc::ndef::TagBits tagBits) +{ + return _activePICC.valid() && _ndef.read(_activePICC.nfcForumTagType(), tlvs, tagBits); +} + +bool NFCLayerF::ndefWrite(const m5::nfc::ndef::TLV& msg) +{ + std::vector tlvs = {msg}; + return msg.isMessageTLV() && _activePICC.valid() && _ndef.write(_activePICC.nfcForumTagType(), tlvs); +} + +bool NFCLayerF::ndefWrite(const std::vector& tlvs) +{ + return _activePICC.valid() && _ndef.write(_activePICC.nfcForumTagType(), tlvs, false); +} + +bool NFCLayerF::writeSupportNDEF(const bool enabled) +{ + if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLite && _activePICC.type != Type::FeliCaLiteS)) { + return false; + } + + uint8_t buf[16]{}; + if (!read16(buf, lite::MC /* Same as lite_s::MC */)) { + return false; + } + + // Already? + if (buf[3 /* SYS_OP */] == (enabled ? 0x01 : 0x00)) { + return true; + } + + buf[3] = enabled ? 0x01 : 0x00; + if (write16(lite::MC, buf, 16)) { + _activePICC.format |= format_ndef; + } + return false; +} + +bool NFCLayerF::dump() +{ + switch (_activePICC.type) { case Type::FeliCaLite: - return dump_felica_lite(picc); + return dump_felica_lite(); case Type::FeliCaLiteS: - return dump_felica_lite_s(picc); + return dump_felica_lite_s(); case Type::FeliCaStandard: + case Type::FeliCaPlug: 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) +bool NFCLayerF::dump(const block_t block) { - // TODO - return dump_block(picc, block); + return _activePICC.valid() && dump_block(block); } // -bool NFCLayerF::dump_felica_lite(const m5::nfc::f::PICC& picc) +bool NFCLayerF::dump_felica_lite() { puts( "Block: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n" @@ -329,35 +400,35 @@ bool NFCLayerF::dump_felica_lite(const m5::nfc::f::PICC& picc) bool ret{true}; for (uint8_t block = 0; block < 0x0F; ++block) { - ret &= dump_block(picc, block); + ret &= dump_block(block); } for (uint8_t block = 0x80; block < 0x89; ++block) { - ret &= dump_block(picc, block); + ret &= dump_block(block); } return ret; } -bool NFCLayerF::dump_felica_lite_s(const m5::nfc::f::PICC& picc) +bool NFCLayerF::dump_felica_lite_s() { bool ret{true}; - ret &= dump_felica_lite(picc); + ret &= dump_felica_lite(); 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(block); } - ret &= dump_block(picc, 0xA0); + ret &= dump_block(0xA0); return ret; } -bool NFCLayerF::dump_block(const m5::nfc::f::PICC& picc, m5::nfc::f::block_t block) +bool NFCLayerF::dump_block(m5::nfc::f::block_t block) { uint8_t buf[16]{}; - if (read16(buf, picc, block)) { + if (read16(buf, block)) { print_block(buf, block.block()); return true; } @@ -368,19 +439,33 @@ bool NFCLayerF::dump_block(const m5::nfc::f::PICC& picc, m5::nfc::f::block_t blo // bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const uint8_t saddr) { + if (_activePICC.check_format(format_ndef)) { + return read(rx, rx_len, block_t(saddr)); + } + rx_len = 0; return false; } bool NFCLayerF::write(const uint8_t saddr, const uint8_t* tx, const uint16_t tx_len) { - return false; + return _activePICC.check_format(format_ndef) && write(block_t(saddr), tx, tx_len); } uint16_t NFCLayerF::firstUserBlock() const { - return 0; + return _activePICC.firstUserBlock(); } uint16_t NFCLayerF::lastUserBlock() const { - return 0; + return _activePICC.lastUserBlock(); +} + +uint8_t NFCLayerF::maximumReadBlocks() const +{ + return _activePICC.maximumReadBlocks(); +} + +uint8_t NFCLayerF::maximumWriteBlocks() const +{ + return _activePICC.maximumWriteBlocks(); } } // namespace nfc diff --git a/src/nfc/layer/nfc_layer_f.hpp b/src/nfc/layer/nfc_layer_f.hpp index 272ebbf..f7ea025 100644 --- a/src/nfc/layer/nfc_layer_f.hpp +++ b/src/nfc/layer/nfc_layer_f.hpp @@ -39,7 +39,26 @@ public: explicit NFCLayerF(UnitST25R3916& u); explicit NFCLayerF(CapST25R3916& u); - ///@name Detection + /*! + @brief Is the specified PICC currently active? + @param picc PICC to check + @return True if this PICC is the one currently selected (ACTIVE state) + */ + inline bool isActive(const m5::nfc::f::PICC& picc) const + { + return _activePICC.valid() && _activePICC == picc; + } + /*! + @brief Retrieve the currently activated PICC + @return Active PICC + @note Returns an empty PICC if no PICC is selected (no ACTIVE state) + */ + const m5::nfc::f::PICC& activatedPICC() const + { + return _activePICC; + } + + ///@name Detection and activation ///@{ /*! @brief Polling @@ -103,25 +122,44 @@ public: */ 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); + + /*! + @brief Activate a specific PICC + @param picc PICC + @return True if successful + */ + bool activate(const m5::nfc::f::PICC& picc); + /*! + @brief Activate a specific PICC + @param picc PICC + @return True if successful + @note For compatibility with other NFCLayer components + */ + inline bool reactivate(const m5::nfc::f::PICC& picc) + { + return activate(picc); + } ///@} - ///@name Request + ///@name For activated PICC ///@{ - 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); - ///@} + /*! + @brief Deactivate PICC + @return True if successful + */ + bool deactivate(); - ///@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 + @return True if successful + @note Using readWithoutEncryption */ - bool read16(uint8_t rx[16], const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t block); + inline bool read16(uint8_t rx[16], const m5::nfc::f::block_t block) + { + return read_16(rx, block, true); + } /*! @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 @@ -129,22 +167,22 @@ public: @param[in/out] rx_len in:buffer size, out:actual read size @param sblock Block to start reading @return True if successful + @note Using readWithoutEncryption @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); + bool read(uint8_t* rx, uint16_t& rx_len, 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 + @return True if successful + @note Using writeWithoutEncryption @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); + bool write16(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 @@ -152,24 +190,87 @@ public: @param tx Buffer @param tx_len Buffer size @return True if successful + @note Using writeWithoutEncryption */ - bool write(const m5::nfc::f::PICC& picc, const m5::nfc::f::block_t sblock, const uint8_t* tx, - const uint16_t tx_len); + bool write(const m5::nfc::f::block_t sblock, const uint8_t* tx, const uint16_t tx_len); ///@} + ///@note For activated PICC + ///@name For NDEF + ///@{ + /*! + @brief Is the PICC data in NDEF format? + @paran[out] valid True if NDEF format + @return True if successful + */ + bool ndefIsValidFormat(bool& valid); + /*! + @brief Read NDEF Message TLV + @param[out] msg Messgae If it does not exist, a Null TLV is returned + @return True if successful + @note If multiple messages of the same type exist, return the first one + @warning Only PICC cards supporting NDEF are valid + */ + bool ndefRead(m5::nfc::ndef::TLV& msg); + /*! + @brief Read any NDEF TLV + @param[out] msgs Messgae vector + @param tagBits Bit indicating the group of NDEF tags to be read + @return True if successful + @warning Only PICC cards supporting NDEF are valid + */ + bool ndefRead(std::vector& tlvs, + const m5::nfc::ndef::TagBits tagBits = m5::nfc::ndef::tagBitsAll); + /*! + @brief Write NDEF message + @param msg Messgae (NDEF Message) + @return True if successful + @note Other existing tags will be preserved + @warning Existing NDEF message TLVs will be overwritten + @warning Only PICC cards supporting NDEF are valid + */ + bool ndefWrite(const m5::nfc::ndef::TLV& msg); + /*! + @brief Write any NDEF Messages TLV + @param msgs Messgae vector + @return True if successful + @note Write starting from the beginning of the user area + @warning Existing NDEF Message TLVs will be overwritten, + @warning so exercise caution if Lock/Memory control is present + @warning Only PICC cards supporting NDEF are valid + */ + bool ndefWrite(const std::vector& tlvs); + + /*! + @brief Write changes for NDEF Support + @param enabled Support NDEF if true, NOT support NDEF if false + @return True if successful + @warning Only FeliCa Lite, Lite-S + */ + bool writeSupportNDEF(const bool enabled); + ///@} + + bool requestService(uint16_t& key_version, const uint16_t node_code); + bool requestService(uint16_t key_version[], const uint16_t* node_code, const uint8_t node_num); + + ///@note For activated PICC + ///@note Dump + ///@{ + /*! @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); + bool dump(); /*! @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); + bool dump(const m5::nfc::f::block_t block); + ///@} protected: virtual bool read(uint8_t* rx, uint16_t& rx_len, const uint8_t saddr) override; @@ -180,10 +281,17 @@ protected: { return 16u; } + virtual uint8_t maximumReadBlocks() const override; + virtual uint8_t maximumWriteBlocks() const override; - 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); + bool read_16(uint8_t rx[16], const m5::nfc::f::block_t block, const bool check_valid); + + bool dump_felica_lite(); + bool dump_felica_lite_s(); + bool dump_block(m5::nfc::f::block_t block); + +protected: + m5::nfc::f::PICC _activePICC{}; private: std::unique_ptr _impl; diff --git a/src/nfc/ndef/ndef.cpp b/src/nfc/ndef/ndef.cpp index c0a73b5..a9c7ebc 100644 --- a/src/nfc/ndef/ndef.cpp +++ b/src/nfc/ndef/ndef.cpp @@ -68,6 +68,31 @@ const char* get_uri_idc_string(const URIProtocol protocol) return uri_idc_table[idx < m5::stl::size(uri_idc_table) ? idx : 0]; } +// + +bool AttributeBlock::valid() const +{ + return version() >= 0x10 && max_block_to_read() && max_block_to_write() && blocks_for_ndef_storage() && + current_ndef_message_length() && write_flag() == WriteFlag::Done && (check_sum() == calculate_check_sum()); +} + +uint16_t AttributeBlock::calculate_check_sum() const +{ + uint16_t sum{}; + for (uint_fast8_t i = 0; i < 14; ++i) { + sum += block[i]; + } + return sum; +} + +uint16_t AttributeBlock::update_check_sum() +{ + uint16_t sum = calculate_check_sum(); + block[14] = sum >> 8; + block[15] = sum & 0xFF; + return sum; +} + } // namespace ndef } // namespace nfc } // namespace m5 diff --git a/src/nfc/ndef/ndef.hpp b/src/nfc/ndef/ndef.hpp index fbc4e53..9a446de 100644 --- a/src/nfc/ndef/ndef.hpp +++ b/src/nfc/ndef/ndef.hpp @@ -24,6 +24,7 @@ namespace ndef { ///@name CC(Capability Container) ///@{ constexpr uint8_t MAGIC_NO{0xE1}; //!< Magic No. in CC + //! @brief Gets the major version inline constexpr uint8_t get_major_version(const uint8_t v) { @@ -88,7 +89,7 @@ constexpr TagBits make_tag_bits_impl(TagBits acc, Tag head, Rest... rest) ///@endcond /*! - @brief Make TagBit from + @brief Make TagBit from tag @param tags Tag(s) @return TagBit */ @@ -127,92 +128,107 @@ inline bool is_terminator_tag(const uint8_t t) } /*! - @enum TNF - @brief Type Name Field for NDEF Record + @struct AttributeBlock + @brief For Type 3 tag (T3T) */ -enum class TNF : uint8_t { - Empty, //!< Empry - Wellknown, //!< NFC Forum well-known-type - MIMEMedia, //!< Media-type as define in RFC2046 - URI, //!< Absolute URI as define in RFC3986 - External, //!< NFC Forum external type - Unknown, //!< Unknown - Unchanged, //!< Unchanged - Reserved, //!< Reserved -}; +struct AttributeBlock { + /*! + @enum WriteFlag + @brief Flag for fault tolerance + */ + enum class WriteFlag : uint8_t { + Done, //!< Done + InProgress = 0x0F, //!< Write in progress + }; -/*! - @struct Attribute - @brief NDEF Record attribute (1st byte) - */ -struct Attribute { - ///@name Bit field - ///@{ - static constexpr uint8_t MB{0x80}; //!< Message Begin - static constexpr uint8_t ME{0x40}; //!< Message End - static constexpr uint8_t CF{0x20}; //!< Chunked Flag - static constexpr uint8_t SR{0x10}; //!< Short Flag - static constexpr uint8_t IL{0x08}; //!< ID Length (If disabled (specified as 0), ID LENGTH and ID can be omitted) - static constexpr uint8_t TNF_MASK{0x07}; //!< Type Name Format - ///@} + /*! + @enum AccessFlag + @brief Permission to read and write + */ + enum class AccessFlag : uint8_t { + ReadOnly, //!< Allow read only + ReadWrite, //!< Allow read and write + }; + AttributeBlock() : block{DEFAULT_VERSION} + { + } - ///@name Getter - ///@{ - inline bool messageBegin() const + // Getter + inline uint8_t version() const { - return value & MB; + return block[0]; } - inline bool messageEnd() const + inline uint8_t max_block_to_read() const { - return value & ME; + return block[1]; } - inline bool chunk() const + inline uint8_t max_block_to_write() const { - return value & CF; + return block[2]; } - inline bool shortRecord() const + inline uint16_t blocks_for_ndef_storage() const { - return value & SR; + return ((uint16_t)block[3] << 8) | block[4]; } - inline bool idLength() const + inline WriteFlag write_flag() const { - return value & IL; + return (block[9] == 0) ? WriteFlag::Done : WriteFlag::InProgress; } - inline TNF tnf() const + inline AccessFlag access_flag() const { - return static_cast(value & TNF_MASK); + return (AccessFlag)block[10]; + } + inline uint32_t current_ndef_message_length() const + { + return ((uint32_t)block[11] << 16) | ((uint32_t)block[12] << 8) | block[13]; + } + inline uint16_t check_sum() const + { + return ((uint16_t)block[14] << 8) | block[15]; } - ///@} - ///@name Setter - ///@{ - inline void messageBegin(const bool b) + // Setter + inline void version(const uint8_t ver) { - value = (value & ~MB) | (b ? MB : 0); + block[0] = ver; } - inline void messageEnd(const bool b) + inline void max_block_to_read(const uint8_t b) { - value = (value & ~ME) | (b ? ME : 0); + block[1] = b; } - inline void chunk(const bool b) + inline void max_block_to_write(const uint8_t b) { - value = (value & ~CF) | (b ? CF : 0); + block[2] = b; } - inline void shortRecord(const bool b) + inline void blocks_for_ndef_storage(const uint16_t s) { - value = (value & ~SR) | (b ? SR : 0); + block[3] = s >> 8; + block[4] = s & 0xFF; } - inline void idLength(const bool b) + inline void write_flag(const WriteFlag f) { - value = (value & ~IL) | (b ? IL : 0); + block[9] = m5::stl::to_underlying(f); } - inline void tnf(const TNF t) + inline void access_flag(const AccessFlag f) { - value = (value & ~TNF_MASK) | (m5::stl::to_underlying(t) & TNF_MASK); + block[10] = m5::stl::to_underlying(f); + } + inline void current_ndef_message_length(const uint32_t len) + { + block[11] = len >> 16; + block[12] = len >> 8; + block[13] = len & 0xFF; } - ///@} - uint8_t value{}; + // + bool valid() const; + uint16_t calculate_check_sum() const; + uint16_t update_check_sum(); + + static constexpr uint8_t DEFAULT_VERSION{0x10}; + + // + uint8_t block[16]{}; }; /*! diff --git a/src/nfc/ndef/ndef_record.hpp b/src/nfc/ndef/ndef_record.hpp index d7b091e..a32625f 100644 --- a/src/nfc/ndef/ndef_record.hpp +++ b/src/nfc/ndef/ndef_record.hpp @@ -20,6 +20,95 @@ namespace ndef { class TLV; +/*! + @enum TNF + @brief Type Name Field for NDEF Record + */ +enum class TNF : uint8_t { + Empty, //!< Empry + Wellknown, //!< NFC Forum well-known-type + MIMEMedia, //!< Media-type as define in RFC2046 + URI, //!< Absolute URI as define in RFC3986 + External, //!< NFC Forum external type + Unknown, //!< Unknown + Unchanged, //!< Unchanged + Reserved, //!< Reserved +}; + +/*! + @struct Attribute + @brief NDEF Record attribute (1st byte) + */ +struct Attribute { + ///@name Bit field + ///@{ + static constexpr uint8_t MB{0x80}; //!< Message Begin + static constexpr uint8_t ME{0x40}; //!< Message End + static constexpr uint8_t CF{0x20}; //!< Chunked Flag + static constexpr uint8_t SR{0x10}; //!< Short Flag + static constexpr uint8_t IL{0x08}; //!< ID Length (If disabled (specified as 0), ID LENGTH and ID can be omitted) + static constexpr uint8_t TNF_MASK{0x07}; //!< Type Name Format + ///@} + + ///@name Getter + ///@{ + inline bool messageBegin() const + { + return value & MB; + } + inline bool messageEnd() const + { + return value & ME; + } + inline bool chunk() const + { + return value & CF; + } + inline bool shortRecord() const + { + return value & SR; + } + inline bool idLength() const + { + return value & IL; + } + inline TNF tnf() const + { + return static_cast(value & TNF_MASK); + } + ///@} + + ///@name Setter + ///@{ + inline void messageBegin(const bool b) + { + value = (value & ~MB) | (b ? MB : 0); + } + inline void messageEnd(const bool b) + { + value = (value & ~ME) | (b ? ME : 0); + } + inline void chunk(const bool b) + { + value = (value & ~CF) | (b ? CF : 0); + } + inline void shortRecord(const bool b) + { + value = (value & ~SR) | (b ? SR : 0); + } + inline void idLength(const bool b) + { + value = (value & ~IL) | (b ? IL : 0); + } + inline void tnf(const TNF t) + { + value = (value & ~TNF_MASK) | (m5::stl::to_underlying(t) & TNF_MASK); + } + ///@} + + uint8_t value{}; +}; + /*! @class Record @brief NDEF Record diff --git a/src/nfc/ndef/ndef_tlv.cpp b/src/nfc/ndef/ndef_tlv.cpp index 668784f..fd7f412 100644 --- a/src/nfc/ndef/ndef_tlv.cpp +++ b/src/nfc/ndef/ndef_tlv.cpp @@ -240,6 +240,30 @@ uint32_t TLV::decode(const uint8_t* buf, const uint32_t len) return buf - top; } +uint32_t TLV::decode_t3t(const uint8_t* buf, const uint32_t len) +{ + clear(); + + uint32_t decoded{}; + uint16_t payload_len{}; + const auto top = buf; + + if (!buf || !len || len < 16) { + return 0; + } + + // Attribute block (16 byte) + AttributeBlock ab{}; + memcpy(ab.block, buf, 16); + + M5_LIB_LOGE("AB:%02X %u/%u/%u %02X/%02X %u %04X/%04X", ab.version(), ab.max_block_to_read(), + ab.max_block_to_write(), ab.blocks_for_ndef_storage(), ab.write_flag(), ab.access_flag(), + ab.current_ndef_message_length(), ab.check_sum(), ab.calculate_check_sum()); + + // _tag = Tag::Message; + return false; +} + void TLV::clear() { _tag = Tag::Null; diff --git a/src/nfc/ndef/ndef_tlv.hpp b/src/nfc/ndef/ndef_tlv.hpp index 17f326e..fd12c66 100644 --- a/src/nfc/ndef/ndef_tlv.hpp +++ b/src/nfc/ndef/ndef_tlv.hpp @@ -117,6 +117,17 @@ public: */ uint32_t decode(const uint8_t* buf, const uint32_t len); + /*! + @brief Decode + @param buf Pointer of the T3T buffer + @param len Buffer length + @retval > 0 Decoded length + @retval == 0 Error + */ + uint32_t decode_t3t(const uint8_t* buf, const uint32_t len); + + + /*! @brief Clear internal buffers @warning Keep the tag diff --git a/src/nfc/nfc.hpp b/src/nfc/nfc.hpp index f6273ea..b6a7c07 100644 --- a/src/nfc/nfc.hpp +++ b/src/nfc/nfc.hpp @@ -9,11 +9,8 @@ */ #ifndef M5_UNIT_UNIFIED_NFC_NFC_NFC_HPP #define M5_UNIT_UNIFIED_NFC_NFC_NFC_HPP -#include "a/nfca.hpp" -#include "b/nfcb.hpp" -#include "f/nfcf.hpp" -#include "v/nfcv.hpp" -#include "ndef/ndef.hpp" + +#include namespace m5 { /*! @@ -24,7 +21,7 @@ namespace nfc { /*! @enum NFC - @brief Operation Mode + @brief NFC type */ enum class NFC : uint8_t { None, //!< No mode @@ -34,7 +31,25 @@ enum class NFC : uint8_t { V //!< NFC-V }; +/*! + @enum NFCForumTag + @brief NFC Forum Tag Type + */ +enum class NFCForumTag : uint8_t { + None, + Type1, //!< Type 1 (NFC-A) + Type2, //!< Type 2 (NFC-A) + Type3, //!< Type 3 (NFC-F) + Type4, //!< Type 4 (NFC-A, NFC-B) + Type5, //!< Type 5 ISO/IEC 15693 +}; + } // namespace nfc } // namespace m5 +// #include "a/nfca.hpp" +// #include "b/nfcb.hpp" +// #include "f/nfcf.hpp" +// #include "v/nfcv.hpp" +// #include "ndef/ndef.hpp" #endif diff --git a/src/unit/unit_ST25R3916.hpp b/src/unit/unit_ST25R3916.hpp index 4cd3f32..fa83286 100644 --- a/src/unit/unit_ST25R3916.hpp +++ b/src/unit/unit_ST25R3916.hpp @@ -13,13 +13,19 @@ #include #include "ST25R3916_definition.hpp" #include "nfc/nfc.hpp" +#include "nfc/a/nfca.hpp" +#include "nfc/a/mifare.hpp" #include "nfc/a/mifare_classic_crypto1.hpp" +#include "nfc/b/nfcb.hpp" +#include "nfc/f/nfcf.hpp" +#include "nfc/v/nfcv.hpp" namespace m5 { namespace unit { + namespace nfc { struct AdapterST25R3916; -} +} // namespace nfc /*! @class UnitST25R3916 diff --git a/src/unit/unit_ST25R3916_nfca.cpp b/src/unit/unit_ST25R3916_nfca.cpp index 046cfcc..f03a48a 100644 --- a/src/unit/unit_ST25R3916_nfca.cpp +++ b/src/unit/unit_ST25R3916_nfca.cpp @@ -102,6 +102,8 @@ namespace unit { // -------------------------------- For NFC-A bool UnitST25R3916::configure_nfc_a() { + _encrypted = false; + #if 0 // // ISO14443A @@ -282,12 +284,10 @@ bool UnitST25R3916::nfcaSelectWithAnticollision(bool& completed, PICC& picc, con } // Resolve collision - // M5_LIB_LOGE(">>> ANTICOLL"); uint8_t rbuf[5]{}; if (!nfca_anti_collision(rbuf, lv)) { return false; } - // M5_LIB_LOGE("<<< ANTICOLL"); // Copy PICC memcpy(picc.uid + (lv - 1) * 3, rbuf + (rbuf[0] == 0x88), 4 - (rbuf[0] == 0x88)); @@ -511,14 +511,6 @@ bool UnitST25R3916::mifare_classic_send_encrypt(const uint8_t* tx, const uint16_ uint8_t sbytes = total_bits >> 3; uint8_t sbits = total_bits & 0x07; - /* - M5_LIB_LOGE(">>>>> send:%u/%u", sbytes, sbits); - m5::utility::log::dump(tx, tx_len, false); - m5::utility::log::dump(tmp_tx, sizeof(tmp_tx), false); - m5::utility::log::dump(enc_tx, sizeof(enc_tx), false); - m5::utility::log::dump(bitstream, sizeof(bitstream), false); - */ - if (!writeSettingsISO14443A(no_tx_par) || // !clearInterrupts() || !writeDirectCommand(CMD_CLEAR_FIFO) || // !writeFIFO(bitstream, sizeof(bitstream)) || !writeNumberOfTransmittedBytes(sbytes, sbits) || // @@ -747,7 +739,7 @@ bool UnitST25R3916::ntagReadPage(uint8_t* rx, uint16_t& rx_len, const uint8_t sp : TIMEOUT_FAST_READ_32PAGE * 2; if (!nfcaTransceive(rx, rx_len, cmd, sizeof(cmd), timeout)) { - M5_LIB_LOGE("Failed to transcive"); + M5_LIB_LOGE("Failed to transceive"); return false; } return true; diff --git a/src/unit/unit_ST25R3916_nfcf.cpp b/src/unit/unit_ST25R3916_nfcf.cpp index 8dc0af9..4cccdfa 100644 --- a/src/unit/unit_ST25R3916_nfcf.cpp +++ b/src/unit/unit_ST25R3916_nfcf.cpp @@ -256,14 +256,15 @@ bool UnitST25R3916::nfcfReadWithoutEncryption(uint8_t* rx, uint16_t& rx_len, con *p++ = ble.block() & 0xFF; } - // m5::utility::log::dump(packet.data(), packet.size(), false); + // 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]); + M5_LIB_LOGE("Failed to readWithoutEncryption (%02X, %u) %u %u %02X %02X", block_list[0].block(), rx_org_len, + actual, rbuf[0], rbuf[10], rbuf[11]); return false; } @@ -317,7 +318,8 @@ bool UnitST25R3916::nfcfWriteWithoutEncryption(const m5::nfc::f::PICC& picc, con 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]); + M5_LIB_LOGE("Failed to writeWithoutEncryption (%02X, %u) %u %u %02X %02X", block_list[0].block(), tx_len, + actual, rbuf[0], rbuf[10], rbuf[11]); return false; } return true;