diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 0faa760b2..ed5039ade 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -8,11 +8,7 @@ App( stack_size=5 * 1024, order=30, resources="resources", - sources=[ - "*.c*", - "!plugins", - "!nfc_cli.c", - ], + sources=["*.c*", "!plugins", "!nfc_cli.c", "!cli"], fap_libs=["assets", "mbedtls"], fap_icon="icon.png", fap_category="NFC", @@ -572,7 +568,46 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="cli_nfc_ep", requires=["cli"], - sources=["nfc_cli.c"], + sources=[ + "helpers/mf_classic_key_cache.c", + "helpers/protocol_support/iso14443_3a/iso14443_3a_render.c", + "helpers/protocol_support/mf_ultralight/mf_ultralight_render.c", + "cli/nfc_cli.c", + "cli/nfc_cli_commands.c", + "cli/nfc_cli_command_processor.c", + "cli/commands/helpers/nfc_cli_format.c", + "cli/commands/helpers/nfc_cli_scanner.c", + "cli/commands/helpers/nfc_cli_protocol_parser.c", + "cli/commands/raw/nfc_cli_command_raw.c", + "cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c", + "cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c", + "cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c", + "cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c", + "cli/commands/apdu/nfc_cli_command_apdu.c", + "cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c", + "cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c", + "cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c", + "cli/commands/dump/nfc_cli_command_dump.c", + "cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c", + "cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c", + "cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c", + "cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c", + "cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c", + "cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c", + "cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c", + "cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c", + "cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c", + "cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c", + "cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c", + "cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c", + "cli/commands/mfu/nfc_cli_command_mfu.c", + "cli/commands/mfu/nfc_cli_action_info.c", + "cli/commands/mfu/nfc_cli_action_rdbl.c", + "cli/commands/mfu/nfc_cli_action_wrbl.c", + "cli/commands/nfc_cli_command_emulate.c", + "cli/commands/nfc_cli_command_scanner.c", + "cli/commands/nfc_cli_command_field.c", + ], ) App( diff --git a/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c new file mode 100644 index 000000000..cbbb86681 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.c @@ -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 +#include +#include + +#include +#include +#include + +#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 +//Command examples: +//apdu -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 +//apdu -p 4a -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 +//apdu -p 4b -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 +//apdu -p 15 -d 00a404000e325041592e5359532e444446303100 00A4040008A000000333010102 diff --git a/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h new file mode 100644 index 000000000..84e4ba5f6 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/nfc_cli_command_apdu.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor apdu_cmd; diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c new file mode 100644 index 000000000..124ba46e3 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.c @@ -0,0 +1,37 @@ +#include "nfc_cli_apdu_iso14443_4a.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h new file mode 100644 index 000000000..907aafeaf --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4a/nfc_cli_apdu_iso14443_4a.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_cli_apdu_common_types.h" + +NfcCommand + nfc_cli_apdu_iso14443_4a_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance); diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c new file mode 100644 index 000000000..eee635515 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.c @@ -0,0 +1,37 @@ +#include "nfc_cli_apdu_iso14443_4b.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h new file mode 100644 index 000000000..e8304f497 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso14443_4b/nfc_cli_apdu_iso14443_4b.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_cli_apdu_common_types.h" + +NfcCommand + nfc_cli_apdu_iso14443_4b_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance); diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c new file mode 100644 index 000000000..bd817d1f6 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.c @@ -0,0 +1,37 @@ +#include "nfc_cli_apdu_iso15693_3.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h new file mode 100644 index 000000000..d93626918 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/iso15693_3/nfc_cli_apdu_iso15693_3.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../nfc_cli_apdu_common_types.h" + +NfcCommand + nfc_cli_apdu_iso15693_3_handler(NfcGenericEvent event, NfcCliApduRequestResponse* instance); diff --git a/applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h b/applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h new file mode 100644 index 000000000..125d26ca5 --- /dev/null +++ b/applications/main/nfc/cli/commands/apdu/protocol_handlers/nfc_cli_apdu_common_types.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +typedef enum { + NfcCliApduErrorNone, + NfcCliApduErrorTimeout, + NfcCliApduErrorNotPresent, + NfcCliApduErrorWrongCrc, + NfcCliApduErrorProtocol, +} NfcCliApduError; + +typedef struct { + NfcProtocol protocol; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + NfcCliApduError result; +} NfcCliApduRequestResponse; diff --git a/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c new file mode 100644 index 000000000..2f968b58e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c @@ -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 +#include +#include +#include +#include + +#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 diff --git a/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h new file mode 100644 index 000000000..e8d7ddc09 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor dump_cmd; diff --git a/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c new file mode 100644 index 000000000..b89b75aa6 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c @@ -0,0 +1,32 @@ +#include "nfc_cli_dump_felica.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h new file mode 100644 index 000000000..5114ff36e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/felica/nfc_cli_dump_felica.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_felica(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c new file mode 100644 index 000000000..a2dfe3b93 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.c @@ -0,0 +1,25 @@ +#include "nfc_cli_dump_iso14443_3a.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h new file mode 100644 index 000000000..42bb4b0f4 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3a/nfc_cli_dump_iso14443_3a.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c new file mode 100644 index 000000000..7a67af75f --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.c @@ -0,0 +1,27 @@ +#include "nfc_cli_dump_iso14443_3b.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h new file mode 100644 index 000000000..eb374303e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_3b/nfc_cli_dump_iso14443_3b.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c new file mode 100644 index 000000000..380f1672c --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c @@ -0,0 +1,26 @@ +#include "nfc_cli_dump_iso14443_4a.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h new file mode 100644 index 000000000..5e2220107 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c new file mode 100644 index 000000000..eae0669fc --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c @@ -0,0 +1,25 @@ +#include "nfc_cli_dump_iso14443_4b.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h new file mode 100644 index 000000000..ffa1df346 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c new file mode 100644 index 000000000..e4a6ea753 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c @@ -0,0 +1,26 @@ +#include "nfc_cli_dump_iso15693_3.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h new file mode 100644 index 000000000..7fa6135ea --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c new file mode 100644 index 000000000..525f171eb --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c @@ -0,0 +1,54 @@ +#include "nfc_cli_dump_mf_classic.h" +#include + +#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, §or_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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h new file mode 100644 index 000000000..c1e3b1de1 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c new file mode 100644 index 000000000..49a5bf04e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c @@ -0,0 +1,27 @@ +#include "nfc_cli_dump_mf_desfire.h" +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h new file mode 100644 index 000000000..89cff77cf --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c new file mode 100644 index 000000000..8ed421605 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c @@ -0,0 +1,31 @@ +#include "nfc_cli_dump_mf_plus.h" +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h new file mode 100644 index 000000000..63954e268 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c new file mode 100644 index 000000000..cc607a349 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c @@ -0,0 +1,39 @@ +#include "nfc_cli_dump_mf_ultralight.h" +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h new file mode 100644 index 000000000..a005f6c16 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h @@ -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); diff --git a/applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h b/applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h new file mode 100644 index 000000000..c14a31bdd --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/nfc_cli_dump_common_types.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include "../../../../helpers/mf_classic_key_cache.h" +#include "../../helpers/nfc_cli_scanner.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#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; diff --git a/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c new file mode 100644 index 000000000..4fdd66a44 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c @@ -0,0 +1,26 @@ +#include "nfc_cli_dump_slix.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h new file mode 100644 index 000000000..91b01d5d1 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/slix/nfc_cli_dump_slix.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_slix(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c new file mode 100644 index 000000000..c6fd0ea19 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c @@ -0,0 +1,29 @@ +#include "nfc_cli_dump_st25tb.h" +#include + +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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h new file mode 100644 index 000000000..e1512864f --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_st25tb(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c new file mode 100644 index 000000000..abe153938 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c @@ -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]); + } +} diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_format.h b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.h new file mode 100644 index 000000000..5f9c2e574 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +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); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c new file mode 100644 index 000000000..21a8d29b4 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.c @@ -0,0 +1,58 @@ +#include "nfc_cli_protocol_parser.h" + +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h new file mode 100644 index 000000000..a87b26fe5 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_protocol_parser.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +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); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c new file mode 100644 index 000000000..12d043cdc --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.c @@ -0,0 +1,96 @@ +#include "nfc_cli_scanner.h" +#include +#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; +} diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h new file mode 100644 index 000000000..9ca9a5705 --- /dev/null +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_scanner.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +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); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c new file mode 100644 index 000000000..3d3ad1125 --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.c @@ -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 +#include +#include +#include + +#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)); + } +} diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h new file mode 100644 index 000000000..998dd8def --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_info.h @@ -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); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c new file mode 100644 index 000000000..d8a43ab71 --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.c @@ -0,0 +1,52 @@ +#include "nfc_cli_action_rdbl.h" + +#include "../helpers/nfc_cli_format.h" + +#include +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h new file mode 100644 index 000000000..ec7e19c2d --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_rdbl.h @@ -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); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c new file mode 100644 index 000000000..bdc20557c --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.c @@ -0,0 +1,71 @@ +#include "nfc_cli_action_rdbl.h" + +#include "../helpers/nfc_cli_format.h" + +#include +#include +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h new file mode 100644 index 000000000..abddd367f --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_action_wrbl.h @@ -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); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c new file mode 100644 index 000000000..0bfcffe3c --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.c @@ -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); diff --git a/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h new file mode 100644 index 000000000..a0530682c --- /dev/null +++ b/applications/main/nfc/cli/commands/mfu/nfc_cli_command_mfu.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor mfu_cmd; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_emulate.c b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.c new file mode 100644 index 000000000..60ff62cc5 --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.c @@ -0,0 +1,126 @@ +#include "nfc_cli_command_emulate.h" +#include "helpers/nfc_cli_format.h" + +#include +#include +#include + +#include + +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 ] +//Command examples: +//emulate -f ext/nfc/test.nfc diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_emulate.h b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.h new file mode 100644 index 000000000..ff1ae5b1a --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_emulate.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor emulate_cmd; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_field.c b/applications/main/nfc/cli/commands/nfc_cli_command_field.c new file mode 100644 index 000000000..753ba451e --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_field.c @@ -0,0 +1,28 @@ +#include "nfc_cli_command_field.h" + +#include + +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, +}; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_field.h b/applications/main/nfc/cli/commands/nfc_cli_command_field.h new file mode 100644 index 000000000..b96ef3b5a --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_field.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor field_cmd; diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_scanner.c b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.c new file mode 100644 index 000000000..fec8d72c1 --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.c @@ -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); diff --git a/applications/main/nfc/cli/commands/nfc_cli_command_scanner.h b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.h new file mode 100644 index 000000000..e41958859 --- /dev/null +++ b/applications/main/nfc/cli/commands/nfc_cli_command_scanner.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor scanner_cmd; diff --git a/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c new file mode 100644 index 000000000..f90adb4c6 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c @@ -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 + +#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 [keys] +//Command examples: +//raw iso14a -sc 3000 +//raw iso14a 3000 +//raw iso14a 3000 -sc diff --git a/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h new file mode 100644 index 000000000..d1d6dadee --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../../nfc_cli_command_base_i.h" + +extern const NfcCliCommandDescriptor raw_cmd; diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c new file mode 100644 index 000000000..c613b0d84 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.c @@ -0,0 +1,101 @@ +#include "nfc_cli_raw_felica.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h new file mode 100644 index 000000000..ef3b6c13a --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/felica/nfc_cli_raw_felica.h @@ -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); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c new file mode 100644 index 000000000..bdeaeff61 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.c @@ -0,0 +1,81 @@ +#include "nfc_cli_raw_iso14443_3a.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h new file mode 100644 index 000000000..5ba7fccbe --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3a/nfc_cli_raw_iso14443_3a.h @@ -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); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c new file mode 100644 index 000000000..01851eb5d --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.c @@ -0,0 +1,121 @@ +#include "nfc_cli_raw_iso14443_3b.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h new file mode 100644 index 000000000..6fed95ef9 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso14443_3b/nfc_cli_raw_iso14443_3b.h @@ -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); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c new file mode 100644 index 000000000..c96b7d83c --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.c @@ -0,0 +1,102 @@ +#include "nfc_cli_raw_iso15693_3.h" +#include "../../../helpers/nfc_cli_format.h" + +#include +#include +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h new file mode 100644 index 000000000..1911abe57 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/iso15693_3/nfc_cli_raw_iso15693_3.h @@ -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); diff --git a/applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h b/applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h new file mode 100644 index 000000000..1eb3ca242 --- /dev/null +++ b/applications/main/nfc/cli/commands/raw/protocol_handlers/nfc_cli_raw_common_types.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +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; diff --git a/applications/main/nfc/cli/nfc_cli.c b/applications/main/nfc/cli/nfc_cli.c new file mode 100644 index 000000000..60250c857 --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli.c @@ -0,0 +1,124 @@ +#include "nfc_cli_commands.h" +#include "nfc_cli_command_processor.h" + +#include "applications/services/loader/loader.h" +#include "applications/services/cli/cli_main_commands.h" +#include +#include + +#define NFC_DESKTOP_APP_NAME "NFC" + +#define TAG "NfcCli" + +#define NFC_PROMPT "[" ANSI_FG_GREEN "nfc" ANSI_RESET "]" + +typedef struct { + Nfc* nfc; + CliRegistry* registry; + CliShell* shell; + NfcCliProcessorContext* processor_context; +} NfcCliContext; + +static void nfc_cli_shell_motd(void* context) { + UNUSED(context); + printf(ANSI_FG_BR_BLUE "\r\n" + " 0000 \r\n" + " 0000 \r\n" + " 000 0000 \r\n" + " 0000 00000 \r\n" + " 000 00000 0000 \r\n" + " 0 0000 0000 00000 \r\n" + " 000000 0000 00000 0000 \r\n" + " 00000000 0000 0000 0000 \r\n" + " 0000000000 0000 00000 0000 \r\n" + " 0000 00000000 00000 00000 0000 \r\n" + " 0000 0000000 00000 00000 0000 \r\n" + " 0000 000000000000 0000 0000 \r\n" + " 00000 000000000 00000 0000 \r\n" + " 00 000000 0000 00000 \r\n" + " 00 00000 0000 \r\n" + " 0000 00000 \r\n" + " 000 0000 \r\n" + " 0000 \r\n" + " 0005 \r\n" + "\r\n" ANSI_FG_BR_WHITE "Welcome to NFC Command Line Interface!\r\n" + "Run `help` or `?` to list available commands\r\n" ANSI_RESET); +} + +static void nfc_cli_subscribe_commands(NfcCliContext* instance) { + size_t cnt = nfc_cli_command_get_count(); + for(size_t i = 0; i < cnt; i++) { + const NfcCliCommandDescriptor* cmd = nfc_cli_command_get_by_index(i); + CliCommandExecuteCallback callback = nfc_cli_command_get_execute(cmd); + if(callback == NULL) continue; + const char* name = nfc_cli_command_get_name(cmd); + cli_registry_add_command( + instance->registry, + name, + CliCommandFlagParallelSafe, + callback, + instance->processor_context); + } +} + +static bool nfc_cli_desktop_app_is_running() { + FuriString* app_name = furi_string_alloc(); + Loader* ldr = furi_record_open(RECORD_LOADER); + bool result = false; + + if(loader_get_application_name(ldr, app_name)) { + result = furi_string_equal_str(app_name, NFC_DESKTOP_APP_NAME); + } + + furi_record_close(RECORD_LOADER); + furi_string_free(app_name); + return result; +} + +static NfcCliContext* nfc_cli_alloc(PipeSide* pipe) { + NfcCliContext* instance = malloc(sizeof(NfcCliContext)); + instance->nfc = nfc_alloc(); + instance->processor_context = nfc_cli_command_processor_alloc(instance->nfc); + + instance->registry = cli_registry_alloc(); + + nfc_cli_subscribe_commands(instance); + + instance->shell = + cli_shell_alloc(nfc_cli_shell_motd, instance, pipe, instance->registry, NULL); + + cli_shell_set_prompt(instance->shell, NFC_PROMPT); + return instance; +} + +void nfc_cli_free(NfcCliContext* instance) { + furi_assert(instance); + nfc_cli_command_processor_free(instance->processor_context); + + cli_shell_free(instance->shell); + cli_registry_free(instance->registry); + + nfc_free(instance->nfc); + free(instance); +} + +void nfc_cli_execute(PipeSide* pipe, FuriString* args, void* context) { + furi_assert(pipe); + UNUSED(args); + UNUSED(context); + + if(nfc_cli_desktop_app_is_running()) { + printf(ANSI_FG_YELLOW + "NFC app is running, unable to run NFC CLI at the same time!\r\n" ANSI_RESET); + return; + } + + NfcCliContext* instance = nfc_cli_alloc(pipe); + + cli_shell_start(instance->shell); + cli_shell_join(instance->shell); + + nfc_cli_free(instance); +} + +CLI_COMMAND_INTERFACE(nfc, nfc_cli_execute, CliCommandFlagParallelSafe, 1024, CLI_APPID); diff --git a/applications/main/nfc/cli/nfc_cli_command_base.h b/applications/main/nfc/cli/nfc_cli_command_base.h new file mode 100644 index 000000000..8d7f0c0ac --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_base.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +/** + * @brief Type for action context to be created before action execution + * must be hanlded through callbacks in each action separately + */ +typedef void NfcCliActionContext; + +/** + * @brief Callback type for function of action context allocation + * @param nfc Instance of NFC subsystem, will be used during action execution + * @return Pointer to action context + */ +typedef NfcCliActionContext* (*NfcCliActionContextAlloc)(Nfc* nfc); + +/** + * @brief Callback for action context deleting + * @param action_ctx Action context to be freed + */ +typedef void (*NfcCliActionContextFree)(NfcCliActionContext* action_ctx); + +/** + * @brief Callback invoked by command processor to determine whether already + * existing context (from previously executed command) can be reused for the new one. + * + * @param action_ctx Action context + * + * In most cases re-creating of a new context is not needed. + * It is used in 'raw' command, where nfc field sometimes need to stay turned on between + * commands. + * + * Handling of this situation and decision about reusing action context is on developer, + * who need to decide, can this command reuse context. + * + * It can be done by comparing parameters of previously executed command and a new one, + * or by some args in command. + * + * See implementation of 'keep_field' flag in 'raw' command for example. + */ +typedef bool (*NfcCliActionContextCanReuse)(NfcCliActionContext* ctx); + +/** + * @brief Action execution callback + * @param pipe provided by cli shell, can be used for command termination + * @param ctx Action context + */ +typedef void (*NfcCliActionHandlerCallback)(PipeSide* pipe, NfcCliActionContext* ctx); + +/** + * @brief Callback used for parsing argument key. + * Each key added to command must have this, otherwise parsing result will always be + * false and command will never be executed. + * + * @param value Text value for the key to be parsed + * @param ctx Action context + * @return true when parsing was fine, otherwise false. If any argument in command input + * generates false during parsing, command will not be executed and error will be shown + */ +typedef bool (*NfcCliArgParseCallback)(FuriString* value, NfcCliActionContext* ctx); + +typedef struct NfcCliKeyDescriptor NfcCliKeyDescriptor; + +typedef struct NfcCliActionDescriptor NfcCliActionDescriptor; + +typedef struct NfcCliCommandDescriptor NfcCliCommandDescriptor; diff --git a/applications/main/nfc/cli/nfc_cli_command_base_i.h b/applications/main/nfc/cli/nfc_cli_command_base_i.h new file mode 100644 index 000000000..2ffc047f9 --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_base_i.h @@ -0,0 +1,130 @@ +#pragma once + +#include "nfc_cli_command_base.h" +#include +#include +#include +#include "nfc_cli_command_processor.h" + +/** + * @brief How to add command. + * + * There are 3 possible option on how to add new command to nfc_cli: + * + * @see Option 1 "Add action command directly to nfc_shell" + * + * In this case command will be invoked with argument string from nfc_shell. + * Command registration must be performed directly by user. + * + * Steps: + * 1. Add new function for command to nfc_cli.c + * 2. In nfc_cli_alloc function register command using cli_registry_add_command after nfc_cli_subscribe_commands + * + * This option is NOT RECOMENDED, because such command will not have any 'help' + * processing and parsing error checks. Argument parsing must also be done by hand. + * + * -------------------------------------------------------------------------- + * + * @see Option 2 "Add action command to collection without further processing" + * + * In this case command will be invoked with argument string from nfc_shell. + * nfc_cli_command_processor is skipped, so argument handling is up to the developer. + * + * Steps: + * 1. Add new pair of nfc_cli_command_.c/.h files to /commands folder + * 2. Define const NfcCliCommandDescriptor instance in .c file and its extern definition in .h file + * 3. Include .h file to nfc_cli_commands.c file below comment "Include new commands here" + * 4. Add new command reference to nfc_cli_commands array + * 5. Add path to nfc_cli_command_.c file into 'cli_nfc' plugin in application.fam file + * + * This option suites for simple commands with no any parameters. + * @see nfc_cli_command_field.c implementation as an example. + * + * -------------------------------------------------------------------------- + * + * @see Option 3 "Add action command to collection with full processing" + * + * In this nfc_cli_command_processor will be invoked for parsing command arguments + * and action execution. Also it will handle errors and help printing. + * + * Steps: + * 1. Add new pair of nfc_cli_command_.c/.h files to /commands folder + * 2. Use macro ADD_NFC_CLI_COMMAND to define command in .c file + * 3. Define command extern in .h file, using command name from macro in form of "_cmd" + * 4. Add all desired actions and keys to your command + * 5. Include .h file to nfc_cli_commands.c file below comment "Include new commands here" + * 6. Add new command reference to nfc_cli_commands array + * 7. Add path to nfc_cli_command_.c file into 'cli_nfc' plugin in application.fam file + * + * This option suites for "difficult" commands which has actions with lots of keys. + * @see nfc_cli_command_emulate.c implementation as an example. + * + */ + +/** + * @brief Used to decorate argument with some properties + */ +typedef struct { + bool required : 1; /**< Command always needs this argument. Missing arguments with this set to true will result execution error.*/ + bool parameter : 1; /**< Such argument requires value after its name, otherwise it is a simple on/off switch */ + bool multivalue : 1; /**< Such argument can take multiple values after its name, like this "-key value1 value2 .. valueN" */ +} FURI_PACKED NfcCliKeyFeatureSupport; + +/** + * @brief Describes key for action + */ +struct NfcCliKeyDescriptor { + NfcCliKeyFeatureSupport features; /**< Features supported defining key behaviour */ + const char* long_name; /**< Long key name starts with '--' symbol in argument string */ + const char* short_name; /**< Short key name starts with '-' symbol in argument string */ + const char* description; /**< Key description showed in help */ + NfcCliArgParseCallback parse; /**< Parsing callback */ +}; + +/** + * @brief Describes action + */ +struct NfcCliActionDescriptor { + const char* name; /**< Action name MUST be the first argument after command.*/ + const char* description; /**< Description showed in help */ + size_t key_count; /**< Amount of key entries in keys array */ + const NfcCliKeyDescriptor* keys; /**< Keys available for action */ + + NfcCliActionHandlerCallback execute; /**< Action callback, invoked if parsing is ok */ + NfcCliActionContextAlloc alloc; /**< Allocates action context during command processing */ + NfcCliActionContextFree free; /**< Frees action context */ + NfcCliActionContextCanReuse can_reuse; /**< Checks context reuse possibility */ +}; + +/** + * @brief Describes command + */ +struct NfcCliCommandDescriptor { + const char* name; /** Used to register command in cli shell */ + const char* description; /**< Description showed in help */ + size_t action_count; /** Amount of actions available in scope of this particular command */ + const NfcCliActionDescriptor** actions; /**< Actions available for command */ + CliCommandExecuteCallback callback; /** Entry point for command */ +}; + +/** + * @brief This macro simplifies command creation. It fills instance of + * NfcCliCommandDescriptor and generates a callback which invokes + * nfc_cli_command_processor inside + */ +#define ADD_NFC_CLI_COMMAND(name, description, actions) \ + static void nfc_cli_command_##name##_callback( \ + PipeSide* pipe, FuriString* args, void* context); \ + \ + const NfcCliCommandDescriptor name##_cmd = { \ + #name, \ + #description, \ + COUNT_OF(actions), \ + actions, \ + nfc_cli_command_##name##_callback, \ + }; \ + \ + static void nfc_cli_command_##name##_callback( \ + PipeSide* pipe, FuriString* args, void* context) { \ + nfc_cli_command_processor_run(&name##_cmd, pipe, args, context); \ + } diff --git a/applications/main/nfc/cli/nfc_cli_command_processor.c b/applications/main/nfc/cli/nfc_cli_command_processor.c new file mode 100644 index 000000000..839ba4267 --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_processor.c @@ -0,0 +1,388 @@ +#include "nfc_cli_command_processor.h" +#include "nfc_cli_commands.h" +#include "nfc_cli_command_base_i.h" + +#include +#include +#include + +#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); +} diff --git a/applications/main/nfc/cli/nfc_cli_command_processor.h b/applications/main/nfc/cli/nfc_cli_command_processor.h new file mode 100644 index 000000000..2c3849c5b --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_command_processor.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include "nfc_cli_command_base.h" + +typedef struct NfcCliProcessorContext NfcCliProcessorContext; + +NfcCliProcessorContext* nfc_cli_command_processor_alloc(Nfc* nfc); +void nfc_cli_command_processor_free(NfcCliProcessorContext* instance); + +void nfc_cli_command_processor_run( + const NfcCliCommandDescriptor* cmd, + PipeSide* pipe, + FuriString* args, + void* context); diff --git a/applications/main/nfc/cli/nfc_cli_commands.c b/applications/main/nfc/cli/nfc_cli_commands.c new file mode 100644 index 000000000..82321cede --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_commands.c @@ -0,0 +1,161 @@ +#include "nfc_cli_commands.h" +#include "nfc_cli_command_base_i.h" + +/** Include new commands here */ +#include "commands/raw/nfc_cli_command_raw.h" +#include "commands/apdu/nfc_cli_command_apdu.h" +#include "commands/dump/nfc_cli_command_dump.h" +#include "commands/mfu/nfc_cli_command_mfu.h" +#include "commands/nfc_cli_command_emulate.h" +#include "commands/nfc_cli_command_scanner.h" +#include "commands/nfc_cli_command_field.h" + +#define TAG "NfcCliCommands" + +/** Add new commands here */ +static const NfcCliCommandDescriptor* nfc_cli_commands[] = { + &apdu_cmd, + &raw_cmd, + &emulate_cmd, + &mfu_cmd, + &scanner_cmd, + &dump_cmd, + &field_cmd, +}; + +size_t nfc_cli_command_get_count() { + return COUNT_OF(nfc_cli_commands); +} + +const NfcCliActionDescriptor* + nfc_cli_command_get_action_by_name(const NfcCliCommandDescriptor* cmd, const FuriString* name) { + furi_assert(cmd); + furi_assert(name); + + for(size_t i = 0; i < cmd->action_count; i++) { + const NfcCliActionDescriptor* action = cmd->actions[i]; + if(furi_string_equal_str(name, action->name)) return action; + } + return NULL; +} + +const NfcCliCommandDescriptor* nfc_cli_command_get_by_index(size_t index) { + furi_assert(index < COUNT_OF(nfc_cli_commands)); + return nfc_cli_commands[index]; +} + +bool nfc_cli_command_has_multiple_actions(const NfcCliCommandDescriptor* cmd) { + furi_assert(cmd); + furi_check(cmd->action_count > 0); + return (cmd->action_count > 1); +} + +const char* nfc_cli_command_get_name(const NfcCliCommandDescriptor* cmd) { + furi_assert(cmd); + return cmd->name; +} + +CliCommandExecuteCallback nfc_cli_command_get_execute(const NfcCliCommandDescriptor* cmd) { + furi_assert(cmd); + return cmd->callback; +} + +static inline const NfcCliKeyDescriptor* nfc_cli_action_get_key_by_name( + const NfcCliActionDescriptor* action, + const FuriString* name, + bool long_name) { + for(size_t i = 0; i < action->key_count; i++) { + const NfcCliKeyDescriptor* key = &action->keys[i]; + const char* buf = long_name ? key->long_name : key->short_name; + if((buf != NULL) && furi_string_equal_str(name, buf)) return key; + } + return NULL; +} + +const NfcCliKeyDescriptor* + nfc_cli_action_get_key_descriptor(const NfcCliActionDescriptor* action, FuriString* argument) { + furi_assert(action); + furi_assert(argument); + + return nfc_cli_action_get_key_by_name(action, argument, furi_string_size(argument) > 1); +} + +size_t nfc_cli_action_get_required_keys_count(const NfcCliActionDescriptor* action) { + furi_assert(action); + + size_t required_key_count = 0; + for(size_t i = 0; i < action->key_count; i++) { + const NfcCliKeyDescriptor* key = &action->keys[i]; + if(!key->features.required) continue; + required_key_count++; + } + return required_key_count; +} + +static int nfc_cli_action_format_key_name(const NfcCliKeyDescriptor* key, FuriString* output) { + int len = 0; + FuriString* name = furi_string_alloc(); + if(key->short_name && key->long_name) { + len = furi_string_printf(name, "-%s, --%s", key->short_name, key->long_name); + } else if(key->short_name && (key->long_name == NULL)) { + len = furi_string_printf(name, "-%s", key->short_name); + } else if((key->short_name == NULL) && key->long_name) { + len = furi_string_printf(name, "--%s", key->long_name); + } + + const char* color = key->features.required ? ANSI_FLIPPER_BRAND_ORANGE : ANSI_RESET; + furi_string_printf(output, "%s%s%s", color, furi_string_get_cstr(name), ANSI_RESET); + furi_string_free(name); + return len; +} + +void nfc_cli_action_format_info(const NfcCliActionDescriptor* action, FuriString* output) { + furi_assert(action); + furi_assert(output); + furi_string_printf( + output, + action->description ? "%s - %s\r\n\n" : "%s\r\n\n", + action->name, + action->description); + + if(action->key_count == 0) return; + + FuriString* buf = furi_string_alloc(); + furi_string_cat_printf( + output, + ANSI_FG_BR_GREEN "Keys " ANSI_FLIPPER_BRAND_ORANGE "(required) " ANSI_RESET + "(optional):\r\n"); + + for(size_t i = 0; i < action->key_count; i++) { + const NfcCliKeyDescriptor* key = &action->keys[i]; + + int len = nfc_cli_action_format_key_name(key, buf); + furi_string_cat_printf(output, "%s", furi_string_get_cstr(buf)); + + if(key->description) { + const int offset = 20; + furi_string_cat_printf( + output, ANSI_CURSOR_RIGHT_BY("%d") "%s", offset - len, key->description); + } + furi_string_cat_printf(output, "\r\n"); + } + furi_string_free(buf); +} + +void nfc_cli_command_format_info(const NfcCliCommandDescriptor* cmd, FuriString* output) { + furi_assert(cmd); + furi_assert(output); + furi_string_printf(output, "%s - %s\r\n", cmd->name, cmd->description); + if(cmd->action_count > 1) { + furi_string_cat_printf(output, "Possible actions: \r\n"); + + for(size_t i = 0; i < cmd->action_count; i++) { + const NfcCliActionDescriptor* action = cmd->actions[i]; + furi_string_cat_printf( + output, + action->description ? "\t%s\t-\t%s\r\n" : "%s\r\n", + action->name, + action->description); + } + } +} diff --git a/applications/main/nfc/cli/nfc_cli_commands.h b/applications/main/nfc/cli/nfc_cli_commands.h new file mode 100644 index 000000000..eb2b30f5a --- /dev/null +++ b/applications/main/nfc/cli/nfc_cli_commands.h @@ -0,0 +1,27 @@ +#pragma once + +#include "nfc_cli_command_base.h" +#include +#include + +size_t nfc_cli_command_get_count(); + +const NfcCliCommandDescriptor* nfc_cli_command_get_by_index(size_t index); + +const char* nfc_cli_command_get_name(const NfcCliCommandDescriptor* cmd); + +CliCommandExecuteCallback nfc_cli_command_get_execute(const NfcCliCommandDescriptor* cmd); + +bool nfc_cli_command_has_multiple_actions(const NfcCliCommandDescriptor* cmd); + +const NfcCliActionDescriptor* + nfc_cli_command_get_action_by_name(const NfcCliCommandDescriptor* cmd, const FuriString* name); + +size_t nfc_cli_action_get_required_keys_count(const NfcCliActionDescriptor* action); + +const NfcCliKeyDescriptor* + nfc_cli_action_get_key_descriptor(const NfcCliActionDescriptor* action, FuriString* argument); + +void nfc_cli_command_format_info(const NfcCliCommandDescriptor* cmd, FuriString* output); + +void nfc_cli_action_format_info(const NfcCliActionDescriptor* action, FuriString* output); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c deleted file mode 100644 index 2b4cdf6d3..000000000 --- a/applications/main/nfc/nfc_cli.c +++ /dev/null @@ -1,215 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define FLAG_EVENT (1 << 10) - -#define NFC_MAX_BUFFER_SIZE (256) -#define NFC_BASE_PROTOCOL_MAX (2) -#define POLLER_DONE (1 << 0) -#define POLLER_ERR (1 << 1) -static NfcProtocol BASE_PROTOCOL[NFC_BASE_PROTOCOL_MAX] = { - NfcProtocolIso14443_4a, - NfcProtocolIso14443_4b}; -typedef struct ApduContext { - BitBuffer* tx_buffer; - BitBuffer* rx_buffer; - bool ready; - FuriThreadId thread_id; -} ApduContext; - -static void nfc_cli_print_usage(void) { - printf("Usage:\r\n"); - printf("nfc \r\n"); - printf("Cmd list:\r\n"); - printf("\tapdu\t - Send APDU and print response \r\n"); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - printf("\tfield\t - turn field on\r\n"); - } -} - -static void nfc_cli_field(PipeSide* pipe, FuriString* args) { - UNUSED(args); - // Check if nfc worker is not busy - if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { - printf("NFC chip failed to start\r\n"); - return; - } - - furi_hal_nfc_acquire(); - furi_hal_nfc_low_power_mode_stop(); - furi_hal_nfc_poller_field_on(); - - printf("Field is on. Don't leave device in this mode for too long.\r\n"); - printf("Press Ctrl+C to abort\r\n"); - - while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { - furi_delay_ms(50); - } - - furi_hal_nfc_low_power_mode_start(); - furi_hal_nfc_release(); -} - -static NfcCommand trx_callback(NfcGenericEvent event, void* context) { - furi_check(context); - ApduContext* apdu_context = (ApduContext*)context; - - if(apdu_context->ready) { - apdu_context->ready = false; - if(NfcProtocolIso14443_4a == event.protocol) { - Iso14443_4aError err = iso14443_4a_poller_send_block( - event.instance, apdu_context->tx_buffer, apdu_context->rx_buffer); - if(Iso14443_4aErrorNone == err) { - furi_thread_flags_set(apdu_context->thread_id, POLLER_DONE); - return NfcCommandContinue; - } else { - furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); - return NfcCommandStop; - } - } else if(NfcProtocolIso14443_4b == event.protocol) { - Iso14443_4bError err = iso14443_4b_poller_send_block( - event.instance, apdu_context->tx_buffer, apdu_context->rx_buffer); - if(Iso14443_4bErrorNone == err) { - furi_thread_flags_set(apdu_context->thread_id, POLLER_DONE); - return NfcCommandContinue; - } else { - furi_thread_flags_set(apdu_context->thread_id, POLLER_ERR); - return NfcCommandStop; - } - } else { - // should never reach here - furi_crash("Unknown protocol"); - } - } else { - furi_delay_ms(100); - } - - return NfcCommandContinue; -} - -static void nfc_cli_apdu(PipeSide* pipe, FuriString* args) { - UNUSED(pipe); - Nfc* nfc = NULL; - NfcPoller* poller = NULL; - FuriString* data = furi_string_alloc(); - uint8_t* req_buffer = NULL; - uint8_t* resp_buffer = NULL; - size_t apdu_size = 0; - size_t resp_size = 0; - NfcProtocol current_protocol = NfcProtocolInvalid; - - do { - if(0 == args_get_first_word_length(args)) { - printf( - "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n"); - break; - } - nfc = nfc_alloc(); - - printf("detecting tag\r\n"); - for(int i = 0; i < NFC_BASE_PROTOCOL_MAX; i++) { - poller = nfc_poller_alloc(nfc, BASE_PROTOCOL[i]); - bool is_detected = nfc_poller_detect(poller); - nfc_poller_free(poller); - if(is_detected) { - current_protocol = BASE_PROTOCOL[i]; - printf("detected tag:%d\r\n", BASE_PROTOCOL[i]); - break; - } - } - if(NfcProtocolInvalid == current_protocol) { - nfc_free(nfc); - printf("Can not find any tag\r\n"); - break; - } - poller = nfc_poller_alloc(nfc, current_protocol); - ApduContext* apdu_context = malloc(sizeof(ApduContext)); - apdu_context->tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); - apdu_context->rx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE); - apdu_context->ready = false; - apdu_context->thread_id = furi_thread_get_current_id(); - - nfc_poller_start(poller, trx_callback, apdu_context); - while(args_read_string_and_trim(args, data)) { - bit_buffer_reset(apdu_context->tx_buffer); - bit_buffer_reset(apdu_context->rx_buffer); - apdu_size = furi_string_size(data) / 2; - req_buffer = malloc(apdu_size); - - hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer); - printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data)); - bit_buffer_copy_bytes(apdu_context->tx_buffer, req_buffer, apdu_size); - apdu_context->ready = true; - uint32_t flags = furi_thread_flags_wait(POLLER_DONE, FuriFlagWaitAny, 3000); - if(0 == (flags & POLLER_DONE)) { - printf("Error or Timeout"); - free(req_buffer); - break; - } - furi_assert(apdu_context->ready == false); - resp_size = bit_buffer_get_size_bytes(apdu_context->rx_buffer) * 2; - if(!resp_size) { - printf("No response\r\n"); - free(req_buffer); - continue; - } - resp_buffer = malloc(resp_size); - uint8_to_hex_chars( - bit_buffer_get_data(apdu_context->rx_buffer), resp_buffer, resp_size); - resp_buffer[resp_size] = 0; - printf("Response: %s\r\n", resp_buffer); - - free(req_buffer); - free(resp_buffer); - } - - nfc_poller_stop(poller); - nfc_poller_free(poller); - nfc_free(nfc); - - bit_buffer_free(apdu_context->tx_buffer); - bit_buffer_free(apdu_context->rx_buffer); - free(apdu_context); - } while(false); - - furi_string_free(data); -} - -static void execute(PipeSide* pipe, FuriString* args, void* context) { - UNUSED(context); - FuriString* cmd; - cmd = furi_string_alloc(); - - do { - if(!args_read_string_and_trim(args, cmd)) { - nfc_cli_print_usage(); - break; - } - if(furi_string_cmp_str(cmd, "apdu") == 0) { - nfc_cli_apdu(pipe, args); - break; - } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - if(furi_string_cmp_str(cmd, "field") == 0) { - nfc_cli_field(pipe, args); - break; - } - } - - nfc_cli_print_usage(); - } while(false); - - furi_string_free(cmd); -} - -CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/nfc/resources/nfc/assets/vendors.nfc b/applications/main/nfc/resources/nfc/assets/vendors.nfc new file mode 100644 index 000000000..7750e99b7 --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/vendors.nfc @@ -0,0 +1,115 @@ +Filetype: NFC Vendors +Version: 1 +# Please do not change IDs in this list. Add new to the end if necessary. +# ID: "Vendor Country" +1: Motorola UK +2: ST Microelectronics SA France +3: Hitachi, Ltd Japan +4: NXP Semiconductors Germany +5: Infineon Technologies AG Germany +6: Cylink USA +7: Texas Instrument France +8: Fujitsu Limited Japan +9: Matsushita Electronics Corporation, Semiconductor Company Japan +10: NEC Japan +11: Oki Electric Industry Co. Ltd Japan +12: Toshiba Corp. Japan +13: Mitsubishi Electric Corp. Japan +14: Samsung Electronics Co. Ltd Korea +15: Hynix / Hyundai, Korea +16: LG-Semiconductors Co. Ltd Korea +17: Emosyn-EM Microelectronics USA +18: INSIDE Technology France +19: ORGA Kartensysteme GmbH Germany +20: SHARP Corporation Japan +21: ATMEL France +22: EM Microelectronic-Marin SA Switzerland +23: KSW Microtec GmbH Germany +24: ZMD AG Germany +25: XICOR, Inc. USA +26: Sony Corporation Japan +27: Malaysia Microelectronic Solutions Sdn. Bhd Malaysia +28: Emosyn USA +29: Shanghai Fudan Microelectronics Co. Ltd. P.R. China +30: Magellan Technology Pty Limited Australia +31: Melexis NV BO Switzerland +32: Renesas Technology Corp. Japan +33: TAGSYS France +34: Transcore USA +35: Shanghai belling corp., ltd. China +36: Masktech Germany Gmbh Germany +37: Innovision Research and Technology Plc UK +38: Hitachi ULSI Systems Co., Ltd. Japan +39: Cypak AB Sweden +40: Ricoh Japan +41: ASK France +42: Unicore Microsystems, LLC Russian Federation +43: Dallas Semiconductor/Maxim USA +44: Impinj, Inc. USA +45: RightPlug Alliance USA +46: Broadcom Corporation USA +47: MStar Semiconductor, Inc Taiwan, ROC +48: BeeDar Technology Inc. USA +49: RFIDsec Denmark +50: Schweizer Electronic AG Germany +51: AMIC Technology Corp Taiwan +52: Mikron JSC Russia +53: Fraunhofer Institute for Photonic Microsystems Germany +54: IDS Microchip AG Switzerland +55: Thinfilm - Kovio USA +56: HMT Microelectronic Ltd Switzerland +57: Silicon Craft Technology Thailand +58: Advanced Film Device Inc. Japan +59: Nitecrest Ltd UK +60: Verayo Inc. USA +61: HID Global USA +62: Productivity Engineering Gmbh Germany +63: Austriamicrosystems AG (reserved) Austria +64: Gemalto SA France +65: Renesas Electronics Corporation Japan +66: 3Alogics Inc Korea +67: Top TroniQ Asia Limited Hong Kong +68: Gentag Inc. USA +69: Invengo Information Technology Co.Ltd China +70: Guangzhou Sysur Microelectronics, Inc China +71: CEITEC S.A. Brazil +72: Shanghai Quanray Electronics Co. Ltd. China +73: MediaTek Inc Taiwan +74: Angstrem PJSC Russia +75: Celisic Semiconductor (Hong Kong) Limited China +76: LEGIC Identsystems AG Switzerland +77: Balluff GmbH Germany +78: Oberthur Technologies France +79: Silterra Malaysia Sdn. Bhd. Malaysia +80: DELTA Danish Electronics, Light & Acoustics Denmark +81: Giesecke & Devrient GmbH Germany +82: Shenzhen China Vision Microelectronics Co., Ltd. China +83: Shanghai Feiju Microelectronics Co. Ltd. China +84: Intel Corporation USA +85: Microsensys GmbH Germany +86: Sonix Technology Co., Ltd. Taiwan +87: Qualcomm Technologies Inc USA +88: Realtek Semiconductor Corp Taiwan +89: Freevision Technologies Co. Ltd China +90: Giantec Semiconductor Inc. China +91: JSC Angstrem-T Russia +92: STARCHIP France +93: SPIRTECH France +94: GANTNER Electronic GmbH Austria +95: Nordic Semiconductor Norway +96: Verisiti Inc USA +97: Wearlinks Technology Inc. China +98: Userstar Information Systems Co., Ltd Taiwan +99: Pragmatic Printing Ltd. UK +100: Associacao do Laboratorio de Sistemas Integraveis Tecnologico - LSI-TEC Brazil +101: Tendyron Corporation China +102: MUTO Smart Co., Ltd. Korea +103: ON Semiconductor USA +104: TUBITAK BILGEM Turkey +105: Huada Semiconductor Co., Ltd China +106: SEVENEY France +107: ISSM France +108: Wisesec Ltd Israel +124: DB HiTek Co Ltd Korea +125: SATO Vicinity Australia +126: Holtek Taiwan diff --git a/applications/services/cli/cli_vcp.h b/applications/services/cli/cli_vcp.h index 10e286183..e9a0671a8 100644 --- a/applications/services/cli/cli_vcp.h +++ b/applications/services/cli/cli_vcp.h @@ -15,7 +15,6 @@ typedef struct CliVcp CliVcp; void cli_vcp_enable(CliVcp* cli_vcp); void cli_vcp_disable(CliVcp* cli_vcp); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index cce1fe94a..737a356e2 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -63,6 +63,7 @@ env.Append( File("protocols/st25tb/st25tb_poller_sync.h"), # Misc File("helpers/nfc_util.h"), + File("helpers/felica_crc.h"), File("helpers/iso14443_crc.h"), File("helpers/iso13239_crc.h"), File("helpers/nfc_data_generator.h"), diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index e9ffb771a..a298f37a9 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -104,6 +104,7 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { break; } } + furi_string_free(temp_str); } while(false); return parsed; diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index b4e7af9d1..1fcaffc53 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -155,15 +155,13 @@ typedef struct { FelicaFSUnion data; } FelicaData; -#pragma pack(push, 1) -typedef struct { +typedef struct FURI_PACKED { uint8_t code; FelicaIDm idm; uint8_t service_num; uint16_t service_code; uint8_t block_count; } FelicaCommandHeader; -#pragma pack(pop) typedef struct { uint8_t length; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 252c46399..be6bca970 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -248,7 +248,8 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void poller_context->error = MfUltralightErrorNone; command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeReadFailed) { - poller_context->error = mfu_event->data->error; + poller_context->error = mf_ultralight_process_error( + mfu_poller->iso14443_3a_poller->iso14443_3a_event_data.error); command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { if(poller_context->auth_context != NULL) { diff --git a/lib/toolbox/cli/shell/cli_shell.c b/lib/toolbox/cli/shell/cli_shell.c index b2648d127..3516b51ce 100644 --- a/lib/toolbox/cli/shell/cli_shell.c +++ b/lib/toolbox/cli/shell/cli_shell.c @@ -442,7 +442,6 @@ static int32_t cli_shell_thread(void* context) { // ========== // Public API // ========== - CliShell* cli_shell_alloc( CliShellMotd motd, void* context, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index a1e8b38f4..cd95e314d 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -129,6 +129,7 @@ Header,+,lib/nanopb/pb.h,, Header,+,lib/nanopb/pb_decode.h,, Header,+,lib/nanopb/pb_encode.h,, Header,+,lib/nfc/helpers/crypto1.h,, +Header,+,lib/nfc/helpers/felica_crc.h,, Header,+,lib/nfc/helpers/iso13239_crc.h,, Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, @@ -1096,6 +1097,9 @@ Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" Function,+,felica_copy,void,"FelicaData*, const FelicaData*" +Function,+,felica_crc_append,void,BitBuffer* +Function,+,felica_crc_check,_Bool,const BitBuffer* +Function,+,felica_crc_trim,void,BitBuffer* Function,+,felica_free,void,FelicaData* Function,+,felica_get_base_data,FelicaData*,const FelicaData* Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType"