mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-26 03:39:58 -07:00
Reset NFC stuff
This commit is contained in:
@@ -659,7 +659,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) continue;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
@@ -676,7 +675,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
} else {
|
||||
blocks_read++;
|
||||
}
|
||||
@@ -699,7 +697,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) continue;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
@@ -716,7 +713,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
} else {
|
||||
blocks_read++;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#define MF_MINI_TOTAL_SECTORS_NUM (5)
|
||||
#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16)
|
||||
#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40)
|
||||
#define MF_MINI_TOTAL_SECTORS_NUM (5)
|
||||
|
||||
#define MF_CLASSIC_SECTORS_MAX (40)
|
||||
#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16)
|
||||
@@ -20,9 +19,9 @@
|
||||
#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
|
||||
|
||||
typedef enum {
|
||||
MfClassicTypeMini,
|
||||
MfClassicType1k,
|
||||
MfClassicType4k,
|
||||
MfClassicTypeMini,
|
||||
} MfClassicType;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -1,758 +0,0 @@
|
||||
#include <furi_hal_random.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <nfc/nfc_device.h>
|
||||
|
||||
#include "../helpers/iso7816.h"
|
||||
|
||||
#include "mrtd.h"
|
||||
|
||||
#define TAG "Mrtd"
|
||||
|
||||
//TODO: Check EF.DIR first? Before LDS1
|
||||
//TODO: ICAO 9303 p11 §4.2 steps
|
||||
//- Read EF.CardAccess (REQUIRED)
|
||||
// If not available or does not contain PACE params, try BAC
|
||||
//- Read EF.DIR (OPTIONAL)
|
||||
// Check list of applications present
|
||||
//- PACE (CONDITIONAL)
|
||||
//- BAC (CONDITIONAL)
|
||||
|
||||
//TODO: idea - generalize ISO7816 reading. List available apps
|
||||
|
||||
#define num_elements(A) (sizeof(A) / sizeof(A[0]))
|
||||
|
||||
static const char* mrtd_auth_file_header = "Flipper MRTD params";
|
||||
static const uint32_t mrtd_auth_file_version = 1;
|
||||
|
||||
static void hexdump(FuriLogLevel level, char* prefix, void* data, size_t length) {
|
||||
if(furi_log_get_level() >= level) {
|
||||
printf("%s ", prefix);
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
printf("%02X ", ((uint8_t*)data)[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void mrtd_trace(MrtdApplication* app) {
|
||||
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
|
||||
if(furi_log_get_level() == FuriLogLevelTrace) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t mrtd_decode_response(uint8_t* buffer, size_t len) {
|
||||
// Last two bytes are return code
|
||||
return (buffer[len - 2] << 8) | buffer[len - 1];
|
||||
}
|
||||
|
||||
//TODO: rename to transceive?
|
||||
//TODO: PRIO output and output written writing seems to crash flipper, sometimes
|
||||
bool mrtd_send_apdu(
|
||||
MrtdApplication* app,
|
||||
uint8_t cla,
|
||||
uint8_t ins,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t lc,
|
||||
const void* data,
|
||||
int16_t le,
|
||||
uint8_t* output,
|
||||
size_t* output_written) {
|
||||
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
|
||||
size_t idx = 0;
|
||||
|
||||
FURI_LOG_T(TAG, "Send APDU, lc: %d, le: %d", lc, le);
|
||||
|
||||
if(app->secure_messaging) {
|
||||
app->ssc_long++;
|
||||
idx = mrtd_protect_apdu(
|
||||
cla, ins, p1, p2, lc, data, le, app->ksenc, app->ksmac, app->ssc_long, tx_rx->tx_data);
|
||||
} else {
|
||||
tx_rx->tx_data[idx++] = cla;
|
||||
tx_rx->tx_data[idx++] = ins;
|
||||
tx_rx->tx_data[idx++] = p1;
|
||||
tx_rx->tx_data[idx++] = p2;
|
||||
if(lc > 0) {
|
||||
tx_rx->tx_data[idx++] = lc;
|
||||
memcpy(tx_rx->tx_data + idx, data, lc);
|
||||
idx += lc;
|
||||
}
|
||||
if(le >= 0) {
|
||||
tx_rx->tx_data[idx++] = le & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
tx_rx->tx_bits = idx * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
//TODO: timeout as param?
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
|
||||
mrtd_trace(app);
|
||||
uint16_t ret_code = mrtd_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8);
|
||||
|
||||
if(app->secure_messaging && ret_code == 0x9000) {
|
||||
app->ssc_long++;
|
||||
ret_code = mrtd_bac_decrypt_verify_sm(
|
||||
tx_rx->rx_data,
|
||||
tx_rx->rx_bits / 8 - 2,
|
||||
app->ksenc,
|
||||
app->ksmac,
|
||||
app->ssc_long,
|
||||
output,
|
||||
output_written);
|
||||
//ret_code = 0x1337; //TODO: remove PRIO
|
||||
}
|
||||
|
||||
//TODO: handle other return codes?
|
||||
if(ret_code == 0x9000) {
|
||||
if(!app->secure_messaging && le > 0) {
|
||||
// Secure Messaging sets output while decrypting
|
||||
output_written = memcpy(output, tx_rx->rx_data, le);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "APDU answer is not 0x9000, but 0x%04X", ret_code);
|
||||
|
||||
switch(ret_code) {
|
||||
case 0x6987:
|
||||
FURI_LOG_I(TAG, "'expected secure messaging data objects are missing'");
|
||||
app->secure_messaging = false;
|
||||
break;
|
||||
case 0x6988:
|
||||
FURI_LOG_I(TAG, "'secure messaging data objects are incorrect'");
|
||||
app->secure_messaging = false;
|
||||
break;
|
||||
case 0xff01:
|
||||
//CUSTOM ERROR CODE from mrtd_helpers.c
|
||||
FURI_LOG_I(TAG, "'invalid padding'");
|
||||
break;
|
||||
case 0xff02:
|
||||
//CUSTOM ERROR CODE from mrtd_helpers.c
|
||||
FURI_LOG_I(TAG, "'verify failed'");
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sending - failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO: rename commands to "mrtd_cmd_..."
|
||||
bool mrtd_select_app(MrtdApplication* app, AIDValue aid) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Send select App: %02X %02X %02X %02X %02X %02X %02X",
|
||||
aid[0],
|
||||
aid[1],
|
||||
aid[2],
|
||||
aid[3],
|
||||
aid[4],
|
||||
aid[5],
|
||||
aid[6]);
|
||||
if(!mrtd_send_apdu(app, 0x00, 0xA4, 0x04, 0x0C, 0x07, aid, -1, NULL, NULL)) {
|
||||
FURI_LOG_W(TAG, "Failed select App");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_get_challenge(MrtdApplication* app, uint8_t challenge[8]) {
|
||||
FURI_LOG_D(TAG, "Send Get Challenge");
|
||||
size_t chal_size;
|
||||
if(!mrtd_send_apdu(app, 0x00, 0x84, 0x00, 0x00, 0x00, NULL, 0x08, challenge, &chal_size)) {
|
||||
FURI_LOG_W(TAG, "Failed get challenge");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_external_authenticate(
|
||||
MrtdApplication* app,
|
||||
uint8_t* cmd_data,
|
||||
size_t cmd_size,
|
||||
uint8_t* out_data,
|
||||
size_t out_size) {
|
||||
furi_assert(cmd_size == 0x28);
|
||||
furi_assert(out_size >= 0x28);
|
||||
|
||||
FURI_LOG_D(TAG, "Send External Authenticate");
|
||||
if(!mrtd_send_apdu(
|
||||
app, 0x00, 0x82, 0x00, 0x00, cmd_size, cmd_data, 0x28, out_data, &out_size)) {
|
||||
FURI_LOG_W(TAG, "Failed External Authenticate");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_select_file(MrtdApplication* app, EFFile file) {
|
||||
uint8_t data[] = {file.file_id >> 8, file.file_id & 0xff};
|
||||
FURI_LOG_D(TAG, "Send select EF: %s (0x%04X)", file.name, file.file_id);
|
||||
if(!mrtd_send_apdu(app, 0x00, 0xA4, 0x02, 0x0C, 0x02, data, -1, NULL, NULL)) {
|
||||
FURI_LOG_E(TAG, "Failed select EF 0x%04X", file.file_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t mrtd_read_binary(MrtdApplication* app, uint8_t* buffer, size_t bufsize, size_t offset) {
|
||||
UNUSED(buffer);
|
||||
UNUSED(bufsize);
|
||||
// 00 B0 offst -
|
||||
FURI_LOG_D(TAG, "Read binary, offset: %d", offset);
|
||||
//TODO: read first 4 bytes, determine length, iterate through file
|
||||
//TODO: limit reading/buffer fill to max bufsize
|
||||
|
||||
//TODO: test with max_read = bufsize (value !0, > file size)
|
||||
int16_t max_read = 0; // 0 = 'everything', -1 = 'nothing', >0 = amount of bytes
|
||||
size_t buf_written = 0;
|
||||
if(!mrtd_send_apdu(
|
||||
app, 0x00, 0xB0, offset >> 8, offset & 0xff, 0x00, NULL, max_read, buffer, &buf_written)) {
|
||||
FURI_LOG_E(TAG, "Failed to read");
|
||||
return 0;
|
||||
}
|
||||
FURI_LOG_D(TAG, "buf_written: %d\n", buf_written);
|
||||
|
||||
return buf_written;
|
||||
}
|
||||
|
||||
//TODO: use short id to read, because it's mandatory for eMRTD
|
||||
//TODO: check for support of extended length in EF.ATR/INFO, see ISO7816-4
|
||||
|
||||
void mrtd_read_dump(MrtdApplication* app, EFFile file) {
|
||||
FURI_LOG_D(TAG, "Read and dump %s:", file.name);
|
||||
|
||||
if(!mrtd_select_file(app, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t data[2048];
|
||||
size_t read = 0;
|
||||
size_t offset = 0;
|
||||
do {
|
||||
read = mrtd_read_binary(app, data, sizeof(data), offset);
|
||||
offset += read;
|
||||
|
||||
hexdump(FuriLogLevelDebug, "Data:", data, read);
|
||||
} while(read > 0);
|
||||
}
|
||||
|
||||
bool parse_ef_dir(EF_DIR_contents* EF_DIR, const uint8_t* data, size_t length) {
|
||||
size_t offset = 0;
|
||||
uint8_t app_idx = 0;
|
||||
|
||||
memset(EF_DIR->applications, 0x00, sizeof(EF_DIR->applications));
|
||||
EF_DIR->applications_count = 0;
|
||||
|
||||
while(offset < length) {
|
||||
TlvInfo tlv = iso7816_tlv_parse(data + offset);
|
||||
|
||||
if(tlv.tag != 0x61 || tlv.length != 0x09) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Invalid EF.DIR, tag at offset %d must be '61' and length 9. Got '%02X' and %d",
|
||||
offset,
|
||||
tlv.tag,
|
||||
tlv.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
tlv = iso7816_tlv_parse(tlv.value);
|
||||
if(tlv.tag != 0x4F || tlv.length != 0x07) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Invalid EF.DIR, subtag at offset %d must be '4F' and length 7", offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(EF_DIR->applications[app_idx], tlv.value, tlv.length);
|
||||
EF_DIR->applications_count = ++app_idx;
|
||||
|
||||
offset = tlv.next - data;
|
||||
}
|
||||
|
||||
//TODO: remove testing block:
|
||||
FURI_LOG_D(TAG, "EF.DIR applications: %d", EF_DIR->applications_count);
|
||||
if(furi_log_get_level() >= FuriLogLevelDebug) {
|
||||
for(uint8_t i = 0; i < EF_DIR->applications_count; ++i) {
|
||||
printf("- ");
|
||||
for(uint8_t n = 0; n < sizeof(AIDValue); ++n) {
|
||||
printf("%02X ", EF_DIR->applications[i][n]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_ef_com(EF_COM_contents* EF_COM, const uint8_t* data, size_t length) {
|
||||
uint16_t lds_tag_path[] = {0x60, 0x5f01};
|
||||
uint16_t unicode_tag_path[] = {0x60, 0x5f36};
|
||||
uint16_t tags_tag_path[] = {0x60, 0x5c};
|
||||
|
||||
TlvInfo tlv_lds_version =
|
||||
iso7816_tlv_select(data, length, lds_tag_path, num_elements(lds_tag_path));
|
||||
if(!tlv_lds_version.tag) {
|
||||
FURI_LOG_W(TAG, "EF.COM LDS version not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
EF_COM->lds_version = tlv_number(tlv_lds_version);
|
||||
|
||||
TlvInfo tlv_unicode_version =
|
||||
iso7816_tlv_select(data, length, unicode_tag_path, num_elements(unicode_tag_path));
|
||||
if(!tlv_unicode_version.tag) {
|
||||
FURI_LOG_W(TAG, "EF.COM Unicode info not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
EF_COM->unicode_version = tlv_number(tlv_unicode_version);
|
||||
|
||||
TlvInfo tlv_tag_list =
|
||||
iso7816_tlv_select(data, length, tags_tag_path, num_elements(tags_tag_path));
|
||||
if(!tlv_tag_list.tag) {
|
||||
FURI_LOG_W(TAG, "EF.CO Tag List not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < MAX_EFCOM_TAGS; ++i) {
|
||||
EF_COM->tag_list[i] = (i < tlv_tag_list.length) ? tlv_tag_list.value[i] : 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mrzcpy(uint8_t* dest, const uint8_t* src, size_t* idx, size_t n) {
|
||||
//FURI_LOG_D(TAG, "mrzcpy %d: %.*s", n, n, src + *idx);
|
||||
//memcpy(dest, src + *idx, n);
|
||||
for(size_t i = 0; i < n; ++i) {
|
||||
uint8_t c = src[i + *idx];
|
||||
if(c == '<') {
|
||||
c = ' ';
|
||||
}
|
||||
dest[i] = c;
|
||||
}
|
||||
dest[n] = 0x00;
|
||||
*idx += n;
|
||||
}
|
||||
|
||||
bool parse_ef_dg1(EF_DG1_contents* DG1, const uint8_t* data, size_t length) {
|
||||
TlvInfo tlv_mrz = iso7816_tlv_select(data, length, (uint16_t[]){0x61, 0x5f1f}, 2);
|
||||
|
||||
if(!tlv_mrz.tag) {
|
||||
FURI_LOG_W(TAG, "DG1, unexpected content. Could not find tag 0x61, 0x5f1f");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* mrz = tlv_mrz.value;
|
||||
size_t idx = 0;
|
||||
|
||||
switch(tlv_mrz.length) {
|
||||
case 90:
|
||||
DG1->type = MrtdTypeTD1;
|
||||
mrzcpy(DG1->doctype, mrz, &idx, 2);
|
||||
mrzcpy(DG1->issuing_state, mrz, &idx, 3);
|
||||
mrzcpy(DG1->docnr, mrz, &idx, 9);
|
||||
idx += 1; // docnr check digit
|
||||
idx += 15; // optional data
|
||||
mrtd_parse_date(&DG1->birth_date, mrz + idx);
|
||||
idx += 6; // birth_date
|
||||
idx += 1; // birth date check digit
|
||||
mrzcpy(DG1->sex, mrz, &idx, 1);
|
||||
mrtd_parse_date(&DG1->expiry_date, mrz + idx);
|
||||
idx += 6; // expiry_date
|
||||
idx += 1; // expiry date check digit
|
||||
mrzcpy(DG1->nationality, mrz, &idx, 3);
|
||||
idx += 11; // optional data
|
||||
idx += 1; // check digit
|
||||
mrzcpy(DG1->name, mrz, &idx, 30);
|
||||
// 30 + 30 + 30
|
||||
break;
|
||||
case 72:
|
||||
DG1->type = MrtdTypeTD2;
|
||||
mrzcpy(DG1->doctype, mrz, &idx, 2);
|
||||
mrzcpy(DG1->issuing_state, mrz, &idx, 3);
|
||||
mrzcpy(DG1->name, mrz, &idx, 31);
|
||||
mrzcpy(DG1->docnr, mrz, &idx, 9);
|
||||
idx += 1; // docnr check digit
|
||||
mrzcpy(DG1->nationality, mrz, &idx, 3);
|
||||
mrtd_parse_date(&DG1->birth_date, mrz + idx);
|
||||
idx += 6; // birth_date
|
||||
idx += 1; // birth date check digit
|
||||
mrzcpy(DG1->sex, mrz, &idx, 1);
|
||||
mrtd_parse_date(&DG1->expiry_date, mrz + idx);
|
||||
idx += 6; // expiry_date
|
||||
idx += 1; // expiry date check digit
|
||||
idx += 7; // optional data
|
||||
idx += 1; // check digit
|
||||
// 36 + 36
|
||||
break;
|
||||
case 88:
|
||||
DG1->type = MrtdTypeTD3;
|
||||
mrzcpy(DG1->doctype, mrz, &idx, 2);
|
||||
mrzcpy(DG1->issuing_state, mrz, &idx, 3);
|
||||
mrzcpy(DG1->name, mrz, &idx, 39);
|
||||
mrzcpy(DG1->docnr, mrz, &idx, 9);
|
||||
idx += 1; // docnr check digit
|
||||
mrzcpy(DG1->nationality, mrz, &idx, 3);
|
||||
mrtd_parse_date(&DG1->birth_date, mrz + idx);
|
||||
idx += 1; // birth date check digit
|
||||
idx += 6; // birth_date
|
||||
mrzcpy(DG1->sex, mrz, &idx, 1);
|
||||
mrtd_parse_date(&DG1->expiry_date, mrz + idx);
|
||||
idx += 6; // expiry_date
|
||||
idx += 1; // expiry date check digit
|
||||
idx += 14; // optional data
|
||||
idx += 1; // check digit
|
||||
idx += 1; // check digit
|
||||
// 44 + 44
|
||||
break;
|
||||
default:
|
||||
FURI_LOG_W(
|
||||
TAG, "Unexpected MRZ length in DG1: %d. TD1=90, TD2=72, TD3=88.", tlv_mrz.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_read_parse_file(MrtdApplication* app, EFFile file) {
|
||||
uint8_t buffer[100];
|
||||
size_t buf_len;
|
||||
|
||||
FURI_LOG_D(TAG, "Read and parse %s (%04X)", file.name, file.file_id);
|
||||
|
||||
if(!mrtd_select_file(app, file)) {
|
||||
FURI_LOG_E(TAG, "Could not select %s", file.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Selected %s", file.name);
|
||||
|
||||
buf_len = mrtd_read_binary(app, buffer, num_elements(buffer), 0);
|
||||
|
||||
if(!buf_len) {
|
||||
FURI_LOG_E(TAG, "Could not read %s", file.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Read %s", file.name);
|
||||
|
||||
bool result = false;
|
||||
|
||||
if(file.file_id == EF.COM.file_id) {
|
||||
result = parse_ef_com(&app->mrtd_data->files.EF_COM, buffer, buf_len);
|
||||
FURI_LOG_D(TAG, "Parsed EF.COM");
|
||||
} else if(file.file_id == EF.DIR.file_id) {
|
||||
result = parse_ef_dir(&app->mrtd_data->files.EF_DIR, buffer, buf_len);
|
||||
FURI_LOG_D(TAG, "Parsed EF.DIR");
|
||||
} else if(file.file_id == EF.DG1.file_id) {
|
||||
result = parse_ef_dg1(&app->mrtd_data->files.DG1, buffer, buf_len);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Don't know how to parse file with id 0x%04X", file.file_id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx, MrtdData* mrtd_data) {
|
||||
MrtdApplication* app = malloc(sizeof(MrtdApplication));
|
||||
|
||||
app->tx_rx = tx_rx;
|
||||
app->mrtd_data = mrtd_data;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void mrtd_free(MrtdApplication* app) {
|
||||
furi_assert(app);
|
||||
free(app);
|
||||
}
|
||||
|
||||
bool mrtd_bac(MrtdApplication* app, MrtdAuthData* auth) {
|
||||
UNUSED(app);
|
||||
|
||||
static bool rand_generator_inited = false;
|
||||
uint8_t rnd_ic[8];
|
||||
uint8_t rnd_ifd[8];
|
||||
uint8_t k_ifd[16];
|
||||
|
||||
if(!rand_generator_inited) {
|
||||
// TODO: should random initialization maybe be system wide?
|
||||
srand(DWT->CYCCNT);
|
||||
rand_generator_inited = true;
|
||||
}
|
||||
|
||||
mrtd_get_challenge(app, rnd_ic);
|
||||
//TODO: remove memcpy rnd_ic
|
||||
//memcpy(rnd_ic, "\x46\x08\xF9\x19\x88\x70\x22\x12", 8);
|
||||
|
||||
furi_hal_random_fill_buf(rnd_ifd, 8);
|
||||
furi_hal_random_fill_buf(k_ifd, 16);
|
||||
//TODO: remove testing code:
|
||||
//memcpy(rnd_ifd, "\x78\x17\x23\x86\x0C\x06\xC2\x26", 8);
|
||||
//memcpy(k_ifd, "\x0B\x79\x52\x40\xCB\x70\x49\xB0\x1C\x19\xB3\x3E\x32\x80\x4F\x0B", 16);
|
||||
|
||||
hexdump(FuriLogLevelDebug, "rnd_ifd:", rnd_ifd, 8);
|
||||
hexdump(FuriLogLevelDebug, "k_ifd:", k_ifd, 16);
|
||||
|
||||
uint8_t kenc[16];
|
||||
uint8_t kmac[16];
|
||||
|
||||
if(!mrtd_bac_keys(auth, kenc, kmac)) {
|
||||
FURI_LOG_E(TAG, "Failed to calculate BAC keys");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t S[32];
|
||||
memcpy(S, rnd_ifd, 8);
|
||||
memcpy(S + 8, rnd_ic, 8);
|
||||
memcpy(S + 16, k_ifd, 16);
|
||||
|
||||
hexdump(FuriLogLevelDebug, "S:", S, 32);
|
||||
|
||||
uint8_t cmd_data[40];
|
||||
uint8_t* eifd = cmd_data;
|
||||
uint8_t* mifd = cmd_data + 32;
|
||||
mrtd_bac_encrypt(S, 32, kenc, eifd);
|
||||
mrtd_bac_padded_mac(eifd, 32, kmac, mifd);
|
||||
|
||||
uint8_t response[40];
|
||||
if(!mrtd_external_authenticate(app, cmd_data, 40, response, 40)) {
|
||||
FURI_LOG_E(TAG, "BAC External Authenticate failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buffer[32]; // Received R = RND.IC (8) || RND.IFD (8) || KIC (16)
|
||||
if(!mrtd_bac_decrypt_verify(response, 40, kenc, kmac, buffer)) {
|
||||
FURI_LOG_W(TAG, "BAC DecryptVerify failed");
|
||||
}
|
||||
|
||||
uint8_t* rnd_ifd_recv = buffer + 8;
|
||||
uint8_t* kic = buffer + 16;
|
||||
|
||||
hexdump(FuriLogLevelDebug, "kic:", kic, 16);
|
||||
|
||||
if(memcmp(rnd_ifd, rnd_ifd_recv, 8)) {
|
||||
FURI_LOG_W(TAG, "BAC RND.IFD sent and received mismatch.");
|
||||
}
|
||||
|
||||
uint8_t kseed[16];
|
||||
for(uint8_t i = 0; i < 16; ++i) {
|
||||
kseed[i] = k_ifd[i] ^ kic[i];
|
||||
//printf("seed %2d = %02X ^ %02X = %02X\r\n", i, k_ifd[i], kic[i], kseed[i]);
|
||||
}
|
||||
|
||||
hexdump(FuriLogLevelDebug, "kseed:", kseed, 16);
|
||||
|
||||
if(!mrtd_bac_keys_from_seed(kseed, app->ksenc, app->ksmac)) {
|
||||
FURI_LOG_E(TAG, "BAC error, could not derive KSenc and KSmac");
|
||||
return false;
|
||||
}
|
||||
hexdump(FuriLogLevelDebug, "ksenc:", app->ksenc, 16);
|
||||
hexdump(FuriLogLevelDebug, "ksmac:", app->ksmac, 16);
|
||||
|
||||
hexdump(FuriLogLevelTrace, "RND.IC:", rnd_ic, 8);
|
||||
hexdump(FuriLogLevelTrace, "RND.IFS:", rnd_ifd, 8);
|
||||
|
||||
app->ssc_long = mrtd_ssc_from_data(rnd_ic, rnd_ifd);
|
||||
FURI_LOG_D(TAG, "SSC: %01llX", app->ssc_long);
|
||||
|
||||
app->secure_messaging = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_authenticate(MrtdApplication* app) {
|
||||
MrtdAuthMethod method = app->mrtd_data->auth.method;
|
||||
app->mrtd_data->auth_success = false;
|
||||
app->mrtd_data->auth_method_used = MrtdAuthMethodNone;
|
||||
FURI_LOG_D(TAG, "Auth method: %d", method);
|
||||
switch(method) {
|
||||
case MrtdAuthMethodAny:
|
||||
//TODO: try PACE, then BAC. For now, fall through to just BAC
|
||||
case MrtdAuthMethodBac:
|
||||
app->mrtd_data->auth_success = mrtd_bac(app, &app->mrtd_data->auth);
|
||||
app->mrtd_data->auth_method_used = MrtdAuthMethodBac;
|
||||
break;
|
||||
case MrtdAuthMethodPace:
|
||||
FURI_LOG_E(TAG, "Auth method PACE not implemented");
|
||||
break;
|
||||
case MrtdAuthMethodNone:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(!app->mrtd_data->auth_success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_auth_params_save(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name) {
|
||||
return mrtd_auth_params_save_file(
|
||||
storage, dialogs, auth_data, file_name, MRTD_APP_FOLDER, MRTD_APP_EXTENSION);
|
||||
}
|
||||
|
||||
void mrtd_date_prepare_format_string(MrtdDate date, FuriString* format_string) {
|
||||
furi_string_printf(format_string, "%02u%02u%02u", date.year, date.month, date.day);
|
||||
}
|
||||
|
||||
bool mrtd_date_parse_format_string(MrtdDate* date, FuriString* format_string) {
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
|
||||
int ret = sscanf(furi_string_get_cstr(format_string), "%02d%02d%02d", &year, &month, &day);
|
||||
if(ret != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
date->year = year;
|
||||
date->month = month;
|
||||
date->day = day;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_auth_params_save_file(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name,
|
||||
const char* folder,
|
||||
const char* extension) {
|
||||
furi_assert(auth_data);
|
||||
|
||||
bool saved = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
// Create mrtd directory if necessary
|
||||
if(!storage_simply_mkdir(storage, MRTD_APP_FOLDER)) break;
|
||||
|
||||
furi_string_printf(temp_str, "%s/%s%s", folder, file_name, extension);
|
||||
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, mrtd_auth_file_header, mrtd_auth_file_version))
|
||||
break;
|
||||
|
||||
// Write auth method
|
||||
furi_string_set(temp_str, mrtd_auth_method_string(auth_data->method));
|
||||
if(!flipper_format_write_string(file, "Method", temp_str)) break;
|
||||
|
||||
// Write birth date
|
||||
mrtd_date_prepare_format_string(auth_data->birth_date, temp_str);
|
||||
if(!flipper_format_write_string(file, "BirthDate", temp_str)) break;
|
||||
|
||||
// Write expiry date
|
||||
mrtd_date_prepare_format_string(auth_data->expiry_date, temp_str);
|
||||
if(!flipper_format_write_string(file, "ExpiryDate", temp_str)) break;
|
||||
|
||||
// Write docnr
|
||||
furi_string_set(temp_str, auth_data->doc_number);
|
||||
if(!flipper_format_write_string(file, "DocNr", temp_str)) break;
|
||||
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
if(!saved) {
|
||||
dialog_message_show_storage_error(dialogs, "Can not save\nparams file");
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool mrtd_auth_params_load(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_path,
|
||||
bool show_dialog) {
|
||||
furi_assert(storage);
|
||||
furi_assert(dialogs);
|
||||
furi_assert(auth_data);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool parsed = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
bool deprecated_version = false;
|
||||
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
MrtdAuthData copy;
|
||||
|
||||
FURI_LOG_D(TAG, "Load auth params");
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, file_path)) break;
|
||||
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
FURI_LOG_D(TAG, "Version: %s", furi_string_get_cstr(temp_str));
|
||||
if(furi_string_cmp_str(temp_str, mrtd_auth_file_header) ||
|
||||
(version != mrtd_auth_file_version)) {
|
||||
deprecated_version = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_string(file, "Method", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "Method: %s", furi_string_get_cstr(temp_str));
|
||||
if(!mrtd_auth_method_parse_string(©.method, furi_string_get_cstr(temp_str))) break;
|
||||
|
||||
if(!flipper_format_read_string(file, "BirthDate", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "BirthDate: %s", furi_string_get_cstr(temp_str));
|
||||
if(!mrtd_date_parse_format_string(©.birth_date, temp_str)) break;
|
||||
|
||||
if(!flipper_format_read_string(file, "ExpiryDate", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "ExpiryDate: %s", furi_string_get_cstr(temp_str));
|
||||
if(!mrtd_date_parse_format_string(©.expiry_date, temp_str)) break;
|
||||
|
||||
if(!flipper_format_read_string(file, "DocNr", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "DocNr: %s", furi_string_get_cstr(temp_str));
|
||||
strlcpy(copy.doc_number, furi_string_get_cstr(temp_str), MRTD_DOCNR_MAX_LENGTH);
|
||||
|
||||
// Everything went fine. Save copy to pointed auth data
|
||||
*auth_data = copy;
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
FURI_LOG_D(TAG, "Load done, success: %d", parsed);
|
||||
|
||||
if(!parsed && show_dialog) {
|
||||
if(deprecated_version) {
|
||||
dialog_message_show_storage_error(dialogs, "File format deprecated");
|
||||
} else {
|
||||
dialog_message_show_storage_error(dialogs, "Can not parse\nfile");
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return parsed;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <helpers/mrtd_helpers.h>
|
||||
|
||||
#define MRTD_APP_FOLDER "/any/nfc/mrtd"
|
||||
#define MRTD_APP_EXTENSION ".mrtd"
|
||||
|
||||
typedef struct {
|
||||
FuriHalNfcTxRxContext* tx_rx;
|
||||
MrtdData* mrtd_data;
|
||||
uint16_t file_offset;
|
||||
uint8_t ksenc[16];
|
||||
uint8_t ksmac[16];
|
||||
uint64_t ssc_long; // TODO: rename without _long
|
||||
|
||||
bool secure_messaging;
|
||||
} MrtdApplication;
|
||||
|
||||
//TODO: description
|
||||
MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx, MrtdData* mrtd_data);
|
||||
bool mrtd_select_app(MrtdApplication* app, AIDValue aid);
|
||||
bool mrtd_authenticate(MrtdApplication* app);
|
||||
bool mrtd_read_parse_file(MrtdApplication* app, EFFile file);
|
||||
|
||||
bool mrtd_auth_params_save(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name);
|
||||
bool mrtd_auth_params_save_file(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name,
|
||||
const char* folder,
|
||||
const char* extension);
|
||||
|
||||
bool mrtd_auth_params_load(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_path,
|
||||
bool show_dialog);
|
||||
@@ -1,169 +0,0 @@
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <st25r3916.h>
|
||||
#include <st25r3916_irq.h>
|
||||
|
||||
#include "nfca_trans_rx.h"
|
||||
|
||||
#define TAG "NfcA-trans-rx"
|
||||
|
||||
void nfca_trans_rx_init(NfcaTransRxState* state) {
|
||||
FURI_LOG_D(TAG, "Starting NfcA transparent rx");
|
||||
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_STOP);
|
||||
st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0xC3);
|
||||
st25r3916WriteRegister(ST25R3916_REG_MODE, 0x88);
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
/* allocate a 512 edge buffer, more than enough */
|
||||
state->reader_signal = pulse_reader_alloc(&gpio_spi_r_miso, 512);
|
||||
/* timebase shall be 1 ns */
|
||||
pulse_reader_set_timebase(state->reader_signal, PulseReaderUnitNanosecond);
|
||||
|
||||
pulse_reader_start(state->reader_signal);
|
||||
|
||||
/* set start values */
|
||||
state->bits_received = 0;
|
||||
state->have_sof = false;
|
||||
state->valid_frame = false;
|
||||
}
|
||||
|
||||
void nfca_trans_rx_deinit(NfcaTransRxState* state) {
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
|
||||
pulse_reader_free(state->reader_signal);
|
||||
}
|
||||
|
||||
void nfca_trans_rx_pause(NfcaTransRxState* state) {
|
||||
pulse_reader_stop(state->reader_signal);
|
||||
}
|
||||
|
||||
void nfca_trans_rx_continue(NfcaTransRxState* state) {
|
||||
pulse_reader_start(state->reader_signal);
|
||||
}
|
||||
|
||||
static void nfca_bit_received(NfcaTransRxState* state, uint8_t bit) {
|
||||
/* According to ISO14443-3 short frames have 7 bits and standard 9 bits per byte,
|
||||
where the 9th bit is odd parity. Data is transmitted LSB first. */
|
||||
uint32_t byte_num = (state->bits_received / 9);
|
||||
uint32_t bit_num = (state->bits_received % 9);
|
||||
|
||||
if(byte_num >= NFCA_FRAME_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(bit_num == 8) {
|
||||
uint32_t parity_value = 1 << (state->bits_received / 9);
|
||||
state->parity_bits &= ~parity_value;
|
||||
state->parity_bits |= bit ? parity_value : 0;
|
||||
} else {
|
||||
uint32_t bit_value = 1 << bit_num;
|
||||
state->frame_data[byte_num] &= ~bit_value;
|
||||
state->frame_data[byte_num] |= bit ? bit_value : 0;
|
||||
}
|
||||
|
||||
state->bits_received++;
|
||||
}
|
||||
|
||||
bool nfca_trans_rx_loop(NfcaTransRxState* state, uint32_t timeout_ms) {
|
||||
furi_assert(state);
|
||||
|
||||
state->valid_frame = false;
|
||||
state->have_sof = false;
|
||||
state->bits_received = 0;
|
||||
|
||||
bool done = false;
|
||||
|
||||
uint32_t timeout_us = timeout_ms * 1000;
|
||||
|
||||
while(!done) {
|
||||
uint32_t nsec = pulse_reader_receive(state->reader_signal, timeout_us);
|
||||
|
||||
bool eof = state->have_sof && (nsec >= (2 * NFCA_TB));
|
||||
bool lost_pulse = false;
|
||||
|
||||
if(state->have_sof && nsec == PULSE_READER_LOST_EDGE) {
|
||||
nsec = NFCA_T1;
|
||||
lost_pulse = true;
|
||||
} else if(nsec == PULSE_READER_NO_EDGE) {
|
||||
done = true;
|
||||
}
|
||||
|
||||
if(IS_T1(nsec) || eof) {
|
||||
timeout_us = (3 * NFCA_TB) / 1000;
|
||||
if(!state->have_sof) {
|
||||
state->frame_time = -(NFCA_TB - nsec);
|
||||
state->have_sof = true;
|
||||
state->valid_frame = false;
|
||||
state->bits_received = 0;
|
||||
state->debug_pos = 0;
|
||||
if(lost_pulse) {
|
||||
state->frame_time -= nsec;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(state->frame_time > NFCA_TB_MIN) {
|
||||
state->frame_time -= NFCA_TB;
|
||||
nfca_bit_received(state, 0);
|
||||
}
|
||||
|
||||
if(IS_ZERO(state->frame_time)) {
|
||||
state->frame_time = -(NFCA_TB - nsec);
|
||||
nfca_bit_received(state, 0);
|
||||
} else if(IS_TX(state->frame_time)) {
|
||||
state->frame_time = -(NFCA_TX - nsec);
|
||||
nfca_bit_received(state, 1);
|
||||
} else {
|
||||
if(eof) {
|
||||
state->have_sof = false;
|
||||
state->valid_frame = true;
|
||||
done = true;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(!state->have_sof) {
|
||||
if(IS_TB(nsec)) {
|
||||
state->frame_time = 0;
|
||||
state->have_sof = true;
|
||||
state->valid_frame = false;
|
||||
state->bits_received = 0;
|
||||
state->debug_pos = 0;
|
||||
if(lost_pulse) {
|
||||
state->frame_time -= nsec;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
state->frame_time = 0;
|
||||
}
|
||||
} else {
|
||||
state->frame_time += nsec;
|
||||
}
|
||||
}
|
||||
|
||||
if(lost_pulse) {
|
||||
state->frame_time -= nsec;
|
||||
}
|
||||
}
|
||||
|
||||
if(state->valid_frame) {
|
||||
if(state->bits_received > 7) {
|
||||
/* a last 0-bit will look like a missing bit */
|
||||
if((state->bits_received % 9) == 8) {
|
||||
nfca_bit_received(state, 0);
|
||||
state->bits_received++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state->valid_frame;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <lib/digital_signal/digital_signal.h>
|
||||
#include <lib/pulse_reader/pulse_reader.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#include "nfc_util.h"
|
||||
|
||||
/* assume fc/128 */
|
||||
#define NFCA_FC (13560000.0f) /* MHz */
|
||||
#define NFCA_FC_K ((uint32_t)(NFCA_FC / 1000)) /* kHz */
|
||||
#define NFCA_T1 (28.0f / NFCA_FC * 1000000000)
|
||||
#define NFCA_T1_MIN (24.0f / NFCA_FC * 1000000000)
|
||||
#define NFCA_T1_MAX (41.0f / NFCA_FC * 1000000000)
|
||||
#define NFCA_TX (64.0f / NFCA_FC * 1000000000) /* 4.7198 µs */
|
||||
#define NFCA_TX_MIN (0.90f * NFCA_TX)
|
||||
#define NFCA_TX_MAX (1.10f * NFCA_TX)
|
||||
#define NFCA_TB (128.0f / NFCA_FC * 1000000000) /* 9.4395 µs */
|
||||
#define NFCA_TB_MIN (0.80f * NFCA_TB)
|
||||
#define NFCA_TB_MAX (1.20f * NFCA_TB)
|
||||
|
||||
#define IS_T1(x) ((x) >= NFCA_T1_MIN && (x) <= NFCA_T1_MAX)
|
||||
#define IS_TX(x) ((x) >= NFCA_TX_MIN && (x) <= NFCA_TX_MAX)
|
||||
#define IS_TB(x) ((x) >= NFCA_TB_MIN && (x) <= NFCA_TB_MAX)
|
||||
#define IS_ZERO(x) ((x) >= -NFCA_T1_MIN / 2 && (x) <= NFCA_T1_MIN / 2)
|
||||
|
||||
#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f)
|
||||
#define DIGITAL_SIGNAL_UNIT_US (100000.0f)
|
||||
|
||||
#define NFCA_FRAME_LENGTH 32
|
||||
#define NFCA_DEBUG_LENGTH 128
|
||||
|
||||
typedef struct {
|
||||
bool have_sof;
|
||||
bool valid_frame;
|
||||
int32_t frame_time;
|
||||
size_t bits_received;
|
||||
uint8_t frame_data[NFCA_FRAME_LENGTH];
|
||||
uint32_t debug_buffer[NFCA_DEBUG_LENGTH];
|
||||
size_t debug_pos;
|
||||
uint32_t parity_bits;
|
||||
PulseReader* reader_signal;
|
||||
} NfcaTransRxState;
|
||||
|
||||
bool nfca_trans_rx_loop(NfcaTransRxState* state, uint32_t timeout_ms);
|
||||
void nfca_trans_rx_deinit(NfcaTransRxState* state);
|
||||
void nfca_trans_rx_init(NfcaTransRxState* state);
|
||||
|
||||
void nfca_trans_rx_pause(NfcaTransRxState* state);
|
||||
void nfca_trans_rx_continue(NfcaTransRxState* state);
|
||||
@@ -1,827 +0,0 @@
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <st25r3916.h>
|
||||
#include <st25r3916_irq.h>
|
||||
|
||||
#include "nfcv.h"
|
||||
#include "nfc_util.h"
|
||||
#include "slix.h"
|
||||
|
||||
#define TAG "NfcV"
|
||||
|
||||
ReturnCode nfcv_inventory(uint8_t* uid) {
|
||||
uint16_t received = 0;
|
||||
rfalNfcvInventoryRes res;
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
for(int tries = 0; tries < 5; tries++) {
|
||||
/* TODO: needs proper abstraction via fury_hal(_ll)_* */
|
||||
ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
if(uid != NULL) {
|
||||
memcpy(uid, res.UID, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) {
|
||||
UNUSED(reader);
|
||||
|
||||
uint16_t received = 0;
|
||||
for(size_t block = 0; block < nfcv_data->block_num; block++) {
|
||||
uint8_t rxBuf[32];
|
||||
FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1));
|
||||
|
||||
ReturnCode ret = ERR_NONE;
|
||||
for(int tries = 0; tries < 5; tries++) {
|
||||
ret = rfalNfcvPollerReadSingleBlock(
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(ret != ERR_NONE) {
|
||||
FURI_LOG_D(TAG, "failed to read: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
memcpy(
|
||||
&(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" %02X %02X %02X %02X",
|
||||
nfcv_data->data[block * nfcv_data->block_size + 0],
|
||||
nfcv_data->data[block * nfcv_data->block_size + 1],
|
||||
nfcv_data->data[block * nfcv_data->block_size + 2],
|
||||
nfcv_data->data[block * nfcv_data->block_size + 3]);
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
uint8_t rxBuf[32];
|
||||
uint16_t received = 0;
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
FURI_LOG_D(TAG, "Read SYSTEM INFORMATION...");
|
||||
|
||||
for(int tries = 0; tries < 5; tries++) {
|
||||
/* TODO: needs proper abstraction via fury_hal(_ll)_* */
|
||||
ret = rfalNfcvPollerGetSystemInformation(
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
nfc_data->type = FuriHalNfcTypeV;
|
||||
nfc_data->uid_len = 8;
|
||||
/* UID is stored reversed in this response */
|
||||
for(int pos = 0; pos < nfc_data->uid_len; pos++) {
|
||||
nfc_data->uid[pos] = rxBuf[2 + (7 - pos)];
|
||||
}
|
||||
nfcv_data->dsfid = rxBuf[10];
|
||||
nfcv_data->afi = rxBuf[11];
|
||||
nfcv_data->block_num = rxBuf[12] + 1;
|
||||
nfcv_data->block_size = rxBuf[13] + 1;
|
||||
nfcv_data->ic_ref = rxBuf[14];
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" UID: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7]);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d",
|
||||
nfcv_data->dsfid,
|
||||
nfcv_data->afi,
|
||||
nfcv_data->block_num,
|
||||
nfcv_data->block_size,
|
||||
nfcv_data->ic_ref);
|
||||
return ret;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Failed: %d", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
furi_assert(reader);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data);
|
||||
|
||||
if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(slix_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlix;
|
||||
} else if(slix2_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX2 detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlix2;
|
||||
} else if(slix_s_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX-S detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlixS;
|
||||
} else if(slix_l_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX-L detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlixL;
|
||||
} else {
|
||||
nfcv_data->sub_type = NfcVTypePlain;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void nfcv_crc(uint8_t* data, uint32_t length) {
|
||||
uint32_t reg = 0xFFFF;
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
reg = reg ^ ((uint32_t)data[i]);
|
||||
for(size_t j = 0; j < 8; j++) {
|
||||
if(reg & 0x0001) {
|
||||
reg = (reg >> 1) ^ 0x8408;
|
||||
} else {
|
||||
reg = (reg >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t crc = ~(uint16_t)(reg & 0xffff);
|
||||
|
||||
data[length + 0] = crc & 0xFF;
|
||||
data[length + 1] = crc >> 8;
|
||||
}
|
||||
|
||||
void nfcv_emu_free(NfcVData* nfcv_data) {
|
||||
if(nfcv_data->emu_air.nfcv_signal) {
|
||||
digital_sequence_free(nfcv_data->emu_air.nfcv_signal);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_unmod_256) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_pulse_32) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_one) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_one);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_zero) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_zero);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_sof) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_sof);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_eof) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_eof);
|
||||
}
|
||||
if(nfcv_data->emu_air.reader_signal) {
|
||||
pulse_reader_free(nfcv_data->emu_air.reader_signal);
|
||||
}
|
||||
|
||||
nfcv_data->emu_air.nfcv_signal = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256 = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32 = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_one = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_zero = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_sof = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_eof = NULL;
|
||||
nfcv_data->emu_air.reader_signal = NULL;
|
||||
}
|
||||
|
||||
void nfcv_emu_alloc(NfcVData* nfcv_data) {
|
||||
if(!nfcv_data->emu_air.nfcv_signal) {
|
||||
/* assuming max frame length is 255 bytes */
|
||||
nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi);
|
||||
}
|
||||
|
||||
if(!nfcv_data->emu_air.nfcv_resp_unmod_256) {
|
||||
/* unmodulated 256/fc signal as building block */
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256 = digital_signal_alloc(4);
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256->start_level = false;
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256->edge_timings[0] =
|
||||
(uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S);
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256->edge_cnt = 1;
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_pulse_32) {
|
||||
/* modulated fc/32 pulse as building block */
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32 = digital_signal_alloc(4);
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->start_level = true;
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->edge_timings[0] =
|
||||
(uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->edge_timings[1] =
|
||||
(uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->edge_cnt = 2;
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_one) {
|
||||
/* logical one: 256/fc unmodulated then 8 pulses fc/32 */
|
||||
nfcv_data->emu_air.nfcv_resp_one = digital_signal_alloc(24);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_one, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_one, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_zero) {
|
||||
/* logical zero: 8 pulses fc/32 then 256/fc unmodulated */
|
||||
nfcv_data->emu_air.nfcv_resp_zero = digital_signal_alloc(24);
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_zero, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_zero, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_sof) {
|
||||
/* SOF: unmodulated 768/fc, 24 pulses fc/32, logic 1 */
|
||||
nfcv_data->emu_air.nfcv_resp_sof = digital_signal_alloc(128);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
for(size_t i = 0; i < 24; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
digital_signal_append(nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_one);
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_eof) {
|
||||
/* EOF: logic 0, 24 pulses fc/32, unmodulated 768/fc */
|
||||
nfcv_data->emu_air.nfcv_resp_eof = digital_signal_alloc(128);
|
||||
digital_signal_append(nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_zero);
|
||||
for(size_t i = 0; i < 24; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
/* add extra silence */
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
}
|
||||
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_SOF, nfcv_data->emu_air.nfcv_resp_sof);
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_BIT0, nfcv_data->emu_air.nfcv_resp_zero);
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_BIT1, nfcv_data->emu_air.nfcv_resp_one);
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_EOF, nfcv_data->emu_air.nfcv_resp_eof);
|
||||
}
|
||||
|
||||
void nfcv_emu_send(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
NfcVData* nfcv,
|
||||
uint8_t* data,
|
||||
uint8_t length,
|
||||
NfcVSendFlags flags,
|
||||
uint32_t send_time) {
|
||||
/* picked default value (0) to match the most common format */
|
||||
if(!flags) {
|
||||
flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof |
|
||||
NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate;
|
||||
}
|
||||
|
||||
if(flags & NfcVSendFlagsCrc) {
|
||||
nfcv_crc(data, length);
|
||||
length += 2;
|
||||
}
|
||||
|
||||
digital_sequence_clear(nfcv->emu_air.nfcv_signal);
|
||||
|
||||
if(flags & NfcVSendFlagsSof) {
|
||||
digital_sequence_add(nfcv->emu_air.nfcv_signal, NFCV_SIG_SOF);
|
||||
}
|
||||
|
||||
for(int bit_total = 0; bit_total < length * 8; bit_total++) {
|
||||
uint32_t byte_pos = bit_total / 8;
|
||||
uint32_t bit_pos = bit_total % 8;
|
||||
uint8_t bit_val = 0x01 << bit_pos;
|
||||
|
||||
digital_sequence_add(
|
||||
nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? NFCV_SIG_BIT1 : NFCV_SIG_BIT0);
|
||||
}
|
||||
|
||||
if(flags & NfcVSendFlagsEof) {
|
||||
digital_sequence_add(nfcv->emu_air.nfcv_signal, NFCV_SIG_EOF);
|
||||
}
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time);
|
||||
digital_sequence_send(nfcv->emu_air.nfcv_signal);
|
||||
FURI_CRITICAL_EXIT();
|
||||
furi_hal_gpio_write(&gpio_spi_r_mosi, false);
|
||||
|
||||
if(tx_rx->sniff_tx) {
|
||||
tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) {
|
||||
for(int pos = 0; pos < 8; pos++) {
|
||||
dst[pos] = src[7 - pos];
|
||||
}
|
||||
}
|
||||
|
||||
static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) {
|
||||
for(int pos = 0; pos < 8; pos++) {
|
||||
if(dst[pos] != src[7 - pos]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nfcv_emu_handle_packet(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
|
||||
if(nfcv_data->frame_length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* parse the frame data for the upcoming part 3 handling */
|
||||
ctx->flags = nfcv_data->frame[0];
|
||||
ctx->command = nfcv_data->frame[1];
|
||||
ctx->addressed = !(ctx->flags & RFAL_NFCV_REQ_FLAG_INVENTORY) &&
|
||||
(ctx->flags & RFAL_NFCV_REQ_FLAG_ADDRESS);
|
||||
ctx->advanced = (ctx->command >= 0xA0);
|
||||
ctx->address_offset = 2 + (ctx->advanced ? 1 : 0);
|
||||
ctx->payload_offset = ctx->address_offset + (ctx->addressed ? 8 : 0);
|
||||
ctx->response_flags = NfcVSendFlagsNormal;
|
||||
ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4130);
|
||||
|
||||
/* standard behavior is implemented */
|
||||
if(ctx->addressed) {
|
||||
uint8_t* address = &nfcv_data->frame[ctx->address_offset];
|
||||
if(nfcv_revuidcmp(address, nfc_data->uid)) {
|
||||
FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" dest: %02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
address[7],
|
||||
address[6],
|
||||
address[5],
|
||||
address[4],
|
||||
address[3],
|
||||
address[2],
|
||||
address[1],
|
||||
address[0]);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" our UID: %02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* then give control to the card subtype specific protocol filter */
|
||||
if(ctx->emu_protocol_filter != NULL) {
|
||||
if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) {
|
||||
if(strlen(nfcv_data->last_command) > 0) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Received command %s (handled by filter)", nfcv_data->last_command);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch(ctx->command) {
|
||||
case ISO15693_INVENTORY: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
ctx->response_buffer[1] = nfcv_data->dsfid;
|
||||
nfcv_revuidcpy(&ctx->response_buffer[2], nfc_data->uid);
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 10, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_STAYQUIET: {
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_LOCKBLOCK: {
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCKBLOCK");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_SELECT: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_READ_MULTI_BLOCK:
|
||||
case ISO15693_READBLOCK: {
|
||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||
uint8_t blocks = 1;
|
||||
|
||||
if(ctx->command == ISO15693_READ_MULTI_BLOCK) {
|
||||
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
||||
}
|
||||
|
||||
if(block + blocks > nfcv_data->block_num) {
|
||||
ctx->response_buffer[0] = ISO15693_ERROR_CMD_NOT_REC;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
} else {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
memcpy(
|
||||
&ctx->response_buffer[1],
|
||||
&nfcv_data->data[nfcv_data->block_size * block],
|
||||
nfcv_data->block_size * blocks);
|
||||
nfcv_emu_send(
|
||||
tx_rx,
|
||||
nfcv_data,
|
||||
ctx->response_buffer,
|
||||
1 + nfcv_data->block_size * blocks,
|
||||
ctx->response_flags,
|
||||
ctx->send_time);
|
||||
}
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block);
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_WRITE_MULTI_BLOCK:
|
||||
case ISO15693_WRITEBLOCK: {
|
||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||
uint8_t blocks = 1;
|
||||
uint8_t data_pos = 1;
|
||||
|
||||
if(ctx->command == ISO15693_WRITE_MULTI_BLOCK) {
|
||||
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
||||
data_pos++;
|
||||
}
|
||||
|
||||
uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos];
|
||||
uint32_t data_len = nfcv_data->block_size * blocks;
|
||||
|
||||
if(block + blocks > nfcv_data->block_num ||
|
||||
ctx->payload_offset + data_len + 2 > nfcv_data->frame_length) {
|
||||
ctx->response_buffer[0] = ISO15693_ERROR_CMD_NOT_REC;
|
||||
} else {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
memcpy(
|
||||
&nfcv_data->data[nfcv_data->block_size * block],
|
||||
&nfcv_data->frame[ctx->payload_offset + data_pos],
|
||||
data_len);
|
||||
}
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
|
||||
if(ctx->command == ISO15693_WRITE_MULTI_BLOCK) {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"WRITE MULTI BLOCK %d, %d blocks",
|
||||
block,
|
||||
blocks);
|
||||
} else {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"WRITE BLOCK %d <- %02X %02X %02X %02X",
|
||||
block,
|
||||
data[0],
|
||||
data[1],
|
||||
data[2],
|
||||
data[3]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_GET_SYSTEM_INFO: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
ctx->response_buffer[1] = 0x0F;
|
||||
nfcv_revuidcpy(&ctx->response_buffer[2], nfc_data->uid);
|
||||
ctx->response_buffer[10] = nfcv_data->dsfid; /* DSFID */
|
||||
ctx->response_buffer[11] = nfcv_data->afi; /* AFI */
|
||||
ctx->response_buffer[12] = nfcv_data->block_num - 1; /* number of blocks */
|
||||
ctx->response_buffer[13] = nfcv_data->block_size - 1; /* block size */
|
||||
ctx->response_buffer[14] = nfcv_data->ic_ref; /* IC reference */
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 15, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"unsupported: %02X",
|
||||
ctx->command);
|
||||
break;
|
||||
}
|
||||
|
||||
if(strlen(nfcv_data->last_command) > 0) {
|
||||
FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command);
|
||||
}
|
||||
}
|
||||
|
||||
void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
nfcv_emu_alloc(nfcv_data);
|
||||
rfal_platform_spi_acquire();
|
||||
/* configure for transparent and passive mode */
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_STOP);
|
||||
/* set enable, rx_enable and field detector enable */
|
||||
st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0xC3);
|
||||
/* target mode: ISO14443 passive mode */
|
||||
st25r3916WriteRegister(ST25R3916_REG_MODE, 0x88);
|
||||
/* let us modulate the field using MOSI, read modulation using MISO */
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
/* if not set already, initialize the default protocol handler */
|
||||
if(!nfcv_data->emu_protocol_ctx) {
|
||||
nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx));
|
||||
nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Starting NfcV emulation");
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" UID: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7]);
|
||||
|
||||
switch(nfcv_data->sub_type) {
|
||||
case NfcVTypeSlixL:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX-L");
|
||||
slix_l_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX-S");
|
||||
slix_s_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX2");
|
||||
slix2_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX");
|
||||
slix_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypePlain:
|
||||
FURI_LOG_D(TAG, " Card type: Plain");
|
||||
break;
|
||||
}
|
||||
|
||||
/* allocate a 512 edge buffer, more than enough */
|
||||
nfcv_data->emu_air.reader_signal = pulse_reader_alloc(&gpio_spi_r_miso, 512);
|
||||
/* timebase shall be 1 ns */
|
||||
pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond);
|
||||
/* and configure to already calculate the number of bits */
|
||||
pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, PULSE_DURATION_NS);
|
||||
pulse_reader_start(nfcv_data->emu_air.reader_signal);
|
||||
}
|
||||
|
||||
void nfcv_emu_deinit(NfcVData* nfcv_data) {
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
|
||||
rfal_platform_spi_release();
|
||||
nfcv_emu_free(nfcv_data);
|
||||
|
||||
if(nfcv_data->emu_protocol_ctx) {
|
||||
free(nfcv_data->emu_protocol_ctx);
|
||||
nfcv_data->emu_protocol_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool nfcv_emu_loop(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
NfcVData* nfcv_data,
|
||||
uint32_t timeout_ms) {
|
||||
bool ret = false;
|
||||
uint32_t frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
uint32_t periods_previous = 0;
|
||||
uint8_t frame_payload[128];
|
||||
uint32_t frame_pos = 0;
|
||||
uint32_t byte_value = 0;
|
||||
uint32_t bits_received = 0;
|
||||
char reset_reason[128];
|
||||
bool wait_for_pulse = false;
|
||||
|
||||
while(true) {
|
||||
uint32_t periods =
|
||||
pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout_ms * 1000);
|
||||
uint32_t timestamp = DWT->CYCCNT;
|
||||
|
||||
if(periods == PULSE_READER_NO_EDGE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(wait_for_pulse) {
|
||||
wait_for_pulse = false;
|
||||
if(periods != 1) {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"SOF: Expected a single low pulse in state %lu, but got %lu",
|
||||
frame_state,
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(frame_state) {
|
||||
case NFCV_FRAME_STATE_SOF1:
|
||||
if(periods == 1) {
|
||||
frame_state = NFCV_FRAME_STATE_SOF2;
|
||||
} else {
|
||||
frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case NFCV_FRAME_STATE_SOF2:
|
||||
/* waiting for the second low period, telling us about coding */
|
||||
if(periods == 6) {
|
||||
frame_state = NFCV_FRAME_STATE_CODING_256;
|
||||
periods_previous = 0;
|
||||
wait_for_pulse = true;
|
||||
} else if(periods == 4) {
|
||||
frame_state = NFCV_FRAME_STATE_CODING_4;
|
||||
periods_previous = 2;
|
||||
wait_for_pulse = true;
|
||||
} else {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"SOF: Expected 4/6 periods, got %lu",
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
}
|
||||
break;
|
||||
|
||||
case NFCV_FRAME_STATE_CODING_256:
|
||||
if(periods_previous > periods) {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"1oo256: Missing %lu periods from previous symbol, got %lu",
|
||||
periods_previous,
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
/* previous symbol left us with some pulse periods */
|
||||
periods -= periods_previous;
|
||||
|
||||
if(periods > 512) {
|
||||
snprintf(
|
||||
reset_reason, sizeof(reset_reason), "1oo256: %lu periods is too much", periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
if(periods == 2) {
|
||||
frame_state = NFCV_FRAME_STATE_EOF;
|
||||
break;
|
||||
}
|
||||
|
||||
periods_previous = 512 - (periods + 1);
|
||||
byte_value = (periods - 1) / 2;
|
||||
frame_payload[frame_pos++] = (uint8_t)byte_value;
|
||||
|
||||
wait_for_pulse = true;
|
||||
|
||||
break;
|
||||
|
||||
case NFCV_FRAME_STATE_CODING_4:
|
||||
if(periods_previous > periods) {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"1oo4: Missing %lu periods from previous symbol, got %lu",
|
||||
periods_previous,
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
/* previous symbol left us with some pulse periods */
|
||||
periods -= periods_previous;
|
||||
periods_previous = 0;
|
||||
|
||||
byte_value >>= 2;
|
||||
bits_received += 2;
|
||||
|
||||
if(periods == 1) {
|
||||
byte_value |= 0x00 << 6;
|
||||
periods_previous = 6;
|
||||
} else if(periods == 3) {
|
||||
byte_value |= 0x01 << 6;
|
||||
periods_previous = 4;
|
||||
} else if(periods == 5) {
|
||||
byte_value |= 0x02 << 6;
|
||||
periods_previous = 2;
|
||||
} else if(periods == 7) {
|
||||
byte_value |= 0x03 << 6;
|
||||
periods_previous = 0;
|
||||
} else if(periods == 2) {
|
||||
frame_state = NFCV_FRAME_STATE_EOF;
|
||||
break;
|
||||
} else {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"1oo4: Expected 1/3/5/7 low pulses, but got %lu",
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
if(bits_received >= 8) {
|
||||
frame_payload[frame_pos++] = (uint8_t)byte_value;
|
||||
bits_received = 0;
|
||||
}
|
||||
wait_for_pulse = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* post-state-machine cleanup and reset */
|
||||
if(frame_state == NFCV_FRAME_STATE_RESET) {
|
||||
frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
|
||||
FURI_LOG_D(TAG, "Resetting state machine, reason: '%s'", reset_reason);
|
||||
} else if(frame_state == NFCV_FRAME_STATE_EOF) {
|
||||
nfcv_data->frame = frame_payload;
|
||||
nfcv_data->frame_length = frame_pos;
|
||||
nfcv_data->eof_timestamp = timestamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(frame_state == NFCV_FRAME_STATE_EOF) {
|
||||
/* we know that this code uses TIM2, so stop pulse reader */
|
||||
pulse_reader_stop(nfcv_data->emu_air.reader_signal);
|
||||
if(tx_rx->sniff_rx) {
|
||||
tx_rx->sniff_rx(frame_payload, frame_pos * 8, false, tx_rx->sniff_context);
|
||||
}
|
||||
nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data);
|
||||
pulse_reader_start(nfcv_data->emu_air.reader_signal);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <lib/digital_signal/digital_signal.h>
|
||||
#include <lib/pulse_reader/pulse_reader.h>
|
||||
#include "nfc_util.h"
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NFCV_FC (13560000.0f) /* MHz */
|
||||
#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */
|
||||
#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */
|
||||
|
||||
#define PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) /* ns */
|
||||
|
||||
#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f)
|
||||
#define DIGITAL_SIGNAL_UNIT_US (100000.0f)
|
||||
|
||||
#define NFCV_TOTAL_BLOCKS_MAX 256
|
||||
#define NFCV_BLOCK_SIZE 4
|
||||
#define NFCV_MAX_DUMP_SIZE (NFCV_BLOCK_SIZE * NFCV_TOTAL_BLOCKS_MAX)
|
||||
|
||||
/* helpers to calculate the send time based on DWT->CYCCNT */
|
||||
#define NFCV_FDT_USEC(usec) (usec * 64)
|
||||
#define NFCV_FDT_FC(ticks) (ticks * 6400 / 1356)
|
||||
|
||||
#define NFCV_FRAME_STATE_SOF1 0
|
||||
#define NFCV_FRAME_STATE_SOF2 1
|
||||
#define NFCV_FRAME_STATE_CODING_4 2
|
||||
#define NFCV_FRAME_STATE_CODING_256 3
|
||||
#define NFCV_FRAME_STATE_EOF 4
|
||||
#define NFCV_FRAME_STATE_RESET 5
|
||||
|
||||
#define NFCV_SIG_SOF 0
|
||||
#define NFCV_SIG_BIT0 1
|
||||
#define NFCV_SIG_BIT1 2
|
||||
#define NFCV_SIG_EOF 3
|
||||
|
||||
/* ISO15693 command codes */
|
||||
#define ISO15693_INVENTORY 0x01
|
||||
#define ISO15693_STAYQUIET 0x02
|
||||
#define ISO15693_READBLOCK 0x20
|
||||
#define ISO15693_WRITEBLOCK 0x21
|
||||
#define ISO15693_LOCKBLOCK 0x22
|
||||
#define ISO15693_READ_MULTI_BLOCK 0x23
|
||||
#define ISO15693_WRITE_MULTI_BLOCK 0x24
|
||||
#define ISO15693_SELECT 0x25
|
||||
#define ISO15693_RESET_TO_READY 0x26
|
||||
#define ISO15693_WRITE_AFI 0x27
|
||||
#define ISO15693_LOCK_AFI 0x28
|
||||
#define ISO15693_WRITE_DSFID 0x29
|
||||
#define ISO15693_LOCK_DSFID 0x2A
|
||||
#define ISO15693_GET_SYSTEM_INFO 0x2B
|
||||
#define ISO15693_READ_MULTI_SECSTATUS 0x2C
|
||||
|
||||
/* ISO15693 RESPONSE ERROR CODES */
|
||||
#define ISO15693_NOERROR 0x00
|
||||
#define ISO15693_ERROR_CMD_NOT_SUP 0x01 // Command not supported
|
||||
#define ISO15693_ERROR_CMD_NOT_REC 0x02 // Command not recognized (eg. parameter error)
|
||||
#define ISO15693_ERROR_CMD_OPTION 0x03 // Command option not supported
|
||||
#define ISO15693_ERROR_GENERIC 0x0F // No additional Info about this error
|
||||
#define ISO15693_ERROR_BLOCK_UNAVAILABLE 0x10
|
||||
#define ISO15693_ERROR_BLOCK_LOCKED_ALREADY 0x11 // cannot lock again
|
||||
#define ISO15693_ERROR_BLOCK_LOCKED 0x12 // cannot be changed
|
||||
#define ISO15693_ERROR_BLOCK_WRITE 0x13 // Writing was unsuccessful
|
||||
#define ISO15693_ERROR_BLOCL_WRITELOCK 0x14 // Locking was unsuccessful
|
||||
|
||||
typedef enum {
|
||||
NfcVAuthMethodManual,
|
||||
NfcVAuthMethodTonieBox,
|
||||
} NfcVAuthMethod;
|
||||
|
||||
typedef enum {
|
||||
NfcVTypePlain = 0,
|
||||
NfcVTypeSlix = 1,
|
||||
NfcVTypeSlixS = 2,
|
||||
NfcVTypeSlixL = 3,
|
||||
NfcVTypeSlix2 = 4,
|
||||
} NfcVSubtype;
|
||||
|
||||
typedef enum {
|
||||
NfcVSendFlagsNormal = 0,
|
||||
NfcVSendFlagsSof = 1 << 0,
|
||||
NfcVSendFlagsCrc = 1 << 1,
|
||||
NfcVSendFlagsEof = 1 << 2,
|
||||
NfcVSendFlagsOneSubcarrier = 0,
|
||||
NfcVSendFlagsTwoSubcarrier = 1 << 3,
|
||||
NfcVSendFlagsLowRate = 0,
|
||||
NfcVSendFlagsHighRate = 1 << 4
|
||||
} NfcVSendFlags;
|
||||
|
||||
typedef struct {
|
||||
uint8_t key_read[4];
|
||||
uint8_t key_write[4];
|
||||
uint8_t key_privacy[4];
|
||||
uint8_t key_destroy[4];
|
||||
uint8_t key_eas[4];
|
||||
uint8_t rand[2];
|
||||
bool privacy;
|
||||
} NfcVSlixData;
|
||||
|
||||
typedef union {
|
||||
NfcVSlixData slix;
|
||||
} NfcVSubtypeData;
|
||||
|
||||
typedef struct {
|
||||
PulseReader* reader_signal;
|
||||
DigitalSignal* nfcv_resp_pulse_32;
|
||||
DigitalSignal* nfcv_resp_unmod;
|
||||
DigitalSignal* nfcv_resp_one;
|
||||
DigitalSignal* nfcv_resp_zero;
|
||||
DigitalSignal* nfcv_resp_sof;
|
||||
DigitalSignal* nfcv_resp_eof;
|
||||
DigitalSignal* nfcv_resp_unmod_256;
|
||||
DigitalSignal* nfcv_resp_unmod_768;
|
||||
DigitalSequence* nfcv_signal;
|
||||
} NfcVEmuAir;
|
||||
|
||||
typedef void (*NfcVEmuProtocolHandler)(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data);
|
||||
typedef bool (*NfcVEmuProtocolFilter)(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data);
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags; /* ISO15693-3 flags of the header as specified */
|
||||
uint8_t command; /* ISO15693-3 command at offset 1 as specified */
|
||||
bool addressed; /* ISO15693-3 flags: addressed frame */
|
||||
bool advanced; /* ISO15693-3 command: advanced command */
|
||||
uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */
|
||||
uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */
|
||||
|
||||
uint8_t response_buffer[128]; /* pre-allocated response buffer */
|
||||
NfcVSendFlags response_flags; /* flags to use when sending response */
|
||||
uint32_t send_time; /* timestamp when to send the response */
|
||||
|
||||
NfcVEmuProtocolFilter emu_protocol_filter;
|
||||
} NfcVEmuProtocolCtx;
|
||||
|
||||
typedef struct {
|
||||
/* common ISO15693 fields, being specified in ISO15693-3 */
|
||||
uint8_t dsfid;
|
||||
uint8_t afi;
|
||||
uint8_t ic_ref;
|
||||
uint16_t block_num;
|
||||
uint8_t block_size;
|
||||
uint8_t data[NFCV_MAX_DUMP_SIZE];
|
||||
|
||||
/* specfic variant infos */
|
||||
NfcVSubtype sub_type;
|
||||
NfcVSubtypeData sub_data;
|
||||
NfcVAuthMethod auth_method;
|
||||
|
||||
/* precalced air level data */
|
||||
NfcVEmuAir emu_air;
|
||||
|
||||
uint8_t* frame; /* ISO15693-2 incoming raw data from air layer */
|
||||
uint8_t frame_length; /* ISO15693-2 length of incoming data */
|
||||
uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */
|
||||
|
||||
/* handler for the protocol layer as specified in ISO15693-3 */
|
||||
NfcVEmuProtocolHandler emu_protocol_handler;
|
||||
void* emu_protocol_ctx;
|
||||
|
||||
/* runtime data */
|
||||
char last_command[128];
|
||||
char error[32];
|
||||
} NfcVData;
|
||||
|
||||
typedef struct {
|
||||
uint16_t blocks_to_read;
|
||||
int16_t blocks_read;
|
||||
} NfcVReader;
|
||||
|
||||
ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data);
|
||||
ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data);
|
||||
ReturnCode nfcv_inventory(uint8_t* uid);
|
||||
bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data);
|
||||
|
||||
void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||
void nfcv_emu_deinit(NfcVData* nfcv_data);
|
||||
bool nfcv_emu_loop(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
NfcVData* nfcv_data,
|
||||
uint32_t timeout_ms);
|
||||
void nfcv_emu_send(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
NfcVData* nfcv,
|
||||
uint8_t* data,
|
||||
uint8_t length,
|
||||
NfcVSendFlags flags,
|
||||
uint32_t send_time);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,407 +0,0 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include "nfcv.h"
|
||||
#include "slix.h"
|
||||
#include "nfc_util.h"
|
||||
#include <furi.h>
|
||||
#include "furi_hal_nfc.h"
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "SLIX"
|
||||
|
||||
static uint32_t slix_read_be(uint8_t* data, uint32_t length) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint32_t pos = 0; pos < length; pos++) {
|
||||
value <<= 8;
|
||||
value |= data[pos];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) {
|
||||
return (nfc_data->uid[3] >> 3) & 3;
|
||||
}
|
||||
|
||||
bool slix_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) &&
|
||||
slix_get_ti(nfc_data) == 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) &&
|
||||
slix_get_ti(nfc_data) == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ReturnCode slix_get_random(NfcVData* data) {
|
||||
uint16_t received = 0;
|
||||
uint8_t rxBuf[32];
|
||||
|
||||
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||
ISO15693_CMD_NXP_GET_RANDOM_NUMBER,
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT,
|
||||
ISO15693_MANUFACTURER_NXP,
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
rxBuf,
|
||||
sizeof(rxBuf),
|
||||
&received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
if(received != 3) {
|
||||
return ERR_PROTO;
|
||||
}
|
||||
if(data != NULL) {
|
||||
data->sub_data.slix.rand[0] = rxBuf[2];
|
||||
data->sub_data.slix.rand[1] = rxBuf[1];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) {
|
||||
furi_assert(rand);
|
||||
|
||||
uint16_t received = 0;
|
||||
uint8_t rxBuf[32];
|
||||
uint8_t cmd_set_pass[] = {
|
||||
password_id,
|
||||
data->sub_data.slix.rand[1],
|
||||
data->sub_data.slix.rand[0],
|
||||
data->sub_data.slix.rand[1],
|
||||
data->sub_data.slix.rand[0]};
|
||||
uint8_t* password = NULL;
|
||||
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
password = data->sub_data.slix.key_read;
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
password = data->sub_data.slix.key_write;
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
password = data->sub_data.slix.key_privacy;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
password = data->sub_data.slix.key_destroy;
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
password = data->sub_data.slix.key_eas;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(!password) {
|
||||
return ERR_NOTSUPP;
|
||||
}
|
||||
|
||||
for(int pos = 0; pos < 4; pos++) {
|
||||
cmd_set_pass[1 + pos] ^= password[3 - pos];
|
||||
}
|
||||
|
||||
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||
ISO15693_CMD_NXP_SET_PASSWORD,
|
||||
RFAL_NFCV_REQ_FLAG_DATA_RATE,
|
||||
ISO15693_MANUFACTURER_NXP,
|
||||
NULL,
|
||||
cmd_set_pass,
|
||||
sizeof(cmd_set_pass),
|
||||
rxBuf,
|
||||
sizeof(rxBuf),
|
||||
&received);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool slix_generic_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in,
|
||||
uint32_t password_supported) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||
|
||||
if(slix->privacy && ctx->command != ISO15693_CMD_NXP_GET_RANDOM_NUMBER &&
|
||||
ctx->command != ISO15693_CMD_NXP_SET_PASSWORD) {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"command 0x%02X ignored, privacy mode",
|
||||
ctx->command);
|
||||
FURI_LOG_D(TAG, "%s", nfcv_data->last_command);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
|
||||
switch(ctx->command) {
|
||||
case ISO15693_CMD_NXP_GET_RANDOM_NUMBER: {
|
||||
slix->rand[0] = furi_hal_random_get();
|
||||
slix->rand[1] = furi_hal_random_get();
|
||||
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
ctx->response_buffer[1] = slix->rand[1];
|
||||
ctx->response_buffer[2] = slix->rand[0];
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"GET_RANDOM_NUMBER -> 0x%02X%02X",
|
||||
slix->rand[0],
|
||||
slix->rand[1]);
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_CMD_NXP_SET_PASSWORD: {
|
||||
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
||||
|
||||
if(!(password_id & password_supported)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||
uint8_t* rand = slix->rand;
|
||||
uint8_t* password = NULL;
|
||||
uint8_t password_rcv[4];
|
||||
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
password = slix->key_read;
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
password = slix->key_write;
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
password = slix->key_privacy;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
password = slix->key_destroy;
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
password = slix->key_eas;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for(int pos = 0; pos < 4; pos++) {
|
||||
password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2];
|
||||
}
|
||||
uint32_t pass_expect = slix_read_be(password, 4);
|
||||
uint32_t pass_received = slix_read_be(password_rcv, 4);
|
||||
|
||||
/* if the password is all-zeroes, just accept any password*/
|
||||
if(!pass_expect || pass_expect == pass_received) {
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
slix->privacy = false;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
FURI_LOG_D(TAG, "Pooof! Got destroyed");
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"SET_PASSWORD #%02X 0x%08lX OK",
|
||||
password_id,
|
||||
pass_received);
|
||||
} else {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"SET_PASSWORD #%02X 0x%08lX/%08lX FAIL",
|
||||
password_id,
|
||||
pass_received,
|
||||
pass_expect);
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_CMD_NXP_ENABLE_PRIVACY: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"ISO15693_CMD_NXP_ENABLE_PRIVACY");
|
||||
|
||||
slix->privacy = true;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
bool slix_l_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(
|
||||
tx_rx,
|
||||
nfc_data,
|
||||
nfcv_data_in,
|
||||
SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix_l_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_l_protocol_filter;
|
||||
}
|
||||
|
||||
bool slix_s_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix_s_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_s_protocol_filter;
|
||||
}
|
||||
|
||||
bool slix_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_protocol_filter;
|
||||
}
|
||||
|
||||
bool slix2_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix2_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix2_protocol_filter;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "nfc_util.h"
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define ISO15693_MANUFACTURER_NXP 0x04
|
||||
|
||||
/* ISO15693-3 CUSTOM NXP COMMANDS */
|
||||
#define ISO15693_CMD_NXP_SET_EAS 0xA2
|
||||
#define ISO15693_CMD_NXP_RESET_EAS 0xA3
|
||||
#define ISO15693_CMD_NXP_LOCK_EAS 0xA4
|
||||
#define ISO15693_CMD_NXP_EAS_ALARM 0xA5
|
||||
#define ISO15693_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6
|
||||
#define ISO15693_CMD_NXP_WRITE_EAS_ID 0xA7
|
||||
#define ISO15693_CMD_NXP_INVENTORY_PAGE_READ 0xB0
|
||||
#define ISO15693_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1
|
||||
#define ISO15693_CMD_NXP_GET_RANDOM_NUMBER 0xB2
|
||||
#define ISO15693_CMD_NXP_SET_PASSWORD 0xB3
|
||||
#define ISO15693_CMD_NXP_WRITE_PASSWORD 0xB4
|
||||
#define ISO15693_CMD_NXP_DESTROY 0xB9
|
||||
#define ISO15693_CMD_NXP_ENABLE_PRIVACY 0xBA
|
||||
|
||||
/* available passwords */
|
||||
#define SLIX_PASS_READ 0x01
|
||||
#define SLIX_PASS_WRITE 0x02
|
||||
#define SLIX_PASS_PRIVACY 0x04
|
||||
#define SLIX_PASS_DESTROY 0x08
|
||||
#define SLIX_PASS_EASAFI 0x10
|
||||
|
||||
#define SLIX_PASS_ALL \
|
||||
(SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)
|
||||
|
||||
bool slix_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix2_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
|
||||
ReturnCode slix_get_random(NfcVData* data);
|
||||
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id);
|
||||
|
||||
void slix_prepare(NfcVData* nfcv_data);
|
||||
void slix_s_prepare(NfcVData* nfcv_data);
|
||||
void slix_l_prepare(NfcVData* nfcv_data);
|
||||
void slix2_prepare(NfcVData* nfcv_data);
|
||||
Reference in New Issue
Block a user