[pn71xx] Remove heap allocation from tag handling and NCI transport (#19738)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Keith Burzinski
2026-09-28 10:58:57 +02:00
committed by GitHub
co-authored by Claude Fable 5.1
parent bd69b17ef6
commit bb7af14012
13 changed files with 441 additions and 323 deletions
+84 -68
View File
@@ -2,56 +2,64 @@
#include "nci_message.h"
#include "esphome/core/log.h"
#include <cstdio>
namespace esphome::nfc {
static const char *const TAG = "NciMessage";
NciMessage::NciMessage(const uint8_t message_type, const std::vector<uint8_t> &payload) {
NciMessage::NciMessage(const uint8_t message_type, const std::span<const uint8_t> payload) {
this->set_message(message_type, payload);
}
NciMessage::NciMessage(const uint8_t message_type, const std::initializer_list<uint8_t> payload)
: NciMessage(message_type, std::span<const uint8_t>(payload.begin(), payload.size())) {}
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
this->reset();
this->set_header(message_type, gid, oid);
}
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
const std::vector<uint8_t> &payload) {
const std::span<const uint8_t> payload) {
this->set_message(message_type, gid, oid, payload);
}
NciMessage::NciMessage(const std::vector<uint8_t> &raw_packet) { this->nci_message_ = raw_packet; };
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
const std::initializer_list<uint8_t> payload)
: NciMessage(message_type, gid, oid, std::span<const uint8_t>(payload.begin(), payload.size())) {}
std::vector<uint8_t> NciMessage::encode() {
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
std::vector<uint8_t> message = this->nci_message_;
return message;
NciMessage::NciMessage(const std::span<const uint8_t> raw_packet) {
this->nci_message_.assign(raw_packet.begin(), raw_packet.end());
}
void NciMessage::reset() { this->nci_message_ = {0, 0, 0}; }
uint8_t NciMessage::get_message_type() const {
return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK;
std::span<const uint8_t> NciMessage::encode() {
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
return this->nci_message_;
}
uint8_t NciMessage::get_gid() const { return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK; }
void NciMessage::reset() {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = 0;
this->nci_message_[NCI_PKT_OID_OFFSET] = 0;
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = 0;
}
uint8_t NciMessage::get_oid() const { return this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK; }
uint8_t NciMessage::get_message_type() const { return this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_MT_MASK; }
uint8_t NciMessage::get_gid() const { return this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_GID_MASK; }
uint8_t NciMessage::get_oid() const { return this->nci_message_[NCI_PKT_OID_OFFSET] & NCI_PKT_OID_MASK; }
uint8_t NciMessage::get_payload_size(const bool recompute) {
if (!this->nci_message_.empty()) {
if (recompute) {
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
}
return this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
return this->nci_message_[NCI_PKT_LENGTH_OFFSET];
}
return 0;
}
uint8_t NciMessage::get_simple_status_response() const {
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
return this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
if (this->nci_message_.size() > NCI_PKT_PAYLOAD_OFFSET) {
return this->nci_message_[NCI_PKT_PAYLOAD_OFFSET];
}
return STATUS_FAILED;
}
@@ -63,104 +71,112 @@ uint8_t NciMessage::get_message_byte(const uint8_t offset) const {
return 0;
}
std::vector<uint8_t> &NciMessage::get_message() { return this->nci_message_; }
std::span<const uint8_t> NciMessage::get_payload() const {
if (this->nci_message_.size() <= NCI_PKT_HEADER_SIZE) {
return {};
}
return {this->nci_message_.data() + NCI_PKT_HEADER_SIZE, this->nci_message_.size() - NCI_PKT_HEADER_SIZE};
}
bool NciMessage::has_payload() const { return this->nci_message_.size() > nfc::NCI_PKT_HEADER_SIZE; }
bool NciMessage::has_payload() const { return this->nci_message_.size() > NCI_PKT_HEADER_SIZE; }
bool NciMessage::message_type_is(const uint8_t message_type) const {
if (!this->nci_message_.empty()) {
return message_type == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK);
return message_type == (this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_MT_MASK);
}
return false;
}
bool NciMessage::message_length_is(const uint8_t message_length, const bool recompute) {
if (this->nci_message_.size() > nfc::NCI_PKT_LENGTH_OFFSET) {
if (this->nci_message_.size() > NCI_PKT_LENGTH_OFFSET) {
if (recompute) {
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
}
return message_length == this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
return message_length == this->nci_message_[NCI_PKT_LENGTH_OFFSET];
}
return false;
}
bool NciMessage::gid_is(const uint8_t gid) const {
if (this->nci_message_.size() > nfc::NCI_PKT_MT_GID_OFFSET) {
return gid == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK);
if (this->nci_message_.size() > NCI_PKT_MT_GID_OFFSET) {
return gid == (this->nci_message_[NCI_PKT_MT_GID_OFFSET] & NCI_PKT_GID_MASK);
}
return false;
}
bool NciMessage::oid_is(const uint8_t oid) const {
if (this->nci_message_.size() > nfc::NCI_PKT_OID_OFFSET) {
return oid == (this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK);
if (this->nci_message_.size() > NCI_PKT_OID_OFFSET) {
return oid == (this->nci_message_[NCI_PKT_OID_OFFSET] & NCI_PKT_OID_MASK);
}
return false;
}
bool NciMessage::simple_status_response_is(const uint8_t response) const {
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
return response == this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
if (this->nci_message_.size() > NCI_PKT_PAYLOAD_OFFSET) {
return response == this->nci_message_[NCI_PKT_PAYLOAD_OFFSET];
}
return false;
}
void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
}
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] =
(message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK);
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = (message_type & NCI_PKT_MT_MASK) | (gid & NCI_PKT_GID_MASK);
this->nci_message_[NCI_PKT_OID_OFFSET] = oid & NCI_PKT_OID_MASK;
}
void NciMessage::set_message(const uint8_t message_type, const std::vector<uint8_t> &payload) {
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = message_type & nfc::NCI_PKT_MT_MASK;
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = 0;
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
void NciMessage::set_message(const uint8_t message_type, const std::span<const uint8_t> payload) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = message_type & NCI_PKT_MT_MASK;
this->nci_message_[NCI_PKT_OID_OFFSET] = 0;
this->append(payload);
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
}
void NciMessage::set_message(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
const std::vector<uint8_t> &payload) {
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] =
(message_type & nfc::NCI_PKT_MT_MASK) | (gid & nfc::NCI_PKT_GID_MASK);
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
const std::span<const uint8_t> payload) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
this->set_header(message_type, gid, oid);
this->append(payload);
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
}
void NciMessage::set_message_type(const uint8_t message_type) {
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
}
auto mt_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_MT_MASK;
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & nfc::NCI_PKT_MT_MASK);
auto mt_masked = this->nci_message_[NCI_PKT_MT_GID_OFFSET] & ~NCI_PKT_MT_MASK;
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & NCI_PKT_MT_MASK);
}
void NciMessage::set_gid(const uint8_t gid) {
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
}
auto gid_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_GID_MASK;
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & nfc::NCI_PKT_GID_MASK);
auto gid_masked = this->nci_message_[NCI_PKT_MT_GID_OFFSET] & ~NCI_PKT_GID_MASK;
this->nci_message_[NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & NCI_PKT_GID_MASK);
}
void NciMessage::set_oid(const uint8_t oid) {
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
if (this->nci_message_.size() < NCI_PKT_HEADER_SIZE) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
}
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
this->nci_message_[NCI_PKT_OID_OFFSET] = oid & NCI_PKT_OID_MASK;
}
void NciMessage::set_payload(const std::vector<uint8_t> &payload) {
std::vector<uint8_t> message(this->nci_message_.begin(), this->nci_message_.begin() + nfc::NCI_PKT_HEADER_SIZE);
message.insert(message.end(), payload.begin(), payload.end());
message[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
this->nci_message_ = message;
void NciMessage::set_payload(const std::span<const uint8_t> payload) {
this->nci_message_.resize(NCI_PKT_HEADER_SIZE);
this->append(payload);
this->nci_message_[NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - NCI_PKT_HEADER_SIZE;
}
void NciMessage::append(const std::span<const uint8_t> data) {
for (const uint8_t byte : data) {
this->nci_message_.push_back(byte);
}
}
void NciMessage::set_payload_size(const uint8_t size) { this->nci_message_.resize(NCI_PKT_HEADER_SIZE + size); }
} // namespace esphome::nfc
+36 -12
View File
@@ -3,19 +3,30 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <vector>
#include <initializer_list>
#include <span>
namespace esphome::nfc {
// An NCI packet is a three-byte header followed by up to 255 payload bytes
static constexpr size_t NCI_PKT_MAX_PAYLOAD_SIZE = 255;
static constexpr size_t NCI_PKT_MAX_SIZE = 3 + NCI_PKT_MAX_PAYLOAD_SIZE;
/// One NCI packet, held in a fixed buffer so building and reading messages never allocates
class NciMessage {
public:
NciMessage() {}
NciMessage(uint8_t message_type, const std::vector<uint8_t> &payload);
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid);
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
NciMessage(const std::vector<uint8_t> &raw_packet);
using Buffer = StaticVector<uint8_t, NCI_PKT_MAX_SIZE>;
std::vector<uint8_t> encode();
NciMessage() { this->reset(); }
NciMessage(uint8_t message_type, std::span<const uint8_t> payload);
NciMessage(uint8_t message_type, std::initializer_list<uint8_t> payload);
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid);
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, std::span<const uint8_t> payload);
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, std::initializer_list<uint8_t> payload);
explicit NciMessage(std::span<const uint8_t> raw_packet);
/// Stamps the payload length into the header and returns the packet ready to send
std::span<const uint8_t> encode();
void reset();
uint8_t get_message_type() const;
@@ -24,7 +35,10 @@ class NciMessage {
uint8_t get_payload_size(bool recompute = false);
uint8_t get_simple_status_response() const;
uint8_t get_message_byte(uint8_t offset) const;
std::vector<uint8_t> &get_message();
Buffer &get_message() { return this->nci_message_; }
const Buffer &get_message() const { return this->nci_message_; }
/// The payload bytes that follow the header
std::span<const uint8_t> get_payload() const;
bool has_payload() const;
bool message_type_is(uint8_t message_type) const;
@@ -34,15 +48,25 @@ class NciMessage {
bool simple_status_response_is(uint8_t response) const;
void set_header(uint8_t message_type, uint8_t gid, uint8_t oid);
void set_message(uint8_t message_type, const std::vector<uint8_t> &payload);
void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
void set_message(uint8_t message_type, std::span<const uint8_t> payload);
void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, std::span<const uint8_t> payload);
void set_message_type(uint8_t message_type);
void set_gid(uint8_t gid);
void set_oid(uint8_t oid);
void set_payload(const std::vector<uint8_t> &payload);
void set_payload(std::span<const uint8_t> payload);
void set_payload(std::initializer_list<uint8_t> payload) {
this->set_payload(std::span<const uint8_t>(payload.begin(), payload.size()));
}
/// Appends bytes to the payload; bytes that do not fit are dropped
void append(std::span<const uint8_t> data);
void append(std::initializer_list<uint8_t> data) {
this->append(std::span<const uint8_t>(data.begin(), data.size()));
}
/// Sets the packet size to the header plus `size` payload bytes, for a bus driver filling the buffer directly
void set_payload_size(uint8_t size);
protected:
std::vector<uint8_t> nci_message_{0, 0, 0}; // three bytes, MT/PBF/GID, OID, payload length/size
Buffer nci_message_; // MT/PBF/GID, OID, payload length, then the payload
};
} // namespace esphome::nfc
+6 -8
View File
@@ -12,17 +12,15 @@ uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
return nfc::STATUS_FAILED;
}
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
rx.reset();
if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) {
return nfc::STATUS_FAILED;
}
uint8_t length = rx.get_payload_size();
if (length > 0) {
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
return nfc::STATUS_FAILED;
}
const uint8_t length = rx.get_payload_size();
rx.set_payload_size(length);
if (length > 0 && !this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
return nfc::STATUS_FAILED;
}
// IRQ normally drops at the end of the read. If another message is queued it rises again at once, and the short
// low pulse may be missed; that means more data is waiting, not that this read failed (UM10936, 3.4).
@@ -33,7 +31,7 @@ uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
}
uint8_t PN7150I2C::write_nfcc(nfc::NciMessage &tx) {
auto encoded = tx.encode();
const auto encoded = tx.encode();
if (this->write(encoded.data(), encoded.size()) == i2c::ERROR_OK) {
return nfc::STATUS_OK;
}
+1 -2
View File
@@ -68,9 +68,8 @@ uint8_t PN7160::verify_reset(nfc::NciMessage &rx, const bool reset_config) {
uint8_t PN7160::process_init_response(nfc::NciMessage &rx) {
// the chip's version information is logged from CORE_RESET_NTF in verify_reset()
if (rx.get_message().size() >= 8) {
std::vector<uint8_t> features(rx.get_message().begin() + 4, rx.get_message().begin() + 8);
char feat_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
ESP_LOGD(TAG, "NFCC features: %s", nfc::format_bytes_to(feat_buf, features));
ESP_LOGD(TAG, "NFCC features: %s", nfc::format_bytes_to(feat_buf, rx.get_payload().subspan(1, 4)));
}
return rx.get_simple_status_response();
+6 -8
View File
@@ -12,17 +12,15 @@ uint8_t PN7160I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
return nfc::STATUS_FAILED;
}
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
rx.reset();
if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) {
return nfc::STATUS_FAILED;
}
uint8_t length = rx.get_payload_size();
if (length > 0) {
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
return nfc::STATUS_FAILED;
}
const uint8_t length = rx.get_payload_size();
rx.set_payload_size(length);
if (length > 0 && !this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
return nfc::STATUS_FAILED;
}
// IRQ normally drops at the end of the read. If another message is queued it rises again at once, and the short
// low pulse may be missed; that means more data is waiting, not that this read failed (UM11495, 6.2.4).
@@ -33,7 +31,7 @@ uint8_t PN7160I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
}
uint8_t PN7160I2C::write_nfcc(nfc::NciMessage &tx) {
auto encoded = tx.encode();
const auto encoded = tx.encode();
if (this->write(encoded.data(), encoded.size()) == i2c::ERROR_OK) {
return nfc::STATUS_OK;
}
+4 -4
View File
@@ -16,14 +16,14 @@ uint8_t PN7160Spi::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
return nfc::STATUS_FAILED;
}
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
rx.reset();
this->enable();
this->write_byte(TDD_SPI_READ); // send "transfer direction detector"
this->read_array(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE);
uint8_t length = rx.get_payload_size();
const uint8_t length = rx.get_payload_size();
rx.set_payload_size(length);
if (length > 0) {
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
this->read_array(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length);
}
this->disable();
@@ -36,7 +36,7 @@ uint8_t PN7160Spi::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
}
uint8_t PN7160Spi::write_nfcc(nfc::NciMessage &tx) {
auto encoded = tx.encode();
const auto encoded = tx.encode();
this->enable();
// send "transfer direction detector"; the NFCC answers 0xFF when it is ready to receive (UM11495, 6.3.3)
const uint8_t status = this->transfer_byte(TDD_SPI_WRITE);
+8
View File
@@ -117,6 +117,12 @@ _CALLBACK_AUTOMATIONS = (
)
_request_ontag_trigger_slot = cg.slot_counter("PN71XX_ON_TAG_TRIGGER_COUNT")
_request_ontagremoved_trigger_slot = cg.slot_counter(
"PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT"
)
async def setup_pn71xx(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
@@ -135,6 +141,7 @@ async def setup_pn71xx(var: MockObj, config: ConfigType) -> None:
for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
_request_ontag_trigger_slot(str(var))
cg.add(var.register_ontag_trigger(trigger))
await automation.build_automation(
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
@@ -142,6 +149,7 @@ async def setup_pn71xx(var: MockObj, config: ConfigType) -> None:
for conf in config.get(CONF_ON_TAG_REMOVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
_request_ontagremoved_trigger_slot(str(var))
cg.add(var.register_ontagremoved_trigger(trigger))
await automation.build_automation(
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
+120 -120
View File
@@ -56,7 +56,10 @@ void PN71xx::set_tag_emulation_message(const std::shared_ptr<nfc::NdefMessage> &
ESP_LOGE(TAG, "Tag emulation message too long: %zu > %u bytes", encoded.size(), CARD_EMU_T4T_MAX_NDEF_SIZE);
return;
}
this->card_emulation_ndef_ = std::move(encoded);
this->card_emulation_ndef_.init(encoded.size());
for (const uint8_t byte : encoded) {
this->card_emulation_ndef_.push_back(byte);
}
ESP_LOGD(TAG, "Tag emulation message set");
}
@@ -190,8 +193,9 @@ uint8_t PN71xx::set_test_mode(const TestMode test_mode, const std::vector<uint8_
this->nci_fsm_set_state_(NCIState::NFCC_RESET);
result.clear();
} else {
result = rx.get_message();
result.erase(result.begin(), result.begin() + 4); // remove NCI header
// the payload after the status byte, if the NFCC sent one
const auto payload = rx.get_payload();
result.assign(payload.begin() + std::min<size_t>(1, payload.size()), payload.end());
if (!result.empty()) {
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes_to(buf, result));
@@ -256,9 +260,7 @@ uint8_t PN71xx::send_init_config_() {
return nfc::STATUS_FAILED;
}
const auto pmu_config = this->pmu_config();
tx.set_message(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID,
std::vector<uint8_t>(pmu_config.begin(), pmu_config.end()));
tx.set_message(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, this->pmu_config());
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Error sending PMU config");
@@ -269,19 +271,16 @@ uint8_t PN71xx::send_init_config_() {
}
uint8_t PN71xx::send_core_config_() {
const auto *core_config_begin = std::begin(CORE_CONFIG_SOLO);
const auto *core_config_end = std::end(CORE_CONFIG_SOLO);
std::span<const uint8_t> core_config = CORE_CONFIG_SOLO;
this->core_config_is_solo_ = true;
if (this->listening_enabled_ && this->polling_enabled_) {
core_config_begin = std::begin(CORE_CONFIG_RW_CE);
core_config_end = std::end(CORE_CONFIG_RW_CE);
core_config = CORE_CONFIG_RW_CE;
this->core_config_is_solo_ = false;
}
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID,
std::vector<uint8_t>(core_config_begin, core_config_end));
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, core_config);
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
ESP_LOGW(TAG, "Error sending core config");
@@ -313,11 +312,10 @@ uint8_t PN71xx::refresh_core_config_() {
}
uint8_t PN71xx::set_discover_map_() {
std::vector<uint8_t> discover_map = {sizeof(RF_DISCOVER_MAP_CONFIG) / 3};
discover_map.insert(discover_map.end(), std::begin(RF_DISCOVER_MAP_CONFIG), std::end(RF_DISCOVER_MAP_CONFIG));
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_MAP_OID, discover_map);
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_MAP_OID,
{sizeof(RF_DISCOVER_MAP_CONFIG) / 3});
tx.append(RF_DISCOVER_MAP_CONFIG);
if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Error sending discover map poll config");
@@ -327,10 +325,9 @@ uint8_t PN71xx::set_discover_map_() {
}
uint8_t PN71xx::set_listen_mode_routing_() {
const auto routing_config = this->listen_mode_routing_config();
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_SET_LISTEN_MODE_ROUTING_OID,
std::vector<uint8_t>(routing_config.begin(), routing_config.end()));
this->listen_mode_routing_config());
if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Error setting listen mode routing config");
@@ -340,27 +337,20 @@ uint8_t PN71xx::set_listen_mode_routing_() {
}
uint8_t PN71xx::start_discovery_() {
const uint8_t *rf_discovery_config = RF_DISCOVERY_CONFIG;
uint8_t length = sizeof(RF_DISCOVERY_CONFIG);
std::span<const uint8_t> rf_discovery_config = RF_DISCOVERY_CONFIG;
if (!this->listening_enabled_) {
length = sizeof(RF_DISCOVERY_POLL_CONFIG);
rf_discovery_config = RF_DISCOVERY_POLL_CONFIG;
} else if (!this->polling_enabled_) {
length = sizeof(RF_DISCOVERY_LISTEN_CONFIG);
rf_discovery_config = RF_DISCOVERY_LISTEN_CONFIG;
}
std::vector<uint8_t> discover_config = std::vector<uint8_t>((length * 2) + 1);
discover_config[0] = length;
for (uint8_t i = 0; i < length; i++) {
discover_config[(i * 2) + 1] = rf_discovery_config[i];
discover_config[(i * 2) + 2] = 0x01; // RF Technology and Mode will be executed in every discovery period
}
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID, discover_config);
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID,
{static_cast<uint8_t>(rf_discovery_config.size())});
for (const uint8_t mode_tech : rf_discovery_config) {
tx.append({mode_tech, 0x01}); // RF Technology and Mode will be executed in every discovery period
}
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
switch (rx.get_simple_status_response()) {
@@ -416,10 +406,9 @@ void PN71xx::select_endpoint_() {
} else if (endpoint.protocol == nfc::PROT_MIFARE) {
interface = nfc::INTF_TAGCMD;
}
std::vector<uint8_t> endpoint_data = {endpoint.id, endpoint.protocol, interface};
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_SELECT_OID, endpoint_data);
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_SELECT_OID,
{endpoint.id, endpoint.protocol, interface});
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Error selecting endpoint");
@@ -491,31 +480,57 @@ uint8_t PN71xx::write_endpoint_(const uint8_t protocol, nfc::NfcTagUid &uid,
return nfc::STATUS_FAILED;
}
std::unique_ptr<nfc::NfcTag> PN71xx::build_tag_(const uint8_t mode_tech, const uint8_t protocol,
const std::vector<uint8_t> &data) {
switch (mode_tech) {
case (nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA): {
// RF technology parameters: SENS_RES (2 bytes), NFCID1 length, NFCID1, ...
if (data.size() < 3) {
ESP_LOGE(TAG, "NFC-A parameters too short");
return nullptr;
}
uint8_t uid_length = data[2];
if (uid_length == 0 || uid_length > nfc::NFC_UID_MAX_LENGTH || data.size() < 3u + uid_length) {
ESP_LOGE(TAG, "Invalid UID length: %u", uid_length);
return nullptr;
}
nfc::NfcTagUid uid(data.begin() + 3, data.begin() + 3 + uid_length);
if (protocol == nfc::PROT_MIFARE) {
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
if (protocol == nfc::PROT_T2T) {
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
}
return make_unique<nfc::NfcTag>(uid);
}
bool PN71xx::parse_uid_(const uint8_t mode_tech, const std::span<const uint8_t> rf_tech_params, nfc::NfcTagUid &uid) {
if (mode_tech != (nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA)) {
return false;
}
return nullptr;
// RF technology parameters: SENS_RES (2 bytes), NFCID1 length, NFCID1, ...
if (rf_tech_params.size() < 3) {
ESP_LOGE(TAG, "NFC-A parameters too short");
return false;
}
const uint8_t uid_length = rf_tech_params[2];
if (uid_length == 0 || uid_length > nfc::NFC_UID_MAX_LENGTH || rf_tech_params.size() < 3u + uid_length) {
ESP_LOGE(TAG, "Invalid UID length: %u", uid_length);
return false;
}
uid.assign(rf_tech_params.begin() + 3, rf_tech_params.begin() + 3 + uid_length);
return true;
}
std::unique_ptr<nfc::NfcTag> PN71xx::build_tag_(const uint8_t protocol, const nfc::NfcTagUid &uid) {
if (protocol == nfc::PROT_MIFARE) {
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
if (protocol == nfc::PROT_T2T) {
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
}
return make_unique<nfc::NfcTag>(uid);
}
size_t PN71xx::find_or_add_tag_(const uint8_t protocol, const nfc::NfcTagUid &uid) {
const auto tag_loc = this->find_tag_uid_(uid);
if (tag_loc.has_value()) {
ESP_LOGVV(TAG, "Tag cache updated");
return tag_loc.value();
}
if (this->discovered_endpoint_.size() >= MAX_DISCOVERED_ENDPOINTS) {
size_t oldest = 0;
for (size_t i = 1; i < this->discovered_endpoint_.size(); i++) {
if (this->discovered_endpoint_[i].last_seen < this->discovered_endpoint_[oldest].last_seen) {
oldest = i;
}
}
ESP_LOGW(TAG, "Tag cache full; dropping the tag seen longest ago");
this->erase_tag_(oldest);
}
this->discovered_endpoint_.emplace_next() = DiscoveredEndpoint{.last_seen = App.get_loop_component_start_time(),
.tag = this->build_tag_(protocol, uid),
.id = 0,
.protocol = protocol,
.trig_called = false};
ESP_LOGVV(TAG, "Tag added to cache");
return this->discovered_endpoint_.size() - 1;
}
optional<size_t> PN71xx::find_tag_uid_(const nfc::NfcTagUid &uid) {
@@ -549,15 +564,23 @@ void PN71xx::purge_old_tags_() {
void PN71xx::erase_tag_(const uint8_t tag_index) {
if (tag_index < this->discovered_endpoint_.size()) {
#ifdef PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT
for (auto *trigger : this->triggers_ontagremoved_) {
trigger->process(this->discovered_endpoint_[tag_index].tag);
}
#endif
for (auto *listener : this->tag_listeners_) {
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
}
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid()));
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
// keep the remaining entries in order; selecting_endpoint_ indexes into this list
for (size_t i = tag_index; i + 1 < this->discovered_endpoint_.size(); i++) {
this->discovered_endpoint_[i] = std::move(this->discovered_endpoint_[i + 1]);
}
// StaticVector::resize() does not destroy the dropped slot; free its tag now, not when the slot is reused
this->discovered_endpoint_[this->discovered_endpoint_.size() - 1].tag.reset();
this->discovered_endpoint_.resize(this->discovered_endpoint_.size() - 1);
}
}
@@ -815,31 +838,16 @@ void PN71xx::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
this->nci_fsm_set_state_(NCIState::EP_DEACTIVATING);
return;
}
auto incoming_tag =
this->build_tag_(mode_tech, protocol,
std::vector<uint8_t>(rx.get_message().begin() + nfc::RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS,
rx.get_message().end()));
if (incoming_tag == nullptr) {
nfc::NfcTagUid uid;
if (!this->parse_uid_(mode_tech,
std::span<const uint8_t>(rx.get_message()).subspan(nfc::RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS),
uid)) {
ESP_LOGE(TAG, "Could not build tag");
} else {
auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid());
if (tag_loc.has_value()) {
this->discovered_endpoint_[tag_loc.value()].id = discovery_id;
this->discovered_endpoint_[tag_loc.value()].protocol = protocol;
this->discovered_endpoint_[tag_loc.value()].last_seen = App.get_loop_component_start_time();
ESP_LOGVV(TAG, "Tag cache updated");
} else {
this->discovered_endpoint_.emplace_back(DiscoveredEndpoint{.last_seen = App.get_loop_component_start_time(),
.tag = std::move(incoming_tag),
.id = discovery_id,
.protocol = protocol,
.trig_called = false});
tag_loc = this->discovered_endpoint_.size() - 1;
ESP_LOGVV(TAG, "Tag added to cache");
}
auto &working_endpoint = this->discovered_endpoint_[tag_loc.value()];
auto &working_endpoint = this->discovered_endpoint_[this->find_or_add_tag_(protocol, uid)];
working_endpoint.id = discovery_id;
working_endpoint.protocol = protocol;
working_endpoint.last_seen = App.get_loop_component_start_time();
switch (this->next_task_) {
case EP_CLEAN:
@@ -886,8 +894,8 @@ void PN71xx::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
if (this->read_endpoint_data_(working_endpoint.protocol, *working_endpoint.tag) != nfc::STATUS_OK) {
ESP_LOGW(TAG, " Unable to read NDEF record(s)");
} else if (working_endpoint.tag->has_ndef_message()) {
const auto message = working_endpoint.tag->get_ndef_message();
const auto records = message->get_records();
const auto &message = working_endpoint.tag->get_ndef_message();
const auto &records = message->get_records();
ESP_LOGD(TAG, " NDEF record(s):");
for (const auto &record : records) {
ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str());
@@ -895,9 +903,11 @@ void PN71xx::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
} else {
ESP_LOGW(TAG, " No NDEF records found");
}
#ifdef PN71XX_ON_TAG_TRIGGER_COUNT
for (auto *trigger : this->triggers_ontag_) {
trigger->process(working_endpoint.tag);
}
#endif
for (auto *listener : this->tag_listeners_) {
listener->tag_on(*working_endpoint.tag);
}
@@ -924,31 +934,20 @@ void PN71xx::process_rf_discover_oid_(nfc::NciMessage &rx) {
ESP_LOGE(TAG, "RF_DISCOVER_NTF too short");
return;
}
auto incoming_tag = this->build_tag_(
rx.get_message_byte(nfc::RF_DISCOVER_NTF_MODE_TECH), rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL),
std::vector<uint8_t>(rx.get_message().begin() + nfc::RF_DISCOVER_NTF_RF_TECH_PARAMS, rx.get_message().end()));
if (incoming_tag == nullptr) {
const uint8_t protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL);
nfc::NfcTagUid uid;
if (!this->parse_uid_(rx.get_message_byte(nfc::RF_DISCOVER_NTF_MODE_TECH),
std::span<const uint8_t>(rx.get_message()).subspan(nfc::RF_DISCOVER_NTF_RF_TECH_PARAMS), uid)) {
ESP_LOGE(TAG, "Could not build tag!");
} else {
auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid());
if (tag_loc.has_value()) {
this->discovered_endpoint_[tag_loc.value()].id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID);
this->discovered_endpoint_[tag_loc.value()].protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL);
this->discovered_endpoint_[tag_loc.value()].last_seen = App.get_loop_component_start_time();
ESP_LOGVV(TAG, "Tag found & updated");
} else {
this->discovered_endpoint_.emplace_back(
DiscoveredEndpoint{.last_seen = App.get_loop_component_start_time(),
.tag = std::move(incoming_tag),
.id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID),
.protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL),
.trig_called = false});
ESP_LOGVV(TAG, "Tag saved");
}
auto &endpoint = this->discovered_endpoint_[this->find_or_add_tag_(protocol, uid)];
endpoint.id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID);
endpoint.protocol = protocol;
endpoint.last_seen = App.get_loop_component_start_time();
}
if (rx.get_message().back() != nfc::RF_DISCOVER_NTF_NT_MORE) {
const auto &ntf = rx.get_message();
if (ntf[ntf.size() - 1] != nfc::RF_DISCOVER_NTF_NT_MORE) {
this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT);
ESP_LOGVV(TAG, "Discovered %zu endpoints", this->discovered_endpoint_.size());
}
@@ -986,17 +985,12 @@ void PN71xx::process_data_message_(nfc::NciMessage &rx) {
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message()));
std::vector<uint8_t> ndef_response;
CardEmuResponse ndef_response;
this->card_emu_t4t_get_response_(rx.get_message(), ndef_response);
if (ndef_response.empty()) {
return; // no message returned, we cannot respond
}
// the CC limits MLe so every response fits in a single data packet (payload length is one byte)
if (ndef_response.size() > UINT8_MAX) {
ESP_LOGE(TAG, "Card emulation response too long: %zu bytes", ndef_response.size());
return;
}
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, ndef_response);
ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes_to(buf, tx.get_message()));
@@ -1005,12 +999,14 @@ void PN71xx::process_data_message_(nfc::NciMessage &rx) {
}
}
bool PN71xx::card_emu_t4t_read_ndef_(const uint16_t offset, const uint8_t length, std::vector<uint8_t> &ndef_response) {
bool PN71xx::card_emu_t4t_read_ndef_(const uint16_t offset, const uint8_t length, CardEmuResponse &ndef_response) {
const auto &ndef_message = this->card_emulation_ndef_;
// the NDEF file is a two-byte big-endian length (NLEN) followed by the message
const uint16_t ndef_msg_size = ndef_message.size();
const uint32_t file_size = ndef_msg_size + 2;
if (offset + static_cast<uint32_t>(length) > file_size) {
// the reply must also hold the two status bytes; the CC's MLe keeps well-behaved readers below this
if (offset + static_cast<uint32_t>(length) > file_size ||
length + sizeof(CARD_EMU_T4T_OK) > CardEmuResponse::capacity()) {
return false;
}
for (uint32_t i = offset; i < offset + static_cast<uint32_t>(length); i++) {
@@ -1029,7 +1025,7 @@ bool PN71xx::card_emu_t4t_read_ndef_(const uint16_t offset, const uint8_t length
return true;
}
void PN71xx::card_emu_t4t_get_response_(const std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response) {
void PN71xx::card_emu_t4t_get_response_(const std::span<const uint8_t> response, CardEmuResponse &ndef_response) {
ndef_response.clear();
if (this->card_emulation_ndef_.empty()) {
ESP_LOGE(TAG, "No NDEF message is set; tag emulation not possible");
@@ -1047,6 +1043,11 @@ void PN71xx::card_emu_t4t_get_response_(const std::vector<uint8_t> &response, st
auto apdu_starts_with = [&](const uint8_t *cmd, size_t cmd_size) {
return apdu_size >= cmd_size && std::equal(cmd, cmd + cmd_size, apdu_begin);
};
auto append = [&](std::span<const uint8_t> bytes) {
for (const uint8_t byte : bytes) {
ndef_response.push_back(byte);
}
};
bool ok = false;
if (apdu_is(CARD_EMU_T4T_APP_SELECT, sizeof(CARD_EMU_T4T_APP_SELECT)) ||
@@ -1071,8 +1072,7 @@ void PN71xx::card_emu_t4t_get_response_(const std::vector<uint8_t> &response, st
if (this->ce_state_ == CardEmulationState::CARD_EMU_CC_SELECTED) {
ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED");
if (offset + static_cast<uint32_t>(length) <= sizeof(CARD_EMU_T4T_CC)) {
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_CC) + offset,
std::begin(CARD_EMU_T4T_CC) + offset + length);
append(std::span<const uint8_t>(CARD_EMU_T4T_CC).subspan(offset, length));
ok = true;
}
} else if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) {
@@ -1084,18 +1084,18 @@ void PN71xx::card_emu_t4t_get_response_(const std::vector<uint8_t> &response, st
const uint8_t length = apdu_begin[4];
if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED && apdu_size >= 5u + length) {
ESP_LOGVV(TAG, "CARD_EMU_T4T_WRITE");
std::vector<uint8_t> ndef_msg_written(apdu_begin + 5, apdu_begin + 5 + length);
char write_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(write_buf, ndef_msg_written));
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length,
nfc::format_bytes_to(write_buf, response.subspan(nfc::NCI_PKT_HEADER_SIZE + 5, length)));
ok = true;
}
}
if (ok) {
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
append(CARD_EMU_T4T_OK);
} else {
ndef_response.clear();
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_NOK), std::end(CARD_EMU_T4T_NOK));
append(CARD_EMU_T4T_NOK);
this->ce_state_ = CardEmulationState::CARD_EMU_IDLE;
}
}
+38 -14
View File
@@ -9,6 +9,7 @@
#include "esphome/core/gpio.h"
#include "esphome/core/helpers.h"
#include <array>
#include <functional>
#include <span>
@@ -68,7 +69,8 @@ static constexpr uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter f
0xF8, // TOTAL_DURATION (low)...
0x02}; // TOTAL_DURATION (high): 760 ms
static constexpr uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes
static constexpr uint8_t RF_DISCOVER_MAP_CONFIG[] = {
// poll modes
nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL,
nfc::INTF_FRAME, // poll mode
nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL,
@@ -131,6 +133,16 @@ enum class TestMode : uint8_t {
TEST_GET_REGISTER,
};
/// A card emulation reply; the CC limits reads so every reply fits one NCI data packet
using CardEmuResponse = StaticVector<uint8_t, nfc::NCI_PKT_MAX_PAYLOAD_SIZE>;
/// Holds pages 3 to 6 (16 bytes) plus an NDEF message of up to 255 bytes and its TLV header, rounded up to whole reads
using UltralightReadBuffer = StaticVector<uint8_t, 272>;
/// Longest NDEF message accepted from a MIFARE Classic tag (the capacity of a 4K tag)
static constexpr uint32_t MIFARE_CLASSIC_MAX_NDEF_SIZE = 3440;
/// Tags tracked at once. A device with a random UID looks like a new tag on every activation, but each entry
/// expires after tag_ttl, so a handful is enough; when the cache is full the entry seen longest ago is evicted.
static constexpr size_t MAX_DISCOVERED_ENDPOINTS = 8;
struct DiscoveredEndpoint {
uint32_t last_seen;
std::unique_ptr<nfc::NfcTag> tag;
@@ -162,8 +174,12 @@ class PN71xx : public nfc::Nfcc, public Component {
void set_polling_on();
bool polling_enabled() { return this->polling_enabled_; }
#ifdef PN71XX_ON_TAG_TRIGGER_COUNT
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
#endif
#ifdef PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
#endif
template<typename F> void add_on_emulated_tag_scan_callback(F &&callback) {
this->on_emulated_tag_scan_callback_.add(std::forward<F>(callback));
@@ -219,7 +235,11 @@ class PN71xx : public nfc::Nfcc, public Component {
uint8_t format_endpoint_(uint8_t protocol);
uint8_t write_endpoint_(uint8_t protocol, nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, uint8_t protocol, const std::vector<uint8_t> &data);
/// Reads the UID from the RF technology parameters of a discovery or activation notification
bool parse_uid_(uint8_t mode_tech, std::span<const uint8_t> rf_tech_params, nfc::NfcTagUid &uid);
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t protocol, const nfc::NfcTagUid &uid);
/// Finds a cached endpoint by UID, or caches a new one, evicting the entry seen longest ago if the cache is full
size_t find_or_add_tag_(uint8_t protocol, const nfc::NfcTagUid &uid);
optional<size_t> find_tag_uid_(const nfc::NfcTagUid &uid);
void purge_old_tags_();
void erase_tag_(uint8_t tag_index);
@@ -237,8 +257,8 @@ class PN71xx : public nfc::Nfcc, public Component {
void process_rf_deactivate_oid_(nfc::NciMessage &rx);
void process_data_message_(nfc::NciMessage &rx);
void card_emu_t4t_get_response_(const std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response);
bool card_emu_t4t_read_ndef_(uint16_t offset, uint8_t length, std::vector<uint8_t> &ndef_response);
void card_emu_t4t_get_response_(std::span<const uint8_t> response, CardEmuResponse &ndef_response);
bool card_emu_t4t_read_ndef_(uint16_t offset, uint8_t length, CardEmuResponse &ndef_response);
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
bool expect_notification = true);
@@ -248,7 +268,7 @@ class PN71xx : public nfc::Nfcc, public Component {
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
uint8_t read_mifare_classic_block_(uint8_t block_num, std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> &data);
uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len);
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
uint8_t sect_to_auth_(uint8_t block_num);
@@ -258,10 +278,10 @@ class PN71xx : public nfc::Nfcc, public Component {
uint8_t halt_mifare_classic_tag_();
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data);
bool is_mifare_ultralight_formatted_(std::span<const uint8_t> page_3_to_6);
uint16_t read_mifare_ultralight_capacity_();
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
uint8_t find_mifare_ultralight_ndef_(std::span<const uint8_t> page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index);
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
@@ -275,13 +295,17 @@ class PN71xx : public nfc::Nfcc, public Component {
};
// members are ordered by alignment, widest first, to minimize padding
CallbackManager<void()> on_emulated_tag_scan_callback_;
CallbackManager<void()> on_finished_write_callback_;
LazyCallbackManager<void()> on_emulated_tag_scan_callback_;
LazyCallbackManager<void()> on_finished_write_callback_;
std::vector<DiscoveredEndpoint> discovered_endpoint_;
std::vector<uint8_t> card_emulation_ndef_; // encoded emulation message; empty when none is set
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
StaticVector<DiscoveredEndpoint, MAX_DISCOVERED_ENDPOINTS> discovered_endpoint_;
FixedVector<uint8_t> card_emulation_ndef_; // encoded emulation message; empty when none is set
#ifdef PN71XX_ON_TAG_TRIGGER_COUNT
StaticVector<nfc::NfcOnTagTrigger *, PN71XX_ON_TAG_TRIGGER_COUNT> triggers_ontag_;
#endif
#ifdef PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT
StaticVector<nfc::NfcOnTagTrigger *, PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT> triggers_ontagremoved_;
#endif
std::shared_ptr<nfc::NdefMessage> next_task_message_to_write_;
GPIOPin *irq_pin_{nullptr};
@@ -1,4 +1,6 @@
#include <algorithm>
#include <array>
#include <cinttypes>
#include <memory>
#include "pn71xx.h"
@@ -17,56 +19,59 @@ uint8_t PN71xx::read_mifare_classic_tag_(nfc::NfcTag &tag) {
ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data");
return nfc::STATUS_FAILED;
}
std::vector<uint8_t> data;
std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> block_data;
if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) {
if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
if (this->read_mifare_classic_block_(current_block, block_data) == nfc::STATUS_OK) {
if (!nfc::decode_mifare_classic_tlv(block_data, message_length, message_start_index)) {
return nfc::STATUS_FAILED;
}
} else {
ESP_LOGE(TAG, "Failed to read block %u", current_block);
return nfc::STATUS_FAILED;
}
if (message_length > MIFARE_CLASSIC_MAX_NDEF_SIZE) {
ESP_LOGE(TAG, "NDEF message too long: %" PRIu32 " bytes", message_length);
return nfc::STATUS_FAILED;
}
uint32_t index = 0;
uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
std::vector<uint8_t> buffer;
const uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
FixedVector<uint8_t> buffer;
if (!buffer.try_init(buffer_size)) {
ESP_LOGE(TAG, "Out of memory reading NDEF message of %" PRIu32 " bytes", buffer_size);
return nfc::STATUS_FAILED;
}
while (index < buffer_size) {
while (buffer.size() < buffer_size) {
if (nfc::mifare_classic_is_first_block(current_block)) {
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Block authentication failed for %u", current_block);
return nfc::STATUS_FAILED;
}
}
std::vector<uint8_t> block_data;
if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Error reading block %u", current_block);
return nfc::STATUS_FAILED;
} else {
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
}
for (const uint8_t byte : block_data) {
buffer.push_back(byte);
}
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
current_block++;
if (nfc::mifare_classic_is_trailer_block(current_block)) {
current_block++;
}
}
if (buffer.begin() + message_start_index < buffer.end()) {
buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
} else {
if (message_start_index >= buffer.size()) {
return nfc::STATUS_FAILED;
}
tag.set_ndef_message(make_unique<nfc::NdefMessage>(buffer));
tag.set_ndef_message(make_unique<nfc::NdefMessage>(std::span<const uint8_t>(buffer).subspan(message_start_index)));
return nfc::STATUS_OK;
}
uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num,
std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> &data) {
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
@@ -84,32 +89,35 @@ uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_
return nfc::STATUS_FAILED;
}
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
// payload: XCHG_DATA status byte, 16 block bytes, one trailing status byte
std::copy_n(rx.get_message().begin() + 4, data.size(), data.begin());
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes_to(buf, data));
return nfc::STATUS_OK;
}
uint8_t PN71xx::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num});
uint8_t key_select = key_num;
switch (key_num) {
case nfc::MIFARE_CMD_AUTH_A:
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A;
key_select = MFC_AUTHENTICATE_PARAM_KS_A;
break;
case nfc::MIFARE_CMD_AUTH_B:
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B;
key_select = MFC_AUTHENTICATE_PARAM_KS_B;
break;
default:
break;
}
if (key != nullptr) {
tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
tx.get_message().insert(tx.get_message().end(), key, key + 6);
key_select |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
}
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_select});
if (key != nullptr) {
tx.append(std::span<const uint8_t>(key, 6));
}
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
@@ -249,7 +257,7 @@ uint8_t PN71xx::write_mifare_classic_block_(uint8_t block_num, const uint8_t *da
}
// write command part two
tx.set_payload({XCHG_DATA_OID});
tx.get_message().insert(tx.get_message().end(), data, data + len);
tx.append(std::span<const uint8_t>(data, len));
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
@@ -268,22 +276,10 @@ uint8_t PN71xx::write_mifare_classic_block_(uint8_t block_num, const uint8_t *da
}
uint8_t PN71xx::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message) {
auto encoded = message->encode();
uint32_t message_length = encoded.size();
uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
encoded.insert(encoded.begin(), 0x03);
if (message_length < 255) {
encoded.insert(encoded.begin() + 1, message_length);
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
encoded.resize(buffer_length, 0);
const auto encoded = message->encode();
const uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(encoded.size());
FixedVector<uint8_t> buffer;
nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
uint32_t index = 0;
uint8_t current_block = 4;
@@ -295,7 +291,7 @@ uint8_t PN71xx::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage
}
}
if (this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE) !=
if (this->write_mifare_classic_block_(current_block, &buffer[index], nfc::MIFARE_CLASSIC_BLOCK_SIZE) !=
nfc::STATUS_OK) {
return nfc::STATUS_FAILED;
}
@@ -1,3 +1,4 @@
#include <algorithm>
#include <array>
#include <cinttypes>
#include <memory>
@@ -10,7 +11,7 @@ namespace esphome::pn71xx {
static const char *const TAG = "pn71xx.mifare_ultralight";
uint8_t PN71xx::read_mifare_ultralight_tag_(nfc::NfcTag &tag) {
std::vector<uint8_t> data;
UltralightReadBuffer data;
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
data) != nfc::STATUS_OK) {
@@ -42,21 +43,24 @@ uint8_t PN71xx::read_mifare_ultralight_tag_(nfc::NfcTag &tag) {
return nfc::STATUS_FAILED;
}
}
// we need to trim off page 3 as well as any bytes ahead of message_start_index
data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
tag.set_ndef_message(make_unique<nfc::NdefMessage>(data));
// skip page 3 as well as any bytes ahead of message_start_index
const size_t skip = message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
if (skip >= data.size()) {
return nfc::STATUS_FAILED;
}
tag.set_ndef_message(make_unique<nfc::NdefMessage>(std::span<const uint8_t>(data).subspan(skip)));
return nfc::STATUS_OK;
}
uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data) {
const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page});
for (size_t i = 0; i * read_increment < num_bytes; i++) {
tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
const uint8_t page = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
tx.set_payload({nfc::MIFARE_CMD_READ, page});
// a short answer (e.g. a NAK for a page beyond the end of the tag) is retried a limited number of times
uint8_t attempts = 0;
do {
@@ -65,16 +69,15 @@ uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b
return nfc::STATUS_FAILED;
}
if (++attempts > NFCC_MAX_COMM_FAILS && rx.get_payload_size() < read_increment) {
ESP_LOGE(TAG, "Short read from page %u", tx.get_message().back());
ESP_LOGE(TAG, "Short read from page %u", page);
return nfc::STATUS_FAILED;
}
} while (rx.get_payload_size() < read_increment);
uint16_t bytes_offset = (i + 1) * read_increment;
auto pages_in_end_itr = bytes_offset <= num_bytes ? rx.get_message().end() - 1
: rx.get_message().end() - (bytes_offset - num_bytes + 1);
if ((pages_in_end_itr > rx.get_message().begin()) && (pages_in_end_itr < rx.get_message().end())) {
data.insert(data.end(), rx.get_message().begin() + nfc::NCI_PKT_HEADER_SIZE, pages_in_end_itr);
// the payload ends with a status byte; keep only the bytes still wanted from this read
const uint16_t wanted = num_bytes - i * read_increment;
const size_t count = std::min<size_t>(read_increment, wanted);
for (const uint8_t byte : rx.get_payload().subspan(0, count)) {
data.push_back(byte);
}
}
@@ -84,7 +87,7 @@ uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b
return nfc::STATUS_OK;
}
bool PN71xx::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
bool PN71xx::is_mifare_ultralight_formatted_(const std::span<const uint8_t> page_3_to_6) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
return (page_3_to_6.size() > p4_offset + 3) &&
@@ -93,7 +96,7 @@ bool PN71xx::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_
}
uint16_t PN71xx::read_mifare_ultralight_capacity_() {
std::vector<uint8_t> data;
UltralightReadBuffer data;
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) {
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
return data[2] * 8U;
@@ -101,7 +104,7 @@ uint16_t PN71xx::read_mifare_ultralight_capacity_() {
return 0;
}
uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::span<const uint8_t> page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
@@ -124,33 +127,22 @@ uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
uint8_t PN71xx::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
uint32_t capacity = this->read_mifare_ultralight_capacity_();
auto encoded = message->encode();
uint32_t message_length = encoded.size();
uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
const auto encoded = message->encode();
const uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(encoded.size());
if (buffer_length > capacity) {
ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
return nfc::STATUS_FAILED;
}
encoded.insert(encoded.begin(), 0x03);
if (message_length < 255) {
encoded.insert(encoded.begin() + 1, message_length);
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
encoded.resize(buffer_length, 0);
FixedVector<uint8_t> buffer;
nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
uint32_t index = 0;
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
while (index < buffer_length) {
if (this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) !=
if (this->write_mifare_ultralight_page_(current_page, &buffer[index], nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) !=
nfc::STATUS_OK) {
return nfc::STATUS_FAILED;
}
@@ -175,11 +167,9 @@ uint8_t PN71xx::clean_mifare_ultralight_() {
}
uint8_t PN71xx::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
payload.insert(payload.end(), write_data, write_data + len);
nfc::NciMessage rx;
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num});
tx.append(std::span<const uint8_t>(write_data, len));
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
ESP_LOGE(TAG, "Error writing page %u", page_num);
+2
View File
@@ -144,6 +144,8 @@
#define PN532_BINARY_SENSOR_COUNT 1
#define PN532_ON_TAG_REMOVED_TRIGGER_COUNT 1
#define PN532_ON_TAG_TRIGGER_COUNT 1
#define PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT 1
#define PN71XX_ON_TAG_TRIGGER_COUNT 1
#define REMOTE_BASE_DUMPER_COUNT 1
#define REMOTE_BASE_LISTENER_COUNT 1
#define USE_REMOTE_PROTOCOL_ABBWELCOME
+68 -5
View File
@@ -12,6 +12,9 @@ namespace {
class FakePN71xx : public PN71xx {
public:
using PN71xx::card_emu_t4t_get_response_;
using PN71xx::discovered_endpoint_;
using PN71xx::erase_tag_;
using PN71xx::find_or_add_tag_;
using PN71xx::transceive_;
std::deque<std::vector<uint8_t>> to_read;
@@ -36,7 +39,8 @@ class FakePN71xx : public PN71xx {
this->write_failures--;
return nfc::STATUS_FAILED;
}
this->written.push_back(tx.encode());
const auto encoded = tx.encode();
this->written.emplace_back(encoded.begin(), encoded.end());
return nfc::STATUS_OK;
}
};
@@ -48,9 +52,9 @@ std::vector<uint8_t> apdu(std::initializer_list<uint8_t> bytes) {
}
std::vector<uint8_t> respond(FakePN71xx &nfcc, std::initializer_list<uint8_t> bytes) {
std::vector<uint8_t> response;
CardEmuResponse response;
nfcc.card_emu_t4t_get_response_(apdu(bytes), response);
return response;
return {response.begin(), response.end()};
}
void select_ndef_file(FakePN71xx &nfcc) {
@@ -58,6 +62,10 @@ void select_ndef_file(FakePN71xx &nfcc) {
respond(nfcc, {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04});
}
std::vector<uint8_t> bytes_of(const nfc::NciMessage &msg) {
return {msg.get_message().begin(), msg.get_message().end()};
}
const std::vector<uint8_t> SW_OK = {0x90, 0x00};
const std::vector<uint8_t> SW_NOT_FOUND = {0x6A, 0x82};
@@ -80,7 +88,7 @@ TEST(PN71xxTransceive, SkipsNotificationAheadOfResponse) {
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {0x00});
nfc::NciMessage rx;
EXPECT_EQ(nfcc.transceive_(tx, rx), nfc::STATUS_OK);
EXPECT_EQ(rx.get_message(), (std::vector<uint8_t>{0x41, 0x06, 0x01, 0x00}));
EXPECT_EQ(bytes_of(rx), (std::vector<uint8_t>{0x41, 0x06, 0x01, 0x00}));
EXPECT_EQ(nfcc.written.size(), 1u);
}
@@ -100,10 +108,65 @@ TEST(PN71xxTransceive, SkipsStaleResponseFromEarlierCommand) {
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID, {0x00});
nfc::NciMessage rx;
EXPECT_EQ(nfcc.transceive_(tx, rx), nfc::STATUS_OK);
EXPECT_EQ(rx.get_message(), (std::vector<uint8_t>{0x41, 0x03, 0x01, 0x00}));
EXPECT_EQ(bytes_of(rx), (std::vector<uint8_t>{0x41, 0x03, 0x01, 0x00}));
EXPECT_EQ(nfcc.written.size(), 1u);
}
nfc::NfcTagUid uid_of(uint8_t last) { return {0x04, 0x00, 0x00, last}; }
// Erasing an entry keeps the others in order and frees the tag of the slot that is dropped.
TEST(PN71xxTagCache, EraseKeepsOrderAndFreesTail) {
FakePN71xx nfcc;
for (uint8_t i = 0; i < 3; i++) {
nfcc.find_or_add_tag_(nfc::PROT_T2T, uid_of(i));
}
nfcc.erase_tag_(1);
ASSERT_EQ(nfcc.discovered_endpoint_.size(), 2u);
EXPECT_EQ(nfcc.discovered_endpoint_[0].tag->get_uid()[3], 0);
EXPECT_EQ(nfcc.discovered_endpoint_[1].tag->get_uid()[3], 2);
EXPECT_EQ(nfcc.discovered_endpoint_.data()[2].tag, nullptr);
nfcc.erase_tag_(1);
ASSERT_EQ(nfcc.discovered_endpoint_.size(), 1u);
EXPECT_EQ(nfcc.discovered_endpoint_.data()[1].tag, nullptr);
}
// A full cache evicts the entry seen longest ago instead of refusing the new tag.
TEST(PN71xxTagCache, FullCacheEvictsOldest) {
FakePN71xx nfcc;
for (uint8_t i = 0; i < MAX_DISCOVERED_ENDPOINTS; i++) {
const size_t loc = nfcc.find_or_add_tag_(nfc::PROT_T2T, uid_of(i));
nfcc.discovered_endpoint_[loc].last_seen = 100 + i;
}
nfcc.discovered_endpoint_[3].last_seen = 1; // seen longest ago
const size_t loc = nfcc.find_or_add_tag_(nfc::PROT_T2T, uid_of(0x99));
ASSERT_EQ(nfcc.discovered_endpoint_.size(), MAX_DISCOVERED_ENDPOINTS);
EXPECT_EQ(nfcc.discovered_endpoint_[loc].tag->get_uid()[3], 0x99);
for (const auto &endpoint : nfcc.discovered_endpoint_) {
EXPECT_NE(endpoint.tag->get_uid()[3], 3);
}
// a known UID is found, not added again
EXPECT_EQ(nfcc.find_or_add_tag_(nfc::PROT_T2T, uid_of(0x99)), loc);
EXPECT_EQ(nfcc.discovered_endpoint_.size(), MAX_DISCOVERED_ENDPOINTS);
}
// Bytes that do not fit the packet are dropped and the length byte stays consistent.
TEST(PN71xxNciMessage, AppendStopsAtPacketSize) {
nfc::NciMessage msg(nfc::NCI_PKT_MT_DATA, {0x01});
std::vector<uint8_t> big(300, 0xAA);
msg.append(big);
const auto encoded = msg.encode();
EXPECT_EQ(encoded.size(), nfc::NCI_PKT_MAX_SIZE);
EXPECT_EQ(msg.get_payload_size(), nfc::NCI_PKT_MAX_PAYLOAD_SIZE);
}
// A read that could not fit the status bytes into one packet is refused.
TEST(PN71xxCardEmulation, OversizedReadIsRejected) {
FakePN71xx nfcc;
nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/pulse_ce");
select_ndef_file(nfcc);
EXPECT_EQ(respond(nfcc, {0x00, 0xB0, 0x00, 0x00, 0xFE}), SW_NOT_FOUND);
}
// A refused write (e.g. NFCC in standby) is sent again.
TEST(PN71xxTransceive, RefusedWriteIsRetried) {
FakePN71xx nfcc;