Files
unleashed-firmware/applications/main/nfc/cli/nfc_cli_command_processor.c
T
RebornedBrain eea53491de [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>
2025-09-29 14:34:49 +04:00

389 lines
14 KiB
C

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