diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index 790fa5766..d31857b09 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -1,5 +1,8 @@ #include "gallagher/gallagher_util.h" #include "mosgortrans/mosgortrans_util.h" +#include "../nfc_app_i.h" +#include "../helpers/protocol_support/nfc_protocol_support_gui_common.h" +#include "../helpers/protocol_support/nfc_protocol_support_unlock_helper.h" /* * A list of app's private functions and objects to expose for plugins. @@ -22,4 +25,20 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( (FuriString * str, const char* name, uint8_t prefix_separator_cnt, - uint8_t suffix_separator_cnt)))); + uint8_t suffix_separator_cnt)), + API_METHOD( + nfc_append_filename_string_when_present, + void, + (NfcApp * instance, FuriString* string)), + API_METHOD(nfc_protocol_support_common_submenu_callback, void, (void* context, uint32_t index)), + API_METHOD( + nfc_protocol_support_common_widget_callback, + void, + (GuiButtonType result, InputType type, void* context)), + API_METHOD(nfc_protocol_support_common_on_enter_empty, void, (NfcApp * instance)), + API_METHOD( + nfc_protocol_support_common_on_event_empty, + bool, + (NfcApp * instance, SceneManagerEvent event)), + API_METHOD(nfc_unlock_helper_setup_from_state, void, (NfcApp * instance)), + API_METHOD(nfc_unlock_helper_card_detected_handler, void, (NfcApp * instance)))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 96ac4c3b7..5699685d3 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -18,6 +18,205 @@ App( fap_category="NFC", ) +# Protocol support plugins + +App( + appid="nfc_iso14443_3a", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_3a_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso14443_3b", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_3b_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_3b/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso14443_4a", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_4a_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso14443_4b", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso14443_4b_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso14443_4b/*.c", + "helpers/protocol_support/iso14443_3b/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_iso15693_3", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_iso15693_3_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/iso15693_3/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_felica", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_felica_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/felica/*.c", + "helpers/felica_*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_mf_ultralight", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_ultralight_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_ultralight/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + "helpers/mf_ultralight_*.c", + ], + fap_libs=["mbedtls"], + fal_embedded=True, +) + +App( + appid="nfc_mf_classic", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_classic_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_classic/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + "helpers/mf_classic_*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_mf_plus", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_plus_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_plus/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_mf_desfire", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_mf_desfire_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/mf_desfire/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_slix", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_slix_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/slix/*.c", + "helpers/protocol_support/iso15693_3/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_st25tb", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_st25tb_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/st25tb/*.c", + ], + fal_embedded=True, +) + + +App( + appid="nfc_ntag4xx", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_ntag4xx_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/ntag4xx/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) +App( + appid="nfc_type_4_tag", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_type_4_tag_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/type_4_tag/*.c", + "helpers/protocol_support/iso14443_4a/*.c", + "helpers/protocol_support/iso14443_3a/*.c", + ], + fal_embedded=True, +) + +App( + appid="nfc_emv", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="nfc_emv_ep", + requires=["nfc"], + sources=[ + "helpers/protocol_support/emv/*.c", + ], + fal_embedded=True, +) + # Parser plugins App( @@ -29,6 +228,15 @@ App( sources=["plugins/supported_cards/all_in_one.c"], ) +App( + appid="smartrider_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="smartrider_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/smartrider.c"], +) + App( appid="microel_parser", apptype=FlipperAppType.PLUGIN, @@ -254,6 +462,7 @@ App( requires=["nfc"], sources=["plugins/supported_cards/ndef.c"], ) + App( appid="ndef_mfc_parser", apptype=FlipperAppType.PLUGIN, @@ -274,6 +483,16 @@ App( sources=["plugins/supported_cards/ndef.c"], ) +App( + appid="ndef_t4t_parser", + apptype=FlipperAppType.PLUGIN, + cdefines=[("NDEF_PROTO", "NDEF_PROTO_T4T")], + entry_point="ndef_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ndef.c"], +) + App( appid="itso_parser", apptype=FlipperAppType.PLUGIN, @@ -320,6 +539,33 @@ App( sources=["plugins/supported_cards/disney_infinity.c"], ) +App( + appid="sonicare_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="sonicare_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/sonicare.c"], +) + +App( + appid="csc_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="csc_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/csc.c"], +) + +App( + appid="ventra_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="ventra_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ventra.c"], +) + App( appid="cli_nfc", targets=["f7"], diff --git a/applications/main/nfc/helpers/nfc_custom_event.h b/applications/main/nfc/helpers/nfc_custom_event.h index 16fbc4749..de8cf23bb 100644 --- a/applications/main/nfc/helpers/nfc_custom_event.h +++ b/applications/main/nfc/helpers/nfc_custom_event.h @@ -13,6 +13,7 @@ typedef enum { NfcCustomEventCardLost, NfcCustomEventViewExit, + NfcCustomEventRetry, NfcCustomEventWorkerExit, NfcCustomEventWorkerUpdate, NfcCustomEventWrongCard, @@ -30,4 +31,6 @@ typedef enum { NfcCustomEventPollerFailure, NfcCustomEventListenerUpdate, + + NfcCustomEventEmulationTimeExpired, } NfcCustomEvent; diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c index 6513eef5f..5c974f463 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.c +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -1,11 +1,9 @@ #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 @@ -52,12 +50,9 @@ struct NfcSupportedCards { NfcSupportedCardsLoadContext* load_context; }; -NfcSupportedCards* nfc_supported_cards_alloc(void) { +NfcSupportedCards* nfc_supported_cards_alloc(CompositeApiResolver* api_resolver) { 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); + instance->api_resolver = api_resolver; NfcSupportedCardsPluginCache_init(instance->plugins_cache_arr); @@ -76,7 +71,6 @@ void nfc_supported_cards_free(NfcSupportedCards* instance) { } NfcSupportedCardsPluginCache_clear(instance->plugins_cache_arr); - composite_api_resolver_free(instance->api_resolver); free(instance); } diff --git a/applications/main/nfc/helpers/nfc_supported_cards.h b/applications/main/nfc/helpers/nfc_supported_cards.h index 5eaee360a..363abe30b 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.h +++ b/applications/main/nfc/helpers/nfc_supported_cards.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -25,7 +26,7 @@ typedef struct NfcSupportedCards NfcSupportedCards; * * @return pointer to allocated NfcSupportedCards instance. */ -NfcSupportedCards* nfc_supported_cards_alloc(void); +NfcSupportedCards* nfc_supported_cards_alloc(CompositeApiResolver* api_resolver); /** * @brief Delete an NfcSupportedCards instance diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv.c b/applications/main/nfc/helpers/protocol_support/emv/emv.c index 06e2ca624..a2232b00e 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv.c @@ -128,4 +128,11 @@ const NfcProtocolSupportBase nfc_protocol_support_emv = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(emv, NfcProtocolEmv); diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 561cd4d2e..0c4234bad 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -133,15 +133,6 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data); @@ -201,7 +192,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_felica, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -213,4 +204,11 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = { .on_enter = nfc_scene_emulate_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(felica, NfcProtocolFelica); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c index 99e211301..adcaaf776 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -67,21 +67,22 @@ static NfcCommand furi_assert(event.protocol == NfcProtocolIso14443_3a); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data; if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_3a_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(iso14443_3a_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -97,15 +98,6 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, @@ -122,7 +114,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { .scene_read_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_read_menu_on_event_iso14443_3a, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read_success = { @@ -144,4 +136,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = { .on_enter = nfc_scene_emulate_on_enter_iso14443_3a, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_3a, NfcProtocolIso14443_3a); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c index 43b541111..1a9931b28 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -60,19 +60,6 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) { furi_string_free(temp_str); } -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - -static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) { - return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { .features = NfcProtocolFeatureNone, @@ -99,7 +86,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_iso14443_3b, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -111,4 +98,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_3b, NfcProtocolIso14443_3b); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h deleted file mode 100644 index 6c7c2a0bc..000000000 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -#include "iso14443_3b.h" - -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c index 17435ccd4..03f5b5ffd 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -70,21 +70,22 @@ NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event furi_assert(event.protocol == NfcProtocolIso14443_4a); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data; if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_4a_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(iso14443_4a_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -100,15 +101,6 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid, @@ -125,7 +117,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { .scene_read_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_read_menu_on_event_iso14443_4a, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read_success = { @@ -147,4 +139,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = { .on_enter = nfc_scene_emulate_on_enter_iso14443_4a, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_4a, NfcProtocolIso14443_4a); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c index 8038e0491..9cd3829f9 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -7,7 +7,6 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" -#include "../iso14443_3b/iso14443_3b_i.h" static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; @@ -61,23 +60,6 @@ static void nfc_scene_read_success_on_enter_iso14443_4b(NfcApp* instance) { furi_string_free(temp_str); } -static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) { - UNUSED(instance); -} - -static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); - return true; - } - - return false; -} - -static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { - return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); -} - const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { .features = NfcProtocolFeatureNone, @@ -94,7 +76,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { .scene_read_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_read_menu_on_event_iso14443_4b, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read_success = { @@ -103,8 +85,8 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { }, .scene_saved_menu = { - .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4b, - .on_event = nfc_scene_saved_menu_on_event_iso14443_4b, + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -116,4 +98,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso14443_4b, NfcProtocolIso14443_4b); diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index 7efd102f1..4c69fbe81 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -80,20 +80,21 @@ static NfcCommand furi_assert(event.protocol == NfcProtocolIso15693_3); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; Iso15693_3ListenerEvent* iso15693_3_event = event.event_data; if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(iso15693_3_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(iso15693_3_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -108,15 +109,6 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance); } -static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid | NfcProtocolFeatureMoreInfo, @@ -149,7 +141,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -161,4 +153,11 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { .on_enter = nfc_scene_emulate_on_enter_iso15693_3, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(iso15693_3, NfcProtocolIso15693_3); 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 4f8540d6e..fdf7fb35a 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 @@ -12,10 +12,9 @@ enum { SubmenuIndexDetectReader = SubmenuIndexCommonMax, - SubmenuIndexWrite, - SubmenuIndexUpdate, SubmenuIndexDictAttack, SubmenuIndexCrackNonces, + SubmenuIndexUpdate, }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -115,6 +114,9 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { Submenu* submenu = instance->submenu; const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + // Doesn't make sense to show "Write to Initial Card" right after reading + submenu_remove_item(submenu, SubmenuIndexCommonWrite); + if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, @@ -160,6 +162,8 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { Submenu* submenu = instance->submenu; const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + submenu_change_item_label(submenu, SubmenuIndexCommonWrite, "Write to Initial Card"); + if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, @@ -175,12 +179,6 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); } - submenu_add_item( - submenu, - "Write to Initial Card", - SubmenuIndexWrite, - nfc_protocol_support_common_submenu_callback, - instance); submenu_add_item( submenu, @@ -215,9 +213,6 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag 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; } else if(event.event == SubmenuIndexCrackNonces) { scene_manager_set_scene_state( instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces); @@ -236,18 +231,15 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana if(event.event == SubmenuIndexDetectReader) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); - consumed = true; - } else if(event.event == SubmenuIndexUpdate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); - consumed = true; } else if(event.event == SubmenuIndexDictAttack) { if(!scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneMfClassicDictAttack)) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); } consumed = true; + } else if(event.event == SubmenuIndexUpdate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + consumed = true; } } @@ -267,8 +259,71 @@ static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManag return consumed; } +static NfcCommand + nfc_scene_write_poller_callback_mf_classic(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcApp* instance = context; + MfClassicPollerEvent* mfc_event = event.event_data; + NfcCommand command = NfcCommandContinue; + const MfClassicData* write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); + + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + furi_string_set(instance->text_box_store, "Use the source\ncard only"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* tag_data = nfc_poller_get_data(instance->poller); + if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) { + mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite; + } else { + furi_string_set( + instance->text_box_store, "Use source card!\nTo write blanks\nuse NFC Magic app"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) { + uint8_t sector = mfc_event->data->sec_tr_data.sector_num; + uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector); + if(mf_classic_is_block_read(write_data, sec_tr)) { + mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr]; + mfc_event->data->sec_tr_data.sector_trailer_provided = true; + } else { + mfc_event->data->sec_tr_data.sector_trailer_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) { + uint8_t block_num = mfc_event->data->write_block_data.block_num; + if(mf_classic_is_block_read(write_data, block_num)) { + mfc_event->data->write_block_data.write_block = write_data->block[block_num]; + mfc_event->data->write_block_data.write_block_provided = true; + } else { + mfc_event->data->write_block_data.write_block_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(mfc_event->type == MfClassicPollerEventTypeFail) { + furi_string_set(instance->text_box_store, "Not all sectors\nwere written\ncorrectly"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_write_on_enter_mf_classic(NfcApp* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_classic, instance); + furi_string_set(instance->text_box_store, "Use the source\ncard only"); +} + const NfcProtocolSupportBase nfc_protocol_support_mf_classic = { - .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo | + NfcProtocolFeatureWrite, .scene_info = { @@ -310,4 +365,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_classic = { .on_enter = nfc_scene_emulate_on_enter_mf_classic, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_scene_write_on_enter_mf_classic, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_classic, NfcProtocolMfClassic); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index deba1bca2..785ccc140 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -124,4 +124,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_desfire = { .on_enter = nfc_scene_emulate_on_enter_mf_desfire, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_desfire, NfcProtocolMfDesfire); diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c index c7b36e21e..2960886f7 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c @@ -25,6 +25,20 @@ static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) { furi_string_free(temp_str); } + +static void nfc_scene_more_info_on_enter_mf_plus(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus); + + furi_string_reset(instance->text_box_store); + nfc_render_mf_plus_data(data, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolMfPlus); @@ -78,7 +92,7 @@ static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) { } const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { - .features = NfcProtocolFeatureEmulateUid, + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, .scene_info = { @@ -87,7 +101,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { }, .scene_more_info = { - .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_enter = nfc_scene_more_info_on_enter_mf_plus, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = @@ -120,4 +134,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { .on_enter = nfc_scene_emulate_on_enter_mf_plus, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_plus, NfcProtocolMfPlus); diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c index 8640fa16d..2311004ad 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c @@ -15,7 +15,21 @@ void nfc_render_mf_plus_info( } void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str) { - nfc_render_mf_plus_version(&data->version, str); + MfPlusVersion empty_version = {0}; + if(memcmp(&data->version, &empty_version, sizeof(MfPlusVersion)) == 0) { + const char* device_name = mf_plus_get_device_name(data, NfcDeviceNameTypeFull); + if(data->type == MfPlusTypeUnknown || data->size == MfPlusSizeUnknown || + data->security_level == MfPlusSecurityLevelUnknown) { + furi_string_cat_printf(str, "This %s", device_name); + furi_string_replace(str, " Unknown", ""); + } else { + furi_string_cat(str, device_name); + } + furi_string_replace(str, "Mifare", "MIFARE"); + furi_string_cat(str, " does not support the GetVersion command, extra info unavailable\n"); + } else { + nfc_render_mf_plus_version(&data->version, str); + } } void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str) { 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 3adf2a1f5..8eb42b89b 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 @@ -14,7 +14,6 @@ enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, - SubmenuIndexWrite, }; enum { @@ -182,24 +181,22 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc const MfUltralightData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + bool is_locked = !mf_ultralight_is_all_data_read(data); - if(!mf_ultralight_is_all_data_read(data)) { + if(is_locked || + (data->type != MfUltralightTypeNTAG213 && data->type != MfUltralightTypeNTAG215 && + data->type != MfUltralightTypeNTAG216 && data->type != MfUltralightTypeUL11 && + data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin)) { + submenu_remove_item(submenu, SubmenuIndexCommonWrite); + } + + if(is_locked) { submenu_add_item( submenu, "Unlock", SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); - } else if( - data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || - data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 || - data->type == MfUltralightTypeUL21 || data->type == MfUltralightTypeOrigin) { - submenu_add_item( - submenu, - "Write", - SubmenuIndexWrite, - nfc_protocol_support_common_submenu_callback, - instance); } } @@ -252,19 +249,57 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcSceneMfUltralightUnlockMenu; scene_manager_next_scene(instance->scene_manager, next_scene); consumed = true; - } else if(event.event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - consumed = true; - } else if(event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - consumed = true; } } return consumed; } +static NfcCommand + nfc_scene_write_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfUltralight); + + NfcApp* instance = context; + MfUltralightPollerEvent* mf_ultralight_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) { + mf_ultralight_event->data->poller_mode = MfUltralightPollerModeWrite; + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { + mf_ultralight_event->data->auth_context.skip_auth = true; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestWriteData) { + mf_ultralight_event->data->write_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardMismatch) { + furi_string_set(instance->text_box_store, "Card of the same\ntype should be\n presented"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardLocked) { + furi_string_set( + instance->text_box_store, "Card protected by\npassword, AUTH0\nor lock bits"); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteFail) { + command = NfcCommandStop; + } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteSuccess) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_write_on_enter_mf_ultralight(NfcApp* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_ultralight, instance); + furi_string_set(instance->text_box_store, "Apply the initial\ncard only"); +} + const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { - .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo | + NfcProtocolFeatureWrite, .scene_info = { @@ -306,4 +341,11 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { .on_enter = nfc_scene_emulate_on_enter_mf_ultralight, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_scene_write_on_enter_mf_ultralight, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(mf_ultralight, NfcProtocolMfUltralight); 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 fe3193385..008f9c317 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -9,9 +9,13 @@ #include "nfc/nfc_app_i.h" -#include "nfc_protocol_support_defs.h" +#include "nfc_protocol_support_base.h" #include "nfc_protocol_support_gui_common.h" +#include + +#define TAG "NfcProtocolSupport" + /** * @brief Common scene entry handler. * @@ -46,6 +50,147 @@ typedef struct { static const NfcProtocolSupportCommonSceneBase nfc_protocol_support_scenes[]; +const NfcProtocolSupportBase nfc_protocol_support_empty = { + .features = NfcProtocolFeatureNone, + + .scene_info = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; + +struct NfcProtocolSupport { + NfcProtocol protocol; + PluginManager* plugin_manager; + const NfcProtocolSupportBase* base; +}; + +const char* nfc_protocol_support_plugin_names[NfcProtocolNum] = { + [NfcProtocolIso14443_3a] = "iso14443_3a", + [NfcProtocolIso14443_3b] = "iso14443_3b", + [NfcProtocolIso14443_4a] = "iso14443_4a", + [NfcProtocolIso14443_4b] = "iso14443_4b", + [NfcProtocolIso15693_3] = "iso15693_3", + [NfcProtocolFelica] = "felica", + [NfcProtocolMfUltralight] = "mf_ultralight", + [NfcProtocolMfClassic] = "mf_classic", + [NfcProtocolMfPlus] = "mf_plus", + [NfcProtocolMfDesfire] = "mf_desfire", + [NfcProtocolSlix] = "slix", + [NfcProtocolSt25tb] = "st25tb", + [NfcProtocolNtag4xx] = "ntag4xx", + [NfcProtocolType4Tag] = "type_4_tag", + [NfcProtocolEmv] = "emv", + /* Add new protocol support plugin names here */ +}; + +void nfc_protocol_support_alloc(NfcProtocol protocol, void* context) { + furi_assert(context); + + NfcApp* instance = context; + + NfcProtocolSupport* protocol_support = malloc(sizeof(NfcProtocolSupport)); + protocol_support->protocol = protocol; + + const char* protocol_name = nfc_protocol_support_plugin_names[protocol]; + FuriString* plugin_path = + furi_string_alloc_printf(APP_ASSETS_PATH("plugins/nfc_%s.fal"), protocol_name); + FURI_LOG_D(TAG, "Loading %s", furi_string_get_cstr(plugin_path)); + + protocol_support->plugin_manager = plugin_manager_alloc( + NFC_PROTOCOL_SUPPORT_PLUGIN_APP_ID, + NFC_PROTOCOL_SUPPORT_PLUGIN_API_VERSION, + composite_api_resolver_get(instance->api_resolver)); + do { + if(plugin_manager_load_single( + protocol_support->plugin_manager, furi_string_get_cstr(plugin_path)) != + PluginManagerErrorNone) { + break; + } + const NfcProtocolSupportPlugin* plugin = + plugin_manager_get_ep(protocol_support->plugin_manager, 0); + + if(plugin->protocol != protocol) { + break; + } + + protocol_support->base = plugin->base; + } while(false); + if(!protocol_support->base) { + protocol_support->base = &nfc_protocol_support_empty; + plugin_manager_free(protocol_support->plugin_manager); + protocol_support->plugin_manager = NULL; + } + + furi_string_free(plugin_path); + + instance->protocol_support = protocol_support; +} + +void nfc_protocol_support_free(void* context) { + furi_assert(context); + + NfcApp* instance = context; + + if(instance->protocol_support->plugin_manager) { + plugin_manager_free(instance->protocol_support->plugin_manager); + } + free(instance->protocol_support); + instance->protocol_support = NULL; +} + +static const NfcProtocolSupportBase* + nfc_protocol_support_get(NfcProtocol protocol, void* context) { + furi_assert(context); + + NfcApp* instance = context; + + if(instance->protocol_support && instance->protocol_support->protocol != protocol) { + nfc_protocol_support_free(instance); + } + if(!instance->protocol_support) { + nfc_protocol_support_alloc(protocol, instance); + } + + return instance->protocol_support->base; +} + // Interface functions void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context) { furi_assert(scene < NfcProtocolSupportSceneCount); @@ -74,17 +219,23 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) nfc_protocol_support_scenes[scene].on_exit(instance); } -bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { - return nfc_protocol_support[protocol]->features & feature; +bool nfc_protocol_support_has_feature( + NfcProtocol protocol, + void* context, + NfcProtocolFeature feature) { + furi_assert(context); + + NfcApp* instance = context; + return nfc_protocol_support_get(protocol, instance)->features & feature; } // Common scene handlers // SceneInfo static void nfc_protocol_support_scene_info_on_enter(NfcApp* instance) { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_info.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_info.on_enter(instance); - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureMoreInfo)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureMoreInfo)) { widget_add_button_element( instance->widget, GuiButtonTypeRight, @@ -124,7 +275,7 @@ static void nfc_protocol_support_scene_info_on_exit(NfcApp* instance) { // SceneMoreInfo static void nfc_protocol_support_scene_more_info_on_enter(NfcApp* instance) { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_more_info.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_more_info.on_enter(instance); } static bool @@ -132,7 +283,8 @@ static bool bool consumed = false; const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event); + consumed = + nfc_protocol_support_get(protocol, instance)->scene_more_info.on_event(instance, event); return consumed; } @@ -157,7 +309,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { //nfc_supported_cards_load_cache(instance->nfc_supported_cards); // Start poller with the appropriate callback - nfc_protocol_support[protocol]->scene_read.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_read.on_enter(instance); nfc_blink_read_start(instance); } @@ -186,7 +338,8 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else { const NfcProtocol protocol = nfc_detected_protocols_get_selected(instance->detected_protocols); - consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_read.on_event(instance, event); } } else if(event.event == NfcCustomEventPollerFailure) { nfc_poller_stop(instance->poller); @@ -199,7 +352,8 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else if(event.event == NfcCustomEventCardDetected) { const NfcProtocol protocol = nfc_detected_protocols_get_selected(instance->detected_protocols); - consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); + consumed = + nfc_protocol_support_get(protocol, instance)->scene_read.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { nfc_poller_stop(instance->poller); @@ -241,7 +395,7 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { instance); } - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, "Emulate UID", @@ -249,7 +403,7 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); - } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + } else if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateFull)) { submenu_add_item( submenu, "Emulate", @@ -258,7 +412,16 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { instance); } - nfc_protocol_support[protocol]->scene_read_menu.on_enter(instance); + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureWrite)) { + submenu_add_item( + submenu, + "Write", + SubmenuIndexCommonWrite, + nfc_protocol_support_common_submenu_callback, + instance); + } + + nfc_protocol_support_get(protocol, instance)->scene_read_menu.on_enter(instance); submenu_add_item( submenu, @@ -291,9 +454,17 @@ static bool dolphin_deed(DolphinDeedNfcEmulate); scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); consumed = true; + } else if(event.event == SubmenuIndexCommonWrite) { + dolphin_deed(DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneWrite); + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_read_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -312,13 +483,17 @@ static void nfc_protocol_support_scene_read_saved_menu_on_exit(NfcApp* instance) static void nfc_protocol_support_scene_read_success_on_enter(NfcApp* instance) { Widget* widget = instance->widget; + popup_set_header(instance->popup, "Parsing", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 23, &A_Loading_24); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + FuriString* temp_str = furi_string_alloc(); if(nfc_supported_cards_parse(instance->nfc_supported_cards, instance->nfc_device, temp_str)) { widget_add_text_scroll_element( instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_read_success.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_read_success.on_enter(instance); } furi_string_free(temp_str); @@ -366,7 +541,7 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { Submenu* submenu = instance->submenu; // Header submenu items - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, "Emulate UID", @@ -374,7 +549,7 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); - } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) { + } else if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateFull)) { submenu_add_item( submenu, "Emulate", @@ -383,7 +558,16 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { instance); } - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEditUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureWrite)) { + submenu_add_item( + submenu, + "Write", + SubmenuIndexCommonWrite, + nfc_protocol_support_common_submenu_callback, + instance); + } + + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEditUid)) { submenu_add_item( submenu, "Edit UID", @@ -393,7 +577,7 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { } // Protocol-dependent menu items - nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_saved_menu.on_enter(instance); // Trailer submenu items if(nfc_has_shadow_file(instance)) { @@ -456,12 +640,19 @@ static bool dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate); scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); consumed = true; + } else if(event.event == SubmenuIndexCommonWrite) { + const bool is_added = + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType); + dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate); + scene_manager_next_scene(instance->scene_manager, NfcSceneWrite); + consumed = true; } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_saved_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -480,8 +671,18 @@ static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { bool name_is_empty = furi_string_empty(instance->file_name); if(name_is_empty) { furi_string_set(instance->file_path, NFC_APP_FOLDER); - name_generator_make_auto_basic( - instance->text_store, NFC_TEXT_STORE_SIZE, NFC_APP_FILENAME_PREFIX); + FuriString* prefix = furi_string_alloc(); + furi_string_set(prefix, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + furi_string_replace(prefix, "Mifare", "MF"); + furi_string_replace(prefix, " Classic", "C"); // MFC + furi_string_replace(prefix, "Desfire", "Des"); // MF Des + furi_string_replace(prefix, "Ultralight", "UL"); // MF UL + furi_string_replace(prefix, " Plus", "+"); // NTAG I2C+ + furi_string_replace(prefix, " (Unknown)", ""); + furi_string_replace_all(prefix, " ", "_"); + name_generator_make_auto( + instance->text_store, NFC_TEXT_STORE_SIZE, furi_string_get_cstr(prefix)); + furi_string_free(prefix); furi_string_set(folder_path, NFC_APP_FOLDER); } else { nfc_text_store_set(instance, "%s", furi_string_get_cstr(instance->file_name)); @@ -527,8 +728,8 @@ static bool DolphinDeedNfcSave); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = - nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event); + consumed = nfc_protocol_support_get(protocol, instance) + ->scene_save_name.on_event(instance, event); } else { consumed = scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneStart); @@ -570,9 +771,9 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64); + widget_add_icon_element(widget, 0, 0, &I_NFC_dolphin_emulation_51x64); - if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { + if(nfc_protocol_support_has_feature(protocol, instance, NfcProtocolFeatureEmulateUid)) { widget_add_string_element( widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID"); @@ -613,7 +814,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { furi_string_reset(instance->text_box_store); // instance->listener is allocated in the respective on_enter() handler - nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_emulate.on_enter(instance); scene_manager_set_scene_state( instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); @@ -692,6 +893,191 @@ static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) { nfc_blink_stop(instance); } +// SceneWrite +/** + * @brief Current view displayed on the write scene. + * + * The emulation scene has five states, some protocols may not use all states. + * Protocol handles poller events, when scene state needs to change it should + * fill text_box_store with a short caption (when applicable) before sending + * the relevant view dispatcher event. + */ +enum { + NfcSceneWriteStateSearching, /**< Ask user to touch the card. Event: on_enter, CardLost. Needs caption. */ + NfcSceneWriteStateWriting, /**< Ask not to move while writing. Event: CardDetected. No caption. */ + NfcSceneWriteStateSuccess, /**< Card written successfully. Event: PollerSuccess. No caption. */ + NfcSceneWriteStateFailure, /**< An error is displayed. Event: PollerFailure. Needs caption. */ + NfcSceneWriteStateWrongCard, /**< Wrong card was presented. Event: WrongCard. Needs caption. */ +}; + +static void nfc_protocol_support_scene_write_popup_callback(void* context) { + NfcApp* instance = context; + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_protocol_support_scene_write_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort && result == GuiButtonTypeLeft) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventRetry); + } +} + +static void nfc_protocol_support_scene_write_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + Widget* widget = instance->widget; + popup_reset(popup); + widget_reset(widget); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneWrite); + NfcView view = NfcViewPopup; + + if(state == NfcSceneWriteStateSearching) { + popup_set_header(popup, "Writing", 95, 20, AlignCenter, AlignCenter); + popup_set_text( + popup, + furi_string_get_cstr(instance->text_box_store), + 95, + 38, + AlignCenter, + AlignCenter); + popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50); + } else if(state == NfcSceneWriteStateWriting) { + popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); + popup_set_icon(popup, 12, 23, &A_Loading_24); + } else if(state == NfcSceneWriteStateSuccess) { + popup_set_header(popup, "Successfully\nwritten!", 126, 2, AlignRight, AlignTop); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_protocol_support_scene_write_popup_callback); + popup_enable_timeout(popup); + } else if(state == NfcSceneWriteStateFailure) { + view = NfcViewWidget; + widget_add_string_element( + widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget_add_string_multiline_element( + widget, + 7, + 17, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(instance->text_box_store)); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_protocol_support_scene_write_widget_callback, + instance); + } else if(state == NfcSceneWriteStateWrongCard) { + view = NfcViewWidget; + widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wrong card!"); + widget_add_string_multiline_element( + widget, + 4, + 17, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(instance->text_box_store)); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_protocol_support_scene_write_widget_callback, + instance); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, view); +} + +static void nfc_protocol_support_scene_write_on_enter(NfcApp* instance) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneWrite, NfcSceneWriteStateSearching); + furi_string_reset(instance->text_box_store); + + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + + // instance->poller is allocated in the respective on_enter() handler + nfc_protocol_support_get(protocol, instance)->scene_write.on_enter(instance); + + nfc_protocol_support_scene_write_setup_view(instance); + nfc_blink_emulate_start(instance); +} + +static bool nfc_protocol_support_scene_write_on_event(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + uint32_t new_state = -1; + bool stop_poller = false; + + if(event.event == NfcCustomEventCardDetected) { + new_state = NfcSceneWriteStateWriting; + consumed = true; + } else if(event.event == NfcCustomEventCardLost) { + new_state = NfcSceneWriteStateSearching; + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + dolphin_deed(DolphinDeedNfcSave); + notification_message(instance->notifications, &sequence_success); + new_state = NfcSceneWriteStateSuccess; + stop_poller = true; + consumed = true; + } else if(event.event == NfcCustomEventPollerFailure) { + notification_message(instance->notifications, &sequence_error); + new_state = NfcSceneWriteStateFailure; + stop_poller = true; + consumed = true; + } else if(event.event == NfcCustomEventWrongCard) { + notification_message(instance->notifications, &sequence_error); + new_state = NfcSceneWriteStateWrongCard; + stop_poller = true; + consumed = true; + } else if(event.event == NfcCustomEventViewExit) { + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.event == NfcCustomEventRetry) { + nfc_protocol_support_scenes[NfcProtocolSupportSceneWrite].on_exit(instance); + nfc_protocol_support_scenes[NfcProtocolSupportSceneWrite].on_enter(instance); + consumed = true; + } + + if(stop_poller) { + if(instance->poller) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + instance->poller = NULL; + } + nfc_blink_stop(instance); + } + if(new_state != (uint32_t)-1) { + scene_manager_set_scene_state(instance->scene_manager, NfcSceneWrite, new_state); + nfc_protocol_support_scene_write_setup_view(instance); + } + } + + return consumed; +} + +static void nfc_protocol_support_scene_write_on_exit(NfcApp* instance) { + if(instance->poller) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + } + + // Clear view + popup_reset(instance->popup); + widget_reset(instance->widget); + furi_string_reset(instance->text_box_store); + + nfc_blink_stop(instance); +} + static void nfc_protocol_support_scene_rpc_on_enter(NfcApp* instance) { UNUSED(instance); } @@ -709,7 +1095,7 @@ static void nfc_protocol_support_scene_rpc_setup_ui_and_emulate(NfcApp* instance nfc_blink_emulate_start(instance); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - nfc_protocol_support[protocol]->scene_emulate.on_enter(instance); + nfc_protocol_support_get(protocol, instance)->scene_emulate.on_enter(instance); instance->rpc_state = NfcRpcStateEmulating; } @@ -807,6 +1193,12 @@ static const NfcProtocolSupportCommonSceneBase .on_event = nfc_protocol_support_scene_emulate_on_event, .on_exit = nfc_protocol_support_scene_emulate_on_exit, }, + [NfcProtocolSupportSceneWrite] = + { + .on_enter = nfc_protocol_support_scene_write_on_enter, + .on_event = nfc_protocol_support_scene_write_on_event, + .on_exit = nfc_protocol_support_scene_write_on_exit, + }, [NfcProtocolSupportSceneRpc] = { .on_enter = nfc_protocol_support_scene_rpc_on_enter, diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h index d3efc3a41..157d00b83 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h @@ -40,7 +40,7 @@ * * | Filename | Explanation | * |:-----------------------|:------------| - * | protocol_name.h | Interface structure declaration used in `nfc_protocol_support_defs.c`. | + * | protocol_name.h | Interface structure declaration. | * | protocol_name.c | Protocol-specific scene implemenatations and definitions. | * | protocol_name_render.h | Protocol-specific rendering (formatting) functions. Used for converting protocol data into textual descriptions. | * | protocol_name_render.c | Implementations for functions declared in `protocol_name_render.h`.| @@ -65,8 +65,13 @@ * * After completing the protocol support, it must be registered within the application in order for it to be usable. * - * In nfc_protocol_support_defs.c, include the `protocol_name.h` file and add a new entry in the `nfc_protocol_support[]` - * array under the appropriate index. + * In `protocol_name.c`, add `NFC_PROTOCOL_SUPPORT_PLUGIN(protocol_name, NfcProtocolName)` at the bottom, + * below the `NfcProtocolSupportBase` structure definition. + * + * In `application.fam`, add a new entry for the plugin, following the other examples. + * + * In nfc_protocol_support.c, add a new entry in the `nfc_protocol_support_plugin_names[]` + * array under the appropriate index with the name of the plugin (without the `nfc_` prefix). * * ## Done! * @@ -80,6 +85,10 @@ #include "nfc_protocol_support_common.h" +typedef struct NfcProtocolSupport NfcProtocolSupport; + +void nfc_protocol_support_free(void* context); + /** * @brief Abstract interface for on_enter() scene handler. * @@ -113,4 +122,7 @@ bool nfc_protocol_support_on_event( */ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context); -bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature); +bool nfc_protocol_support_has_feature( + NfcProtocol protocol, + void* context, + NfcProtocolFeature feature); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h index eec736ca2..04648120e 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h @@ -9,6 +9,8 @@ #include "../../nfc_app.h" #include "../../nfc_app_i.h" +#include + /** * @brief Scene entry handler. * @@ -114,4 +116,47 @@ typedef struct { * It is responsible for creating a listener and for handling its events. */ NfcProtocolSupportSceneBase scene_emulate; + + /** + * @brief Handlers for protocol-specific write scene. + * + * This scene is activated when a write operation is in progress. + * It is responsible for creating a poller, handling its events and + * displaying short captions for what is happening. + */ + NfcProtocolSupportSceneBase scene_write; } NfcProtocolSupportBase; + +/** + * @brief Unique string identifier for protocol support plugins. + */ +#define NFC_PROTOCOL_SUPPORT_PLUGIN_APP_ID "NfcProtocolSupportPlugin" + +/** + * @brief Currently supported plugin API version. + */ +#define NFC_PROTOCOL_SUPPORT_PLUGIN_API_VERSION 1 + +/** + * @brief Protocol support plugin interface. + */ +typedef struct { + NfcProtocol protocol; /**< Identifier of the protocol this plugin implements. */ + const NfcProtocolSupportBase* base; /**< Pointer to the protocol support interface. */ +} NfcProtocolSupportPlugin; + +#define NFC_PROTOCOL_SUPPORT_PLUGIN(name, protocol) \ + static const NfcProtocolSupportPlugin nfc_protocol_support_##name##_desc = { \ + protocol, \ + &nfc_protocol_support_##name, \ + }; \ + \ + static const FlipperAppPluginDescriptor plugin_descriptor_##name = { \ + .appid = NFC_PROTOCOL_SUPPORT_PLUGIN_APP_ID, \ + .ep_api_version = NFC_PROTOCOL_SUPPORT_PLUGIN_API_VERSION, \ + .entry_point = &nfc_protocol_support_##name##_desc, \ + }; \ + \ + const FlipperAppPluginDescriptor* nfc_##name##_ep(void) { \ + return &plugin_descriptor_##name; \ + } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h index 6e3214106..574c23a4c 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_common.h @@ -13,6 +13,7 @@ typedef enum { NfcProtocolFeatureEmulateFull = 1UL << 1, /**< Complete emulation is supported. */ NfcProtocolFeatureEditUid = 1UL << 2, /**< UID editing is supported. */ NfcProtocolFeatureMoreInfo = 1UL << 3, /**< More information is provided. */ + NfcProtocolFeatureWrite = 1UL << 4, /**< Writing to real card is supported. */ } NfcProtocolFeature; /** @@ -30,6 +31,7 @@ typedef enum { NfcProtocolSupportSceneSavedMenu, /**< Menu for the card that was loaded from file. */ NfcProtocolSupportSceneSaveName, /**< Shown when saving or renaming a file. */ NfcProtocolSupportSceneEmulate, /**< Shown when emulating a card. */ + NfcProtocolSupportSceneWrite, /**< Shown when writing to a card. */ NfcProtocolSupportSceneRpc, /**< Shown in remote-controlled (RPC) mode. */ NfcProtocolSupportSceneCount, /**< Special value equal to total scene count. Internal use. */ diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c deleted file mode 100644 index a80cd6cc0..000000000 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file nfc_protocol_support_defs.c - * @brief Application-level protocol support definitions. - * - * This file is to be modified whenever support for - * a new protocol is to be added. - */ -#include "nfc_protocol_support_base.h" - -#include - -#include "iso14443_3a/iso14443_3a.h" -#include "iso14443_3b/iso14443_3b.h" -#include "iso14443_4a/iso14443_4a.h" -#include "iso14443_4b/iso14443_4b.h" -#include "iso15693_3/iso15693_3.h" -#include "felica/felica.h" -#include "mf_ultralight/mf_ultralight.h" -#include "mf_classic/mf_classic.h" -#include "mf_plus/mf_plus.h" -#include "mf_desfire/mf_desfire.h" -#include "emv/emv.h" -#include "slix/slix.h" -#include "st25tb/st25tb.h" - -/** - * @brief Array of pointers to concrete protocol support implementations. - * - * When adding support for a new protocol, add it to the end of this array - * under its respective index. - * - * @see nfc_protocol.h - */ -const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { - [NfcProtocolIso14443_3a] = &nfc_protocol_support_iso14443_3a, - [NfcProtocolIso14443_3b] = &nfc_protocol_support_iso14443_3b, - [NfcProtocolIso14443_4a] = &nfc_protocol_support_iso14443_4a, - [NfcProtocolIso14443_4b] = &nfc_protocol_support_iso14443_4b, - [NfcProtocolIso15693_3] = &nfc_protocol_support_iso15693_3, - [NfcProtocolFelica] = &nfc_protocol_support_felica, - [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, - [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, - [NfcProtocolMfPlus] = &nfc_protocol_support_mf_plus, - [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, - [NfcProtocolSlix] = &nfc_protocol_support_slix, - [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, - [NfcProtocolEmv] = &nfc_protocol_support_emv, - /* Add new protocol support implementations here */ -}; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h deleted file mode 100644 index 7a9d5b637..000000000 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @file nfc_protocol_support_defs.h - * @brief Application-level protocol support declarations. - */ -#pragma once - -#include "nfc_protocol_support_base.h" - -/** - * @brief Declaraion of array of pointers to protocol support implementations. - */ -extern const NfcProtocolSupportBase* nfc_protocol_support[]; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c index 8c38f8475..ba309244a 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c @@ -26,9 +26,9 @@ void nfc_protocol_support_common_byte_input_done_callback(void* context) { } void nfc_protocol_support_common_text_input_done_callback(void* context) { - NfcApp* nfc = context; + NfcApp* instance = context; - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventTextInputDone); } void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) { diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h index 3230f1a7e..4e4edb080 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h @@ -15,6 +15,7 @@ enum { SubmenuIndexCommonSave, /**< Save menu option. */ SubmenuIndexCommonEmulate, /**< Emulate menu option. */ + SubmenuIndexCommonWrite, /**< Write menu option. */ SubmenuIndexCommonEdit, /**< Edit menu option. */ SubmenuIndexCommonInfo, /**< Info menu option. */ SubmenuIndexCommonRename, /**< Rename menu option. */ @@ -23,6 +24,10 @@ enum { SubmenuIndexCommonMax, /**< Special value, internal use. */ }; +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief Common submenu callback. * @@ -84,3 +89,7 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance); * @returns always true. */ bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h index 34f52496b..9cb35971e 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h @@ -5,5 +5,13 @@ typedef enum { NfcSceneReadMenuStateCardFound, } NfcSceneUnlockReadState; +#ifdef __cplusplus +extern "C" { +#endif + void nfc_unlock_helper_setup_from_state(NfcApp* instance); void nfc_unlock_helper_card_detected_handler(NfcApp* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c new file mode 100644 index 000000000..1ea8951f9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.c @@ -0,0 +1,140 @@ +#include "ntag4xx.h" +#include "ntag4xx_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_ntag4xx(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx); + + FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_ntag4xx_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_ntag4xx(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx); + + furi_string_reset(instance->text_box_store); + nfc_render_ntag4xx_data(data, instance->text_box_store); + + text_box_set_font(instance->text_box, TextBoxFontHex); + text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); +} + +static NfcCommand nfc_scene_read_poller_callback_ntag4xx(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolNtag4xx); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + const Ntag4xxPollerEvent* ntag4xx_event = event.event_data; + + if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolNtag4xx, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadFailed) { + command = NfcCommandReset; + } + + return command; +} + +static void nfc_scene_read_on_enter_ntag4xx(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_ntag4xx, instance); +} + +static void nfc_scene_read_success_on_enter_ntag4xx(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Ntag4xxData* data = nfc_device_get_data(device, NfcProtocolNtag4xx); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_ntag4xx_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_ntag4xx(NfcApp* instance) { + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_ntag4xx = { + .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_ntag4xx, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; + +NFC_PROTOCOL_SUPPORT_PLUGIN(ntag4xx, NfcProtocolNtag4xx); diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h new file mode 100644 index 000000000..09a8388fa --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_ntag4xx; diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c new file mode 100644 index 000000000..0cb587726 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.c @@ -0,0 +1,110 @@ +#include "ntag4xx_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_ntag4xx_info( + const Ntag4xxData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(ntag4xx_get_base_data(data), str); + + const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version); + if(type >= Ntag4xxTypeUnknown) { + furi_string_cat(str, "Memory Size: unknown"); + } else { + size_t size_cc = 32; + size_t size_ndef = 0; + size_t size_proprietary = 0; + bool has_tagtamper = false; + switch(type) { + case Ntag4xxType413DNA: + size_ndef = 128; + size_proprietary = 0; + break; + case Ntag4xxType424DNATT: + has_tagtamper = true; + /* fall-through */ + case Ntag4xxType424DNA: + size_ndef = 256; + size_proprietary = 128; + break; + case Ntag4xxType426QDNATT: + has_tagtamper = true; + /* fall-through */ + case Ntag4xxType426QDNA: + size_ndef = 768; + size_proprietary = 128; + break; + default: + break; + } + furi_string_cat_printf( + str, "\nMemory Size: %zu bytes\n", size_cc + size_ndef + size_proprietary); + furi_string_cat_printf(str, "Usable NDEF Size: %zu bytes\n", size_ndef - sizeof(uint16_t)); + furi_string_cat_printf(str, "Capability Cont.: %zu bytes\n", size_cc); + if(size_proprietary) { + furi_string_cat_printf(str, "Proprietary File: %zu bytes\n", size_proprietary); + } + furi_string_cat_printf(str, "TagTamper: %ssupported", has_tagtamper ? "" : "not "); + } + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(ntag4xx_get_base_data(data), str); +} + +void nfc_render_ntag4xx_data(const Ntag4xxData* data, FuriString* str) { + nfc_render_ntag4xx_version(&data->version, str); +} + +void nfc_render_ntag4xx_version(const Ntag4xxVersion* data, FuriString* str) { + furi_string_cat_printf( + str, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + data->uid[0], + data->uid[1], + data->uid[2], + data->uid[3], + data->uid[4], + data->uid[5], + data->uid[6]); + furi_string_cat_printf( + str, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->hw_vendor, + data->hw_type, + data->hw_subtype, + data->hw_major, + data->hw_minor, + data->hw_storage, + data->hw_proto); + furi_string_cat_printf( + str, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->sw_vendor, + data->sw_type, + data->sw_subtype, + data->sw_major, + data->sw_minor, + data->sw_storage, + data->sw_proto); + furi_string_cat_printf( + str, + "batch %02x:%02x:%02x:%02x:%01x\n" + "week %d year %d\n" + "fab key %02x id %02x\n", + data->batch[0], + data->batch[1], + data->batch[2], + data->batch[3], + data->batch_extra, + data->prod_week, + data->prod_year, + (data->fab_key_4b << 1) | (data->fab_key_1b), + data->optional.fab_key_id); +} diff --git a/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h new file mode 100644 index 000000000..ca81cf4c4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/ntag4xx/ntag4xx_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_ntag4xx_info( + const Ntag4xxData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_ntag4xx_data(const Ntag4xxData* data, FuriString* str); + +void nfc_render_ntag4xx_version(const Ntag4xxVersion* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 35592eaa1..32094140d 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -78,20 +78,21 @@ static NfcCommand nfc_scene_emulate_listener_callback_slix(NfcGenericEvent event furi_assert(event.protocol == NfcProtocolSlix); furi_assert(event.event_data); - NfcApp* nfc = context; + NfcApp* instance = context; SlixListenerEvent* slix_event = event.event_data; if(slix_event->type == SlixListenerEventTypeCustomCommand) { - if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) { - furi_string_cat_printf(nfc->text_box_store, "R:"); + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); for(size_t i = 0; i < bit_buffer_get_size_bytes(slix_event->data->buffer); i++) { furi_string_cat_printf( - nfc->text_box_store, + instance->text_box_store, " %02X", bit_buffer_get_byte(slix_event->data->buffer, i)); } - furi_string_push_back(nfc->text_box_store, '\n'); - view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate); + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); } } @@ -105,15 +106,6 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) { nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance); } -static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_slix = { .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, @@ -145,7 +137,7 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_slix, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -157,4 +149,11 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = { .on_enter = nfc_scene_emulate_on_enter_slix, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(slix, NfcProtocolSlix); diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index 0305d614c..af123aee4 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -61,15 +61,6 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); - return true; - } - - return false; -} - const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .features = NfcProtocolFeatureNone, @@ -96,7 +87,7 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .scene_saved_menu = { .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_scene_saved_menu_on_event_st25tb, + .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_save_name = { @@ -108,4 +99,11 @@ const NfcProtocolSupportBase nfc_protocol_support_st25tb = { .on_enter = nfc_protocol_support_common_on_enter_empty, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_write = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, }; + +NFC_PROTOCOL_SUPPORT_PLUGIN(st25tb, NfcProtocolSt25tb); diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..3b1fc91f6 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.c @@ -0,0 +1,260 @@ +#include "type_4_tag.h" +#include "type_4_tag_render.h" + +#include +#include +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" + +enum { + NfcSceneMoreInfoStateASCII, + NfcSceneMoreInfoStateRawData, +}; + +static void nfc_scene_info_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_type_4_tag_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_more_info_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + furi_string_reset(instance->text_box_store); + uint32_t scene_state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo); + + if(scene_state == NfcSceneMoreInfoStateASCII) { + if(simple_array_get_count(data->ndef_data) == 0) { + furi_string_cat_str(instance->text_box_store, "No NDEF data to show"); + } else { + pretty_format_bytes_hex_canonical( + instance->text_box_store, + TYPE_4_TAG_RENDER_BYTES_PER_LINE, + PRETTY_FORMAT_FONT_MONOSPACE, + simple_array_cget_data(data->ndef_data), + simple_array_get_count(data->ndef_data)); + } + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Raw Data", + nfc_protocol_support_common_widget_callback, + instance); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Info", + nfc_protocol_support_common_widget_callback, + instance); + } else if(scene_state == NfcSceneMoreInfoStateRawData) { + nfc_render_type_4_tag_dump(data, instance->text_box_store); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "ASCII", + nfc_protocol_support_common_widget_callback, + instance); + } +} + +static bool nfc_scene_more_info_on_event_type_4_tag(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) || + (event.type == SceneManagerEventTypeBack)) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII); + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData); + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + return consumed; +} + +static NfcCommand nfc_scene_read_poller_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + const Type4TagPollerEvent* type_4_tag_event = event.event_data; + + if(type_4_tag_event->type == Type4TagPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolType4Tag, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } else if(type_4_tag_event->type == Type4TagPollerEventTypeReadFailed) { + command = NfcCommandReset; + } + + return command; +} + +static void nfc_scene_read_on_enter_type_4_tag(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_type_4_tag, instance); +} + +static void nfc_scene_read_success_on_enter_type_4_tag(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_type_4_tag_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static NfcCommand + nfc_scene_emulate_listener_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcApp* instance = context; + Type4TagListenerEvent* type_4_tag_event = event.event_data; + + if(type_4_tag_event->type == Type4TagListenerEventTypeCustomCommand) { + if(furi_string_size(instance->text_box_store) < NFC_LOG_SIZE_MAX) { + furi_string_cat_printf(instance->text_box_store, "R:"); + for(size_t i = 0; i < bit_buffer_get_size_bytes(type_4_tag_event->data->buffer); i++) { + furi_string_cat_printf( + instance->text_box_store, + " %02X", + bit_buffer_get_byte(type_4_tag_event->data->buffer, i)); + } + furi_string_push_back(instance->text_box_store, '\n'); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventListenerUpdate); + } + } + + return NfcCommandContinue; +} + +static void nfc_scene_emulate_on_enter_type_4_tag(NfcApp* instance) { + const Type4TagData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag); + + instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolType4Tag, data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_type_4_tag, instance); +} + +static NfcCommand + nfc_scene_write_poller_callback_type_4_tag(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolType4Tag); + + NfcApp* instance = context; + Type4TagPollerEvent* type_4_tag_event = event.event_data; + NfcCommand command = NfcCommandContinue; + + if(type_4_tag_event->type == Type4TagPollerEventTypeRequestMode) { + type_4_tag_event->data->poller_mode.mode = Type4TagPollerModeWrite; + type_4_tag_event->data->poller_mode.data = + nfc_device_get_data(instance->nfc_device, NfcProtocolType4Tag); + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + } else if(type_4_tag_event->type == Type4TagPollerEventTypeWriteFail) { + const char* error_str = type_4_tag_event->data->error == Type4TagErrorCardLocked ? + "Card does not\nallow writing\nnew data" : + "Failed to\nwrite new data"; + furi_string_set(instance->text_box_store, error_str); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; + } else if(type_4_tag_event->type == Type4TagPollerEventTypeWriteSuccess) { + furi_string_reset(instance->text_box_store); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +static void nfc_scene_write_on_enter_type_4_tag(NfcApp* instance) { + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolType4Tag); + nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_type_4_tag, instance); + furi_string_set(instance->text_box_store, "Apply card\nto the back"); +} + +const NfcProtocolSupportBase nfc_protocol_support_type_4_tag = { + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo | + NfcProtocolFeatureWrite, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_type_4_tag, + .on_event = nfc_scene_more_info_on_event_type_4_tag, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_write = + { + .on_enter = nfc_scene_write_on_enter_type_4_tag, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; + +NFC_PROTOCOL_SUPPORT_PLUGIN(type_4_tag, NfcProtocolType4Tag); diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..e9d43a3b4 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_type_4_tag; diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c new file mode 100644 index 000000000..2dc51a6e8 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.c @@ -0,0 +1,59 @@ +#include "type_4_tag_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_type_4_tag_info( + const Type4TagData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(type_4_tag_get_base_data(data), str); + + furi_string_cat(str, "\n:::::::::::::::[Stored NDEF]:::::::::::::::\n"); + furi_string_cat_printf(str, "Current NDEF Size: %lu", simple_array_get_count(data->ndef_data)); + + if(data->is_tag_specific) { + furi_string_cat(str, "\n::::::::::::::::::[Tag Specs]::::::::::::::::::\n"); + furi_string_cat_printf( + str, + "Card: %s\n", + furi_string_empty(data->platform_name) ? "unknown" : + furi_string_get_cstr(data->platform_name)); + furi_string_cat_printf( + str, "T4T Mapping Version: %u.%u\n", data->t4t_version.major, data->t4t_version.minor); + furi_string_cat_printf(str, "NDEF File ID: %04X\n", data->ndef_file_id); + furi_string_cat_printf(str, "Max NDEF Size: %u\n", data->ndef_max_len); + furi_string_cat_printf( + str, "APDU Sizes: R:%u W:%u\n", data->chunk_max_read, data->chunk_max_write); + furi_string_cat_printf( + str, + "Read Lock: %02X%s\n", + data->ndef_read_lock, + data->ndef_read_lock == 0 ? " (unlocked)" : ""); + furi_string_cat_printf( + str, + "Write Lock: %02X%s", + data->ndef_write_lock, + data->ndef_write_lock == 0 ? " (unlocked)" : ""); + } + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(type_4_tag_get_base_data(data), str); +} + +void nfc_render_type_4_tag_dump(const Type4TagData* data, FuriString* str) { + size_t ndef_len = simple_array_get_count(data->ndef_data); + if(ndef_len == 0) { + furi_string_cat_str(str, "No NDEF data to show"); + return; + } + const uint8_t* ndef_data = simple_array_cget_data(data->ndef_data); + furi_string_cat_printf(str, "\e*"); + for(size_t i = 0; i < ndef_len; i += TYPE_4_TAG_RENDER_BYTES_PER_LINE) { + const uint8_t* line_data = &ndef_data[i]; + for(size_t j = 0; j < TYPE_4_TAG_RENDER_BYTES_PER_LINE; j += 2) { + furi_string_cat_printf(str, " %02X%02X", line_data[j], line_data[j + 1]); + } + } +} diff --git a/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h new file mode 100644 index 000000000..abb45317c --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/type_4_tag/type_4_tag_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +#define TYPE_4_TAG_RENDER_BYTES_PER_LINE (4U) + +void nfc_render_type_4_tag_info( + const Type4TagData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_type_4_tag_dump(const Type4TagData* data, FuriString* str); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index c8973fb0d..5f95e4859 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -1,7 +1,9 @@ #include "nfc_app_i.h" +#include "api/nfc_app_api_interface.h" #include "helpers/protocol_support/nfc_protocol_support.h" #include +#include bool nfc_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -49,12 +51,16 @@ NfcApp* nfc_app_alloc(void) { instance->nfc = nfc_alloc(); + 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); + instance->detected_protocols = nfc_detected_protocols_alloc(); instance->felica_auth = felica_auth_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(); + instance->nfc_supported_cards = nfc_supported_cards_alloc(instance->api_resolver); // Nfc device instance->nfc_device = nfc_device_alloc(); @@ -148,6 +154,9 @@ void nfc_app_free(NfcApp* instance) { slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); nfc_supported_cards_free(instance->nfc_supported_cards); + if(instance->protocol_support) { + nfc_protocol_support_free(instance); + } // Nfc device nfc_device_free(instance->nfc_device); @@ -415,6 +424,11 @@ bool nfc_load_from_file_select(NfcApp* instance) { if(!dialog_file_browser_show( instance->dialogs, instance->file_path, instance->file_path, &browser_options)) break; + + nfc_show_loading_popup(instance, true); + nfc_supported_cards_load_cache(instance->nfc_supported_cards); + nfc_show_loading_popup(instance, false); + success = nfc_load_file(instance, instance->file_path, true); } while(!success); @@ -464,7 +478,7 @@ static bool nfc_is_hal_ready(void) { static void nfc_show_initial_scene_for_device(NfcApp* nfc) { NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device); uint32_t scene = nfc_protocol_support_has_feature( - prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ? + prot, nfc, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ? NfcSceneEmulate : NfcSceneSavedMenu; // Load plugins (parsers) in case if we are in the saved menu @@ -523,11 +537,6 @@ int32_t nfc_app(void* p) { } else { view_dispatcher_attach_to_gui( nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); - // Load plugins (parsers) one time in case if we running app normally - nfc_show_loading_popup(nfc, true); - nfc_supported_cards_load_cache(nfc->nfc_supported_cards); - nfc_show_loading_popup(nfc, false); - // Switch to the initial scene scene_manager_next_scene(nfc->scene_manager, NfcSceneStart); } diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 78f6e8e98..4336d25e0 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,10 +32,12 @@ #include "helpers/mfkey32_logger.h" #include "helpers/nfc_emv_parser.h" #include "helpers/mf_classic_key_cache.h" +#include "helpers/protocol_support/nfc_protocol_support.h" #include "helpers/nfc_supported_cards.h" #include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" +#include #include #include #include @@ -149,6 +151,8 @@ struct NfcApp { Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; + CompositeApiResolver* api_resolver; + NfcProtocolSupport* protocol_support; NfcSupportedCards* nfc_supported_cards; NfcDevice* nfc_device; @@ -176,6 +180,10 @@ typedef enum { NfcSceneSaveConfirmStateCrackNonces, } NfcSceneSaveConfirmState; +#ifdef __cplusplus +extern "C" { +#endif + int32_t nfc_task(void* p); void nfc_text_store_set(NfcApp* nfc, const char* text, ...); @@ -213,3 +221,7 @@ void nfc_make_app_folder(NfcApp* instance); void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string); void nfc_app_run_external(NfcApp* nfc, const char* app_path); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/plugins/supported_cards/csc.c b/applications/main/nfc/plugins/supported_cards/csc.c new file mode 100644 index 000000000..a96d74007 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/csc.c @@ -0,0 +1,149 @@ +/* +* Parser for CSC Service Works Reloadable Cash Card (US) +* Date created 2024/5/26 +* Zinong Li +* Discord @torron0483 +* Github @zinongli +*/ + +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include + +#include +#include + +#define TAG "CSC" + +bool csc_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + bool parsed = false; + + do { + if(data->type != MfClassicType1k) break; // Check card type + + // Verify memory format (checksum is later) + const uint8_t refill_block_num = 2; + const uint8_t current_balance_block_num = 4; + const uint8_t current_balance_copy_block_num = 8; + + const uint8_t* current_balance_block_start_ptr = + &data->block[current_balance_block_num].data[0]; + const uint8_t* current_balance_copy_block_start_ptr = + &data->block[current_balance_copy_block_num].data[0]; + + uint32_t current_balance_and_times = + bit_lib_bytes_to_num_le(current_balance_block_start_ptr, 4); + uint32_t current_balance_and_times_copy = + bit_lib_bytes_to_num_le(current_balance_copy_block_start_ptr, 4); + + // Failed verification if balance != backup + if(current_balance_and_times != current_balance_and_times_copy) { + FURI_LOG_D(TAG, "Backup verification failed"); + break; + } + + // Even if balance = 0, e.g. new card, refilled times can't be zero + if(current_balance_and_times == 0 || current_balance_and_times_copy == 0) { + FURI_LOG_D(TAG, "Value bytes empty"); + break; + } + + // Parse data + const uint8_t card_lives_block_num = 9; + const uint8_t refill_sign_block_num = 13; + + const uint8_t* refilled_balance_block_start_ptr = &data->block[refill_block_num].data[9]; + const uint8_t* refill_times_block_start_ptr = &data->block[refill_block_num].data[5]; + const uint8_t* card_lives_block_start_ptr = &data->block[card_lives_block_num].data[0]; + const uint8_t* refill_sign_block_start_ptr = &data->block[refill_sign_block_num].data[0]; + + uint32_t refilled_balance = bit_lib_bytes_to_num_le(refilled_balance_block_start_ptr, 2); + uint32_t refilled_balance_dollar = refilled_balance / 100; + uint8_t refilled_balance_cent = refilled_balance % 100; + + uint32_t current_balance = bit_lib_bytes_to_num_le(current_balance_block_start_ptr, 2); + uint32_t current_balance_dollar = current_balance / 100; + uint8_t current_balance_cent = current_balance % 100; + + // How many times it can still be used + uint32_t card_lives = bit_lib_bytes_to_num_le(card_lives_block_start_ptr, 2); + + uint32_t refill_times = bit_lib_bytes_to_num_le(refill_times_block_start_ptr, 2); + // This is zero when you buy the card. but after refilling it, the refilling machine will leave a non-zero signature here + uint64_t refill_sign = bit_lib_bytes_to_num_le(refill_sign_block_start_ptr, 8); + + size_t uid_len = 0; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + uint32_t card_uid = bit_lib_bytes_to_num_le(uid, 4); + + // Last byte of refill block is checksum + const uint8_t* checksum_block = data->block[refill_block_num].data; + uint8_t xor_result = 0; + for(size_t i = 0; i < 16; ++i) { + xor_result ^= checksum_block[i]; + } + + if(refill_sign == 0 && refill_times == 1) { + // New cards don't comply to checksum but refill time should be once + furi_string_printf( + parsed_data, + "\e#CSC Service Works\n" + "UID: %lu\n" + "New Card\n" + "Card Value: %lu.%02u USD\n" + "Card Usages Left: %lu", + card_uid, + refilled_balance_dollar, + refilled_balance_cent, + card_lives); + } else { + if(xor_result != 0) { + FURI_LOG_D(TAG, "Checksum failed"); + break; + } + furi_string_printf( + parsed_data, + "\e#CSC Service Works\n" + "UID: %lu\n" + "Balance: %lu.%02u USD\n" + "Last Top-up: %lu.%02u USD\n" + "Top-up Count: %lu\n" + "Card Usages Left: %lu", + card_uid, + current_balance_dollar, + current_balance_cent, + refilled_balance_dollar, + refilled_balance_cent, + refill_times, + card_lives); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin csc_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = csc_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor csc_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &csc_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* csc_plugin_ep(void) { + return &csc_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/emv.c b/applications/main/nfc/plugins/supported_cards/emv.c index 97b16acf8..2f5b84790 100644 --- a/applications/main/nfc/plugins/supported_cards/emv.c +++ b/applications/main/nfc/plugins/supported_cards/emv.c @@ -199,6 +199,6 @@ static const FlipperAppPluginDescriptor emv_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* emv_plugin_ep() { +const FlipperAppPluginDescriptor* emv_plugin_ep(void) { return &emv_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/kazan.c b/applications/main/nfc/plugins/supported_cards/kazan.c index 403685468..65838e25c 100644 --- a/applications/main/nfc/plugins/supported_cards/kazan.c +++ b/applications/main/nfc/plugins/supported_cards/kazan.c @@ -223,7 +223,7 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { break; } - if(!mf_classic_is_card_read(data)) { + if(error == MfClassicErrorPartialRead) { error = mf_classic_poller_sync_read(nfc, &keys_v2, data); if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v1"); @@ -231,7 +231,7 @@ static bool kazan_read(Nfc* nfc, NfcDevice* device) { } } - if(!mf_classic_is_card_read(data)) { + if(error == MfClassicErrorPartialRead) { error = mf_classic_poller_sync_read(nfc, &keys_v3, data); if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data: keys_v3"); @@ -241,7 +241,7 @@ static bool kazan_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); @@ -409,6 +409,6 @@ static const FlipperAppPluginDescriptor kazan_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* kazan_plugin_ep() { +const FlipperAppPluginDescriptor* kazan_plugin_ep(void) { return &kazan_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/metromoney.c b/applications/main/nfc/plugins/supported_cards/metromoney.c index 3a3d1fe6e..089bb48bd 100644 --- a/applications/main/nfc/plugins/supported_cards/metromoney.c +++ b/applications/main/nfc/plugins/supported_cards/metromoney.c @@ -185,6 +185,6 @@ static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* metromoney_plugin_ep() { +const FlipperAppPluginDescriptor* metromoney_plugin_ep(void) { return &metromoney_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index c00fef6b5..240e6dc2f 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -199,7 +199,7 @@ static bool mizip_parse(const NfcDevice* device, FuriString* parsed_data) { MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); - if(key != cfg.keys[cfg.verify_sector].b) return false; + if(key != cfg.keys[cfg.verify_sector].b) break; //Get UID uint8_t uid[UID_LENGTH]; diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index 06982e111..86d31052e 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,8 @@ #define NDEF_PROTO_UL (1) #define NDEF_PROTO_MFC (2) #define NDEF_PROTO_SLIX (3) -#define NDEF_PROTO_TOTAL (4) +#define NDEF_PROTO_T4T (4) +#define NDEF_PROTO_TOTAL (5) #ifndef NDEF_PROTO #error Must specify what protocol to use with NDEF_PROTO define! @@ -40,10 +42,10 @@ #error Invalid NDEF_PROTO specified! #endif -#define NDEF_TITLE(device, parsed_data) \ - furi_string_printf( \ - parsed_data, \ - "\e#NDEF Format Data\nCard type: %s\n", \ +#define NDEF_TITLE(device, parsed_data) \ + furi_string_printf( \ + parsed_data, \ + "\e#NDEF Format Data\nCard: %s\n", \ nfc_device_get_name(device, NfcDeviceNameTypeFull)) // ---=== structures ===--- @@ -151,6 +153,11 @@ typedef struct { const uint8_t* start; size_t size; } slix; +#elif NDEF_PROTO == NDEF_PROTO_T4T + struct { + const uint8_t* data; + size_t size; + } t4t; #endif } Ndef; @@ -230,6 +237,13 @@ static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) { memcpy(buf, ndef->slix.start + pos, len); return true; +#elif NDEF_PROTO == NDEF_PROTO_T4T + + // Memory space is contiguous, simply need to remap to data pointer + if(pos + len > ndef->t4t.size) return false; + memcpy(buf, ndef->t4t.data + pos, len); + return true; + #else UNUSED(ndef); @@ -1039,6 +1053,44 @@ static bool ndef_slix_parse(const NfcDevice* device, FuriString* parsed_data) { return parsed > 0; } +#elif NDEF_PROTO == NDEF_PROTO_T4T + +static bool ndef_t4t_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const Type4TagData* data = nfc_device_get_data(device, NfcProtocolType4Tag); + size_t data_start = 0; + size_t data_size = simple_array_get_count(data->ndef_data); + + NDEF_TITLE(device, parsed_data); + + furi_string_replace(parsed_data, "Card: ", "Protocol: "); + if(data->is_tag_specific && !furi_string_empty(data->platform_name)) { + furi_string_cat_printf( + parsed_data, "Card: %s\n", furi_string_get_cstr(data->platform_name)); + } + + Ndef ndef = { + .output = parsed_data, + .t4t = + { + .data = data_size == 0 ? NULL : simple_array_cget_data(data->ndef_data), + .size = data_size, + }, + }; + size_t parsed = ndef_parse_message(&ndef, data_start, data_size - data_start, 1, false); + + if(parsed) { + furi_string_trim(parsed_data, "\n"); + furi_string_cat(parsed_data, "\n"); + } else { + furi_string_reset(parsed_data); + } + + return parsed > 0; +} + #endif // ---=== boilerplate ===--- @@ -1056,6 +1108,9 @@ static const NfcSupportedCardsPlugin ndef_plugin = { #elif NDEF_PROTO == NDEF_PROTO_SLIX .parse = ndef_slix_parse, .protocol = NfcProtocolSlix, +#elif NDEF_PROTO == NDEF_PROTO_T4T + .parse = ndef_t4t_parse, + .protocol = NfcProtocolType4Tag, #endif }; diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 49bbaebe8..add7ab560 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -247,7 +247,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { // Print card number with 4-digit groups. "3" in "3078" denotes a ticket type "3 - full ticket", will differ on discounted cards. furi_string_cat_printf(parsed_data, "Number: "); FuriString* card_number_s = furi_string_alloc(); - furi_string_cat_printf(card_number_s, "%llu", card_number); + furi_string_cat_printf(card_number_s, "%lld", card_number); FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); for(uint8_t i = 0; i < 24; i += 4) { for(uint8_t j = 0; j < 4; j++) { diff --git a/applications/main/nfc/plugins/supported_cards/smartrider.c b/applications/main/nfc/plugins/supported_cards/smartrider.c new file mode 100644 index 000000000..073f0410d --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/smartrider.c @@ -0,0 +1,334 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define MAX_TRIPS 10 +#define TAG "SmartRider" +#define MAX_BLOCKS 64 +#define MAX_DATE_ITERATIONS 366 + +static const uint8_t STANDARD_KEYS[3][6] = { + {0x20, 0x31, 0xD1, 0xE5, 0x7A, 0x3B}, + {0x4C, 0xA6, 0x02, 0x9F, 0x94, 0x73}, + {0x19, 0x19, 0x53, 0x98, 0xE3, 0x2F}}; + +typedef struct { + uint32_t timestamp; + uint16_t cost; + uint16_t transaction_number; + uint16_t journey_number; + char route[5]; + uint8_t tap_on : 1; + uint8_t block; +} __attribute__((packed)) TripData; + +typedef struct { + uint32_t balance; + uint16_t issued_days; + uint16_t expiry_days; + uint16_t purchase_cost; + uint16_t auto_load_threshold; + uint16_t auto_load_value; + char card_serial_number[11]; + uint8_t token; + TripData trips[MAX_TRIPS]; + uint8_t trip_count; +} __attribute__((packed)) SmartRiderData; + +static const char* const CONCESSION_TYPES[] = { + "Pre-issue", + "Standard Fare", + "Student", + NULL, + "Tertiary", + NULL, + "Seniors", + "Health Care", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "PTA Staff", + "Pensioner", + "Free Travel"}; + +static inline const char* get_concession_type(uint8_t token) { + return (token <= 0x10) ? CONCESSION_TYPES[token] : "Unknown"; +} + +static bool authenticate_and_read( + Nfc* nfc, + uint8_t sector, + const uint8_t* key, + MfClassicKeyType key_type, + MfClassicBlock* block_data) { + MfClassicKey mf_key; + memcpy(mf_key.data, key, 6); + uint8_t block = mf_classic_get_first_block_num_of_sector(sector); + + if(mf_classic_poller_sync_auth(nfc, block, &mf_key, key_type, NULL) != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Authentication failed for sector %d key type %d", sector, key_type); + return false; + } + + if(mf_classic_poller_sync_read_block(nfc, block, &mf_key, key_type, block_data) != + MfClassicErrorNone) { + FURI_LOG_D(TAG, "Read failed for sector %d", sector); + return false; + } + + return true; +} + +static bool smartrider_verify(Nfc* nfc) { + furi_assert(nfc); + MfClassicBlock block_data; + + for(int i = 0; i < 3; i++) { + if(!authenticate_and_read( + nfc, + i * 6, + STANDARD_KEYS[i], + i % 2 == 0 ? MfClassicKeyTypeA : MfClassicKeyTypeB, + &block_data) || + memcmp(block_data.data, STANDARD_KEYS[i], 6) != 0) { + FURI_LOG_D(TAG, "Authentication or key mismatch for key %d", i); + return false; + } + } + + FURI_LOG_I(TAG, "SmartRider card verified"); + return true; +} + +static inline bool + parse_trip_data(const MfClassicBlock* block_data, TripData* trip, uint8_t block_number) { + trip->timestamp = bit_lib_bytes_to_num_le(block_data->data + 3, 4); + trip->tap_on = (block_data->data[7] & 0x10) == 0x10; + memcpy(trip->route, block_data->data + 8, 4); + trip->route[4] = '\0'; + trip->cost = bit_lib_bytes_to_num_le(block_data->data + 13, 2); + trip->transaction_number = bit_lib_bytes_to_num_le(block_data->data, 2); + trip->journey_number = bit_lib_bytes_to_num_le(block_data->data + 2, 2); + trip->block = block_number; + return true; +} + +static bool smartrider_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + MfClassicType type; + if(mf_classic_poller_sync_detect_type(nfc, &type) != MfClassicErrorNone || + type != MfClassicType1k) { + mf_classic_free(data); + return false; + } + data->type = type; + + MfClassicDeviceKeys keys = {.key_a_mask = 0, .key_b_mask = 0}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + memcpy(keys.key_a[i].data, STANDARD_KEYS[i == 0 ? 0 : 1], sizeof(STANDARD_KEYS[0])); + if(i > 0) { + memcpy(keys.key_b[i].data, STANDARD_KEYS[2], sizeof(STANDARD_KEYS[0])); + FURI_BIT_SET(keys.key_b_mask, i); + } + FURI_BIT_SET(keys.key_a_mask, i); + } + + MfClassicError error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + mf_classic_free(data); + return false; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + mf_classic_free(data); + return true; +} + +static bool is_leap_year(uint16_t year) { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} + +static void calculate_date(uint32_t timestamp, char* date_str, size_t date_str_size) { + uint32_t seconds_since_2000 = timestamp; + uint32_t days_since_2000 = seconds_since_2000 / 86400; + uint16_t year = 2000; + uint8_t month = 1; + uint16_t day = 1; + + static const uint16_t days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + while(days_since_2000 >= (is_leap_year(year) ? 366 : 365)) { + days_since_2000 -= (is_leap_year(year) ? 366 : 365); + year++; + } + + for(month = 0; month < 12; month++) { + uint16_t dim = days_in_month[month]; + if(month == 1 && is_leap_year(year)) { + dim++; + } + if(days_since_2000 < dim) { + break; + } + days_since_2000 -= dim; + } + + day = days_since_2000 + 1; + month++; // Adjust month to 1-based + + if(date_str_size > 0) { + size_t written = 0; + written += snprintf(date_str + written, date_str_size - written, "%02u", day); + if(written < date_str_size - 1) { + written += snprintf(date_str + written, date_str_size - written, "/"); + } + if(written < date_str_size - 1) { + written += snprintf(date_str + written, date_str_size - written, "%02u", month); + } + if(written < date_str_size - 1) { + written += snprintf(date_str + written, date_str_size - written, "/"); + } + if(written < date_str_size - 1) { + snprintf(date_str + written, date_str_size - written, "%02u", year % 100); + } + } else { + // If the buffer size is 0, do nothing + } +} + +static bool smartrider_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + SmartRiderData sr_data = {0}; + + if(data->type != MfClassicType1k) { + FURI_LOG_E(TAG, "Invalid card type"); + return false; + } + + const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); + if(!sec_tr || memcmp(sec_tr->key_a.data, STANDARD_KEYS[0], 6) != 0) { + FURI_LOG_E(TAG, "Key verification failed for sector 0"); + return false; + } + + static const uint8_t required_blocks[] = {14, 4, 5, 1, 52, 50, 0}; + for(size_t i = 0; i < COUNT_OF(required_blocks); i++) { + if(required_blocks[i] >= MAX_BLOCKS || + !mf_classic_is_block_read(data, required_blocks[i])) { + FURI_LOG_E(TAG, "Required block %d is not read or out of range", required_blocks[i]); + return false; + } + } + + sr_data.balance = bit_lib_bytes_to_num_le(data->block[14].data + 7, 2); + sr_data.issued_days = bit_lib_bytes_to_num_le(data->block[4].data + 16, 2); + sr_data.expiry_days = bit_lib_bytes_to_num_le(data->block[4].data + 18, 2); + sr_data.auto_load_threshold = bit_lib_bytes_to_num_le(data->block[4].data + 20, 2); + sr_data.auto_load_value = bit_lib_bytes_to_num_le(data->block[4].data + 22, 2); + sr_data.token = data->block[5].data[8]; + sr_data.purchase_cost = bit_lib_bytes_to_num_le(data->block[0].data + 14, 2); + + snprintf( + sr_data.card_serial_number, + sizeof(sr_data.card_serial_number), + "%02X%02X%02X%02X%02X", + data->block[1].data[6], + data->block[1].data[7], + data->block[1].data[8], + data->block[1].data[9], + data->block[1].data[10]); + + for(uint8_t block_number = 40; block_number <= 52 && sr_data.trip_count < MAX_TRIPS; + block_number++) { + if((block_number != 43 && block_number != 47 && block_number != 51) && + mf_classic_is_block_read(data, block_number) && + parse_trip_data( + &data->block[block_number], &sr_data.trips[sr_data.trip_count], block_number)) { + sr_data.trip_count++; + } + } + + // Sort trips by timestamp (descending order) + for(uint8_t i = 0; i < sr_data.trip_count - 1; i++) { + for(uint8_t j = 0; j < sr_data.trip_count - i - 1; j++) { + if(sr_data.trips[j].timestamp < sr_data.trips[j + 1].timestamp) { + TripData temp = sr_data.trips[j]; + sr_data.trips[j] = sr_data.trips[j + 1]; + sr_data.trips[j + 1] = temp; + } + } + } + + furi_string_printf( + parsed_data, + "\e#SmartRider\nBalance: $%lu.%02lu\nConcession: %s\nSerial: %s%s\n" + "Total Cost: $%u.%02u\nAuto-Load: $%u.%02u/$%u.%02u\n\e#Tag On/Off History\n", + sr_data.balance / 100, + sr_data.balance % 100, + get_concession_type(sr_data.token), + memcmp(sr_data.card_serial_number, "00", 2) == 0 ? "SR0" : "", + memcmp(sr_data.card_serial_number, "00", 2) == 0 ? sr_data.card_serial_number + 2 : + sr_data.card_serial_number, + sr_data.purchase_cost / 100, + sr_data.purchase_cost % 100, + sr_data.auto_load_threshold / 100, + sr_data.auto_load_threshold % 100, + sr_data.auto_load_value / 100, + sr_data.auto_load_value % 100); + + for(uint8_t i = 0; i < sr_data.trip_count; i++) { + char date_str[9]; + calculate_date(sr_data.trips[i].timestamp, date_str, sizeof(date_str)); + + uint32_t cost = sr_data.trips[i].cost; + if(cost > 0) { + furi_string_cat_printf( + parsed_data, + "%s %c $%lu.%02lu %s\n", + date_str, + sr_data.trips[i].tap_on ? '+' : '-', + cost / 100, + cost % 100, + sr_data.trips[i].route); + } else { + furi_string_cat_printf( + parsed_data, + "%s %c %s\n", + date_str, + sr_data.trips[i].tap_on ? '+' : '-', + sr_data.trips[i].route); + } + } + + return true; +} +static const NfcSupportedCardsPlugin smartrider_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = smartrider_verify, + .read = smartrider_read, + .parse = smartrider_parse, +}; + +__attribute__((used)) const FlipperAppPluginDescriptor* smartrider_plugin_ep() { + static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &smartrider_plugin, + }; + return &plugin_descriptor; +} + +// made with love by jay candel <3 diff --git a/applications/main/nfc/plugins/supported_cards/sonicare.c b/applications/main/nfc/plugins/supported_cards/sonicare.c new file mode 100644 index 000000000..3e2a9b15a --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/sonicare.c @@ -0,0 +1,111 @@ +// Parser for Philips Sonicare toothbrush heads. +// Made by @Sil333033 +// Thanks to Cyrill Künzi for this research! https://kuenzi.dev/toothbrush/ + +#include "nfc_supported_card_plugin.h" + +#include +#include + +#define TAG "Sonicare" + +typedef enum { + SonicareHeadWhite, + SonicareHeadBlack, + SonicareHeadUnkown, +} SonicareHead; + +static SonicareHead sonicare_get_head_type(const MfUltralightData* data) { + // data.page[34].data got 4 bytes + // 31:32:31:34 for black (not sure) + // 31:31:31:31 for white (not sure) + // the data should be in here based on the research, but i cant find it. + // page 34 byte 0 is always 0x30 for the white brushes i have, so i guess thats white + // TODO: Get a black brush and test this + + if(data->page[34].data[0] == 0x30) { + return SonicareHeadWhite; + } else { + return SonicareHeadUnkown; + } +} + +static uint32_t sonicare_get_seconds_brushed(const MfUltralightData* data) { + uint32_t seconds_brushed = 0; + + seconds_brushed += data->page[36].data[0]; + seconds_brushed += data->page[36].data[1] << 8; + + return seconds_brushed; +} + +static bool sonicare_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + // Check for NDEF link match + const char* test = "philips.com/nfcbrushheadtap"; + // Data is a array of arrays, cast to char array and compare + if(strncmp(test, (const char*)&data->page[5].data[3], strlen(test)) != 0) { + FURI_LOG_D(TAG, "Not a Philips Sonicare head"); + break; + } + + const SonicareHead head_type = sonicare_get_head_type(data); + const uint32_t seconds_brushed = sonicare_get_seconds_brushed(data); + + FuriString* head_type_str = furi_string_alloc(); + + switch(head_type) { + case SonicareHeadWhite: + furi_string_printf(head_type_str, "White"); + break; + case SonicareHeadBlack: + furi_string_printf(head_type_str, "Black"); + break; + case SonicareHeadUnkown: + default: + furi_string_printf(head_type_str, "Unknown"); + break; + } + + furi_string_printf( + parsed_data, + "\e#Philips Sonicare head\nColor: %s\nTime brushed: %02.0f:%02.0f:%02ld\n", + furi_string_get_cstr(head_type_str), + floor(seconds_brushed / 3600), + floor((seconds_brushed / 60) % 60), + seconds_brushed % 60); + + furi_string_free(head_type_str); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin sonicare_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = sonicare_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor sonicare_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &sonicare_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* sonicare_plugin_ep(void) { + return &sonicare_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index dc87d3072..240c6c585 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -112,7 +112,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); - if(key != two_cities_4k_keys[4].a) return false; + if(key != two_cities_4k_keys[4].a) break; // ===== // PLANTAIN diff --git a/applications/main/nfc/plugins/supported_cards/ventra.c b/applications/main/nfc/plugins/supported_cards/ventra.c new file mode 100644 index 000000000..9abdc1ed7 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/ventra.c @@ -0,0 +1,292 @@ +// Parser for CTA Ventra Ultralight cards +// Made by @hazardousvoltage +// Based on my own research, with... +// Credit to https://www.lenrek.net/experiments/compass-tickets/ & MetroDroid project for underlying info +// +// This parser can decode the paper single-use and single/multi-day paper passes using Ultralight EV1 +// The plastic cards are DESFire and fully locked down, not much useful info extractable +// TODO: +// - Sort the duplicate/rare ticket types +// - Database of stop IDs for trains? Buses there's just too damn many, but you can find them here: +// https://data.cityofchicago.org/Transportation/CTA-Bus-Stops-kml/84eu-buny/about_data +// - Generalize to handle all known Cubic Nextfare Ultralight systems? Anyone wants to send me specimen dumps, hit me up on Discord. + +#include "nfc_supported_card_plugin.h" + +#include +#include +#include "datetime.h" +#include + +#define TAG "Ventra" + +DateTime ventra_exp_date = {0}, ventra_validity_date = {0}; +uint8_t ventra_high_seq = 0, ventra_cur_blk = 0, ventra_mins_active = 0; + +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); +} + +static DateTime dt_delta(DateTime dt, uint8_t delta_days) { + // returns shifted DateTime, from initial DateTime and time offset in seconds + DateTime dt_shifted = {0}; + datetime_timestamp_to_datetime( + datetime_datetime_to_timestamp(&dt) - (uint64_t)delta_days * 86400, &dt_shifted); + return dt_shifted; +} + +/* +static long dt_diff(DateTime dta, DateTime dtb) { + // returns difference in seconds between two DateTimes + long diff; + diff = datetime_datetime_to_timestamp(&dta) - datetime_datetime_to_timestamp(&dtb); + return diff; +} +*/ + +// Card is expired if: +// - Hard expiration date passed (90 days from purchase, encoded in product record) +// - Soft expiration date passed: +// - For passes, n days after first use +// - For tickets, 2 hours after first use +// Calculating these is dumber than it needs to be, see xact record parser. +bool isExpired(void) { + uint32_t ts_hard_exp = datetime_datetime_to_timestamp(&ventra_exp_date); + uint32_t ts_soft_exp = datetime_datetime_to_timestamp(&ventra_validity_date); + uint32_t ts_now = time_now(); + return (ts_now >= ts_hard_exp || ts_now > ts_soft_exp); +} + +static FuriString* ventra_parse_xact(const MfUltralightData* data, uint8_t blk, bool is_pass) { + FuriString* ventra_xact_str = furi_string_alloc(); + uint16_t ts = data->page[blk].data[0] | data->page[blk].data[1] << 8; + uint8_t tran_type = ts & 0x1F; + ts >>= 5; + uint8_t day = data->page[blk].data[2]; + uint32_t work = data->page[blk + 1].data[0] | data->page[blk + 1].data[1] << 8 | + data->page[blk + 1].data[2] << 16; + uint8_t seq = work & 0x7F; + uint16_t exp = (work >> 7) & 0x7FF; + uint8_t exp_day = data->page[blk + 2].data[0]; + uint16_t locus = data->page[blk + 2].data[1] | data->page[blk + 2].data[2] << 8; + uint8_t line = data->page[blk + 2].data[3]; + + // This computes the block timestamp, based on the card expiration date and delta from it + DateTime dt = dt_delta(ventra_exp_date, day); + dt.hour = (ts & 0x7FF) / 60; + dt.minute = (ts & 0x7FF) % 60; + + // If sequence is 0, block isn't used yet (new card with only one active block, typically the first one. + // Otherwise, the block with higher sequence is the latest transaction, and the other block is prior transaction. + // Not necessarily in that order on the card. We need the latest data to compute validity and pretty-print them + // in reverse chrono. So this mess sets some globals as to which block is current, computes the validity times, etc. + if(seq == 0) { + furi_string_printf(ventra_xact_str, "-- EMPTY --"); + return (ventra_xact_str); + } + if(seq > ventra_high_seq) { + ventra_high_seq = seq; + ventra_cur_blk = blk; + ventra_mins_active = data->page[blk + 1].data[3]; + // Figure out the soft expiration. For passes it's easy, the readers update the "exp" field in the transaction record. + // Tickets, not so much, readers don't update "exp", but each xact record has "minutes since last tap" which is + // updated and carried forward. That, plus transaction timestamp, gives the expiration time. + if(tran_type == 6) { // Furthermore, purchase transactions set bogus expiration dates + if(is_pass) { + ventra_validity_date = dt_delta(ventra_exp_date, exp_day); + ventra_validity_date.hour = (exp & 0x7FF) / 60; + ventra_validity_date.minute = (exp & 0x7FF) % 60; + } else { + uint32_t validity_ts = datetime_datetime_to_timestamp(&dt); + validity_ts += (120 - ventra_mins_active) * 60; + datetime_timestamp_to_datetime(validity_ts, &ventra_validity_date); + } + } + } + + // Type 0 = Purchase, 1 = Train ride, 2 = Bus ride + // TODO: Check PACE and see if it uses a different line code + char linemap[3] = "PTB"; + char* xact_fmt = (line == 2) ? "%c %5d %04d-%02d-%02d %02d:%02d" : + "%c %04X %04d-%02d-%02d %02d:%02d"; + // I like a nice concise display showing all the relevant infos without having to scroll... + // Line StopID DateTime + furi_string_printf( + ventra_xact_str, + xact_fmt, + (line < 3) ? linemap[line] : '?', + locus, + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute); + return (ventra_xact_str); +} + +static bool ventra_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + // This test can probably be improved -- it matches every Ventra I've seen, but will also match others + // in the same family. Or maybe we just generalize this parser. + if(data->page[4].data[0] != 0x0A || data->page[4].data[1] != 4 || + data->page[4].data[2] != 0 || data->page[6].data[0] != 0 || + data->page[6].data[1] != 0 || data->page[6].data[2] != 0) { + FURI_LOG_D(TAG, "Not Ventra Ultralight"); + break; + } + + // Parse the product record, display interesting data & extract info needed to parse transaction blocks + // Had this in its own function, ended up just setting a bunch of shitty globals, so inlined it instead. + FuriString* ventra_prod_str = furi_string_alloc(); + uint8_t otp = data->page[3].data[0]; + uint8_t prod_code = data->page[5].data[2]; + bool is_pass = false; + switch(prod_code) { + case 2: + case 0x1F: // Only ever seen one of these, it parses like a Single + furi_string_cat_printf(ventra_prod_str, "Single"); + break; + case 3: + case 0x3F: + is_pass = true; + furi_string_cat_printf(ventra_prod_str, "1-Day"); + break; + case 4: // Last I checked, 3 day passes only available at airport TVMs & social service agencies + is_pass = true; + furi_string_cat_printf(ventra_prod_str, "3-Day"); + break; + default: + is_pass = + true; // There are some card types I don't know what they are, but they parse like a pass, not a ticket. + furi_string_cat_printf(ventra_prod_str, "0x%02X", data->page[5].data[2]); + break; + } + + uint16_t date_y = data->page[4].data[3] | (data->page[5].data[0] << 8); + uint8_t date_d = date_y & 0x1F; + uint8_t date_m = (date_y >> 5) & 0x0F; + date_y >>= 9; + date_y += 2000; + ventra_exp_date.day = date_d; + ventra_exp_date.month = date_m; + ventra_exp_date.year = date_y; + ventra_validity_date = ventra_exp_date; // Until we know otherwise + + // Parse the transaction blocks. This sets a few sloppy globals, but it's too complex and repetitive to inline. + FuriString* ventra_xact_str1 = ventra_parse_xact(data, 8, is_pass); + FuriString* ventra_xact_str2 = ventra_parse_xact(data, 12, is_pass); + + uint8_t card_state = 1; + uint8_t rides_left = 0; + + char* card_states[5] = {"???", "NEW", "ACT", "USED", "EXP"}; + + if(ventra_high_seq > 1) card_state = 2; + // On "ticket" product, the OTP bits mark off rides used. Bit 0 seems to be unused, the next 3 are set as rides are used. + // Some, not all, readers will set the high bits to 0x7 when a card is tapped after it's expired or depleted. Have not + // seen other combinations, but if we do, we'll make a nice ???. 1-day passes set the OTP bit 1 on first use. 3-day + // passes do not. But we don't really care, since they don't matter on passes, unless you're trying to rollback one. + if(!is_pass) { + switch(otp) { + case 0: + rides_left = 3; + break; + case 2: + card_state = 2; + rides_left = 2; + break; + case 6: + card_state = 2; + rides_left = 1; + break; + case 0x0E: + case 0x7E: + card_state = 3; + rides_left = 0; + break; + default: + card_state = 0; + rides_left = 0; + break; + } + } + if(isExpired()) { + card_state = 4; + rides_left = 0; + } + + furi_string_printf( + parsed_data, + "\e#Ventra %s (%s)\n", + furi_string_get_cstr(ventra_prod_str), + card_states[card_state]); + + furi_string_cat_printf( + parsed_data, + "Exp: %04d-%02d-%02d %02d:%02d\n", + ventra_validity_date.year, + ventra_validity_date.month, + ventra_validity_date.day, + ventra_validity_date.hour, + ventra_validity_date.minute); + + if(rides_left) { + furi_string_cat_printf(parsed_data, "Rides left: %d\n", rides_left); + } + + furi_string_cat_printf( + parsed_data, + "%s\n", + furi_string_get_cstr(ventra_cur_blk == 8 ? ventra_xact_str1 : ventra_xact_str2)); + + furi_string_cat_printf( + parsed_data, + "%s\n", + furi_string_get_cstr(ventra_cur_blk == 8 ? ventra_xact_str2 : ventra_xact_str1)); + + furi_string_cat_printf( + parsed_data, "TVM ID: %02X%02X\n", data->page[7].data[1], data->page[7].data[0]); + furi_string_cat_printf(parsed_data, "Tx count: %d\n", ventra_high_seq); + furi_string_cat_printf( + parsed_data, + "Hard Expiry: %04d-%02d-%02d", + ventra_exp_date.year, + ventra_exp_date.month, + ventra_exp_date.day); + + furi_string_free(ventra_prod_str); + furi_string_free(ventra_xact_str1); + furi_string_free(ventra_xact_str2); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin ventra_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = ventra_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor ventra_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &ventra_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* ventra_plugin_ep(void) { + return &ventra_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c index cb0254b14..8ba36605b 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona.c @@ -209,6 +209,6 @@ static const FlipperAppPluginDescriptor zolotaya_korona_plugin_descriptor = { }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* zolotaya_korona_plugin_ep() { +const FlipperAppPluginDescriptor* zolotaya_korona_plugin_ep(void) { return &zolotaya_korona_plugin_descriptor; } diff --git a/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c b/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c index f2eccd7c0..0e39b8a58 100644 --- a/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c +++ b/applications/main/nfc/plugins/supported_cards/zolotaya_korona_online.c @@ -139,6 +139,6 @@ static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor }; /* Plugin entry point - must return a pointer to const descriptor */ -const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() { +const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep(void) { return &zolotaya_korona_online_plugin_descriptor; } diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 55f8aa3be..b902bc940 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -18,6 +18,7 @@ ADD_SCENE(nfc, extra_actions, ExtraActions) ADD_SCENE(nfc, read_success, ReadSuccess) ADD_SCENE(nfc, read_menu, ReadMenu) ADD_SCENE(nfc, emulate, Emulate) +ADD_SCENE(nfc, write, Write) ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, debug, Debug) ADD_SCENE(nfc, field, Field) @@ -25,10 +26,6 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) -ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) -ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) -ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail) -ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard) ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) @@ -48,10 +45,6 @@ ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard) -ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial) -ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess) -ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail) -ADD_SCENE(nfc, mf_classic_write_initial_wrong_card, MfClassicWriteInitialWrongCard) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 7ef3f9d87..a824c990d 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -16,6 +16,10 @@ void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) { void nfc_scene_detect_on_enter(void* context) { NfcApp* instance = context; + nfc_show_loading_popup(instance, true); + nfc_supported_cards_load_cache(instance->nfc_supported_cards); + nfc_show_loading_popup(instance, false); + // Setup view popup_reset(instance->popup); popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 024bc5c1e..500dd759a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -236,7 +236,6 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { dict_attack_set_card_state(instance->dict_attack, true); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); nfc_blink_read_start(instance); - notification_message(instance->notifications, &sequence_display_backlight_enforce_on); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); @@ -392,5 +391,4 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { } nfc_blink_stop(instance); - notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c index f3aefb781..ac9e7a192 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c @@ -47,6 +47,9 @@ bool nfc_scene_mf_classic_update_initial_wrong_card_on_event( if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(instance->scene_manager); } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSavedMenu); } return consumed; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c deleted file mode 100644 index 12e7ba1ec..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "../nfc_app_i.h" - -#include - -enum { - NfcSceneMfClassicWriteInitialStateCardSearch, - NfcSceneMfClassicWriteInitialStateCardFound, -}; - -NfcCommand - nfc_scene_mf_classic_write_initial_worker_callback(NfcGenericEvent event, void* context) { - furi_assert(context); - furi_assert(event.event_data); - furi_assert(event.protocol == NfcProtocolMfClassic); - - NfcCommand command = NfcCommandContinue; - NfcApp* instance = context; - MfClassicPollerEvent* mfc_event = event.event_data; - const MfClassicData* write_data = - nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); - - if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); - } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardLost); - } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { - const MfClassicData* tag_data = nfc_poller_get_data(instance->poller); - if(iso14443_3a_is_equal(tag_data->iso14443_3a_data, write_data->iso14443_3a_data)) { - mfc_event->data->poller_mode.mode = MfClassicPollerModeWrite; - } else { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); - command = NfcCommandStop; - } - } else if(mfc_event->type == MfClassicPollerEventTypeRequestSectorTrailer) { - uint8_t sector = mfc_event->data->sec_tr_data.sector_num; - uint8_t sec_tr = mf_classic_get_sector_trailer_num_by_sector(sector); - if(mf_classic_is_block_read(write_data, sec_tr)) { - mfc_event->data->sec_tr_data.sector_trailer = write_data->block[sec_tr]; - mfc_event->data->sec_tr_data.sector_trailer_provided = true; - } else { - mfc_event->data->sec_tr_data.sector_trailer_provided = false; - } - } else if(mfc_event->type == MfClassicPollerEventTypeRequestWriteBlock) { - uint8_t block_num = mfc_event->data->write_block_data.block_num; - if(mf_classic_is_block_read(write_data, block_num)) { - mfc_event->data->write_block_data.write_block = write_data->block[block_num]; - mfc_event->data->write_block_data.write_block_provided = true; - } else { - mfc_event->data->write_block_data.write_block_provided = false; - } - } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - command = NfcCommandStop; - } else if(mfc_event->type == MfClassicPollerEventTypeFail) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); - command = NfcCommandStop; - } - return command; -} - -static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = - scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial); - - if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { - popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); - popup_set_text( - instance->popup, "Use the source\ncard only", 95, 38, AlignCenter, AlignCenter); - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_classic_write_initial_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardSearch); - nfc_scene_mf_classic_write_initial_setup_view(instance); - - // Setup and start worker - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); - nfc_poller_start( - instance->poller, nfc_scene_mf_classic_write_initial_worker_callback, instance); - - nfc_blink_emulate_start(instance); -} - -bool nfc_scene_mf_classic_write_initial_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardFound); - nfc_scene_mf_classic_write_initial_setup_view(instance); - consumed = true; - } else if(event.event == NfcCustomEventCardLost) { - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardSearch); - nfc_scene_mf_classic_write_initial_setup_view(instance); - consumed = true; - } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene( - instance->scene_manager, NfcSceneMfClassicWriteInitialWrongCard); - consumed = true; - } else if(event.event == NfcCustomEventPollerSuccess) { - scene_manager_next_scene( - instance->scene_manager, NfcSceneMfClassicWriteInitialSuccess); - consumed = true; - } else if(event.event == NfcCustomEventPollerFailure) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitialFail); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_classic_write_initial_on_exit(void* context) { - NfcApp* instance = context; - - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfClassicWriteInitial, - NfcSceneMfClassicWriteInitialStateCardSearch); - // Clear view - popup_reset(instance->popup); - - nfc_blink_stop(instance); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c deleted file mode 100644 index 4d4367ec8..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_fail.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_classic_write_initial_fail_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_mf_classic_write_initial_fail_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); - widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Not all sectors\nwere written\ncorrectly."); - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Finish", - nfc_scene_mf_classic_write_initial_fail_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_write_initial_fail_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); - } - return consumed; -} - -void nfc_scene_mf_classic_write_initial_fail_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c deleted file mode 100644 index 100c5c431..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_success.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_classic_write_initial_success_popup_callback(void* context) { - NfcApp* instance = context; - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(instance->notifications, &sequence_success); - - Popup* popup = instance->popup; - popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); - popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); - popup_set_timeout(popup, 1500); - popup_set_context(popup, instance); - popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_classic_write_initial_success_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneSavedMenu); - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_initial_success_on_exit(void* context) { - NfcApp* instance = context; - - // Clear view - popup_reset(instance->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c deleted file mode 100644 index 3d49b3cac..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_classic_write_initial_wrong_card_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_mf_classic_write_initial_wrong_card_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Use The Source Card!"); - widget_add_string_multiline_element( - widget, - 4, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Go to NFC Magic\napp if you want to\nwrite blanks"); - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - nfc_scene_mf_classic_write_initial_wrong_card_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_classic_write_initial_wrong_card_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(instance->scene_manager); - } - } - return consumed; -} - -void nfc_scene_mf_classic_write_initial_wrong_card_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c deleted file mode 100644 index 157d6ce1b..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "../nfc_app_i.h" - -#include - -enum { - NfcSceneMfUltralightWriteStateCardSearch, - NfcSceneMfUltralightWriteStateCardFound, -}; - -NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) { - furi_assert(context); - furi_assert(event.event_data); - furi_assert(event.protocol == NfcProtocolMfUltralight); - - NfcCommand command = NfcCommandContinue; - NfcApp* instance = context; - MfUltralightPollerEvent* mfu_event = event.event_data; - - if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) { - mfu_event->data->poller_mode = MfUltralightPollerModeWrite; - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); - } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { - mfu_event->data->auth_context.skip_auth = true; - } else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) { - mfu_event->data->write_data = - nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); - } else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard); - command = NfcCommandStop; - } else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); - command = NfcCommandStop; - } else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) { - command = NfcCommandStop; - } else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) { - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - command = NfcCommandStop; - } - return command; -} - -static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = - scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite); - - if(state == NfcSceneMfUltralightWriteStateCardSearch) { - popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); - popup_set_text( - instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - } else { - popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); - popup_set_icon(popup, 12, 23, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -void nfc_scene_mf_ultralight_write_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcEmulate); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightWrite, - NfcSceneMfUltralightWriteStateCardSearch); - nfc_scene_mf_ultralight_write_setup_view(instance); - - // Setup and start worker - FURI_LOG_D("WMFU", "Card searching..."); - instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); - nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance); - - nfc_blink_emulate_start(instance); -} - -bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightWrite, - NfcSceneMfUltralightWriteStateCardFound); - nfc_scene_mf_ultralight_write_setup_view(instance); - consumed = true; - } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard); - consumed = true; - } else if(event.event == NfcCustomEventPollerSuccess) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess); - consumed = true; - } else if(event.event == NfcCustomEventPollerFailure) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail); - consumed = true; - } - } - - return consumed; -} - -void nfc_scene_mf_ultralight_write_on_exit(void* context) { - NfcApp* instance = context; - - nfc_poller_stop(instance->poller); - nfc_poller_free(instance->poller); - - scene_manager_set_scene_state( - instance->scene_manager, - NfcSceneMfUltralightWrite, - NfcSceneMfUltralightWriteStateCardSearch); - // Clear view - popup_reset(instance->popup); - - nfc_blink_stop(instance); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c deleted file mode 100644 index fcfb5f2b0..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_fail.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_ultralight_write_fail_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_mf_ultralight_write_fail_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); - widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Card protected by\npassword, AUTH0\nor lock bits"); - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Finish", - nfc_scene_mf_ultralight_write_fail_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) { - bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); - uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu; - - return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id); -} - -bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance); - } - return consumed; -} - -void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c deleted file mode 100644 index bb34190d2..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_write_success.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) { - NfcApp* instance = context; - view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); -} - -void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { - NfcApp* instance = context; - dolphin_deed(DolphinDeedNfcSave); - - notification_message(instance->notifications, &sequence_success); - - Popup* popup = instance->popup; - popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); - popup_set_header(popup, "Successfully\nwritten", 5, 22, AlignLeft, AlignBottom); - popup_set_timeout(popup, 1500); - popup_set_context(popup, instance); - popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - -bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventViewExit) { - bool was_saved = - scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); - - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess); - } - } - return consumed; -} - -void nfc_scene_mf_ultralight_write_success_on_exit(void* context) { - NfcApp* instance = context; - - // Clear view - popup_reset(instance->popup); -} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c deleted file mode 100644 index bc34a45b4..000000000 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_wrong_card.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "../nfc_app_i.h" - -void nfc_scene_mf_ultralight_wrong_card_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_mf_ultralight_wrong_card_on_enter(void* context) { - NfcApp* instance = context; - Widget* widget = instance->widget; - - notification_message(instance->notifications, &sequence_error); - - widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); - widget_add_string_multiline_element( - widget, - 4, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Card of the same\ntype should be\n presented"); - //"Data management\nis only possible\nwith card of same type"); - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Retry", - nfc_scene_mf_ultralight_wrong_card_widget_callback, - instance); - - // Setup and start worker - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); -} - -bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) { - NfcApp* instance = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(instance->scene_manager); - } - } - return consumed; -} - -void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) { - NfcApp* instance = context; - - widget_reset(instance->widget); -} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index e9603afd1..ff30eb318 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -1,6 +1,12 @@ #include "../helpers/protocol_support/nfc_protocol_support.h" +#include "../nfc_app_i.h" void nfc_scene_read_on_enter(void* context) { + NfcApp* instance = context; + nfc_show_loading_popup(instance, true); + nfc_supported_cards_load_cache(instance->nfc_supported_cards); + nfc_show_loading_popup(instance, false); + nfc_protocol_support_on_enter(NfcProtocolSupportSceneRead, context); } diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index b6e8f8298..23343daf8 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -56,37 +56,25 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { + consumed = true; if(event.event == SubmenuIndexRead) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead); scene_manager_next_scene(nfc->scene_manager, NfcSceneDetect); dolphin_deed(DolphinDeedNfcRead); - consumed = true; } else if(event.event == SubmenuIndexDetectReader) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader); scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); - consumed = true; } else if(event.event == SubmenuIndexSaved) { - // Save the scene state explicitly in each branch, so that - // if the user cancels loading a file, the Saved menu item - // is properly reselected. - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexSaved); scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect); - consumed = true; } else if(event.event == SubmenuIndexExtraAction) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneStart, SubmenuIndexExtraAction); scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions); - consumed = true; } else if(event.event == SubmenuIndexAddManually) { - scene_manager_set_scene_state( - nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually); scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType); - consumed = true; } else if(event.event == SubmenuIndexDebug) { - scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug); scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug); - consumed = true; + } else { + consumed = false; + } + if(consumed) { + scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event); } } return consumed; diff --git a/applications/main/nfc/scenes/nfc_scene_write.c b/applications/main/nfc/scenes/nfc_scene_write.c new file mode 100644 index 000000000..a5f8fbb13 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_write.c @@ -0,0 +1,13 @@ +#include "../helpers/protocol_support/nfc_protocol_support.h" + +void nfc_scene_write_on_enter(void* context) { + nfc_protocol_support_on_enter(NfcProtocolSupportSceneWrite, context); +} + +bool nfc_scene_write_on_event(void* context, SceneManagerEvent event) { + return nfc_protocol_support_on_event(NfcProtocolSupportSceneWrite, context, event); +} + +void nfc_scene_write_on_exit(void* context) { + nfc_protocol_support_on_exit(NfcProtocolSupportSceneWrite, context); +} diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 648e213c0..0668db531 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -7,6 +7,7 @@ struct Submenu { View* view; + FuriTimer* locked_timer; }; @@ -19,6 +20,7 @@ typedef struct { }; void* callback_context; bool has_extended_events; + bool locked; FuriString* locked_message; } SubmenuItem; @@ -68,6 +70,7 @@ typedef struct { FuriString* header; size_t position; size_t window_position; + bool locked_message_visible; bool is_vertical; } SubmenuModel; @@ -76,9 +79,9 @@ static void submenu_process_up(Submenu* submenu); static void submenu_process_down(Submenu* submenu); static void submenu_process_ok(Submenu* submenu, InputType input_type); -static size_t submenu_items_on_screen(bool header, bool vertical) { - size_t res = (vertical) ? 8 : 4; - return (header) ? res - 1 : res; +static size_t submenu_items_on_screen(SubmenuModel* model) { + size_t res = (model->is_vertical) ? 8 : 4; + return (furi_string_empty(model->header)) ? res : res - 1; } static void submenu_view_draw_callback(Canvas* canvas, void* _model) { @@ -101,9 +104,9 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { const size_t item_position = position - model->window_position; - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); - uint8_t y_offset = furi_string_empty(model->header) ? 0 : 16; + const size_t items_on_screen = submenu_items_on_screen(model); + uint8_t y_offset = furi_string_empty(model->header) ? 0 : item_height; + bool is_locked = SubmenuItemArray_cref(it)->locked; if(item_position < items_on_screen) { if(position == model->position) { @@ -119,7 +122,7 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); } - if(SubmenuItemArray_cref(it)->locked) { + if(is_locked) { canvas_draw_icon( canvas, item_width - 10, @@ -127,10 +130,8 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { &I_Lock_7x8); } - FuriString* disp_str; - disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); - elements_string_fit_width( - canvas, disp_str, item_width - (SubmenuItemArray_cref(it)->locked ? 21 : 11)); + FuriString* disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label); + elements_string_fit_width(canvas, disp_str, item_width - (is_locked ? 21 : 11)); canvas_draw_str( canvas, @@ -161,25 +162,14 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_draw_rframe(canvas, frame_x, frame_y, frame_width, frame_height, 3); canvas_draw_rframe(canvas, frame_x + 1, frame_y + 1, frame_width - 2, frame_height - 2, 2); - if(model->is_vertical) { - elements_multiline_text_aligned( - canvas, - 32, - 42, - AlignCenter, - AlignCenter, - furi_string_get_cstr( - SubmenuItemArray_get(model->items, model->position)->locked_message)); - } else { - elements_multiline_text_aligned( - canvas, - 84, - 32, - AlignCenter, - AlignCenter, - furi_string_get_cstr( - SubmenuItemArray_get(model->items, model->position)->locked_message)); - } + elements_multiline_text_aligned( + canvas, + (model->is_vertical) ? 32 : 84, + (model->is_vertical) ? 42 : 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + SubmenuItemArray_get(model->items, model->position)->locked_message)); } } @@ -195,8 +185,7 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { { locked_message_visible = model->locked_message_visible; }, false); - if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && - locked_message_visible) { + if(locked_message_visible && (event->type == InputTypeShort || event->type == InputTypeLong)) { with_view_model( submenu->view, SubmenuModel * model, { model->locked_message_visible = false; }, true); consumed = true; @@ -303,6 +292,9 @@ void submenu_add_lockable_item( SubmenuItem* item = NULL; furi_check(label); furi_check(submenu); + if(locked) { + furi_check(locked_message); + } with_view_model( submenu->view, @@ -366,6 +358,25 @@ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* lab true); } +void submenu_remove_item(Submenu* submenu, uint32_t index) { + furi_check(submenu); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + SubmenuItemArray_it_t it; + for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); + SubmenuItemArray_next(it)) { + if(index == SubmenuItemArray_cref(it)->index) { + SubmenuItemArray_remove(model->items, it); + break; + } + } + }, + true); +} + void submenu_reset(Submenu* submenu) { furi_check(submenu); view_set_orientation(submenu->view, ViewOrientationHorizontal); @@ -431,8 +442,7 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); if(items_size <= items_on_screen) { model->window_position = 0; @@ -451,8 +461,7 @@ void submenu_process_up(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); const size_t items_size = SubmenuItemArray_size(model->items); if(model->position > 0) { @@ -475,8 +484,7 @@ void submenu_process_down(Submenu* submenu) { submenu->view, SubmenuModel * model, { - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); const size_t items_size = SubmenuItemArray_size(model->items); if(model->position < items_size - 1) { @@ -504,7 +512,8 @@ void submenu_process_ok(Submenu* submenu, InputType input_type) { if(model->position < items_size) { item = SubmenuItemArray_get(model->items, model->position); } - if(item && item->locked) { + if(item && item->locked && + (input_type == InputTypeShort || input_type == InputTypeLong)) { model->locked_message_visible = true; furi_timer_start(submenu->locked_timer, furi_kernel_get_tick_frequency() * 3); } @@ -540,11 +549,9 @@ void submenu_set_header(Submenu* submenu, const char* header) { } void submenu_set_orientation(Submenu* submenu, ViewOrientation orientation) { - furi_assert(submenu); - const bool is_vertical = - (orientation == ViewOrientationVertical || orientation == ViewOrientationVerticalFlip) ? - true : - false; + furi_check(submenu); + const bool is_vertical = orientation == ViewOrientationVertical || + orientation == ViewOrientationVerticalFlip; view_set_orientation(submenu->view, orientation); @@ -558,8 +565,7 @@ void submenu_set_orientation(Submenu* submenu, ViewOrientation orientation) { // Need if _set_orientation is called after _set_selected_item size_t position = model->position; const size_t items_size = SubmenuItemArray_size(model->items); - const size_t items_on_screen = - submenu_items_on_screen(!furi_string_empty(model->header), model->is_vertical); + const size_t items_on_screen = submenu_items_on_screen(model); if(position >= items_size) { position = 0; diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index 750167543..64ba650ee 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -98,6 +98,14 @@ void submenu_add_item_ex( */ void submenu_change_item_label(Submenu* submenu, uint32_t index, const char* label); +/** Remove item from submenu + * + * @param submenu Submenu instance + * @param index menu item index, used for callback, may be + * the same with other items, first one is removed + */ +void submenu_remove_item(Submenu* submenu, uint32_t index); + /** Remove all items from submenu * * @param submenu Submenu instance @@ -120,13 +128,14 @@ uint32_t submenu_get_selected_item(Submenu* submenu); void submenu_set_selected_item(Submenu* submenu, uint32_t index); /** Set optional header for submenu + * Must be called before adding items OR after adding items and before set_selected_item() * * @param submenu Submenu instance * @param header header to set */ void submenu_set_header(Submenu* submenu, const char* header); -/** Set Orientation +/** Set submenu orientation * * @param submenu Submenu instance * @param orientation either vertical or horizontal diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 02b1f09f8..cce1fe94a 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -19,39 +19,48 @@ env.Append( File("protocols/iso14443_3b/iso14443_3b.h"), File("protocols/iso14443_4a/iso14443_4a.h"), File("protocols/iso14443_4b/iso14443_4b.h"), + File("protocols/iso15693_3/iso15693_3.h"), + File("protocols/felica/felica.h"), File("protocols/mf_ultralight/mf_ultralight.h"), File("protocols/mf_classic/mf_classic.h"), File("protocols/mf_plus/mf_plus.h"), File("protocols/mf_desfire/mf_desfire.h"), - File("protocols/emv/emv.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), - File("protocols/felica/felica.h"), + File("protocols/ntag4xx/ntag4xx.h"), + File("protocols/type_4_tag/type_4_tag.h"), + File("protocols/emv/emv.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), File("protocols/iso14443_4a/iso14443_4a_poller.h"), File("protocols/iso14443_4b/iso14443_4b_poller.h"), + File("protocols/iso15693_3/iso15693_3_poller.h"), + File("protocols/felica/felica_poller.h"), File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_plus/mf_plus_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/slix/slix_poller.h"), - File("protocols/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), - File("protocols/felica/felica_poller.h"), + File("protocols/ntag4xx/ntag4xx_poller.h"), + File("protocols/type_4_tag/type_4_tag_poller.h"), + File("protocols/emv/emv_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), + File("protocols/iso15693_3/iso15693_3_listener.h"), + File("protocols/felica/felica_listener.h"), File("protocols/mf_ultralight/mf_ultralight_listener.h"), File("protocols/mf_classic/mf_classic_listener.h"), - File("protocols/felica/felica_listener.h"), + File("protocols/slix/slix_listener.h"), + File("protocols/type_4_tag/type_4_tag_listener.h"), # Sync API File("protocols/iso14443_3a/iso14443_3a_poller_sync.h"), + File("protocols/felica/felica_poller_sync.h"), File("protocols/mf_ultralight/mf_ultralight_poller_sync.h"), File("protocols/mf_classic/mf_classic_poller_sync.h"), File("protocols/st25tb/st25tb_poller_sync.h"), - File("protocols/felica/felica_poller_sync.h"), # Misc File("helpers/nfc_util.h"), File("helpers/iso14443_crc.h"), diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index ef9baaabc..1d76f4bf3 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -14,6 +14,8 @@ #define ISO14443_4_BLOCK_PCB_MASK (0x03) #define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_I_MASK (1U << 1) +#define ISO14443_4_BLOCK_PCB_I_ZERO_MASK (7U << 5) #define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2) #define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3) #define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4) @@ -33,8 +35,14 @@ #define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) #define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) +#define ISO14443_4_BLOCK_CID_MASK (0x0F) + #define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask)) +#define ISO14443_4_BLOCK_PCB_IS_I_BLOCK(pcb) \ + (ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_MASK) && \ + (((pcb) & ISO14443_4_BLOCK_PCB_I_ZERO_MASK) == 0)) + #define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) @@ -47,14 +55,23 @@ #define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK) +#define ISO14443_4_LAYER_NAD_NOT_SUPPORTED ((uint8_t) - 1) +#define ISO14443_4_LAYER_NAD_NOT_SET ((uint8_t) - 2) + struct Iso14443_4Layer { uint8_t pcb; uint8_t pcb_prev; + + // Listener specific + uint8_t cid; + uint8_t nad; }; -static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance) { +static inline void iso14443_4_layer_update_pcb(Iso14443_4Layer* instance, bool toggle_num) { instance->pcb_prev = instance->pcb; - instance->pcb ^= (uint8_t)0x01; + if(toggle_num) { + instance->pcb ^= (uint8_t)0x01; + } } Iso14443_4Layer* iso14443_4_layer_alloc(void) { @@ -73,6 +90,9 @@ void iso14443_4_layer_reset(Iso14443_4Layer* instance) { furi_assert(instance); instance->pcb_prev = 0; instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; + + instance->cid = ISO14443_4_LAYER_CID_NOT_SUPPORTED; + instance->nad = ISO14443_4_LAYER_NAD_NOT_SUPPORTED; } void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) { @@ -96,7 +116,7 @@ void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool (CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB; } -void iso14443_4_layer_encode_block( +void iso14443_4_layer_encode_command( Iso14443_4Layer* instance, const BitBuffer* input_data, BitBuffer* block_data) { @@ -105,7 +125,7 @@ void iso14443_4_layer_encode_block( bit_buffer_append_byte(block_data, instance->pcb); bit_buffer_append(block_data, input_data); - iso14443_4_layer_update_pcb(instance); + iso14443_4_layer_update_pcb(instance, true); } static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) { @@ -113,7 +133,7 @@ static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_d return data[0]; } -bool iso14443_4_layer_decode_block( +bool iso14443_4_layer_decode_response( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data) { @@ -127,7 +147,7 @@ bool iso14443_4_layer_decode_block( ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); instance->pcb &= ISO14443_4_BLOCK_PCB_MASK; - iso14443_4_layer_update_pcb(instance); + iso14443_4_layer_update_pcb(instance, true); } else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) { const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && @@ -147,7 +167,7 @@ bool iso14443_4_layer_decode_block( return ret; } -Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( +Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data) { @@ -199,3 +219,100 @@ Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( return ret; } + +void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid) { + instance->cid = cid; +} + +void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad) { + instance->nad = nad ? 0 : ISO14443_4_LAYER_NAD_NOT_SUPPORTED; +} + +Iso14443_4LayerResult iso14443_4_layer_decode_command( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + uint8_t prologue_len = 0; + instance->pcb = bit_buffer_get_byte(input_data, prologue_len++); + + if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb)) { + if(instance->pcb & ISO14443_4_BLOCK_PCB_I_CID_MASK) { + const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) & + ISO14443_4_BLOCK_CID_MASK; + if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) { + return Iso14443_4LayerResultSkip; + } + } else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) { + return Iso14443_4LayerResultSkip; + } + // TODO: properly handle block chaining + if(instance->pcb & ISO14443_4_BLOCK_PCB_I_NAD_MASK) { + if(instance->nad == ISO14443_4_LAYER_NAD_NOT_SUPPORTED) { + return Iso14443_4LayerResultSkip; + } + instance->nad = bit_buffer_get_byte(input_data, prologue_len++); + } + bit_buffer_copy_right(block_data, input_data, prologue_len); + iso14443_4_layer_update_pcb(instance, false); + return Iso14443_4LayerResultData; + + } else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb)) { + if(instance->pcb & ISO14443_4_BLOCK_PCB_S_CID_MASK) { + const uint8_t cid = bit_buffer_get_byte(input_data, prologue_len++) & + ISO14443_4_BLOCK_CID_MASK; + if(instance->cid == ISO14443_4_LAYER_CID_NOT_SUPPORTED || cid != instance->cid) { + return Iso14443_4LayerResultSkip; + } + } else if(instance->cid != ISO14443_4_LAYER_CID_NOT_SUPPORTED && instance->cid != 0) { + return Iso14443_4LayerResultSkip; + } + if((instance->pcb & ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) == 0) { + // DESELECT + bit_buffer_copy(block_data, input_data); + return Iso14443_4LayerResultSend | Iso14443_4LayerResultHalt; + } else { + // WTX ACK or wrong value + return Iso14443_4LayerResultSkip; + } + + } else if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb)) { + // TODO: properly handle R blocks while chaining + iso14443_4_layer_update_pcb(instance, true); + instance->pcb |= ISO14443_4_BLOCK_PCB_R_NACK_MASK; + bit_buffer_reset(block_data); + bit_buffer_append_byte(block_data, instance->pcb); + iso14443_4_layer_update_pcb(instance, false); + return Iso14443_4LayerResultSend; + } + return Iso14443_4LayerResultSkip; +} + +bool iso14443_4_layer_encode_response( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data) { + furi_assert(instance); + + if(ISO14443_4_BLOCK_PCB_IS_I_BLOCK(instance->pcb_prev)) { + bit_buffer_append_byte(block_data, 0x00); + if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_CID_MASK) { + bit_buffer_append_byte(block_data, instance->cid); + } + // TODO: properly handle block chaining and related R block responses + if(instance->pcb_prev & ISO14443_4_BLOCK_PCB_I_NAD_MASK && + instance->nad != ISO14443_4_LAYER_NAD_NOT_SET) { + bit_buffer_append_byte(block_data, instance->nad); + instance->nad = ISO14443_4_LAYER_NAD_NOT_SET; + } else { + instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_NAD_MASK; + } + instance->pcb &= ~ISO14443_4_BLOCK_PCB_I_CHAIN_MASK; + bit_buffer_set_byte(block_data, 0, instance->pcb); + bit_buffer_append(block_data, input_data); + iso14443_4_layer_update_pcb(instance, false); + return true; + } + return false; +} diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 9daf25775..1e9ac713f 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -19,21 +19,47 @@ void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present); void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present); -void iso14443_4_layer_encode_block( +// Poller mode + +void iso14443_4_layer_encode_command( Iso14443_4Layer* instance, const BitBuffer* input_data, BitBuffer* block_data); -bool iso14443_4_layer_decode_block( +bool iso14443_4_layer_decode_response( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data); -Iso14443_4aError iso14443_4_layer_decode_block_pwt_ext( +Iso14443_4aError iso14443_4_layer_decode_response_pwt_ext( Iso14443_4Layer* instance, BitBuffer* output_data, const BitBuffer* block_data); +// Listener mode + +typedef enum { + Iso14443_4LayerResultSkip = (0), + Iso14443_4LayerResultData = (1 << 1), + Iso14443_4LayerResultSend = (1 << 2), + Iso14443_4LayerResultHalt = (1 << 3), +} Iso14443_4LayerResult; + +Iso14443_4LayerResult iso14443_4_layer_decode_command( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +bool iso14443_4_layer_encode_response( + Iso14443_4Layer* instance, + const BitBuffer* input_data, + BitBuffer* block_data); + +#define ISO14443_4_LAYER_CID_NOT_SUPPORTED ((uint8_t) - 1) +void iso14443_4_layer_set_cid(Iso14443_4Layer* instance, uint8_t cid); + +void iso14443_4_layer_set_nad_supported(Iso14443_4Layer* instance, bool nad); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/helpers/nxp_native_command.c b/lib/nfc/helpers/nxp_native_command.c new file mode 100644 index 000000000..d5d535891 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command.c @@ -0,0 +1,115 @@ +#include "nxp_native_command.h" + +#include + +#define TAG "NxpNativeCommand" + +Iso14443_4aError nxp_native_command_iso14443_4a_poller( + Iso14443_4aPoller* iso14443_4a_poller, + NxpNativeCommandStatus* status_code, + const BitBuffer* input_buffer, + BitBuffer* result_buffer, + NxpNativeCommandMode command_mode, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_check(iso14443_4a_poller); + furi_check(tx_buffer); + furi_check(rx_buffer); + furi_check(input_buffer); + furi_check(result_buffer); + furi_check(command_mode < NxpNativeCommandModeMAX); + + Iso14443_4aError error = Iso14443_4aErrorNone; + *status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + + do { + bit_buffer_reset(tx_buffer); + if(command_mode == NxpNativeCommandModePlain) { + bit_buffer_append(tx_buffer, input_buffer); + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA); + bit_buffer_append_byte(tx_buffer, bit_buffer_get_byte(input_buffer, 0)); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2); + if(bit_buffer_get_size_bytes(input_buffer) > 1) { + bit_buffer_append_byte(tx_buffer, bit_buffer_get_size_bytes(input_buffer) - 1); + bit_buffer_append_right(tx_buffer, input_buffer, 1); + } + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE); + } + + bit_buffer_reset(rx_buffer); + error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer); + + if(error != Iso14443_4aErrorNone) { + break; + } + + bit_buffer_reset(tx_buffer); + if(command_mode == NxpNativeCommandModePlain) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME); + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_CLA); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P1); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_P2); + bit_buffer_append_byte(tx_buffer, NXP_NATIVE_COMMAND_ISO_LE); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buffer); + *status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + bit_buffer_reset(result_buffer); + if(command_mode == NxpNativeCommandModePlain && response_len >= sizeof(uint8_t)) { + *status_code = bit_buffer_get_byte(rx_buffer, 0); + if(response_len > sizeof(uint8_t)) { + bit_buffer_copy_right(result_buffer, rx_buffer, sizeof(uint8_t)); + } + } else if( + command_mode == NxpNativeCommandModeIsoWrapped && + response_len >= 2 * sizeof(uint8_t) && + bit_buffer_get_byte(rx_buffer, response_len - 2) == NXP_NATIVE_COMMAND_ISO_SW1) { + *status_code = bit_buffer_get_byte(rx_buffer, response_len - 1); + if(response_len > 2 * sizeof(uint8_t)) { + bit_buffer_copy_left(result_buffer, rx_buffer, response_len - 2 * sizeof(uint8_t)); + } + } + + while(*status_code == NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME) { + bit_buffer_reset(rx_buffer); + error = iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer); + + if(error != Iso14443_4aErrorNone) { + break; + } + + const size_t rx_size = bit_buffer_get_size_bytes(rx_buffer); + const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(result_buffer) - + bit_buffer_get_size_bytes(result_buffer); + + if(command_mode == NxpNativeCommandModePlain) { + *status_code = rx_size >= 1 ? bit_buffer_get_byte(rx_buffer, 0) : + NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + if(rx_size <= rx_capacity_remaining + 1) { + bit_buffer_append_right(result_buffer, rx_buffer, sizeof(uint8_t)); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); + } + } else if(command_mode == NxpNativeCommandModeIsoWrapped) { + if(rx_size >= 2 && + bit_buffer_get_byte(rx_buffer, rx_size - 2) == NXP_NATIVE_COMMAND_ISO_SW1) { + *status_code = bit_buffer_get_byte(rx_buffer, rx_size - 1); + } else { + *status_code = NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR; + } + if(rx_size <= rx_capacity_remaining + 2) { + bit_buffer_set_size_bytes(rx_buffer, rx_size - 2); + bit_buffer_append(result_buffer, rx_buffer); + } else { + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 2); + } + } + } + } while(false); + + return error; +} diff --git a/lib/nfc/helpers/nxp_native_command.h b/lib/nfc/helpers/nxp_native_command.h new file mode 100644 index 000000000..61677a3f6 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command.h @@ -0,0 +1,92 @@ +#pragma once + +#include "nxp_native_command_mode.h" + +#include + +// ISO 7816 command wrapping +#define NXP_NATIVE_COMMAND_ISO_CLA (0x90) +#define NXP_NATIVE_COMMAND_ISO_P1 (0x00) +#define NXP_NATIVE_COMMAND_ISO_P2 (0x00) +#define NXP_NATIVE_COMMAND_ISO_LE (0x00) +// ISO 7816 status wrapping +#define NXP_NATIVE_COMMAND_ISO_SW1 (0x91) + +// Successful operation +#define NXP_NATIVE_COMMAND_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define NXP_NATIVE_COMMAND_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define NXP_NATIVE_COMMAND_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define NXP_NATIVE_COMMAND_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define NXP_NATIVE_COMMAND_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define NXP_NATIVE_COMMAND_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define NXP_NATIVE_COMMAND_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define NXP_NATIVE_COMMAND_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define NXP_NATIVE_COMMAND_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define NXP_NATIVE_COMMAND_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Currently not allowed to authenticate. Keep trying until full delay is spent +#define NXP_NATIVE_COMMAND_STATUS_STATUS_AUTHENTICATION_DELAY (0xAD) +// Current authentication status does not allow the requested command +#define NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define NXP_NATIVE_COMMAND_STATUS_ADDITIONAL_FRAME (0xAF) +// Attempt to read/write data from/to beyond the file's/record's limits +// Attempt to exceed the limits of a value file. +#define NXP_NATIVE_COMMAND_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define NXP_NATIVE_COMMAND_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define NXP_NATIVE_COMMAND_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define NXP_NATIVE_COMMAND_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define NXP_NATIVE_COMMAND_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define NXP_NATIVE_COMMAND_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define NXP_NATIVE_COMMAND_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define NXP_NATIVE_COMMAND_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define NXP_NATIVE_COMMAND_STATUS_FILE_INTEGRITY_ERROR (0xF1) + +typedef uint8_t NxpNativeCommandStatus; + +/** + * @brief Transmit and receive NXP Native Command chunks in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The result_buffer will be filled with any data received as a response to data + * sent from input_buffer, with a timeout defined by the fwt parameter. + * + * The tx_buffer and rx_buffer are used as working areas to handle individual + * command chunks and responses. + * + * @param[in, out] iso14443_4a_poller pointer to the instance to be used in the transaction. + * @param[out] status_code pointer to a status variable to hold the result of the operation. + * @param[in] input_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] result_buffer pointer to the buffer to be filled with received data. + * @param[in] command_mode what command formatting mode to use for the transaction. + * @param[in, out] tx_buffer pointer to the buffer for command construction. + * @param[in, out] rx_buffer pointer to the buffer for response handling. + * @return Iso14443_4aErrorNone and STATUS_OPERATION_OK on success, an error code on failure. + */ +Iso14443_4aError nxp_native_command_iso14443_4a_poller( + Iso14443_4aPoller* iso14443_4a_poller, + NxpNativeCommandStatus* status_code, + const BitBuffer* input_buffer, + BitBuffer* result_buffer, + NxpNativeCommandMode command_mode, + BitBuffer* tx_buffer, + BitBuffer* rx_buffer); diff --git a/lib/nfc/helpers/nxp_native_command_mode.h b/lib/nfc/helpers/nxp_native_command_mode.h new file mode 100644 index 000000000..7684b4d48 --- /dev/null +++ b/lib/nfc/helpers/nxp_native_command_mode.h @@ -0,0 +1,11 @@ +#pragma once + +/** + * @brief Enumeration of possible command modes. + */ +typedef enum { + NxpNativeCommandModePlain, /**< Plain native commands. */ + NxpNativeCommandModeIsoWrapped, /**< ISO 7816-wrapped commands. */ + + NxpNativeCommandModeMAX, +} NxpNativeCommandMode; diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 4f4358711..90e65b282 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -646,33 +646,6 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } -NfcError nfc_iso15693_detect_mode(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_detect_mode(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - -NfcError nfc_iso15693_force_1outof4(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof4(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - -NfcError nfc_iso15693_force_1outof256(Nfc* instance) { - furi_check(instance); - - FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof256(); - NfcError ret = nfc_process_hal_error(error); - - return ret; -} - NfcError nfc_felica_listener_set_sensf_res_data( Nfc* instance, const uint8_t* idm, diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index ebd29dc4b..8fbf90d1f 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -380,30 +380,6 @@ NfcError nfc_felica_listener_set_sensf_res_data( */ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); -/** - * @brief Set ISO15693 parser mode to autodetect - * -* @param[in,out] instance pointer to the instance to be configured. - * @returns NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_detect_mode(Nfc* instance); - -/** - * @brief Set ISO15693 parser mode to 1OutOf4, disables autodetection - * - * @param[in,out] instance pointer to the instance to be configured. - * @return NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_force_1outof4(Nfc* instance); - -/** - * @brief Set ISO15693 parser mode to 1OutOf256, disables autodetection - * - * @param[in,out] instance pointer to the instance to be configured. - * @return NfcErrorNone on success, any other error code on failure. -*/ -NfcError nfc_iso15693_force_1outof256(Nfc* instance); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h index 8a550ca0a..7b2a4424d 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h @@ -26,6 +26,47 @@ typedef struct { Iso14443_3aListenerEventData* data; } Iso14443_3aListenerEvent; +/** + * @brief Transmit Iso14443_3a frames in listener mode. + * + * Must ONLY be used inside the callback function. + * + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError + iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); + +/** + * @brief Transmit Iso14443_3a frames with custom parity bits in listener mode. + * + * Must ONLY be used inside the callback function. + * + * Custom parity bits must be set in the tx_buffer. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + +/** + * @brief Transmit Iso14443_3a standard frames in listener mode. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_3aErrorNone on success, an error code on failure. + */ +Iso14443_3aError iso14443_3a_listener_send_standard_frame( + Iso14443_3aListener* instance, + const BitBuffer* tx_buffer); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h index 0113a1cb8..ef9edf18f 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h @@ -26,17 +26,6 @@ struct Iso14443_3aListener { void* context; }; -Iso14443_3aError - iso14443_3a_listener_tx(Iso14443_3aListener* instance, const BitBuffer* tx_buffer); - -Iso14443_3aError iso14443_3a_listener_tx_with_custom_parity( - Iso14443_3aListener* instance, - const BitBuffer* tx_buffer); - -Iso14443_3aError iso14443_3a_listener_send_standard_frame( - Iso14443_3aListener* instance, - const BitBuffer* tx_buffer); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h index 9b0230975..f28b26ddd 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_i.h @@ -2,7 +2,8 @@ #include "iso14443_4a.h" -#define ISO14443_4A_CMD_READ_ATS (0xE0) +#define ISO14443_4A_CMD_READ_ATS (0xE0) +#define ISO14443_4A_READ_ATS_CID_MASK (0x0F) // ATS bit definitions #define ISO14443_4A_ATS_T0_TA1 (1U << 4) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 2519fb90c..95bae45d4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -14,7 +14,9 @@ static Iso14443_4aListener* Iso14443_4aListener* instance = malloc(sizeof(Iso14443_4aListener)); instance->iso14443_3a_listener = iso14443_3a_listener; instance->data = data; + instance->iso14443_4_layer = iso14443_4_layer_alloc(); + instance->rx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); instance->tx_buffer = bit_buffer_alloc(ISO14443_4A_LISTENER_BUF_SIZE); instance->iso14443_4a_event.data = &instance->iso14443_4a_event_data; @@ -27,13 +29,18 @@ static Iso14443_4aListener* static void iso14443_4a_listener_free(Iso14443_4aListener* instance) { furi_assert(instance); - furi_assert(instance->data); - furi_assert(instance->tx_buffer); + iso14443_4_layer_free(instance->iso14443_4_layer); + bit_buffer_free(instance->rx_buffer); bit_buffer_free(instance->tx_buffer); free(instance); } +static void iso14443_4a_listener_reset(Iso14443_4aListener* instance) { + instance->state = Iso14443_4aListenerStateIdle; + iso14443_4_layer_reset(instance->iso14443_4_layer); +} + static void iso14443_4a_listener_set_callback( Iso14443_4aListener* instance, NfcGenericCallback callback, @@ -68,20 +75,46 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) == Iso14443_4aErrorNone) { instance->state = Iso14443_4aListenerStateActive; + if(iso14443_4a_supports_frame_option( + instance->data, Iso14443_4aFrameOptionCid)) { + const uint8_t cid = bit_buffer_get_byte(rx_buffer, 1) & + ISO14443_4A_READ_ATS_CID_MASK; + iso14443_4_layer_set_cid(instance->iso14443_4_layer, cid); + } + iso14443_4_layer_set_nad_supported( + instance->iso14443_4_layer, + iso14443_4a_supports_frame_option( + instance->data, Iso14443_4aFrameOptionNad)); } } } else { - instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; - instance->iso14443_4a_event.data->buffer = rx_buffer; + Iso14443_4LayerResult status = iso14443_4_layer_decode_command( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); + if(status & Iso14443_4LayerResultSend) { + iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->rx_buffer); + } + if(status & Iso14443_4LayerResultHalt) { + iso14443_4a_listener_reset(instance); + if(instance->callback) { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeHalted; + instance->callback(instance->generic_event, instance->context); + } + command = NfcCommandSleep; + } + if(status & Iso14443_4LayerResultData) { + instance->iso14443_4a_event.type = Iso14443_4aListenerEventTypeReceivedData; + instance->iso14443_4a_event.data->buffer = instance->rx_buffer; - if(instance->callback) { - command = instance->callback(instance->generic_event, instance->context); + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } } } } else if( iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { - instance->state = Iso14443_4aListenerStateIdle; + iso14443_4a_listener_reset(instance); instance->iso14443_4a_event.type = iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted ? diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h index 04f0b197a..ff4bad7e4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -25,6 +25,18 @@ typedef struct { Iso14443_4aListenerEventData* data; } Iso14443_4aListenerEvent; +/** + * @brief Transmit Iso14443_4a blocks in listener mode. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError + iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c index 8590c22ad..6ea5ae5e4 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.c @@ -30,3 +30,17 @@ Iso14443_4aError instance->iso14443_3a_listener, instance->tx_buffer); return iso14443_4a_process_error(error); } + +Iso14443_4aError + iso14443_4a_listener_send_block(Iso14443_4aListener* instance, const BitBuffer* tx_buffer) { + bit_buffer_reset(instance->tx_buffer); + + if(!iso14443_4_layer_encode_response( + instance->iso14443_4_layer, tx_buffer, instance->tx_buffer)) { + return Iso14443_4aErrorProtocol; + } + + const Iso14443_3aError error = iso14443_3a_listener_send_standard_frame( + instance->iso14443_3a_listener, instance->tx_buffer); + return iso14443_4a_process_error(error); +} diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h index d4e884f6f..c0ed6d1cb 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener_i.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "iso14443_4a_listener.h" #include "iso14443_4a_i.h" @@ -17,8 +18,10 @@ typedef enum { struct Iso14443_4aListener { Iso14443_3aListener* iso14443_3a_listener; Iso14443_4aData* data; + Iso14443_4Layer* iso14443_4_layer; Iso14443_4aListenerState state; + BitBuffer* rx_buffer; BitBuffer* tx_buffer; NfcGenericEvent generic_event; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a2342a92d..97c512205 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -65,7 +65,7 @@ Iso14443_4aError iso14443_4a_poller_send_block( furi_check(rx_buffer); bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4aError error = Iso14443_4aErrorNone; @@ -106,7 +106,7 @@ Iso14443_4aError iso14443_4a_poller_send_block( } while(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX)); } - if(!iso14443_4_layer_decode_block( + if(!iso14443_4_layer_decode_response( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4aErrorProtocol; break; @@ -155,7 +155,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( uint8_t attempts_left = ISO14443_4A_SEND_BLOCK_MAX_ATTEMPTS; bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4aError error = Iso14443_4aErrorNone; @@ -180,7 +180,7 @@ Iso14443_4aError iso14443_4a_poller_send_block_pwt_ext( break; } else { - error = iso14443_4_layer_decode_block_pwt_ext( + error = iso14443_4_layer_decode_response_pwt_ext( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer); if(error == Iso14443_4aErrorSendExtra) { if(--attempts_left == 0) break; diff --git a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c index da82e1417..2db2da9c1 100644 --- a/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c +++ b/lib/nfc/protocols/iso14443_4b/iso14443_4b_poller_i.c @@ -24,7 +24,7 @@ Iso14443_4bError iso14443_4b_poller_send_block( furi_check(rx_buffer); bit_buffer_reset(instance->tx_buffer); - iso14443_4_layer_encode_block(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); + iso14443_4_layer_encode_command(instance->iso14443_4_layer, tx_buffer, instance->tx_buffer); Iso14443_4bError error = Iso14443_4bErrorNone; @@ -36,7 +36,7 @@ Iso14443_4bError iso14443_4b_poller_send_block( error = iso14443_4b_process_error(iso14443_3b_error); break; - } else if(!iso14443_4_layer_decode_block( + } else if(!iso14443_4_layer_decode_response( instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4bErrorProtocol; break; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index ec60b336b..cb5c6cac8 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -17,6 +17,13 @@ extern "C" { #define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F) #define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5) +#define MF_DESFIRE_CMD_CREATE_APPLICATION (0xCA) +#define MF_DESFIRE_CMD_CREATE_STD_DATA_FILE (0xCD) +#define MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE (0xCB) +#define MF_DESFIRE_CMD_CREATE_VALUE_FILE (0xCC) +#define MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE (0xC1) +#define MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE (0xC0) + #define MF_DESFIRE_CMD_READ_DATA (0xBD) #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index eba9c4312..b90ef8ccf 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -60,7 +60,7 @@ bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { bit_buffer_write_bytes(buf, data, sizeof(MfDesfireVersion)); } - return can_parse; + return can_parse && (data->hw_type & 0x0F) == 0x01; } bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* buf) { @@ -81,17 +81,17 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu return can_parse; } -bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { - typedef struct FURI_PACKED { - bool is_master_key_changeable : 1; - bool is_free_directory_list : 1; - bool is_free_create_delete : 1; - bool is_config_changeable : 1; - uint8_t change_key_id : 4; - uint8_t max_keys : 4; - uint8_t flags : 4; - } MfDesfireKeySettingsLayout; +typedef struct FURI_PACKED { + bool is_master_key_changeable : 1; + bool is_free_directory_list : 1; + bool is_free_create_delete : 1; + bool is_config_changeable : 1; + uint8_t change_key_id : 4; + uint8_t max_keys : 4; + uint8_t flags : 4; +} MfDesfireKeySettingsLayout; +bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeySettingsLayout); if(can_parse) { @@ -111,6 +111,21 @@ bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* return can_parse; } +void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf) { + MfDesfireKeySettingsLayout layout; + + layout.is_master_key_changeable = data->is_master_key_changeable; + layout.is_free_directory_list = data->is_free_directory_list; + layout.is_free_create_delete = data->is_free_create_delete; + layout.is_config_changeable = data->is_config_changeable; + + layout.change_key_id = data->change_key_id; + layout.max_keys = data->max_keys; + layout.flags = data->flags; + + bit_buffer_append_bytes(buf, (uint8_t*)&layout, sizeof(MfDesfireKeySettingsLayout)); +} + bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireKeyVersion); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 921bbb9de..ac572ae58 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -2,55 +2,11 @@ #include "mf_desfire.h" +#include + #define MF_DESFIRE_FFF_PICC_PREFIX "PICC" #define MF_DESFIRE_FFF_APP_PREFIX "Application" -// Successful operation -#define MF_DESFIRE_STATUS_OPERATION_OK (0x00) -// No changes done to backup files, CommitTransaction / AbortTransaction not necessary -#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C) -// Insufficient NV-Memory to complete command -#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E) -// Command code not supported -#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C) -// CRC or MAC does not match data Padding bytes not valid -#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E) -// Invalid key number specified -#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40) -// Length of command string invalid -#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E) -// Current configuration / status does not allow the requested command -#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D) -// Value of the parameter(s) invalid -#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E) -// Requested AID not present on PICC -#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0) -// Unrecoverable error within application, application will be disabled -#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1) -// Current authentication status does not allow the requested command -#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE) -// Additional data frame is expected to be sent -#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF) -// Attempt to read/write data from/to beyond the file's/record's limits -// Attempt to exceed the limits of a value file. -#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE) -// Unrecoverable error within PICC, PICC will be disabled -#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1) -// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD -#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA) -// PICC was disabled by an unrecoverable error -#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD) -// Number of Applications limited to 28, no additional CreateApplication possible -#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE) -// Creation of file/application failed because file/application with same number already exists -#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE) -// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated -#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE) -// Specified file number does not exist -#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0) -// Unrecoverable error within file, file will be disabled -#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1) - // SimpleArray configurations extern const SimpleArrayConfig mf_desfire_key_version_array_config; @@ -68,6 +24,8 @@ bool mf_desfire_free_memory_parse(MfDesfireFreeMemory* data, const BitBuffer* bu bool mf_desfire_key_settings_parse(MfDesfireKeySettings* data, const BitBuffer* buf); +void mf_desfire_key_settings_dump(const MfDesfireKeySettings* data, BitBuffer* buf); + bool mf_desfire_key_version_parse(MfDesfireKeyVersion* data, const BitBuffer* buf); bool mf_desfire_application_id_parse( diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index 45e5a27f9..e9c5e3b6d 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -251,8 +251,7 @@ static bool mf_desfire_poller_detect(NfcGenericEvent event, void* context) { MfDesfireError error = mf_desfire_poller_read_key_version(instance, 0, &key_version); if(error != MfDesfireErrorNone) break; - MfDesfireVersion version = {}; - error = mf_desfire_poller_read_version(instance, &version); + error = mf_desfire_poller_read_version(instance, &instance->data->version); if(error != MfDesfireErrorNone) break; protocol_detected = true; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h index 707df42cd..4010938e5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.h @@ -3,6 +3,7 @@ #include "mf_desfire.h" #include +#include #ifdef __cplusplus extern "C" { @@ -38,6 +39,16 @@ typedef struct { MfDesfirePollerEventData* data; /**< Pointer to event specific data. */ } MfDesfirePollerEvent; +/** + * @brief Change command mode used in poller mode. + * + * @param[in, out] instance pointer to the instance to affect. + * @param[in] command_mode command mode to use in further communication with the card. + */ +void mf_desfire_poller_set_command_mode( + MfDesfirePoller* instance, + NxpNativeCommandMode command_mode); + /** * @brief Transmit and receive MfDesfire chunks in poller mode. * @@ -51,11 +62,16 @@ typedef struct { * @param[out] rx_buffer pointer to the buffer to be filled with received data. * @return MfDesfireErrorNone on success, an error code on failure. */ -MfDesfireError mf_desfire_send_chunks( +MfDesfireError mf_desfire_poller_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +/** + * @warning deprecated, use mf_desfire_poller_send_chunks instead + */ +#define mf_desfire_send_chunks mf_desfire_poller_send_chunks + /** * @brief Read MfDesfire card version. * @@ -187,6 +203,44 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( const SimpleArray* file_ids, SimpleArray* data); +/** + * @brief Create Application on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id pointer to the application id for the new application. + * @param[in] key_settings pointer to the key settings for the new application. + * @param[in] iso_df_id optional iso identifier for the new application. + * @param[in] iso_df_name optional iso name for the new application. + * @param[in] iso_df_name_len length of the optional iso application name. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_create_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id, + const MfDesfireKeySettings* key_settings, + uint16_t iso_df_id, + const uint8_t* iso_df_name, + uint8_t iso_df_name_len); + +/** + * @brief Create File on MfDesfire card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] id file id for the new file. + * @param[in] data pointer to the file settings for the new file. + * @param[in] iso_ef_id optional iso identifier for the new file. + * @return MfDesfireErrorNone on success, an error code on failure. + */ +MfDesfireError mf_desfire_poller_create_file( + MfDesfirePoller* instance, + MfDesfireFileId id, + const MfDesfireFileSettings* data, + uint16_t iso_ef_id); + /** * @brief Read file data on MfDesfire card. * diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 8b57fcc4c..a44395c60 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -1,6 +1,7 @@ #include "mf_desfire_poller_i.h" #include +#include #include "mf_desfire_i.h" @@ -21,76 +22,48 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { switch(status_code) { - case MF_DESFIRE_STATUS_OPERATION_OK: + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: return MfDesfireErrorNone; - case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + case NXP_NATIVE_COMMAND_STATUS_AUTHENTICATION_ERROR: return MfDesfireErrorAuthentication; - case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE: + case NXP_NATIVE_COMMAND_STATUS_ILLEGAL_COMMAND_CODE: return MfDesfireErrorCommandNotSupported; default: return MfDesfireErrorProtocol; } } -MfDesfireError mf_desfire_send_chunks( +void mf_desfire_poller_set_command_mode( + MfDesfirePoller* instance, + NxpNativeCommandMode command_mode) { + furi_check(instance); + furi_check(instance->state == MfDesfirePollerStateIdle); + furi_check(command_mode < NxpNativeCommandModeMAX); + + instance->command_mode = command_mode; +} + +MfDesfireError mf_desfire_poller_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { furi_check(instance); - furi_check(instance->iso14443_4a_poller); - furi_check(instance->tx_buffer); - furi_check(instance->rx_buffer); - furi_check(tx_buffer); - furi_check(rx_buffer); - MfDesfireError error = MfDesfireErrorNone; + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + instance->command_mode, + instance->tx_buffer, + instance->rx_buffer); - do { - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_desfire_process_error(iso14443_4a_error); - break; - } - - bit_buffer_reset(instance->tx_buffer); - bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME); - - if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { - bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - } else { - bit_buffer_reset(rx_buffer); - } - - while( - bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) { - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_desfire_process_error(iso14443_4a_error); - break; - } - - const size_t rx_size = bit_buffer_get_size_bytes(instance->rx_buffer); - const size_t rx_capacity_remaining = - bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - - if(rx_size <= rx_capacity_remaining + 1) { - bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - } else { - FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); - } - } - } while(false); - - if(error == MfDesfireErrorNone) { - uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0); - error = mf_desfire_process_status_code(err_code); + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return mf_desfire_process_error(iso14443_4a_error); } - return error; + return mf_desfire_process_status_code(status_code); } MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfireVersion* data) { @@ -102,7 +75,8 @@ MfDesfireError mf_desfire_poller_read_version(MfDesfirePoller* instance, MfDesfi MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -124,7 +98,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -146,7 +121,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -170,7 +146,7 @@ MfDesfireError mf_desfire_poller_read_key_version( bit_buffer_set_byte(instance->input_buffer, 1, key_num); MfDesfireError error = - mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error == MfDesfireErrorNone) { if(!mf_desfire_key_version_parse(data, instance->result_buffer)) { error = MfDesfireErrorProtocol; @@ -210,7 +186,8 @@ MfDesfireError MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -243,7 +220,7 @@ MfDesfireError mf_desfire_poller_select_application( instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); MfDesfireError error = - mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); return error; } @@ -258,7 +235,8 @@ MfDesfireError mf_desfire_poller_read_file_ids(MfDesfirePoller* instance, Simple MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -293,7 +271,8 @@ MfDesfireError mf_desfire_poller_read_file_settings( MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; @@ -329,6 +308,108 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( return error; } +MfDesfireError mf_desfire_poller_create_application( + MfDesfirePoller* instance, + const MfDesfireApplicationId* id, + const MfDesfireKeySettings* key_settings, + uint16_t iso_df_id, + const uint8_t* iso_df_name, + uint8_t iso_df_name_len) { + furi_check(instance); + furi_check(key_settings); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_CREATE_APPLICATION); + bit_buffer_append_bytes( + instance->input_buffer, (const uint8_t*)id, sizeof(MfDesfireApplicationId)); + mf_desfire_key_settings_dump(key_settings, instance->input_buffer); + + if(iso_df_name && iso_df_name_len) { + uint8_t ks2_pos = bit_buffer_get_size_bytes(instance->input_buffer) - 1; + uint8_t ks2 = bit_buffer_get_byte(instance->input_buffer, ks2_pos); + ks2 |= (1 << 5); // Mark file id present + bit_buffer_set_byte(instance->input_buffer, ks2_pos, ks2); + + uint8_t iso_df_id_le[sizeof(iso_df_id)]; + bit_lib_num_to_bytes_le(iso_df_id, sizeof(iso_df_id_le), iso_df_id_le); + bit_buffer_append_bytes(instance->input_buffer, iso_df_id_le, sizeof(iso_df_id_le)); + + bit_buffer_append_bytes(instance->input_buffer, iso_df_name, iso_df_name_len); + } + + MfDesfireError error = + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + +MfDesfireError mf_desfire_poller_create_file( + MfDesfirePoller* instance, + MfDesfireFileId id, + const MfDesfireFileSettings* data, + uint16_t iso_ef_id) { + furi_check(instance); + furi_check(data); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte( + instance->input_buffer, + data->type == MfDesfireFileTypeStandard ? MF_DESFIRE_CMD_CREATE_STD_DATA_FILE : + data->type == MfDesfireFileTypeBackup ? MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE : + data->type == MfDesfireFileTypeValue ? MF_DESFIRE_CMD_CREATE_VALUE_FILE : + data->type == MfDesfireFileTypeLinearRecord ? MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE : + data->type == MfDesfireFileTypeCyclicRecord ? MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE : + 0x00); + bit_buffer_append_byte(instance->input_buffer, id); + if(iso_ef_id) { + uint8_t iso_ef_id_le[sizeof(iso_ef_id)]; + bit_lib_num_to_bytes_le(iso_ef_id, sizeof(iso_ef_id_le), iso_ef_id_le); + bit_buffer_append_bytes(instance->input_buffer, iso_ef_id_le, sizeof(iso_ef_id_le)); + } + bit_buffer_append_byte(instance->input_buffer, data->comm); + bit_buffer_append_bytes( + instance->input_buffer, + (const uint8_t*)data->access_rights, + sizeof(MfDesfireFileAccessRights) * data->access_rights_len); + + if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { + uint8_t data_size_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->data.size, sizeof(data_size_le), data_size_le); + bit_buffer_append_bytes(instance->input_buffer, data_size_le, sizeof(data_size_le)); + + } else if(data->type == MfDesfireFileTypeValue) { + uint8_t lo_limit_le[sizeof(data->value.lo_limit)]; + bit_lib_num_to_bytes_le(data->value.lo_limit, sizeof(lo_limit_le), lo_limit_le); + bit_buffer_append_bytes(instance->input_buffer, lo_limit_le, sizeof(lo_limit_le)); + + uint8_t hi_limit_le[sizeof(data->value.hi_limit)]; + bit_lib_num_to_bytes_le(data->value.hi_limit, sizeof(hi_limit_le), hi_limit_le); + bit_buffer_append_bytes(instance->input_buffer, hi_limit_le, sizeof(hi_limit_le)); + + uint8_t value_le[sizeof(data->value.limited_credit_value)]; + bit_lib_num_to_bytes_le(data->value.limited_credit_value, sizeof(value_le), value_le); + bit_buffer_append_bytes(instance->input_buffer, value_le, sizeof(value_le)); + + bit_buffer_append_byte(instance->input_buffer, data->value.limited_credit_enabled); + + } else if( + data->type == MfDesfireFileTypeLinearRecord || + data->type == MfDesfireFileTypeCyclicRecord) { + uint8_t record_size_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->record.size, sizeof(record_size_le), record_size_le); + bit_buffer_append_bytes(instance->input_buffer, record_size_le, sizeof(record_size_le)); + + uint8_t record_max_le[3 * sizeof(uint8_t)]; + bit_lib_num_to_bytes_le(data->record.max, sizeof(record_max_le), record_max_le); + bit_buffer_append_bytes(instance->input_buffer, record_max_le, sizeof(record_max_le)); + } + + MfDesfireError error = + mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + return error; +} + static MfDesfireError mf_desfire_poller_read_file( MfDesfirePoller* instance, MfDesfireFileId id, @@ -354,7 +435,8 @@ static MfDesfireError mf_desfire_poller_read_file( bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3); bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3); - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer); @@ -400,7 +482,8 @@ MfDesfireError mf_desfire_poller_read_file_value( MfDesfireError error; do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); + error = mf_desfire_poller_send_chunks( + instance, instance->input_buffer, instance->result_buffer); if(error != MfDesfireErrorNone) break; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index 19e38bebb..179fd44b5 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -30,6 +30,7 @@ typedef enum { struct MfDesfirePoller { Iso14443_4aPoller* iso14443_4a_poller; + NxpNativeCommandMode command_mode; MfDesfirePollerSessionState session_state; MfDesfirePollerState state; MfDesfireError error; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index bd32956d6..b66cf5ea2 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -15,8 +15,8 @@ const uint8_t mf_plus_ats_t1_tk_values[][MF_PLUS_T1_TK_VALUE_LEN] = { {0xC1, 0x05, 0x2F, 0x2F, 0x00, 0x35, 0xC7}, // Mifare Plus S {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X - {0xC1, 0x05, 0x2F, 0x2F, 0x00, 0xF6, 0xD1}, // Mifare Plus SE - {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE + {0xC1, 0x05, 0x21, 0x30, 0x00, 0xF6, 0xD1}, // Mifare Plus SE + {0xC1, 0x05, 0x21, 0x30, 0x10, 0xF6, 0xD1}, // Mifare Plus SE }; MfPlusError mf_plus_get_type_from_version( @@ -27,7 +27,7 @@ MfPlusError mf_plus_get_type_from_version( MfPlusError error = MfPlusErrorProtocol; - if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) { + if((mf_plus_data->version.hw_type & 0x0F) == 0x02) { error = MfPlusErrorNone; // Mifare Plus EV1/EV2 @@ -85,16 +85,15 @@ MfPlusError MfPlusError error = MfPlusErrorProtocol; - if(simple_array_get_count(iso4_data->ats_data.t1_tk) != MF_PLUS_T1_TK_VALUE_LEN) { + const size_t historical_bytes_len = simple_array_get_count(iso4_data->ats_data.t1_tk); + if(historical_bytes_len != MF_PLUS_T1_TK_VALUE_LEN) { return MfPlusErrorProtocol; } + const uint8_t* historical_bytes = simple_array_cget_data(iso4_data->ats_data.t1_tk); switch(iso4_data->iso14443_3a_data->sak) { case 0x08: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 2K SL1 mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize2K; @@ -102,11 +101,7 @@ MfPlusError FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); error = MfPlusErrorNone; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { // Mifare Plus X 2K SL1 mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize2K; @@ -115,14 +110,8 @@ MfPlusError FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); error = MfPlusErrorNone; } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[2], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 || - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[3], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 || + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) { // Mifare Plus SE 1K SL1 mf_plus_data->type = MfPlusTypeSE; mf_plus_data->size = MfPlusSize1K; @@ -154,10 +143,7 @@ MfPlusError break; case 0x18: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 4K SL1 mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize4K; @@ -165,11 +151,7 @@ MfPlusError FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); error = MfPlusErrorNone; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { // Mifare Plus X 4K SL1 mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize4K; @@ -183,10 +165,7 @@ MfPlusError break; case 0x20: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[0], historical_bytes_len) == 0) { // Mifare Plus S 2/4K SL3 FURI_LOG_D(TAG, "Mifare Plus S SL3"); mf_plus_data->type = MfPlusTypeS; @@ -207,21 +186,20 @@ MfPlusError } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); } - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + } else if(memcmp(historical_bytes, mf_plus_ats_t1_tk_values[1], historical_bytes_len) == 0) { + // Mifare Plus X 2/4K SL3 mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; FURI_LOG_D(TAG, "Mifare Plus X SL3"); if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) { + // Mifare Plus X 2K SL3 mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); error = MfPlusErrorNone; } else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) { + // Mifare Plus X 4K SL3 mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); @@ -229,6 +207,16 @@ MfPlusError } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); } + } else if( + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[2], historical_bytes_len) == 0 || + memcmp(historical_bytes, mf_plus_ats_t1_tk_values[3], historical_bytes_len) == 0) { + // Mifare Plus SE 1K SL3 + mf_plus_data->type = MfPlusTypeSE; + mf_plus_data->size = MfPlusSize1K; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL3"); + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); } @@ -238,22 +226,12 @@ MfPlusError } MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { - bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); if(can_parse) { bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); - } else if( - bit_buffer_get_size_bytes(buf) == 8 && - bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) { - // HACK(-nofl): There are supposed to be three parts to the GetVersion command, - // with the second and third parts fetched by sending the AdditionalFrame - // command. I don't know whether the entire MIFARE Plus line uses status as - // the first byte, so let's just assume we only have the first part of - // the response if it's size 8 and starts with the AF status. The second - // part of the response is the same size and status byte, but so far - // we're only reading one response. - can_parse = true; - bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1); + } else { + memset(data, 0, sizeof(MfPlusVersion)); } return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index 302f5a178..cadc435b9 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -2,10 +2,9 @@ #include "mf_plus.h" -#define MF_PLUS_FFF_PICC_PREFIX "PICC" +#include -#define MF_PLUS_STATUS_OPERATION_OK (0x90) -#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF) +#define MF_PLUS_FFF_PICC_PREFIX "PICC" MfPlusError mf_plus_get_type_from_version( const Iso14443_4aData* iso14443_4a_data, diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index cab906f1d..b2e4231ff 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -19,28 +19,36 @@ MfPlusError mf_plus_process_error(Iso14443_4aError error) { } } -MfPlusError mf_plus_poller_send_chunk( +MfPlusError mf_plus_process_status_code(uint8_t status_code) { + switch(status_code) { + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: + return MfPlusErrorNone; + default: + return MfPlusErrorProtocol; + } +} + +MfPlusError mf_plus_poller_send_chunks( MfPlusPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { furi_assert(instance); - furi_assert(instance->iso14443_4a_poller); - furi_assert(instance->tx_buffer); - furi_assert(instance->rx_buffer); - furi_assert(tx_buffer); - furi_assert(rx_buffer); - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); - MfPlusError error = mf_plus_process_error(iso14443_4a_error); + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + NxpNativeCommandModePlain, + instance->tx_buffer, + instance->rx_buffer); - if(error == MfPlusErrorNone) { - bit_buffer_copy(rx_buffer, instance->rx_buffer); + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return mf_plus_process_error(iso14443_4a_error); } - bit_buffer_reset(instance->tx_buffer); - - return error; + return mf_plus_process_status_code(status_code); } MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) { @@ -50,7 +58,7 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); MfPlusError error = - mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer); + mf_plus_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); if(error == MfPlusErrorNone) { error = mf_plus_version_parse(data, instance->result_buffer); } diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 2b9714613..0f1766029 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -22,9 +22,11 @@ #include #include #include -#include #include #include +#include +#include +#include /** * @brief List of registered NFC device implementations. @@ -45,6 +47,8 @@ const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, + [NfcProtocolNtag4xx] = &nfc_device_ntag4xx, + [NfcProtocolType4Tag] = &nfc_device_type_4_tag, [NfcProtocolEmv] = &nfc_device_emv, /* Add new protocols here */ }; diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 5ad73f6fe..7efb274b6 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -3,10 +3,11 @@ #include #include #include +#include #include #include #include -#include +#include const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, @@ -14,11 +15,14 @@ const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, [NfcProtocolIso14443_4b] = NULL, [NfcProtocolIso15693_3] = &nfc_listener_iso15693_3, + [NfcProtocolFelica] = &nfc_listener_felica, [NfcProtocolMfUltralight] = &mf_ultralight_listener, [NfcProtocolMfClassic] = &mf_classic_listener, + [NfcProtocolMfPlus] = NULL, [NfcProtocolMfDesfire] = NULL, [NfcProtocolSlix] = &nfc_listener_slix, [NfcProtocolSt25tb] = NULL, - [NfcProtocolFelica] = &nfc_listener_felica, + [NfcProtocolNtag4xx] = NULL, + [NfcProtocolType4Tag] = &nfc_listener_type_4_tag, [NfcProtocolEmv] = NULL, }; diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index 21eef26ba..afcd9ab36 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -10,9 +10,11 @@ #include #include #include -#include #include #include +#include +#include +#include const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, @@ -27,6 +29,8 @@ const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, [NfcProtocolSt25tb] = &nfc_poller_st25tb, + [NfcProtocolNtag4xx] = &ntag4xx_poller, + [NfcProtocolType4Tag] = &type_4_tag_poller, [NfcProtocolEmv] = &emv_poller, /* Add new pollers here */ }; diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 4106e689a..6767deb07 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -12,19 +12,19 @@ * ``` * **************************** Protocol tree structure *************************** * - * (Start) - * | - * +------------------------+-----------+---------+------------+ - * | | | | | - * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB - * | | | - * +---------------+-------------+ ISO14443-4B SLIX - * | | | - * ISO14443-4A Mf Ultralight Mf Classic - * | - * +-----+-----+ - * | | - * Mf Desfire EMV + * (Start) + * | + * +------------------------+-----------+---------+------------+ + * | | | | | + * ISO14443-3A ISO14443-3B Felica ISO15693-3 ST25TB + * | | | + * +---------------+-------------+ ISO14443-4B SLIX + * | | | + * ISO14443-4A Mf Ultralight Mf Classic + * | + * +-----+----+----------+----------+---------+ + * | | | | | + * Mf Desfire Mf Plus NTAG4xx Type 4 Tag EMV * ``` * * When implementing a new protocol, its place in the tree must be determined first. @@ -62,8 +62,10 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { /** List of ISO14443-4A child protocols. */ static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { - NfcProtocolMfDesfire, NfcProtocolMfPlus, + NfcProtocolMfDesfire, + NfcProtocolNtag4xx, + NfcProtocolType4Tag, NfcProtocolEmv, }; @@ -156,6 +158,18 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolNtag4xx] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, + [NfcProtocolType4Tag] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, [NfcProtocolEmv] = { .parent_protocol = NfcProtocolIso14443_4a, diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 12866528e..d33624d15 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -188,6 +188,8 @@ typedef enum { NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, + NfcProtocolNtag4xx, + NfcProtocolType4Tag, NfcProtocolEmv, /* Add new protocols here */ diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx.c b/lib/nfc/protocols/ntag4xx/ntag4xx.c new file mode 100644 index 000000000..7b0991903 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx.c @@ -0,0 +1,192 @@ +#include "ntag4xx_i.h" + +#include + +#define NTAG4XX_PROTOCOL_NAME "NTAG4xx" + +#define NTAG4XX_HW_MAJOR_TYPE_413_DNA (0x10) +#define NTAG4XX_HW_MAJOR_TYPE_424_DNA (0x30) + +#define NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG (0x08) + +static const char* ntag4xx_type_strings[] = { + [Ntag4xxType413DNA] = "NTAG413 DNA", + [Ntag4xxType424DNA] = "NTAG424 DNA", + [Ntag4xxType424DNATT] = "NTAG424 DNA TagTamper", + [Ntag4xxType426QDNA] = "NTAG426Q DNA", + [Ntag4xxType426QDNATT] = "NTAG426Q DNA TagTamper", + [Ntag4xxTypeUnknown] = "UNK", +}; + +const NfcDeviceBase nfc_device_ntag4xx = { + .protocol_name = NTAG4XX_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)ntag4xx_alloc, + .free = (NfcDeviceFree)ntag4xx_free, + .reset = (NfcDeviceReset)ntag4xx_reset, + .copy = (NfcDeviceCopy)ntag4xx_copy, + .verify = (NfcDeviceVerify)ntag4xx_verify, + .load = (NfcDeviceLoad)ntag4xx_load, + .save = (NfcDeviceSave)ntag4xx_save, + .is_equal = (NfcDeviceEqual)ntag4xx_is_equal, + .get_name = (NfcDeviceGetName)ntag4xx_get_device_name, + .get_uid = (NfcDeviceGetUid)ntag4xx_get_uid, + .set_uid = (NfcDeviceSetUid)ntag4xx_set_uid, + .get_base_data = (NfcDeviceGetBaseData)ntag4xx_get_base_data, +}; + +Ntag4xxData* ntag4xx_alloc(void) { + Ntag4xxData* data = malloc(sizeof(Ntag4xxData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->device_name = furi_string_alloc(); + return data; +} + +void ntag4xx_free(Ntag4xxData* data) { + furi_check(data); + + ntag4xx_reset(data); + iso14443_4a_free(data->iso14443_4a_data); + furi_string_free(data->device_name); + free(data); +} + +void ntag4xx_reset(Ntag4xxData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(Ntag4xxVersion)); +} + +void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other) { + furi_check(data); + furi_check(other); + + ntag4xx_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; +} + +bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!ntag4xx_version_load(&data->version, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, NTAG4XX_PROTOCOL_NAME " specific data")) break; + if(!ntag4xx_version_save(&data->version, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + memcmp(&data->version, &other->version, sizeof(Ntag4xxVersion)) == 0; +} + +Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version) { + Ntag4xxType type = Ntag4xxTypeUnknown; + + switch(version->hw_major) { + case NTAG4XX_HW_MAJOR_TYPE_413_DNA: + type = Ntag4xxType413DNA; + break; + case NTAG4XX_HW_MAJOR_TYPE_424_DNA: + if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) { + type = Ntag4xxType424DNATT; + } else { + type = Ntag4xxType424DNA; + } + break; + // TODO: there is no info online or in other implementations (NXP TagInfo, NFC Tools, Proxmark3) + // about what the HWMajorVersion is supposed to be for NTAG426Q DNA, and they don't seem to be for sale + // case NTAG4XX_HW_MAJOR_TYPE_426Q_DNA: + // if(version->hw_subtype & NTAG4XX_HW_SUBTYPE_TAGTAMPER_FLAG) { + // type = Ntag4xxType426QDNATT; + // } else { + // type = Ntag4xxType426QDNA; + // } + // break; + default: + break; + } + + return type; +} + +const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type) { + furi_check(data); + + const Ntag4xxType type = ntag4xx_get_type_from_version(&data->version); + + if(type == Ntag4xxTypeUnknown) { + furi_string_printf(data->device_name, "Unknown %s", NTAG4XX_PROTOCOL_NAME); + } else { + furi_string_printf(data->device_name, "%s", ntag4xx_type_strings[type]); + if(name_type == NfcDeviceNameTypeShort) { + furi_string_replace(data->device_name, "TagTamper", "TT"); + } + } + + return furi_string_get_cstr(data->device_name); +} + +const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool ntag4xx_set_uid(Ntag4xxData* data, const uint8_t* uid, size_t uid_len) { + furi_check(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* ntag4xx_get_base_data(const Ntag4xxData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx.h b/lib/nfc/protocols/ntag4xx/ntag4xx.h new file mode 100644 index 000000000..56e5fbd65 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx.h @@ -0,0 +1,114 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NTAG4XX_UID_SIZE (7) +#define NTAG4XX_BATCH_SIZE (4) +#define NTAG4XX_BATCH_EXTRA_BITS 4 +#define NTAG4XX_FAB_KEY_SIZE_BITS_4 4 +#define NTAG4XX_FAB_KEY_SIZE_BITS_1 1 +#define NTAG4XX_PROD_WEEK_SIZE_BITS 7 + +#define NTAG4XX_CMD_GET_VERSION (0x60) + +typedef enum { + Ntag4xxErrorNone, + Ntag4xxErrorNotPresent, + Ntag4xxErrorProtocol, + Ntag4xxErrorTimeout, +} Ntag4xxError; + +typedef enum { + Ntag4xxType413DNA, + Ntag4xxType424DNA, + Ntag4xxType424DNATT, + Ntag4xxType426QDNA, + Ntag4xxType426QDNATT, + + Ntag4xxTypeUnknown, + Ntag4xxTypeNum, +} Ntag4xxType; + +#pragma pack(push, 1) +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[NTAG4XX_UID_SIZE]; + // [36b batch][5b fab key][7b prod week] + // 5b fab key is split 4b in last byte of batch and 1b in prod week + // Due to endianness, they appear swapped in the struct definition + uint8_t batch[NTAG4XX_BATCH_SIZE]; + struct { + uint8_t fab_key_4b : NTAG4XX_FAB_KEY_SIZE_BITS_4; + uint8_t batch_extra : NTAG4XX_BATCH_EXTRA_BITS; + }; + struct { + uint8_t prod_week : NTAG4XX_PROD_WEEK_SIZE_BITS; + uint8_t fab_key_1b : NTAG4XX_FAB_KEY_SIZE_BITS_1; + }; + uint8_t prod_year; + struct { + uint8_t fab_key_id; + } optional; +} Ntag4xxVersion; +#pragma pack(pop) + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + Ntag4xxVersion version; + FuriString* device_name; +} Ntag4xxData; + +extern const NfcDeviceBase nfc_device_ntag4xx; + +// Virtual methods + +Ntag4xxData* ntag4xx_alloc(void); + +void ntag4xx_free(Ntag4xxData* data); + +void ntag4xx_reset(Ntag4xxData* data); + +void ntag4xx_copy(Ntag4xxData* data, const Ntag4xxData* other); + +bool ntag4xx_verify(Ntag4xxData* data, const FuriString* device_type); + +bool ntag4xx_load(Ntag4xxData* data, FlipperFormat* ff, uint32_t version); + +bool ntag4xx_save(const Ntag4xxData* data, FlipperFormat* ff); + +bool ntag4xx_is_equal(const Ntag4xxData* data, const Ntag4xxData* other); + +const char* ntag4xx_get_device_name(const Ntag4xxData* data, NfcDeviceNameType name_type); + +const uint8_t* ntag4xx_get_uid(const Ntag4xxData* data, size_t* uid_len); + +bool ntag4xx_set_uid(Ntag4xxData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* ntag4xx_get_base_data(const Ntag4xxData* data); + +// Helpers + +Ntag4xxType ntag4xx_get_type_from_version(const Ntag4xxVersion* const version); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_i.c b/lib/nfc/protocols/ntag4xx/ntag4xx_i.c new file mode 100644 index 000000000..b4ee3a9eb --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_i.c @@ -0,0 +1,54 @@ +#include "ntag4xx_i.h" + +#define TAG "Ntag4xx" + +#define NTAG4XX_FFF_VERSION_KEY \ + NTAG4XX_FFF_PICC_PREFIX " " \ + "Version" + +Ntag4xxError ntag4xx_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return Ntag4xxErrorNone; + case Iso14443_4aErrorNotPresent: + return Ntag4xxErrorNotPresent; + case Iso14443_4aErrorTimeout: + return Ntag4xxErrorTimeout; + default: + return Ntag4xxErrorProtocol; + } +} + +Ntag4xxError ntag4xx_process_status_code(uint8_t status_code) { + switch(status_code) { + case NXP_NATIVE_COMMAND_STATUS_OPERATION_OK: + return Ntag4xxErrorNone; + default: + return Ntag4xxErrorProtocol; + } +} + +bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf) { + const size_t buf_size = bit_buffer_get_size_bytes(buf); + const bool can_parse = buf_size == sizeof(Ntag4xxVersion) || + buf_size == sizeof(Ntag4xxVersion) - sizeof(data->optional); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(Ntag4xxVersion)); + if(buf_size < sizeof(Ntag4xxVersion)) { + memset(&data->optional, 0, sizeof(data->optional)); + } + } + + return can_parse && (data->hw_type & 0x0F) == 0x04; +} + +bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, NTAG4XX_FFF_VERSION_KEY, (uint8_t*)data, sizeof(Ntag4xxVersion)); +} + +bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, NTAG4XX_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(Ntag4xxVersion)); +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_i.h b/lib/nfc/protocols/ntag4xx/ntag4xx_i.h new file mode 100644 index 000000000..5a71cf05a --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_i.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ntag4xx.h" + +#include + +#define NTAG4XX_FFF_PICC_PREFIX "PICC" + +// Internal helpers + +Ntag4xxError ntag4xx_process_error(Iso14443_4aError error); + +Ntag4xxError ntag4xx_process_status_code(uint8_t status_code); + +// Parse internal Ntag4xx structures + +bool ntag4xx_version_parse(Ntag4xxVersion* data, const BitBuffer* buf); + +// Load internal Ntag4xx structures + +bool ntag4xx_version_load(Ntag4xxVersion* data, FlipperFormat* ff); + +// Save internal Ntag4xx structures + +bool ntag4xx_version_save(const Ntag4xxVersion* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c new file mode 100644 index 000000000..38e6f19d4 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.c @@ -0,0 +1,165 @@ +#include "ntag4xx_poller_i.h" + +#include + +#include + +#define TAG "Ntag4xxPoller" + +#define NTAG4XX_BUF_SIZE (64U) +#define NTAG4XX_RESULT_BUF_SIZE (512U) + +typedef NfcCommand (*Ntag4xxPollerReadHandler)(Ntag4xxPoller* instance); + +static const Ntag4xxData* ntag4xx_poller_get_data(Ntag4xxPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Ntag4xxPoller* ntag4xx_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Ntag4xxPoller* instance = malloc(sizeof(Ntag4xxPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = ntag4xx_alloc(); + instance->tx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(NTAG4XX_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(NTAG4XX_RESULT_BUF_SIZE); + + instance->ntag4xx_event.data = &instance->ntag4xx_event_data; + + instance->general_event.protocol = NfcProtocolNtag4xx; + instance->general_event.event_data = &instance->ntag4xx_event; + instance->general_event.instance = instance; + + return instance; +} + +static void ntag4xx_poller_free(Ntag4xxPoller* instance) { + furi_assert(instance); + + ntag4xx_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + free(instance); +} + +static NfcCommand ntag4xx_poller_handler_idle(Ntag4xxPoller* instance) { + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = Ntag4xxPollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand ntag4xx_poller_handler_read_version(Ntag4xxPoller* instance) { + instance->error = ntag4xx_poller_read_version(instance, &instance->data->version); + if(instance->error == Ntag4xxErrorNone) { + FURI_LOG_D(TAG, "Read version success"); + instance->state = Ntag4xxPollerStateReadSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read version"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->state = Ntag4xxPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand ntag4xx_poller_handler_read_failed(Ntag4xxPoller* instance) { + FURI_LOG_D(TAG, "Read Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed; + instance->ntag4xx_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Ntag4xxPollerStateIdle; + return command; +} + +static NfcCommand ntag4xx_poller_handler_read_success(Ntag4xxPoller* instance) { + FURI_LOG_D(TAG, "Read success"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Ntag4xxPollerReadHandler ntag4xx_poller_read_handler[Ntag4xxPollerStateNum] = { + [Ntag4xxPollerStateIdle] = ntag4xx_poller_handler_idle, + [Ntag4xxPollerStateReadVersion] = ntag4xx_poller_handler_read_version, + [Ntag4xxPollerStateReadFailed] = ntag4xx_poller_handler_read_failed, + [Ntag4xxPollerStateReadSuccess] = ntag4xx_poller_handler_read_success, +}; + +static void ntag4xx_poller_set_callback( + Ntag4xxPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand ntag4xx_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Ntag4xxPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = ntag4xx_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->ntag4xx_event.type = Ntag4xxPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool ntag4xx_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Ntag4xxPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + do { + Ntag4xxError error = ntag4xx_poller_read_version(instance, &instance->data->version); + if(error != Ntag4xxErrorNone) break; + + protocol_detected = true; + } while(false); + } + + return protocol_detected; +} + +const NfcPollerBase ntag4xx_poller = { + .alloc = (NfcPollerAlloc)ntag4xx_poller_alloc, + .free = (NfcPollerFree)ntag4xx_poller_free, + .set_callback = (NfcPollerSetCallback)ntag4xx_poller_set_callback, + .run = (NfcPollerRun)ntag4xx_poller_run, + .detect = (NfcPollerDetect)ntag4xx_poller_detect, + .get_data = (NfcPollerGetData)ntag4xx_poller_get_data, +}; diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h new file mode 100644 index 000000000..ce7adc785 --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller.h @@ -0,0 +1,43 @@ +#pragma once + +#include "ntag4xx.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Ntag4xxPoller opaque type definition. + */ +typedef struct Ntag4xxPoller Ntag4xxPoller; + +/** + * @brief Enumeration of possible Ntag4xx poller event types. + */ +typedef enum { + Ntag4xxPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Ntag4xxPollerEventTypeReadFailed, /**< Poller failed to read card. */ +} Ntag4xxPollerEventType; + +/** + * @brief Ntag4xx poller event data. + */ +typedef union { + Ntag4xxError error; /**< Error code indicating card reading fail reason. */ +} Ntag4xxPollerEventData; + +/** + * @brief Ntag4xx poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Ntag4xxPollerEventType type; /**< Type of emmitted event. */ + Ntag4xxPollerEventData* data; /**< Pointer to event specific data. */ +} Ntag4xxPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h new file mode 100644 index 000000000..ac0cdce9b --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase ntag4xx_poller; diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c new file mode 100644 index 000000000..5b8b7191d --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.c @@ -0,0 +1,52 @@ +#include "ntag4xx_poller_i.h" + +#include + +#include "ntag4xx_i.h" + +#define TAG "Ntag4xxPoller" + +Ntag4xxError ntag4xx_poller_send_chunks( + Ntag4xxPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + furi_check(instance); + + NxpNativeCommandStatus status_code = NXP_NATIVE_COMMAND_STATUS_OPERATION_OK; + Iso14443_4aError iso14443_4a_error = nxp_native_command_iso14443_4a_poller( + instance->iso14443_4a_poller, + &status_code, + tx_buffer, + rx_buffer, + NxpNativeCommandModeIsoWrapped, + instance->tx_buffer, + instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return ntag4xx_process_error(iso14443_4a_error); + } + + return ntag4xx_process_status_code(status_code); +} + +Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data) { + furi_check(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, NTAG4XX_CMD_GET_VERSION); + + Ntag4xxError error; + + do { + error = + ntag4xx_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer); + + if(error != Ntag4xxErrorNone) break; + + if(!ntag4xx_version_parse(data, instance->result_buffer)) { + error = Ntag4xxErrorProtocol; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h new file mode 100644 index 000000000..b77e694ef --- /dev/null +++ b/lib/nfc/protocols/ntag4xx/ntag4xx_poller_i.h @@ -0,0 +1,40 @@ +#pragma once + +#include "ntag4xx_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Ntag4xxPollerStateIdle, + Ntag4xxPollerStateReadVersion, + Ntag4xxPollerStateReadFailed, + Ntag4xxPollerStateReadSuccess, + + Ntag4xxPollerStateNum, +} Ntag4xxPollerState; + +struct Ntag4xxPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Ntag4xxPollerState state; + Ntag4xxError error; + Ntag4xxData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + Ntag4xxPollerEventData ntag4xx_event_data; + Ntag4xxPollerEvent ntag4xx_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Ntag4xxError ntag4xx_poller_read_version(Ntag4xxPoller* instance, Ntag4xxVersion* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.c b/lib/nfc/protocols/type_4_tag/type_4_tag.c new file mode 100644 index 000000000..03527b844 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.c @@ -0,0 +1,170 @@ +#include "type_4_tag_i.h" + +#define TYPE_4_TAG_PROTOCOL_NAME "Type 4 Tag" + +const NfcDeviceBase nfc_device_type_4_tag = { + .protocol_name = TYPE_4_TAG_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)type_4_tag_alloc, + .free = (NfcDeviceFree)type_4_tag_free, + .reset = (NfcDeviceReset)type_4_tag_reset, + .copy = (NfcDeviceCopy)type_4_tag_copy, + .verify = (NfcDeviceVerify)type_4_tag_verify, + .load = (NfcDeviceLoad)type_4_tag_load, + .save = (NfcDeviceSave)type_4_tag_save, + .is_equal = (NfcDeviceEqual)type_4_tag_is_equal, + .get_name = (NfcDeviceGetName)type_4_tag_get_device_name, + .get_uid = (NfcDeviceGetUid)type_4_tag_get_uid, + .set_uid = (NfcDeviceSetUid)type_4_tag_set_uid, + .get_base_data = (NfcDeviceGetBaseData)type_4_tag_get_base_data, +}; + +Type4TagData* type_4_tag_alloc(void) { + Type4TagData* data = malloc(sizeof(Type4TagData)); + data->iso14443_4a_data = iso14443_4a_alloc(); + data->device_name = furi_string_alloc(); + data->platform_name = furi_string_alloc(); + data->ndef_data = simple_array_alloc(&simple_array_config_uint8_t); + return data; +} + +void type_4_tag_free(Type4TagData* data) { + furi_check(data); + + type_4_tag_reset(data); + simple_array_free(data->ndef_data); + furi_string_free(data->platform_name); + furi_string_free(data->device_name); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void type_4_tag_reset(Type4TagData* data) { + furi_check(data); + + iso14443_4a_reset(data->iso14443_4a_data); + + data->is_tag_specific = false; + furi_string_reset(data->device_name); + furi_string_reset(data->platform_name); + data->t4t_version.value = 0; + data->chunk_max_read = 0; + data->chunk_max_write = 0; + data->ndef_file_id = 0; + data->ndef_max_len = 0; + data->ndef_read_lock = 0; + data->ndef_write_lock = 0; + + simple_array_reset(data->ndef_data); +} + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + type_4_tag_reset(data); + + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->is_tag_specific = other->is_tag_specific; + furi_string_set(data->device_name, other->device_name); + furi_string_set(data->platform_name, other->platform_name); + data->t4t_version.value = other->t4t_version.value; + data->chunk_max_read = other->chunk_max_read; + data->chunk_max_write = other->chunk_max_write; + data->ndef_file_id = other->ndef_file_id; + data->ndef_max_len = other->ndef_max_len; + data->ndef_read_lock = other->ndef_read_lock; + data->ndef_write_lock = other->ndef_write_lock; + + simple_array_copy(data->ndef_data, other->ndef_data); +} + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type) { + UNUSED(data); + UNUSED(device_type); + + return false; +} + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + + if(!type_4_tag_ndef_data_load(data, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff) { + furi_check(data); + furi_check(ff); + + FuriString* prefix = furi_string_alloc(); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + + if(!flipper_format_write_comment_cstr(ff, TYPE_4_TAG_PROTOCOL_NAME " specific data")) + break; + if(!type_4_tag_ndef_data_save(data, ff)) break; + + success = true; + } while(false); + + furi_string_free(prefix); + return success; +} + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other) { + furi_check(data); + furi_check(other); + + return iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data) && + data->is_tag_specific == other->is_tag_specific && + data->t4t_version.value == other->t4t_version.value && + data->chunk_max_read == other->chunk_max_read && + data->chunk_max_write == other->chunk_max_write && + data->ndef_file_id == other->ndef_file_id && + data->ndef_max_len == other->ndef_max_len && + data->ndef_read_lock == other->ndef_read_lock && + data->ndef_write_lock == other->ndef_write_lock && + simple_array_is_equal(data->ndef_data, other->ndef_data); +} + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type) { + UNUSED(data); + UNUSED(name_type); + return TYPE_4_TAG_PROTOCOL_NAME; +} + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len) { + furi_check(data); + furi_check(uid_len); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len) { + furi_check(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data) { + furi_check(data); + + return data->iso14443_4a_data; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag.h b/lib/nfc/protocols/type_4_tag/type_4_tag.h new file mode 100644 index 000000000..29c41a5be --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE (2048U - sizeof(uint16_t)) + +typedef enum { + Type4TagErrorNone, + Type4TagErrorNotPresent, + Type4TagErrorProtocol, + Type4TagErrorTimeout, + Type4TagErrorWrongFormat, + Type4TagErrorNotSupported, + Type4TagErrorApduFailed, + Type4TagErrorCardUnformatted, + Type4TagErrorCardLocked, + Type4TagErrorCustomCommand, +} Type4TagError; + +typedef enum { + Type4TagPlatformUnknown, + Type4TagPlatformNtag4xx, + Type4TagPlatformMfDesfire, +} Type4TagPlatform; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + FuriString* device_name; + // Tag specific data + bool is_tag_specific; + Type4TagPlatform platform; + FuriString* platform_name; + union { + struct { + uint8_t minor : 4; + uint8_t major : 4; + }; + uint8_t value; + } t4t_version; + uint16_t chunk_max_read; + uint16_t chunk_max_write; + uint16_t ndef_file_id; + uint16_t ndef_max_len; + uint8_t ndef_read_lock; + uint8_t ndef_write_lock; + // Data contained, not tag specific + SimpleArray* ndef_data; +} Type4TagData; + +extern const NfcDeviceBase nfc_device_type_4_tag; + +// Virtual methods + +Type4TagData* type_4_tag_alloc(void); + +void type_4_tag_free(Type4TagData* data); + +void type_4_tag_reset(Type4TagData* data); + +void type_4_tag_copy(Type4TagData* data, const Type4TagData* other); + +bool type_4_tag_verify(Type4TagData* data, const FuriString* device_type); + +bool type_4_tag_load(Type4TagData* data, FlipperFormat* ff, uint32_t version); + +bool type_4_tag_save(const Type4TagData* data, FlipperFormat* ff); + +bool type_4_tag_is_equal(const Type4TagData* data, const Type4TagData* other); + +const char* type_4_tag_get_device_name(const Type4TagData* data, NfcDeviceNameType name_type); + +const uint8_t* type_4_tag_get_uid(const Type4TagData* data, size_t* uid_len); + +bool type_4_tag_set_uid(Type4TagData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* type_4_tag_get_base_data(const Type4TagData* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c new file mode 100644 index 000000000..69e9aadf9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.c @@ -0,0 +1,163 @@ +#include "type_4_tag_i.h" + +#include + +#define TAG "Type4Tag" + +#define TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY "NDEF Data Size" +#define TYPE_4_TAG_FFF_NDEF_DATA_KEY "NDEF Data" + +#define TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE (16U) + +const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_MF_NAME}; +const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN] = {TYPE_4_TAG_ISO_DF_NAME}; + +Type4TagError type_4_tag_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return Type4TagErrorNone; + case Iso14443_4aErrorNotPresent: + return Type4TagErrorNotPresent; + case Iso14443_4aErrorTimeout: + return Type4TagErrorTimeout; + default: + return Type4TagErrorProtocol; + } +} + +void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len) { + furi_check(len >= TYPE_4_TAG_T4T_CC_MIN_SIZE); + Type4TagCc* cc = (Type4TagCc*)buf; + + bit_lib_num_to_bytes_be(TYPE_4_TAG_T4T_CC_MIN_SIZE, sizeof(cc->len), (void*)&cc->len); + cc->t4t_vno = TYPE_4_TAG_T4T_CC_VNO; + bit_lib_num_to_bytes_be( + data->is_tag_specific ? MIN(data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN, + sizeof(cc->mle), + (void*)&cc->mle); + bit_lib_num_to_bytes_be( + data->is_tag_specific ? MIN(data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN, + sizeof(cc->mlc), + (void*)&cc->mlc); + + cc->tlv[0].type = Type4TagCcTlvTypeNdefFileCtrl; + cc->tlv[0].len = sizeof(cc->tlv[0].value.ndef_file_ctrl); + + bit_lib_num_to_bytes_be( + data->is_tag_specific ? data->ndef_file_id : TYPE_4_TAG_T4T_NDEF_EF_ID, + sizeof(cc->tlv[0].value.ndef_file_ctrl.file_id), + (void*)&cc->tlv[0].value.ndef_file_ctrl.file_id); + bit_lib_num_to_bytes_be( + sizeof(uint16_t) + + (data->is_tag_specific ? data->ndef_max_len : TYPE_4_TAG_DEFAULT_NDEF_SIZE), + sizeof(cc->tlv[0].value.ndef_file_ctrl.max_len), + (void*)&cc->tlv[0].value.ndef_file_ctrl.max_len); + cc->tlv[0].value.ndef_file_ctrl.read_perm = + data->is_tag_specific ? data->ndef_read_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + cc->tlv[0].value.ndef_file_ctrl.write_perm = + data->is_tag_specific ? data->ndef_write_lock : TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; +} + +Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len) { + if(len < TYPE_4_TAG_T4T_CC_MIN_SIZE) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + return Type4TagErrorWrongFormat; + } + + const Type4TagCc* cc = (const Type4TagCc*)buf; + if(cc->t4t_vno != TYPE_4_TAG_T4T_CC_VNO) { + FURI_LOG_E(TAG, "Unsupported T4T version"); + return Type4TagErrorNotSupported; + } + + const Type4TagCcTlv* tlv = cc->tlv; + const Type4TagCcTlvNdefFileCtrl* ndef_file_ctrl = NULL; + const void* end = MIN((void*)cc + cc->len, (void*)cc + len); + while((void*)tlv < end) { + if(tlv->type == Type4TagCcTlvTypeNdefFileCtrl) { + ndef_file_ctrl = &tlv->value.ndef_file_ctrl; + break; + } + + if(tlv->len < 0xFF) { + tlv = (void*)&tlv->value + tlv->len; + } else { + uint16_t len = bit_lib_bytes_to_num_be((void*)&tlv->len + 1, sizeof(uint16_t)); + tlv = (void*)&tlv->value + sizeof(len) + len; + } + } + if(!ndef_file_ctrl) { + FURI_LOG_E(TAG, "No NDEF file ctrl TLV"); + return Type4TagErrorWrongFormat; + } + + data->t4t_version.value = cc->t4t_vno; + data->chunk_max_read = bit_lib_bytes_to_num_be((void*)&cc->mle, sizeof(cc->mle)); + data->chunk_max_write = bit_lib_bytes_to_num_be((void*)&cc->mlc, sizeof(cc->mlc)); + data->ndef_file_id = + bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->file_id, sizeof(ndef_file_ctrl->file_id)); + data->ndef_max_len = + bit_lib_bytes_to_num_be((void*)&ndef_file_ctrl->max_len, sizeof(ndef_file_ctrl->max_len)) - + sizeof(uint16_t); + data->ndef_read_lock = ndef_file_ctrl->read_perm; + data->ndef_write_lock = ndef_file_ctrl->write_perm; + + return Type4TagErrorNone; +} + +bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff) { + uint32_t ndef_data_size; + if(!flipper_format_read_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) { + return false; + } + if(ndef_data_size == 0) { + return true; + } + + simple_array_init(data->ndef_data, ndef_data_size); + + uint32_t ndef_data_pos = 0; + uint8_t* ndef_data = simple_array_get_data(data->ndef_data); + while(ndef_data_size > 0) { + uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE); + + if(!flipper_format_read_hex( + ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) { + simple_array_reset(data->ndef_data); + return false; + } + + ndef_data_pos += ndef_line_size; + ndef_data_size -= ndef_line_size; + } + + return true; +} + +bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff) { + uint32_t ndef_data_size = simple_array_get_count(data->ndef_data); + if(!flipper_format_write_uint32(ff, TYPE_4_TAG_FFF_NDEF_DATA_SIZE_KEY, &ndef_data_size, 1)) { + return false; + } + if(ndef_data_size == 0) { + return true; + } + + uint32_t ndef_data_pos = 0; + uint8_t* ndef_data = simple_array_get_data(data->ndef_data); + while(ndef_data_size > 0) { + uint8_t ndef_line_size = MIN(ndef_data_size, TYPE_4_TAG_FFF_NDEF_DATA_PER_LINE); + + if(!flipper_format_write_hex( + ff, TYPE_4_TAG_FFF_NDEF_DATA_KEY, &ndef_data[ndef_data_pos], ndef_line_size)) { + return false; + } + + ndef_data_pos += ndef_line_size; + ndef_data_size -= ndef_line_size; + } + + return true; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h new file mode 100644 index 000000000..f47d75236 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_i.h @@ -0,0 +1,110 @@ +#pragma once + +#include "type_4_tag.h" + +// ISO SELECT FILE command and parameters +#define TYPE_4_TAG_ISO_SELECT_CMD 0x00, 0xA4 +#define TYPE_4_TAG_ISO_SELECT_P1_BY_NAME (0x04) +#define TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID (0x02) +#define TYPE_4_TAG_ISO_SELECT_P1_BY_ID (0x00) +#define TYPE_4_TAG_ISO_SELECT_P2_EMPTY (0x0C) +#define TYPE_4_TAG_ISO_SELECT_LE_EMPTY (0x00) + +// ISO READ BINARY command and parameters +#define TYPE_4_TAG_ISO_READ_CMD 0x00, 0xB0 +#define TYPE_4_TAG_ISO_READ_P1_ID_MASK (1 << 7) +#define TYPE_4_TAG_ISO_READ_P_BEGINNING 0x00, 0x00 +#define TYPE_4_TAG_ISO_READ_P_OFFSET_MAX (32767U) +#define TYPE_4_TAG_ISO_READ_LE_FULL (0x00) + +// ISO UPDATE BINARY command and parameters +#define TYPE_4_TAG_ISO_WRITE_CMD 0x00, 0xD6 +#define TYPE_4_TAG_ISO_WRITE_P1_ID_MASK (1 << 7) +#define TYPE_4_TAG_ISO_WRITE_P_BEGINNING 0x00, 0x00 +#define TYPE_4_TAG_ISO_WRITE_LE_EMPTY (0x00) + +// Common APDU parameters and values +#define TYPE_4_TAG_ISO_STATUS_LEN (2U) +#define TYPE_4_TAG_ISO_STATUS_SUCCESS 0x90, 0x00 +#define TYPE_4_TAG_ISO_STATUS_OFFSET_ERR 0x6B, 0x00 +#define TYPE_4_TAG_ISO_STATUS_NOT_FOUND 0x6A, 0x82 +#define TYPE_4_TAG_ISO_STATUS_NO_SUPPORT 0x6A, 0x81 +#define TYPE_4_TAG_ISO_STATUS_BAD_PARAMS 0x6A, 0x86 +#define TYPE_4_TAG_ISO_STATUS_NO_CMD 0x68, 0x00 +#define TYPE_4_TAG_ISO_RW_CHUNK_LEN (255U) + +// Common IDs and Names, note: +// MF = Master File (PICC/Card Level) +// DF = Dedicated File (Application) +// EF = Elementary File (File) +#define TYPE_4_TAG_ISO_NAME_LEN (7U) +#define TYPE_4_TAG_ISO_MF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x00 +#define TYPE_4_TAG_ISO_DF_NAME 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 +#define TYPE_4_TAG_ISO_ID_LEN (2U) +#define TYPE_4_TAG_ISO_MF_ID (0x3F00) +#define TYPE_4_TAG_ISO_DF_ID (0xE110) +#define TYPE_4_TAG_T4T_CC_EF_ID (0xE103) +#define TYPE_4_TAG_T4T_NDEF_EF_ID (0xE104) + +// Capability Container parsing parameters +#define TYPE_4_TAG_T4T_CC_VNO (0x20) +#define TYPE_4_TAG_T4T_CC_RW_LOCK_NONE (0x00) +#define TYPE_4_TAG_T4T_CC_MIN_SIZE (sizeof(Type4TagCc) + sizeof(Type4TagCcTlv)) + +// Implementation-specific sizes and defaults +// 4a layer adds 1..3 byte prefix, 3a layer adds 2 byte suffix and has 256 byte buffer +#define TYPE_4_TAG_BUF_SIZE (256U - 3U - 2U) +// Read returns 2 byte status trailer, write sends 5 byte command header +#define TYPE_4_TAG_CHUNK_LEN MIN(TYPE_4_TAG_BUF_SIZE - 5U, TYPE_4_TAG_ISO_RW_CHUNK_LEN) +#define TYPE_4_TAG_DEFAULT_NDEF_SIZE TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE + +extern const uint8_t type_4_tag_iso_mf_name[TYPE_4_TAG_ISO_NAME_LEN]; +extern const uint8_t type_4_tag_iso_df_name[TYPE_4_TAG_ISO_NAME_LEN]; + +// Capability Container parsing structures + +typedef enum FURI_PACKED { + Type4TagCcTlvTypeNdefFileCtrl = 0x04, + Type4TagCcTlvTypeProprietaryFileCtrl = 0x05, +} Type4TagCcTlvType; + +typedef struct FURI_PACKED { + uint16_t file_id; + uint16_t max_len; + uint8_t read_perm; + uint8_t write_perm; +} Type4TagCcTlvNdefFileCtrl; + +typedef union FURI_PACKED { + Type4TagCcTlvNdefFileCtrl ndef_file_ctrl; +} Type4TagCcTlvValue; + +typedef struct FURI_PACKED { + Type4TagCcTlvType type; + uint8_t len; + Type4TagCcTlvValue value; +} Type4TagCcTlv; + +typedef struct FURI_PACKED { + uint16_t len; + uint8_t t4t_vno; + uint16_t mle; + uint16_t mlc; + Type4TagCcTlv tlv[]; +} Type4TagCc; + +// Internal helpers + +Type4TagError type_4_tag_process_error(Iso14443_4aError error); + +void type_4_tag_cc_dump(const Type4TagData* data, uint8_t* buf, size_t len); + +Type4TagError type_4_tag_cc_parse(Type4TagData* data, const uint8_t* buf, size_t len); + +// Load internal Type4Tag structures + +bool type_4_tag_ndef_data_load(Type4TagData* data, FlipperFormat* ff); + +// Save internal Type4Tag structures + +bool type_4_tag_ndef_data_save(const Type4TagData* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c new file mode 100644 index 000000000..58e94470d --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.c @@ -0,0 +1,88 @@ +#include "type_4_tag_listener_i.h" +#include "type_4_tag_listener_defs.h" +#include "type_4_tag_i.h" + +#define TAG "Type4TagListener" + +static void type_4_tag_listener_reset_state(Type4TagListener* instance) { + instance->state = Type4TagListenerStateIdle; +} + +static Type4TagListener* + type_4_tag_listener_alloc(Iso14443_4aListener* iso14443_4a_listener, Type4TagData* data) { + furi_assert(iso14443_4a_listener); + + Type4TagListener* instance = malloc(sizeof(Type4TagListener)); + instance->iso14443_4a_listener = iso14443_4a_listener; + instance->data = data; + + instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + + instance->type_4_tag_event.data = &instance->type_4_tag_event_data; + instance->generic_event.protocol = NfcProtocolType4Tag; + instance->generic_event.instance = instance; + instance->generic_event.event_data = &instance->type_4_tag_event; + + return instance; +} + +static void type_4_tag_listener_free(Type4TagListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->tx_buffer); + + bit_buffer_free(instance->tx_buffer); + free(instance); +} + +static void type_4_tag_listener_set_callback( + Type4TagListener* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + + instance->callback = callback; + instance->context = context; +} + +static const Type4TagData* type_4_tag_listener_get_data(Type4TagListener* instance) { + furi_assert(instance); + furi_assert(instance->data); + + return instance->data; +} + +static NfcCommand type_4_tag_listener_run(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso15693_3); + furi_assert(event.event_data); + + Type4TagListener* instance = context; + Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data; + BitBuffer* rx_buffer = iso14443_4a_event->data->buffer; + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeFieldOff) { + type_4_tag_listener_reset_state(instance); + command = NfcCommandSleep; + } else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeHalted) { + type_4_tag_listener_reset_state(instance); + } else if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) { + const Type4TagError error = type_4_tag_listener_handle_apdu(instance, rx_buffer); + if(error == Type4TagErrorCustomCommand && instance->callback) { + instance->type_4_tag_event.type = Type4TagListenerEventTypeCustomCommand; + instance->type_4_tag_event.data->buffer = rx_buffer; + command = instance->callback(instance->generic_event, instance->context); + } + } + + return command; +} + +const NfcListenerBase nfc_listener_type_4_tag = { + .alloc = (NfcListenerAlloc)type_4_tag_listener_alloc, + .free = (NfcListenerFree)type_4_tag_listener_free, + .set_callback = (NfcListenerSetCallback)type_4_tag_listener_set_callback, + .get_data = (NfcListenerGetData)type_4_tag_listener_get_data, + .run = (NfcListenerRun)type_4_tag_listener_run, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h new file mode 100644 index 000000000..7e0301322 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener.h @@ -0,0 +1,26 @@ +#pragma once + +#include "type_4_tag.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Type4TagListener Type4TagListener; + +typedef enum { + Type4TagListenerEventTypeCustomCommand, +} Type4TagListenerEventType; + +typedef struct { + BitBuffer* buffer; +} Type4TagListenerEventData; + +typedef struct { + Type4TagListenerEventType type; + Type4TagListenerEventData* data; +} Type4TagListenerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h new file mode 100644 index 000000000..87206fe6b --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcListenerBase nfc_listener_type_4_tag; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c new file mode 100644 index 000000000..7da6fadc9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.c @@ -0,0 +1,382 @@ +#include "type_4_tag_listener_i.h" +#include "type_4_tag_i.h" + +#include + +#define TAG "Type4TagListener" + +typedef Type4TagError (*Type4TagListenerApduHandler)( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le); + +typedef struct { + uint8_t cla_ins[2]; + Type4TagListenerApduHandler handler; +} Type4TagListenerApduCommand; + +static const uint8_t type_4_tag_success_apdu[] = {TYPE_4_TAG_ISO_STATUS_SUCCESS}; +static const uint8_t type_4_tag_offset_error_apdu[] = {TYPE_4_TAG_ISO_STATUS_OFFSET_ERR}; +static const uint8_t type_4_tag_not_found_apdu[] = {TYPE_4_TAG_ISO_STATUS_NOT_FOUND}; +static const uint8_t type_4_tag_no_support_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_SUPPORT}; +static const uint8_t type_4_tag_bad_params_apdu[] = {TYPE_4_TAG_ISO_STATUS_BAD_PARAMS}; +static const uint8_t type_4_tag_no_cmd_apdu[] = {TYPE_4_TAG_ISO_STATUS_NO_CMD}; + +static Type4TagError type_4_tag_listener_iso_select( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(p2); + UNUSED(le); + + if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_NAME && lc == TYPE_4_TAG_ISO_NAME_LEN) { + if(memcmp(type_4_tag_iso_mf_name, data, sizeof(type_4_tag_iso_mf_name)) == 0) { + instance->state = Type4TagListenerStateSelectedPicc; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(memcmp(type_4_tag_iso_df_name, data, sizeof(type_4_tag_iso_df_name)) == 0) { + instance->state = Type4TagListenerStateSelectedApplication; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + } else if( + (p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID || p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID) && + lc == TYPE_4_TAG_ISO_ID_LEN) { + uint16_t id = bit_lib_bytes_to_num_be(data, sizeof(uint16_t)); + + if(p1 == TYPE_4_TAG_ISO_SELECT_P1_BY_ID) { + if(id == TYPE_4_TAG_ISO_MF_ID) { + instance->state = Type4TagListenerStateSelectedPicc; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(id == TYPE_4_TAG_ISO_DF_ID) { + instance->state = Type4TagListenerStateSelectedApplication; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + } + + if(instance->state >= Type4TagListenerStateSelectedApplication) { + if(id == TYPE_4_TAG_T4T_CC_EF_ID) { + instance->state = Type4TagListenerStateSelectedCapabilityContainer; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(id == (instance->data->is_tag_specific ? instance->data->ndef_file_id : + TYPE_4_TAG_T4T_NDEF_EF_ID)) { + instance->state = Type4TagListenerStateSelectedNdefMessage; + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + } + } + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu)); + return Type4TagErrorCustomCommand; +} + +static Type4TagError type_4_tag_listener_iso_read( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(lc); + UNUSED(data); + + size_t offset; + if(p1 & TYPE_4_TAG_ISO_READ_P1_ID_MASK) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorCustomCommand; + } else { + offset = (p1 << 8) + p2; + } + + if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) { + uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE]; + if(offset >= sizeof(cc_buf)) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf)); + + bit_buffer_append_bytes( + instance->tx_buffer, cc_buf + offset, MIN(sizeof(cc_buf) - offset, le)); + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + if(instance->state == Type4TagListenerStateSelectedNdefMessage) { + size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data); + bool included_len = false; + if(offset < sizeof(uint16_t)) { + uint8_t ndef_file_len_be[sizeof(uint16_t)]; + bit_lib_num_to_bytes_be(ndef_file_len, sizeof(ndef_file_len_be), ndef_file_len_be); + uint8_t read_len = MIN(sizeof(ndef_file_len_be) - offset, le); + bit_buffer_append_bytes(instance->tx_buffer, &ndef_file_len_be[offset], read_len); + included_len = true; + offset = sizeof(uint16_t); + le -= read_len; + } + offset -= sizeof(uint16_t); + + if(offset >= ndef_file_len) { + if(included_len) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } else { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + } + const uint8_t* ndef_data = simple_array_cget_data(instance->data->ndef_data); + bit_buffer_append_bytes( + instance->tx_buffer, &ndef_data[offset], MIN(ndef_file_len - offset, le)); + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu)); + return Type4TagErrorCustomCommand; +} + +static Type4TagError type_4_tag_listener_iso_write( + Type4TagListener* instance, + uint8_t p1, + uint8_t p2, + size_t lc, + const uint8_t* data, + size_t le) { + UNUSED(le); + + size_t offset; + if(p1 & TYPE_4_TAG_ISO_WRITE_P1_ID_MASK) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorCustomCommand; + } else { + offset = (p1 << 8) + p2; + } + + if(instance->state == Type4TagListenerStateSelectedCapabilityContainer) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_support_apdu, sizeof(type_4_tag_no_support_apdu)); + return Type4TagErrorNotSupported; + } + + if(instance->state == Type4TagListenerStateSelectedNdefMessage) { + if(offset + lc > sizeof(uint16_t) + (instance->data->is_tag_specific ? + instance->data->ndef_max_len : + TYPE_4_TAG_DEFAULT_NDEF_SIZE)) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_offset_error_apdu, + sizeof(type_4_tag_offset_error_apdu)); + return Type4TagErrorWrongFormat; + } + + const size_t ndef_file_len = simple_array_get_count(instance->data->ndef_data); + size_t ndef_file_len_new = ndef_file_len; + if(offset < sizeof(uint16_t)) { + const uint8_t write_len = sizeof(uint16_t) - offset; + ndef_file_len_new = bit_lib_bytes_to_num_be(data, write_len); + offset = sizeof(uint16_t); + data += offset; + lc -= write_len; + } + offset -= sizeof(uint16_t); + + ndef_file_len_new = MAX(ndef_file_len_new, offset + lc); + if(ndef_file_len_new != ndef_file_len) { + SimpleArray* ndef_data_temp = simple_array_alloc(&simple_array_config_uint8_t); + if(ndef_file_len_new > 0) { + simple_array_init(ndef_data_temp, ndef_file_len_new); + if(ndef_file_len > 0) { + memcpy( + simple_array_get_data(ndef_data_temp), + simple_array_get_data(instance->data->ndef_data), + MIN(ndef_file_len_new, ndef_file_len)); + } + } + simple_array_copy(instance->data->ndef_data, ndef_data_temp); + simple_array_free(ndef_data_temp); + } + + if(ndef_file_len_new > 0 && lc > 0) { + uint8_t* ndef_data = simple_array_get_data(instance->data->ndef_data); + memcpy(&ndef_data[offset], data, lc); + } + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_success_apdu, sizeof(type_4_tag_success_apdu)); + return Type4TagErrorNone; + } + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_not_found_apdu, sizeof(type_4_tag_not_found_apdu)); + return Type4TagErrorCustomCommand; +} + +static const Type4TagListenerApduCommand type_4_tag_listener_commands[] = { + { + .cla_ins = {TYPE_4_TAG_ISO_SELECT_CMD}, + .handler = type_4_tag_listener_iso_select, + }, + { + .cla_ins = {TYPE_4_TAG_ISO_READ_CMD}, + .handler = type_4_tag_listener_iso_read, + }, + { + .cla_ins = {TYPE_4_TAG_ISO_WRITE_CMD}, + .handler = type_4_tag_listener_iso_write, + }, +}; + +Type4TagError + type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer) { + Type4TagError error = Type4TagErrorNone; + + bit_buffer_reset(instance->tx_buffer); + + do { + typedef struct { + uint8_t cla_ins[2]; + uint8_t p1; + uint8_t p2; + uint8_t body[]; + } Type4TagApdu; + + const size_t buf_size = bit_buffer_get_size_bytes(rx_buffer); + + if(buf_size < sizeof(Type4TagApdu)) { + error = Type4TagErrorWrongFormat; + break; + } + + const Type4TagApdu* apdu = (const Type4TagApdu*)bit_buffer_get_data(rx_buffer); + + Type4TagListenerApduHandler handler = NULL; + for(size_t i = 0; i < COUNT_OF(type_4_tag_listener_commands); i++) { + const Type4TagListenerApduCommand* command = &type_4_tag_listener_commands[i]; + if(memcmp(apdu->cla_ins, command->cla_ins, sizeof(apdu->cla_ins)) == 0) { + handler = command->handler; + break; + } + } + if(!handler) { + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_no_cmd_apdu, sizeof(type_4_tag_no_cmd_apdu)); + error = Type4TagErrorCustomCommand; + break; + } + + size_t body_size = buf_size - offsetof(Type4TagApdu, body); + size_t lc; + const uint8_t* data = apdu->body; + size_t le; + if(body_size == 0) { + lc = 0; + data = NULL; + le = 0; + } else if(body_size == 1) { + lc = 0; + data = NULL; + le = apdu->body[0]; + if(le == 0) { + le = 256; + } + } else if(body_size == 3 && apdu->body[0] == 0) { + lc = 0; + data = NULL; + le = bit_lib_bytes_to_num_be(&apdu->body[1], sizeof(uint16_t)); + if(le == 0) { + le = 65536; + } + } else { + bool extended_lc = false; + if(data[0] == 0) { + extended_lc = true; + lc = bit_lib_bytes_to_num_be(&data[1], sizeof(uint16_t)); + data += 1 + sizeof(uint16_t); + body_size -= 1 + sizeof(uint16_t); + } else { + lc = data[0]; + data++; + body_size--; + } + if(lc == 0 || body_size < lc) { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_bad_params_apdu, + sizeof(type_4_tag_bad_params_apdu)); + error = Type4TagErrorWrongFormat; + break; + } + + if(body_size == lc) { + le = 0; + } else if(!extended_lc && body_size - lc == 1) { + le = data[lc]; + if(le == 0) { + le = 256; + } + } else if(extended_lc && body_size - lc == 2) { + le = bit_lib_bytes_to_num_be(&data[lc], sizeof(uint16_t)); + if(le == 0) { + le = 65536; + } + } else { + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_bad_params_apdu, + sizeof(type_4_tag_bad_params_apdu)); + error = Type4TagErrorWrongFormat; + break; + } + } + + error = handler(instance, apdu->p1, apdu->p2, lc, data, le); + } while(false); + + if(bit_buffer_get_size_bytes(instance->tx_buffer) > 0) { + const Iso14443_4aError iso14443_4a_error = + iso14443_4a_listener_send_block(instance->iso14443_4a_listener, instance->tx_buffer); + + // Keep error flag to show unknown command on screen + if(error != Type4TagErrorCustomCommand) { + error = type_4_tag_process_error(iso14443_4a_error); + } + } + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h new file mode 100644 index 000000000..fd602c974 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_listener_i.h @@ -0,0 +1,38 @@ +#pragma once + +#include "type_4_tag_listener.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagListenerStateIdle, + Type4TagListenerStateSelectedPicc, + Type4TagListenerStateSelectedApplication, + Type4TagListenerStateSelectedCapabilityContainer, + Type4TagListenerStateSelectedNdefMessage, +} Type4TagListenerState; + +struct Type4TagListener { + Iso14443_4aListener* iso14443_4a_listener; + Type4TagData* data; + Type4TagListenerState state; + + BitBuffer* tx_buffer; + + NfcGenericEvent generic_event; + Type4TagListenerEvent type_4_tag_event; + Type4TagListenerEventData type_4_tag_event_data; + NfcGenericCallback callback; + void* context; +}; + +Type4TagError + type_4_tag_listener_handle_apdu(Type4TagListener* instance, const BitBuffer* rx_buffer); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c new file mode 100644 index 000000000..0291d2df9 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.c @@ -0,0 +1,289 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_poller_defs.h" +#include "type_4_tag_i.h" + +#define TAG "Type4TagPoller" + +typedef NfcCommand (*Type4TagPollerReadHandler)(Type4TagPoller* instance); + +static const Type4TagData* type_4_tag_poller_get_data(Type4TagPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +static Type4TagPoller* type_4_tag_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + Type4TagPoller* instance = malloc(sizeof(Type4TagPoller)); + instance->iso14443_4a_poller = iso14443_4a_poller; + instance->data = type_4_tag_alloc(); + instance->tx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(TYPE_4_TAG_BUF_SIZE); + + instance->type_4_tag_event.data = &instance->type_4_tag_event_data; + + instance->general_event.protocol = NfcProtocolType4Tag; + instance->general_event.event_data = &instance->type_4_tag_event; + instance->general_event.instance = instance; + + return instance; +} + +static void type_4_tag_poller_free(Type4TagPoller* instance) { + furi_assert(instance); + + type_4_tag_free(instance->data); + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + free(instance); +} + +static NfcCommand type_4_tag_poller_handler_idle(Type4TagPoller* instance) { + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = Type4TagPollerStateRequestMode; + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_request_mode(Type4TagPoller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->type_4_tag_event.type = Type4TagPollerEventTypeRequestMode; + instance->type_4_tag_event.data->poller_mode.mode = Type4TagPollerModeRead; + instance->type_4_tag_event.data->poller_mode.data = NULL; + + command = instance->callback(instance->general_event, instance->context); + instance->mode = instance->type_4_tag_event.data->poller_mode.mode; + if(instance->mode == Type4TagPollerModeWrite) { + type_4_tag_copy(instance->data, instance->type_4_tag_event.data->poller_mode.data); + } + + instance->state = Type4TagPollerStateDetectPlatform; + return command; +} + +static NfcCommand type_4_tag_poller_handler_detect_platform(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_detect_platform(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Detect platform success"); + } else { + FURI_LOG_W(TAG, "Failed to detect platform"); + } + instance->state = Type4TagPollerStateSelectApplication; + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_select_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_select_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Select application success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to select application"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateApplication; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read CC success"); + instance->state = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerStateReadNdefMessage : + Type4TagPollerStateWriteNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to read CC"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateCapabilityContainer; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_read_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_read_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Read NDEF success"); + instance->state = Type4TagPollerStateSuccess; + } else { + FURI_LOG_E(TAG, "Failed to read NDEF"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_app(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_app(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create application success"); + instance->state = Type4TagPollerStateSelectApplication; + } else { + FURI_LOG_E(TAG, "Failed to create application"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_cc(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_cc(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create CC success"); + instance->state = Type4TagPollerStateReadCapabilityContainer; + } else { + FURI_LOG_E(TAG, "Failed to create CC"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_create_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_create_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Create NDEF success"); + instance->state = Type4TagPollerStateWriteNdefMessage; + } else { + FURI_LOG_E(TAG, "Failed to create NDEF"); + instance->state = Type4TagPollerStateFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_write_ndef(Type4TagPoller* instance) { + instance->error = type_4_tag_poller_write_ndef(instance); + if(instance->error == Type4TagErrorNone) { + FURI_LOG_D(TAG, "Write NDEF success"); + instance->state = Type4TagPollerStateSuccess; + } else { + FURI_LOG_E(TAG, "Failed to write NDEF"); + if(instance->mode == Type4TagPollerModeWrite && + instance->error == Type4TagErrorCardUnformatted) { + instance->state = Type4TagPollerStateCreateNdefMessage; + } else { + instance->state = Type4TagPollerStateFailed; + } + } + + return NfcCommandContinue; +} + +static NfcCommand type_4_tag_poller_handler_failed(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Operation Failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerEventTypeReadFailed : + Type4TagPollerEventTypeWriteFail; + instance->type_4_tag_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = Type4TagPollerStateIdle; + return command; +} + +static NfcCommand type_4_tag_poller_handler_success(Type4TagPoller* instance) { + FURI_LOG_D(TAG, "Operation succeeded"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->type_4_tag_event.type = instance->mode == Type4TagPollerModeRead ? + Type4TagPollerEventTypeReadSuccess : + Type4TagPollerEventTypeWriteSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const Type4TagPollerReadHandler type_4_tag_poller_read_handler[Type4TagPollerStateNum] = { + [Type4TagPollerStateIdle] = type_4_tag_poller_handler_idle, + [Type4TagPollerStateRequestMode] = type_4_tag_poller_handler_request_mode, + [Type4TagPollerStateDetectPlatform] = type_4_tag_poller_handler_detect_platform, + [Type4TagPollerStateSelectApplication] = type_4_tag_poller_handler_select_app, + [Type4TagPollerStateReadCapabilityContainer] = type_4_tag_poller_handler_read_cc, + [Type4TagPollerStateReadNdefMessage] = type_4_tag_poller_handler_read_ndef, + [Type4TagPollerStateCreateApplication] = type_4_tag_poller_handler_create_app, + [Type4TagPollerStateCreateCapabilityContainer] = type_4_tag_poller_handler_create_cc, + [Type4TagPollerStateCreateNdefMessage] = type_4_tag_poller_handler_create_ndef, + [Type4TagPollerStateWriteNdefMessage] = type_4_tag_poller_handler_write_ndef, + [Type4TagPollerStateFailed] = type_4_tag_poller_handler_failed, + [Type4TagPollerStateSuccess] = type_4_tag_poller_handler_success, +}; + +static void type_4_tag_poller_set_callback( + Type4TagPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand type_4_tag_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + furi_assert(instance->callback); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = type_4_tag_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->type_4_tag_event.type = Type4TagPollerEventTypeReadFailed; + instance->type_4_tag_event.data->error = + type_4_tag_process_error(iso14443_4a_event->data->error); + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +static bool type_4_tag_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolIso14443_4a); + + Type4TagPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool protocol_detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + Type4TagError error = type_4_tag_poller_select_app(instance); + if(error == Type4TagErrorNone) { + protocol_detected = true; + } + } + + return protocol_detected; +} + +const NfcPollerBase type_4_tag_poller = { + .alloc = (NfcPollerAlloc)type_4_tag_poller_alloc, + .free = (NfcPollerFree)type_4_tag_poller_free, + .set_callback = (NfcPollerSetCallback)type_4_tag_poller_set_callback, + .run = (NfcPollerRun)type_4_tag_poller_run, + .detect = (NfcPollerDetect)type_4_tag_poller_detect, + .get_data = (NfcPollerGetData)type_4_tag_poller_get_data, +}; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h new file mode 100644 index 000000000..5ac4fd0f2 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller.h @@ -0,0 +1,63 @@ +#pragma once + +#include "type_4_tag.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type4TagPoller opaque type definition. + */ +typedef struct Type4TagPoller Type4TagPoller; + +/** + * @brief Enumeration of possible Type4Tag poller event types. + */ +typedef enum { + Type4TagPollerEventTypeRequestMode, /**< Poller requests for operating mode. */ + Type4TagPollerEventTypeReadSuccess, /**< Card was read successfully. */ + Type4TagPollerEventTypeReadFailed, /**< Poller failed to read card. */ + Type4TagPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ + Type4TagPollerEventTypeWriteFail, /**< Poller failed to write card. */ +} Type4TagPollerEventType; + +/** + * @brief Enumeration of possible Type4Tag poller operating modes. + */ +typedef enum { + Type4TagPollerModeRead, /**< Poller will only read card. It's a default mode. */ + Type4TagPollerModeWrite, /**< Poller will write already saved card to another presented card. */ +} Type4TagPollerMode; + +/** + * @brief Type4Tag poller request mode event data. + * + * This instance of this structure must be filled on Type4TagPollerEventTypeRequestMode event. + */ +typedef struct { + Type4TagPollerMode mode; /**< Mode to be used by poller. */ + const Type4TagData* data; /**< Data to be used by poller. */ +} Type4TagPollerEventDataRequestMode; + +/** + * @brief Type4Tag poller event data. + */ +typedef union { + Type4TagError error; /**< Error code indicating card reading fail reason. */ + Type4TagPollerEventDataRequestMode poller_mode; /**< Poller mode context. */ +} Type4TagPollerEventData; + +/** + * @brief Type4Tag poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + Type4TagPollerEventType type; /**< Type of emmitted event. */ + Type4TagPollerEventData* data; /**< Pointer to event specific data. */ +} Type4TagPollerEvent; + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h new file mode 100644 index 000000000..fe71b560a --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase type_4_tag_poller; diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c new file mode 100644 index 000000000..52f3d6220 --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.c @@ -0,0 +1,501 @@ +#include "type_4_tag_poller_i.h" +#include "type_4_tag_i.h" + +#include + +#include +#include +#include +#include +#include + +#define TAG "Type4TagPoller" + +static const MfDesfireApplicationId mf_des_picc_app_id = {.data = {0x00, 0x00, 0x00}}; +static const MfDesfireApplicationId mf_des_t4t_app_id = {.data = {0x10, 0xEE, 0xEE}}; +static const MfDesfireKeySettings mf_des_t4t_app_key_settings = { + .is_master_key_changeable = true, + .is_free_directory_list = true, + .is_free_create_delete = true, + .is_config_changeable = true, + .change_key_id = 0, + .max_keys = 1, + .flags = 0, +}; +#define MF_DES_T4T_CC_FILE_ID (0x01) +static const MfDesfireFileSettings mf_des_t4t_cc_file = { + .type = MfDesfireFileTypeStandard, + .comm = MfDesfireFileCommunicationSettingsPlaintext, + .access_rights[0] = 0xEEEE, + .access_rights_len = 1, + .data.size = TYPE_4_TAG_T4T_CC_MIN_SIZE, +}; +#define MF_DES_T4T_NDEF_FILE_ID (0x02) +static const MfDesfireFileSettings mf_des_t4t_ndef_file_default = { + .type = MfDesfireFileTypeStandard, + .comm = MfDesfireFileCommunicationSettingsPlaintext, + .access_rights[0] = 0xEEE0, + .access_rights_len = 1, +}; + +Type4TagError type_4_tag_apdu_trx(Type4TagPoller* instance, BitBuffer* tx_buf, BitBuffer* rx_buf) { + furi_check(instance); + + bit_buffer_reset(rx_buf); + + Iso14443_4aError iso14443_4a_error = + iso14443_4a_poller_send_block(instance->iso14443_4a_poller, tx_buf, rx_buf); + + bit_buffer_reset(tx_buf); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + return type_4_tag_process_error(iso14443_4a_error); + } + + size_t response_len = bit_buffer_get_size_bytes(rx_buf); + if(response_len < TYPE_4_TAG_ISO_STATUS_LEN) { + return Type4TagErrorWrongFormat; + } + + static const uint8_t success[TYPE_4_TAG_ISO_STATUS_LEN] = {TYPE_4_TAG_ISO_STATUS_SUCCESS}; + uint8_t status[TYPE_4_TAG_ISO_STATUS_LEN] = { + bit_buffer_get_byte(rx_buf, response_len - 2), + bit_buffer_get_byte(rx_buf, response_len - 1), + }; + bit_buffer_set_size_bytes(rx_buf, response_len - 2); + + if(memcmp(status, success, sizeof(status)) == 0) { + return Type4TagErrorNone; + } else { + FURI_LOG_E(TAG, "APDU failed: %02X%02X", status[0], status[1]); + return Type4TagErrorApduFailed; + } +} + +static Type4TagError type_4_tag_poller_iso_select_name( + Type4TagPoller* instance, + const uint8_t* name, + uint8_t name_len) { + static const uint8_t type_4_tag_iso_select_name_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_NAME, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + }; + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_iso_select_name_apdu, + sizeof(type_4_tag_iso_select_name_apdu)); + bit_buffer_append_byte(instance->tx_buffer, name_len); + bit_buffer_append_bytes(instance->tx_buffer, name, name_len); + + Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted; + + return error; +} + +static Type4TagError + type_4_tag_poller_iso_select_file(Type4TagPoller* instance, uint16_t file_id) { + static const uint8_t type_4_tag_iso_select_file_apdu[] = { + TYPE_4_TAG_ISO_SELECT_CMD, + TYPE_4_TAG_ISO_SELECT_P1_BY_EF_ID, + TYPE_4_TAG_ISO_SELECT_P2_EMPTY, + sizeof(file_id), + }; + uint8_t file_id_be[sizeof(file_id)]; + bit_lib_num_to_bytes_be(file_id, sizeof(file_id), file_id_be); + + bit_buffer_append_bytes( + instance->tx_buffer, + type_4_tag_iso_select_file_apdu, + sizeof(type_4_tag_iso_select_file_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, file_id_be, sizeof(file_id_be)); + + Type4TagError error = type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardUnformatted; + + return error; +} + +static Type4TagError type_4_tag_poller_iso_read( + Type4TagPoller* instance, + uint16_t offset, + uint16_t length, + uint8_t* buffer) { + const uint8_t chunk_max = instance->data->is_tag_specific ? + MIN(instance->data->chunk_max_read, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN; + if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) { + FURI_LOG_E(TAG, "File too large: %zu bytes", length); + return Type4TagErrorNotSupported; + } + + static const uint8_t type_4_tag_iso_read_apdu[] = { + TYPE_4_TAG_ISO_READ_CMD, + }; + + while(length > 0) { + uint8_t chunk_len = MIN(length, chunk_max); + uint8_t offset_be[sizeof(offset)]; + bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be); + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_iso_read_apdu, sizeof(type_4_tag_iso_read_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be)); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + + Type4TagError error = + type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error != Type4TagErrorNone) { + return error; + } + if(bit_buffer_get_size_bytes(instance->rx_buffer) != chunk_len) { + FURI_LOG_E( + TAG, + "Wrong chunk len: %zu != %zu", + bit_buffer_get_size_bytes(instance->rx_buffer), + chunk_len); + return Type4TagErrorWrongFormat; + } + + memcpy(buffer, bit_buffer_get_data(instance->rx_buffer), chunk_len); + buffer += chunk_len; + offset += chunk_len; + length -= chunk_len; + } + + return Type4TagErrorNone; +} + +static Type4TagError type_4_tag_poller_iso_write( + Type4TagPoller* instance, + uint16_t offset, + uint16_t length, + uint8_t* buffer) { + const uint8_t chunk_max = instance->data->is_tag_specific ? + MIN(instance->data->chunk_max_write, TYPE_4_TAG_CHUNK_LEN) : + TYPE_4_TAG_CHUNK_LEN; + if(offset + length > TYPE_4_TAG_ISO_READ_P_OFFSET_MAX + chunk_max - sizeof(length)) { + FURI_LOG_E(TAG, "File too large: %zu bytes", length); + return Type4TagErrorNotSupported; + } + + static const uint8_t type_4_tag_iso_write_apdu[] = { + TYPE_4_TAG_ISO_WRITE_CMD, + }; + + while(length > 0) { + uint8_t chunk_len = MIN(length, chunk_max); + uint8_t offset_be[sizeof(offset)]; + bit_lib_num_to_bytes_be(offset, sizeof(offset_be), offset_be); + + bit_buffer_append_bytes( + instance->tx_buffer, type_4_tag_iso_write_apdu, sizeof(type_4_tag_iso_write_apdu)); + bit_buffer_append_bytes(instance->tx_buffer, offset_be, sizeof(offset_be)); + bit_buffer_append_byte(instance->tx_buffer, chunk_len); + bit_buffer_append_bytes(instance->tx_buffer, buffer, chunk_len); + + Type4TagError error = + type_4_tag_apdu_trx(instance, instance->tx_buffer, instance->rx_buffer); + if(error == Type4TagErrorApduFailed) error = Type4TagErrorCardLocked; + if(error != Type4TagErrorNone) { + return error; + } + + buffer += chunk_len; + offset += chunk_len; + length -= chunk_len; + } + + return Type4TagErrorNone; +} + +Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance) { + furi_check(instance); + + Iso14443_4aPollerEvent iso14443_4a_event = { + .type = Iso14443_4aPollerEventTypeReady, + .data = NULL, + }; + NfcGenericEvent event = { + .protocol = NfcProtocolIso14443_4a, + .instance = instance->iso14443_4a_poller, + .event_data = &iso14443_4a_event, + }; + + Type4TagPlatform platform = Type4TagPlatformUnknown; + NfcDevice* device = nfc_device_alloc(); + + do { + FURI_LOG_D(TAG, "Detect NTAG4xx"); + Ntag4xxPoller* ntag4xx = ntag4xx_poller.alloc(instance->iso14443_4a_poller); + if(ntag4xx_poller.detect(event, ntag4xx)) { + platform = Type4TagPlatformNtag4xx; + nfc_device_set_data(device, NfcProtocolNtag4xx, ntag4xx_poller.get_data(ntag4xx)); + } + ntag4xx_poller.free(ntag4xx); + if(platform != Type4TagPlatformUnknown) break; + + FURI_LOG_D(TAG, "Detect DESFire"); + MfDesfirePoller* mf_desfire = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_desfire, NxpNativeCommandModeIsoWrapped); + if(mf_desfire_poller.detect(event, mf_desfire)) { + platform = Type4TagPlatformMfDesfire; + nfc_device_set_data( + device, NfcProtocolMfDesfire, mf_desfire_poller.get_data(mf_desfire)); + } + mf_desfire_poller.free(mf_desfire); + if(platform != Type4TagPlatformUnknown) break; + } while(false); + + Type4TagError error; + if(platform != Type4TagPlatformUnknown) { + furi_string_set( + instance->data->platform_name, nfc_device_get_name(device, NfcDeviceNameTypeFull)); + error = Type4TagErrorNone; + } else { + furi_string_reset(instance->data->platform_name); + error = Type4TagErrorNotSupported; + } + instance->data->platform = platform; + nfc_device_free(device); + + return error; +} + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance) { + furi_check(instance); + + FURI_LOG_D(TAG, "Select application"); + return type_4_tag_poller_iso_select_name( + instance, type_4_tag_iso_df_name, sizeof(type_4_tag_iso_df_name)); +} + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select CC"); + error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read CC len"); + uint16_t cc_len; + uint8_t cc_len_be[sizeof(cc_len)]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_len_be), cc_len_be); + if(error != Type4TagErrorNone) break; + cc_len = bit_lib_bytes_to_num_be(cc_len_be, sizeof(cc_len_be)); + + FURI_LOG_D(TAG, "Read CC"); + uint8_t cc_buf[cc_len]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(cc_buf), cc_buf); + if(error != Type4TagErrorNone) break; + + error = type_4_tag_cc_parse(instance->data, cc_buf, sizeof(cc_buf)); + if(error != Type4TagErrorNone) break; + instance->data->is_tag_specific = true; + + FURI_LOG_D(TAG, "Detected NDEF file ID %04X", instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Read NDEF len"); + uint16_t ndef_len; + uint8_t ndef_len_be[sizeof(ndef_len)]; + error = type_4_tag_poller_iso_read(instance, 0, sizeof(ndef_len_be), ndef_len_be); + if(error != Type4TagErrorNone) break; + ndef_len = bit_lib_bytes_to_num_be(ndef_len_be, sizeof(ndef_len_be)); + + if(ndef_len == 0) { + FURI_LOG_D(TAG, "NDEF file is empty"); + break; + } + + FURI_LOG_D(TAG, "Read NDEF"); + simple_array_init(instance->data->ndef_data, ndef_len); + uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data); + error = type_4_tag_poller_iso_read(instance, sizeof(ndef_len), ndef_len, ndef_buf); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, "Read %hu bytes from NDEF file %04X", ndef_len, instance->data->ndef_file_id); + } while(false); + + return error; +} + +Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Select DESFire PICC"); + mf_des_error = mf_desfire_poller_select_application(mf_des, &mf_des_picc_app_id); + if(mf_des_error != MfDesfireErrorNone) { + error = Type4TagErrorProtocol; + break; + } + + FURI_LOG_D(TAG, "Create DESFire T4T app"); + mf_des_error = mf_desfire_poller_create_application( + mf_des, + &mf_des_t4t_app_id, + &mf_des_t4t_app_key_settings, + TYPE_4_TAG_ISO_DF_ID, + type_4_tag_iso_df_name, + sizeof(type_4_tag_iso_df_name)); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Create DESFire CC"); + mf_des_error = mf_desfire_poller_create_file( + mf_des, MF_DES_T4T_CC_FILE_ID, &mf_des_t4t_cc_file, TYPE_4_TAG_T4T_CC_EF_ID); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + FURI_LOG_D(TAG, "Select CC"); + error = type_4_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Write DESFire CC"); + instance->data->t4t_version.value = TYPE_4_TAG_T4T_CC_VNO; + instance->data->chunk_max_read = 0x3A; + instance->data->chunk_max_write = 0x34; + instance->data->ndef_file_id = TYPE_4_TAG_T4T_NDEF_EF_ID; + instance->data->ndef_max_len = TYPE_4_TAG_MF_DESFIRE_NDEF_SIZE; + instance->data->ndef_read_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + instance->data->ndef_write_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE; + instance->data->is_tag_specific = true; + uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE]; + type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf)); + error = type_4_tag_poller_iso_write(instance, 0, sizeof(cc_buf), cc_buf); + if(error != Type4TagErrorNone) break; + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance) { + Type4TagError error = Type4TagErrorNotSupported; + + if(instance->data->platform == Type4TagPlatformMfDesfire) { + MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller); + mf_desfire_poller_set_command_mode(mf_des, NxpNativeCommandModeIsoWrapped); + MfDesfireError mf_des_error; + + do { + FURI_LOG_D(TAG, "Create DESFire NDEF"); + MfDesfireFileSettings mf_des_t4t_ndef_file = mf_des_t4t_ndef_file_default; + mf_des_t4t_ndef_file.data.size = sizeof(uint16_t) + (instance->data->is_tag_specific ? + instance->data->ndef_max_len : + TYPE_4_TAG_DEFAULT_NDEF_SIZE); + mf_des_error = mf_desfire_poller_create_file( + mf_des, MF_DES_T4T_NDEF_FILE_ID, &mf_des_t4t_ndef_file, TYPE_4_TAG_T4T_NDEF_EF_ID); + if(mf_des_error != MfDesfireErrorNone) { + if(mf_des_error != MfDesfireErrorNotPresent && + mf_des_error != MfDesfireErrorTimeout) { + error = Type4TagErrorCardLocked; + } else { + error = Type4TagErrorProtocol; + } + break; + } + + error = Type4TagErrorNone; + } while(false); + + mf_desfire_poller.free(mf_des); + } + + return error; +} + +Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance) { + furi_check(instance); + + Type4TagError error; + + do { + FURI_LOG_D(TAG, "Select NDEF"); + error = type_4_tag_poller_iso_select_file(instance, instance->data->ndef_file_id); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D(TAG, "Write NDEF len"); + uint16_t ndef_len = simple_array_get_count(instance->data->ndef_data); + uint8_t ndef_len_be[sizeof(ndef_len)]; + bit_lib_num_to_bytes_be(ndef_len, sizeof(ndef_len_be), ndef_len_be); + error = type_4_tag_poller_iso_write(instance, 0, sizeof(ndef_len_be), ndef_len_be); + if(error != Type4TagErrorNone) break; + + if(ndef_len == 0) { + FURI_LOG_D(TAG, "NDEF file is empty"); + break; + } + + FURI_LOG_D(TAG, "Write NDEF"); + uint8_t* ndef_buf = simple_array_get_data(instance->data->ndef_data); + error = type_4_tag_poller_iso_write(instance, sizeof(ndef_len), ndef_len, ndef_buf); + if(error != Type4TagErrorNone) break; + + FURI_LOG_D( + TAG, "Wrote %hu bytes to NDEF file %04X", ndef_len, instance->data->ndef_file_id); + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h new file mode 100644 index 000000000..1f9142e6e --- /dev/null +++ b/lib/nfc/protocols/type_4_tag/type_4_tag_poller_i.h @@ -0,0 +1,61 @@ +#pragma once + +#include "type_4_tag_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Type4TagPollerStateIdle, + Type4TagPollerStateRequestMode, + Type4TagPollerStateDetectPlatform, + Type4TagPollerStateSelectApplication, + Type4TagPollerStateReadCapabilityContainer, + Type4TagPollerStateReadNdefMessage, + Type4TagPollerStateCreateApplication, + Type4TagPollerStateCreateCapabilityContainer, + Type4TagPollerStateCreateNdefMessage, + Type4TagPollerStateWriteNdefMessage, + Type4TagPollerStateFailed, + Type4TagPollerStateSuccess, + + Type4TagPollerStateNum, +} Type4TagPollerState; + +struct Type4TagPoller { + Iso14443_4aPoller* iso14443_4a_poller; + Type4TagPollerState state; + Type4TagPollerMode mode; + Type4TagError error; + Type4TagData* data; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + Type4TagPollerEventData type_4_tag_event_data; + Type4TagPollerEvent type_4_tag_event; + NfcGenericEvent general_event; + NfcGenericCallback callback; + void* context; +}; + +Type4TagError type_4_tag_poller_detect_platform(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_select_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_read_ndef(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance); + +Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c5d417f3e..73ae7daae 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,86.1,, +Version,+,86.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -155,6 +155,9 @@ Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h,, Header,+,lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b.h,, Header,+,lib/nfc/protocols/iso14443_4b/iso14443_4b_poller.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_listener.h,, +Header,+,lib/nfc/protocols/iso15693_3/iso15693_3_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_listener.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, @@ -167,11 +170,17 @@ Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h,, +Header,+,lib/nfc/protocols/ntag4xx/ntag4xx.h,, +Header,+,lib/nfc/protocols/ntag4xx/ntag4xx_poller.h,, Header,+,lib/nfc/protocols/slix/slix.h,, +Header,+,lib/nfc/protocols/slix/slix_listener.h,, Header,+,lib/nfc/protocols/slix/slix_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller.h,, Header,+,lib/nfc/protocols/st25tb/st25tb_poller_sync.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag_listener.h,, +Header,+,lib/nfc/protocols/type_4_tag/type_4_tag_poller.h,, Header,+,lib/one_wire/maxim_crc.h,, Header,+,lib/one_wire/one_wire_host.h,, Header,+,lib/one_wire/one_wire_slave.h,, @@ -2166,6 +2175,9 @@ Function,+,iso14443_3a_get_device_name,const char*,"const Iso14443_3aData*, NfcD Function,+,iso14443_3a_get_sak,uint8_t,const Iso14443_3aData* Function,+,iso14443_3a_get_uid,const uint8_t*,"const Iso14443_3aData*, size_t*" Function,+,iso14443_3a_is_equal,_Bool,"const Iso14443_3aData*, const Iso14443_3aData*" +Function,+,iso14443_3a_listener_send_standard_frame,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*" +Function,+,iso14443_3a_listener_tx,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*" +Function,+,iso14443_3a_listener_tx_with_custom_parity,Iso14443_3aError,"Iso14443_3aListener*, const BitBuffer*" Function,+,iso14443_3a_load,_Bool,"Iso14443_3aData*, FlipperFormat*, uint32_t" Function,+,iso14443_3a_poller_activate,Iso14443_3aError,"Iso14443_3aPoller*, Iso14443_3aData*" Function,+,iso14443_3a_poller_check_presence,Iso14443_3aError,Iso14443_3aPoller* @@ -2212,6 +2224,7 @@ Function,+,iso14443_4a_get_fwt_fc_max,uint32_t,const Iso14443_4aData* Function,+,iso14443_4a_get_historical_bytes,const uint8_t*,"const Iso14443_4aData*, uint32_t*" Function,+,iso14443_4a_get_uid,const uint8_t*,"const Iso14443_4aData*, size_t*" Function,+,iso14443_4a_is_equal,_Bool,"const Iso14443_4aData*, const Iso14443_4aData*" +Function,+,iso14443_4a_listener_send_block,Iso14443_4aError,"Iso14443_4aListener*, const BitBuffer*" Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" @@ -2256,6 +2269,13 @@ Function,+,iso15693_3_get_uid,const uint8_t*,"const Iso15693_3Data*, size_t*" Function,+,iso15693_3_is_block_locked,_Bool,"const Iso15693_3Data*, uint8_t" Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Data*" Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t" +Function,+,iso15693_3_poller_activate,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3Data*" +Function,+,iso15693_3_poller_get_blocks_security,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t" +Function,+,iso15693_3_poller_get_system_info,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3SystemInfo*" +Function,+,iso15693_3_poller_inventory,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*" +Function,+,iso15693_3_poller_read_block,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t" +Function,+,iso15693_3_poller_read_blocks,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t" +Function,+,iso15693_3_poller_send_frame,Iso15693_3Error,"Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,iso15693_3_reset,void,Iso15693_3Data* Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*" Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t" @@ -2682,6 +2702,8 @@ Function,+,mf_desfire_get_file_settings,const MfDesfireFileSettings*,"const MfDe Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*" Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*" Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t" +Function,+,mf_desfire_poller_create_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*, const MfDesfireKeySettings*, uint16_t, const uint8_t*, uint8_t" +Function,+,mf_desfire_poller_create_file,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, const MfDesfireFileSettings*, uint16_t" Function,+,mf_desfire_poller_read_application,MfDesfireError,"MfDesfirePoller*, MfDesfireApplication*" Function,+,mf_desfire_poller_read_application_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*" Function,+,mf_desfire_poller_read_applications,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*" @@ -2698,9 +2720,10 @@ Function,+,mf_desfire_poller_read_key_version,MfDesfireError,"MfDesfirePoller*, Function,+,mf_desfire_poller_read_key_versions,MfDesfireError,"MfDesfirePoller*, SimpleArray*, uint32_t" Function,+,mf_desfire_poller_read_version,MfDesfireError,"MfDesfirePoller*, MfDesfireVersion*" Function,+,mf_desfire_poller_select_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*" +Function,+,mf_desfire_poller_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" +Function,+,mf_desfire_poller_set_command_mode,void,"MfDesfirePoller*, NxpNativeCommandMode" Function,+,mf_desfire_reset,void,MfDesfireData* Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" -Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" Function,+,mf_plus_alloc,MfPlusData*, @@ -2914,9 +2937,6 @@ Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuff Function,+,nfc_iso14443a_poller_trx_custom_parity,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_iso14443a_poller_trx_sdd_frame,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_iso14443a_poller_trx_short_frame,NfcError,"Nfc*, NfcIso14443aShortFrame, BitBuffer*, uint32_t" -Function,+,nfc_iso15693_detect_mode,NfcError,Nfc* -Function,+,nfc_iso15693_force_1outof256,NfcError,Nfc* -Function,+,nfc_iso15693_force_1outof4,NfcError,Nfc* Function,+,nfc_iso15693_listener_tx_sof,NfcError,Nfc* Function,+,nfc_listener_alloc,NfcListener*,"Nfc*, NfcProtocol, const NfcDeviceData*" Function,+,nfc_listener_free,void,NfcListener* @@ -2956,6 +2976,19 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] +Function,+,ntag4xx_alloc,Ntag4xxData*, +Function,+,ntag4xx_copy,void,"Ntag4xxData*, const Ntag4xxData*" +Function,+,ntag4xx_free,void,Ntag4xxData* +Function,+,ntag4xx_get_base_data,Iso14443_4aData*,const Ntag4xxData* +Function,+,ntag4xx_get_device_name,const char*,"const Ntag4xxData*, NfcDeviceNameType" +Function,+,ntag4xx_get_type_from_version,Ntag4xxType,const Ntag4xxVersion* const +Function,+,ntag4xx_get_uid,const uint8_t*,"const Ntag4xxData*, size_t*" +Function,+,ntag4xx_is_equal,_Bool,"const Ntag4xxData*, const Ntag4xxData*" +Function,+,ntag4xx_load,_Bool,"Ntag4xxData*, FlipperFormat*, uint32_t" +Function,+,ntag4xx_reset,void,Ntag4xxData* +Function,+,ntag4xx_save,_Bool,"const Ntag4xxData*, FlipperFormat*" +Function,+,ntag4xx_set_uid,_Bool,"Ntag4xxData*, const uint8_t*, size_t" +Function,+,ntag4xx_verify,_Bool,"Ntag4xxData*, const FuriString*" Function,+,number_input_alloc,NumberInput*, Function,+,number_input_free,void,NumberInput* Function,+,number_input_get_view,View*,NumberInput* @@ -3660,6 +3693,7 @@ Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* Function,+,submenu_get_selected_item,uint32_t,Submenu* Function,+,submenu_get_view,View*,Submenu* +Function,+,submenu_remove_item,void,"Submenu*, uint32_t" Function,+,submenu_reset,void,Submenu* Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_orientation,void,"Submenu*, ViewOrientation" @@ -3725,6 +3759,18 @@ Function,-,toupper_l,int,"int, locale_t" Function,-,trunc,double,double Function,-,truncf,float,float Function,-,truncl,long double,long double +Function,+,type_4_tag_alloc,Type4TagData*, +Function,+,type_4_tag_copy,void,"Type4TagData*, const Type4TagData*" +Function,+,type_4_tag_free,void,Type4TagData* +Function,+,type_4_tag_get_base_data,Iso14443_4aData*,const Type4TagData* +Function,+,type_4_tag_get_device_name,const char*,"const Type4TagData*, NfcDeviceNameType" +Function,+,type_4_tag_get_uid,const uint8_t*,"const Type4TagData*, size_t*" +Function,+,type_4_tag_is_equal,_Bool,"const Type4TagData*, const Type4TagData*" +Function,+,type_4_tag_load,_Bool,"Type4TagData*, FlipperFormat*, uint32_t" +Function,+,type_4_tag_reset,void,Type4TagData* +Function,+,type_4_tag_save,_Bool,"const Type4TagData*, FlipperFormat*" +Function,+,type_4_tag_set_uid,_Bool,"Type4TagData*, const uint8_t*, size_t" +Function,+,type_4_tag_verify,_Bool,"Type4TagData*, const FuriString*" Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int" Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* @@ -4116,7 +4162,9 @@ Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_plus,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, +Variable,-,nfc_device_ntag4xx,const NfcDeviceBase, Variable,-,nfc_device_st25tb,const NfcDeviceBase, +Variable,-,nfc_device_type_4_tag,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence, Variable,+,sequence_blink_blue_10,const NotificationSequence, Variable,+,sequence_blink_blue_100,const NotificationSequence,