Merge branch 'dev' into datetime-module

This commit is contained in:
Aaron Tulino
2025-09-29 17:51:40 -07:00
committed by GitHub
130 changed files with 6138 additions and 279 deletions

View File

@@ -244,3 +244,20 @@ App(
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_args",
sources=["tests/common/*.c", "tests/args/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)
App(
appid="test_notification",
sources=["tests/common/*.c", "tests/notification/*.c"],
apptype=FlipperAppType.PLUGIN,
entry_point="get_api",
requires=["unit_tests"],
)

View File

@@ -0,0 +1,211 @@
#include "../test.h" // IWYU pragma: keep
#include <toolbox/args.h>
const uint32_t one_ms = 1;
const uint32_t one_s = 1000 * one_ms;
const uint32_t one_m = 60 * one_s;
const uint32_t one_h = 60 * one_m;
MU_TEST(args_read_duration_default_values_test) {
FuriString* args_string;
uint32_t value = 0;
// Check default == NULL (ms)
args_string = furi_string_alloc_set_str("1");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, one_ms);
furi_string_free(args_string);
value = 0;
// Check default == ms
args_string = furi_string_alloc_set_str("1");
mu_check(args_read_duration(args_string, &value, "ms"));
mu_assert_int_eq(value, one_ms);
furi_string_free(args_string);
value = 0;
// Check default == s
args_string = furi_string_alloc_set_str("1");
mu_check(args_read_duration(args_string, &value, "s"));
mu_assert_int_eq(value, one_s);
furi_string_free(args_string);
value = 0;
// Check default == m
args_string = furi_string_alloc_set_str("1");
mu_check(args_read_duration(args_string, &value, "m"));
mu_assert_int_eq(value, one_m);
furi_string_free(args_string);
value = 0;
// Check default == h
args_string = furi_string_alloc_set_str("1");
mu_check(args_read_duration(args_string, &value, "h"));
mu_assert_int_eq(value, one_h);
furi_string_free(args_string);
value = 0;
}
MU_TEST(args_read_duration_suffix_values_test) {
FuriString* args_string;
uint32_t value = 0;
// Check ms
args_string = furi_string_alloc_set_str("1ms");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, one_ms);
furi_string_free(args_string);
value = 0;
// Check s
args_string = furi_string_alloc_set_str("1s");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, one_s);
furi_string_free(args_string);
value = 0;
// Check m
args_string = furi_string_alloc_set_str("1m");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, one_m);
furi_string_free(args_string);
value = 0;
// Check h
args_string = furi_string_alloc_set_str("1h");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, one_h);
furi_string_free(args_string);
value = 0;
}
MU_TEST(args_read_duration_values_test) {
FuriString* args_string;
uint32_t value = 0;
// Check for ms
args_string = furi_string_alloc_set_str("4294967295ms");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 4294967295U);
furi_string_free(args_string);
// Check for s
args_string = furi_string_alloc_set_str("4294967s");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 4294967U * one_s);
furi_string_free(args_string);
// Check for m
args_string = furi_string_alloc_set_str("71582m");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 71582U * one_m);
furi_string_free(args_string);
// Check for h
args_string = furi_string_alloc_set_str("1193h");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 1193U * one_h);
furi_string_free(args_string);
// Check for ms in float
args_string = furi_string_alloc_set_str("4.2ms");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 4);
furi_string_free(args_string);
// Check for s in float
args_string = furi_string_alloc_set_str("1.5s");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, (uint32_t)(1.5 * one_s));
furi_string_free(args_string);
// Check for m in float
args_string = furi_string_alloc_set_str("1.5m");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, (uint32_t)(1.5 * one_m));
furi_string_free(args_string);
// Check for h in float
args_string = furi_string_alloc_set_str("1.5h");
mu_check(args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, (uint32_t)(1.5 * one_h));
furi_string_free(args_string);
}
MU_TEST(args_read_duration_errors_test) {
FuriString* args_string;
uint32_t value = 0;
// Check wrong suffix
args_string = furi_string_alloc_set_str("1x");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check wrong suffix
args_string = furi_string_alloc_set_str("1xs");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check negative value
args_string = furi_string_alloc_set_str("-1s");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check wrong values
// Check only suffix
args_string = furi_string_alloc_set_str("s");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check doubled point
args_string = furi_string_alloc_set_str("0.1.1");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check overflow values
// Check for ms
args_string = furi_string_alloc_set_str("4294967296ms");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check for s
args_string = furi_string_alloc_set_str("4294968s");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check for m
args_string = furi_string_alloc_set_str("71583m");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
// Check for h
args_string = furi_string_alloc_set_str("1194h");
mu_check(!args_read_duration(args_string, &value, NULL));
mu_assert_int_eq(value, 0);
furi_string_free(args_string);
}
MU_TEST_SUITE(toolbox_args_read_duration_suite) {
MU_RUN_TEST(args_read_duration_default_values_test);
MU_RUN_TEST(args_read_duration_suffix_values_test);
MU_RUN_TEST(args_read_duration_values_test);
MU_RUN_TEST(args_read_duration_errors_test);
}
int run_minunit_test_toolbox_args(void) {
MU_RUN_SUITE(toolbox_args_read_duration_suite);
return MU_EXIT_CODE;
}
TEST_API_DEFINE(run_minunit_test_toolbox_args)

View File

@@ -389,8 +389,8 @@ void minunit_printf_warning(const char* format, ...);
__func__, \
__FILE__, \
__LINE__, \
minunit_tmp_e, \
minunit_tmp_r, \
minunit_tmp_e, \
minunit_tmp_m); \
minunit_status = 1; \
return; \

View File

@@ -268,9 +268,9 @@ static void mf_ultralight_reader_test(const char* path) {
nfc_listener_stop(mfu_listener);
nfc_listener_free(mfu_listener);
mu_assert(
mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)),
"Data not matches");
MfUltralightData* mfu_other_data =
(MfUltralightData*)nfc_device_get_data(nfc_device, NfcProtocolMfUltralight);
mu_assert(mf_ultralight_is_equal(mfu_data, mfu_other_data), "Data mismatch");
mf_ultralight_free(mfu_data);
nfc_device_free(nfc_device);

View File

@@ -0,0 +1,165 @@
#include "../test.h" // IWYU pragma: keep
#include <float_tools.h>
#include <notification/notification_messages_notes.h>
void frequency_assert(const char* note_name, const NotificationMessage* message) {
double a = notification_messages_notes_frequency_from_name(note_name);
double b = message->data.sound.frequency;
const double epsilon = message->data.sound.frequency > 5000 ? 0.02f : 0.01f;
mu_assert_double_between(b - epsilon, b + epsilon, a);
}
MU_TEST(notification_messages_notes_frequency_from_name_test) {
// Upper case
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("C0"),
notification_messages_notes_frequency_from_name("c0")));
// Mixed case
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("Cs0"),
notification_messages_notes_frequency_from_name("cs0")));
// Check errors
mu_check(
float_is_equal(notification_messages_notes_frequency_from_name("0"), 0.0)); // Without note
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("C"), 0.0)); // Without octave
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("C9"), 0.0)); // Unsupported octave
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("C10"), 0.0)); // Unsupported octave
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("X0"), 0.0)); // Unknown note
mu_check(float_is_equal(
notification_messages_notes_frequency_from_name("CCC0"), 0.0)); // Note name overflow
// Notes and structures
frequency_assert("c0", &message_note_c0);
frequency_assert("cs0", &message_note_cs0);
frequency_assert("d0", &message_note_d0);
frequency_assert("ds0", &message_note_ds0);
frequency_assert("e0", &message_note_e0);
frequency_assert("f0", &message_note_f0);
frequency_assert("fs0", &message_note_fs0);
frequency_assert("g0", &message_note_g0);
frequency_assert("gs0", &message_note_gs0);
frequency_assert("a0", &message_note_a0);
frequency_assert("as0", &message_note_as0);
frequency_assert("b0", &message_note_b0);
frequency_assert("c1", &message_note_c1);
frequency_assert("cs1", &message_note_cs1);
frequency_assert("d1", &message_note_d1);
frequency_assert("ds1", &message_note_ds1);
frequency_assert("e1", &message_note_e1);
frequency_assert("f1", &message_note_f1);
frequency_assert("fs1", &message_note_fs1);
frequency_assert("g1", &message_note_g1);
frequency_assert("gs1", &message_note_gs1);
frequency_assert("a1", &message_note_a1);
frequency_assert("as1", &message_note_as1);
frequency_assert("b1", &message_note_b1);
frequency_assert("c2", &message_note_c2);
frequency_assert("cs2", &message_note_cs2);
frequency_assert("d2", &message_note_d2);
frequency_assert("ds2", &message_note_ds2);
frequency_assert("e2", &message_note_e2);
frequency_assert("f2", &message_note_f2);
frequency_assert("fs2", &message_note_fs2);
frequency_assert("g2", &message_note_g2);
frequency_assert("gs2", &message_note_gs2);
frequency_assert("a2", &message_note_a2);
frequency_assert("as2", &message_note_as2);
frequency_assert("b2", &message_note_b2);
frequency_assert("c3", &message_note_c3);
frequency_assert("cs3", &message_note_cs3);
frequency_assert("d3", &message_note_d3);
frequency_assert("ds3", &message_note_ds3);
frequency_assert("e3", &message_note_e3);
frequency_assert("f3", &message_note_f3);
frequency_assert("fs3", &message_note_fs3);
frequency_assert("g3", &message_note_g3);
frequency_assert("gs3", &message_note_gs3);
frequency_assert("a3", &message_note_a3);
frequency_assert("as3", &message_note_as3);
frequency_assert("b3", &message_note_b3);
frequency_assert("c4", &message_note_c4);
frequency_assert("cs4", &message_note_cs4);
frequency_assert("d4", &message_note_d4);
frequency_assert("ds4", &message_note_ds4);
frequency_assert("e4", &message_note_e4);
frequency_assert("f4", &message_note_f4);
frequency_assert("fs4", &message_note_fs4);
frequency_assert("g4", &message_note_g4);
frequency_assert("gs4", &message_note_gs4);
frequency_assert("a4", &message_note_a4);
frequency_assert("as4", &message_note_as4);
frequency_assert("b4", &message_note_b4);
frequency_assert("c5", &message_note_c5);
frequency_assert("cs5", &message_note_cs5);
frequency_assert("d5", &message_note_d5);
frequency_assert("ds5", &message_note_ds5);
frequency_assert("e5", &message_note_e5);
frequency_assert("f5", &message_note_f5);
frequency_assert("fs5", &message_note_fs5);
frequency_assert("g5", &message_note_g5);
frequency_assert("gs5", &message_note_gs5);
frequency_assert("a5", &message_note_a5);
frequency_assert("as5", &message_note_as5);
frequency_assert("b5", &message_note_b5);
frequency_assert("c6", &message_note_c6);
frequency_assert("cs6", &message_note_cs6);
frequency_assert("d6", &message_note_d6);
frequency_assert("ds6", &message_note_ds6);
frequency_assert("e6", &message_note_e6);
frequency_assert("f6", &message_note_f6);
frequency_assert("fs6", &message_note_fs6);
frequency_assert("g6", &message_note_g6);
frequency_assert("gs6", &message_note_gs6);
frequency_assert("a6", &message_note_a6);
frequency_assert("as6", &message_note_as6);
frequency_assert("b6", &message_note_b6);
frequency_assert("c7", &message_note_c7);
frequency_assert("cs7", &message_note_cs7);
frequency_assert("d7", &message_note_d7);
frequency_assert("ds7", &message_note_ds7);
frequency_assert("e7", &message_note_e7);
frequency_assert("f7", &message_note_f7);
frequency_assert("fs7", &message_note_fs7);
frequency_assert("g7", &message_note_g7);
frequency_assert("gs7", &message_note_gs7);
frequency_assert("a7", &message_note_a7);
frequency_assert("as7", &message_note_as7);
frequency_assert("b7", &message_note_b7);
frequency_assert("c8", &message_note_c8);
frequency_assert("cs8", &message_note_cs8);
frequency_assert("d8", &message_note_d8);
frequency_assert("ds8", &message_note_ds8);
frequency_assert("e8", &message_note_e8);
frequency_assert("f8", &message_note_f8);
frequency_assert("fs8", &message_note_fs8);
frequency_assert("g8", &message_note_g8);
frequency_assert("gs8", &message_note_gs8);
frequency_assert("a8", &message_note_a8);
frequency_assert("as8", &message_note_as8);
frequency_assert("b8", &message_note_b8);
}
MU_TEST_SUITE(notes_suite) {
MU_RUN_TEST(notification_messages_notes_frequency_from_name_test);
}
int run_minunit_test_notes(void) {
MU_RUN_SUITE(notes_suite);
return MU_EXIT_CODE;
}
TEST_API_DEFINE(run_minunit_test_notes)

View File

@@ -8,11 +8,7 @@ App(
stack_size=5 * 1024,
order=30,
resources="resources",
sources=[
"*.c*",
"!plugins",
"!nfc_cli.c",
],
sources=["*.c*", "!plugins", "!nfc_cli.c", "!cli"],
fap_libs=["assets", "mbedtls"],
fap_icon="icon.png",
fap_category="NFC",
@@ -263,7 +259,46 @@ App(
apptype=FlipperAppType.PLUGIN,
entry_point="cli_nfc_ep",
requires=["cli"],
sources=["nfc_cli.c"],
sources=[
"helpers/mf_classic_key_cache.c",
"helpers/protocol_support/iso14443_3a/iso14443_3a_render.c",
"helpers/protocol_support/mf_ultralight/mf_ultralight_render.c",
"cli/nfc_cli.c",
"cli/nfc_cli_commands.c",
"cli/nfc_cli_command_processor.c",
"cli/commands/helpers/nfc_cli_format.c",
"cli/commands/helpers/nfc_cli_scanner.c",
"cli/commands/helpers/nfc_cli_protocol_parser.c",
"cli/commands/raw/nfc_cli_command_raw.c",
"cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c",
"cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c",
"cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c",
"cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c",
"cli/commands/apdu/nfc_cli_command_apdu.c",
"cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c",
"cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c",
"cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c",
"cli/commands/dump/nfc_cli_command_dump.c",
"cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c",
"cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c",
"cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c",
"cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c",
"cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c",
"cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c",
"cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c",
"cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c",
"cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c",
"cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c",
"cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c",
"cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c",
"cli/commands/mfu/nfc_cli_command_mfu.c",
"cli/commands/mfu/nfc_cli_action_info.c",
"cli/commands/mfu/nfc_cli_action_rdbl.c",
"cli/commands/mfu/nfc_cli_action_wrbl.c",
"cli/commands/nfc_cli_command_emulate.c",
"cli/commands/nfc_cli_command_scanner.c",
"cli/commands/nfc_cli_command_field.c",
],
)
App(

View File

@@ -0,0 +1,311 @@
#include "nfc_cli_command_apdu.h"
#include "../helpers/nfc_cli_format.h"
#include "../helpers/nfc_cli_protocol_parser.h"
#include "protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h"
#include "protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h"
#include "protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h"
#include <furi.h>
#include <nfc/nfc.h>
#include <nfc/nfc_poller.h>
#include <toolbox/args.h>
#include <m-array.h>
#include <m-algo.h>
#define TAG "APDU"
#define NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE (256)
typedef NfcCommand (
*NfcCliApduProtocolHandler)(NfcGenericEvent event, NfcCliApduRequestResponse* instance);
static const char* raw_error_names[] = {
[NfcCliApduErrorNone] = "None",
[NfcCliApduErrorTimeout] = "Timeout",
[NfcCliApduErrorProtocol] = "Internal protocol",
[NfcCliApduErrorWrongCrc] = "Wrong CRC",
[NfcCliApduErrorNotPresent] = "No card",
};
typedef enum {
NfcCliProtocolRequestTypeNormalExecute,
NfcCliProtocolRequestTypeAbort,
} NfcCliProtocolRequestType;
typedef struct {
uint8_t* data;
size_t size;
} NfcCliApduData;
static void ApduItem_init(NfcCliApduData* item) {
item->size = 0;
item->data = NULL;
}
static void ApduItem_init_set(NfcCliApduData* item, const NfcCliApduData* src) {
item->data = malloc(src->size);
item->size = src->size;
memcpy(item->data, src->data, src->size);
}
static void ApduItem_set(NfcCliApduData* item, const NfcCliApduData* src) {
if(item->data == NULL) {
item->data = malloc(src->size);
} else if(item->size != src->size) {
uint8_t* buf = realloc(item->data, src->size);
furi_check(buf);
item->data = buf;
}
item->size = src->size;
memcpy(item->data, src->data, src->size);
}
static void ApduItem_clear(NfcCliApduData* item) {
if(item->data) free(item->data);
item->data = NULL;
item->size = 0;
}
ARRAY_DEF(
NfcCliApduItemArray,
NfcCliApduData,
(INIT(API_2(ApduItem_init)),
SET(API_6(ApduItem_set)),
INIT_SET(API_6(ApduItem_init_set)),
CLEAR(API_2(ApduItem_clear))))
typedef struct {
Nfc* nfc;
bool auto_detect;
NfcCliApduItemArray_t apdu;
NfcCliApduRequestResponse data;
FuriSemaphore* sem_done;
FuriMessageQueue* input_queue;
} NfcCliApduContext;
static NfcCliActionContext* nfc_cli_apdu_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliApduContext* instance = malloc(sizeof(NfcCliApduContext));
instance->nfc = nfc;
instance->data.protocol = NfcProtocolInvalid;
instance->auto_detect = true;
NfcCliApduItemArray_init(instance->apdu);
instance->data.rx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE);
instance->data.tx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE);
instance->sem_done = furi_semaphore_alloc(1, 0);
instance->input_queue = furi_message_queue_alloc(1, sizeof(NfcCliProtocolRequestType));
return instance;
}
static void nfc_cli_apdu_free_ctx(NfcCliActionContext* action_ctx) {
furi_assert(action_ctx);
NfcCliApduContext* instance = action_ctx;
instance->nfc = NULL;
NfcCliApduItemArray_clear(instance->apdu);
bit_buffer_free(instance->data.rx_buffer);
bit_buffer_free(instance->data.tx_buffer);
furi_semaphore_free(instance->sem_done);
furi_message_queue_free(instance->input_queue);
free(instance);
}
static inline void nfc_cli_apdu_print_result(const NfcCliApduContext* instance) {
nfc_cli_printf_array(
bit_buffer_get_data(instance->data.tx_buffer),
bit_buffer_get_size_bytes(instance->data.tx_buffer),
"\r\nTx: ");
if(instance->data.result != NfcCliApduErrorNone)
printf("\r\nError: \"%s\"\r\n", raw_error_names[instance->data.result]);
size_t rx_size = bit_buffer_get_size_bytes(instance->data.rx_buffer);
if(rx_size > 0) {
nfc_cli_printf_array(
bit_buffer_get_data(instance->data.rx_buffer),
bit_buffer_get_size_bytes(instance->data.rx_buffer),
"\r\nRx: ");
printf("\r\n");
}
}
static NfcProtocol nfc_cli_apdu_protocol_autodetect(Nfc* nfc) {
const NfcProtocol supported_protocols[] = {
NfcProtocolIso14443_4a,
NfcProtocolIso14443_4b,
NfcProtocolIso15693_3,
};
const char* supported_names[] = {"Iso14443_4a", "Iso14443_4b", "Iso15693_3"};
NfcProtocol protocol = NfcProtocolInvalid;
for(uint8_t i = 0; i < COUNT_OF(supported_protocols); i++) {
NfcPoller* poller = nfc_poller_alloc(nfc, supported_protocols[i]);
bool is_detected = nfc_poller_detect(poller);
nfc_poller_free(poller);
if(is_detected) {
protocol = supported_protocols[i];
printf("Detected tag: %s\r\n", supported_names[i]);
break;
}
}
return protocol;
}
static NfcCliApduProtocolHandler nfc_cli_apdu_poller_get_handler(NfcProtocol protocol) {
if(protocol == NfcProtocolIso14443_4a)
return nfc_cli_apdu_iso14443_4a_handler;
else if(protocol == NfcProtocolIso14443_4b)
return nfc_cli_apdu_iso14443_4b_handler;
else if(protocol == NfcProtocolIso15693_3)
return nfc_cli_apdu_iso15693_3_handler;
else
return NULL;
}
static NfcCommand nfc_cli_apdu_poller_callback(NfcGenericEvent event, void* context) {
NfcCliApduContext* instance = context;
FURI_LOG_D(TAG, "Poller callback");
NfcCliProtocolRequestType request_type = NfcCliProtocolRequestTypeAbort;
furi_message_queue_get(instance->input_queue, &request_type, FuriWaitForever);
NfcCommand command = NfcCommandStop;
if(request_type == NfcCliProtocolRequestTypeAbort) {
FURI_LOG_D(TAG, "Aborting poller callback");
} else {
NfcCliApduProtocolHandler handler =
nfc_cli_apdu_poller_get_handler(instance->data.protocol);
if(handler) command = handler(event, &instance->data);
}
furi_semaphore_release(instance->sem_done);
return command;
}
static void nfc_cli_apdu_execute(PipeSide* pipe, void* context) {
UNUSED(pipe);
furi_assert(context);
NfcCliApduContext* instance = context;
if(instance->auto_detect) {
instance->data.protocol = nfc_cli_apdu_protocol_autodetect(instance->nfc);
}
if(instance->data.protocol != NfcProtocolInvalid) {
NfcPoller* poller = nfc_poller_alloc(instance->nfc, instance->data.protocol);
NfcCliProtocolRequestType request_type = NfcCliProtocolRequestTypeNormalExecute;
nfc_poller_start(poller, nfc_cli_apdu_poller_callback, instance);
NfcCliApduItemArray_it_t it;
for(NfcCliApduItemArray_it(it, instance->apdu); !NfcCliApduItemArray_end_p(it);
NfcCliApduItemArray_next(it)) {
const NfcCliApduData* item = NfcCliApduItemArray_cref(it);
bit_buffer_copy_bytes(instance->data.tx_buffer, item->data, item->size);
bit_buffer_reset(instance->data.rx_buffer);
furi_message_queue_put(instance->input_queue, &request_type, FuriWaitForever);
furi_semaphore_acquire(instance->sem_done, FuriWaitForever);
nfc_cli_apdu_print_result(instance);
if(instance->data.result != NfcCliApduErrorNone) break;
}
request_type = NfcCliProtocolRequestTypeAbort;
furi_message_queue_put(instance->input_queue, &request_type, FuriWaitForever);
nfc_poller_stop(poller);
nfc_poller_free(poller);
}
}
static const NfcProtocolNameValuePair supported_protocols[] = {
{.name = "4a", .value = NfcProtocolIso14443_4a},
{.name = "4b", .value = NfcProtocolIso14443_4b},
{.name = "15", .value = NfcProtocolIso15693_3},
};
static bool nfc_cli_apdu_parse_protocol(FuriString* value, void* output) {
NfcCliApduContext* ctx = output;
ctx->auto_detect = false;
NfcCliProtocolParser* parser =
nfc_cli_protocol_parser_alloc(supported_protocols, COUNT_OF(supported_protocols));
bool result = nfc_cli_protocol_parser_get(parser, value, &ctx->data.protocol);
nfc_cli_protocol_parser_free(parser);
return result;
}
static bool nfc_cli_apdu_parse_data(FuriString* value, void* output) {
NfcCliApduContext* ctx = output;
bool result = false;
FuriString* word = furi_string_alloc();
while(args_read_string_and_trim(value, word)) {
size_t len = furi_string_size(word);
if(len % 2 != 0) break;
size_t data_length = len / 2;
const size_t max_len = UINT16_MAX;
if(data_length > max_len) {
printf(
ANSI_FG_RED "\r\nData payload is too long, max length = %d bytes\r\n" ANSI_RESET,
max_len);
break;
}
NfcCliApduData* item = NfcCliApduItemArray_push_new(ctx->apdu);
item->size = data_length;
item->data = malloc(data_length);
result = args_read_hex_bytes(word, item->data, item->size);
}
furi_string_free(word);
return result;
}
const NfcCliKeyDescriptor apdu_keys[] = {
{
.long_name = "protocol",
.short_name = "p",
.description = "set protocol (4a, 4b, 15) directly, otherwise autodetected",
.features = {.parameter = true, .required = false},
.parse = nfc_cli_apdu_parse_protocol,
},
{
.long_name = "data",
.short_name = "d",
.description = "apdu payloads in format p1 p2 p3",
.features = {.parameter = true, .multivalue = true, .required = true},
.parse = nfc_cli_apdu_parse_data,
},
};
const NfcCliActionDescriptor apdu_action = {
.name = "apdu",
.description = "Send APDU data to iso14443_4a, iso14443_4b or iso15693_3",
.alloc = nfc_cli_apdu_alloc_ctx,
.free = nfc_cli_apdu_free_ctx,
.execute = nfc_cli_apdu_execute,
.key_count = COUNT_OF(apdu_keys),
.keys = apdu_keys,
};
const NfcCliActionDescriptor* apdu_actions_collection[] = {&apdu_action};
//Command descriptor
ADD_NFC_CLI_COMMAND(apdu, "", apdu_actions_collection);
//Command usage: apdu <protocol> <data>
//Command examples:
//apdu -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102
//apdu -p 4a -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102
//apdu -p 4b -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102
//apdu -p 15 -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor apdu_cmd;

