diff --git a/esphome/components/nfc/nci_message.cpp b/esphome/components/nfc/nci_message.cpp index a144b8a86d..d00c8af5ea 100644 --- a/esphome/components/nfc/nci_message.cpp +++ b/esphome/components/nfc/nci_message.cpp @@ -2,56 +2,64 @@ #include "nci_message.h" #include "esphome/core/log.h" -#include - namespace esphome::nfc { -static const char *const TAG = "NciMessage"; - -NciMessage::NciMessage(const uint8_t message_type, const std::vector &payload) { +NciMessage::NciMessage(const uint8_t message_type, const std::span payload) { this->set_message(message_type, payload); } +NciMessage::NciMessage(const uint8_t message_type, const std::initializer_list payload) + : NciMessage(message_type, std::span(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 &payload) { + const std::span payload) { this->set_message(message_type, gid, oid, payload); } -NciMessage::NciMessage(const std::vector &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 payload) + : NciMessage(message_type, gid, oid, std::span(payload.begin(), payload.size())) {} -std::vector NciMessage::encode() { - this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE; - std::vector message = this->nci_message_; - return message; +NciMessage::NciMessage(const std::span 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 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 &NciMessage::get_message() { return this->nci_message_; } +std::span 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 &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 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 &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 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 &payload) { - std::vector 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 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 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 diff --git a/esphome/components/nfc/nci_message.h b/esphome/components/nfc/nci_message.h index 8e8b110336..de8a4cf56a 100644 --- a/esphome/components/nfc/nci_message.h +++ b/esphome/components/nfc/nci_message.h @@ -3,19 +3,30 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include +#include +#include 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 &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 &payload); - NciMessage(const std::vector &raw_packet); + using Buffer = StaticVector; - std::vector encode(); + NciMessage() { this->reset(); } + NciMessage(uint8_t message_type, std::span payload); + NciMessage(uint8_t message_type, std::initializer_list 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 payload); + NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, std::initializer_list payload); + explicit NciMessage(std::span raw_packet); + + /// Stamps the payload length into the header and returns the packet ready to send + std::span 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 &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 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 &payload); - void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector &payload); + void set_message(uint8_t message_type, std::span payload); + void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, std::span 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 &payload); + void set_payload(std::span payload); + void set_payload(std::initializer_list payload) { + this->set_payload(std::span(payload.begin(), payload.size())); + } + /// Appends bytes to the payload; bytes that do not fit are dropped + void append(std::span data); + void append(std::initializer_list data) { + this->append(std::span(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 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 diff --git a/esphome/components/pn7150_i2c/pn7150_i2c.cpp b/esphome/components/pn7150_i2c/pn7150_i2c.cpp index a4e45b8584..58d9daa311 100644 --- a/esphome/components/pn7150_i2c/pn7150_i2c.cpp +++ b/esphome/components/pn7150_i2c/pn7150_i2c.cpp @@ -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; } diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index 6dcb73bee1..185ff0ce09 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -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 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(); diff --git a/esphome/components/pn7160_i2c/pn7160_i2c.cpp b/esphome/components/pn7160_i2c/pn7160_i2c.cpp index 799a258b98..d559c6c251 100644 --- a/esphome/components/pn7160_i2c/pn7160_i2c.cpp +++ b/esphome/components/pn7160_i2c/pn7160_i2c.cpp @@ -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; } diff --git a/esphome/components/pn7160_spi/pn7160_spi.cpp b/esphome/components/pn7160_spi/pn7160_spi.cpp index 43420f9efd..85a6c1fb7d 100644 --- a/esphome/components/pn7160_spi/pn7160_spi.cpp +++ b/esphome/components/pn7160_spi/pn7160_spi.cpp @@ -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); diff --git a/esphome/components/pn71xx/__init__.py b/esphome/components/pn71xx/__init__.py index 8cda915feb..a8825747d1 100644 --- a/esphome/components/pn71xx/__init__.py +++ b/esphome/components/pn71xx/__init__.py @@ -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 diff --git a/esphome/components/pn71xx/pn71xx.cpp b/esphome/components/pn71xx/pn71xx.cpp index f13e2de702..3d0b46efb4 100644 --- a/esphome/components/pn71xx/pn71xx.cpp +++ b/esphome/components/pn71xx/pn71xx.cpp @@ -56,7 +56,10 @@ void PN71xx::set_tag_emulation_message(const std::shared_ptr & 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::vectornci_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(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(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 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(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 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(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 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 discover_config = std::vector((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(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 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 PN71xx::build_tag_(const uint8_t mode_tech, const uint8_t protocol, - const std::vector &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(uid, nfc::MIFARE_CLASSIC); - } - if (protocol == nfc::PROT_T2T) { - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); - } - return make_unique(uid); - } +bool PN71xx::parse_uid_(const uint8_t mode_tech, const std::span 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 PN71xx::build_tag_(const uint8_t protocol, const nfc::NfcTagUid &uid) { + if (protocol == nfc::PROT_MIFARE) { + return make_unique(uid, nfc::MIFARE_CLASSIC); + } + if (protocol == nfc::PROT_T2T) { + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + } + return make_unique(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 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(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(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(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(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 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 &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(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(length) > file_size || + length + sizeof(CARD_EMU_T4T_OK) > CardEmuResponse::capacity()) { return false; } for (uint32_t i = offset; i < offset + static_cast(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 &response, std::vector &ndef_response) { +void PN71xx::card_emu_t4t_get_response_(const std::span 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 &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 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 &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(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(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 &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 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; } } diff --git a/esphome/components/pn71xx/pn71xx.h b/esphome/components/pn71xx/pn71xx.h index f9cfddc2e9..940d1874fc 100644 --- a/esphome/components/pn71xx/pn71xx.h +++ b/esphome/components/pn71xx/pn71xx.h @@ -9,6 +9,7 @@ #include "esphome/core/gpio.h" #include "esphome/core/helpers.h" +#include #include #include @@ -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; +/// 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; +/// 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 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 void add_on_emulated_tag_scan_callback(F &&callback) { this->on_emulated_tag_scan_callback_.add(std::forward(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 &message); - std::unique_ptr build_tag_(uint8_t mode_tech, uint8_t protocol, const std::vector &data); + /// Reads the UID from the RF technology parameters of a discovery or activation notification + bool parse_uid_(uint8_t mode_tech, std::span rf_tech_params, nfc::NfcTagUid &uid); + std::unique_ptr 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 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 &response, std::vector &ndef_response); - bool card_emu_t4t_read_ndef_(uint16_t offset, uint8_t length, std::vector &ndef_response); + void card_emu_t4t_get_response_(std::span 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 &data); + uint8_t read_mifare_classic_block_(uint8_t block_num, std::array &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 &data); - bool is_mifare_ultralight_formatted_(const std::vector &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 page_3_to_6); uint16_t read_mifare_ultralight_capacity_(); - uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t find_mifare_ultralight_ndef_(std::span 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 &message); @@ -275,13 +295,17 @@ class PN71xx : public nfc::Nfcc, public Component { }; // members are ordered by alignment, widest first, to minimize padding - CallbackManager on_emulated_tag_scan_callback_; - CallbackManager on_finished_write_callback_; + LazyCallbackManager on_emulated_tag_scan_callback_; + LazyCallbackManager on_finished_write_callback_; - std::vector discovered_endpoint_; - std::vector card_emulation_ndef_; // encoded emulation message; empty when none is set - std::vector triggers_ontag_; - std::vector triggers_ontagremoved_; + StaticVector discovered_endpoint_; + FixedVector card_emulation_ndef_; // encoded emulation message; empty when none is set +#ifdef PN71XX_ON_TAG_TRIGGER_COUNT + StaticVector triggers_ontag_; +#endif +#ifdef PN71XX_ON_TAG_REMOVED_TRIGGER_COUNT + StaticVector triggers_ontagremoved_; +#endif std::shared_ptr next_task_message_to_write_; GPIOPin *irq_pin_{nullptr}; diff --git a/esphome/components/pn71xx/pn71xx_mifare_classic.cpp b/esphome/components/pn71xx/pn71xx_mifare_classic.cpp index 0bd6148e8a..c1c927634d 100644 --- a/esphome/components/pn71xx/pn71xx_mifare_classic.cpp +++ b/esphome/components/pn71xx/pn71xx_mifare_classic.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include #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 data; + std::array 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 buffer; + const uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length); + FixedVector 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 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(buffer)); + tag.set_ndef_message(make_unique(std::span(buffer).subspan(message_start_index))); return nfc::STATUS_OK; } -uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { +uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num, + std::array &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::vectorsect_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(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(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 &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 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_ptrwrite_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; } diff --git a/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp b/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp index b8fac21bbb..53874e18de 100644 --- a/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp +++ b/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -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 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(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(std::span(data).subspan(skip))); return nfc::STATUS_OK; } -uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &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(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 &page_3_to_6) { +bool PN71xx::is_mifare_ultralight_formatted_(const std::span 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 &page_3_ } uint16_t PN71xx::read_mifare_ultralight_capacity_() { - std::vector 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 &page_3_to_6, uint8_t &message_length, +uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::span 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 &page_3_ uint8_t PN71xx::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &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 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 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(write_data, len)); if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Error writing page %u", page_num); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 1226847fcf..da03f0e3d1 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -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 diff --git a/tests/components/pn71xx/pn71xx_test.cpp b/tests/components/pn71xx/pn71xx_test.cpp index ceb2d7305c..a856400dcd 100644 --- a/tests/components/pn71xx/pn71xx_test.cpp +++ b/tests/components/pn71xx/pn71xx_test.cpp @@ -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> 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 apdu(std::initializer_list bytes) { } std::vector respond(FakePN71xx &nfcc, std::initializer_list bytes) { - std::vector 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 bytes_of(const nfc::NciMessage &msg) { + return {msg.get_message().begin(), msg.get_message().end()}; +} + const std::vector SW_OK = {0x90, 0x00}; const std::vector 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{0x41, 0x06, 0x01, 0x00})); + EXPECT_EQ(bytes_of(rx), (std::vector{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{0x41, 0x03, 0x01, 0x00})); + EXPECT_EQ(bytes_of(rx), (std::vector{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 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;