ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
pn532_mifare_classic.cpp
Go to the documentation of this file.
1#include <array>
2#include <memory>
3
4#include "pn532.h"
5#include "esphome/core/log.h"
6
7namespace esphome {
8namespace pn532 {
9
10static const char *const TAG = "pn532.mifare_classic";
11
12std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid) {
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_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
18 std::vector<uint8_t> data;
19 if (this->read_mifare_classic_block_(current_block, data)) {
20 if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
21 return make_unique<nfc::NfcTag>(uid, nfc::ERROR);
22 }
23 } else {
24 ESP_LOGE(TAG, "Failed to read block %d", current_block);
25 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
26 }
27 } else {
28 ESP_LOGV(TAG, "Tag is not NDEF formatted");
29 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
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_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
39 ESP_LOGE(TAG, "Error, Block authentication failed for %d", current_block);
40 }
41 }
42 std::vector<uint8_t> block_data;
43 if (this->read_mifare_classic_block_(current_block, block_data)) {
44 buffer.insert(buffer.end(), block_data.begin(), block_data.end());
45 } else {
46 ESP_LOGE(TAG, "Error reading block %d", current_block);
47 }
48
49 index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
50 current_block++;
51
52 if (nfc::mifare_classic_is_trailer_block(current_block)) {
53 current_block++;
54 }
55 }
56
57 if (buffer.begin() + message_start_index < buffer.end()) {
58 buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
59 } else {
60 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
61 }
62
63 return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC, buffer);
64}
65
66bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
67 if (!this->write_command_({
68 PN532_COMMAND_INDATAEXCHANGE,
69 0x01, // One card
70 nfc::MIFARE_CMD_READ,
71 block_num,
72 })) {
73 return false;
74 }
75
76 if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) {
77 return false;
78 }
79 data.erase(data.begin());
80
81 char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
82 ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes_to(data_buf, data));
83 return true;
84}
85
86bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key) {
87 std::vector<uint8_t> data({
88 PN532_COMMAND_INDATAEXCHANGE,
89 0x01, // One card
90 key_num, // Mifare Key slot
91 block_num, // Block number
92 });
93 data.insert(data.end(), key, key + 6);
94 data.insert(data.end(), uid.begin(), uid.end());
95 if (!this->write_command_(data)) {
96 ESP_LOGE(TAG, "Authentication failed - Block %d", block_num);
97 return false;
98 }
99
100 std::vector<uint8_t> response;
101 if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
102 ESP_LOGE(TAG, "Authentication failed - Block 0x%02x", block_num);
103 return false;
104 }
105
106 return true;
107}
108
110 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
111 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
112 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> TRAILER_BUFFER = {
113 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
114
115 bool error = false;
116
117 for (int block = 0; block < 64; block += 4) {
118 if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
119 continue;
120 }
121 if (block != 0) {
122 if (!this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
123 ESP_LOGE(TAG, "Unable to write block %d", block);
124 error = true;
125 }
126 }
127 if (!this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
128 ESP_LOGE(TAG, "Unable to write block %d", block + 1);
129 error = true;
130 }
131 if (!this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
132 ESP_LOGE(TAG, "Unable to write block %d", block + 2);
133 error = true;
134 }
135 if (!this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size())) {
136 ESP_LOGE(TAG, "Unable to write block %d", block + 3);
137 error = true;
138 }
139 }
140
141 return !error;
142}
143
145 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
146 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
147 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
148 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
149 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
150 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
151 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
152 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
153 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
154 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
155 static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> NDEF_TRAILER = {
156 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
157
158 if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
159 ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!");
160 return false;
161 }
162 if (!this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()))
163 return false;
164 if (!this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()))
165 return false;
166 if (!this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()))
167 return false;
168
169 ESP_LOGD(TAG, "Sector 0 formatted to NDEF");
170
171 for (int block = 4; block < 64; block += 4) {
172 if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
173 return false;
174 }
175 if (block == 4) {
176 if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size())) {
177 ESP_LOGE(TAG, "Unable to write block %d", block);
178 }
179 } else {
180 if (!this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
181 ESP_LOGE(TAG, "Unable to write block %d", block);
182 }
183 }
184 if (!this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
185 ESP_LOGE(TAG, "Unable to write block %d", block + 1);
186 }
187 if (!this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
188 ESP_LOGE(TAG, "Unable to write block %d", block + 2);
189 }
190 if (!this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size())) {
191 ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3);
192 }
193 }
194 return true;
195}
196
197bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
198 std::vector<uint8_t> cmd({
199 PN532_COMMAND_INDATAEXCHANGE,
200 0x01, // One card
201 nfc::MIFARE_CMD_WRITE,
202 block_num,
203 });
204 cmd.insert(cmd.end(), data, data + len);
205 if (!this->write_command_(cmd)) {
206 ESP_LOGE(TAG, "Error writing block %d", block_num);
207 return false;
208 }
209
210 std::vector<uint8_t> response;
211 if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
212 ESP_LOGE(TAG, "Error writing block %d", block_num);
213 return false;
214 }
215
216 return true;
217}
218
220 auto encoded = message->encode();
221
222 uint32_t message_length = encoded.size();
223 uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
224
225 encoded.insert(encoded.begin(), 0x03);
226 if (message_length < 255) {
227 encoded.insert(encoded.begin() + 1, message_length);
228 } else {
229 encoded.insert(encoded.begin() + 1, 0xFF);
230 encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
231 encoded.insert(encoded.begin() + 3, message_length & 0xFF);
232 }
233 encoded.push_back(0xFE);
234
235 encoded.resize(buffer_length, 0);
236
237 uint32_t index = 0;
238 uint8_t current_block = 4;
239
240 while (index < buffer_length) {
241 if (nfc::mifare_classic_is_first_block(current_block)) {
242 if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
243 return false;
244 }
245 }
246
247 if (!this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE)) {
248 return false;
249 }
250 index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
251 current_block++;
252
253 if (nfc::mifare_classic_is_trailer_block(current_block)) {
254 // Skipping as cannot write to trailer
255 current_block++;
256 }
257 }
258 return true;
259}
260
261} // namespace pn532
262} // namespace esphome
bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key)
bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid)
virtual bool read_response(uint8_t command, std::vector< uint8_t > &data)=0
bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid)
bool write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message)
std::unique_ptr< nfc::NfcTag > read_mifare_classic_tag_(nfc::NfcTagUid &uid)
bool read_mifare_classic_block_(uint8_t block_num, std::vector< uint8_t > &data)
bool write_command_(const std::vector< uint8_t > &data)
Definition pn532.cpp:244
bool write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len)
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