View File

@@ -0,0 +1,37 @@
#include "nfc_cli_apdu_iso14443_4a.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/protocols/iso14443_4a/iso14443_4a.h>
#include <nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
#define TAG "ISO14A_4A"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static NfcCliApduError nfc_cli_apdu_iso14443_4a_process_error(Iso14443_4aError error) {
switch(error) {
case Iso14443_4aErrorNone:
return NfcCliApduErrorNone;
case Iso14443_4aErrorTimeout:
return NfcCliApduErrorTimeout;
case Iso14443_4aErrorNotPresent:
return NfcCliApduErrorNotPresent;
default:
return NfcCliApduErrorProtocol;
}
}
NfcCommand
nfc_cli_apdu_iso14443_4a_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance) {
Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
Iso14443_4aError err = iso14443_4a_poller_send_block(
event.instance, instance->tx_buffer, instance->rx_buffer);
instance->result = nfc_cli_apdu_iso14443_4a_process_error(err);
if(err != Iso14443_4aErrorNone) command = NfcCommandStop;
}
return command;
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "../nfc_cli_apdu_common_types.h"
NfcCommand
nfc_cli_apdu_iso14443_4a_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance);

View File

@@ -0,0 +1,37 @@
#include "nfc_cli_apdu_iso14443_4b.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/protocols/iso14443_4b/iso14443_4b.h>
#include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
#define TAG "ISO14A_4B"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static NfcCliApduError nfc_cli_apdu_iso14443_4b_process_error(Iso14443_4bError error) {
switch(error) {
case Iso14443_4bErrorNone:
return NfcCliApduErrorNone;
case Iso14443_4bErrorTimeout:
return NfcCliApduErrorTimeout;
case Iso14443_4bErrorNotPresent:
return NfcCliApduErrorNotPresent;
default:
return NfcCliApduErrorProtocol;
}
}
NfcCommand
nfc_cli_apdu_iso14443_4b_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance) {
Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
Iso14443_4bError err = iso14443_4b_poller_send_block(
event.instance, instance->tx_buffer, instance->rx_buffer);
instance->result = nfc_cli_apdu_iso14443_4b_process_error(err);
if(err != Iso14443_4bErrorNone) command = NfcCommandStop;
}
return command;
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "../nfc_cli_apdu_common_types.h"
NfcCommand
nfc_cli_apdu_iso14443_4b_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance);

View File

@@ -0,0 +1,37 @@
#include "nfc_cli_apdu_iso15693_3.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/protocols/iso15693_3/iso15693_3.h>
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
#define TAG "ISO15"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static NfcCliApduError nfc_cli_apdu_iso15693_3_process_error(Iso15693_3Error error) {
switch(error) {
case Iso15693_3ErrorNone:
return NfcCliApduErrorNone;
case Iso15693_3ErrorTimeout:
return NfcCliApduErrorTimeout;
case Iso15693_3ErrorNotPresent:
return NfcCliApduErrorNotPresent;
default:
return NfcCliApduErrorProtocol;
}
}
NfcCommand
nfc_cli_apdu_iso15693_3_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance) {
Iso15693_3PollerEvent* iso15693_3_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) {
Iso15693_3Error err = iso15693_3_poller_send_frame(
event.instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC);
instance->result = nfc_cli_apdu_iso15693_3_process_error(err);
if(err != Iso15693_3ErrorNone) command = NfcCommandStop;
}
return command;
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "../nfc_cli_apdu_common_types.h"
NfcCommand
nfc_cli_apdu_iso15693_3_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance);

View File

@@ -0,0 +1,20 @@
#pragma once
#include <furi.h>
#include <nfc/nfc.h>
#include <nfc/nfc_poller.h>
typedef enum {
NfcCliApduErrorNone,
NfcCliApduErrorTimeout,
NfcCliApduErrorNotPresent,
NfcCliApduErrorWrongCrc,
NfcCliApduErrorProtocol,
} NfcCliApduError;
typedef struct {
NfcProtocol protocol;
BitBuffer* tx_buffer;
BitBuffer* rx_buffer;
NfcCliApduError result;
} NfcCliApduRequestResponse;

View File

@@ -0,0 +1,319 @@
#include "nfc_cli_command_dump.h"
#include "protocols/nfc_cli_dump_common_types.h"
#include "../helpers/nfc_cli_format.h"
#include "../helpers/nfc_cli_protocol_parser.h"
#include "../helpers/nfc_cli_scanner.h"
#include "protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h"
#include "protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h"
#include "protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h"
#include "protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h"
#include "protocols/iso15693_3/nfc_cli_dump_iso15693_3.h"
#include "protocols/mf_classic/nfc_cli_dump_mf_classic.h"
#include "protocols/mf_desfire/nfc_cli_dump_mf_desfire.h"
#include "protocols/mf_plus/nfc_cli_dump_mf_plus.h"
#include "protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h"
#include "protocols/slix/nfc_cli_dump_slix.h"
#include "protocols/st25tb/nfc_cli_dump_st25tb.h"
#include "protocols/felica/nfc_cli_dump_felica.h"
#include <datetime.h>
#include <furi_hal_rtc.h>
#include <toolbox/strint.h>
#include <toolbox/path.h>
#include <toolbox/args.h>
#define NFC_DEFAULT_FOLDER EXT_PATH("nfc")
#define NFC_FILE_EXTENSION ".nfc"
#define NFC_CLI_DEFAULT_FILENAME_PREFIX "dump"
#define NFC_CLI_DUMP_DEFAULT_TIMEOUT (5000)
#define TAG "DUMP"
static const char* nfc_cli_dump_error_names[NfcCliDumpErrorNum] = {
[NfcCliDumpErrorNone] = "",
[NfcCliDumpErrorNotPresent] = "card not present",
[NfcCliDumpErrorAuthFailed] = "authentication failed",
[NfcCliDumpErrorTimeout] = "timeout",
[NfcCliDumpErrorFailedToRead] = "failed to read",
};
static NfcCliActionContext* nfc_cli_dump_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliDumpContext* instance = malloc(sizeof(NfcCliDumpContext));
instance->nfc = nfc;
instance->file_path = furi_string_alloc();
instance->storage = furi_record_open(RECORD_STORAGE);
instance->sem_done = furi_semaphore_alloc(1, 0);
instance->nfc_device = nfc_device_alloc();
instance->desired_protocol = NfcProtocolInvalid;
instance->auth_ctx.skip_auth = true;
instance->auth_ctx.key_size = 0;
instance->timeout = NFC_CLI_DUMP_DEFAULT_TIMEOUT;
instance->mfc_key_cache = mf_classic_key_cache_alloc();
instance->scanner = nfc_cli_scanner_alloc(nfc);
return instance;
}
static void nfc_cli_dump_free_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliDumpContext* instance = ctx;
instance->desired_protocol = NfcProtocolInvalid;
furi_string_free(instance->file_path);
instance->nfc = NULL;
furi_record_close(RECORD_STORAGE);
furi_semaphore_free(instance->sem_done);
nfc_device_free(instance->nfc_device);
mf_classic_key_cache_free(instance->mfc_key_cache);
nfc_cli_scanner_free(instance->scanner);
free(instance);
}
static bool nfc_cli_dump_parse_filename_key(FuriString* value, void* output) {
furi_assert(value);
furi_assert(output);
NfcCliDumpContext* ctx = output;
furi_string_set(ctx->file_path, value);
return true;
}
NfcGenericCallback protocol_poller_callbacks[NfcProtocolNum] = {
[NfcProtocolMfUltralight] = nfc_cli_dump_poller_callback_mf_ultralight,
[NfcProtocolMfClassic] = nfc_cli_dump_poller_callback_mf_classic,
[NfcProtocolFelica] = nfc_cli_dump_poller_callback_felica,
[NfcProtocolIso14443_3a] = nfc_cli_dump_poller_callback_iso14443_3a,
[NfcProtocolIso14443_3b] = nfc_cli_dump_poller_callback_iso14443_3b,
[NfcProtocolIso14443_4a] = nfc_cli_dump_poller_callback_iso14443_4a,
[NfcProtocolIso14443_4b] = nfc_cli_dump_poller_callback_iso14443_4b,
[NfcProtocolIso15693_3] = nfc_cli_dump_poller_callback_iso15693_3,
[NfcProtocolSlix] = nfc_cli_dump_poller_callback_slix,
[NfcProtocolMfDesfire] = nfc_cli_dump_poller_callback_mf_desfire,
[NfcProtocolMfPlus] = nfc_cli_dump_poller_callback_mf_plus,
[NfcProtocolSt25tb] = nfc_cli_dump_poller_callback_st25tb,
};
static void nfc_cli_dump_generate_filename(FuriString* file_path) {
furi_string_set_str(file_path, NFC_DEFAULT_FOLDER);
DateTime dt;
furi_hal_rtc_get_datetime(&dt);
furi_string_cat_printf(
file_path,
"/%s-%.4d%.2d%.2d-%.2d%.2d%.2d%s",
NFC_CLI_DEFAULT_FILENAME_PREFIX,
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
NFC_FILE_EXTENSION);
}
static bool nfc_cli_dump_check_filepath_valid(FuriString* file_path, Storage* storage) {
bool file_exists = false;
bool dir_exists = false;
FuriString* buf = furi_string_alloc();
path_extract_dirname(furi_string_get_cstr(file_path), buf);
dir_exists = storage_dir_exists(storage, furi_string_get_cstr(buf));
file_exists = storage_file_exists(storage, furi_string_get_cstr(file_path));
bool result = true;
if(!dir_exists) {
printf(ANSI_FG_RED "Path \'%s\' doesn't exist\r\n" ANSI_RESET, furi_string_get_cstr(buf));
result = false;
} else if(file_exists) {
printf(
ANSI_FG_RED "File \'%s\' already exists\r\n" ANSI_RESET,
furi_string_get_cstr(file_path));
result = false;
}
furi_string_free(buf);
return result;
}
static bool nfc_cli_dump_process_filename(NfcCliDumpContext* instance) {
bool result = false;
if(furi_string_empty(instance->file_path)) {
nfc_cli_dump_generate_filename(instance->file_path);
result = true;
} else {
result = nfc_cli_dump_check_filepath_valid(instance->file_path, instance->storage);
}
return result;
}
static size_t nfc_cli_dump_set_protocol(NfcCliDumpContext* instance) {
size_t protocol_count = 0;
if(instance->desired_protocol != NfcProtocolInvalid) {
protocol_count = 1;
} else {
if(!nfc_cli_scanner_detect_protocol(instance->scanner, instance->timeout)) {
NfcCliDumpError error = NfcCliDumpErrorTimeout;
printf(ANSI_FG_RED "Error: %s\r\n" ANSI_RESET, nfc_cli_dump_error_names[error]);
} else {
nfc_cli_scanner_list_detected_protocols(instance->scanner);
protocol_count = nfc_cli_scanner_detected_protocol_num(instance->scanner);
instance->desired_protocol = nfc_cli_scanner_get_protocol(instance->scanner, 0);
}
}
return protocol_count;
}
static bool nfc_cli_dump_card(NfcCliDumpContext* instance) {
instance->poller = nfc_poller_alloc(instance->nfc, instance->desired_protocol);
NfcGenericCallback callback = protocol_poller_callbacks[instance->desired_protocol];
if(callback) {
nfc_poller_start(instance->poller, callback, instance);
FuriStatus status = furi_semaphore_acquire(instance->sem_done, instance->timeout);
if(status == FuriStatusErrorTimeout) instance->result = NfcCliDumpErrorTimeout;
nfc_poller_stop(instance->poller);
}
nfc_poller_free(instance->poller);
return instance->result == NfcCliDumpErrorNone;
}
static void nfc_cli_dump_execute(PipeSide* pipe, NfcCliActionContext* context) {
UNUSED(pipe);
furi_assert(context);
NfcCliDumpContext* instance = context;
do {
if(!nfc_cli_dump_process_filename(instance)) break;
size_t protocol_count = nfc_cli_dump_set_protocol(instance);
if(instance->desired_protocol == NfcProtocolInvalid) break;
printf("Dumping as \"%s\"\r\n", nfc_cli_get_protocol_name(instance->desired_protocol));
if(protocol_count > 1) printf("Use \'-p\' key to specify another protocol\r\n");
if(nfc_cli_dump_card(instance)) {
const char* path = furi_string_get_cstr(instance->file_path);
if(nfc_device_save(instance->nfc_device, path)) {
printf("Dump saved to \'%s\'\r\n", path);
}
} else {
printf(
ANSI_FG_RED "Error: %s\r\n" ANSI_RESET,
nfc_cli_dump_error_names[instance->result]);
}
} while(false);
}
static const NfcProtocolNameValuePair supported_protocols[] = {
{.name = "14_3a", .value = NfcProtocolIso14443_3a},
{.name = "14_3b", .value = NfcProtocolIso14443_3b},
{.name = "14_4a", .value = NfcProtocolIso14443_4a},
{.name = "14_4b", .value = NfcProtocolIso14443_4b},
{.name = "15", .value = NfcProtocolIso15693_3},
{.name = "felica", .value = NfcProtocolFelica},
{.name = "mfu", .value = NfcProtocolMfUltralight},
{.name = "mfc", .value = NfcProtocolMfClassic},
{.name = "mfp", .value = NfcProtocolMfPlus},
{.name = "des", .value = NfcProtocolMfDesfire},
{.name = "slix", .value = NfcProtocolSlix},
{.name = "st25", .value = NfcProtocolSt25tb},
};
static bool nfc_cli_dump_parse_protocol(FuriString* value, void* output) {
furi_assert(value);
furi_assert(output);
NfcCliDumpContext* ctx = output;
NfcCliProtocolParser* parser =
nfc_cli_protocol_parser_alloc(supported_protocols, COUNT_OF(supported_protocols));
bool result = nfc_cli_protocol_parser_get(parser, value, &ctx->desired_protocol);
nfc_cli_protocol_parser_free(parser);
return result;
}
static bool nfc_cli_dump_parse_key(FuriString* value, void* output) {
furi_assert(value);
furi_assert(output);
NfcCliDumpContext* ctx = output;
NfcCliDumpAuthContext* auth_ctx = &ctx->auth_ctx;
bool result = false;
do {
size_t len = furi_string_size(value);
if(len % 2 != 0) break;
size_t data_length = len / 2;
if(data_length != MF_ULTRALIGHT_AUTH_PASSWORD_SIZE &&
data_length != MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE) {
printf(ANSI_FG_RED "Error: Wrong key size" ANSI_RESET);
break;
}
if(!args_read_hex_bytes(value, auth_ctx->key.key, data_length)) break;
auth_ctx->key_size = data_length;
auth_ctx->skip_auth = false;
result = true;
} while(false);
return result;
}
bool nfc_cli_dump_parse_timeout(FuriString* value, NfcCliActionContext* output) {
NfcCliDumpContext* ctx = output;
StrintParseError err = strint_to_uint32(furi_string_get_cstr(value), NULL, &ctx->timeout, 10);
return err == StrintParseNoError;
}
const NfcCliKeyDescriptor dump_keys[] = {
{
.long_name = "key",
.short_name = "k",
.description = "key to path auth in protocols which requires it",
.features = {.required = false, .parameter = true},
.parse = nfc_cli_dump_parse_key,
},
{
.long_name = "protocol",
.short_name = "p",
.description = "desired protocol",
.features = {.required = false, .parameter = true},
.parse = nfc_cli_dump_parse_protocol,
},
{
.features = {.required = false, .parameter = true},
.long_name = "file",
.short_name = "f",
.description = "path to new file",
.parse = nfc_cli_dump_parse_filename_key,
},
{
.features = {.required = false, .parameter = true},
.long_name = "timeout",
.short_name = "t",
.description = "timeout value in milliseconds",
.parse = nfc_cli_dump_parse_timeout,
},
};
const NfcCliActionDescriptor dump_action = {
.name = "dump",
.description = "Dump tag to .nfc file",
.alloc = nfc_cli_dump_alloc_ctx,
.free = nfc_cli_dump_free_ctx,
.execute = nfc_cli_dump_execute,
.key_count = COUNT_OF(dump_keys),
.keys = dump_keys,
};
const NfcCliActionDescriptor* dump_actions_collection[] = {&dump_action};
//Command descriptor
ADD_NFC_CLI_COMMAND(dump, "", dump_actions_collection);
//Command examples:
//dump -f ext/nfc/test.nfc

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor dump_cmd;

View File

