mirror of
https://github.com/m5stack/M5Unit-NFC.git
synced 2026-05-20 11:48:34 -07:00
Fixes for ULC, and access with MAC for NFC-F
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user