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