@@ -0,0 +1,32 @@
#include "nfc_cli_dump_felica.h"
#include <nfc/protocols/felica/felica_poller.h>
NfcCommand nfc_cli_dump_poller_callback_felica(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolFelica);
NfcCliDumpContext* instance = context;
const FelicaPollerEvent* felica_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(felica_event->type == FelicaPollerEventTypeReady ||
felica_event->type == FelicaPollerEventTypeIncomplete) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller));
command = NfcCommandStop;
instance->result = NfcCliDumpErrorNone;
} else if(felica_event->type == FelicaPollerEventTypeError) {
command = NfcCommandStop;
instance->result = NfcCliDumpErrorFailedToRead;
} else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
FelicaAuthenticationContext* ctx = felica_event->data->auth_context;
const NfcCliDumpAuthContext* dump_auth_ctx = &instance->auth_ctx;
ctx->skip_auth = dump_auth_ctx->skip_auth;
ctx->card_key = dump_auth_ctx->key.felica_key;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_felica(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,25 @@
#include "nfc_cli_dump_iso14443_3a.h"
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
NfcCommand nfc_cli_dump_poller_callback_iso14443_3a(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_3a);
NfcCliDumpContext* instance = context;
const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolIso14443_3a, nfc_poller_get_data(instance->poller));
command = NfcCommandStop;
} else if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeError) {
command = NfcCommandStop;
instance->result = NfcCliDumpErrorFailedToRead;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_iso14443_3a(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,27 @@
#include "nfc_cli_dump_iso14443_3b.h"
#include <nfc/protocols/iso14443_3b/iso14443_3b_poller.h>
NfcCommand nfc_cli_dump_poller_callback_iso14443_3b(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_3b);
NfcCliDumpContext* instance = context;
const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolIso14443_3b, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
command = NfcCommandStop;
} else if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeError) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return NfcCommandContinue;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_iso14443_3b(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,26 @@
#include "nfc_cli_dump_iso14443_4a.h"
#include <nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
NfcCommand nfc_cli_dump_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_4a);
NfcCliDumpContext* instance = context;
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
command = NfcCommandStop;
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_iso14443_4a(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,25 @@
#include "nfc_cli_dump_iso14443_4b.h"
#include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
NfcCommand nfc_cli_dump_poller_callback_iso14443_4b(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_4b);
NfcCliDumpContext* instance = context;
const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(instance->poller));
command = NfcCommandStop;
} else if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeError) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_iso14443_4b(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,26 @@
#include "nfc_cli_dump_iso15693_3.h"
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
NfcCommand nfc_cli_dump_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso15693_3);
NfcCliDumpContext* instance = context;
const Iso15693_3PollerEvent* iso15693_3_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolIso15693_3, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
} else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_iso15693_3(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,54 @@
#include "nfc_cli_dump_mf_classic.h"
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
#define TAG "MFC"
NfcCommand nfc_cli_dump_poller_callback_mf_classic(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfClassic);
NfcCliDumpContext* instance = context;
const MfClassicPollerEvent* mfc_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller));
size_t uid_len = 0;
const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
if(mf_classic_key_cache_load(instance->mfc_key_cache, uid, uid_len)) {
FURI_LOG_D(TAG, "Key cache found");
mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
} else {
FURI_LOG_D(TAG, "Key cache not found");
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
} else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
uint8_t sector_num = 0;
MfClassicKey key = {};
MfClassicKeyType key_type = MfClassicKeyTypeA;
if(mf_classic_key_cache_get_next_key(
instance->mfc_key_cache, &sector_num, &key, &key_type)) {
mfc_event->data->read_sector_request_data.sector_num = sector_num;
mfc_event->data->read_sector_request_data.key = key;
mfc_event->data->read_sector_request_data.key_type = key_type;
mfc_event->data->read_sector_request_data.key_provided = true;
} else {
mfc_event->data->read_sector_request_data.key_provided = false;
}
} else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
command = NfcCommandStop;
} else if(mfc_event->type == MfClassicPollerEventTypeFail) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_mf_classic(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,27 @@
#include "nfc_cli_dump_mf_desfire.h"
#include <nfc/protocols/mf_desfire/mf_desfire_poller.h>
#define TAG "MFDES"
NfcCommand nfc_cli_dump_poller_callback_mf_desfire(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfDesfire);
NfcCommand command = NfcCommandContinue;
NfcCliDumpContext* instance = context;
const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
furi_semaphore_release(instance->sem_done);
command = NfcCommandStop;
} else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandReset;
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_mf_desfire(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,31 @@
#include "nfc_cli_dump_mf_plus.h"
#include <nfc/protocols/mf_plus/mf_plus_poller.h>
#define TAG "MFPLUS"
NfcCommand nfc_cli_dump_poller_callback_mf_plus(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.protocol == NfcProtocolMfPlus);
furi_assert(event.event_data);
NfcCliDumpContext* instance = context;
const MfPlusPollerEvent* mf_plus_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(mf_plus_event->type == MfPlusPollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfPlus, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
command = NfcCommandStop;
} else if(mf_plus_event->type == MfPlusPollerEventTypeReadFailed) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandReset;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_mf_plus(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,39 @@
#include "nfc_cli_dump_mf_ultralight.h"
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
#define TAG "MFU"
NfcCommand nfc_cli_dump_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfUltralight);
NfcCliDumpContext* instance = context;
const MfUltralightPollerEvent* mf_ultralight_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller));
const NfcCliDumpAuthContext* auth_ctx = &instance->auth_ctx;
mf_ultralight_event->data->auth_context.skip_auth = auth_ctx->skip_auth;
mf_ultralight_event->data->auth_context.password = auth_ctx->key.password;
mf_ultralight_event->data->auth_context.tdes_key = auth_ctx->key.tdes_key;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthFailed) {
instance->result = NfcCliDumpErrorAuthFailed;
command = NfcCommandStop;
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadFailed) {
instance->result = NfcCliDumpErrorAuthFailed;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_mf_ultralight(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,59 @@
#pragma once
#include <furi.h>
#include "../../../../helpers/mf_classic_key_cache.h"
#include "../../helpers/nfc_cli_scanner.h"
#include <nfc/nfc.h>
#include <nfc/protocols/nfc_protocol.h>
#include <nfc/nfc_device.h>
#include <nfc/nfc_poller.h>
#include <nfc/protocols/felica/felica.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <storage/storage.h>
#define NFC_CLI_DUMP_KEY_MAX_SIZE (16)
typedef union {
MfUltralightAuthPassword password;
FelicaCardKey felica_key;
MfUltralightC3DesAuthKey tdes_key;
uint8_t key[NFC_CLI_DUMP_KEY_MAX_SIZE];
} NfcCliDumpKeyUnion;
typedef struct {
NfcCliDumpKeyUnion key;
uint8_t key_size;
bool skip_auth;
} NfcCliDumpAuthContext;
typedef enum {
NfcCliDumpErrorNone,
NfcCliDumpErrorTimeout,
NfcCliDumpErrorNotPresent,
NfcCliDumpErrorFailedToRead,
NfcCliDumpErrorAuthFailed,
NfcCliDumpErrorNum,
} NfcCliDumpError;
typedef struct {
Nfc* nfc;
FuriString* file_path;
Storage* storage;
NfcCliScanner* scanner;
NfcProtocol desired_protocol;
uint32_t timeout;
FuriSemaphore* sem_done;
NfcCliDumpError result;
NfcCliDumpAuthContext auth_ctx;
MfClassicKeyCache* mfc_key_cache;
NfcPoller* poller;
NfcDevice* nfc_device;
} NfcCliDumpContext;

View File

@@ -0,0 +1,26 @@
#include "nfc_cli_dump_slix.h"
#include <nfc/protocols/slix/slix_poller.h>
NfcCommand nfc_cli_dump_poller_callback_slix(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolSlix);
NfcCliDumpContext* instance = context;
const SlixPollerEvent* slix_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(slix_event->type == SlixPollerEventTypeReady) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller));
command = NfcCommandStop;
} else if(slix_event->type == SlixPollerEventTypeError) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_slix(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,29 @@
#include "nfc_cli_dump_st25tb.h"
#include <nfc/protocols/st25tb/st25tb_poller.h>
NfcCommand nfc_cli_dump_poller_callback_st25tb(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolSt25tb);
NfcCliDumpContext* instance = context;
const St25tbPollerEvent* st25tb_event = event.event_data;
NfcCommand command = NfcCommandContinue;
if(st25tb_event->type == St25tbPollerEventTypeRequestMode) {
st25tb_event->data->mode_request.mode = St25tbPollerModeRead;
} else if(st25tb_event->type == St25tbPollerEventTypeSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller));
instance->result = NfcCliDumpErrorNone;
command = NfcCommandStop;
} else if(st25tb_event->type == St25tbPollerEventTypeFailure) {
instance->result = NfcCliDumpErrorFailedToRead;
command = NfcCommandStop;
}
if(command == NfcCommandStop) {
furi_semaphore_release(instance->sem_done);
}
return command;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_dump_common_types.h"
NfcCommand nfc_cli_dump_poller_callback_st25tb(NfcGenericEvent event, void* context);

View File

@@ -0,0 +1,61 @@
#include "nfc_cli_format.h"
static const char* protocol_names[NfcProtocolNum] = {
[NfcProtocolIso14443_3a] = "Iso14443-3a",
[NfcProtocolIso14443_3b] = "Iso14443-3b",
[NfcProtocolIso14443_4a] = "Iso14443-4a",
[NfcProtocolIso14443_4b] = "Iso14443-4b",
[NfcProtocolIso15693_3] = "Iso15693-3",
[NfcProtocolFelica] = "FeliCa",
[NfcProtocolMfUltralight] = "Mifare Ultralight",
[NfcProtocolMfClassic] = "Mifare Classic",
[NfcProtocolMfDesfire] = "Mifare DESFire",
[NfcProtocolMfPlus] = "Mifare Plus",
[NfcProtocolSlix] = "Slix",
[NfcProtocolSt25tb] = "St25tb",
};
const char* nfc_cli_get_protocol_name(NfcProtocol protocol) {
furi_assert(protocol < NfcProtocolNum);
return protocol_names[protocol];
}
static const char* mf_ultralight_error_names[] = {
[MfUltralightErrorNone] = "OK",
[MfUltralightErrorNotPresent] = "Card not present",
[MfUltralightErrorProtocol] = "Protocol failure",
[MfUltralightErrorAuth] = "Auth failed",
[MfUltralightErrorTimeout] = "Timeout",
};
const char* nfc_cli_mf_ultralight_get_error(MfUltralightError error) {
furi_assert(error < COUNT_OF(mf_ultralight_error_names));
return mf_ultralight_error_names[error];
}
void nfc_cli_format_array(
const uint8_t* data,
const size_t data_size,
const char* header,
FuriString* output) {
furi_assert(data);
furi_assert(data_size > 0);
furi_assert(header);
furi_assert(output);
furi_string_cat_printf(output, "%s", header);
for(size_t i = 0; i < data_size; i++) {
furi_string_cat_printf(output, "%02X ", data[i]);
}
}
void nfc_cli_printf_array(const uint8_t* data, const size_t data_size, const char* header) {
furi_assert(data);
furi_assert(data_size > 0);
furi_assert(header);
printf("%s", header);
for(size_t i = 0; i < data_size; i++) {
printf("%02X ", data[i]);
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <furi.h>
#include <nfc/protocols/nfc_protocol.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
const char* nfc_cli_get_protocol_name(NfcProtocol protocol);
const char* nfc_cli_mf_ultralight_get_error(MfUltralightError error);
void nfc_cli_format_array(
const uint8_t* data,
const size_t data_size,
const char* header,
FuriString* output);
void nfc_cli_printf_array(const uint8_t* data, const size_t data_size, const char* header);

View File

@@ -0,0 +1,58 @@
#include "nfc_cli_protocol_parser.h"
#include <m-bptree.h>
#define PROTOCOLS_TREE_RANK 4
BPTREE_DEF2(
ProtocolTree,
PROTOCOLS_TREE_RANK,
FuriString*,
FURI_STRING_OPLIST,
NfcProtocol,
M_POD_OPLIST);
#define M_OPL_ProtocolTree_t() BPTREE_OPLIST(ProtocolTree, M_POD_OPLIST)
struct NfcCliProtocolParser {
ProtocolTree_t protocols;
};
NfcCliProtocolParser* nfc_cli_protocol_parser_alloc(
const NfcProtocolNameValuePair* valid_protocols,
const size_t valid_count) {
furi_assert(valid_protocols);
furi_assert(valid_count > 0);
NfcCliProtocolParser* instance = malloc(sizeof(NfcCliProtocolParser));
FuriString* name = furi_string_alloc();
ProtocolTree_init(instance->protocols);
for(size_t i = 0; i < valid_count; i++) {
const NfcProtocolNameValuePair* item = &valid_protocols[i];
furi_string_set_str(name, item->name);
ProtocolTree_set_at(instance->protocols, name, item->value);
}
furi_string_free(name);
return instance;
}
void nfc_cli_protocol_parser_free(NfcCliProtocolParser* instance) {
furi_assert(instance);
ProtocolTree_clear(instance->protocols);
free(instance);
}
bool nfc_cli_protocol_parser_get(
NfcCliProtocolParser* instance,
FuriString* key,
NfcProtocol* result) {
furi_assert(instance);
furi_assert(key);
NfcProtocol* protocol = ProtocolTree_get(instance->protocols, key);
if(protocol) *result = *protocol;
return protocol != NULL;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <furi.h>
#include <nfc/protocols/nfc_protocol.h>
typedef struct {
const char* name;
NfcProtocol value;
} NfcProtocolNameValuePair;
typedef struct NfcCliProtocolParser NfcCliProtocolParser;
NfcCliProtocolParser* nfc_cli_protocol_parser_alloc(
const NfcProtocolNameValuePair* valid_protocols,
const size_t valid_count);
void nfc_cli_protocol_parser_free(NfcCliProtocolParser* instance);
bool nfc_cli_protocol_parser_get(
NfcCliProtocolParser* instance,
FuriString* key,
NfcProtocol* result);

View File

@@ -0,0 +1,96 @@
#include "nfc_cli_scanner.h"
#include <nfc/nfc_scanner.h>
#include "nfc_cli_format.h"
#define NFC_CLI_SCANNER_FLAG_DETECTED (1UL << 0)
struct NfcCliScanner {
Nfc* nfc;
size_t protocols_detected_num;
NfcProtocol protocols_detected[NfcProtocolNum];
FuriThreadId thread_id;
NfcScanner* scanner;
};
NfcCliScanner* nfc_cli_scanner_alloc(Nfc* nfc) {
NfcCliScanner* instance = malloc(sizeof(NfcCliScanner));
instance->nfc = nfc;
instance->thread_id = furi_thread_get_current_id();
return instance;
}
void nfc_cli_scanner_free(NfcCliScanner* instance) {
furi_assert(instance);
free(instance);
}
static void nfc_cli_scanner_detect_callback(NfcScannerEvent event, void* context) {
furi_assert(context);
NfcCliScanner* instance = context;
if(event.type == NfcScannerEventTypeDetected) {
instance->protocols_detected_num = event.data.protocol_num;
memcpy(
instance->protocols_detected,
event.data.protocols,
event.data.protocol_num * sizeof(NfcProtocol));
furi_thread_flags_set(instance->thread_id, NFC_CLI_SCANNER_FLAG_DETECTED);
}
}
bool nfc_cli_scanner_detect_protocol(NfcCliScanner* instance, uint32_t timeout) {
instance->scanner = nfc_scanner_alloc(instance->nfc);
nfc_scanner_start(instance->scanner, nfc_cli_scanner_detect_callback, instance);
uint32_t event =
furi_thread_flags_wait(NFC_CLI_SCANNER_FLAG_DETECTED, FuriFlagWaitAny, timeout);
nfc_scanner_stop(instance->scanner);
nfc_scanner_free(instance->scanner);
return (event == NFC_CLI_SCANNER_FLAG_DETECTED);
}
void nfc_cli_scanner_begin_scan(NfcCliScanner* instance) {
instance->scanner = nfc_scanner_alloc(instance->nfc);
nfc_scanner_start(instance->scanner, nfc_cli_scanner_detect_callback, instance);
}
bool nfc_cli_scanner_wait_scan(NfcCliScanner* instance, uint32_t timeout) {
UNUSED(instance);
uint32_t event =
furi_thread_flags_wait(NFC_CLI_SCANNER_FLAG_DETECTED, FuriFlagWaitAny, timeout);
return (event == NFC_CLI_SCANNER_FLAG_DETECTED);
}
void nfc_cli_scanner_end_scan(NfcCliScanner* instance) {
nfc_scanner_stop(instance->scanner);
nfc_scanner_free(instance->scanner);
}
void nfc_cli_scanner_list_detected_protocols(NfcCliScanner* instance) {
printf("Protocols detected: ");
size_t n = instance->protocols_detected_num;
for(size_t i = 0; i < n; i++) {
const char* name = nfc_cli_get_protocol_name(instance->protocols_detected[i]);
printf((i == (n - 1)) ? "%s\r\n" : "%s, ", name);
}
}
bool nfc_cli_scanner_protocol_was_detected(NfcCliScanner* instance, NfcProtocol protocol) {
furi_assert(instance);
furi_assert(protocol < NfcProtocolNum);
for(size_t i = 0; i < instance->protocols_detected_num; i++) {
if(instance->protocols_detected[i] == protocol) return true;
}
return false;
}
NfcProtocol nfc_cli_scanner_get_protocol(NfcCliScanner* instance, size_t idx) {
furi_assert(instance);
furi_assert(idx < instance->protocols_detected_num);
return instance->protocols_detected[idx];
}
size_t nfc_cli_scanner_detected_protocol_num(NfcCliScanner* instance) {
furi_assert(instance);
return instance->protocols_detected_num;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <furi.h>
#include <nfc.h>
#include <nfc/protocols/nfc_protocol.h>
typedef struct NfcCliScanner NfcCliScanner;
NfcCliScanner* nfc_cli_scanner_alloc(Nfc* nfc);
void nfc_cli_scanner_free(NfcCliScanner* instance);
bool nfc_cli_scanner_detect_protocol(NfcCliScanner* instance, uint32_t timeout);
void nfc_cli_scanner_begin_scan(NfcCliScanner* instance);
bool nfc_cli_scanner_wait_scan(NfcCliScanner* instance, uint32_t timeout);
void nfc_cli_scanner_end_scan(NfcCliScanner* instance);
void nfc_cli_scanner_list_detected_protocols(NfcCliScanner* instance);
size_t nfc_cli_scanner_detected_protocol_num(NfcCliScanner* instance);
bool nfc_cli_scanner_protocol_was_detected(NfcCliScanner* instance, NfcProtocol protocol);
NfcProtocol nfc_cli_scanner_get_protocol(NfcCliScanner* instance, size_t idx);

View File

@@ -0,0 +1,227 @@
#include "nfc_cli_action_info.h"
#include "../../../helpers/protocol_support/mf_ultralight/mf_ultralight_render.h"
#include "../helpers/nfc_cli_format.h"
#include <furi.h>
#include <flipper_format/flipper_format.h>
#include <storage/storage.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#define TAG "INFO"
typedef struct {
uint8_t magic;
union {
uint8_t value;
struct {
uint8_t minor : 4;
uint8_t major : 4;
};
} version;
uint8_t size;
union {
uint8_t value;
struct {
uint8_t write : 4;
uint8_t read : 4;
};
} access;
} FURI_PACKED MfUltralightCapabilityContainer;
typedef struct {
Nfc* nfc;
MfUltralightData* data;
} NfcCliMfuContext;
static void nfc_cli_mfu_info_get_vendor(const uint8_t vendor_key, FuriString* output) {
furi_assert(output);
FuriString* buf = furi_string_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_existing(ff, EXT_PATH("nfc/assets/vendors.nfc"))) {
FURI_LOG_W(TAG, "NFC Vendors dict not found");
break;
}
char uid_str[5];
snprintf(uid_str, sizeof(uid_str), "%d", vendor_key);
if(flipper_format_read_string(ff, uid_str, buf))
furi_string_printf(output, "%s, %s", uid_str, furi_string_get_cstr(buf));
else
furi_string_printf(output, "unknown");
} while(false);
flipper_format_free(ff);
furi_record_close(RECORD_STORAGE);
furi_string_free(buf);
}
const char*
nfc_cli_mfu_capability_container_get_access_description(const uint8_t value, bool read) {
const char* description = "RFU"; //value 0x01 - 0x07, and 0xF when read
if(value == 0x00)
description = "access fully granted";
else if(value >= 0x08 && value <= 0x0E)
description = "proprietary";
else if(value == 0x0F && !read)
description = "no access granted at all";
return description;
}
static void nfc_cli_mfu_info_print_common(const MfUltralightData* data) {
FuriString* str = furi_string_alloc();
printf(ANSI_FG_GREEN "\r\n\tTag information\r\n" ANSI_RESET);
printf(
"Type: " ANSI_FG_YELLOW "%s\r\n" ANSI_RESET,
mf_ultralight_get_device_name(data, NfcDeviceNameTypeFull));
nfc_cli_mfu_info_get_vendor(data->iso14443_3a_data->uid[0], str);
printf("Vendor ID: %s\r\n", furi_string_get_cstr(str));
furi_string_reset(str);
nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, str);
printf("%s\r\n", furi_string_get_cstr(str));
printf("BCC0: %02X\r\nBCC1: %02X\r\n", data->page[0].data[3], data->page[2].data[0]);
furi_string_free(str);
}
static void nfc_cli_mfu_info_print_ndef(const MfUltralightData* data) {
const MfUltralightCapabilityContainer* cc =
(const MfUltralightCapabilityContainer*)data->page[3].data;
if(cc->magic == 0xE1) {
printf(ANSI_FG_GREEN "\r\n\tNDEF Message\r\n" ANSI_RESET);
nfc_cli_printf_array(data->page[3].data, 4, "Capability container: ");
printf(
"\r\nMagic number: %02X\r\nVersion %d.%d\r\nSize: [%02X] - %d bytes\r\n",
cc->magic,
cc->version.major,
cc->version.minor,
cc->size,
cc->size * 8);
printf(
"Access read: [%02X] - %s",
cc->access.read,
nfc_cli_mfu_capability_container_get_access_description(cc->access.read, true));
printf(
"Access write: [%02X] - %s",
cc->access.write,
nfc_cli_mfu_capability_container_get_access_description(cc->access.write, false));
}
}
static void nfc_cli_mfu_info_print_counter(const MfUltralightData* data) {
uint32_t features = mf_ultralight_get_feature_support_set(data->type);
if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportReadCounter)) return;
printf(ANSI_FG_GREEN "\r\n\n\tTag counters\r\n" ANSI_RESET);
uint8_t i =
mf_ultralight_support_feature(features, MfUltralightFeatureSupportSingleCounter) ? 2 : 0;
for(; i < MF_ULTRALIGHT_COUNTER_NUM; i++) {
printf("Counter [%d]: ", i);
nfc_cli_printf_array(data->counter[i].data, MF_ULTRALIGHT_COUNTER_SIZE, "");
printf(" Value: %lu\r\n", data->counter[i].counter);
const uint8_t tf = data->tearing_flag[i].data;
printf(
"Tearing [%d]: [%02X] %s",
i,
tf,
tf == MF_ULTRALIGHT_TEARING_FLAG_DEFAULT ? "(ok)" : "");
}
}
static void nfc_cli_mfu_info_print_signature(const MfUltralightData* data) {
uint32_t features = mf_ultralight_get_feature_support_set(data->type);
if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportReadSignature)) return;
const MfUltralightSignature* signature = &data->signature;
printf(ANSI_FG_GREEN "\r\n\n\tTag signature\r\n" ANSI_RESET);
nfc_cli_printf_array(signature->data, sizeof(signature->data), "ECC signature: ");
}
static void nfc_cli_mfu_info_print_version_storage_size(uint8_t storage_size) {
uint16_t max_size = 1 << ((storage_size >> 1) + 1);
uint16_t min_exact_size = 1 << (storage_size >> 1);
bool exact_size = !(storage_size & 0x01);
if(exact_size)
printf("[%02X], (%u bytes)", storage_size, min_exact_size);
else
printf("[%02X], (%u <-> %u bytes)", storage_size, min_exact_size, max_size);
}
static void nfc_cli_mfu_info_print_version(const MfUltralightData* data) {
uint32_t features = mf_ultralight_get_feature_support_set(data->type);
if(!mf_ultralight_support_feature(features, MfUltralightFeatureSupportReadVersion)) return;
const MfUltralightVersion* version = &data->version;
printf(ANSI_FG_GREEN "\r\n\n\tTag Version\r\n" ANSI_RESET);
nfc_cli_printf_array((uint8_t*)version, sizeof(MfUltralightVersion), "Raw bytes: ");
FuriString* str = furi_string_alloc();
nfc_cli_mfu_info_get_vendor(version->vendor_id, str);
printf("\r\nVendor ID: %s\r\n", furi_string_get_cstr(str));
furi_string_free(str);
printf("Product type: %02X\r\n", version->prod_type);
printf(
"Protocol type: %02X%s\r\n",
version->protocol_type,
(version->protocol_type == 0x3) ? ", ISO14443-3 Compliant" : "");
printf(
"Product subtype: [%02X], %s\r\n",
version->prod_subtype,
(version->prod_subtype == 1) ? "17 pF" : "50pF");
printf(
"Major version: %02X\r\nMinor version: %02X\r\nSize: ",
version->prod_ver_major,
version->prod_ver_minor);
nfc_cli_mfu_info_print_version_storage_size(version->storage_size);
}
NfcCliActionContext* nfc_cli_mfu_info_alloc_ctx(Nfc* nfc) {
NfcCliMfuContext* instance = malloc(sizeof(NfcCliMfuContext));
instance->nfc = nfc;
instance->data = mf_ultralight_alloc();
return instance;
}
void nfc_cli_mfu_info_free_ctx(NfcCliActionContext* ctx) {
NfcCliMfuContext* instance = ctx;
mf_ultralight_free(instance->data);
free(instance);
}
void nfc_cli_mfu_info_execute(PipeSide* pipe, NfcCliActionContext* ctx) {
furi_assert(pipe);
furi_assert(ctx);
NfcCliMfuContext* instance = ctx;
MfUltralightError error =
mf_ultralight_poller_sync_read_card(instance->nfc, instance->data, NULL);
if(error == MfUltralightErrorNone) {
const MfUltralightData* data = instance->data;
nfc_cli_mfu_info_print_common(data);
nfc_cli_mfu_info_print_ndef(data);
nfc_cli_mfu_info_print_counter(data);
nfc_cli_mfu_info_print_signature(data);
nfc_cli_mfu_info_print_version(data);
} else {
printf(ANSI_FG_RED "Error: %s" ANSI_RESET, nfc_cli_mf_ultralight_get_error(error));
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
NfcCliActionContext* nfc_cli_mfu_info_alloc_ctx(Nfc* nfc);
void nfc_cli_mfu_info_free_ctx(NfcCliActionContext* ctx);
void nfc_cli_mfu_info_execute(PipeSide* pipe, NfcCliActionContext* ctx);

View File

@@ -0,0 +1,52 @@
#include "nfc_cli_action_rdbl.h"
#include "../helpers/nfc_cli_format.h"
#include <furi.h>
#include <toolbox/strint.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0)
typedef struct {
Nfc* nfc;
uint16_t block;
} NfcCliMfuRdblContext;
NfcCliActionContext* nfc_cli_mfu_rdbl_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliMfuRdblContext* instance = malloc(sizeof(NfcCliMfuRdblContext));
instance->nfc = nfc;
return instance;
}
void nfc_cli_mfu_rdbl_free_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliMfuRdblContext* instance = ctx;
free(instance);
}
void nfc_cli_mfu_rdbl_execute(PipeSide* pipe, NfcCliActionContext* ctx) {
furi_assert(pipe);
NfcCliMfuRdblContext* instance = ctx;
MfUltralightPage page = {0};
MfUltralightError error =
mf_ultralight_poller_sync_read_page(instance->nfc, instance->block, &page);
if(error == MfUltralightErrorNone) {
printf("\r\nBlock: %d ", instance->block);
nfc_cli_printf_array(page.data, sizeof(MfUltralightPage), "Data: ");
printf("\r\n");
} else {
printf(ANSI_FG_RED "Error: %s" ANSI_RESET, nfc_cli_mf_ultralight_get_error(error));
}
}
bool nfc_cli_mfu_rdbl_parse_block(FuriString* value, NfcCliActionContext* output) {
NfcCliMfuRdblContext* ctx = output;
StrintParseError err = strint_to_uint16(furi_string_get_cstr(value), NULL, &ctx->block, 10);
return err == StrintParseNoError;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
NfcCliActionContext* nfc_cli_mfu_rdbl_alloc_ctx(Nfc* nfc);
void nfc_cli_mfu_rdbl_free_ctx(NfcCliActionContext* ctx);
void nfc_cli_mfu_rdbl_execute(PipeSide* pipe, NfcCliActionContext* ctx);
bool nfc_cli_mfu_rdbl_parse_block(FuriString* value, NfcCliActionContext* ctx);

View File

@@ -0,0 +1,71 @@
#include "nfc_cli_action_rdbl.h"
#include "../helpers/nfc_cli_format.h"
#include <furi.h>
#include <toolbox/args.h>
#include <toolbox/strint.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#define MF_ULTRALIGHT_POLLER_COMPLETE_EVENT (1UL << 0)
typedef struct {
Nfc* nfc;
uint16_t block;
MfUltralightPage page;
} NfcCliMfuWrblContext;
NfcCliActionContext* nfc_cli_mfu_wrbl_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliMfuWrblContext* instance = malloc(sizeof(NfcCliMfuWrblContext));
instance->nfc = nfc;
return instance;
}
void nfc_cli_mfu_wrbl_free_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliMfuWrblContext* instance = ctx;
free(instance);
}
void nfc_cli_mfu_wrbl_execute(PipeSide* pipe, NfcCliActionContext* ctx) {
furi_assert(pipe);
NfcCliMfuWrblContext* instance = ctx;
MfUltralightError error =
mf_ultralight_poller_sync_write_page(instance->nfc, instance->block, &instance->page);
if(error == MfUltralightErrorNone) {
printf(ANSI_FG_BR_GREEN "\r\nSuccess\r\n" ANSI_RESET);
printf("Block: %d ", instance->block);
nfc_cli_printf_array(instance->page.data, sizeof(MfUltralightPage), "Data: ");
printf("\r\n");
} else {
printf(ANSI_FG_RED "Error: %s" ANSI_RESET, nfc_cli_mf_ultralight_get_error(error));
}
}
bool nfc_cli_mfu_wrbl_parse_block(FuriString* value, NfcCliActionContext* output) {
NfcCliMfuWrblContext* ctx = output;
StrintParseError err = strint_to_uint16(furi_string_get_cstr(value), NULL, &ctx->block, 10);
return err == StrintParseNoError;
}
bool nfc_cli_mfu_wrbl_parse_data(FuriString* value, void* output) {
NfcCliMfuWrblContext* ctx = output;
bool result = false;
do {
size_t len = furi_string_size(value);
if(len % 2 != 0) break;
size_t data_length = len / 2;
if(data_length != MF_ULTRALIGHT_PAGE_SIZE) break;
result = args_read_hex_bytes(value, ctx->page.data, data_length);
} while(false);
return result;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
NfcCliActionContext* nfc_cli_mfu_wrbl_alloc_ctx(Nfc* nfc);
void nfc_cli_mfu_wrbl_free_ctx(NfcCliActionContext* ctx);
void nfc_cli_mfu_wrbl_execute(PipeSide* pipe, NfcCliActionContext* ctx);
bool nfc_cli_mfu_wrbl_parse_block(FuriString* value, NfcCliActionContext* ctx);
bool nfc_cli_mfu_wrbl_parse_data(FuriString* value, void* output);

View File

@@ -0,0 +1,77 @@
#include "nfc_cli_command_mfu.h"
#include "nfc_cli_action_info.h"
#include "nfc_cli_action_rdbl.h"
#include "nfc_cli_action_wrbl.h"
#define TAG "MFU"
//mfu info
const NfcCliActionDescriptor info_action = {
.name = "info",
.description = "Get basic information about the card",
.alloc = nfc_cli_mfu_info_alloc_ctx,
.free = nfc_cli_mfu_info_free_ctx,
.execute = nfc_cli_mfu_info_execute,
.key_count = 0,
.keys = NULL,
};
const NfcCliKeyDescriptor rdbl_action_keys[] = {
{
.short_name = "b",
.long_name = "block",
.features = {.required = true, .parameter = true},
.description = "desired block number",
.parse = nfc_cli_mfu_rdbl_parse_block,
},
};
//mfu rdbl -b 0
//mfu rdbl --block 0
const NfcCliActionDescriptor rdbl_action = {
.name = "rdbl",
.description = "Read block from ultralight card",
.alloc = nfc_cli_mfu_rdbl_alloc_ctx,
.free = nfc_cli_mfu_rdbl_free_ctx,
.execute = nfc_cli_mfu_rdbl_execute,
.key_count = COUNT_OF(rdbl_action_keys),
.keys = rdbl_action_keys,
};
const NfcCliKeyDescriptor wrbl_action_keys[] = {
{
.short_name = "b",
.long_name = "block",
.features = {.required = true, .parameter = true},
.description = "desired block number",
.parse = nfc_cli_mfu_wrbl_parse_block,
},
{
.short_name = "d",
.long_name = "data",
.features = {.required = true, .parameter = true},
.description = "new data for block",
.parse = nfc_cli_mfu_wrbl_parse_data,
},
};
//mfu wrbl -b 0 -d DEADBEAF
//mfu rdbl --block 0 -- data DEADBEEF
const NfcCliActionDescriptor wrbl_action = {
.name = "wrbl",
.description = "Read block from ultralight card",
.alloc = nfc_cli_mfu_wrbl_alloc_ctx,
.free = nfc_cli_mfu_wrbl_free_ctx,
.execute = nfc_cli_mfu_wrbl_execute,
.key_count = COUNT_OF(wrbl_action_keys),
.keys = wrbl_action_keys,
};
const NfcCliActionDescriptor* mfu_actions[] = {
&rdbl_action,
&info_action,
&wrbl_action,
};
//Command descriptor
ADD_NFC_CLI_COMMAND(mfu, "Mifare Ultralight specific commands", mfu_actions);

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor mfu_cmd;

View File

@@ -0,0 +1,126 @@
#include "nfc_cli_command_emulate.h"
#include "helpers/nfc_cli_format.h"
#include <nfc.h>
#include <nfc_listener.h>
#include <nfc_device.h>
#include <storage/storage.h>
typedef struct {
Nfc* nfc;
NfcDevice* nfc_device;
FuriString* file_path;
Storage* storage;
} NfcCliEmulateContext;
static NfcCliActionContext* nfc_cli_emulate_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliEmulateContext* instance = malloc(sizeof(NfcCliEmulateContext));
instance->nfc = nfc;
instance->file_path = furi_string_alloc();
instance->nfc_device = nfc_device_alloc();
instance->storage = furi_record_open(RECORD_STORAGE);
return instance;
}
static void nfc_cli_emulate_free_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliEmulateContext* instance = ctx;
furi_record_close(RECORD_STORAGE);
furi_string_free(instance->file_path);
nfc_device_free(instance->nfc_device);
free(instance);
}
static const NfcProtocol supported_protocols[] = {
NfcProtocolIso14443_3a,
NfcProtocolIso14443_4a,
NfcProtocolIso15693_3,
NfcProtocolMfUltralight,
NfcProtocolMfClassic,
NfcProtocolSlix,
NfcProtocolFelica,
};
static bool nfc_cli_emulate_protocol_supports_emulation(NfcProtocol protocol) {
for(size_t i = 0; i < COUNT_OF(supported_protocols); i++) {
if(supported_protocols[i] == protocol) return true;
}
return false;
}
static void nfc_cli_emulate_execute(PipeSide* pipe, NfcCliActionContext* context) {
UNUSED(pipe);
furi_assert(context);
NfcCliEmulateContext* instance = context;
do {
const char* path = furi_string_get_cstr(instance->file_path);
if(!storage_common_exists(instance->storage, path)) {
printf(ANSI_FG_RED "Wrong path \'%s\'.\r\n" ANSI_RESET, path);
break;
}
if(!nfc_device_load(instance->nfc_device, path)) {
printf(ANSI_FG_RED "Failed to load \'%s\'.\r\n" ANSI_RESET, path);
break;
}
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
if(!nfc_cli_emulate_protocol_supports_emulation(protocol)) {
printf(
ANSI_FG_RED "Error. Emulation for %s is not supported\r\n" ANSI_RESET,
nfc_cli_get_protocol_name(protocol));
break;
}
const NfcDeviceData* data = nfc_device_get_data(instance->nfc_device, protocol);
NfcListener* listener = nfc_listener_alloc(instance->nfc, protocol, data);
nfc_listener_start(listener, NULL, NULL);
printf("\r\nEmulating. Press Ctrl+C to abort\r\n");
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(100);
}
nfc_listener_stop(listener);
nfc_listener_free(listener);
} while(false);
}
static bool nfc_cli_emulate_parse_filename_key(FuriString* value, void* output) {
furi_assert(value);
furi_assert(output);
NfcCliEmulateContext* ctx = output;
furi_string_set(ctx->file_path, value);
return true;
}
const NfcCliKeyDescriptor emulate_keys[] = {
{
.features = {.required = true, .parameter = true},
.long_name = "file",
.short_name = "f",
.description = "path to new file",
.parse = nfc_cli_emulate_parse_filename_key,
},
};
const NfcCliActionDescriptor emulate_action = {
.name = "emulate",
.description = "Emulate .nfc file content",
.alloc = nfc_cli_emulate_alloc_ctx,
.free = nfc_cli_emulate_free_ctx,
.execute = nfc_cli_emulate_execute,
.key_count = COUNT_OF(emulate_keys),
.keys = emulate_keys,
};
const NfcCliActionDescriptor* emulate_actions_collection[] = {&emulate_action};
//Command descriptor
ADD_NFC_CLI_COMMAND(emulate, "", emulate_actions_collection);
//Command usage: emulate [-f <file>]
//Command examples:
//emulate -f ext/nfc/test.nfc

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor emulate_cmd;

