Fixes for ULC, and access with MAC for NFC-F

This commit is contained in:
GOB
2025-12-10 12:48:17 +09:00
parent 2e4558593a
commit 0fc8b985e8
17 changed files with 897 additions and 186 deletions
+4
View File
@@ -34,6 +34,10 @@ namespace m5 {
@brief Unit-related namespace
*/
namespace unit {
using UnitNFC = m5::unit::UnitST25R3916;
using HackerCapNFC = m5::unit::CapST25R3916;
} // namespace unit
} // namespace m5
#endif
+1 -1
View File
@@ -116,7 +116,7 @@ constexpr uint16_t user_area_size_table[] = {
constexpr NFCForumTag nfc_forum_tag_table[] = {
NFCForumTag::None, //
NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, NFCForumTag::None, // Classic
NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // UltraLight
NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // Light
NFCForumTag::None, NFCForumTag::None, // Plus
NFCForumTag::Type4, NFCForumTag::Type4, NFCForumTag::Type4, // DESFire
NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, NFCForumTag::Type2, // NTAG
+1 -1
View File
@@ -281,7 +281,7 @@ enum class Command : uint8_t {
PERSONALIZE_UID_USAGE = 0x40, //!< MIFARE Classic Personalize UID Usage
SET_MOD_TYPE = 0x43, //!< MIFARE Classic SET_MOD_TYPE
// NTAG
GET_VERSION = 0x60, //!< NTAG 21x. Gets the version information
GET_VERSION = 0x60, //!< NTAG 21x/UL EV1,Nano Gets the version information
FAST_READ = 0x3A, //!< NTAG 21x. excluding 210u. Read multiple pages
READ_CNT = 0x39, //!< NTAG 213/5/6. Read counter value
PWD_AUTH = 0x1B, //!< NTAG 21x excluding 210u. Authentication for protected area
+95
View File
@@ -86,6 +86,101 @@ uint8_t get_maxumum_write_blocks(const Type t)
return max_write_block_table[idx < m5::stl::size(max_block_table) ? idx : 0];
}
bool make_session_key(uint8_t sk[16], const uint8_t ck[16], const uint8_t rc[16])
{
using m5::utility::crypto::TripleDES;
using Key16 = TripleDES::Key16;
// 1) (CK[7..0] reversed + CK[15..8] reversed)
Key16 key{};
for (int i = 0; i < 8; ++i) {
key[i] = ck[7 - i]; // CK1 reversed
key[8 + i] = ck[15 - i]; // CK2 reversed
}
// 2) (RC[7..0] reversed + RC[15..8] reversed)
uint8_t data[16]{};
for (int i = 0; i < 8; ++i) {
data[i] = rc[7 - i]; // RC1 reversed
data[8 + i] = rc[15 - i]; // RC2 reversed
}
// 3) 2-key 3DES CBC(IV=0)
TripleDES des(TripleDES::Mode::CBC, TripleDES::Padding::None);
uint8_t tmp[16]{};
if (des.encrypt(tmp, data, sizeof(data), key) != 16) {
return false;
}
// 4) expand
// SK1 = reverse(tmp[0..7])
// SK2 = reverse(tmp[8..15])
for (int i = 0; i < 8; ++i) {
sk[i] = tmp[7 - i]; // SK1
sk[8 + i] = tmp[15 - i]; // SK2
}
return true;
}
bool generate_mac(uint8_t mac[8], const uint8_t* plain, uint32_t plain_len, const uint8_t* block_data,
uint32_t block_len, const uint8_t sk1[8], const uint8_t sk2[8], const uint8_t rc[16])
{
using m5::utility::crypto::TripleDES;
if (!mac || !block_data || !block_len || !sk1 || !sk2 || !rc) {
return false;
}
// key1[::-1] + key2[::-1]
TripleDES::Key16 key{};
for (int i = 0; i < 8; ++i) {
key[i] = sk1[7 - i];
key[8 + i] = sk2[7 - i];
}
// IV = RC1[7..0] (first 8byte in RC)
uint8_t iv[8]{};
for (int i = 0; i < 8; ++i) {
iv[i] = rc[7 - i];
}
TripleDES des(TripleDES::Mode::CBC, TripleDES::Padding::None, iv);
// plain[::-1] + concat(each 8byte chunk reversed)
std::vector<uint8_t> buf;
buf.reserve(((plain_len + block_len) + 7) & ~7u);
// plain[::-1]
if (plain && plain_len) {
for (uint32_t i = 0; i < plain_len; ++i) {
buf.push_back(plain[plain_len - 1 - i]);
}
}
// block_data[i:i+8][::-1]
for (uint32_t off = 0; off < block_len; off += 8) {
uint32_t chunk = std::min<uint32_t>(8, block_len - off);
uint8_t tmp[8]{};
std::memcpy(tmp, block_data + off, chunk);
for (uint32_t i = 0; i < 8; ++i) {
buf.push_back(tmp[7 - i]);
}
}
std::vector<uint8_t> out(buf.size());
auto len = des.encrypt(out.data(), buf.data(), static_cast<uint32_t>(buf.size()), key);
if (len != buf.size()) {
return false;
}
for (int i = 0; i < 8; ++i) {
mac[i] = out[out.size() - 1 - i];
}
return true;
}
//
std::string PICC::idmAsString() const
{
+89 -36
View File
@@ -45,18 +45,6 @@ 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
*/
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;
@@ -224,12 +212,33 @@ struct block_t {
}
};
/*!
@namespacce lite
@brief For FeliCa Standard
*/
namespace standard {
/*!
@enum Mode
@brief Mode for Standard
*/
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
};
} // namespace standard
/*!
@namespacce lite
@brief For FeliCa Lite
*/
namespace lite {
///@name
///@name Block
///@{
constexpr block_t S_PAD0{0x00};
constexpr block_t S_PAD1{0x01};
constexpr block_t S_PAD2{0x02};
@@ -254,6 +263,7 @@ constexpr block_t SYS_C{0x85};
constexpr block_t CKV{0x86};
constexpr block_t CK{0x87};
constexpr block_t MC{0x88};
///@}
} // namespace lite
@@ -262,34 +272,49 @@ constexpr block_t MC{0x88};
@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};
/*!
@enum Mode
@brief Mode for LiteS
*/
enum class Mode : uint8_t {
Mode00, //!< External authentication incomplete, polling response possible
Mode01, //!< External authentication incomplete, polling response not possible
Mode10, //!< External authentication complete, polling response possibl
Mode11, //!< External authentication complete, polling response not possible
};
///@name Block
///@{
constexpr block_t S_PAD0{0x00}; // Same as Lite
constexpr block_t S_PAD1{0x01}; // Same as Lite
constexpr block_t S_PAD2{0x02}; // Same as Lite
constexpr block_t S_PAD3{0x03}; // Same as Lite
constexpr block_t S_PAD4{0x04}; // Same as Lite
constexpr block_t S_PAD5{0x05}; // Same as Lite
constexpr block_t S_PAD6{0x06}; // Same as Lite
constexpr block_t S_PAD7{0x07}; // Same as Lite
constexpr block_t S_PAD8{0x08}; // Same as Lite
constexpr block_t S_PAD9{0x09}; // Same as Lite
constexpr block_t S_PAD10{0x0A}; // Same as Lite
constexpr block_t S_PAD11{0x0B}; // Same as Lite
constexpr block_t S_PAD12{0X0C}; // Same as Lite
constexpr block_t S_PAD13{0x0D}; // Same as Lite
constexpr block_t REG{0x0E}; // Same as Lite
constexpr block_t RC{0x80}; // Same as Lite
constexpr block_t MAC{0x81}; // Same as Lite
constexpr block_t ID{0x082}; // Same as Lite
constexpr block_t D_ID{0x83}; // Same as Lite
constexpr block_t SER_C{0x84}; // Same as Lite
constexpr block_t SYS_C{0x85}; // Same as Lite
constexpr block_t CKV{0x86}; // Same as Lite
constexpr block_t CK{0x87}; // Same as Lite
constexpr block_t MC{0x88}; // Same as Lite
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
@@ -499,6 +524,34 @@ inline bool can_write_reg(const REG& o, const REG& n)
return (o.regA() >= n.regA()) && (o.regB() >= n.regB());
}
///@name For MAC
///@{
/*!
@brief Make session key
@param[out] sk Session key (sk1 8byte + sk2 8byte)
@param ck Card key
@param rc Random challenge
@return True if successful
*/
bool make_session_key(uint8_t sk[16], const uint8_t ck[16], const uint8_t rc[16]);
/*!
@brief Generate MAC
@param[out] mac MAC
@param plain Plain blocks (If nullptr, do not use)
@param plain_num Number of plain (If zero, do not use)
@param block_data Block data
@param block_len Length of block_data
@param sk1 Session key 1
@param sk2 Session key 2
@param rc Random challenge
@return True if successful
*/
bool generate_mac(uint8_t mac[8], const uint8_t* plain, uint32_t plain_len, const uint8_t* block_data,
uint32_t block_len, const uint8_t sk1[8], const uint8_t sk2[8], const uint8_t rc[16]);
///@}
} // namespace f
} // namespace nfc
} // namespace m5
+2 -2
View File
@@ -111,8 +111,7 @@ bool NDEFLayer::read_with_tlv(std::vector<m5::nfc::ndef::TLV>& tlvs, const m5::n
}
uint16_t buf_size = (last_block - block + 1) * _interface.userBlockUnitSize();
;
buf = static_cast<uint8_t*>(malloc(buf_size));
buf = static_cast<uint8_t*>(malloc(buf_size));
if (!buf) {
M5_LIB_LOGE("Failed to allocate memory %u", buf_size);
return false;
@@ -460,6 +459,7 @@ bool calculate_ndef_size(uint32_t& size, const uint8_t* p, const uint8_t* end, c
size = required;
return true;
}
#endif
} // namespace ndef
+96 -2
View File
@@ -13,6 +13,7 @@
#include <inttypes.h>
#include <M5Utility.hpp>
#include <algorithm>
#include <esp_random.h>
using namespace m5::nfc::a;
using namespace m5::nfc::a::mifare;
@@ -61,6 +62,15 @@ void dump_block(const uint8_t buf[16], const int16_t block = -1, const int16_t s
}
::puts(tmp);
}
void rotate_byte_left(uint8_t out[8], const uint8_t in[8])
{
for (int i = 0; i < 7; ++i) {
out[i] = in[i + 1];
}
out[7] = in[0];
}
} // namespace
namespace m5 {
@@ -97,16 +107,18 @@ bool NFCLayerA::detect(std::vector<PICC>& piccs, const uint32_t timeout_ms)
uint16_t atqa{};
do {
// Exists PICC?
M5_LIB_LOGI(">>>");
if (!request(atqa)) {
break;
}
M5_LIB_LOGI("<<<");
// M5_LIB_LOGE("==> ATQA:%04X", atqa);
// Select
if (!select(picc)) {
return false;
}
M5_LIB_LOGV("Detect:%s %s", picc.uidAsString().c_str(), picc.typeAsString().c_str());
M5_LIB_LOGE("Detect:%s %s", picc.uidAsString().c_str(), picc.typeAsString().c_str());
// Hlt
if (!deactivate()) {
@@ -147,7 +159,11 @@ bool NFCLayerA::reactivate(const PICC& picc)
// If arg referrence is the same as _activePICC, it will cause an error, so it must be a separate instance (*1)
PICC tmp = picc;
uint16_t discard{};
return tmp.valid() && deactivate() && wakeup(discard) && activate(tmp);
if (tmp.valid() && deactivate()) {
m5::utility::delay(2);
return wakeup(discard) && activate(tmp);
}
return false;
}
bool NFCLayerA::deactivate()
@@ -650,6 +666,84 @@ bool NFCLayerA::mifareUltralightChangeFormatToNTAG()
return true;
}
bool NFCLayerA::mifareUltralightCAuthenticate(const uint8_t key[16])
{
using m5::utility::crypto::TripleDES;
TripleDES::Key16 key16{};
memcpy(key16.data(), key, 16);
// Auth step 1. Receive ek(RndB)
uint8_t ek_rndB[8]{};
if (!_impl->mifare_ultralightC_authenticate1(ek_rndB)) {
M5_LIB_LOGE("Failed to auth1");
return false;
}
// Decrypt ek
uint8_t iv[8]{};
uint8_t rndB[8]{};
{
TripleDES des{TripleDES::Mode::CBC, TripleDES::Padding::None, iv};
if (!des.decrypt(rndB, ek_rndB, sizeof(ek_rndB), key16)) {
M5_LIB_LOGE("Failed to decrypt");
return false;
}
}
// Make rndA
uint8_t rndA[8]{};
for (auto& r : rndA) {
r = esp_random();
}
// Make RndB',RandA'
uint8_t rndB_rot[8]{};
uint8_t rndA_rot[8]{};
rotate_byte_left(rndB_rot, rndB);
rotate_byte_left(rndA_rot, rndA);
// Make plain
uint8_t plain_AB[16]{};
memcpy(plain_AB, rndA, 8);
memcpy(plain_AB + 8, rndB_rot, 8);
// Make ek(RndA || RndB')
uint8_t ek_AB[16]{};
{
TripleDES des{TripleDES::Mode::CBC, TripleDES::Padding::None, ek_rndB};
if (!des.encrypt(ek_AB, plain_AB, sizeof(plain_AB), key16)) {
M5_LIB_LOGE("Failed to encrypt");
return false;
}
}
// Auth step 2. Send [AF || ek(RndA||RndB')], Receive [RndA']
uint8_t ek_rndA_rot_from_card[8]{};
if (!_impl->mifare_ultralightC_authenticate2(ek_rndA_rot_from_card, ek_AB)) {
M5_LIB_LOGE("Failed to auth2");
return false;
}
// Decrypt RndA'
uint8_t rndA_rot_from_card[8]{};
{
TripleDES des{TripleDES::Mode::CBC, TripleDES::Padding::None, ek_AB + 8};
if (!des.decrypt(rndA_rot_from_card, ek_rndA_rot_from_card, sizeof(ek_rndA_rot_from_card), key16)) {
return false;
}
}
// Compare
if (memcmp(rndA_rot, rndA_rot_from_card, 8) != 0) {
M5_LIB_LOGE("Not match");
m5::utility::log::dump(rndA_rot, 8, false);
m5::utility::log::dump(rndA_rot_from_card, 8, false);
return false;
}
return true;
}
bool NFCLayerA::ndefIsValidFormat(bool& valid)
{
valid = false;
+12 -5
View File
@@ -183,7 +183,7 @@ public:
@param tx_len Buffer size
@param safety Fail to write to out of the user memory area if true (safety measure)
@return True if successful
@warning Supports NTAG and UltraLight only
@warning Supports NTAG and Ultralight series only
@warning If the tx_len is less than 4 bytes, the remaining space is filled with 0x00
@warning If the tx_len is larger than 4 bytes, only the first 4 bytes will be written
*/
@@ -207,7 +207,7 @@ public:
@param tx Buffer
@param tx_len buffer size
@return True if successful
@warning For NTAG and UltraLight, the tx is in 4-byte units; for others, it is in 16-byte units
@warning For NTAG and Ultralight series, the tx is in 4-byte units; for others, it is in 16-byte units
@warning If the value is less than the unit, it is padded with 0x00
@pre Target blocks must be authenticatable using the specified key if MIFARE classic
*/
@@ -368,11 +368,16 @@ public:
@brief Write change to NFC Type-2 (NDEF) format for MIFARE Ultralight/C
@return True if successful
@note Returns true if the data is already in NDEF format or if the PICC is an NTAG
@warning Only MIFARE Ultralight,UltralightC
@warning Only MIFARE Ultralight series
@warning Changes are irreversible and cannot be undone
@warning If the relevant area has already been overwritten, changes may not be possible
*/
bool mifareUltralightChangeFormatToNTAG();
/*!
@brief Authentication for MIFARE UltralightC
*/
bool mifareUltralightCAuthenticate(const uint8_t key[16]);
///@}
///@note For activated PICC
@@ -478,9 +483,11 @@ struct NFCLayerA::Adapter {
virtual bool nfca_write_page(const uint8_t addr, const uint8_t tx[4]) = 0; // WRITE_PAGE
virtual bool mifare_classic_authenticate(const bool auth_a, const m5::nfc::a::PICC& picc, const uint8_t block,
const m5::nfc::a::mifare::classic::Key& key) = 0;
const m5::nfc::a::mifare::classic::Key& key) = 0;
virtual bool mifare_classic_value_block(const m5::nfc::a::Command cmd, const uint8_t block,
const uint32_t arg = 0) = 0;
const uint32_t arg = 0) = 0;
virtual bool mifare_ultralightC_authenticate1(uint8_t ek[8]) = 0;
virtual bool mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]) = 0;
virtual bool ntag_read_page(uint8_t* rx, uint16_t& rx_len, const uint8_t spage,
const uint8_t epage) = 0; // FAST_READ
+326 -12
View File
@@ -13,6 +13,7 @@
#include <inttypes.h>
#include <M5Utility.hpp>
#include <algorithm>
#include <esp_random.h>
using namespace m5::nfc::f;
using namespace m5::nfc::ndef;
@@ -52,6 +53,14 @@ inline bool is_same_idm_and_pmm(const PICC& a, const PICC& b)
return a.idm == b.idm && a.pmm == b.pmm;
}
const uint8_t* make_rc(uint8_t rc[16])
{
for (uint_fast8_t i = 0; i < 16; ++i) {
rc[i] = esp_random();
}
return rc;
}
} // namespace
namespace m5 {
@@ -81,6 +90,7 @@ bool NFCLayerF::detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* pri
{
uint8_t slots = timeslot_to_slot(time_slot);
_authenticated = false;
piccs.clear();
piccs.reserve(slots);
@@ -101,6 +111,8 @@ bool NFCLayerF::detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* pri
continue;
}
M5_LIB_LOGV("detect %s", picc1.idmAsString().c_str());
_activePICC = picc1;
// 2. Check IDm for NFCIP-1 Transport Protocol / DFC
@@ -196,7 +208,7 @@ bool NFCLayerF::detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* pri
// Re-check
if (type == Type::FeliCaStandard) {
Mode mode{};
standard::Mode mode{};
if (!_impl->requestResponse(mode, _activePICC)) {
continue;
}
@@ -220,7 +232,8 @@ bool NFCLayerF::detect(std::vector<m5::nfc::f::PICC>& piccs, const uint16_t* pri
bool NFCLayerF::activate(const m5::nfc::f::PICC& picc)
{
if (picc.valid()) {
_activePICC = picc;
_activePICC = picc;
_authenticated = false;
return true;
}
return false;
@@ -228,7 +241,8 @@ bool NFCLayerF::activate(const m5::nfc::f::PICC& picc)
bool NFCLayerF::deactivate()
{
_activePICC = PICC{};
_activePICC = PICC{};
_authenticated = false;
return true;
}
@@ -243,7 +257,7 @@ bool NFCLayerF::requestService(uint16_t key_version[], const uint16_t* node_code
return _activePICC.valid() && _impl->requestService(key_version, _activePICC, node_code, node_size);
}
bool NFCLayerF::requestResponse(m5::nfc::f::Mode& mode)
bool NFCLayerF::requestResponse(m5::nfc::f::standard::Mode& mode)
{
return _activePICC.valid() && _impl->requestResponse(mode, _activePICC);
}
@@ -253,14 +267,6 @@ bool NFCLayerF::requestSystemCode(uint16_t code_list[255], uint8_t& code_num)
return _activePICC.valid() && _impl->requestSystemCode(code_list, code_num, _activePICC);
}
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 && (check_valid ? _activePICC.valid() : true) &&
_impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, &block, 1) && rx_len == 16;
}
bool NFCLayerF::read16(uint8_t rx[16], const m5::nfc::f::block_t block, const uint16_t service_code)
{
return read16(rx, &block, 1, &service_code, 1);
@@ -275,6 +281,17 @@ bool NFCLayerF::read16(uint8_t rx[16], const m5::nfc::f::block_t* block, const u
rx_len == 16;
}
bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::block_t* block, const uint8_t block_num)
{
if (!_activePICC.valid() || !rx || !rx_len || !block || !block_num || block_num > _activePICC.maximumReadBlocks()) {
return false;
}
uint16_t sc{service_random_read};
return _impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, block, block_num) ||
(rx_len != 16 * block_num);
}
bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const block_t sblock)
{
auto rx_len_org = rx_len;
@@ -317,6 +334,67 @@ bool NFCLayerF::read(uint8_t* rx, uint16_t& rx_len, const block_t sblock)
return true;
}
bool NFCLayerF::readWithMAC16(uint8_t rx[16], const m5::nfc::f::block_t block)
{
if (!_authenticated) {
M5_LIB_LOGW("NOT authenticated");
return false;
}
if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLite && _activePICC.type != Type::FeliCaLiteS)) {
return false;
}
const bool liteS = (_activePICC.type == Type::FeliCaLiteS);
block_t mac_block = liteS ? lite_s::MAC_A : lite::MAC;
block_t block_list[] = {block, mac_block};
uint8_t rbuf[16 * 2]{};
uint16_t rx_len = sizeof(rbuf);
if (!read(rbuf, rx_len, block_list, 2) || rx_len < sizeof(rbuf)) {
M5_LIB_LOGE("Failed to read");
return false;
}
const uint8_t* data_block = rbuf;
const uint8_t* mac_card = rbuf + 16;
const uint8_t* sk1 = _sk;
const uint8_t* sk2 = _sk + 8;
const uint8_t* rc = _rc;
uint8_t mac_host[8]{};
if (liteS) {
uint8_t plain[8] = {
static_cast<uint8_t>(block.block()),
0x00,
static_cast<uint8_t>(mac_block.block()),
0x00,
0xFF,
0xFF,
0xFF,
0xFF,
};
if (!generate_mac(mac_host, plain, sizeof(plain), data_block, 16, sk1, sk2, rc)) {
return false;
}
} else {
if (!generate_mac(mac_host, nullptr, 0, data_block, 16, sk1, sk2, rc)) {
return false;
}
}
if (std::memcmp(mac_host, mac_card, 8) != 0) {
M5_LIB_LOGE("MAC mismatch");
// M5_LIB_LOGE("Not match %u", liteS);
m5::utility::log::dump(mac_host, 8, false);
m5::utility::log::dump(mac_card, 8, false);
return false;
}
std::memcpy(rx, data_block, 16);
return true;
}
bool NFCLayerF::write16(const m5::nfc::f::block_t block, const uint8_t tx[16], const uint16_t tx_len)
{
if (_activePICC.valid() && tx && tx_len) {
@@ -349,6 +427,85 @@ bool NFCLayerF::write(const m5::nfc::f::block_t sblock, const uint8_t* tx, const
return true;
}
bool NFCLayerF::writeWithMAC16(const m5::nfc::f::block_t block, const uint8_t tx[16], const uint16_t tx_len)
{
if (!_authenticated) {
M5_LIB_LOGW("NOT authenticated");
return false;
}
if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLite && _activePICC.type != Type::FeliCaLiteS)) {
return false;
}
uint8_t tx2[16]{};
memcpy(tx2, tx, std::min<uint16_t>(tx_len, sizeof(tx2)));
const bool liteS = (_activePICC.type == Type::FeliCaLiteS);
block_t mac_block = liteS ? lite_s::MAC_A : lite::MAC;
const uint8_t* sk1 = _sk;
const uint8_t* sk2 = _sk + 8;
const uint8_t* rc = _rc;
uint8_t mac_host[8]{};
uint8_t wcnt[4]{};
if (liteS) {
// Read WCNT
uint8_t wcnt_block[16]{};
if (!read16(wcnt_block, lite_s::WCNT)) {
return false;
}
std::memcpy(wcnt, wcnt_block, 4); // Using first 4 bytes (for Link Lite-S mode)
uint8_t plain[8] = {
wcnt[0],
wcnt[1],
wcnt[2],
wcnt[3], // Always 0 if Lite/Lite-S
static_cast<uint8_t>(block.block()),
0x00,
static_cast<uint8_t>(mac_block.block()),
0x00,
};
if (!generate_mac(mac_host, plain, sizeof(plain), tx2, 16, sk2, sk1, rc)) {
return false;
}
} else {
if (!generate_mac(mac_host, nullptr /* plain*/, 0 /*plain num */, tx2, 16, sk1, sk2, rc)) {
return false;
}
}
uint8_t wbuf[32]{};
std::memcpy(wbuf, tx2, 16);
std::memcpy(wbuf + 16, mac_host, 8);
std::memcpy(wbuf + 24, wcnt, 4);
block_t block_list[2] = {block, mac_block};
m5::utility::log::dump(wbuf, 32);
return false;
return write_32(block_list, wbuf);
}
bool NFCLayerF::internalAuthenticate(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16])
{
if (!_activePICC.valid() || (_activePICC.type != Type::FeliCaLiteS)) {
return false;
}
return internal_authenticate_lite_s(ck, ckv, rc);
}
bool NFCLayerF::externalAuthenticate(const uint8_t ck[16], const uint16_t ckv)
{
if (!_activePICC.valid() || _activePICC.type != Type::FeliCaLiteS) {
return false;
}
if (!_authenticated) {
M5_LIB_LOGW("NOT authenticated");
return false;
}
return external_authenticate_lite_s(ck, ckv);
}
bool NFCLayerF::ndefIsValidFormat(bool& valid)
{
valid = false;
@@ -422,6 +579,163 @@ bool NFCLayerF::dump(const block_t block)
}
//
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 && (check_valid ? _activePICC.valid() : true) &&
_impl->readWithoutEncryption(rx, rx_len, _activePICC, &sc, 1, &block, 1) && rx_len == 16;
}
bool NFCLayerF::write_32(const m5::nfc::f::block_t block[2], const uint8_t tx[32])
{
// 2nd block must be MAC_A
uint16_t sc{service_random_read_write};
if (block && tx && block[1].block() == lite_s::MAC_A.block()) {
return _impl->writeWithoutEncryption(_activePICC, &sc, 1, block, 2, tx, 32);
}
return false;
}
bool NFCLayerF::internal_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16],
const bool include_wcnt)
{
_authenticated = false;
// Make session key
uint8_t sk[16]{};
const uint8_t* sk1 = sk;
const uint8_t* sk2 = sk + 8;
if (!make_session_key(sk, ck, rc)) {
M5_LIB_LOGE("Failed to make_session_key");
return false;
}
// m5::utility::log::dump(sk, 16, false);
// Write RC
if (!write16(lite::RC, rc, 16)) {
M5_LIB_LOGE("Failed to write CK");
return false;
}
// Read ID,CKV, WCNT, MAC(Lite)/MAC_A(Lite-S)
block_t block_list[4] = {lite::ID, lite::CKV, //
(include_wcnt ? lite_s::WCNT : lite_s::MAC_A), //
(include_wcnt ? lite_s::MAC_A : block_t{0x00})}; //
uint8_t rbuf[16 * 4]{};
uint16_t rx_len = include_wcnt ? (4 * 16) : (3 * 16);
auto needs = rx_len;
if (!read(rbuf, rx_len, block_list, include_wcnt ? 4 : 3) || rx_len != needs) {
M5_LIB_LOGE("Failed to read blocks %u/%u", rx_len, needs);
return false;
}
// m5::utility::log::dump(rbuf, rx_len, false);
// Compare CKV
const uint8_t* ckv_block = rbuf + 16; // 2nd block
const uint16_t ckv_card = (static_cast<uint16_t>(ckv_block[0]) << 8) | static_cast<uint16_t>(ckv_block[1]);
if (ckv_card != ckv) {
M5_LIB_LOGE("CKV mismatch %04X,%04X", ckv_card, ckv);
return false;
}
// Compare MAC
uint8_t mac_host[8]{};
uint8_t plain[8] = {static_cast<uint8_t>(block_list[0].block()),
0x00,
static_cast<uint8_t>(block_list[1].block()),
0x00,
static_cast<uint8_t>(block_list[2].block()),
0x00,
static_cast<uint8_t>(include_wcnt ? block_list[3].block() : 0xFF),
static_cast<uint8_t>(include_wcnt ? 0x00 : 0xFF)};
if (!generate_mac(mac_host, plain, sizeof(plain), //
rbuf, 32 /*ID + CKV*/ + (include_wcnt != 0) * 16 /*WCNT*/, //
sk1, sk2, rc)) {
M5_LIB_LOGE("Failed to generate_mac");
return false;
}
// m5::utility::log::dump(mac, 8, false);
const uint8_t* mac_card = rbuf + 32 + (include_wcnt != 0) * 16; // MAC_A
if (std::memcmp(mac_host, mac_card, 8) != 0) {
M5_LIB_LOGE("MAC mismatch CKV:%04X,%04X", ckv_card, ckv);
// M5_LIB_LOGE("Not match %u", liteS);
m5::utility::log::dump(mac_host, 8, false);
m5::utility::log::dump(mac_card, 8, false);
return false;
}
memcpy(_sk, sk, sizeof(_sk));
memcpy(_rc, rc, sizeof(_rc));
_authenticated = true;
return true;
}
bool NFCLayerF::external_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv)
{
// Write RC
uint8_t rc[16]{};
make_rc(rc);
if (!write16(lite_s::RC, rc, sizeof(rc))) {
return false;
}
// internal auth with WCNT
if (!internal_authenticate_lite_s(ck, ckv, rc)) {
M5_LIB_LOGE("Failed to internal_authenticate_lite_s");
return false;
}
uint8_t wcnt[16]{};
if (!read16(wcnt, lite_s::WCNT)) {
M5_LIB_LOGE("Failed to internal_authenticate_lite_s");
return false;
}
M5_LIB_LOGE("WCNT:%02X:%02X:%02X:%02X", wcnt[0], wcnt[1], wcnt[2], wcnt[3]);
//
uint8_t state[16]{
0x01 /* Authed */,
};
uint8_t plain_w[8] = {wcnt[0],
wcnt[1],
wcnt[2],
wcnt[3], // Always 0x00 if Lite/Lite-S
static_cast<uint8_t>(lite_s::STATE.block()),
0x00,
static_cast<uint8_t>(lite_s::MAC_A.block()),
0x00};
const uint8_t* sk1 = _sk;
const uint8_t* sk2 = _sk + 8;
uint8_t mac_w[8]{};
if (!generate_mac(mac_w, plain_w, sizeof(plain_w), state, sizeof(state), sk2, sk1, rc)) {
M5_LIB_LOGE("Failed to generate_mac");
return false;
}
uint8_t tx[32]{};
std::memcpy(tx, state, 16);
std::memcpy(tx + 16, mac_w, 8);
std::memcpy(tx + 24, wcnt, 4);
block_t block_list[2] = {lite_s::STATE, lite_s::MAC_A};
if (!write_32(block_list, tx)) {
M5_LIB_LOGE("Failed to write_32");
return false;
}
/*
read16(state, lite_s::STATE);
M5_LIB_LOGE("=== STATE");
m5::utility::log::dump(state, 16);
*/
return true;
}
bool NFCLayerF::dump_felica_lite()
{
+52 -4
View File
@@ -175,7 +175,7 @@ public:
@param[out] mode Mode if detected
@warning FeliCa Standard only
*/
bool requestResponse(m5::nfc::f::Mode& mode);
bool requestResponse(m5::nfc::f::standard::Mode& mode);
/*!
@brief Request system code
@@ -244,6 +244,17 @@ public:
*/
bool read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::block_t sblock);
/*!
@breif Read the specified block list
@param[out] rx Buffer (At least 16 * block_num)
@param[in/out] rx_len in:buffer size, out:actual read size
@param block Target block array
@param block_num Number of block
@return True if successful
@warning rx in 16-byte units
*/
bool read(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::block_t* block, const uint8_t block_num);
/*!
@brief Write the 1 block
@param block Target block
@@ -267,11 +278,34 @@ public:
///@note For activated PICC
///@name Read/Write with MAC
///@{
/*!
@brief Internal authentication
@param ck Card key
@param ckv Card key version
@param rc Random challenge
@return True if successful
*/
bool internalAuthenticate(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16]);
/*!
@brief External authentication
@param wcnt WCNT value
@return True if successful
@pre internalAuthenticate
*/
bool externalAuthenticate(const uint8_t ck[16], const uint16_t ckv);
void clearAuthenticate()
{
_authenticated = false;
}
/*!
@brief Read the 1 block
@param[out] rx Output buffer
@param block Target block
@return True if successful
@pre internalAuthentication
*/
bool readWithMAC16(uint8_t rx[16], const m5::nfc::f::block_t block);
@@ -292,7 +326,7 @@ public:
///@{
/*!
@brief Is the PICC data in NDEF format?
@param[out] valid True if NDEF format
[ @param[out] valid True if NDEF format
@return True if successful
*/
bool ndefIsValidFormat(bool& valid);
@@ -334,18 +368,32 @@ protected:
virtual uint8_t maximumReadBlocks() const override;
virtual uint8_t maximumWriteBlocks() const override;
bool read_16(uint8_t rx[16], const m5::nfc::f::block_t block, const bool check_valid);
bool read_16(uint8_t rx[16], const m5::nfc::f::block_t block, const bool check_picc_valid);
bool write_32(const m5::nfc::f::block_t block[2], const uint8_t tx[32]); // For write with MAC
bool dump_felica_lite();
bool dump_felica_lite_s();
bool dump_block(m5::nfc::f::block_t block);
bool internal_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv, const uint8_t rc[16],
const bool include_wcnt = false);
bool external_authenticate_lite_s(const uint8_t ck[16], const uint16_t ckv);
protected:
m5::nfc::f::PICC _activePICC{};
private:
std::unique_ptr<Adapter> _impl;
m5::nfc::ndef::NDEFLayer _ndef;
union {
uint8_t _sk[16]{};
struct {
uint8_t _sk1[8];
uint8_t _sk2[8];
};
};
uint8_t _rc[16]{};
bool _authenticated{};
};
///@cond
@@ -358,7 +406,7 @@ struct NFCLayerF::Adapter {
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 requestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc) = 0;
virtual bool requestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc) = 0;
virtual bool requestSystemCode(uint16_t code_list[255], uint8_t& code_num, const m5::nfc::f::PICC& picc) = 0;
virtual bool readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
@@ -47,6 +47,8 @@ struct AdapterST25R3916ForA final : NFCLayerA::Adapter {
const m5::nfc::a::mifare::classic::Key& key) override;
virtual bool mifare_classic_value_block(const m5::nfc::a::Command cmd, const uint8_t block,
const uint32_t arg = 0) override;
virtual bool mifare_ultralightC_authenticate1(uint8_t ek[8]) override;
virtual bool mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]) override;
virtual bool ntag_read_page(uint8_t* rx, uint16_t& rx_len, const uint8_t spage,
const uint8_t epage) override; // FAST_READ
@@ -119,6 +121,16 @@ bool AdapterST25R3916ForA::mifare_classic_value_block(const m5::nfc::a::Command
return _u.mifareClassicValueBlock(cmd, block, arg);
}
bool AdapterST25R3916ForA::mifare_ultralightC_authenticate1(uint8_t ek[8])
{
return _u.mifareUltralightCAuthenticate1(ek);
}
bool AdapterST25R3916ForA::mifare_ultralightC_authenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16])
{
return _u.mifareUltralightCAuthenticate2(rx_ek, tx_ek);
}
//
namespace {
std::unique_ptr<NFCLayerA::Adapter> make_st25r3916_adapter(UnitST25R3916& u)
+2 -2
View File
@@ -31,7 +31,7 @@ struct AdapterST25R3916ForF final : NFCLayerF::Adapter {
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 requestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc) override;
virtual bool requestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc) override;
virtual bool requestSystemCode(uint16_t code_list[255], uint8_t& code_num, const m5::nfc::f::PICC& picc) override;
virtual bool readWithoutEncryption(uint8_t* rx, uint16_t& rx_len, const m5::nfc::f::PICC& picc,
@@ -56,7 +56,7 @@ bool AdapterST25R3916ForF::requestService(uint16_t key_version[], const m5::nfc:
return _u.nfcfRequestService(key_version, picc, node_code, node_num);
}
bool AdapterST25R3916ForF::requestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc)
bool AdapterST25R3916ForF::requestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc)
{
return _u.nfcfRequestResponse(mode, picc);
}
+7
View File
@@ -232,6 +232,13 @@ constexpr uint8_t OP_DIRECT_COMMAND{0xC0}; // 11xxxxxxb;
*/
namespace regval {
///@cond
// 0x00 IO configuration register 1
constexpr uint8_t i2c_thd1{0x20};
constexpr uint8_t i2c_thd0{0x10};
constexpr uint16_t i2c_thd116{0x2000};
constexpr uint16_t i2c_thd016{0x1000};
// 0x01 IO configuration register 2
constexpr uint8_t sup3v{0x80};
constexpr uint8_t io_drv_lvl{0x04};
+59 -88
View File
@@ -66,6 +66,11 @@ constexpr uint16_t io_config12_i2c{io_drv_lvl};
constexpr uint16_t io_config12_spi{miso_pd1 | miso_pd2};
// constexpr uint16_t io_config12_spi{miso_pd1 | miso_pd2 | io_drv_lvl};
constexpr uint16_t get_i2c_thd_bits16(const uint32_t clk)
{
return (clk >= 1000 * 1000u) ? (i2c_thd016 | i2c_thd116) : ((clk >= 400 * 1000u) ? i2c_thd016 : 0x0000);
}
float regulated_voltages(const uint8_t regulator_display_reg_value, const bool voltage5V = false)
{
auto rv = (regulator_display_reg_value >> 4) & 0x0F;
@@ -131,7 +136,6 @@ void IRAM_ATTR UnitST25R3916::on_irq(void* arg)
bool UnitST25R3916::begin()
{
// Attach interrupt
M5_LIB_LOGE(">>>> IRQ:%u", _cfg.using_irq);
if (_cfg.using_irq) {
M5_LIB_LOGE("Using IRQ:%u", _cfg.irq);
pinMode(_cfg.irq, INPUT_PULLDOWN);
@@ -140,51 +144,63 @@ bool UnitST25R3916::begin()
}
// Chip detection
M5_LIB_LOGE(">>>> Chip detection");
uint8_t type{}, rev{};
if (!readICIdentity(type, rev) || type != VALID_IDENTIFY_TYPE || rev == 0) {
M5_LIB_LOGE("Not detected ST25R3916 %02X,%02X", type, rev);
return false;
}
M5_LIB_LOGE("<<<< Chip detection %02X:%02X", type, rev);
// Power-on sequence
// 1) Set to default
M5_LIB_LOGE(">>>> Set to default");
if (!writeDirectCommand(CMD_SET_DEFAULT)) {
M5_LIB_LOGE("Failed to CMD_SET_DEFAULT");
return false;
}
// 2) To prevent the internal overheat protection to trigger below the junction temperature
M5_LIB_LOGE(">>>> Protection");
if (!writeDirectCommand(CMD_TEST_ACCESS, protection_command, sizeof(protection_command))) {
M5_LIB_LOGE("Failed to send protection command");
return false;
}
// 3) I/O settings
M5_LIB_LOGE(">>>> I/O");
if (!writeIOConfiguration((adapter()->type() == Adapter::Type::I2C ? io_config12_i2c : io_config12_spi) |
(_cfg.vdd_voltage_5V ? 0x0000 : sup3v))) {
uint16_t params{};
if (adapter()->type() == Adapter::Type::I2C) {
// I2C settings
uint16_t i2c_thd = get_i2c_thd_bits16(component_config().clock);
params = i2c_thd | io_config12_i2c | (_cfg.vdd_voltage_5V ? 0x0000 : sup3v);
} else if (adapter()->type() == Adapter::Type::SPI) {
// SPI settings
params = io_config12_spi | (_cfg.vdd_voltage_5V ? 0x0000 : sup3v);
} else {
M5_LIB_LOGE("Not support connection %u", adapter()->type());
return false;
}
if (!writeIOConfiguration(params)) {
M5_LIB_LOGE("Failed to writeIOConfiguration");
return false;
}
#if 0
{
uint8_t io1{}, io2{};
readIOConfiguration1(io1);
readIOConfiguration2(io2);
M5_LIB_LOGE(">>>>> IO1:%02X IO2:%02X", io1, io2);
}
#endif
// 4) The internal voltage regulators have to be configuration
// It is recommended to use direct command Adjust regulators to improve the system PSRR.
M5_LIB_LOGE(">>>> Mask");
if (!writeMaskInterrupts(0xFFFF00FF) && clearInterrupts()) { // Mask all interrupts exclusive error
M5_LIB_LOGE("Failed to writeMaskInterrupt");
return false;
}
// Adjust regulators
M5_LIB_LOGE(">>>> Adjust 1");
if (!writeOperationControl(en)) {
M5_LIB_LOGE("Failed to writeOperationControl");
return false;
}
M5_LIB_LOGE(">>>> Adjust 2");
if (!writeDirectCommand(CMD_ADJUST_REGULATORS)) {
M5_LIB_LOGE("Failed to CMD_ADJUST_REGULATORS");
return false;
@@ -192,14 +208,12 @@ bool UnitST25R3916::begin()
m5::utility::delay(5); // Need wait
// Check vdd voltage
M5_LIB_LOGE(">>>> VDD");
uint8_t value{};
if (readRegulatorDisplay(value)) {
M5_LIB_LOGD("Regulated voltages:%02X:%1.1fV", value, regulated_voltages(value, _cfg.vdd_voltage_5V));
}
// Antenna Settings
M5_LIB_LOGE(">>>> Antenna");
uint8_t txd{};
if (!readTXDriver(txd) || !writeTXDriver((txd & 0x0F) | ((_cfg.tx_am_modulation & 0x0F) << 4))) {
M5_LIB_LOGE("Failed to TXDriver");
@@ -209,7 +223,6 @@ bool UnitST25R3916::begin()
M5_LIB_LOGD("TXD:%02X", txd);
//
#if 0
// MRT/SQT
if (!write_mask_receiver_timer(0) || !write_squelch_timer(0)) {
@@ -222,7 +235,6 @@ bool UnitST25R3916::begin()
M5_LIB_LOGE("====== MRT:%02X SQT:%02X", mrt, sqt);
#endif
M5_LIB_LOGE(">>>> Config");
return configureNFCMode(_cfg.mode);
}
@@ -340,73 +352,6 @@ bool UnitST25R3916::nfc_initial_field_on()
return ret && modify_bit_register8(REG_OPERATION_CONTROL, tx_en | rx_en, 0x00);
#endif
#if 0
/*******************************************************************************/
ReturnCode RfalRfST25R3916Class::st25r3916PerformCollisionAvoidance(uint8_t FieldONCmd, uint8_t pdThreshold, uint8_t caThreshold, uint8_t nTRFW)
{
uint8_t treMask;
uint32_t irqs;
ReturnCode err;
if ((FieldONCmd != ST25R3916_CMD_INITIAL_RF_COLLISION) && (FieldONCmd != ST25R3916_CMD_RESPONSE_RF_COLLISION_N)) {
return ERR_PARAM;
}
err = ERR_INTERNAL;
/* Check if new thresholds are to be applied */
if ((pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) || (caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET)) {
treMask = 0;
if (pdThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) {
treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask;
}
if (caThreshold != ST25R3916_THRESHOLD_DO_NOT_SET) {
treMask |= ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask;
}
/* Set Detection Threshold and|or Collision Avoidance Threshold */
st25r3916ChangeRegisterBits(ST25R3916_REG_FIELD_THRESHOLD_ACTV, treMask, (pdThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask) | (caThreshold & ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask));
}
/* Set n x TRFW */
st25r3916ChangeRegisterBits(ST25R3916_REG_AUX, ST25R3916_REG_AUX_nfc_n_mask, nTRFW);
/*******************************************************************************/
/* Enable and clear CA specific interrupts and execute command */
st25r3916GetInterrupt((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON));
st25r3916EnableInterrupts((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON));
st25r3916ExecuteCommand(FieldONCmd);
/*******************************************************************************/
/* Wait for initial APON interrupt, indicating anticollision avoidance done and ST25R3916's
* field is now on, or a CAC indicating a collision */
irqs = st25r3916WaitForInterruptsTimed((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_APON), ST25R3916_TOUT_CA);
if ((ST25R3916_IRQ_MASK_CAC & irqs) != 0U) { /* Collision occurred */
err = ERR_RF_COLLISION;
} else if ((ST25R3916_IRQ_MASK_APON & irqs) != 0U) {
/* After APON wait for CAT interrupt, indication field was switched on minimum guard time has been fulfilled */
irqs = st25r3916WaitForInterruptsTimed((ST25R3916_IRQ_MASK_CAT), ST25R3916_TOUT_CA);
if ((ST25R3916_IRQ_MASK_CAT & irqs) != 0U) { /* No Collision detected, Field On */
err = ERR_NONE;
}
} else {
/* MISRA 15.7 - Empty else */
}
/* Clear any previous External Field events and disable CA specific interrupts */
st25r3916GetInterrupt((ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_EON));
st25r3916DisableInterrupts((ST25R3916_IRQ_MASK_CAC | ST25R3916_IRQ_MASK_CAT | ST25R3916_IRQ_MASK_APON));
return err;
}
#endif
}
bool UnitST25R3916::configure_nfc_b()
@@ -481,7 +426,7 @@ bool UnitST25R3916::readFIFOSize(uint16_t& bytes, uint8_t& bits)
return false;
}
bool UnitST25R3916::readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size)
uint32_t UnitST25R3916::readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size)
{
actual = 0;
@@ -497,9 +442,9 @@ bool UnitST25R3916::readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_
return false;
}
actual = readSz;
return true;
return ((uint16_t)bits << 16) | bytes;
}
return false;
return 0u;
}
bool UnitST25R3916::writeFIFO(const uint8_t* buf, const uint16_t buf_size)
@@ -528,6 +473,32 @@ bool UnitST25R3916::readICIdentity(uint8_t& type, uint8_t& rev)
return false;
}
bool UnitST25R3916::disableField()
{
return writeDirectCommand(CMD_STOP_ALL_ACTIVITIES) && writeOperationControl(0x00);
}
bool UnitST25R3916::enableField()
{
// Adjust regulators
if (!writeOperationControl(en)) {
M5_LIB_LOGE("Failed to writeOperationControl");
return false;
}
if (!writeDirectCommand(CMD_ADJUST_REGULATORS)) {
M5_LIB_LOGE("Failed to CMD_ADJUST_REGULATORS");
return false;
}
m5::utility::delay(5); // Need wait
// Check vdd voltage
uint8_t value{};
if (readRegulatorDisplay(value)) {
M5_LIB_LOGD("Regulated voltages:%02X:%1.1fV", value, regulated_voltages(value, _cfg.vdd_voltage_5V));
}
return configureNFCMode(_cfg.mode);
}
//
bool UnitST25R3916::read_register8(const uint8_t reg, uint8_t& v)
{
@@ -661,6 +632,7 @@ bool UnitST25R3916::wait_for_FIFO(const uint32_t timeout_ms, const uint16_t requ
const uint16_t reqSize = required_size ? required_size : 1;
if (is_irq32_rxe(irq)) {
// M5_LIB_LOGE(" rxe OK IRQ:%08X", irq);
return true;
}
// M5_LIB_LOGE("IRQ:%08X %u", irq, timeout_ms);
@@ -671,11 +643,10 @@ bool UnitST25R3916::wait_for_FIFO(const uint32_t timeout_ms, const uint16_t requ
uint16_t bytes{};
uint8_t bits{};
do {
readFIFOSize(bytes, bits);
if (bytes >= reqSize) {
if (readFIFOSize(bytes, bits) && bytes >= reqSize) {
break;
}
m5::utility::delay(1);
std::this_thread::yield();
} while (m5::utility::millis() <= timeout_at);
// M5_LIB_LOGE(" FIFO:%u,%u/%u", bytes, bits, required_size);
return readFIFOSize(bytes, bits) && bytes >= reqSize;
+44 -8
View File
@@ -149,9 +149,10 @@ public:
@param[out] actual Actual read size
@param[out] buf Buffer
@param buf_size Buffer size
@return True if successful
@retval == 0 Failed
@retval != 0 Upper 16 bits: Number of bits read Lower 16 bits: Number of bytes read
*/
bool readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size);
uint32_t readFIFO(uint16_t& actual, uint8_t* buf, const uint16_t buf_size);
/*!
@brief Write to FIFO
@param buf Buffer
@@ -1685,6 +1686,22 @@ public:
bool readICIdentity(uint8_t& type, uint8_t& rev);
///@}
///@name Field
///@{
/*!
@brief Disable the Field to stop communication with the PICC
@return True if successful
@note Disconnect power supply to the PICC
*/
bool disableField();
/*!
@brief Enable the Field to begin communication with the PICC
@return True if successful
@brief Begin supplying power to the PICC
*/
bool enableField();
///@}
// ----------------------------------------------------------------------------------------------
///@name NFC-A
///@{
@@ -1695,10 +1712,11 @@ public:
@param tx Send buffer
@param tx_len Size of send buffer
@param timeout_ms Timeout(ms)
@return True if successful
@retval == 0 Failed
@retval != 0 Upper 16 bits: Number of bits read Lower 16 bits: Number of bytes read
*/
bool nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms);
uint32_t nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms);
/*!
@brief Request for idle PICC
@param[out atqa ATQA
@@ -1761,7 +1779,7 @@ public:
bool nfcaHlt();
///@}
///@name MIFARE classic
///@name MIFARE
///@{
/*!
@brief Authentication using keyA of the specified block
@@ -1794,8 +1812,23 @@ public:
@param cmd Command
@param block Block address
@param arg Arrgument for command if needs
@return True if successful
*/
bool mifareClassicValueBlock(const m5::nfc::a::Command cmd, const uint8_t block, const uint32_t arg = 0);
/*!
@brief Authentication step 1 for UltralightC
@param[out] ek ek(RndB) 8-byte encrypted PICC random number RndB
@return True if successful
*/
bool mifareUltralightCAuthenticate1(uint8_t ek[8]);
/*!
@brief Authentication step 1 for UltralightC
@param[out] rx_ek ek(RndA') 8-byte encrypted, shifted PCD random number RndA'
@param tx_ek ek(RandA || RndB') 16-byte encrypted random numbers RNDA concatenated by RndB'
@return True if successful
*/
bool mifareUltralightCAuthenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16]);
///@}
///@name NTAG
@@ -1857,7 +1890,7 @@ public:
@return True if successful
@warning FeliCa Standard only
*/
bool nfcfRequestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc);
bool nfcfRequestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc);
/*!
@brief Request system code
@@ -1943,12 +1976,15 @@ protected:
// NFC-A
bool nfca_request_wakeup(uint16_t& atqa, const bool req);
bool nfca_anti_collision(uint8_t rbuf[5], const uint8_t lv);
bool mifare_transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms);
bool mifare_classic_send_encrypt(const uint8_t* tx, const uint16_t tx_len);
bool mifare_classic_transceive_encrypt(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms, const bool include_crc, const bool decrypt);
bool mifare_classic_authenticate(const m5::nfc::a::Command cmd, const m5::nfc::a::PICC& picc, const uint8_t block,
const m5::nfc::a::mifare::classic::Key& key);
bool ntag_get_version(uint8_t info[10]);
bool ntag_get_version(uint8_t info[8]);
private:
config_t _cfg{};
+92 -22
View File
@@ -9,6 +9,7 @@
*/
#include "unit_ST25R3916.hpp"
#include <M5Utility.hpp>
#include <esp_random.h>
using namespace m5::utility::mmh3;
@@ -127,8 +128,8 @@ bool UnitST25R3916::configure_nfc_a()
nfc_initial_field_on();
}
bool UnitST25R3916::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms)
uint32_t UnitST25R3916::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms)
{
CHECK_MODE();
@@ -157,11 +158,13 @@ bool UnitST25R3916::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t*
}
uint16_t actual{};
if (readFIFO(actual, rx, rx_len_org)) {
auto bb = readFIFO(actual, rx, rx_len_org);
if (bb) {
// M5_LIB_LOGE("readFIFO %u/%u %u/%u %02X", actual, rx_len_org, bb >> 16, bb & 0xFFFF, rx[0]);
rx_len = actual;
return true;
return bb;
}
M5_LIB_LOGE("Failed to readFIFO %u/%u", actual, rx_len_org);
M5_LIB_LOGD("Failed to readFIFO %u/%u", actual, rx_len_org);
return false;
}
@@ -192,9 +195,9 @@ bool UnitST25R3916::nfca_request_wakeup(uint16_t& atqa, const bool request)
if (readFIFO(actual, rbuf, sizeof(rbuf)) && actual) {
if (actual == 2) {
atqa = ((uint16_t)rbuf[1] << 8) | (uint16_t)rbuf[0];
M5_LIB_LOGD("ATQA:%04X", atqa);
}
// When ocuur collisions, the ATQA value is inaccurate
// M5_LIB_LOGE("ATQA:%04X %u", atqa, actual);
// When ocuur collisions, the ATQA value is inaccurate
return true;
}
}
@@ -234,7 +237,7 @@ bool UnitST25R3916::nfca_anti_collision(uint8_t rbuf[5], const uint8_t lv)
auto irq = wait_for_interrupt(I_rxe32 | I_col32, TIMEOUT_ANTICOLL);
collision = is_irq32_collision(irq);
if ((!collision && !is_irq32_rxe(irq))) {
M5_LIB_LOGD("Failed ANTICOL:%02X %08X", lv, irq);
M5_LIB_LOGE("Failed Lv::%u col:%u %08X", lv, collision, irq);
return false;
}
uint8_t cd{};
@@ -312,19 +315,38 @@ bool UnitST25R3916::nfcaSelectWithAnticollision(bool& completed, PICC& picc, con
picc.type =
sak_to_type(sak); // WARNING: This is a preliminary diagnosis; a more accurate diagnosis is required
picc.blocks = get_number_of_blocks(picc.type);
// More check for type
// More detailed type identification
if (picc.type == Type::MIFARE_Ultralight) {
uint8_t ver[10]{};
picc.type = Type::Unknown;
uint8_t ver[8]{};
uint16_t discard{};
// GetVersion
if (ntag_get_version(ver)) {
picc.type = version_to_type(ver);
picc.blocks = get_number_of_blocks(picc.type);
picc.type = version_to_type(ver);
} else {
// PICC to IDLE... so need reactivate
uint16_t discard{};
// PICC is IDLE... so need reactivate
completed = nfcaWakeup(discard) && nfcaSelect(picc);
}
if (picc.type == Type::Unknown) {
uint8_t discard_ek[8]{};
if (mifareUltralightCAuthenticate1(discard_ek)) {
// ULC has AUTH
picc.type = Type::MIFARE_UltralightC;
// Throw an Hlt to transition to IDLE
// Otherwise, subsequent commands become invalid in an incomplete state, causing unexpected IDLE
nfcaHlt();
} else {
// really UL
picc.type = Type::MIFARE_Ultralight;
}
picc.blocks = get_number_of_blocks(picc.type);
// PICC is IDLE... so need reactivate
completed = nfcaWakeup(discard) && nfcaSelect(picc);
}
}
}
// M5_LIB_LOGE(">>>> Select %02X %u %u", sak, completed, has_sak_dependent_bit(sak));
return completed || has_sak_dependent_bit(sak); // completed or continue
}
@@ -334,9 +356,7 @@ bool UnitST25R3916::nfcaSelect(const PICC& picc)
CHECK_MODE();
if (!picc.valid()) {
return false;
}
// Select even if picc is not valid
bool completed{};
uint8_t select_frame[7] = {0x93, 0x70};
@@ -372,6 +392,8 @@ bool UnitST25R3916::nfcaSelect(const PICC& picc)
++lv;
} while (!completed && lv < 4);
// M5_LIB_LOGE(" >>>> SELECT Result:%u", completed);
return completed;
}
@@ -379,6 +401,8 @@ bool UnitST25R3916::nfcaHlt()
{
CHECK_MODE();
_encrypted = false;
const uint8_t hlt_frame[2] = {m5::stl::to_underlying(Command::HLTA), 0x00};
if (_encrypted) {
@@ -391,10 +415,10 @@ bool UnitST25R3916::nfcaHlt()
!clearInterrupts() || !writeDirectCommand(CMD_CLEAR_FIFO) || //
!writeFIFO(hlt_frame, sizeof(hlt_frame)) || !writeNumberOfTransmittedBytes(sizeof(hlt_frame), 0) || //
!writeDirectCommand(CMD_TRANSMIT_WITH_CRC)) {
M5_LIB_LOGE("Failed to hlt");
return false;
}
}
_encrypted = false;
// No response is coming back, so need to confirm if it was sent
auto irq = wait_for_interrupt(I_txe32, TIMEOUT_HALT);
return is_irq32_txe(irq);
@@ -485,6 +509,22 @@ bool UnitST25R3916::nfcaWritePage(const uint8_t page, const uint8_t tx[4])
}
// -------------------------------- For MIFARE classic
bool UnitST25R3916::mifare_transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms)
{
if (!rx | !rx_len || !tx || !tx_len) {
return false;
}
auto bb = nfcaTransceive(rx, rx_len, tx, tx_len, timeout_ms);
if (!bb) {
return false;
}
// Check NACK
uint16_t bytes = bb & 0xffff;
uint16_t bits = (bb >> 16) & 0xFF;
return (bytes == 1 && bits == 4) ? rx[0] == ACK_NIBBLE : true;
}
bool UnitST25R3916::mifare_classic_send_encrypt(const uint8_t* tx, const uint16_t tx_len)
{
if (!tx || !tx_len || tx_len > 32) {
@@ -720,6 +760,36 @@ bool UnitST25R3916::mifareClassicValueBlock(const m5::nfc::a::Command cmd, const
return !wait_for_FIFO(TIMEOUT_VALUE_BLOCK); // Consider the timeout a success
}
bool UnitST25R3916::mifareUltralightCAuthenticate1(uint8_t ek[8])
{
uint8_t cmd[2] = {m5::stl::to_underlying(Command::AUTHENTICATE_1), 0x00};
uint8_t rx[9]{};
uint16_t rx_len{9};
if (ek && mifare_transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_AUTH1) && rx_len == 9 && rx[0] == 0xAF) {
memcpy(ek, rx + 1, 8);
return true;
}
return false;
}
bool UnitST25R3916::mifareUltralightCAuthenticate2(uint8_t rx_ek[8], const uint8_t tx_ek[16])
{
if (!rx_ek || !tx_ek) {
return false;
}
uint8_t cmd[1 + 16] = {m5::stl::to_underlying(Command::AUTHENTICATE_2)};
memcpy(cmd + 1, tx_ek, 16);
uint8_t rx[9]{};
uint16_t rx_len{9};
if (mifare_transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_AUTH2) && rx_len == 9 && rx[0] == 0x00) {
memcpy(rx_ek, rx + 1, 8);
return true;
}
return false;
}
// -------------------------------- For NTAG
bool UnitST25R3916::ntagReadPage(uint8_t* rx, uint16_t& rx_len, const uint8_t spage, const uint8_t epage)
{
@@ -745,11 +815,11 @@ bool UnitST25R3916::ntagReadPage(uint8_t* rx, uint16_t& rx_len, const uint8_t sp
return true;
}
bool UnitST25R3916::ntag_get_version(uint8_t info[10])
bool UnitST25R3916::ntag_get_version(uint8_t info[8])
{
uint8_t gv[1] = {m5::stl::to_underlying(Command::GET_VERSION)};
uint16_t rx_len = 10;
return nfcaTransceive(info, rx_len, gv, sizeof(gv), TIMEOUT_GET_VERSION);
uint8_t cmd[1] = {m5::stl::to_underlying(Command::GET_VERSION)};
uint16_t rx_len = 8;
return info && mifare_transceive(info, rx_len, cmd, sizeof(cmd), TIMEOUT_GET_VERSION);
}
} // namespace unit
+3 -3
View File
@@ -218,11 +218,11 @@ bool UnitST25R3916::nfcfRequestService(uint16_t key_version[], const m5::nfc::f:
return true;
}
bool UnitST25R3916::nfcfRequestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f::PICC& picc)
bool UnitST25R3916::nfcfRequestResponse(m5::nfc::f::standard::Mode& mode, const m5::nfc::f::PICC& picc)
{
CHECK_MODE();
mode = Mode::Mode0;
mode = standard::Mode::Mode0;
if (picc.type != Type::FeliCaStandard) {
return false;
@@ -249,7 +249,7 @@ bool UnitST25R3916::nfcfRequestResponse(m5::nfc::f::Mode& mode, const m5::nfc::f
// m5::utility::log::dump(rbuf, rx_len, false);
mode = static_cast<Mode>(rbuf[10]);
mode = static_cast<standard::Mode>(rbuf[10]);
return true;
}