ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
pn532_mifare_classic.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <cinttypes>
4#include <memory>
5
6#include "pn532.h"
7#include "esphome/core/log.h"
8
9namespace esphome::pn532 {
10
11ESPHOME_LOG_TAG(TAG, "pn532.mifare_classic");
12
13std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid) {
14 uint8_t current_block = 4;
15 uint8_t message_start_index = 0;
16 uint32_t message_length = 0;
17
18 MifareReadData block_data;
19 if (this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
20 if (this->read_mifare_classic_block_(current_block, block_data)) {
21 if (!nfc::decode_mifare_classic_tlv(block_data, message_length, message_start_index)) {
22 return make_unique<nfc::NfcTag>(uid, nfc::ERROR);
23 }
24 } else {
25 ESP_LOGE(TAG, "Failed to read block %d", current_block);
26 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
27 }
28 } else {
29 ESP_LOGV(TAG, "Tag is not NDEF formatted");
30 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
31 }
32 if (message_length > MIFARE_CLASSIC_MAX_NDEF_SIZE) {
33 ESP_LOGE(TAG, "NDEF message too long: %" PRIu32 " bytes", message_length);
34 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
35 }
36
37 const uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
39 if (!buffer.try_init(buffer_size)) {
40 ESP_LOGE(TAG, "Out of memory reading NDEF message of %" PRIu32 " bytes", buffer_size);
41 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
42 }
43
44 while (buffer.size() < buffer_size) {
45 if (nfc::mifare_classic_is_first_block(current_block)) {
46 if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
47 ESP_LOGE(TAG, "Error, Block authentication failed for %d", current_block);
48 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
49 }
50 }
51 if (!this->read_mifare_classic_block_(current_block, block_data)) {
52 ESP_LOGE(TAG, "Error reading block %d", current_block);
53 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
54 }
55 for (const uint8_t byte : block_data) {
56 buffer.push_back(byte);
57 }
58
59 current_block++;
60 if (nfc::mifare_classic_is_trailer_block(current_block)) {
61 current_block++;
62 }
63 }
64
65 if (message_start_index >= buffer.size()) {
66 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
67 }
68
69 return make_unique<nfc::NfcTag>(
70 uid, nfc::MIFARE_CLASSIC,
71 make_unique<nfc::NdefMessage>(std::span<const uint8_t>(buffer).subspan(message_start_index)));
72}
73
74bool PN532::read_mifare_classic_block_(uint8_t block_num, MifareReadData &data) {
75 if (!this->mifare_read_(block_num, data)) {
76 return false;
77 }
78
79 char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
80 ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes_to(data_buf, data));
81 return true;
82}
83
84bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key) {
85 // InDataExchange, Tg, key slot, block, key (6), UID (4)
87 PN532_COMMAND_INDATAEXCHANGE,
88 0x01, // One card
89 key_num, // Mifare Key slot
90 block_num, // Block number
91 };
92 for (size_t i = 0; i < 6; i++) {
93 data.push_back(key[i]);
94 }
95 // the command takes exactly 4 UID bytes (UM0701-02, 7.3.8); for 7-byte UIDs these are the last 4, as in libnfc
96 if (uid.size() < 4) {
97 return false;
98 }
99 for (size_t i = uid.size() - 4; i < uid.size(); i++) {
100 data.push_back(uid[i]);
101 }
102
103 PN532Frame response;
104 if (!this->in_data_exchange_(data, response)) {
105 ESP_LOGE(TAG, "Authentication failed - Block 0x%02x", block_num);
106 return false;
107 }
108
109 return true;
110}
111
113 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
114 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
115 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> TRAILER_BUFFER = {
116 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
117
118 bool error = false;
119
120 for (int block = 0; block < 64; block += 4) {
121 if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
122 continue;
123 }
124 if (block != 0) {
125 if (!this->write_mifare_classic_block_(block, BLANK_BUFFER)) {
126 ESP_LOGE(TAG, "Unable to write block %d", block);
127 error = true;
128 }
129 }
130 if (!this->write_mifare_classic_block_(block + 1, BLANK_BUFFER)) {
131 ESP_LOGE(TAG, "Unable to write block %d", block + 1);
132 error = true;
133 }
134 if (!this->write_mifare_classic_block_(block + 2, BLANK_BUFFER)) {
135 ESP_LOGE(TAG, "Unable to write block %d", block + 2);
136 error = true;
137 }
138 if (!this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER)) {
139 ESP_LOGE(TAG, "Unable to write block %d", block + 3);
140 error = true;
141 }
142 }
143
144 return !error;
145}
146
148 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
149 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
150 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
151 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
152 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
153 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
154 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
155 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
156 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
157 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
158 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> NDEF_TRAILER = {
159 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
160
161 if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
162 ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!");
163 return false;
164 }
165 if (!this->write_mifare_classic_block_(1, BLOCK_1_DATA))
166 return false;
167 if (!this->write_mifare_classic_block_(2, BLOCK_2_DATA))
168 return false;
169 if (!this->write_mifare_classic_block_(3, BLOCK_3_TRAILER))
170 return false;
171
172 ESP_LOGD(TAG, "Sector 0 formatted to NDEF");
173
174 bool error = false;
175
176 for (int block = 4; block < 64; block += 4) {
177 if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
178 return false;
179 }
180 if (block == 4) {
181 if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE)) {
182 ESP_LOGE(TAG, "Unable to write block %d", block);
183 error = true;
184 }
185 } else {
186 if (!this->write_mifare_classic_block_(block, BLANK_BLOCK)) {
187 ESP_LOGE(TAG, "Unable to write block %d", block);
188 error = true;
189 }
190 }
191 if (!this->write_mifare_classic_block_(block + 1, BLANK_BLOCK)) {
192 ESP_LOGE(TAG, "Unable to write block %d", block + 1);
193 error = true;
194 }
195 if (!this->write_mifare_classic_block_(block + 2, BLANK_BLOCK)) {
196 ESP_LOGE(TAG, "Unable to write block %d", block + 2);
197 error = true;
198 }
199 if (!this->write_mifare_classic_block_(block + 3, NDEF_TRAILER)) {
200 ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3);
201 error = true;
202 }
203 }
204 return !error;
205}
206
207bool PN532::write_mifare_classic_block_(uint8_t block_num, const std::span<const uint8_t> data) {
209 PN532_COMMAND_INDATAEXCHANGE,
210 0x01, // One card
211 nfc::MIFARE_CMD_WRITE,
212 block_num,
213 };
214 for (const uint8_t byte : data) {
215 cmd.push_back(byte);
216 }
217
218 PN532Frame response;
219 if (!this->in_data_exchange_(cmd, response)) {
220 ESP_LOGE(TAG, "Error writing block %d", block_num);
221 return false;
222 }
223
224 return true;
225}
226
228 const auto encoded = message->encode();
229 const uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(encoded.size());
231 nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
232
233 uint32_t index = 0;
234 uint8_t current_block = 4;
235
236 while (index < buffer_length) {
237 if (nfc::mifare_classic_is_first_block(current_block)) {
238 if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
239 return false;
240 }
241 }
242
243 if (!this->write_mifare_classic_block_(current_block,
244 std::span<const uint8_t>(&buffer[index], nfc::MIFARE_CLASSIC_BLOCK_SIZE))) {
245 return false;
246 }
247 index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
248 current_block++;
249
250 if (nfc::mifare_classic_is_trailer_block(current_block)) {
251 // Skipping as cannot write to trailer
252 current_block++;
253 }
254 }
255 return true;
256}
257
258} // namespace esphome::pn532
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:624
size_t size() const
Definition helpers.h:355
void push_back(const T &value)
Definition helpers.h:325
bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key)
bool mifare_read_(uint8_t address, MifareReadData &data)
Sends MIFARE READ for address and returns the 16 bytes the tag answers with.
Definition pn532.cpp:459
bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid)
bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid)
bool read_mifare_classic_block_(uint8_t block_num, MifareReadData &data)
bool write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message)
std::unique_ptr< nfc::NfcTag > read_mifare_classic_tag_(nfc::NfcTagUid &uid)
bool write_mifare_classic_block_(uint8_t block_num, std::span< const uint8_t > data)
bool in_data_exchange_(std::span< const uint8_t > command, PN532Frame &response)
Sends an InDataExchange command and reads the response; returns false unless the status byte reports ...
Definition pn532.cpp:440
const LogString * message
Definition component.cpp:35
const char * key
uint32_t buffer
Definition gif.cpp:50
char * format_bytes_to(char *buffer, std::span< const uint8_t > bytes)
Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use.
Definition nfc.cpp:14
bool mifare_classic_is_trailer_block(uint8_t block_num)
Definition nfc.cpp:112
bool decode_mifare_classic_tlv(const std::span< const uint8_t > data, uint32_t &message_length, uint8_t &message_start_index)
Definition nfc.cpp:39
uint32_t get_mifare_classic_buffer_size(uint32_t message_length)
Definition nfc.cpp:91
void fill_ndef_tlv(const std::span< const uint8_t > message, const uint32_t buffer_length, FixedVector< uint8_t > &buffer)
Fills buffer with the NDEF TLV (type, length, message, terminator) padded with zeros to buffer_length
Definition nfc.cpp:72
bool mifare_classic_is_first_block(uint8_t block_num)
Definition nfc.cpp:104
ESPHOME_LOG_TAG(TAG, "pn532")
std::array< uint8_t, MIFARE_READ_SIZE > MifareReadData
Definition pn532.h:59
static void uint32_t