View File

@@ -0,0 +1,28 @@
#include "nfc_cli_command_field.h"
#include <furi_hal_nfc.h>
static void nfc_cli_field(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(args);
UNUSED(context);
furi_hal_nfc_low_power_mode_stop();
furi_hal_nfc_poller_field_on();
printf("Field is on. Don't leave device in this mode for too long.\r\n");
printf("Press Ctrl+C to abort\r\n");
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(50);
}
furi_hal_nfc_low_power_mode_start();
}
const NfcCliCommandDescriptor field_cmd = {
.name = "field",
.description = "Turns NFC field on",
.callback = nfc_cli_field,
.action_count = 0,
.actions = NULL,
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor field_cmd;

View File

@@ -0,0 +1,97 @@
#include "nfc_cli_command_scanner.h"
#include "helpers/nfc_cli_scanner.h"
#include "helpers/nfc_cli_format.h"
typedef struct {
NfcCliScanner* scanner;
bool display_tree;
} NfcCliCmdScannerContext;
static NfcCliActionContext* nfc_cli_command_scanner_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliCmdScannerContext* instance = malloc(sizeof(NfcCliCmdScannerContext));
instance->scanner = nfc_cli_scanner_alloc(nfc);
instance->display_tree = false;
return instance;
}
static void nfc_cli_command_scanner_free_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliCmdScannerContext* instance = ctx;
nfc_cli_scanner_free(instance->scanner);
free(instance);
}
static void
nfc_cli_command_scanner_format_protocol_tree(NfcProtocol protocol, FuriString* output) {
const char* names[10] = {0};
uint8_t cnt = 0;
while(protocol != NfcProtocolInvalid) {
names[cnt++] = nfc_cli_get_protocol_name(protocol);
protocol = nfc_protocol_get_parent(protocol);
}
for(int8_t i = cnt - 1; i >= 0; i--) {
furi_string_cat_printf(output, (i == 0) ? "%s" : "%s -> ", names[i]);
}
}
static void nfc_cli_command_scanner_format_detected_protocols(NfcCliScanner* instance) {
FuriString* str = furi_string_alloc();
printf("Protocols detected: \r\n");
for(size_t i = 0; i < nfc_cli_scanner_detected_protocol_num(instance); i++) {
furi_string_reset(str);
NfcProtocol protocol = nfc_cli_scanner_get_protocol(instance, i);
nfc_cli_command_scanner_format_protocol_tree(protocol, str);
printf("Protocol [%zu]: %s\r\n", i + 1, furi_string_get_cstr(str));
}
furi_string_free(str);
}
static void nfc_cli_command_scanner_execute(PipeSide* pipe, void* context) {
NfcCliCmdScannerContext* instance = context;
printf("Press Ctrl+C to abort\r\n\n");
nfc_cli_scanner_begin_scan(instance->scanner);
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) &&
!nfc_cli_scanner_wait_scan(instance->scanner, 50))
;
nfc_cli_scanner_end_scan(instance->scanner);
if(!instance->display_tree)
nfc_cli_scanner_list_detected_protocols(instance->scanner);
else
nfc_cli_command_scanner_format_detected_protocols(instance->scanner);
}
static bool nfc_cli_command_scanner_parse_tree(FuriString* value, void* output) {
UNUSED(value);
NfcCliCmdScannerContext* ctx = output;
ctx->display_tree = true;
return true;
}
const NfcCliKeyDescriptor tree_key = {
.short_name = "t",
.long_name = "tree",
.features = {.parameter = false, .required = false, .multivalue = false},
.description = "displays protocol hierarchy for each detected protocol",
.parse = nfc_cli_command_scanner_parse_tree,
};
const NfcCliActionDescriptor scanner_action = {
.name = "scanner",
.description = "Detect tag type",
.key_count = 1,
.keys = &tree_key,
.execute = nfc_cli_command_scanner_execute,
.alloc = nfc_cli_command_scanner_alloc_ctx,
.free = nfc_cli_command_scanner_free_ctx,
};
const NfcCliActionDescriptor* scanner_actions_collection[] = {&scanner_action};
ADD_NFC_CLI_COMMAND(scanner, "", scanner_actions_collection);

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor scanner_cmd;

View File

