Fixes isoDEP chain RX R-ACK block number and NFC-B ATTRIB PARAM2

This commit is contained in:
GOB
2026-05-11 18:47:06 +09:00
parent 114f1d07ce
commit 5d995e9bd7
5 changed files with 81 additions and 39 deletions
+1 -1
View File
@@ -186,7 +186,7 @@ enum class Command : uint8_t {
///@name Timeout
///@{
constexpr uint32_t TIMEOUT_REQ_WUP_B{5};
constexpr uint32_t TIMEOUT_ATTRIB{5};
constexpr uint32_t TIMEOUT_ATTRIB{50};
constexpr uint32_t TIMEOUT_HLTB{5};
constexpr uint32_t TIMEOUT_DESELECT{5};
///@}
+1 -1
View File
@@ -276,7 +276,7 @@ bool FileSystem::readBinary(std::vector<uint8_t>& out, const uint16_t offset,
// m5::utility::log::dump(cmd.data(), cmd.size(), false);
std::vector<uint8_t> rx;
rx.resize(le + 2 + 16);
rx.resize(le * 2 + 64); // generous margin for PICC chain overshoot
uint16_t rx_len = clamp_u16_size(rx.size());
if (!_isoDEP.transceiveAPDU(rx.data(), rx_len, cmd.data(), static_cast<uint16_t>(cmd.size())) || rx_len < 2) {
+12 -6
View File
@@ -169,7 +169,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t*
const uint32_t timeout_ms = _cfg.fwt_ms;
// Send I-Block and receive first frame
// M5_LIB_LOGE("I-Block TX: %u bytes, timeout=%u", tpos, timeout_ms);
M5_LIB_LOGV("isoDEP TX I-block[%u] timeout=%u max_rx=%u", tpos, timeout_ms, max_frame_size_rx);
if (!_layer.transceive(rx_buf, rlen, tx_buf, tpos, timeout_ms)) {
M5_LIB_LOGE("transceive failed, rlen=%u", rlen);
if (rlen > 0) {
@@ -181,6 +181,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t*
PRINT_ERROR(">>>>ERROR 1 %u %02X", rlen, rx_buf[0]);
return false;
}
M5_LIB_LOGV("isoDEP RX[%u] PCB=%02X", rlen, rx_buf[0]);
// Parse loop: WTX can replace rx_buf/rlen and we continue parsing without re-sending I-Block.
for (;;) {
@@ -206,7 +207,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t*
return false;
}
const uint8_t pcb = rx_buf[0];
uint8_t pcb = rx_buf[0];
// --- S-Block (WTX) ---
if (is_s_wtx(pcb)) {
@@ -314,15 +315,19 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t*
// Response chaining: send R-ACK and receive next I-Block (WTX may appear)
while (resp_more) {
uint8_t r_ack[2]{};
uint16_t rp = 0;
r_ack[rp++] = make_r_ack(i_bn(pcb), _cfg.use_cid);
uint16_t rp = 0;
const uint8_t ack_bn = i_bn(pcb) ^ 0x01;
r_ack[rp++] = make_r_ack(ack_bn, _cfg.use_cid);
if (_cfg.use_cid) r_ack[rp++] = (uint8_t)(_cfg.cid & 0x0F);
M5_LIB_LOGV("isoDEP chain TX R-ACK bn=%u (collected=%u)", ack_bn, rx_written);
uint16_t rlen2 = sizeof(rx_buf);
if (!_layer.transceive(rx_buf, rlen2, r_ack, rp, _cfg.fwt_ms)) {
M5_LIB_LOGE("isoDEP chain RX failed, rlen=%u", rlen2);
PRINT_ERROR(">>>>ERROR 12");
return false;
}
M5_LIB_LOGV("isoDEP chain RX[%u] PCB=%02X", rlen2, rx_buf[0]);
for (;;) {
if (_cfg.rx_crc && rlen2 >= 3) rlen2 -= 2;
@@ -381,8 +386,8 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t*
const uint16_t inf_len2 = (uint16_t)(rlen2 - idx2);
if (rx_written + inf_len2 > rx_inf_len_org) {
PRINT_ERROR("rx_written %u inf_len2 %u rx_inf_len %u", rx_written, inf_len2,
rx_inf_len);
PRINT_ERROR("rx_written %u inf_len2 %u rx_inf_len_org %u", rx_written, inf_len2,
rx_inf_len_org);
// m5::utility::log::dump(rx_inf, rx_written, false);
return false;
@@ -391,6 +396,7 @@ bool IsoDEP::transceiveINF(uint8_t* rx_inf, uint16_t& rx_inf_len, const uint8_t*
memcpy(rx_inf + rx_written, rx_buf + idx2, inf_len2);
rx_written = (uint16_t)(rx_written + inf_len2);
pcb = pcb2;
resp_more = i_has_more(pcb2);
break;
}
+51 -21
View File
@@ -43,6 +43,20 @@ constexpr uint8_t required_slots(const Require slots)
{
return 1U << m5::stl::to_underlying(slots);
}
// ISO/IEC 14443-4 FSDI table (Frame Size for Device Index, bit[7:4] of ATTRIB PARAM2)
constexpr uint8_t fsdi_for_size(const uint16_t bytes)
{
return (bytes >= 256) ? 8
: (bytes >= 128) ? 7
: (bytes >= 96) ? 6
: (bytes >= 64) ? 5
: (bytes >= 48) ? 4
: (bytes >= 40) ? 3
: (bytes >= 32) ? 2
: (bytes >= 24) ? 1
: 0;
}
} // namespace
namespace m5 {
@@ -69,10 +83,11 @@ bool NFCLayerB::receive(uint8_t* rx, uint16_t& rx_len, const uint32_t timeout_ms
return _impl->receive(rx, rx_len, timeout_ms);
}
bool NFCLayerB::detect(m5::nfc::b::PICC& picc, const uint8_t afi, const uint32_t timeout_ms)
bool NFCLayerB::detect(m5::nfc::b::PICC& picc, const uint8_t afi, const uint32_t timeout_ms,
const uint32_t req_timeout_ms)
{
std::vector<PICC> piccs{};
if (detect(piccs, afi, 1, timeout_ms)) {
if (detect(piccs, afi, 1, timeout_ms, req_timeout_ms)) {
picc = piccs.front();
return true;
}
@@ -80,7 +95,7 @@ bool NFCLayerB::detect(m5::nfc::b::PICC& picc, const uint8_t afi, const uint32_t
}
bool NFCLayerB::detect(std::vector<m5::nfc::b::PICC>& piccs, const uint8_t afi, const uint8_t max_piccs,
const uint32_t timeout_ms)
const uint32_t timeout_ms, const uint32_t req_timeout_ms)
{
piccs.clear();
@@ -88,7 +103,7 @@ bool NFCLayerB::detect(std::vector<m5::nfc::b::PICC>& piccs, const uint8_t afi,
do {
uint8_t rx[ATQB_LENGTH]{};
uint16_t rx_len = sizeof(rx);
if (!request(rx, rx_len, afi, Require::Slot1)) {
if (!request(rx, rx_len, afi, Require::Slot1, req_timeout_ms)) {
continue;
}
@@ -110,7 +125,7 @@ bool NFCLayerB::detect(std::vector<m5::nfc::b::PICC>& piccs, const uint8_t afi,
return !piccs.empty();
}
bool NFCLayerB::select(m5::nfc::b::PICC& picc)
bool NFCLayerB::select(m5::nfc::b::PICC& picc, const uint32_t timeout_ms)
{
// Wakeup for READY
uint16_t len = ATQB_LENGTH;
@@ -118,21 +133,35 @@ bool NFCLayerB::select(m5::nfc::b::PICC& picc)
return false;
}
M5_LIB_LOGI("ATQB protocol: %02X %02X %02X (FSCI=%u, ISO14443-4=%d, FWI=%u)", picc.protocol[0], picc.protocol[1],
picc.protocol[2], (picc.protocol[1] >> 4) & 0x0F, (picc.protocol[1] & 0x01),
(picc.protocol[2] >> 4) & 0x0F);
// ATTRIB PARAM2 (per ISO/IEC 14443-3 §7.10.2 with b1 = LSB):
// bit[3:0] = FSDI (PCD's max receive frame size index)
// bit[5:4] = PCD->PICC bit rate divisor
// bit[7:6] = PICC->PCD bit rate divisor
// Take min(PCD FIFO, PICC FSCI) so the PICC chains I-blocks within what we can receive.
const uint16_t pcd_rx_cap = maximum_fifo_depth() - 2 /*CRC*/;
const uint8_t pcd_fsdi = fsdi_for_size(pcd_rx_cap);
const uint8_t picc_fsci = picc.maximumFrameLengthBits();
const uint8_t fsdi = std::min<uint8_t>(pcd_fsdi, picc_fsci);
// ATTRIB
uint8_t cmd[1 + 4 + 1 + 1 + 1 + 1] = {m5::stl::to_underlying(Command::ATTRIB)}; // without option
memcpy(cmd + 1, picc.pupi, 4);
cmd[5] = 0x00; // PARAM1
cmd[6] = picc.maximumFrameLengthBits(); // PARAM2 | com speed
cmd[7] = picc.protocol[1] & 0x0F; // PARAM 3protocol type
cmd[8] = 0x00; // PARAM 4
cmd[5] = 0x00; // PARAM1
cmd[6] = (uint8_t)(fsdi & 0x0F); // PARAM2: FSDI(PCD cap) bit[3:0] | rates=0 (106k both)
cmd[7] = picc.protocol[1] & 0x0F; // PARAM3 protocol type
cmd[8] = 0x00; // PARAM 4
std::vector<uint8_t> frame;
frame.assign(cmd, cmd + sizeof(cmd));
uint8_t rx[128]{};
uint16_t rx_len = sizeof(rx);
if (!transceive(rx, rx_len, frame.data(), frame.size(), TIMEOUT_ATTRIB) || !rx_len) {
M5_LIB_LOGE("Failed to select");
if (!transceive(rx, rx_len, frame.data(), frame.size(), timeout_ms) || !rx_len) {
M5_LIB_LOGE("Failed to select: rx_len=%u", rx_len);
return false;
}
@@ -140,14 +169,14 @@ bool NFCLayerB::select(m5::nfc::b::PICC& picc)
return true;
}
bool NFCLayerB::hlt(const uint8_t pupi[4])
bool NFCLayerB::hlt(const uint8_t pupi[4], const uint32_t timeout_ms)
{
if (pupi) {
uint8_t cmd[1 + 4] = {m5::stl::to_underlying(Command::HLTB)};
memcpy(cmd + 1, pupi, 4);
uint8_t rx[1]{};
uint8_t rx[1 + 2]{}; // 1 byte payload + 2 byte CRC_B
uint16_t rx_len = sizeof(rx);
if (!transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_HLTB) || rx_len < 1) {
if (!transceive(rx, rx_len, cmd, sizeof(cmd), timeout_ms) || rx_len < 1) {
M5_LIB_LOGE("Failed to hlt %02X%02X%02X%02X", cmd[1], cmd[2], cmd[3], cmd[4]);
return false;
}
@@ -156,17 +185,18 @@ bool NFCLayerB::hlt(const uint8_t pupi[4])
return false;
}
bool NFCLayerB::deselect(const uint8_t pupi[4], const uint8_t cid)
bool NFCLayerB::deselect(const uint8_t pupi[4], const uint8_t cid, const uint32_t timeout_ms)
{
uint8_t cmd[2] = {m5::stl::to_underlying(cid != 0xFF ? Command::DESELECT_WITH_CID : Command::DESELECT)};
uint16_t cmd_len = 1 + (cid != 0xFF);
if (cid != 0xFF) {
cmd[1] = cid;
}
uint8_t rx[2]{};
uint16_t rx_len = cmd_len;
uint8_t rx[2 + 2]{}; // payload (1 or 2) + 2 byte CRC_B
uint16_t rx_len =
cmd_len + 2; // Match actual response size to keep wait_for_FIFO fallback equivalent to the old behavior
if (!transceive(rx, rx_len, cmd, cmd_len, TIMEOUT_DESELECT) || rx_len < cmd_len) {
if (!transceive(rx, rx_len, cmd, cmd_len, timeout_ms) || rx_len < cmd_len) {
M5_LIB_LOGE("Failed to deselecte %02X:%02X", cmd[0], cmd[1]);
return false;
}
@@ -188,7 +218,7 @@ bool NFCLayerB::deactivate()
//
bool NFCLayerB::request_wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi, const Require slots,
const bool wakeup)
const bool wakeup, const uint32_t timeout_ms)
{
if (!atqb || atqb_len < ATQB_LENGTH) {
return false;
@@ -204,7 +234,7 @@ bool NFCLayerB::request_wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t
atqb_len = 0;
// Ignore non-responsive slots and proceed to the next one.
if (transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_REQ_WUP_B) && rx_len == sizeof(rx) && rx[0] == 0x50) {
if (transceive(rx, rx_len, cmd, sizeof(cmd), timeout_ms) && rx_len == sizeof(rx) && rx[0] == 0x50) {
// Occur collision if CRC error
const uint16_t crc = crc16.range(rx, ATQB_LENGTH + 1);
if (crc == ((uint16_t)rx[13] << 8 | rx[12])) {
@@ -219,7 +249,7 @@ bool NFCLayerB::request_wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t
rx_len = sizeof(rx);
slot_marker[0] = ((uint8_t)i << 4) | 0x05;
// Ignore non-responsive slots and proceed to the next one.
if (!transceive(rx, rx_len, slot_marker, sizeof(slot_marker), TIMEOUT_REQ_WUP_B) || rx[0] != 0x50 ||
if (!transceive(rx, rx_len, slot_marker, sizeof(slot_marker), timeout_ms) || rx[0] != 0x50 ||
rx_len < sizeof(rx)) {
continue;
}
+16 -10
View File
@@ -28,6 +28,7 @@ namespace m5 {
namespace unit {
class UnitST25R3916;
class CapST25R3916;
class UnitWS1850S;
} // namespace unit
namespace nfc {
@@ -40,6 +41,7 @@ public:
struct Adapter;
explicit NFCLayerB(m5::unit::UnitST25R3916& u);
explicit NFCLayerB(m5::unit::CapST25R3916& u);
explicit NFCLayerB(m5::unit::UnitWS1850S& u);
virtual bool transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
const uint32_t timeout_ms) override;
@@ -83,9 +85,10 @@ public:
@post PICC transitions: IDLE -> READY on successful response
*/
inline bool request(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi = 0x00,
const m5::nfc::b::Require slots = m5::nfc::b::Require::Slot1)
const m5::nfc::b::Require slots = m5::nfc::b::Require::Slot1,
const uint32_t timeout_ms = m5::nfc::b::TIMEOUT_REQ_WUP_B)
{
return request_wakeup(atqb, atqb_len, afi, slots, false);
return request_wakeup(atqb, atqb_len, afi, slots, false, timeout_ms);
}
/*!
@brief Send WUPB to wake a PICC from IDLE or HALT
@@ -98,9 +101,10 @@ public:
@post PICC transitions: IDLE/HALT -> READY on successful response
*/
inline bool wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi = 0x00,
const m5::nfc::b::Require slots = m5::nfc::b::Require::Slot1)
const m5::nfc::b::Require slots = m5::nfc::b::Require::Slot1,
const uint32_t timeout_ms = m5::nfc::b::TIMEOUT_REQ_WUP_B)
{
return request_wakeup(atqb, atqb_len, afi, slots, true);
return request_wakeup(atqb, atqb_len, afi, slots, true, timeout_ms);
}
/*!
@@ -111,7 +115,8 @@ public:
@return True if detected
@note The detected PICC is typically put into HALT during enumeration to allow discovering others
*/
bool detect(m5::nfc::b::PICC& picc, const uint8_t afi = 0x00, const uint32_t timeout_ms = 100U);
bool detect(m5::nfc::b::PICC& picc, const uint8_t afi = 0x00, const uint32_t timeout_ms = 50U,
const uint32_t req_timeout_ms = m5::nfc::b::TIMEOUT_REQ_WUP_B);
/*!
@brief Detect idle PICCs
@param[out] piccs Detected PICC PICCs (one per activated PICC candidate)
@@ -122,11 +127,11 @@ public:
@note The detected PICC is typically put into HALT during enumeration to allow discovering others
*/
bool detect(std::vector<m5::nfc::b::PICC>& piccs, const uint8_t afi = 0x00, const uint8_t max_piccs = 4,
const uint32_t timeout_ms = 1000U);
const uint32_t timeout_ms = 1000U, const uint32_t req_timeout_ms = m5::nfc::b::TIMEOUT_REQ_WUP_B);
/*!
*/
bool select(m5::nfc::b::PICC& picc);
bool select(m5::nfc::b::PICC& picc, const uint32_t timeout_ms = m5::nfc::b::TIMEOUT_ATTRIB);
#if 0
bool activate(m5::nfc::b::PICC& picc);
bool reactivate(const m5::nfc::b::PICC& picc);
@@ -140,14 +145,15 @@ public:
///@name For activated PICC
///@{
bool hlt(const uint8_t pupi[4]);
bool deselect(const uint8_t pupi[4], const uint8_t cid = 0xFF);
bool hlt(const uint8_t pupi[4], const uint32_t timeout_ms = m5::nfc::b::TIMEOUT_HLTB);
bool deselect(const uint8_t pupi[4], const uint8_t cid = 0xFF,
const uint32_t timeout_ms = m5::nfc::b::TIMEOUT_DESELECT);
bool deactivate();
///@}
protected:
bool request_wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi, const m5::nfc::b::Require slots,
const bool wakeup);
const bool wakeup, const uint32_t timeout_ms = m5::nfc::b::TIMEOUT_REQ_WUP_B);
virtual bool read(uint8_t* rx, uint16_t& rx_len, const uint16_t saddr) override
{