diff --git a/ReadMe.md b/ReadMe.md index ee3d394bf..f0e45d585 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -194,7 +194,7 @@ See full list and sources here: [xMasterX/all-the-plugins](https://github.com/xM ### - [How to use Flipper as new remote (Nice FlorS, BFT Mitto, Somfy Telis, Aprimatic, AN-Motors, etc..)](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md) -### - [~~Configure Sub-GHz Remote App~~](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) Not recomeded, please use embedded configurator +### - [~~Configure Sub-GHz Remote App~~](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) Not recommended, please use embedded configurator ## **Plugins** 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 9f5867ab0..4c7d20a63 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", ], @@ -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, @@ -155,6 +182,15 @@ App( sources=["plugins/supported_cards/zolotaya_korona_online.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/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 84d1aabd2..bf562bf58 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/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 ca3510fb4..62426be7b 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -33,6 +33,7 @@ #include "helpers/nfc_emv_parser.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/slix_unlock.h" #include #include @@ -130,6 +131,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/plugins/supported_cards/aime.c b/applications/main/nfc/plugins/supported_cards/aime.c index d9759421a..c747a70b9 100644 --- a/applications/main/nfc/plugins/supported_cards/aime.c +++ b/applications/main/nfc/plugins/supported_cards/aime.c @@ -67,7 +67,7 @@ static bool aime_read(Nfc* nfc, NfcDevice* device) { nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); 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/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c new file mode 100644 index 000000000..1d390196c --- /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 %lu", 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 == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (error == MfClassicErrorNone); + } 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/hid.c b/applications/main/nfc/plugins/supported_cards/hid.c index 0622e3dc1..cb0178ecc 100644 --- a/applications/main/nfc/plugins/supported_cards/hid.c +++ b/applications/main/nfc/plugins/supported_cards/hid.c @@ -67,7 +67,7 @@ static bool hid_read(Nfc* nfc, NfcDevice* device) { nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index a1bcbb1f8..841d126d1 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -34,7 +34,7 @@ typedef struct { uint64_t b; } MfClassicKeyPair; -static const MfClassicKeyPair kazan_1k_keys_standart[] = { +static const MfClassicKeyPair kazan_1k_keys_v1[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, @@ -53,7 +53,7 @@ static const MfClassicKeyPair kazan_1k_keys_standart[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, }; -static const MfClassicKeyPair kazan_1k_keys_old[] = { +static const MfClassicKeyPair kazan_1k_keys_v2[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, @@ -72,6 +72,25 @@ static const MfClassicKeyPair kazan_1k_keys_old[] = { {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, }; +static const MfClassicKeyPair kazan_1k_keys_v3[] = { + {.a = 0x165D3B5280C0, .b = 0xFC7C2BB34E0F}, + {.a = 0xC178E3DA7A39, .b = 0xC70FB78B4934}, + {.a = 0x1BDF96089D2F, .b = 0x9500F058ABC5}, + {.a = 0xB65AA70AD524, .b = 0x733A63B8B7F3}, + {.a = 0x8BDB8FECDCAF, .b = 0xB0048EE71C0F}, + {.a = 0xBC10468ABF05, .b = 0x1700A7D5C034}, + {.a = 0xE7F3282E0C7D, .b = 0x65909B89BDA5}, + {.a = 0x986C63DD0355, .b = 0x901C125ED37D}, + {.a = 0x2058EAEE8446, .b = 0xCB9B23815F87}, + {.a = 0x492F3744A1DC, .b = 0x6B770AADA274}, + {.a = 0x87EB933B9BF7, .b = 0xFC98A9460EE5}, + {.a = 0x7EBC8337F8F0, .b = 0x887C97E53DBC}, + {.a = 0xA369BF6D4452, .b = 0x03BBA7CA2F24}, + {.a = 0x37569D7992EF, .b = 0x710BBD01B3B8}, + {.a = 0xD4AA94C4B5E8, .b = 0x7F5C4D210F0B}, + {.a = 0x521B8C4B2123, .b = 0x2D2392CC43A7}, +}; + enum SubscriptionType { SUBSCRIPTION_TYPE_UNKNOWN, SUBSCRIPTION_TYPE_PURSE, @@ -96,6 +115,9 @@ enum SubscriptionType get_subscription_type(uint8_t value, FuriString* tariff_na case 0x53: furi_string_printf(tariff_name, "Standart purse"); return SUBSCRIPTION_TYPE_PURSE; + case 0x01: + furi_string_printf(tariff_name, "Token"); + return SUBSCRIPTION_TYPE_ABONNEMENT_BY_TRIPS; default: furi_string_printf(tariff_name, "Unknown"); return SUBSCRIPTION_TYPE_UNKNOWN; @@ -106,21 +128,34 @@ static bool kazan_verify(Nfc* nfc) { bool verified = false; do { - const uint8_t verification_sector_number = 10; + const uint8_t verification_sector_number = 8; const uint8_t verification_block_number = mf_classic_get_first_block_num_of_sector(verification_sector_number) + 1; FURI_LOG_D(TAG, "Verifying sector %u", verification_sector_number); - MfClassicKey key = {0}; + MfClassicKey key_1 = {0}; nfc_util_num2bytes( - kazan_1k_keys_standart[verification_sector_number].a, COUNT_OF(key.data), key.data); + kazan_1k_keys_v1[verification_sector_number].a, COUNT_OF(key_1.data), key_1.data); MfClassicAuthContext auth_context; MfClassicError error = mf_classic_poller_sync_auth( - nfc, verification_block_number, &key, MfClassicKeyTypeA, &auth_context); + nfc, verification_block_number, &key_1, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", verification_block_number, error); - break; + FURI_LOG_D( + TAG, "Failed to read block %u: %d. Keys: v1", verification_block_number, error); + + MfClassicKey key_2 = {0}; + nfc_util_num2bytes( + kazan_1k_keys_v2[verification_sector_number].a, COUNT_OF(key_2.data), key_2.data); + + MfClassicAuthContext auth_context; + MfClassicError error = mf_classic_poller_sync_auth( + nfc, verification_block_number, &key_2, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d. Keys: v2", verification_block_number, error); + break; + } } verified = true; @@ -145,41 +180,57 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { data->type = type; if(type != MfClassicType1k) break; - MfClassicDeviceKeys keys = { + MfClassicDeviceKeys keys_v1 = { .key_a_mask = 0, .key_b_mask = 0, }; - MfClassicDeviceKeys keys_old = { + MfClassicDeviceKeys keys_v2 = { + .key_a_mask = 0, + .key_b_mask = 0, + }; + + MfClassicDeviceKeys keys_v3 = { .key_a_mask = 0, .key_b_mask = 0, }; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes( - kazan_1k_keys_standart[i].a, sizeof(MfClassicKey), keys.key_a[i].data); - nfc_util_num2bytes( - kazan_1k_keys_old[i].a, sizeof(MfClassicKey), keys_old.key_a[i].data); - FURI_BIT_SET(keys.key_a_mask, i); - FURI_BIT_SET(keys_old.key_a_mask, i); + nfc_util_num2bytes(kazan_1k_keys_v1[i].a, sizeof(MfClassicKey), keys_v1.key_a[i].data); + nfc_util_num2bytes(kazan_1k_keys_v2[i].a, sizeof(MfClassicKey), keys_v2.key_a[i].data); + nfc_util_num2bytes(kazan_1k_keys_v3[i].a, sizeof(MfClassicKey), keys_v3.key_a[i].data); - nfc_util_num2bytes( - kazan_1k_keys_standart[i].b, sizeof(MfClassicKey), keys.key_b[i].data); - nfc_util_num2bytes( - kazan_1k_keys_old[i].b, sizeof(MfClassicKey), keys_old.key_b[i].data); - FURI_BIT_SET(keys.key_b_mask, i); - FURI_BIT_SET(keys_old.key_b_mask, i); + FURI_BIT_SET(keys_v1.key_a_mask, i); + FURI_BIT_SET(keys_v2.key_a_mask, i); + FURI_BIT_SET(keys_v3.key_a_mask, i); + + nfc_util_num2bytes(kazan_1k_keys_v1[i].b, sizeof(MfClassicKey), keys_v1.key_b[i].data); + nfc_util_num2bytes(kazan_1k_keys_v2[i].b, sizeof(MfClassicKey), keys_v2.key_b[i].data); + nfc_util_num2bytes(kazan_1k_keys_v3[i].b, sizeof(MfClassicKey), keys_v3.key_b[i].data); + + FURI_BIT_SET(keys_v1.key_b_mask, i); + FURI_BIT_SET(keys_v2.key_b_mask, i); + FURI_BIT_SET(keys_v3.key_b_mask, i); } - error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { - FURI_LOG_W(TAG, "Failed to read data: standart keys"); + error = mf_classic_poller_sync_read(nfc, &keys_v1, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data: keys_v1"); break; } + if(!mf_classic_is_card_read(data)) { - error = mf_classic_poller_sync_read(nfc, &keys_old, data); - if(error != MfClassicErrorNone) { - FURI_LOG_W(TAG, "Failed to read data: old keys"); + error = mf_classic_poller_sync_read(nfc, &keys_v2, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data: keys_v1"); + break; + } + } + + if(!mf_classic_is_card_read(data)) { + error = mf_classic_poller_sync_read(nfc, &keys_v3, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data: keys_v3"); break; } } @@ -202,18 +253,19 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) { bool parsed = false; do { - const uint8_t verification_sector_number = 10; const uint8_t ticket_sector_number = 8; const uint8_t balance_sector_number = 9; // Verify keys MfClassicKeyPair keys = {}; const MfClassicSectorTrailer* sec_tr = - mf_classic_get_sector_trailer_by_sector(data, verification_sector_number); + mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); keys.a = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + keys.b = nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); - if(keys.a != 0xF7A545095C49) { + if(((keys.a != kazan_1k_keys_v1[8].a) && (keys.a != kazan_1k_keys_v2[8].a)) || + ((keys.b != kazan_1k_keys_v1[8].b) && (keys.b != kazan_1k_keys_v2[8].b))) { FURI_LOG_D(TAG, "Parser: Failed to verify key a: %llu", keys.a); break; } diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c index 2b33da153..34855bcb5 100644 --- a/applications/main/nfc/plugins/supported_cards/metromoney.c +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -107,14 +107,14 @@ static bool metromoney_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); 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..12c196d11 --- /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 == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (error == MfClassicErrorNone); + } 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..4fd1bd5e0 --- /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 %lu", 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 == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (error == MfClassicErrorNone); + } 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; +} diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index a40f59a16..1a2497f2c 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -362,20 +362,26 @@ static bool ndef_parse(const NfcDevice* device, FuriString* parsed_data) { // Memory layout documentation: // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/nfc/doc/type_2_tag.html#id2 - // Double check static values layout - // First 4 static reserved pages for UID, internal and lock bytes - // (Not sure if NDEF cata can be found in cards with different layout) - if(data->page[0].data[0] != 0x04) break; - if(data->page[2].data[1] != 0x48) break; // Internal - if(data->page[2].data[2] != 0x00) break; // Lock bytes - if(data->page[2].data[3] != 0x00) break; // ... - if(data->page[3].data[0] != 0xE1) break; // Capability container - if(data->page[3].data[1] != 0x10) break; // ... + // Check card type can contain NDEF + if(data->type != MfUltralightTypeNTAG203 && data->type != MfUltralightTypeNTAG213 && + data->type != MfUltralightTypeNTAG215 && data->type != MfUltralightTypeNTAG216 && + data->type != MfUltralightTypeNTAGI2C1K && data->type != MfUltralightTypeNTAGI2C2K) { + break; + } - // Data content starts here at 5th page + // Double check Capability Container (CC) and find data area bounds + struct { + uint8_t nfc_magic_number; + uint8_t document_version_number; + uint8_t data_area_size; + uint8_t read_write_access; + }* cc = (void*)&data->page[3].data[0]; + if(cc->nfc_magic_number != 0xE1) break; + if(cc->document_version_number != 0x10) break; const uint8_t* cur = &data->page[4].data[0]; - const uint8_t* end = &data->page[0].data[0] + - (mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE); + const uint8_t* end = cur + (cc->data_area_size * 2 * MF_ULTRALIGHT_PAGE_SIZE); + size_t max_size = mf_ultralight_get_pages_total(data->type) * MF_ULTRALIGHT_PAGE_SIZE; + end = MIN(end, &data->page[0].data[0] + max_size); size_t message_num = 0; // Parse as TLV (see docs above) diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c index 2f3523caa..4b59e853e 100644 --- a/applications/main/nfc/plugins/supported_cards/saflok.c +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -137,14 +137,14 @@ static bool saflok_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c index c272b8062..321526320 100644 --- a/applications/main/nfc/plugins/supported_cards/social_moscow.c +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -1534,14 +1534,14 @@ static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 2bc4ba579..22e262241 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1559,14 +1559,14 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index 86ea91f99..874337ec7 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -108,14 +108,14 @@ static bool washcity_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = mf_classic_is_card_read(data); + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c index 1778cd38e..2965e7e4f 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c @@ -40,37 +40,6 @@ static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x 0xAE, 0xE0, 0xAE, 0xAD, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 -#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) -#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) -#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 - -void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { - uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; - uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; - - datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; - - while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { - days -= furi_hal_rtc_get_days_per_year(datetime->year); - (datetime->year)++; - } - - datetime->month = 1; - while(days >= furi_hal_rtc_get_days_per_month( - furi_hal_rtc_is_leap_year(datetime->year), datetime->month)) { - days -= furi_hal_rtc_get_days_per_month( - furi_hal_rtc_is_leap_year(datetime->year), datetime->month); - (datetime->month)++; - } - - datetime->day = days + 1; - datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; - datetime->minute = - (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; - datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; -} - uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) { furi_assert(src); furi_assert(len_bytes <= 9); @@ -151,7 +120,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da const uint16_t refill_counter = nfc_util_bytes2num_little_endian(block_start_ptr + 10, 2); FuriHalRtcDateTime last_refill_datetime = {0}; - timestamp_to_datetime(last_refill_timestamp, &last_refill_datetime); + furi_hal_rtc_timestamp_to_datetime(last_refill_timestamp, &last_refill_datetime); // block 2: trip block block_start_ptr = &data->block[start_trip_block_number + 2].data[0]; @@ -166,7 +135,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da const uint8_t prev_balance_kop = prev_balance % 100; FuriHalRtcDateTime last_trip_datetime = {0}; - timestamp_to_datetime(last_trip_timestamp, &last_trip_datetime); + furi_hal_rtc_timestamp_to_datetime(last_trip_timestamp, &last_trip_datetime); // PARSE DATA FROM PURSE SECTOR const uint8_t start_purse_block_number = diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 9453b8a3f..609b48c09 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1,90 +1,145 @@ ########################### # Do not edit, this file will be overwritten after firmware update # Use the user_dict file for user keys -# Last update 19th October, 2022 -# ------------------------- -# MIFARE DEFAULT KEYS -# -- ICEMAN FORK VERSION -- -# -- CONTRIBUTE TO THIS LIST, SHARING IS CARING -- -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic -# DEFAULTKEY(FIRSTKEYUSEDBYPROGRAMIFNOUSERDEFINEDKEY) +# Last updated 17 December 2023 +########################### +# +-----------------------------------------------------------------------------------------------------+ +# | https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic | +# +-----------------------------------------------------------------------------------------------------+ +# Mifare Default Keys +# -- iceman fork version -- +# -- contribute to this list, sharing is caring -- +# Default key FFFFFFFFFFFF -# BLANKKEY +# Blank key 000000000000 -# NFC FORUM MADKEY -# MAD ACCESS KEY A (REVERSED) +# NFC Forum MADkey +A0A1A2A3A4A5 +# MAD access key A (reversed) A5A4A3A2A1A0 -# MAD ACCESS KEY B +# MAD access key B 89ECA97F8C2A -# KEY A WIEN -# KEY B WIEN -# ICOPY-X +# Mifare 1k EV1 (S50) hidden blocks, Signature data +# 16 A +5C8FF9990DA2 +# 17 A +75CCB59C9BED +# 16 B +D01AFEEB890A +# 17 B +4B791BEA7BCC +# QL88 keys +# 17 A/B +2612C6DE84CA +707B11FC1481 +# QL88 diversifed +03F9067646AE +2352C5B56D85 +B0B1B2B3B4B5 +C0C1C2C3C4C5 +D0D1D2D3D4D5 +AABBCCDDEEFF +4D3A99C351DD +1A982C7E459A +# key A Wien +D3F7D3F7D3F7 +# key B Wien +5A1B85FCE20A +714C5C886E97 +587EE5F9350F +A0478CC39091 +533CB6C723F6 +8FD0A4F256E9 +# iCopy-X E00000000000 E7D6064C5860 B27CCAB30DBD -# LIB / NAT BIEB +# lib / Nat Bieb D2ECE8B9395E -# NSCP DEFAULT KEY +# NSCP default key 1494E81663D7 -# KIEV KEYS +# NFC tools +7C9FB8474242 +# Kiev keys 569369C5A0E5 632193BE1C3C 644672BD4AFE 8FE644038790 9DE89E070277 +B5FF67CBA951 EFF603E1EFE9 F14EE7CAE863 -# KIEV / OV-CHIPKAART -B5FF67CBA951 +# ICT S14 A/B +9C28A60F7249 +C9826AF02794 # RKF -# VÄSTTRAFIKEN KEYA, RKF ÖSTGÖTATRAFIKEN KEYA +# Vasttrafiken KeyA, RKF OstgotaTrafiken KeyA FC00018778F7 +# Vasttrafiken KeyA 0297927C0F77 54726176656C -# VÄSTTRAFIKEN KEYB +# Vasttrafiken KeyB 00000FFE2488 776974687573 EE0042F88840 -# RKF SLKEYA +# RKF SLKeyA 26940B21FF5D A64598A77478 -# RKF SLKEYB +# RKF SLKeyB 5C598C9C58B5 E4D2770A89BE -# RKF REJSKORTDANMARK KEYA +# RKF Rejskort Danmark KeyA 722BFCC5375F +# RKF Rejskort Danmark KeyB F1D83F964314 -# RKF JOJOPRIVAKEYA +# RKF JOJOPRIVA KeyA 505249564141 -# RKF JOJOPRIVAKEYB +# RKF JOJOPRIVA KeyB 505249564142 -# RKF JOJOGROUPKEYA +# RKF JOJOGROUP KeyA 47524F555041 -# RKF JOJOGROUPKEYB -47524F555042 434F4D4D4F41 +# RKF JOJOGROUP KeyB +47524F555042 434F4D4D4F42 +# TNP3xxx 4B0B20107CCB -# TNP3XXX -# ACCESS CONTROL SYSTEM +# Access control system 605F5E5D5C5B -# MORE KEYS FROM MFC_DEFAULT_KEYS.LUA +# NSP Global keys A and B (uk housing access control) +199404281970 +199404281998 +# Data from http://www.proxmark.org/forum/viewtopic.php?pid=25925#p25925 +# Tengo Cards Key A +FFF011223358 +FF9F11223358 +# Elevator system Kherson, Ukraine +AC37E76385F5 +576DCFFF2F25 +# Car wash system +1EE38419EF39 +26578719DCD9 +# more Keys from mfc_default_keys.lua 000000000001 000000000002 00000000000A 00000000000B +010203040506 0123456789AB 100000000000 111111111111 +123456789ABC 12F2EE3478C1 14D446E33363 1999A3554A55 200000000000 222222222222 27DD91F1FCF1 -# DIRECTORYANDEVENTLOGKEYB +# Hotel system +505209016A1F +# Directory and eventlog KeyB 2BA9621E0A36 -# DIRECTORYANDEVENTLOGKEYA +# Directory and eventlog KeyA 4AF9D7ADEBE4 333333333333 33F974B42769 @@ -104,7 +159,8 @@ A00000000000 A053A292A4AF A94133013401 AAAAAAAAAAAA -# KEYFROMLADYADA.NET +# Key from ladyada.net +ABCDEF123456 B00000000000 B127C6F41436 BBBBBBBBBBBB @@ -113,25 +169,24 @@ C934FE34D934 CCCCCCCCCCCC DDDDDDDDDDDD EEEEEEEEEEEE -# ELEVATOR -# DATA FROM FORUM +# elevator +# data from forum FFFFFF545846 F1A97341A9FC -# HOTEL SYSTEM +# hotel system 44AB09010845 85FED980EA5A -# ARD (FR) KEY A +# ARD (fr) key A 43454952534E -# ARD (FR) KEY B +# ARD (fr) key B 4A2B29111213 4143414F5250 -# TEHRAN RAILWAY +# Tehran Railway A9B43414F585 1FB235AC1388 -# DATA FROM HTTP://IRQ5.IO/2013/04/13/DECODING-BCARD-CONFERENCE-BADGES/ -# BCARD KEYB +# Data from http://irq5.io/2013/04/13/decoding-bcard-conference-badges/ +# BCARD KeyB F4A9EF2AFC6D -# DATA FROM ... # S0 B 89EAC97F8C2A # S4 A @@ -141,12 +196,13 @@ F4A9EF2AFC6D # S6 B FB0B20DF1F34 A9F953DEF0A3 +# Data from forum 74A386AD0A6D 3F7A5C2DBD81 21EDF95E7433 C121FF19F681 3D5D9996359A -# HERE BE BIP KEYS... +# Here be BIP keys... 3A42F33AF429 1FC235AC1309 6338A371C0ED @@ -179,32 +235,46 @@ D49E2826664F 51284C3686A6 3DF14C8000A1 6A470D54127C -# DATA FROM HTTP://PASTEBIN.COM/AK9BFTPW -# LÄNSTRAFIKEN I VÄSTERBOTTEN +# Data from http://pastebin.com/AK9Bftpw +# Lanstrafiken i Vasterbotten 48FFE71294A0 E3429281EFC1 16F21A82EC84 460722122510 -# 3DPRINTER -# EPI ENVISIONTE 3DPRINTER +# 3dprinter +# EPI Envisionte AAFB06045877 -# GYM -# FYSIKEN A +# gym +# Fysiken A 3E65E4FB65B3 -# FYSIKEN B +# Fysiken B 25094DF6F148 -# CLEVERFIT +# https://mattionline.de/fitnessstudio-armband-reverse-engineering/ +# https://mattionline.de/milazycracker/ +# gym wistband A, same as Fysiken A +# gym wistband B +81CC25EBBB6A +195DC63DB3A3 +# CleverFit A05DBD98E0FC -# HOTEL KEYCARD +# GoFit +AA4DDA458EBB +EAB8066C7479 +# Nordic Wellness A, same as Fysiken A +# Nordic Wellness B +E5519E1CC92B +# Hotel KeyCard D3B595E9DD63 AFBECD121004 -# SIMONSVOSS +# SimonsVoss 6471A5EF2D1A # ID06 4E3552426B32 22BDACF5A33F 6E7747394E63 763958704B78 +# Onity S1 A/B +8A19D40CF2B5 # 24-7 D21762B2DE3B 0E83A374B513 @@ -218,9 +288,9 @@ F101622750B7 710732200D34 7C335FB121B5 B39AE17435DC -# KEY A +# key A 454841585443 -# DATA FROM HTTP://PASTEBIN.COM/GQ6NK38G +# Data from http://pastebin.com/gQ6nk38G D39BB83F5297 85675B200017 528C9DFFE28C @@ -245,12 +315,10 @@ FEE470A4CB58 75EDE6A84460 DF27A8F1CB8E B0C9DD55DD4D -# DATA FROM HTTP://BIT.LY/1BDSBJL +# Data from http://bit.ly/1bdSbJl A0B0C0D0E0F0 A1B1C1D1E1F1 -# DATA FROM MSK SOCIAL -A229E68AD9E5 -49C2B5296EF4 +# Data from msk social 2735FC181807 2ABA9519F574 84FD7F7A12B6 @@ -274,7 +342,9 @@ C7C0ADB3284F D8A274B2E026 B20B83CB145C 9AFA6CB4FC3D -# DATA FROM HTTP://PASTEBIN.COM/RRJUEDCM +A229E68AD9E5 +49C2B5296EF4 +# Data from http://pastebin.com/RRJUEDCM 0D258FE90296 E55A3CA71826 A4F204203F56 @@ -287,17 +357,17 @@ EEB420209D0C 1ACC3189578C C2B7EC7D4EB1 369A4663ACD2 -# DATA FROM HTTPS://GITHUB.COM/ZHANGJINGYE03/ZXCARDUMPER -# ZXCARD KEY A/B +# Data from https://github.com/zhangjingye03/zxcardumper +# zxcard Key A/B 668770666644 003003003003 -# DATA FROM HTTP://PHREAKERCLUB.COM/FORUM/SHOWTHREAD.PHP?P=41266 +# Data from http://phreakerclub.com/forum/showthread.php?p=41266 26973EA74321 71F3A315AD26 51044EFB5AAB AC70CA327A04 EB0A8FF88ADE -# TRANSPORT SYSTEM METROMONEY +# Transport system Metromoney 2803BCB0C7E1 9C616585E26D 4FA9EB49F75E @@ -305,7 +375,7 @@ EB0A8FF88ADE A160FCD5EC4C 112233445566 361A62F35BC9 -# TRANSPORT SYSTEM SPAIN +# Transport system Spain 83F3CB98C258 070D486BC555 A9B018868CC1 @@ -338,15 +408,89 @@ C52876869800 5145C34DBA19 25352912CD8D 81B20C274C3F -# DATA FROM MALL -# PLAYLAND BALIKESIR +00B70875AF1D +04B787B2F3A5 +05412723F1B6 +05C301C8795A +066F5AF3CCEE +0A1B6C50E04E +0AD0956DF6EE +0AD6B7E37183 +0F3A4D48757B +1417E5671417 +18AB07270506 +18E887D625B4 +1ABC15934F5A +1AF66F83F5BE +260480290483 +2900AAC52BC3 +2910AFE15C99 +374521A38BCC +3A4C47757B07 +3A524B7A7B37 +3C4ABB877EAF +3F3A534B7B7B +4B787B273A50 +4B92DF1BF25D +4F0E4AE8051A +514B797B2F3A +529CF51F05C5 +52B26C199862 +57A18BFEC381 +5A7D87876EA8 +64CBADC7A313 +65B6C3200736 +67B1B3A4E497 +6B0454D5D3C3 +6B3B7AF45777 +6C273F431564 +702C1BF025DD +738385948494 +76E450094393 +777B1F3A4F4A +7B173A4E4976 +81504133B13C +826576A1AB68 +8A55194F6587 +8DFACF11E778 +8FD6D76742DC +9AFEE1F65742 +9D56D83658AC +9FAC23197904 +A1AB3A08712C +A514B797B373 +A58AB5619631 +A5BB18152EF1 +A777B233A4F4 +AB19BC885A29 +AB91BDA25F00 +AE98BA1E6F2C +B133A4D48757 +B3A4C47757B0 +B6803136F5AF +B793ADA6DB0C +B95BFDEBA7E4 +C0AA2BBD27CD +C27F5C1A9C2B +C9BE49675FE4 +CCCE24102003 +CDE668FDCDBA +D23A31A4AAB9 +DEDD7688BC38 +E9AE90885C39 +F0A3C5182007 +F3A524B7A7B3 +# Data from mall +# playland balikesir ABBA1234FCB0 -# A TRIO BOWLING BAHCELIEVLER +# A trio bowling bahcelievler 314F495254FF 4152414B4E41 -# KARINCA PARK NIGDE +# karinca park nigde 4E474434FFFF -# DATA FROM HTTPS://GITHUB.COM/RADIOWAR/NFCGUI +# hotel system +537930363139 +# Data from https://github.com/RadioWar/NFCGUI 44DD5A385AAF 21A600056CB0 B1ACA33180A5 @@ -383,11 +527,13 @@ CBA6AE869AD5 A7ABBC77CC9E F792C4C76A5C BFB6796A11DB -# DATA FROM SALTO A/B +# Data from Salto A/B 6A1987C40A21 7F33625BC129 +6BE9314930D8 +# Data from forum 2338B4913111 -# DATA FROM STOYE +# Data from stoye CB779C50E1BD A27D3804C259 003CC420001A @@ -414,37 +560,34 @@ D9A37831DCE5 C5CFE06D9EA3 C0DECE673829 A56C2DF9A26D -# DATA FROM HTTPS://PASTEBIN.COM/VBWAST74 +# Data from https://pastebin.com/vbwast74 68D3F7307C89 -# SMART RIDER. WESTERN AUSTRALIAN PUBLIC TRANSPORT CARDS +# Smart Rider. Western Australian Public Transport Cards 568C9083F71C -# BANGKOK METRO KEY +117E5C165B10 +24BB421C7973 +3E3A546650EA +41F262D3AB66 +514956AB3142 +863933AE8388 +# Bangkok metro key 97F5DA640B18 -# METRO VALENCIA KEY +# Metro Valencia key A8844B0BCA06 -# HTC EINDHOVEN KEY +# HTC Eindhoven key 857464D3AAD1 -# VIGIK KEYS -# VARIOUS SOURCES : -# * HTTPS://GITHUB.COM/DUMPDOS/VIGIK -# * HTTP://NEWFFR.COM/VIEWTOPIC.PHP?&FORUM=235&TOPIC=11559 -# * OWN DUMPS -# FRENCH VIGIK +# Vigik Keys +# Various sources : +# * https://github.com/DumpDos/Vigik +# * http://newffr.com/viewtopic.php?&forum=235&topic=11559 +# * Own dumps +# French VIGIK # VIGIK1 A 314B49474956 # VIGIK1 B 564C505F4D41 BA5B895DA162 -# VIGIK MYSTERY KEYS MIFARE 1K EV1 (S50) -# 16 A -5C8FF9990DA2 -# 17 A -75CCB59C9BED -# 16 B -D01AFEEB890A -# 17 B -4B791BEA7BCC -# BTCINO UNDETERMINED SPREAKD 0X01->0X13 KEY +# BTCINO UNDETERMINED SPREAKD 0x01->0x13 key 021209197591 2EF720F2AF76 414C41524F4E @@ -452,8 +595,8 @@ D01AFEEB890A 4A6352684677 BF1F4424AF76 536653644C65 -# INTRATONE COGELEC -# DATA FROM HTTP://BOUZDECK.COM/RFID/32-CLONING-A-MIFARE-CLASSIC-1K-TAG.HTML +# Intratone Cogelec +# Data from http://bouzdeck.com/rfid/32-cloning-a-mifare-classic-1k-tag.html 484558414354 A22AE129C013 49FAE4E3849F @@ -470,7 +613,7 @@ E64A986A5D94 66D2B7DC39EF 6BC1E1AE547D 22729A9BD40F -# DATA FROM HTTPS://DFIR.LU/BLOG/CLONING-A-MIFARE-CLASSIC-1K-TAG.HTML +# Data from https://dfir.lu/blog/cloning-a-mifare-classic-1k-tag.html 925B158F796F FAD63ECB5891 BBA840BA1C57 @@ -485,9 +628,9 @@ CC6B3B3CD263 703140FD6D86 157C9A513FA5 E2A5DC8E066F -# DATA FROM FORUM, SCHLAGE 9691T FOB +# Data from forum, schlage 9691T fob EF1232AB18A0 -# DATA FROM A OYSTER CARD +# Data from a oyster card 374BF468607F BFC8E353AF63 15CAFD6159F6 @@ -517,9 +660,9 @@ A2ABB693CE34 91F93A5564C9 E10623E7A016 B725F9CBF183 -# DATA FROM FDI TAG +# Data from FDi tag 8829DA9DAF76 -# DATA FROM GITHUB ISSUE +# Data from GitHub issue 0A7932DC7E65 11428B5BCE06 11428B5BCE07 @@ -529,7 +672,6 @@ B725F9CBF183 11428B5BCE0F 18971D893494 25D60050BF6E -3FA7217EC575 44F0B5FBE344 7B296F353C6B 8553263F4FF0 @@ -543,15 +685,18 @@ D4FE03CE5B09 D4FE03CE5B0A D4FE03CE5B0F E241E8AFCBAF -# DATA FROM FORUM POST +# Transport system Argentina - SUBE +# Shared key - sec 3 blk 15 +3FA7217EC575 +# Data from forum post 123F8888F322 050908080008 -# DATA FROM HOIST +# Data from hoist 4F9F59C9C875 -# DATA FROM PASTEBIN +# Data from pastebin 66F3ED00FED7 F7A39753D018 -# DATA FROM HTTPS://PASTEBIN.COM/Z7PEEZIF +# Data from https://pastebin.com/Z7pEeZif 386B4D634A65 666E564F4A44 564777315276 @@ -582,24 +727,24 @@ F7A39753D018 6F506F493353 31646241686C 77646B633657 -# DATA FROM TRANSPERT +# Data from TransPert 2031D1E57A3B 53C11F90822A 9189449EA24E -# DATA FROM GITHUB +# data from Github 410B9B40B872 2CB1A90071C8 -# DATA FROM 8697389ACA26 1AB23CD45EF6 013889343891 0000000018DE 16DDCB6B3F24 -# DATA FROM HTTPS://PASTEBIN.COM/VWDRZW7D -# VINGCARD MIFARE 4K STAFF CARD +# Data from https://pastebin.com/vwDRZW7d +# Vingcard Mifare 4k Staff card EC0A9B1A9E06 6C94E1CED026 0F230695923F +0000014B5C31 BEDB604CC9D1 B8A1F613CF3D B578F38A5C61 @@ -608,34 +753,36 @@ B66AC040203A 2E641D99AD5B AD4FB33388BF 69FB7B7CD8EE -# HOTEL +# Hotel 2A6D9205E7CA +13B91C226E56 +# KABA Hotel Locks 2A2C13CC242A 27FBC86A00D0 01FA3FC68349 +# Smart Rider. Western Australian Public Transport Cards 6D44B5AAF464 1717E34A7A8A -# RFIDEAS +# RFIDeas 6B6579737472 -# HID MIFARE CLASSIC 1K KEY +# HID MIFARE Classic 1k Key 484944204953 204752454154 # HID MIFARE SO 3B7E4FD575AD 11496F97752A -# LUXEO/AZTEK CASHLESS VENDING +# Luxeo/Aztek cashless vending 415A54454B4D # BQT 321958042333 -# APERIO KEY_A SECTOR 1, 12, 13, 14, 15 DATA START 0 LENGTH 48 +# Aperio KEY_A Sector 1, 12, 13, 14, 15 Data Start 0 Length 48 160A91D29A9C -# GALLAGHER +# Gallagher B7BF0C13066E -# PIK COMFORT MOSCOW KEYS (ISBC MIFARE PLUS SE 1K) +# PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K) 009FB42D98ED 002E626E2820 -# BOSTON, MA, USA TRANSIT - MBTA CHARLIE CARD -# CHARLIE +# Boston, MA, USA Transit - MBTA Charlie Card 3060206F5B0A 5EC39B022F2B 3A09594C8587 @@ -670,13 +817,14 @@ D80511FC2AB4 BB467463ACD6 E67C8010502D FF58BA1B4478 -# DATA FROM HTTPS://PASTEBIN.COM/KZ8XP4EV +# Data from https://pastebin.com/Kz8xp4ev FBF225DC5D58 -# DATA HTTPS://PASTEBIN.COM/BEM6BDAE -# VINGCARD.TXT +# Data https://pastebin.com/BEm6bdAE +# vingcard.txt +# Note: most likely diversified +96A301BCE267 4708111C8604 3D50D902EA48 -96A301BCE267 6700F10FEC09 7A09CC1DB70A 560F7CFF2D81 @@ -688,17 +836,18 @@ FBF225DC5D58 D9BCDE7FC489 0C03A720F208 6018522FAC02 -# DATA FROM HTTPS://PASTEBIN.COM/4T2YFMGT -# MIFARE TECHNISCHE UNIVERSITÄT GRAZ TUG +# Data from https://pastebin.com/4t2yFMgt +# Mifare technische Universitat Graz TUG D58660D1ACDE 50A11381502C C01FC822C6E5 0854BF31111E -# MORE KEYS: -8A19D40CF2B5 +# More keys - Found 8A at Sebel Hotel in Canberra, Australia AE8587108640 +# SafLock standalone door locks 135B88A94B8B -# RUSSIAN TROIKA CARD +# Russian Troika card +EC29806D9738 08B386463229 0E8F64340BA4 0F1C63013DBA @@ -753,7 +902,10 @@ EAAC88E5DC99 F8493407799D 6B8BD9860763 D3A297DC2698 -# KEYS FROM MIFARECLASSICTOOL PROJECT +# Data from reddit +34635A313344 +593367486137 +# Keys from Mifare Classic Tool project 044CE1872BC3 045CECA15535 0BE5FAC8B06A @@ -808,15 +960,81 @@ FD8705E721B0 00ADA2CD516D 237A4D0D9119 0ED7846C2BC9 -# HOTEL ADINA +FFFFD06F83E3 +FFFFAE82366C +F89C86B2A961 +F83466888612 +ED3A7EFBFF56 +E96246531342 +E1DD284379D4 +DFED39FFBB76 +DB5181C92CBE +CFC738403AB0 +BCFE01BCFE01 +BA28CFD15EE8 +B0699AD03D17 +AABBCC660429 +A4EF6C3BB692 +A2B2C9D187FB +9B1DD7C030A1 +9AEDF9931EC1 +8F9B229047AC +872B71F9D15A +833FBD3CFE51 +5D293AFC8D7E +5554AAA96321 +474249437569 +435330666666 +1A2B3C4D5E6F +123456ABCDEF +83BAB5ACAD62 +64E2283FCF5E +64A2EE93B12B +46868F6D5677 +40E5EA1EFC00 +37D4DCA92451 +2012053082AD +2011092119F1 +200306202033 +1795902DBAF9 +17505586EF02 +022FE48B3072 +013940233313 +# Hotel Adina 9EBC3EB37130 -# MOST LIKELY DIVERSED INDIVIDUAL KEYS. -# DATA FROM HTTPS://GITHUB.COM/KORSEHINDI/PROXMARK3/COMMIT/24FDBFA9A1D5C996AAA5C192BC07E4AB28DB4C5C +# Misc. keys from hotels & library cards in Germany +914F57280CE3 +324A82200018 +370AEE95CD69 +2E032AD6850D +1FEDA39D38EC +288B7A34DBF8 +0965E3193497 +18C628493F7F +064D9423938A +995FD2A2351E +7C7D672BC62E +217250FB7014 +AE7478CCAEE7 +ABBF6D116EAF +05862C58EDFB +E43B7F185460 +6A59AA9A959B +B79E5B175227 +7BC9EBB8274B +B2AFBF2331D4 +223E5847DD79 +640524D2A39B +AEE297CB2FD6 +3DA5DFA54604 +0CF1A2AA1F8D +# most likely diversifed individual keys. +# data from https://github.com/korsehindi/proxmark3/commit/24fdbfa9a1d5c996aaa5c192bc07e4ab28db4c5c 491CDC863104 A2F63A485632 98631ED2B229 19F1FFE02563 -# ARGENTINA +# Argentina 563A22C01FC8 43CA22C13091 25094DF2C1BD @@ -888,7 +1106,7 @@ AE43F36C1A9A BE7C4F6C7A9A 5EC7938F140A 82D58AA49CCB -# MELONCARD +# MELON CARD 323334353637 CEE3632EEFF5 827ED62B31A7 @@ -904,7 +1122,7 @@ A7FB4824ACBF 00F0BD116D70 4CFF128FA3EF 10F3BEBC01DF -# TRANSPORTES INSULAR LA PALMA +# Transportes Insular La Palma 0172066B2F03 0000085F0000 1A80B93F7107 @@ -937,7 +1155,7 @@ B1A862985913 3B0172066B2F 3F1A87298691 F3F0172066B2 -# TEHRAN EZPAY +# Tehran ezpay 38A88AEC1C43 CBD2568BC7C6 7BCB4774EC8F @@ -953,11 +1171,11 @@ D3B1C7EA5C53 604AC8D87C7E 8E7B29460F12 BB3D7B11D224 -# CHACO +# Chaco B210CFA436D2 B8B1CFA646A8 A9F95891F0A4 -# KEYS FROM APK APPLICATION "SCAN BADGE" +# Keys from APK application "Scan Badge" 4A4C474F524D 444156494442 434143445649 @@ -976,7 +1194,7 @@ A0004A000036 DFE73BE48AC6 B069D0D03D17 000131B93F28 -# FROM THE DFW AREA, TX, USA +# From the DFW Area, TX, USA A506370E7C0F 26396F2042E7 70758FDD31E0 @@ -992,7 +1210,7 @@ EF4C5A7AC6FC B47058139187 8268046CD154 67CC03B7D577 -# FROM THE HTL MÖDLING, NÖ, AT +# From the HTL Modling, NO, AT A5524645CD91 D964406E67B4 99858A49C119 @@ -1006,27 +1224,28 @@ C27D999912EA E45230E7A9E8 535F47D35E39 FB6C88B7E279 -# METRO CARD, AT +# Metro Card, AT 223C3427108A -# UNKNOWN, AT +# Unknown, AT 23D4CDFF8DA3 E6849FCC324B 12FD3A94DF0E +# Unknown, AT 0B83797A9C64 39AD2963D3D1 -# HOTEL BERLIN CLASSIC ROOM A KEY +# Hotel Berlin Classic room A KEY 34B16CD59FF8 -# HOTEL BERLIN CLASSIC ROOM B KEY +# Hotel Berlin Classic room B KEY BB2C0007D022 -# COINMATIC LAUNDRY SMART CARD -# DATA FROM: HTTPS://PASTEBIN.COM/XZQILTUF +# Coinmatic laundry Smart card +# data from: https://pastebin.com/XZQiLtUf 0734BFB93DAB 85A438F72A8A -# DATA FROM FORUM, CHINESE HOTEL +# Data from forum, Chinese hotel 58AC17BF3629 B62307B62307 A2A3CCA2A3CC -# GRANADA, ES TRANSPORT CARD +# Granada, ES Transport Card 000000270000 0F385FFB6529 29173860FC76 @@ -1043,14 +1262,14 @@ B385EFA64290 C9739233861F F3864FCCA693 FC9839273862 -# VARIOUS HOTEL KEYS +# various hotel keys 34D3C568B348 91FF18E63887 4D8B8B95FDEE 354A787087F1 4A306E62E9B6 B9C874AE63D0 -# DATA FROM OFFICIAL REPO +# Data from official repo F00DFEEDD0D0 0BB31DC123E5 7578BF2C66A9 @@ -1067,19 +1286,20 @@ B8937130B6BA D7744A1A0C44 82908B57EF4F FE04ECFE5577 -# COMFORT INN HOTEL +# comfort inn hotel 4D57414C5648 4D48414C5648 -# UNKNOWN HOTEL KEY +# unknown hotel key 6D9B485A4845 -# BOSCH SOLUTION 6000 -# FOUND IN TAGINFO APP -# RATB KEY +# Bosch Solution 6000 +5A7A52D5E20D +# Found in TagInfo app +# RATB key C1E51C63B8F5 1DB710648A65 -# E-GO CARD KEY +# E-GO card key 18F34C92A56E -# LIBRARY CARD MFP - SL1 +# Library Card MFP - SL1 4A832584637D CA679D6291B0 30D9690FC5BC @@ -1094,7 +1314,7 @@ AADE86B1F9C1 C67BEB41FFBF B84D52971107 52B0D3F6116E -# DATA FROM HTTPS://PASTEBIN.COM/CLSQQ9XN +# Data from https://pastebin.com/cLSQQ9xN CA3A24669D45 4087C6A75A96 403F09848B87 @@ -1102,28 +1322,28 @@ D73438698EEA 5F31F6FCD3A0 A0974382C4C5 A82045A10949 -# DATA FROM HTTPS://PASTEBIN.COM/2IV8H93H -# FUNNIVARIUM -# FORUM ANKARA +# Data from https://pastebin.com/2iV8h93h +# funnivarium +# forum ankara 2602FFFFFFFF -# MACERA ADASI -# ANKARA KENTPARK +# macera adasi +# ankara kentpark # INACTIVE 0A4600FF00FF DFF293979FA7 4D6F62692E45 4118D7EF0902 -# PETROL OFISI -# POSITIVE CARD -# ODE-GEC +# petrol ofisi +# positive card +# ode-gec 0406080A0C0E -# KONYA ELKART +# konya elkart 988ACDECDFB0 120D00FFFFFF -# BOWLINGO -# SERDIVAN AVYM +# bowlingo +# serdivan avym 4AE23A562A80 -# KART54 +# kart 54 2AFFD6F88B97 A9F3F289B70C DB6819558A25 @@ -1131,28 +1351,28 @@ DB6819558A25 B16B2E573235 42EF7BF572AB 274E6101FC5E -# CRAZY PARK -# KIZILAY AVM +# crazy park +# kizilay avm 00DD300F4F10 -# KARTSISTEM B +# kartsistem B FEE2A3FBC5B6 -# TORU ENT -# TAURUS AVM +# toru ent +# taurus avm 005078565703 -# VING? +# Ving? 0602721E8F06 FC0B50AF8700 F7BA51A9434E -# ESKART -# ESKISEHIR TRANSPORT CARD +# eskart +# eskisehir transport card E902395C1744 4051A85E7F2D 7357EBD483CC D8BA1AA9ABA0 76939DDD9E97 3BF391815A8D -# MUZEKART -# MUSEUM CARD FOR TURKEY +# muzekart +# museum card for turkey 7C87013A648A E8794FB14C63 9F97C182585B @@ -1182,8 +1402,8 @@ D0DDDF2933EC 240F0BB84681 9E7168064993 2F8A867B06B4 -# BURSAKART -# BURSA TRANSPORT CARD +# bursakart +# bursa transport card 755D49191A78 DAC7E0CBA8FD 68D3263A8CD6 @@ -1191,20 +1411,20 @@ DAC7E0CBA8FD 0860318A3A89 1927A45A83D3 B2FE3B2875A6 -# PLAYLAND -# MALTEPE PARK +# playland +# maltepe park ABCC1276FCB0 AABAFFCC7612 -# LUNASAN -# KOCAELI FAIR +# lunasan +# kocaeli fair 26107E7006A0 -# GAMEFACTORY -# OZDILEK +# gamefactory +# ozdilek 17D071403C20 534F4C415249 534F4C303232 -# NESPRESSO, SMART CARD -# KEY-GEN ALGO, THESE KEYS ARE FOR ONE CARD +# Nespresso, smart card +# key-gen algo, these keys are for one card (keys diversified) FF9A84635BD2 6F30126EE7E4 6039ABB101BB @@ -1281,17 +1501,19 @@ AE76242931F1 124578ABFEDC ABFEDC124578 4578ABFEDC12 -# PREMIER INN HOTEL CHAIN +# Data from +# premier inn hotel chain 5E594208EF02 AF9E38D36582 -# NORWEGIAN BUILDING SITE IDENTICATION CARD. (HMS KORT) +# Norwegian building site identication card. (HMS KORT) +# Key a 10DF4D1859C8 -# KEY B +# Key B B5244E79B0C8 -# UKRAINE HOTEL +# Ukraine hotel F5C1C4C5DE34 -# DATA FROM MIFARE CLASSIC TOOL REPO -# ROTTERDAM UNIVERSITY OF APPLIED SCIENCES CAMPUS CARD +# Data from Mifare Classic Tool repo +# Rotterdam University of applied sciences campus card BB7923232725 A95BD5BB4FC5 B099335628DF @@ -1314,7 +1536,8 @@ B5ADEFCA46C4 BF3FE47637EC B290401B0CAD AD11006B0601 -# ARMENIAN METRO +# Data from Mifare Classic Tool repo +# Armenian Metro E4410EF8ED2D 6A68A7D83E11 0D6057E8133B @@ -1323,7 +1546,7 @@ D3F3B958B8A3 2196FAD8115B 7C469FE86855 CE99FBC8BD26 -# KEYS FROM EUROTHERMES GROUP (SWITZERLAND) +# keys from Eurothermes group (Switzerland) D66D91829013 75B691829013 83E391829013 @@ -1338,18 +1561,134 @@ FED791829013 29A791829013 668091829013 00008627C10A -# KEYS FROM NSP MANCHESTER UNIVERSITY UK ACCOMODATION STAFF AND STUDENTS -199404281970 -199404281998 -# EASYCARD +# easycard 310D51E539CA 2CCDA1358323 03E0094CEDFE 562E6EF73DB6 F53E9F4114A9 AD38C17DE7D2 -# SOME KEYS OF HTTPS://W3BSIT3-DNS.COM AND HTTPS://IKEY.RU -# STRELKA EXTENSION +# SUBE cards keys (new) +2DEB57A3EA8F +32C1BB023F87 +70E3AD3F2D29 +202ECDCCC642 +3686192D813F +24501C422387 +2C7813A721C3 +FFE04BE3D995 +D28F090677A1 +DE2D83E2DCCC +A66A478712EA +643232ADB2D5 +C7F4A4478415 +95C013B70D99 +3C383889362A +3C6D9C4A90FA +51BEDBA005E5 +74BF7363F354 +53B09DB89111 +E98075318085 +2F904641D75F +7F60AEF68136 +F5C1B3F62FDA +3E6E5713BA10 +8B75A29D4AB2 +7E6545076619 +# SUBE cards keys (old) +4C5A766DFE3A +32C6768847F5 +F68930789631 +8B42B6D64B02 +B627A3CB13F8 +562A4FB8260B +88DDC24E1671 +91CB7802A559 +7A3E0F5B63FC +8CA2C9DC8292 +5CCC6D50EAAC +DE4F5AA9A7F3 +52D0145E1AF5 +C10F92A4E57E +7D6E7AF43C97 +DE1E7D5F6DF1 +F4CB751B031A +C54474936B59 +2A1F900D4533 +6303CDCBB233 +F115E91357B3 +BFE25035B0C8 +62FF943EB069 +7C82EF592001 +D5C172325DD3 +992B152E834A +CE75D7EADEAF +# Russian Podorozhnik card (Saint-Petersburg transport) +# may be combined with Troika +038B5F9B5A2A +04DC35277635 +0C420A20E056 +152FD0C420A7 +296FC317A513 +29C35FA068FB +31BEC3D9E510 +462225CD34CF +4B7CB25354D3 +5583698DF085 +578A9ADA41E3 +6F95887A4FD3 +7600E889ADF9 +86120E488ABF +8818A9C5D406 +8C90C70CFF4A +8E65B3AF7D22 +9764FEC3154A +9BA241DB3F56 +AD2BDC097023 +B0A2AAF3A1BA +B69D40D1A439 +C956C3B80DA3 +CA96A487DE0B +D0A4131FB290 +D27058C6E2C7 +E19504C39461 +FA1FBB3F0F1F +FF16014FEFC7 +# Food GEM +6686FADE5566 +# Samsung Data Systems (SDS) - Electronic Locks +# Gen 1 S10 KA/KB is FFFFFFFFFFFF, incompatible with Gen 2 locks +# SDS Gen 2 S10 KB +C22E04247D9A +# Data from Discord, French pool +# SDS Gen 2 S10 KA +9B7C25052FC3 +494446555455 +# Data from Discord, seems to be related to ASSA +427553754D47 +# Keys found on Edith Cowan University Smart Riders +9A677289564D +186C59E6AFC9 +DDDAA35A9749 +9D0D0A829F49 +# Mercator Pika Card, Slovenia +97D77FAE77D3 +5AF445D2B87A +# Vilniecio/JUDU kortele, Lithuania +# A +16901CB400BC +F0FE56621A42 +8C187E78EE9C +FE2A42E85CA8 +# B +6A6C80423226 +F4CE4AF888AE +307448829EBC +C2A0105EB028 +# Keys from Flipper Zero Community +# Last update: Aug 13, 2022 +# unknown if keys are diversified or static default +# Strelka Extension 5C83859F2224 66B504430416 70D1CF2C6843 @@ -1358,7 +1697,7 @@ C4D3911AD1B3 CAD7D4A6A996 DA898ACBB854 FEA1295774F9 -# MOSCOW PUBLIC TOILETS CARD +# Moscow Public Toilets Card 807119F81418 22C8BCD10AAA 0AAABA420191 @@ -1367,7 +1706,7 @@ DBF9F79AB7A2 34EDE51B4C22 C8BCD10AAABA BCD10AAABA42 -# MOSCOW SOCIAL CARD +# Moscow Social Card 2F87F74090D1 E53EAEFE478F CE2797E73070 @@ -1409,11 +1748,7 @@ F750C0095199 82DA4B93DB1C 9CF46DB5FD46 93EB64ACF43D -# KEYS FROM RFIDRESEARCHGROUP PROXMARK3 PROJECT -# HTTPS://GITHUB.COM/RFIDRESEARCHGROUP/PROXMARK3/BLOB/MASTER/CLIENT/DICTIONARIES/MFC_DEFAULT_KEYS.DIC -13B91C226E56 -5A7A52D5E20D -# IRON LOGIC +# Iron Logic RU A3A26EF4C6B0 2C3FEAAE99FC E85B73382E1F @@ -1431,10 +1766,354 @@ DEC0CEB0CE24 413BED2AE45B D6261A9A4B3F CB9D507CE56D -# MORE KEYS FROM THE PM3 REPO -# KEYS OF ARMENIAN UNDERGROUND TICKET +# Armenian Underground Ticket A0A1A2A8A4A5 -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_bmp_sorted.dic +# Badge Maker Leaked from https://github.com/UberGuidoZ +1A1B1C1D1E1F +1665FE2AE945 +158B51947A8E +E167EC67C7FF +D537320FF90E +5E56BFA9E2C9 +F81CED821B63 +C81584EF5EDF +9551F8F9259D +36E1765CE3E8 +509052C8E42E +776C9B03BE71 +C608E13ADD50 +BEE8B345B949 +ED0EC56EEFDD +9716D5241E28 +05D1FC14DC31 +3321FB75A356 +F22A78E29880 +EC211D12C98D +8CCA8F62A551 +B637E46AD674 +39605B3C8917 +3882719778A1 +9F27D36C4230 +DB32A6811327 +8AA8544A2207 +8C5819E780A4 +7549E90353A2 +2E52ABE0CE95 +E46210ED98AB +61D030C0D7A8 +18E20102821E +DA59354DFB88 +040047C12B75 +D10008074A6F +686E736F6E20 +446176696453 +6F6674776172 +6520446F7665 +# Apartment keyfobs (USA) (Corvette830) +E60F8387F0B9 +FFD46FF6C5EE +4F9661ED2E70 +576A798C9904 +1C5179C4A8A1 +16CA203B811B +11AC8C8F3AF2 +# The Westin Jakarta Indonesia (D4DB0D) +# Peppers Hotel Unknown location (D4D0D) +6E0DD4136B0A +141940E9B71B +3B1D3AAC866E +95E9EE4CCF8F +FEA6B332F04A +BE0EC5155806 +0500D6BFCC4F +FC5AC7678BE3 +F09BB8DD142D +B4B3FFEDBE0A +540E0D2D1D08 +# Schlage 9691T Keyfob (seasnaill) +7579B671051A +4F4553746B41 +# Vigik ScanBadge App (fr.badgevigik.scanbadge) +# Website https://badge-vigik.fr/ (Alex) +0000A2B3C86F +021200C20307 +021209197507 +1E34B127AF9C +303041534956 +4143532D494E +41454E521985 +43412D627400 +455249524345 +456666456666 +45B722C63319 +484585414354 +4D414C414741 +536563644C65 +57D27B730760 +593DD8FE167A +6472616E7265 +65626F726369 +680E95F3C287 +709BA7D4F920 +8829DAD9AF76 +92D0A0999CBA +948EE7CFC9DB +9EB7C8A6D4E3 +A22AE12C9013 +AFC984A3576E +# Vigik verified by quantum-x +# https://github.com/RfidResearchGroup/proxmark3/pull/1742#issuecomment-1206113976 +A00027000099 +A00016000028 +A00003000028 +A0000F000345 +A00001000030 +A00002000086 +A00002000036 +A00002000088 +A00000000058 +A00000000096 +A00000000008 +A00000043D79 +A00000000064 +A00025000030 +A00003000057 +# BH USA 2013 conference +012279BAD3E5 +# iGuard Simple (and reverse) keys +AAAAAAFFFFFF +FFFFFFAAAAAA +# Random Hotel A Key Sec 0 Blk 3 - KABA Lock (VideoMan) +3111A3A303EB +# Transport system Uruguay - STM +# Shared key - sec 0 blk 3 +D144BD193063 +# Data from http://www.proxmark.org/forum/viewtopic.php?pid=45659#p45659 +3515AE068CAD +# Keys Catering +6A0D531DA1A7 +4BB29463DC29 +# Keys Swim +8627C10A7014 +453857395635 +# Unknown hotel system Sec 0 / A +353038383134 +# Brazil transport Sec 8 / A +50D4C54FCDF5 +# Bandai Namco Passport [fka Banapassport] / Sega Aime Card +# Dumped on the Flipper Devices Discord Server +6090D00632F5 +019761AA8082 +574343467632 +A99164400748 +62742819AD7C +CC5075E42BA1 +B9DF35A0814C +8AF9C718F23D +58CD5C3673CB +FC80E88EB88C +7A3CDAD7C023 +30424C029001 +024E4E44001F +ECBBFA57C6AD +4757698143BD +1D30972E6485 +F8526D1A8D6D +1300EC8C7E80 +F80A65A87FFA +DEB06ED4AF8E +4AD96BF28190 +000390014D41 +0800F9917CB0 +730050555253 +4146D4A956C4 +131157FBB126 +E69DD9015A43 +337237F254D5 +9A8389F32FBF +7B8FB4A7100B +C8382A233993 +7B304F2A12A6 +FC9418BF788B +# Data from "the more the marriott" mifare project (colonelborkmundus) +# aka The Horde +# These keys seem to be from Vingcard / Saflok system which means they are diversified +# and not static default keys. To verify this, the UID from such a card is needed. +# 20230125-01, Elite Member Marriott Rewards +43012BD9EB87 +# 20230125-02, Elite Member Marriott Rewards +3119A70628EB +# 20230125-03, Elite Member Marriott Rewards +23C9FDD9A366 +# 20230125-04, Elite Member Marriott Rewards +7B4DFC6D6525 +# 20230125-05, Elite Member Marriott Rewards +1330824CD356 +# 20230125-06, Elite Member Marriott Rewards +30AAD6A711EF +# 20230125-07, Fairfield Inn & Suites Marriott +7B3B589A5525 +# 20230125-08, Moxy Hotels +20C166C00ADB +# 20230125-09, Westin Hotels & Resorts +7D0A1C277C05 +2058580A941F +8C29F8320617 +# 20230125-10, Westin Hotels & Resorts +C40964215509 +D44CFC178460 +5697519A8F02 +# 20230125-12, AC Hotels Marriott +7B56B2B38725 +# 20230125-13, AC Hotels Marriott +8EA8EC3F2320 +# 20230125-14, Waldorf Astoria Chicago +011C6CF459E8 +# 20230125-24, Aria Resort & Casino +A18D9F4E75AF +# 20230125-25, Aria Resort & Casino +316B8FAA12EF +# 20230125-26, Residence Inn Mariott +3122AE5341EB +# 20230125-27, Residence Inn Mariott +F72CD208FDF9 +# 20230125-28, Marriott +035C70558D7B +# 20230125-29, Marriott +12AB4C37BB8B +# 20230125-30, Marriott +9966588CB9A0 +# 20230125-31, Sheraton +42FC522DE987 +# 20230125-32, The Industrialist +2158E314C3DF +# 20230125-39, The Ritz-Carlton Balharbour +30FB20D0EFEF +# 20230125-40, The Ritz-Carlton Balharbour +66A3B064CC4B +# 20230125-41, The Ritz-Carlton Balharbour +D18296CD9E6E +# 20230125-42, The Ritz-Carlton Balharbour +D20289CD9E6E +# 20230125-44, Graduate Hotels +209A2B910545 +C49DAE1C6049 +# 20230125-46, AmericInn +8AC04C1A4A25 +# 20230129-53, Marriott Bonvoy +6E029927600D +3E173F64C01C +C670A9AD6066 +# 20230413-69, Westin +487339FA02E0 +# 20230413-70, Marriott Bonvoy +DBD5CA4EE467 +A0B1F234006C +180DE12B700E +# 20230413-71, Westin +1352C68F7A56 +# 20230413-76, Ritz Carlton +318BD98C1CEF +# 20230413-77, Marriott +D23C1CB1216E +# 20230413-78, Caesars +A1D92F808CAF +# 20230413-79, The Cosmopolitan, Vegas +# 20230413-80, Aria +1153C319B4F8 +# 20230413-81, Aria +110C819BBEF8 +# 20230413-82, Aria +1332117E8756 +# 20230413-83, Kimpton +500AE915F50A +5032E362B484 +8B63AB712753 +# 20230413-85, Kimpton +06106E187106 +2E45C23DC541 +D9FF8BEE7550 +# 20230413-87, Marriott +42F7A186BF87 +# 20230413-88, Meritage Resort +D213B093B79A +# 20230413-89, Meritage Resort +216024C49EDF +# 20230413-90, Gaylord Palms +D201DBB6AB6E +# 20230413-91, Residence Inn +9F4AD875BB30 +# 20230413-92, Marriott +3352DB1E8777 +# 20230413-94, Marriott +09074A146605 +151F3E85EC46 +# Travelodge by Wyndham Berkeley +0000FFFFFFFF +4663ACD2FFFF +EDC317193709 +# Hotel Santa Cruz +75FAB77E2E5B +# saflok brand HOTEL key +32F093536677 +# A WaterFront Hotel in Oakland +3351916B5A77 +# Ballys (2018) +336E34CC2177 +# Random Hawaiian Hotel +A1670589B2AF +# SF Hotel (SoMa area) +2E0F00700000 +# Unknown PACS from Western Australia +CA80E51FA52B +A71E80EA35E1 +05597810D63D +# Hotel Key from Las Vegas +EA0CA627FD06 +80BB8436024C +5044068C5183 +# Key from Hotel M Montreal (probably diversified) +7E5E05866ED6 +661ABF99AFAD +# Key from evo Montreal (probably diversified) +1064BA5D6DF8 +# Hotel key +CE0F4F15E909 +D60DE9436219 +# ATM Area de Girona, spanish transport card +A01000000000 +A02000000000 +A03000000000 +A04000000000 +A05000000000 +A06000000000 +A07000000000 +A08000000000 +A09000000000 +A10000000000 +A11000000000 +A12000000000 +A13000000000 +A14000000000 +A15000000000 +B01000000000 +B02000000000 +B03000000000 +B04000000000 +B05000000000 +B06000000000 +B07000000000 +B08000000000 +B09000000000 +B10000000000 +B11000000000 +B12000000000 +B13000000000 +B14000000000 +B15000000000 +# +--------------------------------------------------------------------------------------------------------+ +# | https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_bmp_sorted.dic | +# +--------------------------------------------------------------------------------------------------------+ 002DE0301481 004173272D18 0058A4884CA5 @@ -2435,7 +3114,9 @@ EE17C426D25E EE487A4C806E EE5931913A8D EED56840AEBA -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_icbmp_sorted.dic +# +----------------------------------------------------------------------------------------------------------+ +# | https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_icbmp_sorted.dic | +# +----------------------------------------------------------------------------------------------------------+ 00383D96411D 005307DB7853 009A4C4C6C49 @@ -3436,276 +4117,59 @@ EE3029556CEB EE49610E6121 EEB704D69BCA EED69A391464 -# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_keys_mrzd_sorted.dic -010203040506 -013940233313 -022FE48B3072 -123456789ABC -123456ABCDEF -17505586EF02 -1795902DBAF9 -1A2B3C4D5E6F -1A982C7E459A -200306202033 -2011092119F1 -2012053082AD -37D4DCA92451 -40E5EA1EFC00 -435330666666 -46868F6D5677 -474249437569 -4D3A99C351DD -533CB6C723F6 -5554AAA96321 -587EE5F9350F -5A1B85FCE20A -5D293AFC8D7E -64A2EE93B12B -64E2283FCF5E -714C5C886E97 -833FBD3CFE51 -83BAB5ACAD62 -872B71F9D15A -8F9B229047AC -8FD0A4F256E9 -9AEDF9931EC1 -9B1DD7C030A1 -A0478CC39091 -A0A1A2A3A4A5 -A2B2C9D187FB -A4EF6C3BB692 -AABBCC660429 -AABBCCDDEEFF -ABCDEF123456 -B0699AD03D17 -B0B1B2B3B4B5 -BA28CFD15EE8 -BCFE01BCFE01 -C0C1C2C3C4C5 -CFC738403AB0 -D0D1D2D3D4D5 -D3F7D3F7D3F7 -DB5181C92CBE -DFED39FFBB76 -E1DD284379D4 -E96246531342 -ED3A7EFBFF56 -F83466888612 -F89C86B2A961 -FFFFAE82366C -FFFFD06F83E3 -# Unknown origin -2DEB57A3EA8F -32C1BB023F87 -70E3AD3F2D29 -202ECDCCC642 -3686192D813F -24501C422387 -2C7813A721C3 -FFE04BE3D995 -D28F090677A1 -DE2D83E2DCCC -A66A478712EA -643232ADB2D5 -C7F4A4478415 -95C013B70D99 -3C383889362A -3C6D9C4A90FA -51BEDBA005E5 -74BF7363F354 -53B09DB89111 -E98075318085 -2F904641D75F -7F60AEF68136 -F5C1B3F62FDA -3E6E5713BA10 -8B75A29D4AB2 -7E6545076619 -4C5A766DFE3A -32C6768847F5 -F68930789631 -8B42B6D64B02 -B627A3CB13F8 -562A4FB8260B -88DDC24E1671 -91CB7802A559 -7A3E0F5B63FC -8CA2C9DC8292 -5CCC6D50EAAC -DE4F5AA9A7F3 -52D0145E1AF5 -C10F92A4E57E -7D6E7AF43C97 -DE1E7D5F6DF1 -F4CB751B031A -C54474936B59 -2A1F900D4533 -6303CDCBB233 -F115E91357B3 -BFE25035B0C8 -62FF943EB069 -7C82EF592001 -D5C172325DD3 -992B152E834A -CE75D7EADEAF -038B5F9B5A2A -04DC35277635 -0C420A20E056 -152FD0C420A7 -296FC317A513 -29C35FA068FB -31BEC3D9E510 -462225CD34CF -4B7CB25354D3 -5583698DF085 -578A9ADA41E3 -6F95887A4FD3 -7600E889ADF9 -86120E488ABF -8818A9C5D406 -8C90C70CFF4A -8E65B3AF7D22 -9764FEC3154A -9BA241DB3F56 -AD2BDC097023 -B0A2AAF3A1BA -B69D40D1A439 -C956C3B80DA3 -CA96A487DE0B -D0A4131FB290 -D27058C6E2C7 -E19504C39461 -FA1FBB3F0F1F -FF16014FEFC7 -# Cracked by UberGuidoZ -# https://github.com/UberGuidoZ -# BadgeMaker Leaked -1A1B1C1D1E1F -1665FE2AE945 -158B51947A8E -5E56BFA9E2C9 -F81CED821B63 -C81584EF5EDF -9551F8F9259D -509052C8E42E -776C9B03BE71 -BEE8B345B949 -05D1FC14DC31 -3321FB75A356 -F22A78E29880 -EC211D12C98D -8CCA8F62A551 -B637E46AD674 -39605B3C8917 -3882719778A1 -9F27D36C4230 -DB32A6811327 -8AA8544A2207 -8C5819E780A4 -7549E90353A2 -E46210ED98AB -18E20102821E -DA59354DFB88 -040047C12B75 -D10008074A6F -686E736F6E20 -446176696453 -6F6674776172 -6520446F7665 -# Apartment keyfobs in USA from Corvette830 -E60F8387F0B9 -FFD46FF6C5EE -4F9661ED2E70 -576A798C9904 -1C5179C4A8A1 -16CA203B811B -11AC8C8F3AF2 -# The Westin Jakarta Indonesia from D4DB0D -# Peppers Hotel Unknown location from D4DB0D -6E0DD4136B0A -141940E9B71B -3B1D3AAC866E -95E9EE4CCF8F -0000014B5C31 -FEA6B332F04A -BE0EC5155806 -0500D6BFCC4F -FC5AC7678BE3 -F09BB8DD142D -B4B3FFEDBE0A -540E0D2D1D08 -# Schlage 9691T Keyfob -7579B671051A -4F4553746B41 -# FOOD REPUBLIC -30C1DC9DD040 -A9B9C1D0E3F1 -# iGuard Simple (and reverse) keys -AAAAAAFFFFFF -FFFFFFAAAAAA -# Vigik verified by quantum-x -# https://github.com/RfidResearchGroup/proxmark3/pull/1742#issuecomment-1206113976 -A00027000099 -A00016000028 -A00003000028 -A0000F000345 -A00001000030 -A00002000086 -A00002000036 -A00002000088 -A00000000058 -A00000000096 -A00000000008 -A00000043D79 -A00000000064 -A00025000030 -A00003000057 -# BH USA 2013 conference -012279BAD3E5 -# Vigik ScanBadge App (fr.badgevigik.scanbadge) -# Website https://badge-vigik.fr/ - By Alex` -0000A2B3C86F -021200C20307 -021209197507 -1E34B127AF9C -303041534956 -4143532D494E -41454E521985 -43412D627400 -455249524345 -456666456666 -45B722C63319 -484585414354 -4D414C414741 -536563644C65 -57D27B730760 -593DD8FE167A -6472616E7265 -65626F726369 -680E95F3C287 -709BA7D4F920 -8829DAD9AF76 -92D0A0999CBA -948EE7CFC9DB -9EB7C8A6D4E3 -A22AE12C9013 -AFC984A3576E +# +-----------------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/ikarus23/MifareClassicTool/blob/master/Mifare%20Classic%20Tool/app/src/main/assets/key-files/extended-std.keys | +# +-----------------------------------------------------------------------------------------------------------------------------------+ +# Wojo coworking space, Fance +FF75AFDA5A3C +558AAD64EB5B +518108E061E2 +FCDDF7767C10 +A6B3F6C8F1D4 +B1C4A8F7F6E3 +# Key from some random card +001122334455 +# Key from hotel in Spain +6CA761AB6CA7 +# +----------------------------------------------------------------------------------------------+ +# | https://github.com/UberGuidoZ/Flipper/blob/main/NFC/mf_classic_dict/mf_classic_dict_user.nfc | +# +----------------------------------------------------------------------------------------------+ # Spackular A/B # data from http://www.proxmark.org/forum/viewtopic.php?pid=45100#p45100 7CB033257498 1153AABAFF6C -# iGuard Simple and Reverse Keys -D537320FF90E -36E1765CE3E8 -C608E13ADD50 -ED0EC56EEFDD -9716D5241E28 -2E52ABE0CE95 -61D030C0D7A8 -# BadgeMaker Leaked from https://github.com/UberGuidoZ -E167EC67C7FF -# Schlage 9691T Keyfob from seasnaill Added by VideoMan. -3111A3A303EB +# Random Hotel A key sec 0 blk 3 - Hoist Group lock +A2CA48CA4C05 +# Chelsea Harbour Hotel London by Cazagen +485242F22BE0 +# 8668/RevKillj0y +164EE10EFFFF +193DFE0FA18E +3D6F823FFFFF +48C8852D15F9 +4E4F584D2105 +7213B13D02E0 +7ADD3D735725 +7F796F60FFFF +8AC04C1A4E15 +9089B668FFFF +AEF617B3D004 +AEF617B3D040 +C1F6C7B55F5E +# Metro card Sec 001 Blk 007 key B +EDA4BF3E7B04 +# +--------------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc | +# +--------------------------------------------------------------------------------------------------------------------------------+ +# Volgograd (Russia) Volna transport cards keys +2B787A063D5D +D37C8F1793F7 +# +----------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/DarkFlippers/unleashed-firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc | +# +----------------------------------------------------------------------------------------------------------------------------+ +# FOOD REPUBLIC +30C1DC9DD040 +A9B9C1D0E3F1 # Transport cards E954024EE754 0CD464CDC100 @@ -3723,7 +4187,6 @@ F18D91EE3033 0E726E11CFCC 1D14130D1A0B 201106141030 -D144BD193063 01E2C14F1B18 0380293A9E6D 08A55BC96DC1 @@ -3764,104 +4227,120 @@ E10F0E7A8DD5 F833E24C3F1C FA8CA10C7D59 FE98F38F3EE2 -#---------------------------------------------------- -# Added by colonelborkmundus, cleaned by scaff.walker -# "the more, the marriott" Mifare project -# 1k GRADUATE HOTEL -C49DAE1C6049 -209A2B910545 -# 1k WESTIN -8C29F8320617 -5697519A8F02 -7D0A1C277C05 -2058580A941F -C40964215509 -D44CFC178460 -# 1k MARRIOT -7B4DFC6D6525 -23C9FDD9A366 -3119A70628EB -30AAD6A711EF -1330824CD356 -43012BD9EB87 -035C70558D7B -9966588CB9A0 -12AB4C37BB8B -# 1k AC HOTELS MARRIOT -8EA8EC3F2320 -7B56B2B38725 -# 1k THE RITZ-CARTLON -30FB20D0EFEF -D20289CD9E6E -66A3B064CC4B -D18296CD9E6E # 1k UNKNOWN 722538817225 -# 1k ARIA RESORT & CASINO -316B8FAA12EF -A18D9F4E75AF # 1k FAIRFIELD INN & SUITES MARRIOT 7AEB989A5525 -7B3B589A5525 215E9DED9DDF 334E91BE3377 310308EC52EF -# 1k RESIDENCE INN MARRIOT -F72CD208FDF9 -# 1k SHERATON -42FC522DE987 # 1k millenium hotels 132F641C948B # 1k MOXY HOTELS -20C166C00ADB 9EE3896C4530 -# 1k RESIDENCE INN MARRIOT -3122AE5341EB -# 1k AMERICINN -8AC04C1A4A25 -# 1k THE INDUSTRIALIST -2158E314C3DF -# 1k WALDORF ASTORIA -011C6CF459E8 # 1k HAWAII HOTEL 2CAD8A83DF28 555D8BBC2D3E 78DF1176C8FD ADC169F922CB -# Volgograd (Russia) Volna transport cards keys -2B787A063D5D -D37C8F1793F7 -# Bandai Namco Passport [fka Banapassport] / Sega Aime Card -6090D00632F5 -019761AA8082 -574343467632 -A99164400748 -62742819AD7C -CC5075E42BA1 -B9DF35A0814C -8AF9C718F23D -58CD5C3673CB -FC80E88EB88C -7A3CDAD7C023 -30424C029001 -024E4E44001F -ECBBFA57C6AD -4757698143BD -1D30972E6485 -F8526D1A8D6D -1300EC8C7E80 -F80A65A87FFA -DEB06ED4AF8E -4AD96BF28190 -000390014D41 -0800F9917CB0 -730050555253 -4146D4A956C4 -131157FBB126 -E69DD9015A43 -337237F254D5 -9A8389F32FBF -7B8FB4A7100B -C8382A233993 -7B304F2A12A6 -FC9418BF788B +# +------------------------------------------------------------------------------------------------------------------------+ +# | https://github.com/Flipper-XFW/Xtreme-Firmware/blob/dev/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc | +# +------------------------------------------------------------------------------------------------------------------------+ +############################################## +# STB - keys from Bucharest Public Transport +# Found with FlipperNestedRecovery by Z3r0L1nk +330075000850 +2FA9B556A4F6 +91B1B62402D5 +9AE05868233F +6D2BF79566A8 +60D53F070572 +09F4EC8D7A66 +D017A84BB582 +1719EB5DAC66 +96AECCC0F7EB +A64536FAC799 +7BF0BE85080F +E9B376925A00 +44B61F116125 +E861FDE1B59F +5CD02DAD8ADE +5FA28B8E8BA4 +C497C3BE8273 +B1EEAA640EF6 +1E13EFF32CE2 +D6818C29ED9B +050BF33DC217 +41CD3CD99DD5 +02DB253DC0C7 +25467EB0212F +DF7C4EC20B50 +5703815494EF +9F14D35BAC08 +F697E87E759D +D9762D114AE5 +AC935925A876 +5AD3FC074A4C +19BA6776233F +EEF144866688 +C34FAA1931CA +8A0DFD9B7AEA +D3AEB15D410B +251BDBF1C71D +286A8893AC6F +6A86C1895A21 +D7142E0F6D0D +B0A3212D47A5 +1581C317B073 +8EF0AA6432FA +AD00EFD353E4 +439FB891279F +141DF3B1C017 +58DBC850A4D5 +14CD299DC0C7 +B48D7E4E508F +32774E46C64F +61152534ACEF +280FD37AD407 +C789E4568B99 +122F595302AA +84A3FD4BA0C6 +B0D58BF147B7 +15B35D0BF715 +############################################## +# Metrorex - keys from Subway Bucharest Public Transport +# Found with FlipperNestedRecovery by Z3r0L1nk +7246FCE86427 +1415FFFED68D +8C524B535E1D +0FC4B1D2EBBA +280713CBA260 +EDC9CC9109A2 +DCD003CF0EA3 +DD30A13519C3 +8A35039F6CD6 +538BF58687EB +E3007FA4F781 +F654D6C7004F +7C20975C6EC9 +C992F85B2DDD +156EED7C5F9D +16785FD65BA7 +A2D5D7469472 +585462E190F2 +FA4D2B3BAFEA +251780F9FBE6 +EA4987F8D096 +48B390984150 +EA19E58DD046 +1C000EB0752F +96227EDADBCF +3F41891454EE +7EDAE7923287 +11DDA4862A1C +############################################## +# Bubbleland Play Card - keys from Bubble Land Arcade +# Found with FlipperNestedRecovery by CaitSith2 +168168168168 +861861861861 +686B35333376 diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index ad500b717..94d845fa3 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -61,4 +61,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_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index 16593cc89..7c4a3d19d 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -31,6 +31,13 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSelectProtocol)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSelectProtocol); + } else if( + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { + const uint32_t possible_scenes[] = {NfcSceneReadMenu, NfcSceneSavedMenu}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + nfc->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); 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..579373624 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -28,7 +28,13 @@ 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) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { 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_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index 21c0d91db..e33660080 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -32,20 +32,10 @@ void nfc_scene_set_type_on_enter(void* context) { nfc_protocol_support_common_submenu_callback, instance); - FuriString* str = furi_string_alloc(); for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { - furi_string_cat_str(str, nfc_data_generator_get_name(i)); - furi_string_replace_str(str, "Mifare", "MIFARE"); - - submenu_add_item( - submenu, - furi_string_get_cstr(str), - i, - nfc_protocol_support_common_submenu_callback, - instance); - furi_string_reset(str); + const char* name = nfc_data_generator_get_name(i); + submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); } - furi_string_free(str); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); } @@ -63,15 +53,6 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { nfc_scene_set_type_init_edit_data(instance->iso14443_3a_edit_data, 4); scene_manager_next_scene(instance->scene_manager, NfcSceneSetSak); consumed = true; - } else if( - (event.event == NfcDataGeneratorTypeMfClassic1k_4b) || - (event.event == NfcDataGeneratorTypeMfClassic1k_7b) || - (event.event == NfcDataGeneratorTypeMfClassic4k_4b) || - (event.event == NfcDataGeneratorTypeMfClassic4k_7b) || - (event.event == NfcDataGeneratorTypeMfClassicMini)) { - nfc_data_generator_fill_data(event.event, instance->nfc_device); - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - consumed = true; } else { nfc_data_generator_fill_data(event.event, instance->nfc_device); scene_manager_set_scene_state( diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index 7376ce0bc..f09d0a430 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -2,29 +2,6 @@ #include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" -// Sync UID from #UID to block 0 data -void mfclassic_sync_uid(NfcDevice* instance) { - size_t uid_len; - const uint8_t* uid = nfc_device_get_uid(instance, &uid_len); - - MfClassicData* mfc_data = (MfClassicData*)nfc_device_get_data(instance, NfcProtocolMfClassic); - uint8_t* block = mfc_data->block[0].data; - - // Sync UID - for(uint8_t i = 0; i < (uint8_t)uid_len; i++) { - block[i] = uid[i]; - } - - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(uint8_t i = 0; i < (uint8_t)uid_len; i++) { - block[uid_len] ^= block[i]; - } - } -} - static void nfc_scene_set_uid_byte_input_changed_callback(void* context) { NfcApp* instance = context; // Retrieve previously saved UID length @@ -67,10 +44,11 @@ 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 { - if(nfc_device_get_protocol(instance->nfc_device) == NfcProtocolMfClassic) - mfclassic_sync_uid(instance->nfc_device); - scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; } 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/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/applications/services/expansion/application.fam b/applications/services/expansion/application.fam index 1402e8413..dbdde0a52 100644 --- a/applications/services/expansion/application.fam +++ b/applications/services/expansion/application.fam @@ -8,5 +8,5 @@ App( ], requires=["rpc_start"], provides=["expansion_settings"], - order=10, + order=150, ) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 48743808b..5b834b48d 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -1,20 +1,18 @@ #include "expansion.h" #include "expansion_i.h" -#include -#include #include #include +#include -#include - +#include "expansion_worker.h" #include "expansion_settings.h" -#include "expansion_protocol.h" #define TAG "ExpansionSrv" -#define EXPANSION_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) +#define EXPANSION_CONTROL_QUEUE_SIZE (8UL) +#define EXPANSION_CONTROL_STACK_SIZE (768UL) typedef enum { ExpansionStateDisabled, @@ -23,370 +21,193 @@ typedef enum { } ExpansionState; typedef enum { - ExpansionSessionStateHandShake, - ExpansionSessionStateConnected, - ExpansionSessionStateRpcActive, -} ExpansionSessionState; + ExpansionMessageTypeEnable, + ExpansionMessageTypeDisable, + ExpansionMessageTypeSetListenSerial, + ExpansionMessageTypeModuleConnected, + ExpansionMessageTypeModuleDisconnected, +} ExpansionMessageType; -typedef enum { - ExpansionSessionExitReasonUnknown, - ExpansionSessionExitReasonUser, - ExpansionSessionExitReasonError, - ExpansionSessionExitReasonTimeout, -} ExpansionSessionExitReason; +typedef union { + FuriHalSerialId serial_id; +} ExpansionMessageData; -typedef enum { - ExpansionFlagStop = 1 << 0, - ExpansionFlagData = 1 << 1, - ExpansionFlagError = 1 << 2, -} ExpansionFlag; - -#define EXPANSION_ALL_FLAGS (ExpansionFlagData | ExpansionFlagStop) +typedef struct { + ExpansionMessageType type; + ExpansionMessageData data; + FuriApiLock api_lock; +} ExpansionMessage; struct Expansion { - ExpansionState state; - ExpansionSessionState session_state; - ExpansionSessionExitReason exit_reason; - FuriStreamBuffer* rx_buf; - FuriSemaphore* tx_semaphore; - FuriMutex* state_mutex; - FuriThread* worker_thread; + FuriThread* thread; + FuriMessageQueue* queue; FuriHalSerialId serial_id; - FuriHalSerialHandle* serial_handle; - RpcSession* rpc_session; + ExpansionWorker* worker; + ExpansionState state; ExpansionSettings settings; }; -static void expansion_detect_callback(void* context); - -// Called in UART IRQ context -static void expansion_serial_rx_callback( - FuriHalSerialHandle* handle, - FuriHalSerialRxEvent event, - void* context) { - furi_assert(handle); - furi_assert(context); - - Expansion* instance = context; - - if(event == FuriHalSerialRxEventData) { - const uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagData); - } -} - -static size_t expansion_receive_callback(uint8_t* data, size_t data_size, void* context) { - Expansion* instance = context; - - size_t received_size = 0; - - while(true) { - received_size += furi_stream_buffer_receive( - instance->rx_buf, data + received_size, data_size - received_size, 0); - - if(received_size == data_size) break; - - const uint32_t flags = furi_thread_flags_wait( - EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); - - if(flags & FuriFlagError) { - if(flags == (unsigned)FuriFlagErrorTimeout) { - // Exiting due to timeout - instance->exit_reason = ExpansionSessionExitReasonTimeout; - } else { - // Exiting due to an unspecified error - instance->exit_reason = ExpansionSessionExitReasonError; - } - break; - } else if(flags & ExpansionFlagStop) { - // Exiting due to explicit request - instance->exit_reason = ExpansionSessionExitReasonUser; - break; - } else if(flags & ExpansionFlagError) { - // Exiting due to RPC error - instance->exit_reason = ExpansionSessionExitReasonError; - break; - } else if(flags & ExpansionFlagData) { - // Go to buffer reading - continue; - } - } - - return received_size; -} - -static inline bool expansion_receive_frame(Expansion* instance, ExpansionFrame* frame) { - return expansion_protocol_decode(frame, expansion_receive_callback, instance) == - ExpansionProtocolStatusOk; -} - -static size_t expansion_send_callback(const uint8_t* data, size_t data_size, void* context) { - Expansion* instance = context; - furi_hal_serial_tx(instance->serial_handle, data, data_size); - furi_hal_serial_tx_wait_complete(instance->serial_handle); - return data_size; -} - -static inline bool expansion_send_frame(Expansion* instance, const ExpansionFrame* frame) { - return expansion_protocol_encode(frame, expansion_send_callback, instance) == - ExpansionProtocolStatusOk; -} - -static bool expansion_send_heartbeat(Expansion* instance) { - const ExpansionFrame frame = { - .header.type = ExpansionFrameTypeHeartbeat, - .content.heartbeat = {}, - }; - - return expansion_send_frame(instance, &frame); -} - -static bool expansion_send_status_response(Expansion* instance, ExpansionFrameError error) { - const ExpansionFrame frame = { - .header.type = ExpansionFrameTypeStatus, - .content.status.error = error, - }; - - return expansion_send_frame(instance, &frame); -} - -static bool - expansion_send_data_response(Expansion* instance, const uint8_t* data, size_t data_size) { - furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); - - ExpansionFrame frame = { - .header.type = ExpansionFrameTypeData, - .content.data.size = data_size, - }; - - memcpy(frame.content.data.bytes, data, data_size); - return expansion_send_frame(instance, &frame); -} - -// Called in Rpc session thread context -static void expansion_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { - Expansion* instance = context; - - for(size_t sent_data_size = 0; sent_data_size < data_size;) { - if(furi_semaphore_acquire( - instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != - FuriStatusOk) { - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagError); - break; - } - - const size_t current_data_size = - MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); - if(!expansion_send_data_response(instance, data + sent_data_size, current_data_size)) - break; - sent_data_size += current_data_size; - } -} - -static bool expansion_rpc_session_open(Expansion* instance) { - Rpc* rpc = furi_record_open(RECORD_RPC); - instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); - - if(instance->rpc_session) { - instance->tx_semaphore = furi_semaphore_alloc(1, 1); - rpc_session_set_context(instance->rpc_session, instance); - rpc_session_set_send_bytes_callback(instance->rpc_session, expansion_rpc_send_callback); - } - - return instance->rpc_session != NULL; -} - -static void expansion_rpc_session_close(Expansion* instance) { - if(instance->rpc_session) { - rpc_session_close(instance->rpc_session); - furi_semaphore_free(instance->tx_semaphore); - } - - furi_record_close(RECORD_RPC); -} - -static bool - expansion_handle_session_state_handshake(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; - const uint32_t baud_rate = rx_frame->content.baud_rate.baud; - - FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); - - if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { - instance->session_state = ExpansionSessionStateConnected; - // Send response at previous baud rate - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - furi_hal_serial_set_br(instance->serial_handle, baud_rate); - - } else { - if(!expansion_send_status_response(instance, ExpansionFrameErrorBaudRate)) break; - FURI_LOG_E(TAG, "Bad baud rate"); - } - success = true; - } while(false); - - return success; -} - -static bool - expansion_handle_session_state_connected(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; - instance->session_state = ExpansionSessionStateRpcActive; - if(!expansion_rpc_session_open(instance)) break; - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { - if(!expansion_send_heartbeat(instance)) break; - - } else { - break; - } - success = true; - } while(false); - - return success; -} - -static bool - expansion_handle_session_state_rpc_active(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type == ExpansionFrameTypeData) { - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - const size_t size_consumed = rpc_session_feed( - instance->rpc_session, - rx_frame->content.data.bytes, - rx_frame->content.data.size, - EXPANSION_PROTOCOL_TIMEOUT_MS); - if(size_consumed != rx_frame->content.data.size) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; - instance->session_state = ExpansionSessionStateConnected; - expansion_rpc_session_close(instance); - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { - if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; - furi_semaphore_release(instance->tx_semaphore); - - } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { - if(!expansion_send_heartbeat(instance)) break; - - } else { - break; - } - success = true; - } while(false); - - return success; -} - -static inline void expansion_state_machine(Expansion* instance) { - typedef bool (*ExpansionSessionStateHandler)(Expansion*, const ExpansionFrame*); - - static const ExpansionSessionStateHandler expansion_handlers[] = { - [ExpansionSessionStateHandShake] = expansion_handle_session_state_handshake, - [ExpansionSessionStateConnected] = expansion_handle_session_state_connected, - [ExpansionSessionStateRpcActive] = expansion_handle_session_state_rpc_active, - }; - - ExpansionFrame rx_frame; - - while(true) { - if(!expansion_receive_frame(instance, &rx_frame)) break; - if(!expansion_handlers[instance->session_state](instance, &rx_frame)) break; - } -} - -static void expansion_worker_pending_callback(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); - - Expansion* instance = context; - furi_thread_join(instance->worker_thread); - - // Do not re-enable detection interrupt on user-requested exit - if(instance->exit_reason != ExpansionSessionExitReasonUser) { - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); - instance->state = ExpansionStateEnabled; - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); - furi_mutex_release(instance->state_mutex); - } -} - -static int32_t expansion_worker(void* context) { - furi_assert(context); - Expansion* instance = context; - - furi_hal_power_insomnia_enter(); - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - - instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); - furi_check(instance->serial_handle); - - FURI_LOG_D(TAG, "Service started"); - - instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_BUFFER_SIZE, 1); - instance->session_state = ExpansionSessionStateHandShake; - instance->exit_reason = ExpansionSessionExitReasonUnknown; - - furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); - - furi_hal_serial_async_rx_start( - instance->serial_handle, expansion_serial_rx_callback, instance, false); - - if(expansion_send_heartbeat(instance)) { - expansion_state_machine(instance); - } - - if(instance->session_state == ExpansionSessionStateRpcActive) { - expansion_rpc_session_close(instance); - } - - FURI_LOG_D(TAG, "Service stopped"); - - furi_hal_serial_control_release(instance->serial_handle); - furi_stream_buffer_free(instance->rx_buf); - - furi_hal_power_insomnia_exit(); - furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); - - return 0; -} +static const char* const expansion_uart_names[] = { + "USART", + "LPUART", +}; // Called from the serial control thread static void expansion_detect_callback(void* context) { furi_assert(context); Expansion* instance = context; - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + ExpansionMessage message = { + .type = ExpansionMessageTypeModuleConnected, + .api_lock = NULL, // Not locking the API here to avoid a deadlock + }; - if(instance->state == ExpansionStateEnabled) { - instance->state = ExpansionStateRunning; - furi_thread_start(instance->worker_thread); + // Not waiting for available queue space, discarding message if there is none + const FuriStatus status = furi_message_queue_put(instance->queue, &message, 0); + UNUSED(status); +} + +static void expansion_worker_callback(void* context) { + furi_assert(context); + Expansion* instance = context; + + ExpansionMessage message = { + .type = ExpansionMessageTypeModuleDisconnected, + .api_lock = NULL, // Not locking the API here to avoid a deadlock + }; + + const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); +} + +static void + expansion_control_handler_enable(Expansion* instance, const ExpansionMessageData* data) { + UNUSED(data); + + if(instance->state != ExpansionStateDisabled) { + return; } - furi_mutex_release(instance->state_mutex); + if(instance->settings.uart_index < FuriHalSerialIdMax) { + instance->state = ExpansionStateEnabled; + instance->serial_id = instance->settings.uart_index; + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + + FURI_LOG_D(TAG, "Detection enabled on %s", expansion_uart_names[instance->serial_id]); + } +} + +static void + expansion_control_handler_disable(Expansion* instance, const ExpansionMessageData* data) { + UNUSED(data); + + if(instance->state == ExpansionStateDisabled) { + return; + } else if(instance->state == ExpansionStateRunning) { + expansion_worker_stop(instance->worker); + expansion_worker_free(instance->worker); + } else { + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + } + + instance->state = ExpansionStateDisabled; + + FURI_LOG_D(TAG, "Detection disabled"); +} + +static void expansion_control_handler_set_listen_serial( + Expansion* instance, + const ExpansionMessageData* data) { + furi_check(data->serial_id < FuriHalSerialIdMax); + + if(instance->state == ExpansionStateRunning) { + expansion_worker_stop(instance->worker); + expansion_worker_free(instance->worker); + + } else if(instance->state == ExpansionStateEnabled) { + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + } + + instance->state = ExpansionStateEnabled; + instance->serial_id = data->serial_id; + + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); + + FURI_LOG_D(TAG, "Listen serial changed to %s", expansion_uart_names[instance->serial_id]); +} + +static void expansion_control_handler_module_connected( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateEnabled) { + return; + } + + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + + instance->state = ExpansionStateRunning; + instance->worker = expansion_worker_alloc(instance->serial_id); + + expansion_worker_set_callback(instance->worker, expansion_worker_callback, instance); + expansion_worker_start(instance->worker); +} + +static void expansion_control_handler_module_disconnected( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateRunning) { + return; + } + + instance->state = ExpansionStateEnabled; + expansion_worker_free(instance->worker); + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); +} + +typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); + +static const ExpansionControlHandler expansion_control_handlers[] = { + [ExpansionMessageTypeEnable] = expansion_control_handler_enable, + [ExpansionMessageTypeDisable] = expansion_control_handler_disable, + [ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial, + [ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected, + [ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected, +}; + +static int32_t expansion_control(void* context) { + furi_assert(context); + Expansion* instance = context; + + for(;;) { + ExpansionMessage message; + + FuriStatus status = furi_message_queue_get(instance->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); + + furi_check(message.type < COUNT_OF(expansion_control_handlers)); + expansion_control_handlers[message.type](instance, &message.data); + + if(message.api_lock != NULL) { + api_lock_unlock(message.api_lock); + } + } + + return 0; } static Expansion* expansion_alloc() { Expansion* instance = malloc(sizeof(Expansion)); - instance->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - instance->worker_thread = furi_thread_alloc_ex(TAG, 768, expansion_worker, instance); + instance->queue = + furi_message_queue_alloc(EXPANSION_CONTROL_QUEUE_SIZE, sizeof(ExpansionMessage)); + instance->thread = + furi_thread_alloc_ex(TAG, EXPANSION_CONTROL_STACK_SIZE, expansion_control, instance); return instance; } @@ -396,6 +217,7 @@ void expansion_on_system_start(void* arg) { Expansion* instance = expansion_alloc(); furi_record_create(RECORD_EXPANSION, instance); + furi_thread_start(instance->thread); expansion_settings_load(&instance->settings); expansion_enable(instance); @@ -404,41 +226,41 @@ void expansion_on_system_start(void* arg) { // Public API functions void expansion_enable(Expansion* instance) { - if(instance->settings.uart_index < FuriHalSerialIdMax) { - expansion_set_listen_serial(instance, instance->settings.uart_index); - } + furi_check(instance); + + ExpansionMessage message = { + .type = ExpansionMessageTypeEnable, + .api_lock = api_lock_alloc_locked(), + }; + + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } void expansion_disable(Expansion* instance) { - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + furi_check(instance); - if(instance->state == ExpansionStateRunning) { - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagStop); - furi_thread_join(instance->worker_thread); - } else if(instance->state == ExpansionStateEnabled) { - FURI_LOG_D(TAG, "Detection disabled"); - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - } + ExpansionMessage message = { + .type = ExpansionMessageTypeDisable, + .api_lock = api_lock_alloc_locked(), + }; - instance->state = ExpansionStateDisabled; - - furi_mutex_release(instance->state_mutex); + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { - expansion_disable(instance); + furi_check(instance); + furi_check(serial_id < FuriHalSerialIdMax); - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); + ExpansionMessage message = { + .type = ExpansionMessageTypeSetListenSerial, + .data.serial_id = serial_id, + .api_lock = api_lock_alloc_locked(), + }; - instance->serial_id = serial_id; - instance->state = ExpansionStateEnabled; - - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); - - furi_mutex_release(instance->state_mutex); - - FURI_LOG_D(TAG, "Detection enabled"); + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } ExpansionSettings* expansion_get_settings(Expansion* instance) { diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c new file mode 100644 index 000000000..fd92063d2 --- /dev/null +++ b/applications/services/expansion/expansion_worker.c @@ -0,0 +1,396 @@ +#include "expansion_worker.h" + +#include +#include +#include + +#include +#include + +#include "expansion_protocol.h" + +#define TAG "ExpansionSrv" + +#define EXPANSION_WORKER_STACK_SZIE (768UL) +#define EXPANSION_WORKER_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) + +typedef enum { + ExpansionWorkerStateHandShake, + ExpansionWorkerStateConnected, + ExpansionWorkerStateRpcActive, +} ExpansionWorkerState; + +typedef enum { + ExpansionWorkerExitReasonUnknown, + ExpansionWorkerExitReasonUser, + ExpansionWorkerExitReasonError, + ExpansionWorkerExitReasonTimeout, +} ExpansionWorkerExitReason; + +typedef enum { + ExpansionWorkerFlagStop = 1 << 0, + ExpansionWorkerFlagData = 1 << 1, + ExpansionWorkerFlagError = 1 << 2, +} ExpansionWorkerFlag; + +#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) + +struct ExpansionWorker { + FuriThread* thread; + FuriStreamBuffer* rx_buf; + FuriSemaphore* tx_semaphore; + + FuriHalSerialId serial_id; + FuriHalSerialHandle* serial_handle; + + RpcSession* rpc_session; + + ExpansionWorkerState state; + ExpansionWorkerExitReason exit_reason; + ExpansionWorkerCallback callback; + void* cb_context; +}; + +// Called in UART IRQ context +static void expansion_worker_serial_rx_callback( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context) { + furi_assert(handle); + furi_assert(context); + + ExpansionWorker* instance = context; + + if(event & (FuriHalSerialRxEventNoiseError | FuriHalSerialRxEventFrameError | + FuriHalSerialRxEventOverrunError)) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagError); + } else if(event & FuriHalSerialRxEventData) { + while(furi_hal_serial_async_rx_available(handle)) { + const uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); + } + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagData); + } +} + +static size_t expansion_worker_receive_callback(uint8_t* data, size_t data_size, void* context) { + ExpansionWorker* instance = context; + + size_t received_size = 0; + + while(true) { + received_size += furi_stream_buffer_receive( + instance->rx_buf, data + received_size, data_size - received_size, 0); + + if(received_size == data_size) break; + + const uint32_t flags = furi_thread_flags_wait( + EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); + + if(flags & FuriFlagError) { + if(flags == (unsigned)FuriFlagErrorTimeout) { + // Exiting due to timeout + instance->exit_reason = ExpansionWorkerExitReasonTimeout; + } else { + // Exiting due to an unspecified error + instance->exit_reason = ExpansionWorkerExitReasonError; + } + break; + } else if(flags & ExpansionWorkerFlagStop) { + // Exiting due to explicit request + instance->exit_reason = ExpansionWorkerExitReasonUser; + break; + } else if(flags & ExpansionWorkerFlagError) { + // Exiting due to RPC error + instance->exit_reason = ExpansionWorkerExitReasonError; + break; + } else if(flags & ExpansionWorkerFlagData) { + // Go to buffer reading + continue; + } + } + + return received_size; +} + +static inline bool + expansion_worker_receive_frame(ExpansionWorker* instance, ExpansionFrame* frame) { + return expansion_protocol_decode(frame, expansion_worker_receive_callback, instance) == + ExpansionProtocolStatusOk; +} + +static size_t + expansion_worker_send_callback(const uint8_t* data, size_t data_size, void* context) { + ExpansionWorker* instance = context; + furi_hal_serial_tx(instance->serial_handle, data, data_size); + furi_hal_serial_tx_wait_complete(instance->serial_handle); + return data_size; +} + +static inline bool + expansion_worker_send_frame(ExpansionWorker* instance, const ExpansionFrame* frame) { + return expansion_protocol_encode(frame, expansion_worker_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_worker_send_heartbeat(ExpansionWorker* instance) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeHeartbeat, + .content.heartbeat = {}, + }; + + return expansion_worker_send_frame(instance, &frame); +} + +static bool + expansion_worker_send_status_response(ExpansionWorker* instance, ExpansionFrameError error) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeStatus, + .content.status.error = error, + }; + + return expansion_worker_send_frame(instance, &frame); +} + +static bool expansion_worker_send_data_response( + ExpansionWorker* instance, + const uint8_t* data, + size_t data_size) { + furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); + + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeData, + .content.data.size = data_size, + }; + + memcpy(frame.content.data.bytes, data, data_size); + return expansion_worker_send_frame(instance, &frame); +} + +// Called in Rpc session thread context +static void expansion_worker_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { + ExpansionWorker* instance = context; + + for(size_t sent_data_size = 0; sent_data_size < data_size;) { + if(furi_semaphore_acquire( + instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != + FuriStatusOk) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagError); + break; + } + + const size_t current_data_size = + MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); + if(!expansion_worker_send_data_response(instance, data + sent_data_size, current_data_size)) + break; + sent_data_size += current_data_size; + } +} + +static bool expansion_worker_rpc_session_open(ExpansionWorker* instance) { + Rpc* rpc = furi_record_open(RECORD_RPC); + instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); + + if(instance->rpc_session) { + instance->tx_semaphore = furi_semaphore_alloc(1, 1); + rpc_session_set_context(instance->rpc_session, instance); + rpc_session_set_send_bytes_callback( + instance->rpc_session, expansion_worker_rpc_send_callback); + } + + return instance->rpc_session != NULL; +} + +static void expansion_worker_rpc_session_close(ExpansionWorker* instance) { + if(instance->rpc_session) { + rpc_session_close(instance->rpc_session); + furi_semaphore_free(instance->tx_semaphore); + } + + furi_record_close(RECORD_RPC); +} + +static bool expansion_worker_handle_state_handshake( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; + const uint32_t baud_rate = rx_frame->content.baud_rate.baud; + + FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); + + if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { + instance->state = ExpansionWorkerStateConnected; + // Send response at previous baud rate + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + furi_hal_serial_set_br(instance->serial_handle, baud_rate); + + } else { + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorBaudRate)) + break; + FURI_LOG_E(TAG, "Bad baud rate"); + } + success = true; + } while(false); + + return success; +} + +static bool expansion_worker_handle_state_connected( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; + instance->state = ExpansionWorkerStateRpcActive; + if(!expansion_worker_rpc_session_open(instance)) break; + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_worker_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +static bool expansion_worker_handle_state_rpc_active( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeData) { + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + const size_t size_consumed = rpc_session_feed( + instance->rpc_session, + rx_frame->content.data.bytes, + rx_frame->content.data.size, + EXPANSION_PROTOCOL_TIMEOUT_MS); + if(size_consumed != rx_frame->content.data.size) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; + instance->state = ExpansionWorkerStateConnected; + expansion_worker_rpc_session_close(instance); + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { + if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; + furi_semaphore_release(instance->tx_semaphore); + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_worker_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +typedef bool (*ExpansionWorkerStateHandler)(ExpansionWorker*, const ExpansionFrame*); + +static const ExpansionWorkerStateHandler expansion_handlers[] = { + [ExpansionWorkerStateHandShake] = expansion_worker_handle_state_handshake, + [ExpansionWorkerStateConnected] = expansion_worker_handle_state_connected, + [ExpansionWorkerStateRpcActive] = expansion_worker_handle_state_rpc_active, +}; + +static inline void expansion_worker_state_machine(ExpansionWorker* instance) { + ExpansionFrame rx_frame; + + while(true) { + if(!expansion_worker_receive_frame(instance, &rx_frame)) break; + if(!expansion_handlers[instance->state](instance, &rx_frame)) break; + } +} + +static int32_t expansion_worker(void* context) { + furi_assert(context); + ExpansionWorker* instance = context; + + furi_hal_power_insomnia_enter(); + + instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); + furi_check(instance->serial_handle); + + FURI_LOG_D(TAG, "Worker started"); + + instance->state = ExpansionWorkerStateHandShake; + instance->exit_reason = ExpansionWorkerExitReasonUnknown; + + furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); + + furi_hal_serial_async_rx_start( + instance->serial_handle, expansion_worker_serial_rx_callback, instance, true); + + if(expansion_worker_send_heartbeat(instance)) { + expansion_worker_state_machine(instance); + } + + if(instance->state == ExpansionWorkerStateRpcActive) { + expansion_worker_rpc_session_close(instance); + } + + FURI_LOG_D(TAG, "Worker stopped"); + + furi_hal_serial_control_release(instance->serial_handle); + furi_hal_power_insomnia_exit(); + + // Do not invoke worker callback on user-requested exit + if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { + instance->callback(instance->cb_context); + } + + return 0; +} + +ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { + ExpansionWorker* instance = malloc(sizeof(ExpansionWorker)); + + instance->thread = furi_thread_alloc_ex( + TAG "Worker", EXPANSION_WORKER_STACK_SZIE, expansion_worker, instance); + instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_WORKER_BUFFER_SIZE, 1); + instance->serial_id = serial_id; + + // Improves responsiveness in heavy games at the expense of dropped frames + furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); + + return instance; +} + +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); +} + +void expansion_worker_set_callback( + ExpansionWorker* instance, + ExpansionWorkerCallback callback, + void* context) { + instance->callback = callback; + instance->cb_context = context; +} + +void expansion_worker_start(ExpansionWorker* instance) { + furi_thread_start(instance->thread); +} + +void expansion_worker_stop(ExpansionWorker* instance) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagStop); + furi_thread_join(instance->thread); +} diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h new file mode 100644 index 000000000..761f79c1d --- /dev/null +++ b/applications/services/expansion/expansion_worker.h @@ -0,0 +1,78 @@ +/** + * @file expansion_worker.h + * @brief Expansion module handling thread wrapper. + * + * The worker is started each time an expansion module is detected + * and handles all of the communication protocols. Likewise, it is stopped + * upon module disconnection or communication error. + * + * @warning This file is a private implementation detail. Please do not attempt to use it in applications. + */ +#pragma once + +#include + +/** + * @brief Expansion worker opaque type declaration. + */ +typedef struct ExpansionWorker ExpansionWorker; + +/** + * @brief Worker callback type. + * + * @see expansion_worker_set_callback() + * + * @param[in,out] context pointer to a user-defined object. + */ +typedef void (*ExpansionWorkerCallback)(void* context); + +/** + * @brief Create an expansion worker instance. + * + * @param[in] serial_id numerical identifier of the serial to be used by the worker. + * @returns pointer to the created instance. + */ +ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id); + +/** + * @brief Delete an expansion worker instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void expansion_worker_free(ExpansionWorker* instance); + +/** + * @brief Set the module disconnect callback. + * + * The callback will be triggered upon worker stop EXCEPT + * when it was stopped via an expansion_worker_stop() call. + * + * In other words, the callback will ONLY be triggered if the worker was + * stopped due to the user disconnecting/resetting/powering down the module, + * or due to some communication error. + * + * @param[in,out] instance pointer to the worker instance to be modified. + * @param[in] callback pointer to the callback function to be called under the above conditions. + * @param[in] context pointer to a user-defined object, will be passed as a parameter to the callback. + */ +void expansion_worker_set_callback( + ExpansionWorker* instance, + ExpansionWorkerCallback callback, + void* context); + +/** + * @brief Start the expansion module worker. + * + * @param[in,out] instance pointer to the worker instance to be started. + */ +void expansion_worker_start(ExpansionWorker* instance); + +/** + * @brief Stop the expansion module worker. + * + * If the worker was stopped via this call (and not because of module disconnect/ + * protocol error), the callback will not be triggered. + * + * @param[in,out] instance pointer to the worker instance to be stopped. + */ +void expansion_worker_stop(ExpansionWorker* instance); diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index bcffbc612..9865db68c 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -19,6 +19,12 @@ Canvas* canvas_init() { Canvas* canvas = malloc(sizeof(Canvas)); canvas->compress_icon = compress_icon_alloc(); + // Initialize mutex + canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + // Initialize callback array + CanvasCallbackPairArray_init(canvas->canvas_callback_pair); + // Setup u8g2 u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); canvas->orientation = CanvasOrientationHorizontal; @@ -37,9 +43,21 @@ Canvas* canvas_init() { void canvas_free(Canvas* canvas) { furi_assert(canvas); compress_icon_free(canvas->compress_icon); + CanvasCallbackPairArray_clear(canvas->canvas_callback_pair); + furi_mutex_free(canvas->mutex); free(canvas); } +static void canvas_lock(Canvas* canvas) { + furi_assert(canvas); + furi_check(furi_mutex_acquire(canvas->mutex, FuriWaitForever) == FuriStatusOk); +} + +static void canvas_unlock(Canvas* canvas) { + furi_assert(canvas); + furi_check(furi_mutex_release(canvas->mutex) == FuriStatusOk); +} + void canvas_reset(Canvas* canvas) { furi_assert(canvas); @@ -53,6 +71,18 @@ void canvas_reset(Canvas* canvas) { void canvas_commit(Canvas* canvas) { furi_assert(canvas); u8g2_SendBuffer(&canvas->fb); + + // Iterate over callbacks + canvas_lock(canvas); + for + M_EACH(p, canvas->canvas_callback_pair, CanvasCallbackPairArray_t) { + p->callback( + canvas_get_buffer(canvas), + canvas_get_buffer_size(canvas), + canvas_get_orientation(canvas), + p->context); + } + canvas_unlock(canvas); } uint8_t* canvas_get_buffer(Canvas* canvas) { @@ -567,3 +597,28 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { CanvasOrientation canvas_get_orientation(const Canvas* canvas) { return canvas->orientation; } + +void canvas_add_framebuffer_callback(Canvas* canvas, CanvasCommitCallback callback, void* context) { + furi_assert(canvas); + + const CanvasCallbackPair p = {callback, context}; + + canvas_lock(canvas); + furi_assert(!CanvasCallbackPairArray_count(canvas->canvas_callback_pair, p)); + CanvasCallbackPairArray_push_back(canvas->canvas_callback_pair, p); + canvas_unlock(canvas); +} + +void canvas_remove_framebuffer_callback( + Canvas* canvas, + CanvasCommitCallback callback, + void* context) { + furi_assert(canvas); + + const CanvasCallbackPair p = {callback, context}; + + canvas_lock(canvas); + furi_assert(CanvasCallbackPairArray_count(canvas->canvas_callback_pair, p) == 1); + CanvasCallbackPairArray_remove_val(canvas->canvas_callback_pair, p); + canvas_unlock(canvas); +} \ No newline at end of file diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index f26fe8a89..73502b719 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -8,11 +8,31 @@ #include "canvas.h" #include #include +#include +#include +#include #ifdef __cplusplus extern "C" { #endif +typedef void (*CanvasCommitCallback)( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context); + +typedef struct { + CanvasCommitCallback callback; + void* context; +} CanvasCallbackPair; + +ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); + +#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) + +ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); + /** Canvas structure */ struct Canvas { @@ -23,6 +43,8 @@ struct Canvas { uint8_t width; uint8_t height; CompressIcon* compress_icon; + CanvasCallbackPairArray_t canvas_callback_pair; + FuriMutex* mutex; }; /** Allocate memory and initialize canvas @@ -102,6 +124,27 @@ void canvas_draw_u8g2_bitmap( const uint8_t* bitmap, uint8_t rotation); +/** Add canvas commit callback. + * + * This callback will be called upon Canvas commit. + * + * @param canvas Canvas instance + * @param callback CanvasCommitCallback + * @param context CanvasCommitCallback context + */ +void canvas_add_framebuffer_callback(Canvas* canvas, CanvasCommitCallback callback, void* context); + +/** Remove canvas commit callback. + * + * @param canvas Canvas instance + * @param callback CanvasCommitCallback + * @param context CanvasCommitCallback context + */ +void canvas_remove_framebuffer_callback( + Canvas* canvas, + CanvasCommitCallback callback, + void* context); + #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 0bdc999b7..9deaf23fe 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -265,14 +265,6 @@ static void gui_redraw(Gui* gui) { } canvas_commit(gui->canvas); - for - M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) { - p->callback( - canvas_get_buffer(gui->canvas), - canvas_get_buffer_size(gui->canvas), - canvas_get_orientation(gui->canvas), - p->context); - } } while(false); gui_unlock(gui); @@ -473,12 +465,7 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - const CanvasCallbackPair p = {callback, context}; - - gui_lock(gui); - furi_assert(!CanvasCallbackPairArray_count(gui->canvas_callback_pair, p)); - CanvasCallbackPairArray_push_back(gui->canvas_callback_pair, p); - gui_unlock(gui); + canvas_add_framebuffer_callback(gui->canvas, callback, context); // Request redraw gui_update(gui); @@ -487,12 +474,7 @@ void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - const CanvasCallbackPair p = {callback, context}; - - gui_lock(gui); - furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 1); - CanvasCallbackPairArray_remove_val(gui->canvas_callback_pair, p); - gui_unlock(gui); + canvas_remove_framebuffer_callback(gui->canvas, callback, context); } size_t gui_get_framebuffer_size(const Gui* gui) { @@ -542,20 +524,19 @@ Gui* gui_alloc() { gui->thread_id = furi_thread_get_current_id(); // Allocate mutex gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - furi_check(gui->mutex); + // Layers for(size_t i = 0; i < GuiLayerMAX; i++) { ViewPortArray_init(gui->layers[i]); } + // Drawing canvas gui->canvas = canvas_init(); - CanvasCallbackPairArray_init(gui->canvas_callback_pair); // Input gui->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); gui->input_events = furi_record_open(RECORD_INPUT_EVENTS); - furi_check(gui->input_events); furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); return gui; diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index a5e269e03..3ca9c05c9 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include "canvas.h" @@ -44,17 +43,6 @@ ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); -typedef struct { - GuiCanvasCommitCallback callback; - void* context; -} CanvasCallbackPair; - -ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); - -#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) - -ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); - /** Gui structure */ struct Gui { // Thread and lock @@ -66,7 +54,6 @@ struct Gui { bool direct_draw; ViewPortArray_t layers[GuiLayerMAX]; Canvas* canvas; - CanvasCallbackPairArray_t canvas_callback_pair; // Input FuriMessageQueue* input_queue; diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 909d0d65d..e03e4f45a 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -189,6 +189,12 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { furi_assert(session); furi_assert(istream->bytes_left); + /* TODO FL-3768 this function may be called after + marking the worker for termination */ + if(session->terminate) { + return false; + } + uint32_t flags = 0; size_t bytes_received = 0; diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 98860332d..ee3526590 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -54,6 +54,7 @@ typedef enum { typedef struct { RpcSession* session; Gui* gui; + const Icon* icon; // Receive part ViewPort* virtual_display_view_port; @@ -380,10 +381,19 @@ static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, (void)session; } +static const Icon* rpc_system_gui_get_owner_icon(RpcOwner owner) { + switch(owner) { + case RpcOwnerUart: + return &I_Exp_module_connected_12x8; + default: + return &I_Rpc_active_7x8; + } +} + static void rpc_active_session_icon_draw_callback(Canvas* canvas, void* context) { - UNUSED(context); furi_assert(canvas); - canvas_draw_icon(canvas, 0, 0, &I_Rpc_active_7x8); + RpcGuiSystem* rpc_gui = context; + canvas_draw_icon(canvas, 0, 0, rpc_gui->icon); } void* rpc_system_gui_alloc(RpcSession* session) { @@ -394,16 +404,16 @@ void* rpc_system_gui_alloc(RpcSession* session) { rpc_gui->session = session; // Active session icon - rpc_gui->rpc_session_active_viewport = view_port_alloc(); - view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(&I_Rpc_active_7x8)); - view_port_draw_callback_set( - rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, session); - if(rpc_session_get_owner(rpc_gui->session) != RpcOwnerBle) { - view_port_enabled_set(rpc_gui->rpc_session_active_viewport, true); - } else { - view_port_enabled_set(rpc_gui->rpc_session_active_viewport, false); + const RpcOwner owner = rpc_session_get_owner(rpc_gui->session); + if(owner != RpcOwnerBle) { + rpc_gui->icon = rpc_system_gui_get_owner_icon(owner); + rpc_gui->rpc_session_active_viewport = view_port_alloc(); + view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(rpc_gui->icon)); + view_port_draw_callback_set( + rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, rpc_gui); + gui_add_view_port( + rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); } - gui_add_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); RpcHandler rpc_handler = { .message_handler = NULL, @@ -445,8 +455,10 @@ void rpc_system_gui_free(void* context) { rpc_gui->virtual_display_not_empty = false; } - gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); - view_port_free(rpc_gui->rpc_session_active_viewport); + if(rpc_gui->rpc_session_active_viewport) { + gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); + view_port_free(rpc_gui->rpc_session_active_viewport); + } if(rpc_gui->is_streaming) { rpc_gui->is_streaming = false; diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 1d71490a2..3b7031a36 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -276,7 +276,7 @@ static void hid_ptt_stop_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) { // teams static void hid_ptt_start_ptt_macos_teams(HidPushToTalk* hid_ptt) { - hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_SPACEBAR); + hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_SPACEBAR); } static void hid_ptt_start_ptt_linux_teams(HidPushToTalk* hid_ptt) { hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_SPACEBAR); diff --git a/assets/icons/StatusBar/Exp_module_connected_12x8.png b/assets/icons/StatusBar/Exp_module_connected_12x8.png new file mode 100644 index 000000000..a5f096682 Binary files /dev/null and b/assets/icons/StatusBar/Exp_module_connected_12x8.png differ diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index b0062e659..2fc12dedb 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -21,6 +21,10 @@ extern "C" { #define FURI_PACKED __attribute__((packed)) #endif +#ifndef FURI_ALWAYS_STATIC_INLINE +#define FURI_ALWAYS_STATIC_INLINE __attribute__((always_inline)) static inline +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif diff --git a/furi/core/timer.c b/furi/core/timer.c index 027e4cf40..f667aae96 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -67,17 +67,22 @@ void furi_timer_free(FuriTimer* instance) { callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer); - furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - - while(furi_timer_is_running(instance)) furi_delay_tick(2); - if((uint32_t)callb & 1U) { + /* If callback memory was allocated, it is only safe to free it with + * the timer inactive. Send a stop command and wait for the timer to + * be in an inactive state. + */ + furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); + while(furi_timer_is_running(instance)) furi_delay_tick(2); + /* Callback memory was allocated from dynamic pool, clear flag */ callb = (TimerCallback_t*)((uint32_t)callb & ~1U); /* Return allocated memory to dynamic pool */ free(callb); } + + furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { @@ -170,4 +175,4 @@ void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { } else { furi_crash(); } -} \ 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 { 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/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/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) { 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; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ffb664a3e..1971f5c41 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,54.0,, +Version,+,54.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1273,6 +1273,7 @@ Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 677137937..cb2c9cce8 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,54.0,, +Version,+,54.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1490,6 +1490,7 @@ Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index cc5a05244..d4f6e193b 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -129,11 +129,9 @@ void furi_hal_gpio_init_ex( LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT); LL_SYSCFG_SetEXTISource(sys_exti_port, sys_exti_line); if(mode == GpioModeInterruptRise || mode == GpioModeInterruptRiseFall) { - LL_EXTI_EnableIT_0_31(exti_line); LL_EXTI_EnableRisingTrig_0_31(exti_line); } if(mode == GpioModeInterruptFall || mode == GpioModeInterruptRiseFall) { - LL_EXTI_EnableIT_0_31(exti_line); LL_EXTI_EnableFallingTrig_0_31(exti_line); } if(mode == GpioModeEventRise || mode == GpioModeEventRiseFall) { @@ -149,6 +147,7 @@ void furi_hal_gpio_init_ex( if(LL_SYSCFG_GetEXTISource(sys_exti_line) == sys_exti_port && LL_EXTI_IsEnabledIT_0_31(exti_line)) { LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); LL_EXTI_DisableRisingTrig_0_31(exti_line); LL_EXTI_DisableFallingTrig_0_31(exti_line); } @@ -199,14 +198,15 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo furi_assert(cb); FURI_CRITICAL_ENTER(); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - volatile GpioInterrupt* interrupt = &gpio_interrupt[pin_num]; - furi_check( - (interrupt->callback == NULL) || - ((interrupt->callback == cb) && (interrupt->context == ctx))); - interrupt->callback = cb; - interrupt->context = ctx; - interrupt->ready = true; + furi_check(gpio_interrupt[pin_num].callback == NULL); + gpio_interrupt[pin_num].callback = cb; + gpio_interrupt[pin_num].context = ctx; + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); + FURI_CRITICAL_EXIT(); } @@ -214,10 +214,13 @@ void furi_hal_gpio_enable_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); if(gpio_interrupt[pin_num].callback) { - gpio_interrupt[pin_num].ready = true; + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); } + FURI_CRITICAL_EXIT(); } @@ -225,8 +228,11 @@ void furi_hal_gpio_disable_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); - uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - gpio_interrupt[pin_num].ready = false; + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); + FURI_CRITICAL_EXIT(); } @@ -234,15 +240,20 @@ void furi_hal_gpio_remove_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); gpio_interrupt[pin_num].callback = NULL; gpio_interrupt[pin_num].context = NULL; - gpio_interrupt[pin_num].ready = false; + FURI_CRITICAL_EXIT(); } -static void furi_hal_gpio_int_call(uint16_t pin_num) { - if(gpio_interrupt[pin_num].callback && gpio_interrupt[pin_num].ready) { +FURI_ALWAYS_STATIC_INLINE void furi_hal_gpio_int_call(uint16_t pin_num) { + if(gpio_interrupt[pin_num].callback) { gpio_interrupt[pin_num].callback(gpio_interrupt[pin_num].context); } } diff --git a/targets/f7/furi_hal/furi_hal_gpio.h b/targets/f7/furi_hal/furi_hal_gpio.h index 0999971bc..9e78872bb 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.h +++ b/targets/f7/furi_hal/furi_hal_gpio.h @@ -24,7 +24,6 @@ typedef void (*GpioExtiCallback)(void* ctx); typedef struct { GpioExtiCallback callback; void* context; - volatile bool ready; } GpioInterrupt; /** diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 1296ee620..e0e2d8d52 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -730,6 +730,13 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle + furi_hal_serial[handle->id].rx_byte_callback = callback; + furi_hal_serial[handle->id].handle = handle; + furi_hal_serial[handle->id].rx_dma_callback = NULL; + furi_hal_serial[handle->id].context = context; + if(handle->id == FuriHalSerialIdUsart) { if(callback) { furi_hal_serial_usart_deinit_dma_rx(); @@ -753,10 +760,6 @@ static void furi_hal_serial_async_rx_configure( LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } - furi_hal_serial[handle->id].rx_byte_callback = callback; - furi_hal_serial[handle->id].handle = handle; - furi_hal_serial[handle->id].rx_dma_callback = NULL; - furi_hal_serial[handle->id].context = context; } void furi_hal_serial_async_rx_start( @@ -782,6 +785,17 @@ void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle) { furi_hal_serial_async_rx_configure(handle, NULL, NULL); } +bool furi_hal_serial_async_rx_available(FuriHalSerialHandle* handle) { + furi_check(FURI_IS_IRQ_MODE()); + furi_assert(handle->id < FuriHalSerialIdMax); + + if(handle->id == FuriHalSerialIdUsart) { + return LL_USART_IsActiveFlag_RXNE_RXFNE(USART1); + } else { + return LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1); + } +} + uint8_t furi_hal_serial_async_rx(FuriHalSerialHandle* handle) { furi_check(FURI_IS_IRQ_MODE()); furi_assert(handle->id < FuriHalSerialIdMax); diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h index 975406670..00010d83c 100644 --- a/targets/f7/furi_hal/furi_hal_serial.h +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -130,6 +130,16 @@ void furi_hal_serial_async_rx_start( */ void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle); +/** Check if there is data available for reading + * + * @warning This function must be called only from the callback + * FuriHalSerialAsyncRxCallback + * + * @param handle Serial handle + * @return true if data is available for reading, false otherwise + */ +bool furi_hal_serial_async_rx_available(FuriHalSerialHandle* handle); + /** Get data Serial receive * * @warning This function must be called only from the callback diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c index 37454823b..d2ab414c2 100644 --- a/targets/f7/furi_hal/furi_hal_serial_control.c +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -47,6 +47,7 @@ typedef struct { FuriHalSerialHandle* log_serial; // Expansion detection + FuriHalSerialHandle* expansion_serial; FuriHalSerialControlExpansionCallback expansion_cb; void* expansion_ctx; } FuriHalSerialControl; @@ -58,7 +59,36 @@ static void furi_hal_serial_control_log_callback(const uint8_t* data, size_t siz furi_hal_serial_tx(handle, data, size); } +static void furi_hal_serial_control_expansion_irq_callback(void* context) { + UNUSED(context); + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeExpansionIrq; + message.api_lock = NULL; + furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); +} + +static void + furi_hal_serial_control_enable_expansion_irq(FuriHalSerialHandle* handle, bool enable) { + const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); + + if(enable) { + furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); + furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + } else { + furi_hal_gpio_remove_int_callback(gpio); + furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + } +} + static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) { + // Disable expansion module detection before reconfiguring UARTs + if(furi_hal_serial_control->expansion_serial) { + furi_hal_serial_control_enable_expansion_irq( + furi_hal_serial_control->expansion_serial, false); + } + if(furi_hal_serial_control->log_serial) { furi_log_remove_handler(furi_hal_serial_control->log_handler); furi_hal_serial_deinit(furi_hal_serial_control->log_serial); @@ -74,15 +104,12 @@ static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) furi_hal_serial_control->log_handler.context = furi_hal_serial_control->log_serial; furi_log_add_handler(furi_hal_serial_control->log_handler); } -} -static void furi_hal_serial_control_expansion_irq_callback(void* context) { - UNUSED(context); - - FuriHalSerialControlMessage message; - message.type = FuriHalSerialControlMessageTypeExpansionIrq; - message.api_lock = NULL; - furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); + // Re-enable expansion module detection (if applicable) + if(furi_hal_serial_control->expansion_serial) { + furi_hal_serial_control_enable_expansion_irq( + furi_hal_serial_control->expansion_serial, true); + } } static bool furi_hal_serial_control_handler_stop(void* input, void* output) { @@ -93,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; @@ -116,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; @@ -157,24 +192,24 @@ static bool furi_hal_serial_control_handler_expansion_set_callback(void* input, FuriHalSerialControlMessageExpCallback* message_input = input; FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[message_input->id]; - const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); - if(message_input->callback) { + const bool enable_irq = message_input->callback != NULL; + + if(enable_irq) { + furi_check(furi_hal_serial_control->expansion_serial == NULL); furi_check(furi_hal_serial_control->expansion_cb == NULL); - - furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); - furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); - furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + furi_hal_serial_control->expansion_serial = handle; } else { + furi_check(furi_hal_serial_control->expansion_serial == handle); furi_check(furi_hal_serial_control->expansion_cb != NULL); - - furi_hal_gpio_remove_int_callback(gpio); - furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_serial_control->expansion_serial = NULL; } furi_hal_serial_control->expansion_cb = message_input->callback; furi_hal_serial_control->expansion_ctx = message_input->context; + furi_hal_serial_control_enable_expansion_irq(handle, enable_irq); + return true; } 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: