mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-18 00:38:10 -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:
+40
-7
@@ -5,20 +5,53 @@ env.Append(
|
||||
"#/lib/nfc",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
# Main
|
||||
File("nfc.h"),
|
||||
File("nfc_device.h"),
|
||||
File("nfc_worker.h"),
|
||||
File("nfc_types.h"),
|
||||
File("helpers/mfkey32.h"),
|
||||
File("parsers/nfc_supported_card.h"),
|
||||
File("helpers/nfc_generators.h"),
|
||||
File("protocols/nfc_util.h"),
|
||||
File("nfc_listener.h"),
|
||||
File("nfc_poller.h"),
|
||||
File("nfc_scanner.h"),
|
||||
# Protocols
|
||||
File("protocols/iso14443_3a/iso14443_3a.h"),
|
||||
File("protocols/iso14443_3b/iso14443_3b.h"),
|
||||
File("protocols/iso14443_4a/iso14443_4a.h"),
|
||||
File("protocols/iso14443_4b/iso14443_4b.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight.h"),
|
||||
File("protocols/mf_classic/mf_classic.h"),
|
||||
File("protocols/mf_desfire/mf_desfire.h"),
|
||||
File("protocols/slix/slix.h"),
|
||||
File("protocols/st25tb/st25tb.h"),
|
||||
# Pollers
|
||||
File("protocols/iso14443_3a/iso14443_3a_poller.h"),
|
||||
File("protocols/iso14443_3b/iso14443_3b_poller.h"),
|
||||
File("protocols/iso14443_4a/iso14443_4a_poller.h"),
|
||||
File("protocols/iso14443_4b/iso14443_4b_poller.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_poller.h"),
|
||||
File("protocols/mf_classic/mf_classic_poller.h"),
|
||||
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
||||
File("protocols/st25tb/st25tb_poller.h"),
|
||||
# Listeners
|
||||
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
|
||||
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_listener.h"),
|
||||
File("protocols/mf_classic/mf_classic_listener.h"),
|
||||
# Sync API
|
||||
File("protocols/iso14443_3a/iso14443_3a_poller_sync_api.h"),
|
||||
File("protocols/mf_ultralight/mf_ultralight_poller_sync_api.h"),
|
||||
File("protocols/mf_classic/mf_classic_poller_sync_api.h"),
|
||||
# Misc
|
||||
File("helpers/nfc_util.h"),
|
||||
File("helpers/iso14443_crc.h"),
|
||||
File("helpers/iso13239_crc.h"),
|
||||
File("helpers/nfc_data_generator.h"),
|
||||
File("helpers/nfc_dict.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="nfc")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
sources = libenv.GlobRecursive("*.c*")
|
||||
sources = libenv.GlobRecursive("*.c*", exclude="deprecated/*c")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "felica_crc.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define FELICA_CRC_POLY (0x1021U) // Polynomial: x^16 + x^12 + x^5 + 1
|
||||
#define FELICA_CRC_INIT (0x0000U)
|
||||
|
||||
uint16_t felica_crc_calculate(const uint8_t* data, size_t length) {
|
||||
uint16_t crc = FELICA_CRC_INIT;
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
crc ^= ((uint16_t)data[i] << 8);
|
||||
for(size_t j = 0; j < 8; j++) {
|
||||
if(crc & 0x8000) {
|
||||
crc <<= 1;
|
||||
crc ^= FELICA_CRC_POLY;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (crc << 8) | (crc >> 8);
|
||||
}
|
||||
|
||||
void felica_crc_append(BitBuffer* buf) {
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
|
||||
const uint16_t crc = felica_crc_calculate(data, data_size);
|
||||
bit_buffer_append_bytes(buf, (const uint8_t*)&crc, FELICA_CRC_SIZE);
|
||||
}
|
||||
|
||||
bool felica_crc_check(const BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
if(data_size <= FELICA_CRC_SIZE) return false;
|
||||
|
||||
uint16_t crc_received;
|
||||
bit_buffer_write_bytes_mid(buf, &crc_received, data_size - FELICA_CRC_SIZE, FELICA_CRC_SIZE);
|
||||
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const uint16_t crc_calc = felica_crc_calculate(data, data_size - FELICA_CRC_SIZE);
|
||||
|
||||
return (crc_calc == crc_received);
|
||||
}
|
||||
|
||||
void felica_crc_trim(BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
furi_assert(data_size > FELICA_CRC_SIZE);
|
||||
|
||||
bit_buffer_set_size_bytes(buf, data_size - FELICA_CRC_SIZE);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "bit_buffer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FELICA_CRC_SIZE sizeof(uint16_t)
|
||||
|
||||
void felica_crc_append(BitBuffer* buf);
|
||||
|
||||
bool felica_crc_check(const BitBuffer* buf);
|
||||
|
||||
void felica_crc_trim(BitBuffer* buf);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "iso13239_crc.h"
|
||||
|
||||
#include <core/check.h>
|
||||
|
||||
#define ISO13239_CRC_INIT_DEFAULT (0xFFFFU)
|
||||
#define ISO13239_CRC_INIT_PICOPASS (0xE012U)
|
||||
#define ISO13239_CRC_POLY (0x8408U)
|
||||
|
||||
static uint16_t
|
||||
iso13239_crc_calculate(Iso13239CrcType type, const uint8_t* data, size_t data_size) {
|
||||
uint16_t crc;
|
||||
|
||||
if(type == Iso13239CrcTypeDefault) {
|
||||
crc = ISO13239_CRC_INIT_DEFAULT;
|
||||
} else if(type == Iso13239CrcTypePicopass) {
|
||||
crc = ISO13239_CRC_INIT_PICOPASS;
|
||||
} else {
|
||||
furi_crash("Wrong ISO13239 CRC type");
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < data_size; ++i) {
|
||||
crc ^= (uint16_t)data[i];
|
||||
for(size_t j = 0; j < 8; ++j) {
|
||||
if(crc & 1U) {
|
||||
crc = (crc >> 1) ^ ISO13239_CRC_POLY;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type == Iso13239CrcTypePicopass ? crc : ~crc;
|
||||
}
|
||||
|
||||
void iso13239_crc_append(Iso13239CrcType type, BitBuffer* buf) {
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
|
||||
const uint16_t crc = iso13239_crc_calculate(type, data, data_size);
|
||||
bit_buffer_append_bytes(buf, (const uint8_t*)&crc, ISO13239_CRC_SIZE);
|
||||
}
|
||||
|
||||
bool iso13239_crc_check(Iso13239CrcType type, const BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
if(data_size <= ISO13239_CRC_SIZE) return false;
|
||||
|
||||
uint16_t crc_received;
|
||||
bit_buffer_write_bytes_mid(
|
||||
buf, &crc_received, data_size - ISO13239_CRC_SIZE, ISO13239_CRC_SIZE);
|
||||
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const uint16_t crc_calc = iso13239_crc_calculate(type, data, data_size - ISO13239_CRC_SIZE);
|
||||
|
||||
return (crc_calc == crc_received);
|
||||
}
|
||||
|
||||
void iso13239_crc_trim(BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
furi_assert(data_size > ISO13239_CRC_SIZE);
|
||||
|
||||
bit_buffer_set_size_bytes(buf, data_size - ISO13239_CRC_SIZE);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO13239_CRC_SIZE sizeof(uint16_t)
|
||||
|
||||
typedef enum {
|
||||
Iso13239CrcTypeDefault,
|
||||
Iso13239CrcTypePicopass,
|
||||
} Iso13239CrcType;
|
||||
|
||||
void iso13239_crc_append(Iso13239CrcType type, BitBuffer* buf);
|
||||
|
||||
bool iso13239_crc_check(Iso13239CrcType type, const BitBuffer* buf);
|
||||
|
||||
void iso13239_crc_trim(BitBuffer* buf);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "iso14443_4_layer.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define ISO14443_4_BLOCK_PCB (1U << 1)
|
||||
#define ISO14443_4_BLOCK_PCB_I (0U)
|
||||
#define ISO14443_4_BLOCK_PCB_R (5U << 5)
|
||||
#define ISO14443_4_BLOCK_PCB_S (3U << 6)
|
||||
|
||||
struct Iso14443_4Layer {
|
||||
uint8_t pcb;
|
||||
uint8_t pcb_prev;
|
||||
};
|
||||
|
||||
static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) {
|
||||
instance->pcb_prev = instance->pcb;
|
||||
instance->pcb ^= (uint8_t)0x01;
|
||||
}
|
||||
|
||||
Iso14443_4Layer* iso14443_4_layer_alloc() {
|
||||
Iso14443_4Layer* instance = malloc(sizeof(Iso14443_4Layer));
|
||||
|
||||
iso14443_4_layer_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void iso14443_4_layer_free(Iso14443_4Layer* instance) {
|
||||
furi_assert(instance);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void iso14443_4_layer_reset(Iso14443_4Layer* instance) {
|
||||
furi_assert(instance);
|
||||
instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB;
|
||||
}
|
||||
|
||||
void iso14443_4_layer_encode_block(
|
||||
Iso14443_4Layer* instance,
|
||||
const BitBuffer* input_data,
|
||||
BitBuffer* block_data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_append_byte(block_data, instance->pcb);
|
||||
bit_buffer_append(block_data, input_data);
|
||||
|
||||
iso14443_4_layer_update_pcb(instance);
|
||||
}
|
||||
|
||||
bool iso14443_4_layer_decode_block(
|
||||
Iso14443_4Layer* instance,
|
||||
BitBuffer* output_data,
|
||||
const BitBuffer* block_data) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool ret = false;
|
||||
|
||||
do {
|
||||
if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break;
|
||||
bit_buffer_copy_right(output_data, block_data, 1);
|
||||
ret = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Iso14443_4Layer Iso14443_4Layer;
|
||||
|
||||
Iso14443_4Layer* iso14443_4_layer_alloc();
|
||||
|
||||
void iso14443_4_layer_free(Iso14443_4Layer* instance);
|
||||
|
||||
void iso14443_4_layer_reset(Iso14443_4Layer* instance);
|
||||
|
||||
void iso14443_4_layer_encode_block(
|
||||
Iso14443_4Layer* instance,
|
||||
const BitBuffer* input_data,
|
||||
BitBuffer* block_data);
|
||||
|
||||
bool iso14443_4_layer_decode_block(
|
||||
Iso14443_4Layer* instance,
|
||||
BitBuffer* output_data,
|
||||
const BitBuffer* block_data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "iso14443_crc.h"
|
||||
|
||||
#include <core/check.h>
|
||||
|
||||
#define ISO14443_3A_CRC_INIT (0x6363U)
|
||||
#define ISO14443_3B_CRC_INIT (0xFFFFU)
|
||||
|
||||
static uint16_t
|
||||
iso14443_crc_calculate(Iso14443CrcType type, const uint8_t* data, size_t data_size) {
|
||||
uint16_t crc;
|
||||
|
||||
if(type == Iso14443CrcTypeA) {
|
||||
crc = ISO14443_3A_CRC_INIT;
|
||||
} else if(type == Iso14443CrcTypeB) {
|
||||
crc = ISO14443_3B_CRC_INIT;
|
||||
} else {
|
||||
furi_crash("Wrong ISO14443 CRC type");
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < data_size; i++) {
|
||||
uint8_t byte = data[i];
|
||||
byte ^= (uint8_t)(crc & 0xff);
|
||||
byte ^= byte << 4;
|
||||
crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ (byte >> 4);
|
||||
}
|
||||
|
||||
return type == Iso14443CrcTypeA ? crc : ~crc;
|
||||
}
|
||||
|
||||
void iso14443_crc_append(Iso14443CrcType type, BitBuffer* buf) {
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
|
||||
const uint16_t crc = iso14443_crc_calculate(type, data, data_size);
|
||||
bit_buffer_append_bytes(buf, (const uint8_t*)&crc, ISO14443_CRC_SIZE);
|
||||
}
|
||||
|
||||
bool iso14443_crc_check(Iso14443CrcType type, const BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
if(data_size <= ISO14443_CRC_SIZE) return false;
|
||||
|
||||
uint16_t crc_received;
|
||||
bit_buffer_write_bytes_mid(
|
||||
buf, &crc_received, data_size - ISO14443_CRC_SIZE, ISO14443_CRC_SIZE);
|
||||
|
||||
const uint8_t* data = bit_buffer_get_data(buf);
|
||||
const uint16_t crc_calc = iso14443_crc_calculate(type, data, data_size - ISO14443_CRC_SIZE);
|
||||
|
||||
return (crc_calc == crc_received);
|
||||
}
|
||||
|
||||
void iso14443_crc_trim(BitBuffer* buf) {
|
||||
const size_t data_size = bit_buffer_get_size_bytes(buf);
|
||||
furi_assert(data_size > ISO14443_CRC_SIZE);
|
||||
|
||||
bit_buffer_set_size_bytes(buf, data_size - ISO14443_CRC_SIZE);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ISO14443_CRC_SIZE sizeof(uint16_t)
|
||||
|
||||
typedef enum {
|
||||
Iso14443CrcTypeA,
|
||||
Iso14443CrcTypeB,
|
||||
} Iso14443CrcType;
|
||||
|
||||
void iso14443_crc_append(Iso14443CrcType type, BitBuffer* buf);
|
||||
|
||||
bool iso14443_crc_check(Iso14443CrcType type, const BitBuffer* buf);
|
||||
|
||||
void iso14443_crc_trim(BitBuffer* buf);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,346 +0,0 @@
|
||||
#include "mf_classic_dict.h"
|
||||
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
|
||||
#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
|
||||
#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc")
|
||||
|
||||
#define TAG "MfClassicDict"
|
||||
|
||||
#define NFC_MF_CLASSIC_KEY_LEN (13)
|
||||
|
||||
struct MfClassicDict {
|
||||
Stream* stream;
|
||||
uint32_t total_keys;
|
||||
};
|
||||
|
||||
bool mf_classic_dict_check_presence(MfClassicDictType dict_type) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
bool dict_present = false;
|
||||
if(dict_type == MfClassicDictTypeSystem) {
|
||||
dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK;
|
||||
} else if(dict_type == MfClassicDictTypeUser) {
|
||||
dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK;
|
||||
} else if(dict_type == MfClassicDictTypeUnitTest) {
|
||||
dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) ==
|
||||
FSE_OK;
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return dict_present;
|
||||
}
|
||||
|
||||
MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) {
|
||||
MfClassicDict* dict = malloc(sizeof(MfClassicDict));
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
dict->stream = buffered_file_stream_alloc(storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
bool dict_loaded = false;
|
||||
do {
|
||||
if(dict_type == MfClassicDictTypeSystem) {
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream,
|
||||
MF_CLASSIC_DICT_FLIPPER_PATH,
|
||||
FSAM_READ_WRITE,
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
} else if(dict_type == MfClassicDictTypeUser) {
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
} else if(dict_type == MfClassicDictTypeUnitTest) {
|
||||
if(!buffered_file_stream_open(
|
||||
dict->stream,
|
||||
MF_CLASSIC_DICT_UNIT_TEST_PATH,
|
||||
FSAM_READ_WRITE,
|
||||
FSOM_OPEN_ALWAYS)) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for new line ending
|
||||
if(!stream_eof(dict->stream)) {
|
||||
if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break;
|
||||
uint8_t last_char = 0;
|
||||
if(stream_read(dict->stream, &last_char, 1) != 1) break;
|
||||
if(last_char != '\n') {
|
||||
FURI_LOG_D(TAG, "Adding new line ending");
|
||||
if(stream_write_char(dict->stream, '\n') != 1) break;
|
||||
}
|
||||
if(!stream_rewind(dict->stream)) break;
|
||||
}
|
||||
|
||||
// Read total amount of keys
|
||||
FuriString* next_line;
|
||||
next_line = furi_string_alloc();
|
||||
while(true) {
|
||||
if(!stream_read_line(dict->stream, next_line)) {
|
||||
FURI_LOG_T(TAG, "No keys left in dict");
|
||||
break;
|
||||
}
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"Read line: %s, len: %zu",
|
||||
furi_string_get_cstr(next_line),
|
||||
furi_string_size(next_line));
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
dict->total_keys++;
|
||||
}
|
||||
furi_string_free(next_line);
|
||||
stream_rewind(dict->stream);
|
||||
|
||||
dict_loaded = true;
|
||||
FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
|
||||
} while(false);
|
||||
|
||||
if(!dict_loaded) {
|
||||
buffered_file_stream_close(dict->stream);
|
||||
free(dict);
|
||||
dict = NULL;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
void mf_classic_dict_free(MfClassicDict* dict) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
buffered_file_stream_close(dict->stream);
|
||||
stream_free(dict->stream);
|
||||
free(dict);
|
||||
}
|
||||
|
||||
static void mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) {
|
||||
furi_string_reset(key_str);
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
furi_string_cat_printf(key_str, "%02X", key_int[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) {
|
||||
uint8_t key_byte_tmp;
|
||||
|
||||
*key_int = 0ULL;
|
||||
for(uint8_t i = 0; i < 12; i += 2) {
|
||||
args_char_to_hex(
|
||||
furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp);
|
||||
*key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) {
|
||||
furi_assert(dict);
|
||||
|
||||
return dict->total_keys;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_rewind(MfClassicDict* dict) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
return stream_rewind(dict->stream);
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
bool key_read = false;
|
||||
furi_string_reset(key);
|
||||
while(!key_read) {
|
||||
if(!stream_read_line(dict->stream, key)) break;
|
||||
if(furi_string_get_char(key, 0) == '#') continue;
|
||||
if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
furi_string_left(key, 12);
|
||||
key_read = true;
|
||||
}
|
||||
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* temp_key;
|
||||
temp_key = furi_string_alloc();
|
||||
bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key);
|
||||
if(key_read) {
|
||||
mf_classic_dict_str_to_int(temp_key, key);
|
||||
}
|
||||
furi_string_free(temp_key);
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* next_line;
|
||||
next_line = furi_string_alloc();
|
||||
|
||||
bool key_found = false;
|
||||
stream_rewind(dict->stream);
|
||||
while(!key_found) { //-V654
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
furi_string_left(next_line, 12);
|
||||
if(!furi_string_equal(key, next_line)) continue;
|
||||
key_found = true;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) {
|
||||
FuriString* temp_key;
|
||||
|
||||
temp_key = furi_string_alloc();
|
||||
mf_classic_dict_int_to_str(key, temp_key);
|
||||
bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key);
|
||||
furi_string_free(temp_key);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
furi_string_cat_printf(key, "\n");
|
||||
|
||||
bool key_added = false;
|
||||
do {
|
||||
if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
|
||||
if(!stream_insert_string(dict->stream, key)) break;
|
||||
dict->total_keys++;
|
||||
key_added = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_left(key, 12);
|
||||
return key_added;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* temp_key;
|
||||
temp_key = furi_string_alloc();
|
||||
mf_classic_dict_int_to_str(key, temp_key);
|
||||
bool key_added = mf_classic_dict_add_key_str(dict, temp_key);
|
||||
|
||||
furi_string_free(temp_key);
|
||||
return key_added;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* next_line;
|
||||
uint32_t index = 0;
|
||||
next_line = furi_string_alloc();
|
||||
furi_string_reset(key);
|
||||
|
||||
bool key_found = false;
|
||||
while(!key_found) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
if(index++ != target) continue;
|
||||
furi_string_set_n(key, next_line, 0, 12);
|
||||
key_found = true;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* temp_key;
|
||||
temp_key = furi_string_alloc();
|
||||
bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target);
|
||||
if(key_found) {
|
||||
mf_classic_dict_str_to_int(temp_key, key);
|
||||
}
|
||||
furi_string_free(temp_key);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* next_line;
|
||||
next_line = furi_string_alloc();
|
||||
|
||||
bool key_found = false;
|
||||
uint32_t index = 0;
|
||||
stream_rewind(dict->stream);
|
||||
while(!key_found) { //-V654
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
furi_string_left(next_line, 12);
|
||||
if(!furi_string_equal(key, next_line)) continue;
|
||||
key_found = true;
|
||||
*target = index;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* temp_key;
|
||||
temp_key = furi_string_alloc();
|
||||
mf_classic_dict_int_to_str(key, temp_key);
|
||||
bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target);
|
||||
|
||||
furi_string_free(temp_key);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
FuriString* next_line;
|
||||
next_line = furi_string_alloc();
|
||||
uint32_t index = 0;
|
||||
|
||||
bool key_removed = false;
|
||||
while(!key_removed) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
if(index++ != target) continue;
|
||||
stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent);
|
||||
if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break;
|
||||
dict->total_keys--;
|
||||
key_removed = true;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
return key_removed;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include <lib/toolbox/stream/file_stream.h>
|
||||
#include <lib/toolbox/stream/buffered_file_stream.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MfClassicDictTypeUser,
|
||||
MfClassicDictTypeSystem,
|
||||
MfClassicDictTypeUnitTest,
|
||||
} MfClassicDictType;
|
||||
|
||||
typedef struct MfClassicDict MfClassicDict;
|
||||
|
||||
bool mf_classic_dict_check_presence(MfClassicDictType dict_type);
|
||||
|
||||
/** Allocate MfClassicDict instance
|
||||
*
|
||||
* @param[in] dict_type The dictionary type
|
||||
*
|
||||
* @return MfClassicDict instance
|
||||
*/
|
||||
MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type);
|
||||
|
||||
/** Free MfClassicDict instance
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
*/
|
||||
void mf_classic_dict_free(MfClassicDict* dict);
|
||||
|
||||
/** Get total keys count
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
*
|
||||
* @return total keys count
|
||||
*/
|
||||
uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict);
|
||||
|
||||
/** Rewind to the beginning
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_rewind(MfClassicDict* dict);
|
||||
|
||||
bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key);
|
||||
|
||||
bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key);
|
||||
|
||||
bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key);
|
||||
|
||||
bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key);
|
||||
|
||||
/** Get key at target offset as uint64_t
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[out] key Pointer to the uint64_t key
|
||||
* @param[in] target Target offset from current position
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target);
|
||||
|
||||
/** Get key at target offset as string_t
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[out] key Found key destination buffer
|
||||
* @param[in] target Target offset from current position
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target);
|
||||
|
||||
bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key);
|
||||
|
||||
/** Add string representation of the key
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[in] key String representation of the key
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key);
|
||||
|
||||
bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target);
|
||||
|
||||
bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target);
|
||||
|
||||
/** Delete key at target offset
|
||||
*
|
||||
* @param dict MfClassicDict instance
|
||||
* @param[in] target Target offset from current position
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,228 +0,0 @@
|
||||
#include "mfkey32.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
#include <storage/storage.h>
|
||||
#include <stream/stream.h>
|
||||
#include <stream/buffered_file_stream.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
#include <lib/nfc/protocols/nfc_util.h>
|
||||
|
||||
#define TAG "Mfkey32"
|
||||
|
||||
#define MFKEY32_LOGS_PATH EXT_PATH("nfc/.mfkey32.log")
|
||||
|
||||
typedef enum {
|
||||
Mfkey32StateIdle,
|
||||
Mfkey32StateAuthReceived,
|
||||
Mfkey32StateAuthNtSent,
|
||||
Mfkey32StateAuthArNrReceived,
|
||||
} Mfkey32State;
|
||||
|
||||
typedef struct {
|
||||
uint32_t cuid;
|
||||
uint8_t sector;
|
||||
MfClassicKey key;
|
||||
uint32_t nt0;
|
||||
uint32_t nr0;
|
||||
uint32_t ar0;
|
||||
uint32_t nt1;
|
||||
uint32_t nr1;
|
||||
uint32_t ar1;
|
||||
} Mfkey32Params;
|
||||
|
||||
ARRAY_DEF(Mfkey32Params, Mfkey32Params, M_POD_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
uint8_t sector;
|
||||
MfClassicKey key;
|
||||
uint32_t nt;
|
||||
uint32_t nr;
|
||||
uint32_t ar;
|
||||
} Mfkey32Nonce;
|
||||
|
||||
struct Mfkey32 {
|
||||
Mfkey32State state;
|
||||
Stream* file_stream;
|
||||
Mfkey32Params_t params_arr;
|
||||
Mfkey32Nonce nonce;
|
||||
uint32_t cuid;
|
||||
Mfkey32ParseDataCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
Mfkey32* mfkey32_alloc(uint32_t cuid) {
|
||||
Mfkey32* instance = malloc(sizeof(Mfkey32));
|
||||
instance->cuid = cuid;
|
||||
instance->state = Mfkey32StateIdle;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->file_stream = buffered_file_stream_alloc(storage);
|
||||
if(!buffered_file_stream_open(
|
||||
instance->file_stream, MFKEY32_LOGS_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||
buffered_file_stream_close(instance->file_stream);
|
||||
stream_free(instance->file_stream);
|
||||
free(instance);
|
||||
instance = NULL;
|
||||
} else {
|
||||
Mfkey32Params_init(instance->params_arr);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void mfkey32_free(Mfkey32* instance) {
|
||||
furi_assert(instance != NULL);
|
||||
|
||||
Mfkey32Params_clear(instance->params_arr);
|
||||
buffered_file_stream_close(instance->file_stream);
|
||||
stream_free(instance->file_stream);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) {
|
||||
FuriString* str = furi_string_alloc_printf(
|
||||
"Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n",
|
||||
params->sector,
|
||||
params->key == MfClassicKeyA ? 'A' : 'B',
|
||||
params->cuid,
|
||||
params->nt0,
|
||||
params->nr0,
|
||||
params->ar0,
|
||||
params->nt1,
|
||||
params->nr1,
|
||||
params->ar1);
|
||||
bool write_success = stream_write_string(instance->file_stream, str);
|
||||
furi_string_free(str);
|
||||
return write_success;
|
||||
}
|
||||
|
||||
static void mfkey32_add_params(Mfkey32* instance) {
|
||||
Mfkey32Nonce* nonce = &instance->nonce;
|
||||
bool nonce_added = false;
|
||||
// Search if we partially collected params
|
||||
if(Mfkey32Params_size(instance->params_arr)) {
|
||||
Mfkey32Params_it_t it;
|
||||
for(Mfkey32Params_it(it, instance->params_arr); !Mfkey32Params_end_p(it);
|
||||
Mfkey32Params_next(it)) {
|
||||
Mfkey32Params* params = Mfkey32Params_ref(it);
|
||||
if((params->sector == nonce->sector) && (params->key == nonce->key)) {
|
||||
params->nt1 = nonce->nt;
|
||||
params->nr1 = nonce->nr;
|
||||
params->ar1 = nonce->ar;
|
||||
nonce_added = true;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Params for sector %d key %c collected",
|
||||
params->sector,
|
||||
params->key == MfClassicKeyA ? 'A' : 'B');
|
||||
// Write on sd card
|
||||
if(mfkey32_write_params(instance, params)) {
|
||||
Mfkey32Params_remove(instance->params_arr, it);
|
||||
if(instance->callback) {
|
||||
instance->callback(Mfkey32EventParamCollected, instance->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!nonce_added) {
|
||||
Mfkey32Params params = {
|
||||
.sector = nonce->sector,
|
||||
.key = nonce->key,
|
||||
.cuid = instance->cuid,
|
||||
.nt0 = nonce->nt,
|
||||
.nr0 = nonce->nr,
|
||||
.ar0 = nonce->ar,
|
||||
};
|
||||
Mfkey32Params_push_back(instance->params_arr, params);
|
||||
}
|
||||
}
|
||||
|
||||
void mfkey32_process_data(
|
||||
Mfkey32* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
|
||||
Mfkey32Nonce* nonce = &instance->nonce;
|
||||
uint16_t data_len = len;
|
||||
if((data_len > 3) && !crc_dropped) {
|
||||
data_len -= 2;
|
||||
}
|
||||
|
||||
bool data_processed = false;
|
||||
if(instance->state == Mfkey32StateIdle) {
|
||||
if(reader_to_tag) {
|
||||
if((data[0] == 0x60) || (data[0] == 0x61)) {
|
||||
nonce->key = data[0] == 0x60 ? MfClassicKeyA : MfClassicKeyB;
|
||||
nonce->sector = mf_classic_get_sector_by_block(data[1]);
|
||||
instance->state = Mfkey32StateAuthReceived;
|
||||
data_processed = true;
|
||||
}
|
||||
}
|
||||
} else if(instance->state == Mfkey32StateAuthReceived) {
|
||||
if(!reader_to_tag) {
|
||||
if(len == 4) {
|
||||
nonce->nt = nfc_util_bytes2num(data, 4);
|
||||
instance->state = Mfkey32StateAuthNtSent;
|
||||
data_processed = true;
|
||||
}
|
||||
}
|
||||
} else if(instance->state == Mfkey32StateAuthNtSent) {
|
||||
if(reader_to_tag) {
|
||||
if(len == 8) {
|
||||
nonce->nr = nfc_util_bytes2num(data, 4);
|
||||
nonce->ar = nfc_util_bytes2num(&data[4], 4);
|
||||
mfkey32_add_params(instance);
|
||||
instance->state = Mfkey32StateIdle;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!data_processed) {
|
||||
instance->state = Mfkey32StateIdle;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t mfkey32_get_auth_sectors(FuriString* data_str) {
|
||||
furi_assert(data_str);
|
||||
|
||||
uint16_t nonces_num = 0;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
Stream* file_stream = buffered_file_stream_alloc(storage);
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!buffered_file_stream_open(
|
||||
file_stream, MFKEY32_LOGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING))
|
||||
break;
|
||||
while(true) {
|
||||
if(!stream_read_line(file_stream, temp_str)) break;
|
||||
size_t uid_pos = furi_string_search(temp_str, "cuid");
|
||||
furi_string_left(temp_str, uid_pos);
|
||||
furi_string_push_back(temp_str, '\n');
|
||||
furi_string_cat(data_str, temp_str);
|
||||
nonces_num++;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
buffered_file_stream_close(file_stream);
|
||||
stream_free(file_stream);
|
||||
furi_string_free(temp_str);
|
||||
|
||||
return nonces_num;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Mfkey32 Mfkey32;
|
||||
|
||||
typedef enum {
|
||||
Mfkey32EventParamCollected,
|
||||
} Mfkey32Event;
|
||||
|
||||
typedef void (*Mfkey32ParseDataCallback)(Mfkey32Event event, void* context);
|
||||
|
||||
Mfkey32* mfkey32_alloc(uint32_t cuid);
|
||||
|
||||
void mfkey32_free(Mfkey32* instance);
|
||||
|
||||
void mfkey32_process_data(
|
||||
Mfkey32* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped);
|
||||
|
||||
void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context);
|
||||
|
||||
uint16_t mfkey32_get_auth_sectors(FuriString* string);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,566 @@
|
||||
#include "nfc_data_generator.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
#include <furi_hal_random.h>
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
|
||||
#define NXP_MANUFACTURER_ID (0x04)
|
||||
|
||||
typedef void (*NfcDataGeneratorHandler)(NfcDevice* nfc_device);
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
NfcDataGeneratorHandler handler;
|
||||
} NfcDataGenerator;
|
||||
|
||||
static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03};
|
||||
static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03};
|
||||
static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03};
|
||||
static const uint8_t default_data_ntag203[] =
|
||||
{0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00};
|
||||
|
||||
static void nfc_generate_mf_ul_uid(uint8_t* uid) {
|
||||
uid[0] = NXP_MANUFACTURER_ID;
|
||||
furi_hal_random_fill_buf(&uid[1], 6);
|
||||
// I'm not sure how this is generated, but the upper nybble always seems to be 8
|
||||
uid[6] &= 0x0F;
|
||||
uid[6] |= 0x80;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_common(MfUltralightData* mfu_data) {
|
||||
mfu_data->iso14443_3a_data->uid_len = 7;
|
||||
nfc_generate_mf_ul_uid(mfu_data->iso14443_3a_data->uid);
|
||||
mfu_data->iso14443_3a_data->atqa[0] = 0x44;
|
||||
mfu_data->iso14443_3a_data->atqa[1] = 0x00;
|
||||
mfu_data->iso14443_3a_data->sak = 0x00;
|
||||
}
|
||||
|
||||
static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) {
|
||||
*bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2];
|
||||
*bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6];
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_copy_uid_with_bcc(MfUltralightData* mfu_data) {
|
||||
memcpy(mfu_data->page[0].data, mfu_data->iso14443_3a_data->uid, 3);
|
||||
memcpy(mfu_data->page[1].data, &mfu_data->iso14443_3a_data->uid[3], 4);
|
||||
|
||||
nfc_generate_calc_bcc(
|
||||
mfu_data->iso14443_3a_data->uid, &mfu_data->page[0].data[3], &mfu_data->page[2].data[0]);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
nfc_generate_mf_ul_common(mfu_data);
|
||||
|
||||
mfu_data->type = MfUltralightTypeUnknown;
|
||||
mfu_data->pages_total = 16;
|
||||
mfu_data->pages_read = 16;
|
||||
nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data);
|
||||
memset(&mfu_data->page[4], 0xff, sizeof(MfUltralightPage));
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_with_config_common(MfUltralightData* mfu_data, uint8_t num_pages) {
|
||||
nfc_generate_mf_ul_common(mfu_data);
|
||||
|
||||
mfu_data->pages_total = num_pages;
|
||||
mfu_data->pages_read = num_pages;
|
||||
nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data);
|
||||
uint16_t config_index = (num_pages - 4);
|
||||
mfu_data->page[config_index].data[0] = 0x04; // STRG_MOD_EN
|
||||
mfu_data->page[config_index].data[3] = 0xff; // AUTH0
|
||||
mfu_data->page[config_index + 1].data[1] = 0x05; // VCTID
|
||||
memset(&mfu_data->page[config_index + 2], 0xff, sizeof(MfUltralightPage)); // Default PWD
|
||||
if(num_pages > 20) {
|
||||
mfu_data->page[config_index - 1].data[3] = MF_ULTRALIGHT_TEARING_FLAG_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_ev1_common(MfUltralightData* mfu_data, uint8_t num_pages) {
|
||||
nfc_generate_mf_ul_with_config_common(mfu_data, num_pages);
|
||||
memcpy(&mfu_data->version, version_bytes_mf0ulx1, sizeof(MfUltralightVersion));
|
||||
for(size_t i = 0; i < 3; ++i) {
|
||||
mfu_data->tearing_flag[i].data = MF_ULTRALIGHT_TEARING_FLAG_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_11(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_mf_ul_ev1_common(mfu_data, 20);
|
||||
mfu_data->type = MfUltralightTypeUL11;
|
||||
mfu_data->version.prod_subtype = 0x01;
|
||||
mfu_data->version.storage_size = 0x0B;
|
||||
mfu_data->page[16].data[0] = 0x00; // Low capacitance version does not have STRG_MOD_EN
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_h11(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_mf_ul_ev1_common(mfu_data, 20);
|
||||
mfu_data->type = MfUltralightTypeUL11;
|
||||
mfu_data->version.prod_subtype = 0x02;
|
||||
mfu_data->version.storage_size = 0x0B;
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_21(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_mf_ul_ev1_common(mfu_data, 41);
|
||||
mfu_data->type = MfUltralightTypeUL21;
|
||||
mfu_data->version.prod_subtype = 0x01;
|
||||
mfu_data->version.storage_size = 0x0E;
|
||||
mfu_data->page[37].data[0] = 0x00; // Low capacitance version does not have STRG_MOD_EN
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_h21(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_mf_ul_ev1_common(mfu_data, 41);
|
||||
mfu_data->type = MfUltralightTypeUL21;
|
||||
mfu_data->version.prod_subtype = 0x02;
|
||||
mfu_data->version.storage_size = 0x0E;
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag203(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_mf_ul_common(mfu_data);
|
||||
mfu_data->type = MfUltralightTypeNTAG203;
|
||||
mfu_data->pages_total = 42;
|
||||
mfu_data->pages_read = 42;
|
||||
nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data);
|
||||
mfu_data->page[2].data[1] = 0x48; // Internal byte
|
||||
memcpy(&mfu_data->page[3], default_data_ntag203, sizeof(MfUltralightPage)); //-V1086
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag21x_common(MfUltralightData* mfu_data, uint8_t num_pages) {
|
||||
nfc_generate_mf_ul_with_config_common(mfu_data, num_pages);
|
||||
memcpy(&mfu_data->version, version_bytes_ntag21x, sizeof(MfUltralightVersion));
|
||||
mfu_data->page[2].data[1] = 0x48; // Internal byte
|
||||
// Capability container
|
||||
mfu_data->page[3].data[0] = 0xE1;
|
||||
mfu_data->page[3].data[1] = 0x10;
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag213(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag21x_common(mfu_data, 45);
|
||||
mfu_data->type = MfUltralightTypeNTAG213;
|
||||
mfu_data->version.storage_size = 0x0F;
|
||||
mfu_data->page[3].data[2] = 0x12;
|
||||
// Default contents
|
||||
memcpy(&mfu_data->page[4], default_data_ntag213, sizeof(default_data_ntag213));
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag215(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag21x_common(mfu_data, 135);
|
||||
mfu_data->type = MfUltralightTypeNTAG215;
|
||||
mfu_data->version.storage_size = 0x11;
|
||||
mfu_data->page[3].data[2] = 0x3E;
|
||||
// Default contents
|
||||
memcpy(&mfu_data->page[4], default_data_ntag215_216, sizeof(default_data_ntag215_216));
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag216(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag21x_common(mfu_data, 231);
|
||||
mfu_data->type = MfUltralightTypeNTAG216;
|
||||
mfu_data->version.storage_size = 0x13;
|
||||
mfu_data->page[3].data[2] = 0x6D;
|
||||
// Default contents
|
||||
memcpy(&mfu_data->page[4], default_data_ntag215_216, sizeof(default_data_ntag215_216));
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_common(
|
||||
MfUltralightData* mfu_data,
|
||||
MfUltralightType type,
|
||||
uint16_t num_pages) {
|
||||
nfc_generate_mf_ul_common(mfu_data);
|
||||
|
||||
mfu_data->type = type;
|
||||
memcpy(&mfu_data->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c));
|
||||
mfu_data->pages_total = num_pages;
|
||||
mfu_data->pages_read = num_pages;
|
||||
memcpy(
|
||||
mfu_data->page[0].data,
|
||||
mfu_data->iso14443_3a_data->uid,
|
||||
mfu_data->iso14443_3a_data->uid_len);
|
||||
mfu_data->page[1].data[3] = mfu_data->iso14443_3a_data->sak;
|
||||
mfu_data->page[2].data[0] = mfu_data->iso14443_3a_data->atqa[0];
|
||||
mfu_data->page[2].data[1] = mfu_data->iso14443_3a_data->atqa[1];
|
||||
|
||||
uint16_t config_register_page = 0;
|
||||
uint16_t session_register_page = 0;
|
||||
|
||||
// Sync with mifare_ultralight.c
|
||||
switch(type) {
|
||||
case MfUltralightTypeNTAGI2C1K:
|
||||
config_register_page = 227;
|
||||
session_register_page = 229;
|
||||
break;
|
||||
case MfUltralightTypeNTAGI2C2K:
|
||||
config_register_page = 481;
|
||||
session_register_page = 483;
|
||||
break;
|
||||
case MfUltralightTypeNTAGI2CPlus1K:
|
||||
case MfUltralightTypeNTAGI2CPlus2K:
|
||||
config_register_page = 232;
|
||||
session_register_page = 234;
|
||||
break;
|
||||
default:
|
||||
furi_crash("Unknown MFUL");
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(
|
||||
&mfu_data->page[config_register_page],
|
||||
default_config_ntag_i2c,
|
||||
sizeof(default_config_ntag_i2c));
|
||||
memcpy(
|
||||
&mfu_data->page[session_register_page],
|
||||
default_config_ntag_i2c,
|
||||
sizeof(default_config_ntag_i2c));
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_1k(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag_i2c_common(mfu_data, MfUltralightTypeNTAGI2C1K, 231);
|
||||
mfu_data->version.prod_ver_minor = 0x01;
|
||||
mfu_data->version.storage_size = 0x13;
|
||||
memcpy(&mfu_data->page[3], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
|
||||
mfu_data->page[3].data[2] = 0x6D; // Size of tag in CC
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_2k(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag_i2c_common(mfu_data, MfUltralightTypeNTAGI2C2K, 485);
|
||||
mfu_data->version.prod_ver_minor = 0x01;
|
||||
mfu_data->version.storage_size = 0x15;
|
||||
memcpy(&mfu_data->page[3], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
|
||||
mfu_data->page[3].data[2] = 0xEA; // Size of tag in CC
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_plus_common(
|
||||
MfUltralightData* mfu_data,
|
||||
MfUltralightType type,
|
||||
uint16_t num_pages) {
|
||||
nfc_generate_ntag_i2c_common(mfu_data, type, num_pages);
|
||||
|
||||
uint16_t config_index = 227;
|
||||
mfu_data->page[config_index].data[3] = 0xff; // AUTH0
|
||||
|
||||
memset(&mfu_data->page[config_index + 2], 0xFF, sizeof(MfUltralightPage)); // Default PWD
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_plus_1k(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag_i2c_plus_common(mfu_data, MfUltralightTypeNTAGI2CPlus1K, 236);
|
||||
mfu_data->version.prod_ver_minor = 0x02;
|
||||
mfu_data->version.storage_size = 0x13;
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_plus_2k(NfcDevice* nfc_device) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
nfc_generate_ntag_i2c_plus_common(mfu_data, MfUltralightTypeNTAGI2CPlus2K, 492);
|
||||
mfu_data->version.prod_ver_minor = 0x02;
|
||||
mfu_data->version.storage_size = 0x15;
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data);
|
||||
mf_ultralight_free(mfu_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) {
|
||||
uid[0] = NXP_MANUFACTURER_ID;
|
||||
furi_hal_random_fill_buf(&uid[1], length - 1);
|
||||
}
|
||||
|
||||
static void
|
||||
nfc_generate_mf_classic_common(MfClassicData* data, uint8_t uid_len, MfClassicType type) {
|
||||
data->iso14443_3a_data->uid_len = uid_len;
|
||||
data->iso14443_3a_data->atqa[0] = 0x44;
|
||||
data->iso14443_3a_data->atqa[1] = 0x00;
|
||||
data->iso14443_3a_data->sak = 0x08;
|
||||
data->type = type;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) {
|
||||
// All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h.
|
||||
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].data;
|
||||
sec_tr->access_bits.data[0] = 0xFF;
|
||||
sec_tr->access_bits.data[1] = 0x07;
|
||||
sec_tr->access_bits.data[2] = 0x80;
|
||||
sec_tr->access_bits.data[3] = 0x69; // Nice
|
||||
|
||||
mf_classic_set_block_read(data, block, &data->block[block]);
|
||||
mf_classic_set_key_found(
|
||||
data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeA, 0xFFFFFFFFFFFF);
|
||||
mf_classic_set_key_found(
|
||||
data, mf_classic_get_sector_by_block(block), MfClassicKeyTypeB, 0xFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_block_0(
|
||||
uint8_t* block,
|
||||
uint8_t uid_len,
|
||||
uint8_t sak,
|
||||
uint8_t atqa0,
|
||||
uint8_t atqa1) {
|
||||
// Block length is always 16 bytes, and the UID can be either 4 or 7 bytes
|
||||
furi_assert(uid_len == 4 || uid_len == 7);
|
||||
furi_assert(block);
|
||||
|
||||
if(uid_len == 4) {
|
||||
// Calculate BCC
|
||||
block[uid_len] = 0;
|
||||
|
||||
for(int i = 0; i < uid_len; i++) {
|
||||
block[uid_len] ^= block[i];
|
||||
}
|
||||
} else {
|
||||
uid_len -= 1;
|
||||
}
|
||||
|
||||
block[uid_len + 1] = sak;
|
||||
block[uid_len + 2] = atqa0;
|
||||
block[uid_len + 3] = atqa1;
|
||||
|
||||
for(int i = uid_len + 4; i < 16; i++) {
|
||||
block[i] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfClassicType type) {
|
||||
MfClassicData* mfc_data = mf_classic_alloc();
|
||||
|
||||
nfc_generate_mf_classic_uid(mfc_data->block[0].data, uid_len);
|
||||
nfc_generate_mf_classic_common(mfc_data, uid_len, type);
|
||||
|
||||
// Set the UID
|
||||
mfc_data->iso14443_3a_data->uid[0] = NXP_MANUFACTURER_ID;
|
||||
for(int i = 1; i < uid_len; i++) {
|
||||
mfc_data->iso14443_3a_data->uid[i] = mfc_data->block[0].data[i];
|
||||
}
|
||||
|
||||
mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]);
|
||||
|
||||
uint16_t block_num = mf_classic_get_total_block_num(type);
|
||||
if(type == MfClassicType4k) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < block_num; i++) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
|
||||
} else {
|
||||
memset(&mfc_data->block[i].data, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
|
||||
}
|
||||
// Set SAK to 18
|
||||
mfc_data->iso14443_3a_data->sak = 0x18;
|
||||
} else if(type == MfClassicType1k) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < block_num; i++) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
|
||||
} else {
|
||||
memset(&mfc_data->block[i].data, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
|
||||
}
|
||||
// Set SAK to 08
|
||||
mfc_data->iso14443_3a_data->sak = 0x08;
|
||||
} else if(type == MfClassicTypeMini) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < block_num; i++) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
|
||||
} else {
|
||||
memset(&mfc_data->block[i].data, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
|
||||
}
|
||||
// Set SAK to 09
|
||||
mfc_data->iso14443_3a_data->sak = 0x09;
|
||||
}
|
||||
|
||||
nfc_generate_mf_classic_block_0(
|
||||
mfc_data->block[0].data,
|
||||
uid_len,
|
||||
mfc_data->iso14443_3a_data->sak,
|
||||
mfc_data->iso14443_3a_data->atqa[0],
|
||||
mfc_data->iso14443_3a_data->atqa[1]);
|
||||
|
||||
mfc_data->type = type;
|
||||
|
||||
nfc_device_set_data(nfc_device, NfcProtocolMfClassic, mfc_data);
|
||||
mf_classic_free(mfc_data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_mini(NfcDevice* nfc_device) {
|
||||
nfc_generate_mf_classic(nfc_device, 4, MfClassicTypeMini);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_1k_4b_uid(NfcDevice* nfc_device) {
|
||||
nfc_generate_mf_classic(nfc_device, 4, MfClassicType1k);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_1k_7b_uid(NfcDevice* nfc_device) {
|
||||
nfc_generate_mf_classic(nfc_device, 7, MfClassicType1k);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_4k_4b_uid(NfcDevice* nfc_device) {
|
||||
nfc_generate_mf_classic(nfc_device, 4, MfClassicType4k);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_4k_7b_uid(NfcDevice* nfc_device) {
|
||||
nfc_generate_mf_classic(nfc_device, 7, MfClassicType4k);
|
||||
}
|
||||
|
||||
static const NfcDataGenerator nfc_data_generator[NfcDataGeneratorTypeNum] = {
|
||||
[NfcDataGeneratorTypeMfUltralight] =
|
||||
{
|
||||
.name = "Mifare Ultralight",
|
||||
.handler = nfc_generate_mf_ul_orig,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfUltralightEV1_11] =
|
||||
{
|
||||
.name = "Mifare Ultralight EV1 11",
|
||||
.handler = nfc_generate_mf_ul_11,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfUltralightEV1_H11] =
|
||||
{
|
||||
.name = "Mifare Ultralight EV1 H11",
|
||||
.handler = nfc_generate_mf_ul_h11,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfUltralightEV1_21] =
|
||||
{
|
||||
.name = "Mifare Ultralight EV1 21",
|
||||
.handler = nfc_generate_mf_ul_21,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfUltralightEV1_H21] =
|
||||
{
|
||||
.name = "Mifare Ultralight EV1 H21",
|
||||
.handler = nfc_generate_mf_ul_h21,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAG203] =
|
||||
{
|
||||
.name = "NTAG203",
|
||||
.handler = nfc_generate_ntag203,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAG213] =
|
||||
{
|
||||
.name = "NTAG213",
|
||||
.handler = nfc_generate_ntag213,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAG215] =
|
||||
{
|
||||
.name = "NTAG215",
|
||||
.handler = nfc_generate_ntag215,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAG216] =
|
||||
{
|
||||
.name = "NTAG216",
|
||||
.handler = nfc_generate_ntag216,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAGI2C1k] =
|
||||
{
|
||||
.name = "NTAG I2C 1k",
|
||||
.handler = nfc_generate_ntag_i2c_1k,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAGI2C2k] =
|
||||
{
|
||||
.name = "NTAG I2C 2k",
|
||||
.handler = nfc_generate_ntag_i2c_2k,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAGI2CPlus1k] =
|
||||
{
|
||||
.name = "NTAG I2C Plus 1k",
|
||||
.handler = nfc_generate_ntag_i2c_plus_1k,
|
||||
},
|
||||
[NfcDataGeneratorTypeNTAGI2CPlus2k] =
|
||||
{
|
||||
.name = "NTAG I2C Plus 2k",
|
||||
.handler = nfc_generate_ntag_i2c_plus_2k,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfClassicMini] =
|
||||
{
|
||||
.name = "Mifare Mini",
|
||||
.handler = nfc_generate_mf_classic_mini,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfClassic1k_4b] =
|
||||
{
|
||||
.name = "Mifare Classic 1k 4byte UID",
|
||||
.handler = nfc_generate_mf_classic_1k_4b_uid,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfClassic1k_7b] =
|
||||
{
|
||||
.name = "Mifare Classic 1k 7byte UID",
|
||||
.handler = nfc_generate_mf_classic_1k_7b_uid,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfClassic4k_4b] =
|
||||
{
|
||||
.name = "Mifare Classic 4k 4byte UID",
|
||||
.handler = nfc_generate_mf_classic_4k_4b_uid,
|
||||
},
|
||||
[NfcDataGeneratorTypeMfClassic4k_7b] =
|
||||
{
|
||||
.name = "Mifare Classic 4k 7byte UID",
|
||||
.handler = nfc_generate_mf_classic_4k_7b_uid,
|
||||
},
|
||||
};
|
||||
|
||||
const char* nfc_data_generator_get_name(NfcDataGeneratorType type) {
|
||||
return nfc_data_generator[type].name;
|
||||
}
|
||||
|
||||
void nfc_data_generator_fill_data(NfcDataGeneratorType type, NfcDevice* nfc_device) {
|
||||
nfc_data_generator[type].handler(nfc_device);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
NfcDataGeneratorTypeMfUltralight,
|
||||
NfcDataGeneratorTypeMfUltralightEV1_11,
|
||||
NfcDataGeneratorTypeMfUltralightEV1_H11,
|
||||
NfcDataGeneratorTypeMfUltralightEV1_21,
|
||||
NfcDataGeneratorTypeMfUltralightEV1_H21,
|
||||
NfcDataGeneratorTypeNTAG203,
|
||||
NfcDataGeneratorTypeNTAG213,
|
||||
NfcDataGeneratorTypeNTAG215,
|
||||
NfcDataGeneratorTypeNTAG216,
|
||||
NfcDataGeneratorTypeNTAGI2C1k,
|
||||
NfcDataGeneratorTypeNTAGI2C2k,
|
||||
NfcDataGeneratorTypeNTAGI2CPlus1k,
|
||||
NfcDataGeneratorTypeNTAGI2CPlus2k,
|
||||
|
||||
NfcDataGeneratorTypeMfClassicMini,
|
||||
NfcDataGeneratorTypeMfClassic1k_4b,
|
||||
NfcDataGeneratorTypeMfClassic1k_7b,
|
||||
NfcDataGeneratorTypeMfClassic4k_4b,
|
||||
NfcDataGeneratorTypeMfClassic4k_7b,
|
||||
|
||||
NfcDataGeneratorTypeNum,
|
||||
|
||||
} NfcDataGeneratorType;
|
||||
|
||||
const char* nfc_data_generator_get_name(NfcDataGeneratorType type);
|
||||
|
||||
void nfc_data_generator_fill_data(NfcDataGeneratorType type, NfcDevice* nfc_device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "nfc_debug_log.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <stream/buffered_file_stream.h>
|
||||
|
||||
#define TAG "NfcDebugLog"
|
||||
|
||||
#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.txt")
|
||||
|
||||
struct NfcDebugLog {
|
||||
Stream* file_stream;
|
||||
FuriString* data_str;
|
||||
};
|
||||
|
||||
NfcDebugLog* nfc_debug_log_alloc() {
|
||||
NfcDebugLog* instance = malloc(sizeof(NfcDebugLog));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->file_stream = buffered_file_stream_alloc(storage);
|
||||
|
||||
if(!buffered_file_stream_open(
|
||||
instance->file_stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||
buffered_file_stream_close(instance->file_stream);
|
||||
stream_free(instance->file_stream);
|
||||
instance->file_stream = NULL;
|
||||
}
|
||||
|
||||
if(!instance->file_stream) {
|
||||
free(instance);
|
||||
instance = NULL;
|
||||
} else {
|
||||
instance->data_str = furi_string_alloc();
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_debug_log_free(NfcDebugLog* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->file_stream);
|
||||
furi_assert(instance->data_str);
|
||||
|
||||
buffered_file_stream_close(instance->file_stream);
|
||||
stream_free(instance->file_stream);
|
||||
furi_string_free(instance->data_str);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void nfc_debug_log_process_data(
|
||||
NfcDebugLog* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->file_stream);
|
||||
furi_assert(instance->data_str);
|
||||
furi_assert(data);
|
||||
UNUSED(crc_dropped);
|
||||
|
||||
furi_string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T');
|
||||
uint16_t data_len = len;
|
||||
for(size_t i = 0; i < data_len; i++) {
|
||||
furi_string_cat_printf(instance->data_str, " %02x", data[i]);
|
||||
}
|
||||
furi_string_push_back(instance->data_str, '\n');
|
||||
|
||||
stream_write_string(instance->file_stream, instance->data_str);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct NfcDebugLog NfcDebugLog;
|
||||
|
||||
NfcDebugLog* nfc_debug_log_alloc();
|
||||
|
||||
void nfc_debug_log_free(NfcDebugLog* instance);
|
||||
|
||||
void nfc_debug_log_process_data(
|
||||
NfcDebugLog* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped);
|
||||
@@ -1,130 +0,0 @@
|
||||
#include "nfc_debug_pcap.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <stream/buffered_file_stream.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#define TAG "NfcDebugPcap"
|
||||
|
||||
#define PCAP_MAGIC 0xa1b2c3d4
|
||||
#define PCAP_MAJOR 2
|
||||
#define PCAP_MINOR 4
|
||||
#define DLT_ISO_14443 264
|
||||
|
||||
#define DATA_PICC_TO_PCD 0xFF
|
||||
#define DATA_PCD_TO_PICC 0xFE
|
||||
#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB
|
||||
#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA
|
||||
|
||||
#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.pcap")
|
||||
|
||||
struct NfcDebugPcap {
|
||||
Stream* file_stream;
|
||||
};
|
||||
|
||||
static Stream* nfc_debug_pcap_open(Storage* storage) {
|
||||
Stream* stream = NULL;
|
||||
stream = buffered_file_stream_alloc(storage);
|
||||
if(!buffered_file_stream_open(stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||
buffered_file_stream_close(stream);
|
||||
stream_free(stream);
|
||||
stream = NULL;
|
||||
} else {
|
||||
if(!stream_tell(stream)) {
|
||||
struct {
|
||||
uint32_t magic;
|
||||
uint16_t major, minor;
|
||||
uint32_t reserved[2];
|
||||
uint32_t snaplen;
|
||||
uint32_t link_type;
|
||||
} __attribute__((__packed__)) pcap_hdr = {
|
||||
.magic = PCAP_MAGIC,
|
||||
.major = PCAP_MAJOR,
|
||||
.minor = PCAP_MINOR,
|
||||
.snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE,
|
||||
.link_type = DLT_ISO_14443,
|
||||
};
|
||||
if(stream_write(stream, (uint8_t*)&pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) {
|
||||
FURI_LOG_E(TAG, "Failed to write pcap header");
|
||||
buffered_file_stream_close(stream);
|
||||
stream_free(stream);
|
||||
stream = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
NfcDebugPcap* nfc_debug_pcap_alloc() {
|
||||
NfcDebugPcap* instance = malloc(sizeof(NfcDebugPcap));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->file_stream = nfc_debug_pcap_open(storage);
|
||||
if(!instance->file_stream) {
|
||||
free(instance);
|
||||
instance = NULL;
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_debug_pcap_free(NfcDebugPcap* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->file_stream);
|
||||
|
||||
buffered_file_stream_close(instance->file_stream);
|
||||
stream_free(instance->file_stream);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void nfc_debug_pcap_process_data(
|
||||
NfcDebugPcap* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped) {
|
||||
furi_assert(instance);
|
||||
furi_assert(data);
|
||||
FuriHalRtcDateTime datetime;
|
||||
furi_hal_rtc_get_datetime(&datetime);
|
||||
|
||||
uint8_t event = 0;
|
||||
if(reader_to_tag) {
|
||||
if(crc_dropped) {
|
||||
event = DATA_PCD_TO_PICC_CRC_DROPPED;
|
||||
} else {
|
||||
event = DATA_PCD_TO_PICC;
|
||||
}
|
||||
} else {
|
||||
if(crc_dropped) {
|
||||
event = DATA_PICC_TO_PCD_CRC_DROPPED;
|
||||
} else {
|
||||
event = DATA_PICC_TO_PCD;
|
||||
}
|
||||
}
|
||||
|
||||
struct {
|
||||
// https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header
|
||||
uint32_t ts_sec;
|
||||
uint32_t ts_usec;
|
||||
uint32_t incl_len;
|
||||
uint32_t orig_len;
|
||||
// https://www.kaiser.cx/posts/pcap-iso14443/#_packet_data
|
||||
uint8_t version;
|
||||
uint8_t event;
|
||||
uint16_t len;
|
||||
} __attribute__((__packed__)) pkt_hdr = {
|
||||
.ts_sec = furi_hal_rtc_datetime_to_timestamp(&datetime),
|
||||
.ts_usec = 0,
|
||||
.incl_len = len + 4,
|
||||
.orig_len = len + 4,
|
||||
.version = 0,
|
||||
.event = event,
|
||||
.len = len << 8 | len >> 8,
|
||||
};
|
||||
stream_write(instance->file_stream, (uint8_t*)&pkt_hdr, sizeof(pkt_hdr));
|
||||
stream_write(instance->file_stream, data, len);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct NfcDebugPcap NfcDebugPcap;
|
||||
|
||||
NfcDebugPcap* nfc_debug_pcap_alloc();
|
||||
|
||||
void nfc_debug_pcap_free(NfcDebugPcap* instance);
|
||||
|
||||
void nfc_debug_pcap_process_data(
|
||||
NfcDebugPcap* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped);
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "nfc_dict.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include <toolbox/stream/buffered_file_stream.h>
|
||||
#include <toolbox/args.h>
|
||||
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
|
||||
#define TAG "NfcDict"
|
||||
|
||||
struct NfcDict {
|
||||
Stream* stream;
|
||||
size_t key_size;
|
||||
size_t key_size_symbols;
|
||||
uint32_t total_keys;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
FS_OpenMode open_mode;
|
||||
} NfcDictFile;
|
||||
|
||||
bool nfc_dict_check_presence(const char* path) {
|
||||
furi_assert(path);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
bool dict_present = storage_common_stat(storage, path, NULL) == FSE_OK;
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return dict_present;
|
||||
}
|
||||
|
||||
NfcDict* nfc_dict_alloc(const char* path, NfcDictMode mode, size_t key_size) {
|
||||
furi_assert(path);
|
||||
|
||||
NfcDict* instance = malloc(sizeof(NfcDict));
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->stream = buffered_file_stream_alloc(storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
FS_OpenMode open_mode = FSOM_OPEN_EXISTING;
|
||||
if(mode == NfcDictModeOpenAlways) {
|
||||
open_mode = FSOM_OPEN_ALWAYS;
|
||||
}
|
||||
instance->key_size = key_size;
|
||||
// Byte = 2 symbols + 1 end of line
|
||||
instance->key_size_symbols = key_size * 2 + 1;
|
||||
|
||||
bool dict_loaded = false;
|
||||
do {
|
||||
if(!buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode)) {
|
||||
buffered_file_stream_close(instance->stream);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for new line ending
|
||||
if(!stream_eof(instance->stream)) {
|
||||
if(!stream_seek(instance->stream, -1, StreamOffsetFromEnd)) break;
|
||||
uint8_t last_char = 0;
|
||||
if(stream_read(instance->stream, &last_char, 1) != 1) break;
|
||||
if(last_char != '\n') {
|
||||
FURI_LOG_D(TAG, "Adding new line ending");
|
||||
if(stream_write_char(instance->stream, '\n') != 1) break;
|
||||
}
|
||||
if(!stream_rewind(instance->stream)) break;
|
||||
}
|
||||
|
||||
// Read total amount of keys
|
||||
FuriString* next_line;
|
||||
next_line = furi_string_alloc();
|
||||
while(true) {
|
||||
if(!stream_read_line(instance->stream, next_line)) {
|
||||
FURI_LOG_T(TAG, "No keys left in dict");
|
||||
break;
|
||||
}
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"Read line: %s, len: %zu",
|
||||
furi_string_get_cstr(next_line),
|
||||
furi_string_size(next_line));
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != instance->key_size_symbols) continue;
|
||||
instance->total_keys++;
|
||||
}
|
||||
furi_string_free(next_line);
|
||||
stream_rewind(instance->stream);
|
||||
|
||||
dict_loaded = true;
|
||||
FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", instance->total_keys);
|
||||
} while(false);
|
||||
|
||||
if(!dict_loaded) {
|
||||
buffered_file_stream_close(instance->stream);
|
||||
free(instance);
|
||||
instance = NULL;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_dict_free(NfcDict* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
|
||||
buffered_file_stream_close(instance->stream);
|
||||
stream_free(instance->stream);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void nfc_dict_int_to_str(NfcDict* instance, const uint8_t* key_int, FuriString* key_str) {
|
||||
furi_string_reset(key_str);
|
||||
for(size_t i = 0; i < instance->key_size; i++) {
|
||||
furi_string_cat_printf(key_str, "%02X", key_int[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_dict_str_to_int(NfcDict* instance, FuriString* key_str, uint64_t* key_int) {
|
||||
uint8_t key_byte_tmp;
|
||||
|
||||
*key_int = 0ULL;
|
||||
for(uint8_t i = 0; i < instance->key_size * 2; i += 2) {
|
||||
args_char_to_hex(
|
||||
furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp);
|
||||
*key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nfc_dict_get_total_keys(NfcDict* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->total_keys;
|
||||
}
|
||||
|
||||
bool nfc_dict_rewind(NfcDict* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
|
||||
return stream_rewind(instance->stream);
|
||||
}
|
||||
|
||||
static bool nfc_dict_get_next_key_str(NfcDict* instance, FuriString* key) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
|
||||
bool key_read = false;
|
||||
furi_string_reset(key);
|
||||
while(!key_read) {
|
||||
if(!stream_read_line(instance->stream, key)) break;
|
||||
if(furi_string_get_char(key, 0) == '#') continue;
|
||||
if(furi_string_size(key) != instance->key_size_symbols) continue;
|
||||
furi_string_left(key, instance->key_size_symbols - 1);
|
||||
key_read = true;
|
||||
}
|
||||
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool nfc_dict_get_next_key(NfcDict* instance, uint8_t* key, size_t key_size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
furi_assert(instance->key_size == key_size);
|
||||
|
||||
FuriString* temp_key = furi_string_alloc();
|
||||
uint64_t key_int = 0;
|
||||
bool key_read = nfc_dict_get_next_key_str(instance, temp_key);
|
||||
if(key_read) {
|
||||
nfc_dict_str_to_int(instance, temp_key, &key_int);
|
||||
nfc_util_num2bytes(key_int, key_size, key);
|
||||
}
|
||||
furi_string_free(temp_key);
|
||||
return key_read;
|
||||
}
|
||||
|
||||
static bool nfc_dict_is_key_present_str(NfcDict* instance, FuriString* key) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
|
||||
FuriString* next_line;
|
||||
next_line = furi_string_alloc();
|
||||
|
||||
bool key_found = false;
|
||||
stream_rewind(instance->stream);
|
||||
while(!key_found) { //-V654
|
||||
if(!stream_read_line(instance->stream, next_line)) break;
|
||||
if(furi_string_get_char(next_line, 0) == '#') continue;
|
||||
if(furi_string_size(next_line) != instance->key_size_symbols) continue;
|
||||
furi_string_left(next_line, instance->key_size_symbols - 1);
|
||||
if(!furi_string_equal(key, next_line)) continue;
|
||||
key_found = true;
|
||||
}
|
||||
|
||||
furi_string_free(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool nfc_dict_is_key_present(NfcDict* instance, const uint8_t* key, size_t key_size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(key);
|
||||
furi_assert(instance->stream);
|
||||
furi_assert(instance->key_size == key_size);
|
||||
|
||||
FuriString* temp_key = furi_string_alloc();
|
||||
nfc_dict_int_to_str(instance, key, temp_key);
|
||||
bool key_found = nfc_dict_is_key_present_str(instance, temp_key);
|
||||
furi_string_free(temp_key);
|
||||
|
||||
return key_found;
|
||||
}
|
||||
|
||||
static bool nfc_dict_add_key_str(NfcDict* instance, FuriString* key) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
|
||||
furi_string_cat_printf(key, "\n");
|
||||
|
||||
bool key_added = false;
|
||||
do {
|
||||
if(!stream_seek(instance->stream, 0, StreamOffsetFromEnd)) break;
|
||||
if(!stream_insert_string(instance->stream, key)) break;
|
||||
instance->total_keys++;
|
||||
key_added = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_left(key, instance->key_size_symbols - 1);
|
||||
return key_added;
|
||||
}
|
||||
|
||||
bool nfc_dict_add_key(NfcDict* instance, const uint8_t* key, size_t key_size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(key);
|
||||
furi_assert(instance->stream);
|
||||
furi_assert(instance->key_size == key_size);
|
||||
|
||||
FuriString* temp_key = furi_string_alloc();
|
||||
nfc_dict_int_to_str(instance, key, temp_key);
|
||||
bool key_added = nfc_dict_add_key_str(instance, temp_key);
|
||||
furi_string_free(temp_key);
|
||||
|
||||
return key_added;
|
||||
}
|
||||
|
||||
bool nfc_dict_delete_key(NfcDict* instance, const uint8_t* key, size_t key_size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->stream);
|
||||
furi_assert(key);
|
||||
furi_assert(instance->key_size == key_size);
|
||||
|
||||
bool key_removed = false;
|
||||
uint8_t* temp_key = malloc(key_size);
|
||||
|
||||
nfc_dict_rewind(instance);
|
||||
while(!key_removed) {
|
||||
if(!nfc_dict_get_next_key(instance, temp_key, key_size)) break;
|
||||
if(memcmp(temp_key, key, key_size) == 0) {
|
||||
int32_t offset = (-1) * (instance->key_size_symbols);
|
||||
stream_seek(instance->stream, offset, StreamOffsetFromCurrent);
|
||||
if(!stream_delete(instance->stream, instance->key_size_symbols)) break;
|
||||
instance->total_keys--;
|
||||
key_removed = true;
|
||||
}
|
||||
}
|
||||
nfc_dict_rewind(instance);
|
||||
free(temp_key);
|
||||
|
||||
return key_removed;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
NfcDictModeOpenExisting,
|
||||
NfcDictModeOpenAlways,
|
||||
} NfcDictMode;
|
||||
|
||||
typedef struct NfcDict NfcDict;
|
||||
|
||||
/** Check dictionary presence
|
||||
*
|
||||
* @param path - dictionary path
|
||||
*
|
||||
* @return true if dictionary exists, false otherwise
|
||||
*/
|
||||
bool nfc_dict_check_presence(const char* path);
|
||||
|
||||
/** Open or create dictionary
|
||||
* Depending on mode, dictionary will be opened or created.
|
||||
*
|
||||
* @param path - dictionary path
|
||||
* @param mode - NfcDictMode value
|
||||
* @param key_size - size of dictionary keys in bytes
|
||||
*
|
||||
* @return NfcDict dictionary instance
|
||||
*/
|
||||
NfcDict* nfc_dict_alloc(const char* path, NfcDictMode mode, size_t key_size);
|
||||
|
||||
/** Close dictionary
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
*/
|
||||
void nfc_dict_free(NfcDict* instance);
|
||||
|
||||
/** Get total number of keys in dictionary
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
*
|
||||
* @return total number of keys in dictionary
|
||||
*/
|
||||
uint32_t nfc_dict_get_total_keys(NfcDict* instance);
|
||||
|
||||
/** Rewind dictionary
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
*
|
||||
* @return true if rewind was successful, false otherwise
|
||||
*/
|
||||
bool nfc_dict_rewind(NfcDict* instance);
|
||||
|
||||
/** Check if key is present in dictionary
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
* @param key - key to check
|
||||
* @param key_size - size of key in bytes
|
||||
*
|
||||
* @return true if key is present, false otherwise
|
||||
*/
|
||||
bool nfc_dict_is_key_present(NfcDict* instance, const uint8_t* key, size_t key_size);
|
||||
|
||||
/** Get next key from dictionary
|
||||
* This function will return next key from dictionary. If there are no more
|
||||
* keys, it will return false, and nfc_dict_rewind() should be called.
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
* @param key - buffer to store key
|
||||
* @param key_size - size of key in bytes
|
||||
*
|
||||
* @return true if key was successfully retrieved, false otherwise
|
||||
*/
|
||||
bool nfc_dict_get_next_key(NfcDict* instance, uint8_t* key, size_t key_size);
|
||||
|
||||
/** Add key to dictionary
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
* @param key - key to add
|
||||
* @param key_size - size of key in bytes
|
||||
*
|
||||
* @return true if key was successfully added, false otherwise
|
||||
*/
|
||||
bool nfc_dict_add_key(NfcDict* instance, const uint8_t* key, size_t key_size);
|
||||
|
||||
/** Delete key from dictionary
|
||||
*
|
||||
* @param instance - NfcDict dictionary instance
|
||||
* @param key - key to delete
|
||||
* @param key_size - size of key in bytes
|
||||
*
|
||||
* @return true if key was successfully deleted, false otherwise
|
||||
*/
|
||||
bool nfc_dict_delete_key(NfcDict* instance, const uint8_t* key, size_t key_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,528 +0,0 @@
|
||||
#include <furi_hal_random.h>
|
||||
#include "nfc_generators.h"
|
||||
|
||||
#define NXP_MANUFACTURER_ID (0x04)
|
||||
|
||||
static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03};
|
||||
static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03};
|
||||
static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03};
|
||||
static const uint8_t default_data_ntag203[] =
|
||||
{0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE};
|
||||
static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00};
|
||||
|
||||
static void nfc_generate_common_start(NfcDeviceData* data) {
|
||||
nfc_device_data_clear(data);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_uid(uint8_t* uid) {
|
||||
uid[0] = NXP_MANUFACTURER_ID;
|
||||
furi_hal_random_fill_buf(&uid[1], 6);
|
||||
// I'm not sure how this is generated, but the upper nybble always seems to be 8
|
||||
uid[6] &= 0x0F;
|
||||
uid[6] |= 0x80;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) {
|
||||
uid[0] = NXP_MANUFACTURER_ID;
|
||||
furi_hal_random_fill_buf(&uid[1], length - 1);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_block_0(
|
||||
uint8_t* block,
|
||||
uint8_t uid_len,
|
||||
uint8_t sak,
|
||||
uint8_t atqa0,
|
||||
uint8_t atqa1) {
|
||||
// Block length is always 16 bytes, and the UID can be either 4 or 7 bytes
|
||||
furi_assert(uid_len == 4 || uid_len == 7);
|
||||
furi_assert(block);
|
||||
|
||||
if(uid_len == 4) {
|
||||
// Calculate BCC
|
||||
block[uid_len] = 0;
|
||||
|
||||
for(int i = 0; i < uid_len; i++) {
|
||||
block[uid_len] ^= block[i];
|
||||
}
|
||||
} else {
|
||||
uid_len -= 1;
|
||||
}
|
||||
|
||||
block[uid_len + 1] = sak;
|
||||
block[uid_len + 2] = atqa0;
|
||||
block[uid_len + 3] = atqa1;
|
||||
|
||||
for(int i = uid_len + 4; i < 16; i++) {
|
||||
block[i] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) {
|
||||
// All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h.
|
||||
MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].value;
|
||||
sec_tr->access_bits[0] = 0xFF;
|
||||
sec_tr->access_bits[1] = 0x07;
|
||||
sec_tr->access_bits[2] = 0x80;
|
||||
sec_tr->access_bits[3] = 0x69; // Nice
|
||||
|
||||
memset(sec_tr->key_a, 0xff, sizeof(sec_tr->key_a));
|
||||
memset(sec_tr->key_b, 0xff, sizeof(sec_tr->key_b));
|
||||
|
||||
mf_classic_set_block_read(data, block, &data->block[block]);
|
||||
mf_classic_set_key_found(
|
||||
data, mf_classic_get_sector_by_block(block), MfClassicKeyA, 0xFFFFFFFFFFFF);
|
||||
mf_classic_set_key_found(
|
||||
data, mf_classic_get_sector_by_block(block), MfClassicKeyB, 0xFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_common(NfcDeviceData* data) {
|
||||
data->nfc_data.type = FuriHalNfcTypeA;
|
||||
data->nfc_data.interface = FuriHalNfcInterfaceRf;
|
||||
data->nfc_data.uid_len = 7;
|
||||
nfc_generate_mf_ul_uid(data->nfc_data.uid);
|
||||
data->nfc_data.atqa[0] = 0x44;
|
||||
data->nfc_data.atqa[1] = 0x00;
|
||||
data->nfc_data.sak = 0x00;
|
||||
data->protocol = NfcDeviceProtocolMifareUl;
|
||||
}
|
||||
|
||||
static void
|
||||
nfc_generate_mf_classic_common(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) {
|
||||
data->nfc_data.type = FuriHalNfcTypeA;
|
||||
data->nfc_data.interface = FuriHalNfcInterfaceRf;
|
||||
data->nfc_data.uid_len = uid_len;
|
||||
data->nfc_data.atqa[0] = 0x44;
|
||||
data->nfc_data.atqa[1] = 0x00;
|
||||
data->nfc_data.sak = 0x08;
|
||||
data->protocol = NfcDeviceProtocolMifareClassic;
|
||||
data->mf_classic_data.type = type;
|
||||
}
|
||||
|
||||
static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) {
|
||||
*bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2];
|
||||
*bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6];
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_copy_uid_with_bcc(NfcDeviceData* data) {
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
memcpy(mful->data, data->nfc_data.uid, 3);
|
||||
memcpy(&mful->data[4], &data->nfc_data.uid[3], 4);
|
||||
nfc_generate_calc_bcc(data->nfc_data.uid, &mful->data[3], &mful->data[8]);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_orig(NfcDeviceData* data) {
|
||||
nfc_generate_common_start(data);
|
||||
nfc_generate_mf_ul_common(data);
|
||||
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeUnknown;
|
||||
mful->data_size = 16 * 4;
|
||||
mful->data_read = mful->data_size;
|
||||
nfc_generate_mf_ul_copy_uid_with_bcc(data);
|
||||
// TODO: what's internal byte on page 2?
|
||||
memset(&mful->data[4 * 4], 0xFF, 4);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_ntag203(NfcDeviceData* data) {
|
||||
nfc_generate_common_start(data);
|
||||
nfc_generate_mf_ul_common(data);
|
||||
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeNTAG203;
|
||||
mful->data_size = 42 * 4;
|
||||
mful->data_read = mful->data_size;
|
||||
nfc_generate_mf_ul_copy_uid_with_bcc(data);
|
||||
mful->data[9] = 0x48; // Internal byte
|
||||
memcpy(&mful->data[3 * 4], default_data_ntag203, sizeof(default_data_ntag203));
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) {
|
||||
nfc_generate_common_start(data);
|
||||
nfc_generate_mf_ul_common(data);
|
||||
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->data_size = num_pages * 4;
|
||||
mful->data_read = mful->data_size;
|
||||
nfc_generate_mf_ul_copy_uid_with_bcc(data);
|
||||
uint16_t config_index = (num_pages - 4) * 4;
|
||||
mful->data[config_index] = 0x04; // STRG_MOD_EN
|
||||
mful->data[config_index + 3] = 0xFF; // AUTH0
|
||||
mful->data[config_index + 5] = 0x05; // VCTID
|
||||
memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD
|
||||
if(num_pages > 20) mful->data[config_index - 1] = MF_UL_TEARING_FLAG_DEFAULT;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_ev1_common(NfcDeviceData* data, uint8_t num_pages) {
|
||||
nfc_generate_mf_ul_with_config_common(data, num_pages);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
memcpy(&mful->version, version_bytes_mf0ulx1, sizeof(version_bytes_mf0ulx1));
|
||||
for(size_t i = 0; i < 3; ++i) {
|
||||
mful->tearing[i] = MF_UL_TEARING_FLAG_DEFAULT;
|
||||
}
|
||||
// TODO: what's internal byte on page 2?
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_11(NfcDeviceData* data) {
|
||||
nfc_generate_mf_ul_ev1_common(data, 20);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeUL11;
|
||||
mful->version.prod_subtype = 0x01;
|
||||
mful->version.storage_size = 0x0B;
|
||||
mful->data[16 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_h11(NfcDeviceData* data) {
|
||||
nfc_generate_mf_ul_ev1_common(data, 20);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeUL11;
|
||||
mful->version.prod_subtype = 0x02;
|
||||
mful->version.storage_size = 0x0B;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_21(NfcDeviceData* data) {
|
||||
nfc_generate_mf_ul_ev1_common(data, 41);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeUL21;
|
||||
mful->version.prod_subtype = 0x01;
|
||||
mful->version.storage_size = 0x0E;
|
||||
mful->data[37 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_ul_h21(NfcDeviceData* data) {
|
||||
nfc_generate_mf_ul_ev1_common(data, 41);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeUL21;
|
||||
mful->version.prod_subtype = 0x02;
|
||||
mful->version.storage_size = 0x0E;
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag21x_common(NfcDeviceData* data, uint8_t num_pages) {
|
||||
nfc_generate_mf_ul_with_config_common(data, num_pages);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
memcpy(&mful->version, version_bytes_ntag21x, sizeof(version_bytes_mf0ulx1));
|
||||
mful->data[9] = 0x48; // Internal byte
|
||||
// Capability container
|
||||
mful->data[12] = 0xE1;
|
||||
mful->data[13] = 0x10;
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag213(NfcDeviceData* data) {
|
||||
nfc_generate_ntag21x_common(data, 45);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeNTAG213;
|
||||
mful->version.storage_size = 0x0F;
|
||||
mful->data[14] = 0x12;
|
||||
// Default contents
|
||||
memcpy(&mful->data[16], default_data_ntag213, sizeof(default_data_ntag213));
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag215(NfcDeviceData* data) {
|
||||
nfc_generate_ntag21x_common(data, 135);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeNTAG215;
|
||||
mful->version.storage_size = 0x11;
|
||||
mful->data[14] = 0x3E;
|
||||
// Default contents
|
||||
memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216));
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag216(NfcDeviceData* data) {
|
||||
nfc_generate_ntag21x_common(data, 231);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = MfUltralightTypeNTAG216;
|
||||
mful->version.storage_size = 0x13;
|
||||
mful->data[14] = 0x6D;
|
||||
// Default contents
|
||||
memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216));
|
||||
}
|
||||
|
||||
static void
|
||||
nfc_generate_ntag_i2c_common(NfcDeviceData* data, MfUltralightType type, uint16_t num_pages) {
|
||||
nfc_generate_common_start(data);
|
||||
nfc_generate_mf_ul_common(data);
|
||||
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->type = type;
|
||||
memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c));
|
||||
mful->data_size = num_pages * 4;
|
||||
mful->data_read = mful->data_size;
|
||||
memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len);
|
||||
mful->data[7] = data->nfc_data.sak;
|
||||
mful->data[8] = data->nfc_data.atqa[0];
|
||||
mful->data[9] = data->nfc_data.atqa[1];
|
||||
|
||||
uint16_t config_register_page;
|
||||
uint16_t session_register_page;
|
||||
|
||||
// Sync with mifare_ultralight.c
|
||||
switch(type) {
|
||||
case MfUltralightTypeNTAGI2C1K:
|
||||
config_register_page = 227;
|
||||
session_register_page = 229;
|
||||
break;
|
||||
case MfUltralightTypeNTAGI2C2K:
|
||||
config_register_page = 481;
|
||||
session_register_page = 483;
|
||||
break;
|
||||
case MfUltralightTypeNTAGI2CPlus1K:
|
||||
case MfUltralightTypeNTAGI2CPlus2K:
|
||||
config_register_page = 232;
|
||||
session_register_page = 234;
|
||||
break;
|
||||
default:
|
||||
furi_crash("Unknown MFUL");
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(
|
||||
&mful->data[config_register_page * 4],
|
||||
default_config_ntag_i2c,
|
||||
sizeof(default_config_ntag_i2c));
|
||||
memcpy(
|
||||
&mful->data[session_register_page * 4],
|
||||
default_config_ntag_i2c,
|
||||
sizeof(default_config_ntag_i2c));
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_1k(NfcDeviceData* data) {
|
||||
nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C1K, 231);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->version.prod_ver_minor = 0x01;
|
||||
mful->version.storage_size = 0x13;
|
||||
|
||||
memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
|
||||
mful->data[14] = 0x6D; // Size of tag in CC
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_2k(NfcDeviceData* data) {
|
||||
nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C2K, 485);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->version.prod_ver_minor = 0x01;
|
||||
mful->version.storage_size = 0x15;
|
||||
|
||||
memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c));
|
||||
mful->data[14] = 0xEA; // Size of tag in CC
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_plus_common(
|
||||
NfcDeviceData* data,
|
||||
MfUltralightType type,
|
||||
uint16_t num_pages) {
|
||||
nfc_generate_ntag_i2c_common(data, type, num_pages);
|
||||
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
uint16_t config_index = 227 * 4;
|
||||
mful->data[config_index + 3] = 0xFF; // AUTH0
|
||||
memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_plus_1k(NfcDeviceData* data) {
|
||||
nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus1K, 236);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->version.prod_ver_minor = 0x02;
|
||||
mful->version.storage_size = 0x13;
|
||||
}
|
||||
|
||||
static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) {
|
||||
nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus2K, 492);
|
||||
MfUltralightData* mful = &data->mf_ul_data;
|
||||
mful->version.prod_ver_minor = 0x02;
|
||||
mful->version.storage_size = 0x15;
|
||||
}
|
||||
|
||||
void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) {
|
||||
nfc_generate_common_start(data);
|
||||
nfc_generate_mf_classic_uid(data->mf_classic_data.block[0].value, uid_len);
|
||||
nfc_generate_mf_classic_common(data, uid_len, type);
|
||||
|
||||
// Set the UID
|
||||
data->nfc_data.uid[0] = NXP_MANUFACTURER_ID;
|
||||
for(int i = 1; i < uid_len; i++) {
|
||||
data->nfc_data.uid[i] = data->mf_classic_data.block[0].value[i];
|
||||
}
|
||||
|
||||
MfClassicData* mfc = &data->mf_classic_data;
|
||||
mf_classic_set_block_read(mfc, 0, &mfc->block[0]);
|
||||
|
||||
if(type == MfClassicType4k) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < 256; i += 1) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc, i);
|
||||
} else {
|
||||
memset(&mfc->block[i].value, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc, i, &mfc->block[i]);
|
||||
}
|
||||
// Set SAK to 18
|
||||
data->nfc_data.sak = 0x18;
|
||||
} else if(type == MfClassicType1k) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc, i);
|
||||
} else {
|
||||
memset(&mfc->block[i].value, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc, i, &mfc->block[i]);
|
||||
}
|
||||
// Set SAK to 08
|
||||
data->nfc_data.sak = 0x08;
|
||||
} else if(type == MfClassicTypeMini) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc, i);
|
||||
} else {
|
||||
memset(&mfc->block[i].value, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc, i, &mfc->block[i]);
|
||||
}
|
||||
// Set SAK to 09
|
||||
data->nfc_data.sak = 0x09;
|
||||
}
|
||||
|
||||
nfc_generate_mf_classic_block_0(
|
||||
data->mf_classic_data.block[0].value,
|
||||
uid_len,
|
||||
data->nfc_data.sak,
|
||||
data->nfc_data.atqa[0],
|
||||
data->nfc_data.atqa[1]);
|
||||
|
||||
mfc->type = type;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_mini(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicTypeMini);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicType1k);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_1k_7b_uid(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 7, MfClassicType1k);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_4k_4b_uid(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicType4k);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 7, MfClassicType4k);
|
||||
}
|
||||
|
||||
static const NfcGenerator mf_ul_generator = {
|
||||
.name = "Mifare Ultralight",
|
||||
.generator_func = nfc_generate_mf_ul_orig,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_11_generator = {
|
||||
.name = "Mifare Ultralight EV1 11",
|
||||
.generator_func = nfc_generate_mf_ul_11,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_h11_generator = {
|
||||
.name = "Mifare Ultralight EV1 H11",
|
||||
.generator_func = nfc_generate_mf_ul_h11,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_21_generator = {
|
||||
.name = "Mifare Ultralight EV1 21",
|
||||
.generator_func = nfc_generate_mf_ul_21,
|
||||
};
|
||||
|
||||
static const NfcGenerator mf_ul_h21_generator = {
|
||||
.name = "Mifare Ultralight EV1 H21",
|
||||
.generator_func = nfc_generate_mf_ul_h21,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag203_generator = {
|
||||
.name = "NTAG203",
|
||||
.generator_func = nfc_generate_mf_ul_ntag203,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag213_generator = {
|
||||
.name = "NTAG213",
|
||||
.generator_func = nfc_generate_ntag213,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag215_generator = {
|
||||
.name = "NTAG215",
|
||||
.generator_func = nfc_generate_ntag215,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag216_generator = {
|
||||
.name = "NTAG216",
|
||||
.generator_func = nfc_generate_ntag216,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_1k_generator = {
|
||||
.name = "NTAG I2C 1k",
|
||||
.generator_func = nfc_generate_ntag_i2c_1k,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_2k_generator = {
|
||||
.name = "NTAG I2C 2k",
|
||||
.generator_func = nfc_generate_ntag_i2c_2k,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_plus_1k_generator = {
|
||||
.name = "NTAG I2C Plus 1k",
|
||||
.generator_func = nfc_generate_ntag_i2c_plus_1k,
|
||||
};
|
||||
|
||||
static const NfcGenerator ntag_i2c_plus_2k_generator = {
|
||||
.name = "NTAG I2C Plus 2k",
|
||||
.generator_func = nfc_generate_ntag_i2c_plus_2k,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_mini_generator = {
|
||||
.name = "Mifare Mini",
|
||||
.generator_func = nfc_generate_mf_mini,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_1k_4b_uid_generator = {
|
||||
.name = "Mifare Classic 1k 4byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_1k_4b_uid,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_1k_7b_uid_generator = {
|
||||
.name = "Mifare Classic 1k 7byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_1k_7b_uid,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_4k_4b_uid_generator = {
|
||||
.name = "Mifare Classic 4k 4byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_4k_4b_uid,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_4k_7b_uid_generator = {
|
||||
.name = "Mifare Classic 4k 7byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_4k_7b_uid,
|
||||
};
|
||||
|
||||
const NfcGenerator* const nfc_generators[] = {
|
||||
&mf_ul_generator,
|
||||
&mf_ul_11_generator,
|
||||
&mf_ul_h11_generator,
|
||||
&mf_ul_21_generator,
|
||||
&mf_ul_h21_generator,
|
||||
&ntag203_generator,
|
||||
&ntag213_generator,
|
||||
&ntag215_generator,
|
||||
&ntag216_generator,
|
||||
&ntag_i2c_1k_generator,
|
||||
&ntag_i2c_2k_generator,
|
||||
&ntag_i2c_plus_1k_generator,
|
||||
&ntag_i2c_plus_2k_generator,
|
||||
&mifare_mini_generator,
|
||||
&mifare_classic_1k_4b_uid_generator,
|
||||
&mifare_classic_1k_7b_uid_generator,
|
||||
&mifare_classic_4k_4b_uid_generator,
|
||||
&mifare_classic_4k_7b_uid_generator,
|
||||
NULL,
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nfc_device.h"
|
||||
|
||||
typedef void (*NfcGeneratorFunc)(NfcDeviceData* data);
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
NfcGeneratorFunc generator_func;
|
||||
} NfcGenerator;
|
||||
|
||||
extern const NfcGenerator* const nfc_generators[];
|
||||
|
||||
void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type);
|
||||
@@ -1,265 +0,0 @@
|
||||
#include "reader_analyzer.h"
|
||||
#include <lib/nfc/protocols/nfc_util.h>
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#include "mfkey32.h"
|
||||
#include "nfc_debug_pcap.h"
|
||||
#include "nfc_debug_log.h"
|
||||
|
||||
#define TAG "ReaderAnalyzer"
|
||||
|
||||
#define READER_ANALYZER_MAX_BUFF_SIZE (1024)
|
||||
|
||||
typedef struct {
|
||||
bool reader_to_tag;
|
||||
bool crc_dropped;
|
||||
uint16_t len;
|
||||
} ReaderAnalyzerHeader;
|
||||
|
||||
typedef enum {
|
||||
ReaderAnalyzerNfcDataMfClassic,
|
||||
} ReaderAnalyzerNfcData;
|
||||
|
||||
struct ReaderAnalyzer {
|
||||
FuriHalNfcDevData nfc_data;
|
||||
|
||||
bool alive;
|
||||
FuriStreamBuffer* stream;
|
||||
FuriThread* thread;
|
||||
|
||||
ReaderAnalyzerParseDataCallback callback;
|
||||
void* context;
|
||||
|
||||
ReaderAnalyzerMode mode;
|
||||
Mfkey32* mfkey32;
|
||||
NfcDebugLog* debug_log;
|
||||
NfcDebugPcap* pcap;
|
||||
};
|
||||
|
||||
const FuriHalNfcDevData reader_analyzer_nfc_data[] = {
|
||||
[ReaderAnalyzerNfcDataMfClassic] =
|
||||
{.sak = 0x08,
|
||||
.atqa = {0x44, 0x00},
|
||||
.interface = FuriHalNfcInterfaceRf,
|
||||
.type = FuriHalNfcTypeA,
|
||||
.uid_len = 7,
|
||||
.uid = {0x04, 0x77, 0x70, 0x2A, 0x23, 0x4F, 0x80},
|
||||
.cuid = 0x2A234F80},
|
||||
};
|
||||
|
||||
void reader_analyzer_parse(ReaderAnalyzer* instance, uint8_t* buffer, size_t size) {
|
||||
if(size < sizeof(ReaderAnalyzerHeader)) return;
|
||||
|
||||
size_t bytes_i = 0;
|
||||
while(bytes_i < size) {
|
||||
ReaderAnalyzerHeader* header = (ReaderAnalyzerHeader*)&buffer[bytes_i];
|
||||
uint16_t len = header->len;
|
||||
if(bytes_i + len > size) break;
|
||||
bytes_i += sizeof(ReaderAnalyzerHeader);
|
||||
if(instance->mfkey32) {
|
||||
mfkey32_process_data(
|
||||
instance->mfkey32,
|
||||
&buffer[bytes_i],
|
||||
len,
|
||||
header->reader_to_tag,
|
||||
header->crc_dropped);
|
||||
}
|
||||
if(instance->pcap) {
|
||||
nfc_debug_pcap_process_data(
|
||||
instance->pcap, &buffer[bytes_i], len, header->reader_to_tag, header->crc_dropped);
|
||||
}
|
||||
if(instance->debug_log) {
|
||||
nfc_debug_log_process_data(
|
||||
instance->debug_log,
|
||||
&buffer[bytes_i],
|
||||
len,
|
||||
header->reader_to_tag,
|
||||
header->crc_dropped);
|
||||
}
|
||||
bytes_i += len;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t reader_analyzer_thread(void* context) {
|
||||
ReaderAnalyzer* reader_analyzer = context;
|
||||
uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {};
|
||||
|
||||
while(reader_analyzer->alive || !furi_stream_buffer_is_empty(reader_analyzer->stream)) {
|
||||
size_t ret = furi_stream_buffer_receive(
|
||||
reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50);
|
||||
if(ret) {
|
||||
reader_analyzer_parse(reader_analyzer, buffer, ret);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReaderAnalyzer* reader_analyzer_alloc() {
|
||||
ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer));
|
||||
|
||||
instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic];
|
||||
instance->alive = false;
|
||||
instance->stream =
|
||||
furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader));
|
||||
|
||||
instance->thread =
|
||||
furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance);
|
||||
furi_thread_set_priority(instance->thread, FuriThreadPriorityLow);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void reader_analyzer_mfkey_callback(Mfkey32Event event, void* context) {
|
||||
furi_assert(context);
|
||||
ReaderAnalyzer* instance = context;
|
||||
|
||||
if(event == Mfkey32EventParamCollected) {
|
||||
if(instance->callback) {
|
||||
instance->callback(ReaderAnalyzerEventMfkeyCollected, instance->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) {
|
||||
furi_assert(instance);
|
||||
|
||||
furi_stream_buffer_reset(instance->stream);
|
||||
if(mode & ReaderAnalyzerModeDebugLog) {
|
||||
instance->debug_log = nfc_debug_log_alloc();
|
||||
}
|
||||
if(mode & ReaderAnalyzerModeMfkey) {
|
||||
instance->mfkey32 = mfkey32_alloc(instance->nfc_data.cuid);
|
||||
if(instance->mfkey32) {
|
||||
mfkey32_set_callback(instance->mfkey32, reader_analyzer_mfkey_callback, instance);
|
||||
}
|
||||
}
|
||||
if(mode & ReaderAnalyzerModeDebugPcap) {
|
||||
instance->pcap = nfc_debug_pcap_alloc();
|
||||
}
|
||||
|
||||
instance->alive = true;
|
||||
furi_thread_start(instance->thread);
|
||||
}
|
||||
|
||||
void reader_analyzer_stop(ReaderAnalyzer* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->alive = false;
|
||||
furi_thread_join(instance->thread);
|
||||
|
||||
if(instance->debug_log) {
|
||||
nfc_debug_log_free(instance->debug_log);
|
||||
instance->debug_log = NULL;
|
||||
}
|
||||
if(instance->mfkey32) {
|
||||
mfkey32_free(instance->mfkey32);
|
||||
instance->mfkey32 = NULL;
|
||||
}
|
||||
if(instance->pcap) {
|
||||
nfc_debug_pcap_free(instance->pcap);
|
||||
instance->pcap = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void reader_analyzer_free(ReaderAnalyzer* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
reader_analyzer_stop(instance);
|
||||
furi_thread_free(instance->thread);
|
||||
furi_stream_buffer_free(instance->stream);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void reader_analyzer_set_callback(
|
||||
ReaderAnalyzer* instance,
|
||||
ReaderAnalyzerParseDataCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
NfcProtocol
|
||||
reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len) {
|
||||
furi_assert(instance);
|
||||
furi_assert(buff_rx);
|
||||
UNUSED(len);
|
||||
NfcProtocol protocol = NfcDeviceProtocolUnknown;
|
||||
|
||||
if((buff_rx[0] == 0x60) || (buff_rx[0] == 0x61)) {
|
||||
protocol = NfcDeviceProtocolMifareClassic;
|
||||
}
|
||||
|
||||
return protocol;
|
||||
}
|
||||
|
||||
FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) {
|
||||
furi_assert(instance);
|
||||
instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic];
|
||||
return &instance->nfc_data;
|
||||
}
|
||||
|
||||
void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) {
|
||||
furi_assert(instance);
|
||||
furi_assert(nfc_data);
|
||||
|
||||
memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData));
|
||||
}
|
||||
|
||||
static void reader_analyzer_write(
|
||||
ReaderAnalyzer* instance,
|
||||
uint8_t* data,
|
||||
uint16_t len,
|
||||
bool reader_to_tag,
|
||||
bool crc_dropped) {
|
||||
ReaderAnalyzerHeader header = {
|
||||
.reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len};
|
||||
size_t data_sent = 0;
|
||||
data_sent = furi_stream_buffer_send(
|
||||
instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever);
|
||||
if(data_sent != sizeof(ReaderAnalyzerHeader)) {
|
||||
FURI_LOG_W(TAG, "Sent %zu out of %zu bytes", data_sent, sizeof(ReaderAnalyzerHeader));
|
||||
}
|
||||
data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever);
|
||||
if(data_sent != len) {
|
||||
FURI_LOG_W(TAG, "Sent %zu out of %u bytes", data_sent, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
reader_analyzer_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
|
||||
UNUSED(crc_dropped);
|
||||
ReaderAnalyzer* reader_analyzer = context;
|
||||
uint16_t bytes = bits < 8 ? 1 : bits / 8;
|
||||
reader_analyzer_write(reader_analyzer, data, bytes, false, crc_dropped);
|
||||
}
|
||||
|
||||
static void
|
||||
reader_analyzer_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
|
||||
UNUSED(crc_dropped);
|
||||
ReaderAnalyzer* reader_analyzer = context;
|
||||
uint16_t bytes = bits < 8 ? 1 : bits / 8;
|
||||
reader_analyzer_write(reader_analyzer, data, bytes, true, crc_dropped);
|
||||
}
|
||||
|
||||
void reader_analyzer_prepare_tx_rx(
|
||||
ReaderAnalyzer* instance,
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
bool is_picc) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_rx);
|
||||
|
||||
if(is_picc) {
|
||||
tx_rx->sniff_tx = reader_analyzer_write_rx;
|
||||
tx_rx->sniff_rx = reader_analyzer_write_tx;
|
||||
} else {
|
||||
tx_rx->sniff_rx = reader_analyzer_write_rx;
|
||||
tx_rx->sniff_tx = reader_analyzer_write_tx;
|
||||
}
|
||||
|
||||
tx_rx->sniff_context = instance;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <lib/nfc/nfc_device.h>
|
||||
|
||||
typedef enum {
|
||||
ReaderAnalyzerModeDebugLog = 0x01,
|
||||
ReaderAnalyzerModeMfkey = 0x02,
|
||||
ReaderAnalyzerModeDebugPcap = 0x04,
|
||||
} ReaderAnalyzerMode;
|
||||
|
||||
typedef enum {
|
||||
ReaderAnalyzerEventMfkeyCollected,
|
||||
} ReaderAnalyzerEvent;
|
||||
|
||||
typedef struct ReaderAnalyzer ReaderAnalyzer;
|
||||
|
||||
typedef void (*ReaderAnalyzerParseDataCallback)(ReaderAnalyzerEvent event, void* context);
|
||||
|
||||
ReaderAnalyzer* reader_analyzer_alloc();
|
||||
|
||||
void reader_analyzer_free(ReaderAnalyzer* instance);
|
||||
|
||||
void reader_analyzer_set_callback(
|
||||
ReaderAnalyzer* instance,
|
||||
ReaderAnalyzerParseDataCallback callback,
|
||||
void* context);
|
||||
|
||||
void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode);
|
||||
|
||||
void reader_analyzer_stop(ReaderAnalyzer* instance);
|
||||
|
||||
NfcProtocol
|
||||
reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len);
|
||||
|
||||
FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance);
|
||||
|
||||
void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data);
|
||||
|
||||
void reader_analyzer_prepare_tx_rx(
|
||||
ReaderAnalyzer* instance,
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
bool is_picc);
|
||||
+649
@@ -0,0 +1,649 @@
|
||||
#ifndef FW_CFG_unit_tests
|
||||
|
||||
#include "nfc.h"
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define TAG "Nfc"
|
||||
|
||||
#define NFC_MAX_BUFFER_SIZE (256)
|
||||
|
||||
typedef enum {
|
||||
NfcStateIdle,
|
||||
NfcStateRunning,
|
||||
} NfcState;
|
||||
|
||||
typedef enum {
|
||||
NfcPollerStateStart,
|
||||
NfcPollerStateReady,
|
||||
NfcPollerStateReset,
|
||||
NfcPollerStateStop,
|
||||
|
||||
NfcPollerStateNum,
|
||||
} NfcPollerState;
|
||||
|
||||
typedef enum {
|
||||
NfcCommStateIdle,
|
||||
NfcCommStateWaitBlockTxTimer,
|
||||
NfcCommStateReadyTx,
|
||||
NfcCommStateWaitTxEnd,
|
||||
NfcCommStateWaitRxStart,
|
||||
NfcCommStateWaitRxEnd,
|
||||
NfcCommStateFailed,
|
||||
} NfcCommState;
|
||||
|
||||
typedef enum {
|
||||
NfcConfigurationStateIdle,
|
||||
NfcConfigurationStateDone,
|
||||
} NfcConfigurationState;
|
||||
|
||||
struct Nfc {
|
||||
NfcState state;
|
||||
NfcPollerState poller_state;
|
||||
NfcCommState comm_state;
|
||||
NfcConfigurationState config_state;
|
||||
NfcMode mode;
|
||||
|
||||
uint32_t fdt_listen_fc;
|
||||
uint32_t mask_rx_time_fc;
|
||||
uint32_t fdt_poll_fc;
|
||||
uint32_t fdt_poll_poll_us;
|
||||
uint32_t guard_time_us;
|
||||
NfcEventCallback callback;
|
||||
void* context;
|
||||
|
||||
uint8_t tx_buffer[NFC_MAX_BUFFER_SIZE];
|
||||
size_t tx_bits;
|
||||
uint8_t rx_buffer[NFC_MAX_BUFFER_SIZE];
|
||||
size_t rx_bits;
|
||||
|
||||
FuriThread* worker_thread;
|
||||
};
|
||||
|
||||
typedef bool (*NfcWorkerPollerStateHandler)(Nfc* instance);
|
||||
|
||||
static const FuriHalNfcTech nfc_tech_table[NfcModeNum][NfcTechNum] = {
|
||||
[NfcModePoller] =
|
||||
{
|
||||
[NfcTechIso14443a] = FuriHalNfcTechIso14443a,
|
||||
[NfcTechIso14443b] = FuriHalNfcTechIso14443b,
|
||||
[NfcTechIso15693] = FuriHalNfcTechIso15693,
|
||||
[NfcTechFelica] = FuriHalNfcTechFelica,
|
||||
},
|
||||
[NfcModeListener] =
|
||||
{
|
||||
[NfcTechIso14443a] = FuriHalNfcTechIso14443a,
|
||||
[NfcTechIso14443b] = FuriHalNfcTechInvalid,
|
||||
[NfcTechIso15693] = FuriHalNfcTechIso15693,
|
||||
[NfcTechFelica] = FuriHalNfcTechInvalid,
|
||||
},
|
||||
};
|
||||
|
||||
static NfcError nfc_process_hal_error(FuriHalNfcError error) {
|
||||
NfcError ret = NfcErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case FuriHalNfcErrorNone:
|
||||
ret = NfcErrorNone;
|
||||
break;
|
||||
case FuriHalNfcErrorIncompleteFrame:
|
||||
ret = NfcErrorIncompleteFrame;
|
||||
break;
|
||||
case FuriHalNfcErrorDataFormat:
|
||||
ret = NfcErrorDataFormat;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = NfcErrorInternal;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int32_t nfc_worker_listener(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Nfc* instance = context;
|
||||
furi_assert(instance->callback);
|
||||
furi_assert(instance->config_state == NfcConfigurationStateDone);
|
||||
|
||||
instance->state = NfcStateRunning;
|
||||
|
||||
furi_hal_nfc_event_start();
|
||||
|
||||
NfcEventData event_data = {};
|
||||
event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
|
||||
NfcEvent nfc_event = {.data = event_data};
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
while(true) {
|
||||
FuriHalNfcEvent event = furi_hal_nfc_listener_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
if(event & FuriHalNfcEventAbortRequest) {
|
||||
nfc_event.type = NfcEventTypeUserAbort;
|
||||
instance->callback(nfc_event, instance->context);
|
||||
break;
|
||||
}
|
||||
if(event & FuriHalNfcEventFieldOn) {
|
||||
nfc_event.type = NfcEventTypeFieldOn;
|
||||
instance->callback(nfc_event, instance->context);
|
||||
}
|
||||
if(event & FuriHalNfcEventFieldOff) {
|
||||
nfc_event.type = NfcEventTypeFieldOff;
|
||||
instance->callback(nfc_event, instance->context);
|
||||
furi_hal_nfc_listener_idle();
|
||||
}
|
||||
if(event & FuriHalNfcEventListenerActive) {
|
||||
nfc_event.type = NfcEventTypeListenerActivated;
|
||||
instance->callback(nfc_event, instance->context);
|
||||
}
|
||||
if(event & FuriHalNfcEventRxEnd) {
|
||||
furi_hal_nfc_timer_block_tx_start(instance->fdt_listen_fc);
|
||||
|
||||
nfc_event.type = NfcEventTypeRxEnd;
|
||||
furi_hal_nfc_listener_rx(
|
||||
instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits);
|
||||
bit_buffer_copy_bits(event_data.buffer, instance->rx_buffer, instance->rx_bits);
|
||||
command = instance->callback(nfc_event, instance->context);
|
||||
if(command == NfcCommandStop) {
|
||||
break;
|
||||
} else if(command == NfcCommandReset) {
|
||||
furi_hal_nfc_listener_enable_rx();
|
||||
} else if(command == NfcCommandSleep) {
|
||||
furi_hal_nfc_listener_sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_hal_nfc_reset_mode();
|
||||
instance->config_state = NfcConfigurationStateIdle;
|
||||
|
||||
bit_buffer_free(event_data.buffer);
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool nfc_worker_poller_start_handler(Nfc* instance) {
|
||||
furi_hal_nfc_poller_field_on();
|
||||
if(instance->guard_time_us) {
|
||||
furi_hal_nfc_timer_block_tx_start_us(instance->guard_time_us);
|
||||
FuriHalNfcEvent event = furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
furi_assert(event & FuriHalNfcEventTimerBlockTxExpired);
|
||||
}
|
||||
instance->poller_state = NfcPollerStateReady;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nfc_worker_poller_ready_handler(Nfc* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
NfcEvent event = {.type = NfcEventTypePollerReady};
|
||||
command = instance->callback(event, instance->context);
|
||||
if(command == NfcCommandReset) {
|
||||
instance->poller_state = NfcPollerStateReset;
|
||||
} else if(command == NfcCommandStop) {
|
||||
instance->poller_state = NfcPollerStateStop;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nfc_worker_poller_reset_handler(Nfc* instance) {
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
furi_delay_ms(100);
|
||||
furi_hal_nfc_low_power_mode_stop();
|
||||
instance->poller_state = NfcPollerStateStart;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nfc_worker_poller_stop_handler(Nfc* instance) {
|
||||
furi_hal_nfc_reset_mode();
|
||||
instance->config_state = NfcConfigurationStateIdle;
|
||||
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
// Wait after field is off some time to reset tag power
|
||||
furi_delay_ms(10);
|
||||
instance->poller_state = NfcPollerStateStart;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const NfcWorkerPollerStateHandler nfc_worker_poller_state_handlers[NfcPollerStateNum] = {
|
||||
[NfcPollerStateStart] = nfc_worker_poller_start_handler,
|
||||
[NfcPollerStateReady] = nfc_worker_poller_ready_handler,
|
||||
[NfcPollerStateReset] = nfc_worker_poller_reset_handler,
|
||||
[NfcPollerStateStop] = nfc_worker_poller_stop_handler,
|
||||
};
|
||||
|
||||
static int32_t nfc_worker_poller(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
Nfc* instance = context;
|
||||
furi_assert(instance->callback);
|
||||
instance->state = NfcStateRunning;
|
||||
instance->poller_state = NfcPollerStateStart;
|
||||
|
||||
furi_hal_nfc_event_start();
|
||||
|
||||
bool exit = false;
|
||||
while(!exit) {
|
||||
exit = nfc_worker_poller_state_handlers[instance->poller_state](instance);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Nfc* nfc_alloc() {
|
||||
furi_check(furi_hal_nfc_acquire() == FuriHalNfcErrorNone);
|
||||
|
||||
Nfc* instance = malloc(sizeof(Nfc));
|
||||
instance->state = NfcStateIdle;
|
||||
instance->comm_state = NfcCommStateIdle;
|
||||
instance->config_state = NfcConfigurationStateIdle;
|
||||
|
||||
instance->worker_thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->worker_thread, "NfcWorker");
|
||||
furi_thread_set_context(instance->worker_thread, instance);
|
||||
furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHighest);
|
||||
furi_thread_set_stack_size(instance->worker_thread, 8 * 1024);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_free(Nfc* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->state == NfcStateIdle);
|
||||
|
||||
furi_thread_free(instance->worker_thread);
|
||||
free(instance);
|
||||
|
||||
furi_hal_nfc_release();
|
||||
}
|
||||
|
||||
void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) {
|
||||
furi_assert(instance);
|
||||
furi_assert(mode < NfcModeNum);
|
||||
furi_assert(tech < NfcTechNum);
|
||||
furi_assert(instance->config_state == NfcConfigurationStateIdle);
|
||||
|
||||
FuriHalNfcTech hal_tech = nfc_tech_table[mode][tech];
|
||||
if(hal_tech == FuriHalNfcTechInvalid) {
|
||||
furi_crash("Unsupported mode for given tech");
|
||||
}
|
||||
FuriHalNfcMode hal_mode = (mode == NfcModePoller) ? FuriHalNfcModePoller :
|
||||
FuriHalNfcModeListener;
|
||||
furi_hal_nfc_low_power_mode_stop();
|
||||
furi_hal_nfc_set_mode(hal_mode, hal_tech);
|
||||
|
||||
instance->mode = mode;
|
||||
instance->config_state = NfcConfigurationStateDone;
|
||||
}
|
||||
|
||||
void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) {
|
||||
furi_assert(instance);
|
||||
instance->fdt_poll_fc = fdt_poll_fc;
|
||||
}
|
||||
|
||||
void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) {
|
||||
furi_assert(instance);
|
||||
instance->fdt_listen_fc = fdt_listen_fc;
|
||||
}
|
||||
|
||||
void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) {
|
||||
furi_assert(instance);
|
||||
instance->fdt_poll_poll_us = fdt_poll_poll_us;
|
||||
}
|
||||
|
||||
void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) {
|
||||
furi_assert(instance);
|
||||
instance->guard_time_us = guard_time_us;
|
||||
}
|
||||
|
||||
void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) {
|
||||
furi_assert(instance);
|
||||
instance->mask_rx_time_fc = mask_rx_time_fc;
|
||||
}
|
||||
|
||||
void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->worker_thread);
|
||||
furi_assert(callback);
|
||||
furi_assert(instance->config_state == NfcConfigurationStateDone);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
if(instance->mode == NfcModePoller) {
|
||||
furi_thread_set_callback(instance->worker_thread, nfc_worker_poller);
|
||||
} else {
|
||||
furi_thread_set_callback(instance->worker_thread, nfc_worker_listener);
|
||||
}
|
||||
instance->comm_state = NfcCommStateIdle;
|
||||
furi_thread_start(instance->worker_thread);
|
||||
}
|
||||
|
||||
void nfc_stop(Nfc* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->state == NfcStateRunning);
|
||||
|
||||
if(instance->mode == NfcModeListener) {
|
||||
furi_hal_nfc_abort();
|
||||
}
|
||||
furi_thread_join(instance->worker_thread);
|
||||
|
||||
instance->state = NfcStateIdle;
|
||||
}
|
||||
|
||||
NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
NfcError ret = NfcErrorNone;
|
||||
|
||||
while(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
}
|
||||
|
||||
FuriHalNfcError error =
|
||||
furi_hal_nfc_listener_tx(bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer));
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in listener TX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static NfcError nfc_poller_trx_state_machine(Nfc* instance, uint32_t fwt_fc) {
|
||||
FuriHalNfcEvent event = 0;
|
||||
NfcError error = NfcErrorNone;
|
||||
|
||||
while(true) {
|
||||
event = furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
if(event & FuriHalNfcEventTimerBlockTxExpired) {
|
||||
if(instance->comm_state == NfcCommStateWaitBlockTxTimer) {
|
||||
instance->comm_state = NfcCommStateReadyTx;
|
||||
}
|
||||
}
|
||||
if(event & FuriHalNfcEventTxEnd) {
|
||||
if(instance->comm_state == NfcCommStateWaitTxEnd) {
|
||||
if(fwt_fc) {
|
||||
furi_hal_nfc_timer_fwt_start(fwt_fc);
|
||||
}
|
||||
furi_hal_nfc_timer_block_tx_start_us(instance->fdt_poll_poll_us);
|
||||
instance->comm_state = NfcCommStateWaitRxStart;
|
||||
}
|
||||
}
|
||||
if(event & FuriHalNfcEventRxStart) {
|
||||
if(instance->comm_state == NfcCommStateWaitRxStart) {
|
||||
furi_hal_nfc_timer_block_tx_stop();
|
||||
furi_hal_nfc_timer_fwt_stop();
|
||||
instance->comm_state = NfcCommStateWaitRxEnd;
|
||||
}
|
||||
}
|
||||
if(event & FuriHalNfcEventRxEnd) {
|
||||
furi_hal_nfc_timer_block_tx_start(instance->fdt_poll_fc);
|
||||
furi_hal_nfc_timer_fwt_stop();
|
||||
instance->comm_state = NfcCommStateWaitBlockTxTimer;
|
||||
break;
|
||||
}
|
||||
if(event & FuriHalNfcEventTimerFwtExpired) {
|
||||
if(instance->comm_state == NfcCommStateWaitRxStart) {
|
||||
error = NfcErrorTimeout;
|
||||
FURI_LOG_D(TAG, "FWT Timeout");
|
||||
if(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
instance->comm_state = NfcCommStateWaitBlockTxTimer;
|
||||
} else {
|
||||
instance->comm_state = NfcCommStateReadyTx;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_poller_trx_custom_parity(
|
||||
Nfc* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
furi_assert(instance->poller_state == NfcPollerStateReady);
|
||||
|
||||
NfcError ret = NfcErrorNone;
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
do {
|
||||
furi_hal_nfc_trx_reset();
|
||||
while(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
FuriHalNfcEvent event =
|
||||
furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
if(event & FuriHalNfcEventTimerBlockTxExpired) break;
|
||||
}
|
||||
bit_buffer_write_bytes_with_parity(
|
||||
tx_buffer, instance->tx_buffer, sizeof(instance->tx_buffer), &instance->tx_bits);
|
||||
error =
|
||||
furi_hal_nfc_iso14443a_poller_tx_custom_parity(instance->tx_buffer, instance->tx_bits);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller TX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
instance->comm_state = NfcCommStateWaitTxEnd;
|
||||
ret = nfc_poller_trx_state_machine(instance, fwt);
|
||||
if(ret != NfcErrorNone) {
|
||||
FURI_LOG_T(TAG, "Failed TRX state machine");
|
||||
break;
|
||||
}
|
||||
|
||||
error = furi_hal_nfc_poller_rx(
|
||||
instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller RX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
if(instance->rx_bits >= 9) {
|
||||
if((instance->rx_bits % 9) != 0) {
|
||||
ret = NfcErrorDataFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bit_buffer_copy_bytes_with_parity(rx_buffer, instance->rx_buffer, instance->rx_bits);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NfcError
|
||||
nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
furi_assert(instance->poller_state == NfcPollerStateReady);
|
||||
|
||||
NfcError ret = NfcErrorNone;
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
do {
|
||||
furi_hal_nfc_trx_reset();
|
||||
while(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
FuriHalNfcEvent event =
|
||||
furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
if(event & FuriHalNfcEventTimerBlockTxExpired) break;
|
||||
}
|
||||
error =
|
||||
furi_hal_nfc_poller_tx(bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer));
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller TX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
instance->comm_state = NfcCommStateWaitTxEnd;
|
||||
ret = nfc_poller_trx_state_machine(instance, fwt);
|
||||
if(ret != NfcErrorNone) {
|
||||
FURI_LOG_T(TAG, "Failed TRX state machine");
|
||||
break;
|
||||
}
|
||||
|
||||
error = furi_hal_nfc_poller_rx(
|
||||
instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller RX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_listener_set_col_res_data(
|
||||
Nfc* instance,
|
||||
uint8_t* uid,
|
||||
uint8_t uid_len,
|
||||
uint8_t* atqa,
|
||||
uint8_t sak) {
|
||||
furi_assert(instance);
|
||||
|
||||
FuriHalNfcError error =
|
||||
furi_hal_nfc_iso14443a_listener_set_col_res_data(uid, uid_len, atqa, sak);
|
||||
instance->comm_state = NfcCommStateIdle;
|
||||
return nfc_process_hal_error(error);
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_poller_trx_short_frame(
|
||||
Nfc* instance,
|
||||
NfcIso14443aShortFrame frame,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
FuriHalNfcaShortFrame short_frame = (frame == NfcIso14443aShortFrameAllReqa) ?
|
||||
FuriHalNfcaShortFrameAllReq :
|
||||
FuriHalNfcaShortFrameSensReq;
|
||||
|
||||
furi_assert(instance->poller_state == NfcPollerStateReady);
|
||||
|
||||
NfcError ret = NfcErrorNone;
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
do {
|
||||
furi_hal_nfc_trx_reset();
|
||||
while(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
FuriHalNfcEvent event =
|
||||
furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
if(event & FuriHalNfcEventTimerBlockTxExpired) break;
|
||||
}
|
||||
error = furi_hal_nfc_iso14443a_poller_trx_short_frame(short_frame);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller TX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
instance->comm_state = NfcCommStateWaitTxEnd;
|
||||
ret = nfc_poller_trx_state_machine(instance, fwt);
|
||||
if(ret != NfcErrorNone) {
|
||||
FURI_LOG_T(TAG, "Failed TRX state machine");
|
||||
break;
|
||||
}
|
||||
|
||||
error = furi_hal_nfc_poller_rx(
|
||||
instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller RX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_poller_trx_sdd_frame(
|
||||
Nfc* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
furi_assert(rx_buffer);
|
||||
|
||||
furi_assert(instance->poller_state == NfcPollerStateReady);
|
||||
|
||||
NfcError ret = NfcErrorNone;
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
do {
|
||||
furi_hal_nfc_trx_reset();
|
||||
while(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
FuriHalNfcEvent event =
|
||||
furi_hal_nfc_poller_wait_event(FURI_HAL_NFC_EVENT_WAIT_FOREVER);
|
||||
if(event & FuriHalNfcEventTimerBlockTxExpired) break;
|
||||
}
|
||||
error = furi_hal_nfc_iso14443a_tx_sdd_frame(
|
||||
bit_buffer_get_data(tx_buffer), bit_buffer_get_size(tx_buffer));
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller TX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
instance->comm_state = NfcCommStateWaitTxEnd;
|
||||
ret = nfc_poller_trx_state_machine(instance, fwt);
|
||||
if(ret != NfcErrorNone) {
|
||||
FURI_LOG_T(TAG, "Failed TRX state machine");
|
||||
break;
|
||||
}
|
||||
|
||||
error = furi_hal_nfc_poller_rx(
|
||||
instance->rx_buffer, sizeof(instance->rx_buffer), &instance->rx_bits);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed in poller RX");
|
||||
ret = nfc_process_hal_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
bit_buffer_copy_bits(rx_buffer, instance->rx_buffer, instance->rx_bits);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) {
|
||||
furi_assert(instance);
|
||||
furi_assert(tx_buffer);
|
||||
|
||||
NfcError ret = NfcErrorNone;
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
const uint8_t* tx_data = bit_buffer_get_data(tx_buffer);
|
||||
const uint8_t* tx_parity = bit_buffer_get_parity(tx_buffer);
|
||||
size_t tx_bits = bit_buffer_get_size(tx_buffer);
|
||||
|
||||
error = furi_hal_nfc_iso14443a_listener_tx_custom_parity(tx_data, tx_parity, tx_bits);
|
||||
ret = nfc_process_hal_error(error);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
while(furi_hal_nfc_timer_block_tx_is_running()) {
|
||||
}
|
||||
|
||||
FuriHalNfcError error = furi_hal_nfc_iso15693_listener_tx_sof();
|
||||
NfcError ret = nfc_process_hal_error(error);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // APP_UNIT_TESTS
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* @file nfc.h
|
||||
* @brief Transport layer Nfc library.
|
||||
*
|
||||
* The Nfc layer is responsible for setting the operating mode (poller or listener)
|
||||
* and technology (ISO14443-3A/B, ISO15693, ...), data exchange between higher
|
||||
* protocol-specific levels and underlying NFC hardware, as well as timings handling.
|
||||
*
|
||||
* In applications using the NFC protocol system there is no need to neiter explicitly
|
||||
* create an Nfc instance nor call any of its functions, as it is all handled
|
||||
* automatically under the hood.
|
||||
*
|
||||
* If the NFC protocol system is not suitable for the application's intended purpose
|
||||
* or there is need of having direct access to the NFC transport layer, then an Nfc
|
||||
* instance must be allocated and the below functions shall be used to implement
|
||||
* the required logic.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Nfc opaque type definition.
|
||||
*/
|
||||
typedef struct Nfc Nfc;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible Nfc event types.
|
||||
*
|
||||
* Not all technologies implement all events (this is due to hardware limitations).
|
||||
*/
|
||||
typedef enum {
|
||||
NfcEventTypeUserAbort, /**< User code explicitly aborted the current operation. */
|
||||
NfcEventTypeFieldOn, /**< Reader's field was detected by the NFC hardware. */
|
||||
NfcEventTypeFieldOff, /**< Reader's field was lost. */
|
||||
NfcEventTypeTxStart, /**< Data transmission has started. */
|
||||
NfcEventTypeTxEnd, /**< Data transmission has ended. */
|
||||
NfcEventTypeRxStart, /**< Data reception has started. */
|
||||
NfcEventTypeRxEnd, /**< Data reception has ended. */
|
||||
|
||||
NfcEventTypeListenerActivated, /**< The listener has been activated by the reader. */
|
||||
NfcEventTypePollerReady, /**< The card has been activated by the poller. */
|
||||
} NfcEventType;
|
||||
|
||||
/**
|
||||
* @brief Nfc event data structure.
|
||||
*/
|
||||
typedef struct {
|
||||
BitBuffer* buffer; /**< Pointer to the received data buffer. */
|
||||
} NfcEventData;
|
||||
|
||||
/**
|
||||
* @brief Nfc event structure.
|
||||
*
|
||||
* Upon emission of an event, an instance of this struct will be passed to the callback.
|
||||
*/
|
||||
typedef struct {
|
||||
NfcEventType type; /**< Type of the emitted event. */
|
||||
NfcEventData data; /**< Event-specific data. */
|
||||
} NfcEvent;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible Nfc commands.
|
||||
*
|
||||
* The event callback must return one of these to determine the next action.
|
||||
*/
|
||||
typedef enum {
|
||||
NfcCommandContinue, /**< Continue operation normally. */
|
||||
NfcCommandReset, /**< Reset the current state. */
|
||||
NfcCommandStop, /**< Stop the current operation. */
|
||||
NfcCommandSleep, /**< Switch Nfc hardware to low-power mode. */
|
||||
} NfcCommand;
|
||||
|
||||
/**
|
||||
* @brief Nfc event callback type.
|
||||
*
|
||||
* A function of this type must be passed as the callback parameter upon start of a an Nfc instance.
|
||||
*
|
||||
* @param [in] event Nfc event, passed by value, complete with protocol type and data.
|
||||
* @param [in,out] context pointer to the user-specific context (set when starting an Nfc instance).
|
||||
* @returns command which the event producer must execute.
|
||||
*/
|
||||
typedef NfcCommand (*NfcEventCallback)(NfcEvent event, void* context);
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible operating modes.
|
||||
*
|
||||
* Not all technologies implement the listener operating mode.
|
||||
*/
|
||||
typedef enum {
|
||||
NfcModePoller, /**< Configure the Nfc instance as a poller. */
|
||||
NfcModeListener, /**< Configure the Nfc instance as a listener. */
|
||||
|
||||
NfcModeNum, /**< Operating mode count. Internal use. */
|
||||
} NfcMode;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of available technologies.
|
||||
*/
|
||||
typedef enum {
|
||||
NfcTechIso14443a, /**< Configure the Nfc instance to use the ISO14443-3A technology. */
|
||||
NfcTechIso14443b, /**< Configure the Nfc instance to use the ISO14443-3B technology. */
|
||||
NfcTechIso15693, /**< Configure the Nfc instance to use the ISO15693 technology. */
|
||||
NfcTechFelica, /**< Configure the Nfc instance to use the FeliCa technology. */
|
||||
|
||||
NfcTechNum, /**< Technologies count. Internal use. */
|
||||
} NfcTech;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible Nfc error codes.
|
||||
*/
|
||||
typedef enum {
|
||||
NfcErrorNone, /**< No error has occurred. */
|
||||
NfcErrorInternal, /**< An unknown error has occured on the lower level. */
|
||||
NfcErrorTimeout, /**< Operation is taking too long (e.g. card does not respond). */
|
||||
NfcErrorIncompleteFrame, /**< An incomplete data frame has been received. */
|
||||
NfcErrorDataFormat, /**< Data has not been parsed due to wrong/unknown format. */
|
||||
} NfcError;
|
||||
|
||||
/**
|
||||
* @brief Allocate an Nfc instance.
|
||||
*
|
||||
* Will exclusively take over the NFC HAL until deleted.
|
||||
*
|
||||
* @returns pointer to the allocated Nfc instance.
|
||||
*/
|
||||
Nfc* nfc_alloc();
|
||||
|
||||
/**
|
||||
* @brief Delete an Nfc instance.
|
||||
*
|
||||
* Will release the NFC HAL lock, making it available for use by others.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be deleted.
|
||||
*/
|
||||
void nfc_free(Nfc* instance);
|
||||
|
||||
/**
|
||||
* @brief Configure the Nfc instance to work in a particular mode.
|
||||
*
|
||||
* Not all technologies implement the listener operating mode.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be configured.
|
||||
* @param[in] mode required operating mode.
|
||||
* @param[in] tech required technology configuration.
|
||||
*/
|
||||
void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech);
|
||||
|
||||
/**
|
||||
* @brief Set poller frame delay time.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] fdt_poll_fc frame delay time, in carrier cycles.
|
||||
*/
|
||||
void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc);
|
||||
|
||||
/**
|
||||
* @brief Set listener frame delay time.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] fdt_listen_fc frame delay time, in carrier cycles.
|
||||
*/
|
||||
void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc);
|
||||
|
||||
/**
|
||||
* @brief Set mask receive time.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] mask_rx_time mask receive time, in carrier cycles.
|
||||
*/
|
||||
void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc);
|
||||
|
||||
/**
|
||||
* @brief Set frame delay time.
|
||||
*
|
||||
* Frame delay time is the minimum time between two consecutive poll frames.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] fdt_poll_poll_us frame delay time, in microseconds.
|
||||
*/
|
||||
void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us);
|
||||
|
||||
/**
|
||||
* @brief Set guard time.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] guard_time_us guard time, in microseconds.
|
||||
*/
|
||||
void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us);
|
||||
|
||||
/**
|
||||
* @brief Start the Nfc instance.
|
||||
*
|
||||
* The instance must be configured to work with a specific technology
|
||||
* in a specific operating mode with a nfc_config() call before starting.
|
||||
*
|
||||
* Once started, the user code will be receiving events through the provided
|
||||
* callback which must handle them according to the logic required.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be started.
|
||||
* @param[in] callback pointer to a user-defined callback function which will receive events.
|
||||
* @param[in] context pointer to a user-specific context (will be passed to the callback).
|
||||
*/
|
||||
void nfc_start(Nfc* instance, NfcEventCallback callback, void* context);
|
||||
|
||||
/**
|
||||
* @brief Stop Nfc instance.
|
||||
*
|
||||
* The instance can only be stopped if it is running.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be stopped.
|
||||
*/
|
||||
void nfc_stop(Nfc* instance);
|
||||
|
||||
/**
|
||||
* @brief Transmit and receive a data frame in poller mode.
|
||||
*
|
||||
* The rx_buffer will be filled with any data received as a response to data
|
||||
* sent from tx_buffer, with a timeout defined by the fwt parameter.
|
||||
*
|
||||
* The data being transmitted and received may be either bit- or byte-oriented.
|
||||
* It shall not contain any technology-specific sequences as start or stop bits
|
||||
* and/or other special symbols, as this is handled on the underlying HAL level.
|
||||
*
|
||||
* Must ONLY be used inside the callback function.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
|
||||
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
|
||||
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError
|
||||
nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt);
|
||||
|
||||
/**
|
||||
* @brief Transmit a data frame in listener mode.
|
||||
*
|
||||
* Used to transmit a response to the reader request in listener mode.
|
||||
*
|
||||
* The data being transmitted may be either bit- or byte-oriented.
|
||||
* It shall not contain any technology-specific sequences as start or stop bits
|
||||
* and/or other special symbols, as this is handled on the underlying HAL level.
|
||||
*
|
||||
* Must ONLY be used inside the callback function.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer);
|
||||
|
||||
/*
|
||||
* Technology-specific functions.
|
||||
*
|
||||
* In a perfect world, this would not be necessary.
|
||||
* However, the current implementation employs NFC hardware that partially implements
|
||||
* certain protocols (e.g. ISO14443-3A), thus requiring methods to access such features.
|
||||
*/
|
||||
|
||||
/******************* ISO14443-3A specific API *******************/
|
||||
|
||||
/**
|
||||
* @brief Enumeration of possible ISO14443-3A short frame types.
|
||||
*/
|
||||
typedef enum {
|
||||
NfcIso14443aShortFrameSensReq,
|
||||
NfcIso14443aShortFrameAllReqa,
|
||||
} NfcIso14443aShortFrame;
|
||||
|
||||
/**
|
||||
* @brief Transmit an ISO14443-3A short frame and receive the response in poller mode.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] frame type of short frame to be sent.
|
||||
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
|
||||
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_iso14443a_poller_trx_short_frame(
|
||||
Nfc* instance,
|
||||
NfcIso14443aShortFrame frame,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
/**
|
||||
* @brief Transmit an ISO14443-3A SDD frame and receive the response in poller mode.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
|
||||
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
|
||||
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_iso14443a_poller_trx_sdd_frame(
|
||||
Nfc* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
/**
|
||||
* @brief Transmit an ISO14443-3A data frame with custom parity bits and receive the response in poller mode.
|
||||
*
|
||||
* Same as nfc_poller_trx(), but uses the parity bits provided by the user code
|
||||
* instead of calculating them automatically.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
|
||||
* @param[out] rx_buffer pointer to the buffer to be filled with received data.
|
||||
* @param[in] fwt frame wait time (response timeout), in carrier cycles.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_iso14443a_poller_trx_custom_parity(
|
||||
Nfc* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
/**
|
||||
* @brief Transmit an ISO14443-3A frame with custom parity bits in listener mode.
|
||||
*
|
||||
* Same as nfc_listener_tx(), but uses the parity bits provided by the user code
|
||||
* instead of calculating them automatically.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] tx_buffer pointer to the buffer containing the data to be transmitted.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer);
|
||||
|
||||
/**
|
||||
* @brief Set ISO14443-3A collision resolution parameters in listener mode.
|
||||
*
|
||||
* Configures the NFC hardware for automatic collision resolution.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be configured.
|
||||
* @param[in] uid pointer to a byte array containing the UID.
|
||||
* @param[in] uid_len UID length in bytes (must be supported by the protocol).
|
||||
* @param[in] atqa ATQA byte value.
|
||||
* @param[in] sak SAK byte value.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_iso14443a_listener_set_col_res_data(
|
||||
Nfc* instance,
|
||||
uint8_t* uid,
|
||||
uint8_t uid_len,
|
||||
uint8_t* atqa,
|
||||
uint8_t sak);
|
||||
|
||||
/**
|
||||
* @brief Send ISO15693 Start of Frame pattern in listener mode
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be configured.
|
||||
* @returns NfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
NfcError nfc_iso15693_listener_tx_sof(Nfc* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file nfc_common.h
|
||||
* @brief Various common NFC-related macros.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* NFC file format version which changed ATQA format. Deprecated. */
|
||||
#define NFC_LSB_ATQA_FORMAT_VERSION (2)
|
||||
/* NFC file format version which is still supported as backwards compatible. */
|
||||
#define NFC_MINIMUM_SUPPORTED_FORMAT_VERSION NFC_LSB_ATQA_FORMAT_VERSION
|
||||
/* NFC file format version which implemented the unified loading process. */
|
||||
#define NFC_UNIFIED_FORMAT_VERSION (4)
|
||||
/* Current NFC file format version. */
|
||||
#define NFC_CURRENT_FORMAT_VERSION NFC_UNIFIED_FORMAT_VERSION
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+287
-1666
File diff suppressed because it is too large
Load Diff
+236
-97
@@ -1,126 +1,265 @@
|
||||
/**
|
||||
* @file nfc_device.h
|
||||
* @brief Abstract interface for managing NFC device data.
|
||||
*
|
||||
* Under the hood, it makes use of the protocol-specific functions that each one of them provides
|
||||
* and abstracts it with a protocol-independent API.
|
||||
*
|
||||
* It does not perform any signal processing, but merely serves as a container with some handy
|
||||
* operations such as loading and saving from and to a file.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <lib/nfc/helpers/mf_classic_dict.h>
|
||||
#include <lib/nfc/protocols/emv.h>
|
||||
#include <lib/nfc/protocols/mifare_ultralight.h>
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
#include <lib/nfc/protocols/mifare_desfire.h>
|
||||
#include <lib/nfc/protocols/nfcv.h>
|
||||
#include "protocols/nfc_device_base.h"
|
||||
#include "protocols/nfc_protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NFC_DEV_NAME_MAX_LEN 22
|
||||
#define NFC_READER_DATA_MAX_SIZE 64
|
||||
#define NFC_DICT_KEY_BATCH_SIZE 10
|
||||
|
||||
#define NFC_APP_FILENAME_PREFIX "NFC"
|
||||
#define NFC_APP_FILENAME_EXTENSION ".nfc"
|
||||
#define NFC_APP_SHADOW_EXTENSION ".shd"
|
||||
/**
|
||||
* @brief NfcDevice opaque type definition.
|
||||
*/
|
||||
typedef struct NfcDevice NfcDevice;
|
||||
|
||||
/**
|
||||
* @brief Loading callback function signature.
|
||||
*
|
||||
* A function with such signature can be set as a callback to indicate
|
||||
* the completion (or a failure) of nfc_device_load() and nfc_device_save() functions.
|
||||
*
|
||||
* This facility is commonly used to control GUI elements, such as progress dialogs.
|
||||
*
|
||||
* @param[in] context user-defined context that was passed in nfc_device_set_loading_callback().
|
||||
* @param[in] state true if the data was loaded successfully, false otherwise.
|
||||
*/
|
||||
typedef void (*NfcLoadingCallback)(void* context, bool state);
|
||||
|
||||
typedef enum {
|
||||
NfcDeviceProtocolUnknown,
|
||||
NfcDeviceProtocolEMV,
|
||||
NfcDeviceProtocolMifareUl,
|
||||
NfcDeviceProtocolMifareClassic,
|
||||
NfcDeviceProtocolMifareDesfire,
|
||||
NfcDeviceProtocolNfcV
|
||||
} NfcProtocol;
|
||||
|
||||
typedef enum {
|
||||
NfcDeviceSaveFormatUid,
|
||||
NfcDeviceSaveFormatBankCard,
|
||||
NfcDeviceSaveFormatMifareUl,
|
||||
NfcDeviceSaveFormatMifareClassic,
|
||||
NfcDeviceSaveFormatMifareDesfire,
|
||||
NfcDeviceSaveFormatNfcV,
|
||||
} NfcDeviceSaveFormat;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[NFC_READER_DATA_MAX_SIZE];
|
||||
uint16_t size;
|
||||
} NfcReaderRequestData;
|
||||
|
||||
typedef struct {
|
||||
MfClassicDict* dict;
|
||||
uint8_t current_sector;
|
||||
} NfcMfClassicDictAttackData;
|
||||
|
||||
typedef enum {
|
||||
NfcReadModeAuto,
|
||||
NfcReadModeMfClassic,
|
||||
NfcReadModeMfUltralight,
|
||||
NfcReadModeMfDesfire,
|
||||
NfcReadModeNFCA,
|
||||
} NfcReadMode;
|
||||
|
||||
typedef struct {
|
||||
FuriHalNfcDevData nfc_data;
|
||||
NfcProtocol protocol;
|
||||
NfcReadMode read_mode;
|
||||
union {
|
||||
NfcReaderRequestData reader_data;
|
||||
NfcMfClassicDictAttackData mf_classic_dict_attack_data;
|
||||
MfUltralightAuth mf_ul_auth;
|
||||
};
|
||||
union {
|
||||
EmvData emv_data;
|
||||
MfUltralightData mf_ul_data;
|
||||
MfClassicData mf_classic_data;
|
||||
MifareDesfireData mf_df_data;
|
||||
NfcVData nfcv_data;
|
||||
};
|
||||
FuriString* parsed_data;
|
||||
} NfcDeviceData;
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
DialogsApp* dialogs;
|
||||
NfcDeviceData dev_data;
|
||||
char dev_name[NFC_DEV_NAME_MAX_LEN + 1];
|
||||
FuriString* load_path;
|
||||
FuriString* folder;
|
||||
NfcDeviceSaveFormat format;
|
||||
bool shadow_file_exist;
|
||||
|
||||
NfcLoadingCallback loading_cb;
|
||||
void* loading_cb_ctx;
|
||||
} NfcDevice;
|
||||
|
||||
/**
|
||||
* @brief Allocate an NfcDevice instance.
|
||||
*
|
||||
* A newly created instance does not hold any data and thus is considered invalid. The most common
|
||||
* use case would be to set its data by calling nfc_device_set_data() right afterwards.
|
||||
*
|
||||
* @returns pointer to the allocated instance.
|
||||
*/
|
||||
NfcDevice* nfc_device_alloc();
|
||||
|
||||
void nfc_device_free(NfcDevice* nfc_dev);
|
||||
/**
|
||||
* @brief Delete an NfcDevice instance.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be deleted.
|
||||
*/
|
||||
void nfc_device_free(NfcDevice* instance);
|
||||
|
||||
void nfc_device_set_name(NfcDevice* dev, const char* name);
|
||||
/**
|
||||
* @brief Clear an NfcDevice instance.
|
||||
*
|
||||
* All data contained in the instance will be deleted and the instance itself will become invalid
|
||||
* as if it was just allocated.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be cleared.
|
||||
*/
|
||||
void nfc_device_clear(NfcDevice* instance);
|
||||
|
||||
bool nfc_device_save(NfcDevice* dev, const char* dev_name);
|
||||
/**
|
||||
* @brief Reset an NfcDevice instance.
|
||||
*
|
||||
* The data contained in the instance will be reset according to the protocol-defined procedure.
|
||||
* Unlike the nfc_device_clear() function, the instance will remain valid.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be reset.
|
||||
*/
|
||||
void nfc_device_reset(NfcDevice* instance);
|
||||
|
||||
bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name);
|
||||
/**
|
||||
* @brief Get the protocol identifier from an NfcDevice instance.
|
||||
*
|
||||
* If the instance is invalid, the return value will be NfcProtocolInvalid.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @returns protocol identifier contained in the instance.
|
||||
*/
|
||||
NfcProtocol nfc_device_get_protocol(const NfcDevice* instance);
|
||||
|
||||
bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog);
|
||||
/**
|
||||
* @brief Get the protocol-specific data from an NfcDevice instance.
|
||||
*
|
||||
* The protocol parameter's behaviour is a bit tricky. The function will check
|
||||
* whether there is such a protocol somewhere in the protocol hierarchy and return
|
||||
* the data exactly from that level.
|
||||
*
|
||||
* Example: Call nfc_device_get_data() on an instance with Mf DESFire protocol.
|
||||
* The protocol hierarchy will look like the following:
|
||||
*
|
||||
* `Mf DESFire --> ISO14443-4A --> ISO14443-3A`
|
||||
*
|
||||
* Thus, the following values of the protocol parameter are valid:
|
||||
*
|
||||
* * NfcProtocolIso14443_3a
|
||||
* * NfcProtocolIso14443_4a
|
||||
* * NfcProtocolMfDesfire
|
||||
*
|
||||
* and passing them to the call would result in the respective data being returned.
|
||||
*
|
||||
* However, supplying a protocol identifier which is not in the hierarchy will
|
||||
* result in a crash. This is to improve type safety.
|
||||
*
|
||||
* @param instance pointer to the instance to be queried
|
||||
* @param protocol protocol identifier of the data to be retrieved.
|
||||
* @returns pointer to the instance's data.
|
||||
*/
|
||||
const NfcDeviceData* nfc_device_get_data(const NfcDevice* instance, NfcProtocol protocol);
|
||||
|
||||
bool nfc_device_load_key_cache(NfcDevice* dev);
|
||||
/**
|
||||
* @brief Get the protocol name by its identifier.
|
||||
*
|
||||
* This function does not require an instance as its return result depends only
|
||||
* the protocol identifier.
|
||||
*
|
||||
* @param[in] protocol numeric identifier of the protocol in question.
|
||||
* @returns pointer to a statically allocated string containing the protocol name.
|
||||
*/
|
||||
const char* nfc_device_get_protocol_name(NfcProtocol protocol);
|
||||
|
||||
bool nfc_file_select(NfcDevice* dev);
|
||||
/**
|
||||
* @brief Get the name of an NfcDevice instance.
|
||||
*
|
||||
* The return value may change depending on the instance's internal state and the name_type parameter.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @param[in] name_type type of the name to be displayed.
|
||||
* @returns pointer to a statically allocated string containing the device name.
|
||||
*/
|
||||
const char* nfc_device_get_name(const NfcDevice* instance, NfcDeviceNameType name_type);
|
||||
|
||||
void nfc_device_data_clear(NfcDeviceData* dev);
|
||||
/**
|
||||
* @brief Get the unique identifier (UID) of an NfcDevice instance.
|
||||
*
|
||||
* The UID length is protocol-dependent. Additionally, a particular protocol might support
|
||||
* several UID lengths.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @param[out] uid_len pointer to the variable to contain the UID length.
|
||||
* @returns pointer to the byte array containing the instance's UID.
|
||||
*/
|
||||
const uint8_t* nfc_device_get_uid(const NfcDevice* instance, size_t* uid_len);
|
||||
|
||||
void nfc_device_clear(NfcDevice* dev);
|
||||
/**
|
||||
* @brief Set the unique identifier (UID) of an NfcDevice instance.
|
||||
*
|
||||
* The UID length must be supported by the instance's protocol.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] uid pointer to the byte array containing the new UID.
|
||||
* @param[in] uid_len length of the UID.
|
||||
* @return true if the UID was valid and set, false otherwise.
|
||||
*/
|
||||
bool nfc_device_set_uid(NfcDevice* instance, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
bool nfc_device_delete(NfcDevice* dev, bool use_load_path);
|
||||
/**
|
||||
* @brief Set the data and protocol of an NfcDevice instance.
|
||||
*
|
||||
* Any data previously contained in the instance will be deleted.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] protocol numeric identifier of the data's protocol.
|
||||
* @param[in] protocol_data pointer to the protocol-specific data.
|
||||
*/
|
||||
void nfc_device_set_data(
|
||||
NfcDevice* instance,
|
||||
NfcProtocol protocol,
|
||||
const NfcDeviceData* protocol_data);
|
||||
|
||||
bool nfc_device_restore(NfcDevice* dev, bool use_load_path);
|
||||
/**
|
||||
* @brief Copy (export) the data contained in an NfcDevice instance to an outside NfcDeviceData instance.
|
||||
*
|
||||
* This function does the inverse of nfc_device_set_data().
|
||||
|
||||
void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context);
|
||||
* The protocol identifier passed as the protocol parameter MUST match the one
|
||||
* stored in the instance, otherwise a crash will occur.
|
||||
* This is to improve type safety.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be copied from.
|
||||
* @param[in] protocol numeric identifier of the instance's protocol.
|
||||
* @param[out] protocol_data pointer to the destination data.
|
||||
*/
|
||||
void nfc_device_copy_data(
|
||||
const NfcDevice* instance,
|
||||
NfcProtocol protocol,
|
||||
NfcDeviceData* protocol_data);
|
||||
|
||||
/**
|
||||
* @brief Check whether an NfcDevice instance holds certain data.
|
||||
*
|
||||
* This function's behaviour is similar to nfc_device_is_equal(), with the difference
|
||||
* that it takes NfcProtocol and NfcDeviceData* instead of the second NfcDevice*.
|
||||
*
|
||||
* The following code snippets [1] and [2] are equivalent:
|
||||
*
|
||||
* [1]
|
||||
* ```c
|
||||
* bool is_equal = nfc_device_is_equal(device1, device2);
|
||||
* ```
|
||||
* [2]
|
||||
* ```c
|
||||
* NfcProtocol protocol = nfc_device_get_protocol(device2);
|
||||
* const NfcDeviceData* data = nfc_device_get_data(device2, protocol);
|
||||
* bool is_equal = nfc_device_is_equal_data(device1, protocol, data);
|
||||
* ```
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be compared.
|
||||
* @param[in] protocol protocol identifier of the data to be compared.
|
||||
* @param[in] protocol_data pointer to the NFC device data to be compared.
|
||||
* @returns true if the instance is of the right type and the data matches, false otherwise.
|
||||
*/
|
||||
bool nfc_device_is_equal_data(
|
||||
const NfcDevice* instance,
|
||||
NfcProtocol protocol,
|
||||
const NfcDeviceData* protocol_data);
|
||||
|
||||
/**
|
||||
* @brief Compare two NfcDevice instances to determine whether they are equal.
|
||||
*
|
||||
* @param[in] instance pointer to the first instance to be compared.
|
||||
* @param[in] other pointer to the second instance to be compared.
|
||||
* @returns true if both instances are considered equal, false otherwise.
|
||||
*/
|
||||
bool nfc_device_is_equal(const NfcDevice* instance, const NfcDevice* other);
|
||||
|
||||
/**
|
||||
* @brief Set the loading callback function.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be modified.
|
||||
* @param[in] callback pointer to a function to be called when the load operation completes.
|
||||
* @param[in] context pointer to a user-specific context (will be passed to the callback).
|
||||
*/
|
||||
void nfc_device_set_loading_callback(
|
||||
NfcDevice* instance,
|
||||
NfcLoadingCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* @brief Save NFC device data form an NfcDevice instance to a file.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be saved.
|
||||
* @param[in] path pointer to a character string with a full file path.
|
||||
* @returns true if the data was successfully saved, false otherwise.
|
||||
*/
|
||||
bool nfc_device_save(NfcDevice* instance, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Load NFC device data to an NfcDevice instance from a file.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be loaded into.
|
||||
* @param[in] path pointer to a character string with a full file path.
|
||||
* @returns true if the data was successfully loaded, false otherwise.
|
||||
*/
|
||||
bool nfc_device_load(NfcDevice* instance, const char* path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "nfc_device_i.h"
|
||||
#include "protocols/nfc_device_defs.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
static NfcDeviceData*
|
||||
nfc_device_search_base_protocol_data(const NfcDevice* instance, NfcProtocol protocol) {
|
||||
NfcProtocol protocol_tmp = instance->protocol;
|
||||
NfcDeviceData* dev_data_tmp = instance->protocol_data;
|
||||
|
||||
while(true) {
|
||||
dev_data_tmp = nfc_devices[protocol_tmp]->get_base_data(dev_data_tmp);
|
||||
protocol_tmp = nfc_protocol_get_parent(protocol_tmp);
|
||||
if(protocol_tmp == protocol) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dev_data_tmp;
|
||||
}
|
||||
|
||||
NfcDeviceData* nfc_device_get_data_ptr(const NfcDevice* instance, NfcProtocol protocol) {
|
||||
furi_assert(instance);
|
||||
furi_assert(protocol < NfcProtocolNum);
|
||||
|
||||
NfcDeviceData* dev_data = NULL;
|
||||
|
||||
if(instance->protocol == protocol) {
|
||||
dev_data = instance->protocol_data;
|
||||
} else if(nfc_protocol_has_parent(instance->protocol, protocol)) {
|
||||
dev_data = nfc_device_search_base_protocol_data(instance, protocol);
|
||||
} else {
|
||||
furi_crash("Incorrect protocol");
|
||||
}
|
||||
|
||||
return dev_data;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file nfc_device_i.h
|
||||
* @brief NfcDevice private types and definitions.
|
||||
*
|
||||
* This file is an implementation detail. It must not be included in
|
||||
* any public API-related headers.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "nfc_device.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief NfcDevice structure definition.
|
||||
*/
|
||||
struct NfcDevice {
|
||||
NfcProtocol protocol; /**< Numeric identifier of the data's protocol*/
|
||||
NfcDeviceData* protocol_data; /**< Pointer to the NFC device data. */
|
||||
|
||||
NfcLoadingCallback
|
||||
loading_callback; /**< Pointer to the function to be called upon loading completion. */
|
||||
void* loading_callback_context; /**< Pointer to the context to be passed to the loading callback. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the mutable (non-const) data from an NfcDevice instance.
|
||||
*
|
||||
* The behaviour is the same as with nfc_device_get_data(), but the
|
||||
* return pointer is non-const, allowing for changing data it is pointing to.
|
||||
*
|
||||
* @see nfc_device.h
|
||||
*
|
||||
* Under the hood, nfc_device_get_data() calls this and then adds const-ness to the return value.
|
||||
*
|
||||
* @param instance pointer to the instance to be queried
|
||||
* @param protocol protocol identifier of the data to be retrieved.
|
||||
* @returns pointer to the instance's (mutable) data.
|
||||
*/
|
||||
NfcDeviceData* nfc_device_get_data_ptr(const NfcDevice* instance, NfcProtocol protocol);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "nfc_listener.h"
|
||||
|
||||
#include <nfc/protocols/nfc_listener_defs.h>
|
||||
#include <nfc/nfc_device_i.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
typedef struct NfcListenerListElement {
|
||||
NfcProtocol protocol;
|
||||
NfcGenericInstance* listener;
|
||||
const NfcListenerBase* listener_api;
|
||||
struct NfcListenerListElement* child;
|
||||
} NfcListenerListElement;
|
||||
|
||||
typedef struct {
|
||||
NfcListenerListElement* head;
|
||||
NfcListenerListElement* tail;
|
||||
} NfcListenerList;
|
||||
|
||||
struct NfcListener {
|
||||
NfcProtocol protocol;
|
||||
Nfc* nfc;
|
||||
NfcListenerList list;
|
||||
NfcDevice* nfc_dev;
|
||||
};
|
||||
|
||||
static void nfc_listener_list_alloc(NfcListener* instance) {
|
||||
instance->list.head = malloc(sizeof(NfcListenerListElement));
|
||||
instance->list.head->protocol = instance->protocol;
|
||||
|
||||
instance->list.head->listener_api = nfc_listeners_api[instance->protocol];
|
||||
instance->list.head->child = NULL;
|
||||
instance->list.tail = instance->list.head;
|
||||
|
||||
// Build linked list
|
||||
do {
|
||||
NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->list.head->protocol);
|
||||
if(parent_protocol == NfcProtocolInvalid) break;
|
||||
|
||||
NfcListenerListElement* parent = malloc(sizeof(NfcListenerListElement));
|
||||
parent->protocol = parent_protocol;
|
||||
parent->listener_api = nfc_listeners_api[parent_protocol];
|
||||
parent->child = instance->list.head;
|
||||
|
||||
instance->list.head = parent;
|
||||
} while(true);
|
||||
|
||||
// Allocate listener instances
|
||||
NfcListenerListElement* iter = instance->list.head;
|
||||
NfcDeviceData* data_tmp = nfc_device_get_data_ptr(instance->nfc_dev, iter->protocol);
|
||||
iter->listener = iter->listener_api->alloc(instance->nfc, data_tmp);
|
||||
|
||||
do {
|
||||
if(iter->child == NULL) break;
|
||||
data_tmp = nfc_device_get_data_ptr(instance->nfc_dev, iter->child->protocol);
|
||||
iter->child->listener = iter->child->listener_api->alloc(iter->listener, data_tmp);
|
||||
iter->listener_api->set_callback(
|
||||
iter->listener, iter->child->listener_api->run, iter->child->listener);
|
||||
|
||||
iter = iter->child;
|
||||
} while(true);
|
||||
}
|
||||
|
||||
static void nfc_listener_list_free(NfcListener* instance) {
|
||||
// Free listener instances
|
||||
do {
|
||||
instance->list.head->listener_api->free(instance->list.head->listener);
|
||||
NfcListenerListElement* child = instance->list.head->child;
|
||||
free(instance->list.head);
|
||||
if(child == NULL) break;
|
||||
instance->list.head = child;
|
||||
} while(true);
|
||||
}
|
||||
|
||||
NfcListener* nfc_listener_alloc(Nfc* nfc, NfcProtocol protocol, const NfcDeviceData* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(protocol < NfcProtocolNum);
|
||||
furi_assert(data);
|
||||
furi_assert(nfc_listeners_api[protocol]);
|
||||
|
||||
NfcListener* instance = malloc(sizeof(NfcListener));
|
||||
instance->nfc = nfc;
|
||||
instance->protocol = protocol;
|
||||
instance->nfc_dev = nfc_device_alloc();
|
||||
nfc_device_set_data(instance->nfc_dev, protocol, data);
|
||||
nfc_listener_list_alloc(instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_listener_free(NfcListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
nfc_listener_list_free(instance);
|
||||
nfc_device_free(instance->nfc_dev);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
NfcCommand nfc_listener_start_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcListener* instance = context;
|
||||
furi_assert(instance->list.head);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
NfcGenericEvent generic_event = {
|
||||
.protocol = NfcProtocolInvalid,
|
||||
.instance = instance->nfc,
|
||||
.event_data = &event,
|
||||
};
|
||||
|
||||
NfcListenerListElement* head_listener = instance->list.head;
|
||||
command = head_listener->listener_api->run(generic_event, head_listener->listener);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void nfc_listener_start(NfcListener* instance, NfcGenericCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
|
||||
NfcListenerListElement* tail_element = instance->list.tail;
|
||||
tail_element->listener_api->set_callback(tail_element->listener, callback, context);
|
||||
nfc_start(instance->nfc, nfc_listener_start_callback, instance);
|
||||
}
|
||||
|
||||
void nfc_listener_stop(NfcListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
nfc_stop(instance->nfc);
|
||||
}
|
||||
|
||||
NfcProtocol nfc_listener_get_protocol(const NfcListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->protocol;
|
||||
}
|
||||
|
||||
const NfcDeviceData* nfc_listener_get_data(const NfcListener* instance, NfcProtocol protocol) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->protocol == protocol);
|
||||
|
||||
NfcListenerListElement* tail_element = instance->list.tail;
|
||||
return tail_element->listener_api->get_data(tail_element->listener);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @file nfc_listener.h
|
||||
* @brief NFC card emulation library.
|
||||
*
|
||||
* Once started, it will respond to supported commands from an NFC reader, thus imitating
|
||||
* (or emulating) an NFC card. The responses will depend on the data that was supplied to
|
||||
* the listener, so various card types and different cards of the same type can be emulated.
|
||||
*
|
||||
* It will also make any changes necessary to the emulated data in response to the
|
||||
* reader commands if the protocol supports it.
|
||||
*
|
||||
* When running, NfcListener will generate events that the calling code must handle
|
||||
* by providing a callback function. The events passed to the callback are protocol-specific
|
||||
* and may include errors, state changes, data reception, special function requests and more.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
#include <nfc/protocols/nfc_device_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief NfcListener opaque type definition.
|
||||
*/
|
||||
typedef struct NfcListener NfcListener;
|
||||
|
||||
/**
|
||||
* @brief Allocate an NfcListener instance.
|
||||
*
|
||||
* @param[in] nfc pointer to an Nfc instance.
|
||||
* @param[in] protocol identifier of the protocol to be used.
|
||||
* @param[in] data pointer to the data to use during emulation.
|
||||
* @returns pointer to an allocated instance.
|
||||
*
|
||||
* @see nfc.h
|
||||
*/
|
||||
NfcListener* nfc_listener_alloc(Nfc* nfc, NfcProtocol protocol, const NfcDeviceData* data);
|
||||
|
||||
/**
|
||||
* @brief Delete an NfcListener instance.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be deleted.
|
||||
*/
|
||||
void nfc_listener_free(NfcListener* instance);
|
||||
|
||||
/**
|
||||
* @brief Start an NfcListener instance.
|
||||
*
|
||||
* The callback logic is protocol-specific, so it cannot be described here in detail.
|
||||
* However, the callback return value ALWAYS determines what the listener should do next:
|
||||
* to continue whatever it was doing prior to the callback run or to stop.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be started.
|
||||
* @param[in] callback pointer to a user-defined callback function which will receive events.
|
||||
* @param[in] context pointer to a user-specific context (will be passed to the callback).
|
||||
*/
|
||||
void nfc_listener_start(NfcListener* instance, NfcGenericCallback callback, void* context);
|
||||
|
||||
/**
|
||||
* @brief Stop an NfcListener instance.
|
||||
*
|
||||
* The emulation process can be stopped explicitly (the other way is via the callback return value).
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be stopped.
|
||||
*/
|
||||
void nfc_listener_stop(NfcListener* instance);
|
||||
|
||||
/**
|
||||
* @brief Get the protocol identifier an NfcListener instance was created with.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @returns identifier of the protocol used by the instance.
|
||||
*/
|
||||
NfcProtocol nfc_listener_get_protocol(const NfcListener* instance);
|
||||
|
||||
/**
|
||||
* @brief Get the data that was that was provided for emulation.
|
||||
*
|
||||
* The protocol identifier passed as the protocol parameter MUST match the one
|
||||
* stored in the instance, otherwise a crash will occur.
|
||||
* This is to improve type safety.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @param[in] protocol assumed protocol identifier of the data to be retrieved.
|
||||
* @returns pointer to the NFC device data.
|
||||
*/
|
||||
const NfcDeviceData* nfc_listener_get_data(const NfcListener* instance, NfcProtocol protocol);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,211 @@
|
||||
#include "nfc_poller.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_defs.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
typedef enum {
|
||||
NfcPollerSessionStateIdle,
|
||||
NfcPollerSessionStateActive,
|
||||
NfcPollerSessionStateStopRequest,
|
||||
} NfcPollerSessionState;
|
||||
|
||||
typedef struct NfcPollerListElement {
|
||||
NfcProtocol protocol;
|
||||
NfcGenericInstance* poller;
|
||||
const NfcPollerBase* poller_api;
|
||||
struct NfcPollerListElement* child;
|
||||
} NfcPollerListElement;
|
||||
|
||||
typedef struct {
|
||||
NfcPollerListElement* head;
|
||||
NfcPollerListElement* tail;
|
||||
} NfcPollerList;
|
||||
|
||||
struct NfcPoller {
|
||||
NfcProtocol protocol;
|
||||
Nfc* nfc;
|
||||
NfcPollerList list;
|
||||
NfcPollerSessionState session_state;
|
||||
bool protocol_detected;
|
||||
};
|
||||
|
||||
static void nfc_poller_list_alloc(NfcPoller* instance) {
|
||||
instance->list.head = malloc(sizeof(NfcPollerListElement));
|
||||
instance->list.head->protocol = instance->protocol;
|
||||
instance->list.head->poller_api = nfc_pollers_api[instance->protocol];
|
||||
instance->list.head->child = NULL;
|
||||
instance->list.tail = instance->list.head;
|
||||
|
||||
do {
|
||||
NfcProtocol parent_protocol = nfc_protocol_get_parent(instance->list.head->protocol);
|
||||
if(parent_protocol == NfcProtocolInvalid) break;
|
||||
|
||||
NfcPollerListElement* parent = malloc(sizeof(NfcPollerListElement));
|
||||
parent->protocol = parent_protocol;
|
||||
parent->poller_api = nfc_pollers_api[parent_protocol];
|
||||
parent->child = instance->list.head;
|
||||
instance->list.head = parent;
|
||||
} while(true);
|
||||
|
||||
NfcPollerListElement* iter = instance->list.head;
|
||||
iter->poller = iter->poller_api->alloc(instance->nfc);
|
||||
|
||||
do {
|
||||
if(iter->child == NULL) break;
|
||||
iter->child->poller = iter->child->poller_api->alloc(iter->poller);
|
||||
iter->poller_api->set_callback(
|
||||
iter->poller, iter->child->poller_api->run, iter->child->poller);
|
||||
|
||||
iter = iter->child;
|
||||
} while(true);
|
||||
}
|
||||
|
||||
static void nfc_poller_list_free(NfcPoller* instance) {
|
||||
do {
|
||||
instance->list.head->poller_api->free(instance->list.head->poller);
|
||||
NfcPollerListElement* child = instance->list.head->child;
|
||||
free(instance->list.head);
|
||||
if(child == NULL) break;
|
||||
instance->list.head = child;
|
||||
} while(true);
|
||||
}
|
||||
|
||||
NfcPoller* nfc_poller_alloc(Nfc* nfc, NfcProtocol protocol) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(protocol < NfcProtocolNum);
|
||||
|
||||
NfcPoller* instance = malloc(sizeof(NfcPoller));
|
||||
instance->session_state = NfcPollerSessionStateIdle;
|
||||
instance->nfc = nfc;
|
||||
instance->protocol = protocol;
|
||||
nfc_poller_list_alloc(instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_poller_free(NfcPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
nfc_poller_list_free(instance);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static NfcCommand nfc_poller_start_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcPoller* instance = context;
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
NfcGenericEvent poller_event = {
|
||||
.protocol = NfcProtocolInvalid,
|
||||
.instance = instance->nfc,
|
||||
.event_data = &event,
|
||||
};
|
||||
|
||||
if(event.type == NfcEventTypePollerReady) {
|
||||
NfcPollerListElement* head_poller = instance->list.head;
|
||||
command = head_poller->poller_api->run(poller_event, head_poller->poller);
|
||||
}
|
||||
|
||||
if(instance->session_state == NfcPollerSessionStateStopRequest) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
furi_assert(instance->session_state == NfcPollerSessionStateIdle);
|
||||
|
||||
NfcPollerListElement* tail_poller = instance->list.tail;
|
||||
tail_poller->poller_api->set_callback(tail_poller->poller, callback, context);
|
||||
|
||||
instance->session_state = NfcPollerSessionStateActive;
|
||||
nfc_start(instance->nfc, nfc_poller_start_callback, instance);
|
||||
}
|
||||
|
||||
void nfc_poller_stop(NfcPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->nfc);
|
||||
|
||||
instance->session_state = NfcPollerSessionStateStopRequest;
|
||||
nfc_stop(instance->nfc);
|
||||
instance->session_state = NfcPollerSessionStateIdle;
|
||||
}
|
||||
|
||||
static NfcCommand nfc_poller_detect_tail_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcPoller* instance = context;
|
||||
NfcPollerListElement* tail_poller = instance->list.tail;
|
||||
instance->protocol_detected = tail_poller->poller_api->detect(event, tail_poller->poller);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
static NfcCommand nfc_poller_detect_head_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcPoller* instance = context;
|
||||
NfcPollerListElement* tail_poller = instance->list.tail;
|
||||
NfcPollerListElement* head_poller = instance->list.head;
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
NfcGenericEvent poller_event = {
|
||||
.protocol = NfcProtocolInvalid,
|
||||
.instance = instance->nfc,
|
||||
.event_data = &event,
|
||||
};
|
||||
|
||||
if(event.type == NfcEventTypePollerReady) {
|
||||
if(tail_poller == head_poller) {
|
||||
instance->protocol_detected =
|
||||
tail_poller->poller_api->detect(poller_event, tail_poller->poller);
|
||||
command = NfcCommandStop;
|
||||
} else {
|
||||
command = head_poller->poller_api->run(poller_event, head_poller->poller);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
bool nfc_poller_detect(NfcPoller* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->session_state == NfcPollerSessionStateIdle);
|
||||
|
||||
instance->session_state = NfcPollerSessionStateActive;
|
||||
NfcPollerListElement* tail_poller = instance->list.tail;
|
||||
NfcPollerListElement* iter = instance->list.head;
|
||||
|
||||
if(tail_poller != instance->list.head) {
|
||||
while(iter->child != tail_poller) iter = iter->child;
|
||||
iter->poller_api->set_callback(iter->poller, nfc_poller_detect_tail_callback, instance);
|
||||
}
|
||||
|
||||
nfc_start(instance->nfc, nfc_poller_detect_head_callback, instance);
|
||||
nfc_stop(instance->nfc);
|
||||
|
||||
if(tail_poller != instance->list.head) {
|
||||
iter->poller_api->set_callback(
|
||||
iter->poller, tail_poller->poller_api->run, tail_poller->poller);
|
||||
}
|
||||
|
||||
return instance->protocol_detected;
|
||||
}
|
||||
|
||||
NfcProtocol nfc_poller_get_protocol(const NfcPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->protocol;
|
||||
}
|
||||
|
||||
const NfcDeviceData* nfc_poller_get_data(const NfcPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
NfcPollerListElement* tail_poller = instance->list.tail;
|
||||
return tail_poller->poller_api->get_data(tail_poller->poller);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @file nfc_poller.h
|
||||
* @brief NFC card reading library.
|
||||
*
|
||||
* Once started, it will try to activate and read a card using the designated protocol,
|
||||
* which is usually obtained by creating and starting an NfcScanner first.
|
||||
*
|
||||
* @see nfc_scanner.h
|
||||
*
|
||||
* When running, NfcPoller will generate events that the calling code must handle
|
||||
* by providing a callback function. The events passed to the callback are protocol-specific
|
||||
* and may include errors, state changes, data reception, special function requests and more.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_generic_event.h>
|
||||
#include <nfc/protocols/nfc_device_base.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief NfcPoller opaque type definition.
|
||||
*/
|
||||
typedef struct NfcPoller NfcPoller;
|
||||
|
||||
/**
|
||||
* @brief Allocate an NfcPoller instance.
|
||||
*
|
||||
* @param[in] nfc pointer to an Nfc instance.
|
||||
* @param[in] protocol identifier of the protocol to be used.
|
||||
* @returns pointer to an allocated instance.
|
||||
*
|
||||
* @see nfc.h
|
||||
*/
|
||||
NfcPoller* nfc_poller_alloc(Nfc* nfc, NfcProtocol protocol);
|
||||
|
||||
/**
|
||||
* @brief Delete an NfcPoller instance.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be deleted.
|
||||
*/
|
||||
void nfc_poller_free(NfcPoller* instance);
|
||||
|
||||
/**
|
||||
* @brief Start an NfcPoller instance.
|
||||
*
|
||||
* The callback logic is protocol-specific, so it cannot be described here in detail.
|
||||
* However, the callback return value ALWAYS determines what the poller should do next:
|
||||
* to continue whatever it was doing prior to the callback run or to stop.
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be started.
|
||||
* @param[in] callback pointer to a user-defined callback function which will receive events.
|
||||
* @param[in] context pointer to a user-specific context (will be passed to the callback).
|
||||
*/
|
||||
void nfc_poller_start(NfcPoller* instance, NfcGenericCallback callback, void* context);
|
||||
|
||||
/**
|
||||
* @brief Stop an NfcPoller instance.
|
||||
*
|
||||
* The reading process can be stopped explicitly (the other way is via the callback return value).
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to be stopped.
|
||||
*/
|
||||
void nfc_poller_stop(NfcPoller* instance);
|
||||
|
||||
/**
|
||||
* @brief Detect whether there is a card supporting a particular protocol in the vicinity.
|
||||
*
|
||||
* The behaviour of this function is protocol-defined, in general, it will do whatever is
|
||||
* necessary to determine whether a card supporting the current protocol is in the vicinity
|
||||
* and whether it is functioning normally.
|
||||
*
|
||||
* It is used automatically inside NfcScanner, so there is usually no need
|
||||
* to call it explicitly.
|
||||
*
|
||||
* @see nfc_scanner.h
|
||||
*
|
||||
* @param[in,out] instance pointer to the instance to perform the detection with.
|
||||
* @returns true if a supported card was detected, false otherwise.
|
||||
*/
|
||||
bool nfc_poller_detect(NfcPoller* instance);
|
||||
|
||||
/**
|
||||
* @brief Get the protocol identifier an NfcPoller instance was created with.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @returns identifier of the protocol used by the instance.
|
||||
*/
|
||||
NfcProtocol nfc_poller_get_protocol(const NfcPoller* instance);
|
||||
|
||||
/**
|
||||
* @brief Get the data that was that was gathered during the reading process.
|
||||
*
|
||||
* @param[in] instance pointer to the instance to be queried.
|
||||
* @returns pointer to the NFC device data.
|
||||
*/
|
||||
const NfcDeviceData* nfc_poller_get_data(const NfcPoller* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,267 @@
|
||||
#include "nfc_scanner.h"
|
||||
#include "nfc_poller.h"
|
||||
|
||||
#include <nfc/protocols/nfc_poller_defs.h>
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define TAG "NfcScanner"
|
||||
|
||||
typedef enum {
|
||||
NfcScannerStateIdle,
|
||||
NfcScannerStateTryBasePollers,
|
||||
NfcScannerStateFindChildrenProtocols,
|
||||
NfcScannerStateDetectChildrenProtocols,
|
||||
NfcScannerStateComplete,
|
||||
|
||||
NfcScannerStateNum,
|
||||
} NfcScannerState;
|
||||
|
||||
typedef enum {
|
||||
NfcScannerSessionStateIdle,
|
||||
NfcScannerSessionStateActive,
|
||||
NfcScannerSessionStateStopRequest,
|
||||
} NfcScannerSessionState;
|
||||
|
||||
struct NfcScanner {
|
||||
Nfc* nfc;
|
||||
NfcScannerState state;
|
||||
NfcScannerSessionState session_state;
|
||||
|
||||
NfcScannerCallback callback;
|
||||
void* context;
|
||||
|
||||
NfcEvent nfc_event;
|
||||
|
||||
NfcProtocol first_detected_protocol;
|
||||
|
||||
size_t base_protocols_num;
|
||||
size_t base_protocols_idx;
|
||||
NfcProtocol base_protocols[NfcProtocolNum];
|
||||
|
||||
size_t detected_base_protocols_num;
|
||||
NfcProtocol detected_base_protocols[NfcProtocolNum];
|
||||
|
||||
size_t children_protocols_num;
|
||||
size_t children_protocols_idx;
|
||||
NfcProtocol children_protocols[NfcProtocolNum];
|
||||
|
||||
size_t detected_protocols_num;
|
||||
NfcProtocol detected_protocols[NfcProtocolNum];
|
||||
|
||||
NfcProtocol current_protocol;
|
||||
|
||||
FuriThread* scan_worker;
|
||||
};
|
||||
|
||||
static void nfc_scanner_reset(NfcScanner* instance) {
|
||||
instance->base_protocols_idx = 0;
|
||||
instance->base_protocols_num = 0;
|
||||
|
||||
instance->children_protocols_idx = 0;
|
||||
instance->children_protocols_num = 0;
|
||||
|
||||
instance->detected_protocols_num = 0;
|
||||
instance->detected_base_protocols_num = 0;
|
||||
|
||||
instance->current_protocol = 0;
|
||||
}
|
||||
|
||||
typedef void (*NfcScannerStateHandler)(NfcScanner* instance);
|
||||
|
||||
void nfc_scanner_state_handler_idle(NfcScanner* instance) {
|
||||
for(size_t i = 0; i < NfcProtocolNum; i++) {
|
||||
NfcProtocol parent_protocol = nfc_protocol_get_parent(i);
|
||||
if(parent_protocol == NfcProtocolInvalid) {
|
||||
instance->base_protocols[instance->base_protocols_num] = i;
|
||||
instance->base_protocols_num++;
|
||||
}
|
||||
}
|
||||
FURI_LOG_D(TAG, "Found %zu base protocols", instance->base_protocols_num);
|
||||
|
||||
instance->first_detected_protocol = NfcProtocolInvalid;
|
||||
instance->state = NfcScannerStateTryBasePollers;
|
||||
}
|
||||
|
||||
void nfc_scanner_state_handler_try_base_pollers(NfcScanner* instance) {
|
||||
do {
|
||||
instance->current_protocol = instance->base_protocols[instance->base_protocols_idx];
|
||||
|
||||
if(instance->first_detected_protocol == instance->current_protocol) {
|
||||
instance->state = NfcScannerStateFindChildrenProtocols;
|
||||
break;
|
||||
}
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->current_protocol);
|
||||
bool protocol_detected = nfc_poller_detect(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(protocol_detected) {
|
||||
instance->detected_protocols[instance->detected_protocols_num] =
|
||||
instance->current_protocol;
|
||||
instance->detected_protocols_num++;
|
||||
|
||||
instance->detected_base_protocols[instance->detected_base_protocols_num] =
|
||||
instance->current_protocol;
|
||||
instance->detected_base_protocols_num++;
|
||||
|
||||
if(instance->first_detected_protocol == NfcProtocolInvalid) {
|
||||
instance->first_detected_protocol = instance->current_protocol;
|
||||
instance->current_protocol = NfcProtocolInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
instance->base_protocols_idx =
|
||||
(instance->base_protocols_idx + 1) % instance->base_protocols_num;
|
||||
} while(false);
|
||||
}
|
||||
|
||||
void nfc_scanner_state_handler_find_children_protocols(NfcScanner* instance) {
|
||||
for(size_t i = 0; i < NfcProtocolNum; i++) {
|
||||
for(size_t j = 0; j < instance->detected_base_protocols_num; j++) {
|
||||
if(nfc_protocol_has_parent(i, instance->detected_base_protocols[j])) {
|
||||
instance->children_protocols[instance->children_protocols_num] = i;
|
||||
instance->children_protocols_num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->children_protocols_num > 0) {
|
||||
instance->state = NfcScannerStateDetectChildrenProtocols;
|
||||
} else {
|
||||
instance->state = NfcScannerStateComplete;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Found %zu children", instance->children_protocols_num);
|
||||
}
|
||||
|
||||
void nfc_scanner_state_handler_detect_children_protocols(NfcScanner* instance) {
|
||||
furi_assert(instance->children_protocols_num);
|
||||
|
||||
instance->current_protocol = instance->children_protocols[instance->children_protocols_idx];
|
||||
|
||||
NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->current_protocol);
|
||||
bool protocol_detected = nfc_poller_detect(poller);
|
||||
nfc_poller_free(poller);
|
||||
|
||||
if(protocol_detected) {
|
||||
instance->detected_protocols[instance->detected_protocols_num] =
|
||||
instance->current_protocol;
|
||||
instance->detected_protocols_num++;
|
||||
}
|
||||
|
||||
instance->children_protocols_idx++;
|
||||
if(instance->children_protocols_idx == instance->children_protocols_num) {
|
||||
instance->state = NfcScannerStateComplete;
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_scanner_filter_detected_protocols(NfcScanner* instance) {
|
||||
size_t filtered_protocols_num = 0;
|
||||
NfcProtocol filtered_protocols[NfcProtocolNum] = {};
|
||||
|
||||
for(size_t i = 0; i < instance->detected_protocols_num; i++) {
|
||||
bool is_parent = false;
|
||||
for(size_t j = i; j < instance->detected_protocols_num; j++) {
|
||||
is_parent = nfc_protocol_has_parent(
|
||||
instance->detected_protocols[j], instance->detected_protocols[i]);
|
||||
if(is_parent) break;
|
||||
}
|
||||
if(!is_parent) {
|
||||
filtered_protocols[filtered_protocols_num] = instance->detected_protocols[i];
|
||||
filtered_protocols_num++;
|
||||
}
|
||||
}
|
||||
|
||||
instance->detected_protocols_num = filtered_protocols_num;
|
||||
memcpy(instance->detected_protocols, filtered_protocols, filtered_protocols_num);
|
||||
}
|
||||
|
||||
void nfc_scanner_state_handler_complete(NfcScanner* instance) {
|
||||
if(instance->detected_protocols_num > 1) {
|
||||
nfc_scanner_filter_detected_protocols(instance);
|
||||
}
|
||||
FURI_LOG_I(TAG, "Detected %zu protocols", instance->detected_protocols_num);
|
||||
|
||||
NfcScannerEvent event = {
|
||||
.type = NfcScannerEventTypeDetected,
|
||||
.data =
|
||||
{
|
||||
.protocol_num = instance->detected_protocols_num,
|
||||
.protocols = instance->detected_protocols,
|
||||
},
|
||||
};
|
||||
|
||||
instance->callback(event, instance->context);
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
static NfcScannerStateHandler nfc_scanner_state_handlers[NfcScannerStateNum] = {
|
||||
[NfcScannerStateIdle] = nfc_scanner_state_handler_idle,
|
||||
[NfcScannerStateTryBasePollers] = nfc_scanner_state_handler_try_base_pollers,
|
||||
[NfcScannerStateFindChildrenProtocols] = nfc_scanner_state_handler_find_children_protocols,
|
||||
[NfcScannerStateDetectChildrenProtocols] = nfc_scanner_state_handler_detect_children_protocols,
|
||||
[NfcScannerStateComplete] = nfc_scanner_state_handler_complete,
|
||||
};
|
||||
|
||||
static int32_t nfc_scanner_worker(void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcScanner* instance = context;
|
||||
|
||||
while(instance->session_state == NfcScannerSessionStateActive) {
|
||||
nfc_scanner_state_handlers[instance->state](instance);
|
||||
}
|
||||
|
||||
nfc_scanner_reset(instance);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
NfcScanner* nfc_scanner_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
NfcScanner* instance = malloc(sizeof(NfcScanner));
|
||||
instance->nfc = nfc;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void nfc_scanner_free(NfcScanner* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
furi_assert(instance->session_state == NfcScannerSessionStateIdle);
|
||||
furi_assert(instance->scan_worker == NULL);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
instance->session_state = NfcScannerSessionStateActive;
|
||||
|
||||
instance->scan_worker = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->scan_worker, "NfcScanWorker");
|
||||
furi_thread_set_context(instance->scan_worker, instance);
|
||||
furi_thread_set_stack_size(instance->scan_worker, 4 * 1024);
|
||||
furi_thread_set_callback(instance->scan_worker, nfc_scanner_worker);
|
||||
|
||||
furi_thread_start(instance->scan_worker);
|
||||
}
|
||||
|
||||
void nfc_scanner_stop(NfcScanner* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->scan_worker);
|
||||
|
||||
instance->session_state = NfcScannerSessionStateStopRequest;
|
||||
furi_thread_join(instance->scan_worker);
|
||||
instance->session_state = NfcScannerSessionStateIdle;
|
||||
|
||||
furi_thread_free(instance->scan_worker);
|
||||
instance->scan_worker = NULL;
|
||||
instance->callback = NULL;
|
||||
instance->context = NULL;
|
||||
instance->state = NfcScannerStateIdle;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file nfc_scanner.h
|
||||
* @brief NFC card detection library.
|
||||
*
|
||||
* Once started, a NfcScanner instance will iterate over all available protocols
|
||||
* and return a list of one or more detected protocol identifiers via a user-provided callback.
|
||||
*
|
||||
* The NfcScanner behaviour is greedy, i.e. it will not stop scanning upon detection of
|
||||
* a just one protocol and will try others as well until all possibilities are exhausted.
|
||||
* This is to allow for multi-protocol card support.
|
||||
*
|
||||
* If no supported cards are in the vicinity, the scanning process will continue
|
||||
* until stopped explicitly.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include <nfc/protocols/nfc_protocol.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief NfcScanner opaque type definition.
|
||||
*/
|
||||
typedef struct NfcScanner NfcScanner;
|
||||
|
||||
/**
|
||||
* @brief Event type passed to the user callback.
|
||||
*/
|
||||
typedef enum {
|
||||
NfcScannerEventTypeDetected, /**< One or more protocols have been detected. */
|
||||
} NfcScannerEventType;
|
||||
|
||||
/**
|
||||
* @brief Event data passed to the user callback.
|
||||
*/
|
||||
typedef struct {
|
||||
size_t protocol_num; /**< Number of detected protocols (one or more). */
|
||||
NfcProtocol* protocols; /**< Pointer to the array of detected protocol identifiers. */
|
||||
} NfcScannerEventData;
|
||||
|
||||
/**
|
||||
* @brief Event passed to the user callback.
|
||||
*/
|
||||
typedef struct {
|
||||
NfcScannerEventType type; /**< Type of event. Determines how the data must be handled. */
|
||||
NfcScannerEventData data; /**< Event-specific data. Handled accordingly to the even type. */
|
||||
} NfcScannerEvent;
|
||||
|
||||
/**
|
||||
* @brief User callback function signature.
|
||||
*
|
||||
* A function with such signature must be provided by the user upon calling nfc_scanner_start().
|
||||
*
|
||||
* @param[in] event occurred event, complete with type and data.
|
||||
* @param[in] context pointer to the context data provided in nfc_scanner_start() call.
|
||||
*/
|
||||
typedef void (*NfcScannerCallback)(NfcScannerEvent event, void* context);
|
||||
|
||||
/**
|
||||
* @brief Allocate an NfcScanner instance.
|
||||
*
|
||||
* @param[in] nfc pointer to an Nfc instance.
|
||||
* @returns pointer to the allocated NfcScanner instance.
|
||||
*
|
||||
* @see nfc.h
|
||||
*/
|
||||
NfcScanner* nfc_scanner_alloc(Nfc* nfc);
|
||||
|
||||
/**
|
||||
* @brief Delete an NfcScanner instance.
|
||||
*
|
||||
* @param[in,out] pointer to the instance to be deleted.
|
||||
*/
|
||||
void nfc_scanner_free(NfcScanner* instance);
|
||||
|
||||
/**
|
||||
* @brief Start an NfcScanner.
|
||||
*
|
||||
* @param[in,out] pointer to the instance to be started.
|
||||
* @param[in] callback pointer to the callback function (will be called upon a detection event).
|
||||
* @param[in] context pointer to the caller-specific context (will be passed to the callback).
|
||||
*/
|
||||
void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* context);
|
||||
|
||||
/**
|
||||
* @brief Stop an NfcScanner.
|
||||
*
|
||||
* @param[in,out] pointer to the instance to be stopped.
|
||||
*/
|
||||
void nfc_scanner_stop(NfcScanner* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "nfc_types.h"
|
||||
|
||||
const char* nfc_get_dev_type(FuriHalNfcType type) {
|
||||
if(type == FuriHalNfcTypeA) {
|
||||
return "NFC-A";
|
||||
} else if(type == FuriHalNfcTypeB) {
|
||||
return "NFC-B";
|
||||
} else if(type == FuriHalNfcTypeF) {
|
||||
return "NFC-F";
|
||||
} else if(type == FuriHalNfcTypeV) {
|
||||
return "NFC-V";
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* nfc_guess_protocol(NfcProtocol protocol) {
|
||||
if(protocol == NfcDeviceProtocolEMV) {
|
||||
return "EMV bank card";
|
||||
} else if(protocol == NfcDeviceProtocolMifareUl) {
|
||||
return "Mifare Ultral/NTAG";
|
||||
} else if(protocol == NfcDeviceProtocolMifareClassic) {
|
||||
return "Mifare Classic";
|
||||
} else if(protocol == NfcDeviceProtocolMifareDesfire) {
|
||||
return "Mifare DESFire";
|
||||
} else {
|
||||
return "Unrecognized";
|
||||
}
|
||||
}
|
||||
|
||||
const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) {
|
||||
if(type == MfUltralightTypeNTAG213) {
|
||||
return "NTAG213";
|
||||
} else if(type == MfUltralightTypeNTAG215) {
|
||||
return "NTAG215";
|
||||
} else if(type == MfUltralightTypeNTAG216) {
|
||||
return "NTAG216";
|
||||
} else if(type == MfUltralightTypeNTAGI2C1K) {
|
||||
return "NTAG I2C 1K";
|
||||
} else if(type == MfUltralightTypeNTAGI2C2K) {
|
||||
return "NTAG I2C 2K";
|
||||
} else if(type == MfUltralightTypeNTAGI2CPlus1K) {
|
||||
return "NTAG I2C Plus 1K";
|
||||
} else if(type == MfUltralightTypeNTAGI2CPlus2K) {
|
||||
return "NTAG I2C Plus 2K";
|
||||
} else if(type == MfUltralightTypeNTAG203) {
|
||||
return "NTAG203";
|
||||
} else if(type == MfUltralightTypeULC) {
|
||||
return "Mifare Ultralight C";
|
||||
} else if(type == MfUltralightTypeUL11 && full_name) {
|
||||
return "Mifare Ultralight 11";
|
||||
} else if(type == MfUltralightTypeUL21 && full_name) {
|
||||
return "Mifare Ultralight 21";
|
||||
} else {
|
||||
return "Mifare Ultralight";
|
||||
}
|
||||
}
|
||||
|
||||
const char* nfc_mf_classic_type(MfClassicType type) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return "Mifare Mini 0.3K";
|
||||
} else if(type == MfClassicType1k) {
|
||||
return "Mifare Classic 1K";
|
||||
} else if(type == MfClassicType4k) {
|
||||
return "Mifare Classic 4K";
|
||||
} else {
|
||||
return "Mifare Classic";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_device.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const char* nfc_get_dev_type(FuriHalNfcType type);
|
||||
|
||||
const char* nfc_guess_protocol(NfcProtocol protocol);
|
||||
|
||||
const char* nfc_mf_ul_type(MfUltralightType type, bool full_name);
|
||||
|
||||
const char* nfc_mf_classic_type(MfClassicType type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,108 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_device.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct NfcWorker NfcWorker;
|
||||
|
||||
typedef enum {
|
||||
// Init states
|
||||
NfcWorkerStateNone,
|
||||
NfcWorkerStateReady,
|
||||
// Main worker states
|
||||
NfcWorkerStateRead,
|
||||
NfcWorkerStateUidEmulate,
|
||||
NfcWorkerStateMfUltralightEmulate,
|
||||
NfcWorkerStateMfClassicEmulate,
|
||||
NfcWorkerStateMfClassicWrite,
|
||||
NfcWorkerStateMfClassicUpdate,
|
||||
NfcWorkerStateReadMfUltralightReadAuth,
|
||||
NfcWorkerStateMfClassicDictAttack,
|
||||
NfcWorkerStateAnalyzeReader,
|
||||
NfcWorkerStateNfcVEmulate,
|
||||
NfcWorkerStateNfcVUnlock,
|
||||
NfcWorkerStateNfcVUnlockAndSave,
|
||||
NfcWorkerStateNfcVSniff,
|
||||
// Debug
|
||||
NfcWorkerStateEmulateApdu,
|
||||
NfcWorkerStateField,
|
||||
// Transition
|
||||
NfcWorkerStateStop,
|
||||
} NfcWorkerState;
|
||||
|
||||
typedef enum {
|
||||
// Reserve first 50 events for application events
|
||||
NfcWorkerEventReserved = 50,
|
||||
|
||||
// Nfc read events
|
||||
NfcWorkerEventReadUidNfcB,
|
||||
NfcWorkerEventReadUidNfcV,
|
||||
NfcWorkerEventReadUidNfcF,
|
||||
NfcWorkerEventReadUidNfcA,
|
||||
NfcWorkerEventReadMfUltralight,
|
||||
NfcWorkerEventReadMfDesfire,
|
||||
NfcWorkerEventReadMfClassicDone,
|
||||
NfcWorkerEventReadMfClassicLoadKeyCache,
|
||||
NfcWorkerEventReadMfClassicDictAttackRequired,
|
||||
NfcWorkerEventReadNfcV,
|
||||
|
||||
// Nfc worker common events
|
||||
NfcWorkerEventSuccess,
|
||||
NfcWorkerEventFail,
|
||||
NfcWorkerEventAborted,
|
||||
NfcWorkerEventCardDetected,
|
||||
NfcWorkerEventNoCardDetected,
|
||||
NfcWorkerEventWrongCardDetected,
|
||||
|
||||
// Read Mifare Classic events
|
||||
NfcWorkerEventNoDictFound,
|
||||
NfcWorkerEventNewSector,
|
||||
NfcWorkerEventNewDictKeyBatch,
|
||||
NfcWorkerEventFoundKeyA,
|
||||
NfcWorkerEventFoundKeyB,
|
||||
NfcWorkerEventKeyAttackStart,
|
||||
NfcWorkerEventKeyAttackStop,
|
||||
NfcWorkerEventKeyAttackNextSector,
|
||||
|
||||
// Write Mifare Classic events
|
||||
NfcWorkerEventWrongCard,
|
||||
|
||||
// Detect Reader events
|
||||
NfcWorkerEventDetectReaderDetected,
|
||||
NfcWorkerEventDetectReaderLost,
|
||||
NfcWorkerEventDetectReaderMfkeyCollected,
|
||||
|
||||
// Mifare Ultralight events
|
||||
NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key
|
||||
NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command
|
||||
NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key
|
||||
NfcWorkerEventNfcVCommandExecuted,
|
||||
NfcWorkerEventNfcVContentChanged,
|
||||
} NfcWorkerEvent;
|
||||
|
||||
typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
|
||||
|
||||
NfcWorker* nfc_worker_alloc();
|
||||
|
||||
NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_free(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_start(
|
||||
NfcWorker* nfc_worker,
|
||||
NfcWorkerState state,
|
||||
NfcDeviceData* dev_data,
|
||||
NfcWorkerCallback callback,
|
||||
void* context);
|
||||
|
||||
void nfc_worker_stop(NfcWorker* nfc_worker);
|
||||
void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker);
|
||||
void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker);
|
||||
void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_worker.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/stream/file_stream.h>
|
||||
|
||||
#include <lib/nfc/protocols/nfc_util.h>
|
||||
#include <lib/nfc/protocols/mifare_common.h>
|
||||
#include <lib/nfc/protocols/mifare_ultralight.h>
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
#include <lib/nfc/protocols/mifare_desfire.h>
|
||||
#include <lib/nfc/protocols/nfca.h>
|
||||
#include <lib/nfc/protocols/nfcv.h>
|
||||
#include <lib/nfc/protocols/slix.h>
|
||||
#include <lib/nfc/helpers/reader_analyzer.h>
|
||||
|
||||
struct NfcWorker {
|
||||
FuriThread* thread;
|
||||
Storage* storage;
|
||||
Stream* dict_stream;
|
||||
|
||||
NfcDeviceData* dev_data;
|
||||
|
||||
NfcWorkerCallback callback;
|
||||
void* context;
|
||||
|
||||
NfcWorkerState state;
|
||||
|
||||
ReaderAnalyzer* reader_analyzer;
|
||||
};
|
||||
|
||||
void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state);
|
||||
|
||||
int32_t nfc_worker_task(void* context);
|
||||
|
||||
void nfc_worker_read(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_read_type(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_emulate_uid(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_write_mf_classic(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_update_mf_classic(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_mf_ul_auth_attack(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_emulate_apdu(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_analyze_reader(NfcWorker* nfc_worker);
|
||||
@@ -1,103 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
#include "all_in_one.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define ALL_IN_ONE_LAYOUT_UNKNOWN 0
|
||||
#define ALL_IN_ONE_LAYOUT_A 1
|
||||
#define ALL_IN_ONE_LAYOUT_D 2
|
||||
#define ALL_IN_ONE_LAYOUT_E2 3
|
||||
#define ALL_IN_ONE_LAYOUT_E3 4
|
||||
#define ALL_IN_ONE_LAYOUT_E5 5
|
||||
#define ALL_IN_ONE_LAYOUT_2 6
|
||||
|
||||
uint8_t all_in_one_get_layout(NfcDeviceData* dev_data) {
|
||||
// I absolutely hate what's about to happen here.
|
||||
|
||||
// Switch on the second half of the third byte of page 5
|
||||
FURI_LOG_I("all_in_one", "Layout byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 2]);
|
||||
FURI_LOG_I(
|
||||
"all_in_one", "Layout half-byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 3] & 0x0F);
|
||||
switch(dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F) {
|
||||
// If it is A, the layout type is a type A layout
|
||||
case 0x0A:
|
||||
return ALL_IN_ONE_LAYOUT_A;
|
||||
case 0x0D:
|
||||
return ALL_IN_ONE_LAYOUT_D;
|
||||
case 0x02:
|
||||
return ALL_IN_ONE_LAYOUT_2;
|
||||
default:
|
||||
FURI_LOG_I(
|
||||
"all_in_one",
|
||||
"Unknown layout type: %d",
|
||||
dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F);
|
||||
return ALL_IN_ONE_LAYOUT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
UNUSED(nfc_worker);
|
||||
// If this is a all_in_one pass, first 2 bytes of page 4 are 0x45 0xD9
|
||||
MfUltralightReader reader = {};
|
||||
MfUltralightData data = {};
|
||||
|
||||
if(!mf_ul_read_card(tx_rx, &reader, &data)) {
|
||||
return false;
|
||||
} else {
|
||||
if(data.data[4 * 4] == 0x45 && data.data[4 * 4 + 1] == 0xD9) {
|
||||
FURI_LOG_I("all_in_one", "Pass verified");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
MfUltralightReader reader = {};
|
||||
MfUltralightData data = {};
|
||||
if(!mf_ul_read_card(tx_rx, &reader, &data)) {
|
||||
return false;
|
||||
} else {
|
||||
memcpy(&nfc_worker->dev_data->mf_ul_data, &data, sizeof(data));
|
||||
FURI_LOG_I("all_in_one", "Card read");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool all_in_one_parser_parse(NfcDeviceData* dev_data) {
|
||||
if(dev_data->mf_ul_data.data[4 * 4] != 0x45 || dev_data->mf_ul_data.data[4 * 4 + 1] != 0xD9) {
|
||||
FURI_LOG_I("all_in_one", "Pass not verified");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t ride_count = 0;
|
||||
uint32_t serial = 0;
|
||||
if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) {
|
||||
// If the layout is A then the ride count is stored in the first byte of page 8
|
||||
ride_count = dev_data->mf_ul_data.data[4 * 8];
|
||||
} else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) {
|
||||
// If the layout is D, the ride count is stored in the second byte of page 9
|
||||
ride_count = dev_data->mf_ul_data.data[4 * 9 + 1];
|
||||
} else {
|
||||
FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data));
|
||||
ride_count = 137;
|
||||
}
|
||||
|
||||
// I hate this with a burning passion.
|
||||
|
||||
// The number starts at the second half of the third byte on page 4, and is 32 bits long
|
||||
// So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte
|
||||
// B8 17 A2 A4 BD becomes 81 7A 2A 4B
|
||||
serial =
|
||||
(dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | dev_data->mf_ul_data.data[4 * 4 + 4] << 12 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4);
|
||||
|
||||
// Format string for rides count
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count);
|
||||
return true;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool all_in_one_parser_parse(NfcDeviceData* dev_data);
|
||||
@@ -1,82 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
#include "plantain_parser.h"
|
||||
#include "troika_parser.h"
|
||||
#include "plantain_4k_parser.h"
|
||||
#include "troika_4k_parser.h"
|
||||
#include "two_cities.h"
|
||||
#include "all_in_one.h"
|
||||
#include "opal.h"
|
||||
|
||||
NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
|
||||
[NfcSupportedCardTypePlantain] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = plantain_parser_verify,
|
||||
.read = plantain_parser_read,
|
||||
.parse = plantain_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeTroika] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = troika_parser_verify,
|
||||
.read = troika_parser_read,
|
||||
.parse = troika_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypePlantain4K] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = plantain_4k_parser_verify,
|
||||
.read = plantain_4k_parser_read,
|
||||
.parse = plantain_4k_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeTroika4K] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = troika_4k_parser_verify,
|
||||
.read = troika_4k_parser_read,
|
||||
.parse = troika_4k_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeTwoCities] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = two_cities_parser_verify,
|
||||
.read = two_cities_parser_read,
|
||||
.parse = two_cities_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeAllInOne] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareUl,
|
||||
.verify = all_in_one_parser_verify,
|
||||
.read = all_in_one_parser_read,
|
||||
.parse = all_in_one_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeOpal] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareDesfire,
|
||||
.verify = stub_parser_verify_read,
|
||||
.read = stub_parser_verify_read,
|
||||
.parse = opal_parser_parse,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) {
|
||||
furi_assert(dev_data);
|
||||
|
||||
bool card_parsed = false;
|
||||
for(size_t i = 0; i < COUNT_OF(nfc_supported_card); i++) {
|
||||
if(nfc_supported_card[i].parse(dev_data)) {
|
||||
card_parsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return card_parsed;
|
||||
}
|
||||
|
||||
bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
UNUSED(nfc_worker);
|
||||
UNUSED(tx_rx);
|
||||
return false;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
#include "../nfc_worker.h"
|
||||
#include "../nfc_device.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
NfcSupportedCardTypePlantain,
|
||||
NfcSupportedCardTypeTroika,
|
||||
NfcSupportedCardTypePlantain4K,
|
||||
NfcSupportedCardTypeTroika4K,
|
||||
NfcSupportedCardTypeTwoCities,
|
||||
NfcSupportedCardTypeAllInOne,
|
||||
NfcSupportedCardTypeOpal,
|
||||
|
||||
NfcSupportedCardTypeEnd,
|
||||
} NfcSupportedCardType;
|
||||
|
||||
typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
typedef bool (*NfcSupportedCardParse)(NfcDeviceData* dev_data);
|
||||
|
||||
typedef struct {
|
||||
NfcProtocol protocol;
|
||||
NfcSupportedCardVerify verify;
|
||||
NfcSupportedCardRead read;
|
||||
NfcSupportedCardParse parse;
|
||||
} NfcSupportedCard;
|
||||
|
||||
extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd];
|
||||
|
||||
bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data);
|
||||
|
||||
// stub_parser_verify_read does nothing, and always reports that it does not
|
||||
// support the card. This is needed for DESFire card parsers which can't
|
||||
// provide keys, and only use NfcSupportedCard->parse.
|
||||
bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* opal.c - Parser for Opal card (Sydney, Australia).
|
||||
*
|
||||
* Copyright 2023 Michael Farrell <micolous+git@gmail.com>
|
||||
*
|
||||
* This will only read "standard" MIFARE DESFire-based Opal cards. Free travel
|
||||
* cards (including School Opal cards, veteran, vision-impaired persons and
|
||||
* TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C
|
||||
* cards and not supported.
|
||||
*
|
||||
* Reference: https://github.com/metrodroid/metrodroid/wiki/Opal
|
||||
*
|
||||
* Note: The card values are all little-endian (like Flipper), but the above
|
||||
* reference was originally written based on Java APIs, which are big-endian.
|
||||
* This implementation presumes a little-endian system.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "nfc_supported_card.h"
|
||||
#include "opal.h"
|
||||
|
||||
#include <applications/services/locale/locale.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const uint8_t opal_aid[3] = {0x31, 0x45, 0x53};
|
||||
static const char* opal_modes[5] =
|
||||
{"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"};
|
||||
static const char* opal_usages[14] = {
|
||||
"New / Unused",
|
||||
"Tap on: new journey",
|
||||
"Tap on: transfer from same mode",
|
||||
"Tap on: transfer from other mode",
|
||||
"", // Manly Ferry: new journey
|
||||
"", // Manly Ferry: transfer from ferry
|
||||
"", // Manly Ferry: transfer from other
|
||||
"Tap off: distance fare",
|
||||
"Tap off: flat fare",
|
||||
"Automated tap off: failed to tap off",
|
||||
"Tap off: end of trip without start",
|
||||
"Tap off: reversal",
|
||||
"Tap on: rejected",
|
||||
"Unknown usage",
|
||||
};
|
||||
|
||||
// Opal file 0x7 structure. Assumes a little-endian CPU.
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
uint32_t serial : 32;
|
||||
uint8_t check_digit : 4;
|
||||
bool blocked : 1;
|
||||
uint16_t txn_number : 16;
|
||||
int32_t balance : 21;
|
||||
uint16_t days : 15;
|
||||
uint16_t minutes : 11;
|
||||
uint8_t mode : 3;
|
||||
uint16_t usage : 4;
|
||||
bool auto_topup : 1;
|
||||
uint8_t weekly_journeys : 4;
|
||||
uint16_t checksum : 16;
|
||||
} OpalFile;
|
||||
|
||||
static_assert(sizeof(OpalFile) == 16);
|
||||
|
||||
// Converts an Opal timestamp to FuriHalRtcDateTime.
|
||||
//
|
||||
// Opal measures days since 1980-01-01 and minutes since midnight, and presumes
|
||||
// all days are 1440 minutes.
|
||||
void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) {
|
||||
if(!out) return;
|
||||
uint16_t diy;
|
||||
out->year = 1980;
|
||||
out->month = 1;
|
||||
// 1980-01-01 is a Tuesday
|
||||
out->weekday = ((days + 1) % 7) + 1;
|
||||
out->hour = minutes / 60;
|
||||
out->minute = minutes % 60;
|
||||
out->second = 0;
|
||||
|
||||
// What year is it?
|
||||
for(;;) {
|
||||
diy = furi_hal_rtc_get_days_per_year(out->year);
|
||||
if(days < diy) break;
|
||||
days -= diy;
|
||||
out->year++;
|
||||
}
|
||||
|
||||
// 1-index the day of the year
|
||||
days++;
|
||||
// What month is it?
|
||||
bool is_leap = furi_hal_rtc_is_leap_year(out->year);
|
||||
|
||||
for(;;) {
|
||||
uint8_t dim = furi_hal_rtc_get_days_per_month(is_leap, out->month);
|
||||
if(days <= dim) break;
|
||||
days -= dim;
|
||||
out->month++;
|
||||
}
|
||||
|
||||
out->day = days;
|
||||
}
|
||||
|
||||
bool opal_parser_parse(NfcDeviceData* dev_data) {
|
||||
if(dev_data->protocol != NfcDeviceProtocolMifareDesfire) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MifareDesfireApplication* app = mf_df_get_application(&dev_data->mf_df_data, &opal_aid);
|
||||
if(app == NULL) {
|
||||
return false;
|
||||
}
|
||||
MifareDesfireFile* f = mf_df_get_file(app, 0x07);
|
||||
if(f == NULL || f->type != MifareDesfireFileTypeStandard || f->settings.data.size != 16 ||
|
||||
!f->contents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OpalFile* o = (OpalFile*)f->contents;
|
||||
|
||||
uint8_t serial2 = o->serial / 10000000;
|
||||
uint16_t serial3 = (o->serial / 1000) % 10000;
|
||||
uint16_t serial4 = (o->serial % 1000);
|
||||
|
||||
if(o->check_digit > 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* sign = "";
|
||||
if(o->balance < 0) {
|
||||
// Negative balance. Make this a positive value again and record the
|
||||
// sign separately, because then we can handle balances of -99..-1
|
||||
// cents, as the "dollars" division below would result in a positive
|
||||
// zero value.
|
||||
o->balance = abs(o->balance); //-V1081
|
||||
sign = "-";
|
||||
}
|
||||
uint8_t cents = o->balance % 100;
|
||||
int32_t dollars = o->balance / 100;
|
||||
|
||||
FuriHalRtcDateTime timestamp;
|
||||
opal_date_time_to_furi(o->days, o->minutes, ×tamp);
|
||||
|
||||
if(o->mode >= 3) {
|
||||
// 3..7 are "reserved", but we use 4 to indicate the Manly Ferry.
|
||||
o->mode = 3;
|
||||
}
|
||||
|
||||
if(o->usage >= 4 && o->usage <= 6) {
|
||||
// Usages 4..6 associated with the Manly Ferry, which correspond to
|
||||
// usages 1..3 for other modes.
|
||||
o->usage -= 3;
|
||||
o->mode = 4;
|
||||
}
|
||||
|
||||
const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); //-V547
|
||||
const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]);
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data,
|
||||
"\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n",
|
||||
sign,
|
||||
dollars,
|
||||
cents,
|
||||
serial2,
|
||||
serial3,
|
||||
serial4,
|
||||
o->check_digit,
|
||||
mode_str,
|
||||
usage_str);
|
||||
FuriString* timestamp_str = furi_string_alloc();
|
||||
locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-");
|
||||
furi_string_cat(dev_data->parsed_data, timestamp_str);
|
||||
furi_string_cat_str(dev_data->parsed_data, " at ");
|
||||
|
||||
locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false);
|
||||
furi_string_cat(dev_data->parsed_data, timestamp_str);
|
||||
|
||||
furi_string_free(timestamp_str);
|
||||
furi_string_cat_printf(
|
||||
dev_data->parsed_data,
|
||||
"\nWeekly journeys: %hhu, Txn #%hu\n",
|
||||
o->weekly_journeys,
|
||||
o->txn_number);
|
||||
|
||||
if(o->auto_topup) {
|
||||
furi_string_cat_str(dev_data->parsed_data, "Auto-topup enabled\n");
|
||||
}
|
||||
if(o->blocked) {
|
||||
furi_string_cat_str(dev_data->parsed_data, "Card blocked\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool opal_parser_parse(NfcDeviceData* dev_data);
|
||||
@@ -1,124 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const MfClassicAuthContext plantain_keys_4k[] = {
|
||||
{.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF},
|
||||
{.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b},
|
||||
{.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a},
|
||||
{.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7},
|
||||
{.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3},
|
||||
{.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb},
|
||||
{.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56},
|
||||
{.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26},
|
||||
{.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf},
|
||||
{.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a},
|
||||
{.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b},
|
||||
{.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3},
|
||||
{.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22},
|
||||
{.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056},
|
||||
{.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510},
|
||||
{.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf},
|
||||
{.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439},
|
||||
{.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406},
|
||||
{.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3},
|
||||
{.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA},
|
||||
{.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f},
|
||||
{.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3},
|
||||
{.sector = 30, .key_a = 0x0eb23cc8110b, .key_b = 0x04dc35277635},
|
||||
{.sector = 31, .key_a = 0xbc4580b7f20b, .key_b = 0xd0a4131fb290},
|
||||
{.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023},
|
||||
{.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9},
|
||||
{.sector = 34, .key_a = 0xfd8705e721b0, .key_b = 0x296fc317a513},
|
||||
{.sector = 35, .key_a = 0x22052b480d11, .key_b = 0xe19504c39461},
|
||||
{.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7},
|
||||
{.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a},
|
||||
{.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7},
|
||||
{.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085},
|
||||
};
|
||||
|
||||
bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 8;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Plant4K", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Plant4K", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(plantain_keys_4k); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader,
|
||||
plantain_keys_4k[i].sector,
|
||||
plantain_keys_4k[i].key_a,
|
||||
plantain_keys_4k[i].key_b);
|
||||
FURI_LOG_T("plant4k", "Added sector %d", plantain_keys_4k[i].sector);
|
||||
}
|
||||
for(int i = 0; i < 5; i++) {
|
||||
if(mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool plantain_4k_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != plantain_keys_4k[8].key_a) return false;
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
uint8_t* temp_ptr = &data->block[4 * 4].value[0];
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = &data->block[0 * 4].value[0];
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_4k_parser_parse(NfcDeviceData* dev_data);
|
||||
@@ -1,97 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const MfClassicAuthContext plantain_keys[] = {
|
||||
{.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b},
|
||||
{.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a},
|
||||
{.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7},
|
||||
{.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3},
|
||||
{.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb},
|
||||
{.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56},
|
||||
{.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26},
|
||||
{.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
};
|
||||
|
||||
bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 8;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Plant", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Plant", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(plantain_keys); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader, plantain_keys[i].sector, plantain_keys[i].key_a, plantain_keys[i].key_b);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
|
||||
}
|
||||
|
||||
uint8_t plantain_calculate_luhn(uint64_t number) {
|
||||
// No.
|
||||
UNUSED(number);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool plantain_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != plantain_keys[8].key_a) return false;
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
uint8_t* temp_ptr = &data->block[4 * 4].value[0];
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = &data->block[0 * 4].value[0];
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_parser_parse(NfcDeviceData* dev_data);
|
||||
|
||||
uint8_t plantain_calculate_luhn(uint64_t number);
|
||||
@@ -1,106 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
static const MfClassicAuthContext troika_4k_keys[] = {
|
||||
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
|
||||
{.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
|
||||
{.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698},
|
||||
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb},
|
||||
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
|
||||
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
|
||||
{.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
|
||||
{.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b},
|
||||
{.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0},
|
||||
{.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
|
||||
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408},
|
||||
{.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df},
|
||||
{.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9},
|
||||
{.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4},
|
||||
{.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216},
|
||||
{.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba},
|
||||
{.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252},
|
||||
{.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01},
|
||||
{.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994},
|
||||
{.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6},
|
||||
{.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32},
|
||||
{.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3},
|
||||
{.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5},
|
||||
{.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a},
|
||||
{.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80},
|
||||
{.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d},
|
||||
{.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2},
|
||||
{.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324},
|
||||
{.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae},
|
||||
{.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda},
|
||||
{.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d},
|
||||
{.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465},
|
||||
{.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4},
|
||||
{.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70},
|
||||
};
|
||||
|
||||
bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 11;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Troika", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Troika", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40;
|
||||
}
|
||||
|
||||
bool troika_4k_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != troika_4k_keys[4].key_a) return false;
|
||||
|
||||
// Verify card type
|
||||
if(data->type != MfClassicType4k) return false;
|
||||
|
||||
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
|
||||
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
|
||||
temp_ptr = &data->block[8 * 4].value[2];
|
||||
uint32_t number = 0;
|
||||
for(size_t i = 1; i < 5; i++) {
|
||||
number <<= 8;
|
||||
number |= temp_ptr[i];
|
||||
}
|
||||
number >>= 4;
|
||||
number |= (temp_ptr[0] & 0xf) << 28;
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_4k_parser_parse(NfcDeviceData* dev_data);
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
static const MfClassicAuthContext troika_keys[] = {
|
||||
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
|
||||
{.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
|
||||
{.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698},
|
||||
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba},
|
||||
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
|
||||
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
|
||||
{.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
|
||||
{.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b},
|
||||
{.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0},
|
||||
{.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
|
||||
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
};
|
||||
|
||||
bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 11;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Troika", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Troika", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(troika_keys); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
|
||||
}
|
||||
|
||||
bool troika_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
bool troika_parsed = false;
|
||||
|
||||
do {
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != troika_keys[8].key_a) break;
|
||||
|
||||
// Verify card type
|
||||
if(data->type != MfClassicType1k) break;
|
||||
|
||||
// Parse data
|
||||
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
|
||||
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
|
||||
temp_ptr = &data->block[8 * 4].value[2];
|
||||
uint32_t number = 0;
|
||||
for(size_t i = 1; i < 5; i++) {
|
||||
number <<= 8;
|
||||
number |= temp_ptr[i];
|
||||
}
|
||||
number >>= 4;
|
||||
number |= (temp_ptr[0] & 0xf) << 28;
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
|
||||
troika_parsed = true;
|
||||
} while(false);
|
||||
|
||||
return troika_parsed;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_parser_parse(NfcDeviceData* dev_data);
|
||||
@@ -1,146 +0,0 @@
|
||||
#include "nfc_supported_card.h"
|
||||
#include "plantain_parser.h" // For plantain-specific stuff
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
static const MfClassicAuthContext two_cities_keys_4k[] = {
|
||||
{.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b},
|
||||
{.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a},
|
||||
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
|
||||
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
|
||||
{.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb},
|
||||
{.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56},
|
||||
{.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26},
|
||||
{.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf},
|
||||
{.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a},
|
||||
{.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b},
|
||||
{.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3},
|
||||
{.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22},
|
||||
{.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056},
|
||||
{.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510},
|
||||
{.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf},
|
||||
{.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439},
|
||||
{.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406},
|
||||
{.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3},
|
||||
{.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA},
|
||||
{.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f},
|
||||
{.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3},
|
||||
{.sector = 30, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7},
|
||||
{.sector = 31, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3},
|
||||
{.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023},
|
||||
{.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9},
|
||||
{.sector = 34, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 35, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7},
|
||||
{.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a},
|
||||
{.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7},
|
||||
{.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085},
|
||||
};
|
||||
|
||||
bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 4;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("2cities", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0xe56ac127dd45, MfClassicKeyA)) {
|
||||
FURI_LOG_D("2cities", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(two_cities_keys_4k); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader,
|
||||
two_cities_keys_4k[i].sector,
|
||||
two_cities_keys_4k[i].key_a,
|
||||
two_cities_keys_4k[i].key_b);
|
||||
FURI_LOG_T("2cities", "Added sector %d", two_cities_keys_4k[i].sector);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40;
|
||||
}
|
||||
|
||||
bool two_cities_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != two_cities_keys_4k[4].key_a) return false;
|
||||
|
||||
// =====
|
||||
// PLANTAIN
|
||||
// =====
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
uint8_t* temp_ptr = &data->block[4 * 4].value[0];
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = &data->block[0 * 4].value[0];
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
}
|
||||
|
||||
// =====
|
||||
// --PLANTAIN--
|
||||
// =====
|
||||
// TROIKA
|
||||
// =====
|
||||
|
||||
uint8_t* troika_temp_ptr = &data->block[8 * 4 + 1].value[5];
|
||||
uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25;
|
||||
troika_temp_ptr = &data->block[8 * 4].value[3];
|
||||
uint32_t troika_number = 0;
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
troika_number <<= 8;
|
||||
troika_number |= troika_temp_ptr[i];
|
||||
}
|
||||
troika_number >>= 4;
|
||||
|
||||
furi_string_printf(
|
||||
dev_data->parsed_data,
|
||||
"\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
|
||||
card_number,
|
||||
balance,
|
||||
troika_number,
|
||||
troika_balance);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool two_cities_parser_parse(NfcDeviceData* dev_data);
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_3a;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso14443_3a;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_3b;
|
||||
@@ -0,0 +1 @@
|
||||
#include "iso14443_3b_i.h"
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
extern const NfcPollerBase nfc_poller_iso14443_3b;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
|
||||
extern const NfcDeviceBase nfc_device_iso14443_4a;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user