[FL-3569] NFC CLI commands (#4158)

* feat: FuriThread stdin

* ci: fix f18

* feat: stdio callback context

* feat: FuriPipe

* POTENTIALLY EXPLOSIVE pipe welding

* fix: non-explosive welding

* Revert welding

* docs: furi_pipe

* feat: pipe event loop integration

* update f18 sdk

* f18

* docs: make doxygen happy

* fix: event loop not triggering when pipe attached to stdio

* fix: partial stdout in pipe

* allow simultaneous in and out subscription in event loop

* feat: vcp i/o

* feat: cli ansi stuffs and history

* feat: more line editing

* working but slow cli rewrite

* restore previous speed after 4 days of debugging 🥲

* fix: cli_app_should_stop

* fix: cli and event_loop memory leaks

* style: remove commented out code

* ci: fix pvs warnings

* fix: unit tests, event_loop crash

* ci: fix build

* ci: silence pvs warning

* feat: cli gpio

* ci: fix formatting

* Fix memory leak during event loop unsubscription

* Event better memory leak fix

* feat: cli completions

* Merge remote-tracking branch 'origin/dev' into portasynthinca3/3928-cli-threads

* merge fixups

* temporarily exclude speaker_debug app

* pvs and unit tests fixups

* feat: commands in fals

* move commands out of flash, code cleanup

* ci: fix errors

* fix: run commands in buffer when stopping session

* speedup cli file transfer

* fix f18

* separate cli_shell into modules

* fix pvs warning

* fix qflipper refusing to connect

* remove temp debug logs

* remove erroneous conclusion

* Fix memory leak during event loop unsubscription

* Event better memory leak fix

* unit test for the fix

* improve thread stdio callback signatures

* pipe stdout timeout

* update api symbols

* fix f18, formatting

* fix pvs warnings

* increase stack size, hope to fix unit tests

* cli completions

* more key combos

* commands in fals

* move commands out of flash

* ci: fix errors

* speedup cli file transfer

* merge fixups

* fix f18

* cli: revert flag changes

* cli: fix formatting

* cli, fbt: loopback perf benchmark

* thread, event_loop: subscribing to thread flags

* cli: signal internal events using thread flags, improve performance

* fix f18, formatting

* event_loop: fix crash

* storage_cli: increase write_chunk buffer size again

* cli: explanation for order=0

* thread, event_loop: thread flags callback refactor

* cli: increase stack size

* cli: rename cli_app_should_stop -> cli_is_pipe_broken_or_is_etx_next_char

* cli: use plain array instead of mlib for history

* cli: prepend file name to static fns

* cli: fix formatting

* cli_shell: increase stack size

* Now cli_shell can be customized with another motd and another command set

* Added custom motd callback definition

* Now user can alloc and free his own cli command set

* cli_vcp can now restart shell with another command set

* Help command modified to show available commands from different command sets

* Api adjustement

* Reworked nfc_cli to start new shell with another command set

* Revert custom shell changes from vcp

* Custom motd callback moved to cli_shell

* Cli Shell now can be started from ongoing cli command

* Help command moved to a separate function so it can be used for custom shell

* Now nfc command spawns separate shell for further nfc commands

* cli_shell: give up pipe to command thread

* fix formatting

* cli_shell: separate into toolbox

* speaker_debug: fix

* fix: format

* Merge branch 'portasynthinca3/3928-3929-cli-fals-threads' into portasynthinca3/3965-cli_shell-toolbox

* fix merge

* fix. merge.

* fix formatting

* fix: cmd flags

* fix: formatting

* Added basic command descriptor structs and macros

* Basic nfc commands definitions added

* Nfc cli commands collection and functions added

* Raw skeleton of nfc cli processor added

* cli: increase default stack depth

* New callbacks for ctx alloc / free added

* nfc_cli moved to cli folder

* Some more logic for command processor

* Scanner command no works via command_processor

* plugin manifest adj

* Argument descriptors were removed, now only keys left

* Some helper command function implemented

* Command processor logic now mostly works

* Added all parsers and dummy implementation of raw cmd

* Now processor checks duplicated keys and treat them as errors

* Some renamings

* Arguments processing moved to separate function

* Now command processor can reuse context of previuos command for the next one if it's allowed

* can_reuse callback added for checking if context can be reused

* command processor is now freed on nfc cli exit

* Some cleanups

* First working version of raw command

* Now input data are placed directly to bit buffer

* Added tag

* Introduced request/response structs

* Moved raw command to a separate folder

* Moved some common types to header

* Added protocol specific handlers for iso14a and felica

* Opened felica crc header for referencing

* Added handler for iso14443_3b

* Opened iso15693_3_poller for referencing

* Added iso15693_3 handler for raw command

* NfcCliRawError enum introduced for response result

* Refactored handlers implementation

* Formatting functions now added as helpers

* New printing result logic

* Not present error value added to enum

* Timeout added to raw command

* Command processor now supports multivalue keys

* Apdu command implementation added

* NfcScanner moved to helpers and command now uses it

* Helper now can format protocol names

* Dump command added

* Added some more functions to scanner helper

* Dump main logic simplified

* Dump handlers moved to protocols folder

* Protocol parser added to simplify searching protocol by name

* Protocol and key arguments added to dump command

* Cleanups

* Apdu now parses protocol using helper parser

* Raw now parses protocol using helper parser

* Wrong naming fix

* Emulate command added to cli

* Description added to action descriptor and command macros

* Description field added to all commands

* Removed unnecessary enum for commands

* Added functions for formatting command and action info

* Proper error messages and help added

* Fix for unsupported single action command

* Function renamed to more appropriate

* Field command moved to all other commands

* Cleanups

* Nfc commands modified with new cli shell

* Removed previous nfc_cli.c after merge

* Removed nfc_cli.h header

* Some renamings and cleanups

* Some comments and instructions added

* Some comments and instructions added

* TODOs removed

* Fix for missing parse callback

* Added not implemented dummy for mfu actions, for now

* Fix name mismatch

* Remove unneeded header

* Mfu command moved to separate folder, also raw info action logic added

* Dictionary with id/vendors added to assets. It is used by nfc_cli_mfu_info_get_vendor function

* One more unneeded header removed

* Moved mfu info action to a separate file

* Info action now uses sync mfu poller

* mfu rdbl action added

* wrbl action added for mfu command

* Some formatting for rdbl command

* Function for formatting mfu errors added

* All mfu actions now show errors in the same way

* Fix error with sync poller. Previously when read failed function returned ErrorNone, now it processes iso14a error to get proper value

* Make PVS happy

* Nfc cli now doesn't start if desktop app is running

* Make action description look more common

* Scanner now has -t key and can show detected protocol hierarchies

* Apdu now checks max input payload data

* Proper format

* Proper error handling added to dump command

* Timeout key added dump command

* Fix merge issue

* formatting

* Pragma pack replaced with FURI_PACKED

* Fix felica memory leak

---------

Co-authored-by: Anna Antonenko <portasynthinca3@gmail.com>
Co-authored-by: Georgii Surkov <georgii.surkov@outlook.com>
Co-authored-by: あく <alleteam@gmail.com>
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
RebornedBrain
2025-09-29 13:34:49 +03:00
committed by GitHub
parent 2ef556d456
commit eea53491de
84 changed files with 4180 additions and 79 deletions

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;