Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow

This commit is contained in:
2026-09-12 10:49:39 +02:00
parent 8ac465229c
commit 1d954287a2
82 changed files with 7611 additions and 395 deletions

View File

@@ -0,0 +1,604 @@
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* # Memory access management via access bits
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* The access conditions for every data block and sector trailer are defined by 3 bits, which
* are stored non-inverted and inverted in the sector trailer of the specified sector.
*
* The access bits control the rights of memory access using the secret keys A and B. The
* access conditions may be altered, provided one knows the relevant key and the current
* access condition allows this operation.
*
* @warning
* With each memory access the internal logic verifies the format of the access
* conditions. If it detects a format violation the whole sector is irreversibly blocked.
*
* +-----------------------------------------------------------------------------------+
* | Sector Trailer |
* +=============+===+===+===+===+===+===+===+===+===+===+====+====+====+====+====+====+
* | Byte number | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
* +-------------+---+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
* | Description | Key A | Access Bits | Key B (optional) |
* +-------------+-----------------------+---------------+-----------------------------+
*
* Bytes 6, 7 and 8 of sector trailer block are bytes that holds access bits.
* Next table shows descriptions for each bit of those bytes.
*
* +-----+------+------+------+------+------+------+------+------+
* | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* +-----+------+------+------+------+------+------+------+------+
* | [6] | ~C23 | ~C22 | ~C21 | ~C20 | ~C13 | ~C12 | ~C11 | ~C10 |
* +-----+------+------+------+------+------+------+------+------+
* | [7] | C13 | C12 | C11 | C10 | ~C33 | ~C32 | ~C31 | ~C30 |
* +-----+------+------+------+------+------+------+------+------+
* | [8] | C33 | C32 | C31 | C30 | C23 | C22 | C21 | C20 |
* +-----+------+------+------+------+------+------+------+------+
*
* Where Cxy is access bit Cx for the block (group) y, for example:
* - C10 is the access bit C1 for the block (group) 0,
* - C20 is the access bit C2 for the block (group) 0,
* - ...,
* - C33 is the access bit C3 for the sector trailer,
* - ~C10 is the inverted bit C10.
*/
#include <string.h>
#include "rc522_types_internal.h"
#include "rc522_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "picc/rc522_mifare.h"
RC522_LOG_DEFINE_BASE();
/**
* The commands used for MIFARE Classic
*/
enum
{
/**
* Perform authentication with Key A
*/
RC522_MIFARE_AUTH_KEY_A_CMD = 0x60,
/**
* Perform authentication with Key B
*/
RC522_MIFARE_AUTH_KEY_B_CMD = 0x61,
/**
* Reads one 16 byte block from the authenticated sector of the PICC
*/
RC522_MIFARE_READ_CMD = 0x30,
/**
* Writes one 16 byte block to the authenticated sector of the PICC
*/
RC522_MIFARE_WRITE_CMD = 0xA0,
/**
* Decrements the contents of a block and stores the result in the internal data register
*/
RC522_MIFARE_DECREMENT_CMD = 0xC0,
/**
* Increments the contents of a block and stores the result in the internal data register
*/
RC522_MIFARE_INCREMENT_CMD = 0xC1,
/**
* Reads the contents of a block into the internal data register
*/
RC522_MIFARE_RESTORE_CMD = 0xC2,
/**
* Writes the contents of the internal data register to a block
*/
RC522_MIFARE_TRANSFER_CMD = 0xB0,
};
/**
* Checks if PICC type is compatible with MIFARE Classic protocol
*/
bool rc522_mifare_type_is_classic_compatible(rc522_picc_type_t type)
{
return type == RC522_PICC_TYPE_MIFARE_MINI || type == RC522_PICC_TYPE_MIFARE_1K
|| type == RC522_PICC_TYPE_MIFARE_4K;
}
inline esp_err_t rc522_mifare_auth_sector(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_key_t *key)
{
RC522_CHECK(sector_desc == NULL);
RC522_RETURN_ON_ERROR(rc522_mifare_auth(rc522, picc, sector_desc->block_0_address, key));
return ESP_OK;
}
esp_err_t rc522_mifare_auth(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address, const rc522_mifare_key_t *key)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(key == NULL);
// TODO: Validate block_address
RC522_LOGD("MIFARE AUTH (block_address=%02" RC522_X ")", block_address);
uint8_t send_data[12];
switch (key->type) {
case RC522_MIFARE_KEY_A:
send_data[0] = RC522_MIFARE_AUTH_KEY_A_CMD;
break;
case RC522_MIFARE_KEY_B:
send_data[0] = RC522_MIFARE_AUTH_KEY_B_CMD;
break;
default:
RC522_LOGE("Invalid key type");
return ESP_ERR_INVALID_ARG;
}
send_data[1] = block_address;
memcpy(send_data + 2, key->value, RC522_MIFARE_KEY_SIZE);
// Use the last uid bytes
// section 3.2.5 "MIFARE Classic Authentication".
// The only missed case is the MF1Sxxxx shortcut activation,
// but it requires cascade tag (CT) byte, that is not part of uid.
memcpy(send_data + 8, picc->uid.value + picc->uid.length - 4, 4);
// Start the authentication.
rc522_picc_transaction_t transaction = {
.pcd_command = RC522_PCD_MF_AUTH_CMD,
.expected_interrupts = RC522_PCD_IDLE_IRQ_BIT,
.bytes = { .ptr = send_data, .length = sizeof(send_data) },
};
esp_err_t ret = rc522_picc_send(rc522, &transaction, NULL);
// 10.3.1.9
//
// "If an error occurs during authentication,
// the ErrorReg register’s ProtocolErr bit is set to logic 1
// and the Status2Reg register’s Crypto1On bit is set to logic 0"
// Timer interrupt fires before ProtocolErr bit is set to 1
// so we will only check for Crypto1On bit (even if ret == ESP_OK)
uint8_t status2;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_STATUS_2_REG, &status2));
if (!(status2 & RC522_PCD_MF_CRYPTO1_ON_BIT)) {
return RC522_ERR_MIFARE_AUTHENTICATION_FAILED;
}
return ret;
}
esp_err_t rc522_mifare_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
uint8_t out_buffer[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_buffer == NULL);
RC522_LOGD("MIFARE READ (block_address=%02" RC522_X ")", block_address);
uint8_t cmd_buffer[4] = { 0 };
// Build command buffer
cmd_buffer[0] = RC522_MIFARE_READ_CMD;
cmd_buffer[1] = block_address;
// Calculate CRC_A
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = cmd_buffer, .length = 2 }, &crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
uint8_t block_buffer[RC522_MIFARE_BLOCK_SIZE + 2] = { 0 }; // +2 for CRC_A
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
.check_crc = true,
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = block_buffer, .length = sizeof(block_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_CHECK_AND_RETURN((result.bytes.length - 2) != RC522_MIFARE_BLOCK_SIZE, ESP_FAIL);
memcpy(out_buffer, block_buffer, sizeof(block_buffer) - 2); // -2 cuz of CRC_A
return ESP_OK;
}
static esp_err_t rc522_mifare_send(const rc522_handle_t rc522, const uint8_t *send_data, uint8_t send_length)
{
RC522_CHECK(send_data == NULL);
RC522_CHECK(send_length > RC522_MIFARE_BLOCK_SIZE);
#pragma GCC diagnostic ignored "-Wcast-qual"
uint8_t *sdata = (uint8_t *)send_data;
#pragma GCC diagnostic pop
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = sdata, .length = send_length }, &crc));
uint8_t buffer[RC522_MIFARE_BLOCK_SIZE + 2]; // +2 for CRC_A
memcpy(buffer, send_data, send_length);
buffer[send_length] = crc.lsb;
buffer[send_length + 1] = crc.msb;
send_length += 2;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = send_length },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
// The PICC must reply with a 4 bit ACK
if (result.bytes.length != 1 || result.valid_bits != 4) {
return ESP_FAIL; // TODO: use custom err
}
if (result.bytes.ptr[0] != RC522_MIFARE_ACK) {
return RC522_ERR_MIFARE_NACK;
}
return ESP_OK;
}
esp_err_t rc522_mifare_get_number_of_sectors(rc522_picc_type_t type, uint8_t *out_result)
{
RC522_CHECK(rc522_mifare_type_is_classic_compatible(type) == false);
RC522_CHECK(out_result == NULL);
switch (type) {
case RC522_PICC_TYPE_MIFARE_MINI:
// Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
*out_result = 5;
return ESP_OK;
case RC522_PICC_TYPE_MIFARE_1K:
// Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
*out_result = 16;
return ESP_OK;
case RC522_PICC_TYPE_MIFARE_4K:
// Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes.
*out_result = 40;
return ESP_OK;
default:
return ESP_FAIL;
}
}
inline uint8_t rc522_mifare_get_sector_index_by_block_address(uint8_t block_address)
{
if (block_address < 128) {
return block_address / 4;
}
else {
return 32 + ((block_address - 128) / 16);
}
}
inline esp_err_t rc522_mifare_get_number_of_blocks_in_sector(uint8_t sector_index, uint8_t *out_result)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_result == NULL);
if (sector_index < 32) {
*out_result = 4;
return ESP_OK;
}
*out_result = 16;
return ESP_OK;
}
inline esp_err_t rc522_mifare_get_sector_block_0_address(uint8_t sector_index, uint8_t *out_result)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_result == NULL);
if (sector_index < 32) {
*out_result = sector_index * 4;
return ESP_OK;
}
*out_result = 128 + (sector_index - 32) * 16;
return ESP_OK;
}
esp_err_t rc522_mifare_get_sector_desc(uint8_t sector_index, rc522_mifare_sector_desc_t *out_sector_desc)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_sector_desc == NULL);
rc522_mifare_sector_desc_t desc;
memset(&desc, 0, sizeof(desc));
desc.index = sector_index;
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_blocks_in_sector(sector_index, &desc.number_of_blocks));
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_0_address(sector_index, &desc.block_0_address));
memcpy(out_sector_desc, &desc, sizeof(rc522_mifare_sector_desc_t));
return ESP_OK;
}
static esp_err_t rc522_mifare_block_at_address_is_sector_trailer(uint8_t block_address, bool *result)
{
RC522_CHECK(result == NULL);
uint8_t sector_index = rc522_mifare_get_sector_index_by_block_address(block_address);
rc522_mifare_sector_desc_t sector = { 0 };
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_desc(sector_index, &sector));
*result = (block_address == (sector.block_0_address + sector.number_of_blocks - 1));
return ESP_OK;
}
static esp_err_t rc522_mifare_verify_access_bits_integrity(const uint8_t trailer_bytes[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(trailer_bytes == NULL);
uint8_t c1 = 0, c2 = 0, c3 = 0;
uint8_t c1_ = 0, c2_ = 0, c3_ = 0;
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[6], &c2_, &c1_));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, &c3_));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
if ((c1 != (~c1_ & 0x0F)) || (c2 != (~c2_ & 0x0F)) || (c3 != (~c3_ & 0x0F))) {
return RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION;
}
return ESP_OK;
}
esp_err_t rc522_mifare_write(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
const uint8_t buffer[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(!rc522_mifare_type_is_classic_compatible(picc->type));
bool is_trailer = false;
RC522_RETURN_ON_ERROR(rc522_mifare_block_at_address_is_sector_trailer(block_address, &is_trailer));
if (is_trailer) {
#ifdef CONFIG_RC522_PREVENT_SECTOR_TRAILER_WRITE
ESP_LOGE(TAG, "");
ESP_LOGE(TAG,
"The block at address %d that you are trying to update is a Sector Trailer block.",
block_address);
ESP_LOGE(TAG, "");
ESP_LOGE(TAG, "Writing to Sector Trailer blocks is prevented by default in the component configuration");
ESP_LOGE(TAG, "to protect inexperienced users from accidentally overwriting keys");
ESP_LOGE(TAG, "or writing incorrect access bits, which could make the sector unusable.");
ESP_LOGE(TAG, "");
ESP_LOGE(TAG, "If you know what you are doing, please use menuconfig to disable this protection.");
ESP_LOGE(TAG, "");
return RC522_ERR_MIFARE_SECTOR_TRAILER_WRITE_NOT_ALLOWED;
#endif
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(buffer));
}
RC522_LOGD("MIFARE WRITE (block_address=%02" RC522_X ")", block_address);
uint8_t cmd_buffer[] = { RC522_MIFARE_WRITE_CMD, block_address };
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, cmd_buffer, sizeof(cmd_buffer)));
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, buffer, RC522_MIFARE_BLOCK_SIZE));
return ESP_OK;
}
inline esp_err_t rc522_mifare_deauth(const rc522_handle_t rc522, const rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_stop_crypto1(rc522));
return ESP_OK;
}
esp_err_t rc522_mifare_get_desc(const rc522_picc_t *picc, rc522_mifare_desc_t *out_mifare_desc)
{
RC522_CHECK(picc == NULL);
RC522_CHECK(out_mifare_desc == NULL);
rc522_mifare_desc_t desc;
memset(&desc, 0, sizeof(desc));
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_sectors(picc->type, &desc.number_of_sectors));
memcpy(out_mifare_desc, &desc, sizeof(rc522_mifare_desc_t));
return ESP_OK;
}
static esp_err_t rc522_mifare_nibbles_to_access_bits(
uint8_t c1, uint8_t c2, uint8_t c3, uint8_t offset, rc522_mifare_access_bits_t *access_bits)
{
RC522_CHECK(offset > 3);
RC522_CHECK(access_bits == NULL);
rc522_mifare_access_bits_t bits = { 0 };
bits.c1 = (c1 & (1 << offset)) >> offset;
bits.c2 = (c2 & (1 << offset)) >> offset;
bits.c3 = (c3 & (1 << offset)) >> offset;
memcpy(access_bits, &bits, sizeof(bits));
return ESP_OK;
}
static esp_err_t rc522_mifare_parse_sector_trailer(
const uint8_t *trailer_bytes, uint8_t trailer_bytes_size, rc522_mifare_sector_trailer_info_t *out_trailer_info)
{
RC522_CHECK(trailer_bytes == NULL);
RC522_CHECK(trailer_bytes_size != RC522_MIFARE_BLOCK_SIZE);
RC522_CHECK(out_trailer_info == NULL);
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(trailer_bytes));
uint8_t c1 = 0, c2 = 0, c3 = 0;
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, NULL));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
rc522_mifare_sector_trailer_info_t info = { 0 };
for (uint8_t i = 0; i < 4; i++) {
RC522_RETURN_ON_ERROR(rc522_mifare_nibbles_to_access_bits(c1, c2, c3, i, &info.access_bits[i]));
}
memcpy(out_trailer_info, &info, sizeof(info));
return ESP_OK;
}
/**
* Checks if block is Value block based on access bits
*/
inline static bool rc522_mifare_block_is_value(rc522_mifare_access_bits_t access_bits)
{
uint8_t bits = (access_bits.c1 << 2) | (access_bits.c2 << 1) | (access_bits.c3 << 0);
return bits == 0b110 || bits == 0b001;
}
static esp_err_t rc522_mifare_parse_value_block(
const uint8_t *bytes, uint8_t bytes_size, rc522_mifare_sector_value_block_info_t *out_value_block_info)
{
RC522_CHECK(bytes == NULL);
RC522_CHECK(bytes_size != RC522_MIFARE_BLOCK_SIZE);
RC522_CHECK(out_value_block_info == NULL);
rc522_mifare_sector_value_block_info_t value_block_info;
memset(&value_block_info, 0, sizeof(value_block_info));
value_block_info.value = 0;
value_block_info.value |= ((int32_t)(bytes[3]) << (8 * 3));
value_block_info.value |= ((int32_t)(bytes[2]) << (8 * 2));
value_block_info.value |= ((int32_t)(bytes[1]) << (8 * 1));
value_block_info.value |= ((int32_t)(bytes[0]) << (8 * 0));
value_block_info.addr = bytes[12];
// TODO: Check for integrity violation
// Use RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION
memcpy(out_value_block_info, &value_block_info, sizeof(value_block_info));
return ESP_OK;
}
inline static esp_err_t rc522_mifare_get_sector_block_group_index(
const rc522_mifare_sector_desc_t *sector, uint8_t block_offset, uint8_t *out_group)
{
RC522_CHECK(sector == NULL);
RC522_CHECK(out_group == NULL);
RC522_CHECK(block_offset >= sector->number_of_blocks);
if (sector->number_of_blocks > 4) {
*out_group = block_offset / 5;
return ESP_OK;
}
*out_group = block_offset;
return ESP_OK;
}
esp_err_t rc522_mifare_read_sector_trailer_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, rc522_mifare_sector_block_t *out_trailer)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(sector_desc == NULL);
RC522_CHECK(out_trailer == NULL);
rc522_mifare_sector_block_t block;
memset(&block, 0x00, sizeof(block));
block.address = (sector_desc->block_0_address + sector_desc->number_of_blocks - 1);
block.type = RC522_MIFARE_BLOCK_TRAILER;
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
block.error = rc522_mifare_parse_sector_trailer(block.bytes, sizeof(block.bytes), &block.trailer_info);
memcpy(&block.access_bits, &block.trailer_info.access_bits[3], sizeof(rc522_mifare_access_bits_t));
memcpy(out_trailer, &block, sizeof(block));
return ESP_OK;
}
esp_err_t rc522_mifare_read_sector_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_sector_block_t *trailer, uint8_t block_offset,
rc522_mifare_sector_block_t *out_block)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(sector_desc == NULL);
RC522_CHECK(trailer == NULL);
RC522_CHECK(block_offset >= sector_desc->number_of_blocks);
RC522_CHECK(out_block == NULL);
RC522_CHECK_WITH_MESSAGE(block_offset == sector_desc->number_of_blocks - 1,
"use rc522_mifare_read_sector_trailer_block() to read the sector trailer block");
RC522_CHECK(trailer->type != RC522_MIFARE_BLOCK_TRAILER);
rc522_mifare_sector_block_t block;
memset(&block, 0, sizeof(block));
block.address = (sector_desc->block_0_address + block_offset);
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
uint8_t group = 0;
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_group_index(sector_desc, block_offset, &group));
memcpy(&block.access_bits, &trailer->trailer_info.access_bits[group], sizeof(rc522_mifare_access_bits_t));
if (block.address == 0) {
block.type = RC522_MIFARE_BLOCK_MANUFACTURER_DATA;
}
else if (rc522_mifare_block_is_value(block.access_bits)) {
block.type = RC522_MIFARE_BLOCK_VALUE;
block.error = rc522_mifare_parse_value_block(block.bytes, sizeof(block.bytes), &block.value_info);
}
else {
block.type = RC522_MIFARE_BLOCK_DATA;
}
memcpy(out_block, &block, sizeof(block));
return ESP_OK;
}

