From 7f551fc6ecb91dc3130004de8ed4a321b52f7c40 Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Tue, 24 Mar 2026 13:37:57 -0700 Subject: [PATCH] Stabilize HF plugin lifecycle and SAM cleanup --- sam_api.c | 20 +- scenes/seader_scene_card_menu.c | 3 +- scenes/seader_scene_read.c | 20 +- scenes/seader_scene_read_card_success.c | 17 +- scenes/seader_scene_read_card_type.c | 12 +- scenes/seader_scene_read_config_card.c | 4 + scenes/seader_scene_sam_present.c | 55 +++--- seader.c | 236 +++++++++++++++++++----- seader.h | 16 ++ seader_i.h | 3 + seader_worker.c | 58 +++++- seader_worker.h | 3 + 12 files changed, 336 insertions(+), 111 deletions(-) diff --git a/sam_api.c b/sam_api.c index b59441c..9ff62d7 100644 --- a/sam_api.c +++ b/sam_api.c @@ -1717,8 +1717,6 @@ NfcCommand seader_worker_card_detect( FURI_LOG_E(TAG, "Failed to encode CSN"); return NfcCommandStop; } - OCTET_STRING_t sak_string = {.buf = &sak, .size = 1}; - OCTET_STRING_t ats_string = {.buf = ats, .size = ats_len}; uint8_t protocol_bytes[] = {0x00, 0x00}; // this won't hold true for Seos cards, but then we won't see the SIO from Seos cards anyway @@ -1742,9 +1740,16 @@ NfcCommand seader_worker_card_detect( ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails); return NfcCommandStop; } - cardDetails.sak = &sak_string; + cardDetails.sak = calloc(1, sizeof(*cardDetails.sak)); + cardDetails.atsOrAtqbOrAtr = calloc(1, sizeof(*cardDetails.atsOrAtqbOrAtr)); + if(!cardDetails.sak || !cardDetails.atsOrAtqbOrAtr || + OCTET_STRING_fromBuf(cardDetails.sak, (const char*)&sak, 1) != 0 || + OCTET_STRING_fromBuf(cardDetails.atsOrAtqbOrAtr, (const char*)ats, ats_len) != 0) { + FURI_LOG_E(TAG, "Failed to encode 14A optional card details"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails); + return NfcCommandStop; + } // TODO: Update asn1 to change atqa to ats - cardDetails.atsOrAtqbOrAtr = &ats_string; } else if(uid_len == 8) { // picopass protocol_bytes[1] = FrameProtocol_iclass; if(OCTET_STRING_fromBuf(&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes)) != 0) { @@ -1759,7 +1764,12 @@ NfcCommand seader_worker_card_detect( ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails); return NfcCommandStop; } - cardDetails.sak = &sak_string; + cardDetails.sak = calloc(1, sizeof(*cardDetails.sak)); + if(!cardDetails.sak || OCTET_STRING_fromBuf(cardDetails.sak, (const char*)&sak, 1) != 0) { + FURI_LOG_E(TAG, "Failed to encode MFC SAK"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails); + return NfcCommandStop; + } } seader_sam_set_state( diff --git a/scenes/seader_scene_card_menu.c b/scenes/seader_scene_card_menu.c index e9343e6..f1ef30b 100644 --- a/scenes/seader_scene_card_menu.c +++ b/scenes/seader_scene_card_menu.c @@ -88,8 +88,7 @@ bool seader_scene_card_menu_on_event(void* context, SceneManagerEvent event) { consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - seader->scene_manager, SeaderSceneSamPresent); + consumed = scene_manager_previous_scene(seader->scene_manager); } return consumed; diff --git a/scenes/seader_scene_read.c b/scenes/seader_scene_read.c index ff37080..2ed0d00 100644 --- a/scenes/seader_scene_read.c +++ b/scenes/seader_scene_read.c @@ -39,6 +39,11 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) { if(event.event == SeaderCustomEventWorkerExit) { scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess); consumed = true; + } else if(event.event == SeaderWorkerEventFail) { + scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess); + consumed = true; + } else if(event.event == SeaderWorkerEventHfTeardownComplete) { + consumed = seader_hf_finish_teardown_action(seader); } else if(event.event == SeaderCustomEventPollerDetect) { Popup* popup = seader->popup; popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop); @@ -51,13 +56,9 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) { consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - seader_hf_mode_set_selected_read_type(seader, SeaderCredentialTypeNone); - seader_hf_mode_clear_detected_types(seader); - seader_hf_mode_deactivate(seader); - seader_hf_plugin_release(seader); - scene_manager_search_and_switch_to_previous_scene( - seader->scene_manager, SeaderSceneSamPresent); - consumed = true; + seader_scene_read_cleanup(seader); + seader->hf_teardown_skip_read_cleanup = true; + consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent); } return consumed; @@ -65,8 +66,9 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) { void seader_scene_read_on_exit(void* context) { Seader* seader = context; - if(seader->worker) { - seader_worker_stop(seader->worker); + if(seader->hf_teardown_skip_read_cleanup) { + seader->hf_teardown_skip_read_cleanup = false; + return; } seader_scene_read_cleanup(seader); seader_worker_release(seader); diff --git a/scenes/seader_scene_read_card_success.c b/scenes/seader_scene_read_card_success.c index d53831b..6e6dcde 100644 --- a/scenes/seader_scene_read_card_success.c +++ b/scenes/seader_scene_read_card_success.c @@ -20,7 +20,6 @@ void seader_scene_read_card_success_on_enter(void* context) { SeaderCredential* credential = seader->credential; PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL; Widget* widget = seader->widget; - seader_hf_mode_deactivate(seader); // Use reusable strings instead of allocating new ones FuriString* type_str = seader->temp_string1; @@ -134,23 +133,25 @@ bool seader_scene_read_card_success_on_event(void* context, SceneManagerEvent ev if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_previous_scene(seader->scene_manager); + consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionRestartRead); } else if(event.event == GuiButtonTypeRight) { if(seader->credential->bit_length > 0) { scene_manager_next_scene(seader->scene_manager, SeaderSceneCardMenu); } else { - scene_manager_search_and_switch_to_previous_scene( - seader->scene_manager, SeaderSceneSamPresent); + consumed = + seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent); + } + if(seader->credential->bit_length > 0) { + consumed = true; } - consumed = true; } else if(event.event == GuiButtonTypeCenter) { scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats); consumed = true; + } else if(event.event == SeaderWorkerEventHfTeardownComplete) { + consumed = seader_hf_finish_teardown_action(seader); } } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_search_and_switch_to_previous_scene( - seader->scene_manager, SeaderSceneSamPresent); - consumed = true; + consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent); } return consumed; } diff --git a/scenes/seader_scene_read_card_type.c b/scenes/seader_scene_read_card_type.c index 7194b63..9498c77 100644 --- a/scenes/seader_scene_read_card_type.c +++ b/scenes/seader_scene_read_card_type.c @@ -44,7 +44,9 @@ bool seader_scene_read_card_type_on_event(void* context, SceneManagerEvent event if(event.type == SceneManagerEventTypeCustom) { const SeaderCredentialType type = event.event; - if(type == SeaderCredentialType14A || type == SeaderCredentialTypeMifareClassic || + if(event.event == SeaderWorkerEventHfTeardownComplete) { + consumed = seader_hf_finish_teardown_action(seader); + } else if(type == SeaderCredentialType14A || type == SeaderCredentialTypeMifareClassic || type == SeaderCredentialTypePicopass) { seader_hf_mode_set_selected_read_type(seader, type); seader_hf_mode_clear_detected_types(seader); @@ -52,13 +54,7 @@ bool seader_scene_read_card_type_on_event(void* context, SceneManagerEvent event consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - seader_hf_mode_set_selected_read_type(seader, SeaderCredentialTypeNone); - seader_hf_mode_clear_detected_types(seader); - seader_hf_mode_deactivate(seader); - seader_hf_plugin_release(seader); - scene_manager_search_and_switch_to_previous_scene( - seader->scene_manager, SeaderSceneSamPresent); - consumed = true; + consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent); } return consumed; diff --git a/scenes/seader_scene_read_config_card.c b/scenes/seader_scene_read_config_card.c index 881b094..55da4e7 100644 --- a/scenes/seader_scene_read_config_card.c +++ b/scenes/seader_scene_read_config_card.c @@ -42,6 +42,10 @@ bool seader_scene_read_config_card_on_event(void* context, SceneManagerEvent eve if(event.event == SeaderCustomEventWorkerExit || event.event == SeaderWorkerEventSuccess) { scene_manager_next_scene(seader->scene_manager, SeaderSceneReadConfigCardSuccess); consumed = true; + } else if(event.event == SeaderWorkerEventFail) { + scene_manager_search_and_switch_to_previous_scene( + seader->scene_manager, SeaderSceneSamPresent); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_search_and_switch_to_previous_scene( diff --git a/scenes/seader_scene_sam_present.c b/scenes/seader_scene_sam_present.c index aae7a3b..2a0d921 100644 --- a/scenes/seader_scene_sam_present.c +++ b/scenes/seader_scene_sam_present.c @@ -9,14 +9,9 @@ enum SubmenuIndex { static uint8_t fwChecks = 3; -void seader_scene_sam_present_submenu_callback(void* context, uint32_t index) { - Seader* seader = context; - view_dispatcher_send_custom_event(seader->view_dispatcher, index); -} - -void seader_scene_sam_present_on_update(void* context) { - Seader* seader = context; +void seader_scene_sam_present_submenu_callback(void* context, uint32_t index); +static void seader_scene_sam_present_rebuild_menu(Seader* seader, uint32_t selected_item) { Submenu* submenu = seader->submenu; submenu_reset(submenu); @@ -42,29 +37,32 @@ void seader_scene_sam_present_on_update(void* context) { seader_scene_sam_present_submenu_callback, seader); } + submenu_add_item( + submenu, + seader->sam_key_label, + SubmenuIndexSamInfo, + seader_scene_sam_present_submenu_callback, + seader); + if(seader->sam_version[0] != 0 && seader->sam_version[1] != 0) { - submenu_add_item( - submenu, - seader->sam_key_label, - SubmenuIndexSamInfo, - seader_scene_sam_present_submenu_callback, - seader); fwChecks = 0; - } else { - submenu_add_item( - submenu, - seader->sam_key_label, - SubmenuIndexSamInfo, - seader_scene_sam_present_submenu_callback, - seader); } - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(seader->scene_manager, SeaderSceneSamPresent)); - + submenu_set_selected_item(submenu, selected_item); view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewMenu); } +void seader_scene_sam_present_submenu_callback(void* context, uint32_t index) { + Seader* seader = context; + view_dispatcher_send_custom_event(seader->view_dispatcher, index); +} + +void seader_scene_sam_present_on_update(void* context) { + Seader* seader = context; + seader_scene_sam_present_rebuild_menu( + seader, scene_manager_get_scene_state(seader->scene_manager, SeaderSceneSamPresent)); +} + void seader_scene_sam_present_on_enter(void* context) { seader_scene_sam_present_on_update(context); } @@ -102,18 +100,19 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) { seader->scene_manager, SeaderSceneSamPresent, event.event); scene_manager_next_scene(seader->scene_manager, SeaderSceneAPDURunner); consumed = true; + } else if(event.event == SeaderWorkerEventHfTeardownComplete) { + consumed = seader_hf_finish_teardown_action(seader); } else if(event.event == SeaderCustomEventSamStatusUpdated) { - seader_scene_sam_present_on_update(context); + seader_scene_sam_present_rebuild_menu( + seader, submenu_get_selected_item(seader->submenu)); consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_stop(seader->scene_manager); - view_dispatcher_stop(seader->view_dispatcher); - consumed = true; + consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionStopApp); } else if(event.type == SceneManagerEventTypeTick) { if(fwChecks > 0 && seader->sam_version[0] != 0 && seader->sam_version[1] != 0) { fwChecks--; - seader_scene_sam_present_on_update(context); + seader_scene_sam_present_rebuild_menu(seader, submenu_get_selected_item(seader->submenu)); } } diff --git a/seader.c b/seader.c index a7b0b32..332e76f 100644 --- a/seader.c +++ b/seader.c @@ -7,10 +7,13 @@ #define SEADER_HF_PLUGIN_PATH APP_ASSETS_PATH("plugins/plugin_hf.fal") typedef struct { - bool done; - bool detected; + volatile bool done; + volatile bool detected; } SeaderHfPicopassDetectContext; +static void seader_hf_worker_event_callback(uint32_t event, void* context); +static void seader_hf_teardown_blocking(Seader* seader); + static NfcCommand seader_hf_picopass_detect_callback(PicopassPollerEvent event, void* context) { SeaderHfPicopassDetectContext* detect_context = context; @@ -215,13 +218,18 @@ static bool seader_hf_plugin_picopass_detect(void* host_ctx) { PicopassPoller* poller = picopass_poller_alloc(seader->nfc); SeaderHfPicopassDetectContext detect_context = {0}; + if(!poller) { + FURI_LOG_W(TAG, "Failed to allocate Picopass detect poller"); + return false; + } + picopass_poller_start(poller, seader_hf_picopass_detect_callback, &detect_context); for(uint8_t i = 0; i < 10 && !detect_context.done; i++) { furi_delay_ms(10); } - detected = detect_context.detected; picopass_poller_stop(poller); + detected = detect_context.detected; picopass_poller_free(poller); return detected; @@ -343,6 +351,15 @@ static const PluginHfHostApi seader_hf_plugin_host_api = { .set_read_error = seader_hf_plugin_set_read_error, }; +static void seader_hf_worker_event_callback(uint32_t event, void* context) { + Seader* seader = context; + if(!seader || !seader->view_dispatcher) { + return; + } + + view_dispatcher_send_custom_event(seader->view_dispatcher, event); +} + bool seader_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Seader* seader = context; @@ -384,8 +401,8 @@ Seader* seader_alloc() { seader_uhf_status_label_format( false, false, false, false, seader->uhf_status_label, sizeof(seader->uhf_status_label)); seader_uhf_snmp_probe_init(&seader->snmp_probe); - seader->nfc = NULL; - seader->nfc_device = NULL; + seader->nfc = nfc_alloc(); + seader->nfc_device = seader->nfc ? nfc_device_alloc() : NULL; seader->scratch.offset = 0U; seader->scratch.high_water = 0U; seader->hf_mode = NULL; @@ -405,8 +422,13 @@ Seader* seader_alloc() { seader->credential = seader_credential_alloc(); - seader->nfc = NULL; - seader->nfc_device = NULL; + if(!seader->nfc || !seader->nfc_device) { + FURI_LOG_W( + TAG, + "HF host NFC objects unavailable at startup nfc=%p device=%p", + seader->nfc, + seader->nfc_device); + } // Open GUI record seader->gui = furi_record_open(RECORD_GUI); @@ -458,6 +480,9 @@ Seader* seader_alloc() { seader->hf_plugin_manager = NULL; seader->plugin_hf = NULL; seader->hf_plugin_ctx = NULL; + seader->hf_session_state = SeaderHfSessionStateUnloaded; + seader->hf_teardown_action = SeaderHfTeardownActionNone; + seader->hf_teardown_skip_read_cleanup = false; return seader; } @@ -469,21 +494,31 @@ void seader_free(Seader* seader) { furi_hal_power_disable_otg(); } - seader_uart_free(seader->uart); - seader->uart = NULL; + seader_hf_teardown_blocking(seader); + seader_hf_mode_deactivate(seader); + seader_worker_release(seader); + if(seader->worker) { + seader_worker_free(seader->worker); + seader->worker = NULL; + } - seader_credential_free(seader->credential); - seader->credential = NULL; + seader_wiegand_plugin_release(seader); + + if(seader->nfc_device) { + nfc_device_free(seader->nfc_device); + seader->nfc_device = NULL; + } if(seader->nfc) { nfc_free(seader->nfc); seader->nfc = NULL; } - if(seader->nfc_device) { - nfc_device_free(seader->nfc_device); - seader->nfc_device = NULL; - } + seader_uart_free(seader->uart); + seader->uart = NULL; + + seader_credential_free(seader->credential); + seader->credential = NULL; // Submenu view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewMenu); @@ -516,13 +551,6 @@ void seader_free(Seader* seader) { furi_string_free(seader->temp_string3); furi_string_free(seader->temp_string4); - seader_hf_mode_deactivate(seader); - seader_worker_release(seader); - if(seader->worker) { - seader_worker_free(seader->worker); - seader->worker = NULL; - } - // View Dispatcher view_dispatcher_free(seader->view_dispatcher); @@ -537,9 +565,6 @@ void seader_free(Seader* seader) { furi_record_close(RECORD_NOTIFICATION); seader->notifications = NULL; - seader_wiegand_plugin_release(seader); - seader_hf_plugin_release(seader); - free(seader); } @@ -651,26 +676,36 @@ void seader_wiegand_plugin_release(Seader* seader) { bool seader_hf_plugin_acquire(Seader* seader) { furi_assert(seader); + if(seader->hf_session_state == SeaderHfSessionStateTearingDown) { + FURI_LOG_W(TAG, "Reject HF plugin acquire during teardown"); + return false; + } + if(seader->plugin_hf && seader->hf_plugin_ctx) { + if(seader->hf_session_state == SeaderHfSessionStateUnloaded) { + seader->hf_session_state = SeaderHfSessionStateLoaded; + } return true; } - if(!seader->nfc) { - seader->nfc = nfc_alloc(); - if(!seader->nfc) { - FURI_LOG_E(TAG, "Failed to allocate host NFC"); - return false; - } + if(seader->hf_plugin_manager || seader->plugin_hf || seader->hf_plugin_ctx) { + FURI_LOG_W( + TAG, + "Normalize partial HF session manager=%p plugin=%p ctx=%p state=%d", + (void*)seader->hf_plugin_manager, + (void*)seader->plugin_hf, + seader->hf_plugin_ctx, + seader->hf_session_state); + seader_hf_plugin_release(seader); } - if(!seader->nfc_device) { - seader->nfc_device = nfc_device_alloc(); - if(!seader->nfc_device) { - FURI_LOG_E(TAG, "Failed to allocate host NFC device"); - nfc_free(seader->nfc); - seader->nfc = NULL; - return false; - } + if(!seader->nfc || !seader->nfc_device) { + FURI_LOG_E( + TAG, + "Host NFC objects unavailable nfc=%p device=%p", + seader->nfc, + seader->nfc_device); + return false; } if(!seader->hf_plugin_manager) { @@ -688,6 +723,9 @@ bool seader_hf_plugin_acquire(Seader* seader) { FURI_LOG_E(TAG, "Failed to load HF plugin"); plugin_manager_free(seader->hf_plugin_manager); seader->hf_plugin_manager = NULL; + seader->plugin_hf = NULL; + seader->hf_plugin_ctx = NULL; + seader->hf_session_state = SeaderHfSessionStateUnloaded; return false; } @@ -697,6 +735,8 @@ bool seader_hf_plugin_acquire(Seader* seader) { FURI_LOG_E(TAG, "Failed to resolve HF plugin entry point"); plugin_manager_free(seader->hf_plugin_manager); seader->hf_plugin_manager = NULL; + seader->hf_plugin_ctx = NULL; + seader->hf_session_state = SeaderHfSessionStateUnloaded; return false; } @@ -706,13 +746,44 @@ bool seader_hf_plugin_acquire(Seader* seader) { plugin_manager_free(seader->hf_plugin_manager); seader->hf_plugin_manager = NULL; seader->plugin_hf = NULL; + seader->hf_session_state = SeaderHfSessionStateUnloaded; return false; } + seader->hf_session_state = SeaderHfSessionStateLoaded; FURI_LOG_I(TAG, "HF plugin loaded: %s", seader->plugin_hf->name); return true; } +static bool seader_hf_has_runtime(const Seader* seader) { + return seader && + (seader->hf_plugin_manager || seader->plugin_hf || seader->hf_plugin_ctx || + seader->poller || seader->picopass_poller); +} + +static void seader_hf_teardown_blocking(Seader* seader) { + if(!seader || !seader_hf_has_runtime(seader)) { + return; + } + + if(!seader_worker_acquire(seader) || !seader->worker || !seader->uart) { + FURI_LOG_W(TAG, "HF blocking teardown fallback"); + seader_hf_plugin_release(seader); + return; + } + + seader->hf_session_state = SeaderHfSessionStateTearingDown; + seader_worker_stop(seader->worker); + FURI_LOG_I(TAG, "HF teardown blocking"); + seader_worker_start( + seader->worker, + SeaderWorkerStateHfTeardown, + seader->uart, + NULL, + seader); + seader_worker_join(seader->worker); +} + void seader_hf_plugin_release(Seader* seader) { furi_assert(seader); @@ -727,23 +798,98 @@ void seader_hf_plugin_release(Seader* seader) { seader->hf_plugin_ctx = NULL; seader->plugin_hf = NULL; + if(seader->poller) { + FURI_LOG_I(TAG, "Stopping host NFC poller"); + nfc_poller_stop(seader->poller); + nfc_poller_free(seader->poller); + seader->poller = NULL; + } + + if(seader->picopass_poller) { + FURI_LOG_I(TAG, "Stopping host Picopass poller"); + picopass_poller_stop(seader->picopass_poller); + picopass_poller_free(seader->picopass_poller); + seader->picopass_poller = NULL; + } + if(seader->hf_plugin_manager) { FURI_LOG_I(TAG, "Unloading HF plugin"); plugin_manager_free(seader->hf_plugin_manager); seader->hf_plugin_manager = NULL; } - if(seader->nfc_device) { - nfc_device_free(seader->nfc_device); - seader->nfc_device = NULL; + seader->hf_session_state = SeaderHfSessionStateUnloaded; +} + +bool seader_hf_finish_teardown_action(Seader* seader) { + if(!seader) { + return false; } - if(seader->nfc) { - nfc_free(seader->nfc); - seader->nfc = NULL; + FURI_LOG_I(TAG, "HF teardown complete action=%d", seader->hf_teardown_action); + seader_show_loading_popup(seader, false); + seader->hf_teardown_skip_read_cleanup = false; + seader_hf_mode_set_selected_read_type(seader, SeaderCredentialTypeNone); + seader_hf_mode_clear_detected_types(seader); + seader_hf_mode_deactivate(seader); + + const SeaderHfTeardownAction action = seader->hf_teardown_action; + seader->hf_teardown_action = SeaderHfTeardownActionNone; + + switch(action) { + case SeaderHfTeardownActionSamPresent: + return scene_manager_search_and_switch_to_another_scene( + seader->scene_manager, SeaderSceneSamPresent); + case SeaderHfTeardownActionRestartRead: + scene_manager_next_scene(seader->scene_manager, SeaderSceneRead); + return true; + case SeaderHfTeardownActionStopApp: + scene_manager_stop(seader->scene_manager); + view_dispatcher_stop(seader->view_dispatcher); + return true; + case SeaderHfTeardownActionNone: + default: + return false; } } +bool seader_hf_request_teardown(Seader* seader, SeaderHfTeardownAction action) { + furi_assert(seader); + + FURI_LOG_I( + TAG, + "HF teardown requested action=%d state=%d worker_state=%d", + action, + seader->hf_session_state, + seader->worker ? seader_worker_get_state(seader->worker) : -1); + + seader->hf_teardown_action = action; + if(!seader_hf_has_runtime(seader)) { + seader->hf_session_state = SeaderHfSessionStateUnloaded; + return seader_hf_finish_teardown_action(seader); + } + + if(!seader_worker_acquire(seader)) { + return seader_hf_finish_teardown_action(seader); + } + + if(seader->hf_session_state == SeaderHfSessionStateTearingDown || + (seader->worker && + seader_worker_get_state(seader->worker) == SeaderWorkerStateHfTeardown)) { + return true; + } + + seader->hf_session_state = SeaderHfSessionStateTearingDown; + seader_worker_stop(seader->worker); + seader_worker_start( + seader->worker, + SeaderWorkerStateHfTeardown, + seader->uart, + seader_hf_worker_event_callback, + seader); + return true; +} + bool seader_worker_acquire(Seader* seader) { furi_assert(seader); diff --git a/seader.h b/seader.h index 7999389..8d1cb1c 100644 --- a/seader.h +++ b/seader.h @@ -6,6 +6,20 @@ typedef struct Seader Seader; typedef struct SeaderPollerContainer SeaderPollerContainer; +typedef enum { + SeaderHfSessionStateUnloaded, + SeaderHfSessionStateLoaded, + SeaderHfSessionStateActive, + SeaderHfSessionStateTearingDown, +} SeaderHfSessionState; + +typedef enum { + SeaderHfTeardownActionNone, + SeaderHfTeardownActionSamPresent, + SeaderHfTeardownActionRestartRead, + SeaderHfTeardownActionStopApp, +} SeaderHfTeardownAction; + bool seader_worker_acquire(Seader* seader); void seader_worker_release(Seader* seader); void seader_scratch_reset(Seader* seader); @@ -14,3 +28,5 @@ bool seader_wiegand_plugin_acquire(Seader* seader); void seader_wiegand_plugin_release(Seader* seader); bool seader_hf_plugin_acquire(Seader* seader); void seader_hf_plugin_release(Seader* seader); +bool seader_hf_request_teardown(Seader* seader, SeaderHfTeardownAction action); +bool seader_hf_finish_teardown_action(Seader* seader); diff --git a/seader_i.h b/seader_i.h index 4d0e361..9ac2aea 100644 --- a/seader_i.h +++ b/seader_i.h @@ -187,6 +187,9 @@ struct Seader { PluginManager* hf_plugin_manager; PluginHf* plugin_hf; void* hf_plugin_ctx; + SeaderHfSessionState hf_session_state; + SeaderHfTeardownAction hf_teardown_action; + bool hf_teardown_skip_read_cleanup; APDULog* apdu_log; SeaderAPDURunnerContext apdu_runner_ctx; diff --git a/seader_worker.c b/seader_worker.c index 71188e7..8edb91a 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -18,11 +18,33 @@ void seader_send_card_detected(SeaderUartBridge* seader_uart, CardDetails_t* cardDetails); void seader_worker_reading(Seader* seader); +static void seader_worker_release_hf_session(Seader* seader) { + if(!seader) { + return; + } + + seader_hf_plugin_release(seader); + if(seader->worker) { + seader_worker_reset_poller_session(seader->worker); + } +} + typedef struct { - bool done; - bool detected; + volatile bool done; + volatile bool detected; } SeaderPicopassDetectContext; +static void seader_worker_clear_active_card(Seader* seader, const char* reason) { + if(!seader) { + return; + } + + if(seader_sam_has_active_card(seader)) { + FURI_LOG_I(TAG, "Clear active SAM card (%s)", reason ? reason : "worker"); + seader_send_no_card_detected(seader); + } +} + static NfcCommand seader_worker_picopass_detect_callback(PicopassPollerEvent event, void* context) { SeaderPicopassDetectContext* detect_context = context; @@ -45,14 +67,19 @@ static bool seader_worker_detect_picopass(Nfc* nfc) { PicopassPoller* poller = picopass_poller_alloc(nfc); SeaderPicopassDetectContext detect_context = {0}; + if(!poller) { + FURI_LOG_W(TAG, "Failed to allocate Picopass detect poller"); + return false; + } + picopass_poller_start(poller, seader_worker_picopass_detect_callback, &detect_context); for(uint8_t i = 0; i < 10 && !detect_context.done; i++) { furi_delay_ms(10); } - detected = detect_context.detected; picopass_poller_stop(poller); + detected = detect_context.detected; picopass_poller_free(poller); return detected; @@ -213,6 +240,15 @@ void seader_worker_stop(SeaderWorker* seader_worker) { furi_thread_join(seader_worker->thread); } +void seader_worker_join(SeaderWorker* seader_worker) { + furi_assert(seader_worker); + if(furi_thread_get_state(seader_worker->thread) == FuriThreadStateStopped) { + return; + } + + furi_thread_join(seader_worker->thread); +} + void seader_worker_change_state(SeaderWorker* seader_worker, SeaderWorkerState state) { seader_worker->state = state; } @@ -427,6 +463,12 @@ int32_t seader_worker_task(void* context) { FURI_LOG_D(TAG, "APDU Runner"); seader_apdu_runner_init(seader); return 0; + } else if(seader_worker->state == SeaderWorkerStateHfTeardown) { + FURI_LOG_I(TAG, "HF teardown started"); + seader_worker_release_hf_session(seader); + if(seader_worker->callback) { + seader_worker->callback(SeaderWorkerEventHfTeardownComplete, seader_worker->context); + } } else if(seader_worker->state == SeaderWorkerStateReading) { FURI_LOG_D(TAG, "Reading mode started"); seader_worker_reading(seader); @@ -442,6 +484,7 @@ void seader_worker_reading(Seader* seader) { if(!seader_hf_plugin_acquire(seader) || !seader->plugin_hf || !seader->hf_plugin_ctx) { FURI_LOG_E(TAG, "HF plugin unavailable"); + strlcpy(seader->read_error, "HF plugin unavailable", sizeof(seader->read_error)); if(seader_worker->callback) { seader_worker->callback(SeaderWorkerEventFail, seader_worker->context); } @@ -475,6 +518,9 @@ void seader_worker_reading(Seader* seader) { if(type_to_read != SeaderCredentialTypeNone) { FURI_LOG_I(TAG, "HF start read for type=%d", type_to_read); detected = seader->plugin_hf->start_read_for_type(seader->hf_plugin_ctx, type_to_read); + if(detected) { + seader->hf_session_state = SeaderHfSessionStateActive; + } FURI_LOG_I(TAG, "HF start read result=%d", detected); } @@ -488,8 +534,9 @@ void seader_worker_reading(Seader* seader) { furi_delay_ms(10); } result_stage = seader_worker->stage; - - seader->plugin_hf->stop(seader->hf_plugin_ctx); + seader_worker_clear_active_card( + seader, + result_stage == SeaderPollerEventTypeComplete ? "read-complete" : "read-abort"); if(result_stage == SeaderPollerEventTypeComplete) { // Notify UI of success @@ -504,7 +551,6 @@ void seader_worker_reading(Seader* seader) { furi_delay_ms(50); } } - seader->plugin_hf->stop(seader->hf_plugin_ctx); FURI_LOG_I(TAG, "Reading loop stopped"); } diff --git a/seader_worker.h b/seader_worker.h index ac7e7fd..ce9e1b3 100644 --- a/seader_worker.h +++ b/seader_worker.h @@ -24,6 +24,7 @@ typedef enum { SeaderWorkerStateVirtualCredential, SeaderWorkerStateAPDURunner, SeaderWorkerStateReading, + SeaderWorkerStateHfTeardown, // Transition SeaderWorkerStateStop, } SeaderWorkerState; @@ -44,6 +45,7 @@ typedef enum { SeaderWorkerEventAPDURunnerUpdate, SeaderWorkerEventAPDURunnerSuccess, SeaderWorkerEventAPDURunnerError, + SeaderWorkerEventHfTeardownComplete, } SeaderWorkerEvent; typedef enum { @@ -71,6 +73,7 @@ void seader_worker_start( void* context); void seader_worker_stop(SeaderWorker* seader_worker); +void seader_worker_join(SeaderWorker* seader_worker); bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t len); void seader_worker_send_version(Seader* seader); void seader_worker_cancel_poller_session(SeaderWorker* seader_worker);