ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
pn71xx_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 "pn71xx.h"
7#include "esphome/core/log.h"
8
9namespace esphome::pn71xx {
10
11ESPHOME_LOG_TAG(TAG, "pn71xx.mifare_classic");
12
14 uint8_t current_block = 4;
15 uint8_t message_start_index = 0;
16 uint32_t message_length = 0;
17
18 if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
19 ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data");
20 return nfc::STATUS_FAILED;
21 }
22 std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> block_data;
23
24 if (this->read_mifare_classic_block_(current_block, block_data) == nfc::STATUS_OK) {
25 if (!nfc::decode_mifare_classic_tlv(block_data, message_length, message_start_index)) {
26 return nfc::STATUS_FAILED;
27 }
28 } else {
29 ESP_LOGE(TAG, "Failed to read block %u", current_block);
30 return nfc::STATUS_FAILED;
31 }
32 if (message_length > MIFARE_CLASSIC_MAX_NDEF_SIZE) {
33 ESP_LOGE(TAG, "NDEF message too long: %" PRIu32 " bytes", message_length);
34 return nfc::STATUS_FAILED;
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 nfc::STATUS_FAILED;
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_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
47 ESP_LOGE(TAG, "Block authentication failed for %u", current_block);
48 return nfc::STATUS_FAILED;
49 }
50 }
51 if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) {
52 ESP_LOGE(TAG, "Error reading block %u", current_block);
53 return nfc::STATUS_FAILED;
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 nfc::STATUS_FAILED;
67 }
68 tag.set_ndef_message(make_unique<nfc::NdefMessage>(std::span<const uint8_t>(buffer).subspan(message_start_index)));
69
70 return nfc::STATUS_OK;
71}
72
73uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num,
74 std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> &data) {
76 nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
77 char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
78
79 ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
80 if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
81 ESP_LOGE(TAG, "Timeout reading tag data");
82 return nfc::STATUS_FAILED;
83 }
84
85 if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
86 (!rx.message_length_is(18))) {
87 ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
88 ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes_to(buf, rx.get_message()));
89 return nfc::STATUS_FAILED;
90 }
91
92 // payload: XCHG_DATA status byte, 16 block bytes, one trailing status byte
93 std::copy_n(rx.get_message().begin() + 4, data.size(), data.begin());
94
95 ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes_to(buf, data));
96 return nfc::STATUS_OK;
97}
98
99uint8_t PN71xx::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
100 uint8_t key_select = key_num;
101 switch (key_num) {
102 case nfc::MIFARE_CMD_AUTH_A:
103 key_select = MFC_AUTHENTICATE_PARAM_KS_A;
104 break;
105
106 case nfc::MIFARE_CMD_AUTH_B:
107 key_select = MFC_AUTHENTICATE_PARAM_KS_B;
108 break;
109
110 default:
111 break;
112 }
113 if (key != nullptr) {
114 key_select |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
115 }
116
118 nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_select});
119 if (key != nullptr) {
120 tx.append(std::span<const uint8_t>(key, 6));
121 }
122
123 char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
124 ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
125 if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
126 ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
127 return nfc::STATUS_FAILED;
128 }
129 if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
130 (rx.get_message()[4] != nfc::STATUS_OK)) {
131 ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
132 ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes_to(buf, rx.get_message()));
133 return nfc::STATUS_FAILED;
134 }
135
136 ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num);
137 return nfc::STATUS_OK;
138}
139
140uint8_t PN71xx::sect_to_auth_(const uint8_t block_num) {
141 const uint8_t first_high_block = nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
142 if (block_num >= first_high_block) {
143 return ((block_num - first_high_block) / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH) +
144 nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
145 }
146 return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW;
147}
148
150 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
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> TRAILER_BUFFER = {
153 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
154
155 auto status = nfc::STATUS_OK;
156
157 for (int block = 0; block < 64; block += 4) {
158 if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
159 continue;
160 }
161 if (block != 0) {
162 if (this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
163 ESP_LOGE(TAG, "Unable to write block %u", block);
164 status = nfc::STATUS_FAILED;
165 }
166 }
167 if (this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
168 ESP_LOGE(TAG, "Unable to write block %u", block + 1);
169 status = nfc::STATUS_FAILED;
170 }
171 if (this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
172 ESP_LOGE(TAG, "Unable to write block %u", block + 2);
173 status = nfc::STATUS_FAILED;
174 }
175 if (this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size()) != nfc::STATUS_OK) {
176 ESP_LOGE(TAG, "Unable to write block %u", block + 3);
177 status = nfc::STATUS_FAILED;
178 }
179 }
180
181 return status;
182}
183
185 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
186 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
187 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
188 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
189 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
190 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
191 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
192 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
193 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
194 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
195 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> NDEF_TRAILER = {
196 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
197
198 if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
199 ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
200 return nfc::STATUS_FAILED;
201 }
202 if (this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()) != nfc::STATUS_OK) {
203 return nfc::STATUS_FAILED;
204 }
205 if (this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()) != nfc::STATUS_OK) {
206 return nfc::STATUS_FAILED;
207 }
208 if (this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()) != nfc::STATUS_OK) {
209 return nfc::STATUS_FAILED;
210 }
211
212 ESP_LOGD(TAG, "Sector 0 formatted with NDEF");
213
214 auto status = nfc::STATUS_OK;
215
216 for (int block = 4; block < 64; block += 4) {
217 if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
218 return nfc::STATUS_FAILED;
219 }
220 if (block == 4) {
221 if (this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size()) !=
222 nfc::STATUS_OK) {
223 ESP_LOGE(TAG, "Unable to write block %u", block);
224 status = nfc::STATUS_FAILED;
225 }
226 } else {
227 if (this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
228 ESP_LOGE(TAG, "Unable to write block %u", block);
229 status = nfc::STATUS_FAILED;
230 }
231 }
232 if (this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
233 ESP_LOGE(TAG, "Unable to write block %u", block + 1);
234 status = nfc::STATUS_FAILED;
235 }
236 if (this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
237 ESP_LOGE(TAG, "Unable to write block %u", block + 2);
238 status = nfc::STATUS_FAILED;
239 }
240 if (this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size()) != nfc::STATUS_OK) {
241 ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
242 status = nfc::STATUS_FAILED;
243 }
244 }
245 return status;
246}
247
248uint8_t PN71xx::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
250 nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
251 char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
252
253 ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes_to(buf, tx.get_message()));
254 if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
255 ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
256 return nfc::STATUS_FAILED;
257 }
258 // write command part two
259 tx.set_payload({XCHG_DATA_OID});
260 tx.append(std::span<const uint8_t>(data, len));
261
262 ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
263 if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
264 ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
265 return nfc::STATUS_FAILED;
266 }
267
268 if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
269 (rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
270 ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
271 ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes_to(buf, rx.get_message()));
272 return nfc::STATUS_FAILED;
273 }
274
275 return nfc::STATUS_OK;
276}
277
278uint8_t PN71xx::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message) {
279 const auto encoded = message->encode();
280 const uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(encoded.size());
282 nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
283
284 uint32_t index = 0;
285 uint8_t current_block = 4;
286
287 while (index < buffer_length) {
288 if (nfc::mifare_classic_is_first_block(current_block)) {
289 if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
290 return nfc::STATUS_FAILED;
291 }
292 }
293
294 if (this->write_mifare_classic_block_(current_block, &buffer[index], nfc::MIFARE_CLASSIC_BLOCK_SIZE) !=
295 nfc::STATUS_OK) {
296 return nfc::STATUS_FAILED;
297 }
298 index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
299 current_block++;
300
301 if (nfc::mifare_classic_is_trailer_block(current_block)) {
302 // Skipping as cannot write to trailer
303 current_block++;
304 }
305 }
306 return nfc::STATUS_OK;
307}
308
311 nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
312
313 char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
314 ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
315 if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
316 ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
317 return nfc::STATUS_FAILED;
318 }
319 return nfc::STATUS_OK;
320}
321
322} // namespace esphome::pn71xx
uint8_t status
Definition bl0942.h:8
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:616
One NCI packet, held in a fixed buffer so building and reading messages never allocates.
Definition nci_message.h:16
bool message_type_is(uint8_t message_type) const
bool simple_status_response_is(uint8_t response) const
bool message_length_is(uint8_t message_length, bool recompute=false)
void set_ndef_message(std::unique_ptr< NdefMessage > ndef_message)
Definition nfc_tag.h:49
uint8_t write_mifare_classic_tag_(const std::shared_ptr< nfc::NdefMessage > &message)
uint8_t sect_to_auth_(uint8_t block_num)
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key)
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout=NFCC_DEFAULT_TIMEOUT, bool expect_notification=true)
Definition pn71xx.cpp:1107
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag)
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)
const LogString * message
Definition component.cpp:35
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, "pn71xx")
const void size_t len
Definition hal.h:68
static void uint32_t