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