From 4c390d9f9f323c7af7093d77392d55d5fd46f354 Mon Sep 17 00:00:00 2001 From: JonasEr <8407728+JonasEr@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:38:08 +0300 Subject: [PATCH] Extend nfc ndef records with Text (#2191) Co-authored-by: Oxan van Leeuwen --- esphome/components/nfc/ndef_message.cpp | 50 +++++++------- esphome/components/nfc/ndef_message.h | 4 +- esphome/components/nfc/ndef_record.cpp | 52 +++++--------- esphome/components/nfc/ndef_record.h | 76 ++++----------------- esphome/components/nfc/ndef_record_text.cpp | 40 +++++++++++ esphome/components/nfc/ndef_record_text.h | 41 +++++++++++ esphome/components/nfc/ndef_record_uri.cpp | 48 +++++++++++++ esphome/components/nfc/ndef_record_uri.h | 76 +++++++++++++++++++++ 8 files changed, 264 insertions(+), 123 deletions(-) create mode 100644 esphome/components/nfc/ndef_record_text.cpp create mode 100644 esphome/components/nfc/ndef_record_text.h create mode 100644 esphome/components/nfc/ndef_record_uri.cpp create mode 100644 esphome/components/nfc/ndef_record_uri.h diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index b1554f41ae..d8c940254e 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -10,16 +10,13 @@ NdefMessage::NdefMessage(std::vector &data) { uint8_t index = 0; while (index <= data.size()) { uint8_t tnf_byte = data[index++]; - bool me = tnf_byte & 0x40; - bool sr = tnf_byte & 0x10; - bool il = tnf_byte & 0x08; - uint8_t tnf = tnf_byte & 0x07; + bool me = tnf_byte & 0x40; // Message End bit (is set if this is the last record of the message) + bool sr = tnf_byte & 0x10; // Short record bit (is set if payload size is less or equal to 255 bytes) + bool il = tnf_byte & 0x08; // ID length bit (is set if ID Length field exists) + uint8_t tnf = tnf_byte & 0x07; // Type Name Format ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); - auto record = make_unique(); - record->set_tnf(tnf); - uint8_t type_length = data[index++]; uint32_t payload_length = 0; if (sr) { @@ -38,28 +35,34 @@ NdefMessage::NdefMessage(std::vector &data) { ESP_LOGVV(TAG, "Lengths: type=%d, payload=%d, id=%d", type_length, payload_length, id_length); std::string type_str(data.begin() + index, data.begin() + index + type_length); - record->set_type(type_str); + index += type_length; + std::string id_str = ""; if (il) { - std::string id_str(data.begin() + index, data.begin() + index + id_length); - record->set_id(id_str); + id_str = std::string(data.begin() + index, data.begin() + index + id_length); index += id_length; } - uint8_t payload_identifier = 0x00; - if (type_str == "U") { - payload_identifier = data[index++]; - payload_length -= 1; + std::vector payload_data(data.begin() + index, data.begin() + index + payload_length); + + std::unique_ptr record; + + // Based on tnf and type, create a more specific NdefRecord object + // constructed from the payload data + if (tnf == TNF_WELL_KNOWN && type_str == "U") { + record = make_unique(payload_data); + } else if (tnf == TNF_WELL_KNOWN && type_str == "T") { + record = make_unique(payload_data); + } else { + // Could not recognize the record, so store as generic one. + record = make_unique(payload_data); + record->set_tnf(tnf); + record->set_type(type_str); } - std::string payload_str(data.begin() + index, data.begin() + index + payload_length); + record->set_id(id_str); - if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { - payload_str.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); - } - - record->set_payload(payload_str); index += payload_length; ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); @@ -82,13 +85,10 @@ bool NdefMessage::add_record(std::unique_ptr record) { bool NdefMessage::add_text_record(const std::string &text) { return this->add_text_record(text, "en"); }; bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { - std::string payload = to_string(text.length()) + encoding + text; - return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); + return this->add_record(make_unique(encoding, text)); } -bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); -} +bool NdefMessage::add_uri_record(const std::string &uri) { return this->add_record(make_unique(uri)); } std::vector NdefMessage::encode() { std::vector data; diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index 20140e8c1a..5e44a06011 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -5,6 +5,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "ndef_record.h" +#include "ndef_record_text.h" +#include "ndef_record_uri.h" namespace esphome { namespace nfc { @@ -18,7 +20,7 @@ class NdefMessage { NdefMessage(const NdefMessage &msg) { records_.reserve(msg.records_.size()); for (const auto &r : msg.records_) { - records_.emplace_back(make_unique(*r)); + records_.emplace_back(r->clone()); } } diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp index a75f5978ec..8a3a7d375d 100644 --- a/esphome/components/nfc/ndef_record.cpp +++ b/esphome/components/nfc/ndef_record.cpp @@ -5,40 +5,22 @@ namespace nfc { static const char *const TAG = "nfc.ndef_record"; -uint32_t NdefRecord::get_encoded_size() { - uint32_t size = 2; - if (this->payload_.length() > 255) { - size += 4; - } else { - size += 1; - } - if (this->id_.length()) { - size += 1; - } - size += (this->type_.length() + this->payload_.length() + this->id_.length()); - return size; +NdefRecord::NdefRecord(std::vector payload_data) { + this->payload_ = std::string(payload_data.begin(), payload_data.end()); } std::vector NdefRecord::encode(bool first, bool last) { std::vector data; - data.push_back(this->get_tnf_byte(first, last)); + // Get encoded payload, this is overriden by more specific record classes + std::vector payload_data = get_encoded_payload(); + + size_t payload_length = payload_data.size(); + + data.push_back(this->create_flag_byte(first, last, payload_length)); data.push_back(this->type_.length()); - uint8_t payload_prefix = 0x00; - uint8_t payload_prefix_length = 0x00; - for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { - std::string prefix = PAYLOAD_IDENTIFIERS[i]; - if (this->payload_.substr(0, prefix.length()).find(prefix) != std::string::npos) { - payload_prefix = i; - payload_prefix_length = prefix.length(); - break; - } - } - - uint32_t payload_length = this->payload_.length() - payload_prefix_length + 1; - if (payload_length <= 255) { data.push_back(payload_length); } else { @@ -58,25 +40,23 @@ std::vector NdefRecord::encode(bool first, bool last) { data.insert(data.end(), this->id_.begin(), this->id_.end()); } - data.push_back(payload_prefix); - - data.insert(data.end(), this->payload_.begin() + payload_prefix_length, this->payload_.end()); + data.insert(data.end(), payload_data.begin(), payload_data.end()); return data; } -uint8_t NdefRecord::get_tnf_byte(bool first, bool last) { - uint8_t value = this->tnf_; +uint8_t NdefRecord::create_flag_byte(bool first, bool last, size_t payload_size) { + uint8_t value = this->tnf_ & 0b00000111; if (first) { - value = value | 0x80; + value = value | 0x80; // Set MB bit } if (last) { - value = value | 0x40; + value = value | 0x40; // Set ME bit } - if (this->payload_.length() <= 255) { - value = value | 0x10; + if (payload_size <= 255) { + value = value | 0x10; // Set SR bit } if (this->id_.length()) { - value = value | 0x08; + value = value | 0x08; // Set IL bit } return value; }; diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h index 680fe20986..4fab1c03e4 100644 --- a/esphome/components/nfc/ndef_record.h +++ b/esphome/components/nfc/ndef_record.h @@ -15,86 +15,40 @@ static const uint8_t TNF_UNKNOWN = 0x05; static const uint8_t TNF_UNCHANGED = 0x06; static const uint8_t TNF_RESERVED = 0x07; -static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; -static const char *const PAYLOAD_IDENTIFIERS[] = {"", - "http://www.", - "https://www.", - "http://", - "https://", - "tel:", - "mailto:", - "ftp://anonymous:anonymous@", - "ftp://ftp.", - "ftps://", - "sftp://", - "smb://", - "nfs://", - "ftp://", - "dav://", - "news:", - "telnet://", - "imap:", - "rtsp://", - "urn:", - "pop:", - "sip:", - "sips:", - "tftp:", - "btspp://", - "btl2cap://", - "btgoep://", - "tcpobex://", - "irdaobex://", - "file://", - "urn:epc:id:", - "urn:epc:tag:", - "urn:epc:pat:", - "urn:epc:raw:", - "urn:epc:", - "urn:nfc:"}; - class NdefRecord { public: NdefRecord(){}; - NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload) { - this->tnf_ = tnf; - this->type_ = type; - this->set_payload(payload); - }; - NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload, const std::string &id) { - this->tnf_ = tnf; - this->type_ = type; - this->set_payload(payload); - this->id_ = id; - }; - NdefRecord(const NdefRecord &rhs) { - this->tnf_ = rhs.tnf_; - this->type_ = rhs.type_; - this->payload_ = rhs.payload_; - this->payload_identifier_ = rhs.payload_identifier_; - this->id_ = rhs.id_; - }; + NdefRecord(std::vector payload_data); void set_tnf(uint8_t tnf) { this->tnf_ = tnf; }; void set_type(const std::string &type) { this->type_ = type; }; - void set_payload_identifier(uint8_t payload_identifier) { this->payload_identifier_ = payload_identifier; }; void set_payload(const std::string &payload) { this->payload_ = payload; }; void set_id(const std::string &id) { this->id_ = id; }; + NdefRecord(const NdefRecord &) = default; + virtual ~NdefRecord() {} + virtual std::unique_ptr clone() const { // To allow copying polymorphic classes + return make_unique(*this); + }; uint32_t get_encoded_size(); std::vector encode(bool first, bool last); - uint8_t get_tnf_byte(bool first, bool last); + + uint8_t create_flag_byte(bool first, bool last, size_t payload_size); const std::string &get_type() const { return this->type_; }; const std::string &get_id() const { return this->id_; }; - const std::string &get_payload() const { return this->payload_; }; + virtual const std::string &get_payload() const { return this->payload_; }; + + virtual std::vector get_encoded_payload() { + std::vector empty_payload; + return empty_payload; + }; protected: uint8_t tnf_; std::string type_; - uint8_t payload_identifier_; - std::string payload_; std::string id_; + std::string payload_; }; } // namespace nfc diff --git a/esphome/components/nfc/ndef_record_text.cpp b/esphome/components/nfc/ndef_record_text.cpp new file mode 100644 index 0000000000..80b0108b46 --- /dev/null +++ b/esphome/components/nfc/ndef_record_text.cpp @@ -0,0 +1,40 @@ +#include "ndef_record_text.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.ndef_record_text"; + +NdefRecordText::NdefRecordText(const std::vector &payload) { + if (payload.empty()) { + ESP_LOGE(TAG, "Record payload too short"); + return; + } + + uint8_t language_code_length = payload[0] & 0b00111111; // Todo, make use of encoding bit? + + this->language_code_ = std::string(payload.begin() + 1, payload.begin() + 1 + language_code_length); + + this->text_ = std::string(payload.begin() + 1 + language_code_length, payload.end()); + + this->tnf_ = TNF_WELL_KNOWN; + + this->type_ = "T"; +} + +std::vector NdefRecordText::get_encoded_payload() { + std::vector data; + + uint8_t flag_byte = this->language_code_.length() & 0b00111111; // UTF8 assumed + + data.push_back(flag_byte); + + data.insert(data.end(), this->language_code_.begin(), this->language_code_.end()); + + data.insert(data.end(), this->text_.begin(), this->text_.end()); + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_text.h b/esphome/components/nfc/ndef_record_text.h new file mode 100644 index 0000000000..94375cc860 --- /dev/null +++ b/esphome/components/nfc/ndef_record_text.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +class NdefRecordText : public NdefRecord { + public: + NdefRecordText(){}; + NdefRecordText(const std::vector &payload); + NdefRecordText(const std::string &language_code, const std::string &text) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "T"; + this->language_code_ = language_code; + this->text_ = text; + }; + NdefRecordText(const std::string &language_code, const std::string &text, const std::string &id) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "T"; + this->language_code_ = language_code; + this->text_ = text; + this->id_ = id; + }; + NdefRecordText(const NdefRecordText &) = default; + + std::unique_ptr clone() const override { return make_unique(*this); }; + + std::vector get_encoded_payload() override; + + const std::string &get_payload() const override { return this->text_; }; + + protected: + std::string text_; + std::string language_code_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_uri.cpp b/esphome/components/nfc/ndef_record_uri.cpp new file mode 100644 index 0000000000..8fd043a1de --- /dev/null +++ b/esphome/components/nfc/ndef_record_uri.cpp @@ -0,0 +1,48 @@ +#include "ndef_record_uri.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.ndef_record_uri"; + +NdefRecordUri::NdefRecordUri(const std::vector &payload) { + if (payload.empty()) { + ESP_LOGE(TAG, "Record payload too short"); + return; + } + + uint8_t payload_identifier = payload[0]; // First byte of payload is prefix code + + std::string uri(payload.begin() + 1, payload.end()); + + if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { + uri.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); + } + + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->set_URI(uri); +} + +std::vector NdefRecordUri::get_encoded_payload() { + std::vector data; + + uint8_t payload_prefix = 0x00; + uint8_t payload_prefix_length = 0x00; + for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { + std::string prefix = PAYLOAD_IDENTIFIERS[i]; + if (this->URI_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + payload_prefix = i; + payload_prefix_length = prefix.length(); + break; + } + } + + data.push_back(payload_prefix); + + data.insert(data.end(), this->URI_.begin() + payload_prefix_length, this->URI_.end()); + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record_uri.h b/esphome/components/nfc/ndef_record_uri.h new file mode 100644 index 0000000000..75f9e19ee0 --- /dev/null +++ b/esphome/components/nfc/ndef_record_uri.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; +static const char *const PAYLOAD_IDENTIFIERS[] = {"", + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:"}; + +class NdefRecordUri : public NdefRecord { + public: + NdefRecordUri(){}; + NdefRecordUri(const std::vector &payload); + NdefRecordUri(const std::string &URI) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->URI_ = URI; + }; + NdefRecordUri(const std::string &URI, const std::string &id) { + this->tnf_ = TNF_WELL_KNOWN; + this->type_ = "U"; + this->URI_ = URI; + this->id_ = id; + }; + NdefRecordUri(const NdefRecordUri &) = default; + std::unique_ptr clone() const override { return make_unique(*this); }; + + void set_URI(const std::string &URI) { this->URI_ = URI; }; + + std::vector get_encoded_payload() override; + const std::string &get_payload() const override { return this->URI_; }; + + protected: + std::string URI_; +}; + +} // namespace nfc +} // namespace esphome