mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-24 03:29:57 -07:00
NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring. Starring: - @gornekich - NFC refactoring project lead, architect, senior developer - @gsurkov - architect, senior developer - @RebornedBrain - senior developer Supporting roles: - @skotopes, @DrZlo13, @hedger - general architecture advisors, code review - @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance Special thanks: @bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
#include "crypto1.h"
|
||||
#include "nfc_util.h"
|
||||
#include <furi.h>
|
||||
|
||||
// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
|
||||
|
||||
#define SWAPENDIAN(x) \
|
||||
((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
|
||||
#define LF_POLY_ODD (0x29CE5C)
|
||||
#define LF_POLY_EVEN (0x870804)
|
||||
|
||||
#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24)
|
||||
|
||||
void crypto1_reset(Crypto1* crypto1) {
|
||||
furi_assert(crypto1);
|
||||
crypto1->even = 0;
|
||||
crypto1->odd = 0;
|
||||
}
|
||||
|
||||
void crypto1_init(Crypto1* crypto1, uint64_t key) {
|
||||
furi_assert(crypto1);
|
||||
crypto1->even = 0;
|
||||
crypto1->odd = 0;
|
||||
for(int8_t i = 47; i > 0; i -= 2) {
|
||||
crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7);
|
||||
crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t crypto1_filter(uint32_t in) {
|
||||
uint32_t out = 0;
|
||||
out = 0xf22c0 >> (in & 0xf) & 16;
|
||||
out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8;
|
||||
out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4;
|
||||
out |= 0x1e458 >> (in >> 12 & 0xf) & 2;
|
||||
out |= 0x0d938 >> (in >> 16 & 0xf) & 1;
|
||||
return FURI_BIT(0xEC57E80A, out);
|
||||
}
|
||||
|
||||
uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint8_t out = crypto1_filter(crypto1->odd);
|
||||
uint32_t feed = out & (!!is_encrypted);
|
||||
feed ^= !!in;
|
||||
feed ^= LF_POLY_ODD & crypto1->odd;
|
||||
feed ^= LF_POLY_EVEN & crypto1->even;
|
||||
crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed));
|
||||
|
||||
FURI_SWAP(crypto1->odd, crypto1->even);
|
||||
return out;
|
||||
}
|
||||
|
||||
uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint8_t out = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint32_t out = 0;
|
||||
for(uint8_t i = 0; i < 32; i++) {
|
||||
out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n) {
|
||||
SWAPENDIAN(x);
|
||||
while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
|
||||
|
||||
return SWAPENDIAN(x);
|
||||
}
|
||||
|
||||
void crypto1_decrypt(
|
||||
Crypto1* crypto,
|
||||
uint8_t* encrypted_data,
|
||||
uint16_t encrypted_data_bits,
|
||||
uint8_t* decrypted_data) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(encrypted_data);
|
||||
furi_assert(decrypted_data);
|
||||
|
||||
if(encrypted_data_bits < 8) {
|
||||
uint8_t decrypted_byte = 0;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3;
|
||||
decrypted_data[0] = decrypted_byte;
|
||||
} else {
|
||||
for(size_t i = 0; i < encrypted_data_bits / 8; i++) {
|
||||
decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crypto1_encrypt(
|
||||
Crypto1* crypto,
|
||||
uint8_t* keystream,
|
||||
uint8_t* plain_data,
|
||||
uint16_t plain_data_bits,
|
||||
uint8_t* encrypted_data,
|
||||
uint8_t* encrypted_parity) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(plain_data);
|
||||
furi_assert(encrypted_data);
|
||||
furi_assert(encrypted_parity);
|
||||
|
||||
if(plain_data_bits < 8) {
|
||||
encrypted_data[0] = 0;
|
||||
for(size_t i = 0; i < plain_data_bits; i++) {
|
||||
encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
|
||||
}
|
||||
} else {
|
||||
memset(encrypted_parity, 0, plain_data_bits / 8 + 1);
|
||||
for(uint8_t i = 0; i < plain_data_bits / 8; i++) {
|
||||
encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
|
||||
plain_data[i];
|
||||
encrypted_parity[i / 8] |=
|
||||
(((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01)
|
||||
<< (7 - (i & 0x0007)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,444 +0,0 @@
|
||||
#include "emv.h"
|
||||
|
||||
#include <core/common_defines.h>
|
||||
|
||||
#define TAG "Emv"
|
||||
|
||||
const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information
|
||||
const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type
|
||||
const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator
|
||||
const PDOLValue pdol_term_trans_qualifies = {
|
||||
0x9F66,
|
||||
{0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers
|
||||
const PDOLValue pdol_addtnl_term_qualifies = {
|
||||
0x9F40,
|
||||
{0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers
|
||||
const PDOLValue pdol_amount_authorise = {
|
||||
0x9F02,
|
||||
{0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised
|
||||
const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount
|
||||
const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code
|
||||
const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code
|
||||
const PDOLValue pdol_term_verification = {
|
||||
0x95,
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results
|
||||
const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date
|
||||
const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type
|
||||
const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert
|
||||
const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number
|
||||
|
||||
const PDOLValue* const pdol_values[] = {
|
||||
&pdol_term_info,
|
||||
&pdol_term_type,
|
||||
&pdol_merchant_type,
|
||||
&pdol_term_trans_qualifies,
|
||||
&pdol_addtnl_term_qualifies,
|
||||
&pdol_amount_authorise,
|
||||
&pdol_amount,
|
||||
&pdol_country_code,
|
||||
&pdol_currency_code,
|
||||
&pdol_term_verification,
|
||||
&pdol_transaction_date,
|
||||
&pdol_transaction_type,
|
||||
&pdol_transaction_cert,
|
||||
&pdol_unpredict_number,
|
||||
};
|
||||
|
||||
static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E,
|
||||
0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31,
|
||||
0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07,
|
||||
0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x50, 0x04,
|
||||
0x56, 0x49, 0x53, 0x41, 0x87, 0x01, 0x01, 0x90, 0x00};
|
||||
static const uint8_t select_app_ans[] = {0x6F, 0x20, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03,
|
||||
0x10, 0x10, 0xA5, 0x15, 0x50, 0x04, 0x56, 0x49, 0x53,
|
||||
0x41, 0x9F, 0x38, 0x0C, 0x9F, 0x66, 0x04, 0x9F, 0x02,
|
||||
0x06, 0x9F, 0x37, 0x04, 0x5F, 0x2A, 0x02, 0x90, 0x00};
|
||||
static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x13, 0x55, 0x70,
|
||||
0x73, 0x83, 0x85, 0x87, 0x73, 0x31, 0xD1, 0x80, 0x22, 0x01,
|
||||
0x38, 0x84, 0x77, 0x94, 0x00, 0x00, 0x1F, 0x5F, 0x34, 0x01,
|
||||
0x00, 0x9F, 0x10, 0x07, 0x06, 0x01, 0x11, 0x03, 0x80, 0x00,
|
||||
0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96,
|
||||
0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06,
|
||||
0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00};
|
||||
|
||||
static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) {
|
||||
if(furi_log_get_level() == FuriLogLevelTrace) {
|
||||
FURI_LOG_T(TAG, "%s", message);
|
||||
printf("TX: ");
|
||||
for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) {
|
||||
printf("%02X ", tx_rx->tx_data[i]);
|
||||
}
|
||||
printf("\r\nRX: ");
|
||||
for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) {
|
||||
printf("%02X ", tx_rx->rx_data[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) {
|
||||
uint16_t i = 0;
|
||||
uint16_t tag = 0, first_byte = 0;
|
||||
uint16_t tlen = 0;
|
||||
bool success = false;
|
||||
|
||||
while(i < len) {
|
||||
first_byte = buff[i];
|
||||
if((first_byte & 31) == 31) { // 2-byte tag
|
||||
tag = buff[i] << 8 | buff[i + 1];
|
||||
i++;
|
||||
FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag);
|
||||
} else {
|
||||
tag = buff[i];
|
||||
FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag);
|
||||
}
|
||||
i++;
|
||||
tlen = buff[i];
|
||||
if((tlen & 128) == 128) { // long length value
|
||||
i++;
|
||||
tlen = buff[i];
|
||||
FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen);
|
||||
} else {
|
||||
FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen);
|
||||
}
|
||||
i++;
|
||||
if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse
|
||||
FURI_LOG_T(TAG, "Constructed TLV %x", tag);
|
||||
if(!emv_decode_response(&buff[i], tlen, app)) {
|
||||
FURI_LOG_T(TAG, "Failed to decode response for %x", tag);
|
||||
// return false;
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
switch(tag) {
|
||||
case EMV_TAG_AID:
|
||||
app->aid_len = tlen;
|
||||
memcpy(app->aid, &buff[i], tlen);
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AID %x", tag);
|
||||
break;
|
||||
case EMV_TAG_PRIORITY:
|
||||
memcpy(&app->priority, &buff[i], tlen);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_CARD_NAME:
|
||||
memcpy(app->name, &buff[i], tlen);
|
||||
app->name[tlen] = '\0';
|
||||
app->name_found = true;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
|
||||
break;
|
||||
case EMV_TAG_PDOL:
|
||||
memcpy(app->pdol.data, &buff[i], tlen);
|
||||
app->pdol.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_AFL:
|
||||
memcpy(app->afl.data, &buff[i], tlen);
|
||||
app->afl.size = tlen;
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_TRACK_1_EQUIV: {
|
||||
char track_1_equiv[80];
|
||||
memcpy(track_1_equiv, &buff[i], tlen);
|
||||
track_1_equiv[tlen] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_TRACK_2_EQUIV: {
|
||||
// 0xD0 delimits PAN from expiry (YYMM)
|
||||
for(int x = 1; x < tlen; x++) {
|
||||
if(buff[i + x + 1] > 0xD0) {
|
||||
memcpy(app->card_number, &buff[i], x + 1);
|
||||
app->card_number_len = x + 1;
|
||||
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
|
||||
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 4-bit to ASCII representation
|
||||
char track_2_equiv[41];
|
||||
uint8_t track_2_equiv_len = 0;
|
||||
for(int x = 0; x < tlen; x++) {
|
||||
char top = (buff[i + x] >> 4) + '0';
|
||||
char bottom = (buff[i + x] & 0x0F) + '0';
|
||||
track_2_equiv[x * 2] = top;
|
||||
track_2_equiv_len++;
|
||||
if(top == '?') break;
|
||||
track_2_equiv[x * 2 + 1] = bottom;
|
||||
track_2_equiv_len++;
|
||||
if(bottom == '?') break;
|
||||
}
|
||||
track_2_equiv[track_2_equiv_len] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_PAN:
|
||||
memcpy(app->card_number, &buff[i], tlen);
|
||||
app->card_number_len = tlen;
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_EXP_DATE:
|
||||
app->exp_year = buff[i];
|
||||
app->exp_month = buff[i + 1];
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_CURRENCY_CODE:
|
||||
app->currency_code = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
break;
|
||||
case EMV_TAG_COUNTRY_CODE:
|
||||
app->country_code = (buff[i] << 8 | buff[i + 1]);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i += tlen;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
|
||||
bool app_aid_found = false;
|
||||
const uint8_t emv_select_ppse_cmd[] = {
|
||||
0x00, 0xA4, // SELECT ppse
|
||||
0x04, 0x00, // P1:By name, P2: empty
|
||||
0x0e, // Lc: Data length
|
||||
0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string:
|
||||
0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE)
|
||||
0x00 // Le
|
||||
};
|
||||
|
||||
memcpy(tx_rx->tx_data, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd));
|
||||
tx_rx->tx_bits = sizeof(emv_select_ppse_cmd) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
FURI_LOG_D(TAG, "Send select PPSE");
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
|
||||
emv_trace(tx_rx, "Select PPSE answer:");
|
||||
if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
|
||||
app_aid_found = true;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to parse application");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed select PPSE");
|
||||
}
|
||||
|
||||
return app_aid_found;
|
||||
}
|
||||
|
||||
static bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
|
||||
app->app_started = false;
|
||||
const uint8_t emv_select_header[] = {
|
||||
0x00,
|
||||
0xA4, // SELECT application
|
||||
0x04,
|
||||
0x00 // P1:By name, P2:First or only occurence
|
||||
};
|
||||
uint16_t size = sizeof(emv_select_header);
|
||||
|
||||
// Copy header
|
||||
memcpy(tx_rx->tx_data, emv_select_header, size);
|
||||
// Copy AID
|
||||
tx_rx->tx_data[size++] = app->aid_len;
|
||||
memcpy(&tx_rx->tx_data[size], app->aid, app->aid_len);
|
||||
size += app->aid_len;
|
||||
tx_rx->tx_data[size++] = 0x00;
|
||||
tx_rx->tx_bits = size * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
FURI_LOG_D(TAG, "Start application");
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
|
||||
emv_trace(tx_rx, "Start application answer:");
|
||||
if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
|
||||
app->app_started = true;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read PAN or PDOL");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to start application");
|
||||
}
|
||||
|
||||
return app->app_started;
|
||||
}
|
||||
|
||||
static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) {
|
||||
bool tag_found;
|
||||
for(uint16_t i = 0; i < src->size; i++) {
|
||||
tag_found = false;
|
||||
for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) {
|
||||
if(src->data[i] == pdol_values[j]->tag) {
|
||||
// Found tag with 1 byte length
|
||||
uint8_t len = src->data[++i];
|
||||
memcpy(dest->data + dest->size, pdol_values[j]->data, len);
|
||||
dest->size += len;
|
||||
tag_found = true;
|
||||
break;
|
||||
} else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) {
|
||||
// Found tag with 2 byte length
|
||||
i += 2;
|
||||
uint8_t len = src->data[i];
|
||||
memcpy(dest->data + dest->size, pdol_values[j]->data, len);
|
||||
dest->size += len;
|
||||
tag_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!tag_found) {
|
||||
// Unknown tag, fill zeros
|
||||
i += 2;
|
||||
uint8_t len = src->data[i];
|
||||
memset(dest->data + dest->size, 0, len);
|
||||
dest->size += len;
|
||||
}
|
||||
}
|
||||
return dest->size;
|
||||
}
|
||||
|
||||
static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
|
||||
bool card_num_read = false;
|
||||
const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00};
|
||||
uint16_t size = sizeof(emv_gpo_header);
|
||||
|
||||
// Copy header
|
||||
memcpy(tx_rx->tx_data, emv_gpo_header, size);
|
||||
APDU pdol_data = {0, {0}};
|
||||
// Prepare and copy pdol parameters
|
||||
emv_prepare_pdol(&pdol_data, &app->pdol);
|
||||
tx_rx->tx_data[size++] = 0x02 + pdol_data.size;
|
||||
tx_rx->tx_data[size++] = 0x83;
|
||||
tx_rx->tx_data[size++] = pdol_data.size;
|
||||
memcpy(tx_rx->tx_data + size, pdol_data.data, pdol_data.size);
|
||||
size += pdol_data.size;
|
||||
tx_rx->tx_data[size++] = 0;
|
||||
tx_rx->tx_bits = size * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
FURI_LOG_D(TAG, "Get proccessing options");
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
|
||||
emv_trace(tx_rx, "Get processing options answer:");
|
||||
if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
|
||||
if(app->card_number_len > 0) {
|
||||
card_num_read = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to get processing options");
|
||||
}
|
||||
|
||||
return card_num_read;
|
||||
}
|
||||
|
||||
static bool emv_read_sfi_record(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
EmvApplication* app,
|
||||
uint8_t sfi,
|
||||
uint8_t record_num) {
|
||||
bool card_num_read = false;
|
||||
uint8_t sfi_param = (sfi << 3) | (1 << 2);
|
||||
uint8_t emv_sfi_header[] = {
|
||||
0x00,
|
||||
0xB2, // READ RECORD
|
||||
record_num, // P1:record_number
|
||||
sfi_param, // P2:SFI
|
||||
0x00 // Le
|
||||
};
|
||||
|
||||
memcpy(tx_rx->tx_data, emv_sfi_header, sizeof(emv_sfi_header));
|
||||
tx_rx->tx_bits = sizeof(emv_sfi_header) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
|
||||
emv_trace(tx_rx, "SFI record:");
|
||||
if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) {
|
||||
card_num_read = true;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num);
|
||||
}
|
||||
|
||||
return card_num_read;
|
||||
}
|
||||
|
||||
static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) {
|
||||
bool card_num_read = false;
|
||||
|
||||
if(app->afl.size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Search PAN in SFI");
|
||||
// Iterate through all files
|
||||
for(size_t i = 0; i < app->afl.size; i += 4) {
|
||||
uint8_t sfi = app->afl.data[i] >> 3;
|
||||
uint8_t record_start = app->afl.data[i + 1];
|
||||
uint8_t record_end = app->afl.data[i + 2];
|
||||
// Iterate through all records in file
|
||||
for(uint8_t record = record_start; record <= record_end; ++record) {
|
||||
card_num_read |= emv_read_sfi_record(tx_rx, app, sfi, record);
|
||||
}
|
||||
}
|
||||
|
||||
return card_num_read;
|
||||
}
|
||||
|
||||
bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(emv_app);
|
||||
bool card_num_read = false;
|
||||
memset(emv_app, 0, sizeof(EmvApplication));
|
||||
|
||||
do {
|
||||
if(!emv_select_ppse(tx_rx, emv_app)) break;
|
||||
if(!emv_select_app(tx_rx, emv_app)) break;
|
||||
if(emv_get_processing_options(tx_rx, emv_app)) {
|
||||
card_num_read = true;
|
||||
} else {
|
||||
card_num_read = emv_read_files(tx_rx, emv_app);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return card_num_read;
|
||||
}
|
||||
|
||||
bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(tx_rx);
|
||||
bool emulation_complete = false;
|
||||
tx_rx->tx_bits = 0;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
do {
|
||||
FURI_LOG_D(TAG, "Read select PPSE command");
|
||||
if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
|
||||
|
||||
memcpy(tx_rx->tx_data, select_ppse_ans, sizeof(select_ppse_ans));
|
||||
tx_rx->tx_bits = sizeof(select_ppse_ans) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
FURI_LOG_D(TAG, "Send select PPSE answer and read select App command");
|
||||
if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
|
||||
|
||||
memcpy(tx_rx->tx_data, select_app_ans, sizeof(select_app_ans));
|
||||
tx_rx->tx_bits = sizeof(select_app_ans) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
FURI_LOG_D(TAG, "Send select App answer and read get PDOL command");
|
||||
if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
|
||||
|
||||
memcpy(tx_rx->tx_data, pdol_ans, sizeof(pdol_ans));
|
||||
tx_rx->tx_bits = sizeof(pdol_ans) * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
FURI_LOG_D(TAG, "Send get PDOL answer");
|
||||
if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
|
||||
|
||||
emulation_complete = true;
|
||||
} while(false);
|
||||
|
||||
return emulation_complete;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define MAX_APDU_LEN 255
|
||||
|
||||
#define EMV_TAG_APP_TEMPLATE 0x61
|
||||
#define EMV_TAG_AID 0x4F
|
||||
#define EMV_TAG_PRIORITY 0x87
|
||||
#define EMV_TAG_PDOL 0x9F38
|
||||
#define EMV_TAG_CARD_NAME 0x50
|
||||
#define EMV_TAG_FCI 0xBF0C
|
||||
#define EMV_TAG_LOG_CTRL 0x9F4D
|
||||
#define EMV_TAG_TRACK_1_EQUIV 0x56
|
||||
#define EMV_TAG_TRACK_2_EQUIV 0x57
|
||||
#define EMV_TAG_PAN 0x5A
|
||||
#define EMV_TAG_AFL 0x94
|
||||
#define EMV_TAG_EXP_DATE 0x5F24
|
||||
#define EMV_TAG_COUNTRY_CODE 0x5F28
|
||||
#define EMV_TAG_CURRENCY_CODE 0x9F42
|
||||
#define EMV_TAG_CARDHOLDER_NAME 0x5F20
|
||||
|
||||
typedef struct {
|
||||
char name[32];
|
||||
uint8_t aid[16];
|
||||
uint16_t aid_len;
|
||||
uint8_t number[10];
|
||||
uint8_t number_len;
|
||||
uint8_t exp_mon;
|
||||
uint8_t exp_year;
|
||||
uint16_t country_code;
|
||||
uint16_t currency_code;
|
||||
} EmvData;
|
||||
|
||||
typedef struct {
|
||||
uint16_t tag;
|
||||
uint8_t data[];
|
||||
} PDOLValue;
|
||||
|
||||
typedef struct {
|
||||
uint8_t size;
|
||||
uint8_t data[MAX_APDU_LEN];
|
||||
} APDU;
|
||||
|
||||
typedef struct {
|
||||
uint8_t priority;
|
||||
uint8_t aid[16];
|
||||
uint8_t aid_len;
|
||||
bool app_started;
|
||||
char name[32];
|
||||
bool name_found;
|
||||
uint8_t card_number[10];
|
||||
uint8_t card_number_len;
|
||||
uint8_t exp_month;
|
||||
uint8_t exp_year;
|
||||
uint16_t country_code;
|
||||
uint16_t currency_code;
|
||||
APDU pdol;
|
||||
APDU afl;
|
||||
} EmvApplication;
|
||||
|
||||
/** Read bank card data
|
||||
* @note Search EMV Application, start it, try to read AID, PAN, card name,
|
||||
* expiration date, currency and country codes
|
||||
*
|
||||
* @param tx_rx FuriHalNfcTxRxContext instance
|
||||
* @param emv_app EmvApplication instance
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app);
|
||||
|
||||
/** Emulate bank card
|
||||
* @note Answer to application selection and PDOL
|
||||
*
|
||||
* @param tx_rx FuriHalNfcTxRxContext instance
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx);
|
||||
147
lib/nfc/protocols/felica/felica.c
Normal file
147
lib/nfc/protocols/felica/felica.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "felica.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include <nfc/nfc_common.h>
|
||||
|
||||
#define FELICA_PROTOCOL_NAME "FeliCa"
|
||||
#define FELICA_DEVICE_NAME "FeliCa"
|
||||
|
||||
#define FELICA_DATA_FORMAT_VERSION "Data format version"
|
||||
#define FELICA_MANUFACTURE_ID "Manufacture id"
|
||||
#define FELICA_MANUFACTURE_PARAMETER "Manufacture parameter"
|
||||
|
||||
static const uint32_t felica_data_format_version = 1;
|
||||
|
||||
const NfcDeviceBase nfc_device_felica = {
|
||||
.protocol_name = FELICA_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)felica_alloc,
|
||||
.free = (NfcDeviceFree)felica_free,
|
||||
.reset = (NfcDeviceReset)felica_reset,
|
||||
.copy = (NfcDeviceCopy)felica_copy,
|
||||
.verify = (NfcDeviceVerify)felica_verify,
|
||||
.load = (NfcDeviceLoad)felica_load,
|
||||
.save = (NfcDeviceSave)felica_save,
|
||||
.is_equal = (NfcDeviceEqual)felica_is_equal,
|
||||
.get_name = (NfcDeviceGetName)felica_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)felica_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)felica_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)felica_get_base_data,
|
||||
};
|
||||
|
||||
FelicaData* felica_alloc() {
|
||||
FelicaData* data = malloc(sizeof(FelicaData));
|
||||
return data;
|
||||
}
|
||||
|
||||
void felica_free(FelicaData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void felica_reset(FelicaData* data) {
|
||||
memset(data, 0, sizeof(FelicaData));
|
||||
}
|
||||
|
||||
void felica_copy(FelicaData* data, const FelicaData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
*data = *other;
|
||||
}
|
||||
|
||||
bool felica_verify(FelicaData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(device_type);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
if(version < NFC_UNIFIED_FORMAT_VERSION) break;
|
||||
|
||||
uint32_t data_format_version = 0;
|
||||
if(!flipper_format_read_uint32(ff, FELICA_DATA_FORMAT_VERSION, &data_format_version, 1))
|
||||
break;
|
||||
if(data_format_version != felica_data_format_version) break;
|
||||
if(!flipper_format_read_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(ff, FELICA_PROTOCOL_NAME " specific data")) break;
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, FELICA_DATA_FORMAT_VERSION, &felica_data_format_version, 1))
|
||||
break;
|
||||
if(!flipper_format_write_hex(ff, FELICA_MANUFACTURE_ID, data->idm.data, FELICA_IDM_SIZE))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool felica_is_equal(const FelicaData* data, const FelicaData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
return memcmp(data, other, sizeof(FelicaData)) == 0;
|
||||
}
|
||||
|
||||
const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
|
||||
return FELICA_DEVICE_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
// Consider Manufacturer ID as UID
|
||||
if(uid_len) {
|
||||
*uid_len = FELICA_IDM_SIZE;
|
||||
}
|
||||
|
||||
return data->idm.data;
|
||||
}
|
||||
|
||||
bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
// Consider Manufacturer ID as UID
|
||||
const bool uid_valid = uid_len == FELICA_IDM_SIZE;
|
||||
if(uid_valid) {
|
||||
memcpy(data->idm.data, uid, uid_len);
|
||||
}
|
||||
|
||||
return uid_valid;
|
||||
}
|
||||
|
||||
FelicaData* felica_get_base_data(const FelicaData* data) {
|
||||
UNUSED(data);
|
||||
furi_crash("No base data");
|
||||
}
|
||||
77
lib/nfc/protocols/felica/felica.h
Normal file
77
lib/nfc/protocols/felica/felica.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FELICA_IDM_SIZE (8U)
|
||||
#define FELICA_PMM_SIZE (8U)
|
||||
|
||||
#define FELICA_GUARD_TIME_US (20000U)
|
||||
#define FELICA_FDT_POLL_FC (10000U)
|
||||
#define FELICA_POLL_POLL_MIN_US (1280U)
|
||||
|
||||
#define FELICA_SYSTEM_CODE_CODE (0xFFFFU)
|
||||
#define FELICA_TIME_SLOT_1 (0x00U)
|
||||
#define FELICA_TIME_SLOT_2 (0x01U)
|
||||
#define FELICA_TIME_SLOT_4 (0x03U)
|
||||
#define FELICA_TIME_SLOT_8 (0x07U)
|
||||
#define FELICA_TIME_SLOT_16 (0x0FU)
|
||||
|
||||
typedef enum {
|
||||
FelicaErrorNone,
|
||||
FelicaErrorNotPresent,
|
||||
FelicaErrorColResFailed,
|
||||
FelicaErrorBufferOverflow,
|
||||
FelicaErrorCommunication,
|
||||
FelicaErrorFieldOff,
|
||||
FelicaErrorWrongCrc,
|
||||
FelicaErrorProtocol,
|
||||
FelicaErrorTimeout,
|
||||
} FelicaError;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_IDM_SIZE];
|
||||
} FelicaIDm;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_PMM_SIZE];
|
||||
} FelicaPMm;
|
||||
|
||||
typedef struct {
|
||||
FelicaIDm idm;
|
||||
FelicaPMm pmm;
|
||||
} FelicaData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_felica;
|
||||
|
||||
FelicaData* felica_alloc();
|
||||
|
||||
void felica_free(FelicaData* data);
|
||||
|
||||
void felica_reset(FelicaData* data);
|
||||
|
||||
void felica_copy(FelicaData* data, const FelicaData* other);
|
||||
|
||||
bool felica_verify(FelicaData* data, const FuriString* device_type);
|
||||
|
||||
bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool felica_save(const FelicaData* data, FlipperFormat* ff);
|
||||
|
||||
bool felica_is_equal(const FelicaData* data, const FelicaData* other);
|
||||
|
||||
const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len);
|
||||
|
||||
bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
FelicaData* felica_get_base_data(const FelicaData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
117
lib/nfc/protocols/felica/felica_poller.c
Normal file
117
lib/nfc/protocols/felica/felica_poller.c
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "felica_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static FelicaPoller* felica_poller_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
FelicaPoller* instance = malloc(sizeof(FelicaPoller));
|
||||
instance->nfc = nfc;
|
||||
instance->tx_buffer = bit_buffer_alloc(FELICA_POLLER_MAX_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(FELICA_POLLER_MAX_BUFFER_SIZE);
|
||||
|
||||
nfc_config(instance->nfc, NfcModePoller, NfcTechFelica);
|
||||
nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US);
|
||||
instance->data = felica_alloc();
|
||||
|
||||
instance->felica_event.data = &instance->felica_event_data;
|
||||
instance->general_event.protocol = NfcProtocolFelica;
|
||||
instance->general_event.event_data = &instance->felica_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void felica_poller_free(FelicaPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(instance->data);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
felica_free(instance->data);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void
|
||||
felica_poller_set_callback(FelicaPoller* instance, NfcGenericCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
FelicaPoller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
if(instance->state != FelicaPollerStateActivated) {
|
||||
FelicaError error = felica_poller_async_activate(instance, instance->data);
|
||||
if(error == FelicaErrorNone) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeReady;
|
||||
instance->felica_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
} else {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
// Add delay to switch context
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
} else {
|
||||
instance->felica_event.type = FelicaPollerEventTypeReady;
|
||||
instance->felica_event_data.error = FelicaErrorNone;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool felica_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
|
||||
bool protocol_detected = false;
|
||||
FelicaPoller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
furi_assert(instance->state == FelicaPollerStateIdle);
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
FelicaError error = felica_poller_async_activate(instance, instance->data);
|
||||
protocol_detected = (error == FelicaErrorNone);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase nfc_poller_felica = {
|
||||
.alloc = (NfcPollerAlloc)felica_poller_alloc,
|
||||
.free = (NfcPollerFree)felica_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)felica_poller_set_callback,
|
||||
.run = (NfcPollerRun)felica_poller_run,
|
||||
.detect = (NfcPollerDetect)felica_poller_detect,
|
||||
.get_data = (NfcPollerGetData)felica_poller_get_data,
|
||||
};
|
||||
30
lib/nfc/protocols/felica/felica_poller.h
Normal file
30
lib/nfc/protocols/felica/felica_poller.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "felica.h"
|
||||
#include <lib/nfc/nfc.h>
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FelicaPoller FelicaPoller;
|
||||
|
||||
typedef enum {
|
||||
FelicaPollerEventTypeError,
|
||||
FelicaPollerEventTypeReady,
|
||||
} FelicaPollerEventType;
|
||||
|
||||
typedef struct {
|
||||
FelicaError error;
|
||||
} FelicaPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
FelicaPollerEventType type;
|
||||
FelicaPollerEventData* data;
|
||||
} FelicaPollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
13
lib/nfc/protocols/felica/felica_poller_defs.h
Normal file
13
lib/nfc/protocols/felica/felica_poller_defs.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const NfcPollerBase nfc_poller_felica;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
128
lib/nfc/protocols/felica/felica_poller_i.c
Normal file
128
lib/nfc/protocols/felica/felica_poller_i.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "felica_poller_i.h"
|
||||
|
||||
#include <nfc/helpers/felica_crc.h>
|
||||
|
||||
#define TAG "FelicaPoller"
|
||||
|
||||
static FelicaError felica_poller_process_error(NfcError error) {
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
return FelicaErrorNone;
|
||||
case NfcErrorTimeout:
|
||||
return FelicaErrorTimeout;
|
||||
default:
|
||||
return FelicaErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
static FelicaError felica_poller_frame_exchange(
|
||||
FelicaPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
|
||||
const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer);
|
||||
furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - FELICA_CRC_SIZE);
|
||||
|
||||
felica_crc_append(instance->tx_buffer);
|
||||
|
||||
FelicaError ret = FelicaErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error =
|
||||
nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = felica_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy(rx_buffer, instance->rx_buffer);
|
||||
if(!felica_crc_check(instance->rx_buffer)) {
|
||||
ret = FelicaErrorWrongCrc;
|
||||
break;
|
||||
}
|
||||
|
||||
felica_crc_trim(rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FelicaError felica_poller_async_polling(
|
||||
FelicaPoller* instance,
|
||||
const FelicaPollerPollingCommand* cmd,
|
||||
FelicaPollerPollingResponse* resp) {
|
||||
furi_assert(instance);
|
||||
furi_assert(cmd);
|
||||
furi_assert(resp);
|
||||
|
||||
FelicaError error = FelicaErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_set_size_bytes(instance->tx_buffer, 2);
|
||||
// Set frame len
|
||||
bit_buffer_set_byte(
|
||||
instance->tx_buffer, 0, sizeof(FelicaPollerPollingCommand) + FELICA_CRC_SIZE);
|
||||
// Set command code
|
||||
bit_buffer_set_byte(instance->tx_buffer, 1, FELICA_POLLER_CMD_POLLING_REQ_CODE);
|
||||
// Set other data
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, (uint8_t*)cmd, sizeof(FelicaPollerPollingCommand));
|
||||
|
||||
error = felica_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT);
|
||||
|
||||
if(error != FelicaErrorNone) break;
|
||||
if(bit_buffer_get_byte(instance->rx_buffer, 1) != FELICA_POLLER_CMD_POLLING_RESP_CODE) {
|
||||
error = FelicaErrorProtocol;
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) <
|
||||
sizeof(FelicaIDm) + sizeof(FelicaPMm) + 1) {
|
||||
error = FelicaErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_write_bytes_mid(instance->rx_buffer, resp->idm.data, 2, sizeof(FelicaIDm));
|
||||
bit_buffer_write_bytes_mid(
|
||||
instance->rx_buffer, resp->pmm.data, sizeof(FelicaIDm) + 2, sizeof(FelicaPMm));
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
felica_reset(data);
|
||||
|
||||
FelicaError ret;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
// Send Polling command
|
||||
const FelicaPollerPollingCommand polling_cmd = {
|
||||
.system_code = FELICA_SYSTEM_CODE_CODE,
|
||||
.request_code = 0,
|
||||
.time_slot = FELICA_TIME_SLOT_1,
|
||||
};
|
||||
FelicaPollerPollingResponse polling_resp = {};
|
||||
|
||||
ret = felica_poller_async_polling(instance, &polling_cmd, &polling_resp);
|
||||
|
||||
if(ret != FelicaErrorNone) {
|
||||
FURI_LOG_T(TAG, "Activation failed error: %d", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
data->idm = polling_resp.idm;
|
||||
data->pmm = polling_resp.pmm;
|
||||
instance->state = FelicaPollerStateActivated;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
60
lib/nfc/protocols/felica/felica_poller_i.h
Normal file
60
lib/nfc/protocols/felica/felica_poller_i.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "felica_poller.h"
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FELICA_POLLER_MAX_BUFFER_SIZE (256U)
|
||||
|
||||
#define FELICA_POLLER_POLLING_FWT (200000U)
|
||||
|
||||
#define FELICA_POLLER_CMD_POLLING_REQ_CODE (0x00U)
|
||||
#define FELICA_POLLER_CMD_POLLING_RESP_CODE (0x01U)
|
||||
|
||||
typedef enum {
|
||||
FelicaPollerStateIdle,
|
||||
FelicaPollerStateActivated,
|
||||
} FelicaPollerState;
|
||||
|
||||
struct FelicaPoller {
|
||||
Nfc* nfc;
|
||||
FelicaPollerState state;
|
||||
FelicaData* data;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
|
||||
NfcGenericEvent general_event;
|
||||
FelicaPollerEvent felica_event;
|
||||
FelicaPollerEventData felica_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t system_code;
|
||||
uint8_t request_code;
|
||||
uint8_t time_slot;
|
||||
} FelicaPollerPollingCommand;
|
||||
|
||||
typedef struct {
|
||||
FelicaIDm idm;
|
||||
FelicaPMm pmm;
|
||||
uint8_t request_data[2];
|
||||
} FelicaPollerPollingResponse;
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance);
|
||||
|
||||
FelicaError felica_poller_async_polling(
|
||||
FelicaPoller* instance,
|
||||
const FelicaPollerPollingCommand* cmd,
|
||||
FelicaPollerPollingResponse* resp);
|
||||
|
||||
FelicaError felica_poller_async_activate(FelicaPoller* instance, FelicaData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
186
lib/nfc/protocols/iso14443_3a/iso14443_3a.c
Normal file
186
lib/nfc/protocols/iso14443_3a/iso14443_3a.c
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "iso14443_3a.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <nfc/nfc_common.h>
|
||||
|
||||
#define ISO14443A_ATS_BIT (1U << 5)
|
||||
|
||||
#define ISO14443_3A_PROTOCOL_NAME_LEGACY "UID"
|
||||
#define ISO14443_3A_PROTOCOL_NAME "ISO14443-3A"
|
||||
#define ISO14443_3A_DEVICE_NAME "ISO14443-3A (Unknown)"
|
||||
|
||||
#define ISO14443_3A_ATQA_KEY "ATQA"
|
||||
#define ISO14443_3A_SAK_KEY "SAK"
|
||||
|
||||
const NfcDeviceBase nfc_device_iso14443_3a = {
|
||||
.protocol_name = ISO14443_3A_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)iso14443_3a_alloc,
|
||||
.free = (NfcDeviceFree)iso14443_3a_free,
|
||||
.reset = (NfcDeviceReset)iso14443_3a_reset,
|
||||
.copy = (NfcDeviceCopy)iso14443_3a_copy,
|
||||
.verify = (NfcDeviceVerify)iso14443_3a_verify,
|
||||
.load = (NfcDeviceLoad)iso14443_3a_load,
|
||||
.save = (NfcDeviceSave)iso14443_3a_save,
|
||||
.is_equal = (NfcDeviceEqual)iso14443_3a_is_equal,
|
||||
.get_name = (NfcDeviceGetName)iso14443_3a_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)iso14443_3a_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)iso14443_3a_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)iso14443_3a_get_base_data,
|
||||
};
|
||||
|
||||
Iso14443_3aData* iso14443_3a_alloc() {
|
||||
Iso14443_3aData* data = malloc(sizeof(Iso14443_3aData));
|
||||
return data;
|
||||
}
|
||||
|
||||
void iso14443_3a_free(Iso14443_3aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void iso14443_3a_reset(Iso14443_3aData* data) {
|
||||
furi_assert(data);
|
||||
memset(data, 0, sizeof(Iso14443_3aData));
|
||||
}
|
||||
|
||||
void iso14443_3a_copy(Iso14443_3aData* data, const Iso14443_3aData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
*data = *other;
|
||||
}
|
||||
|
||||
bool iso14443_3a_verify(Iso14443_3aData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal(device_type, ISO14443_3A_PROTOCOL_NAME_LEGACY);
|
||||
}
|
||||
|
||||
bool iso14443_3a_load(Iso14443_3aData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Common to all format versions
|
||||
if(!flipper_format_read_hex(ff, ISO14443_3A_ATQA_KEY, data->atqa, 2)) break;
|
||||
if(!flipper_format_read_hex(ff, ISO14443_3A_SAK_KEY, &data->sak, 1)) break;
|
||||
|
||||
if(version > NFC_LSB_ATQA_FORMAT_VERSION) {
|
||||
// Swap ATQA bytes for newer versions
|
||||
FURI_SWAP(data->atqa[0], data->atqa[1]);
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool iso14443_3a_save(const Iso14443_3aData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
// Save ATQA in MSB order for correct companion apps display
|
||||
const uint8_t atqa[2] = {data->atqa[1], data->atqa[0]};
|
||||
if(!flipper_format_write_comment_cstr(ff, ISO14443_3A_PROTOCOL_NAME " specific data"))
|
||||
break;
|
||||
|
||||
// Write ATQA and SAK
|
||||
if(!flipper_format_write_hex(ff, ISO14443_3A_ATQA_KEY, atqa, 2)) break;
|
||||
if(!flipper_format_write_hex(ff, ISO14443_3A_SAK_KEY, &data->sak, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool iso14443_3a_is_equal(const Iso14443_3aData* data, const Iso14443_3aData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
return memcmp(data, other, sizeof(Iso14443_3aData)) == 0;
|
||||
}
|
||||
|
||||
const char* iso14443_3a_get_device_name(const Iso14443_3aData* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
return ISO14443_3A_DEVICE_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* iso14443_3a_get_uid(const Iso14443_3aData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
if(uid_len) {
|
||||
*uid_len = data->uid_len;
|
||||
}
|
||||
|
||||
return data->uid;
|
||||
}
|
||||
|
||||
bool iso14443_3a_set_uid(Iso14443_3aData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
const bool uid_valid = uid_len == ISO14443_3A_UID_4_BYTES ||
|
||||
uid_len == ISO14443_3A_UID_7_BYTES ||
|
||||
uid_len == ISO14443_3A_UID_10_BYTES;
|
||||
|
||||
if(uid_valid) {
|
||||
memcpy(data->uid, uid, uid_len);
|
||||
data->uid_len = uid_len;
|
||||
}
|
||||
|
||||
return uid_valid;
|
||||
}
|
||||
|
||||
Iso14443_3aData* iso14443_3a_get_base_data(const Iso14443_3aData* data) {
|
||||
UNUSED(data);
|
||||
furi_crash("No base data");
|
||||
}
|
||||
|
||||
uint32_t iso14443_3a_get_cuid(const Iso14443_3aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
uint32_t cuid = 0;
|
||||
const uint8_t* cuid_start = data->uid;
|
||||
if(data->uid_len == ISO14443_3A_UID_7_BYTES) {
|
||||
cuid_start = &data->uid[3];
|
||||
}
|
||||
cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | (cuid_start[3]);
|
||||
|
||||
return cuid;
|
||||
}
|
||||
|
||||
bool iso14443_3a_supports_iso14443_4(const Iso14443_3aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->sak & ISO14443A_ATS_BIT;
|
||||
}
|
||||
|
||||
uint8_t iso14443_3a_get_sak(const Iso14443_3aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->sak;
|
||||
}
|
||||
|
||||
void iso14443_3a_get_atqa(const Iso14443_3aData* data, uint8_t atqa[2]) {
|
||||
furi_assert(data);
|
||||
furi_assert(atqa);
|
||||
|
||||
memcpy(atqa, data->atqa, sizeof(data->atqa));
|
||||
}
|
||||
|
||||
void iso14443_3a_set_sak(Iso14443_3aData* data, uint8_t sak) {
|
||||
furi_assert(data);
|
||||
|
||||
data->sak = sak;
|
||||
}
|
||||
|
||||
void iso14443_3a_set_atqa(Iso14443_3aData* data, const uint8_t atqa[2]) {
|
||||
furi_assert(data);
|
||||
furi_assert(atqa);
|
||||
|
||||
memcpy(data->atqa, atqa, sizeof(data->atqa));
|
||||
}
|
||||
107
lib/nfc/protocols/iso14443_3a/iso14443_3a.h
Normal file
107
lib/nfc/protocols/iso14443_3a/iso14443_3a.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO14443_3A_UID_4_BYTES (4U)
|
||||
#define ISO14443_3A_UID_7_BYTES (7U)
|
||||
#define ISO14443_3A_UID_10_BYTES (10U)
|
||||
#define ISO14443_3A_MAX_UID_SIZE ISO14443_3A_UID_10_BYTES
|
||||
|
||||
#define ISO14443_3A_GUARD_TIME_US (5000)
|
||||
#define ISO14443_3A_FDT_POLL_FC (1620)
|
||||
#define ISO14443_3A_FDT_LISTEN_FC (1172)
|
||||
#define ISO14443_3A_POLLER_MASK_RX_FS ((ISO14443_3A_FDT_LISTEN_FC) / 2)
|
||||
#define ISO14443_3A_POLL_POLL_MIN_US (1100)
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aErrorNone,
|
||||
Iso14443_3aErrorNotPresent,
|
||||
Iso14443_3aErrorColResFailed,
|
||||
Iso14443_3aErrorBufferOverflow,
|
||||
Iso14443_3aErrorCommunication,
|
||||
Iso14443_3aErrorFieldOff,
|
||||
Iso14443_3aErrorWrongCrc,
|
||||
Iso14443_3aErrorTimeout,
|
||||
} Iso14443_3aError;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sens_resp[2];
|
||||
} Iso14443_3aSensResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sel_cmd;
|
||||
uint8_t sel_par;
|
||||
uint8_t data[4]; // max data bit is 32
|
||||
} Iso14443_3aSddReq;
|
||||
|
||||
typedef struct {
|
||||
uint8_t nfcid[4];
|
||||
uint8_t bss;
|
||||
} Iso14443_3aSddResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sel_cmd;
|
||||
uint8_t sel_par;
|
||||
uint8_t nfcid[4];
|
||||
uint8_t bcc;
|
||||
} Iso14443_3aSelReq;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sak;
|
||||
} Iso14443_3aSelResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t uid[ISO14443_3A_MAX_UID_SIZE];
|
||||
uint8_t uid_len;
|
||||
uint8_t atqa[2];
|
||||
uint8_t sak;
|
||||
} Iso14443_3aData;
|
||||
|
||||
Iso14443_3aData* iso14443_3a_alloc();
|
||||
|
||||
void iso14443_3a_free(Iso14443_3aData* data);
|
||||
|
||||
void iso14443_3a_reset(Iso14443_3aData* data);
|
||||
|
||||
void iso14443_3a_copy(Iso14443_3aData* data, const Iso14443_3aData* other);
|
||||
|
||||
bool iso14443_3a_verify(Iso14443_3aData* data, const FuriString* device_type);
|
||||
|
||||
bool iso14443_3a_load(Iso14443_3aData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool iso14443_3a_save(const Iso14443_3aData* data, FlipperFormat* ff);
|
||||
|
||||
bool iso14443_3a_is_equal(const Iso14443_3aData* data, const Iso14443_3aData* other);
|
||||
|
||||
const char* iso14443_3a_get_device_name(const Iso14443_3aData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* iso14443_3a_get_uid(const Iso14443_3aData* data, size_t* uid_len);
|
||||
|
||||
bool iso14443_3a_set_uid(Iso14443_3aData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_3aData* iso14443_3a_get_base_data(const Iso14443_3aData* data);
|
||||
|
||||
uint32_t iso14443_3a_get_cuid(const Iso14443_3aData* data);
|
||||
|
||||
// Getters and tests
|
||||
|
||||
bool iso14443_3a_supports_iso14443_4(const Iso14443_3aData* data);
|
||||
|
||||
uint8_t iso14443_3a_get_sak(const Iso14443_3aData* data);
|
||||
|
||||
void iso14443_3a_get_atqa(const Iso14443_3aData* data, uint8_t atqa[2]);
|
||||
|
||||
// Setters
|
||||
|
||||
void iso14443_3a_set_sak(Iso14443_3aData* data, uint8_t sak);
|
||||
|
||||
void iso14443_3a_set_atqa(Iso14443_3aData* data, const uint8_t atqa[2]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h
Normal file
5
lib/nfc/protocols/iso14443_3a/iso14443_3a_device_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_3a;
|
||||
127
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c
Normal file
127
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.c
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "iso14443_3a_listener_i.h"
|
||||
|
||||
#include "nfc/protocols/nfc_listener_base.h"
|
||||
#include "nfc/helpers/iso14443_crc.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/nfc/nfc.h>
|
||||
|
||||
#define TAG "Iso14443_3aListener"
|
||||
|
||||
#define ISO14443_3A_LISTENER_MAX_BUFFER_SIZE (256)
|
||||
|
||||
static bool iso14443_3a_listener_halt_received(BitBuffer* buf) {
|
||||
bool halt_cmd_received = false;
|
||||
|
||||
do {
|
||||
if(bit_buffer_get_size_bytes(buf) != 4) break;
|
||||
if(!iso14443_crc_check(Iso14443CrcTypeA, buf)) break;
|
||||
if(bit_buffer_get_byte(buf, 0) != 0x50) break;
|
||||
if(bit_buffer_get_byte(buf, 1) != 0x00) break;
|
||||
halt_cmd_received = true;
|
||||
} while(false);
|
||||
|
||||
return halt_cmd_received;
|
||||
}
|
||||
|
||||
Iso14443_3aListener* iso14443_3a_listener_alloc(Nfc* nfc, Iso14443_3aData* data) {
|
||||
furi_assert(nfc);
|
||||
|
||||
Iso14443_3aListener* instance = malloc(sizeof(Iso14443_3aListener));
|
||||
instance->nfc = nfc;
|
||||
instance->data = data;
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO14443_3A_LISTENER_MAX_BUFFER_SIZE);
|
||||
|
||||
instance->iso14443_3a_event.data = &instance->iso14443_3a_event_data;
|
||||
instance->generic_event.protocol = NfcProtocolIso14443_3a;
|
||||
instance->generic_event.instance = instance;
|
||||
instance->generic_event.event_data = &instance->iso14443_3a_event;
|
||||
|
||||
nfc_set_fdt_listen_fc(instance->nfc, ISO14443_3A_FDT_LISTEN_FC);
|
||||
nfc_config(instance->nfc, NfcModeListener, NfcTechIso14443a);
|
||||
nfc_iso14443a_listener_set_col_res_data(
|
||||
instance->nfc,
|
||||
instance->data->uid,
|
||||
instance->data->uid_len,
|
||||
instance->data->atqa,
|
||||
instance->data->sak);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void iso14443_3a_listener_free(Iso14443_3aListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->tx_buffer);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void iso14443_3a_listener_set_callback(
|
||||
Iso14443_3aListener* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
const Iso14443_3aData* iso14443_3a_listener_get_data(Iso14443_3aListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
NfcCommand iso14443_3a_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Iso14443_3aListener* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypeListenerActivated) {
|
||||
instance->state = Iso14443_3aListenerStateActive;
|
||||
} else if(nfc_event->type == NfcEventTypeFieldOff) {
|
||||
instance->state = Iso14443_3aListenerStateIdle;
|
||||
if(instance->callback) {
|
||||
instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeFieldOff;
|
||||
instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
command = NfcCommandSleep;
|
||||
} else if(nfc_event->type == NfcEventTypeRxEnd) {
|
||||
if(iso14443_3a_listener_halt_received(nfc_event->data.buffer)) {
|
||||
if(instance->callback) {
|
||||
instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeHalted;
|
||||
instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
command = NfcCommandSleep;
|
||||
} else {
|
||||
if(iso14443_crc_check(Iso14443CrcTypeA, nfc_event->data.buffer)) {
|
||||
instance->iso14443_3a_event.type =
|
||||
Iso14443_3aListenerEventTypeReceivedStandardFrame;
|
||||
iso14443_crc_trim(nfc_event->data.buffer);
|
||||
} else {
|
||||
instance->iso14443_3a_event.type = Iso14443_3aListenerEventTypeReceivedData;
|
||||
}
|
||||
instance->iso14443_3a_event_data.buffer = nfc_event->data.buffer;
|
||||
if(instance->callback) {
|
||||
command = instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
const NfcListenerBase nfc_listener_iso14443_3a = {
|
||||
.alloc = (NfcListenerAlloc)iso14443_3a_listener_alloc,
|
||||
.free = (NfcListenerFree)iso14443_3a_listener_free,
|
||||
.set_callback = (NfcListenerSetCallback)iso14443_3a_listener_set_callback,
|
||||
.get_data = (NfcListenerGetData)iso14443_3a_listener_get_data,
|
||||
.run = (NfcListenerRun)iso14443_3a_listener_run,
|
||||
};
|
||||
31
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h
Normal file
31
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3a.h"
|
||||
#include <nfc/nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_3aListener Iso14443_3aListener;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aListenerEventTypeFieldOff,
|
||||
Iso14443_3aListenerEventTypeHalted,
|
||||
|
||||
Iso14443_3aListenerEventTypeReceivedStandardFrame,
|
||||
Iso14443_3aListenerEventTypeReceivedData,
|
||||
} Iso14443_3aListenerEventType;
|
||||
|
||||
typedef struct {
|
||||
BitBuffer* buffer;
|
||||
} Iso14443_3aListenerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aListenerEventType type;
|
||||
Iso14443_3aListenerEventData* data;
|
||||
} Iso14443_3aListenerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
extern const NfcListenerBase nfc_listener_iso14443_3a;
|
||||
73
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c
Normal file
73
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "iso14443_3a_listener_i.h"
|
||||
|
||||
#include "nfc/helpers/iso14443_crc.h"
|
||||
|
||||
#define TAG "Iso14443_3aListener"
|
||||
|
||||
static Iso14443_3aError iso14443_3a_listener_process_nfc_error(NfcError error) {
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
|
||||
if(error == NfcErrorNone) {
|
||||
ret = Iso14443_3aErrorNone;
|
||||
} else if(error == NfcErrorTimeout) {
|
||||
ret = Iso14443_3aErrorTimeout;
|
||||
} else {
|
||||
ret = Iso14443_3aErrorFieldOff;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError
|
||||
iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
NfcError error = nfc_listener_tx(instance->nfc, tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_W(TAG, "Tx error: %d", error);
|
||||
ret = iso14443_3a_listener_process_nfc_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity(
|
||||
Iso14443_3aListener* instance,
|
||||
const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
NfcError error = nfc_iso14443a_listener_tx_custom_parity(instance->nfc, tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_W(TAG, "Tx error: %d", error);
|
||||
ret = iso14443_3a_listener_process_nfc_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
Iso14443_3aError iso14443_3a_listener_send_standard_frame(
|
||||
Iso14443_3aListener* instance,
|
||||
const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(instance->tx_buffer);
|
||||
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
do {
|
||||
bit_buffer_copy(instance->tx_buffer, tx_buffer);
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer);
|
||||
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_W(TAG, "Tx error: %d", error);
|
||||
ret = iso14443_3a_listener_process_nfc_error(error);
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
42
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h
Normal file
42
lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3a_listener.h"
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aListenerStateIdle,
|
||||
Iso14443_3aListenerStateActive,
|
||||
} Iso14443_3aListenerState;
|
||||
|
||||
struct Iso14443_3aListener {
|
||||
Nfc* nfc;
|
||||
Iso14443_3aData* data;
|
||||
Iso14443_3aListenerState state;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
|
||||
NfcGenericEvent generic_event;
|
||||
Iso14443_3aListenerEvent iso14443_3a_event;
|
||||
Iso14443_3aListenerEventData iso14443_3a_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
Iso14443_3aError
|
||||
iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer);
|
||||
|
||||
Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity(
|
||||
Iso14443_3aListener* instance,
|
||||
const BitBuffer* tx_buffer);
|
||||
|
||||
Iso14443_3aError iso14443_3a_listener_send_standard_frame(
|
||||
Iso14443_3aListener* instance,
|
||||
const BitBuffer* tx_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
128
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c
Normal file
128
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "iso14443_3a_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ISO14443_3A"
|
||||
|
||||
const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static Iso14443_3aPoller* iso14443_3a_poller_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
Iso14443_3aPoller* instance = malloc(sizeof(Iso14443_3aPoller));
|
||||
instance->nfc = nfc;
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO14443_3A_POLLER_MAX_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(ISO14443_3A_POLLER_MAX_BUFFER_SIZE);
|
||||
|
||||
nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443a);
|
||||
nfc_set_guard_time_us(instance->nfc, ISO14443_3A_GUARD_TIME_US);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3A_FDT_POLL_FC);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3A_POLL_POLL_MIN_US);
|
||||
instance->data = iso14443_3a_alloc();
|
||||
|
||||
instance->iso14443_3a_event.data = &instance->iso14443_3a_event_data;
|
||||
instance->general_event.protocol = NfcProtocolIso14443_3a;
|
||||
instance->general_event.event_data = &instance->iso14443_3a_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void iso14443_3a_poller_free_new(Iso14443_3aPoller* iso14443_3a_poller) {
|
||||
furi_assert(iso14443_3a_poller);
|
||||
|
||||
Iso14443_3aPoller* instance = iso14443_3a_poller;
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(instance->data);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
iso14443_3a_free(instance->data);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void iso14443_3a_poller_set_callback(
|
||||
Iso14443_3aPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_3a_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Iso14443_3aPoller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
if(instance->state != Iso14443_3aPollerStateActivated) {
|
||||
Iso14443_3aData data = {};
|
||||
Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, &data);
|
||||
if(error == Iso14443_3aErrorNone) {
|
||||
instance->state = Iso14443_3aPollerStateActivated;
|
||||
instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady;
|
||||
instance->iso14443_3a_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
} else {
|
||||
instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeError;
|
||||
instance->iso14443_3a_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
// Add delay to switch context
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
} else {
|
||||
instance->iso14443_3a_event.type = Iso14443_3aPollerEventTypeReady;
|
||||
instance->iso14443_3a_event_data.error = Iso14443_3aErrorNone;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
if(command == NfcCommandReset) {
|
||||
instance->state = Iso14443_3aPollerStateIdle;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool iso14443_3a_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
|
||||
bool protocol_detected = false;
|
||||
Iso14443_3aPoller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
furi_assert(instance->state == Iso14443_3aPollerStateIdle);
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
Iso14443_3aError error = iso14443_3a_poller_async_activate(instance, NULL);
|
||||
protocol_detected = (error == Iso14443_3aErrorNone);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase nfc_poller_iso14443_3a = {
|
||||
.alloc = (NfcPollerAlloc)iso14443_3a_poller_alloc,
|
||||
.free = (NfcPollerFree)iso14443_3a_poller_free_new,
|
||||
.set_callback = (NfcPollerSetCallback)iso14443_3a_poller_set_callback,
|
||||
.run = (NfcPollerRun)iso14443_3a_poller_run,
|
||||
.detect = (NfcPollerDetect)iso14443_3a_poller_detect,
|
||||
.get_data = (NfcPollerGetData)iso14443_3a_poller_get_data,
|
||||
};
|
||||
42
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h
Normal file
42
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3a.h"
|
||||
#include <lib/nfc/nfc.h>
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_3aPoller Iso14443_3aPoller;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aPollerEventTypeError,
|
||||
Iso14443_3aPollerEventTypeReady,
|
||||
} Iso14443_3aPollerEventType;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aError error;
|
||||
} Iso14443_3aPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aPollerEventType type;
|
||||
Iso14443_3aPollerEventData* data;
|
||||
} Iso14443_3aPollerEvent;
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_txrx(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_send_standard_frame(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h
Normal file
5
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso14443_3a;
|
||||
293
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c
Normal file
293
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.c
Normal file
@@ -0,0 +1,293 @@
|
||||
#include "iso14443_3a_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "nfc/helpers/iso14443_crc.h"
|
||||
|
||||
#define TAG "ISO14443_3A"
|
||||
|
||||
static Iso14443_3aError iso14443_3a_poller_process_error(NfcError error) {
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
if(error == NfcErrorNone) {
|
||||
ret = Iso14443_3aErrorNone;
|
||||
} else if(error == NfcErrorTimeout) {
|
||||
ret = Iso14443_3aErrorTimeout;
|
||||
} else {
|
||||
ret = Iso14443_3aErrorNotPresent;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Iso14443_3aError iso14443_3a_poller_standard_frame_exchange(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
uint16_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer);
|
||||
furi_assert(tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - 2);
|
||||
|
||||
bit_buffer_copy(instance->tx_buffer, tx_buffer);
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_buffer);
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error =
|
||||
nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = iso14443_3a_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy(rx_buffer, instance->rx_buffer);
|
||||
if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_buffer)) {
|
||||
ret = Iso14443_3aErrorWrongCrc;
|
||||
break;
|
||||
}
|
||||
|
||||
iso14443_crc_trim(rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
|
||||
NfcError error = NfcErrorNone;
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
do {
|
||||
error = nfc_iso14443a_poller_trx_short_frame(
|
||||
instance->nfc,
|
||||
NfcIso14443aShortFrameSensReq,
|
||||
instance->rx_buffer,
|
||||
ISO14443_3A_FDT_LISTEN_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = iso14443_3a_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(instance->col_res.sens_resp)) {
|
||||
ret = Iso14443_3aErrorCommunication;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
furi_assert(instance->tx_buffer);
|
||||
|
||||
uint8_t halt_cmd[2] = {0x50, 0x00};
|
||||
bit_buffer_copy_bytes(instance->tx_buffer, halt_cmd, sizeof(halt_cmd));
|
||||
|
||||
iso14443_3a_poller_standard_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3A_FDT_LISTEN_FC);
|
||||
|
||||
instance->state = Iso14443_3aPollerStateIdle;
|
||||
return Iso14443_3aErrorNone;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_async_activate(
|
||||
Iso14443_3aPoller* instance,
|
||||
Iso14443_3aData* iso14443_3a_data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
|
||||
// Reset Iso14443_3a poller state
|
||||
memset(&instance->col_res, 0, sizeof(instance->col_res));
|
||||
memset(instance->data, 0, sizeof(Iso14443_3aData));
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
// Halt if necessary
|
||||
if(instance->state != Iso14443_3aPollerStateIdle) {
|
||||
iso14443_3a_poller_halt(instance);
|
||||
instance->state = Iso14443_3aPollerStateIdle;
|
||||
}
|
||||
|
||||
NfcError error = NfcErrorNone;
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
|
||||
bool activated = false;
|
||||
do {
|
||||
error = nfc_iso14443a_poller_trx_short_frame(
|
||||
instance->nfc,
|
||||
NfcIso14443aShortFrameSensReq,
|
||||
instance->rx_buffer,
|
||||
ISO14443_3A_FDT_LISTEN_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = Iso14443_3aErrorNotPresent;
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(instance->col_res.sens_resp)) {
|
||||
FURI_LOG_W(TAG, "Wrong sens response size");
|
||||
ret = Iso14443_3aErrorCommunication;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer,
|
||||
&instance->col_res.sens_resp,
|
||||
sizeof(instance->col_res.sens_resp));
|
||||
memcpy(
|
||||
instance->data->atqa,
|
||||
&instance->col_res.sens_resp,
|
||||
sizeof(instance->col_res.sens_resp));
|
||||
|
||||
instance->state = Iso14443_3aPollerStateColResInProgress;
|
||||
instance->col_res.cascade_level = 0;
|
||||
instance->col_res.state = Iso14443_3aPollerColResStateStateNewCascade;
|
||||
|
||||
while(instance->state == Iso14443_3aPollerStateColResInProgress) {
|
||||
if(instance->col_res.state == Iso14443_3aPollerColResStateStateNewCascade) {
|
||||
bit_buffer_set_size_bytes(instance->tx_buffer, 2);
|
||||
bit_buffer_set_byte(
|
||||
instance->tx_buffer,
|
||||
0,
|
||||
ISO14443_3A_POLLER_SEL_CMD(instance->col_res.cascade_level));
|
||||
bit_buffer_set_byte(instance->tx_buffer, 1, ISO14443_3A_POLLER_SEL_PAR(2, 0));
|
||||
error = nfc_iso14443a_poller_trx_sdd_frame(
|
||||
instance->nfc,
|
||||
instance->tx_buffer,
|
||||
instance->rx_buffer,
|
||||
ISO14443_3A_FDT_LISTEN_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_E(TAG, "Sdd request failed: %d", error);
|
||||
instance->state = Iso14443_3aPollerStateColResFailed;
|
||||
ret = Iso14443_3aErrorColResFailed;
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != 5) {
|
||||
FURI_LOG_E(TAG, "Sdd response wrong length");
|
||||
instance->state = Iso14443_3aPollerStateColResFailed;
|
||||
ret = Iso14443_3aErrorColResFailed;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer, &instance->col_res.sdd_resp, sizeof(Iso14443_3aSddResp));
|
||||
instance->col_res.state = Iso14443_3aPollerColResStateStateSelectCascade;
|
||||
} else if(instance->col_res.state == Iso14443_3aPollerColResStateStateSelectCascade) {
|
||||
instance->col_res.sel_req.sel_cmd =
|
||||
ISO14443_3A_POLLER_SEL_CMD(instance->col_res.cascade_level);
|
||||
instance->col_res.sel_req.sel_par = ISO14443_3A_POLLER_SEL_PAR(7, 0);
|
||||
memcpy(
|
||||
instance->col_res.sel_req.nfcid,
|
||||
instance->col_res.sdd_resp.nfcid,
|
||||
sizeof(instance->col_res.sdd_resp.nfcid));
|
||||
instance->col_res.sel_req.bcc = instance->col_res.sdd_resp.bss;
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer,
|
||||
(uint8_t*)&instance->col_res.sel_req,
|
||||
sizeof(instance->col_res.sel_req));
|
||||
ret = iso14443_3a_poller_send_standard_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3A_FDT_LISTEN_FC);
|
||||
if(ret != Iso14443_3aErrorNone) {
|
||||
FURI_LOG_E(TAG, "Sel request failed: %d", ret);
|
||||
instance->state = Iso14443_3aPollerStateColResFailed;
|
||||
ret = Iso14443_3aErrorColResFailed;
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) !=
|
||||
sizeof(instance->col_res.sel_resp)) {
|
||||
FURI_LOG_E(TAG, "Sel response wrong length");
|
||||
instance->state = Iso14443_3aPollerStateColResFailed;
|
||||
ret = Iso14443_3aErrorColResFailed;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer,
|
||||
&instance->col_res.sel_resp,
|
||||
sizeof(instance->col_res.sel_resp));
|
||||
FURI_LOG_T(TAG, "Sel resp: %02X", instance->col_res.sel_resp.sak);
|
||||
if(instance->col_res.sel_req.nfcid[0] == ISO14443_3A_POLLER_SDD_CL) {
|
||||
// Copy part of UID
|
||||
memcpy(
|
||||
&instance->data->uid[instance->data->uid_len],
|
||||
&instance->col_res.sel_req.nfcid[1],
|
||||
3);
|
||||
instance->data->uid_len += 3;
|
||||
instance->col_res.cascade_level++;
|
||||
instance->col_res.state = Iso14443_3aPollerColResStateStateNewCascade;
|
||||
} else {
|
||||
FURI_LOG_T(TAG, "Col resolution complete");
|
||||
instance->data->sak = instance->col_res.sel_resp.sak;
|
||||
memcpy(
|
||||
&instance->data->uid[instance->data->uid_len],
|
||||
&instance->col_res.sel_req.nfcid[0],
|
||||
4);
|
||||
instance->data->uid_len += 4;
|
||||
instance->col_res.state = Iso14443_3aPollerColResStateStateSuccess;
|
||||
instance->state = Iso14443_3aPollerStateActivated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activated = (instance->state == Iso14443_3aPollerStateActivated);
|
||||
} while(false);
|
||||
|
||||
if(activated && iso14443_3a_data) {
|
||||
*iso14443_3a_data = *instance->data;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_txrx_custom_parity(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
NfcError error =
|
||||
nfc_iso14443a_poller_trx_custom_parity(instance->nfc, tx_buffer, rx_buffer, fwt);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = iso14443_3a_poller_process_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_txrx(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
Iso14443_3aError ret = Iso14443_3aErrorNone;
|
||||
NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = iso14443_3a_poller_process_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_send_standard_frame(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
Iso14443_3aError ret =
|
||||
iso14443_3a_poller_standard_frame_exchange(instance, tx_buffer, rx_buffer, fwt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
81
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h
Normal file
81
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3a_poller.h"
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO14443_3A_POLLER_MAX_BUFFER_SIZE (512U)
|
||||
|
||||
#define ISO14443_3A_POLLER_SEL_CMD(cascade_lvl) (0x93 + 2 * (cascade_lvl))
|
||||
#define ISO14443_3A_POLLER_SEL_PAR(bytes, bits) (((bytes) << 4 & 0xf0U) | ((bits)&0x0fU))
|
||||
#define ISO14443_3A_POLLER_SDD_CL (0x88U)
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aPollerColResStateStateIdle,
|
||||
Iso14443_3aPollerColResStateStateNewCascade,
|
||||
Iso14443_3aPollerColResStateStateSelectCascade,
|
||||
Iso14443_3aPollerColResStateStateSuccess,
|
||||
Iso14443_3aPollerColResStateStateFail,
|
||||
} Iso14443_3aPollerColResState;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aPollerColResState state;
|
||||
Iso14443_3aSensResp sens_resp;
|
||||
Iso14443_3aSddReq sdd_req;
|
||||
Iso14443_3aSddResp sdd_resp;
|
||||
Iso14443_3aSelReq sel_req;
|
||||
Iso14443_3aSelResp sel_resp;
|
||||
uint8_t cascade_level;
|
||||
} Iso14443_3aPollerColRes;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aPollerStateIdle,
|
||||
Iso14443_3aPollerStateColResInProgress,
|
||||
Iso14443_3aPollerStateColResFailed,
|
||||
Iso14443_3aPollerStateActivated,
|
||||
} Iso14443_3aPollerState;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3aPollerConfigStateIdle,
|
||||
Iso14443_3aPollerConfigStateDone,
|
||||
} Iso14443_3aPollerConfigState;
|
||||
|
||||
struct Iso14443_3aPoller {
|
||||
Nfc* nfc;
|
||||
Iso14443_3aPollerState state;
|
||||
Iso14443_3aPollerConfigState config_state;
|
||||
Iso14443_3aPollerColRes col_res;
|
||||
Iso14443_3aData* data;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
|
||||
NfcGenericEvent general_event;
|
||||
Iso14443_3aPollerEvent iso14443_3a_event;
|
||||
Iso14443_3aPollerEventData iso14443_3a_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
const Iso14443_3aData* iso14443_3a_poller_get_data(Iso14443_3aPoller* instance);
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_check_presence(Iso14443_3aPoller* instance);
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_async_activate(
|
||||
Iso14443_3aPoller* instance,
|
||||
Iso14443_3aData* iso14443_3a_data);
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_halt(Iso14443_3aPoller* instance);
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_txrx_custom_parity(
|
||||
Iso14443_3aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
58
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c
Normal file
58
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "iso14443_3a_poller_sync_api.h"
|
||||
|
||||
#include "iso14443_3a_poller_i.h"
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE (1UL << 0)
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aPoller* instance;
|
||||
FuriThreadId thread_id;
|
||||
Iso14443_3aError error;
|
||||
Iso14443_3aData data;
|
||||
} Iso14443_3aPollerContext;
|
||||
|
||||
NfcCommand iso14443_3a_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
|
||||
Iso14443_3aPollerContext* poller_context = context;
|
||||
Iso14443_3aPoller* iso14443_3a_poller = event.instance;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
iso14443_3a_copy(&poller_context->data, iso14443_3a_poller->data);
|
||||
}
|
||||
poller_context->error = iso14443_3a_event->data->error;
|
||||
|
||||
furi_thread_flags_set(poller_context->thread_id, ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(iso14443_3a_data);
|
||||
|
||||
Iso14443_3aPollerContext poller_context = {};
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
|
||||
nfc_poller_start(poller, iso14443_3a_poller_read_callback, &poller_context);
|
||||
furi_thread_flags_wait(
|
||||
ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(ISO14443_3A_POLLER_FLAG_COMMAND_COMPLETE);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(poller_context.error == Iso14443_3aErrorNone) {
|
||||
*iso14443_3a_data = poller_context.data;
|
||||
}
|
||||
|
||||
return poller_context.error;
|
||||
}
|
||||
14
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h
Normal file
14
lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3a.h"
|
||||
#include <nfc/nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Iso14443_3aError iso14443_3a_poller_read(Nfc* nfc, Iso14443_3aData* iso14443_3a_data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
223
lib/nfc/protocols/iso14443_3b/iso14443_3b.c
Normal file
223
lib/nfc/protocols/iso14443_3b/iso14443_3b.c
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "iso14443_3b_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
#include <nfc/nfc_common.h>
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define ISO14443_3B_PROTOCOL_NAME "ISO14443-3B"
|
||||
#define ISO14443_3B_DEVICE_NAME "ISO14443-3B (Unknown)"
|
||||
|
||||
#define ISO14443_3B_APP_DATA_KEY "Application data"
|
||||
#define ISO14443_3B_PROTOCOL_INFO_KEY "Protocol info"
|
||||
|
||||
#define ISO14443_3B_FDT_POLL_DEFAULT_FC (ISO14443_3B_FDT_POLL_FC)
|
||||
|
||||
const NfcDeviceBase nfc_device_iso14443_3b = {
|
||||
.protocol_name = ISO14443_3B_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)iso14443_3b_alloc,
|
||||
.free = (NfcDeviceFree)iso14443_3b_free,
|
||||
.reset = (NfcDeviceReset)iso14443_3b_reset,
|
||||
.copy = (NfcDeviceCopy)iso14443_3b_copy,
|
||||
.verify = (NfcDeviceVerify)iso14443_3b_verify,
|
||||
.load = (NfcDeviceLoad)iso14443_3b_load,
|
||||
.save = (NfcDeviceSave)iso14443_3b_save,
|
||||
.is_equal = (NfcDeviceEqual)iso14443_3b_is_equal,
|
||||
.get_name = (NfcDeviceGetName)iso14443_3b_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)iso14443_3b_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)iso14443_3b_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)iso14443_3b_get_base_data,
|
||||
};
|
||||
|
||||
Iso14443_3bData* iso14443_3b_alloc() {
|
||||
Iso14443_3bData* data = malloc(sizeof(Iso14443_3bData));
|
||||
return data;
|
||||
}
|
||||
|
||||
void iso14443_3b_free(Iso14443_3bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void iso14443_3b_reset(Iso14443_3bData* data) {
|
||||
memset(data, 0, sizeof(Iso14443_3bData));
|
||||
}
|
||||
|
||||
void iso14443_3b_copy(Iso14443_3bData* data, const Iso14443_3bData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
*data = *other;
|
||||
}
|
||||
|
||||
bool iso14443_3b_verify(Iso14443_3bData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(device_type);
|
||||
// No support for old ISO14443-3B
|
||||
return false;
|
||||
}
|
||||
|
||||
bool iso14443_3b_load(Iso14443_3bData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
if(version < NFC_UNIFIED_FORMAT_VERSION) break;
|
||||
|
||||
if(!flipper_format_read_hex(
|
||||
ff, ISO14443_3B_APP_DATA_KEY, data->app_data, ISO14443_3B_APP_DATA_SIZE))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
ISO14443_3B_PROTOCOL_INFO_KEY,
|
||||
(uint8_t*)&data->protocol_info,
|
||||
sizeof(Iso14443_3bProtocolInfo)))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool iso14443_3b_save(const Iso14443_3bData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(ff, ISO14443_3B_PROTOCOL_NAME " specific data"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff, ISO14443_3B_APP_DATA_KEY, data->app_data, ISO14443_3B_APP_DATA_SIZE))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
ISO14443_3B_PROTOCOL_INFO_KEY,
|
||||
(uint8_t*)&data->protocol_info,
|
||||
sizeof(Iso14443_3bProtocolInfo)))
|
||||
break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool iso14443_3b_is_equal(const Iso14443_3bData* data, const Iso14443_3bData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
return memcmp(data, other, sizeof(Iso14443_3bData)) == 0;
|
||||
}
|
||||
|
||||
const char* iso14443_3b_get_device_name(const Iso14443_3bData* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
|
||||
return ISO14443_3B_DEVICE_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* iso14443_3b_get_uid(const Iso14443_3bData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
furi_assert(uid_len);
|
||||
|
||||
*uid_len = ISO14443_3B_UID_SIZE;
|
||||
return data->uid;
|
||||
}
|
||||
|
||||
bool iso14443_3b_set_uid(Iso14443_3bData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
const bool uid_valid = uid_len == ISO14443_3B_UID_SIZE;
|
||||
|
||||
if(uid_valid) {
|
||||
memcpy(data->uid, uid, uid_len);
|
||||
}
|
||||
|
||||
return uid_valid;
|
||||
}
|
||||
|
||||
Iso14443_3bData* iso14443_3b_get_base_data(const Iso14443_3bData* data) {
|
||||
UNUSED(data);
|
||||
furi_crash("No base data");
|
||||
}
|
||||
|
||||
bool iso14443_3b_supports_iso14443_4(const Iso14443_3bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->protocol_info.protocol_type == 0x01;
|
||||
}
|
||||
|
||||
bool iso14443_3b_supports_bit_rate(const Iso14443_3bData* data, Iso14443_3bBitRate bit_rate) {
|
||||
furi_assert(data);
|
||||
|
||||
const uint8_t capability = data->protocol_info.bit_rate_capability;
|
||||
|
||||
switch(bit_rate) {
|
||||
case Iso14443_3bBitRateBoth106Kbit:
|
||||
return capability == ISO14443_3B_BIT_RATE_BOTH_106KBIT;
|
||||
case Iso14443_3bBitRatePiccToPcd212Kbit:
|
||||
return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_212KBIT;
|
||||
case Iso14443_3bBitRatePiccToPcd424Kbit:
|
||||
return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_424KBIT;
|
||||
case Iso14443_3bBitRatePiccToPcd848Kbit:
|
||||
return capability & ISO14443_3B_BIT_RATE_PICC_TO_PCD_848KBIT;
|
||||
case Iso14443_3bBitRatePcdToPicc212Kbit:
|
||||
return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_212KBIT;
|
||||
case Iso14443_3bBitRatePcdToPicc424Kbit:
|
||||
return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_424KBIT;
|
||||
case Iso14443_3bBitRatePcdToPicc848Kbit:
|
||||
return capability & ISO14443_3B_BIT_RATE_PCD_TO_PICC_848KBIT;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool iso14443_3b_supports_frame_option(const Iso14443_3bData* data, Iso14443_3bFrameOption option) {
|
||||
furi_assert(data);
|
||||
|
||||
switch(option) {
|
||||
case Iso14443_3bFrameOptionNad:
|
||||
return data->protocol_info.fo & ISO14443_3B_FRAME_OPTION_NAD;
|
||||
case Iso14443_3bFrameOptionCid:
|
||||
return data->protocol_info.fo & ISO14443_3B_FRAME_OPTION_CID;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* iso14443_3b_get_application_data(const Iso14443_3bData* data, size_t* data_size) {
|
||||
furi_assert(data);
|
||||
furi_assert(data_size);
|
||||
|
||||
*data_size = ISO14443_3B_APP_DATA_SIZE;
|
||||
return data->app_data;
|
||||
}
|
||||
|
||||
uint16_t iso14443_3b_get_frame_size_max(const Iso14443_3bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
const uint8_t fs_bits = data->protocol_info.max_frame_size;
|
||||
|
||||
if(fs_bits < 5) {
|
||||
return fs_bits * 8 + 16;
|
||||
} else if(fs_bits == 5) {
|
||||
return 64;
|
||||
} else if(fs_bits == 6) {
|
||||
return 96;
|
||||
} else if(fs_bits < 13) {
|
||||
return 128U << (fs_bits - 7);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t iso14443_3b_get_fwt_fc_max(const Iso14443_3bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
const uint8_t fwi = data->protocol_info.fwi;
|
||||
return fwi < 0x0F ? 4096UL << fwi : ISO14443_3B_FDT_POLL_DEFAULT_FC;
|
||||
}
|
||||
83
lib/nfc/protocols/iso14443_3b/iso14443_3b.h
Normal file
83
lib/nfc/protocols/iso14443_3b/iso14443_3b.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base.h>
|
||||
|
||||
#include <core/string.h>
|
||||
#include <toolbox/bit_buffer.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3bErrorNone,
|
||||
Iso14443_3bErrorNotPresent,
|
||||
Iso14443_3bErrorColResFailed,
|
||||
Iso14443_3bErrorBufferOverflow,
|
||||
Iso14443_3bErrorCommunication,
|
||||
Iso14443_3bErrorFieldOff,
|
||||
Iso14443_3bErrorWrongCrc,
|
||||
Iso14443_3bErrorTimeout,
|
||||
} Iso14443_3bError;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3bBitRateBoth106Kbit,
|
||||
Iso14443_3bBitRatePiccToPcd212Kbit,
|
||||
Iso14443_3bBitRatePiccToPcd424Kbit,
|
||||
Iso14443_3bBitRatePiccToPcd848Kbit,
|
||||
Iso14443_3bBitRatePcdToPicc212Kbit,
|
||||
Iso14443_3bBitRatePcdToPicc424Kbit,
|
||||
Iso14443_3bBitRatePcdToPicc848Kbit,
|
||||
} Iso14443_3bBitRate;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3bFrameOptionNad,
|
||||
Iso14443_3bFrameOptionCid,
|
||||
} Iso14443_3bFrameOption;
|
||||
|
||||
typedef struct Iso14443_3bData Iso14443_3bData;
|
||||
|
||||
// Virtual methods
|
||||
|
||||
Iso14443_3bData* iso14443_3b_alloc();
|
||||
|
||||
void iso14443_3b_free(Iso14443_3bData* data);
|
||||
|
||||
void iso14443_3b_reset(Iso14443_3bData* data);
|
||||
|
||||
void iso14443_3b_copy(Iso14443_3bData* data, const Iso14443_3bData* other);
|
||||
|
||||
bool iso14443_3b_verify(Iso14443_3bData* data, const FuriString* device_type);
|
||||
|
||||
bool iso14443_3b_load(Iso14443_3bData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool iso14443_3b_save(const Iso14443_3bData* data, FlipperFormat* ff);
|
||||
|
||||
bool iso14443_3b_is_equal(const Iso14443_3bData* data, const Iso14443_3bData* other);
|
||||
|
||||
const char* iso14443_3b_get_device_name(const Iso14443_3bData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* iso14443_3b_get_uid(const Iso14443_3bData* data, size_t* uid_len);
|
||||
|
||||
bool iso14443_3b_set_uid(Iso14443_3bData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_3bData* iso14443_3b_get_base_data(const Iso14443_3bData* data);
|
||||
|
||||
// Getters and tests
|
||||
|
||||
bool iso14443_3b_supports_iso14443_4(const Iso14443_3bData* data);
|
||||
|
||||
bool iso14443_3b_supports_bit_rate(const Iso14443_3bData* data, Iso14443_3bBitRate bit_rate);
|
||||
|
||||
bool iso14443_3b_supports_frame_option(const Iso14443_3bData* data, Iso14443_3bFrameOption option);
|
||||
|
||||
const uint8_t* iso14443_3b_get_application_data(const Iso14443_3bData* data, size_t* data_size);
|
||||
|
||||
uint16_t iso14443_3b_get_frame_size_max(const Iso14443_3bData* data);
|
||||
|
||||
uint32_t iso14443_3b_get_fwt_fc_max(const Iso14443_3bData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h
Normal file
5
lib/nfc/protocols/iso14443_3b/iso14443_3b_device_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_3b;
|
||||
1
lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c
Normal file
1
lib/nfc/protocols/iso14443_3b/iso14443_3b_i.c
Normal file
@@ -0,0 +1 @@
|
||||
#include "iso14443_3b_i.h"
|
||||
37
lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h
Normal file
37
lib/nfc/protocols/iso14443_3b/iso14443_3b_i.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3b.h"
|
||||
|
||||
#define ISO14443_3B_UID_SIZE (4U)
|
||||
#define ISO14443_3B_APP_DATA_SIZE (4U)
|
||||
|
||||
#define ISO14443_3B_GUARD_TIME_US (5000U)
|
||||
#define ISO14443_3B_FDT_POLL_FC (9000U)
|
||||
#define ISO14443_3B_POLL_POLL_MIN_US (1280U)
|
||||
|
||||
#define ISO14443_3B_BIT_RATE_BOTH_106KBIT (0U << 0)
|
||||
#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_212KBIT (1U << 0)
|
||||
#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_424KBIT (1U << 1)
|
||||
#define ISO14443_3B_BIT_RATE_PCD_TO_PICC_848KBIT (1U << 2)
|
||||
#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_212KBIT (1U << 4)
|
||||
#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_424KBIT (1U << 5)
|
||||
#define ISO14443_3B_BIT_RATE_PICC_TO_PCD_848KBIT (1U << 6)
|
||||
#define ISO14443_3B_BIT_RATE_BOTH_SAME_COMPULSORY (1U << 7)
|
||||
|
||||
#define ISO14443_3B_FRAME_OPTION_NAD (1U << 1)
|
||||
#define ISO14443_3B_FRAME_OPTION_CID (1U << 0)
|
||||
|
||||
typedef struct {
|
||||
uint8_t bit_rate_capability;
|
||||
uint8_t protocol_type : 4;
|
||||
uint8_t max_frame_size : 4;
|
||||
uint8_t fo : 2;
|
||||
uint8_t adc : 2;
|
||||
uint8_t fwi : 4;
|
||||
} Iso14443_3bProtocolInfo;
|
||||
|
||||
struct Iso14443_3bData {
|
||||
uint8_t uid[ISO14443_3B_UID_SIZE];
|
||||
uint8_t app_data[ISO14443_3B_APP_DATA_SIZE];
|
||||
Iso14443_3bProtocolInfo protocol_info;
|
||||
};
|
||||
121
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c
Normal file
121
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.c
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "iso14443_3b_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ISO14443_3bPoller"
|
||||
|
||||
const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static Iso14443_3bPoller* iso14443_3b_poller_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
Iso14443_3bPoller* instance = malloc(sizeof(Iso14443_3bPoller));
|
||||
instance->nfc = nfc;
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO14443_3B_POLLER_MAX_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(ISO14443_3B_POLLER_MAX_BUFFER_SIZE);
|
||||
|
||||
nfc_config(instance->nfc, NfcModePoller, NfcTechIso14443b);
|
||||
nfc_set_guard_time_us(instance->nfc, ISO14443_3B_GUARD_TIME_US);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, ISO14443_3B_FDT_POLL_FC);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, ISO14443_3B_POLL_POLL_MIN_US);
|
||||
instance->data = iso14443_3b_alloc();
|
||||
|
||||
instance->iso14443_3b_event.data = &instance->iso14443_3b_event_data;
|
||||
instance->general_event.protocol = NfcProtocolIso14443_3b;
|
||||
instance->general_event.event_data = &instance->iso14443_3b_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void iso14443_3b_poller_free(Iso14443_3bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(instance->data);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
iso14443_3b_free(instance->data);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void iso14443_3b_poller_set_callback(
|
||||
Iso14443_3bPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_3b_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Iso14443_3bPoller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
if(instance->state != Iso14443_3bPollerStateActivated) {
|
||||
Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data);
|
||||
if(error == Iso14443_3bErrorNone) {
|
||||
instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady;
|
||||
instance->iso14443_3b_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
} else {
|
||||
instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeError;
|
||||
instance->iso14443_3b_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
// Add delay to switch context
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
} else {
|
||||
instance->iso14443_3b_event.type = Iso14443_3bPollerEventTypeReady;
|
||||
instance->iso14443_3b_event_data.error = Iso14443_3bErrorNone;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool iso14443_3b_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
|
||||
bool protocol_detected = false;
|
||||
Iso14443_3bPoller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
furi_assert(instance->state == Iso14443_3bPollerStateIdle);
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
Iso14443_3bError error = iso14443_3b_poller_async_activate(instance, instance->data);
|
||||
protocol_detected = (error == Iso14443_3bErrorNone);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase nfc_poller_iso14443_3b = {
|
||||
.alloc = (NfcPollerAlloc)iso14443_3b_poller_alloc,
|
||||
.free = (NfcPollerFree)iso14443_3b_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)iso14443_3b_poller_set_callback,
|
||||
.run = (NfcPollerRun)iso14443_3b_poller_run,
|
||||
.detect = (NfcPollerDetect)iso14443_3b_poller_detect,
|
||||
.get_data = (NfcPollerGetData)iso14443_3b_poller_get_data,
|
||||
};
|
||||
30
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h
Normal file
30
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3b.h"
|
||||
#include <lib/nfc/nfc.h>
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_3bPoller Iso14443_3bPoller;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3bPollerEventTypeError,
|
||||
Iso14443_3bPollerEventTypeReady,
|
||||
} Iso14443_3bPollerEventType;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3bError error;
|
||||
} Iso14443_3bPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3bPollerEventType type;
|
||||
Iso14443_3bPollerEventData* data;
|
||||
} Iso14443_3bPollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h
Normal file
5
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso14443_3b;
|
||||
194
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c
Normal file
194
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.c
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "iso14443_3b_poller_i.h"
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define TAG "Iso14443_3bPoller"
|
||||
|
||||
#define ISO14443_3B_ATTRIB_FRAME_SIZE_256 (0x08)
|
||||
|
||||
static Iso14443_3bError iso14443_3b_poller_process_error(NfcError error) {
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
return Iso14443_3bErrorNone;
|
||||
case NfcErrorTimeout:
|
||||
return Iso14443_3bErrorTimeout;
|
||||
default:
|
||||
return Iso14443_3bErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
static Iso14443_3bError iso14443_3b_poller_prepare_trx(Iso14443_3bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
if(instance->state == Iso14443_3bPollerStateIdle) {
|
||||
return iso14443_3b_poller_async_activate(instance, NULL);
|
||||
}
|
||||
|
||||
return Iso14443_3bErrorNone;
|
||||
}
|
||||
|
||||
static Iso14443_3bError iso14443_3b_poller_frame_exchange(
|
||||
Iso14443_3bPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
|
||||
const size_t tx_bytes = bit_buffer_get_size_bytes(tx_buffer);
|
||||
furi_assert(
|
||||
tx_bytes <= bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO14443_CRC_SIZE);
|
||||
|
||||
bit_buffer_copy(instance->tx_buffer, tx_buffer);
|
||||
iso14443_crc_append(Iso14443CrcTypeB, instance->tx_buffer);
|
||||
|
||||
Iso14443_3bError ret = Iso14443_3bErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error =
|
||||
nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = iso14443_3b_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy(rx_buffer, instance->rx_buffer);
|
||||
if(!iso14443_crc_check(Iso14443CrcTypeB, instance->rx_buffer)) {
|
||||
ret = Iso14443_3bErrorWrongCrc;
|
||||
break;
|
||||
}
|
||||
|
||||
iso14443_crc_trim(rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3bError
|
||||
iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
|
||||
iso14443_3b_reset(data);
|
||||
|
||||
Iso14443_3bError ret;
|
||||
|
||||
do {
|
||||
instance->state = Iso14443_3bPollerStateColResInProgress;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
// Send REQB
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x05);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x00);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x08);
|
||||
|
||||
ret = iso14443_3b_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3B_FDT_POLL_FC);
|
||||
if(ret != Iso14443_3bErrorNone) {
|
||||
instance->state = Iso14443_3bPollerStateColResFailed;
|
||||
break;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t flag;
|
||||
uint8_t uid[ISO14443_3B_UID_SIZE];
|
||||
uint8_t app_data[ISO14443_3B_APP_DATA_SIZE];
|
||||
Iso14443_3bProtocolInfo protocol_info;
|
||||
} Iso14443_3bAtqBLayout;
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(Iso14443_3bAtqBLayout)) {
|
||||
FURI_LOG_D(TAG, "Unexpected REQB response");
|
||||
instance->state = Iso14443_3bPollerStateColResFailed;
|
||||
ret = Iso14443_3bErrorCommunication;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = Iso14443_3bPollerStateActivationInProgress;
|
||||
|
||||
const Iso14443_3bAtqBLayout* atqb =
|
||||
(const Iso14443_3bAtqBLayout*)bit_buffer_get_data(instance->rx_buffer);
|
||||
|
||||
memcpy(data->uid, atqb->uid, ISO14443_3B_UID_SIZE);
|
||||
memcpy(data->app_data, atqb->app_data, ISO14443_3B_APP_DATA_SIZE);
|
||||
|
||||
data->protocol_info = atqb->protocol_info;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
// Send ATTRIB
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x1d);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, data->uid, ISO14443_3B_UID_SIZE);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x00);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO14443_3B_ATTRIB_FRAME_SIZE_256);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x01);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x00);
|
||||
|
||||
ret = iso14443_3b_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, iso14443_3b_get_fwt_fc_max(data));
|
||||
if(ret != Iso14443_3bErrorNone) {
|
||||
instance->state = Iso14443_3bPollerStateActivationFailed;
|
||||
break;
|
||||
}
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != 1 ||
|
||||
bit_buffer_get_byte(instance->rx_buffer, 0) != 0) {
|
||||
FURI_LOG_D(TAG, "Unexpected ATTRIB response");
|
||||
instance->state = Iso14443_3bPollerStateActivationFailed;
|
||||
ret = Iso14443_3bErrorCommunication;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = Iso14443_3bPollerStateActivated;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x50);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, instance->data->uid, ISO14443_3B_UID_SIZE);
|
||||
|
||||
Iso14443_3bError ret;
|
||||
|
||||
do {
|
||||
ret = iso14443_3b_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO14443_3B_FDT_POLL_FC);
|
||||
if(ret != Iso14443_3bErrorNone) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(uint8_t) ||
|
||||
bit_buffer_get_byte(instance->rx_buffer, 0) != 0) {
|
||||
ret = Iso14443_3bErrorCommunication;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = Iso14443_3bPollerStateIdle;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso14443_3bError iso14443_3b_poller_send_frame(
|
||||
Iso14443_3bPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer) {
|
||||
Iso14443_3bError ret;
|
||||
|
||||
do {
|
||||
ret = iso14443_3b_poller_prepare_trx(instance);
|
||||
if(ret != Iso14443_3bErrorNone) break;
|
||||
|
||||
ret = iso14443_3b_poller_frame_exchange(
|
||||
instance, tx_buffer, rx_buffer, iso14443_3b_get_fwt_fc_max(instance->data));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
49
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h
Normal file
49
lib/nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_3b_poller.h"
|
||||
#include "iso14443_3b_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO14443_3B_POLLER_MAX_BUFFER_SIZE (256U)
|
||||
|
||||
typedef enum {
|
||||
Iso14443_3bPollerStateIdle,
|
||||
Iso14443_3bPollerStateColResInProgress,
|
||||
Iso14443_3bPollerStateColResFailed,
|
||||
Iso14443_3bPollerStateActivationInProgress,
|
||||
Iso14443_3bPollerStateActivationFailed,
|
||||
Iso14443_3bPollerStateActivated,
|
||||
} Iso14443_3bPollerState;
|
||||
|
||||
struct Iso14443_3bPoller {
|
||||
Nfc* nfc;
|
||||
Iso14443_3bPollerState state;
|
||||
Iso14443_3bData* data;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
|
||||
NfcGenericEvent general_event;
|
||||
Iso14443_3bPollerEvent iso14443_3b_event;
|
||||
Iso14443_3bPollerEventData iso14443_3b_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
const Iso14443_3bData* iso14443_3b_poller_get_data(Iso14443_3bPoller* instance);
|
||||
|
||||
Iso14443_3bError
|
||||
iso14443_3b_poller_async_activate(Iso14443_3bPoller* instance, Iso14443_3bData* data);
|
||||
|
||||
Iso14443_3bError iso14443_3b_poller_halt(Iso14443_3bPoller* instance);
|
||||
|
||||
Iso14443_3bError iso14443_3b_poller_send_frame(
|
||||
Iso14443_3bPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
300
lib/nfc/protocols/iso14443_4a/iso14443_4a.c
Normal file
300
lib/nfc/protocols/iso14443_4a/iso14443_4a.c
Normal file
@@ -0,0 +1,300 @@
|
||||
#include "iso14443_4a_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define ISO14443_4A_PROTOCOL_NAME "ISO14443-4A"
|
||||
#define ISO14443_4A_DEVICE_NAME "ISO14443-4A (Unknown)"
|
||||
|
||||
#define ISO14443_4A_T0_KEY "T0"
|
||||
#define ISO14443_4A_TA1_KEY "TA(1)"
|
||||
#define ISO14443_4A_TB1_KEY "TB(1)"
|
||||
#define ISO14443_4A_TC1_KEY "TC(1)"
|
||||
#define ISO14443_4A_T1_TK_KEY "T1...Tk"
|
||||
|
||||
#define ISO14443_4A_FDT_DEFAULT_FC ISO14443_3A_FDT_POLL_FC
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aInterfaceByteTA1,
|
||||
Iso14443_4aInterfaceByteTB1,
|
||||
Iso14443_4aInterfaceByteTC1,
|
||||
} Iso14443_4aInterfaceByte;
|
||||
|
||||
const NfcDeviceBase nfc_device_iso14443_4a = {
|
||||
.protocol_name = ISO14443_4A_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)iso14443_4a_alloc,
|
||||
.free = (NfcDeviceFree)iso14443_4a_free,
|
||||
.reset = (NfcDeviceReset)iso14443_4a_reset,
|
||||
.copy = (NfcDeviceCopy)iso14443_4a_copy,
|
||||
.verify = (NfcDeviceVerify)iso14443_4a_verify,
|
||||
.load = (NfcDeviceLoad)iso14443_4a_load,
|
||||
.save = (NfcDeviceSave)iso14443_4a_save,
|
||||
.is_equal = (NfcDeviceEqual)iso14443_4a_is_equal,
|
||||
.get_name = (NfcDeviceGetName)iso14443_4a_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)iso14443_4a_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)iso14443_4a_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)iso14443_4a_get_base_data,
|
||||
};
|
||||
|
||||
Iso14443_4aData* iso14443_4a_alloc() {
|
||||
Iso14443_4aData* data = malloc(sizeof(Iso14443_4aData));
|
||||
|
||||
data->iso14443_3a_data = iso14443_3a_alloc();
|
||||
data->ats_data.t1_tk = simple_array_alloc(&simple_array_config_uint8_t);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void iso14443_4a_free(Iso14443_4aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
simple_array_free(data->ats_data.t1_tk);
|
||||
iso14443_3a_free(data->iso14443_3a_data);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void iso14443_4a_reset(Iso14443_4aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_reset(data->iso14443_3a_data);
|
||||
|
||||
data->ats_data.tl = 1;
|
||||
data->ats_data.t0 = 0;
|
||||
data->ats_data.ta_1 = 0;
|
||||
data->ats_data.tb_1 = 0;
|
||||
data->ats_data.tc_1 = 0;
|
||||
|
||||
simple_array_reset(data->ats_data.t1_tk);
|
||||
}
|
||||
|
||||
void iso14443_4a_copy(Iso14443_4aData* data, const Iso14443_4aData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data);
|
||||
|
||||
data->ats_data.tl = other->ats_data.tl;
|
||||
data->ats_data.t0 = other->ats_data.t0;
|
||||
data->ats_data.ta_1 = other->ats_data.ta_1;
|
||||
data->ats_data.tb_1 = other->ats_data.tb_1;
|
||||
data->ats_data.tc_1 = other->ats_data.tc_1;
|
||||
|
||||
simple_array_copy(data->ats_data.t1_tk, other->ats_data.t1_tk);
|
||||
}
|
||||
|
||||
bool iso14443_4a_verify(Iso14443_4aData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(device_type);
|
||||
|
||||
// Empty, unified file format only
|
||||
return false;
|
||||
}
|
||||
|
||||
bool iso14443_4a_load(Iso14443_4aData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break;
|
||||
|
||||
Iso14443_4aAtsData* ats_data = &data->ats_data;
|
||||
|
||||
ats_data->tl = 1;
|
||||
|
||||
if(flipper_format_key_exist(ff, ISO14443_4A_T0_KEY)) {
|
||||
if(!flipper_format_read_hex(ff, ISO14443_4A_T0_KEY, &ats_data->t0, 1)) break;
|
||||
++ats_data->tl;
|
||||
}
|
||||
|
||||
if(ats_data->t0 & ISO14443_4A_ATS_T0_TA1) {
|
||||
if(!flipper_format_key_exist(ff, ISO14443_4A_TA1_KEY)) break;
|
||||
if(!flipper_format_read_hex(ff, ISO14443_4A_TA1_KEY, &ats_data->ta_1, 1)) break;
|
||||
++ats_data->tl;
|
||||
}
|
||||
if(ats_data->t0 & ISO14443_4A_ATS_T0_TB1) {
|
||||
if(!flipper_format_key_exist(ff, ISO14443_4A_TB1_KEY)) break;
|
||||
if(!flipper_format_read_hex(ff, ISO14443_4A_TB1_KEY, &ats_data->tb_1, 1)) break;
|
||||
++ats_data->tl;
|
||||
}
|
||||
if(ats_data->t0 & ISO14443_4A_ATS_T0_TC1) {
|
||||
if(!flipper_format_key_exist(ff, ISO14443_4A_TC1_KEY)) break;
|
||||
if(!flipper_format_read_hex(ff, ISO14443_4A_TC1_KEY, &ats_data->tc_1, 1)) break;
|
||||
++ats_data->tl;
|
||||
}
|
||||
|
||||
if(flipper_format_key_exist(ff, ISO14443_4A_T1_TK_KEY)) {
|
||||
uint32_t t1_tk_size;
|
||||
if(!flipper_format_get_value_count(ff, ISO14443_4A_T1_TK_KEY, &t1_tk_size)) break;
|
||||
|
||||
if(t1_tk_size > 0) {
|
||||
simple_array_init(ats_data->t1_tk, t1_tk_size);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
ISO14443_4A_T1_TK_KEY,
|
||||
simple_array_get_data(ats_data->t1_tk),
|
||||
t1_tk_size))
|
||||
break;
|
||||
ats_data->tl += t1_tk_size;
|
||||
}
|
||||
}
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool iso14443_4a_save(const Iso14443_4aData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break;
|
||||
if(!flipper_format_write_comment_cstr(ff, ISO14443_4A_PROTOCOL_NAME " specific data"))
|
||||
break;
|
||||
|
||||
const Iso14443_4aAtsData* ats_data = &data->ats_data;
|
||||
|
||||
if(ats_data->tl > 1) {
|
||||
if(!flipper_format_write_hex(ff, ISO14443_4A_T0_KEY, &ats_data->t0, 1)) break;
|
||||
|
||||
if(ats_data->t0 & ISO14443_4A_ATS_T0_TA1) {
|
||||
if(!flipper_format_write_hex(ff, ISO14443_4A_TA1_KEY, &ats_data->ta_1, 1)) break;
|
||||
}
|
||||
if(ats_data->t0 & ISO14443_4A_ATS_T0_TB1) {
|
||||
if(!flipper_format_write_hex(ff, ISO14443_4A_TB1_KEY, &ats_data->tb_1, 1)) break;
|
||||
}
|
||||
if(ats_data->t0 & ISO14443_4A_ATS_T0_TC1) {
|
||||
if(!flipper_format_write_hex(ff, ISO14443_4A_TC1_KEY, &ats_data->tc_1, 1)) break;
|
||||
}
|
||||
|
||||
const uint32_t t1_tk_size = simple_array_get_count(ats_data->t1_tk);
|
||||
if(t1_tk_size > 0) {
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
ISO14443_4A_T1_TK_KEY,
|
||||
simple_array_cget_data(ats_data->t1_tk),
|
||||
t1_tk_size))
|
||||
break;
|
||||
}
|
||||
}
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool iso14443_4a_is_equal(const Iso14443_4aData* data, const Iso14443_4aData* other) {
|
||||
return iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data);
|
||||
}
|
||||
|
||||
const char* iso14443_4a_get_device_name(const Iso14443_4aData* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
return ISO14443_4A_DEVICE_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* iso14443_4a_get_uid(const Iso14443_4aData* data, size_t* uid_len) {
|
||||
return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len);
|
||||
}
|
||||
|
||||
bool iso14443_4a_set_uid(Iso14443_4aData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len);
|
||||
}
|
||||
|
||||
Iso14443_3aData* iso14443_4a_get_base_data(const Iso14443_4aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->iso14443_3a_data;
|
||||
}
|
||||
|
||||
uint16_t iso14443_4a_get_frame_size_max(const Iso14443_4aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
const uint8_t fsci = data->ats_data.t0 & 0x0F;
|
||||
|
||||
if(fsci < 5) {
|
||||
return fsci * 8 + 16;
|
||||
} else if(fsci == 5) {
|
||||
return 64;
|
||||
} else if(fsci == 6) {
|
||||
return 96;
|
||||
} else if(fsci < 13) {
|
||||
return 128U << (fsci - 7);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t iso14443_4a_get_fwt_fc_max(const Iso14443_4aData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
uint32_t fwt_fc_max = ISO14443_4A_FDT_DEFAULT_FC;
|
||||
|
||||
do {
|
||||
if(!(data->ats_data.tl > 1)) break;
|
||||
if(!(data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1)) break;
|
||||
|
||||
const uint8_t fwi = data->ats_data.tb_1 >> 4;
|
||||
if(fwi == 0x0F) break;
|
||||
|
||||
fwt_fc_max = 4096UL << fwi;
|
||||
} while(false);
|
||||
|
||||
return fwt_fc_max;
|
||||
}
|
||||
|
||||
const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uint32_t* count) {
|
||||
furi_assert(data);
|
||||
furi_assert(count);
|
||||
|
||||
*count = simple_array_get_count(data->ats_data.t1_tk);
|
||||
return simple_array_cget_data(data->ats_data.t1_tk);
|
||||
}
|
||||
|
||||
bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate) {
|
||||
furi_assert(data);
|
||||
|
||||
if(!(data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1))
|
||||
return bit_rate == Iso14443_4aBitRateBoth106Kbit;
|
||||
|
||||
const uint8_t ta_1 = data->ats_data.ta_1;
|
||||
|
||||
switch(bit_rate) {
|
||||
case Iso14443_4aBitRateBoth106Kbit:
|
||||
return ta_1 == ISO14443_4A_ATS_TA1_BOTH_SAME_COMPULSORY;
|
||||
case Iso14443_4aBitRatePiccToPcd212Kbit:
|
||||
return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_212KBIT;
|
||||
case Iso14443_4aBitRatePiccToPcd424Kbit:
|
||||
return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_424KBIT;
|
||||
case Iso14443_4aBitRatePiccToPcd848Kbit:
|
||||
return ta_1 & ISO14443_4A_ATS_TA1_PCD_TO_PICC_848KBIT;
|
||||
case Iso14443_4aBitRatePcdToPicc212Kbit:
|
||||
return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_212KBIT;
|
||||
case Iso14443_4aBitRatePcdToPicc424Kbit:
|
||||
return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_424KBIT;
|
||||
case Iso14443_4aBitRatePcdToPicc848Kbit:
|
||||
return ta_1 & ISO14443_4A_ATS_TA1_PICC_TO_PCD_848KBIT;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool iso14443_4a_supports_frame_option(const Iso14443_4aData* data, Iso14443_4aFrameOption option) {
|
||||
furi_assert(data);
|
||||
|
||||
const Iso14443_4aAtsData* ats_data = &data->ats_data;
|
||||
if(!(ats_data->t0 & ISO14443_4A_ATS_T0_TC1)) return false;
|
||||
|
||||
switch(option) {
|
||||
case Iso14443_4aFrameOptionNad:
|
||||
return ats_data->tc_1 & ISO14443_4A_ATS_TC1_NAD;
|
||||
case Iso14443_4aFrameOptionCid:
|
||||
return ats_data->tc_1 & ISO14443_4A_ATS_TC1_CID;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
73
lib/nfc/protocols/iso14443_4a/iso14443_4a.h
Normal file
73
lib/nfc/protocols/iso14443_4a/iso14443_4a.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aErrorNone,
|
||||
Iso14443_4aErrorNotPresent,
|
||||
Iso14443_4aErrorProtocol,
|
||||
Iso14443_4aErrorTimeout,
|
||||
} Iso14443_4aError;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aBitRateBoth106Kbit,
|
||||
Iso14443_4aBitRatePiccToPcd212Kbit,
|
||||
Iso14443_4aBitRatePiccToPcd424Kbit,
|
||||
Iso14443_4aBitRatePiccToPcd848Kbit,
|
||||
Iso14443_4aBitRatePcdToPicc212Kbit,
|
||||
Iso14443_4aBitRatePcdToPicc424Kbit,
|
||||
Iso14443_4aBitRatePcdToPicc848Kbit,
|
||||
} Iso14443_4aBitRate;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aFrameOptionNad,
|
||||
Iso14443_4aFrameOptionCid,
|
||||
} Iso14443_4aFrameOption;
|
||||
|
||||
typedef struct Iso14443_4aData Iso14443_4aData;
|
||||
|
||||
// Virtual methods
|
||||
|
||||
Iso14443_4aData* iso14443_4a_alloc();
|
||||
|
||||
void iso14443_4a_free(Iso14443_4aData* data);
|
||||
|
||||
void iso14443_4a_reset(Iso14443_4aData* data);
|
||||
|
||||
void iso14443_4a_copy(Iso14443_4aData* data, const Iso14443_4aData* other);
|
||||
|
||||
bool iso14443_4a_verify(Iso14443_4aData* data, const FuriString* device_type);
|
||||
|
||||
bool iso14443_4a_load(Iso14443_4aData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool iso14443_4a_save(const Iso14443_4aData* data, FlipperFormat* ff);
|
||||
|
||||
bool iso14443_4a_is_equal(const Iso14443_4aData* data, const Iso14443_4aData* other);
|
||||
|
||||
const char* iso14443_4a_get_device_name(const Iso14443_4aData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* iso14443_4a_get_uid(const Iso14443_4aData* data, size_t* uid_len);
|
||||
|
||||
bool iso14443_4a_set_uid(Iso14443_4aData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_3aData* iso14443_4a_get_base_data(const Iso14443_4aData* data);
|
||||
|
||||
// Getters & Tests
|
||||
|
||||
uint16_t iso14443_4a_get_frame_size_max(const Iso14443_4aData* data);
|
||||
|
||||
uint32_t iso14443_4a_get_fwt_fc_max(const Iso14443_4aData* data);
|
||||
|
||||
const uint8_t* iso14443_4a_get_historical_bytes(const Iso14443_4aData* data, uint32_t* count);
|
||||
|
||||
bool iso14443_4a_supports_bit_rate(const Iso14443_4aData* data, Iso14443_4aBitRate bit_rate);
|
||||
|
||||
bool iso14443_4a_supports_frame_option(const Iso14443_4aData* data, Iso14443_4aFrameOption option);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h
Normal file
5
lib/nfc/protocols/iso14443_4a/iso14443_4a_device_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_4a;
|
||||
71
lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c
Normal file
71
lib/nfc/protocols/iso14443_4a/iso14443_4a_i.c
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "iso14443_4a_i.h"
|
||||
|
||||
bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf) {
|
||||
bool can_parse = false;
|
||||
|
||||
do {
|
||||
const size_t buf_size = bit_buffer_get_size_bytes(buf);
|
||||
if(buf_size == 0) break;
|
||||
|
||||
size_t current_index = 0;
|
||||
|
||||
const uint8_t tl = bit_buffer_get_byte(buf, current_index++);
|
||||
if(tl != buf_size) break;
|
||||
|
||||
data->tl = tl;
|
||||
|
||||
if(tl > 1) {
|
||||
const uint8_t t0 = bit_buffer_get_byte(buf, current_index++);
|
||||
|
||||
const bool has_ta_1 = t0 & ISO14443_4A_ATS_T0_TA1;
|
||||
const bool has_tb_1 = t0 & ISO14443_4A_ATS_T0_TB1;
|
||||
const bool has_tc_1 = t0 & ISO14443_4A_ATS_T0_TC1;
|
||||
|
||||
const uint8_t buf_size_min =
|
||||
2 + (has_ta_1 ? 1 : 0) + (has_tb_1 ? 1 : 0) + (has_tc_1 ? 1 : 0);
|
||||
|
||||
if(buf_size < buf_size_min) break;
|
||||
|
||||
data->t0 = t0;
|
||||
|
||||
if(has_ta_1) {
|
||||
data->ta_1 = bit_buffer_get_byte(buf, current_index++);
|
||||
}
|
||||
if(has_tb_1) {
|
||||
data->tb_1 = bit_buffer_get_byte(buf, current_index++);
|
||||
}
|
||||
if(has_tc_1) {
|
||||
data->tc_1 = bit_buffer_get_byte(buf, current_index++);
|
||||
}
|
||||
|
||||
const uint8_t t1_tk_size = buf_size - buf_size_min;
|
||||
|
||||
if(t1_tk_size > 0) {
|
||||
simple_array_init(data->t1_tk, t1_tk_size);
|
||||
bit_buffer_write_bytes_mid(
|
||||
buf, simple_array_get_data(data->t1_tk), current_index, t1_tk_size);
|
||||
}
|
||||
}
|
||||
|
||||
can_parse = true;
|
||||
} while(false);
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error) {
|
||||
switch(error) {
|
||||
case Iso14443_3aErrorNone:
|
||||
return Iso14443_4aErrorNone;
|
||||
case Iso14443_3aErrorNotPresent:
|
||||
return Iso14443_4aErrorNotPresent;
|
||||
case Iso14443_3aErrorColResFailed:
|
||||
case Iso14443_3aErrorCommunication:
|
||||
case Iso14443_3aErrorWrongCrc:
|
||||
return Iso14443_4aErrorProtocol;
|
||||
case Iso14443_3aErrorTimeout:
|
||||
return Iso14443_4aErrorTimeout;
|
||||
default:
|
||||
return Iso14443_4aErrorProtocol;
|
||||
}
|
||||
}
|
||||
42
lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h
Normal file
42
lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_4a.h"
|
||||
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
#define ISO14443_4A_CMD_READ_ATS (0xE0)
|
||||
|
||||
// ATS bit definitions
|
||||
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
|
||||
#define ISO14443_4A_ATS_T0_TB1 (1U << 5)
|
||||
#define ISO14443_4A_ATS_T0_TC1 (1U << 6)
|
||||
|
||||
#define ISO14443_4A_ATS_TA1_BOTH_106KBIT (0U << 0)
|
||||
#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_212KBIT (1U << 0)
|
||||
#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_424KBIT (1U << 1)
|
||||
#define ISO14443_4A_ATS_TA1_PCD_TO_PICC_848KBIT (1U << 2)
|
||||
#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_212KBIT (1U << 4)
|
||||
#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_424KBIT (1U << 5)
|
||||
#define ISO14443_4A_ATS_TA1_PICC_TO_PCD_848KBIT (1U << 6)
|
||||
#define ISO14443_4A_ATS_TA1_BOTH_SAME_COMPULSORY (1U << 7)
|
||||
|
||||
#define ISO14443_4A_ATS_TC1_NAD (1U << 0)
|
||||
#define ISO14443_4A_ATS_TC1_CID (1U << 1)
|
||||
|
||||
typedef struct {
|
||||
uint8_t tl;
|
||||
uint8_t t0;
|
||||
uint8_t ta_1;
|
||||
uint8_t tb_1;
|
||||
uint8_t tc_1;
|
||||
SimpleArray* t1_tk;
|
||||
} Iso14443_4aAtsData;
|
||||
|
||||
struct Iso14443_4aData {
|
||||
Iso14443_3aData* iso14443_3a_data;
|
||||
Iso14443_4aAtsData ats_data;
|
||||
};
|
||||
|
||||
bool iso14443_4a_ats_parse(Iso14443_4aAtsData* data, const BitBuffer* buf);
|
||||
|
||||
Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error);
|
||||
99
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c
Normal file
99
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "iso14443_4a_listener_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
#define TAG "Iso14443_4aListener"
|
||||
|
||||
#define ISO14443_4A_LISTENER_BUF_SIZE (256U)
|
||||
|
||||
static Iso14443_4aListener*
|
||||
iso14443_4a_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, Iso14443_4aData* data) {
|
||||
furi_assert(iso14443_3a_listener);
|
||||
|
||||
Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener));
|
||||
instance->iso14443_3a_listener = iso14443_3a_listener;
|
||||
instance->data = data;
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE);
|
||||
|
||||
instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data;
|
||||
instance->generic_event.protocol = NfcProtocolIso14443_4a;
|
||||
instance->generic_event.instance = instance;
|
||||
instance->generic_event.event_data = &instance->iso14443_4a_event;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void iso14443_4a_listener_free(Iso14443_4aListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->tx_buffer);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void iso14443_4a_listener_set_callback(
|
||||
Iso14443_4aListener* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static const Iso14443_4aData* iso14443_4a_listener_get_data(Iso14443_4aListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Iso14443_4aListener* instance = context;
|
||||
Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data;
|
||||
BitBuffer* rx_buffer = iso14443_3a_event->data->buffer;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) {
|
||||
if(instance->state == Iso14443_4aListenerStateIdle) {
|
||||
if(bit_buffer_get_size_bytes(rx_buffer) == 2 &&
|
||||
bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) {
|
||||
if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) !=
|
||||
Iso14443_4aErrorNone) {
|
||||
command = NfcCommandContinue;
|
||||
} else {
|
||||
instance->state = Iso14443_4aListenerStateActive;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData;
|
||||
instance->iso14443_4a_event.data->buffer = rx_buffer;
|
||||
|
||||
if(instance->callback) {
|
||||
command = instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
}
|
||||
} else if(
|
||||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ||
|
||||
iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) {
|
||||
instance->state = Iso14443_4aListenerStateIdle;
|
||||
command = NfcCommandContinue;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
const NfcListenerBase nfc_listener_iso14443_4a = {
|
||||
.alloc = (NfcListenerAlloc)iso14443_4a_listener_alloc,
|
||||
.free = (NfcListenerFree)iso14443_4a_listener_free,
|
||||
.set_callback = (NfcListenerSetCallback)iso14443_4a_listener_set_callback,
|
||||
.get_data = (NfcListenerGetData)iso14443_4a_listener_get_data,
|
||||
.run = (NfcListenerRun)iso14443_4a_listener_run,
|
||||
};
|
||||
29
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h
Normal file
29
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h>
|
||||
|
||||
#include "iso14443_4a.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_4aListener Iso14443_4aListener;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aListenerEventTypeHalted,
|
||||
Iso14443_4aListenerEventTypeReceivedData,
|
||||
} Iso14443_4aListenerEventType;
|
||||
|
||||
typedef struct {
|
||||
BitBuffer* buffer;
|
||||
} Iso14443_4aListenerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4aListenerEventType type;
|
||||
Iso14443_4aListenerEventData* data;
|
||||
} Iso14443_4aListenerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
extern const NfcListenerBase nfc_listener_iso14443_4a;
|
||||
32
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c
Normal file
32
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "iso14443_4a_listener_i.h"
|
||||
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h>
|
||||
|
||||
Iso14443_4aError
|
||||
iso14443_4a_listener_send_ats(Iso14443_4aListener* instance, const Iso14443_4aAtsData* data) {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, data->tl);
|
||||
|
||||
if(data->tl > 1) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, data->t0);
|
||||
if(data->t0 & ISO14443_4A_ATS_T0_TA1) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, data->ta_1);
|
||||
}
|
||||
if(data->t0 & ISO14443_4A_ATS_T0_TB1) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, data->tb_1);
|
||||
}
|
||||
if(data->t0 & ISO14443_4A_ATS_T0_TC1) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, data->tc_1);
|
||||
}
|
||||
|
||||
const uint32_t t1_tk_size = simple_array_get_count(data->t1_tk);
|
||||
if(t1_tk_size != 0) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, simple_array_cget_data(data->t1_tk), t1_tk_size);
|
||||
}
|
||||
}
|
||||
|
||||
const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame(
|
||||
instance->iso14443_3a_listener, instance->tx_buffer);
|
||||
return iso14443_4a_process_error(error);
|
||||
}
|
||||
36
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h
Normal file
36
lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
|
||||
#include "iso14443_4a_listener.h"
|
||||
#include "iso14443_4a_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aListenerStateIdle,
|
||||
Iso14443_4aListenerStateActive,
|
||||
} Iso14443_4aListenerState;
|
||||
|
||||
struct Iso14443_4aListener {
|
||||
Iso14443_3aListener* iso14443_3a_listener;
|
||||
Iso14443_4aData* data;
|
||||
Iso14443_4aListenerState state;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
|
||||
NfcGenericEvent generic_event;
|
||||
Iso14443_4aListenerEvent iso14443_4a_event;
|
||||
Iso14443_4aListenerEventData iso14443_4a_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
Iso14443_4aError
|
||||
iso14443_4a_listener_send_ats(Iso14443_4aListener* instance, const Iso14443_4aAtsData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
154
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c
Normal file
154
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.c
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "iso14443_4a_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "Iso14443_4aPoller"
|
||||
|
||||
#define ISO14443_4A_POLLER_BUF_SIZE (256U)
|
||||
|
||||
typedef NfcCommand (*Iso14443_4aPollerStateHandler)(Iso14443_4aPoller* instance);
|
||||
|
||||
const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static Iso14443_4aPoller* iso14443_4a_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) {
|
||||
Iso14443_4aPoller* instance = malloc(sizeof(Iso14443_4aPoller));
|
||||
instance->iso14443_3a_poller = iso14443_3a_poller;
|
||||
instance->data = iso14443_4a_alloc();
|
||||
instance->iso14443_4_layer = iso14443_4_layer_alloc();
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE);
|
||||
|
||||
instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data;
|
||||
|
||||
instance->general_event.protocol = NfcProtocolIso14443_4a;
|
||||
instance->general_event.event_data = &instance->iso14443_4a_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void iso14443_4a_poller_free(Iso14443_4aPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
iso14443_4a_free(instance->data);
|
||||
iso14443_4_layer_free(instance->iso14443_4_layer);
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4a_poller_handler_idle(Iso14443_4aPoller* instance) {
|
||||
iso14443_3a_copy(
|
||||
instance->data->iso14443_3a_data,
|
||||
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
||||
|
||||
iso14443_4_layer_reset(instance->iso14443_4_layer);
|
||||
|
||||
instance->poller_state = Iso14443_4aPollerStateReadAts;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4a_poller_handler_read_ats(Iso14443_4aPoller* instance) {
|
||||
Iso14443_4aError error =
|
||||
iso14443_4a_poller_async_read_ats(instance, &instance->data->ats_data);
|
||||
if(error == Iso14443_4aErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read ATS success");
|
||||
instance->poller_state = Iso14443_4aPollerStateReady;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Failed to read ATS");
|
||||
instance->poller_state = Iso14443_4aPollerStateError;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4a_poller_handler_error(Iso14443_4aPoller* instance) {
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
instance->iso14443_4a_event_data.error = instance->error;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
instance->poller_state = Iso14443_4aPollerStateIdle;
|
||||
return command;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4a_poller_handler_ready(Iso14443_4aPoller* instance) {
|
||||
instance->iso14443_4a_event.type = Iso14443_4aPollerEventTypeReady;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
return command;
|
||||
}
|
||||
|
||||
static const Iso14443_4aPollerStateHandler
|
||||
iso14443_4a_poller_state_handler[Iso14443_4aPollerStateNum] = {
|
||||
[Iso14443_4aPollerStateIdle] = iso14443_4a_poller_handler_idle,
|
||||
[Iso14443_4aPollerStateReadAts] = iso14443_4a_poller_handler_read_ats,
|
||||
[Iso14443_4aPollerStateError] = iso14443_4a_poller_handler_error,
|
||||
[Iso14443_4aPollerStateReady] = iso14443_4a_poller_handler_ready,
|
||||
};
|
||||
|
||||
static void iso14443_4a_poller_set_callback(
|
||||
Iso14443_4aPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4a_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
|
||||
Iso14443_4aPoller* instance = context;
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->callback);
|
||||
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
furi_assert(iso14443_3a_event);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
command = iso14443_4a_poller_state_handler[instance->poller_state](instance);
|
||||
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
|
||||
instance->iso14443_4a_event.type = Iso14443_4aPollerEventTypeError;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool iso14443_4a_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
|
||||
const Iso14443_4aPoller* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
furi_assert(iso14443_3a_event);
|
||||
iso14443_3a_copy(
|
||||
instance->data->iso14443_3a_data,
|
||||
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
||||
|
||||
bool protocol_detected = false;
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
protocol_detected = iso14443_3a_supports_iso14443_4(instance->data->iso14443_3a_data);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase nfc_poller_iso14443_4a = {
|
||||
.alloc = (NfcPollerAlloc)iso14443_4a_poller_alloc,
|
||||
.free = (NfcPollerFree)iso14443_4a_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)iso14443_4a_poller_set_callback,
|
||||
.run = (NfcPollerRun)iso14443_4a_poller_run,
|
||||
.detect = (NfcPollerDetect)iso14443_4a_poller_detect,
|
||||
.get_data = (NfcPollerGetData)iso14443_4a_poller_get_data,
|
||||
};
|
||||
29
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h
Normal file
29
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
|
||||
|
||||
#include "iso14443_4a.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_4aPoller Iso14443_4aPoller;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aPollerEventTypeError,
|
||||
Iso14443_4aPollerEventTypeReady,
|
||||
} Iso14443_4aPollerEventType;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4aError error;
|
||||
} Iso14443_4aPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4aPollerEventType type;
|
||||
Iso14443_4aPollerEventData* data;
|
||||
} Iso14443_4aPollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h
Normal file
5
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso14443_4a;
|
||||
83
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c
Normal file
83
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "iso14443_4a_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "iso14443_4a_i.h"
|
||||
|
||||
#define TAG "Iso14443_4aPoller"
|
||||
|
||||
#define ISO14443_4A_FSDI_256 (0x8U)
|
||||
|
||||
Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
instance->poller_state = Iso14443_4aPollerStateIdle;
|
||||
|
||||
return Iso14443_4aErrorNone;
|
||||
}
|
||||
|
||||
Iso14443_4aError
|
||||
iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO14443_4A_CMD_READ_ATS);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO14443_4A_FSDI_256 << 4);
|
||||
|
||||
Iso14443_4aError error = Iso14443_4aErrorNone;
|
||||
|
||||
do {
|
||||
const Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_buffer,
|
||||
instance->rx_buffer,
|
||||
ISO14443_4A_POLLER_ATS_FWT_FC);
|
||||
|
||||
if(iso14443_3a_error != Iso14443_3aErrorNone) {
|
||||
FURI_LOG_E(TAG, "ATS request failed");
|
||||
error = iso14443_4a_process_error(iso14443_3a_error);
|
||||
break;
|
||||
|
||||
} else if(!iso14443_4a_ats_parse(data, instance->rx_buffer)) {
|
||||
FURI_LOG_E(TAG, "Failed to parse ATS response");
|
||||
error = Iso14443_4aErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
Iso14443_4aError iso14443_4a_poller_send_block(
|
||||
Iso14443_4aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
|
||||
|
||||
Iso14443_4aError error = Iso14443_4aErrorNone;
|
||||
|
||||
do {
|
||||
Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_buffer,
|
||||
instance->rx_buffer,
|
||||
iso14443_4a_get_fwt_fc_max(instance->data));
|
||||
|
||||
if(iso14443_3a_error != Iso14443_3aErrorNone) {
|
||||
error = iso14443_4a_process_error(iso14443_3a_error);
|
||||
break;
|
||||
|
||||
} else if(!iso14443_4_layer_decode_block(
|
||||
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
|
||||
error = Iso14443_4aErrorProtocol;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
62
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h
Normal file
62
lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h>
|
||||
#include <nfc/helpers/iso14443_4_layer.h>
|
||||
|
||||
#include "iso14443_4a_poller.h"
|
||||
#include "iso14443_4a_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO14443_4A_POLLER_ATS_FWT_FC (40000)
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aPollerStateIdle,
|
||||
Iso14443_4aPollerStateReadAts,
|
||||
Iso14443_4aPollerStateError,
|
||||
Iso14443_4aPollerStateReady,
|
||||
|
||||
Iso14443_4aPollerStateNum,
|
||||
} Iso14443_4aPollerState;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4aPollerSessionStateIdle,
|
||||
Iso14443_4aPollerSessionStateActive,
|
||||
Iso14443_4aPollerSessionStateStopRequest,
|
||||
} Iso14443_4aPollerSessionState;
|
||||
|
||||
struct Iso14443_4aPoller {
|
||||
Iso14443_3aPoller* iso14443_3a_poller;
|
||||
Iso14443_4aPollerState poller_state;
|
||||
Iso14443_4aPollerSessionState session_state;
|
||||
Iso14443_4aError error;
|
||||
Iso14443_4aData* data;
|
||||
Iso14443_4Layer* iso14443_4_layer;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
Iso14443_4aPollerEventData iso14443_4a_event_data;
|
||||
Iso14443_4aPollerEvent iso14443_4a_event;
|
||||
NfcGenericEvent general_event;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
Iso14443_4aError iso14443_4a_process_error(Iso14443_3aError error);
|
||||
|
||||
const Iso14443_4aData* iso14443_4a_poller_get_data(Iso14443_4aPoller* instance);
|
||||
|
||||
Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance);
|
||||
|
||||
Iso14443_4aError
|
||||
iso14443_4a_poller_async_read_ats(Iso14443_4aPoller* instance, Iso14443_4aAtsData* data);
|
||||
|
||||
Iso14443_4aError iso14443_4a_poller_send_block(
|
||||
Iso14443_4aPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
94
lib/nfc/protocols/iso14443_4b/iso14443_4b.c
Normal file
94
lib/nfc/protocols/iso14443_4b/iso14443_4b.c
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "iso14443_4b_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
#define ISO14443_4B_PROTOCOL_NAME "ISO14443-4B"
|
||||
#define ISO14443_4B_DEVICE_NAME "ISO14443-4B (Unknown)"
|
||||
|
||||
const NfcDeviceBase nfc_device_iso14443_4b = {
|
||||
.protocol_name = ISO14443_4B_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)iso14443_4b_alloc,
|
||||
.free = (NfcDeviceFree)iso14443_4b_free,
|
||||
.reset = (NfcDeviceReset)iso14443_4b_reset,
|
||||
.copy = (NfcDeviceCopy)iso14443_4b_copy,
|
||||
.verify = (NfcDeviceVerify)iso14443_4b_verify,
|
||||
.load = (NfcDeviceLoad)iso14443_4b_load,
|
||||
.save = (NfcDeviceSave)iso14443_4b_save,
|
||||
.is_equal = (NfcDeviceEqual)iso14443_4b_is_equal,
|
||||
.get_name = (NfcDeviceGetName)iso14443_4b_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)iso14443_4b_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)iso14443_4b_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)iso14443_4b_get_base_data,
|
||||
};
|
||||
|
||||
Iso14443_4bData* iso14443_4b_alloc() {
|
||||
Iso14443_4bData* data = malloc(sizeof(Iso14443_4bData));
|
||||
|
||||
data->iso14443_3b_data = iso14443_3b_alloc();
|
||||
return data;
|
||||
}
|
||||
|
||||
void iso14443_4b_free(Iso14443_4bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3b_free(data->iso14443_3b_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void iso14443_4b_reset(Iso14443_4bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3b_reset(data->iso14443_3b_data);
|
||||
}
|
||||
|
||||
void iso14443_4b_copy(Iso14443_4bData* data, const Iso14443_4bData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
iso14443_3b_copy(data->iso14443_3b_data, other->iso14443_3b_data);
|
||||
}
|
||||
|
||||
bool iso14443_4b_verify(Iso14443_4bData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(device_type);
|
||||
|
||||
// Empty, unified file format only
|
||||
return false;
|
||||
}
|
||||
|
||||
bool iso14443_4b_load(Iso14443_4bData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
return iso14443_3b_load(data->iso14443_3b_data, ff, version);
|
||||
}
|
||||
|
||||
bool iso14443_4b_save(const Iso14443_4bData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
return iso14443_3b_save(data->iso14443_3b_data, ff);
|
||||
}
|
||||
|
||||
bool iso14443_4b_is_equal(const Iso14443_4bData* data, const Iso14443_4bData* other) {
|
||||
return iso14443_3b_is_equal(data->iso14443_3b_data, other->iso14443_3b_data);
|
||||
}
|
||||
|
||||
const char* iso14443_4b_get_device_name(const Iso14443_4bData* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
return ISO14443_4B_DEVICE_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* iso14443_4b_get_uid(const Iso14443_4bData* data, size_t* uid_len) {
|
||||
return iso14443_3b_get_uid(data->iso14443_3b_data, uid_len);
|
||||
}
|
||||
|
||||
bool iso14443_4b_set_uid(Iso14443_4bData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3b_set_uid(data->iso14443_3b_data, uid, uid_len);
|
||||
}
|
||||
|
||||
Iso14443_3bData* iso14443_4b_get_base_data(const Iso14443_4bData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->iso14443_3b_data;
|
||||
}
|
||||
46
lib/nfc/protocols/iso14443_4b/iso14443_4b.h
Normal file
46
lib/nfc/protocols/iso14443_4b/iso14443_4b.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/iso14443_3b/iso14443_3b.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4bErrorNone,
|
||||
Iso14443_4bErrorNotPresent,
|
||||
Iso14443_4bErrorProtocol,
|
||||
Iso14443_4bErrorTimeout,
|
||||
} Iso14443_4bError;
|
||||
|
||||
typedef struct Iso14443_4bData Iso14443_4bData;
|
||||
|
||||
// Virtual methods
|
||||
|
||||
Iso14443_4bData* iso14443_4b_alloc();
|
||||
|
||||
void iso14443_4b_free(Iso14443_4bData* data);
|
||||
|
||||
void iso14443_4b_reset(Iso14443_4bData* data);
|
||||
|
||||
void iso14443_4b_copy(Iso14443_4bData* data, const Iso14443_4bData* other);
|
||||
|
||||
bool iso14443_4b_verify(Iso14443_4bData* data, const FuriString* device_type);
|
||||
|
||||
bool iso14443_4b_load(Iso14443_4bData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool iso14443_4b_save(const Iso14443_4bData* data, FlipperFormat* ff);
|
||||
|
||||
bool iso14443_4b_is_equal(const Iso14443_4bData* data, const Iso14443_4bData* other);
|
||||
|
||||
const char* iso14443_4b_get_device_name(const Iso14443_4bData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* iso14443_4b_get_uid(const Iso14443_4bData* data, size_t* uid_len);
|
||||
|
||||
bool iso14443_4b_set_uid(Iso14443_4bData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_3bData* iso14443_4b_get_base_data(const Iso14443_4bData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h
Normal file
5
lib/nfc/protocols/iso14443_4b/iso14443_4b_device_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_4b;
|
||||
18
lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c
Normal file
18
lib/nfc/protocols/iso14443_4b/iso14443_4b_i.c
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "iso14443_4b_i.h"
|
||||
|
||||
Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error) {
|
||||
switch(error) {
|
||||
case Iso14443_3bErrorNone:
|
||||
return Iso14443_4bErrorNone;
|
||||
case Iso14443_3bErrorNotPresent:
|
||||
return Iso14443_4bErrorNotPresent;
|
||||
case Iso14443_3bErrorColResFailed:
|
||||
case Iso14443_3bErrorCommunication:
|
||||
case Iso14443_3bErrorWrongCrc:
|
||||
return Iso14443_4bErrorProtocol;
|
||||
case Iso14443_3bErrorTimeout:
|
||||
return Iso14443_4bErrorTimeout;
|
||||
default:
|
||||
return Iso14443_4bErrorProtocol;
|
||||
}
|
||||
}
|
||||
9
lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h
Normal file
9
lib/nfc/protocols/iso14443_4b/iso14443_4b_i.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso14443_4b.h"
|
||||
|
||||
struct Iso14443_4bData {
|
||||
Iso14443_3bData* iso14443_3b_data;
|
||||
};
|
||||
|
||||
Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error);
|
||||
138
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c
Normal file
138
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.c
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "iso14443_4b_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "Iso14443_4bPoller"
|
||||
|
||||
#define ISO14443_4A_POLLER_BUF_SIZE (256U)
|
||||
|
||||
typedef NfcCommand (*Iso14443_4bPollerStateHandler)(Iso14443_4bPoller* instance);
|
||||
|
||||
const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static Iso14443_4bPoller* iso14443_4b_poller_alloc(Iso14443_3bPoller* iso14443_3b_poller) {
|
||||
Iso14443_4bPoller* instance = malloc(sizeof(Iso14443_4bPoller));
|
||||
instance->iso14443_3b_poller = iso14443_3b_poller;
|
||||
instance->data = iso14443_4b_alloc();
|
||||
instance->iso14443_4_layer = iso14443_4_layer_alloc();
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_POLLER_BUF_SIZE);
|
||||
|
||||
instance->iso14443_4b_event.data = &instance->iso14443_4b_event_data;
|
||||
|
||||
instance->general_event.protocol = NfcProtocolIso14443_4b;
|
||||
instance->general_event.event_data = &instance->iso14443_4b_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void iso14443_4b_poller_free(Iso14443_4bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
iso14443_4b_free(instance->data);
|
||||
iso14443_4_layer_free(instance->iso14443_4_layer);
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4b_poller_handler_idle(Iso14443_4bPoller* instance) {
|
||||
iso14443_3b_copy(
|
||||
instance->data->iso14443_3b_data,
|
||||
iso14443_3b_poller_get_data(instance->iso14443_3b_poller));
|
||||
|
||||
iso14443_4_layer_reset(instance->iso14443_4_layer);
|
||||
instance->poller_state = Iso14443_4bPollerStateReady;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4b_poller_handler_error(Iso14443_4bPoller* instance) {
|
||||
iso14443_3b_poller_halt(instance->iso14443_3b_poller);
|
||||
instance->iso14443_4b_event_data.error = instance->error;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
instance->poller_state = Iso14443_4bPollerStateIdle;
|
||||
return command;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4b_poller_handler_ready(Iso14443_4bPoller* instance) {
|
||||
instance->iso14443_4b_event.type = Iso14443_4bPollerEventTypeReady;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
return command;
|
||||
}
|
||||
|
||||
static const Iso14443_4bPollerStateHandler
|
||||
iso14443_4b_poller_state_handler[Iso14443_4bPollerStateNum] = {
|
||||
[Iso14443_4bPollerStateIdle] = iso14443_4b_poller_handler_idle,
|
||||
[Iso14443_4bPollerStateError] = iso14443_4b_poller_handler_error,
|
||||
[Iso14443_4bPollerStateReady] = iso14443_4b_poller_handler_ready,
|
||||
};
|
||||
|
||||
static void iso14443_4b_poller_set_callback(
|
||||
Iso14443_4bPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand iso14443_4b_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3b);
|
||||
|
||||
Iso14443_4bPoller* instance = context;
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->callback);
|
||||
|
||||
Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data;
|
||||
furi_assert(iso14443_3b_event);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) {
|
||||
command = iso14443_4b_poller_state_handler[instance->poller_state](instance);
|
||||
} else if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeError) {
|
||||
instance->iso14443_4b_event.type = Iso14443_4bPollerEventTypeError;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool iso14443_4b_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3b);
|
||||
|
||||
const Iso14443_4bPoller* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data;
|
||||
furi_assert(iso14443_3b_event);
|
||||
iso14443_3b_copy(
|
||||
instance->data->iso14443_3b_data,
|
||||
iso14443_3b_poller_get_data(instance->iso14443_3b_poller));
|
||||
|
||||
bool protocol_detected = false;
|
||||
|
||||
if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) {
|
||||
protocol_detected = iso14443_3b_supports_iso14443_4(instance->data->iso14443_3b_data);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase nfc_poller_iso14443_4b = {
|
||||
.alloc = (NfcPollerAlloc)iso14443_4b_poller_alloc,
|
||||
.free = (NfcPollerFree)iso14443_4b_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)iso14443_4b_poller_set_callback,
|
||||
.run = (NfcPollerRun)iso14443_4b_poller_run,
|
||||
.detect = (NfcPollerDetect)iso14443_4b_poller_detect,
|
||||
.get_data = (NfcPollerGetData)iso14443_4b_poller_get_data,
|
||||
};
|
||||
29
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h
Normal file
29
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_3b/iso14443_3b_poller.h>
|
||||
|
||||
#include "iso14443_4b.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_4bPoller Iso14443_4bPoller;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4bPollerEventTypeError,
|
||||
Iso14443_4bPollerEventTypeReady,
|
||||
} Iso14443_4bPollerEventType;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4bError error;
|
||||
} Iso14443_4bPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4bPollerEventType type;
|
||||
Iso14443_4bPollerEventData* data;
|
||||
} Iso14443_4bPollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h
Normal file
5
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso14443_4b;
|
||||
45
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c
Normal file
45
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "iso14443_4b_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "iso14443_4b_i.h"
|
||||
|
||||
#define TAG "Iso14443_4bPoller"
|
||||
|
||||
Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
iso14443_3b_poller_halt(instance->iso14443_3b_poller);
|
||||
instance->poller_state = Iso14443_4bPollerStateIdle;
|
||||
|
||||
return Iso14443_4bErrorNone;
|
||||
}
|
||||
|
||||
Iso14443_4bError iso14443_4b_poller_send_block(
|
||||
Iso14443_4bPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer);
|
||||
|
||||
Iso14443_4bError error = Iso14443_4bErrorNone;
|
||||
|
||||
do {
|
||||
Iso14443_3bError iso14443_3b_error = iso14443_3b_poller_send_frame(
|
||||
instance->iso14443_3b_poller, instance->tx_buffer, instance->rx_buffer);
|
||||
|
||||
if(iso14443_3b_error != Iso14443_3bErrorNone) {
|
||||
error = iso14443_4b_process_error(iso14443_3b_error);
|
||||
break;
|
||||
|
||||
} else if(!iso14443_4_layer_decode_block(
|
||||
instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) {
|
||||
error = Iso14443_4bErrorProtocol;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
56
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h
Normal file
56
lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h>
|
||||
#include <nfc/helpers/iso14443_4_layer.h>
|
||||
|
||||
#include "iso14443_4b_poller.h"
|
||||
#include "iso14443_4b_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4bPollerStateIdle,
|
||||
Iso14443_4bPollerStateError,
|
||||
Iso14443_4bPollerStateReady,
|
||||
|
||||
Iso14443_4bPollerStateNum,
|
||||
} Iso14443_4bPollerState;
|
||||
|
||||
typedef enum {
|
||||
Iso14443_4bPollerSessionStateIdle,
|
||||
Iso14443_4bPollerSessionStateActive,
|
||||
Iso14443_4bPollerSessionStateStopRequest,
|
||||
} Iso14443_4bPollerSessionState;
|
||||
|
||||
struct Iso14443_4bPoller {
|
||||
Iso14443_3bPoller* iso14443_3b_poller;
|
||||
Iso14443_4bPollerState poller_state;
|
||||
Iso14443_4bPollerSessionState session_state;
|
||||
Iso14443_4bError error;
|
||||
Iso14443_4bData* data;
|
||||
Iso14443_4Layer* iso14443_4_layer;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
Iso14443_4bPollerEventData iso14443_4b_event_data;
|
||||
Iso14443_4bPollerEvent iso14443_4b_event;
|
||||
NfcGenericEvent general_event;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
Iso14443_4bError iso14443_4b_process_error(Iso14443_3bError error);
|
||||
|
||||
const Iso14443_4bData* iso14443_4b_poller_get_data(Iso14443_4bPoller* instance);
|
||||
|
||||
Iso14443_4bError iso14443_4b_poller_halt(Iso14443_4bPoller* instance);
|
||||
|
||||
Iso14443_4bError iso14443_4b_poller_send_block(
|
||||
Iso14443_4bPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
358
lib/nfc/protocols/iso15693_3/iso15693_3.c
Normal file
358
lib/nfc/protocols/iso15693_3/iso15693_3.c
Normal file
@@ -0,0 +1,358 @@
|
||||
#include "iso15693_3.h"
|
||||
#include "iso15693_3_device_defs.h"
|
||||
|
||||
#include <nfc/nfc_common.h>
|
||||
|
||||
#define ISO15693_3_PROTOCOL_NAME "ISO15693-3"
|
||||
#define ISO15693_3_PROTOCOL_NAME_LEGACY "ISO15693"
|
||||
#define ISO15693_3_DEVICE_NAME "ISO15693-3 (Unknown)"
|
||||
|
||||
#define ISO15693_3_LOCK_DSFID_LEGACY (1U << 0)
|
||||
#define ISO15693_3_LOCK_AFI_LEGACY (1U << 1)
|
||||
|
||||
#define ISO15693_3_DSFID_KEY "DSFID"
|
||||
#define ISO15693_3_AFI_KEY "AFI"
|
||||
#define ISO15693_3_IC_REF_KEY "IC Reference"
|
||||
#define ISO15693_3_BLOCK_COUNT_KEY "Block Count"
|
||||
#define ISO15693_3_BLOCK_SIZE_KEY "Block Size"
|
||||
#define ISO15693_3_DATA_CONTENT_KEY "Data Content"
|
||||
#define ISO15693_3_LOCK_DSFID_KEY "Lock DSFID"
|
||||
#define ISO15693_3_LOCK_AFI_KEY "Lock AFI"
|
||||
#define ISO15693_3_SECURITY_STATUS_KEY "Security Status"
|
||||
|
||||
const NfcDeviceBase nfc_device_iso15693_3 = {
|
||||
.protocol_name = ISO15693_3_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)iso15693_3_alloc,
|
||||
.free = (NfcDeviceFree)iso15693_3_free,
|
||||
.reset = (NfcDeviceReset)iso15693_3_reset,
|
||||
.copy = (NfcDeviceCopy)iso15693_3_copy,
|
||||
.verify = (NfcDeviceVerify)iso15693_3_verify,
|
||||
.load = (NfcDeviceLoad)iso15693_3_load,
|
||||
.save = (NfcDeviceSave)iso15693_3_save,
|
||||
.is_equal = (NfcDeviceEqual)iso15693_3_is_equal,
|
||||
.get_name = (NfcDeviceGetName)iso15693_3_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)iso15693_3_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)iso15693_3_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)iso15693_3_get_base_data,
|
||||
};
|
||||
|
||||
Iso15693_3Data* iso15693_3_alloc() {
|
||||
Iso15693_3Data* data = malloc(sizeof(Iso15693_3Data));
|
||||
|
||||
data->block_data = simple_array_alloc(&simple_array_config_uint8_t);
|
||||
data->block_security = simple_array_alloc(&simple_array_config_uint8_t);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void iso15693_3_free(Iso15693_3Data* data) {
|
||||
furi_assert(data);
|
||||
|
||||
simple_array_free(data->block_data);
|
||||
simple_array_free(data->block_security);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void iso15693_3_reset(Iso15693_3Data* data) {
|
||||
furi_assert(data);
|
||||
|
||||
memset(data->uid, 0, ISO15693_3_UID_SIZE);
|
||||
memset(&data->system_info, 0, sizeof(Iso15693_3SystemInfo));
|
||||
memset(&data->settings, 0, sizeof(Iso15693_3Settings));
|
||||
|
||||
simple_array_reset(data->block_data);
|
||||
simple_array_reset(data->block_security);
|
||||
}
|
||||
|
||||
void iso15693_3_copy(Iso15693_3Data* data, const Iso15693_3Data* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
memcpy(data->uid, other->uid, ISO15693_3_UID_SIZE);
|
||||
|
||||
data->system_info = other->system_info;
|
||||
data->settings = other->settings;
|
||||
|
||||
simple_array_copy(data->block_data, other->block_data);
|
||||
simple_array_copy(data->block_security, other->block_security);
|
||||
}
|
||||
|
||||
bool iso15693_3_verify(Iso15693_3Data* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal(device_type, ISO15693_3_PROTOCOL_NAME_LEGACY);
|
||||
}
|
||||
|
||||
static inline bool iso15693_3_load_security_legacy(Iso15693_3Data* data, FlipperFormat* ff) {
|
||||
bool loaded = false;
|
||||
uint8_t* legacy_data = NULL;
|
||||
|
||||
do {
|
||||
uint32_t value_count;
|
||||
if(!flipper_format_get_value_count(ff, ISO15693_3_SECURITY_STATUS_KEY, &value_count))
|
||||
break;
|
||||
if(simple_array_get_count(data->block_security) + 1 != value_count) break;
|
||||
|
||||
legacy_data = malloc(value_count);
|
||||
if(!flipper_format_read_hex(ff, ISO15693_3_SECURITY_STATUS_KEY, legacy_data, value_count))
|
||||
break;
|
||||
|
||||
// First legacy data byte is lock bits
|
||||
data->settings.lock_bits.dsfid = legacy_data[0] & ISO15693_3_LOCK_DSFID_LEGACY;
|
||||
data->settings.lock_bits.afi = legacy_data[0] & ISO15693_3_LOCK_AFI_LEGACY;
|
||||
|
||||
// The rest are block security
|
||||
memcpy(
|
||||
&legacy_data[1],
|
||||
simple_array_get_data(data->block_security),
|
||||
simple_array_get_count(data->block_security));
|
||||
|
||||
loaded = true;
|
||||
} while(false);
|
||||
|
||||
if(legacy_data) free(legacy_data);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static inline bool iso15693_3_load_security(Iso15693_3Data* data, FlipperFormat* ff) {
|
||||
bool loaded = false;
|
||||
|
||||
do {
|
||||
uint32_t value_count;
|
||||
if(!flipper_format_get_value_count(ff, ISO15693_3_SECURITY_STATUS_KEY, &value_count))
|
||||
break;
|
||||
if(simple_array_get_count(data->block_security) != value_count) break;
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
ISO15693_3_SECURITY_STATUS_KEY,
|
||||
simple_array_get_data(data->block_security),
|
||||
simple_array_get_count(data->block_security)))
|
||||
break;
|
||||
|
||||
loaded = true;
|
||||
} while(false);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
UNUSED(version);
|
||||
|
||||
bool loaded = false;
|
||||
|
||||
do {
|
||||
if(flipper_format_key_exist(ff, ISO15693_3_DSFID_KEY)) {
|
||||
if(!flipper_format_read_hex(ff, ISO15693_3_DSFID_KEY, &data->system_info.dsfid, 1))
|
||||
break;
|
||||
data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_DSFID;
|
||||
}
|
||||
|
||||
if(flipper_format_key_exist(ff, ISO15693_3_AFI_KEY)) {
|
||||
if(!flipper_format_read_hex(ff, ISO15693_3_AFI_KEY, &data->system_info.afi, 1)) break;
|
||||
data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_AFI;
|
||||
}
|
||||
|
||||
if(flipper_format_key_exist(ff, ISO15693_3_IC_REF_KEY)) {
|
||||
if(!flipper_format_read_hex(ff, ISO15693_3_IC_REF_KEY, &data->system_info.ic_ref, 1))
|
||||
break;
|
||||
data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_IC_REF;
|
||||
}
|
||||
|
||||
const bool has_lock_bits = flipper_format_key_exist(ff, ISO15693_3_LOCK_DSFID_KEY) &&
|
||||
flipper_format_key_exist(ff, ISO15693_3_LOCK_AFI_KEY);
|
||||
if(has_lock_bits) {
|
||||
Iso15693_3LockBits* lock_bits = &data->settings.lock_bits;
|
||||
if(!flipper_format_read_bool(ff, ISO15693_3_LOCK_DSFID_KEY, &lock_bits->dsfid, 1))
|
||||
break;
|
||||
if(!flipper_format_read_bool(ff, ISO15693_3_LOCK_AFI_KEY, &lock_bits->afi, 1)) break;
|
||||
}
|
||||
|
||||
if(flipper_format_key_exist(ff, ISO15693_3_BLOCK_COUNT_KEY) &&
|
||||
flipper_format_key_exist(ff, ISO15693_3_BLOCK_SIZE_KEY)) {
|
||||
uint32_t block_count;
|
||||
if(!flipper_format_read_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) break;
|
||||
|
||||
data->system_info.block_count = block_count;
|
||||
data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY;
|
||||
|
||||
if(!flipper_format_read_hex(
|
||||
ff, ISO15693_3_BLOCK_SIZE_KEY, &(data->system_info.block_size), 1))
|
||||
break;
|
||||
|
||||
simple_array_init(
|
||||
data->block_data, data->system_info.block_size * data->system_info.block_count);
|
||||
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
ISO15693_3_DATA_CONTENT_KEY,
|
||||
simple_array_get_data(data->block_data),
|
||||
simple_array_get_count(data->block_data)))
|
||||
break;
|
||||
|
||||
if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) {
|
||||
simple_array_init(data->block_security, data->system_info.block_count);
|
||||
|
||||
const bool security_loaded = has_lock_bits ?
|
||||
iso15693_3_load_security(data, ff) :
|
||||
iso15693_3_load_security_legacy(data, ff);
|
||||
if(!security_loaded) break;
|
||||
}
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
} while(false);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(ff, ISO15693_3_PROTOCOL_NAME " specific data"))
|
||||
break;
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
if(!flipper_format_write_comment_cstr(ff, "Data Storage Format Identifier")) break;
|
||||
if(!flipper_format_write_hex(ff, ISO15693_3_DSFID_KEY, &data->system_info.dsfid, 1))
|
||||
break;
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
if(!flipper_format_write_comment_cstr(ff, "Application Family Identifier")) break;
|
||||
if(!flipper_format_write_hex(ff, ISO15693_3_AFI_KEY, &data->system_info.afi, 1)) break;
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
|
||||
if(!flipper_format_write_comment_cstr(ff, "IC Reference - Vendor specific meaning"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(ff, ISO15693_3_IC_REF_KEY, &data->system_info.ic_ref, 1))
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_comment_cstr(ff, "Lock Bits")) break;
|
||||
if(!flipper_format_write_bool(
|
||||
ff, ISO15693_3_LOCK_DSFID_KEY, &data->settings.lock_bits.dsfid, 1))
|
||||
break;
|
||||
if(!flipper_format_write_bool(
|
||||
ff, ISO15693_3_LOCK_AFI_KEY, &data->settings.lock_bits.afi, 1))
|
||||
break;
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
|
||||
const uint32_t block_count = data->system_info.block_count;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
ff, "Number of memory blocks, valid range = 1..256"))
|
||||
break;
|
||||
if(!flipper_format_write_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
ff, "Size of a single memory block, valid range = 01...20 (hex)"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff, ISO15693_3_BLOCK_SIZE_KEY, &data->system_info.block_size, 1))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
ISO15693_3_DATA_CONTENT_KEY,
|
||||
simple_array_cget_data(data->block_data),
|
||||
simple_array_get_count(data->block_data)))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
ff, "Block Security Status: 01 = locked, 00 = not locked"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
ISO15693_3_SECURITY_STATUS_KEY,
|
||||
simple_array_cget_data(data->block_security),
|
||||
simple_array_get_count(data->block_security)))
|
||||
break;
|
||||
}
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool iso15693_3_is_equal(const Iso15693_3Data* data, const Iso15693_3Data* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
return memcmp(data->uid, other->uid, ISO15693_3_UID_SIZE) == 0 &&
|
||||
memcmp(&data->settings, &other->settings, sizeof(Iso15693_3Settings)) == 0 &&
|
||||
memcmp(&data->system_info, &other->system_info, sizeof(Iso15693_3SystemInfo)) == 0 &&
|
||||
simple_array_is_equal(data->block_data, other->block_data) &&
|
||||
simple_array_is_equal(data->block_security, other->block_security);
|
||||
}
|
||||
|
||||
const char* iso15693_3_get_device_name(const Iso15693_3Data* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
|
||||
return ISO15693_3_DEVICE_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* iso15693_3_get_uid(const Iso15693_3Data* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
if(uid_len) *uid_len = ISO15693_3_UID_SIZE;
|
||||
return data->uid;
|
||||
}
|
||||
|
||||
bool iso15693_3_set_uid(Iso15693_3Data* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
furi_assert(uid);
|
||||
|
||||
bool uid_valid = uid_len == ISO15693_3_UID_SIZE;
|
||||
|
||||
if(uid_valid) {
|
||||
memcpy(data->uid, uid, uid_len);
|
||||
// All ISO15693-3 cards must have this as first UID byte
|
||||
data->uid[0] = 0xe0;
|
||||
}
|
||||
|
||||
return uid_valid;
|
||||
}
|
||||
|
||||
Iso15693_3Data* iso15693_3_get_base_data(const Iso15693_3Data* data) {
|
||||
UNUSED(data);
|
||||
furi_crash("No base data");
|
||||
}
|
||||
|
||||
bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index) {
|
||||
furi_assert(data);
|
||||
furi_assert(block_index < data->system_info.block_count);
|
||||
|
||||
return *(const uint8_t*)simple_array_cget(data->block_security, block_index);
|
||||
}
|
||||
|
||||
uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->uid[1];
|
||||
}
|
||||
|
||||
uint16_t iso15693_3_get_block_count(const Iso15693_3Data* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->system_info.block_count;
|
||||
}
|
||||
|
||||
uint8_t iso15693_3_get_block_size(const Iso15693_3Data* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->system_info.block_size;
|
||||
}
|
||||
|
||||
const uint8_t* iso15693_3_get_block_data(const Iso15693_3Data* data, uint8_t block_index) {
|
||||
furi_assert(data);
|
||||
furi_assert(data->system_info.block_count > block_index);
|
||||
|
||||
return (const uint8_t*)simple_array_cget(
|
||||
data->block_data, block_index * data->system_info.block_size);
|
||||
}
|
||||
163
lib/nfc/protocols/iso15693_3/iso15693_3.h
Normal file
163
lib/nfc/protocols/iso15693_3/iso15693_3.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <toolbox/simple_array.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO15693_3_UID_SIZE (8U)
|
||||
|
||||
#define ISO15693_3_GUARD_TIME_US (5000U)
|
||||
#define ISO15693_3_FDT_POLL_FC (4202U)
|
||||
#define ISO15693_3_FDT_LISTEN_FC (4320U)
|
||||
#define ISO15693_3_POLL_POLL_MIN_US (1500U)
|
||||
|
||||
#define ISO15693_3_REQ_FLAG_SUBCARRIER_1 (0U << 0)
|
||||
#define ISO15693_3_REQ_FLAG_SUBCARRIER_2 (1U << 0)
|
||||
#define ISO15693_3_REQ_FLAG_DATA_RATE_LO (0U << 1)
|
||||
#define ISO15693_3_REQ_FLAG_DATA_RATE_HI (1U << 1)
|
||||
#define ISO15693_3_REQ_FLAG_INVENTORY_T4 (0U << 2)
|
||||
#define ISO15693_3_REQ_FLAG_INVENTORY_T5 (1U << 2)
|
||||
#define ISO15693_3_REQ_FLAG_EXTENSION (1U << 3)
|
||||
|
||||
#define ISO15693_3_REQ_FLAG_T4_SELECTED (1U << 4)
|
||||
#define ISO15693_3_REQ_FLAG_T4_ADDRESSED (1U << 5)
|
||||
#define ISO15693_3_REQ_FLAG_T4_OPTION (1U << 6)
|
||||
|
||||
#define ISO15693_3_REQ_FLAG_T5_AFI_PRESENT (1U << 4)
|
||||
#define ISO15693_3_REQ_FLAG_T5_N_SLOTS_16 (0U << 5)
|
||||
#define ISO15693_3_REQ_FLAG_T5_N_SLOTS_1 (1U << 5)
|
||||
#define ISO15693_3_REQ_FLAG_T5_OPTION (1U << 6)
|
||||
|
||||
#define ISO15693_3_RESP_FLAG_NONE (0U)
|
||||
#define ISO15693_3_RESP_FLAG_ERROR (1U << 0)
|
||||
#define ISO15693_3_RESP_FLAG_EXTENSION (1U << 3)
|
||||
|
||||
#define ISO15693_3_RESP_ERROR_NOT_SUPPORTED (0x01U)
|
||||
#define ISO15693_3_RESP_ERROR_FORMAT (0x02U)
|
||||
#define ISO15693_3_RESP_ERROR_OPTION (0x03U)
|
||||
#define ISO15693_3_RESP_ERROR_UNKNOWN (0x0FU)
|
||||
#define ISO15693_3_RESP_ERROR_BLOCK_UNAVAILABLE (0x10U)
|
||||
#define ISO15693_3_RESP_ERROR_BLOCK_ALREADY_LOCKED (0x11U)
|
||||
#define ISO15693_3_RESP_ERROR_BLOCK_LOCKED (0x12U)
|
||||
#define ISO15693_3_RESP_ERROR_BLOCK_WRITE (0x13U)
|
||||
#define ISO15693_3_RESP_ERROR_BLOCK_LOCK (0x14U)
|
||||
#define ISO15693_3_RESP_ERROR_CUSTOM_START (0xA0U)
|
||||
#define ISO15693_3_RESP_ERROR_CUSTOM_END (0xDFU)
|
||||
|
||||
#define ISO15693_3_CMD_MANDATORY_START (0x01U)
|
||||
#define ISO15693_3_CMD_INVENTORY (0x01U)
|
||||
#define ISO15693_3_CMD_STAY_QUIET (0x02U)
|
||||
#define ISO15693_3_CMD_MANDATORY_RFU (0x03U)
|
||||
#define ISO15693_3_CMD_OPTIONAL_START (0x20U)
|
||||
#define ISO15693_3_CMD_READ_BLOCK (0x20U)
|
||||
#define ISO15693_3_CMD_WRITE_BLOCK (0x21U)
|
||||
#define ISO15693_3_CMD_LOCK_BLOCK (0x22U)
|
||||
#define ISO15693_3_CMD_READ_MULTI_BLOCKS (0x23U)
|
||||
#define ISO15693_3_CMD_WRITE_MULTI_BLOCKS (0x24U)
|
||||
#define ISO15693_3_CMD_SELECT (0x25U)
|
||||
#define ISO15693_3_CMD_RESET_TO_READY (0x26U)
|
||||
#define ISO15693_3_CMD_WRITE_AFI (0x27U)
|
||||
#define ISO15693_3_CMD_LOCK_AFI (0x28U)
|
||||
#define ISO15693_3_CMD_WRITE_DSFID (0x29U)
|
||||
#define ISO15693_3_CMD_LOCK_DSFID (0x2AU)
|
||||
#define ISO15693_3_CMD_GET_SYS_INFO (0x2BU)
|
||||
#define ISO15693_3_CMD_GET_BLOCKS_SECURITY (0x2CU)
|
||||
#define ISO15693_3_CMD_OPTIONAL_RFU (0x2DU)
|
||||
#define ISO15693_3_CMD_CUSTOM_START (0xA0U)
|
||||
|
||||
#define ISO15693_3_MANDATORY_COUNT (ISO15693_3_CMD_MANDATORY_RFU - ISO15693_3_CMD_MANDATORY_START)
|
||||
#define ISO15693_3_OPTIONAL_COUNT (ISO15693_3_CMD_OPTIONAL_RFU - ISO15693_3_CMD_OPTIONAL_START)
|
||||
|
||||
#define ISO15693_3_SYSINFO_FLAG_DSFID (1U << 0)
|
||||
#define ISO15693_3_SYSINFO_FLAG_AFI (1U << 1)
|
||||
#define ISO15693_3_SYSINFO_FLAG_MEMORY (1U << 2)
|
||||
#define ISO15693_3_SYSINFO_FLAG_IC_REF (1U << 3)
|
||||
|
||||
typedef enum {
|
||||
Iso15693_3ErrorNone,
|
||||
Iso15693_3ErrorNotPresent,
|
||||
Iso15693_3ErrorBufferEmpty,
|
||||
Iso15693_3ErrorBufferOverflow,
|
||||
Iso15693_3ErrorFieldOff,
|
||||
Iso15693_3ErrorWrongCrc,
|
||||
Iso15693_3ErrorTimeout,
|
||||
Iso15693_3ErrorFormat,
|
||||
Iso15693_3ErrorIgnore,
|
||||
Iso15693_3ErrorNotSupported,
|
||||
Iso15693_3ErrorUidMismatch,
|
||||
Iso15693_3ErrorFullyHandled,
|
||||
Iso15693_3ErrorUnexpectedResponse,
|
||||
Iso15693_3ErrorInternal,
|
||||
Iso15693_3ErrorCustom,
|
||||
Iso15693_3ErrorUnknown,
|
||||
} Iso15693_3Error;
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t dsfid;
|
||||
uint8_t afi;
|
||||
uint8_t ic_ref;
|
||||
uint16_t block_count;
|
||||
uint8_t block_size;
|
||||
} Iso15693_3SystemInfo;
|
||||
|
||||
typedef struct {
|
||||
bool dsfid;
|
||||
bool afi;
|
||||
} Iso15693_3LockBits;
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3LockBits lock_bits;
|
||||
} Iso15693_3Settings;
|
||||
|
||||
typedef struct {
|
||||
uint8_t uid[ISO15693_3_UID_SIZE];
|
||||
Iso15693_3SystemInfo system_info;
|
||||
Iso15693_3Settings settings;
|
||||
SimpleArray* block_data;
|
||||
SimpleArray* block_security;
|
||||
} Iso15693_3Data;
|
||||
|
||||
Iso15693_3Data* iso15693_3_alloc();
|
||||
|
||||
void iso15693_3_free(Iso15693_3Data* data);
|
||||
|
||||
void iso15693_3_reset(Iso15693_3Data* data);
|
||||
|
||||
void iso15693_3_copy(Iso15693_3Data* data, const Iso15693_3Data* other);
|
||||
|
||||
bool iso15693_3_verify(Iso15693_3Data* data, const FuriString* device_type);
|
||||
|
||||
bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff);
|
||||
|
||||
bool iso15693_3_is_equal(const Iso15693_3Data* data, const Iso15693_3Data* other);
|
||||
|
||||
const char* iso15693_3_get_device_name(const Iso15693_3Data* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* iso15693_3_get_uid(const Iso15693_3Data* data, size_t* uid_len);
|
||||
|
||||
bool iso15693_3_set_uid(Iso15693_3Data* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso15693_3Data* iso15693_3_get_base_data(const Iso15693_3Data* data);
|
||||
|
||||
// Getters and tests
|
||||
|
||||
bool iso15693_3_is_block_locked(const Iso15693_3Data* data, uint8_t block_index);
|
||||
|
||||
uint8_t iso15693_3_get_manufacturer_id(const Iso15693_3Data* data);
|
||||
|
||||
uint16_t iso15693_3_get_block_count(const Iso15693_3Data* data);
|
||||
|
||||
uint8_t iso15693_3_get_block_size(const Iso15693_3Data* data);
|
||||
|
||||
const uint8_t* iso15693_3_get_block_data(const Iso15693_3Data* data, uint8_t block_index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h
Normal file
5
lib/nfc/protocols/iso15693_3/iso15693_3_device_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso15693_3;
|
||||
263
lib/nfc/protocols/iso15693_3/iso15693_3_i.c
Normal file
263
lib/nfc/protocols/iso15693_3/iso15693_3_i.c
Normal file
@@ -0,0 +1,263 @@
|
||||
#include "iso15693_3_i.h"
|
||||
|
||||
bool iso15693_3_error_response_parse(Iso15693_3Error* error, const BitBuffer* buf) {
|
||||
furi_assert(error);
|
||||
|
||||
if(bit_buffer_get_size_bytes(buf) == 0) {
|
||||
// YEET!
|
||||
*error = Iso15693_3ErrorBufferEmpty;
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t error;
|
||||
} ErrorResponseLayout;
|
||||
|
||||
const ErrorResponseLayout* resp = (const ErrorResponseLayout*)bit_buffer_get_data(buf);
|
||||
|
||||
if((resp->flags & ISO15693_3_RESP_FLAG_ERROR) == 0) {
|
||||
// No error flag is set, the data does not contain an error frame
|
||||
return false;
|
||||
} else if(bit_buffer_get_size_bytes(buf) < sizeof(ErrorResponseLayout)) {
|
||||
// Error bit is set, but not enough data to determine the error
|
||||
*error = Iso15693_3ErrorUnexpectedResponse;
|
||||
return true;
|
||||
} else if(
|
||||
resp->error >= ISO15693_3_RESP_ERROR_CUSTOM_START &&
|
||||
resp->error <= ISO15693_3_RESP_ERROR_CUSTOM_END) {
|
||||
// Custom vendor-specific error, must be checked in the respective protocol implementation
|
||||
*error = Iso15693_3ErrorCustom;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(resp->error) {
|
||||
case ISO15693_3_RESP_ERROR_NOT_SUPPORTED:
|
||||
case ISO15693_3_RESP_ERROR_OPTION:
|
||||
*error = Iso15693_3ErrorNotSupported;
|
||||
break;
|
||||
case ISO15693_3_RESP_ERROR_FORMAT:
|
||||
*error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
case ISO15693_3_RESP_ERROR_BLOCK_UNAVAILABLE:
|
||||
case ISO15693_3_RESP_ERROR_BLOCK_ALREADY_LOCKED:
|
||||
case ISO15693_3_RESP_ERROR_BLOCK_LOCKED:
|
||||
case ISO15693_3_RESP_ERROR_BLOCK_WRITE:
|
||||
case ISO15693_3_RESP_ERROR_BLOCK_LOCK:
|
||||
*error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
case ISO15693_3_RESP_ERROR_UNKNOWN:
|
||||
default:
|
||||
*error = Iso15693_3ErrorUnknown;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_inventory_response_parse(uint8_t* data, const BitBuffer* buf) {
|
||||
furi_assert(data);
|
||||
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(iso15693_3_error_response_parse(&ret, buf)) break;
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t dsfid;
|
||||
uint8_t uid[ISO15693_3_UID_SIZE];
|
||||
} InventoryResponseLayout;
|
||||
|
||||
if(bit_buffer_get_size_bytes(buf) != sizeof(InventoryResponseLayout)) {
|
||||
ret = Iso15693_3ErrorUnexpectedResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
const InventoryResponseLayout* resp =
|
||||
(const InventoryResponseLayout*)bit_buffer_get_data(buf);
|
||||
// Reverse UID for backward compatibility
|
||||
for(uint32_t i = 0; i < ISO15693_3_UID_SIZE; ++i) {
|
||||
data[i] = resp->uid[ISO15693_3_UID_SIZE - i - 1];
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_system_info_response_parse(Iso15693_3SystemInfo* data, const BitBuffer* buf) {
|
||||
furi_assert(data);
|
||||
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(iso15693_3_error_response_parse(&ret, buf)) break;
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t info_flags;
|
||||
uint8_t uid[ISO15693_3_UID_SIZE];
|
||||
uint8_t extra[];
|
||||
} SystemInfoResponseLayout;
|
||||
|
||||
if(bit_buffer_get_size_bytes(buf) < sizeof(SystemInfoResponseLayout)) {
|
||||
ret = Iso15693_3ErrorUnexpectedResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
const SystemInfoResponseLayout* resp =
|
||||
(const SystemInfoResponseLayout*)bit_buffer_get_data(buf);
|
||||
|
||||
const uint8_t* extra = resp->extra;
|
||||
const size_t extra_size = (resp->info_flags & ISO15693_3_SYSINFO_FLAG_DSFID ? 1 : 0) +
|
||||
(resp->info_flags & ISO15693_3_SYSINFO_FLAG_AFI ? 1 : 0) +
|
||||
(resp->info_flags & ISO15693_3_SYSINFO_FLAG_MEMORY ? 2 : 0) +
|
||||
(resp->info_flags & ISO15693_3_SYSINFO_FLAG_IC_REF ? 1 : 0);
|
||||
|
||||
if(extra_size != bit_buffer_get_size_bytes(buf) - sizeof(SystemInfoResponseLayout)) {
|
||||
ret = Iso15693_3ErrorUnexpectedResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
data->flags = resp->info_flags;
|
||||
|
||||
if(data->flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
data->dsfid = *extra++;
|
||||
}
|
||||
|
||||
if(data->flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
data->afi = *extra++;
|
||||
}
|
||||
|
||||
if(data->flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
|
||||
// Add 1 to get actual values
|
||||
data->block_count = *extra++ + 1;
|
||||
data->block_size = (*extra++ & 0x1F) + 1;
|
||||
}
|
||||
|
||||
if(data->flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
|
||||
data->ic_ref = *extra;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf) {
|
||||
furi_assert(data);
|
||||
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(iso15693_3_error_response_parse(&ret, buf)) break;
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t block_data[];
|
||||
} ReadBlockResponseLayout;
|
||||
|
||||
const size_t buf_size = bit_buffer_get_size_bytes(buf);
|
||||
const size_t received_block_size = buf_size - sizeof(ReadBlockResponseLayout);
|
||||
|
||||
if(buf_size <= sizeof(ReadBlockResponseLayout) || received_block_size != block_size) {
|
||||
ret = Iso15693_3ErrorUnexpectedResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
const ReadBlockResponseLayout* resp =
|
||||
(const ReadBlockResponseLayout*)bit_buffer_get_data(buf);
|
||||
memcpy(data, resp->block_data, received_block_size);
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_get_block_security_response_parse(
|
||||
uint8_t* data,
|
||||
uint16_t block_count,
|
||||
const BitBuffer* buf) {
|
||||
furi_assert(data);
|
||||
furi_assert(block_count);
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(iso15693_3_error_response_parse(&ret, buf)) break;
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t block_security[];
|
||||
} GetBlockSecurityResponseLayout;
|
||||
|
||||
const size_t buf_size = bit_buffer_get_size_bytes(buf);
|
||||
const size_t received_block_count = buf_size - sizeof(GetBlockSecurityResponseLayout);
|
||||
|
||||
if(buf_size <= sizeof(GetBlockSecurityResponseLayout) ||
|
||||
received_block_count != block_count) {
|
||||
ret = Iso15693_3ErrorUnexpectedResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
const GetBlockSecurityResponseLayout* resp =
|
||||
(const GetBlockSecurityResponseLayout*)bit_buffer_get_data(buf);
|
||||
|
||||
memcpy(data, resp->block_security, received_block_count);
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void iso15693_3_append_uid(const Iso15693_3Data* data, BitBuffer* buf) {
|
||||
for(size_t i = 0; i < ISO15693_3_UID_SIZE; ++i) {
|
||||
// Reverse the UID
|
||||
bit_buffer_append_byte(buf, data->uid[ISO15693_3_UID_SIZE - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void iso15693_3_append_block(const Iso15693_3Data* data, uint8_t block_num, BitBuffer* buf) {
|
||||
furi_assert(block_num < data->system_info.block_count);
|
||||
|
||||
const uint32_t block_offset = block_num * data->system_info.block_size;
|
||||
const uint8_t* block_data = simple_array_cget(data->block_data, block_offset);
|
||||
|
||||
bit_buffer_append_bytes(buf, block_data, data->system_info.block_size);
|
||||
}
|
||||
|
||||
void iso15693_3_set_block_locked(Iso15693_3Data* data, uint8_t block_index, bool locked) {
|
||||
furi_assert(data);
|
||||
furi_assert(block_index < data->system_info.block_count);
|
||||
|
||||
*(uint8_t*)simple_array_get(data->block_security, block_index) = locked ? 1 : 0;
|
||||
}
|
||||
|
||||
void iso15693_3_set_block_data(
|
||||
Iso15693_3Data* data,
|
||||
uint8_t block_num,
|
||||
const uint8_t* block_data,
|
||||
size_t block_data_size) {
|
||||
furi_assert(block_num < data->system_info.block_count);
|
||||
furi_assert(block_data_size == data->system_info.block_size);
|
||||
|
||||
const uint32_t block_offset = block_num * data->system_info.block_size;
|
||||
uint8_t* block = simple_array_get(data->block_data, block_offset);
|
||||
|
||||
memcpy(block, block_data, block_data_size);
|
||||
}
|
||||
|
||||
void iso15693_3_append_block_security(
|
||||
const Iso15693_3Data* data,
|
||||
uint8_t block_num,
|
||||
BitBuffer* buf) {
|
||||
bit_buffer_append_byte(buf, *(uint8_t*)simple_array_cget(data->block_security, block_num));
|
||||
}
|
||||
|
||||
bool iso15693_3_is_equal_uid(const Iso15693_3Data* data, const uint8_t* uid) {
|
||||
for(size_t i = 0; i < ISO15693_3_UID_SIZE; ++i) {
|
||||
if(data->uid[i] != uid[ISO15693_3_UID_SIZE - i - 1]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
58
lib/nfc/protocols/iso15693_3/iso15693_3_i.h
Normal file
58
lib/nfc/protocols/iso15693_3/iso15693_3_i.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso15693_3.h"
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Check if the buffer contains an error frame and if it does, determine
|
||||
* the error type.
|
||||
* NOTE: No changes are done to the result if no error is present.
|
||||
*
|
||||
* @param [out] data Pointer to the resulting error value.
|
||||
* @param [in] buf Data buffer to be checked
|
||||
*
|
||||
* @return True if data contains an error frame or is empty, false otherwise
|
||||
*/
|
||||
bool iso15693_3_error_response_parse(Iso15693_3Error* error, const BitBuffer* buf);
|
||||
|
||||
Iso15693_3Error iso15693_3_inventory_response_parse(uint8_t* data, const BitBuffer* buf);
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_system_info_response_parse(Iso15693_3SystemInfo* data, const BitBuffer* buf);
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf);
|
||||
|
||||
Iso15693_3Error iso15693_3_get_block_security_response_parse(
|
||||
uint8_t* data,
|
||||
uint16_t block_count,
|
||||
const BitBuffer* buf);
|
||||
|
||||
void iso15693_3_append_uid(const Iso15693_3Data* data, BitBuffer* buf);
|
||||
|
||||
void iso15693_3_append_block(const Iso15693_3Data* data, uint8_t block_num, BitBuffer* buf);
|
||||
|
||||
void iso15693_3_set_block_locked(Iso15693_3Data* data, uint8_t block_index, bool locked);
|
||||
|
||||
void iso15693_3_set_block_data(
|
||||
Iso15693_3Data* data,
|
||||
uint8_t block_num,
|
||||
const uint8_t* block_data,
|
||||
size_t block_data_size);
|
||||
|
||||
void iso15693_3_append_block_security(
|
||||
const Iso15693_3Data* data,
|
||||
uint8_t block_num,
|
||||
BitBuffer* buf);
|
||||
|
||||
// NOTE: the uid parameter has reversed byte order with respect to data
|
||||
bool iso15693_3_is_equal_uid(const Iso15693_3Data* data, const uint8_t* uid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
110
lib/nfc/protocols/iso15693_3/iso15693_3_listener.c
Normal file
110
lib/nfc/protocols/iso15693_3/iso15693_3_listener.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "iso15693_3_listener_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#define TAG "Iso15693_3Listener"
|
||||
|
||||
#define ISO15693_3_LISTENER_BUFFER_SIZE (64U)
|
||||
|
||||
Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) {
|
||||
furi_assert(nfc);
|
||||
|
||||
Iso15693_3Listener* instance = malloc(sizeof(Iso15693_3Listener));
|
||||
instance->nfc = nfc;
|
||||
instance->data = data;
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO15693_3_LISTENER_BUFFER_SIZE);
|
||||
|
||||
instance->iso15693_3_event.data = &instance->iso15693_3_event_data;
|
||||
instance->generic_event.protocol = NfcProtocolIso15693_3;
|
||||
instance->generic_event.instance = instance;
|
||||
instance->generic_event.event_data = &instance->iso15693_3_event;
|
||||
|
||||
nfc_set_fdt_listen_fc(instance->nfc, ISO15693_3_FDT_LISTEN_FC);
|
||||
nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void iso15693_3_listener_free(Iso15693_3Listener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void iso15693_3_listener_set_callback(
|
||||
Iso15693_3Listener* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
const Iso15693_3Data* iso15693_3_listener_get_data(Iso15693_3Listener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Iso15693_3Listener* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypeRxEnd) {
|
||||
BitBuffer* rx_buffer = nfc_event->data.buffer;
|
||||
|
||||
if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) {
|
||||
iso13239_crc_trim(rx_buffer);
|
||||
|
||||
const Iso15693_3Error error = iso15693_3_listener_process_request(instance, rx_buffer);
|
||||
|
||||
if(error == Iso15693_3ErrorNotSupported) {
|
||||
if(instance->callback) {
|
||||
instance->iso15693_3_event.type = Iso15693_3ListenerEventTypeCustomCommand;
|
||||
instance->iso15693_3_event.data->buffer = rx_buffer;
|
||||
command = instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
|
||||
} else if(error == Iso15693_3ErrorUidMismatch) {
|
||||
iso15693_3_listener_process_uid_mismatch(instance, rx_buffer);
|
||||
}
|
||||
|
||||
} else if(bit_buffer_get_size(rx_buffer) == 0) {
|
||||
// Special case: Single EOF
|
||||
const Iso15693_3Error error = iso15693_3_listener_process_single_eof(instance);
|
||||
if(error == Iso15693_3ErrorUnexpectedResponse) {
|
||||
if(instance->callback) {
|
||||
instance->iso15693_3_event.type = Iso15693_3ListenerEventTypeSingleEof;
|
||||
command = instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(
|
||||
TAG, "Wrong CRC, buffer size: %zu", bit_buffer_get_size(nfc_event->data.buffer));
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
const NfcListenerBase nfc_listener_iso15693_3 = {
|
||||
.alloc = (NfcListenerAlloc)iso15693_3_listener_alloc,
|
||||
.free = (NfcListenerFree)iso15693_3_listener_free,
|
||||
.set_callback = (NfcListenerSetCallback)iso15693_3_listener_set_callback,
|
||||
.get_data = (NfcListenerGetData)iso15693_3_listener_get_data,
|
||||
.run = (NfcListenerRun)iso15693_3_listener_run,
|
||||
};
|
||||
30
lib/nfc/protocols/iso15693_3/iso15693_3_listener.h
Normal file
30
lib/nfc/protocols/iso15693_3/iso15693_3_listener.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc_listener.h>
|
||||
|
||||
#include "iso15693_3.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso15693_3Listener Iso15693_3Listener;
|
||||
|
||||
typedef enum {
|
||||
Iso15693_3ListenerEventTypeFieldOff,
|
||||
Iso15693_3ListenerEventTypeCustomCommand,
|
||||
Iso15693_3ListenerEventTypeSingleEof,
|
||||
} Iso15693_3ListenerEventType;
|
||||
|
||||
typedef struct {
|
||||
BitBuffer* buffer;
|
||||
} Iso15693_3ListenerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3ListenerEventType type;
|
||||
Iso15693_3ListenerEventData* data;
|
||||
} Iso15693_3ListenerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h
Normal file
5
lib/nfc/protocols/iso15693_3/iso15693_3_listener_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
extern const NfcListenerBase nfc_listener_iso15693_3;
|
||||
883
lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c
Normal file
883
lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c
Normal file
@@ -0,0 +1,883 @@
|
||||
#include "iso15693_3_listener_i.h"
|
||||
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#define TAG "Iso15693_3Listener"
|
||||
|
||||
typedef Iso15693_3Error (*Iso15693_3RequestHandler)(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags);
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3RequestHandler mandatory[ISO15693_3_MANDATORY_COUNT];
|
||||
Iso15693_3RequestHandler optional[ISO15693_3_OPTIONAL_COUNT];
|
||||
} Iso15693_3ListenerHandlerTable;
|
||||
|
||||
static Iso15693_3Error
|
||||
iso15693_3_listener_extension_handler(Iso15693_3Listener* instance, uint32_t command, ...) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(instance->extension_table == NULL) break;
|
||||
|
||||
Iso15693_3ExtensionHandler handler = NULL;
|
||||
|
||||
if(command < ISO15693_3_CMD_MANDATORY_RFU) {
|
||||
const Iso15693_3ExtensionHandler* mandatory = instance->extension_table->mandatory;
|
||||
handler = mandatory[command - ISO15693_3_CMD_MANDATORY_START];
|
||||
} else if(command >= ISO15693_3_CMD_OPTIONAL_START && command < ISO15693_3_CMD_OPTIONAL_RFU) {
|
||||
const Iso15693_3ExtensionHandler* optional = instance->extension_table->optional;
|
||||
handler = optional[command - ISO15693_3_CMD_OPTIONAL_START];
|
||||
}
|
||||
|
||||
if(handler == NULL) break;
|
||||
|
||||
va_list args;
|
||||
va_start(args, command);
|
||||
|
||||
error = handler(instance->extension_context, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
} while(false);
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_inventory_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
const bool afi_flag = flags & ISO15693_3_REQ_FLAG_T5_AFI_PRESENT;
|
||||
const size_t data_size_min = sizeof(uint8_t) * (afi_flag ? 2 : 1);
|
||||
|
||||
if(data_size < data_size_min) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
if(afi_flag) {
|
||||
const uint8_t afi = *data++;
|
||||
// When AFI flag is set, ignore non-matching requests
|
||||
if(afi != instance->data->system_info.afi) break;
|
||||
}
|
||||
|
||||
const uint8_t mask_len = *data++;
|
||||
const size_t data_size_required = data_size_min + mask_len;
|
||||
|
||||
if(data_size != data_size_required) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
if(mask_len != 0) {
|
||||
// TODO FL-3633: Take mask_len and mask_value into account (if present)
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_INVENTORY);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.dsfid); // DSFID
|
||||
iso15693_3_append_uid(instance->data, instance->tx_buffer); // UID
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_stay_quiet_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(data);
|
||||
UNUSED(data_size);
|
||||
UNUSED(flags);
|
||||
|
||||
instance->state = Iso15693_3ListenerStateQuiet;
|
||||
return Iso15693_3ErrorIgnore;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_read_block_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
} Iso15693_3ReadBlockRequestLayout;
|
||||
|
||||
const Iso15693_3ReadBlockRequestLayout* request =
|
||||
(const Iso15693_3ReadBlockRequestLayout*)data;
|
||||
|
||||
if(data_size != sizeof(Iso15693_3ReadBlockRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t block_index = request->block_num;
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
|
||||
if(block_index >= block_count_max) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(
|
||||
instance, ISO15693_3_CMD_READ_BLOCK, block_index);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) {
|
||||
iso15693_3_append_block_security(
|
||||
instance->data, block_index, instance->tx_buffer); // Block security (optional)
|
||||
}
|
||||
|
||||
iso15693_3_append_block(instance->data, block_index, instance->tx_buffer); // Block data
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_write_block_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
uint8_t block_data[];
|
||||
} Iso15693_3WriteBlockRequestLayout;
|
||||
|
||||
const Iso15693_3WriteBlockRequestLayout* request =
|
||||
(const Iso15693_3WriteBlockRequestLayout*)data;
|
||||
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
if(data_size <= sizeof(Iso15693_3WriteBlockRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t block_index = request->block_num;
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
const uint32_t block_size_max = instance->data->system_info.block_size;
|
||||
const size_t block_size_received = data_size - sizeof(Iso15693_3WriteBlockRequestLayout);
|
||||
|
||||
if(block_index >= block_count_max) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
} else if(block_size_received != block_size_max) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
} else if(iso15693_3_is_block_locked(instance->data, block_index)) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(
|
||||
instance, ISO15693_3_CMD_WRITE_BLOCK, block_index, request->block_data);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
iso15693_3_set_block_data(
|
||||
instance->data, block_index, request->block_data, block_size_received);
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_lock_block_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
} Iso15693_3LockBlockRequestLayout;
|
||||
|
||||
const Iso15693_3LockBlockRequestLayout* request =
|
||||
(const Iso15693_3LockBlockRequestLayout*)data;
|
||||
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
if(data_size != sizeof(Iso15693_3LockBlockRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t block_index = request->block_num;
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
|
||||
if(block_index >= block_count_max) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
} else if(iso15693_3_is_block_locked(instance->data, block_index)) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(
|
||||
instance, ISO15693_3_CMD_LOCK_BLOCK, block_index);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
iso15693_3_set_block_locked(instance->data, block_index, true);
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t first_block_num;
|
||||
uint8_t block_count;
|
||||
} Iso15693_3ReadMultiBlocksRequestLayout;
|
||||
|
||||
const Iso15693_3ReadMultiBlocksRequestLayout* request =
|
||||
(const Iso15693_3ReadMultiBlocksRequestLayout*)data;
|
||||
|
||||
if(data_size != sizeof(Iso15693_3ReadMultiBlocksRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t block_index_start = request->first_block_num;
|
||||
const uint32_t block_index_end = block_index_start + request->block_count;
|
||||
|
||||
const uint32_t block_count = request->block_count + 1;
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
const uint32_t block_count_available = block_count_max - block_index_start;
|
||||
|
||||
if(block_count > block_count_available) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(
|
||||
instance,
|
||||
ISO15693_3_CMD_READ_MULTI_BLOCKS,
|
||||
(uint32_t)block_index_start,
|
||||
(uint32_t)block_index_end);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
for(uint32_t i = block_index_start; i <= block_index_end; ++i) {
|
||||
if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) {
|
||||
iso15693_3_append_block_security(
|
||||
instance->data, i, instance->tx_buffer); // Block security (optional)
|
||||
}
|
||||
iso15693_3_append_block(instance->data, i, instance->tx_buffer); // Block data
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_write_multi_blocks_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t first_block_num;
|
||||
uint8_t block_count;
|
||||
uint8_t block_data[];
|
||||
} Iso15693_3WriteMultiBlocksRequestLayout;
|
||||
|
||||
const Iso15693_3WriteMultiBlocksRequestLayout* request =
|
||||
(const Iso15693_3WriteMultiBlocksRequestLayout*)data;
|
||||
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
if(data_size <= sizeof(Iso15693_3WriteMultiBlocksRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t block_index_start = request->first_block_num;
|
||||
const uint32_t block_index_end = block_index_start + request->block_count;
|
||||
|
||||
const uint32_t block_count = request->block_count + 1;
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
const uint32_t block_count_available = block_count_max - block_index_start;
|
||||
|
||||
const size_t block_data_size = data_size - sizeof(Iso15693_3WriteMultiBlocksRequestLayout);
|
||||
const size_t block_size = block_data_size / block_count;
|
||||
const size_t block_size_max = instance->data->system_info.block_size;
|
||||
|
||||
if(block_count > block_count_available) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
} else if(block_size != block_size_max) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(
|
||||
instance, ISO15693_3_CMD_WRITE_MULTI_BLOCKS, block_index_start, block_index_end);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
for(uint32_t i = block_index_start; i <= block_index_end; ++i) {
|
||||
if(iso15693_3_is_block_locked(instance->data, i)) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
for(uint32_t i = block_index_start; i < block_count + request->first_block_num; ++i) {
|
||||
const uint8_t* block_data = &request->block_data[block_size * i];
|
||||
iso15693_3_set_block_data(instance->data, i, block_data, block_size);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_select_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(data);
|
||||
UNUSED(data_size);
|
||||
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(!(flags & ISO15693_3_REQ_FLAG_T4_ADDRESSED)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = Iso15693_3ListenerStateSelected;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_reset_to_ready_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(data);
|
||||
UNUSED(data_size);
|
||||
UNUSED(flags);
|
||||
|
||||
instance->state = Iso15693_3ListenerStateReady;
|
||||
return Iso15693_3ErrorNone;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_write_afi_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t afi;
|
||||
} Iso15693_3WriteAfiRequestLayout;
|
||||
|
||||
const Iso15693_3WriteAfiRequestLayout* request =
|
||||
(const Iso15693_3WriteAfiRequestLayout*)data;
|
||||
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
if(data_size <= sizeof(Iso15693_3WriteAfiRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
} else if(instance->data->settings.lock_bits.afi) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_WRITE_AFI);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
instance->data->system_info.afi = request->afi;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_lock_afi_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(data);
|
||||
UNUSED(data_size);
|
||||
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
Iso15693_3LockBits* lock_bits = &instance->data->settings.lock_bits;
|
||||
|
||||
if(lock_bits->afi) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_LOCK_AFI);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
lock_bits->afi = true;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_write_dsfid_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t dsfid;
|
||||
} Iso15693_3WriteDsfidRequestLayout;
|
||||
|
||||
const Iso15693_3WriteDsfidRequestLayout* request =
|
||||
(const Iso15693_3WriteDsfidRequestLayout*)data;
|
||||
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
if(data_size <= sizeof(Iso15693_3WriteDsfidRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
} else if(instance->data->settings.lock_bits.dsfid) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_WRITE_DSFID);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
instance->data->system_info.dsfid = request->dsfid;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_lock_dsfid_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(data);
|
||||
UNUSED(data_size);
|
||||
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
instance->session_state.wait_for_eof = flags & ISO15693_3_REQ_FLAG_T4_OPTION;
|
||||
|
||||
Iso15693_3LockBits* lock_bits = &instance->data->settings.lock_bits;
|
||||
|
||||
if(lock_bits->dsfid) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_extension_handler(instance, ISO15693_3_CMD_LOCK_DSFID);
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
|
||||
lock_bits->dsfid = true;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_get_system_info_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(data);
|
||||
UNUSED(data_size);
|
||||
UNUSED(flags);
|
||||
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
const uint8_t system_flags = instance->data->system_info.flags;
|
||||
bit_buffer_append_byte(instance->tx_buffer, system_flags); // System info flags
|
||||
|
||||
iso15693_3_append_uid(instance->data, instance->tx_buffer); // UID
|
||||
|
||||
if(system_flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.dsfid);
|
||||
}
|
||||
if(system_flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.afi);
|
||||
}
|
||||
if(system_flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
|
||||
const uint8_t memory_info[2] = {
|
||||
instance->data->system_info.block_count - 1,
|
||||
instance->data->system_info.block_size - 1,
|
||||
};
|
||||
bit_buffer_append_bytes(instance->tx_buffer, memory_info, COUNT_OF(memory_info));
|
||||
}
|
||||
if(system_flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, instance->data->system_info.ic_ref);
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_get_multi_blocks_security_handler(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t flags) {
|
||||
UNUSED(flags);
|
||||
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t first_block_num;
|
||||
uint8_t block_count;
|
||||
} Iso15693_3GetMultiBlocksSecurityRequestLayout;
|
||||
|
||||
const Iso15693_3GetMultiBlocksSecurityRequestLayout* request =
|
||||
(const Iso15693_3GetMultiBlocksSecurityRequestLayout*)data;
|
||||
|
||||
if(data_size < sizeof(Iso15693_3GetMultiBlocksSecurityRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t block_index_start = request->first_block_num;
|
||||
const uint32_t block_index_end = block_index_start + request->block_count;
|
||||
|
||||
const uint32_t block_count_max = instance->data->system_info.block_count;
|
||||
|
||||
if(block_index_end >= block_count_max) {
|
||||
error = Iso15693_3ErrorInternal;
|
||||
break;
|
||||
}
|
||||
|
||||
for(uint32_t i = block_index_start; i <= block_index_end; ++i) {
|
||||
bit_buffer_append_byte(
|
||||
instance->tx_buffer, iso15693_3_is_block_locked(instance->data, i) ? 1 : 0);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
const Iso15693_3ListenerHandlerTable iso15693_3_handler_table = {
|
||||
.mandatory =
|
||||
{
|
||||
iso15693_3_listener_inventory_handler,
|
||||
iso15693_3_listener_stay_quiet_handler,
|
||||
},
|
||||
.optional =
|
||||
{
|
||||
iso15693_3_listener_read_block_handler,
|
||||
iso15693_3_listener_write_block_handler,
|
||||
iso15693_3_listener_lock_block_handler,
|
||||
iso15693_3_listener_read_multi_blocks_handler,
|
||||
iso15693_3_listener_write_multi_blocks_handler,
|
||||
iso15693_3_listener_select_handler,
|
||||
iso15693_3_listener_reset_to_ready_handler,
|
||||
iso15693_3_listener_write_afi_handler,
|
||||
iso15693_3_listener_lock_afi_handler,
|
||||
iso15693_3_listener_write_dsfid_handler,
|
||||
iso15693_3_listener_lock_dsfid_handler,
|
||||
iso15693_3_listener_get_system_info_handler,
|
||||
iso15693_3_listener_get_multi_blocks_security_handler,
|
||||
},
|
||||
};
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_handle_standard_request(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size,
|
||||
uint8_t command,
|
||||
uint8_t flags) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
Iso15693_3RequestHandler handler = NULL;
|
||||
|
||||
if(command < ISO15693_3_CMD_MANDATORY_RFU) {
|
||||
handler = iso15693_3_handler_table.mandatory[command - ISO15693_3_CMD_MANDATORY_START];
|
||||
} else if(command >= ISO15693_3_CMD_OPTIONAL_START && command < ISO15693_3_CMD_OPTIONAL_RFU) {
|
||||
handler = iso15693_3_handler_table.optional[command - ISO15693_3_CMD_OPTIONAL_START];
|
||||
}
|
||||
|
||||
if(handler == NULL) {
|
||||
error = Iso15693_3ErrorNotSupported;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_NONE);
|
||||
|
||||
error = handler(instance, data, data_size, flags);
|
||||
|
||||
// The request was fully handled in the protocol extension, no further action necessary
|
||||
if(error == Iso15693_3ErrorFullyHandled) {
|
||||
error = Iso15693_3ErrorNone;
|
||||
}
|
||||
|
||||
// Several commands may not require an answer
|
||||
if(error == Iso15693_3ErrorFormat || error == Iso15693_3ErrorIgnore) break;
|
||||
|
||||
if(error != Iso15693_3ErrorNone) {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_FLAG_ERROR);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_RESP_ERROR_UNKNOWN);
|
||||
}
|
||||
|
||||
Iso15693_3ListenerSessionState* session_state = &instance->session_state;
|
||||
|
||||
if(!session_state->wait_for_eof) {
|
||||
error = iso15693_3_listener_send_frame(instance, instance->tx_buffer);
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static inline Iso15693_3Error iso15693_3_listener_handle_custom_request(
|
||||
Iso15693_3Listener* instance,
|
||||
const uint8_t* data,
|
||||
size_t data_size) {
|
||||
Iso15693_3Error error;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t manufacturer;
|
||||
uint8_t extra[];
|
||||
} Iso15693_3CustomRequestLayout;
|
||||
|
||||
if(data_size < sizeof(Iso15693_3CustomRequestLayout)) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const Iso15693_3CustomRequestLayout* request = (const Iso15693_3CustomRequestLayout*)data;
|
||||
|
||||
if(request->manufacturer != iso15693_3_get_manufacturer_id(instance->data)) {
|
||||
error = Iso15693_3ErrorIgnore;
|
||||
break;
|
||||
}
|
||||
|
||||
// This error code will trigger the CustomCommand listener event
|
||||
error = Iso15693_3ErrorNotSupported;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_set_extension_handler_table(
|
||||
Iso15693_3Listener* instance,
|
||||
const Iso15693_3ExtensionHandlerTable* table,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(context);
|
||||
|
||||
instance->extension_table = table;
|
||||
instance->extension_context = context;
|
||||
return Iso15693_3ErrorNone;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_ready(Iso15693_3Listener* instance) {
|
||||
furi_assert(instance);
|
||||
instance->state = Iso15693_3ListenerStateReady;
|
||||
return Iso15693_3ErrorNone;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_listener_process_nfc_error(NfcError error) {
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
if(error == NfcErrorNone) {
|
||||
ret = Iso15693_3ErrorNone;
|
||||
} else if(error == NfcErrorTimeout) {
|
||||
ret = Iso15693_3ErrorTimeout;
|
||||
} else {
|
||||
ret = Iso15693_3ErrorFieldOff;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_listener_send_frame(Iso15693_3Listener* instance, const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
bit_buffer_copy(instance->tx_buffer, tx_buffer);
|
||||
iso13239_crc_append(Iso13239CrcTypeDefault, instance->tx_buffer);
|
||||
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
return iso15693_3_listener_process_nfc_error(error);
|
||||
}
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_listener_process_request(Iso15693_3Listener* instance, const BitBuffer* rx_buffer) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t command;
|
||||
uint8_t data[];
|
||||
} Iso15693_3RequestLayout;
|
||||
|
||||
const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer);
|
||||
const size_t buf_size_min = sizeof(Iso15693_3RequestLayout);
|
||||
|
||||
if(buf_size < buf_size_min) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
const Iso15693_3RequestLayout* request =
|
||||
(const Iso15693_3RequestLayout*)bit_buffer_get_data(rx_buffer);
|
||||
|
||||
Iso15693_3ListenerSessionState* session_state = &instance->session_state;
|
||||
|
||||
if((request->flags & ISO15693_3_REQ_FLAG_INVENTORY_T5) == 0) {
|
||||
session_state->selected = request->flags & ISO15693_3_REQ_FLAG_T4_SELECTED;
|
||||
session_state->addressed = request->flags & ISO15693_3_REQ_FLAG_T4_ADDRESSED;
|
||||
|
||||
if(session_state->selected && session_state->addressed) {
|
||||
// A request mode can be either addressed or selected, but not both
|
||||
error = Iso15693_3ErrorUnknown;
|
||||
break;
|
||||
} else if(instance->state == Iso15693_3ListenerStateQuiet) {
|
||||
// If the card is quiet, ignore non-addressed commands
|
||||
if(session_state->addressed) {
|
||||
error = Iso15693_3ErrorIgnore;
|
||||
break;
|
||||
}
|
||||
} else if(instance->state != Iso15693_3ListenerStateSelected) {
|
||||
// If the card is not selected, ignore selected commands
|
||||
if(session_state->selected) {
|
||||
error = Iso15693_3ErrorIgnore;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the card is quiet, ignore inventory commands
|
||||
if(instance->state == Iso15693_3ListenerStateQuiet) {
|
||||
error = Iso15693_3ErrorIgnore;
|
||||
break;
|
||||
}
|
||||
|
||||
session_state->selected = false;
|
||||
session_state->addressed = false;
|
||||
}
|
||||
|
||||
if(request->command >= ISO15693_3_CMD_CUSTOM_START) {
|
||||
// Custom commands are properly handled in the protocol-specific top-level poller
|
||||
error = iso15693_3_listener_handle_custom_request(
|
||||
instance, request->data, buf_size - buf_size_min);
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t* data;
|
||||
size_t data_size;
|
||||
|
||||
if(session_state->addressed) {
|
||||
// In addressed mode, UID must be included in each command
|
||||
const size_t buf_size_min_addr = buf_size_min + ISO15693_3_UID_SIZE;
|
||||
|
||||
if(buf_size < buf_size_min_addr) {
|
||||
error = Iso15693_3ErrorFormat;
|
||||
break;
|
||||
} else if(!iso15693_3_is_equal_uid(instance->data, request->data)) {
|
||||
error = Iso15693_3ErrorUidMismatch;
|
||||
break;
|
||||
}
|
||||
|
||||
data = &request->data[ISO15693_3_UID_SIZE];
|
||||
data_size = buf_size - buf_size_min_addr;
|
||||
|
||||
} else {
|
||||
data = request->data;
|
||||
data_size = buf_size - buf_size_min;
|
||||
}
|
||||
|
||||
error = iso15693_3_listener_handle_standard_request(
|
||||
instance, data, data_size, request->command, request->flags);
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_process_single_eof(Iso15693_3Listener* instance) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(!instance->session_state.wait_for_eof) {
|
||||
error = Iso15693_3ErrorUnexpectedResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->session_state.wait_for_eof = false;
|
||||
|
||||
error = iso15693_3_listener_send_frame(instance, instance->tx_buffer);
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_process_uid_mismatch(
|
||||
Iso15693_3Listener* instance,
|
||||
const BitBuffer* rx_buffer) {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
|
||||
// No checks, assuming they have been made beforehand
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
uint8_t command;
|
||||
} Iso15693_3RequestLayout;
|
||||
|
||||
const Iso15693_3RequestLayout* request =
|
||||
(const Iso15693_3RequestLayout*)bit_buffer_get_data(rx_buffer);
|
||||
|
||||
if(request->command == ISO15693_3_CMD_SELECT) {
|
||||
if(instance->state == Iso15693_3ListenerStateSelected) {
|
||||
error = iso15693_3_listener_ready(instance);
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
70
lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h
Normal file
70
lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
|
||||
#include "iso15693_3_listener.h"
|
||||
|
||||
#include "iso15693_3_i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
Iso15693_3ListenerStateReady,
|
||||
Iso15693_3ListenerStateSelected,
|
||||
Iso15693_3ListenerStateQuiet,
|
||||
} Iso15693_3ListenerState;
|
||||
|
||||
typedef struct {
|
||||
bool selected;
|
||||
bool addressed;
|
||||
bool wait_for_eof;
|
||||
} Iso15693_3ListenerSessionState;
|
||||
|
||||
typedef Iso15693_3Error (*Iso15693_3ExtensionHandler)(void* context, va_list args);
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3ExtensionHandler mandatory[ISO15693_3_MANDATORY_COUNT];
|
||||
Iso15693_3ExtensionHandler optional[ISO15693_3_OPTIONAL_COUNT];
|
||||
} Iso15693_3ExtensionHandlerTable;
|
||||
|
||||
struct Iso15693_3Listener {
|
||||
Nfc* nfc;
|
||||
Iso15693_3Data* data;
|
||||
Iso15693_3ListenerState state;
|
||||
Iso15693_3ListenerSessionState session_state;
|
||||
BitBuffer* tx_buffer;
|
||||
|
||||
NfcGenericEvent generic_event;
|
||||
Iso15693_3ListenerEvent iso15693_3_event;
|
||||
Iso15693_3ListenerEventData iso15693_3_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
|
||||
const Iso15693_3ExtensionHandlerTable* extension_table;
|
||||
void* extension_context;
|
||||
};
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_set_extension_handler_table(
|
||||
Iso15693_3Listener* instance,
|
||||
const Iso15693_3ExtensionHandlerTable* table,
|
||||
void* context);
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_ready(Iso15693_3Listener* instance);
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_listener_send_frame(Iso15693_3Listener* instance, const BitBuffer* tx_buffer);
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_listener_process_request(Iso15693_3Listener* instance, const BitBuffer* rx_buffer);
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_process_single_eof(Iso15693_3Listener* instance);
|
||||
|
||||
Iso15693_3Error iso15693_3_listener_process_uid_mismatch(
|
||||
Iso15693_3Listener* instance,
|
||||
const BitBuffer* rx_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
122
lib/nfc/protocols/iso15693_3/iso15693_3_poller.c
Normal file
122
lib/nfc/protocols/iso15693_3/iso15693_3_poller.c
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "iso15693_3_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "ISO15693_3Poller"
|
||||
|
||||
const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static Iso15693_3Poller* iso15693_3_poller_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
Iso15693_3Poller* instance = malloc(sizeof(Iso15693_3Poller));
|
||||
instance->nfc = nfc;
|
||||
instance->tx_buffer = bit_buffer_alloc(ISO15693_3_POLLER_MAX_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(ISO15693_3_POLLER_MAX_BUFFER_SIZE);
|
||||
|
||||
nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693);
|
||||
nfc_set_guard_time_us(instance->nfc, ISO15693_3_GUARD_TIME_US);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, ISO15693_3_FDT_POLL_FC);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, ISO15693_3_POLL_POLL_MIN_US);
|
||||
instance->data = iso15693_3_alloc();
|
||||
|
||||
instance->iso15693_3_event.data = &instance->iso15693_3_event_data;
|
||||
instance->general_event.protocol = NfcProtocolIso15693_3;
|
||||
instance->general_event.event_data = &instance->iso15693_3_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void iso15693_3_poller_free(Iso15693_3Poller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(instance->data);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
iso15693_3_free(instance->data);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void iso15693_3_poller_set_callback(
|
||||
Iso15693_3Poller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand iso15693_3_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
Iso15693_3Poller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
if(instance->state != Iso15693_3PollerStateActivated) {
|
||||
Iso15693_3Error error = iso15693_3_poller_async_activate(instance, instance->data);
|
||||
if(error == Iso15693_3ErrorNone) {
|
||||
instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady;
|
||||
instance->iso15693_3_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
} else {
|
||||
instance->iso15693_3_event.type = Iso15693_3PollerEventTypeError;
|
||||
instance->iso15693_3_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
// Add delay to switch context
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
} else {
|
||||
instance->iso15693_3_event.type = Iso15693_3PollerEventTypeReady;
|
||||
instance->iso15693_3_event_data.error = Iso15693_3ErrorNone;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool iso15693_3_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
|
||||
bool protocol_detected = false;
|
||||
Iso15693_3Poller* instance = context;
|
||||
NfcEvent* nfc_event = event.event_data;
|
||||
furi_assert(instance->state == Iso15693_3PollerStateIdle);
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
uint8_t uid[ISO15693_3_UID_SIZE];
|
||||
Iso15693_3Error error = iso15693_3_poller_async_inventory(instance, uid);
|
||||
protocol_detected = (error == Iso15693_3ErrorNone);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase nfc_poller_iso15693_3 = {
|
||||
.alloc = (NfcPollerAlloc)iso15693_3_poller_alloc,
|
||||
.free = (NfcPollerFree)iso15693_3_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)iso15693_3_poller_set_callback,
|
||||
.run = (NfcPollerRun)iso15693_3_poller_run,
|
||||
.detect = (NfcPollerDetect)iso15693_3_poller_detect,
|
||||
.get_data = (NfcPollerGetData)iso15693_3_poller_get_data,
|
||||
};
|
||||
29
lib/nfc/protocols/iso15693_3/iso15693_3_poller.h
Normal file
29
lib/nfc/protocols/iso15693_3/iso15693_3_poller.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso15693_3.h"
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso15693_3Poller Iso15693_3Poller;
|
||||
|
||||
typedef enum {
|
||||
Iso15693_3PollerEventTypeError,
|
||||
Iso15693_3PollerEventTypeReady,
|
||||
} Iso15693_3PollerEventType;
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3Error error;
|
||||
} Iso15693_3PollerEventData;
|
||||
|
||||
typedef struct {
|
||||
Iso15693_3PollerEventType type;
|
||||
Iso15693_3PollerEventData* data;
|
||||
} Iso15693_3PollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h
Normal file
5
lib/nfc/protocols/iso15693_3/iso15693_3_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso15693_3;
|
||||
303
lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c
Normal file
303
lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c
Normal file
@@ -0,0 +1,303 @@
|
||||
#include "iso15693_3_poller_i.h"
|
||||
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#define TAG "Iso15693_3Poller"
|
||||
|
||||
#define BITS_IN_BYTE (8)
|
||||
|
||||
#define ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY (32U)
|
||||
|
||||
static Iso15693_3Error iso15693_3_poller_process_nfc_error(NfcError error) {
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
return Iso15693_3ErrorNone;
|
||||
case NfcErrorTimeout:
|
||||
return Iso15693_3ErrorTimeout;
|
||||
default:
|
||||
return Iso15693_3ErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_poller_filter_error(Iso15693_3Error error) {
|
||||
switch(error) {
|
||||
/* If a particular optional command is not supported, the card might
|
||||
* respond with a "Not supported" error or not respond at all.
|
||||
* Therefore, treat these errors as non-critical ones. */
|
||||
case Iso15693_3ErrorNotSupported:
|
||||
case Iso15693_3ErrorTimeout:
|
||||
return Iso15693_3ErrorNone;
|
||||
default:
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
if(instance->state == Iso15693_3PollerStateIdle) {
|
||||
return iso15693_3_poller_async_activate(instance, NULL);
|
||||
}
|
||||
|
||||
return Iso15693_3ErrorNone;
|
||||
}
|
||||
|
||||
static Iso15693_3Error iso15693_3_poller_frame_exchange(
|
||||
Iso15693_3Poller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
do {
|
||||
if(bit_buffer_get_size_bytes(tx_buffer) >
|
||||
bit_buffer_get_capacity_bytes(instance->tx_buffer) - ISO13239_CRC_SIZE) {
|
||||
ret = Iso15693_3ErrorBufferOverflow;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy(instance->tx_buffer, tx_buffer);
|
||||
iso13239_crc_append(Iso13239CrcTypeDefault, instance->tx_buffer);
|
||||
|
||||
NfcError error =
|
||||
nfc_poller_trx(instance->nfc, instance->tx_buffer, instance->rx_buffer, fwt);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = iso15693_3_poller_process_nfc_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!iso13239_crc_check(Iso13239CrcTypeDefault, instance->rx_buffer)) {
|
||||
ret = Iso15693_3ErrorWrongCrc;
|
||||
break;
|
||||
}
|
||||
|
||||
iso13239_crc_trim(instance->rx_buffer);
|
||||
bit_buffer_copy(rx_buffer, instance->rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
|
||||
iso15693_3_reset(data);
|
||||
|
||||
Iso15693_3Error ret;
|
||||
|
||||
do {
|
||||
instance->state = Iso15693_3PollerStateColResInProgress;
|
||||
|
||||
// Inventory: Mandatory command
|
||||
ret = iso15693_3_poller_async_inventory(instance, data->uid);
|
||||
if(ret != Iso15693_3ErrorNone) {
|
||||
instance->state = Iso15693_3PollerStateColResFailed;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = Iso15693_3PollerStateActivated;
|
||||
|
||||
// Get system info: Optional command
|
||||
Iso15693_3SystemInfo* system_info = &data->system_info;
|
||||
ret = iso15693_3_poller_async_get_system_info(instance, system_info);
|
||||
if(ret != Iso15693_3ErrorNone) {
|
||||
ret = iso15693_3_poller_filter_error(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
// Read blocks: Optional command
|
||||
simple_array_init(data->block_data, system_info->block_count * system_info->block_size);
|
||||
ret = iso15693_3_poller_async_read_blocks(
|
||||
instance,
|
||||
simple_array_get_data(data->block_data),
|
||||
system_info->block_count,
|
||||
system_info->block_size);
|
||||
if(ret != Iso15693_3ErrorNone) {
|
||||
ret = iso15693_3_poller_filter_error(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get block security status: Optional command
|
||||
simple_array_init(data->block_security, system_info->block_count);
|
||||
|
||||
ret = iso15693_3_poller_async_get_blocks_security(
|
||||
instance, simple_array_get_data(data->block_security), system_info->block_count);
|
||||
if(ret != Iso15693_3ErrorNone) {
|
||||
ret = iso15693_3_poller_filter_error(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
furi_assert(uid);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
// Send INVENTORY
|
||||
bit_buffer_append_byte(
|
||||
instance->tx_buffer,
|
||||
ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI |
|
||||
ISO15693_3_REQ_FLAG_INVENTORY_T5 | ISO15693_3_REQ_FLAG_T5_N_SLOTS_1);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_INVENTORY);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x00);
|
||||
|
||||
Iso15693_3Error ret;
|
||||
|
||||
do {
|
||||
ret = iso15693_3_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
|
||||
ret = iso15693_3_inventory_response_parse(uid, instance->rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_get_system_info(
|
||||
Iso15693_3Poller* instance,
|
||||
Iso15693_3SystemInfo* data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
// Send GET SYSTEM INFO
|
||||
bit_buffer_append_byte(
|
||||
instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI);
|
||||
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_GET_SYS_INFO);
|
||||
|
||||
Iso15693_3Error ret;
|
||||
|
||||
do {
|
||||
ret = iso15693_3_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
|
||||
ret = iso15693_3_system_info_response_parse(data, instance->rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_read_block(
|
||||
Iso15693_3Poller* instance,
|
||||
uint8_t* data,
|
||||
uint8_t block_number,
|
||||
uint8_t block_size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
bit_buffer_append_byte(
|
||||
instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI);
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_READ_BLOCK);
|
||||
bit_buffer_append_byte(instance->tx_buffer, block_number);
|
||||
|
||||
Iso15693_3Error ret;
|
||||
|
||||
do {
|
||||
ret = iso15693_3_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
|
||||
ret = iso15693_3_read_block_response_parse(data, block_size, instance->rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_read_blocks(
|
||||
Iso15693_3Poller* instance,
|
||||
uint8_t* data,
|
||||
uint16_t block_count,
|
||||
uint8_t block_size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
furi_assert(block_count);
|
||||
furi_assert(block_size);
|
||||
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
for(uint32_t i = 0; i < block_count; ++i) {
|
||||
ret = iso15693_3_poller_async_read_block(instance, &data[block_size * i], i, block_size);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_get_blocks_security(
|
||||
Iso15693_3Poller* instance,
|
||||
uint8_t* data,
|
||||
uint16_t block_count) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
|
||||
// Limit the number of blocks to 32 in a single query
|
||||
const uint32_t num_queries = block_count / ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY +
|
||||
(block_count % ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY ? 1 : 0);
|
||||
|
||||
Iso15693_3Error ret = Iso15693_3ErrorNone;
|
||||
|
||||
for(uint32_t i = 0; i < num_queries; ++i) {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
bit_buffer_append_byte(
|
||||
instance->tx_buffer,
|
||||
ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI);
|
||||
|
||||
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_GET_BLOCKS_SECURITY);
|
||||
|
||||
const uint8_t start_block_num = i * ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY;
|
||||
bit_buffer_append_byte(instance->tx_buffer, start_block_num);
|
||||
|
||||
const uint8_t block_count_per_query =
|
||||
MIN(block_count - start_block_num, (uint16_t)ISO15693_3_POLLER_NUM_BLOCKS_PER_QUERY);
|
||||
// Block count byte must be 1 less than the desired count
|
||||
bit_buffer_append_byte(instance->tx_buffer, block_count_per_query - 1);
|
||||
|
||||
ret = iso15693_3_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
|
||||
ret = iso15693_3_get_block_security_response_parse(
|
||||
&data[start_block_num], block_count_per_query, instance->rx_buffer);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_send_frame(
|
||||
Iso15693_3Poller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
Iso15693_3Error ret;
|
||||
|
||||
do {
|
||||
ret = iso15693_3_poller_prepare_trx(instance);
|
||||
if(ret != Iso15693_3ErrorNone) break;
|
||||
|
||||
ret = iso15693_3_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
70
lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h
Normal file
70
lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "iso15693_3_poller.h"
|
||||
|
||||
#include "iso15693_3_i.h"
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO15693_3_POLLER_MAX_BUFFER_SIZE (64U)
|
||||
|
||||
typedef enum {
|
||||
Iso15693_3PollerStateIdle,
|
||||
Iso15693_3PollerStateColResInProgress,
|
||||
Iso15693_3PollerStateColResFailed,
|
||||
Iso15693_3PollerStateActivated,
|
||||
} Iso15693_3PollerState;
|
||||
|
||||
struct Iso15693_3Poller {
|
||||
Nfc* nfc;
|
||||
Iso15693_3PollerState state;
|
||||
Iso15693_3Data* data;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
|
||||
NfcGenericEvent general_event;
|
||||
Iso15693_3PollerEvent iso15693_3_event;
|
||||
Iso15693_3PollerEventData iso15693_3_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance);
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_activate(Iso15693_3Poller* instance, Iso15693_3Data* data);
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_inventory(Iso15693_3Poller* instance, uint8_t* uid);
|
||||
|
||||
Iso15693_3Error
|
||||
iso15693_3_poller_async_get_system_info(Iso15693_3Poller* instance, Iso15693_3SystemInfo* data);
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_read_block(
|
||||
Iso15693_3Poller* instance,
|
||||
uint8_t* data,
|
||||
uint8_t block_number,
|
||||
uint8_t block_size);
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_read_blocks(
|
||||
Iso15693_3Poller* instance,
|
||||
uint8_t* data,
|
||||
uint16_t block_count,
|
||||
uint8_t block_size);
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_async_get_blocks_security(
|
||||
Iso15693_3Poller* instance,
|
||||
uint8_t* data,
|
||||
uint16_t block_count);
|
||||
|
||||
Iso15693_3Error iso15693_3_poller_send_frame(
|
||||
Iso15693_3Poller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
172
lib/nfc/protocols/mf_classic/crypto1.c
Normal file
172
lib/nfc/protocols/mf_classic/crypto1.c
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "crypto1.h"
|
||||
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
#include <furi.h>
|
||||
|
||||
// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
|
||||
|
||||
#define SWAPENDIAN(x) \
|
||||
((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
|
||||
#define LF_POLY_ODD (0x29CE5C)
|
||||
#define LF_POLY_EVEN (0x870804)
|
||||
|
||||
#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24)
|
||||
|
||||
Crypto1* crypto1_alloc() {
|
||||
Crypto1* instance = malloc(sizeof(Crypto1));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void crypto1_free(Crypto1* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void crypto1_reset(Crypto1* crypto1) {
|
||||
furi_assert(crypto1);
|
||||
crypto1->even = 0;
|
||||
crypto1->odd = 0;
|
||||
}
|
||||
|
||||
void crypto1_init(Crypto1* crypto1, uint64_t key) {
|
||||
furi_assert(crypto1);
|
||||
crypto1->even = 0;
|
||||
crypto1->odd = 0;
|
||||
for(int8_t i = 47; i > 0; i -= 2) {
|
||||
crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7);
|
||||
crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t crypto1_filter(uint32_t in) {
|
||||
uint32_t out = 0;
|
||||
out = 0xf22c0 >> (in & 0xf) & 16;
|
||||
out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8;
|
||||
out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4;
|
||||
out |= 0x1e458 >> (in >> 12 & 0xf) & 2;
|
||||
out |= 0x0d938 >> (in >> 16 & 0xf) & 1;
|
||||
return FURI_BIT(0xEC57E80A, out);
|
||||
}
|
||||
|
||||
uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint8_t out = crypto1_filter(crypto1->odd);
|
||||
uint32_t feed = out & (!!is_encrypted);
|
||||
feed ^= !!in;
|
||||
feed ^= LF_POLY_ODD & crypto1->odd;
|
||||
feed ^= LF_POLY_EVEN & crypto1->even;
|
||||
crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed));
|
||||
|
||||
FURI_SWAP(crypto1->odd, crypto1->even);
|
||||
return out;
|
||||
}
|
||||
|
||||
uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint8_t out = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
|
||||
furi_assert(crypto1);
|
||||
uint32_t out = 0;
|
||||
for(uint8_t i = 0; i < 32; i++) {
|
||||
out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n) {
|
||||
SWAPENDIAN(x);
|
||||
while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
|
||||
|
||||
return SWAPENDIAN(x);
|
||||
}
|
||||
|
||||
void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(buff);
|
||||
furi_assert(out);
|
||||
|
||||
size_t bits = bit_buffer_get_size(buff);
|
||||
bit_buffer_set_size(out, bits);
|
||||
const uint8_t* encrypted_data = bit_buffer_get_data(buff);
|
||||
if(bits < 8) {
|
||||
uint8_t decrypted_byte = 0;
|
||||
uint8_t encrypted_byte = encrypted_data[0];
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2;
|
||||
decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3;
|
||||
bit_buffer_set_byte(out, 0, decrypted_byte);
|
||||
} else {
|
||||
for(size_t i = 0; i < bits / 8; i++) {
|
||||
uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
|
||||
bit_buffer_set_byte(out, i, decrypted_byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(buff);
|
||||
furi_assert(out);
|
||||
|
||||
size_t bits = bit_buffer_get_size(buff);
|
||||
bit_buffer_set_size(out, bits);
|
||||
const uint8_t* plain_data = bit_buffer_get_data(buff);
|
||||
if(bits < 8) {
|
||||
uint8_t encrypted_byte = 0;
|
||||
for(size_t i = 0; i < bits; i++) {
|
||||
encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
|
||||
}
|
||||
bit_buffer_set_byte(out, 0, encrypted_byte);
|
||||
} else {
|
||||
for(size_t i = 0; i < bits / 8; i++) {
|
||||
uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
|
||||
plain_data[i];
|
||||
bool parity_bit =
|
||||
((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crypto1_encrypt_reader_nonce(
|
||||
Crypto1* crypto,
|
||||
uint64_t key,
|
||||
uint32_t cuid,
|
||||
uint8_t* nt,
|
||||
uint8_t* nr,
|
||||
BitBuffer* out) {
|
||||
furi_assert(crypto);
|
||||
furi_assert(nt);
|
||||
furi_assert(nr);
|
||||
furi_assert(out);
|
||||
|
||||
bit_buffer_set_size_bytes(out, 8);
|
||||
uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t));
|
||||
|
||||
crypto1_init(crypto, key);
|
||||
crypto1_word(crypto, nt_num ^ cuid, 0);
|
||||
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i];
|
||||
bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
|
||||
nr[i] = byte;
|
||||
}
|
||||
|
||||
nt_num = prng_successor(nt_num, 32);
|
||||
for(size_t i = 4; i < 8; i++) {
|
||||
nt_num = prng_successor(nt_num, 8);
|
||||
uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num);
|
||||
bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01);
|
||||
bit_buffer_set_byte_with_parity(out, i, byte, parity_bit);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -12,6 +11,10 @@ typedef struct {
|
||||
uint32_t even;
|
||||
} Crypto1;
|
||||
|
||||
Crypto1* crypto1_alloc();
|
||||
|
||||
void crypto1_free(Crypto1* instance);
|
||||
|
||||
void crypto1_reset(Crypto1* crypto1);
|
||||
|
||||
void crypto1_init(Crypto1* crypto1, uint64_t key);
|
||||
@@ -22,24 +25,20 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted);
|
||||
|
||||
uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
|
||||
|
||||
uint32_t crypto1_filter(uint32_t in);
|
||||
void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out);
|
||||
|
||||
void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out);
|
||||
|
||||
void crypto1_encrypt_reader_nonce(
|
||||
Crypto1* crypto,
|
||||
uint64_t key,
|
||||
uint32_t cuid,
|
||||
uint8_t* nt,
|
||||
uint8_t* nr,
|
||||
BitBuffer* out);
|
||||
|
||||
uint32_t prng_successor(uint32_t x, uint32_t n);
|
||||
|
||||
void crypto1_decrypt(
|
||||
Crypto1* crypto,
|
||||
uint8_t* encrypted_data,
|
||||
uint16_t encrypted_data_bits,
|
||||
uint8_t* decrypted_data);
|
||||
|
||||
void crypto1_encrypt(
|
||||
Crypto1* crypto,
|
||||
uint8_t* keystream,
|
||||
uint8_t* plain_data,
|
||||
uint16_t plain_data_bits,
|
||||
uint8_t* encrypted_data,
|
||||
uint8_t* encrypted_parity);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
740
lib/nfc/protocols/mf_classic/mf_classic.c
Normal file
740
lib/nfc/protocols/mf_classic/mf_classic.c
Normal file
@@ -0,0 +1,740 @@
|
||||
#include "mf_classic.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
#include <toolbox/hex.h>
|
||||
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
|
||||
#define MF_CLASSIC_PROTOCOL_NAME "Mifare Classic"
|
||||
|
||||
typedef struct {
|
||||
uint8_t sectors_total;
|
||||
uint16_t blocks_total;
|
||||
const char* full_name;
|
||||
const char* type_name;
|
||||
} MfClassicFeatures;
|
||||
|
||||
static const uint32_t mf_classic_data_format_version = 2;
|
||||
|
||||
static const MfClassicFeatures mf_classic_features[MfClassicTypeNum] = {
|
||||
[MfClassicTypeMini] =
|
||||
{
|
||||
.sectors_total = 5,
|
||||
.blocks_total = 20,
|
||||
.full_name = "Mifare Classic Mini 0.3K",
|
||||
.type_name = "MINI",
|
||||
},
|
||||
[MfClassicType1k] =
|
||||
{
|
||||
.sectors_total = 16,
|
||||
.blocks_total = 64,
|
||||
.full_name = "Mifare Classic 1K",
|
||||
.type_name = "1K",
|
||||
},
|
||||
[MfClassicType4k] =
|
||||
{
|
||||
.sectors_total = 40,
|
||||
.blocks_total = 256,
|
||||
.full_name = "Mifare Classic 4K",
|
||||
.type_name = "4K",
|
||||
},
|
||||
};
|
||||
|
||||
const NfcDeviceBase nfc_device_mf_classic = {
|
||||
.protocol_name = MF_CLASSIC_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)mf_classic_alloc,
|
||||
.free = (NfcDeviceFree)mf_classic_free,
|
||||
.reset = (NfcDeviceReset)mf_classic_reset,
|
||||
.copy = (NfcDeviceCopy)mf_classic_copy,
|
||||
.verify = (NfcDeviceVerify)mf_classic_verify,
|
||||
.load = (NfcDeviceLoad)mf_classic_load,
|
||||
.save = (NfcDeviceSave)mf_classic_save,
|
||||
.is_equal = (NfcDeviceEqual)mf_classic_is_equal,
|
||||
.get_name = (NfcDeviceGetName)mf_classic_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)mf_classic_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)mf_classic_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)mf_classic_get_base_data,
|
||||
};
|
||||
|
||||
MfClassicData* mf_classic_alloc() {
|
||||
MfClassicData* data = malloc(sizeof(MfClassicData));
|
||||
data->iso14443_3a_data = iso14443_3a_alloc();
|
||||
return data;
|
||||
}
|
||||
|
||||
void mf_classic_free(MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_free(data->iso14443_3a_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mf_classic_reset(MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_reset(data->iso14443_3a_data);
|
||||
}
|
||||
|
||||
void mf_classic_copy(MfClassicData* data, const MfClassicData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data);
|
||||
for(size_t i = 0; i < COUNT_OF(data->block); i++) {
|
||||
data->block[i] = other->block[i];
|
||||
}
|
||||
for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) {
|
||||
data->block_read_mask[i] = other->block_read_mask[i];
|
||||
}
|
||||
data->type = other->type;
|
||||
data->key_a_mask = other->key_a_mask;
|
||||
data->key_b_mask = other->key_b_mask;
|
||||
}
|
||||
|
||||
bool mf_classic_verify(MfClassicData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal_str(device_type, "Mifare Classic");
|
||||
}
|
||||
|
||||
static void mf_classic_parse_block(FuriString* block_str, MfClassicData* data, uint8_t block_num) {
|
||||
furi_string_trim(block_str);
|
||||
MfClassicBlock block_tmp = {};
|
||||
bool is_sector_trailer = mf_classic_is_sector_trailer(block_num);
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint16_t block_unknown_bytes_mask = 0;
|
||||
|
||||
furi_string_trim(block_str);
|
||||
for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
|
||||
char hi = furi_string_get_char(block_str, 3 * i);
|
||||
char low = furi_string_get_char(block_str, 3 * i + 1);
|
||||
uint8_t byte = 0;
|
||||
if(hex_char_to_uint8(hi, low, &byte)) {
|
||||
block_tmp.data[i] = byte;
|
||||
} else {
|
||||
FURI_BIT_SET(block_unknown_bytes_mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
if(block_unknown_bytes_mask != 0xffff) {
|
||||
if(is_sector_trailer) {
|
||||
MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp;
|
||||
// Load Key A
|
||||
// Key A mask 0b0000000000111111 = 0x003f
|
||||
if((block_unknown_bytes_mask & 0x003f) == 0) {
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a.data, sizeof(MfClassicKey));
|
||||
mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeA, key);
|
||||
}
|
||||
// Load Access Bits
|
||||
// Access bits mask 0b0000001111000000 = 0x03c0
|
||||
if((block_unknown_bytes_mask & 0x03c0) == 0) {
|
||||
mf_classic_set_block_read(data, block_num, &block_tmp);
|
||||
}
|
||||
// Load Key B
|
||||
// Key B mask 0b1111110000000000 = 0xfc00
|
||||
if((block_unknown_bytes_mask & 0xfc00) == 0) {
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b.data, sizeof(MfClassicKey));
|
||||
mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeB, key);
|
||||
}
|
||||
} else {
|
||||
if(block_unknown_bytes_mask == 0) {
|
||||
mf_classic_set_block_read(data, block_num, &block_tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Read ISO14443_3A data
|
||||
if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break;
|
||||
|
||||
// Read Mifare Classic type
|
||||
if(!flipper_format_read_string(ff, "Mifare Classic type", temp_str)) break;
|
||||
bool type_parsed = false;
|
||||
for(size_t i = 0; i < MfClassicTypeNum; i++) {
|
||||
if(furi_string_equal_str(temp_str, mf_classic_features[i].type_name)) {
|
||||
data->type = i;
|
||||
type_parsed = true;
|
||||
}
|
||||
}
|
||||
if(!type_parsed) break;
|
||||
|
||||
// Read format version
|
||||
uint32_t data_format_version = 0;
|
||||
bool old_format = false;
|
||||
// Read Mifare Classic format version
|
||||
if(!flipper_format_read_uint32(ff, "Data format version", &data_format_version, 1)) {
|
||||
// Load unread sectors with zero keys access for backward compatibility
|
||||
if(!flipper_format_rewind(ff)) break;
|
||||
old_format = true;
|
||||
} else {
|
||||
if(data_format_version < mf_classic_data_format_version) {
|
||||
old_format = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read Mifare Classic blocks
|
||||
bool block_read = true;
|
||||
FuriString* block_str = furi_string_alloc();
|
||||
uint16_t blocks_total = mf_classic_get_total_block_num(data->type);
|
||||
for(size_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_read_string(ff, furi_string_get_cstr(temp_str), block_str)) {
|
||||
block_read = false;
|
||||
break;
|
||||
}
|
||||
mf_classic_parse_block(block_str, data, i);
|
||||
}
|
||||
furi_string_free(block_str);
|
||||
if(!block_read) break;
|
||||
|
||||
// Set keys and blocks as unknown for backward compatibility
|
||||
if(old_format) {
|
||||
data->key_a_mask = 0ULL;
|
||||
data->key_b_mask = 0ULL;
|
||||
memset(data->block_read_mask, 0, sizeof(data->block_read_mask));
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static void
|
||||
mf_classic_set_block_str(FuriString* block_str, const MfClassicData* data, uint8_t block_num) {
|
||||
furi_string_reset(block_str);
|
||||
bool is_sec_trailer = mf_classic_is_sector_trailer(block_num);
|
||||
if(is_sec_trailer) {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
// Write key A
|
||||
for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) {
|
||||
if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a.data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
// Write Access bytes
|
||||
for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) {
|
||||
if(mf_classic_is_block_read(data, block_num)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits.data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
// Write key B
|
||||
for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) {
|
||||
if(mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b.data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Write data block
|
||||
for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
|
||||
if(mf_classic_is_block_read(data, block_num)) {
|
||||
furi_string_cat_printf(block_str, "%02X ", data->block[block_num].data[i]);
|
||||
} else {
|
||||
furi_string_cat_printf(block_str, "?? ");
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_string_trim(block_str);
|
||||
}
|
||||
|
||||
bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(ff, "Mifare Classic specific data")) break;
|
||||
if(!flipper_format_write_string_cstr(
|
||||
ff, "Mifare Classic type", mf_classic_features[data->type].type_name))
|
||||
break;
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, "Data format version", &mf_classic_data_format_version, 1))
|
||||
break;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
ff, "Mifare Classic blocks, \'??\' means unknown data"))
|
||||
break;
|
||||
|
||||
uint16_t blocks_total = mf_classic_get_total_block_num(data->type);
|
||||
FuriString* block_str = furi_string_alloc();
|
||||
bool block_saved = true;
|
||||
for(size_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
mf_classic_set_block_str(block_str, data, i);
|
||||
if(!flipper_format_write_string(ff, furi_string_get_cstr(temp_str), block_str)) {
|
||||
block_saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(block_str);
|
||||
if(!block_saved) break;
|
||||
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other) {
|
||||
bool is_equal = false;
|
||||
bool data_array_is_equal = true;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break;
|
||||
if(data->type != other->type) break;
|
||||
if(data->key_a_mask != other->key_a_mask) break;
|
||||
if(data->key_b_mask != other->key_b_mask) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->block_read_mask); i++) {
|
||||
if(data->block_read_mask[i] != other->block_read_mask[i]) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->block); i++) {
|
||||
if(memcmp(&data->block[i], &other->block[i], sizeof(data->block[i])) != 0) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
is_equal = true;
|
||||
} while(false);
|
||||
|
||||
return is_equal;
|
||||
}
|
||||
|
||||
const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type) {
|
||||
furi_assert(data);
|
||||
furi_assert(data->type < MfClassicTypeNum);
|
||||
|
||||
if(name_type == NfcDeviceNameTypeFull) {
|
||||
return mf_classic_features[data->type].full_name;
|
||||
} else {
|
||||
return mf_classic_features[data->type].type_name;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len);
|
||||
}
|
||||
|
||||
bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len);
|
||||
}
|
||||
|
||||
Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->iso14443_3a_data;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
return mf_classic_features[type].sectors_total;
|
||||
}
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type) {
|
||||
return mf_classic_features[type].blocks_total;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector) {
|
||||
uint8_t block_num = 0;
|
||||
|
||||
if(sector < 32) {
|
||||
block_num = sector * 4 + 3;
|
||||
} else if(sector < 40) {
|
||||
block_num = 32 * 4 + (sector - 32) * 16 + 15;
|
||||
} else {
|
||||
furi_crash("Wrong sector num");
|
||||
}
|
||||
|
||||
return block_num;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) {
|
||||
uint8_t sec_tr_block_num = 0;
|
||||
|
||||
if(block < 128) {
|
||||
sec_tr_block_num = block | 0x03;
|
||||
} else {
|
||||
sec_tr_block_num = block | 0x0f;
|
||||
}
|
||||
|
||||
return sec_tr_block_num;
|
||||
}
|
||||
|
||||
MfClassicSectorTrailer*
|
||||
mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num) {
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t sec_tr_block = mf_classic_get_sector_trailer_num_by_sector(sector_num);
|
||||
MfClassicSectorTrailer* sec_trailer = (MfClassicSectorTrailer*)&data->block[sec_tr_block];
|
||||
|
||||
return sec_trailer;
|
||||
}
|
||||
|
||||
bool mf_classic_is_sector_trailer(uint8_t block) {
|
||||
return block == mf_classic_get_sector_trailer_num_by_block(block);
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_sector_by_block(uint8_t block) {
|
||||
uint8_t sector = 0;
|
||||
|
||||
if(block < 128) {
|
||||
sector = (block | 0x03) / 4;
|
||||
} else {
|
||||
sector = 32 + ((block | 0x0f) - 32 * 4) / 16;
|
||||
}
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr) {
|
||||
furi_assert(block);
|
||||
|
||||
uint32_t v = *(uint32_t*)&block->data[0];
|
||||
uint32_t v_inv = *(uint32_t*)&block->data[sizeof(uint32_t)];
|
||||
uint32_t v1 = *(uint32_t*)&block->data[sizeof(uint32_t) * 2];
|
||||
|
||||
bool val_checks =
|
||||
((v == v1) && (v == ~v_inv) && (block->data[12] == (~block->data[13] & 0xFF)) &&
|
||||
(block->data[14] == (~block->data[15] & 0xFF)) && (block->data[12] == block->data[14]));
|
||||
if(value) {
|
||||
*value = (int32_t)v;
|
||||
}
|
||||
if(addr) {
|
||||
*addr = block->data[12];
|
||||
}
|
||||
return val_checks;
|
||||
}
|
||||
|
||||
void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block) {
|
||||
furi_assert(block);
|
||||
|
||||
uint32_t v_inv = ~((uint32_t)value);
|
||||
|
||||
memcpy(&block->data[0], &value, 4); //-V1086
|
||||
memcpy(&block->data[4], &v_inv, 4); //-V1086
|
||||
memcpy(&block->data[8], &value, 4); //-V1086
|
||||
|
||||
block->data[12] = addr;
|
||||
block->data[13] = ~addr & 0xFF;
|
||||
block->data[14] = addr;
|
||||
block->data[15] = ~addr & 0xFF;
|
||||
}
|
||||
|
||||
bool mf_classic_is_key_found(
|
||||
const MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type) {
|
||||
furi_assert(data);
|
||||
|
||||
bool key_found = false;
|
||||
if(key_type == MfClassicKeyTypeA) {
|
||||
key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1);
|
||||
} else if(key_type == MfClassicKeyTypeB) {
|
||||
key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1);
|
||||
}
|
||||
|
||||
return key_found;
|
||||
}
|
||||
|
||||
void mf_classic_set_key_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type,
|
||||
uint64_t key) {
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t key_arr[6] = {};
|
||||
MfClassicSectorTrailer* sec_trailer =
|
||||
mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
nfc_util_num2bytes(key, 6, key_arr);
|
||||
if(key_type == MfClassicKeyTypeA) {
|
||||
memcpy(sec_trailer->key_a.data, key_arr, sizeof(MfClassicKey));
|
||||
FURI_BIT_SET(data->key_a_mask, sector_num);
|
||||
} else if(key_type == MfClassicKeyTypeB) {
|
||||
memcpy(sec_trailer->key_b.data, key_arr, sizeof(MfClassicKey));
|
||||
FURI_BIT_SET(data->key_b_mask, sector_num);
|
||||
}
|
||||
}
|
||||
|
||||
void mf_classic_set_key_not_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type) {
|
||||
furi_assert(data);
|
||||
|
||||
if(key_type == MfClassicKeyTypeA) {
|
||||
FURI_BIT_CLEAR(data->key_a_mask, sector_num);
|
||||
} else if(key_type == MfClassicKeyTypeB) {
|
||||
FURI_BIT_CLEAR(data->key_b_mask, sector_num);
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num) {
|
||||
furi_assert(data);
|
||||
|
||||
return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1);
|
||||
}
|
||||
|
||||
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) {
|
||||
furi_assert(data);
|
||||
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
memcpy(&data->block[block_num].data[6], &block_data->data[6], 4);
|
||||
} else {
|
||||
memcpy(data->block[block_num].data, block_data->data, MF_CLASSIC_BLOCK_SIZE);
|
||||
}
|
||||
FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
|
||||
furi_assert(sector < 40);
|
||||
|
||||
uint8_t block = 0;
|
||||
if(sector < 32) {
|
||||
block = sector * 4;
|
||||
} else {
|
||||
block = 32 * 4 + (sector - 32) * 16;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
|
||||
furi_assert(sector < 40);
|
||||
return sector < 32 ? 4 : 16;
|
||||
}
|
||||
|
||||
void mf_classic_get_read_sectors_and_keys(
|
||||
const MfClassicData* data,
|
||||
uint8_t* sectors_read,
|
||||
uint8_t* keys_found) {
|
||||
furi_assert(data);
|
||||
furi_assert(sectors_read);
|
||||
furi_assert(keys_found);
|
||||
|
||||
*sectors_read = 0;
|
||||
*keys_found = 0;
|
||||
uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
|
||||
for(size_t i = 0; i < sectors_total; i++) {
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyTypeA)) {
|
||||
*keys_found += 1;
|
||||
}
|
||||
if(mf_classic_is_key_found(data, i, MfClassicKeyTypeB)) {
|
||||
*keys_found += 1;
|
||||
}
|
||||
uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
|
||||
uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i);
|
||||
bool blocks_read = true;
|
||||
for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) {
|
||||
blocks_read = mf_classic_is_block_read(data, j);
|
||||
if(!blocks_read) break;
|
||||
}
|
||||
if(blocks_read) {
|
||||
*sectors_read += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mf_classic_is_card_read(const MfClassicData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found);
|
||||
bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2);
|
||||
|
||||
return card_read;
|
||||
}
|
||||
|
||||
bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num) {
|
||||
furi_assert(data);
|
||||
|
||||
bool sector_read = false;
|
||||
do {
|
||||
if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeA)) break;
|
||||
if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyTypeB)) break;
|
||||
uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num);
|
||||
uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num);
|
||||
uint8_t block_read = true;
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
block_read = mf_classic_is_block_read(data, i);
|
||||
if(!block_read) break;
|
||||
}
|
||||
sector_read = block_read;
|
||||
} while(false);
|
||||
|
||||
return sector_read;
|
||||
}
|
||||
|
||||
static bool mf_classic_is_allowed_access_sector_trailer(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action) {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
uint8_t* access_bits_arr = sec_tr->access_bits.data;
|
||||
uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) |
|
||||
((access_bits_arr[2] >> 7) & 0x01);
|
||||
|
||||
switch(action) {
|
||||
case MfClassicActionKeyARead: {
|
||||
return false;
|
||||
}
|
||||
case MfClassicActionKeyAWrite:
|
||||
case MfClassicActionKeyBWrite: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x04 || AC == 0x03)));
|
||||
}
|
||||
case MfClassicActionKeyBRead: {
|
||||
return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01));
|
||||
}
|
||||
case MfClassicActionACRead: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA) ||
|
||||
(key_type == MfClassicKeyTypeB && !(AC == 0x00 || AC == 0x02 || AC == 0x01)));
|
||||
}
|
||||
case MfClassicActionACWrite: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x01)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x03 || AC == 0x05)));
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mf_classic_is_allowed_access_data_block(
|
||||
MfClassicSectorTrailer* sec_tr,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action) {
|
||||
furi_assert(sec_tr);
|
||||
|
||||
uint8_t* access_bits_arr = sec_tr->access_bits.data;
|
||||
|
||||
if(block_num == 0 && action == MfClassicActionDataWrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector_block = 0;
|
||||
if(block_num <= 128) {
|
||||
sector_block = block_num & 0x03;
|
||||
} else {
|
||||
sector_block = (block_num & 0x0f) / 5;
|
||||
}
|
||||
|
||||
uint8_t AC;
|
||||
switch(sector_block) {
|
||||
case 0x00: {
|
||||
AC = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) |
|
||||
((access_bits_arr[2] >> 4) & 0x01);
|
||||
break;
|
||||
}
|
||||
case 0x01: {
|
||||
AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) |
|
||||
((access_bits_arr[2] >> 5) & 0x01);
|
||||
break;
|
||||
}
|
||||
case 0x02: {
|
||||
AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) |
|
||||
((access_bits_arr[2] >> 6) & 0x01);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(action) {
|
||||
case MfClassicActionDataRead: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) ||
|
||||
(key_type == MfClassicKeyTypeB && !(AC == 0x07)));
|
||||
}
|
||||
case MfClassicActionDataWrite: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00)) ||
|
||||
(key_type == MfClassicKeyTypeB &&
|
||||
(AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03)));
|
||||
}
|
||||
case MfClassicActionDataInc: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06)));
|
||||
}
|
||||
case MfClassicActionDataDec: {
|
||||
return (
|
||||
(key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) ||
|
||||
(key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01)));
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mf_classic_is_allowed_access(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action) {
|
||||
furi_assert(data);
|
||||
|
||||
bool access_allowed = false;
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
access_allowed =
|
||||
mf_classic_is_allowed_access_sector_trailer(data, block_num, key_type, action);
|
||||
} else {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
access_allowed =
|
||||
mf_classic_is_allowed_access_data_block(sec_tr, block_num, key_type, action);
|
||||
}
|
||||
|
||||
return access_allowed;
|
||||
}
|
||||
|
||||
bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num) {
|
||||
furi_assert(sec_tr);
|
||||
|
||||
// Check if key A can write, if it can, it's transport configuration, not data block
|
||||
return !mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) &&
|
||||
(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataInc) ||
|
||||
mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, block_num, MfClassicKeyTypeB, MfClassicActionDataDec));
|
||||
}
|
||||
235
lib/nfc/protocols/mf_classic/mf_classic.h
Normal file
235
lib/nfc/protocols/mf_classic/mf_classic.h
Normal file
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U)
|
||||
#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U)
|
||||
#define MF_CLASSIC_CMD_READ_BLOCK (0x30U)
|
||||
#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U)
|
||||
#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U)
|
||||
#define MF_CLASSIC_CMD_VALUE_INC (0xC1U)
|
||||
#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U)
|
||||
#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U)
|
||||
|
||||
#define MF_CLASSIC_CMD_HALT_MSB (0x50)
|
||||
#define MF_CLASSIC_CMD_HALT_LSB (0x00)
|
||||
#define MF_CLASSIC_CMD_ACK (0x0A)
|
||||
#define MF_CLASSIC_CMD_NACK (0x00)
|
||||
|
||||
#define MF_CLASSIC_TOTAL_SECTORS_MAX (40)
|
||||
#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256)
|
||||
#define MF_CLASSIC_READ_MASK_SIZE (MF_CLASSIC_TOTAL_BLOCKS_MAX / 32)
|
||||
#define MF_CLASSIC_BLOCK_SIZE (16)
|
||||
#define MF_CLASSIC_KEY_SIZE (6)
|
||||
#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
|
||||
|
||||
#define MF_CLASSIC_NT_SIZE (4)
|
||||
#define MF_CLASSIC_NR_SIZE (4)
|
||||
#define MF_CLASSIC_AR_SIZE (4)
|
||||
#define MF_CLASSIC_AT_SIZE (4)
|
||||
|
||||
typedef enum {
|
||||
MfClassicErrorNone,
|
||||
MfClassicErrorNotPresent,
|
||||
MfClassicErrorProtocol,
|
||||
MfClassicErrorAuth,
|
||||
MfClassicErrorTimeout,
|
||||
} MfClassicError;
|
||||
|
||||
typedef enum {
|
||||
MfClassicTypeMini,
|
||||
MfClassicType1k,
|
||||
MfClassicType4k,
|
||||
|
||||
MfClassicTypeNum,
|
||||
} MfClassicType;
|
||||
|
||||
typedef enum {
|
||||
MfClassicActionDataRead,
|
||||
MfClassicActionDataWrite,
|
||||
MfClassicActionDataInc,
|
||||
MfClassicActionDataDec,
|
||||
|
||||
MfClassicActionKeyARead,
|
||||
MfClassicActionKeyAWrite,
|
||||
MfClassicActionKeyBRead,
|
||||
MfClassicActionKeyBWrite,
|
||||
MfClassicActionACRead,
|
||||
MfClassicActionACWrite,
|
||||
} MfClassicAction;
|
||||
|
||||
typedef enum {
|
||||
MfClassicValueCommandIncrement,
|
||||
MfClassicValueCommandDecrement,
|
||||
MfClassicValueCommandRestore,
|
||||
|
||||
MfClassicValueCommandInvalid,
|
||||
} MfClassicValueCommand;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_BLOCK_SIZE];
|
||||
} MfClassicBlock;
|
||||
|
||||
typedef enum {
|
||||
MfClassicKeyTypeA,
|
||||
MfClassicKeyTypeB,
|
||||
} MfClassicKeyType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_KEY_SIZE];
|
||||
} MfClassicKey;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_ACCESS_BYTES_SIZE];
|
||||
} MfClassicAccessBits;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_NT_SIZE];
|
||||
} MfClassicNt;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_AT_SIZE];
|
||||
} MfClassicAt;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_NR_SIZE];
|
||||
} MfClassicNr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_CLASSIC_AR_SIZE];
|
||||
} MfClassicAr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicNt nt;
|
||||
MfClassicNr nr;
|
||||
MfClassicAr ar;
|
||||
MfClassicAt at;
|
||||
} MfClassicAuthContext;
|
||||
|
||||
typedef union {
|
||||
MfClassicBlock block;
|
||||
struct {
|
||||
MfClassicKey key_a;
|
||||
MfClassicAccessBits access_bits;
|
||||
MfClassicKey key_b;
|
||||
};
|
||||
} MfClassicSectorTrailer;
|
||||
|
||||
typedef struct {
|
||||
uint64_t key_a_mask;
|
||||
MfClassicKey key_a[MF_CLASSIC_TOTAL_SECTORS_MAX];
|
||||
uint64_t key_b_mask;
|
||||
MfClassicKey key_b[MF_CLASSIC_TOTAL_SECTORS_MAX];
|
||||
} MfClassicDeviceKeys;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_3aData* iso14443_3a_data;
|
||||
MfClassicType type;
|
||||
uint32_t block_read_mask[MF_CLASSIC_READ_MASK_SIZE];
|
||||
uint64_t key_a_mask;
|
||||
uint64_t key_b_mask;
|
||||
MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
|
||||
} MfClassicData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_mf_classic;
|
||||
|
||||
MfClassicData* mf_classic_alloc();
|
||||
|
||||
void mf_classic_free(MfClassicData* data);
|
||||
|
||||
void mf_classic_reset(MfClassicData* data);
|
||||
|
||||
void mf_classic_copy(MfClassicData* data, const MfClassicData* other);
|
||||
|
||||
bool mf_classic_verify(MfClassicData* data, const FuriString* device_type);
|
||||
|
||||
bool mf_classic_load(MfClassicData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool mf_classic_save(const MfClassicData* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_classic_is_equal(const MfClassicData* data, const MfClassicData* other);
|
||||
|
||||
const char* mf_classic_get_device_name(const MfClassicData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len);
|
||||
|
||||
bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data);
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type);
|
||||
|
||||
uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector);
|
||||
|
||||
uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector);
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_sector(uint8_t sector);
|
||||
|
||||
uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block);
|
||||
|
||||
MfClassicSectorTrailer*
|
||||
mf_classic_get_sector_trailer_by_sector(const MfClassicData* data, uint8_t sector_num);
|
||||
|
||||
bool mf_classic_is_sector_trailer(uint8_t block);
|
||||
|
||||
uint8_t mf_classic_get_sector_by_block(uint8_t block);
|
||||
|
||||
bool mf_classic_block_to_value(const MfClassicBlock* block, int32_t* value, uint8_t* addr);
|
||||
|
||||
void mf_classic_value_to_block(int32_t value, uint8_t addr, MfClassicBlock* block);
|
||||
|
||||
bool mf_classic_is_key_found(
|
||||
const MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type);
|
||||
|
||||
void mf_classic_set_key_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type,
|
||||
uint64_t key);
|
||||
|
||||
void mf_classic_set_key_not_found(
|
||||
MfClassicData* data,
|
||||
uint8_t sector_num,
|
||||
MfClassicKeyType key_type);
|
||||
|
||||
bool mf_classic_is_block_read(const MfClassicData* data, uint8_t block_num);
|
||||
|
||||
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
|
||||
|
||||
bool mf_classic_is_sector_read(const MfClassicData* data, uint8_t sector_num);
|
||||
|
||||
void mf_classic_get_read_sectors_and_keys(
|
||||
const MfClassicData* data,
|
||||
uint8_t* sectors_read,
|
||||
uint8_t* keys_found);
|
||||
|
||||
bool mf_classic_is_card_read(const MfClassicData* data);
|
||||
|
||||
bool mf_classic_is_value_block(MfClassicSectorTrailer* sec_tr, uint8_t block_num);
|
||||
|
||||
bool mf_classic_is_allowed_access_data_block(
|
||||
MfClassicSectorTrailer* sec_tr,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action);
|
||||
|
||||
bool mf_classic_is_allowed_access(
|
||||
MfClassicData* data,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAction action);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
656
lib/nfc/protocols/mf_classic/mf_classic_listener.c
Normal file
656
lib/nfc/protocols/mf_classic/mf_classic_listener.c
Normal file
@@ -0,0 +1,656 @@
|
||||
#include "mf_classic_listener_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "MfClassicListener"
|
||||
|
||||
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
||||
|
||||
typedef MfClassicListenerCommand (
|
||||
*MfClassicListenerCommandHandler)(MfClassicListener* instance, BitBuffer* buf);
|
||||
|
||||
typedef struct {
|
||||
uint8_t cmd_start_byte;
|
||||
size_t cmd_len_bits;
|
||||
size_t command_num;
|
||||
MfClassicListenerCommandHandler* handler;
|
||||
} MfClassicListenerCmd;
|
||||
|
||||
static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) {
|
||||
instance->total_block_num = mf_classic_get_total_block_num(instance->data->type);
|
||||
}
|
||||
|
||||
static void mf_classic_listener_reset_state(MfClassicListener* instance) {
|
||||
crypto1_reset(instance->crypto);
|
||||
memset(&instance->auth_context, 0, sizeof(MfClassicAuthContext));
|
||||
instance->comm_state = MfClassicListenerCommStatePlain;
|
||||
instance->state = MfClassicListenerStateIdle;
|
||||
instance->cmd_in_progress = false;
|
||||
instance->current_cmd_handler_idx = 0;
|
||||
instance->transfer_value = 0;
|
||||
instance->value_cmd = MfClassicValueCommandInvalid;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_halt_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
|
||||
if(bit_buffer_get_byte(buff, 1) == MF_CLASSIC_CMD_HALT_LSB) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
command = MfClassicListenerCommandSleep;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand mf_classic_listener_auth_first_part_handler(
|
||||
MfClassicListener* instance,
|
||||
MfClassicKeyType key_type,
|
||||
uint8_t block_num) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
|
||||
do {
|
||||
instance->state = MfClassicListenerStateIdle;
|
||||
|
||||
if(block_num >= instance->total_block_num) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
|
||||
MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(instance->data, sector_num);
|
||||
MfClassicKey* key = (key_type == MfClassicKeyTypeA) ? &sec_tr->key_a : &sec_tr->key_b;
|
||||
uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey));
|
||||
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
||||
|
||||
instance->auth_context.key_type = key_type;
|
||||
instance->auth_context.block_num = block_num;
|
||||
|
||||
furi_hal_random_fill_buf(instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
|
||||
crypto1_init(instance->crypto, key_num);
|
||||
if(instance->comm_state == MfClassicListenerCommStatePlain) {
|
||||
crypto1_word(instance->crypto, nt_num ^ cuid, 0);
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->auth_context.nt.data,
|
||||
sizeof(MfClassicNt));
|
||||
iso14443_3a_listener_tx(instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
} else {
|
||||
uint8_t key_stream[4] = {};
|
||||
nfc_util_num2bytes(nt_num ^ cuid, sizeof(uint32_t), key_stream);
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_plain_buffer, instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
crypto1_encrypt(
|
||||
instance->crypto,
|
||||
key_stream,
|
||||
instance->tx_plain_buffer,
|
||||
instance->tx_encrypted_buffer);
|
||||
|
||||
iso14443_3a_listener_tx_with_custom_parity(
|
||||
instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
}
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_auth_key_a_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler(
|
||||
instance, MfClassicKeyTypeA, bit_buffer_get_byte(buff, 1));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_auth_key_b_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = mf_classic_listener_auth_first_part_handler(
|
||||
instance, MfClassicKeyTypeB, bit_buffer_get_byte(buff, 1));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_auth_second_part_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandSilent;
|
||||
|
||||
do {
|
||||
instance->cmd_in_progress = false;
|
||||
|
||||
if(bit_buffer_get_size_bytes(buff) != (sizeof(MfClassicNr) + sizeof(MfClassicAr))) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes_mid(buff, instance->auth_context.nr.data, 0, sizeof(MfClassicNr));
|
||||
bit_buffer_write_bytes_mid(
|
||||
buff, instance->auth_context.ar.data, sizeof(MfClassicNr), sizeof(MfClassicAr));
|
||||
|
||||
if(instance->callback) {
|
||||
instance->mfc_event.type = MfClassicListenerEventTypeAuthContextPartCollected,
|
||||
instance->mfc_event_data.auth_context = instance->auth_context;
|
||||
instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
|
||||
uint32_t nr_num = nfc_util_bytes2num(instance->auth_context.nr.data, sizeof(MfClassicNr));
|
||||
uint32_t ar_num = nfc_util_bytes2num(instance->auth_context.ar.data, sizeof(MfClassicAr));
|
||||
|
||||
crypto1_word(instance->crypto, nr_num, 1);
|
||||
uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt));
|
||||
uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0);
|
||||
if(secret_poller != prng_successor(nt_num, 64)) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64));
|
||||
mf_classic_listener_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t at_num = prng_successor(nt_num, 96);
|
||||
nfc_util_num2bytes(at_num, sizeof(uint32_t), instance->auth_context.at.data);
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr));
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
iso14443_3a_listener_tx_with_custom_parity(
|
||||
instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
|
||||
instance->state = MfClassicListenerStateAuthComplete;
|
||||
instance->comm_state = MfClassicListenerCommStateEncrypted;
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
|
||||
if(instance->callback) {
|
||||
instance->mfc_event.type = MfClassicListenerEventTypeAuthContextFullCollected,
|
||||
instance->mfc_event_data.auth_context = instance->auth_context;
|
||||
instance->callback(instance->generic_event, instance->context);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_read_block_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
if(instance->state != MfClassicListenerStateAuthComplete) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num);
|
||||
if(sector_num != auth_sector_num) break;
|
||||
|
||||
MfClassicBlock access_block = instance->data->block[block_num];
|
||||
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
MfClassicSectorTrailer* access_sec_tr = (MfClassicSectorTrailer*)&access_block;
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyARead)) {
|
||||
memset(access_sec_tr->key_a.data, 0, sizeof(MfClassicKey));
|
||||
}
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionKeyBRead)) {
|
||||
memset(access_sec_tr->key_b.data, 0, sizeof(MfClassicKey));
|
||||
}
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionACRead)) {
|
||||
memset(access_sec_tr->access_bits.data, 0, sizeof(MfClassicAccessBits));
|
||||
}
|
||||
} else if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionDataRead)) {
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_plain_buffer, access_block.data, sizeof(MfClassicBlock));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
iso14443_3a_listener_tx_with_custom_parity(
|
||||
instance->iso14443_3a_listener, instance->tx_encrypted_buffer);
|
||||
command = MfClassicListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand mf_classic_listener_write_block_first_part_handler(
|
||||
MfClassicListener* instance,
|
||||
BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
if(instance->state != MfClassicListenerStateAuthComplete) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
if(block_num >= instance->total_block_num) break;
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num);
|
||||
if(sector_num != auth_sector_num) break;
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand mf_classic_listener_write_block_second_part_handler(
|
||||
MfClassicListener* instance,
|
||||
BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
instance->cmd_in_progress = false;
|
||||
|
||||
size_t buff_size = bit_buffer_get_size_bytes(buff);
|
||||
if(buff_size != sizeof(MfClassicBlock)) break;
|
||||
|
||||
uint8_t block_num = auth_ctx->block_num;
|
||||
MfClassicKeyType key_type = auth_ctx->key_type;
|
||||
MfClassicBlock block = instance->data->block[block_num];
|
||||
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)█
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionKeyAWrite)) {
|
||||
bit_buffer_write_bytes_mid(buff, sec_tr->key_a.data, 0, sizeof(MfClassicKey));
|
||||
}
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionKeyBWrite)) {
|
||||
bit_buffer_write_bytes_mid(buff, sec_tr->key_b.data, 10, sizeof(MfClassicKey));
|
||||
}
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionACWrite)) {
|
||||
bit_buffer_write_bytes_mid(
|
||||
buff, sec_tr->access_bits.data, 6, sizeof(MfClassicAccessBits));
|
||||
}
|
||||
} else {
|
||||
if(mf_classic_is_allowed_access(
|
||||
instance->data, block_num, key_type, MfClassicActionDataWrite)) {
|
||||
bit_buffer_write_bytes_mid(buff, block.data, 0, sizeof(MfClassicBlock));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance->data->block[block_num] = block;
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_cmd_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
if(instance->state != MfClassicListenerStateAuthComplete) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
if(block_num >= instance->total_block_num) break;
|
||||
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
uint8_t auth_sector_num = mf_classic_get_sector_by_block(auth_ctx->block_num);
|
||||
if(sector_num != auth_sector_num) break;
|
||||
|
||||
uint8_t cmd = bit_buffer_get_byte(buff, 0);
|
||||
MfClassicAction action = MfClassicActionDataDec;
|
||||
if(cmd == MF_CLASSIC_CMD_VALUE_DEC) {
|
||||
instance->value_cmd = MfClassicValueCommandDecrement;
|
||||
} else if(cmd == MF_CLASSIC_CMD_VALUE_INC) {
|
||||
instance->value_cmd = MfClassicValueCommandIncrement;
|
||||
action = MfClassicActionDataInc;
|
||||
} else if(cmd == MF_CLASSIC_CMD_VALUE_RESTORE) {
|
||||
instance->value_cmd = MfClassicValueCommandRestore;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mf_classic_is_allowed_access(instance->data, block_num, auth_ctx->key_type, action)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mf_classic_block_to_value(
|
||||
&instance->data->block[block_num], &instance->transfer_value, NULL)) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_dec_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
return mf_classic_listener_value_cmd_handler(instance, buff);
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_inc_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
return mf_classic_listener_value_cmd_handler(instance, buff);
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_restore_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
return mf_classic_listener_value_cmd_handler(instance, buff);
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_data_receive_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
|
||||
do {
|
||||
if(bit_buffer_get_size_bytes(buff) != 4) break;
|
||||
|
||||
int32_t data;
|
||||
bit_buffer_write_bytes_mid(buff, &data, 0, sizeof(data));
|
||||
|
||||
if(data < 0) {
|
||||
data = -data;
|
||||
}
|
||||
|
||||
if(instance->value_cmd == MfClassicValueCommandDecrement) {
|
||||
data = -data;
|
||||
} else if(instance->value_cmd == MfClassicValueCommandRestore) {
|
||||
data = 0;
|
||||
}
|
||||
|
||||
instance->transfer_value += data;
|
||||
|
||||
instance->cmd_in_progress = true;
|
||||
instance->current_cmd_handler_idx++;
|
||||
command = MfClassicListenerCommandSilent;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommand
|
||||
mf_classic_listener_value_transfer_handler(MfClassicListener* instance, BitBuffer* buff) {
|
||||
MfClassicListenerCommand command = MfClassicListenerCommandNack;
|
||||
MfClassicAuthContext* auth_ctx = &instance->auth_context;
|
||||
|
||||
do {
|
||||
instance->cmd_in_progress = false;
|
||||
|
||||
if(bit_buffer_get_size_bytes(buff) != 2) break;
|
||||
if(bit_buffer_get_byte(buff, 0) != MF_CLASSIC_CMD_VALUE_TRANSFER) break;
|
||||
|
||||
uint8_t block_num = bit_buffer_get_byte(buff, 1);
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, auth_ctx->key_type, MfClassicActionDataDec)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mf_classic_value_to_block(
|
||||
instance->transfer_value, block_num, &instance->data->block[block_num]);
|
||||
instance->transfer_value = 0;
|
||||
|
||||
command = MfClassicListenerCommandAck;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = {
|
||||
mf_classic_listener_halt_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = {
|
||||
mf_classic_listener_auth_key_a_handler,
|
||||
mf_classic_listener_auth_second_part_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = {
|
||||
mf_classic_listener_auth_key_b_handler,
|
||||
mf_classic_listener_auth_second_part_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = {
|
||||
mf_classic_listener_read_block_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = {
|
||||
mf_classic_listener_write_block_first_part_handler,
|
||||
mf_classic_listener_write_block_second_part_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = {
|
||||
mf_classic_listener_value_dec_handler,
|
||||
mf_classic_listener_value_data_receive_handler,
|
||||
mf_classic_listener_value_transfer_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = {
|
||||
mf_classic_listener_value_inc_handler,
|
||||
mf_classic_listener_value_data_receive_handler,
|
||||
mf_classic_listener_value_transfer_handler,
|
||||
};
|
||||
|
||||
static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = {
|
||||
mf_classic_listener_value_restore_handler,
|
||||
mf_classic_listener_value_data_receive_handler,
|
||||
mf_classic_listener_value_transfer_handler,
|
||||
};
|
||||
|
||||
static const MfClassicListenerCmd mf_classic_listener_cmd_handlers[] = {
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_HALT_MSB,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_halt_handlers),
|
||||
.handler = mf_classic_listener_halt_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_A,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_auth_key_a_handlers),
|
||||
.handler = mf_classic_listener_auth_key_a_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_AUTH_KEY_B,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_auth_key_b_handlers),
|
||||
.handler = mf_classic_listener_auth_key_b_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_READ_BLOCK,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_read_block_handlers),
|
||||
.handler = mf_classic_listener_read_block_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_WRITE_BLOCK,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_write_block_handlers),
|
||||
.handler = mf_classic_listener_write_block_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_VALUE_DEC,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_value_dec_handlers),
|
||||
.handler = mf_classic_listener_value_dec_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_VALUE_INC,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_value_inc_handlers),
|
||||
.handler = mf_classic_listener_value_inc_handlers,
|
||||
},
|
||||
{
|
||||
.cmd_start_byte = MF_CLASSIC_CMD_VALUE_RESTORE,
|
||||
.cmd_len_bits = 2 * 8,
|
||||
.command_num = COUNT_OF(mf_classic_listener_value_restore_handlers),
|
||||
.handler = mf_classic_listener_value_restore_handlers,
|
||||
},
|
||||
};
|
||||
|
||||
static void mf_classic_listener_send_short_frame(MfClassicListener* instance, uint8_t data) {
|
||||
BitBuffer* tx_buffer = instance->tx_plain_buffer;
|
||||
|
||||
bit_buffer_set_size(instance->tx_plain_buffer, 4);
|
||||
bit_buffer_set_byte(instance->tx_plain_buffer, 0, data);
|
||||
if(instance->comm_state == MfClassicListenerCommStateEncrypted) {
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
tx_buffer = instance->tx_encrypted_buffer;
|
||||
}
|
||||
|
||||
iso14443_3a_listener_tx_with_custom_parity(instance->iso14443_3a_listener, tx_buffer);
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_listener_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicListener* instance = context;
|
||||
Iso14443_3aListenerEvent* iso3_event = event.event_data;
|
||||
BitBuffer* rx_buffer_plain;
|
||||
|
||||
if(iso3_event->type == Iso14443_3aListenerEventTypeFieldOff) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
command = NfcCommandSleep;
|
||||
} else if(
|
||||
(iso3_event->type == Iso14443_3aListenerEventTypeReceivedData) ||
|
||||
(iso3_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame)) {
|
||||
if(instance->comm_state == MfClassicListenerCommStateEncrypted) {
|
||||
if(instance->state == MfClassicListenerStateAuthComplete) {
|
||||
crypto1_decrypt(
|
||||
instance->crypto, iso3_event->data->buffer, instance->rx_plain_buffer);
|
||||
rx_buffer_plain = instance->rx_plain_buffer;
|
||||
if(iso14443_crc_check(Iso14443CrcTypeA, rx_buffer_plain)) {
|
||||
iso14443_crc_trim(rx_buffer_plain);
|
||||
}
|
||||
} else {
|
||||
rx_buffer_plain = iso3_event->data->buffer;
|
||||
}
|
||||
} else {
|
||||
rx_buffer_plain = iso3_event->data->buffer;
|
||||
}
|
||||
|
||||
MfClassicListenerCommand mfc_command = MfClassicListenerCommandNack;
|
||||
if(instance->cmd_in_progress) {
|
||||
mfc_command =
|
||||
mf_classic_listener_cmd_handlers[instance->current_cmd_idx]
|
||||
.handler[instance->current_cmd_handler_idx](instance, rx_buffer_plain);
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(mf_classic_listener_cmd_handlers); i++) {
|
||||
if(bit_buffer_get_size(rx_buffer_plain) !=
|
||||
mf_classic_listener_cmd_handlers[i].cmd_len_bits) {
|
||||
continue;
|
||||
}
|
||||
if(bit_buffer_get_byte(rx_buffer_plain, 0) !=
|
||||
mf_classic_listener_cmd_handlers[i].cmd_start_byte) {
|
||||
continue;
|
||||
}
|
||||
instance->current_cmd_idx = i;
|
||||
instance->current_cmd_handler_idx = 0;
|
||||
mfc_command =
|
||||
mf_classic_listener_cmd_handlers[i].handler[0](instance, rx_buffer_plain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(mfc_command == MfClassicListenerCommandAck) {
|
||||
mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_ACK);
|
||||
} else if(mfc_command == MfClassicListenerCommandNack) {
|
||||
mf_classic_listener_send_short_frame(instance, MF_CLASSIC_CMD_NACK);
|
||||
} else if(mfc_command == MfClassicListenerCommandSilent) {
|
||||
command = NfcCommandReset;
|
||||
} else if(mfc_command == MfClassicListenerCommandSleep) {
|
||||
command = NfcCommandSleep;
|
||||
}
|
||||
} else if(iso3_event->type == Iso14443_3aListenerEventTypeHalted) {
|
||||
mf_classic_listener_reset_state(instance);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
MfClassicListener*
|
||||
mf_classic_listener_alloc(Iso14443_3aListener* iso14443_3a_listener, MfClassicData* data) {
|
||||
MfClassicListener* instance = malloc(sizeof(MfClassicListener));
|
||||
instance->iso14443_3a_listener = iso14443_3a_listener;
|
||||
instance->data = data;
|
||||
mf_classic_listener_prepare_emulation(instance);
|
||||
|
||||
instance->crypto = crypto1_alloc();
|
||||
instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
|
||||
instance->mfc_event.data = &instance->mfc_event_data;
|
||||
instance->generic_event.protocol = NfcProtocolMfClassic;
|
||||
instance->generic_event.event_data = &instance->mfc_event;
|
||||
instance->generic_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void mf_classic_listener_free(MfClassicListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->crypto);
|
||||
furi_assert(instance->rx_plain_buffer);
|
||||
furi_assert(instance->tx_encrypted_buffer);
|
||||
furi_assert(instance->tx_plain_buffer);
|
||||
|
||||
crypto1_free(instance->crypto);
|
||||
bit_buffer_free(instance->rx_plain_buffer);
|
||||
bit_buffer_free(instance->tx_encrypted_buffer);
|
||||
bit_buffer_free(instance->tx_plain_buffer);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void mf_classic_listener_set_callback(
|
||||
MfClassicListener* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
const MfClassicData* mf_classic_listener_get_data(const MfClassicListener* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
const NfcListenerBase mf_classic_listener = {
|
||||
.alloc = (NfcListenerAlloc)mf_classic_listener_alloc,
|
||||
.free = (NfcListenerFree)mf_classic_listener_free,
|
||||
.set_callback = (NfcListenerSetCallback)mf_classic_listener_set_callback,
|
||||
.get_data = (NfcListenerGetData)mf_classic_listener_get_data,
|
||||
.run = (NfcListenerRun)mf_classic_listener_run,
|
||||
};
|
||||
27
lib/nfc/protocols/mf_classic/mf_classic_listener.h
Normal file
27
lib/nfc/protocols/mf_classic/mf_classic_listener.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MfClassicListener MfClassicListener;
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerEventTypeAuthContextPartCollected,
|
||||
MfClassicListenerEventTypeAuthContextFullCollected,
|
||||
} MfClassicListenerEventType;
|
||||
|
||||
typedef union {
|
||||
MfClassicAuthContext auth_context;
|
||||
} MfClassicListenerEventData;
|
||||
|
||||
typedef struct {
|
||||
MfClassicListenerEventType type;
|
||||
MfClassicListenerEventData* data;
|
||||
} MfClassicListenerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
13
lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h
Normal file
13
lib/nfc/protocols/mf_classic/mf_classic_listener_defs.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_listener_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const NfcListenerBase mf_classic_listener;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
62
lib/nfc/protocols/mf_classic/mf_classic_listener_i.h
Normal file
62
lib/nfc/protocols/mf_classic/mf_classic_listener_i.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic_listener.h"
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h>
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
#include "crypto1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerCommandProcessed,
|
||||
MfClassicListenerCommandAck,
|
||||
MfClassicListenerCommandNack,
|
||||
MfClassicListenerCommandSilent,
|
||||
MfClassicListenerCommandSleep,
|
||||
} MfClassicListenerCommand;
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerStateIdle,
|
||||
MfClassicListenerStateAuthComplete,
|
||||
} MfClassicListenerState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicListenerCommStatePlain,
|
||||
MfClassicListenerCommStateEncrypted,
|
||||
} MfClassicListenerCommState;
|
||||
|
||||
struct MfClassicListener {
|
||||
Iso14443_3aListener* iso14443_3a_listener;
|
||||
MfClassicListenerState state;
|
||||
MfClassicListenerCommState comm_state;
|
||||
|
||||
MfClassicData* data;
|
||||
BitBuffer* tx_plain_buffer;
|
||||
BitBuffer* tx_encrypted_buffer;
|
||||
BitBuffer* rx_plain_buffer;
|
||||
|
||||
Crypto1* crypto;
|
||||
MfClassicAuthContext auth_context;
|
||||
|
||||
// Value operation data
|
||||
int32_t transfer_value;
|
||||
MfClassicValueCommand value_cmd;
|
||||
|
||||
NfcGenericEvent generic_event;
|
||||
MfClassicListenerEvent mfc_event;
|
||||
MfClassicListenerEventData mfc_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
|
||||
bool cmd_in_progress;
|
||||
size_t current_cmd_idx;
|
||||
size_t current_cmd_handler_idx;
|
||||
|
||||
size_t total_block_num;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
951
lib/nfc/protocols/mf_classic/mf_classic_poller.c
Normal file
951
lib/nfc/protocols/mf_classic/mf_classic_poller.c
Normal file
@@ -0,0 +1,951 @@
|
||||
#include "mf_classic_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfClassicPoller"
|
||||
|
||||
#define MF_CLASSIC_MAX_BUFF_SIZE (64)
|
||||
|
||||
typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance);
|
||||
|
||||
MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) {
|
||||
furi_assert(iso14443_3a_poller);
|
||||
|
||||
MfClassicPoller* instance = malloc(sizeof(MfClassicPoller));
|
||||
instance->iso14443_3a_poller = iso14443_3a_poller;
|
||||
instance->data = mf_classic_alloc();
|
||||
instance->crypto = crypto1_alloc();
|
||||
instance->tx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->tx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->rx_plain_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->rx_encrypted_buffer = bit_buffer_alloc(MF_CLASSIC_MAX_BUFF_SIZE);
|
||||
instance->current_type_check = MfClassicType4k;
|
||||
|
||||
instance->mfc_event.data = &instance->mfc_event_data;
|
||||
|
||||
instance->general_event.protocol = NfcProtocolMfClassic;
|
||||
instance->general_event.event_data = &instance->mfc_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void mf_classic_poller_free(MfClassicPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
furi_assert(instance->crypto);
|
||||
furi_assert(instance->tx_plain_buffer);
|
||||
furi_assert(instance->rx_plain_buffer);
|
||||
furi_assert(instance->tx_encrypted_buffer);
|
||||
furi_assert(instance->rx_encrypted_buffer);
|
||||
|
||||
mf_classic_free(instance->data);
|
||||
crypto1_free(instance->crypto);
|
||||
bit_buffer_free(instance->tx_plain_buffer);
|
||||
bit_buffer_free(instance->rx_plain_buffer);
|
||||
bit_buffer_free(instance->tx_encrypted_buffer);
|
||||
bit_buffer_free(instance->rx_encrypted_buffer);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance) {
|
||||
MfClassicPollerEventDataUpdate* data_update = &instance->mfc_event_data.data_update;
|
||||
|
||||
mf_classic_get_read_sectors_and_keys(
|
||||
instance->data, &data_update->sectors_read, &data_update->keys_found);
|
||||
data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate;
|
||||
return instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
static void mf_classic_poller_check_key_b_is_readable(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data) {
|
||||
do {
|
||||
if(!mf_classic_is_sector_trailer(block_num)) break;
|
||||
if(!mf_classic_is_allowed_access(
|
||||
instance->data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBRead))
|
||||
break;
|
||||
|
||||
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data;
|
||||
uint64_t key_b = nfc_util_bytes2num(sec_tr->key_b.data, sizeof(MfClassicKey));
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
mf_classic_set_key_found(instance->data, sector_num, MfClassicKeyTypeB, key_b);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandReset;
|
||||
|
||||
if(instance->current_type_check == MfClassicType4k) {
|
||||
iso14443_3a_copy(
|
||||
instance->data->iso14443_3a_data,
|
||||
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
||||
MfClassicError error = mf_classic_async_get_nt(instance, 254, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
instance->data->type = MfClassicType4k;
|
||||
instance->state = MfClassicPollerStateStart;
|
||||
instance->current_type_check = MfClassicType4k;
|
||||
FURI_LOG_D(TAG, "4K detected");
|
||||
} else {
|
||||
instance->current_type_check = MfClassicType1k;
|
||||
}
|
||||
} else if(instance->current_type_check == MfClassicType1k) {
|
||||
MfClassicError error = mf_classic_async_get_nt(instance, 62, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
instance->data->type = MfClassicType1k;
|
||||
FURI_LOG_D(TAG, "1K detected");
|
||||
} else {
|
||||
instance->data->type = MfClassicTypeMini;
|
||||
FURI_LOG_D(TAG, "Mini detected");
|
||||
}
|
||||
instance->current_type_check = MfClassicType4k;
|
||||
instance->state = MfClassicPollerStateStart;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
instance->sectors_total = mf_classic_get_total_sectors_num(instance->data->type);
|
||||
memset(&instance->mode_ctx, 0, sizeof(MfClassicPollerModeContext));
|
||||
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestMode;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) {
|
||||
mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) {
|
||||
instance->state = MfClassicPollerStateRequestReadSector;
|
||||
} else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) {
|
||||
instance->state = MfClassicPollerStateRequestSectorTrailer;
|
||||
} else {
|
||||
furi_crash("Invalid mode selected");
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_sector_trailer(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
|
||||
if(write_ctx->current_sector == instance->sectors_total) {
|
||||
instance->state = MfClassicPollerStateSuccess;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestSectorTrailer;
|
||||
instance->mfc_event_data.sec_tr_data.sector_num = write_ctx->current_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(instance->mfc_event_data.sec_tr_data.sector_trailer_provided) {
|
||||
instance->state = MfClassicPollerStateCheckWriteConditions;
|
||||
memcpy(
|
||||
&write_ctx->sec_tr,
|
||||
&instance->mfc_event_data.sec_tr_data.sector_trailer,
|
||||
sizeof(MfClassicSectorTrailer));
|
||||
write_ctx->current_block =
|
||||
MAX(1, mf_classic_get_first_block_num_of_sector(write_ctx->current_sector));
|
||||
|
||||
} else {
|
||||
write_ctx->current_sector++;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_handler_check_write_conditions(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
MfClassicSectorTrailer* sec_tr = &write_ctx->sec_tr;
|
||||
|
||||
do {
|
||||
// Check last block in sector to write
|
||||
uint8_t sec_tr_block_num =
|
||||
mf_classic_get_sector_trailer_num_by_sector(write_ctx->current_sector);
|
||||
if(write_ctx->current_block == sec_tr_block_num) {
|
||||
write_ctx->current_sector++;
|
||||
instance->state = MfClassicPollerStateRequestSectorTrailer;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check write and read access
|
||||
if(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr, write_ctx->current_block, MfClassicKeyTypeA, MfClassicActionDataWrite)) {
|
||||
write_ctx->key_type_write = MfClassicKeyTypeA;
|
||||
} else if(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeB,
|
||||
MfClassicActionDataWrite)) {
|
||||
write_ctx->key_type_write = MfClassicKeyTypeB;
|
||||
} else if(mf_classic_is_value_block(sec_tr, write_ctx->current_block)) {
|
||||
write_ctx->is_value_block = true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Not allowed to write block %d", write_ctx->current_block);
|
||||
write_ctx->current_block++;
|
||||
break;
|
||||
}
|
||||
|
||||
if(mf_classic_is_allowed_access_data_block(
|
||||
sec_tr,
|
||||
write_ctx->current_block,
|
||||
write_ctx->key_type_write,
|
||||
MfClassicActionDataRead)) {
|
||||
write_ctx->key_type_read = write_ctx->key_type_write;
|
||||
} else {
|
||||
write_ctx->key_type_read = write_ctx->key_type_write == MfClassicKeyTypeA ?
|
||||
MfClassicKeyTypeB :
|
||||
MfClassicKeyTypeA;
|
||||
if(!mf_classic_is_allowed_access_data_block(
|
||||
sec_tr,
|
||||
write_ctx->current_block,
|
||||
write_ctx->key_type_read,
|
||||
MfClassicActionDataRead)) {
|
||||
FURI_LOG_D(TAG, "Not allowed to read block %d", write_ctx->current_block);
|
||||
write_ctx->current_block++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write_ctx->need_halt_before_write =
|
||||
(write_ctx->key_type_read != write_ctx->key_type_write);
|
||||
instance->state = MfClassicPollerStateReadBlock;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
|
||||
MfClassicKey* auth_key = write_ctx->key_type_read == MfClassicKeyTypeA ?
|
||||
&write_ctx->sec_tr.key_a :
|
||||
&write_ctx->sec_tr.key_b;
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
// Authenticate to sector
|
||||
error = mf_classic_async_auth(
|
||||
instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
// Read block from tag
|
||||
error =
|
||||
mf_classic_async_read_block(instance, write_ctx->current_block, &write_ctx->tag_block);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %d", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
if(write_ctx->is_value_block) {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateWriteValueBlock;
|
||||
} else {
|
||||
if(write_ctx->need_halt_before_write) {
|
||||
mf_classic_async_halt(instance);
|
||||
}
|
||||
instance->state = MfClassicPollerStateWriteBlock;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
MfClassicKey* auth_key = write_ctx->key_type_write == MfClassicKeyTypeA ?
|
||||
&write_ctx->sec_tr.key_a :
|
||||
&write_ctx->sec_tr.key_b;
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
// Request block to write
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock;
|
||||
instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfc_event_data.write_block_data.write_block_provided) break;
|
||||
|
||||
// Compare tag and saved block
|
||||
if(memcmp(
|
||||
write_ctx->tag_block.data,
|
||||
instance->mfc_event_data.write_block_data.write_block.data,
|
||||
sizeof(MfClassicBlock)) == 0) {
|
||||
FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block);
|
||||
break;
|
||||
}
|
||||
|
||||
// Reauth if necessary
|
||||
if(write_ctx->need_halt_before_write) {
|
||||
error = mf_classic_async_auth(
|
||||
instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Failed to auth to block %d for writing", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write block
|
||||
error = mf_classic_async_write_block(
|
||||
instance,
|
||||
write_ctx->current_block,
|
||||
&instance->mfc_event_data.write_block_data.write_block);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block);
|
||||
instance->state = MfClassicPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
mf_classic_async_halt(instance);
|
||||
write_ctx->current_block++;
|
||||
instance->state = MfClassicPollerStateCheckWriteConditions;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx;
|
||||
|
||||
do {
|
||||
// Request block to write
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestWriteBlock;
|
||||
instance->mfc_event_data.write_block_data.block_num = write_ctx->current_block;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(!instance->mfc_event_data.write_block_data.write_block_provided) break;
|
||||
|
||||
// Compare tag and saved block
|
||||
if(memcmp(
|
||||
write_ctx->tag_block.data,
|
||||
instance->mfc_event_data.write_block_data.write_block.data,
|
||||
sizeof(MfClassicBlock)) == 0) {
|
||||
FURI_LOG_D(TAG, "Block %d is equal. Skip writing", write_ctx->current_block);
|
||||
break;
|
||||
}
|
||||
|
||||
bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeA,
|
||||
MfClassicActionDataInc);
|
||||
bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeB,
|
||||
MfClassicActionDataInc);
|
||||
bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeA,
|
||||
MfClassicActionDataDec);
|
||||
bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block(
|
||||
&write_ctx->sec_tr,
|
||||
write_ctx->current_block,
|
||||
MfClassicKeyTypeB,
|
||||
MfClassicActionDataDec);
|
||||
|
||||
int32_t source_value = 0;
|
||||
int32_t target_value = 0;
|
||||
if(!mf_classic_block_to_value(
|
||||
&instance->mfc_event_data.write_block_data.write_block, &source_value, NULL))
|
||||
break;
|
||||
if(!mf_classic_block_to_value(&write_ctx->tag_block, &target_value, NULL)) break;
|
||||
|
||||
MfClassicKeyType auth_key_type = MfClassicKeyTypeA;
|
||||
MfClassicValueCommand value_cmd = MfClassicValueCommandIncrement;
|
||||
int32_t diff = source_value - target_value;
|
||||
if(diff > 0) {
|
||||
if(key_a_inc_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeA;
|
||||
value_cmd = MfClassicValueCommandIncrement;
|
||||
} else if(key_b_inc_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeB;
|
||||
value_cmd = MfClassicValueCommandIncrement;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Unable to increment value block");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(key_a_dec_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeA;
|
||||
value_cmd = MfClassicValueCommandDecrement;
|
||||
diff *= -1;
|
||||
} else if(key_b_dec_allowed) {
|
||||
auth_key_type = MfClassicKeyTypeB;
|
||||
value_cmd = MfClassicValueCommandDecrement;
|
||||
diff *= -1;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Unable to decrement value block");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a :
|
||||
&write_ctx->sec_tr.key_b;
|
||||
|
||||
MfClassicError error =
|
||||
mf_classic_async_auth(instance, write_ctx->current_block, key, auth_key_type, NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_cmd(instance, write_ctx->current_block, value_cmd, diff);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_transfer(instance, write_ctx->current_block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
mf_classic_async_halt(instance);
|
||||
write_ctx->is_value_block = false;
|
||||
write_ctx->current_block++;
|
||||
instance->state = MfClassicPollerStateCheckWriteConditions;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_read_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx;
|
||||
MfClassicPollerEventDataReadSectorRequest* sec_read =
|
||||
&instance->mfc_event_data.read_sector_request_data;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestReadSector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
if(!sec_read->key_provided) {
|
||||
instance->state = MfClassicPollerStateSuccess;
|
||||
} else {
|
||||
sec_read_ctx->current_sector = sec_read->sector_num;
|
||||
sec_read_ctx->key = sec_read->key;
|
||||
sec_read_ctx->key_type = sec_read->key_type;
|
||||
sec_read_ctx->current_block =
|
||||
mf_classic_get_first_block_num_of_sector(sec_read->sector_num);
|
||||
sec_read_ctx->auth_passed = false;
|
||||
instance->state = MfClassicPollerStateReadSectorBlocks;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
MfClassicPollerReadContext* sec_read_ctx = &instance->mode_ctx.read_ctx;
|
||||
|
||||
do {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
if(!sec_read_ctx->auth_passed) {
|
||||
uint64_t key = nfc_util_bytes2num(sec_read_ctx->key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Auth to block %d with key %c: %06llx",
|
||||
sec_read_ctx->current_block,
|
||||
sec_read_ctx->key_type == MfClassicKeyTypeA ? 'A' : 'B',
|
||||
key);
|
||||
error = mf_classic_async_auth(
|
||||
instance,
|
||||
sec_read_ctx->current_block,
|
||||
&sec_read_ctx->key,
|
||||
sec_read_ctx->key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
sec_read_ctx->auth_passed = true;
|
||||
if(!mf_classic_is_key_found(
|
||||
instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type)) {
|
||||
mf_classic_set_key_found(
|
||||
instance->data, sec_read_ctx->current_sector, sec_read_ctx->key_type, key);
|
||||
}
|
||||
}
|
||||
if(mf_classic_is_block_read(instance->data, sec_read_ctx->current_block)) break;
|
||||
|
||||
FURI_LOG_D(TAG, "Reading block %d", sec_read_ctx->current_block);
|
||||
MfClassicBlock read_block = {};
|
||||
error = mf_classic_async_read_block(instance, sec_read_ctx->current_block, &read_block);
|
||||
if(error == MfClassicErrorNone) {
|
||||
mf_classic_set_block_read(instance->data, sec_read_ctx->current_block, &read_block);
|
||||
if(sec_read_ctx->key_type == MfClassicKeyTypeA) {
|
||||
mf_classic_poller_check_key_b_is_readable(
|
||||
instance, sec_read_ctx->current_block, &read_block);
|
||||
}
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
sec_read_ctx->auth_passed = false;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint8_t sec_tr_num = mf_classic_get_sector_trailer_num_by_sector(sec_read_ctx->current_sector);
|
||||
sec_read_ctx->current_block++;
|
||||
if(sec_read_ctx->current_block > sec_tr_num) {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateRequestReadSector;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeRequestKey;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
if(instance->mfc_event_data.key_request_data.key_provided) {
|
||||
dict_attack_ctx->current_key = instance->mfc_event_data.key_request_data.key;
|
||||
instance->state = MfClassicPollerStateAuthKeyA;
|
||||
} else {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
||||
instance->state = MfClassicPollerStateAuthKeyB;
|
||||
} else {
|
||||
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key A found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
dict_attack_ctx->current_block = block;
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateAuthKeyB;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB)) {
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeA)) {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
} else {
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
}
|
||||
} else {
|
||||
uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key B found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->current_sector, MfClassicKeyTypeB, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
dict_attack_ctx->current_block = block;
|
||||
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_next_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
dict_attack_ctx->current_sector++;
|
||||
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
||||
instance->state = MfClassicPollerStateSuccess;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeNextSector;
|
||||
instance->mfc_event_data.next_sector_data.current_sector = dict_attack_ctx->current_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
uint8_t block_num = dict_attack_ctx->current_block;
|
||||
MfClassicBlock block = {};
|
||||
|
||||
do {
|
||||
if(mf_classic_is_block_read(instance->data, block_num)) break;
|
||||
|
||||
if(!dict_attack_ctx->auth_passed) {
|
||||
error = mf_classic_async_auth(
|
||||
instance,
|
||||
block_num,
|
||||
&dict_attack_ctx->current_key,
|
||||
dict_attack_ctx->current_key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Reading block %d", block_num);
|
||||
error = mf_classic_async_read_block(instance, block_num, &block);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
FURI_LOG_D(TAG, "Failed to read block %d", block_num);
|
||||
} else {
|
||||
mf_classic_set_block_read(instance->data, block_num, &block);
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
mf_classic_poller_check_key_b_is_readable(instance, block_num, &block);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint8_t sec_tr_block_num =
|
||||
mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->current_sector);
|
||||
dict_attack_ctx->current_block++;
|
||||
if(dict_attack_ctx->current_block > sec_tr_block_num) {
|
||||
mf_classic_poller_handle_data_update(instance);
|
||||
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
|
||||
if(dict_attack_ctx->current_sector == instance->sectors_total) {
|
||||
instance->state = MfClassicPollerStateNextSector;
|
||||
} else {
|
||||
dict_attack_ctx->reuse_key_sector = dict_attack_ctx->current_sector;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
||||
instance->mfc_event_data.key_attack_data.current_sector =
|
||||
dict_attack_ctx->reuse_key_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyB;
|
||||
} else {
|
||||
dict_attack_ctx->reuse_key_sector++;
|
||||
if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateRequestKey;
|
||||
} else {
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart;
|
||||
instance->mfc_event_data.key_attack_data.current_sector =
|
||||
dict_attack_ctx->reuse_key_sector;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
instance->state = MfClassicPollerStateKeyReuseAuthKeyA;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA)) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
} else {
|
||||
uint8_t block =
|
||||
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key A found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeA;
|
||||
dict_attack_ctx->current_block = block;
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateKeyReuseReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
if(mf_classic_is_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB)) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
} else {
|
||||
uint8_t block =
|
||||
mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector);
|
||||
uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey));
|
||||
FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key);
|
||||
|
||||
MfClassicError error = mf_classic_async_auth(
|
||||
instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_I(TAG, "Key B found");
|
||||
mf_classic_set_key_found(
|
||||
instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key);
|
||||
|
||||
command = mf_classic_poller_handle_data_update(instance);
|
||||
dict_attack_ctx->current_key_type = MfClassicKeyTypeB;
|
||||
dict_attack_ctx->current_block = block;
|
||||
|
||||
dict_attack_ctx->auth_passed = true;
|
||||
instance->state = MfClassicPollerStateKeyReuseReadSector;
|
||||
} else {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx;
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
uint8_t block_num = dict_attack_ctx->current_block;
|
||||
MfClassicBlock block = {};
|
||||
|
||||
do {
|
||||
if(mf_classic_is_block_read(instance->data, block_num)) break;
|
||||
|
||||
if(!dict_attack_ctx->auth_passed) {
|
||||
error = mf_classic_async_auth(
|
||||
instance,
|
||||
block_num,
|
||||
&dict_attack_ctx->current_key,
|
||||
dict_attack_ctx->current_key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) {
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Reading block %d", block_num);
|
||||
error = mf_classic_async_read_block(instance, block_num, &block);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
FURI_LOG_D(TAG, "Failed to read block %d", block_num);
|
||||
} else {
|
||||
mf_classic_set_block_read(instance->data, block_num, &block);
|
||||
if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) {
|
||||
mf_classic_poller_check_key_b_is_readable(instance, block_num, &block);
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
uint16_t sec_tr_block_num =
|
||||
mf_classic_get_sector_trailer_num_by_sector(dict_attack_ctx->reuse_key_sector);
|
||||
dict_attack_ctx->current_block++;
|
||||
if(dict_attack_ctx->current_block > sec_tr_block_num) {
|
||||
mf_classic_async_halt(instance);
|
||||
dict_attack_ctx->auth_passed = false;
|
||||
|
||||
mf_classic_poller_handle_data_update(instance);
|
||||
instance->state = MfClassicPollerStateKeyReuseStart;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeSuccess;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_handler_fail(MfClassicPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeFail;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfClassicPollerStateDetectType;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const MfClassicPollerReadHandler
|
||||
mf_classic_poller_dict_attack_handler[MfClassicPollerStateNum] = {
|
||||
[MfClassicPollerStateDetectType] = mf_classic_poller_handler_detect_type,
|
||||
[MfClassicPollerStateStart] = mf_classic_poller_handler_start,
|
||||
[MfClassicPollerStateRequestSectorTrailer] =
|
||||
mf_classic_poller_handler_request_sector_trailer,
|
||||
[MfClassicPollerStateCheckWriteConditions] = mf_classic_handler_check_write_conditions,
|
||||
[MfClassicPollerStateReadBlock] = mf_classic_poller_handler_read_block,
|
||||
[MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block,
|
||||
[MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block,
|
||||
[MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector,
|
||||
[MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key,
|
||||
[MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector,
|
||||
[MfClassicPollerStateReadSectorBlocks] =
|
||||
mf_classic_poller_handler_request_read_sector_blocks,
|
||||
[MfClassicPollerStateAuthKeyA] = mf_classic_poller_handler_auth_a,
|
||||
[MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b,
|
||||
[MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector,
|
||||
[MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start,
|
||||
[MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a,
|
||||
[MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b,
|
||||
[MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector,
|
||||
[MfClassicPollerStateSuccess] = mf_classic_poller_handler_success,
|
||||
[MfClassicPollerStateFail] = mf_classic_poller_handler_fail,
|
||||
};
|
||||
|
||||
NfcCommand mf_classic_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(context);
|
||||
|
||||
MfClassicPoller* instance = context;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
if(instance->card_state == MfClassicCardStateLost) {
|
||||
instance->card_state = MfClassicCardStateDetected;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeCardDetected;
|
||||
instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
command = mf_classic_poller_dict_attack_handler[instance->state](instance);
|
||||
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
|
||||
if(instance->card_state == MfClassicCardStateDetected) {
|
||||
instance->card_state = MfClassicCardStateLost;
|
||||
instance->mfc_event.type = MfClassicPollerEventTypeCardLost;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
bool mf_classic_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(context);
|
||||
|
||||
Iso14443_3aPoller* iso3_poller = event.instance;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
bool detected = false;
|
||||
const uint8_t auth_cmd[] = {MF_CLASSIC_CMD_AUTH_KEY_A, 0};
|
||||
BitBuffer* tx_buffer = bit_buffer_alloc(COUNT_OF(auth_cmd));
|
||||
bit_buffer_copy_bytes(tx_buffer, auth_cmd, COUNT_OF(auth_cmd));
|
||||
BitBuffer* rx_buffer = bit_buffer_alloc(sizeof(MfClassicNt));
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
Iso14443_3aError error = iso14443_3a_poller_send_standard_frame(
|
||||
iso3_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC);
|
||||
if(error == Iso14443_3aErrorWrongCrc) {
|
||||
if(bit_buffer_get_size_bytes(rx_buffer) == sizeof(MfClassicNt)) {
|
||||
detected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bit_buffer_free(tx_buffer);
|
||||
bit_buffer_free(rx_buffer);
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
void mf_classic_poller_set_callback(
|
||||
MfClassicPoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
const MfClassicData* mf_classic_poller_get_data(const MfClassicPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->data);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
const NfcPollerBase mf_classic_poller = {
|
||||
.alloc = (NfcPollerAlloc)mf_classic_poller_alloc,
|
||||
.free = (NfcPollerFree)mf_classic_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)mf_classic_poller_set_callback,
|
||||
.run = (NfcPollerRun)mf_classic_poller_run,
|
||||
.detect = (NfcPollerDetect)mf_classic_poller_detect,
|
||||
.get_data = (NfcPollerGetData)mf_classic_poller_get_data,
|
||||
};
|
||||
109
lib/nfc/protocols/mf_classic/mf_classic_poller.h
Normal file
109
lib/nfc/protocols/mf_classic/mf_classic_poller.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic.h"
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MfClassicPoller MfClassicPoller;
|
||||
|
||||
typedef enum {
|
||||
// Start event
|
||||
MfClassicPollerEventTypeRequestMode,
|
||||
|
||||
// Read with key cache events
|
||||
MfClassicPollerEventTypeRequestReadSector,
|
||||
|
||||
// Write events
|
||||
MfClassicPollerEventTypeRequestSectorTrailer,
|
||||
MfClassicPollerEventTypeRequestWriteBlock,
|
||||
|
||||
// Dictionary attack events
|
||||
MfClassicPollerEventTypeRequestKey,
|
||||
MfClassicPollerEventTypeNextSector,
|
||||
MfClassicPollerEventTypeDataUpdate,
|
||||
MfClassicPollerEventTypeFoundKeyA,
|
||||
MfClassicPollerEventTypeFoundKeyB,
|
||||
MfClassicPollerEventTypeCardNotDetected,
|
||||
MfClassicPollerEventTypeKeyAttackStart,
|
||||
MfClassicPollerEventTypeKeyAttackStop,
|
||||
MfClassicPollerEventTypeKeyAttackNextSector,
|
||||
|
||||
// Common events
|
||||
MfClassicPollerEventTypeCardDetected,
|
||||
MfClassicPollerEventTypeCardLost,
|
||||
MfClassicPollerEventTypeSuccess,
|
||||
MfClassicPollerEventTypeFail,
|
||||
} MfClassicPollerEventType;
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerModeRead,
|
||||
MfClassicPollerModeWrite,
|
||||
MfClassicPollerModeDictAttack,
|
||||
} MfClassicPollerMode;
|
||||
|
||||
typedef struct {
|
||||
MfClassicPollerMode mode;
|
||||
const MfClassicData* data;
|
||||
} MfClassicPollerEventDataRequestMode;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
} MfClassicPollerEventDataDictAttackNextSector;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sectors_read;
|
||||
uint8_t keys_found;
|
||||
uint8_t current_sector;
|
||||
} MfClassicPollerEventDataUpdate;
|
||||
|
||||
typedef struct {
|
||||
MfClassicKey key;
|
||||
bool key_provided;
|
||||
} MfClassicPollerEventDataKeyRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sector_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
bool key_provided;
|
||||
} MfClassicPollerEventDataReadSectorRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t sector_num;
|
||||
MfClassicBlock sector_trailer;
|
||||
bool sector_trailer_provided;
|
||||
} MfClassicPollerEventDataSectorTrailerRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicBlock write_block;
|
||||
bool write_block_provided;
|
||||
} MfClassicPollerEventDataWriteBlockRequest;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
} MfClassicPollerEventKeyAttackData;
|
||||
|
||||
typedef union {
|
||||
MfClassicError error;
|
||||
MfClassicPollerEventDataRequestMode poller_mode;
|
||||
MfClassicPollerEventDataDictAttackNextSector next_sector_data;
|
||||
MfClassicPollerEventDataKeyRequest key_request_data;
|
||||
MfClassicPollerEventDataUpdate data_update;
|
||||
MfClassicPollerEventDataReadSectorRequest read_sector_request_data;
|
||||
MfClassicPollerEventKeyAttackData key_attack_data;
|
||||
MfClassicPollerEventDataSectorTrailerRequest sec_tr_data;
|
||||
MfClassicPollerEventDataWriteBlockRequest write_block_data;
|
||||
} MfClassicPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
MfClassicPollerEventType type;
|
||||
MfClassicPollerEventData* data;
|
||||
} MfClassicPollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
13
lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h
Normal file
13
lib/nfc/protocols/mf_classic/mf_classic_poller_defs.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const NfcPollerBase mf_classic_poller;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
386
lib/nfc/protocols/mf_classic/mf_classic_poller_i.c
Normal file
386
lib/nfc/protocols/mf_classic/mf_classic_poller_i.c
Normal file
@@ -0,0 +1,386 @@
|
||||
#include "mf_classic_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define TAG "MfCLassicPoller"
|
||||
|
||||
MfClassicError mf_classic_process_error(Iso14443_3aError error) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case Iso14443_3aErrorNone:
|
||||
ret = MfClassicErrorNone;
|
||||
break;
|
||||
case Iso14443_3aErrorNotPresent:
|
||||
ret = MfClassicErrorNotPresent;
|
||||
break;
|
||||
case Iso14443_3aErrorColResFailed:
|
||||
case Iso14443_3aErrorCommunication:
|
||||
case Iso14443_3aErrorWrongCrc:
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
case Iso14443_3aErrorTimeout:
|
||||
ret = MfClassicErrorTimeout;
|
||||
break;
|
||||
default:
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_get_nt(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B :
|
||||
MF_CLASSIC_CMD_AUTH_KEY_A;
|
||||
uint8_t auth_cmd[2] = {auth_type, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd));
|
||||
|
||||
error = iso14443_3a_poller_send_standard_frame(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_plain_buffer,
|
||||
instance->rx_plain_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorWrongCrc) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
if(nt) {
|
||||
bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt));
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_auth(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
iso14443_3a_copy(
|
||||
instance->data->iso14443_3a_data,
|
||||
iso14443_3a_poller_get_data(instance->iso14443_3a_poller));
|
||||
|
||||
MfClassicNt nt = {};
|
||||
ret = mf_classic_async_get_nt(instance, block_num, key_type, &nt);
|
||||
if(ret != MfClassicErrorNone) break;
|
||||
if(data) {
|
||||
data->nt = nt;
|
||||
}
|
||||
|
||||
uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data);
|
||||
uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey));
|
||||
MfClassicNr nr = {};
|
||||
furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr));
|
||||
|
||||
crypto1_encrypt_reader_nonce(
|
||||
instance->crypto, key_num, cuid, nt.data, nr.data, instance->tx_encrypted_buffer);
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorAuth;
|
||||
}
|
||||
|
||||
crypto1_word(instance->crypto, 0, 0);
|
||||
instance->auth_state = MfClassicAuthStatePassed;
|
||||
|
||||
if(data) {
|
||||
data->nr = nr;
|
||||
const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer);
|
||||
memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr));
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt));
|
||||
}
|
||||
} while(false);
|
||||
|
||||
if(ret != MfClassicErrorNone) {
|
||||
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_halt(MfClassicPoller* instance) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorTimeout) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
instance->auth_state = MfClassicAuthStateIdle;
|
||||
instance->iso14443_3a_poller->state = Iso14443_3aPollerStateIdle;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_read_block(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t read_block_cmd[2] = {MF_CLASSIC_CMD_READ_BLOCK, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, read_block_cmd, sizeof(read_block_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) !=
|
||||
(sizeof(MfClassicBlock) + 2)) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(!iso14443_crc_check(Iso14443CrcTypeA, instance->rx_plain_buffer)) {
|
||||
FURI_LOG_D(TAG, "CRC error");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
iso14443_crc_trim(instance->rx_plain_buffer);
|
||||
bit_buffer_write_bytes(instance->rx_plain_buffer, data->data, sizeof(MfClassicBlock));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_write_block(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_value_cmd(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicValueCommand cmd,
|
||||
int32_t data) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t cmd_value = 0;
|
||||
if(cmd == MfClassicValueCommandDecrement) {
|
||||
cmd_value = MF_CLASSIC_CMD_VALUE_DEC;
|
||||
} else if(cmd == MfClassicValueCommandIncrement) {
|
||||
cmd_value = MF_CLASSIC_CMD_VALUE_INC;
|
||||
} else {
|
||||
cmd_value = MF_CLASSIC_CMD_VALUE_RESTORE;
|
||||
}
|
||||
uint8_t value_cmd[2] = {cmd_value, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, value_cmd, sizeof(value_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, (uint8_t*)&data, sizeof(data));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
|
||||
// Command processed if tag doesn't respond
|
||||
if(error != Iso14443_3aErrorTimeout) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
ret = MfClassicErrorNone;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num) {
|
||||
MfClassicError ret = MfClassicErrorNone;
|
||||
Iso14443_3aError error = Iso14443_3aErrorNone;
|
||||
|
||||
do {
|
||||
uint8_t transfer_cmd[2] = {MF_CLASSIC_CMD_VALUE_TRANSFER, block_num};
|
||||
bit_buffer_copy_bytes(instance->tx_plain_buffer, transfer_cmd, sizeof(transfer_cmd));
|
||||
iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer);
|
||||
|
||||
crypto1_encrypt(
|
||||
instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer);
|
||||
|
||||
error = iso14443_3a_poller_txrx_custom_parity(
|
||||
instance->iso14443_3a_poller,
|
||||
instance->tx_encrypted_buffer,
|
||||
instance->rx_encrypted_buffer,
|
||||
MF_CLASSIC_FWT_FC);
|
||||
if(error != Iso14443_3aErrorNone) {
|
||||
ret = mf_classic_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) {
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
crypto1_decrypt(
|
||||
instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer);
|
||||
|
||||
if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) {
|
||||
FURI_LOG_D(TAG, "Not ACK received");
|
||||
ret = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
205
lib/nfc/protocols/mf_classic/mf_classic_poller_i.h
Normal file
205
lib/nfc/protocols/mf_classic/mf_classic_poller_i.h
Normal file
@@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic_poller.h"
|
||||
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h>
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
#include "crypto1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_CLASSIC_FWT_FC (60000)
|
||||
|
||||
typedef enum {
|
||||
MfClassicAuthStateIdle,
|
||||
MfClassicAuthStatePassed,
|
||||
} MfClassicAuthState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicCardStateDetected,
|
||||
MfClassicCardStateLost,
|
||||
} MfClassicCardState;
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerStateDetectType,
|
||||
MfClassicPollerStateStart,
|
||||
|
||||
// Write states
|
||||
MfClassicPollerStateRequestSectorTrailer,
|
||||
MfClassicPollerStateCheckWriteConditions,
|
||||
MfClassicPollerStateReadBlock,
|
||||
MfClassicPollerStateWriteBlock,
|
||||
MfClassicPollerStateWriteValueBlock,
|
||||
|
||||
// Read states
|
||||
MfClassicPollerStateRequestReadSector,
|
||||
MfClassicPollerStateReadSectorBlocks,
|
||||
|
||||
// Dict attack states
|
||||
MfClassicPollerStateNextSector,
|
||||
MfClassicPollerStateRequestKey,
|
||||
MfClassicPollerStateReadSector,
|
||||
MfClassicPollerStateAuthKeyA,
|
||||
MfClassicPollerStateAuthKeyB,
|
||||
MfClassicPollerStateKeyReuseStart,
|
||||
MfClassicPollerStateKeyReuseAuthKeyA,
|
||||
MfClassicPollerStateKeyReuseAuthKeyB,
|
||||
MfClassicPollerStateKeyReuseReadSector,
|
||||
MfClassicPollerStateSuccess,
|
||||
MfClassicPollerStateFail,
|
||||
|
||||
MfClassicPollerStateNum,
|
||||
} MfClassicPollerState;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
MfClassicSectorTrailer sec_tr;
|
||||
uint16_t current_block;
|
||||
bool is_value_block;
|
||||
MfClassicKeyType key_type_read;
|
||||
MfClassicKeyType key_type_write;
|
||||
bool need_halt_before_write;
|
||||
MfClassicBlock tag_block;
|
||||
} MfClassicPollerWriteContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
MfClassicKey current_key;
|
||||
MfClassicKeyType current_key_type;
|
||||
bool auth_passed;
|
||||
uint16_t current_block;
|
||||
uint8_t reuse_key_sector;
|
||||
} MfClassicPollerDictAttackContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t current_sector;
|
||||
uint16_t current_block;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicKey key;
|
||||
bool auth_passed;
|
||||
} MfClassicPollerReadContext;
|
||||
|
||||
typedef union {
|
||||
MfClassicPollerWriteContext write_ctx;
|
||||
MfClassicPollerDictAttackContext dict_attack_ctx;
|
||||
MfClassicPollerReadContext read_ctx;
|
||||
|
||||
} MfClassicPollerModeContext;
|
||||
|
||||
struct MfClassicPoller {
|
||||
Iso14443_3aPoller* iso14443_3a_poller;
|
||||
|
||||
MfClassicPollerState state;
|
||||
MfClassicAuthState auth_state;
|
||||
MfClassicCardState card_state;
|
||||
|
||||
MfClassicType current_type_check;
|
||||
uint8_t sectors_total;
|
||||
MfClassicPollerModeContext mode_ctx;
|
||||
|
||||
Crypto1* crypto;
|
||||
BitBuffer* tx_plain_buffer;
|
||||
BitBuffer* tx_encrypted_buffer;
|
||||
BitBuffer* rx_plain_buffer;
|
||||
BitBuffer* rx_encrypted_buffer;
|
||||
MfClassicData* data;
|
||||
|
||||
NfcGenericEvent general_event;
|
||||
MfClassicPollerEvent mfc_event;
|
||||
MfClassicPollerEventData mfc_event_data;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t block;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicNt nt;
|
||||
} MfClassicCollectNtContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicBlock block;
|
||||
} MfClassicReadBlockContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicBlock block;
|
||||
} MfClassicWriteBlockContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
int32_t value;
|
||||
} MfClassicReadValueContext;
|
||||
|
||||
typedef struct {
|
||||
uint8_t block_num;
|
||||
MfClassicKey key;
|
||||
MfClassicKeyType key_type;
|
||||
MfClassicValueCommand value_cmd;
|
||||
int32_t data;
|
||||
int32_t new_value;
|
||||
} MfClassicChangeValueContext;
|
||||
|
||||
typedef struct {
|
||||
MfClassicDeviceKeys keys;
|
||||
uint8_t current_sector;
|
||||
} MfClassicReadContext;
|
||||
|
||||
typedef union {
|
||||
MfClassicCollectNtContext collect_nt_context;
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicReadBlockContext read_block_context;
|
||||
MfClassicWriteBlockContext write_block_context;
|
||||
MfClassicReadValueContext read_value_context;
|
||||
MfClassicChangeValueContext change_value_context;
|
||||
MfClassicReadContext read_context;
|
||||
} MfClassicPollerContextData;
|
||||
|
||||
MfClassicError mf_classic_process_error(Iso14443_3aError error);
|
||||
|
||||
MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller);
|
||||
|
||||
void mf_classic_poller_free(MfClassicPoller* instance);
|
||||
|
||||
MfClassicError mf_classic_async_get_nt(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt);
|
||||
|
||||
MfClassicError mf_classic_async_auth(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data);
|
||||
|
||||
MfClassicError mf_classic_async_halt(MfClassicPoller* instance);
|
||||
|
||||
MfClassicError
|
||||
mf_classic_async_read_block(MfClassicPoller* instance, uint8_t block_num, MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_async_write_block(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_async_value_cmd(
|
||||
MfClassicPoller* instance,
|
||||
uint8_t block_num,
|
||||
MfClassicValueCommand cmd,
|
||||
int32_t data);
|
||||
|
||||
MfClassicError mf_classic_async_value_transfer(MfClassicPoller* instance, uint8_t block_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
524
lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c
Normal file
524
lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.c
Normal file
@@ -0,0 +1,524 @@
|
||||
#include "mf_classic_poller_i.h"
|
||||
|
||||
#include <nfc/nfc_poller.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfClassicPoller"
|
||||
|
||||
#define MF_CLASSIC_POLLER_COMPLETE_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
MfClassicPollerCmdTypeCollectNt,
|
||||
MfClassicPollerCmdTypeAuth,
|
||||
MfClassicPollerCmdTypeReadBlock,
|
||||
MfClassicPollerCmdTypeWriteBlock,
|
||||
MfClassicPollerCmdTypeReadValue,
|
||||
MfClassicPollerCmdTypeChangeValue,
|
||||
|
||||
MfClassicPollerCmdTypeNum,
|
||||
} MfClassicPollerCmdType;
|
||||
|
||||
typedef struct {
|
||||
MfClassicPollerCmdType cmd_type;
|
||||
FuriThreadId thread_id;
|
||||
MfClassicError error;
|
||||
MfClassicPollerContextData data;
|
||||
} MfClassicPollerContext;
|
||||
|
||||
typedef MfClassicError (
|
||||
*MfClassicPollerCmdHandler)(MfClassicPoller* poller, MfClassicPollerContextData* data);
|
||||
|
||||
static MfClassicError mf_classic_poller_collect_nt_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
return mf_classic_async_get_nt(
|
||||
poller,
|
||||
data->collect_nt_context.block,
|
||||
data->collect_nt_context.key_type,
|
||||
&data->collect_nt_context.nt);
|
||||
}
|
||||
|
||||
static MfClassicError
|
||||
mf_classic_poller_auth_handler(MfClassicPoller* poller, MfClassicPollerContextData* data) {
|
||||
return mf_classic_async_auth(
|
||||
poller,
|
||||
data->auth_context.block_num,
|
||||
&data->auth_context.key,
|
||||
data->auth_context.key_type,
|
||||
&data->auth_context);
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_read_block_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->read_block_context.block_num,
|
||||
&data->read_block_context.key,
|
||||
data->read_block_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_read_block(
|
||||
poller, data->read_block_context.block_num, &data->read_block_context.block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_write_block_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->read_block_context.block_num,
|
||||
&data->read_block_context.key,
|
||||
data->read_block_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_write_block(
|
||||
poller, data->write_block_context.block_num, &data->write_block_context.block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_read_value_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->read_value_context.block_num,
|
||||
&data->read_value_context.key,
|
||||
data->read_value_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
MfClassicBlock block = {};
|
||||
error = mf_classic_async_read_block(poller, data->read_value_context.block_num, &block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
if(!mf_classic_block_to_value(&block, &data->read_value_context.value, NULL)) {
|
||||
error = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_change_value_handler(
|
||||
MfClassicPoller* poller,
|
||||
MfClassicPollerContextData* data) {
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
do {
|
||||
error = mf_classic_async_auth(
|
||||
poller,
|
||||
data->change_value_context.block_num,
|
||||
&data->change_value_context.key,
|
||||
data->change_value_context.key_type,
|
||||
NULL);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_cmd(
|
||||
poller,
|
||||
data->change_value_context.block_num,
|
||||
data->change_value_context.value_cmd,
|
||||
data->change_value_context.data);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_value_transfer(poller, data->change_value_context.block_num);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
MfClassicBlock block = {};
|
||||
error = mf_classic_async_read_block(poller, data->change_value_context.block_num, &block);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
error = mf_classic_async_halt(poller);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
if(!mf_classic_block_to_value(&block, &data->change_value_context.new_value, NULL)) {
|
||||
error = MfClassicErrorProtocol;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const MfClassicPollerCmdHandler mf_classic_poller_cmd_handlers[MfClassicPollerCmdTypeNum] = {
|
||||
[MfClassicPollerCmdTypeCollectNt] = mf_classic_poller_collect_nt_handler,
|
||||
[MfClassicPollerCmdTypeAuth] = mf_classic_poller_auth_handler,
|
||||
[MfClassicPollerCmdTypeReadBlock] = mf_classic_poller_read_block_handler,
|
||||
[MfClassicPollerCmdTypeWriteBlock] = mf_classic_poller_write_block_handler,
|
||||
[MfClassicPollerCmdTypeReadValue] = mf_classic_poller_read_value_handler,
|
||||
[MfClassicPollerCmdTypeChangeValue] = mf_classic_poller_change_value_handler,
|
||||
};
|
||||
|
||||
static NfcCommand mf_classic_poller_cmd_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.instance);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_3a);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(context);
|
||||
|
||||
MfClassicPollerContext* poller_context = context;
|
||||
Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
|
||||
Iso14443_3aPoller* iso14443_3a_poller = event.instance;
|
||||
MfClassicPoller* mfc_poller = mf_classic_poller_alloc(iso14443_3a_poller);
|
||||
|
||||
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
|
||||
poller_context->error = mf_classic_poller_cmd_handlers[poller_context->cmd_type](
|
||||
mfc_poller, &poller_context->data);
|
||||
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
|
||||
poller_context->error = mf_classic_process_error(iso14443_3a_event->data->error);
|
||||
}
|
||||
|
||||
furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
|
||||
mf_classic_poller_free(mfc_poller);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
static MfClassicError mf_classic_poller_cmd_execute(Nfc* nfc, MfClassicPollerContext* poller_ctx) {
|
||||
furi_assert(poller_ctx->cmd_type < MfClassicPollerCmdTypeNum);
|
||||
|
||||
poller_ctx->thread_id = furi_thread_get_current_id();
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a);
|
||||
nfc_poller_start(poller, mf_classic_poller_cmd_callback, poller_ctx);
|
||||
furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
return poller_ctx->error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_collect_nt(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt) {
|
||||
furi_assert(nfc);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeCollectNt,
|
||||
.data.collect_nt_context.block = block_num,
|
||||
.data.collect_nt_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
if(nt) {
|
||||
*nt = poller_context.data.collect_nt_context.nt;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_auth(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeAuth,
|
||||
.data.auth_context.block_num = block_num,
|
||||
.data.auth_context.key = *key,
|
||||
.data.auth_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
if(data) {
|
||||
*data = poller_context.data.auth_context;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_read_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(data);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeReadBlock,
|
||||
.data.read_block_context.block_num = block_num,
|
||||
.data.read_block_context.key = *key,
|
||||
.data.read_block_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
*data = poller_context.data.read_block_context.block;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_write_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(data);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeWriteBlock,
|
||||
.data.write_block_context.block_num = block_num,
|
||||
.data.write_block_context.key = *key,
|
||||
.data.write_block_context.key_type = key_type,
|
||||
.data.write_block_context.block = *data,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_read_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t* value) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(value);
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeReadValue,
|
||||
.data.write_block_context.block_num = block_num,
|
||||
.data.write_block_context.key = *key,
|
||||
.data.write_block_context.key_type = key_type,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
*value = poller_context.data.read_value_context.value;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_change_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t data,
|
||||
int32_t* new_value) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(key);
|
||||
furi_assert(new_value);
|
||||
|
||||
MfClassicValueCommand command = MfClassicValueCommandRestore;
|
||||
int32_t command_data = 0;
|
||||
if(data > 0) {
|
||||
command = MfClassicValueCommandIncrement;
|
||||
command_data = data;
|
||||
} else if(data < 0) {
|
||||
command = MfClassicValueCommandDecrement;
|
||||
command_data = -data;
|
||||
}
|
||||
|
||||
MfClassicPollerContext poller_context = {
|
||||
.cmd_type = MfClassicPollerCmdTypeChangeValue,
|
||||
.data.change_value_context.block_num = block_num,
|
||||
.data.change_value_context.key = *key,
|
||||
.data.change_value_context.key_type = key_type,
|
||||
.data.change_value_context.value_cmd = command,
|
||||
.data.change_value_context.data = command_data,
|
||||
};
|
||||
|
||||
MfClassicError error = mf_classic_poller_cmd_execute(nfc, &poller_context);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
*new_value = poller_context.data.change_value_context.new_value;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool mf_classic_poller_read_get_next_key(
|
||||
MfClassicReadContext* read_ctx,
|
||||
uint8_t* sector_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType* key_type) {
|
||||
bool next_key_found = false;
|
||||
|
||||
for(uint8_t i = read_ctx->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) {
|
||||
if(FURI_BIT(read_ctx->keys.key_a_mask, i)) {
|
||||
FURI_BIT_CLEAR(read_ctx->keys.key_a_mask, i);
|
||||
*key = read_ctx->keys.key_a[i];
|
||||
*key_type = MfClassicKeyTypeA;
|
||||
*sector_num = i;
|
||||
|
||||
next_key_found = true;
|
||||
break;
|
||||
}
|
||||
if(FURI_BIT(read_ctx->keys.key_b_mask, i)) {
|
||||
FURI_BIT_CLEAR(read_ctx->keys.key_b_mask, i);
|
||||
*key = read_ctx->keys.key_b[i];
|
||||
*key_type = MfClassicKeyTypeB;
|
||||
*sector_num = i;
|
||||
|
||||
next_key_found = true;
|
||||
read_ctx->current_sector = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next_key_found;
|
||||
}
|
||||
|
||||
NfcCommand mf_classic_poller_read_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolMfClassic);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
MfClassicPollerContext* poller_context = context;
|
||||
MfClassicPollerEvent* mfc_event = event.event_data;
|
||||
|
||||
if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
|
||||
poller_context->error = MfClassicErrorNotPresent;
|
||||
command = NfcCommandStop;
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
|
||||
mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
|
||||
MfClassicPollerEventDataReadSectorRequest* req_data =
|
||||
&mfc_event->data->read_sector_request_data;
|
||||
MfClassicKey key = {};
|
||||
MfClassicKeyType key_type = MfClassicKeyTypeA;
|
||||
uint8_t sector_num = 0;
|
||||
if(mf_classic_poller_read_get_next_key(
|
||||
&poller_context->data.read_context, §or_num, &key, &key_type)) {
|
||||
req_data->sector_num = sector_num;
|
||||
req_data->key = key;
|
||||
req_data->key_type = key_type;
|
||||
req_data->key_provided = true;
|
||||
} else {
|
||||
req_data->key_provided = false;
|
||||
}
|
||||
} else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
if(command == NfcCommandStop) {
|
||||
furi_thread_flags_set(poller_context->thread_id, MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
MfClassicError
|
||||
mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(keys);
|
||||
furi_assert(data);
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
MfClassicPollerContext poller_context = {};
|
||||
poller_context.thread_id = furi_thread_get_current_id();
|
||||
poller_context.data.read_context.keys = *keys;
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfClassic);
|
||||
nfc_poller_start(poller, mf_classic_poller_read_callback, &poller_context);
|
||||
furi_thread_flags_wait(MF_CLASSIC_POLLER_COMPLETE_EVENT, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_thread_flags_clear(MF_CLASSIC_POLLER_COMPLETE_EVENT);
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
|
||||
if(poller_context.error != MfClassicErrorNone) {
|
||||
error = poller_context.error;
|
||||
} else {
|
||||
const MfClassicData* mfc_data = nfc_poller_get_data(poller);
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
|
||||
mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found);
|
||||
if((sectors_read > 0) || (keys_found > 0)) {
|
||||
mf_classic_copy(data, mfc_data);
|
||||
} else {
|
||||
error = MfClassicErrorNotPresent;
|
||||
}
|
||||
}
|
||||
|
||||
nfc_poller_free(poller);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(type);
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
|
||||
const uint8_t mf_classic_verify_block[MfClassicTypeNum] = {
|
||||
[MfClassicTypeMini] = 0,
|
||||
[MfClassicType1k] = 62,
|
||||
[MfClassicType4k] = 254,
|
||||
};
|
||||
|
||||
size_t i = 0;
|
||||
for(i = 0; i < COUNT_OF(mf_classic_verify_block); i++) {
|
||||
error = mf_classic_poller_collect_nt(
|
||||
nfc, mf_classic_verify_block[MfClassicTypeNum - i - 1], MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
*type = MfClassicTypeNum - i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
59
lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h
Normal file
59
lib/nfc/protocols/mf_classic/mf_classic_poller_sync_api.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_classic.h"
|
||||
#include <nfc/nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
MfClassicError mf_classic_poller_collect_nt(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicNt* nt);
|
||||
|
||||
MfClassicError mf_classic_poller_auth(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicAuthContext* data);
|
||||
|
||||
MfClassicError mf_classic_poller_read_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_poller_write_block(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
MfClassicBlock* data);
|
||||
|
||||
MfClassicError mf_classic_poller_read_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t* value);
|
||||
|
||||
MfClassicError mf_classic_poller_change_value(
|
||||
Nfc* nfc,
|
||||
uint8_t block_num,
|
||||
MfClassicKey* key,
|
||||
MfClassicKeyType key_type,
|
||||
int32_t data,
|
||||
int32_t* new_value);
|
||||
|
||||
MfClassicError mf_classic_poller_detect_type(Nfc* nfc, MfClassicType* type);
|
||||
|
||||
MfClassicError
|
||||
mf_classic_poller_read(Nfc* nfc, const MfClassicDeviceKeys* keys, MfClassicData* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
290
lib/nfc/protocols/mf_desfire/mf_desfire.c
Normal file
290
lib/nfc/protocols/mf_desfire/mf_desfire.c
Normal file
@@ -0,0 +1,290 @@
|
||||
#include "mf_desfire_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire"
|
||||
|
||||
const NfcDeviceBase nfc_device_mf_desfire = {
|
||||
.protocol_name = MF_DESFIRE_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)mf_desfire_alloc,
|
||||
.free = (NfcDeviceFree)mf_desfire_free,
|
||||
.reset = (NfcDeviceReset)mf_desfire_reset,
|
||||
.copy = (NfcDeviceCopy)mf_desfire_copy,
|
||||
.verify = (NfcDeviceVerify)mf_desfire_verify,
|
||||
.load = (NfcDeviceLoad)mf_desfire_load,
|
||||
.save = (NfcDeviceSave)mf_desfire_save,
|
||||
.is_equal = (NfcDeviceEqual)mf_desfire_is_equal,
|
||||
.get_name = (NfcDeviceGetName)mf_desfire_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)mf_desfire_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)mf_desfire_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)mf_desfire_get_base_data,
|
||||
};
|
||||
|
||||
MfDesfireData* mf_desfire_alloc() {
|
||||
MfDesfireData* data = malloc(sizeof(MfDesfireData));
|
||||
data->iso14443_4a_data = iso14443_4a_alloc();
|
||||
data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config);
|
||||
data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config);
|
||||
data->applications = simple_array_alloc(&mf_desfire_application_array_config);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void mf_desfire_free(MfDesfireData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
mf_desfire_reset(data);
|
||||
simple_array_free(data->applications);
|
||||
simple_array_free(data->application_ids);
|
||||
simple_array_free(data->master_key_versions);
|
||||
iso14443_4a_free(data->iso14443_4a_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mf_desfire_reset(MfDesfireData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_4a_reset(data->iso14443_4a_data);
|
||||
|
||||
memset(&data->version, 0, sizeof(MfDesfireVersion));
|
||||
memset(&data->free_memory, 0, sizeof(MfDesfireFreeMemory));
|
||||
|
||||
simple_array_reset(data->master_key_versions);
|
||||
simple_array_reset(data->application_ids);
|
||||
simple_array_reset(data->applications);
|
||||
}
|
||||
|
||||
void mf_desfire_copy(MfDesfireData* data, const MfDesfireData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
mf_desfire_reset(data);
|
||||
|
||||
iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data);
|
||||
|
||||
data->version = other->version;
|
||||
data->free_memory = other->free_memory;
|
||||
data->master_key_settings = other->master_key_settings;
|
||||
|
||||
simple_array_copy(data->master_key_versions, other->master_key_versions);
|
||||
simple_array_copy(data->application_ids, other->application_ids);
|
||||
simple_array_copy(data->applications, other->applications);
|
||||
}
|
||||
|
||||
bool mf_desfire_verify(MfDesfireData* data, const FuriString* device_type) {
|
||||
UNUSED(data);
|
||||
return furi_string_equal_str(device_type, MF_DESFIRE_PROTOCOL_NAME);
|
||||
}
|
||||
|
||||
bool mf_desfire_load(MfDesfireData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* prefix = furi_string_alloc();
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break;
|
||||
|
||||
if(!mf_desfire_version_load(&data->version, ff)) break;
|
||||
if(!mf_desfire_free_memory_load(&data->free_memory, ff)) break;
|
||||
if(!mf_desfire_key_settings_load(
|
||||
&data->master_key_settings, MF_DESFIRE_FFF_PICC_PREFIX, ff))
|
||||
break;
|
||||
|
||||
const uint32_t master_key_version_count = data->master_key_settings.max_keys;
|
||||
simple_array_init(data->master_key_versions, master_key_version_count);
|
||||
|
||||
uint32_t i;
|
||||
for(i = 0; i < master_key_version_count; ++i) {
|
||||
if(!mf_desfire_key_version_load(
|
||||
simple_array_get(data->master_key_versions, i),
|
||||
MF_DESFIRE_FFF_PICC_PREFIX,
|
||||
i,
|
||||
ff))
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != master_key_version_count) break;
|
||||
|
||||
uint32_t application_count;
|
||||
if(!mf_desfire_application_count_load(&application_count, ff)) break;
|
||||
|
||||
if(application_count > 0) {
|
||||
simple_array_init(data->application_ids, application_count);
|
||||
if(!mf_desfire_application_ids_load(
|
||||
simple_array_get_data(data->application_ids), application_count, ff))
|
||||
break;
|
||||
|
||||
simple_array_init(data->applications, application_count);
|
||||
for(i = 0; i < application_count; ++i) {
|
||||
const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i);
|
||||
furi_string_printf(
|
||||
prefix,
|
||||
"%s %02x%02x%02x",
|
||||
MF_DESFIRE_FFF_APP_PREFIX,
|
||||
app_id->data[0],
|
||||
app_id->data[1],
|
||||
app_id->data[2]);
|
||||
|
||||
if(!mf_desfire_application_load(
|
||||
simple_array_get(data->applications, i), furi_string_get_cstr(prefix), ff))
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != application_count) break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(prefix);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_save(const MfDesfireData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* prefix = furi_string_alloc();
|
||||
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(ff, MF_DESFIRE_PROTOCOL_NAME " specific data"))
|
||||
break;
|
||||
if(!mf_desfire_version_save(&data->version, ff)) break;
|
||||
if(!mf_desfire_free_memory_save(&data->free_memory, ff)) break;
|
||||
if(!mf_desfire_key_settings_save(
|
||||
&data->master_key_settings, MF_DESFIRE_FFF_PICC_PREFIX, ff))
|
||||
break;
|
||||
|
||||
const uint32_t master_key_version_count =
|
||||
simple_array_get_count(data->master_key_versions);
|
||||
|
||||
uint32_t i;
|
||||
for(i = 0; i < master_key_version_count; ++i) {
|
||||
if(!mf_desfire_key_version_save(
|
||||
simple_array_cget(data->master_key_versions, i),
|
||||
MF_DESFIRE_FFF_PICC_PREFIX,
|
||||
i,
|
||||
ff))
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != master_key_version_count) break;
|
||||
|
||||
const uint32_t application_count = simple_array_get_count(data->application_ids);
|
||||
if(!mf_desfire_application_count_save(&application_count, ff)) break;
|
||||
|
||||
if(application_count > 0) {
|
||||
if(!mf_desfire_application_ids_save(
|
||||
simple_array_cget_data(data->application_ids), application_count, ff))
|
||||
break;
|
||||
|
||||
for(i = 0; i < application_count; ++i) {
|
||||
const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i);
|
||||
furi_string_printf(
|
||||
prefix,
|
||||
"%s %02x%02x%02x",
|
||||
MF_DESFIRE_FFF_APP_PREFIX,
|
||||
app_id->data[0],
|
||||
app_id->data[1],
|
||||
app_id->data[2]);
|
||||
|
||||
const MfDesfireApplication* app = simple_array_cget(data->applications, i);
|
||||
if(!mf_desfire_application_save(app, furi_string_get_cstr(prefix), ff)) break;
|
||||
}
|
||||
|
||||
if(i != application_count) break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(prefix);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) &&
|
||||
memcmp(&data->version, &other->version, sizeof(MfDesfireVersion)) == 0 &&
|
||||
memcmp(&data->free_memory, &other->free_memory, sizeof(MfDesfireFreeMemory)) == 0 &&
|
||||
memcmp(
|
||||
&data->master_key_settings,
|
||||
&other->master_key_settings,
|
||||
sizeof(MfDesfireKeySettings)) == 0 &&
|
||||
simple_array_is_equal(data->master_key_versions, other->master_key_versions) &&
|
||||
simple_array_is_equal(data->application_ids, other->application_ids) &&
|
||||
simple_array_is_equal(data->applications, other->applications);
|
||||
}
|
||||
|
||||
const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) {
|
||||
UNUSED(data);
|
||||
UNUSED(name_type);
|
||||
return MF_DESFIRE_PROTOCOL_NAME;
|
||||
}
|
||||
|
||||
const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len);
|
||||
}
|
||||
|
||||
bool mf_desfire_set_uid(MfDesfireData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len);
|
||||
}
|
||||
|
||||
Iso14443_4aData* mf_desfire_get_base_data(const MfDesfireData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->iso14443_4a_data;
|
||||
}
|
||||
|
||||
const MfDesfireApplication*
|
||||
mf_desfire_get_application(const MfDesfireData* data, const MfDesfireApplicationId* app_id) {
|
||||
MfDesfireApplication* app = NULL;
|
||||
|
||||
for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) {
|
||||
const MfDesfireApplicationId* current_app_id = simple_array_cget(data->application_ids, i);
|
||||
if(memcmp(app_id, current_app_id, sizeof(MfDesfireApplicationId)) == 0) {
|
||||
app = simple_array_get(data->applications, i);
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
const MfDesfireFileSettings*
|
||||
mf_desfire_get_file_settings(const MfDesfireApplication* data, const MfDesfireFileId* file_id) {
|
||||
MfDesfireFileSettings* file_settings = NULL;
|
||||
|
||||
for(uint32_t i = 0; i < simple_array_get_count(data->file_ids); ++i) {
|
||||
const MfDesfireFileId* current_file_id = simple_array_cget(data->file_ids, i);
|
||||
if(*file_id == *current_file_id) {
|
||||
file_settings = simple_array_get(data->file_settings, i);
|
||||
}
|
||||
}
|
||||
|
||||
return file_settings;
|
||||
}
|
||||
|
||||
const MfDesfireFileData*
|
||||
mf_desfire_get_file_data(const MfDesfireApplication* data, const MfDesfireFileId* file_id) {
|
||||
MfDesfireFileData* file_data = NULL;
|
||||
|
||||
for(uint32_t i = 0; i < simple_array_get_count(data->file_ids); ++i) {
|
||||
const MfDesfireFileId* current_file_id = simple_array_cget(data->file_ids, i);
|
||||
if(*file_id == *current_file_id) {
|
||||
file_data = simple_array_get(data->file_data, i);
|
||||
}
|
||||
}
|
||||
|
||||
return file_data;
|
||||
}
|
||||
191
lib/nfc/protocols/mf_desfire/mf_desfire.h
Normal file
191
lib/nfc/protocols/mf_desfire/mf_desfire.h
Normal file
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a.h>
|
||||
|
||||
#include <lib/toolbox/simple_array.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MF_DESFIRE_CMD_GET_VERSION (0x60)
|
||||
#define MF_DESFIRE_CMD_GET_FREE_MEMORY (0x6E)
|
||||
#define MF_DESFIRE_CMD_GET_KEY_SETTINGS (0x45)
|
||||
#define MF_DESFIRE_CMD_GET_KEY_VERSION (0x64)
|
||||
#define MF_DESFIRE_CMD_GET_APPLICATION_IDS (0x6A)
|
||||
#define MF_DESFIRE_CMD_SELECT_APPLICATION (0x5A)
|
||||
#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F)
|
||||
#define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5)
|
||||
|
||||
#define MF_DESFIRE_CMD_READ_DATA (0xBD)
|
||||
#define MF_DESFIRE_CMD_GET_VALUE (0x6C)
|
||||
#define MF_DESFIRE_CMD_READ_RECORDS (0xBB)
|
||||
|
||||
#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF)
|
||||
|
||||
#define MF_DESFIRE_MAX_KEYS (14)
|
||||
#define MF_DESFIRE_MAX_FILES (32)
|
||||
|
||||
#define MF_DESFIRE_UID_SIZE (7)
|
||||
#define MF_DESFIRE_BATCH_SIZE (5)
|
||||
#define MF_DESFIRE_APP_ID_SIZE (3)
|
||||
|
||||
typedef struct {
|
||||
uint8_t hw_vendor;
|
||||
uint8_t hw_type;
|
||||
uint8_t hw_subtype;
|
||||
uint8_t hw_major;
|
||||
uint8_t hw_minor;
|
||||
uint8_t hw_storage;
|
||||
uint8_t hw_proto;
|
||||
|
||||
uint8_t sw_vendor;
|
||||
uint8_t sw_type;
|
||||
uint8_t sw_subtype;
|
||||
uint8_t sw_major;
|
||||
uint8_t sw_minor;
|
||||
uint8_t sw_storage;
|
||||
uint8_t sw_proto;
|
||||
|
||||
uint8_t uid[MF_DESFIRE_UID_SIZE];
|
||||
uint8_t batch[MF_DESFIRE_BATCH_SIZE];
|
||||
uint8_t prod_week;
|
||||
uint8_t prod_year;
|
||||
} MfDesfireVersion;
|
||||
|
||||
typedef struct {
|
||||
uint32_t bytes_free;
|
||||
bool is_present;
|
||||
} MfDesfireFreeMemory; // EV1+ only
|
||||
|
||||
typedef struct {
|
||||
bool is_master_key_changeable;
|
||||
bool is_free_directory_list;
|
||||
bool is_free_create_delete;
|
||||
bool is_config_changeable;
|
||||
uint8_t change_key_id;
|
||||
uint8_t max_keys;
|
||||
uint8_t flags;
|
||||
} MfDesfireKeySettings;
|
||||
|
||||
typedef uint8_t MfDesfireKeyVersion;
|
||||
|
||||
typedef struct {
|
||||
MfDesfireKeySettings key_settings;
|
||||
SimpleArray* key_versions;
|
||||
} MfDesfireKeyConfiguration;
|
||||
|
||||
typedef enum {
|
||||
MfDesfireFileTypeStandard = 0,
|
||||
MfDesfireFileTypeBackup = 1,
|
||||
MfDesfireFileTypeValue = 2,
|
||||
MfDesfireFileTypeLinearRecord = 3,
|
||||
MfDesfireFileTypeCyclicRecord = 4,
|
||||
} MfDesfireFileType;
|
||||
|
||||
typedef enum {
|
||||
MfDesfireFileCommunicationSettingsPlaintext = 0,
|
||||
MfDesfireFileCommunicationSettingsAuthenticated = 1,
|
||||
MfDesfireFileCommunicationSettingsEnciphered = 3,
|
||||
} MfDesfireFileCommunicationSettings;
|
||||
|
||||
typedef uint8_t MfDesfireFileId;
|
||||
typedef uint16_t MfDesfireFileAccessRights;
|
||||
|
||||
typedef struct {
|
||||
MfDesfireFileType type;
|
||||
MfDesfireFileCommunicationSettings comm;
|
||||
MfDesfireFileAccessRights access_rights;
|
||||
union {
|
||||
struct {
|
||||
uint32_t size;
|
||||
} data;
|
||||
struct {
|
||||
uint32_t lo_limit;
|
||||
uint32_t hi_limit;
|
||||
uint32_t limited_credit_value;
|
||||
bool limited_credit_enabled;
|
||||
} value;
|
||||
struct {
|
||||
uint32_t size;
|
||||
uint32_t max;
|
||||
uint32_t cur;
|
||||
} record;
|
||||
};
|
||||
} MfDesfireFileSettings;
|
||||
|
||||
typedef struct {
|
||||
SimpleArray* data;
|
||||
} MfDesfireFileData;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MF_DESFIRE_APP_ID_SIZE];
|
||||
} MfDesfireApplicationId;
|
||||
|
||||
typedef struct MfDesfireApplication {
|
||||
MfDesfireKeySettings key_settings;
|
||||
SimpleArray* key_versions;
|
||||
SimpleArray* file_ids;
|
||||
SimpleArray* file_settings;
|
||||
SimpleArray* file_data;
|
||||
} MfDesfireApplication;
|
||||
|
||||
typedef enum {
|
||||
MfDesfireErrorNone,
|
||||
MfDesfireErrorNotPresent,
|
||||
MfDesfireErrorProtocol,
|
||||
MfDesfireErrorTimeout,
|
||||
} MfDesfireError;
|
||||
|
||||
typedef struct {
|
||||
Iso14443_4aData* iso14443_4a_data;
|
||||
MfDesfireVersion version;
|
||||
MfDesfireFreeMemory free_memory;
|
||||
MfDesfireKeySettings master_key_settings;
|
||||
SimpleArray* master_key_versions;
|
||||
SimpleArray* application_ids;
|
||||
SimpleArray* applications;
|
||||
} MfDesfireData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_mf_desfire;
|
||||
|
||||
// Virtual methods
|
||||
|
||||
MfDesfireData* mf_desfire_alloc();
|
||||
|
||||
void mf_desfire_free(MfDesfireData* data);
|
||||
|
||||
void mf_desfire_reset(MfDesfireData* data);
|
||||
|
||||
void mf_desfire_copy(MfDesfireData* data, const MfDesfireData* other);
|
||||
|
||||
bool mf_desfire_verify(MfDesfireData* data, const FuriString* device_type);
|
||||
|
||||
bool mf_desfire_load(MfDesfireData* data, FlipperFormat* ff, uint32_t version);
|
||||
|
||||
bool mf_desfire_save(const MfDesfireData* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other);
|
||||
|
||||
const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type);
|
||||
|
||||
const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len);
|
||||
|
||||
bool mf_desfire_set_uid(MfDesfireData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
Iso14443_4aData* mf_desfire_get_base_data(const MfDesfireData* data);
|
||||
|
||||
// Getters and tests
|
||||
|
||||
const MfDesfireApplication*
|
||||
mf_desfire_get_application(const MfDesfireData* data, const MfDesfireApplicationId* app_id);
|
||||
|
||||
const MfDesfireFileSettings*
|
||||
mf_desfire_get_file_settings(const MfDesfireApplication* data, const MfDesfireFileId* file_id);
|
||||
|
||||
const MfDesfireFileData*
|
||||
mf_desfire_get_file_data(const MfDesfireApplication* data, const MfDesfireFileId* file_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
790
lib/nfc/protocols/mf_desfire/mf_desfire_i.c
Normal file
790
lib/nfc/protocols/mf_desfire/mf_desfire_i.c
Normal file
@@ -0,0 +1,790 @@
|
||||
#include "mf_desfire_i.h"
|
||||
|
||||
#define BITS_IN_BYTE (8U)
|
||||
|
||||
#define MF_DESFIRE_FFF_VERSION_KEY \
|
||||
MF_DESFIRE_FFF_PICC_PREFIX " " \
|
||||
"Version"
|
||||
#define MF_DESFIRE_FFF_FREE_MEM_KEY \
|
||||
MF_DESFIRE_FFF_PICC_PREFIX " " \
|
||||
"Free Memory"
|
||||
|
||||
#define MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY "Change Key ID"
|
||||
#define MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY "Config Changeable"
|
||||
#define MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY "Free Create Delete"
|
||||
#define MF_DESFIRE_FFF_FREE_DIR_LIST_KEY "Free Directory List"
|
||||
#define MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY "Key Changeable"
|
||||
#define MF_DESFIRE_FFF_FLAGS_KEY "Flags"
|
||||
#define MF_DESFIRE_FFF_MAX_KEYS_KEY "Max Keys"
|
||||
|
||||
#define MF_DESFIRE_FFF_KEY_SUB_PREFIX "Key"
|
||||
#define MF_DESFIRE_FFF_KEY_VERSION_KEY "Version"
|
||||
|
||||
#define MF_DESFIRE_FFF_APPLICATION_COUNT_KEY \
|
||||
MF_DESFIRE_FFF_APP_PREFIX " " \
|
||||
"Count"
|
||||
#define MF_DESFIRE_FFF_APPLICATION_IDS_KEY \
|
||||
MF_DESFIRE_FFF_APP_PREFIX " " \
|
||||
"IDs"
|
||||
|
||||
#define MF_DESFIRE_FFF_FILE_SUB_PREFIX "File"
|
||||
#define MF_DESFIRE_FFF_FILE_IDS_KEY \
|
||||
MF_DESFIRE_FFF_FILE_SUB_PREFIX " " \
|
||||
"IDs"
|
||||
#define MF_DESFIRE_FFF_FILE_TYPE_KEY "Type"
|
||||
#define MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY "Communication Settings"
|
||||
#define MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY "Access Rights"
|
||||
|
||||
#define MF_DESFIRE_FFF_FILE_SIZE_KEY "Size"
|
||||
|
||||
#define MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY "Hi Limit"
|
||||
#define MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY "Lo Limit"
|
||||
#define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY "Limited Credit Value"
|
||||
#define MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY "Limited Credit Enabled"
|
||||
|
||||
#define MF_DESFIRE_FFF_FILE_MAX_KEY "Max"
|
||||
#define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur"
|
||||
|
||||
bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) {
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t bytes_free : 3 * BITS_IN_BYTE;
|
||||
} MfDesfireFreeMemoryLayout;
|
||||
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireFreeMemoryLayout);
|
||||
|
||||
if(can_parse) {
|
||||
MfDesfireFreeMemoryLayout layout;
|
||||
bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFreeMemoryLayout));
|
||||
data->bytes_free = layout.bytes_free;
|
||||
}
|
||||
|
||||
data->is_present = can_parse;
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) {
|
||||
typedef struct __attribute__((packed)) {
|
||||
bool is_master_key_changeable : 1;
|
||||
bool is_free_directory_list : 1;
|
||||
bool is_free_create_delete : 1;
|
||||
bool is_config_changeable : 1;
|
||||
uint8_t change_key_id : 4;
|
||||
uint8_t max_keys : 4;
|
||||
uint8_t flags : 4;
|
||||
} MfDesfireKeySettingsLayout;
|
||||
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout);
|
||||
|
||||
if(can_parse) {
|
||||
MfDesfireKeySettingsLayout layout;
|
||||
bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireKeySettingsLayout));
|
||||
|
||||
data->is_master_key_changeable = layout.is_master_key_changeable;
|
||||
data->is_free_directory_list = layout.is_free_directory_list;
|
||||
data->is_free_create_delete = layout.is_free_create_delete;
|
||||
data->is_config_changeable = layout.is_config_changeable;
|
||||
|
||||
data->change_key_id = layout.change_key_id;
|
||||
data->max_keys = layout.max_keys;
|
||||
data->flags = layout.flags;
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion);
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes(buf, data, sizeof(MfDesfireKeyVersion));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_desfire_application_id_parse(
|
||||
MfDesfireApplicationId* data,
|
||||
uint32_t index,
|
||||
const BitBuffer* buf) {
|
||||
const bool can_parse =
|
||||
bit_buffer_get_size_bytes(buf) >=
|
||||
(index * sizeof(MfDesfireApplicationId) + sizeof(MfDesfireApplicationId));
|
||||
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes_mid(
|
||||
buf, data, index * sizeof(MfDesfireApplicationId), sizeof(MfDesfireApplicationId));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf) {
|
||||
const bool can_parse = bit_buffer_get_size_bytes(buf) >=
|
||||
(index * sizeof(MfDesfireFileId) + sizeof(MfDesfireFileId));
|
||||
if(can_parse) {
|
||||
bit_buffer_write_bytes_mid(
|
||||
buf, data, index * sizeof(MfDesfireFileId), sizeof(MfDesfireFileId));
|
||||
}
|
||||
|
||||
return can_parse;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf) {
|
||||
bool parsed = false;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t type;
|
||||
uint8_t comm;
|
||||
uint16_t access_rights;
|
||||
} MfDesfireFileSettingsHeader;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t size : 3 * BITS_IN_BYTE;
|
||||
} MfDesfireFileSettingsData;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t lo_limit;
|
||||
uint32_t hi_limit;
|
||||
uint32_t limited_credit_value;
|
||||
uint8_t limited_credit_enabled;
|
||||
} MfDesfireFileSettingsValue;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t size : 3 * BITS_IN_BYTE;
|
||||
uint32_t max : 3 * BITS_IN_BYTE;
|
||||
uint32_t cur : 3 * BITS_IN_BYTE;
|
||||
} MfDesfireFileSettingsRecord;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
MfDesfireFileSettingsHeader header;
|
||||
union {
|
||||
MfDesfireFileSettingsData data;
|
||||
MfDesfireFileSettingsValue value;
|
||||
MfDesfireFileSettingsRecord record;
|
||||
};
|
||||
} MfDesfireFileSettingsLayout;
|
||||
|
||||
do {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
const size_t min_data_size =
|
||||
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData);
|
||||
|
||||
if(data_size < min_data_size) break;
|
||||
|
||||
MfDesfireFileSettingsLayout layout;
|
||||
bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout));
|
||||
|
||||
data->type = layout.header.type;
|
||||
data->comm = layout.header.comm;
|
||||
data->access_rights = layout.header.access_rights;
|
||||
|
||||
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
|
||||
if(data_size != min_data_size) break;
|
||||
|
||||
data->data.size = layout.data.size;
|
||||
|
||||
} else if(data->type == MfDesfireFileTypeValue) {
|
||||
if(data_size !=
|
||||
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue))
|
||||
break;
|
||||
|
||||
data->value.lo_limit = layout.value.lo_limit;
|
||||
data->value.hi_limit = layout.value.hi_limit;
|
||||
data->value.limited_credit_value = layout.value.hi_limit;
|
||||
data->value.limited_credit_enabled = layout.value.limited_credit_enabled;
|
||||
|
||||
} else if(
|
||||
data->type == MfDesfireFileTypeLinearRecord ||
|
||||
data->type == MfDesfireFileTypeCyclicRecord) {
|
||||
if(data_size !=
|
||||
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord))
|
||||
break;
|
||||
|
||||
data->record.size = layout.record.size;
|
||||
data->record.max = layout.record.max;
|
||||
data->record.cur = layout.record.cur;
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
|
||||
if(data_size > 0) {
|
||||
simple_array_init(data->data, data_size);
|
||||
bit_buffer_write_bytes(buf, simple_array_get_data(data->data), data_size);
|
||||
}
|
||||
|
||||
// Success no matter whether there is data or not
|
||||
return true;
|
||||
}
|
||||
|
||||
void mf_desfire_file_data_init(MfDesfireFileData* data) {
|
||||
data->data = simple_array_alloc(&simple_array_config_uint8_t);
|
||||
}
|
||||
|
||||
void mf_desfire_application_init(MfDesfireApplication* data) {
|
||||
data->key_versions = simple_array_alloc(&mf_desfire_key_version_array_config);
|
||||
data->file_ids = simple_array_alloc(&mf_desfire_file_id_array_config);
|
||||
data->file_settings = simple_array_alloc(&mf_desfire_file_settings_array_config);
|
||||
data->file_data = simple_array_alloc(&mf_desfire_file_data_array_config);
|
||||
}
|
||||
|
||||
void mf_desfire_file_data_reset(MfDesfireFileData* data) {
|
||||
simple_array_free(data->data);
|
||||
memset(data, 0, sizeof(MfDesfireFileData));
|
||||
}
|
||||
|
||||
void mf_desfire_application_reset(MfDesfireApplication* data) {
|
||||
simple_array_free(data->key_versions);
|
||||
simple_array_free(data->file_ids);
|
||||
simple_array_free(data->file_settings);
|
||||
simple_array_free(data->file_data);
|
||||
memset(data, 0, sizeof(MfDesfireApplication));
|
||||
}
|
||||
|
||||
void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other) {
|
||||
simple_array_copy(data->data, other->data);
|
||||
}
|
||||
|
||||
void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other) {
|
||||
data->key_settings = other->key_settings;
|
||||
simple_array_copy(data->key_versions, other->key_versions);
|
||||
simple_array_copy(data->file_ids, other->file_ids);
|
||||
simple_array_copy(data->file_settings, other->file_settings);
|
||||
simple_array_copy(data->file_data, other->file_data);
|
||||
}
|
||||
|
||||
bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff) {
|
||||
return flipper_format_read_hex(
|
||||
ff, MF_DESFIRE_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfDesfireVersion));
|
||||
}
|
||||
|
||||
bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff) {
|
||||
data->is_present = flipper_format_key_exist(ff, MF_DESFIRE_FFF_FREE_MEM_KEY);
|
||||
return data->is_present ?
|
||||
flipper_format_read_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) :
|
||||
true;
|
||||
}
|
||||
|
||||
bool mf_desfire_key_settings_load(
|
||||
MfDesfireKeySettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
bool success = false;
|
||||
|
||||
FuriString* key = furi_string_alloc();
|
||||
|
||||
do {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY);
|
||||
if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1)) break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY);
|
||||
if(!flipper_format_read_bool(ff, furi_string_get_cstr(key), &data->is_config_changeable, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY);
|
||||
if(!flipper_format_read_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY);
|
||||
if(!flipper_format_read_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY);
|
||||
if(!flipper_format_read_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY);
|
||||
if(flipper_format_key_exist(ff, furi_string_get_cstr(key))) {
|
||||
if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break;
|
||||
}
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY);
|
||||
if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_key_version_load(
|
||||
MfDesfireKeyVersion* data,
|
||||
const char* prefix,
|
||||
uint32_t index,
|
||||
FlipperFormat* ff) {
|
||||
FuriString* key = furi_string_alloc_printf(
|
||||
"%s %s %lu %s",
|
||||
prefix,
|
||||
MF_DESFIRE_FFF_KEY_SUB_PREFIX,
|
||||
index,
|
||||
MF_DESFIRE_FFF_KEY_VERSION_KEY);
|
||||
const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, 1);
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff) {
|
||||
FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY);
|
||||
const bool success = flipper_format_get_value_count(ff, furi_string_get_cstr(key), data);
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_ids_load(
|
||||
MfDesfireFileId* data,
|
||||
uint32_t count,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY);
|
||||
const bool success = flipper_format_read_hex(ff, furi_string_get_cstr(key), data, count);
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_settings_load(
|
||||
MfDesfireFileSettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
bool success = false;
|
||||
|
||||
FuriString* key = furi_string_alloc();
|
||||
|
||||
do {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY);
|
||||
if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->type, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY);
|
||||
if(!flipper_format_read_hex(ff, furi_string_get_cstr(key), (uint8_t*)&data->comm, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(key),
|
||||
(uint8_t*)&data->access_rights,
|
||||
sizeof(MfDesfireFileAccessRights)))
|
||||
break;
|
||||
|
||||
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
|
||||
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1))
|
||||
break;
|
||||
|
||||
} else if(data->type == MfDesfireFileTypeValue) {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY);
|
||||
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY);
|
||||
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.lo_limit, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY);
|
||||
if(!flipper_format_read_uint32(
|
||||
ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY);
|
||||
if(!flipper_format_read_bool(
|
||||
ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1))
|
||||
break;
|
||||
} else if(
|
||||
data->type == MfDesfireFileTypeLinearRecord ||
|
||||
data->type == MfDesfireFileTypeCyclicRecord) {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
|
||||
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY);
|
||||
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY);
|
||||
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1))
|
||||
break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(key);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff) {
|
||||
bool success = false;
|
||||
do {
|
||||
if(!flipper_format_key_exist(ff, prefix)) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t data_size;
|
||||
if(!flipper_format_get_value_count(ff, prefix, &data_size)) break;
|
||||
|
||||
simple_array_init(data->data, data_size);
|
||||
|
||||
if(!flipper_format_read_hex(ff, prefix, simple_array_get_data(data->data), data_size))
|
||||
break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff) {
|
||||
return flipper_format_read_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1);
|
||||
}
|
||||
|
||||
bool mf_desfire_application_ids_load(
|
||||
MfDesfireApplicationId* data,
|
||||
uint32_t count,
|
||||
FlipperFormat* ff) {
|
||||
return flipper_format_read_hex(
|
||||
ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId));
|
||||
}
|
||||
|
||||
bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff) {
|
||||
FuriString* sub_prefix = furi_string_alloc();
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!mf_desfire_key_settings_load(&data->key_settings, prefix, ff)) break;
|
||||
|
||||
const uint32_t key_version_count = data->key_settings.max_keys;
|
||||
simple_array_init(data->key_versions, key_version_count);
|
||||
|
||||
uint32_t i;
|
||||
for(i = 0; i < key_version_count; ++i) {
|
||||
if(!mf_desfire_key_version_load(simple_array_get(data->key_versions, i), prefix, i, ff))
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != key_version_count) break;
|
||||
|
||||
uint32_t file_count;
|
||||
if(!mf_desfire_file_count_load(&file_count, prefix, ff)) break;
|
||||
|
||||
simple_array_init(data->file_ids, file_count);
|
||||
if(!mf_desfire_file_ids_load(simple_array_get_data(data->file_ids), file_count, prefix, ff))
|
||||
break;
|
||||
|
||||
simple_array_init(data->file_settings, file_count);
|
||||
simple_array_init(data->file_data, file_count);
|
||||
|
||||
for(i = 0; i < file_count; ++i) {
|
||||
const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i);
|
||||
furi_string_printf(
|
||||
sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id);
|
||||
|
||||
MfDesfireFileSettings* file_settings = simple_array_get(data->file_settings, i);
|
||||
if(!mf_desfire_file_settings_load(file_settings, furi_string_get_cstr(sub_prefix), ff))
|
||||
break;
|
||||
|
||||
MfDesfireFileData* file_data = simple_array_get(data->file_data, i);
|
||||
if(!mf_desfire_file_data_load(file_data, furi_string_get_cstr(sub_prefix), ff)) break;
|
||||
}
|
||||
|
||||
if(i != file_count) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(sub_prefix);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff) {
|
||||
return flipper_format_write_hex(
|
||||
ff, MF_DESFIRE_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfDesfireVersion));
|
||||
}
|
||||
|
||||
bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff) {
|
||||
return data->is_present ?
|
||||
flipper_format_write_uint32(ff, MF_DESFIRE_FFF_FREE_MEM_KEY, &data->bytes_free, 1) :
|
||||
true;
|
||||
}
|
||||
|
||||
bool mf_desfire_key_settings_save(
|
||||
const MfDesfireKeySettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
bool success = false;
|
||||
|
||||
FuriString* key = furi_string_alloc();
|
||||
|
||||
do {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CHANGE_KEY_ID_KEY);
|
||||
if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->change_key_id, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_CONFIG_CHANGEABLE_KEY);
|
||||
if(!flipper_format_write_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_config_changeable, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_CREATE_DELETE_KEY);
|
||||
if(!flipper_format_write_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_free_create_delete, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FREE_DIR_LIST_KEY);
|
||||
if(!flipper_format_write_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_free_directory_list, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_KEY_CHANGEABLE_KEY);
|
||||
if(!flipper_format_write_bool(
|
||||
ff, furi_string_get_cstr(key), &data->is_master_key_changeable, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FLAGS_KEY);
|
||||
if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->flags, 1)) break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_MAX_KEYS_KEY);
|
||||
if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), &data->max_keys, 1)) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_key_version_save(
|
||||
const MfDesfireKeyVersion* data,
|
||||
const char* prefix,
|
||||
uint32_t index,
|
||||
FlipperFormat* ff) {
|
||||
FuriString* key = furi_string_alloc_printf(
|
||||
"%s %s %lu %s",
|
||||
prefix,
|
||||
MF_DESFIRE_FFF_KEY_SUB_PREFIX,
|
||||
index,
|
||||
MF_DESFIRE_FFF_KEY_VERSION_KEY);
|
||||
const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, 1);
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_ids_save(
|
||||
const MfDesfireFileId* data,
|
||||
uint32_t count,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
FuriString* key = furi_string_alloc_printf("%s %s", prefix, MF_DESFIRE_FFF_FILE_IDS_KEY);
|
||||
const bool success = flipper_format_write_hex(ff, furi_string_get_cstr(key), data, count);
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_settings_save(
|
||||
const MfDesfireFileSettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
bool success = false;
|
||||
|
||||
FuriString* key = furi_string_alloc();
|
||||
|
||||
do {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_TYPE_KEY);
|
||||
if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->type, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COMM_SETTINGS_KEY);
|
||||
if(!flipper_format_write_hex(ff, furi_string_get_cstr(key), (const uint8_t*)&data->comm, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(key),
|
||||
(const uint8_t*)&data->access_rights,
|
||||
sizeof(MfDesfireFileAccessRights)))
|
||||
break;
|
||||
|
||||
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
|
||||
if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1))
|
||||
break;
|
||||
|
||||
} else if(data->type == MfDesfireFileTypeValue) {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY);
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, furi_string_get_cstr(key), &data->value.hi_limit, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LO_LIMIT_KEY);
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, furi_string_get_cstr(key), &data->value.lo_limit, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_VALUE_KEY);
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, furi_string_get_cstr(key), &data->value.limited_credit_value, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_LIMIT_CREDIT_ENABLED_KEY);
|
||||
if(!flipper_format_write_bool(
|
||||
ff, furi_string_get_cstr(key), &data->value.limited_credit_enabled, 1))
|
||||
break;
|
||||
} else if(
|
||||
data->type == MfDesfireFileTypeLinearRecord ||
|
||||
data->type == MfDesfireFileTypeCyclicRecord) {
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY);
|
||||
if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.size, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_MAX_KEY);
|
||||
if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.max, 1))
|
||||
break;
|
||||
|
||||
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY);
|
||||
if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1))
|
||||
break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(key);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool mf_desfire_file_data_save(
|
||||
const MfDesfireFileData* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
const uint32_t data_size = simple_array_get_count(data->data);
|
||||
return data_size > 0 ? flipper_format_write_hex(
|
||||
ff, prefix, simple_array_cget_data(data->data), data_size) :
|
||||
true;
|
||||
}
|
||||
|
||||
bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff) {
|
||||
return flipper_format_write_uint32(ff, MF_DESFIRE_FFF_APPLICATION_COUNT_KEY, data, 1);
|
||||
}
|
||||
|
||||
bool mf_desfire_application_ids_save(
|
||||
const MfDesfireApplicationId* data,
|
||||
uint32_t count,
|
||||
FlipperFormat* ff) {
|
||||
return flipper_format_write_hex(
|
||||
ff, MF_DESFIRE_FFF_APPLICATION_IDS_KEY, data->data, count * sizeof(MfDesfireApplicationId));
|
||||
}
|
||||
|
||||
bool mf_desfire_application_save(
|
||||
const MfDesfireApplication* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff) {
|
||||
FuriString* sub_prefix = furi_string_alloc();
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!mf_desfire_key_settings_save(&data->key_settings, prefix, ff)) break;
|
||||
|
||||
const uint32_t key_version_count = data->key_settings.max_keys;
|
||||
|
||||
uint32_t i;
|
||||
for(i = 0; i < key_version_count; ++i) {
|
||||
if(!mf_desfire_key_version_save(
|
||||
simple_array_cget(data->key_versions, i), prefix, i, ff))
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != key_version_count) break;
|
||||
|
||||
const uint32_t file_count = simple_array_get_count(data->file_ids);
|
||||
if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff))
|
||||
break;
|
||||
|
||||
for(i = 0; i < file_count; ++i) {
|
||||
const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i);
|
||||
furi_string_printf(
|
||||
sub_prefix, "%s %s %u", prefix, MF_DESFIRE_FFF_FILE_SUB_PREFIX, *file_id);
|
||||
|
||||
const MfDesfireFileSettings* file_settings = simple_array_cget(data->file_settings, i);
|
||||
if(!mf_desfire_file_settings_save(file_settings, furi_string_get_cstr(sub_prefix), ff))
|
||||
break;
|
||||
|
||||
const MfDesfireFileData* file_data = simple_array_cget(data->file_data, i);
|
||||
if(!mf_desfire_file_data_save(file_data, furi_string_get_cstr(sub_prefix), ff)) break;
|
||||
}
|
||||
|
||||
if(i != file_count) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(sub_prefix);
|
||||
return success;
|
||||
}
|
||||
|
||||
const SimpleArrayConfig mf_desfire_key_version_array_config = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(MfDesfireKeyVersion),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig mf_desfire_app_id_array_config = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(MfDesfireApplicationId),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig mf_desfire_file_id_array_config = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(MfDesfireFileId),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig mf_desfire_file_settings_array_config = {
|
||||
.init = NULL,
|
||||
.copy = NULL,
|
||||
.reset = NULL,
|
||||
.type_size = sizeof(MfDesfireFileSettings),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig mf_desfire_file_data_array_config = {
|
||||
.init = (SimpleArrayInit)mf_desfire_file_data_init,
|
||||
.copy = (SimpleArrayCopy)mf_desfire_file_data_copy,
|
||||
.reset = (SimpleArrayReset)mf_desfire_file_data_reset,
|
||||
.type_size = sizeof(MfDesfireData),
|
||||
};
|
||||
|
||||
const SimpleArrayConfig mf_desfire_application_array_config = {
|
||||
.init = (SimpleArrayInit)mf_desfire_application_init,
|
||||
.copy = (SimpleArrayCopy)mf_desfire_application_copy,
|
||||
.reset = (SimpleArrayReset)mf_desfire_application_reset,
|
||||
.type_size = sizeof(MfDesfireApplication),
|
||||
};
|
||||
140
lib/nfc/protocols/mf_desfire/mf_desfire_i.h
Normal file
140
lib/nfc/protocols/mf_desfire/mf_desfire_i.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_desfire.h"
|
||||
|
||||
#define MF_DESFIRE_FFF_PICC_PREFIX "PICC"
|
||||
#define MF_DESFIRE_FFF_APP_PREFIX "Application"
|
||||
|
||||
// SimpleArray configurations
|
||||
|
||||
extern const SimpleArrayConfig mf_desfire_key_version_array_config;
|
||||
extern const SimpleArrayConfig mf_desfire_app_id_array_config;
|
||||
extern const SimpleArrayConfig mf_desfire_file_id_array_config;
|
||||
extern const SimpleArrayConfig mf_desfire_file_settings_array_config;
|
||||
extern const SimpleArrayConfig mf_desfire_file_data_array_config;
|
||||
extern const SimpleArrayConfig mf_desfire_application_array_config;
|
||||
|
||||
// Parse internal MfDesfire structures
|
||||
|
||||
bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_application_id_parse(
|
||||
MfDesfireApplicationId* data,
|
||||
uint32_t index,
|
||||
const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_file_id_parse(MfDesfireFileId* data, uint32_t index, const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer* buf);
|
||||
|
||||
bool mf_desfire_file_data_parse(MfDesfireFileData* data, const BitBuffer* buf);
|
||||
|
||||
// Init internal MfDesfire structures
|
||||
|
||||
void mf_desfire_file_data_init(MfDesfireFileData* data);
|
||||
|
||||
void mf_desfire_application_init(MfDesfireApplication* data);
|
||||
|
||||
// Reset internal MfDesfire structures
|
||||
|
||||
void mf_desfire_file_data_reset(MfDesfireFileData* data);
|
||||
|
||||
void mf_desfire_application_reset(MfDesfireApplication* data);
|
||||
|
||||
// Copy internal MfDesfire structures
|
||||
|
||||
void mf_desfire_file_data_copy(MfDesfireFileData* data, const MfDesfireFileData* other);
|
||||
|
||||
void mf_desfire_application_copy(MfDesfireApplication* data, const MfDesfireApplication* other);
|
||||
|
||||
// Load internal MfDesfire structures
|
||||
|
||||
bool mf_desfire_version_load(MfDesfireVersion* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_free_memory_load(MfDesfireFreeMemory* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_key_settings_load(
|
||||
MfDesfireKeySettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_key_version_load(
|
||||
MfDesfireKeyVersion* data,
|
||||
const char* prefix,
|
||||
uint32_t index,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_count_load(uint32_t* data, const char* prefix, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_ids_load(
|
||||
MfDesfireFileId* data,
|
||||
uint32_t count,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_settings_load(
|
||||
MfDesfireFileSettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_data_load(MfDesfireFileData* data, const char* prefix, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_application_count_load(uint32_t* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_application_ids_load(
|
||||
MfDesfireApplicationId* data,
|
||||
uint32_t count,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_application_load(MfDesfireApplication* data, const char* prefix, FlipperFormat* ff);
|
||||
|
||||
// Save internal MFDesfire structures
|
||||
|
||||
bool mf_desfire_version_save(const MfDesfireVersion* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_free_memory_save(const MfDesfireFreeMemory* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_key_settings_save(
|
||||
const MfDesfireKeySettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_key_version_save(
|
||||
const MfDesfireKeyVersion* data,
|
||||
const char* prefix,
|
||||
uint32_t index,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_ids_save(
|
||||
const MfDesfireFileId* data,
|
||||
uint32_t count,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_settings_save(
|
||||
const MfDesfireFileSettings* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_file_data_save(
|
||||
const MfDesfireFileData* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_application_count_save(const uint32_t* data, FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_application_ids_save(
|
||||
const MfDesfireApplicationId* data,
|
||||
uint32_t count,
|
||||
FlipperFormat* ff);
|
||||
|
||||
bool mf_desfire_application_save(
|
||||
const MfDesfireApplication* data,
|
||||
const char* prefix,
|
||||
FlipperFormat* ff);
|
||||
243
lib/nfc/protocols/mf_desfire/mf_desfire_poller.c
Normal file
243
lib/nfc/protocols/mf_desfire/mf_desfire_poller.c
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "mf_desfire_poller_i.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "MfDesfirePoller"
|
||||
|
||||
#define MF_DESFIRE_BUF_SIZE_MAX (64U)
|
||||
|
||||
typedef NfcCommand (*MfDesfirePollerReadHandler)(MfDesfirePoller* instance);
|
||||
|
||||
const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
|
||||
static MfDesfirePoller* mf_desfire_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) {
|
||||
MfDesfirePoller* instance = malloc(sizeof(MfDesfirePoller));
|
||||
instance->iso14443_4a_poller = iso14443_4a_poller;
|
||||
instance->data = mf_desfire_alloc();
|
||||
instance->tx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX);
|
||||
instance->rx_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX);
|
||||
instance->input_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX);
|
||||
instance->result_buffer = bit_buffer_alloc(MF_DESFIRE_BUF_SIZE_MAX);
|
||||
|
||||
instance->mf_desfire_event.data = &instance->mf_desfire_event_data;
|
||||
|
||||
instance->general_event.protocol = NfcProtocolMfDesfire;
|
||||
instance->general_event.event_data = &instance->mf_desfire_event;
|
||||
instance->general_event.instance = instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void mf_desfire_poller_free(MfDesfirePoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
mf_desfire_free(instance->data);
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
bit_buffer_free(instance->input_buffer);
|
||||
bit_buffer_free(instance->result_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_idle(MfDesfirePoller* instance) {
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_reset(instance->result_buffer);
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
iso14443_4a_copy(
|
||||
instance->data->iso14443_4a_data,
|
||||
iso14443_4a_poller_get_data(instance->iso14443_4a_poller));
|
||||
|
||||
instance->state = MfDesfirePollerStateReadVersion;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instance) {
|
||||
instance->error = mf_desfire_poller_async_read_version(instance, &instance->data->version);
|
||||
if(instance->error == MfDesfireErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read version success");
|
||||
instance->state = MfDesfirePollerStateReadFreeMemory;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read version");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->state = MfDesfirePollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) {
|
||||
instance->error =
|
||||
mf_desfire_poller_async_read_free_memory(instance, &instance->data->free_memory);
|
||||
if(instance->error == MfDesfireErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read free memory success");
|
||||
instance->state = MfDesfirePollerStateReadMasterKeySettings;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read free memory");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->state = MfDesfirePollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) {
|
||||
instance->error =
|
||||
mf_desfire_poller_async_read_key_settings(instance, &instance->data->master_key_settings);
|
||||
if(instance->error == MfDesfireErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read master key settings success");
|
||||
instance->state = MfDesfirePollerStateReadMasterKeyVersion;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read master key settings");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->state = MfDesfirePollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePoller* instance) {
|
||||
instance->error = mf_desfire_poller_async_read_key_versions(
|
||||
instance,
|
||||
instance->data->master_key_versions,
|
||||
instance->data->master_key_settings.max_keys);
|
||||
if(instance->error == MfDesfireErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read master key version success");
|
||||
instance->state = MfDesfirePollerStateReadApplicationIds;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read master key version");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->state = MfDesfirePollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller* instance) {
|
||||
instance->error =
|
||||
mf_desfire_poller_async_read_application_ids(instance, instance->data->application_ids);
|
||||
if(instance->error == MfDesfireErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read application ids success");
|
||||
instance->state = MfDesfirePollerStateReadApplications;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read application ids");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->state = MfDesfirePollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_applications(MfDesfirePoller* instance) {
|
||||
instance->error = mf_desfire_poller_async_read_applications(
|
||||
instance, instance->data->application_ids, instance->data->applications);
|
||||
if(instance->error == MfDesfireErrorNone) {
|
||||
FURI_LOG_D(TAG, "Read applications success");
|
||||
instance->state = MfDesfirePollerStateReadSuccess;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to read applications");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->state = MfDesfirePollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_fail(MfDesfirePoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Failed");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->mf_desfire_event.data->error = instance->error;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
instance->state = MfDesfirePollerStateIdle;
|
||||
return command;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_handler_read_success(MfDesfirePoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read success.");
|
||||
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
|
||||
instance->mf_desfire_event.type = MfDesfirePollerEventTypeReadSuccess;
|
||||
NfcCommand command = instance->callback(instance->general_event, instance->context);
|
||||
return command;
|
||||
}
|
||||
|
||||
static const MfDesfirePollerReadHandler mf_desfire_poller_read_handler[MfDesfirePollerStateNum] = {
|
||||
[MfDesfirePollerStateIdle] = mf_desfire_poller_handler_idle,
|
||||
[MfDesfirePollerStateReadVersion] = mf_desfire_poller_handler_read_version,
|
||||
[MfDesfirePollerStateReadFreeMemory] = mf_desfire_poller_handler_read_free_memory,
|
||||
[MfDesfirePollerStateReadMasterKeySettings] =
|
||||
mf_desfire_poller_handler_read_master_key_settings,
|
||||
[MfDesfirePollerStateReadMasterKeyVersion] = mf_desfire_poller_handler_read_master_key_version,
|
||||
[MfDesfirePollerStateReadApplicationIds] = mf_desfire_poller_handler_read_application_ids,
|
||||
[MfDesfirePollerStateReadApplications] = mf_desfire_poller_handler_read_applications,
|
||||
[MfDesfirePollerStateReadFailed] = mf_desfire_poller_handler_read_fail,
|
||||
[MfDesfirePollerStateReadSuccess] = mf_desfire_poller_handler_read_success,
|
||||
};
|
||||
|
||||
static void mf_desfire_poller_set_callback(
|
||||
MfDesfirePoller* instance,
|
||||
NfcGenericCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static NfcCommand mf_desfire_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
||||
|
||||
MfDesfirePoller* instance = context;
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->callback);
|
||||
|
||||
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||
furi_assert(iso14443_4a_event);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||
command = mf_desfire_poller_read_handler[instance->state](instance);
|
||||
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
|
||||
instance->mf_desfire_event.type = MfDesfirePollerEventTypeReadFailed;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
||||
|
||||
MfDesfirePoller* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
|
||||
furi_assert(iso14443_4a_event);
|
||||
|
||||
bool protocol_detected = false;
|
||||
|
||||
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
|
||||
MfDesfireVersion version = {};
|
||||
const MfDesfireError error = mf_desfire_poller_async_read_version(instance, &version);
|
||||
protocol_detected = (error == MfDesfireErrorNone);
|
||||
}
|
||||
|
||||
return protocol_detected;
|
||||
}
|
||||
|
||||
const NfcPollerBase mf_desfire_poller = {
|
||||
.alloc = (NfcPollerAlloc)mf_desfire_poller_alloc,
|
||||
.free = (NfcPollerFree)mf_desfire_poller_free,
|
||||
.set_callback = (NfcPollerSetCallback)mf_desfire_poller_set_callback,
|
||||
.run = (NfcPollerRun)mf_desfire_poller_run,
|
||||
.detect = (NfcPollerDetect)mf_desfire_poller_detect,
|
||||
.get_data = (NfcPollerGetData)mf_desfire_poller_get_data,
|
||||
};
|
||||
31
lib/nfc/protocols/mf_desfire/mf_desfire_poller.h
Normal file
31
lib/nfc/protocols/mf_desfire/mf_desfire_poller.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_desfire.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MfDesfirePoller MfDesfirePoller;
|
||||
|
||||
typedef enum {
|
||||
MfDesfirePollerEventTypeReadSuccess,
|
||||
MfDesfirePollerEventTypeReadFailed,
|
||||
} MfDesfirePollerEventType;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
MfDesfireError error;
|
||||
};
|
||||
} MfDesfirePollerEventData;
|
||||
|
||||
typedef struct {
|
||||
MfDesfirePollerEventType type;
|
||||
MfDesfirePollerEventData* data;
|
||||
} MfDesfirePollerEvent;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5
lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h
Normal file
5
lib/nfc/protocols/mf_desfire/mf_desfire_poller_defs.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase mf_desfire_poller;
|
||||
474
lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c
Normal file
474
lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c
Normal file
@@ -0,0 +1,474 @@
|
||||
#include "mf_desfire_poller_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "mf_desfire_i.h"
|
||||
|
||||
#define TAG "MfDesfirePoller"
|
||||
|
||||
MfDesfireError mf_desfire_process_error(Iso14443_4aError error) {
|
||||
switch(error) {
|
||||
case Iso14443_4aErrorNone:
|
||||
return MfDesfireErrorNone;
|
||||
case Iso14443_4aErrorNotPresent:
|
||||
return MfDesfireErrorNotPresent;
|
||||
case Iso14443_4aErrorTimeout:
|
||||
return MfDesfireErrorTimeout;
|
||||
default:
|
||||
return MfDesfireErrorProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_send_chunks(
|
||||
MfDesfirePoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->iso14443_4a_poller);
|
||||
furi_assert(instance->tx_buffer);
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
MfDesfireError error = MfDesfireErrorNone;
|
||||
|
||||
do {
|
||||
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
|
||||
instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer);
|
||||
|
||||
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||
error = mf_desfire_process_error(iso14443_4a_error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT);
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) {
|
||||
bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
|
||||
} else {
|
||||
bit_buffer_reset(rx_buffer);
|
||||
}
|
||||
|
||||
while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) {
|
||||
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block(
|
||||
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
|
||||
|
||||
if(iso14443_4a_error != Iso14443_4aErrorNone) {
|
||||
error = mf_desfire_process_error(iso14443_4a_error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t));
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_VERSION);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_version_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FREE_MEMORY);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_key_settings(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireKeySettings* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_KEY_SETTINGS);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_key_settings_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_key_versions(
|
||||
MfDesfirePoller* instance,
|
||||
SimpleArray* data,
|
||||
uint32_t count) {
|
||||
furi_assert(instance);
|
||||
furi_assert(count > 0);
|
||||
|
||||
simple_array_init(data, count);
|
||||
|
||||
bit_buffer_set_size_bytes(instance->input_buffer, sizeof(uint8_t) * 2);
|
||||
bit_buffer_set_byte(instance->input_buffer, 0, MF_DESFIRE_CMD_GET_KEY_VERSION);
|
||||
|
||||
MfDesfireError error = MfDesfireErrorNone;
|
||||
|
||||
for(uint32_t i = 0; i < count; ++i) {
|
||||
bit_buffer_set_byte(instance->input_buffer, 1, i);
|
||||
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_key_version_parse(simple_array_get(data, i), instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_APPLICATION_IDS);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
const uint32_t app_id_count =
|
||||
bit_buffer_get_size_bytes(instance->result_buffer) / sizeof(MfDesfireApplicationId);
|
||||
if(app_id_count == 0) break;
|
||||
|
||||
simple_array_init(data, app_id_count);
|
||||
|
||||
for(uint32_t i = 0; i < app_id_count; ++i) {
|
||||
if(!mf_desfire_application_id_parse(
|
||||
simple_array_get(data, i), i, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_select_application(
|
||||
MfDesfirePoller* instance,
|
||||
const MfDesfireApplicationId* id) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_SELECT_APPLICATION);
|
||||
bit_buffer_append_bytes(
|
||||
instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId));
|
||||
|
||||
MfDesfireError error =
|
||||
mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FILE_IDS);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
const uint32_t id_count =
|
||||
bit_buffer_get_size_bytes(instance->result_buffer) / sizeof(MfDesfireFileId);
|
||||
|
||||
if(id_count == 0) break;
|
||||
simple_array_init(data, id_count);
|
||||
|
||||
for(uint32_t i = 0; i < id_count; ++i) {
|
||||
if(!mf_desfire_file_id_parse(simple_array_get(data, i), i, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_settings(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
MfDesfireFileSettings* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_FILE_SETTINGS);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_file_settings_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_settings_multi(
|
||||
MfDesfirePoller* instance,
|
||||
const SimpleArray* file_ids,
|
||||
SimpleArray* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
MfDesfireError error = MfDesfireErrorNone;
|
||||
|
||||
const uint32_t file_id_count = simple_array_get_count(file_ids);
|
||||
if(file_id_count > 0) {
|
||||
simple_array_init(data, file_id_count);
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < file_id_count; ++i) {
|
||||
const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i);
|
||||
error = mf_desfire_poller_async_read_file_settings(
|
||||
instance, file_id, simple_array_get(data, i));
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_data(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_file_data_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_value(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
MfDesfireFileData* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_GET_VALUE);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_file_data_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_records(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_reset(instance->input_buffer);
|
||||
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA);
|
||||
bit_buffer_append_byte(instance->input_buffer, id);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3);
|
||||
bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer);
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
if(!mf_desfire_file_data_parse(data, instance->result_buffer)) {
|
||||
error = MfDesfireErrorProtocol;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_data_multi(
|
||||
MfDesfirePoller* instance,
|
||||
const SimpleArray* file_ids,
|
||||
const SimpleArray* file_settings,
|
||||
SimpleArray* data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(simple_array_get_count(file_ids) == simple_array_get_count(file_settings));
|
||||
|
||||
MfDesfireError error = MfDesfireErrorNone;
|
||||
|
||||
const uint32_t file_id_count = simple_array_get_count(file_ids);
|
||||
if(file_id_count > 0) {
|
||||
simple_array_init(data, file_id_count);
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < file_id_count; ++i) {
|
||||
const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i);
|
||||
const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i);
|
||||
const MfDesfireFileType file_type = file_settings_cur->type;
|
||||
|
||||
MfDesfireFileData* file_data = simple_array_get(data, i);
|
||||
|
||||
if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) {
|
||||
error = mf_desfire_poller_async_read_file_data(
|
||||
instance, file_id, 0, file_settings_cur->data.size, file_data);
|
||||
} else if(file_type == MfDesfireFileTypeValue) {
|
||||
error = mf_desfire_poller_async_read_file_value(instance, file_id, file_data);
|
||||
} else if(
|
||||
file_type == MfDesfireFileTypeLinearRecord ||
|
||||
file_type == MfDesfireFileTypeCyclicRecord) {
|
||||
error = mf_desfire_poller_async_read_file_records(
|
||||
instance, file_id, 0, file_settings_cur->data.size, file_data);
|
||||
}
|
||||
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_application(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireApplication* data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
|
||||
MfDesfireError error;
|
||||
|
||||
do {
|
||||
error = mf_desfire_poller_async_read_key_settings(instance, &data->key_settings);
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
error = mf_desfire_poller_async_read_key_versions(
|
||||
instance, data->key_versions, data->key_settings.max_keys);
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
error = mf_desfire_poller_async_read_file_ids(instance, data->file_ids);
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
error = mf_desfire_poller_async_read_file_settings_multi(
|
||||
instance, data->file_ids, data->file_settings);
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
error = mf_desfire_poller_async_read_file_data_multi(
|
||||
instance, data->file_ids, data->file_settings, data->file_data);
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_applications(
|
||||
MfDesfirePoller* instance,
|
||||
const SimpleArray* app_ids,
|
||||
SimpleArray* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
MfDesfireError error = MfDesfireErrorNone;
|
||||
|
||||
const uint32_t app_id_count = simple_array_get_count(app_ids);
|
||||
if(app_id_count > 0) {
|
||||
simple_array_init(data, app_id_count);
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < app_id_count; ++i) {
|
||||
do {
|
||||
error = mf_desfire_poller_async_select_application(
|
||||
instance, simple_array_cget(app_ids, i));
|
||||
if(error != MfDesfireErrorNone) break;
|
||||
|
||||
MfDesfireApplication* current_app = simple_array_get(data, i);
|
||||
error = mf_desfire_poller_async_read_application(instance, current_app);
|
||||
|
||||
} while(false);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
126
lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h
Normal file
126
lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include "mf_desfire_poller.h"
|
||||
|
||||
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MfDesfirePollerStateIdle,
|
||||
MfDesfirePollerStateReadVersion,
|
||||
MfDesfirePollerStateReadFreeMemory,
|
||||
MfDesfirePollerStateReadMasterKeySettings,
|
||||
MfDesfirePollerStateReadMasterKeyVersion,
|
||||
MfDesfirePollerStateReadApplicationIds,
|
||||
MfDesfirePollerStateReadApplications,
|
||||
MfDesfirePollerStateReadFailed,
|
||||
MfDesfirePollerStateReadSuccess,
|
||||
|
||||
MfDesfirePollerStateNum,
|
||||
} MfDesfirePollerState;
|
||||
|
||||
typedef enum {
|
||||
MfDesfirePollerSessionStateIdle,
|
||||
MfDesfirePollerSessionStateActive,
|
||||
MfDesfirePollerSessionStateStopRequest,
|
||||
} MfDesfirePollerSessionState;
|
||||
|
||||
struct MfDesfirePoller {
|
||||
Iso14443_4aPoller* iso14443_4a_poller;
|
||||
MfDesfirePollerSessionState session_state;
|
||||
MfDesfirePollerState state;
|
||||
MfDesfireError error;
|
||||
MfDesfireData* data;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
BitBuffer* input_buffer;
|
||||
BitBuffer* result_buffer;
|
||||
MfDesfirePollerEventData mf_desfire_event_data;
|
||||
MfDesfirePollerEvent mf_desfire_event;
|
||||
NfcGenericEvent general_event;
|
||||
NfcGenericCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
MfDesfireError mf_desfire_process_error(Iso14443_4aError error);
|
||||
|
||||
const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance);
|
||||
|
||||
MfDesfireError mf_desfire_send_chunks(
|
||||
MfDesfirePoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer);
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_version(MfDesfirePoller* instance, MfDesfireVersion* data);
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_free_memory(MfDesfirePoller* instance, MfDesfireFreeMemory* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_key_settings(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireKeySettings* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_key_versions(
|
||||
MfDesfirePoller* instance,
|
||||
SimpleArray* data,
|
||||
uint32_t count);
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_application_ids(MfDesfirePoller* instance, SimpleArray* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_select_application(
|
||||
MfDesfirePoller* instance,
|
||||
const MfDesfireApplicationId* id);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_ids(MfDesfirePoller* instance, SimpleArray* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_settings(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
MfDesfireFileSettings* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_settings_multi(
|
||||
MfDesfirePoller* instance,
|
||||
const SimpleArray* file_ids,
|
||||
SimpleArray* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_data(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_value(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
MfDesfireFileData* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_records(
|
||||
MfDesfirePoller* instance,
|
||||
MfDesfireFileId id,
|
||||
uint32_t offset,
|
||||
size_t size,
|
||||
MfDesfireFileData* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_file_data_multi(
|
||||
MfDesfirePoller* instance,
|
||||
const SimpleArray* file_ids,
|
||||
const SimpleArray* file_settings,
|
||||
SimpleArray* data);
|
||||
|
||||
MfDesfireError
|
||||
mf_desfire_poller_async_read_application(MfDesfirePoller* instance, MfDesfireApplication* data);
|
||||
|
||||
MfDesfireError mf_desfire_poller_async_read_applications(
|
||||
MfDesfirePoller* instance,
|
||||
const SimpleArray* app_ids,
|
||||
SimpleArray* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
627
lib/nfc/protocols/mf_ultralight/mf_ultralight.c
Normal file
627
lib/nfc/protocols/mf_ultralight/mf_ultralight.c
Normal file
@@ -0,0 +1,627 @@
|
||||
#include "mf_ultralight.h"
|
||||
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define MF_ULTRALIGHT_PROTOCOL_NAME "NTAG/Ultralight"
|
||||
|
||||
#define MF_ULTRALIGHT_FORMAT_VERSION_KEY "Data format version"
|
||||
#define MF_ULTRALIGHT_TYPE_KEY MF_ULTRALIGHT_PROTOCOL_NAME " type"
|
||||
#define MF_ULTRALIGHT_SIGNATURE_KEY "Signature"
|
||||
#define MF_ULTRALIGHT_MIFARE_VERSION_KEY "Mifare version"
|
||||
#define MF_ULTRALIGHT_COUNTER_KEY "Counter"
|
||||
#define MF_ULTRALIGHT_TEARING_KEY "Tearing"
|
||||
#define MF_ULTRALIGHT_PAGES_TOTAL_KEY "Pages total"
|
||||
#define MF_ULTRALIGHT_PAGES_READ_KEY "Pages read"
|
||||
#define MF_ULTRALIGHT_PAGE_KEY "Page"
|
||||
#define MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY "Failed authentication attempts"
|
||||
|
||||
typedef struct {
|
||||
const char* device_name;
|
||||
uint16_t total_pages;
|
||||
uint16_t config_page;
|
||||
uint32_t feature_set;
|
||||
} MfUltralightFeatures;
|
||||
|
||||
static const uint32_t mf_ultralight_data_format_version = 2;
|
||||
|
||||
static const MfUltralightFeatures mf_ultralight_features[MfUltralightTypeNum] = {
|
||||
[MfUltralightTypeUnknown] =
|
||||
{
|
||||
.device_name = "Mifare Ultralight",
|
||||
.total_pages = 16,
|
||||
.config_page = 0,
|
||||
.feature_set = MfUltralightFeatureSupportCompatibleWrite,
|
||||
},
|
||||
[MfUltralightTypeMfulC] =
|
||||
{
|
||||
.device_name = "Mifare Ultralight C",
|
||||
.total_pages = 48,
|
||||
.config_page = 0,
|
||||
.feature_set = MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportAuthenticate,
|
||||
},
|
||||
[MfUltralightTypeNTAG203] =
|
||||
{
|
||||
.device_name = "NTAG203",
|
||||
.total_pages = 42,
|
||||
.config_page = 0,
|
||||
.feature_set = MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportCounterInMemory,
|
||||
},
|
||||
[MfUltralightTypeUL11] =
|
||||
{
|
||||
.device_name = "Mifare Ultralight 11",
|
||||
.total_pages = 20,
|
||||
.config_page = 16,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportReadCounter |
|
||||
MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl,
|
||||
},
|
||||
[MfUltralightTypeUL21] =
|
||||
{
|
||||
.device_name = "Mifare Ultralight 21",
|
||||
.total_pages = 41,
|
||||
.config_page = 37,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportReadCounter |
|
||||
MfUltralightFeatureSupportCheckTearingFlag | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportIncCounter | MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportVcsl |
|
||||
MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAG213] =
|
||||
{
|
||||
.device_name = "NTAG213",
|
||||
.total_pages = 45,
|
||||
.config_page = 41,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter |
|
||||
MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAG215] =
|
||||
{
|
||||
.device_name = "NTAG215",
|
||||
.total_pages = 135,
|
||||
.config_page = 131,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter |
|
||||
MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAG216] =
|
||||
{
|
||||
.device_name = "NTAG216",
|
||||
.total_pages = 231,
|
||||
.config_page = 227,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportReadCounter | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportCompatibleWrite |
|
||||
MfUltralightFeatureSupportPasswordAuth | MfUltralightFeatureSupportSingleCounter |
|
||||
MfUltralightFeatureSupportAsciiMirror | MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAGI2C1K] =
|
||||
{
|
||||
.device_name = "NTAG I2C 1K",
|
||||
.total_pages = 231,
|
||||
.config_page = 0,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAGI2C2K] =
|
||||
{
|
||||
.device_name = "NTAG I2C 2K",
|
||||
.total_pages = 485,
|
||||
.config_page = 0,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportFastRead |
|
||||
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAGI2CPlus1K] =
|
||||
{
|
||||
.device_name = "NTAG I2C Plus 1K",
|
||||
.total_pages = 236,
|
||||
.config_page = 227,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth |
|
||||
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite |
|
||||
MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
[MfUltralightTypeNTAGI2CPlus2K] =
|
||||
{
|
||||
.device_name = "NTAG I2C Plus 2K",
|
||||
.total_pages = 492,
|
||||
.config_page = 227,
|
||||
.feature_set =
|
||||
MfUltralightFeatureSupportReadVersion | MfUltralightFeatureSupportReadSignature |
|
||||
MfUltralightFeatureSupportFastRead | MfUltralightFeatureSupportPasswordAuth |
|
||||
MfUltralightFeatureSupportSectorSelect | MfUltralightFeatureSupportFastWrite |
|
||||
MfUltralightFeatureSupportDynamicLock,
|
||||
},
|
||||
};
|
||||
|
||||
const NfcDeviceBase nfc_device_mf_ultralight = {
|
||||
.protocol_name = MF_ULTRALIGHT_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)mf_ultralight_alloc,
|
||||
.free = (NfcDeviceFree)mf_ultralight_free,
|
||||
.reset = (NfcDeviceReset)mf_ultralight_reset,
|
||||
.copy = (NfcDeviceCopy)mf_ultralight_copy,
|
||||
.verify = (NfcDeviceVerify)mf_ultralight_verify,
|
||||
.load = (NfcDeviceLoad)mf_ultralight_load,
|
||||
.save = (NfcDeviceSave)mf_ultralight_save,
|
||||
.is_equal = (NfcDeviceEqual)mf_ultralight_is_equal,
|
||||
.get_name = (NfcDeviceGetName)mf_ultralight_get_device_name,
|
||||
.get_uid = (NfcDeviceGetUid)mf_ultralight_get_uid,
|
||||
.set_uid = (NfcDeviceSetUid)mf_ultralight_set_uid,
|
||||
.get_base_data = (NfcDeviceGetBaseData)mf_ultralight_get_base_data,
|
||||
};
|
||||
|
||||
MfUltralightData* mf_ultralight_alloc() {
|
||||
MfUltralightData* data = malloc(sizeof(MfUltralightData));
|
||||
data->iso14443_3a_data = iso14443_3a_alloc();
|
||||
return data;
|
||||
}
|
||||
|
||||
void mf_ultralight_free(MfUltralightData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_free(data->iso14443_3a_data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mf_ultralight_reset(MfUltralightData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
iso14443_3a_reset(data->iso14443_3a_data);
|
||||
}
|
||||
|
||||
void mf_ultralight_copy(MfUltralightData* data, const MfUltralightData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
|
||||
iso14443_3a_copy(data->iso14443_3a_data, other->iso14443_3a_data);
|
||||
for(size_t i = 0; i < COUNT_OF(data->counter); i++) {
|
||||
data->counter[i] = other->counter[i];
|
||||
}
|
||||
for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) {
|
||||
data->tearing_flag[i] = other->tearing_flag[i];
|
||||
}
|
||||
for(size_t i = 0; i < COUNT_OF(data->page); i++) {
|
||||
data->page[i] = other->page[i];
|
||||
}
|
||||
|
||||
data->type = other->type;
|
||||
data->version = other->version;
|
||||
data->signature = other->signature;
|
||||
|
||||
data->pages_read = other->pages_read;
|
||||
data->pages_total = other->pages_total;
|
||||
data->auth_attempts = other->auth_attempts;
|
||||
}
|
||||
|
||||
static const char*
|
||||
mf_ultralight_get_device_name_by_type(MfUltralightType type, NfcDeviceNameType name_type) {
|
||||
if(name_type == NfcDeviceNameTypeShort &&
|
||||
(type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21)) {
|
||||
type = MfUltralightTypeUnknown;
|
||||
}
|
||||
|
||||
return mf_ultralight_features[type].device_name;
|
||||
}
|
||||
|
||||
bool mf_ultralight_verify(MfUltralightData* data, const FuriString* device_type) {
|
||||
furi_assert(data);
|
||||
|
||||
bool verified = false;
|
||||
|
||||
for(MfUltralightType i = 0; i < MfUltralightTypeNum; i++) {
|
||||
const char* name = mf_ultralight_get_device_name_by_type(i, NfcDeviceNameTypeFull);
|
||||
verified = furi_string_equal(device_type, name);
|
||||
if(verified) {
|
||||
data->type = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
bool mf_ultralight_load(MfUltralightData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Read ISO14443_3A data
|
||||
if(!iso14443_3a_load(data->iso14443_3a_data, ff, version)) break;
|
||||
|
||||
// Read Ultralight specific data
|
||||
// Read Mifare Ultralight format version
|
||||
uint32_t data_format_version = 0;
|
||||
if(!flipper_format_read_uint32(
|
||||
ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &data_format_version, 1)) {
|
||||
if(!flipper_format_rewind(ff)) break;
|
||||
}
|
||||
|
||||
// Read Mifare Ultralight type
|
||||
if(data_format_version > 1) {
|
||||
if(!flipper_format_read_string(ff, MF_ULTRALIGHT_TYPE_KEY, temp_str)) break;
|
||||
if(!mf_ultralight_verify(data, temp_str)) break;
|
||||
}
|
||||
|
||||
// Read signature
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
MF_ULTRALIGHT_SIGNATURE_KEY,
|
||||
data->signature.data,
|
||||
sizeof(MfUltralightSignature)))
|
||||
break;
|
||||
// Read Mifare version
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
MF_ULTRALIGHT_MIFARE_VERSION_KEY,
|
||||
(uint8_t*)&data->version,
|
||||
sizeof(MfUltralightVersion)))
|
||||
break;
|
||||
// Read counters and tearing flags
|
||||
bool counters_parsed = true;
|
||||
for(size_t i = 0; i < 3; i++) {
|
||||
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i);
|
||||
if(!flipper_format_read_uint32(
|
||||
ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) {
|
||||
counters_parsed = false;
|
||||
break;
|
||||
}
|
||||
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) {
|
||||
counters_parsed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!counters_parsed) break;
|
||||
// Read pages
|
||||
uint32_t pages_total = 0;
|
||||
if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break;
|
||||
uint32_t pages_read = 0;
|
||||
if(data_format_version < mf_ultralight_data_format_version) {
|
||||
pages_read = pages_total;
|
||||
} else {
|
||||
if(!flipper_format_read_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1))
|
||||
break;
|
||||
}
|
||||
data->pages_total = pages_total;
|
||||
data->pages_read = pages_read;
|
||||
|
||||
if((pages_read > MF_ULTRALIGHT_MAX_PAGE_NUM) || (pages_total > MF_ULTRALIGHT_MAX_PAGE_NUM))
|
||||
break;
|
||||
|
||||
bool pages_parsed = true;
|
||||
for(size_t i = 0; i < pages_total; i++) {
|
||||
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
data->page[i].data,
|
||||
sizeof(MfUltralightPage))) {
|
||||
pages_parsed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!pages_parsed) break;
|
||||
|
||||
// Read authentication counter
|
||||
if(!flipper_format_read_uint32(
|
||||
ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1)) {
|
||||
data->auth_attempts = 0;
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool mf_ultralight_save(const MfUltralightData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
bool saved = false;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_save(data->iso14443_3a_data, ff)) break;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(ff, MF_ULTRALIGHT_PROTOCOL_NAME " specific data"))
|
||||
break;
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, MF_ULTRALIGHT_FORMAT_VERSION_KEY, &mf_ultralight_data_format_version, 1))
|
||||
break;
|
||||
|
||||
const char* device_type_name =
|
||||
mf_ultralight_get_device_name_by_type(data->type, NfcDeviceNameTypeFull);
|
||||
if(!flipper_format_write_string_cstr(ff, MF_ULTRALIGHT_TYPE_KEY, device_type_name)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
MF_ULTRALIGHT_SIGNATURE_KEY,
|
||||
data->signature.data,
|
||||
sizeof(MfUltralightSignature)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
MF_ULTRALIGHT_MIFARE_VERSION_KEY,
|
||||
(uint8_t*)&data->version,
|
||||
sizeof(MfUltralightVersion)))
|
||||
break;
|
||||
|
||||
// Write conters and tearing flags data
|
||||
bool counters_saved = true;
|
||||
for(size_t i = 0; i < 3; i++) {
|
||||
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_COUNTER_KEY, i);
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, furi_string_get_cstr(temp_str), &data->counter[i].counter, 1)) {
|
||||
counters_saved = false;
|
||||
break;
|
||||
}
|
||||
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_TEARING_KEY, i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff, furi_string_get_cstr(temp_str), &data->tearing_flag[i].data, 1)) {
|
||||
counters_saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!counters_saved) break;
|
||||
|
||||
// Write pages data
|
||||
uint32_t pages_total = data->pages_total;
|
||||
uint32_t pages_read = data->pages_read;
|
||||
if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_TOTAL_KEY, &pages_total, 1)) break;
|
||||
if(!flipper_format_write_uint32(ff, MF_ULTRALIGHT_PAGES_READ_KEY, &pages_read, 1)) break;
|
||||
bool pages_saved = true;
|
||||
for(size_t i = 0; i < data->pages_total; i++) {
|
||||
furi_string_printf(temp_str, "%s %d", MF_ULTRALIGHT_PAGE_KEY, i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
data->page[i].data,
|
||||
sizeof(MfUltralightPage))) {
|
||||
pages_saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!pages_saved) break;
|
||||
|
||||
// Write authentication counter
|
||||
if(!flipper_format_write_uint32(
|
||||
ff, MF_ULTRALIGHT_FAILED_ATTEMPTS_KEY, &data->auth_attempts, 1))
|
||||
break;
|
||||
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool mf_ultralight_is_equal(const MfUltralightData* data, const MfUltralightData* other) {
|
||||
bool is_equal = false;
|
||||
bool data_array_is_equal = true;
|
||||
|
||||
do {
|
||||
if(!iso14443_3a_is_equal(data->iso14443_3a_data, other->iso14443_3a_data)) break;
|
||||
if(data->type != other->type) break;
|
||||
if(data->pages_read != other->pages_read) break;
|
||||
if(data->pages_total != other->pages_total) break;
|
||||
if(data->auth_attempts != other->auth_attempts) break;
|
||||
|
||||
if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break;
|
||||
if(memcmp(&data->signature, &other->signature, sizeof(data->signature)) != 0) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->counter); i++) {
|
||||
if(memcmp(&data->counter[i], &other->counter[i], sizeof(data->counter[i])) != 0) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->tearing_flag); i++) {
|
||||
if(memcmp(
|
||||
&data->tearing_flag[i],
|
||||
&other->tearing_flag[i],
|
||||
sizeof(data->tearing_flag[i])) != 0) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(data->page); i++) {
|
||||
if(memcmp(&data->page[i], &other->page[i], sizeof(data->page[i])) != 0) {
|
||||
data_array_is_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!data_array_is_equal) break;
|
||||
|
||||
is_equal = true;
|
||||
} while(false);
|
||||
|
||||
return is_equal;
|
||||
}
|
||||
|
||||
const char*
|
||||
mf_ultralight_get_device_name(const MfUltralightData* data, NfcDeviceNameType name_type) {
|
||||
furi_assert(data);
|
||||
furi_assert(data->type < MfUltralightTypeNum);
|
||||
|
||||
return mf_ultralight_get_device_name_by_type(data->type, name_type);
|
||||
}
|
||||
|
||||
const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_get_uid(data->iso14443_3a_data, uid_len);
|
||||
}
|
||||
|
||||
bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
|
||||
return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len);
|
||||
}
|
||||
|
||||
Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
return data->iso14443_3a_data;
|
||||
}
|
||||
|
||||
MfUltralightType mf_ultralight_get_type_by_version(MfUltralightVersion* version) {
|
||||
furi_assert(version);
|
||||
|
||||
MfUltralightType type = MfUltralightTypeUnknown;
|
||||
|
||||
if(version->storage_size == 0x0B || version->storage_size == 0x00) {
|
||||
type = MfUltralightTypeUL11;
|
||||
} else if(version->storage_size == 0x0E) {
|
||||
type = MfUltralightTypeUL21;
|
||||
} else if(version->storage_size == 0x0F) {
|
||||
type = MfUltralightTypeNTAG213;
|
||||
} else if(version->storage_size == 0x11) {
|
||||
type = MfUltralightTypeNTAG215;
|
||||
} else if(version->prod_subtype == 5 && version->prod_ver_major == 2) {
|
||||
if(version->prod_ver_minor == 1) {
|
||||
if(version->storage_size == 0x13) {
|
||||
type = MfUltralightTypeNTAGI2C1K;
|
||||
} else if(version->storage_size == 0x15) {
|
||||
type = MfUltralightTypeNTAGI2C2K;
|
||||
}
|
||||
} else if(version->prod_ver_minor == 2) {
|
||||
if(version->storage_size == 0x13) {
|
||||
type = MfUltralightTypeNTAGI2CPlus1K;
|
||||
} else if(version->storage_size == 0x15) {
|
||||
type = MfUltralightTypeNTAGI2CPlus2K;
|
||||
}
|
||||
}
|
||||
} else if(version->storage_size == 0x13) {
|
||||
type = MfUltralightTypeNTAG216;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
uint16_t mf_ultralight_get_pages_total(MfUltralightType type) {
|
||||
return mf_ultralight_features[type].total_pages;
|
||||
}
|
||||
|
||||
uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type) {
|
||||
return mf_ultralight_features[type].feature_set;
|
||||
}
|
||||
|
||||
bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data) {
|
||||
furi_assert(iso14443_3a_data);
|
||||
|
||||
bool mfu_detected = (iso14443_3a_data->atqa[0] == 0x44) &&
|
||||
(iso14443_3a_data->atqa[1] == 0x00) && (iso14443_3a_data->sak == 0x00);
|
||||
|
||||
return mfu_detected;
|
||||
}
|
||||
|
||||
uint16_t mf_ultralight_get_config_page_num(MfUltralightType type) {
|
||||
return mf_ultralight_features[type].config_page;
|
||||
}
|
||||
|
||||
uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type) {
|
||||
uint8_t config_page = mf_ultralight_features[type].config_page;
|
||||
return (config_page != 0) ? config_page + 2 : 0;
|
||||
}
|
||||
|
||||
bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page) {
|
||||
uint8_t pwd_page = mf_ultralight_get_pwd_page_num(type);
|
||||
uint8_t pack_page = pwd_page + 1;
|
||||
return ((pwd_page != 0) && (page == pwd_page || page == pack_page));
|
||||
}
|
||||
|
||||
bool mf_ultralight_support_feature(const uint32_t feature_set, const uint32_t features_to_check) {
|
||||
return (feature_set & features_to_check) != 0;
|
||||
}
|
||||
|
||||
bool mf_ultralight_get_config_page(const MfUltralightData* data, MfUltralightConfigPages** config) {
|
||||
furi_assert(data);
|
||||
furi_assert(config);
|
||||
|
||||
bool config_pages_found = false;
|
||||
|
||||
uint16_t config_page = mf_ultralight_features[data->type].config_page;
|
||||
if(config_page != 0) {
|
||||
*config = (MfUltralightConfigPages*)&data->page[config_page]; //-V1027
|
||||
config_pages_found = true;
|
||||
}
|
||||
|
||||
return config_pages_found;
|
||||
}
|
||||
|
||||
bool mf_ultralight_is_all_data_read(const MfUltralightData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
bool all_read = false;
|
||||
if(data->pages_read == data->pages_total ||
|
||||
(data->type == MfUltralightTypeMfulC && data->pages_read == data->pages_total - 4)) {
|
||||
// Having read all the pages doesn't mean that we've got everything.
|
||||
// By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000,
|
||||
// so a default read on an auth-supported NTAG is never complete.
|
||||
uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type);
|
||||
if(!mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportPasswordAuth)) {
|
||||
all_read = true;
|
||||
} else {
|
||||
MfUltralightConfigPages* config = NULL;
|
||||
if(mf_ultralight_get_config_page(data, &config)) {
|
||||
uint32_t pass =
|
||||
nfc_util_bytes2num(config->password.data, sizeof(MfUltralightAuthPassword));
|
||||
uint16_t pack =
|
||||
nfc_util_bytes2num(config->pack.data, sizeof(MfUltralightAuthPack));
|
||||
all_read = ((pass != 0) || (pack != 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return all_read;
|
||||
}
|
||||
|
||||
bool mf_ultralight_is_counter_configured(const MfUltralightData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
MfUltralightConfigPages* config = NULL;
|
||||
bool configured = false;
|
||||
|
||||
switch(data->type) {
|
||||
case MfUltralightTypeNTAG213:
|
||||
case MfUltralightTypeNTAG215:
|
||||
case MfUltralightTypeNTAG216:
|
||||
if(mf_ultralight_get_config_page(data, &config)) {
|
||||
configured = config->access.nfc_cnt_en;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
configured = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return configured;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user