From f6eb79e1e5b850a8e107462ee47f68803e98602a Mon Sep 17 00:00:00 2001 From: Nick Mooney Date: Fri, 9 Feb 2024 21:00:17 +1300 Subject: [PATCH 01/11] NFC: Add support for Gallagher access control (MIFARE Classic only) (#3306) * Merge remote-tracking branch 'origin/dev' into dev * Add basic API interface (inspired by advanced plugins example), modify NfcSupportedCardsLoadContext to contain a pointer to a resolver * WIP: API resolver implemented / passed to plugin via context, still having resolution issues * Attempt to add constants to the nfc_app_api_table list * WIP: We're defining the constants directly in nfc_app_api.c to see if this fixes our woes, which it does not. * WIP: Remove furi_assert(false) (lmao) * Working implementation of Gallagher decoding via exposed plugin API * lib: api_hashtable: change log level for symbol not found message * nfc app: alloc composite resolver along with supported cards * nfc app: rework nfc api structure * nfc app: fix memory leakage in supported cards Co-authored-by: gornekich --- .../main/nfc/api/gallagher/gallagher_util.c | 59 +++++++++++++ .../main/nfc/api/gallagher/gallagher_util.h | 33 +++++++ .../main/nfc/api/nfc_app_api_interface.h | 9 ++ .../main/nfc/api/nfc_app_api_table.cpp | 27 ++++++ .../main/nfc/api/nfc_app_api_table_i.h | 13 +++ applications/main/nfc/application.fam | 11 ++- .../main/nfc/helpers/nfc_supported_cards.c | 42 ++++++--- .../nfc/plugins/supported_cards/gallagher.c | 87 +++++++++++++++++++ .../api_hashtable/api_hashtable.cpp | 2 +- 9 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 applications/main/nfc/api/gallagher/gallagher_util.c create mode 100644 applications/main/nfc/api/gallagher/gallagher_util.h create mode 100644 applications/main/nfc/api/nfc_app_api_interface.h create mode 100644 applications/main/nfc/api/nfc_app_api_table.cpp create mode 100644 applications/main/nfc/api/nfc_app_api_table_i.h create mode 100644 applications/main/nfc/plugins/supported_cards/gallagher.c diff --git a/applications/main/nfc/api/gallagher/gallagher_util.c b/applications/main/nfc/api/gallagher/gallagher_util.c new file mode 100644 index 000000000..caa3650e7 --- /dev/null +++ b/applications/main/nfc/api/gallagher/gallagher_util.c @@ -0,0 +1,59 @@ +/* gallagher_util.c - Utilities for parsing Gallagher cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#include "gallagher_util.h" + +#define GALLAGHER_CREDENTIAL_SECTOR 15 + +/* The Gallagher obfuscation algorithm is a 256-byte substitution table. The below array is generated from + * https://github.com/megabug/gallagher-research/blob/master/formats/cardholder/substitution-table.bin. +*/ +const uint8_t GALLAGHER_DECODE_TABLE[256] = { + 0x2f, 0x6e, 0xdd, 0xdf, 0x1d, 0xf, 0xb0, 0x76, 0xad, 0xaf, 0x7f, 0xbb, 0x77, 0x85, 0x11, + 0x6d, 0xf4, 0xd2, 0x84, 0x42, 0xeb, 0xf7, 0x34, 0x55, 0x4a, 0x3a, 0x10, 0x71, 0xe7, 0xa1, + 0x62, 0x1a, 0x3e, 0x4c, 0x14, 0xd3, 0x5e, 0xb2, 0x7d, 0x56, 0xbc, 0x27, 0x82, 0x60, 0xe3, + 0xae, 0x1f, 0x9b, 0xaa, 0x2b, 0x95, 0x49, 0x73, 0xe1, 0x92, 0x79, 0x91, 0x38, 0x6c, 0x19, + 0xe, 0xa9, 0xe2, 0x8d, 0x66, 0xc7, 0x5a, 0xf5, 0x1c, 0x80, 0x99, 0xbe, 0x4e, 0x41, 0xf0, + 0xe8, 0xa6, 0x20, 0xab, 0x87, 0xc8, 0x1e, 0xa0, 0x59, 0x7b, 0xc, 0xc3, 0x3c, 0x61, 0xcc, + 0x40, 0x9e, 0x6, 0x52, 0x1b, 0x32, 0x8c, 0x12, 0x93, 0xbf, 0xef, 0x3b, 0x25, 0xd, 0xc2, + 0x88, 0xd1, 0xe0, 0x7, 0x2d, 0x70, 0xc6, 0x29, 0x6a, 0x4d, 0x47, 0x26, 0xa3, 0xe4, 0x8b, + 0xf6, 0x97, 0x2c, 0x5d, 0x3d, 0xd7, 0x96, 0x28, 0x2, 0x8, 0x30, 0xa7, 0x22, 0xc9, 0x65, + 0xf8, 0xb7, 0xb4, 0x8a, 0xca, 0xb9, 0xf2, 0xd0, 0x17, 0xff, 0x46, 0xfb, 0x9a, 0xba, 0x8f, + 0xb6, 0x69, 0x68, 0x8e, 0x21, 0x6f, 0xc4, 0xcb, 0xb3, 0xce, 0x51, 0xd4, 0x81, 0x0, 0x2e, + 0x9c, 0x74, 0x63, 0x45, 0xd9, 0x16, 0x35, 0x5f, 0xed, 0x78, 0x9f, 0x1, 0x48, 0x4, 0xc1, + 0x33, 0xd6, 0x4f, 0x94, 0xde, 0x31, 0x9d, 0xa, 0xac, 0x18, 0x4b, 0xcd, 0x98, 0xb8, 0x37, + 0xa2, 0x83, 0xec, 0x3, 0xd8, 0xda, 0xe5, 0x7a, 0x6b, 0x53, 0xd5, 0x15, 0xa4, 0x43, 0xe9, + 0x90, 0x67, 0x58, 0xc0, 0xa5, 0xfa, 0x2a, 0xb1, 0x75, 0x50, 0x39, 0x5c, 0xe6, 0xdc, 0x89, + 0xfc, 0xcf, 0xfe, 0xf9, 0x57, 0x54, 0x64, 0xa8, 0xee, 0x23, 0xb, 0xf1, 0xea, 0xfd, 0xdb, + 0xbd, 0x9, 0xb5, 0x5b, 0x5, 0x86, 0x13, 0xf3, 0x24, 0xc5, 0x3f, 0x44, 0x72, 0x7c, 0x7e, + 0x36}; + +// The second block of a Gallagher credential sector is the literal +// "www.cardax.com " (note two padding spaces) +const uint8_t GALLAGHER_CARDAX_ASCII[MF_CLASSIC_BLOCK_SIZE] = + {'w', 'w', 'w', '.', 'c', 'a', 'r', 'd', 'a', 'x', '.', 'c', 'o', 'm', ' ', ' '}; + +/* Precondition: cardholder_data_obfuscated points to at least 8 safe-to-read bytes of memory. +*/ +void gallagher_deobfuscate_and_parse_credential( + GallagherCredential* credential, + const uint8_t* cardholder_data_obfuscated) { + uint8_t cardholder_data_deobfuscated[8]; + for(int i = 0; i < 8; i++) { + cardholder_data_deobfuscated[i] = GALLAGHER_DECODE_TABLE[cardholder_data_obfuscated[i]]; + } + + // Pull out values from the deobfuscated data + credential->region = (cardholder_data_deobfuscated[3] >> 1) & 0x0F; + credential->facility = ((uint16_t)(cardholder_data_deobfuscated[5] & 0x0F) << 12) + + ((uint16_t)cardholder_data_deobfuscated[1] << 4) + + (((uint16_t)cardholder_data_deobfuscated[7] >> 4) & 0x0F); + credential->card = ((uint32_t)cardholder_data_deobfuscated[0] << 16) + + ((uint32_t)(cardholder_data_deobfuscated[4] & 0x1F) << 11) + + ((uint32_t)cardholder_data_deobfuscated[2] << 3) + + (((uint32_t)cardholder_data_deobfuscated[3] >> 5) & 0x07); + credential->issue = cardholder_data_deobfuscated[7] & 0x0F; +} \ No newline at end of file diff --git a/applications/main/nfc/api/gallagher/gallagher_util.h b/applications/main/nfc/api/gallagher/gallagher_util.h new file mode 100644 index 000000000..79e098389 --- /dev/null +++ b/applications/main/nfc/api/gallagher/gallagher_util.h @@ -0,0 +1,33 @@ +/* gallagher_util.h - Utilities for parsing Gallagher cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#pragma once + +#include + +#define GALLAGHER_CREDENTIAL_SECTOR 15 + +#ifdef __cplusplus +extern "C" { +#endif + +extern const uint8_t GALLAGHER_DECODE_TABLE[256]; +extern const uint8_t GALLAGHER_CARDAX_ASCII[MF_CLASSIC_BLOCK_SIZE]; + +typedef struct GallagherCredential { + uint8_t region; + uint8_t issue; + uint16_t facility; + uint32_t card; +} GallagherCredential; + +void gallagher_deobfuscate_and_parse_credential( + GallagherCredential* credential, + const uint8_t* cardholder_data_obfuscated); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/nfc/api/nfc_app_api_interface.h b/applications/main/nfc/api/nfc_app_api_interface.h new file mode 100644 index 000000000..162355982 --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const nfc_application_api_interface; diff --git a/applications/main/nfc/api/nfc_app_api_table.cpp b/applications/main/nfc/api/nfc_app_api_table.cpp new file mode 100644 index 000000000..ca190665b --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "nfc_app_api_table_i.h" + +static_assert(!has_hash_collisions(nfc_app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface nfc_application_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = nfc_app_api_table.cbegin(), + .table_cend = nfc_app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const nfc_application_api_interface = + &nfc_application_hashtable_api_interface; diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h new file mode 100644 index 000000000..08bff072e --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -0,0 +1,13 @@ +#include "gallagher/gallagher_util.h" + +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto nfc_app_api_table = sort(create_array_t( + API_METHOD( + gallagher_deobfuscate_and_parse_credential, + void, + (GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)), + API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index c0acace5d..1e6291c4d 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -9,7 +9,7 @@ App( order=30, resources="resources", sources=[ - "*.c", + "*.c*", "!plugins", "!nfc_cli.c", ], @@ -110,6 +110,15 @@ App( sources=["plugins/supported_cards/umarsh.c"], ) +App( + appid="gallagher_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="gallagher_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/gallagher.c"], +) + App( appid="clipper_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c index 6016ae178..f5668b506 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.c +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -1,9 +1,11 @@ #include "nfc_supported_cards.h" +#include "../api/nfc_app_api_interface.h" #include "../plugins/supported_cards/nfc_supported_card_plugin.h" #include #include +#include #include #include @@ -45,6 +47,7 @@ typedef struct { } NfcSupportedCardsLoadContext; struct NfcSupportedCards { + CompositeApiResolver* api_resolver; NfcSupportedCardsPluginCache_t plugins_cache_arr; NfcSupportedCardsLoadState load_state; NfcSupportedCardsLoadContext* load_context; @@ -52,6 +55,11 @@ struct NfcSupportedCards { NfcSupportedCards* nfc_supported_cards_alloc() { NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards)); + + instance->api_resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(instance->api_resolver, firmware_api_interface); + composite_api_resolver_add(instance->api_resolver, nfc_application_api_interface); + NfcSupportedCardsPluginCache_init(instance->plugins_cache_arr); return instance; @@ -67,8 +75,9 @@ void nfc_supported_cards_free(NfcSupportedCards* instance) { NfcSupportedCardsPluginCache* plugin_cache = NfcSupportedCardsPluginCache_ref(iter); furi_string_free(plugin_cache->path); } - NfcSupportedCardsPluginCache_clear(instance->plugins_cache_arr); + + composite_api_resolver_free(instance->api_resolver); free(instance); } @@ -100,15 +109,17 @@ static void nfc_supported_cards_load_context_free(NfcSupportedCardsLoadContext* free(instance); } -static const NfcSupportedCardsPlugin* - nfc_supported_cards_get_plugin(NfcSupportedCardsLoadContext* instance, FuriString* path) { +static const NfcSupportedCardsPlugin* nfc_supported_cards_get_plugin( + NfcSupportedCardsLoadContext* instance, + const FuriString* path, + const ElfApiInterface* api_interface) { furi_assert(instance); furi_assert(path); const NfcSupportedCardsPlugin* plugin = NULL; do { if(instance->app) flipper_application_free(instance->app); - instance->app = flipper_application_alloc(instance->storage, firmware_api_interface); + instance->app = flipper_application_alloc(instance->storage, api_interface); if(flipper_application_preload(instance->app, furi_string_get_cstr(path)) != FlipperApplicationPreloadStatusSuccess) break; @@ -129,8 +140,9 @@ static const NfcSupportedCardsPlugin* return plugin; } -static const NfcSupportedCardsPlugin* - nfc_supported_cards_get_next_plugin(NfcSupportedCardsLoadContext* instance) { +static const NfcSupportedCardsPlugin* nfc_supported_cards_get_next_plugin( + NfcSupportedCardsLoadContext* instance, + const ElfApiInterface* api_interface) { const NfcSupportedCardsPlugin* plugin = NULL; do { @@ -145,7 +157,7 @@ static const NfcSupportedCardsPlugin* path_concat(NFC_SUPPORTED_CARDS_PLUGINS_PATH, instance->file_name, instance->file_path); - plugin = nfc_supported_cards_get_plugin(instance, instance->file_path); + plugin = nfc_supported_cards_get_plugin(instance, instance->file_path, api_interface); } while(plugin == NULL); //-V654 return plugin; @@ -162,8 +174,10 @@ void nfc_supported_cards_load_cache(NfcSupportedCards* instance) { instance->load_context = nfc_supported_cards_load_context_alloc(); while(true) { + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_next_plugin(instance->load_context); + nfc_supported_cards_get_next_plugin(instance->load_context, api_interface); if(plugin == NULL) break; //-V547 NfcSupportedCardsPluginCache plugin_cache = {}; //-V779 @@ -216,8 +230,10 @@ bool nfc_supported_cards_read(NfcSupportedCards* instance, NfcDevice* device, Nf if(plugin_cache->protocol != protocol) continue; if((plugin_cache->feature & NfcSupportedCardsPluginFeatureHasRead) == 0) continue; - const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_plugin(instance->load_context, plugin_cache->path); + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); + const NfcSupportedCardsPlugin* plugin = nfc_supported_cards_get_plugin( + instance->load_context, plugin_cache->path, api_interface); if(plugin == NULL) continue; if(plugin->verify) { @@ -262,8 +278,10 @@ bool nfc_supported_cards_parse( if(plugin_cache->protocol != protocol) continue; if((plugin_cache->feature & NfcSupportedCardsPluginFeatureHasParse) == 0) continue; - const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_plugin(instance->load_context, plugin_cache->path); + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); + const NfcSupportedCardsPlugin* plugin = nfc_supported_cards_get_plugin( + instance->load_context, plugin_cache->path, api_interface); if(plugin == NULL) continue; if(plugin->parse) { diff --git a/applications/main/nfc/plugins/supported_cards/gallagher.c b/applications/main/nfc/plugins/supported_cards/gallagher.c new file mode 100644 index 000000000..8b6a58987 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/gallagher.c @@ -0,0 +1,87 @@ +/* gallagher.c - NFC supported cards plugin for Gallagher access control cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#include "nfc_supported_card_plugin.h" +#include "../../api/gallagher/gallagher_util.h" + +#include +#include +#include +#include + +static bool gallagher_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + if(!(data->type == MfClassicType1k || data->type == MfClassicType4k)) { + return false; + } + + // It's possible for a single tag to contain multiple credentials, + // but this is currently unimplementecd. + const uint8_t credential_sector_start_block_number = + mf_classic_get_first_block_num_of_sector(GALLAGHER_CREDENTIAL_SECTOR); + + // Test 1: The first 8 bytes and the second 8 bytes should be bitwise inverses. + const uint8_t* credential_block_start_ptr = + &data->block[credential_sector_start_block_number].data[0]; + uint64_t cardholder_credential = nfc_util_bytes2num(credential_block_start_ptr, 8); + uint64_t cardholder_credential_inverse = nfc_util_bytes2num(credential_block_start_ptr + 8, 8); + // Due to endianness, this is testing the bytes in the wrong order, + // but the result still should be correct. + if(cardholder_credential != ~cardholder_credential_inverse) { + return false; + } + + // Test 2: The contents of the second block should be equal to the GALLAGHER_CARDAX_ASCII constant. + const uint8_t* cardax_block_start_ptr = + &data->block[credential_sector_start_block_number + 1].data[0]; + if(memcmp(cardax_block_start_ptr, GALLAGHER_CARDAX_ASCII, MF_CLASSIC_BLOCK_SIZE) != 0) { + return false; + } + + // Deobfuscate the credential data + GallagherCredential credential; + gallagher_deobfuscate_and_parse_credential(&credential, credential_block_start_ptr); + + char display_region = 'A'; + // Per https://github.com/megabug/gallagher-research/blob/master/formats/cardholder/cardholder.md, + // regions are generally A-P. + if(credential.region < 16) { + display_region = display_region + (char)credential.region; + } else { + display_region = '?'; + } + + furi_string_cat_printf( + parsed_data, + "\e#Gallagher NZ\nFacility %c%u\nCard %lu (IL %u)", + display_region, + credential.facility, + credential.card, + credential.issue); + return true; +} + +static const NfcSupportedCardsPlugin gallagher_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = gallagher_parse, +}; + +static const FlipperAppPluginDescriptor gallagher_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &gallagher_plugin, +}; + +/* Plugin entry point */ +const FlipperAppPluginDescriptor* gallagher_plugin_ep() { + return &gallagher_plugin_descriptor; +} \ No newline at end of file diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp index ef22ee9ad..11f2d7ecf 100644 --- a/lib/flipper_application/api_hashtable/api_hashtable.cpp +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -21,7 +21,7 @@ bool elf_resolve_from_hashtable( auto find_res = std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key); if((find_res == hashtable_interface->table_cend || (find_res->hash != hash))) { - FURI_LOG_W( + FURI_LOG_T( TAG, "Can't find symbol with hash %lx @ %p!", hash, hashtable_interface->table_cbegin); result = false; } else { From 6bc63b7734a47887fdf9e7be8841cac460fc7f28 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 9 Feb 2024 08:07:54 +0000 Subject: [PATCH 02/11] [FL-3676] Slix disable privacy (#3425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * slix: add unlock option * slix: add features for nxp get info and signature commands * slix: working unlock * nfc app: rewrite slix unlock * slix poller: simplify unlock state handler * nfc app: fix slix key setting * nfc app: fix navigation * slix poller: code clean up * slix: resolve TODO, clean code * nfc app: fix naming * nfc app: rework slix unlock success scene * slix poller: add documentation * slix listener: fix password comparison Co-authored-by: あく --- applications/main/nfc/helpers/slix_unlock.c | 64 +++++++++++++++ applications/main/nfc/helpers/slix_unlock.h | 30 +++++++ applications/main/nfc/nfc_app.c | 2 + applications/main/nfc/nfc_app_i.h | 2 + .../main/nfc/scenes/nfc_scene_config.h | 5 ++ .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 +++ .../main/nfc/scenes/nfc_scene_retry_confirm.c | 6 +- .../nfc/scenes/nfc_scene_slix_key_input.c | 48 +++++++++++ .../main/nfc/scenes/nfc_scene_slix_unlock.c | 70 ++++++++++++++++ .../nfc/scenes/nfc_scene_slix_unlock_menu.c | 60 ++++++++++++++ .../scenes/nfc_scene_slix_unlock_success.c | 71 ++++++++++++++++ .../iso15693_3/iso15693_3_poller_i.c | 33 +------- lib/nfc/protocols/slix/slix.c | 2 +- lib/nfc/protocols/slix/slix.h | 4 + lib/nfc/protocols/slix/slix_i.c | 27 +++++++ lib/nfc/protocols/slix/slix_i.h | 4 +- lib/nfc/protocols/slix/slix_listener.c | 2 +- lib/nfc/protocols/slix/slix_listener_i.c | 2 +- lib/nfc/protocols/slix/slix_poller.c | 81 +++++++++++++++---- lib/nfc/protocols/slix/slix_poller.h | 34 ++++++++ lib/nfc/protocols/slix/slix_poller_i.c | 74 +++++++++++++++-- lib/nfc/protocols/slix/slix_poller_i.h | 5 ++ 22 files changed, 576 insertions(+), 60 deletions(-) create mode 100644 applications/main/nfc/helpers/slix_unlock.c create mode 100644 applications/main/nfc/helpers/slix_unlock.h create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c diff --git a/applications/main/nfc/helpers/slix_unlock.c b/applications/main/nfc/helpers/slix_unlock.c new file mode 100644 index 000000000..931ec1790 --- /dev/null +++ b/applications/main/nfc/helpers/slix_unlock.c @@ -0,0 +1,64 @@ +#include "slix_unlock.h" + +#include + +#define SLIX_UNLOCK_PASSWORD_NUM_MAX (2) + +struct SlixUnlock { + SlixUnlockMethod method; + SlixPassword password_arr[SLIX_UNLOCK_PASSWORD_NUM_MAX]; + size_t password_arr_len; + size_t password_idx; +}; + +static const SlixPassword tonie_box_pass_arr[] = {0x5B6EFD7F, 0x0F0F0F0F}; + +SlixUnlock* slix_unlock_alloc() { + SlixUnlock* instance = malloc(sizeof(SlixUnlock)); + + return instance; +} + +void slix_unlock_free(SlixUnlock* instance) { + furi_assert(instance); + + free(instance); +} + +void slix_unlock_reset(SlixUnlock* instance) { + furi_assert(instance); + + memset(instance, 0, sizeof(SlixUnlock)); +} + +void slix_unlock_set_method(SlixUnlock* instance, SlixUnlockMethod method) { + furi_assert(instance); + + instance->method = method; + if(method == SlixUnlockMethodTonieBox) { + instance->password_arr_len = COUNT_OF(tonie_box_pass_arr); + memcpy(instance->password_arr, tonie_box_pass_arr, sizeof(tonie_box_pass_arr)); + } +} + +void slix_unlock_set_password(SlixUnlock* instance, SlixPassword password) { + furi_assert(instance); + furi_assert(instance->method == SlixUnlockMethodManual); + + instance->password_arr[0] = password; + instance->password_arr_len = 1; +} + +bool slix_unlock_get_next_password(SlixUnlock* instance, SlixPassword* password) { + furi_assert(instance); + furi_assert(password); + + bool password_set = false; + if(instance->password_arr_len) { + *password = instance->password_arr[instance->password_idx++]; + instance->password_idx %= instance->password_arr_len; + password_set = true; + } + + return password_set; +} diff --git a/applications/main/nfc/helpers/slix_unlock.h b/applications/main/nfc/helpers/slix_unlock.h new file mode 100644 index 000000000..c378891e4 --- /dev/null +++ b/applications/main/nfc/helpers/slix_unlock.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SlixUnlockMethodManual, + SlixUnlockMethodTonieBox, +} SlixUnlockMethod; + +typedef struct SlixUnlock SlixUnlock; + +SlixUnlock* slix_unlock_alloc(); + +void slix_unlock_free(SlixUnlock* instance); + +void slix_unlock_reset(SlixUnlock* instance); + +void slix_unlock_set_method(SlixUnlock* instance, SlixUnlockMethod method); + +void slix_unlock_set_password(SlixUnlock* instance, SlixPassword password); + +bool slix_unlock_get_next_password(SlixUnlock* instance, SlixPassword* password); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index c0ca2a917..8120268e0 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -51,6 +51,7 @@ NfcApp* nfc_app_alloc() { instance->nfc = nfc_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); + instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); instance->nfc_supported_cards = nfc_supported_cards_alloc(); @@ -141,6 +142,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); mf_ultralight_auth_free(instance->mf_ul_auth); + slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); nfc_supported_cards_free(instance->nfc_supported_cards); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 324ed6a5d..706815e62 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,6 +32,7 @@ #include "helpers/mfkey32_logger.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/slix_unlock.h" #include #include @@ -129,6 +130,7 @@ struct NfcApp { NfcListener* listener; MfUltralightAuth* mf_ul_auth; + SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 70e7c3d46..035c4949c 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -59,4 +59,9 @@ ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) ADD_SCENE(nfc, set_uid, SetUid) +ADD_SCENE(nfc, slix_unlock_menu, SlixUnlockMenu) +ADD_SCENE(nfc, slix_key_input, SlixKeyInput) +ADD_SCENE(nfc, slix_unlock, SlixUnlock) +ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) + ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index d14f80b62..2943c0c55 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, + SubmenuIndexSlixUnlock, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { @@ -34,6 +35,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "Unlock SLIX-L", + SubmenuIndexSlixUnlock, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_set_selected_item( submenu, scene_manager_get_scene_state(instance->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexReadCardType) { scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); consumed = true; + } else if(event.event == SubmenuIndexSlixUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlockMenu); + consumed = true; } scene_manager_set_scene_state(instance->scene_manager, NfcSceneExtraActions, event.event); } diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 622996094..b6ad8144d 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -28,7 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSlixUnlock)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneSlixUnlock); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfClassicDictAttack)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicDictAttack); } else if(scene_manager_has_previous_scene( diff --git a/applications/main/nfc/scenes/nfc_scene_slix_key_input.c b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c new file mode 100644 index 000000000..1724eea8d --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c @@ -0,0 +1,48 @@ +#include "../nfc_app_i.h" + +#include + +void nfc_scene_slix_key_input_byte_input_callback(void* context) { + NfcApp* instance = context; + + SlixPassword password = nfc_util_bytes2num(instance->byte_input_store, sizeof(SlixPassword)); + slix_unlock_set_password(instance->slix_unlock, password); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_slix_key_input_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the password in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_slix_key_input_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(SlixPassword)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_slix_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlock); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_slix_key_input_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c new file mode 100644 index 000000000..b01876e06 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c @@ -0,0 +1,70 @@ +#include "../nfc_app_i.h" + +#include + +NfcCommand nfc_scene_slix_unlock_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSlix); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + SlixPollerEvent* slix_event = event.event_data; + if(slix_event->type == SlixPollerEventTypePrivacyUnlockRequest) { + SlixPassword pwd = 0; + bool get_password_success = slix_unlock_get_next_password(instance->slix_unlock, &pwd); + slix_event->data->privacy_password.password = pwd; + slix_event->data->privacy_password.password_set = get_password_success; + } else if(slix_event->type == SlixPollerEventTypeError) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + } else if(slix_event->type == SlixPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +void nfc_scene_slix_unlock_on_enter(void* context) { + NfcApp* instance = context; + + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix); + nfc_poller_start(instance->poller, nfc_scene_slix_unlock_worker_callback, instance); +} + +bool nfc_scene_slix_unlock_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + UNUSED(instance); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventPollerFailure) { + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlockSuccess); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSlixUnlockMenu); + } + + return consumed; +} + +void nfc_scene_slix_unlock_on_exit(void* context) { + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c new file mode 100644 index 000000000..78eb9884e --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c @@ -0,0 +1,60 @@ +#include "../nfc_app_i.h" + +enum SubmenuIndex { + SubmenuIndexSlixUnlockMenuManual, + SubmenuIndexSlixUnlockMenuTonieBox, +}; + +void nfc_scene_slix_unlock_menu_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_slix_unlock_menu_on_enter(void* context) { + NfcApp* instance = context; + Submenu* submenu = instance->submenu; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSlixUnlockMenu); + submenu_add_item( + submenu, + "Enter Password Manually", + SubmenuIndexSlixUnlockMenuManual, + nfc_scene_slix_unlock_menu_submenu_callback, + instance); + submenu_add_item( + submenu, + "Auth As TommyBox", + SubmenuIndexSlixUnlockMenuTonieBox, + nfc_scene_slix_unlock_menu_submenu_callback, + instance); + submenu_set_selected_item(submenu, state); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_slix_unlock_menu_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSlixUnlockMenuManual) { + slix_unlock_set_method(instance->slix_unlock, SlixUnlockMethodManual); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexSlixUnlockMenuTonieBox) { + slix_unlock_set_method(instance->slix_unlock, SlixUnlockMethodTonieBox); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlock); + consumed = true; + } + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneSlixUnlockMenu, event.event); + } + return consumed; +} + +void nfc_scene_slix_unlock_menu_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c new file mode 100644 index 000000000..f25eabab2 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c @@ -0,0 +1,71 @@ +#include "../nfc_app_i.h" + +static void nfc_scene_slix_unlock_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_slix_unlock_success_on_enter(void* context) { + NfcApp* instance = context; + + Widget* widget = instance->widget; + widget_add_string_element(widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "SLIX Unlocked!"); + + FuriString* temp_str = furi_string_alloc_set_str("UID:"); + size_t uid_len = 0; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", uid[i]); + } + furi_string_cat_printf(temp_str, "\nPrivacy Mode: Disabled"); + widget_add_string_multiline_element( + widget, 0, 12, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_slix_unlock_success_widget_callback, + instance); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "More", + nfc_scene_slix_unlock_success_widget_callback, + instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_slix_unlock_success_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + + return consumed; +} + +void nfc_scene_slix_unlock_success_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index ca6f5435e..949390305 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -32,17 +32,7 @@ static Iso15693_3Error iso15693_3_poller_filter_error(Iso15693_3Error error) { } } -static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) { - furi_assert(instance); - - if(instance->state == Iso15693_3PollerStateIdle) { - return iso15693_3_poller_activate(instance, NULL); - } - - return Iso15693_3ErrorNone; -} - -static Iso15693_3Error iso15693_3_poller_frame_exchange( +Iso15693_3Error iso15693_3_poller_send_frame( Iso15693_3Poller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, @@ -156,7 +146,7 @@ Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* Iso15693_3Error ret; do { - ret = iso15693_3_poller_frame_exchange( + ret = iso15693_3_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); if(ret != Iso15693_3ErrorNone) break; @@ -183,7 +173,7 @@ Iso15693_3Error Iso15693_3Error ret; do { - ret = iso15693_3_poller_frame_exchange( + ret = iso15693_3_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); if(ret != Iso15693_3ErrorNone) break; @@ -284,20 +274,3 @@ Iso15693_3Error iso15693_3_poller_get_blocks_security( return ret; } - -Iso15693_3Error iso15693_3_poller_send_frame( - Iso15693_3Poller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt) { - Iso15693_3Error ret; - - do { - ret = iso15693_3_poller_prepare_trx(instance); - if(ret != Iso15693_3ErrorNone) break; - - ret = iso15693_3_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); - } while(false); - - return ret; -} diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 12dc6750d..b575baa72 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -338,7 +338,7 @@ const Iso15693_3Data* slix_get_base_data(const SlixData* data) { } SlixType slix_get_type(const SlixData* data) { - SlixType type = SlixTypeCount; + SlixType type = SlixTypeUnknown; do { if(iso15693_3_get_manufacturer_id(data->iso15693_3_data) != SLIX_NXP_MANUFACTURER_CODE) diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h index f6c1453c5..26341072b 100644 --- a/lib/nfc/protocols/slix/slix.h +++ b/lib/nfc/protocols/slix/slix.h @@ -37,6 +37,7 @@ extern "C" { #define SLIX_TYPE_FEATURE_EAS (1U << 4) #define SLIX_TYPE_FEATURE_SIGNATURE (1U << 5) #define SLIX_TYPE_FEATURE_PROTECTION (1U << 6) +#define SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO (1U << 7) typedef uint32_t SlixTypeFeatures; @@ -56,7 +57,9 @@ typedef enum { SlixTypeSlixS, SlixTypeSlixL, SlixTypeSlix2, + SlixTypeCount, + SlixTypeUnknown, } SlixType; typedef enum { @@ -71,6 +74,7 @@ typedef enum { typedef uint32_t SlixPassword; typedef uint8_t SlixSignature[SLIX_SIGNATURE_SIZE]; typedef bool SlixPrivacy; +typedef uint16_t SlixRandomNumber; typedef struct { uint8_t pointer; diff --git a/lib/nfc/protocols/slix/slix_i.c b/lib/nfc/protocols/slix/slix_i.c index 97d66484c..26ac80be9 100644 --- a/lib/nfc/protocols/slix/slix_i.c +++ b/lib/nfc/protocols/slix/slix_i.c @@ -99,6 +99,33 @@ SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer return error; } +SlixError slix_get_random_number_response_parse(SlixRandomNumber* data, const BitBuffer* buf) { + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t random_number[2]; + } SlixGetRandomNumberResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixGetRandomNumberResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixGetRandomNumberResponseLayout* response = + (const SlixGetRandomNumberResponseLayout*)bit_buffer_get_data(buf); + *data = (response->random_number[1] << 8) | response->random_number[0]; + } while(false); + + return error; +} + void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password) { furi_assert(data); furi_assert(password_type < SlixPasswordTypeCount); diff --git a/lib/nfc/protocols/slix/slix_i.h b/lib/nfc/protocols/slix/slix_i.h index b5e445f31..4a15b50ff 100644 --- a/lib/nfc/protocols/slix/slix_i.h +++ b/lib/nfc/protocols/slix/slix_i.h @@ -48,7 +48,7 @@ extern "C" { #define SLIX_TYPE_FEATURES_SLIX2 \ (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS | SLIX_TYPE_FEATURE_SIGNATURE | \ - SLIX_TYPE_FEATURE_PROTECTION) + SLIX_TYPE_FEATURE_PROTECTION | SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO) #define SLIX2_FEATURE_FLAGS \ (SLIX_FEATURE_FLAG_UM_PP | SLIX_FEATURE_FLAG_COUNTER | SLIX_FEATURE_FLAG_EAS_ID | \ @@ -74,6 +74,8 @@ SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffe SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf); +SlixError slix_get_random_number_response_parse(SlixRandomNumber* data, const BitBuffer* buf); + // Setters void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password); diff --git a/lib/nfc/protocols/slix/slix_listener.c b/lib/nfc/protocols/slix/slix_listener.c index 204be5ab9..6ff390380 100644 --- a/lib/nfc/protocols/slix/slix_listener.c +++ b/lib/nfc/protocols/slix/slix_listener.c @@ -63,7 +63,7 @@ static NfcCommand slix_listener_run(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { const SlixError error = slix_listener_process_request(instance, rx_buffer); if(error == SlixErrorWrongPassword) { - command = NfcCommandStop; + command = NfcCommandSleep; } } diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c index dfcb6c880..15ab2cd3c 100644 --- a/lib/nfc/protocols/slix/slix_listener_i.c +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -31,7 +31,7 @@ static SlixPasswordType slix_listener_get_password_type_by_id(uint8_t id) { static SlixPassword slix_listener_unxor_password(const SlixPassword password_xored, uint16_t random) { - return password_xored ^ ((SlixPassword)random << 16 | random); + return REVERSE_BYTES_U32(password_xored ^ ((SlixPassword)random << 16 | random)); } static SlixError slix_listener_set_password( diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 46a171194..d9d38d102 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -44,33 +44,83 @@ static void slix_poller_free(SlixPoller* instance) { static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { iso15693_3_copy( instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); - - instance->poller_state = SlixPollerStateGetNxpSysInfo; + instance->type = slix_get_type(instance->data); + if(instance->type >= SlixTypeCount) { + instance->error = SlixErrorNotSupported; + instance->poller_state = SlixPollerStateError; + } else { + instance->poller_state = SlixPollerStateGetNxpSysInfo; + } return NfcCommandContinue; } static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { - instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); - if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReadSignature; + if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO)) { + instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReadSignature; + } else { + instance->poller_state = SlixPollerStateError; + } } else { - instance->poller_state = SlixPollerStateError; + instance->poller_state = SlixPollerStateReadSignature; } return NfcCommandContinue; } static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { - instance->error = slix_poller_read_signature(instance, &instance->data->signature); - if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReady; + if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_SIGNATURE)) { + instance->error = slix_poller_read_signature(instance, &instance->data->signature); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReady; + } else { + instance->poller_state = SlixPollerStateError; + } } else { - instance->poller_state = SlixPollerStateError; + instance->poller_state = SlixPollerStateReady; } return NfcCommandContinue; } +static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->poller_state = SlixPollerStateError; + + instance->slix_event.type = SlixPollerEventTypePrivacyUnlockRequest; + command = instance->callback(instance->general_event, instance->context); + + bool slix_unlocked = false; + do { + if(!instance->slix_event_data.privacy_password.password_set) break; + SlixPassword pwd = instance->slix_event_data.privacy_password.password; + FURI_LOG_I(TAG, "Trying to disable privacy mode with password: %08lX", pwd); + + instance->error = slix_poller_get_random_number(instance, &instance->random_number); + if(instance->error != SlixErrorNone) break; + + instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + if(instance->error != SlixErrorNone) { + command = NfcCommandReset; + break; + } + + FURI_LOG_I(TAG, "Privacy mode disabled"); + instance->data->passwords[SlixPasswordTypePrivacy] = pwd; + instance->poller_state = SlixPollerStateIdle; + slix_unlocked = true; + } while(false); + + if(!slix_unlocked) { + instance->error = SlixErrorTimeout; + instance->poller_state = SlixPollerStateError; + furi_delay_ms(100); + } + + return command; +} + static NfcCommand slix_poller_handler_error(SlixPoller* instance) { instance->slix_event_data.error = instance->error; instance->slix_event.type = SlixPollerEventTypeError; @@ -90,6 +140,7 @@ static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum [SlixPollerStateError] = slix_poller_handler_error, [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, + [SlixPollerStatePrivacyUnlock] = slix_poller_handler_privacy_unlock, [SlixPollerStateReady] = slix_poller_handler_ready, }; @@ -117,8 +168,8 @@ static NfcCommand slix_poller_run(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { command = slix_poller_state_handler[instance->poller_state](instance); } else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) { - instance->slix_event.type = SlixPollerEventTypeError; - command = instance->callback(instance->general_event, instance->context); + instance->poller_state = SlixPollerStatePrivacyUnlock; + command = slix_poller_state_handler[instance->poller_state](instance); } return command; @@ -138,11 +189,7 @@ static bool slix_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { - if(slix_get_type(instance->data) < SlixTypeCount) { - SlixSystemInfo system_info = {}; - SlixError error = slix_poller_get_nxp_system_info(instance, &system_info); - protocol_detected = (error == SlixErrorNone); - } + protocol_detected = (slix_get_type(instance->data) < SlixTypeCount); } return protocol_detected; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index 62d60be5f..4ea7f880d 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -18,14 +18,24 @@ typedef struct SlixPoller SlixPoller; */ typedef enum { SlixPollerEventTypeError, /**< An error occured while reading card. */ + SlixPollerEventTypePrivacyUnlockRequest, /**< Poller requests password to disable privacy mode. */ SlixPollerEventTypeReady, /**< The card was successfully read by the poller. */ } SlixPollerEventType; +/** + * @brief Slix poller privacy unlock context data. + */ +typedef struct { + SlixPassword password; /**< Privacy password. */ + bool password_set; /**< Filed to indicate that password was set or not. */ +} SlixPollerEventDataPrivacyUnlockContext; + /** * @brief Slixs poller event data. */ typedef union { SlixError error; /**< Error code indicating card reaing fail reason. */ + SlixPollerEventDataPrivacyUnlockContext privacy_password; /**< Privacy unlock event context. */ } SlixPollerEventData; /** @@ -80,6 +90,30 @@ SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* */ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data); +/** + * @brief Get random number from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixRandomNumber structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* data); + +/** + * @brief Set password to card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] type SlixPasswordType instance. + * @param[out] password SlixPassword instance. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError + slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 6d7bdf377..5000efceb 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -1,4 +1,5 @@ #include "slix_poller_i.h" +#include #include @@ -6,18 +7,22 @@ #define TAG "SlixPoller" -static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command) { +static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command, bool skip_uid) { bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); - bit_buffer_append_byte( - instance->tx_buffer, - ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | - ISO15693_3_REQ_FLAG_T4_ADDRESSED); + uint8_t flags = ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI; + if(!skip_uid) { + flags |= ISO15693_3_REQ_FLAG_T4_ADDRESSED; + } + + bit_buffer_append_byte(instance->tx_buffer, flags); bit_buffer_append_byte(instance->tx_buffer, command); bit_buffer_append_byte(instance->tx_buffer, SLIX_NXP_MANUFACTURER_CODE); - iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); + if(!skip_uid) { + iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); + } } SlixError slix_poller_send_frame( @@ -36,7 +41,7 @@ SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* furi_assert(instance); furi_assert(data); - slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION); + slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION, false); SlixError error = SlixErrorNone; @@ -54,7 +59,7 @@ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) furi_assert(instance); furi_assert(data); - slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE); + slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE, false); SlixError error = SlixErrorNone; @@ -67,3 +72,56 @@ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) return error; } + +SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_GET_RANDOM_NUMBER, true); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(error != SlixErrorNone) break; + + error = slix_get_random_number_response_parse(data, instance->rx_buffer); + } while(false); + + return error; +} + +SlixError + slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) { + furi_assert(instance); + + bool skip_uid = (type == SlixPasswordTypePrivacy); + slix_poller_prepare_request(instance, SLIX_CMD_SET_PASSWORD, skip_uid); + + uint8_t password_type = (0x01 << type); + bit_buffer_append_byte(instance->tx_buffer, password_type); + + uint8_t rn_l = instance->random_number >> 8; + uint8_t rn_h = instance->random_number; + uint32_t double_rand_num = (rn_h << 24) | (rn_l << 16) | (rn_h << 8) | rn_l; + uint32_t xored_password = double_rand_num ^ password; + uint8_t xored_password_arr[4] = {}; + nfc_util_num2bytes(xored_password, 4, xored_password_arr); + bit_buffer_append_bytes(instance->tx_buffer, xored_password_arr, 4); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, SLIX_POLLER_SET_PASSWORD_FWT); + if(error != SlixErrorNone) break; + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len != 1) { + error = SlixErrorWrongPassword; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h index 1fda1a7d2..7a3b543b7 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.h +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -4,6 +4,8 @@ #include "slix_poller.h" +#define SLIX_POLLER_SET_PASSWORD_FWT (100000) + #ifdef __cplusplus extern "C" { #endif @@ -12,6 +14,7 @@ typedef enum { SlixPollerStateIdle, SlixPollerStateGetNxpSysInfo, SlixPollerStateReadSignature, + SlixPollerStatePrivacyUnlock, SlixPollerStateReady, SlixPollerStateError, SlixPollerStateNum, @@ -19,9 +22,11 @@ typedef enum { struct SlixPoller { Iso15693_3Poller* iso15693_3_poller; + SlixType type; SlixData* data; SlixPollerState poller_state; SlixError error; + SlixRandomNumber random_number; BitBuffer* tx_buffer; BitBuffer* rx_buffer; From ebd09a198133bbfd649e066b48e64ff006236caa Mon Sep 17 00:00:00 2001 From: Alessandro Rossi Date: Fri, 9 Feb 2024 09:16:14 +0100 Subject: [PATCH 03/11] Added NFC plugin; Some parser (#3335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add parser * Fix microel.c * Fix NFC parser positive return * fix mizip * Fix NFC parser positive return * Add parse to hi! tag * fix false positive reading and kdf * hi formatting * fix oom in kdf * nfc app: fix types in hi plugin * nfc app: fix return in function body in microel plugin Co-authored-by: gornekich Co-authored-by: あく --- applications/main/nfc/application.fam | 27 ++ .../main/nfc/plugins/supported_cards/hi.c | 226 +++++++++++++++ .../nfc/plugins/supported_cards/microel.c | 228 ++++++++++++++++ .../main/nfc/plugins/supported_cards/mizip.c | 257 ++++++++++++++++++ 4 files changed, 738 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/hi.c create mode 100644 applications/main/nfc/plugins/supported_cards/microel.c create mode 100644 applications/main/nfc/plugins/supported_cards/mizip.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 1e6291c4d..4bcc79823 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -29,6 +29,33 @@ App( sources=["plugins/supported_cards/all_in_one.c"], ) +App( + appid="microel_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="microel_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/microel.c"], +) + +App( + appid="mizip_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="mizip_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/mizip.c"], +) + +App( + appid="hi_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hi_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hi.c"], +) + App( appid="opal_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c new file mode 100644 index 000000000..21e602877 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -0,0 +1,226 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "HI!" +#define KEY_LENGTH 6 +#define HI_KEY_TO_GEN 5 +#define UID_LENGTH 7 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + MfClassicKeyPair* keys; + uint32_t verify_sector; +} HiCardConfig; + +static MfClassicKeyPair hi_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x30871CF60CF1}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 + {.a = 0x42FFE4C76209, .b = 0x7B30CFD04CBD}, // 005 + {.a = 0x01ED8145BDF8, .b = 0x92257F472FCE}, // 006 + {.a = 0x7583A07D21A6, .b = 0x51CA6EA8EE26}, // 007 + {.a = 0x1E10BF5D6A1D, .b = 0x87B9B9BFABA6}, // 008 + {.a = 0xF9DB1B2B21BA, .b = 0x80A781F4134C}, // 009 + {.a = 0x7F5283FACB72, .b = 0x73250009D75A}, // 010 + {.a = 0xE48E86A03078, .b = 0xCFFBBF08A254}, // 011 + {.a = 0x39AB26301F60, .b = 0xC71A6E532C83}, // 012 + {.a = 0xAD656C6C639F, .b = 0xFD9819CBD20A}, // 013 + {.a = 0xF0E15160DB3E, .b = 0x3F622D515ADD}, // 014 + {.a = 0x03F44E033C42, .b = 0x61E897875F46}, // 015 +}; + +//KDF +void hi_generate_key(uint8_t* uid, uint8_t keyA[5][KEY_LENGTH], uint8_t keyB[5][KEY_LENGTH]) { + // Static XOR table for key generation + static const uint8_t xor_table_keyB[4][6] = { + {0x1F, 0xC4, 0x4D, 0x94, 0x6A, 0x31}, + {0x12, 0xC1, 0x5C, 0x70, 0xDF, 0x31}, + {0x56, 0xF0, 0x13, 0x1B, 0x63, 0xF2}, + {0x4E, 0xFA, 0xC2, 0xF8, 0xC9, 0xCC}}; + + static const uint8_t xor_table_keyA[4][6] = { + {0xB6, 0xE6, 0xAE, 0x72, 0x91, 0x0D}, + {0x6D, 0x38, 0x50, 0xFB, 0x42, 0x89}, + {0x1E, 0x5F, 0xC7, 0xED, 0xAA, 0x02}, + {0x7E, 0xB9, 0xCA, 0xF1, 0x9C, 0x59}}; + + // Permutation table for rearranging elements in uid + static const uint8_t xorOrderA[6] = {0, 1, 2, 3, 0, 2}; + static const uint8_t xorOrderB[6] = {1, 3, 3, 2, 1, 0}; + + // Generate key based on uid and XOR table + for(uint8_t j = 1; j < 5; j++) { + for(uint8_t i = 0; i < 6; i++) { + keyA[j][i] = uid[xorOrderA[i]] ^ xor_table_keyA[j - 1][i]; + keyB[j][i] = uid[xorOrderB[i]] ^ xor_table_keyB[j - 1][i]; + } + } +} + +static bool hi_get_card_config(HiCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->verify_sector = 0; + config->keys = hi_1k_keys; + } else { + success = false; + } + + return success; +} + +static bool hi_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeB, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d, this is not a HI card", block_num, error); + break; + } + FURI_LOG_D(TAG, "Found a HI Card"); + verified = true; + } while(false); + + return verified; +} + +static bool hi_verify(Nfc* nfc) { + return hi_verify_type(nfc, MfClassicType1k); +} + +static bool hi_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering HI KDF"); + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, data->type)) break; + + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + uint8_t keyA[HI_KEY_TO_GEN][KEY_LENGTH]; + uint8_t keyB[HI_KEY_TO_GEN][KEY_LENGTH]; + hi_generate_key(uid, keyA, keyB); + + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { + cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); + cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + } + } + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool hi_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, data->type)) break; + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + if(key != cfg.keys[cfg.verify_sector].b) return false; + + //Get UID + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + //parse data + furi_string_cat_printf(parsed_data, "\e#HI! Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf(parsed_data, "\n"); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hi_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hi_verify, + .read = hi_read, + .parse = hi_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hi_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hi_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hi_plugin_ep() { + return &hi_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/microel.c b/applications/main/nfc/plugins/supported_cards/microel.c new file mode 100644 index 000000000..899ad2a7e --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/microel.c @@ -0,0 +1,228 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "Microel" +#define KEY_LENGTH 6 +#define UID_LENGTH 4 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair microel_1k_keys[] = { + {.a = 0x000000000000, .b = 0x000000000000}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 002 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 003 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 004 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 005 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 006 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 007 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 008 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 009 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 010 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 011 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 012 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 013 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 014 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 015 +}; + +const uint8_t verify_sector = 1; + +void calculateSumHex(const uint8_t* uid, size_t uidSize, uint8_t sumHex[]) { + const uint8_t xorKey[] = {0x01, 0x92, 0xA7, 0x75, 0x2B, 0xF9}; + int sum = 0; + + for(size_t i = 0; i < uidSize; i++) { + sum += uid[i]; + } + + int sumTwoDigits = sum % 256; + + if(sumTwoDigits % 2 == 1) { + sumTwoDigits += 2; + } + + for(size_t i = 0; i < sizeof(xorKey); i++) { + sumHex[i] = sumTwoDigits ^ xorKey[i]; + } +} + +void generateKeyA(const uint8_t* uid, uint8_t uidSize, uint8_t keyA[]) { + uint8_t sumHex[6]; + calculateSumHex(uid, uidSize, sumHex); + uint8_t firstCharacter = (sumHex[0] >> 4) & 0xF; + + if(firstCharacter == 0x2 || firstCharacter == 0x3 || firstCharacter == 0xA || + firstCharacter == 0xB) { + // XOR WITH 0x40 + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = 0x40 ^ sumHex[i]; + } + } else if( + firstCharacter == 0x6 || firstCharacter == 0x7 || firstCharacter == 0xE || + firstCharacter == 0xF) { + // XOR WITH 0xC0 + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = 0xC0 ^ sumHex[i]; + } + } else { + //Key a is the same as sumHex + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = sumHex[i]; + } + } +} + +void generateKeyB(uint8_t keyA[], size_t keyASize, uint8_t keyB[]) { + for(size_t i = 0; i < keyASize; i++) { + keyB[i] = 0xFF ^ keyA[i]; + } +} + +static bool microel_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering Microel KDF"); + + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + //Get UID and check if it is 4 bytes + size_t uid_len; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + FURI_LOG_D(TAG, "UID identified: %02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]); + if(uid_len != UID_LENGTH) break; + + // Generate keys + uint8_t keyA[KEY_LENGTH]; + uint8_t keyB[KEY_LENGTH]; + generateKeyA(uid, UID_LENGTH, keyA); + generateKeyB(keyA, KEY_LENGTH, keyB); + + // Check key 0a to verify if it is a microel card + MfClassicKey key = {0}; + nfc_util_num2bytes(nfc_util_bytes2num(keyA, KEY_LENGTH), COUNT_OF(key.data), key.data); + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(0); // This is 0 + MfClassicAuthContext auth_context; + error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + break; + } + + // Save keys generated to stucture + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(microel_1k_keys[i].a == 0x000000000000) { + microel_1k_keys[i].a = nfc_util_bytes2num(keyA, KEY_LENGTH); + } + if(microel_1k_keys[i].b == 0x000000000000) { + microel_1k_keys[i].b = nfc_util_bytes2num(keyB, KEY_LENGTH); + } + } + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(microel_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(microel_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool microel_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + //Get UID + size_t uid_len; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + if(uid_len != UID_LENGTH) break; + + // Generate key from uid + uint8_t keyA[KEY_LENGTH]; + generateKeyA(uid, UID_LENGTH, keyA); + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + uint64_t key_for_check_from_array = nfc_util_bytes2num(keyA, KEY_LENGTH); + if(key != key_for_check_from_array) break; + + //Get credit in block number 8 + const uint8_t* temp_ptr = data->block[4].data; + uint16_t balance = (temp_ptr[6] << 8) | (temp_ptr[5]); + uint16_t previus_balance = (data->block[5].data[6] << 8) | (data->block[5].data[5]); + furi_string_cat_printf(parsed_data, "\e#Microel Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf( + parsed_data, "\nCurrent Credit: %d.%02d E \n", balance / 100, balance % 100); + furi_string_cat_printf( + parsed_data, + "Previus Credit: %d.%02d E \n", + previus_balance / 100, + previus_balance % 100); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin microel_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = + NULL, // the verification I need is based on verifying the keys generated via uid and try to authenticate not like on mizip that there is default b0 but added verify in read function + .read = microel_read, + .parse = microel_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor microel_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = µel_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* microel_plugin_ep() { + return µel_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c new file mode 100644 index 000000000..bbcf9fdbc --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -0,0 +1,257 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "MiZIP" +#define KEY_LENGTH 6 +#define MIZIP_KEY_TO_GEN 5 +#define UID_LENGTH 4 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + MfClassicKeyPair* keys; + uint32_t verify_sector; +} MizipCardConfig; + +static MfClassicKeyPair mizip_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xb4c132439eef}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 + {.a = 0x0222179AB995, .b = 0x13321774F9B5}, // 005 + {.a = 0xB25CBD76A7B4, .b = 0x7571359B4274}, // 006 + {.a = 0xDA857B4907CC, .b = 0xD26B856175F7}, // 007 + {.a = 0x16D85830C443, .b = 0x8F790871A21E}, // 008 + {.a = 0x88BD5098FC82, .b = 0xFCD0D77745E4}, // 009 + {.a = 0x983349449D78, .b = 0xEA2631FBDEDD}, // 010 + {.a = 0xC599F962F3D9, .b = 0x949B70C14845}, // 011 + {.a = 0x72E668846BE8, .b = 0x45490B5AD707}, // 012 + {.a = 0xBCA105E5685E, .b = 0x248DAF9D674D}, // 013 + {.a = 0x4F6FE072D1FD, .b = 0x4250A05575FA}, // 014 + {.a = 0x56438ABE8152, .b = 0x59A45912B311}, // 015 +}; + +static MfClassicKeyPair mizip_mini_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xb4c132439eef}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 +}; + +//KDF +void mizip_generate_key(uint8_t* uid, uint8_t keyA[5][KEY_LENGTH], uint8_t keyB[5][KEY_LENGTH]) { + // Static XOR table for key generation + static const uint8_t xor_table_keyA[4][6] = { + {0x09, 0x12, 0x5A, 0x25, 0x89, 0xE5}, + {0xAB, 0x75, 0xC9, 0x37, 0x92, 0x2F}, + {0xE2, 0x72, 0x41, 0xAF, 0x2C, 0x09}, + {0x31, 0x7A, 0xB7, 0x2F, 0x44, 0x90}}; + + static const uint8_t xor_table_keyB[4][6] = { + {0xF1, 0x2C, 0x84, 0x53, 0xD8, 0x21}, + {0x73, 0xE7, 0x99, 0xFE, 0x32, 0x41}, + {0xAA, 0x4D, 0x13, 0x76, 0x56, 0xAE}, + {0xB0, 0x13, 0x27, 0x27, 0x2D, 0xFD}}; + + // Permutation table for rearranging elements in uid + static const uint8_t xorOrderA[6] = {0, 1, 2, 3, 0, 1}; + static const uint8_t xorOrderB[6] = {2, 3, 0, 1, 2, 3}; + + // Generate key based on uid and XOR table + for(uint8_t j = 1; j < 5; j++) { + for(uint8_t i = 0; i < 6; i++) { + keyA[j][i] = uid[xorOrderA[i]] ^ xor_table_keyA[j - 1][i]; + keyB[j][i] = uid[xorOrderB[i]] ^ xor_table_keyB[j - 1][i]; + } + } +} + +static bool mizip_get_card_config(MizipCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->verify_sector = 0; + config->keys = mizip_1k_keys; + } else if(type == MfClassicTypeMini) { + config->verify_sector = 0; + config->keys = mizip_mini_keys; + } else { + success = false; + } + + return success; +} + +static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeB, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d, this is not a MiZIP card", block_num, error); + break; + } + FURI_LOG_D(TAG, "Found a MiZIP Card"); + verified = true; + } while(false); + + return verified; +} + +static bool mizip_verify(Nfc* nfc) { + return mizip_verify_type(nfc, MfClassicType1k) || mizip_verify_type(nfc, MfClassicTypeMini); +} + +static bool mizip_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering MiZIP KDF"); + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + //temp fix but fix mf_classic_poller_sync_detect_type because view type mfclassic1k and not verify mfmini + data->type = MfClassicTypeMini; + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, data->type)) break; + + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + uint8_t keyA[MIZIP_KEY_TO_GEN][KEY_LENGTH]; + uint8_t keyB[MIZIP_KEY_TO_GEN][KEY_LENGTH]; + mizip_generate_key(uid, keyA, keyB); + + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { + cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); + cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + } + } + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool mizip_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, data->type)) break; + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + if(key != cfg.keys[cfg.verify_sector].b) return false; + + //Get UID + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + //Get credit + uint8_t credit_pointer = 0x08; + uint8_t previus_credit_pointer = 0x09; + if(data->block[10].data[0] == 0x55) { + credit_pointer = 0x09; + previus_credit_pointer = 0x08; + } + uint16_t balance = (data->block[credit_pointer].data[2] << 8) | + (data->block[credit_pointer].data[1]); + uint16_t previus_balance = (data->block[previus_credit_pointer].data[2] << 8) | + (data->block[previus_credit_pointer].data[1]); + + //parse data + furi_string_cat_printf(parsed_data, "\e#MiZIP Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf( + parsed_data, "\nCurrent Credit: %d.%02d E \n", balance / 100, balance % 100); + furi_string_cat_printf( + parsed_data, + "Previus Credit: %d.%02d E \n", + previus_balance / 100, + previus_balance % 100); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin mizip_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = mizip_verify, + .read = mizip_read, + .parse = mizip_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor mizip_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &mizip_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* mizip_plugin_ep() { + return &mizip_plugin_descriptor; +} From 50e0521bf7f2e37e98286520da51db9b6d4c1556 Mon Sep 17 00:00:00 2001 From: Tolly Hill Date: Fri, 9 Feb 2024 08:36:06 +0000 Subject: [PATCH 04/11] NFC: Custom UID entry when adding manually (#3363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Custom UID entry when adding manually * Fix incorrect types * Add Change UID option post-generation * Update UID derived data when using set_uid method * Fix PVS warnings Co-authored-by: gornekich Co-authored-by: あく --- .../protocol_support/mf_classic/mf_classic.c | 3 ++ .../mf_ultralight/mf_ultralight.c | 11 +++-- .../protocol_support/nfc_protocol_support.c | 9 ++++ .../main/nfc/plugins/supported_cards/hi.c | 2 +- .../main/nfc/plugins/supported_cards/mizip.c | 2 +- .../main/nfc/scenes/nfc_scene_set_uid.c | 4 ++ lib/nfc/helpers/nfc_data_generator.c | 43 +++++-------------- lib/nfc/protocols/mf_classic/mf_classic.c | 20 ++++++++- .../protocols/mf_ultralight/mf_ultralight.c | 14 +++++- 9 files changed, 69 insertions(+), 39 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 148601191..6abaa48cd 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -199,6 +199,9 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag } else if(event.event == SubmenuIndexDictAttack) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } } diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index f0a46dd87..eb6911df7 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -266,16 +266,21 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexUnlock) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); - return true; + consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - return true; + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } } - return false; + return consumed; } const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 2d46810a2..80fbb63de 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -233,6 +233,15 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneGenerateInfo)) { + submenu_add_item( + submenu, + "Change UID", + SubmenuIndexCommonEdit, + nfc_protocol_support_common_submenu_callback, + instance); + } + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c index 21e602877..6807ab00c 100644 --- a/applications/main/nfc/plugins/supported_cards/hi.c +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -88,7 +88,7 @@ static bool hi_verify_type(Nfc* nfc, MfClassicType type) { if(!hi_get_card_config(&cfg, type)) break; const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); - FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index bbcf9fdbc..b76c381e9 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -99,7 +99,7 @@ static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { if(!mizip_get_card_config(&cfg, type)) break; const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); - FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index df8a4dc72..f09d0a430 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -44,6 +44,10 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); consumed = true; } + } else if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneReadMenu)) { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneReadMenu); + consumed = true; } else { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 21f062605..0ecccc3ac 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -35,26 +35,16 @@ static void nfc_generate_mf_ul_uid(uint8_t* uid) { } static void nfc_generate_mf_ul_common(MfUltralightData* mfu_data) { + uint8_t uid[7]; mfu_data->iso14443_3a_data->uid_len = 7; - nfc_generate_mf_ul_uid(mfu_data->iso14443_3a_data->uid); + nfc_generate_mf_ul_uid(uid); + mf_ultralight_set_uid(mfu_data, uid, 7); + mfu_data->iso14443_3a_data->atqa[0] = 0x44; mfu_data->iso14443_3a_data->atqa[1] = 0x00; mfu_data->iso14443_3a_data->sak = 0x00; } -static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { - *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; - *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; -} - -static void nfc_generate_mf_ul_copy_uid_with_bcc(MfUltralightData* mfu_data) { - memcpy(mfu_data->page[0].data, mfu_data->iso14443_3a_data->uid, 3); - memcpy(mfu_data->page[1].data, &mfu_data->iso14443_3a_data->uid[3], 4); - - nfc_generate_calc_bcc( - mfu_data->iso14443_3a_data->uid, &mfu_data->page[0].data[3], &mfu_data->page[2].data[0]); -} - static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { MfUltralightData* mfu_data = mf_ultralight_alloc(); nfc_generate_mf_ul_common(mfu_data); @@ -62,7 +52,6 @@ static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { mfu_data->type = MfUltralightTypeUnknown; mfu_data->pages_total = 16; mfu_data->pages_read = 16; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); memset(&mfu_data->page[4], 0xff, sizeof(MfUltralightPage)); nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); @@ -74,7 +63,7 @@ static void nfc_generate_mf_ul_with_config_common(MfUltralightData* mfu_data, ui mfu_data->pages_total = num_pages; mfu_data->pages_read = num_pages; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + uint16_t config_index = (num_pages - 4); mfu_data->page[config_index].data[0] = 0x04; // STRG_MOD_EN mfu_data->page[config_index].data[3] = 0xff; // AUTH0 @@ -150,7 +139,6 @@ static void nfc_generate_ntag203(NfcDevice* nfc_device) { mfu_data->type = MfUltralightTypeNTAG203; mfu_data->pages_total = 42; mfu_data->pages_read = 42; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); mfu_data->page[2].data[1] = 0x48; // Internal byte memcpy(&mfu_data->page[3], default_data_ntag203, sizeof(MfUltralightPage)); //-V1086 @@ -379,14 +367,7 @@ static void nfc_generate_mf_classic_block_0( furi_assert(uid_len == 4 || uid_len == 7); furi_assert(block); - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(int i = 0; i < uid_len; i++) { - block[uid_len] ^= block[i]; - } - } else { + if(uid_len == 7) { uid_len -= 1; } @@ -402,14 +383,12 @@ static void nfc_generate_mf_classic_block_0( static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfClassicType type) { MfClassicData* mfc_data = mf_classic_alloc(); - nfc_generate_mf_classic_uid(mfc_data->block[0].data, uid_len); - nfc_generate_mf_classic_common(mfc_data, uid_len, type); + uint8_t uid[ISO14443_3A_MAX_UID_SIZE]; - // Set the UID - mfc_data->iso14443_3a_data->uid[0] = NXP_MANUFACTURER_ID; - for(int i = 1; i < uid_len; i++) { - mfc_data->iso14443_3a_data->uid[i] = mfc_data->block[0].data[i]; - } + nfc_generate_mf_classic_uid(uid, uid_len); + mf_classic_set_uid(mfc_data, uid, uid_len); + + nfc_generate_mf_classic_common(mfc_data, uid_len, type); mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index e68e8c718..27236df4b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -346,7 +346,25 @@ const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) { bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) { furi_assert(data); - return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + bool uid_valid = iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + + if(uid_valid) { + uint8_t* block = data->block[0].data; + + // Copy UID to block 0 + memcpy(block, data->iso14443_3a_data->uid, uid_len); + + if(uid_len == 4) { + // Calculate BCC byte + block[uid_len] = 0; + + for(size_t i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } + } + + return uid_valid; } Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index fd845f3c0..3cffa56ff 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -482,7 +482,19 @@ const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_l bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) { furi_assert(data); - return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + bool uid_valid = iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + + if(uid_valid) { + // Copy UID across first 2 pages + memcpy(data->page[0].data, data->iso14443_3a_data->uid, 3); + memcpy(data->page[1].data, &data->iso14443_3a_data->uid[3], 4); + + // Calculate BCC bytes + data->page[0].data[3] = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; + data->page[2].data[0] = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; + } + + return uid_valid; } Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) { From c0db3d541e6fe6ea202b909044c9fd3f5056831d Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:49:58 +0400 Subject: [PATCH 05/11] [FL-3753] UI SubGhz: fix UI only_rx scene (#3379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UI SubGhz: fix UI only_rx scene * UI SubGhz: delete unused scene Co-authored-by: あく --- .../main/subghz/scenes/subghz_scene_config.h | 1 - .../subghz/scenes/subghz_scene_show_only_rx.c | 49 ------------------- applications/main/subghz/subghz_i.c | 8 +-- 3 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 applications/main/subghz/scenes/subghz_scene_show_only_rx.c diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 47655958b..60bc8528a 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -8,7 +8,6 @@ ADD_SCENE(subghz, saved, Saved) ADD_SCENE(subghz, transmitter, Transmitter) ADD_SCENE(subghz, show_error, ShowError) ADD_SCENE(subghz, show_error_sub, ShowErrorSub) -ADD_SCENE(subghz, show_only_rx, ShowOnlyRx) ADD_SCENE(subghz, saved_menu, SavedMenu) ADD_SCENE(subghz, delete, Delete) ADD_SCENE(subghz, delete_success, DeleteSuccess) diff --git a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c deleted file mode 100644 index 3522bf8aa..000000000 --- a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../subghz_i.h" -#include "../helpers/subghz_custom_event.h" - -void subghz_scene_show_only_rx_popup_callback(void* context) { - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneShowOnlyRX); -} - -void subghz_scene_show_only_rx_on_enter(void* context) { - SubGhz* subghz = context; - - // Setup view - Popup* popup = subghz->popup; - - const char* header_text = "Transmission is blocked"; - const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; - if(!furi_hal_region_is_provisioned()) { - header_text = "Firmware update needed"; - message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; - } - - popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); - popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); - popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); - - popup_set_timeout(popup, 1500); - popup_set_context(popup, subghz); - popup_set_callback(popup, subghz_scene_show_only_rx_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); -} - -bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzCustomEventSceneShowOnlyRX) { - scene_manager_previous_scene(subghz->scene_manager); - return true; - } - } - return false; -} - -void subghz_scene_show_only_rx_on_exit(void* context) { - SubGhz* subghz = context; - Popup* popup = subghz->popup; - - popup_reset(popup); -} diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 4358b164d..b553d00de 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -60,15 +60,15 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { DialogsApp* dialogs = subghz->dialogs; DialogMessage* message = dialog_message_alloc(); - const char* header_text = "Transmission is blocked"; - const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; + const char* header_text = "Transmission is Blocked!"; + const char* message_text = "Transmission on\nthis frequency is\nrestricted in your\nregion"; if(!furi_hal_region_is_provisioned()) { header_text = "Firmware update needed"; message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; } - dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); - dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); + dialog_message_set_header(message, header_text, 63, 0, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 1, 13, AlignLeft, AlignTop); dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); From b2a7bb0696316c94a75d1d5f3e21dcce2068014e Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Feb 2024 11:59:24 +0300 Subject: [PATCH 06/11] [FL-3672] CLI: cat command crash workaround (#3358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cli input len limit, CDC EP type fix Co-authored-by: あく --- applications/services/cli/cli.c | 6 ++- targets/f7/furi_hal/furi_hal_usb_cdc.c | 53 ++++++++++++++++---------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 55a603a20..41e658df1 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -6,6 +6,8 @@ #define TAG "CliSrv" +#define CLI_INPUT_LEN_LIMIT 256 + Cli* cli_alloc() { Cli* cli = malloc(sizeof(Cli)); @@ -356,7 +358,9 @@ void cli_process_input(Cli* cli) { cli_handle_backspace(cli); } else if(in_chr == CliSymbolAsciiCR) { cli_handle_enter(cli); - } else if(in_chr >= 0x20 && in_chr < 0x7F) { //-V560 + } else if( + (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 + (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { if(cli->cursor_position == furi_string_size(cli->line)) { furi_string_push_back(cli->line, in_chr); cli_putc(cli, in_chr); diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index e9cb51e20..014c98bad 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -7,13 +7,13 @@ #include "usb.h" #include "usb_cdc.h" -#define CDC0_RXD_EP 0x01 +#define CDC0_RXD_EP 0x02 #define CDC0_TXD_EP 0x82 -#define CDC0_NTF_EP 0x83 +#define CDC0_NTF_EP 0x81 #define CDC1_RXD_EP 0x04 -#define CDC1_TXD_EP 0x85 -#define CDC1_NTF_EP 0x86 +#define CDC1_TXD_EP 0x84 +#define CDC1_NTF_EP 0x83 #define CDC_NTF_SZ 0x08 @@ -438,7 +438,9 @@ static void cdc_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { struct usb_string_descriptor* dev_prod_desc = malloc(len * 2 + 2); dev_prod_desc->bLength = len * 2 + 2; dev_prod_desc->bDescriptorType = USB_DTYPE_STRING; - for(uint8_t i = 0; i < len; i++) dev_prod_desc->wString[i] = name[i]; + for(uint8_t i = 0; i < len; i++) { + dev_prod_desc->wString[i] = name[i]; + } name = (char*)furi_hal_version_get_name_ptr(); len = (name == NULL) ? (0) : (strlen(name)); @@ -446,7 +448,9 @@ static void cdc_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { dev_serial_desc->bLength = (len + 5) * 2 + 2; dev_serial_desc->bDescriptorType = USB_DTYPE_STRING; memcpy(dev_serial_desc->wString, "f\0l\0i\0p\0_\0", 5 * 2); - for(uint8_t i = 0; i < len; i++) dev_serial_desc->wString[i + 5] = name[i]; + for(uint8_t i = 0; i < len; i++) { + dev_serial_desc->wString[i + 5] = name[i]; + } cdc_if_cur->str_prod_descr = dev_prod_desc; cdc_if_cur->str_serial_descr = dev_serial_desc; @@ -500,18 +504,20 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { } void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { - if(if_num == 0) + if(if_num == 0) { usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); - else + } else { usbd_ep_write(usb_dev, CDC1_TXD_EP, buf, len); + } } int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { int32_t len = 0; - if(if_num == 0) + if(if_num == 0) { len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); - else + } else { len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + } return ((len < 0) ? 0 : len); } @@ -540,14 +546,16 @@ static void cdc_rx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); uint8_t if_num = 0; - if(ep == CDC0_RXD_EP) + if(ep == CDC0_RXD_EP) { if_num = 0; - else + } else { if_num = 1; + } if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->rx_ep_callback != NULL) + if(callbacks[if_num]->rx_ep_callback != NULL) { callbacks[if_num]->rx_ep_callback(cb_ctx[if_num]); + } } } @@ -555,14 +563,16 @@ static void cdc_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); uint8_t if_num = 0; - if(ep == CDC0_TXD_EP) + if(ep == CDC0_TXD_EP) { if_num = 0; - else + } else { if_num = 1; + } if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->tx_ep_callback != NULL) + if(callbacks[if_num]->tx_ep_callback != NULL) { callbacks[if_num]->tx_ep_callback(cb_ctx[if_num]); + } } } @@ -642,25 +652,28 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == (USB_REQ_INTERFACE | USB_REQ_CLASS) && (req->wIndex == 0 || req->wIndex == 2)) { - if(req->wIndex == 0) + if(req->wIndex == 0) { if_num = 0; - else + } else { if_num = 1; + } switch(req->bRequest) { case USB_CDC_SET_CONTROL_LINE_STATE: if(callbacks[if_num] != NULL) { cdc_ctrl_line_state[if_num] = req->wValue; - if(callbacks[if_num]->ctrl_line_callback != NULL) + if(callbacks[if_num]->ctrl_line_callback != NULL) { callbacks[if_num]->ctrl_line_callback( cb_ctx[if_num], cdc_ctrl_line_state[if_num]); + } } return usbd_ack; case USB_CDC_SET_LINE_CODING: memcpy(&cdc_config[if_num], req->data, sizeof(cdc_config[0])); if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->config_callback != NULL) + if(callbacks[if_num]->config_callback != NULL) { callbacks[if_num]->config_callback(cb_ctx[if_num], &cdc_config[if_num]); + } } return usbd_ack; case USB_CDC_GET_LINE_CODING: From 3c73123a81a7aeb1032680a2f33496c1396bb2b9 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Feb 2024 12:33:47 +0300 Subject: [PATCH 07/11] HID app: keyboard modifiers fix (#3378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HID App: Modifier keys fix * Toggled keys indication * New enter and backspace button icons Co-authored-by: あく --- .../hid_app/assets/Alt_pressed_17x10.png | Bin 0 -> 4227 bytes .../system/hid_app/assets/Backspace_9x7.png | Bin 0 -> 6628 bytes .../hid_app/assets/Cmd_pressed_17x10.png | Bin 0 -> 4234 bytes .../hid_app/assets/Ctrl_pressed_17x10.png | Bin 0 -> 4227 bytes .../system/hid_app/assets/Return_10x7.png | Bin 0 -> 6630 bytes .../hid_app/assets/Shift_pressed_7x10.png | Bin 0 -> 6626 bytes .../system/hid_app/views/hid_keyboard.c | 212 ++++++++++-------- 7 files changed, 115 insertions(+), 97 deletions(-) create mode 100644 applications/system/hid_app/assets/Alt_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Backspace_9x7.png create mode 100644 applications/system/hid_app/assets/Cmd_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Ctrl_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Return_10x7.png create mode 100644 applications/system/hid_app/assets/Shift_pressed_7x10.png diff --git a/applications/system/hid_app/assets/Alt_pressed_17x10.png b/applications/system/hid_app/assets/Alt_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..e7421c8c0bf7d189200b2b953219f24b287f093d GIT binary patch literal 4227 zcmeHKYitx%6rSawWdYGr&_JS_sX(R7&dl!4?9QZHEZsg<9VoCswK4JT%-wcmU(QUs z+m@h$ii!~mY7_ybJbVI*f%pOy9~GlPFvK6cOf%D;wK(7YC*nbWCrscG>BKq{k_D??BJotWF9o|^bwv3t^{`l<5ldrUW`Od1=(%)N83kO@BZx^Z+ z+!w94e)ViidQ#`ARnPxEyY$GKr^jD7^~A(S_!4eZ`?~Fik8U}(_3)-y7Y=-I;?Bp% z+TVWlp+yCf_u-C*%ZoQe&ODP`yXf;6#QY1L&x{)P zTkDQ96L#IUiF~W_##MOPI_6Y9H(}qg{E_V)c-^l3moD$0Q@H2w$4#$AhECl*|Ge<-!p~oww{P5!KTcV+a&z6q0ecoUjQ+;vc0r1z)ljWb zE8QW=iI@{9i7(Oijz#0TnD&zvt_ZTJrb&loV$AiEwU9F92@N|uPLGqJ zD$;6`i!8M}N;D-R&InXyAz1!k!R{obyjMYEf%DicI4uj)AETtFpb8G%r`q z!=#{IjU_EFAiI~Pp+>sN>J^(gV{NBrAmEx)W4 zRu$0_ImI18OxQZjt>jsL;*n|RbrwTYcQuv4$ z@f4$k70Tm94CP}LFNNH^hvRr3gWcgC5YsdjvJypmMrA@N02TJ|ir`Z?N=B^DgkmYg zARmQUA>#HSA*^^<3rdm2%7hj}aGYuk)nhstueT;l!o{+hpr3R(nQl!@6d4h4@RN6_ z@y2xbL`aR{nFcc1WW5aMcC(zD=Y3wE&(&=-8*4gbqKRphNo&SD77;cB#3D0K0l?D3 zUc_<@BO{@O5{amv%oW(&v;@~lK?VvS0|RKT6%oaXt^vUV*MBB1Q^nxU3#{1g;=Gtsf&t zk#$hF^o}LffX!0?!YGENSnf)~=N|CU7&_8yHe$uf8$fxr5tJ&f?zD{pQotnI&C+@Y>R0&g5F|NZwt4bO0C;te6_uH07QJe*x!(u95%% literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Backspace_9x7.png b/applications/system/hid_app/assets/Backspace_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..e098cf676167cdd78687b61cc1b61caf6e6b5666 GIT binary patch literal 6628 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 literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Cmd_pressed_17x10.png b/applications/system/hid_app/assets/Cmd_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..274c3e070ae42bccbb0b463aa3396a12486704f6 GIT binary patch literal 4234 zcmeHKd2AF_7@y^kvRttTF*Lz(Do`o2bL`IU&ZG;L-O{eIP@qDwiE(!3Z9A}+JJasA zMMRK8G;#^bQPF~;7!DyAP!7QZF`9COK*G^*$Pr0U&WI5FW_G)@N@AMGf8EL3_r33Z z-|v0z_rCY-&YNXpO8S@wm@y3N<1O`!hyHPB=8P`zeQF?a6S^&^!W-~!D?WtlQ{qfT_FD&j2|$CC>vnKV_r)Fs3e8ALtiCV?oY_ z{sUL^aeeb3D5W(w;+g+xP zFByq@i`{mHx7$f@!?47vnnle8o^jdxM_A|e9+%%sIlNgsU*ZeCyQpl5ZPcIJme*aF zyTWn3{;F?9aOsAo?|#_S{WVi1+K|0yF*()$?Tu9zKU?(kj>d-Jw;QhVry4BV^OR!l zr-o;LdA~j}>{4Un%G(o%pZRF%;5(P!9y-Tf$mKMx-PLk-%eieW8(+9{a?gdy^K;GH z*S|40+vk|Qf3~~elfd<5(KU+YW1Ki%sqv|7n8d^@|(qNQ$suzU0Kx?MtL7qIDfE-lL$bgg0c z^&tnI+emC3HLwvaUQ1uj!D#oRy~Z?8zr}ww>&Nxejt;tVW#rsdn=0>iIW()f{}Gdwg%nFDmCIkQoAx*>+pi6mnwTKNYMu^} z#PA?hiYZY|iG*<-Q}jn_G#5d@Jl+~#DC+a2;lpZ*1>l29h*65M&{QZywe?W7;#vSn z1@ur4wY)BhsPRaR)WjrIT#Le5URwxRO8ZA^VnJg&vP7XE3W2E#t1|7D)Gt@sL#Loh z2}KPrAiJHVrUV|4)h;%D#@J5VK)^kX+s?Xm?}jm0`Fw&WBGu^Oc|9&dUtf?Tk|GO+ z$S_VMvl2t{D8P_5&dQRaHQ*%eh-XBZ;bkPTZJ@kiRTIM!(xCv{q5zJAlMpN0{ba!E zL;%4e(k})?QetVw$@w{lO}4dx7#mX{E5%^jsB|b9plF*_lKe7D(z4_yZM@S@`kg#S zav~BD?{sji-)=z3k}xU~3yE-?N=U3iR5V;=Oz4CQMP*(W!CL4CnzEp%1;D{Yj8?)m zi3bzqN(fERM4e5>L336s!`ZEjgJ+z{m<_y%D5gRt>X=5EG~D%L5nwYwtf=QH02q4M zi{OqSQH#XNBaxtsNEg`jv=6S6ENY@h)DVECOKy2<$t|F*0-X$Bhx0{bB~bTY*81ha z3&9?ayi`$P{W?RHyipU-E6GuE7*vd_gyY7g5JV}t1y!s?vJod>C8wlnF0uV;h49Rf+B#cUJn9^%T>y8Vle{oW17_=!e;Fr`v@q$`NrHf&T zGl=(5eo|-gD3`$T&P6)JZzo-ybajY<4jFf5S0`N^VxU9Do!Rxj(Pe({I)%dUFDMQ# zOF3ld3cPAP=6}A#gWcDk4|lCw0-B!D(#a}@^~%zl30tx12~hUXyuRWdmrVWo^)9Gb z`qyz#m3loz<@wEx3-+2ZJi}di#{bR4m1}XD=la~3z(RV!Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Ctrl_pressed_17x10.png b/applications/system/hid_app/assets/Ctrl_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..978a1090c2ee9f38e07c66adc5f4a3250885a048 GIT binary patch literal 4227 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 W(tV}+OFw`b5Nq>|)W6JpWB(JHjv}J~ literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 9060c1d6a..1ee2c01c9 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -20,19 +20,18 @@ typedef struct { uint8_t x; uint8_t y; uint8_t last_key_code; - uint16_t modifier_code; bool ok_pressed; bool back_pressed; bool connected; - char key_string[5]; HidTransport transport; } HidKeyboardModel; typedef struct { uint8_t width; - char* key; + char key; + char shift_key; const Icon* icon; - char* shift_key; + const Icon* icon_toggled; uint8_t value; } HidKeyboardKey; @@ -66,85 +65,97 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, }, { - {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, - {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, - {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, - {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, - {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, - {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, - {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, - {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, - {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, - {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, - {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 1, .icon = NULL, .key = '1', .shift_key = '!', .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = '2', .shift_key = '@', .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = '3', .shift_key = '#', .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = '4', .shift_key = '$', .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = '5', .shift_key = '%', .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = '6', .shift_key = '^', .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = '7', .shift_key = '&', .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = '8', .shift_key = '*', .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = '9', .shift_key = '(', .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = '0', .shift_key = ')', .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Backspace_9x7, .value = HID_KEYBOARD_DELETE}, {.width = 0, .value = HID_KEYBOARD_DELETE}, }, { - {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, - {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, - {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, - {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, - {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, - {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, - {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, - {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, - {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, - {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, - {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, .icon = NULL, .key = 'q', .shift_key = 'Q', .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = 'w', .shift_key = 'W', .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = 'e', .shift_key = 'E', .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = 'r', .shift_key = 'R', .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = 't', .shift_key = 'T', .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = 'y', .shift_key = 'Y', .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = 'u', .shift_key = 'U', .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = 'i', .shift_key = 'I', .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = 'o', .shift_key = 'O', .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = 'p', .shift_key = 'P', .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = '[', .shift_key = '{', .value = HID_KEYBOARD_OPEN_BRACKET}, {.width = 1, .icon = NULL, - .key = "]", - .shift_key = "}", + .key = ']', + .shift_key = '}', .value = HID_KEYBOARD_CLOSE_BRACKET}, }, { - {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, - {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, - {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, - {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, - {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, - {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, - {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, - {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, - {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, - {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, - {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 1, .icon = NULL, .key = 'a', .shift_key = 'A', .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = 's', .shift_key = 'S', .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = 'd', .shift_key = 'D', .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = 'f', .shift_key = 'F', .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = 'g', .shift_key = 'G', .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = 'h', .shift_key = 'H', .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = 'j', .shift_key = 'J', .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = 'k', .shift_key = 'K', .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = 'l', .shift_key = 'L', .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ';', .shift_key = ':', .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Return_10x7, .value = HID_KEYBOARD_RETURN}, {.width = 0, .value = HID_KEYBOARD_RETURN}, }, { - {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, - {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, - {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, - {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, - {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, - {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, - {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, - {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, - {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, - {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = NULL, .key = 'z', .shift_key = 'Z', .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = 'x', .shift_key = 'X', .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = 'c', .shift_key = 'C', .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = 'v', .shift_key = 'V', .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = 'b', .shift_key = 'B', .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = 'n', .shift_key = 'N', .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = 'm', .shift_key = 'M', .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = '/', .shift_key = '?', .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = '\\', .shift_key = '|', .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = '`', .shift_key = '~', .value = HID_KEYBOARD_GRAVE_ACCENT}, {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, - {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + {.width = 1, .icon = NULL, .key = '-', .shift_key = '_', .value = HID_KEYBOARD_MINUS}, }, { - {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, - {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, - {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, - {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, + .icon = &I_Pin_arrow_up_7x9, + .icon_toggled = &I_Shift_pressed_7x10, + .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ',', .shift_key = '<', .value = HID_KEYBOARD_COMMA}, + {.width = 1, .icon = NULL, .key = '.', .shift_key = '>', .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = ' ', .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, - {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, - {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = NULL, .key = '\'', .shift_key = '\"', .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = '=', .shift_key = '+', .value = HID_KEYBOARD_EQUAL_SIGN}, {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, }, { - {.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL}, + {.width = 2, + .icon = &I_Ctrl_17x10, + .icon_toggled = &I_Ctrl_pressed_17x10, + .value = HID_KEYBOARD_L_CTRL}, {.width = 0, .value = HID_KEYBOARD_L_CTRL}, - {.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT}, + {.width = 2, + .icon = &I_Alt_17x10, + .icon_toggled = &I_Alt_pressed_17x10, + .value = HID_KEYBOARD_L_ALT}, {.width = 0, .value = HID_KEYBOARD_L_ALT}, - {.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI}, + {.width = 2, + .icon = &I_Cmd_17x10, + .icon_toggled = &I_Cmd_pressed_17x10, + .value = HID_KEYBOARD_L_GUI}, {.width = 0, .value = HID_KEYBOARD_L_GUI}, {.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB}, {.width = 0, .value = HID_KEYBOARD_TAB}, @@ -155,13 +166,6 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { }, }; -static void hid_keyboard_to_upper(char* str) { - while(*str) { - *str = toupper((unsigned char)*str); - str++; - } -} - static void hid_keyboard_draw_key( Canvas* canvas, HidKeyboardModel* model, @@ -192,28 +196,32 @@ static void hid_keyboard_draw_key( KEY_HEIGHT); } if(key.icon != NULL) { + const Icon* key_icon = key.icon; + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->shift && key.value == HID_KEYBOARD_L_SHIFT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + if(key.icon_toggled) { + key_icon = key.icon_toggled; + } + } // Draw the icon centered on the button canvas_draw_icon( canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, - key.icon); + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key_icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key_icon->height / 2, + key_icon); } else { + char key_str[2] = "\0\0"; // If shift is toggled use the shift key when available - strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); - // Upper case if ctrl or alt was toggled true - if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || - (model->alt && key.value == HID_KEYBOARD_L_ALT) || - (model->gui && key.value == HID_KEYBOARD_L_GUI)) { - hid_keyboard_to_upper(model->key_string); - } + key_str[0] = (model->shift && key.shift_key != 0) ? key.shift_key : key.key; canvas_draw_str_aligned( canvas, MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, AlignCenter, AlignCenter, - model->key_string); + key_str); } } @@ -244,6 +252,8 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { initY = model->y - 4; } + elements_scrollbar(canvas, initY, 3); + for(uint8_t y = initY; y < ROW_COUNT; y++) { const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; uint8_t x = 0; @@ -286,6 +296,14 @@ static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoin 0); // Skip zero width keys, pretend they are one key } +static void hid_keyboard_modifier_set(Hid* hid, uint16_t keycode, bool is_pressed) { + if(is_pressed) { + hid_hal_keyboard_press(hid, keycode); + } else { + hid_hal_keyboard_release(hid, keycode); + } +} + static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { with_view_model( hid_keyboard->view, @@ -300,35 +318,25 @@ static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { // Toggle the modifier key when clicked, and click the key if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { model->shift = !model->shift; - if(model->shift) - model->modifier_code |= KEY_MOD_LEFT_SHIFT; - else - model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + hid_keyboard_modifier_set( + hid_keyboard->hid, KEY_MOD_LEFT_SHIFT, model->shift); } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { model->alt = !model->alt; - if(model->alt) - model->modifier_code |= KEY_MOD_LEFT_ALT; - else - model->modifier_code &= ~KEY_MOD_LEFT_ALT; + hid_keyboard_modifier_set(hid_keyboard->hid, KEY_MOD_LEFT_ALT, model->alt); } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { model->ctrl = !model->ctrl; - if(model->ctrl) - model->modifier_code |= KEY_MOD_LEFT_CTRL; - else - model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + hid_keyboard_modifier_set( + hid_keyboard->hid, KEY_MOD_LEFT_CTRL, model->ctrl); } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { model->gui = !model->gui; - if(model->gui) - model->modifier_code |= KEY_MOD_LEFT_GUI; - else - model->modifier_code &= ~KEY_MOD_LEFT_GUI; + hid_keyboard_modifier_set(hid_keyboard->hid, KEY_MOD_LEFT_GUI, model->gui); + } else { + hid_hal_keyboard_press(hid_keyboard->hid, model->last_key_code); } - hid_hal_keyboard_press( - hid_keyboard->hid, model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { // Release happens after short and long presses - hid_hal_keyboard_release( - hid_keyboard->hid, model->modifier_code | model->last_key_code); + hid_hal_keyboard_release(hid_keyboard->hid, model->last_key_code); model->ok_pressed = false; } } else if(event->key == InputKeyBack) { @@ -364,6 +372,16 @@ static bool hid_keyboard_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeLong && event->key == InputKeyBack) { hid_hal_keyboard_release_all(hid_keyboard->hid); + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { + model->shift = false; + model->alt = false; + model->ctrl = false; + model->gui = false; + }, + true); } else { hid_keyboard_process(hid_keyboard, event); consumed = true; From 962d809ad7b2dde665ef431be90d07b1ad1be4dc Mon Sep 17 00:00:00 2001 From: tomellens <156447023+tomellens@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:39:35 +0000 Subject: [PATCH 08/11] Update tv.ir (#3399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index d10e14df2..8a646ae51 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1724,3 +1724,41 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 +# +# Sony KD-55AG8 Smart TV +# +name: Power +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Mute +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +name: Vol_up +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Ch_next +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 11 00 00 00 From bcdb9cb13cbcea9d487e9f6f18d8f50c95ec3609 Mon Sep 17 00:00:00 2001 From: JuicyPigWalker <34217507+JuicyPigWalker@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:12:46 +0100 Subject: [PATCH 09/11] Update tv.ir (#3421) * Update tv.ir: Added tv "TCL 50P715X1" recorded remote signals. Hope everything is okay, this is my first GitHub contribution. * Update tv.ir: Fixed my own mistakes when uploading the signals. * Infrared: revert 962d809ad7b2dde665ef431be90d07b1ad1be4dc Co-authored-by: Yoel <34217507+yoelci@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .../infrared/resources/infrared/assets/tv.ir | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 8a646ae51..ff8101b90 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1724,41 +1724,41 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 -# -# Sony KD-55AG8 Smart TV -# +# +# Model TCL 50P715X1 +# name: Power type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 15 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 # name: Mute type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 14 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 # name: Vol_up type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 12 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 # name: Vol_dn type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 13 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 # name: Ch_next type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 10 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 # name: Ch_prev type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 11 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 From 4c2e99799738d4d41aaf5b2e6e524c98794f8f83 Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Fri, 9 Feb 2024 15:20:54 +0300 Subject: [PATCH 10/11] Disable expansion interrupt if the handle was acquired --- targets/f7/furi_hal/furi_hal_serial_control.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c index 11ef23a6f..d2ab414c2 100644 --- a/targets/f7/furi_hal/furi_hal_serial_control.c +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -120,16 +120,21 @@ static bool furi_hal_serial_control_handler_stop(void* input, void* output) { static bool furi_hal_serial_control_handler_acquire(void* input, void* output) { FuriHalSerialId serial_id = *(FuriHalSerialId*)input; - if(furi_hal_serial_control->handles[serial_id].in_use) { + FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[serial_id]; + + if(handle->in_use) { *(FuriHalSerialHandle**)output = NULL; } else { // Logging if(furi_hal_serial_control->log_config_serial_id == serial_id) { furi_hal_serial_control_log_set_handle(NULL); + // Expansion + } else if(furi_hal_serial_control->expansion_serial == handle) { + furi_hal_serial_control_enable_expansion_irq(handle, false); } // Return handle - furi_hal_serial_control->handles[serial_id].in_use = true; - *(FuriHalSerialHandle**)output = &furi_hal_serial_control->handles[serial_id]; + handle->in_use = true; + *(FuriHalSerialHandle**)output = handle; } return true; @@ -143,9 +148,12 @@ static bool furi_hal_serial_control_handler_release(void* input, void* output) { furi_hal_serial_deinit(handle); handle->in_use = false; - // Return back logging if(furi_hal_serial_control->log_config_serial_id == handle->id) { + // Return back logging furi_hal_serial_control_log_set_handle(handle); + } else if(furi_hal_serial_control->expansion_serial == handle) { + // Re-enable expansion + furi_hal_serial_control_enable_expansion_irq(handle, true); } return true; From 73deff88635e061188d851a6755a0f65a02fd1dd Mon Sep 17 00:00:00 2001 From: Georgii Surkov Date: Fri, 9 Feb 2024 16:31:45 +0300 Subject: [PATCH 11/11] Do not use a timer callback --- applications/services/expansion/expansion.c | 4 ++-- .../services/expansion/expansion_worker.c | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 99cdd984f..8f260f915 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -57,7 +57,7 @@ static void expansion_detect_callback(void* context) { ExpansionMessage message = { .type = ExpansionMessageTypeModuleConnected, - .api_lock = NULL, // Not locking the API here + .api_lock = NULL, // Not locking the API here to avoid a deadlock }; // Not waiting for available queue space, discarding message if there is none @@ -71,7 +71,7 @@ static void expansion_worker_callback(void* context) { ExpansionMessage message = { .type = ExpansionMessageTypeModuleDisconnected, - .api_lock = NULL, // Not locking the API here + .api_lock = NULL, // Not locking the API here to avoid a deadlock }; const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index c2219a40d..fd92063d2 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -317,18 +317,6 @@ static inline void expansion_worker_state_machine(ExpansionWorker* instance) { } } -static void expansion_worker_pending_callback(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); - - ExpansionWorker* instance = context; - furi_thread_join(instance->thread); - - if(instance->callback != NULL) { - instance->callback(instance->cb_context); - } -} - static int32_t expansion_worker(void* context) { furi_assert(context); ExpansionWorker* instance = context; @@ -361,9 +349,9 @@ static int32_t expansion_worker(void* context) { furi_hal_serial_control_release(instance->serial_handle); furi_hal_power_insomnia_exit(); - // Do not invoke pending callback on user-requested exit - if(instance->exit_reason != ExpansionWorkerExitReasonUser) { - furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); + // Do not invoke worker callback on user-requested exit + if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { + instance->callback(instance->cb_context); } return 0; @@ -385,6 +373,7 @@ ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { void expansion_worker_free(ExpansionWorker* instance) { furi_stream_buffer_free(instance->rx_buf); + furi_thread_join(instance->thread); furi_thread_free(instance->thread); free(instance); }