@@ -0,0 +1,352 @@
#include "nfc_cli_command_raw.h"
#include "../helpers/nfc_cli_format.h"
#include "../helpers/nfc_cli_protocol_parser.h"
#include "protocol_handlers/nfc_cli_raw_common_types.h"
#include "protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h"
#include "protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h"
#include "protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h"
#include "protocol_handlers/felica/nfc_cli_raw_felica.h"
#include <toolbox/args.h>
#define NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE (256)
#define TAG "RAW"
typedef enum {
NfcCliProtocolRequestTypeNormalExecute,
NfcCliProtocolRequestTypeAbort,
} NfcCliProtocolRequestType;
typedef enum {
NfcPollerStateStopped,
NfcPollerStateStarted,
} NfcPollerState;
typedef NfcCommand (*NfcCliRawProtocolSpecificHandler)(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response);
typedef struct {
Nfc* nfc;
NfcCliRawRequest request;
NfcCliRawResponse response;
NfcPoller* poller;
NfcPollerState poller_state;
NfcCliProtocolRequestType request_type;
FuriMessageQueue* input_queue;
FuriSemaphore* sem_done;
} NfcCliRawCmdContext;
static const char* raw_error_names[] = {
[NfcCliRawErrorNone] = "None",
[NfcCliRawErrorTimeout] = "Timeout",
[NfcCliRawErrorProtocol] = "Internal protocol",
[NfcCliRawErrorWrongCrc] = "Wrong CRC",
[NfcCliRawErrorNotPresent] = "No card",
};
static NfcCliActionContext* nfc_cli_raw_alloc_ctx(Nfc* nfc) {
furi_assert(nfc);
NfcCliRawCmdContext* instance = malloc(sizeof(NfcCliRawCmdContext));
instance->nfc = nfc;
instance->request.protocol = NfcProtocolInvalid;
instance->request.tx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE);
instance->response.rx_buffer = bit_buffer_alloc(NFC_CLI_PROTOCOL_SUPPORT_MAX_BUFFER_SIZE);
instance->input_queue = furi_message_queue_alloc(5, sizeof(NfcCliProtocolRequestType));
instance->sem_done = furi_semaphore_alloc(1, 0);
instance->response.activation_string = furi_string_alloc();
instance->request.timeout = 0;
return instance;
}
static void nfc_cli_raw_abort_nfc_thread(NfcCliRawCmdContext* instance) {
if(instance->poller_state == NfcPollerStateStarted) {
instance->request_type = NfcCliProtocolRequestTypeAbort;
furi_message_queue_put(instance->input_queue, &instance->request_type, FuriWaitForever);
furi_semaphore_acquire(instance->sem_done, FuriWaitForever);
instance->poller_state = NfcPollerStateStopped;
}
if(instance->poller) nfc_poller_stop(instance->poller);
}
static void nfc_cli_raw_free_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliRawCmdContext* instance = ctx;
nfc_cli_raw_abort_nfc_thread(instance);
if(instance->poller) nfc_poller_free(instance->poller);
furi_message_queue_free(instance->input_queue);
furi_semaphore_free(instance->sem_done);
furi_string_free(instance->response.activation_string);
bit_buffer_free(instance->response.rx_buffer);
bit_buffer_free(instance->request.tx_buffer);
instance->nfc = NULL;
free(instance);
}
static bool nfc_cli_raw_can_reuse_ctx(NfcCliActionContext* ctx) {
furi_assert(ctx);
NfcCliRawCmdContext* instance = ctx;
NfcCliRawRequest* request = &instance->request;
bool result = request->keep_field;
request->keep_field = false;
request->append_crc = false;
request->select = false;
instance->request.timeout = 0;
return result;
}
const NfcCliRawProtocolSpecificHandler nfc_cli_raw_protocol_handlers[] = {
[NfcProtocolIso14443_3a] = nfc_cli_raw_iso14443_3a_handler,
[NfcProtocolIso14443_3b] = nfc_cli_raw_iso14443_3b_handler,
[NfcProtocolIso14443_4a] = NULL,
[NfcProtocolIso14443_4b] = NULL,
[NfcProtocolIso15693_3] = nfc_cli_raw_iso15693_3_handler,
[NfcProtocolFelica] = nfc_cli_raw_felica_handler,
[NfcProtocolMfUltralight] = NULL,
[NfcProtocolMfClassic] = NULL,
[NfcProtocolMfDesfire] = NULL,
[NfcProtocolSlix] = NULL,
[NfcProtocolSt25tb] = NULL,
};
static NfcCommand nfc_cli_raw_poller_callback(NfcGenericEventEx event, void* context) {
NfcEvent* nfc_event = event.parent_event_data;
NfcCliRawCmdContext* instance = context;
NfcCommand command = NfcCommandContinue;
if(nfc_event->type == NfcEventTypePollerReady) {
FURI_LOG_D(TAG, "Poller callback");
NfcCliProtocolRequestType request_type = NfcCliProtocolRequestTypeAbort;
furi_message_queue_get(instance->input_queue, &request_type, FuriWaitForever);
if(request_type == NfcCliProtocolRequestTypeAbort) {
command = NfcCommandStop;
} else {
const NfcCliRawProtocolSpecificHandler handler =
nfc_cli_raw_protocol_handlers[instance->request.protocol];
if(handler) handler(event.poller, &instance->request, &instance->response);
}
}
furi_semaphore_release(instance->sem_done);
if(command == NfcCommandStop) {
FURI_LOG_D(TAG, "Aborting poller callback");
instance->poller_state = NfcPollerStateStopped;
}
return command;
}
static inline void nfc_cli_raw_print_result(const NfcCliRawCmdContext* instance) {
if(!furi_string_empty(instance->response.activation_string))
printf("%s\r\n", furi_string_get_cstr(instance->response.activation_string));
nfc_cli_printf_array(
bit_buffer_get_data(instance->request.tx_buffer),
bit_buffer_get_size_bytes(instance->request.tx_buffer),
"Tx: ");
if(instance->response.result != NfcCliRawErrorNone)
printf("\r\nError: \"%s\"\r\n", raw_error_names[instance->response.result]);
size_t rx_size = bit_buffer_get_size_bytes(instance->response.rx_buffer);
if(rx_size > 0) {
nfc_cli_printf_array(
bit_buffer_get_data(instance->response.rx_buffer),
bit_buffer_get_size_bytes(instance->response.rx_buffer),
"\r\nRx: ");
}
}
static void nfc_cli_raw_execute(PipeSide* pipe, void* context) {
UNUSED(pipe);
furi_assert(context);
NfcCliRawCmdContext* instance = context;
furi_string_reset(instance->response.activation_string);
if(instance->poller_state == NfcPollerStateStopped) {
if(instance->poller == NULL)
instance->poller = nfc_poller_alloc(instance->nfc, instance->request.protocol);
nfc_poller_start_ex(instance->poller, nfc_cli_raw_poller_callback, instance);
instance->poller_state = NfcPollerStateStarted;
}
instance->request_type = NfcCliProtocolRequestTypeNormalExecute;
furi_message_queue_put(instance->input_queue, &instance->request_type, FuriWaitForever);
furi_semaphore_acquire(instance->sem_done, FuriWaitForever);
nfc_cli_raw_print_result(instance);
}
static const NfcProtocolNameValuePair supported_protocols[] = {
{.name = "14a", .value = NfcProtocolIso14443_3a},
{.name = "iso14a", .value = NfcProtocolIso14443_3a},
{.name = "14b", .value = NfcProtocolIso14443_3b},
{.name = "iso14b", .value = NfcProtocolIso14443_3b},
{.name = "15", .value = NfcProtocolIso15693_3},
{.name = "felica", .value = NfcProtocolFelica},
};
static bool nfc_cli_raw_parse_protocol(FuriString* value, void* output) {
NfcCliRawCmdContext* ctx = output;
NfcProtocol new_protocol = NfcProtocolInvalid;
NfcCliProtocolParser* parser =
nfc_cli_protocol_parser_alloc(supported_protocols, COUNT_OF(supported_protocols));
bool result = nfc_cli_protocol_parser_get(parser, value, &new_protocol);
nfc_cli_protocol_parser_free(parser);
if(result && ctx->request.protocol != NfcProtocolInvalid &&
ctx->request.protocol != new_protocol) {
printf(
ANSI_FG_RED "Error: previous %s != new %s. Unable to continue." ANSI_RESET,
nfc_cli_get_protocol_name(ctx->request.protocol),
nfc_cli_get_protocol_name(new_protocol));
result = false;
}
if(result) {
ctx->request.protocol = new_protocol;
}
return result;
}
static bool nfc_cli_raw_parse_data(FuriString* value, void* output) {
NfcCliRawCmdContext* ctx = output;
bool result = false;
do {
size_t len = furi_string_size(value);
if(len % 2 != 0) break;
size_t data_length = len / 2;
uint8_t* data = malloc(data_length);
if(args_read_hex_bytes(value, data, data_length)) {
bit_buffer_reset(ctx->request.tx_buffer);
bit_buffer_copy_bytes(ctx->request.tx_buffer, data, data_length);
result = true;
}
free(data);
} while(false);
return result;
}
static bool nfc_cli_raw_parse_timeout(FuriString* value, void* output) {
furi_assert(value);
furi_assert(output);
NfcCliRawCmdContext* ctx = output;
bool result = false;
int timeout = 0;
if(args_read_int_and_trim(value, &timeout)) {
ctx->request.timeout = timeout;
result = true;
}
return result;
}
static bool nfc_cli_raw_parse_select(FuriString* value, void* output) {
UNUSED(value);
NfcCliRawCmdContext* ctx = output;
ctx->request.select = true;
return true;
}
static bool nfc_cli_raw_parse_crc(FuriString* value, void* output) {
UNUSED(value);
NfcCliRawCmdContext* ctx = output;
ctx->request.append_crc = true;
return true;
}
static bool nfc_cli_raw_parse_keep(FuriString* value, void* output) {
UNUSED(value);
NfcCliRawCmdContext* ctx = output;
ctx->request.keep_field = true;
return true;
}
const NfcCliKeyDescriptor raw_action_keys[] = {
{
.long_name = NULL,
.short_name = "t",
.features = {.parameter = true, .required = false},
.description = "timeout in fc",
.parse = nfc_cli_raw_parse_timeout,
},
{
.long_name = NULL,
.short_name = "k",
.description = "keep signal field ON after receive",
.parse = nfc_cli_raw_parse_keep,
},
{
.long_name = NULL,
.short_name = "c",
.description = "calculate and append CRC",
.parse = nfc_cli_raw_parse_crc,
},
{
.long_name = NULL,
.short_name = "s",
.description = "Select on FieldOn",
.parse = nfc_cli_raw_parse_select,
},
{
.long_name = "protocol",
.short_name = "p",
.description = "desired protocol. Possible values: 14a, iso14a, 14b, iso14b, 15, felica",
.features = {.parameter = true, .required = true},
.parse = nfc_cli_raw_parse_protocol,
},
{
.long_name = "data",
.short_name = "d",
.description = "Raw bytes to send in HEX format",
.features = {.parameter = true, .required = true},
.parse = nfc_cli_raw_parse_data,
},
};
const NfcCliActionDescriptor raw_action = {
.name = "raw",
.description = "Sends raw bytes using different protocols",
.key_count = COUNT_OF(raw_action_keys),
.keys = raw_action_keys,
.execute = nfc_cli_raw_execute,
.alloc = nfc_cli_raw_alloc_ctx,
.free = nfc_cli_raw_free_ctx,
.can_reuse = nfc_cli_raw_can_reuse_ctx,
};
const NfcCliActionDescriptor* raw_actions_collection[] = {&raw_action};
ADD_NFC_CLI_COMMAND(raw, "", raw_actions_collection);
//Command usage: raw <protocol> [keys] <data>
//Command examples:
//raw iso14a -sc 3000
//raw iso14a 3000
//raw iso14a 3000 -sc

View File

@@ -0,0 +1,5 @@
#pragma once
#include "../../nfc_cli_command_base_i.h"
extern const NfcCliCommandDescriptor raw_cmd;

View File

@@ -0,0 +1,101 @@
#include "nfc_cli_raw_felica.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/helpers/felica_crc.h>
#include <nfc/protocols/felica/felica.h>
#include <nfc/protocols/felica/felica_poller_i.h>
#define TAG "FELICA"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static inline void felica_format_activation_data(const FelicaData* data, FuriString* output) {
nfc_cli_format_array(data->idm.data, FELICA_IDM_SIZE, "IDm: ", output);
nfc_cli_format_array(data->pmm.data, FELICA_PMM_SIZE, " PMm: ", output);
}
static NfcCliRawError nfc_cli_raw_felica_process_error(FelicaError error) {
switch(error) {
case FelicaErrorNone:
return NfcCliRawErrorNone;
case FelicaErrorTimeout:
return NfcCliRawErrorTimeout;
case FelicaErrorWrongCrc:
return NfcCliRawErrorWrongCrc;
case FelicaErrorNotPresent:
return NfcCliRawErrorNotPresent;
default:
return NfcCliRawErrorProtocol;
}
}
static FelicaError nfc_cli_raw_felica_poller_process_error(NfcError error) {
switch(error) {
case NfcErrorNone:
return FelicaErrorNone;
case NfcErrorTimeout:
return FelicaErrorTimeout;
default:
return FelicaErrorNotPresent;
}
}
static inline NfcCliRawError
nfc_cli_raw_felica_activate(NfcGenericInstance* poller, FuriString* activation_string) {
FelicaData felica_data = {};
FelicaPoller* felica_poller = poller;
FURI_LOG_D(TAG, "Activating...");
FelicaError error = felica_poller_activate(felica_poller, &felica_data);
if(error == FelicaErrorNone) {
felica_format_activation_data(&felica_data, activation_string);
}
return nfc_cli_raw_felica_process_error(error);
}
static inline NfcCliRawError nfc_cli_raw_felica_txrx(
NfcGenericInstance* poller,
BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t timeout) {
FURI_LOG_D(TAG, "TxRx");
FelicaPoller* felica_poller = poller;
bit_buffer_reset(rx_buffer);
FelicaError error = FelicaErrorNone;
NfcError nfc_error = nfc_poller_trx(felica_poller->nfc, tx_buffer, rx_buffer, timeout);
if(nfc_error != NfcErrorNone) {
error = nfc_cli_raw_felica_poller_process_error(nfc_error);
} else if(!felica_crc_check(rx_buffer)) {
error = FelicaErrorWrongCrc;
}
return nfc_cli_raw_felica_process_error(error);
}
NfcCommand nfc_cli_raw_felica_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response) {
do {
if(request->select) {
response->result = nfc_cli_raw_felica_activate(poller, response->activation_string);
}
if(response->result != NfcCliRawErrorNone) break;
if(BIT_BUFFER_EMPTY(request->tx_buffer)) break;
if(request->append_crc) {
FURI_LOG_D(TAG, "Add CRC");
felica_crc_append(request->tx_buffer);
}
uint32_t timeout = request->timeout > 0 ? request->timeout : FELICA_FDT_POLL_FC;
response->result =
nfc_cli_raw_felica_txrx(poller, request->tx_buffer, response->rx_buffer, timeout);
} while(false);
return request->keep_field ? NfcCommandContinue : NfcCommandStop;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "../nfc_cli_raw_common_types.h"
NfcCommand nfc_cli_raw_felica_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response);

View File

@@ -0,0 +1,81 @@
#include "nfc_cli_raw_iso14443_3a.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/helpers/iso14443_crc.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
#define TAG "ISO14A"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static NfcCliRawError nfc_cli_raw_iso14443_3a_process_error(Iso14443_3aError error) {
switch(error) {
case Iso14443_3aErrorNone:
return NfcCliRawErrorNone;
case Iso14443_3aErrorTimeout:
return NfcCliRawErrorTimeout;
case Iso14443_3aErrorWrongCrc:
return NfcCliRawErrorWrongCrc;
case Iso14443_3aErrorNotPresent:
return NfcCliRawErrorNotPresent;
default:
return NfcCliRawErrorProtocol;
}
}
static void iso14443_3a_format_activation_data(const Iso14443_3aData* data, FuriString* output) {
nfc_cli_format_array(data->uid, data->uid_len, "UID: ", output);
furi_string_cat_printf(
output, " ATQA: %02X%02X SAK: %02X", data->atqa[0], data->atqa[1], data->sak);
}
static inline NfcCliRawError
nfc_cli_raw_iso14443_3a_activate(NfcGenericInstance* poller, FuriString* activation_string) {
Iso14443_3aData iso3_data = {};
FURI_LOG_D(TAG, "Activating...");
Iso14443_3aError error = iso14443_3a_poller_activate(poller, &iso3_data);
if(error == Iso14443_3aErrorNone)
iso14443_3a_format_activation_data(&iso3_data, activation_string);
return nfc_cli_raw_iso14443_3a_process_error(error);
}
static inline NfcCliRawError nfc_cli_raw_iso14443_3a_txrx(
NfcGenericInstance* poller,
BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t timeout) {
FURI_LOG_D(TAG, "TxRx");
bit_buffer_reset(rx_buffer);
Iso14443_3aError error = iso14443_3a_poller_txrx(poller, tx_buffer, rx_buffer, timeout);
return nfc_cli_raw_iso14443_3a_process_error(error);
}
NfcCommand nfc_cli_raw_iso14443_3a_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response) {
do {
response->result = NfcCliRawErrorNone;
if(request->select) {
response->result =
nfc_cli_raw_iso14443_3a_activate(poller, response->activation_string);
}
if(response->result != NfcCliRawErrorNone) break;
if(BIT_BUFFER_EMPTY(request->tx_buffer)) break;
if(request->append_crc) {
FURI_LOG_D(TAG, "Add CRC");
iso14443_crc_append(Iso14443CrcTypeA, request->tx_buffer);
}
uint32_t timeout = request->timeout > 0 ? request->timeout : ISO14443_3A_FDT_LISTEN_FC;
response->result =
nfc_cli_raw_iso14443_3a_txrx(poller, request->tx_buffer, response->rx_buffer, timeout);
} while(false);
return request->keep_field ? NfcCommandContinue : NfcCommandStop;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "../nfc_cli_raw_common_types.h"
NfcCommand nfc_cli_raw_iso14443_3a_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response);

View File

@@ -0,0 +1,121 @@
#include "nfc_cli_raw_iso14443_3b.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/helpers/iso14443_crc.h>
#include <nfc/protocols/iso14443_3b/iso14443_3b_i.h>
#include <nfc/protocols/iso14443_3b/iso14443_3b_poller_i.h>
#define TAG "ISO14B"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static NfcCliRawError nfc_cli_raw_iso14443_3b_process_error(Iso14443_3bError error) {
switch(error) {
case Iso14443_3bErrorNone:
return NfcCliRawErrorNone;
case Iso14443_3bErrorTimeout:
return NfcCliRawErrorTimeout;
case Iso14443_3bErrorWrongCrc:
return NfcCliRawErrorWrongCrc;
case Iso14443_3bErrorNotPresent:
return NfcCliRawErrorNotPresent;
default:
return NfcCliRawErrorProtocol;
}
}
static Iso14443_3bError nfc_cli_raw_iso14443_3b_poller_process_error(NfcError error) {
switch(error) {
case NfcErrorNone:
return Iso14443_3bErrorNone;
case NfcErrorTimeout:
return Iso14443_3bErrorTimeout;
default:
return Iso14443_3bErrorNotPresent;
}
}
static void iso14443_3b_format_activation_data(const Iso14443_3bData* data, FuriString* output) {
nfc_cli_format_array(data->uid, ISO14443_3B_UID_SIZE, "UID: ", output);
const Iso14443_3bProtocolInfo* info = &data->protocol_info;
furi_string_cat_printf(
output,
" BitRate: %d, Protocol: %d, Max Frame Size: %d, Fo: %d, Adc: %d, Fwi: %d",
info->bit_rate_capability,
info->protocol_type,
info->max_frame_size,
info->fo,
info->adc,
info->fwi);
}
static inline NfcCliRawError nfc_cli_raw_iso14443_3b_activate(
NfcGenericInstance* poller,
Iso14443_3bData* iso3b_data,
FuriString* activation_string) {
FURI_LOG_D(TAG, "Activating...");
Iso14443_3bError error = iso14443_3b_poller_activate(poller, iso3b_data);
if(error == Iso14443_3bErrorNone)
iso14443_3b_format_activation_data(iso3b_data, activation_string);
return nfc_cli_raw_iso14443_3b_process_error(error);
}
static inline NfcCliRawError nfc_cli_raw_iso14443_3b_txrx(
NfcGenericInstance* poller,
BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t timeout) {
FURI_LOG_D(TAG, "TxRx");
Iso14443_3bPoller* iso14b_poller = poller;
bit_buffer_reset(rx_buffer);
Iso14443_3bError error = Iso14443_3bErrorNone;
NfcError nfc_error = nfc_poller_trx(iso14b_poller->nfc, tx_buffer, rx_buffer, timeout);
if(nfc_error != NfcErrorNone) {
error = nfc_cli_raw_iso14443_3b_poller_process_error(nfc_error);
} else if(!iso14443_crc_check(Iso14443CrcTypeB, rx_buffer)) {
error = Iso14443_3bErrorWrongCrc;
}
return nfc_cli_raw_iso14443_3b_process_error(error);
}
NfcCommand nfc_cli_raw_iso14443_3b_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response) {
Iso14443_3bData iso3b_data = {0};
bool activated = false;
do {
response->result = NfcCliRawErrorNone;
if(request->select) {
response->result =
nfc_cli_raw_iso14443_3b_activate(poller, &iso3b_data, response->activation_string);
activated = response->result == NfcCliRawErrorNone;
}
if(response->result != NfcCliRawErrorNone) break;
if(BIT_BUFFER_EMPTY(request->tx_buffer)) break;
uint32_t timeout = ISO14443_3B_FDT_POLL_FC;
if(request->timeout > 0) {
timeout = request->timeout;
} else if(activated) {
timeout = iso14443_3b_get_fwt_fc_max(&iso3b_data);
}
if(request->append_crc) {
FURI_LOG_D(TAG, "Add CRC");
iso14443_crc_append(Iso14443CrcTypeB, request->tx_buffer);
}
response->result =
nfc_cli_raw_iso14443_3b_txrx(poller, request->tx_buffer, response->rx_buffer, timeout);
} while(false);
return request->keep_field ? NfcCommandContinue : NfcCommandStop;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "../nfc_cli_raw_common_types.h"
NfcCommand nfc_cli_raw_iso14443_3b_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response);

View File

@@ -0,0 +1,102 @@
#include "nfc_cli_raw_iso15693_3.h"
#include "../../../helpers/nfc_cli_format.h"
#include <nfc/helpers/iso13239_crc.h>
#include <nfc/protocols/iso15693_3/iso15693_3.h>
#include <nfc/protocols/iso15693_3/iso15693_3_poller_i.h>
#define TAG "ISO15"
#define BIT_BUFFER_EMPTY(buffer) ((bit_buffer_get_size_bytes(buffer) == 0))
static NfcCliRawError nfc_cli_raw_iso15693_3_process_error(Iso15693_3Error error) {
switch(error) {
case Iso15693_3ErrorNone:
return NfcCliRawErrorNone;
case Iso15693_3ErrorTimeout:
return NfcCliRawErrorTimeout;
case Iso15693_3ErrorWrongCrc:
return NfcCliRawErrorWrongCrc;
case Iso15693_3ErrorNotPresent:
return NfcCliRawErrorNotPresent;
default:
return NfcCliRawErrorProtocol;
}
}
static Iso15693_3Error nfc_cli_raw_iso15693_3_poller_process_nfc_error(NfcError error) {
switch(error) {
case NfcErrorNone:
return Iso15693_3ErrorNone;
case NfcErrorTimeout:
return Iso15693_3ErrorTimeout;
default:
return Iso15693_3ErrorNotPresent;
}
}
static inline void iso15693_3_format_activation_data(const uint8_t* data, FuriString* output) {
nfc_cli_format_array(data, ISO15693_3_UID_SIZE, "UID: ", output);
}
static inline NfcCliRawError
nfc_cli_raw_iso15693_3_activate(NfcGenericInstance* poller, FuriString* activation_string) {
FURI_LOG_D(TAG, "Activating...");
Iso15693_3Poller* iso15_poller = poller;
uint8_t uid[ISO15693_3_UID_SIZE] = {0};
Iso15693_3Error error = iso15693_3_poller_inventory(iso15_poller, uid);
if(error == Iso15693_3ErrorNone) {
iso15693_3_format_activation_data(uid, activation_string);
}
return nfc_cli_raw_iso15693_3_process_error(error);
}
static inline NfcCliRawError nfc_cli_raw_iso15693_3_txrx(
NfcGenericInstance* poller,
BitBuffer* tx_buffer,
BitBuffer* rx_buffer,
uint32_t timeout) {
FURI_LOG_D(TAG, "TxRx");
Iso15693_3Poller* iso15_poller = poller;
bit_buffer_reset(rx_buffer);
Iso15693_3Error error = Iso15693_3ErrorNone;
NfcError nfc_error = nfc_poller_trx(iso15_poller->nfc, tx_buffer, rx_buffer, timeout);
if(nfc_error != NfcErrorNone) {
error = nfc_cli_raw_iso15693_3_poller_process_nfc_error(nfc_error);
} else if(!iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) {
error = Iso15693_3ErrorWrongCrc;
}
return nfc_cli_raw_iso15693_3_process_error(error);
}
NfcCommand nfc_cli_raw_iso15693_3_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response) {
do {
if(request->select) {
response->result =
nfc_cli_raw_iso15693_3_activate(poller, response->activation_string);
}
if(response->result != NfcCliRawErrorNone) break;
if(BIT_BUFFER_EMPTY(request->tx_buffer)) break;
if(request->append_crc) {
FURI_LOG_D(TAG, "Add CRC");
iso13239_crc_append(Iso13239CrcTypeDefault, request->tx_buffer);
}
uint32_t timeout = request->timeout > 0 ? request->timeout : ISO15693_3_FDT_POLL_FC;
response->result =
nfc_cli_raw_iso15693_3_txrx(poller, request->tx_buffer, response->rx_buffer, timeout);
} while(false);
return request->keep_field ? NfcCommandContinue : NfcCommandStop;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "../nfc_cli_raw_common_types.h"
NfcCommand nfc_cli_raw_iso15693_3_handler(
NfcGenericInstance* poller,
const NfcCliRawRequest* request,
NfcCliRawResponse* const response);

View File

@@ -0,0 +1,28 @@
#pragma once
#include <furi.h>
#include <nfc/nfc.h>
#include <nfc/nfc_poller.h>
typedef enum {
NfcCliRawErrorNone,
NfcCliRawErrorTimeout,
NfcCliRawErrorNotPresent,
NfcCliRawErrorWrongCrc,
NfcCliRawErrorProtocol,
} NfcCliRawError;
typedef struct {
bool select;
bool keep_field;
bool append_crc;
NfcProtocol protocol;
BitBuffer* tx_buffer;
uint32_t timeout;
} NfcCliRawRequest;
typedef struct {
NfcCliRawError result;
BitBuffer* rx_buffer;
FuriString* activation_string;
} NfcCliRawResponse;

View File

@@ -0,0 +1,124 @@
#include "nfc_cli_commands.h"
#include "nfc_cli_command_processor.h"
#include "applications/services/loader/loader.h"
#include "applications/services/cli/cli_main_commands.h"
#include <toolbox/cli/shell/cli_shell.h>
#include <toolbox/cli/cli_registry.h>
#define NFC_DESKTOP_APP_NAME "NFC"
#define TAG "NfcCli"
#define NFC_PROMPT "[" ANSI_FG_GREEN "nfc" ANSI_RESET "]"
typedef struct {
Nfc* nfc;
CliRegistry* registry;
CliShell* shell;
NfcCliProcessorContext* processor_context;
} NfcCliContext;
static void nfc_cli_shell_motd(void* context) {
UNUSED(context);
printf(ANSI_FG_BR_BLUE "\r\n"
" 0000 \r\n"
" 0000 \r\n"
" 000 0000 \r\n"
" 0000 00000 \r\n"
" 000 00000 0000 \r\n"
" 0 0000 0000 00000 \r\n"
" 000000 0000 00000 0000 \r\n"
" 00000000 0000 0000 0000 \r\n"
" 0000000000 0000 00000 0000 \r\n"
" 0000 00000000 00000 00000 0000 \r\n"
" 0000 0000000 00000 00000 0000 \r\n"
" 0000 000000000000 0000 0000 \r\n"
" 00000 000000000 00000 0000 \r\n"
" 00 000000 0000 00000 \r\n"
" 00 00000 0000 \r\n"
" 0000 00000 \r\n"
" 000 0000 \r\n"
" 0000 \r\n"
" 0005 \r\n"
"\r\n" ANSI_FG_BR_WHITE "Welcome to NFC Command Line Interface!\r\n"
"Run `help` or `?` to list available commands\r\n" ANSI_RESET);
}
static void nfc_cli_subscribe_commands(NfcCliContext* instance) {
size_t cnt = nfc_cli_command_get_count();
for(size_t i = 0; i < cnt; i++) {
const NfcCliCommandDescriptor* cmd = nfc_cli_command_get_by_index(i);
CliCommandExecuteCallback callback = nfc_cli_command_get_execute(cmd);
if(callback == NULL) continue;
const char* name = nfc_cli_command_get_name(cmd);
cli_registry_add_command(
instance->registry,
name,
CliCommandFlagParallelSafe,
callback,
instance->processor_context);
}
}
static bool nfc_cli_desktop_app_is_running() {
FuriString* app_name = furi_string_alloc();
Loader* ldr = furi_record_open(RECORD_LOADER);
bool result = false;
if(loader_get_application_name(ldr, app_name)) {
result = furi_string_equal_str(app_name, NFC_DESKTOP_APP_NAME);
}
furi_record_close(RECORD_LOADER);
furi_string_free(app_name);
return result;
}
static NfcCliContext* nfc_cli_alloc(PipeSide* pipe) {
NfcCliContext* instance = malloc(sizeof(NfcCliContext));
instance->nfc = nfc_alloc();
instance->processor_context = nfc_cli_command_processor_alloc(instance->nfc);
instance->registry = cli_registry_alloc();
nfc_cli_subscribe_commands(instance);
instance->shell =
cli_shell_alloc(nfc_cli_shell_motd, instance, pipe, instance->registry, NULL);
cli_shell_set_prompt(instance->shell, NFC_PROMPT);
return instance;
}
void nfc_cli_free(NfcCliContext* instance) {
furi_assert(instance);
nfc_cli_command_processor_free(instance->processor_context);
cli_shell_free(instance->shell);
cli_registry_free(instance->registry);
nfc_free(instance->nfc);
free(instance);
}
void nfc_cli_execute(PipeSide* pipe, FuriString* args, void* context) {
furi_assert(pipe);
UNUSED(args);
UNUSED(context);
if(nfc_cli_desktop_app_is_running()) {
printf(ANSI_FG_YELLOW
"NFC app is running, unable to run NFC CLI at the same time!\r\n" ANSI_RESET);
return;
}
NfcCliContext* instance = nfc_cli_alloc(pipe);
cli_shell_start(instance->shell);
cli_shell_join(instance->shell);
nfc_cli_free(instance);
}
CLI_COMMAND_INTERFACE(nfc, nfc_cli_execute, CliCommandFlagParallelSafe, 1024, CLI_APPID);

View File

@@ -0,0 +1,69 @@
#pragma once
#include <furi.h>
#include <toolbox/cli/cli_command.h>
#include <nfc/nfc.h>
/**
* @brief Type for action context to be created before action execution
* must be hanlded through callbacks in each action separately
*/
typedef void NfcCliActionContext;
/**
* @brief Callback type for function of action context allocation
* @param nfc Instance of NFC subsystem, will be used during action execution
* @return Pointer to action context
*/
typedef NfcCliActionContext* (*NfcCliActionContextAlloc)(Nfc* nfc);
/**
* @brief Callback for action context deleting
* @param action_ctx Action context to be freed
*/
typedef void (*NfcCliActionContextFree)(NfcCliActionContext* action_ctx);
/**
* @brief Callback invoked by command processor to determine whether already
* existing context (from previously executed command) can be reused for the new one.
*
* @param action_ctx Action context
*
* In most cases re-creating of a new context is not needed.
* It is used in 'raw' command, where nfc field sometimes need to stay turned on between
* commands.
*
* Handling of this situation and decision about reusing action context is on developer,
* who need to decide, can this command reuse context.
*
* It can be done by comparing parameters of previously executed command and a new one,
* or by some args in command.
*
* See implementation of 'keep_field' flag in 'raw' command for example.
*/
typedef bool (*NfcCliActionContextCanReuse)(NfcCliActionContext* ctx);
/**
* @brief Action execution callback
* @param pipe provided by cli shell, can be used for command termination
* @param ctx Action context
*/
typedef void (*NfcCliActionHandlerCallback)(PipeSide* pipe, NfcCliActionContext* ctx);
/**
* @brief Callback used for parsing argument key.
* Each key added to command must have this, otherwise parsing result will always be
* false and command will never be executed.
*
* @param value Text value for the key to be parsed
* @param ctx Action context
* @return true when parsing was fine, otherwise false. If any argument in command input
* generates false during parsing, command will not be executed and error will be shown
*/
typedef bool (*NfcCliArgParseCallback)(FuriString* value, NfcCliActionContext* ctx);
typedef struct NfcCliKeyDescriptor NfcCliKeyDescriptor;
typedef struct NfcCliActionDescriptor NfcCliActionDescriptor;
typedef struct NfcCliCommandDescriptor NfcCliCommandDescriptor;

View File

@@ -0,0 +1,130 @@
#pragma once
#include "nfc_cli_command_base.h"
#include <toolbox/cli/cli_ansi.h>
#include <nfc/nfc.h>
#include <nfc/protocols/nfc_protocol.h>
#include "nfc_cli_command_processor.h"
/**
* @brief How to add command.
*
* There are 3 possible option on how to add new command to nfc_cli:
*
* @see Option 1 "Add action command directly to nfc_shell"
*
* In this case command will be invoked with argument string from nfc_shell.
* Command registration must be performed directly by user.
*
* Steps:
* 1. Add new function for command to nfc_cli.c
* 2. In nfc_cli_alloc function register command using cli_registry_add_command after nfc_cli_subscribe_commands
*
* This option is NOT RECOMENDED, because such command will not have any 'help'
* processing and parsing error checks. Argument parsing must also be done by hand.
*
* --------------------------------------------------------------------------
*
* @see Option 2 "Add action command to collection without further processing"
*
* In this case command will be invoked with argument string from nfc_shell.
* nfc_cli_command_processor is skipped, so argument handling is up to the developer.
*
* Steps:
* 1. Add new pair of nfc_cli_command_<cmd>.c/.h files to /commands folder
* 2. Define const NfcCliCommandDescriptor instance in .c file and its extern definition in .h file
* 3. Include .h file to nfc_cli_commands.c file below comment "Include new commands here"
* 4. Add new command reference to nfc_cli_commands array
* 5. Add path to nfc_cli_command_<cmd>.c file into 'cli_nfc' plugin in application.fam file
*
* This option suites for simple commands with no any parameters.
* @see nfc_cli_command_field.c implementation as an example.
*
* --------------------------------------------------------------------------
*
* @see Option 3 "Add action command to collection with full processing"
*
* In this nfc_cli_command_processor will be invoked for parsing command arguments
* and action execution. Also it will handle errors and help printing.
*
* Steps:
* 1. Add new pair of nfc_cli_command_<cmd>.c/.h files to /commands folder
* 2. Use macro ADD_NFC_CLI_COMMAND to define command in .c file
* 3. Define command extern in .h file, using command name from macro in form of "<name>_cmd"
* 4. Add all desired actions and keys to your command
* 5. Include .h file to nfc_cli_commands.c file below comment "Include new commands here"
* 6. Add new command reference to nfc_cli_commands array
* 7. Add path to nfc_cli_command_<cmd>.c file into 'cli_nfc' plugin in application.fam file
*
* This option suites for "difficult" commands which has actions with lots of keys.
* @see nfc_cli_command_emulate.c implementation as an example.
*
*/
/**
* @brief Used to decorate argument with some properties
*/
typedef struct {
bool required : 1; /**< Command always needs this argument. Missing arguments with this set to true will result execution error.*/
bool parameter : 1; /**< Such argument requires value after its name, otherwise it is a simple on/off switch */
bool multivalue : 1; /**< Such argument can take multiple values after its name, like this "-key value1 value2 .. valueN" */
} FURI_PACKED NfcCliKeyFeatureSupport;
/**
* @brief Describes key for action
*/
struct NfcCliKeyDescriptor {
NfcCliKeyFeatureSupport features; /**< Features supported defining key behaviour */
const char* long_name; /**< Long key name starts with '--' symbol in argument string */
const char* short_name; /**< Short key name starts with '-' symbol in argument string */
const char* description; /**< Key description showed in help */
NfcCliArgParseCallback parse; /**< Parsing callback */
};
/**
* @brief Describes action
*/
struct NfcCliActionDescriptor {
const char* name; /**< Action name MUST be the first argument after command.*/
const char* description; /**< Description showed in help */
size_t key_count; /**< Amount of key entries in keys array */
const NfcCliKeyDescriptor* keys; /**< Keys available for action */
NfcCliActionHandlerCallback execute; /**< Action callback, invoked if parsing is ok */
NfcCliActionContextAlloc alloc; /**< Allocates action context during command processing */
NfcCliActionContextFree free; /**< Frees action context */
NfcCliActionContextCanReuse can_reuse; /**< Checks context reuse possibility */
};
/**
* @brief Describes command
*/
struct NfcCliCommandDescriptor {
const char* name; /** Used to register command in cli shell */
const char* description; /**< Description showed in help */
size_t action_count; /** Amount of actions available in scope of this particular command */
const NfcCliActionDescriptor** actions; /**< Actions available for command */
CliCommandExecuteCallback callback; /** Entry point for command */
};
/**
* @brief This macro simplifies command creation. It fills instance of
* NfcCliCommandDescriptor and generates a callback which invokes
* nfc_cli_command_processor inside
*/
#define ADD_NFC_CLI_COMMAND(name, description, actions) \
static void nfc_cli_command_##name##_callback( \
PipeSide* pipe, FuriString* args, void* context); \
\
const NfcCliCommandDescriptor name##_cmd = { \
#name, \
#description, \
COUNT_OF(actions), \
actions, \
nfc_cli_command_##name##_callback, \
}; \
\
static void nfc_cli_command_##name##_callback( \
PipeSide* pipe, FuriString* args, void* context) { \
nfc_cli_command_processor_run(&name##_cmd, pipe, args, context); \
}

View File

@@ -0,0 +1,388 @@
#include "nfc_cli_command_processor.h"
#include "nfc_cli_commands.h"
#include "nfc_cli_command_base_i.h"
#include <m-string.h>
#include <args.h>
#include <hex.h>
#define TAG "NfcCliProcessor"
#define NFC_CLI_KEYS_FOUND_SIZE_BYTES (10 * sizeof(NfcCliKeyDescriptor*))
typedef enum {
NfcCliArgumentTypeShortNameKey,
NfcCliArgumentTypeShortNameKeyGroup,
NfcCliArgumentTypeLongNameKey,
NfcCliArgumentTypeUnknown
} NfcCliArgumentType;
/**
* @brief Error codes for different processing states
*/
typedef enum {
NfcCliProcessorErrorNone, /**< Command was parsed successfully and execute callback will be invoked*/
NfcCliProcessorErrorNoneButHelp, /**< There was no error, but help needs to be printed. Command wil not be executed */
NfcCliProcessorErrorActionNotFound, /**< Wrong action was passed as first command parameter */
NfcCliProcessorErrorKeyNotSupported, /**< Unsupported key was passed in arguments. Details will be printed in erro_message*/
NfcCliProcessorErrorKeyParameterInGroup, /**< Parameter which requires value was passed in group. Example: -sckd */
NfcCliProcessorErrorKeyParameterValueMissing, /**< Value is missing for the parameter which requires it */
NfcCliProcessorErrorKeyDuplication, /**< Some argument key was duplicated in input parameters */
NfcCliProcessorErrorKeyParseError, /**< Error happened during argument value parsing */
NfcCliProcessorErrorKeyRequiredMissing, /**< Some keys required for command execution is missing*/
NfcCliProcessorErrorNum
} NfcCliProcessorError;
struct NfcCliProcessorContext {
const NfcCliCommandDescriptor* cmd;
const NfcCliActionDescriptor* action;
const NfcCliKeyDescriptor** keys_found;
uint8_t total_keys_found;
uint8_t required_keys_expected;
uint8_t required_keys_found;
Nfc* nfc;
void* action_context;
FuriString* error_message;
};
static const NfcCliActionDescriptor*
nfc_cli_get_action_from_args(const NfcCliCommandDescriptor* cmd, FuriString* args) {
const NfcCliActionDescriptor* action = cmd->actions[0];
bool multiple_action_cmd = nfc_cli_command_has_multiple_actions(cmd);
if(multiple_action_cmd) {
action = NULL;
FuriString* arg_str = furi_string_alloc();
if(args_read_string_and_trim(args, arg_str)) {
action = nfc_cli_command_get_action_by_name(cmd, arg_str);
}
furi_string_free(arg_str);
}
return action;
}
static bool nfc_cli_action_can_reuse_context(
NfcCliProcessorContext* instance,
const NfcCliActionDescriptor* new_action) {
bool result = false;
do {
if(instance->action != new_action) break;
if(new_action->can_reuse == NULL) break;
result = new_action->can_reuse(instance->action_context);
} while(false);
return result;
}
static void nfc_cli_action_free(NfcCliProcessorContext* instance) {
if(instance->action && instance->action->free) {
FURI_LOG_D(TAG, "Free previous \"%s\" action context", instance->action->name);
instance->action->free(instance->action_context);
}
instance->action = NULL;
}
static NfcCliProcessorError
nfc_cli_action_alloc(NfcCliProcessorContext* instance, FuriString* args) {
const NfcCliCommandDescriptor* cmd = instance->cmd;
NfcCliProcessorError result = NfcCliProcessorErrorNone;
do {
const NfcCliActionDescriptor* action = nfc_cli_get_action_from_args(cmd, args);
if(action == NULL) {
result = NfcCliProcessorErrorActionNotFound;
furi_string_printf(instance->error_message, "Action not found");
break;
}
if(!nfc_cli_action_can_reuse_context(instance, action)) {
nfc_cli_action_free(instance);
instance->action = action;
if(action->alloc && action->free) {
FURI_LOG_D(TAG, "Allocating context for action \"%s\"", action->name);
instance->action_context = instance->action->alloc(instance->nfc);
} else if(action->alloc && (action->free == NULL)) {
FURI_LOG_W(
TAG,
"Free callback not defined for action \"%s\". Skip allocation to avoid memory leak.",
action->name);
instance->action_context = NULL;
} else {
FURI_LOG_D(TAG, "No alloc context callback for action \"%s\"", action->name);
instance->action_context = NULL;
}
} else
FURI_LOG_D(TAG, "Reusing context from previous \"%s\" action", action->name);
memset(instance->keys_found, 0, NFC_CLI_KEYS_FOUND_SIZE_BYTES);
instance->required_keys_expected = nfc_cli_action_get_required_keys_count(action);
instance->required_keys_found = 0;
instance->total_keys_found = 0;
} while(false);
return result;
}
static NfcCliArgumentType nfc_cli_get_argument_type(FuriString* argument) {
size_t arg_len = furi_string_size(argument);
NfcCliArgumentType type = NfcCliArgumentTypeUnknown;
if(arg_len > 2) {
char ch1 = furi_string_get_char(argument, 0);
char ch2 = furi_string_get_char(argument, 1);
if(ch1 == '-') {
type = (ch2 == '-') ? NfcCliArgumentTypeLongNameKey :
NfcCliArgumentTypeShortNameKeyGroup;
}
} else if(arg_len == 2) {
char ch1 = furi_string_get_char(argument, 0);
type = (ch1 == '-') ? NfcCliArgumentTypeShortNameKey : NfcCliArgumentTypeUnknown;
}
return type;
}
static bool
nfc_cli_check_duplicate_keys(NfcCliProcessorContext* instance, const NfcCliKeyDescriptor* key) {
bool result = false;
for(size_t i = 0; i < instance->total_keys_found; i++) {
const NfcCliKeyDescriptor* buf = instance->keys_found[i];
if(buf != key) continue;
result = true;
break;
}
return result;
}
static void nfc_cli_trim_multivalue_arg(FuriString* args, FuriString* value) {
furi_string_set(value, args);
size_t index = furi_string_search_char(value, '-', 0);
if(index != STRING_FAILURE) {
furi_string_left(value, index);
furi_string_right(args, index);
} else {
furi_string_reset(args);
}
}
static NfcCliProcessorError nfc_cli_parse_single_key(
NfcCliProcessorContext* instance,
FuriString* argument,
FuriString* args,
bool from_group) {
FuriString* value_str = furi_string_alloc();
NfcCliProcessorError result = NfcCliProcessorErrorNone;
do {
const NfcCliKeyDescriptor* key =
nfc_cli_action_get_key_descriptor(instance->action, argument);
if(key == NULL) {
if(furi_string_equal_str(argument, "h"))
result = NfcCliProcessorErrorNoneButHelp;
else {
furi_string_printf(
instance->error_message,
"Key \'%s\' is not supported",
furi_string_get_cstr(argument));
result = NfcCliProcessorErrorKeyNotSupported;
}
break;
}
if(key->features.parameter && from_group) {
furi_string_printf(
instance->error_message,
"Parameter key \'%s\' can\'t be grouped",
furi_string_get_cstr(argument));
result = NfcCliProcessorErrorKeyParameterInGroup;
break;
}
if(nfc_cli_check_duplicate_keys(instance, key)) {
furi_string_printf(
instance->error_message, "Duplicated key \'%s\'", furi_string_get_cstr(argument));
result = NfcCliProcessorErrorKeyDuplication;
break;
}
if(key->features.multivalue && !key->features.parameter) break;
if(key->features.multivalue) {
nfc_cli_trim_multivalue_arg(args, value_str);
FURI_LOG_D(TAG, "Multivalue: %s", furi_string_get_cstr(value_str));
} else if(key->features.parameter && !args_read_string_and_trim(args, value_str)) {
result = NfcCliProcessorErrorKeyParameterValueMissing;
furi_string_printf(
instance->error_message,
"Missing value for \'%s\'",
furi_string_get_cstr(argument));
break;
}
if(key->parse == NULL) {
furi_string_printf(
instance->error_message,
"Parse callback for key \'%s\' not defined",
furi_string_get_cstr(argument));
result = NfcCliProcessorErrorKeyParseError;
break;
}
FURI_LOG_D(TAG, "Parsing key \"%s\"", furi_string_get_cstr(argument));
if(!key->parse(value_str, instance->action_context)) {
furi_string_printf(
instance->error_message,
"Unable to parse value \'%s\' for key \'%s\'",
furi_string_get_cstr(value_str),
furi_string_get_cstr(argument));
result = NfcCliProcessorErrorKeyParseError;
break;
}
instance->keys_found[instance->total_keys_found] = key;
instance->total_keys_found++;
if(key->features.required) instance->required_keys_found++;
} while(false);
furi_string_free(value_str);
return result;
}
static NfcCliProcessorError
nfc_cli_parse_group_key(NfcCliProcessorContext* instance, FuriString* argument) {
NfcCliProcessorError result = NfcCliProcessorErrorNone;
FURI_LOG_D(TAG, "Parsing key group\"%s\"", furi_string_get_cstr(argument));
FuriString* arg_buf = furi_string_alloc();
for(size_t i = 0; i < furi_string_size(argument); i++) {
furi_string_set_n(arg_buf, argument, i, 1);
result = nfc_cli_parse_single_key(instance, arg_buf, NULL, true);
if(result != NfcCliProcessorErrorNone) break;
}
furi_string_free(arg_buf);
return result;
}
static NfcCliProcessorError nfc_cli_parse_argument(
NfcCliProcessorContext* instance,
FuriString* argument,
FuriString* args) {
NfcCliArgumentType type = nfc_cli_get_argument_type(argument);
furi_string_trim(argument, "-");
NfcCliProcessorError result = NfcCliProcessorErrorNone;
if(type == NfcCliArgumentTypeShortNameKeyGroup)
result = nfc_cli_parse_group_key(instance, argument);
else if((type == NfcCliArgumentTypeShortNameKey) || (type == NfcCliArgumentTypeLongNameKey)) {
result = nfc_cli_parse_single_key(instance, argument, args, false);
} else if(type == NfcCliArgumentTypeUnknown) { //-V547
result = NfcCliProcessorErrorKeyNotSupported;
furi_string_printf(
instance->error_message,
"Key \'%s\' is not supported",
furi_string_get_cstr(argument));
}
return result;
}
static NfcCliProcessorError
nfc_cli_process_arguments(NfcCliProcessorContext* instance, FuriString* args) {
NfcCliProcessorError result = NfcCliProcessorErrorNone;
FuriString* argument = furi_string_alloc();
while(args_read_string_and_trim(args, argument)) {
result = nfc_cli_parse_argument(instance, argument, args);
if(result != NfcCliProcessorErrorNone) break;
}
furi_string_free(argument);
if((result == NfcCliProcessorErrorNone) &&
(instance->required_keys_expected != instance->required_keys_found)) {
furi_string_printf(instance->error_message, "Some required keys missing");
result = NfcCliProcessorErrorKeyRequiredMissing;
}
return result;
}
static inline void nfc_cli_command_process_error(
const NfcCliProcessorContext* instance,
NfcCliProcessorError error) {
do {
if(error == NfcCliProcessorErrorNone) break;
if(error != NfcCliProcessorErrorNoneButHelp)
printf(
ANSI_FG_RED "Error: %s\r\n" ANSI_RESET,
furi_string_get_cstr(instance->error_message));
if(error == NfcCliProcessorErrorActionNotFound)
nfc_cli_command_format_info(instance->cmd, instance->error_message);
else
nfc_cli_action_format_info(instance->action, instance->error_message);
printf("\n%s", furi_string_get_cstr(instance->error_message));
} while(false);
}
void nfc_cli_command_processor_run(
const NfcCliCommandDescriptor* cmd,
PipeSide* pipe,
FuriString* args,
void* context) {
furi_assert(pipe);
furi_assert(cmd);
furi_assert(args);
NfcCliProcessorContext* instance = context;
furi_string_reset(instance->error_message);
NfcCliProcessorError error = NfcCliProcessorErrorNone;
instance->cmd = cmd;
do {
error = nfc_cli_action_alloc(instance, args);
if(error != NfcCliProcessorErrorNone) break;
error = nfc_cli_process_arguments(instance, args);
if(error != NfcCliProcessorErrorNone) break;
if(instance->action && instance->action->execute) {
instance->action->execute(pipe, instance->action_context);
} else {
FURI_LOG_W(TAG, "Action execute callback missing");
}
} while(false);
nfc_cli_command_process_error(instance, error);
}
NfcCliProcessorContext* nfc_cli_command_processor_alloc(Nfc* nfc) {
furi_assert(nfc);
NfcCliProcessorContext* instance = malloc(sizeof(NfcCliProcessorContext));
instance->nfc = nfc;
instance->keys_found = malloc(NFC_CLI_KEYS_FOUND_SIZE_BYTES);
instance->total_keys_found = 0;
instance->required_keys_found = 0;
instance->required_keys_expected = 0;
instance->error_message = furi_string_alloc();
return instance;
}
void nfc_cli_command_processor_free(NfcCliProcessorContext* instance) {
furi_assert(instance);
nfc_cli_action_free(instance);
free(instance->keys_found);
furi_string_free(instance->error_message);
instance->nfc = NULL;
free(instance);
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <furi.h>
#include <nfc/nfc.h>
#include "nfc_cli_command_base.h"
typedef struct NfcCliProcessorContext NfcCliProcessorContext;
NfcCliProcessorContext* nfc_cli_command_processor_alloc(Nfc* nfc);
void nfc_cli_command_processor_free(NfcCliProcessorContext* instance);
void nfc_cli_command_processor_run(
const NfcCliCommandDescriptor* cmd,
PipeSide* pipe,
FuriString* args,
void* context);

View File

@@ -0,0 +1,161 @@
#include "nfc_cli_commands.h"
#include "nfc_cli_command_base_i.h"
/** Include new commands here */
#include "commands/raw/nfc_cli_command_raw.h"
#include "commands/apdu/nfc_cli_command_apdu.h"
#include "commands/dump/nfc_cli_command_dump.h"
#include "commands/mfu/nfc_cli_command_mfu.h"
#include "commands/nfc_cli_command_emulate.h"
#include "commands/nfc_cli_command_scanner.h"
#include "commands/nfc_cli_command_field.h"
#define TAG "NfcCliCommands"
/** Add new commands here */
static const NfcCliCommandDescriptor* nfc_cli_commands[] = {
&apdu_cmd,
&raw_cmd,
&emulate_cmd,
&mfu_cmd,
&scanner_cmd,
&dump_cmd,
&field_cmd,
};
size_t nfc_cli_command_get_count() {
return COUNT_OF(nfc_cli_commands);
}
const NfcCliActionDescriptor*
nfc_cli_command_get_action_by_name(const NfcCliCommandDescriptor* cmd, const FuriString* name) {
furi_assert(cmd);
furi_assert(name);
for(size_t i = 0; i < cmd->action_count; i++) {
const NfcCliActionDescriptor* action = cmd->actions[i];
if(furi_string_equal_str(name, action->name)) return action;
}
return NULL;
}
const NfcCliCommandDescriptor* nfc_cli_command_get_by_index(size_t index) {
furi_assert(index < COUNT_OF(nfc_cli_commands));
return nfc_cli_commands[index];
}
bool nfc_cli_command_has_multiple_actions(const NfcCliCommandDescriptor* cmd) {
furi_assert(cmd);
furi_check(cmd->action_count > 0);
return (cmd->action_count > 1);
}
const char* nfc_cli_command_get_name(const NfcCliCommandDescriptor* cmd) {
furi_assert(cmd);
return cmd->name;
}
CliCommandExecuteCallback nfc_cli_command_get_execute(const NfcCliCommandDescriptor* cmd) {
furi_assert(cmd);
return cmd->callback;
}
static inline const NfcCliKeyDescriptor* nfc_cli_action_get_key_by_name(
const NfcCliActionDescriptor* action,
const FuriString* name,
bool long_name) {
for(size_t i = 0; i < action->key_count; i++) {
const NfcCliKeyDescriptor* key = &action->keys[i];
const char* buf = long_name ? key->long_name : key->short_name;
if((buf != NULL) && furi_string_equal_str(name, buf)) return key;
}
return NULL;
}
const NfcCliKeyDescriptor*
nfc_cli_action_get_key_descriptor(const NfcCliActionDescriptor* action, FuriString* argument) {
furi_assert(action);
furi_assert(argument);
return nfc_cli_action_get_key_by_name(action, argument, furi_string_size(argument) > 1);
}
size_t nfc_cli_action_get_required_keys_count(const NfcCliActionDescriptor* action) {
furi_assert(action);
size_t required_key_count = 0;
for(size_t i = 0; i < action->key_count; i++) {
const NfcCliKeyDescriptor* key = &action->keys[i];
if(!key->features.required) continue;
required_key_count++;
}
return required_key_count;
}
static int nfc_cli_action_format_key_name(const NfcCliKeyDescriptor* key, FuriString* output) {
int len = 0;
FuriString* name = furi_string_alloc();
if(key->short_name && key->long_name) {
len = furi_string_printf(name, "-%s, --%s", key->short_name, key->long_name);
} else if(key->short_name && (key->long_name == NULL)) {
len = furi_string_printf(name, "-%s", key->short_name);
} else if((key->short_name == NULL) && key->long_name) {
len = furi_string_printf(name, "--%s", key->long_name);
}
const char* color = key->features.required ? ANSI_FLIPPER_BRAND_ORANGE : ANSI_RESET;
furi_string_printf(output, "%s%s%s", color, furi_string_get_cstr(name), ANSI_RESET);
furi_string_free(name);
return len;
}
void nfc_cli_action_format_info(const NfcCliActionDescriptor* action, FuriString* output) {
furi_assert(action);
furi_assert(output);
furi_string_printf(
output,
action->description ? "%s - %s\r\n\n" : "%s\r\n\n",
action->name,
action->description);
if(action->key_count == 0) return;
FuriString* buf = furi_string_alloc();
furi_string_cat_printf(
output,
ANSI_FG_BR_GREEN "Keys " ANSI_FLIPPER_BRAND_ORANGE "(required) " ANSI_RESET
"(optional):\r\n");
for(size_t i = 0; i < action->key_count; i++) {
const NfcCliKeyDescriptor* key = &action->keys[i];
int len = nfc_cli_action_format_key_name(key, buf);
furi_string_cat_printf(output, "%s", furi_string_get_cstr(buf));
if(key->description) {
const int offset = 20;
furi_string_cat_printf(
output, ANSI_CURSOR_RIGHT_BY("%d") "%s", offset - len, key->description);
}
furi_string_cat_printf(output, "\r\n");
}
furi_string_free(buf);
}
void nfc_cli_command_format_info(const NfcCliCommandDescriptor* cmd, FuriString* output) {
furi_assert(cmd);
furi_assert(output);
furi_string_printf(output, "%s - %s\r\n", cmd->name, cmd->description);
if(cmd->action_count > 1) {
furi_string_cat_printf(output, "Possible actions: \r\n");
for(size_t i = 0; i < cmd->action_count; i++) {
const NfcCliActionDescriptor* action = cmd->actions[i];
furi_string_cat_printf(
output,
action->description ? "\t%s\t-\t%s\r\n" : "%s\r\n",
action->name,
action->description);
}
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "nfc_cli_command_base.h"
#include <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_ansi.h>
size_t nfc_cli_command_get_count();
const NfcCliCommandDescriptor* nfc_cli_command_get_by_index(size_t index);
const char* nfc_cli_command_get_name(const NfcCliCommandDescriptor* cmd);
CliCommandExecuteCallback nfc_cli_command_get_execute(const NfcCliCommandDescriptor* cmd);
bool nfc_cli_command_has_multiple_actions(const NfcCliCommandDescriptor* cmd);
const NfcCliActionDescriptor*
nfc_cli_command_get_action_by_name(const NfcCliCommandDescriptor* cmd, const FuriString* name);
size_t nfc_cli_action_get_required_keys_count(const NfcCliActionDescriptor* action);
const NfcCliKeyDescriptor*
nfc_cli_action_get_key_descriptor(const NfcCliActionDescriptor* action, FuriString* argument);
void nfc_cli_command_format_info(const NfcCliCommandDescriptor* cmd, FuriString* output);
void nfc_cli_action_format_info(const NfcCliActionDescriptor* action, FuriString* output);

View File

@@ -15,6 +15,7 @@ enum {
SubmenuIndexUnlockByReader,
SubmenuIndexUnlockByPassword,
SubmenuIndexWrite,
SubmenuIndexDictAttack
};
enum {
@@ -150,7 +151,15 @@ static NfcCommand
}
if(!mf_ultralight_event->data->auth_context.skip_auth) {
mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password;
mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key;
// Only set tdes_key for Manual/Reader auth types, not for dictionary attacks
if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual ||
instance->mf_ul_auth->type == MfUltralightAuthTypeReader) {
mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key;
mf_ultralight_event->data->key_request_data.key_provided = true;
} else {
mf_ultralight_event->data->key_request_data.key_provided = false;
}
}
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) {
instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack;
@@ -166,15 +175,31 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) {
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventCardDetected) {
nfc_unlock_helper_card_detected_handler(instance);
if(event.event == NfcCustomEventPollerSuccess) {
notification_message(instance->notifications, &sequence_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
return true;
} else if(event.event == NfcCustomEventPollerIncomplete) {
const MfUltralightData* data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
if(data->type == MfUltralightTypeMfulC &&
instance->mf_ul_auth->type == MfUltralightAuthTypeNone) {
// Start dict attack for MFUL C cards only if no specific auth was attempted
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
} else {
if(data->pages_read == data->pages_total) {
notification_message(instance->notifications, &sequence_success);
} else {
notification_message(instance->notifications, &sequence_semi_success);
}
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
}
}
return true;
}
}
return false;
}
static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) {
@@ -190,6 +215,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
SubmenuIndexUnlock,
nfc_protocol_support_common_submenu_callback,
instance);
if(data->type == MfUltralightTypeMfulC) {
submenu_add_item(
submenu,
"Unlock with Dictionary",
SubmenuIndexDictAttack,
nfc_protocol_support_common_submenu_callback,
instance);
}
} else if(
data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 ||
data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 ||
@@ -258,6 +291,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
} else if(event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
consumed = true;
} else if(event.event == SubmenuIndexDictAttack) {
if(!scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneMfUltralightCDictAttack)) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
}
consumed = true;
}
}
return consumed;