View File

@@ -0,0 +1,618 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_picc.h"
#include "rc522_types_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "picc/rc522_nxp.h"
#include "picc/rc522_mifare.h"
RC522_LOG_DEFINE_BASE();
const uint8_t RC522_NXP_DEFAULT_PWD[RC522_NXP_PWD_SIZE] = { 0xFF, 0xFF, 0xFF, 0xFF };
const uint8_t RC522_NXP_DEFAULT_PACK[RC522_NXP_PACK_SIZE] = { 0x00 };
// Helper to check if the card responded with a NACK
inline esp_err_t rc522_nxp_check_for_nak(rc522_picc_transaction_result_t *result)
{
if (result->bytes.length == 1 && result->valid_bits == 4 && result->bytes.ptr[0] != RC522_MIFARE_ACK) {
return RC522_ERR_NXP_NACK;
}
return ESP_OK;
}
// WRITE supported? (not classic write - see COMPAT_WRITE)
inline bool rc522_nxp_type_has_write(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
case RC522_PICC_TYPE_MIFARE_UL_C:
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG2xx:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// FAST_READ supported?
inline bool rc522_nxp_type_has_fast_read(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// READ_CNT supported?
inline bool rc522_nxp_type_has_read_cnt(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// PWD_AUTH supported?
inline bool rc522_nxp_type_has_pwd_auth(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// Get originality signature size, or 0 if not supported
inline uint8_t rc522_nxp_type_sig_size(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 48;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return 32;
default:
return 0;
}
}
// Convert GET_VERSION response to type
static rc522_picc_type_t rc522_nxp_type_from_version(rc522_nxp_picc_version_t *version)
{
rc522_nxp_product_type_t type = version->product_type;
rc522_nxp_major_version_t major_version = version->major_version;
uint8_t size = version->storage_size;
if (type == RC522_NXP_PRODUCT_TYPE_NTAG) {
if (major_version != RC522_NXP_MAJ_VER_NTAG21) { // Not an NTAG21x
RC522_LOGW("Unknown NTAG product version: %02" RC522_X, major_version);
}
else if (size == 0x0F) { // 0x0F => between 128 and 256 user bytes
return RC522_PICC_TYPE_NTAG213;
}
else if (size == 0x11) { // 0x11 => between 256 and 512 user bytes
return RC522_PICC_TYPE_NTAG215;
}
else if (size == 0x13) { // 0x13 => between 512 and 1024 user byets
return RC522_PICC_TYPE_NTAG216;
}
else { // Unknown size?
RC522_LOGW("Unknown NTAG size: %02" RC522_X, size);
}
}
else if (type == RC522_NXP_PRODUCT_TYPE_UL) {
if (major_version == RC522_NXP_MAJ_VER_UL_EV1) {
if (size == 0x0B) { // 0x0B => between 32 and 64 user bytes
return RC522_PICC_TYPE_MIFARE_UL_EV1_1;
}
else if (size == 0x0E) { // 0x0E => 128 user bytes
return RC522_PICC_TYPE_MIFARE_UL_EV1_2;
}
}
else if (major_version == RC522_NXP_MAJ_VER_UL_NANO) {
return RC522_PICC_TYPE_MIFARE_UL_NANO;
}
else if (major_version == RC522_NXP_MAJ_VER_UL_AES) {
return RC522_PICC_TYPE_MIFARE_UL_AES;
}
else {
RC522_LOGW("Unknown UL product version: %02" RC522_X, major_version);
}
}
else {
RC522_LOGE("Unknown product type: %02" RC522_X, type);
}
return RC522_PICC_TYPE_UNKNOWN;
}
uint8_t rc522_nxp_get_user_page_count(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 12;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 36;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 12;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 32;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 10;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 36;
case RC522_PICC_TYPE_NTAG213:
return 36;
case RC522_PICC_TYPE_NTAG215:
return 126;
case RC522_PICC_TYPE_NTAG216:
return 222;
default:
return 0;
}
}
uint8_t rc522_nxp_get_user_mem_start(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
case RC522_PICC_TYPE_MIFARE_UL_C:
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG2xx:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return 0x04;
default:
return 0;
}
}
uint8_t rc522_nxp_get_user_mem_end(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 0x0F;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 0x27;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 0x0F;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 0x23;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 0x0D;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 0x27;
case RC522_PICC_TYPE_NTAG213:
return 0x27;
case RC522_PICC_TYPE_NTAG215:
return 0x81;
case RC522_PICC_TYPE_NTAG216:
return 0xE1;
default:
return 0;
}
}
uint8_t rc522_nxp_get_page_count(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 16;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 48;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 20;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 41;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 14;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 60;
case RC522_PICC_TYPE_NTAG213:
return 45;
case RC522_PICC_TYPE_NTAG215:
return 135;
case RC522_PICC_TYPE_NTAG216:
return 231;
default:
return 0;
}
}
esp_err_t rc522_nxp_keyauth_supported(const rc522_handle_t rc522, const rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_LOGD("AUTHENTICATE (support check)");
uint8_t buffer[11] = { 0 }; // combined send(4)/recv(11) buffer
buffer[0] = RC522_PICC_CMD_UL_AUTH;
// buffer[1] is argument; 0x00 is fine for support check
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc));
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = 4 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret == ESP_OK && buffer[0] != 0xAF) {
return RC522_ERR_NXP_NACK;
}
return ret;
}
esp_err_t rc522_nxp_get_type(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_type_t *out_type)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_type == NULL);
RC522_CHECK(picc->type != RC522_PICC_TYPE_MIFARE_UL);
RC522_LOGD("Attempting to get NXP PICC type");
RC522_LOGD("Checking for GET_VERSION support...");
// In case of early exit due to errors
rc522_picc_type_t type = RC522_PICC_TYPE_MIFARE_UL;
rc522_nxp_picc_version_t version_info = { 0 };
esp_err_t ret = rc522_nxp_get_version(rc522, picc, &version_info);
if (ret == ESP_OK) { // GET_VERSION supported
type = rc522_nxp_type_from_version(&version_info);
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT) { // No GET_VERSION
// Failure probably resulted in a HALT, so make sure we can still connect
RC522_LOGD("No L3 GET_VERSION; reselecting");
rc522_picc_uid_t uid;
uint8_t sak;
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
// Fall back to checking for authentication availability (C)
RC522_LOGD("Checking for authentication support...");
ret = rc522_nxp_keyauth_supported(rc522, picc);
if (ret == ESP_OK) {
// Chip supports authentication
// TODO: Do we need to complete authentication, or can we leave it here?
// Note datasheet also mentions a "MIFARE Hospitality" here, but there
// doesn't seem to be any more information about it
type = RC522_PICC_TYPE_MIFARE_UL_C;
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_MIFARE_NACK) { // Not supported
// Only Ultralight without auth is the original
type = RC522_PICC_TYPE_MIFARE_UL_;
}
else { // Some other error in AUTHENTICATE
RC522_RETURN_ON_ERROR(ret);
}
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
}
else { // A different error occured in GET_VERSION
RC522_RETURN_ON_ERROR(ret);
}
RC522_LOGD("Found %s", rc522_picc_type_name(type));
*out_type = type;
return ESP_OK;
}
esp_err_t rc522_nxp_get_version(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_picc_version_t *out_version)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_version == NULL);
RC522_LOGD("GET_VERSION");
uint8_t buffer[10]; // Combined buffer; response is 8-byte data + 2-byte CRC
buffer[0] = RC522_PICC_CMD_GETV;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 1 }, &crc));
buffer[1] = crc.lsb;
buffer[2] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = 3 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
memcpy(out_version, buffer, sizeof(rc522_nxp_picc_version_t));
return ESP_OK;
}
esp_err_t rc522_nxp_read(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, uint8_t out_buffer[RC522_NXP_PAGE_SIZE * 4])
{
// Same protocol as MIFARE cards, so defer
return rc522_mifare_read(rc522, picc, address, out_buffer);
}
esp_err_t rc522_nxp_fast_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t start_page,
uint8_t end_page, rc522_nxp_fast_read_data_t *out_buffer)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_buffer == NULL);
RC522_CHECK(!rc522_nxp_type_has_fast_read(picc->type));
// some sanity checks - valid range, output buffer sufficiently large
RC522_CHECK(start_page > end_page);
RC522_CHECK(out_buffer->buffer_size < (end_page - start_page + 1) * 4);
RC522_LOGD("NXP FAST_READ (start=%02" RC522_X ", end=%02" RC522_X ")", start_page, end_page);
uint8_t cmd_buffer[5] = { RC522_NXP_FAST_READ, start_page, end_page, 0, 0 };
uint8_t byte_count = (end_page - start_page + 1) * 4;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = 3,
},
&crc));
cmd_buffer[3] = crc.lsb;
cmd_buffer[4] = crc.msb;
// I'd rather not malloc() here, but we need extra space for the CRC which
// we're not guaranteed to have in out_buffer
// TODO: Allow for separate CRC fetch and processing to avoid this?
uint8_t *recv_buffer = malloc(byte_count + 2); // Allow for CRC_A
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
.check_crc = true,
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = recv_buffer, .length = byte_count + 2 },
};
// Bunch of manual handling here to make sure free() is called
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
else if ((result.bytes.length - 2) != byte_count) {
free(recv_buffer);
return ESP_FAIL;
}
memcpy(out_buffer->bytes, recv_buffer, byte_count);
out_buffer->read_size = byte_count;
free(recv_buffer);
return ESP_OK;
}
esp_err_t rc522_nxp_write(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, const uint8_t buffer[RC522_NXP_PAGE_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(!rc522_nxp_type_has_write(picc->type));
RC522_LOGD("NXP WRITE (address=%02" RC522_X ")", address);
uint8_t cmd_buffer[RC522_NXP_PAGE_SIZE + 4] = { 0 };
cmd_buffer[0] = RC522_NXP_WRITE;
cmd_buffer[1] = address;
memcpy(&cmd_buffer[2], buffer, RC522_NXP_PAGE_SIZE);
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2 + RC522_NXP_PAGE_SIZE] = crc.lsb;
cmd_buffer[2 + RC522_NXP_PAGE_SIZE + 1] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
return rc522_nxp_check_for_nak(&result);
}
esp_err_t rc522_nxp_read_cnt(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t counter_no, uint32_t *out_count)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_count == NULL);
RC522_CHECK(!rc522_nxp_type_has_read_cnt(picc->type));
RC522_LOGD("NXP READ_CNT (counter=%02" RC522_X ")", counter_no);
uint8_t cmd_buffer[5] = {
// send(4)/recv(5) buffer
RC522_NXP_READ_CNT,
counter_no,
0,
0, // CRC
0 // Space for reply (3 bytes + CRC)
};
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = 4 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
// TODO: Verify this counter is correct
*out_count = (cmd_buffer[2] << 16) | (cmd_buffer[1] << 8) | cmd_buffer[0];
return ESP_OK;
}
esp_err_t rc522_nxp_pwd_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
const uint8_t pwd[RC522_NXP_PWD_SIZE], const uint8_t pack[RC522_NXP_PACK_SIZE], rc522_picc_state_t *out_state)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(pwd == NULL);
RC522_CHECK(pack == NULL);
RC522_CHECK(!rc522_nxp_type_has_pwd_auth(picc->type));
RC522_LOGD("NXP PWD_AUTH");
uint8_t cmd_buffer[RC522_NXP_PWD_SIZE + 3] = { 0 }; // send(11)/recv(4) buffer
cmd_buffer[0] = RC522_NXP_PWD_AUTH;
memcpy(&cmd_buffer[1], pwd, RC522_NXP_PWD_SIZE);
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[1 + RC522_NXP_PWD_SIZE] = crc.lsb;
cmd_buffer[1 + RC522_NXP_PWD_SIZE + 1] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
// If authentication fails (timeout or NAK), the card will HALT, so assume
// it has until we know otherwise
*out_state = RC522_PICC_STATE_HALT;
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
// Successful authentication, set state
*out_state = RC522_PICC_STATE_AUTHENTICATED;
// Validate the PACK - not critical to authenticate the card, but good
// practice to verify
RC522_CHECK_AND_RETURN(memcmp(pack, cmd_buffer, RC522_NXP_PACK_SIZE) != 0, RC522_ERR_NXP_PACK_MISMATCH);
*out_state = RC522_PICC_STATE_AUTHENTICATED;
return ESP_OK;
}
esp_err_t rc522_nxp_read_sig(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_sig_t *out_sig)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_sig == NULL);
uint8_t sig_size = rc522_nxp_type_sig_size(picc->type);
RC522_CHECK(sig_size == 0);
RC522_CHECK(out_sig->buffer_size < sig_size);
RC522_LOGD("NXP READ_SIG (size=%d)", sig_size);
uint8_t cmd_buffer[4] = {
RC522_NXP_READ_SIG,
0,
0,
0 // CRC
};
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
// TODO: Combine to single malloc() for both buffers?
// Again, I'd rather not do a large malloc() here, but we need the space for
// CRC. See TODO in FAST_READ
uint8_t *recv_buffer = malloc(sig_size + 2); // +2 for CRC
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = recv_buffer, .length = sig_size + 2 },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
ret = rc522_nxp_check_for_nak(&result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
memcpy(out_sig->bytes, recv_buffer, sig_size);
out_sig->sig_size = sig_size;
free(recv_buffer);
return ESP_OK;
}