From 4ffc656a230912621e44eac81929ddc54abbbb00 Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Sat, 7 Mar 2026 00:27:56 -0800 Subject: [PATCH] Improve SAM read state handling and failure recovery --- sam_api.c | 153 +++++++++++++++++++++++- sam_api.h | 1 + scenes/seader_scene_read_card_success.c | 19 ++- scenes/seader_scene_read_common.c | 5 +- seader_i.h | 1 + seader_worker.c | 5 + trace_log.c | 27 +++++ trace_log.h | 28 ++++- 8 files changed, 230 insertions(+), 9 deletions(-) diff --git a/sam_api.c b/sam_api.c index 41d8e8b..8f61b16 100644 --- a/sam_api.c +++ b/sam_api.c @@ -51,6 +51,105 @@ static void } } +#ifdef SEADER_ENABLE_TRACE_LOG + +static void seader_trace_mfc_packed_frame(const char* prefix, const uint8_t* buffer, size_t len) { + if(!buffer || len == 0) { + seader_trace(TAG, "%s ", prefix); + return; + } + + if(len < 2) { + seader_trace_hex(TAG, prefix, buffer, len); + return; + } + + uint8_t packed[SEADER_POLLER_MAX_BUFFER_SIZE] = {0}; + if(len > sizeof(packed)) { + seader_trace_hex(TAG, prefix, buffer, len); + return; + } + memcpy(packed, buffer, len); + + uint8_t parity = 0; + size_t decoded_len = len - 1; + uint8_t decoded[SEADER_POLLER_MAX_BUFFER_SIZE] = {0}; + char parity_bits[SEADER_POLLER_MAX_BUFFER_SIZE + 1] = {0}; + + for(size_t i = 0; i < len; i++) { + bit_lib_reverse_bits(packed + i, 0, 8); + } + + for(size_t i = 0; i < decoded_len; i++) { + bool val = bit_lib_get_bit(packed + i + 1, i); + bit_lib_set_bit(&parity, i, val); + } + + for(size_t i = 0; i < decoded_len; i++) { + packed[i] = (packed[i] << i) | (packed[i + 1] >> (8 - i)); + bit_lib_reverse_bits(packed + i, 0, 8); + decoded[i] = packed[i]; + parity_bits[i] = bit_lib_get_bit(&parity, i) ? '1' : '0'; + } + parity_bits[decoded_len] = '\0'; + + seader_trace_hex(TAG, prefix, buffer, len); + seader_trace_hex(TAG, "mfc tx decoded", decoded, decoded_len); + seader_trace(TAG, "mfc tx parity bits=%s", parity_bits); +} + +static void seader_trace_mfc_bitbuffer( + const char* prefix, + BitBuffer* buffer, + bool include_parity) { + if(!buffer) { + seader_trace(TAG, "%s ", prefix); + return; + } + + size_t len = bit_buffer_get_size_bytes(buffer); + uint8_t bytes[SEADER_POLLER_MAX_BUFFER_SIZE] = {0}; + char parity_bits[SEADER_POLLER_MAX_BUFFER_SIZE + 1] = {0}; + + if(len > sizeof(bytes)) len = sizeof(bytes); + + for(size_t i = 0; i < len; i++) { + bytes[i] = bit_buffer_get_byte(buffer, i); + if(include_parity) { + const uint8_t* parity = bit_buffer_get_parity(buffer); + parity_bits[i] = bit_lib_get_bit(parity, i) ? '1' : '0'; + } + } + + if(include_parity) { + parity_bits[len] = '\0'; + } + + seader_trace_hex(TAG, prefix, bytes, len); + if(include_parity) { + seader_trace(TAG, "%s parity=%s", prefix, parity_bits); + } +} + +#else + +static void seader_trace_mfc_packed_frame(const char* prefix, const uint8_t* buffer, size_t len) { + (void)prefix; + (void)buffer; + (void)len; +} + +static void seader_trace_mfc_bitbuffer( + const char* prefix, + BitBuffer* buffer, + bool include_parity) { + (void)prefix; + (void)buffer; + (void)include_parity; +} + +#endif + uint8_t updateBlock2[] = {RFAL_PICOPASS_CMD_UPDATE, 0x02}; uint8_t select_seos_app[] = @@ -96,6 +195,13 @@ bool seader_sam_can_accept_card(const Seader* seader) { return seader->sam_state == SeaderSamStateIdle; } +bool seader_sam_has_active_card(const Seader* seader) { + return + seader->sam_state == SeaderSamStateDetectPending || + seader->sam_state == SeaderSamStateConversation || + seader->sam_state == SeaderSamStateFinishing; +} + PicopassError seader_worker_fake_epurse_update(BitBuffer* tx_buffer, BitBuffer* rx_buffer) { const uint8_t* buffer = bit_buffer_get_data(tx_buffer); uint8_t fake_response[8]; @@ -732,7 +838,9 @@ static void seader_abort_active_read(Seader* seader) { seader->sam_state, seader->sam_intent); seader_worker->stage = SeaderPollerEventTypeFail; - seader_sam_set_state(seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING); + if(!seader_sam_has_active_card(seader) && seader->sam_state != SeaderSamStateClearPending) { + seader_sam_set_state(seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING); + } view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit); } @@ -1050,15 +1158,38 @@ void seader_mfc_transmit( BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE); do { + seader_trace( + TAG, + "mfc tx format=%02x%02x%02x len=%u", + format[0], + format[1], + format[2], + (unsigned)len); + if( + (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x40) || + (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x24) || + (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x44)) { + seader_trace_mfc_packed_frame("mfc tx raw", buffer, len); + } else { + seader_trace_hex(TAG, "mfc tx raw", buffer, len); + } + if(format[0] == 0x00 && format[1] == 0xC0 && format[2] == 0x00) { bit_buffer_append_bytes(tx_buffer, buffer, len); MfClassicError error = mf_classic_poller_send_frame(mfc_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC); if(error != MfClassicErrorNone) { FURI_LOG_W(TAG, "mf_classic_poller_send_frame error %d", error); + seader_trace(TAG, "mfc send_frame error=%d", error); seader_worker->stage = SeaderPollerEventTypeFail; break; } + + seader_trace_hex( + TAG, + "mfc rx raw", + bit_buffer_get_data(rx_buffer), + bit_buffer_get_size_bytes(rx_buffer)); } else if( (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x40) || (format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x24) || @@ -1090,6 +1221,7 @@ void seader_mfc_transmit( bit_buffer_set_byte_with_parity( tx_buffer, i, buffer[i], bit_lib_get_bit(&tx_parity, i)); } + seader_trace_mfc_bitbuffer("mfc tx bitbuffer", tx_buffer, true); // Log the BitBuffer contents efficiently size_t tx_size = bit_buffer_get_size_bytes(tx_buffer); @@ -1099,6 +1231,7 @@ void seader_mfc_transmit( tx_data[i] = bit_buffer_get_byte(tx_buffer, i); } seader_log_hex_data(TAG, "NFC Send without parity", tx_data, tx_size); + seader_trace_hex(TAG, "mfc tx no parity", tx_data, tx_size); free(tx_data); } @@ -1106,12 +1239,22 @@ void seader_mfc_transmit( mfc_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC); if(error != MfClassicErrorNone) { FURI_LOG_W(TAG, "mf_classic_poller_send_encrypted_frame error %d", error); + seader_trace(TAG, "mfc send_custom_parity error=%d", error); + if( + error == MfClassicErrorTimeout && + seader->credential->type == SeaderCredentialTypeMifareClassic) { + snprintf( + seader->read_error, + sizeof(seader->read_error), + "Protected read timed out.\nNo supported data\nor wrong key."); + } seader_worker->stage = SeaderPollerEventTypeFail; break; } size_t length = bit_buffer_get_size_bytes(rx_buffer); const uint8_t* rx_parity = bit_buffer_get_parity(rx_buffer); + seader_trace_mfc_bitbuffer("mfc rx bitbuffer", rx_buffer, true); // Log the BitBuffer contents efficiently uint8_t* rx_data = malloc(length); @@ -1120,6 +1263,7 @@ void seader_mfc_transmit( rx_data[i] = bit_buffer_get_byte(rx_buffer, i); } seader_log_hex_data(TAG, "NFC Response without parity", rx_data, length); + seader_trace_hex(TAG, "mfc rx no parity", rx_data, length); free(rx_data); } @@ -1166,11 +1310,18 @@ void seader_mfc_transmit( rx_data_parity[i] = bit_buffer_get_byte(rx_buffer, i); } seader_log_hex_data(TAG, "NFC Response with parity", rx_data_parity, length); + seader_trace_hex(TAG, "mfc rx parity", rx_data_parity, length); free(rx_data_parity); } } else { FURI_LOG_W(TAG, "UNHANDLED FORMAT"); + seader_trace( + TAG, + "mfc unhandled format=%02x%02x%02x", + format[0], + format[1], + format[2]); } seader_send_nfc_rx( diff --git a/sam_api.h b/sam_api.h index 25a5f83..b8bc75e 100644 --- a/sam_api.h +++ b/sam_api.h @@ -28,6 +28,7 @@ NfcCommand seader_worker_card_detect( void seader_send_no_card_detected(Seader* seader); bool seader_sam_can_accept_card(const Seader* seader); +bool seader_sam_has_active_card(const Seader* seader); bool seader_process_success_response_i( Seader* seader, diff --git a/scenes/seader_scene_read_card_success.c b/scenes/seader_scene_read_card_success.c index 75b04ba..c5e2f9e 100644 --- a/scenes/seader_scene_read_card_success.c +++ b/scenes/seader_scene_read_card_success.c @@ -54,6 +54,7 @@ void seader_scene_read_card_success_on_enter(void* context) { } } else { furi_string_set(type_str, "Read error"); + furi_string_set(bitlength_str, seader->read_error[0] ? seader->read_error : "Read failed"); SeaderWorker* seader_worker = seader->worker; SeaderUartBridge* seader_uart = seader_worker->uart; @@ -64,10 +65,15 @@ void seader_scene_read_card_success_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", seader_scene_read_card_success_widget_callback, seader); - widget_add_button_element( - widget, GuiButtonTypeRight, "More", seader_scene_read_card_success_widget_callback, seader); + if(credential->bit_length > 0) { + widget_add_button_element( + widget, GuiButtonTypeRight, "More", seader_scene_read_card_success_widget_callback, seader); + } else { + widget_add_button_element( + widget, GuiButtonTypeRight, "Back", seader_scene_read_card_success_widget_callback, seader); + } - if(plugin) { + if(plugin && credential->bit_length > 0) { size_t format_count = plugin->count(credential->bit_length, credential->credential); if(format_count > 0) { widget_add_button_element( @@ -127,7 +133,12 @@ bool seader_scene_read_card_success_on_event(void* context, SceneManagerEvent ev if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(seader->scene_manager); } else if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(seader->scene_manager, SeaderSceneCardMenu); + 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 = true; } else if(event.event == GuiButtonTypeCenter) { scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats); diff --git a/scenes/seader_scene_read_common.c b/scenes/seader_scene_read_common.c index 2a86845..99a463d 100644 --- a/scenes/seader_scene_read_common.c +++ b/scenes/seader_scene_read_common.c @@ -11,6 +11,7 @@ void seader_scene_read_prepare(Seader* seader) { if(seader->sam_state == SeaderSamStateIdle) { seader->samCommand = SamCommand_PR_NOTHING; } + memset(seader->read_error, 0, sizeof(seader->read_error)); seader_worker_reset_poller_session(seader->worker); } @@ -21,9 +22,7 @@ void seader_scene_read_cleanup(Seader* seader) { "SceneRead", "cleanup sam=%d state=%d intent=%d", seader->samCommand, seader->sam_state, seader->sam_intent); seader_worker_cancel_poller_session(seader->worker); - if( - seader->sam_state == SeaderSamStateDetectPending || - seader->sam_state == SeaderSamStateConversation) { + if(seader_sam_has_active_card(seader)) { seader_send_no_card_detected(seader); } diff --git a/seader_i.h b/seader_i.h index 8698ca8..de5f92b 100644 --- a/seader_i.h +++ b/seader_i.h @@ -133,6 +133,7 @@ struct Seader { size_t ATR_len; char text_store[SEADER_TEXT_STORE_SIZE + 1]; + char read_error[SEADER_TEXT_STORE_SIZE + 1]; FuriString* text_box_store; // Reusable strings to optimize scene string allocations diff --git a/seader_worker.c b/seader_worker.c index c333e21..79bd58d 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -496,9 +496,14 @@ NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* contex } else if(seader_worker->stage == SeaderPollerEventTypeComplete) { ret = NfcCommandStop; } else if(seader_worker->stage == SeaderPollerEventTypeFail) { + seader_trace(TAG, "mfc ready in Fail"); + view_dispatcher_send_custom_event( + seader->view_dispatcher, SeaderCustomEventWorkerExit); ret = NfcCommandStop; } } else if(mfc_event->type == MfClassicPollerEventTypeFail) { + seader_trace(TAG, "mfc poller event fail"); + view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit); ret = NfcCommandStop; } diff --git a/trace_log.c b/trace_log.c index e236758..4b3240a 100644 --- a/trace_log.c +++ b/trace_log.c @@ -1,5 +1,7 @@ #include "trace_log.h" +#ifdef SEADER_ENABLE_TRACE_LOG + #include #include @@ -33,3 +35,28 @@ void seader_trace(const char* tag, const char* fmt, ...) { snprintf(line, sizeof(line), "[%s] %s\n", tag, message); seader_trace_write(FSOM_OPEN_ALWAYS, line); } + +void seader_trace_hex(const char* tag, const char* prefix, const uint8_t* data, size_t len) { + if(!data || len == 0) { + seader_trace(tag, "%s ", prefix); + return; + } + + /* Keep trace lines bounded; enough to diagnose short RF exchanges. */ + const size_t max_bytes = 32; + size_t trace_len = len > max_bytes ? max_bytes : len; + + char hex[(max_bytes * 2) + 1]; + for(size_t i = 0; i < trace_len; i++) { + snprintf(hex + (i * 2), sizeof(hex) - (i * 2), "%02x", data[i]); + } + hex[trace_len * 2] = '\0'; + + if(trace_len < len) { + seader_trace(tag, "%s len=%u data=%s...", prefix, (unsigned)len, hex); + } else { + seader_trace(tag, "%s len=%u data=%s", prefix, (unsigned)len, hex); + } +} + +#endif diff --git a/trace_log.h b/trace_log.h index 3bf280d..cda847f 100644 --- a/trace_log.h +++ b/trace_log.h @@ -1,8 +1,34 @@ #pragma once -#include +#include +#include + +#ifdef SEADER_ENABLE_TRACE_LOG + +/* Define SEADER_ENABLE_TRACE_LOG in the app build to enable SD-card tracing. */ #define SEADER_TRACE_FILE_NAME APP_DATA_PATH("trace.log") void seader_trace_reset(void); void seader_trace(const char* tag, const char* fmt, ...); +void seader_trace_hex(const char* tag, const char* prefix, const uint8_t* data, size_t len); + +#else + +static inline void seader_trace_reset(void) { +} + +static inline void seader_trace(const char* tag, const char* fmt, ...) { + (void)tag; + (void)fmt; +} + +static inline void + seader_trace_hex(const char* tag, const char* prefix, const uint8_t* data, size_t len) { + (void)tag; + (void)prefix; + (void)data; + (void)len; +} + +#endif