View File

@@ -47,6 +47,7 @@
#include <lib/nfc/nfc.h>
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h>
#include <lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
#include <lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h>
#include <nfc/nfc_poller.h>
@@ -64,7 +65,7 @@
#define NFC_NAME_SIZE 22
#define NFC_TEXT_STORE_SIZE 128
#define NFC_BYTE_INPUT_STORE_SIZE 10
#define NFC_BYTE_INPUT_STORE_SIZE 16
#define NFC_LOG_SIZE_MAX (1024)
#define NFC_APP_FOLDER EXT_PATH("nfc")
#define NFC_APP_EXTENSION ".nfc"
@@ -80,6 +81,10 @@
#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc")
#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \
(NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc")
#define NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH \
(NFC_APP_FOLDER "/assets/mf_ultralight_c_dict_user.nfc")
#define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \
(NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc")
#define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap"))
@@ -107,6 +112,14 @@ typedef struct {
bool enhanced_dict;
} NfcMfClassicDictAttackContext;
typedef struct {
KeysDict* dict;
bool auth_success;
bool is_card_present;
size_t dict_keys_total;
size_t dict_keys_current;
} NfcMfUltralightCDictContext;
struct NfcApp {
DialogsApp* dialogs;
Storage* storage;
@@ -145,6 +158,7 @@ struct NfcApp {
MfUltralightAuth* mf_ul_auth;
SlixUnlock* slix_unlock;
NfcMfClassicDictAttackContext nfc_dict_context;
NfcMfUltralightCDictContext mf_ultralight_c_dict_context;
Mfkey32Logger* mfkey32_logger;
MfUserDict* mf_user_dict;
MfClassicKeyCache* mfc_key_cache;

View File

@@ -1,67 +0,0 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/hex.h>
#include <toolbox/pipe.h>
#include <furi_hal_nfc.h>
#define FLAG_EVENT (1 << 10)
static void nfc_cli_print_usage(void) {
printf("Usage:\r\n");
printf("nfc <cmd>\r\n");
printf("Cmd list:\r\n");
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
printf("\tfield\t - turn field on\r\n");
}
}
static void nfc_cli_field(PipeSide* pipe, FuriString* args) {
UNUSED(args);
// Check if nfc worker is not busy
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
printf("NFC chip failed to start\r\n");
return;
}
furi_hal_nfc_acquire();
furi_hal_nfc_low_power_mode_stop();
furi_hal_nfc_poller_field_on();
printf("Field is on. Don't leave device in this mode for too long.\r\n");
printf("Press Ctrl+C to abort\r\n");
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(50);
}
furi_hal_nfc_low_power_mode_start();
furi_hal_nfc_release();
}
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
cmd = furi_string_alloc();
do {
if(!args_read_string_and_trim(args, cmd)) {
nfc_cli_print_usage();
break;
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
if(furi_string_cmp_str(cmd, "field") == 0) {
nfc_cli_field(pipe, args);
break;
}
}
nfc_cli_print_usage();
} while(false);
furi_string_free(cmd);
}
CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID);

View File

@@ -0,0 +1,55 @@
# Sample Key (BREAKMEIFYOUCAN!)
425245414B4D454946594F5543414E21
# Hexadecimal-Reversed Sample Key
12E4143455F495649454D4B414542524
# Byte-Reversed Sample Key (!NACUOYFIEMKAERB)
214E4143554F594649454D4B41455242
# Semnox Key (IEMKAERB!NACUOY )
49454D4B41455242214E4143554F5900
# Modified Semnox Key (IEMKAERB!NACUOYF)
49454D4B41455242214E4143554F5946
# Mix of Proxmark and ChameleonMiniLiveDebugger
00000000000000000000000000000000
000102030405060708090A0B0C0D0E0F
01010101010101010101010101010101
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
00112233445566778899AABBCCDDEEFF
47454D5850524553534F53414D504C45
79702553797025537970255379702553
4E617468616E2E4C6920546564647920
43464F494D48504E4C4359454E528841
6AC292FAA1315B4D858AB3A3D7D5933A
404142434445464748494A4B4C4D4E4F
2B7E151628AED2A6ABF7158809CF4F3C
FBEED618357133667C85E08F7236A8DE
F7DDAC306AE266CCF90BC11EE46D513B
54686973206973206D79206B65792020
A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7
B0B1B2B3B4B5B6B7B0B1B2B3B4B5B6B7
B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF
D3F7D3F7D3F7D3F7D3F7D3F7D3F7D3F7
11111111111111111111111111111111
22222222222222222222222222222222
33333333333333333333333333333333
44444444444444444444444444444444
55555555555555555555555555555555
66666666666666666666666666666666
77777777777777777777777777777777
88888888888888888888888888888888
99999999999999999999999999999999
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
0102030405060708090A0B0C0D0E0F10
00010203040506070809101112131415
01020304050607080910111213141516
16151413121110090807060504030201
15141312111009080706050403020100
0F0E0D0C0B0A09080706050403020100
100F0E0D0C0B0A090807060504030201
303132333435363738393A3B3C3D3E3F
9CABF398358405AE2F0E2B3D31C99A8A
605F5E5D5C5B5A59605F5E5D5C5B5A59

View File

@@ -0,0 +1,115 @@
Filetype: NFC Vendors
Version: 1
# Please do not change IDs in this list. Add new to the end if necessary.
# ID: "Vendor Country"
1: Motorola UK
2: ST Microelectronics SA France
3: Hitachi, Ltd Japan
4: NXP Semiconductors Germany
5: Infineon Technologies AG Germany
6: Cylink USA
7: Texas Instrument France
8: Fujitsu Limited Japan
9: Matsushita Electronics Corporation, Semiconductor Company Japan
10: NEC Japan
11: Oki Electric Industry Co. Ltd Japan
12: Toshiba Corp. Japan
13: Mitsubishi Electric Corp. Japan
14: Samsung Electronics Co. Ltd Korea
15: Hynix / Hyundai, Korea
16: LG-Semiconductors Co. Ltd Korea
17: Emosyn-EM Microelectronics USA
18: INSIDE Technology France
19: ORGA Kartensysteme GmbH Germany
20: SHARP Corporation Japan
21: ATMEL France
22: EM Microelectronic-Marin SA Switzerland
23: KSW Microtec GmbH Germany
24: ZMD AG Germany
25: XICOR, Inc. USA
26: Sony Corporation Japan
27: Malaysia Microelectronic Solutions Sdn. Bhd Malaysia
28: Emosyn USA
29: Shanghai Fudan Microelectronics Co. Ltd. P.R. China
30: Magellan Technology Pty Limited Australia
31: Melexis NV BO Switzerland
32: Renesas Technology Corp. Japan
33: TAGSYS France
34: Transcore USA
35: Shanghai belling corp., ltd. China
36: Masktech Germany Gmbh Germany
37: Innovision Research and Technology Plc UK
38: Hitachi ULSI Systems Co., Ltd. Japan
39: Cypak AB Sweden
40: Ricoh Japan
41: ASK France
42: Unicore Microsystems, LLC Russian Federation
43: Dallas Semiconductor/Maxim USA
44: Impinj, Inc. USA
45: RightPlug Alliance USA
46: Broadcom Corporation USA
47: MStar Semiconductor, Inc Taiwan, ROC
48: BeeDar Technology Inc. USA
49: RFIDsec Denmark
50: Schweizer Electronic AG Germany
51: AMIC Technology Corp Taiwan
52: Mikron JSC Russia
53: Fraunhofer Institute for Photonic Microsystems Germany
54: IDS Microchip AG Switzerland
55: Thinfilm - Kovio USA
56: HMT Microelectronic Ltd Switzerland
57: Silicon Craft Technology Thailand
58: Advanced Film Device Inc. Japan
59: Nitecrest Ltd UK
60: Verayo Inc. USA
61: HID Global USA
62: Productivity Engineering Gmbh Germany
63: Austriamicrosystems AG (reserved) Austria
64: Gemalto SA France
65: Renesas Electronics Corporation Japan
66: 3Alogics Inc Korea
67: Top TroniQ Asia Limited Hong Kong
68: Gentag Inc. USA
69: Invengo Information Technology Co.Ltd China
70: Guangzhou Sysur Microelectronics, Inc China
71: CEITEC S.A. Brazil
72: Shanghai Quanray Electronics Co. Ltd. China
73: MediaTek Inc Taiwan
74: Angstrem PJSC Russia
75: Celisic Semiconductor (Hong Kong) Limited China
76: LEGIC Identsystems AG Switzerland
77: Balluff GmbH Germany
78: Oberthur Technologies France
79: Silterra Malaysia Sdn. Bhd. Malaysia
80: DELTA Danish Electronics, Light & Acoustics Denmark
81: Giesecke & Devrient GmbH Germany
82: Shenzhen China Vision Microelectronics Co., Ltd. China
83: Shanghai Feiju Microelectronics Co. Ltd. China
84: Intel Corporation USA
85: Microsensys GmbH Germany
86: Sonix Technology Co., Ltd. Taiwan
87: Qualcomm Technologies Inc USA
88: Realtek Semiconductor Corp Taiwan
89: Freevision Technologies Co. Ltd China
90: Giantec Semiconductor Inc. China
91: JSC Angstrem-T Russia
92: STARCHIP France
93: SPIRTECH France
94: GANTNER Electronic GmbH Austria
95: Nordic Semiconductor Norway
96: Verisiti Inc USA
97: Wearlinks Technology Inc. China
98: Userstar Information Systems Co., Ltd Taiwan
99: Pragmatic Printing Ltd. UK
100: Associacao do Laboratorio de Sistemas Integraveis Tecnologico - LSI-TEC Brazil
101: Tendyron Corporation China
102: MUTO Smart Co., Ltd. Korea
103: ON Semiconductor USA
104: TUBITAK BILGEM Turkey
105: Huada Semiconductor Co., Ltd China
106: SEVENEY France
107: ISSM France
108: Wisesec Ltd Israel
124: DB HiTek Co Ltd Korea
125: SATO Vicinity Australia
126: Holtek Taiwan

View File

@@ -25,6 +25,7 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm)
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
ADD_SCENE(nfc, save_confirm, SaveConfirm)
ADD_SCENE(nfc, mf_ultralight_c_dict_attack, MfUltralightCDictAttack)
ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite)
ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess)
ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail)
@@ -57,6 +58,12 @@ ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete)
ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd)
ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
ADD_SCENE(nfc, mf_ultralight_c_keys, MfUltralightCKeys)
ADD_SCENE(nfc, mf_ultralight_c_keys_list, MfUltralightCKeysList)
ADD_SCENE(nfc, mf_ultralight_c_keys_delete, MfUltralightCKeysDelete)
ADD_SCENE(nfc, mf_ultralight_c_keys_add, MfUltralightCKeysAdd)
ADD_SCENE(nfc, mf_ultralight_c_keys_warn_duplicate, MfUltralightCKeysWarnDuplicate)
ADD_SCENE(nfc, set_type, SetType)
ADD_SCENE(nfc, set_sak, SetSak)
ADD_SCENE(nfc, set_atqa, SetAtqa)

View File

@@ -28,6 +28,10 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneMfClassicKeys);
} else if(scene_manager_has_previous_scene(
nfc->scene_manager, NfcSceneMfUltralightCKeys)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneMfUltralightCKeys);
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneFileSelect);

View File

@@ -3,6 +3,7 @@
enum SubmenuIndex {
SubmenuIndexReadCardType,
SubmenuIndexMfClassicKeys,
SubmenuIndexMfUltralightCKeys,
SubmenuIndexMfUltralightUnlock,
SubmenuIndexSlixUnlock,
};
@@ -29,6 +30,12 @@ void nfc_scene_extra_actions_on_enter(void* context) {
SubmenuIndexMfClassicKeys,
nfc_scene_extra_actions_submenu_callback,
instance);
submenu_add_item(
submenu,
"MIFARE Ultralight C Keys",
SubmenuIndexMfUltralightCKeys,
nfc_scene_extra_actions_submenu_callback,
instance);
submenu_add_item(
submenu,
"Unlock NTAG/Ultralight",
@@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
if(event.event == SubmenuIndexMfClassicKeys) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
consumed = true;
} else if(event.event == SubmenuIndexMfUltralightCKeys) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeys);
consumed = true;
} else if(event.event == SubmenuIndexMfUltralightUnlock) {
mf_ultralight_auth_reset(instance->mf_ul_auth);
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);

View File

@@ -1,7 +1,5 @@
#include "../nfc_app_i.h"
#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100)
void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {

View File

@@ -39,7 +39,7 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve
instance->scene_manager, NfcSceneMfClassicKeysWarnDuplicate);
} else if(keys_dict_add_key(dict, key.data, sizeof(MfClassicKey))) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess);
dolphin_deed(DolphinDeedNfcMfcAdd);
dolphin_deed(DolphinDeedNfcKeyAdd);
} else {
scene_manager_previous_scene(instance->scene_manager);
}

View File

@@ -0,0 +1,238 @@
#include "../nfc_app_i.h"
#include <dolphin/dolphin.h>
#define TAG "NfcMfUlCDictAttack"
// TODO: Support card_detected properly -nofl
enum {
DictAttackStateUserDictInProgress,
DictAttackStateSystemDictInProgress,
};
NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event, void* context) {
furi_assert(context);
furi_assert(event.event_data);
furi_assert(event.protocol == NfcProtocolMfUltralight);
NfcCommand command = NfcCommandContinue;
NfcApp* instance = context;
MfUltralightPollerEvent* poller_event = event.event_data;
if(poller_event->type == MfUltralightPollerEventTypeRequestMode) {
poller_event->data->poller_mode = MfUltralightPollerModeDictAttack;
command = NfcCommandContinue;
} else if(poller_event->type == MfUltralightPollerEventTypeRequestKey) {
MfUltralightC3DesAuthKey key = {};
if(keys_dict_get_next_key(
instance->mf_ultralight_c_dict_context.dict,
key.data,
sizeof(MfUltralightC3DesAuthKey))) {
poller_event->data->key_request_data.key = key;
poller_event->data->key_request_data.key_provided = true;
instance->mf_ultralight_c_dict_context.dict_keys_current++;
if(instance->mf_ultralight_c_dict_context.dict_keys_current % 10 == 0) {
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate);
}
} else {
poller_event->data->key_request_data.key_provided = false;
}
} else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller));
// Check if this is a successful authentication by looking at the poller's auth context
const MfUltralightData* data = nfc_poller_get_data(instance->poller);
// Update page information
dict_attack_set_pages_read(instance->dict_attack, data->pages_read);
dict_attack_set_pages_total(instance->dict_attack, data->pages_total);
if(data->pages_read == data->pages_total) {
// Full read indicates successful authentication in dict attack mode
instance->mf_ultralight_c_dict_context.auth_success = true;
dict_attack_set_key_found(instance->dict_attack, true);
}
view_dispatcher_send_custom_event(
instance->view_dispatcher, NfcCustomEventDictAttackComplete);
command = NfcCommandStop;
}
return command;
}
void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback(
DictAttackEvent event,
void* context) {
furi_assert(context);
NfcApp* instance = context;
if(event == DictAttackEventSkipPressed) {
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip);
}
}
void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) {
uint32_t state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
// Set attack type to Ultralight C
dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC);
if(state == DictAttackStateUserDictInProgress) {
do {
if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) {
state = DictAttackStateSystemDictInProgress;
break;
}
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenAlways,
sizeof(MfUltralightC3DesAuthKey));
if(keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict) == 0) {
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
state = DictAttackStateSystemDictInProgress;
break;
}
dict_attack_set_header(instance->dict_attack, "MFUL C User Dictionary");
} while(false);
}
if(state == DictAttackStateSystemDictInProgress) {
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH,
KeysDictModeOpenExisting,
sizeof(MfUltralightC3DesAuthKey));
dict_attack_set_header(instance->dict_attack, "MFUL C System Dictionary");
}
instance->mf_ultralight_c_dict_context.dict_keys_total =
keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict);
dict_attack_set_total_dict_keys(
instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total);
instance->mf_ultralight_c_dict_context.dict_keys_current = 0;
dict_attack_set_current_dict_key(
instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current);
// Set initial Ultralight C specific values
dict_attack_set_key_found(instance->dict_attack, false);
dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count
dict_attack_set_pages_read(instance->dict_attack, 0);
dict_attack_set_callback(
instance->dict_attack,
nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback,
instance);
scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack, state);
}
void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) {
NfcApp* instance = context;
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightCDictAttack,
DictAttackStateUserDictInProgress);
nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance);
// Setup and start worker
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance);
dict_attack_set_card_state(instance->dict_attack, true);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack);
nfc_blink_read_start(instance);
}
void nfc_scene_mf_ul_c_dict_attack_update_view(NfcApp* instance) {
dict_attack_set_card_state(
instance->dict_attack, instance->mf_ultralight_c_dict_context.is_card_present);
dict_attack_set_current_dict_key(
instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current);
}
bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
uint32_t state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventDictAttackComplete) {
if(state == DictAttackStateUserDictInProgress) {
if(instance->mf_ultralight_c_dict_context.auth_success) {
notification_message(instance->notifications, &sequence_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
consumed = true;
} else {
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightCDictAttack,
DictAttackStateSystemDictInProgress);
nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance);
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
nfc_poller_start(
instance->poller,
nfc_mf_ultralight_c_dict_attack_worker_callback,
instance);
consumed = true;
}
} else {
// Could check if card is fully read here like MFC dict attack, but found key means fully read
if(instance->mf_ultralight_c_dict_context.auth_success) {
notification_message(instance->notifications, &sequence_success);
} else {
notification_message(instance->notifications, &sequence_semi_success);
}
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
consumed = true;
}
} else if(event.event == NfcCustomEventDictAttackDataUpdate) {
dict_attack_set_current_dict_key(
instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current);
consumed = true;
} else if(event.event == NfcCustomEventDictAttackSkip) {
if(state == DictAttackStateUserDictInProgress) {
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightCDictAttack,
DictAttackStateSystemDictInProgress);
nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance);
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
nfc_poller_start(
instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance);
} else {
notification_message(instance->notifications, &sequence_semi_success);
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
dolphin_deed(DolphinDeedNfcReadSuccess);
}
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm);
consumed = true;
}
return consumed;
}
void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) {
NfcApp* instance = context;
nfc_poller_stop(instance->poller);
nfc_poller_free(instance->poller);
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneMfUltralightCDictAttack,
DictAttackStateUserDictInProgress);
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
instance->mf_ultralight_c_dict_context.dict_keys_total = 0;
instance->mf_ultralight_c_dict_context.dict_keys_current = 0;
instance->mf_ultralight_c_dict_context.auth_success = false;
instance->mf_ultralight_c_dict_context.is_card_present = false;
nfc_blink_stop(instance);
}

View File

@@ -0,0 +1,96 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_c_keys_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
}
}
void nfc_scene_mf_ultralight_c_keys_on_enter(void* context) {
NfcApp* instance = context;
// Load flipper dict keys total
uint32_t flipper_dict_keys_total = 0;
KeysDict* dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH,
KeysDictModeOpenExisting,
sizeof(MfUltralightC3DesAuthKey));
flipper_dict_keys_total = keys_dict_get_total_keys(dict);
keys_dict_free(dict);
// Load user dict keys total
uint32_t user_dict_keys_total = 0;
dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenAlways,
sizeof(MfUltralightC3DesAuthKey));
user_dict_keys_total = keys_dict_get_total_keys(dict);
keys_dict_free(dict);
FuriString* temp_str = furi_string_alloc();
widget_add_string_element(
instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Ultralight C Keys");
furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total);
widget_add_string_element(
instance->widget,
0,
20,
AlignLeft,
AlignTop,
FontSecondary,
furi_string_get_cstr(temp_str));
furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total);
widget_add_string_element(
instance->widget,
0,
32,
AlignLeft,
AlignTop,
FontSecondary,
furi_string_get_cstr(temp_str));
widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36);
widget_add_button_element(
instance->widget,
GuiButtonTypeCenter,
"Add",
nfc_scene_mf_ultralight_c_keys_widget_callback,
instance);
if(user_dict_keys_total > 0) {
widget_add_button_element(
instance->widget,
GuiButtonTypeRight,
"List",
nfc_scene_mf_ultralight_c_keys_widget_callback,
instance);
}
furi_string_free(temp_str);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
bool nfc_scene_mf_ultralight_c_keys_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeCenter) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysAdd);
consumed = true;
} else if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysList);
consumed = true;
}
}
return consumed;
}
void nfc_scene_mf_ultralight_c_keys_on_exit(void* context) {
NfcApp* instance = context;
widget_reset(instance->widget);
}

View File

@@ -0,0 +1,63 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_c_keys_add_byte_input_callback(void* context) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone);
}
void nfc_scene_mf_ultralight_c_keys_add_on_enter(void* context) {
NfcApp* instance = context;
// Setup view
ByteInput* byte_input = instance->byte_input;
byte_input_set_header_text(byte_input, "Enter the key in hex");
byte_input_set_result_callback(
byte_input,
nfc_scene_mf_ultralight_c_keys_add_byte_input_callback,
NULL,
instance,
instance->byte_input_store,
sizeof(MfUltralightC3DesAuthKey));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput);
}
bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventByteInputDone) {
// Add key to dict
KeysDict* dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenAlways,
sizeof(MfUltralightC3DesAuthKey));
MfUltralightC3DesAuthKey key = {};
memcpy(key.data, instance->byte_input_store, sizeof(MfUltralightC3DesAuthKey));
if(keys_dict_is_key_present(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) {
scene_manager_next_scene(
instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate);
} else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess);
dolphin_deed(DolphinDeedNfcKeyAdd);
} else {
scene_manager_previous_scene(instance->scene_manager);
}
keys_dict_free(dict);
consumed = true;
}
}
return consumed;
}
void nfc_scene_mf_ultralight_c_keys_add_on_exit(void* context) {
NfcApp* instance = context;
// Clear view
byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(instance->byte_input, "");
}

View File

@@ -0,0 +1,108 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_c_keys_delete_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
NfcApp* instance = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
}
}
void nfc_scene_mf_ultralight_c_keys_delete_on_enter(void* context) {
NfcApp* instance = context;
uint32_t key_index =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCKeysDelete);
FuriString* key_str = furi_string_alloc();
widget_add_string_element(
instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?");
widget_add_button_element(
instance->widget,
GuiButtonTypeLeft,
"Cancel",
nfc_scene_mf_ultralight_c_keys_delete_widget_callback,
instance);
widget_add_button_element(
instance->widget,
GuiButtonTypeRight,
"Delete",
nfc_scene_mf_ultralight_c_keys_delete_widget_callback,
instance);
KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenAlways,
sizeof(MfUltralightC3DesAuthKey));
size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict);
furi_assert(key_index < dict_keys_num);
MfUltralightC3DesAuthKey stack_key;
for(size_t i = 0; i < (key_index + 1); i++) {
bool key_loaded = keys_dict_get_next_key(
mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey));
furi_assert(key_loaded);
}
furi_string_reset(key_str);
for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) {
furi_string_cat_printf(key_str, "%02X", stack_key.data[i]);
}
widget_add_string_element(
instance->widget,
64,
32,
AlignCenter,
AlignCenter,
FontSecondary,
furi_string_get_cstr(key_str));
keys_dict_free(mf_ultralight_c_user_dict);
furi_string_free(key_str);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
}
bool nfc_scene_mf_ultralight_c_keys_delete_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeRight) {
uint32_t key_index = scene_manager_get_scene_state(
instance->scene_manager, NfcSceneMfUltralightCKeysDelete);
KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenAlways,
sizeof(MfUltralightC3DesAuthKey));
size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict);
furi_assert(key_index < dict_keys_num);
MfUltralightC3DesAuthKey stack_key;
for(size_t i = 0; i < (key_index + 1); i++) {
bool key_loaded = keys_dict_get_next_key(
mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey));
furi_assert(key_loaded);
}
bool key_delete_success = keys_dict_delete_key(
mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey));
keys_dict_free(mf_ultralight_c_user_dict);
if(key_delete_success) {
scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess);
} else {
scene_manager_previous_scene(instance->scene_manager);
}
} else if(event.event == GuiButtonTypeLeft) {
scene_manager_previous_scene(instance->scene_manager);
}
consumed = true;
}
return consumed;
}
void nfc_scene_mf_ultralight_c_keys_delete_on_exit(void* context) {
NfcApp* instance = context;
widget_reset(instance->widget);
}

View File

@@ -0,0 +1,66 @@
#include "../nfc_app_i.h"
#define NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX (100)
void nfc_scene_mf_ultralight_c_keys_list_submenu_callback(void* context, uint32_t index) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(instance->view_dispatcher, index);
}
void nfc_scene_mf_ultralight_c_keys_list_on_enter(void* context) {
NfcApp* instance = context;
KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc(
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
KeysDictModeOpenAlways,
sizeof(MfUltralightC3DesAuthKey));
submenu_set_header(instance->submenu, "Select key to delete:");
FuriString* temp_str = furi_string_alloc();
size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict);
size_t keys_num = MIN((size_t)NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX, dict_keys_num);
MfUltralightC3DesAuthKey stack_key;
if(keys_num > 0) {
for(size_t i = 0; i < keys_num; i++) {
bool key_loaded = keys_dict_get_next_key(
mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey));
furi_assert(key_loaded);
furi_string_reset(temp_str);
for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) {
furi_string_cat_printf(temp_str, "%02X", stack_key.data[i]);
}
submenu_add_item(
instance->submenu,
furi_string_get_cstr(temp_str),
i,
nfc_scene_mf_ultralight_c_keys_list_submenu_callback,
instance);
}
}
keys_dict_free(mf_ultralight_c_user_dict);
furi_string_free(temp_str);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
}
bool nfc_scene_mf_ultralight_c_keys_list_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneMfUltralightCKeysDelete, event.event);
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysDelete);
}
return consumed;
}
void nfc_scene_mf_ultralight_c_keys_list_on_exit(void* context) {
NfcApp* instance = context;
submenu_reset(instance->submenu);
}

View File

@@ -0,0 +1,49 @@
#include "../nfc_app_i.h"
void nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback(void* context) {
NfcApp* instance = context;
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit);
}
void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_enter(void* context) {
NfcApp* instance = context;
// Setup view
Popup* popup = instance->popup;
popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42);
popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop);
popup_set_text(
popup,
"Please enter a\n"
"different key.",
4,
24,
AlignLeft,
AlignTop);
popup_set_timeout(popup, 1500);
popup_set_context(popup, instance);
popup_set_callback(popup, nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
}
bool nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) {
NfcApp* instance = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventViewExit) {
consumed = scene_manager_search_and_switch_to_previous_scene(
instance->scene_manager, NfcSceneMfUltralightCKeysAdd);
}
}
return consumed;
}
void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_exit(void* context) {
NfcApp* instance = context;
popup_reset(instance->popup);
}

View File

@@ -28,6 +28,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneMfClassicKeys);
} else if(scene_manager_has_previous_scene(
nfc->scene_manager, NfcSceneMfUltralightCKeys)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
nfc->scene_manager, NfcSceneMfUltralightCKeys);
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) {
NfcSceneSaveConfirmState scene_state =
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm);

View File

@@ -13,12 +13,13 @@ struct DictAttack {
typedef struct {
FuriString* header;
bool card_detected;
DictAttackType attack_type;
// MIFARE Classic specific
uint8_t sectors_total;
uint8_t sectors_read;
uint8_t current_sector;
uint8_t keys_found;
size_t dict_keys_total;
size_t dict_keys_current;
bool is_key_attack;
uint8_t key_attack_current_sector;
MfClassicNestedPhase nested_phase;
@@ -26,17 +27,18 @@ typedef struct {
MfClassicBackdoor backdoor;
uint16_t nested_target_key;
uint16_t msb_count;
// Ultralight C specific
uint8_t pages_total;
uint8_t pages_read;
bool key_found;
// Common
size_t dict_keys_total;
size_t dict_keys_current;
} DictAttackViewModel;
static void dict_attack_draw_callback(Canvas* canvas, void* model) {
DictAttackViewModel* m = model;
if(!m->card_detected) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(
canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly.");
} else {
static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) {
char draw_str[32] = {};
canvas_set_font(canvas, FontSecondary);
@@ -72,8 +74,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
}
}
canvas_draw_str_aligned(
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header));
canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header));
if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) {
uint8_t nonce_sector =
m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2);
@@ -122,11 +123,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total);
} else {
snprintf(
draw_str,
sizeof(draw_str),
"%zu/%zu",
m->dict_keys_current,
m->dict_keys_total);
draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total);
}
}
if(dict_progress > 1.0f) {
@@ -141,9 +138,53 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
m->keys_found,
m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR);
canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str);
snprintf(
draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total);
snprintf(draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total);
canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str);
}
static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel* m) {
char draw_str[32] = {};
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header));
snprintf(draw_str, sizeof(draw_str), "Trying keys");
canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str);
float dict_progress =
m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total);
if(m->dict_keys_current == 0) {
snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total);
} else {
snprintf(draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total);
}
if(dict_progress > 1.0f) {
dict_progress = 1.0f;
}
elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str);
canvas_set_font(canvas, FontSecondary);
snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No");
canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str);
snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total);
canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str);
}
static void dict_attack_draw_callback(Canvas* canvas, void* model) {
DictAttackViewModel* m = model;
if(!m->card_detected) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Lost the tag!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(
canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly.");
} else {
if(m->attack_type == DictAttackTypeMfClassic) {
dict_attack_draw_mf_classic(canvas, m);
} else if(m->attack_type == DictAttackTypeMfUltralightC) {
dict_attack_draw_mf_ultralight_c(canvas, m);
}
}
elements_button_center(canvas, "Skip");
}
@@ -195,18 +236,28 @@ void dict_attack_reset(DictAttack* instance) {
instance->view,
DictAttackViewModel * model,
{
model->attack_type = DictAttackTypeMfClassic;
// MIFARE Classic fields
model->sectors_total = 0;
model->sectors_read = 0;
model->current_sector = 0;
model->keys_found = 0;
model->dict_keys_total = 0;
model->dict_keys_current = 0;
model->is_key_attack = false;
model->nested_phase = MfClassicNestedPhaseNone;
model->prng_type = MfClassicPrngTypeUnknown;
model->backdoor = MfClassicBackdoorUnknown;
model->nested_target_key = 0;
model->msb_count = 0;
// Ultralight C fields
model->pages_total = 0;
model->pages_read = 0;
model->key_found = false;
// Common fields
model->dict_keys_total = 0;
model->dict_keys_current = 0;
furi_string_reset(model->header);
},
false);
@@ -355,3 +406,31 @@ void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) {
with_view_model(
instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true);
}
void dict_attack_set_type(DictAttack* instance, DictAttackType type) {
furi_assert(instance);
with_view_model(
instance->view, DictAttackViewModel * model, { model->attack_type = type; }, true);
}
void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) {
furi_assert(instance);
with_view_model(
instance->view, DictAttackViewModel * model, { model->pages_total = pages_total; }, true);
}
void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) {
furi_assert(instance);
with_view_model(
instance->view, DictAttackViewModel * model, { model->pages_read = pages_read; }, true);
}
void dict_attack_set_key_found(DictAttack* instance, bool key_found) {
furi_assert(instance);
with_view_model(
instance->view, DictAttackViewModel * model, { model->key_found = key_found; }, true);
}

View File

@@ -8,6 +8,11 @@
extern "C" {
#endif
typedef enum {
DictAttackTypeMfClassic,
DictAttackTypeMfUltralightC,
} DictAttackType;
typedef struct DictAttack DictAttack;
typedef enum {
@@ -56,6 +61,14 @@ void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key
void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count);
void dict_attack_set_type(DictAttack* instance, DictAttackType type);
void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total);
void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read);
void dict_attack_set_key_found(DictAttack* instance, bool key_found);
#ifdef __cplusplus
}
#endif

View File

@@ -49,3 +49,11 @@ App(
requires=["cli"],
sources=["commands/subshell_demo.c"],
)
App(
appid="cli_buzzer",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_buzzer_ep",
requires=["cli"],
sources=["commands/buzzer.c"],
)

Some files were not shown because too many files have changed in this diff Show More