From 9c8b24bb410ef37e42e99a4d6475de690833468a Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Wed, 25 Mar 2026 00:55:52 -0700 Subject: [PATCH 1/5] begin work on multi tech --- Makefile | 2 + lib/host_tests/test_hf_read_plan.c | 118 +++++++++++++++++++++++++++++ lib/host_tests/test_main.c | 2 + seader_hf_read_plan.c | 49 ++++++++++++ seader_hf_read_plan.h | 23 ++++++ seader_worker.c | 31 ++++---- 6 files changed, 211 insertions(+), 14 deletions(-) create mode 100644 lib/host_tests/test_hf_read_plan.c create mode 100644 seader_hf_read_plan.c create mode 100644 seader_hf_read_plan.h diff --git a/Makefile b/Makefile index 83080b2..bb5b89b 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ test-host: lib/host_tests/test_snmp.c \ lib/host_tests/test_uhf_status_label.c \ lib/host_tests/test_credential_sio_label.c \ + lib/host_tests/test_hf_read_plan.c \ lib/host_tests/test_runtime_policy.c \ lib/host_tests/t1_test_stubs.c \ lib/host_tests/bit_buffer_mock.c \ @@ -40,6 +41,7 @@ test-host: uhf_status_label.c \ uhf_tag_config_view.c \ uhf_snmp_probe.c \ + seader_hf_read_plan.c \ runtime_policy.c \ -o build/host_tests/seader_tests ./build/host_tests/seader_tests diff --git a/lib/host_tests/test_hf_read_plan.c b/lib/host_tests/test_hf_read_plan.c new file mode 100644 index 0000000..c839b66 --- /dev/null +++ b/lib/host_tests/test_hf_read_plan.c @@ -0,0 +1,118 @@ +#include "munit.h" + +#include "seader_hf_read_plan.h" + +static MunitResult test_selected_type_starts_immediately( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = { + SeaderCredentialType14A, + SeaderCredentialTypeMifareClassic, + }; + const SeaderHfReadPlan plan = seader_hf_read_plan_build( + SeaderCredentialTypePicopass, detected_types, 2U); + + munit_assert_int(plan.decision, ==, SeaderHfReadDecisionStartRead); + munit_assert_int(plan.type_to_read, ==, SeaderCredentialTypePicopass); + munit_assert_size(plan.detected_type_count, ==, 0U); + return MUNIT_OK; +} + +static MunitResult test_no_detected_types_continues_polling( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderHfReadPlan plan = seader_hf_read_plan_build(SeaderCredentialTypeNone, NULL, 0U); + + munit_assert_int(plan.decision, ==, SeaderHfReadDecisionContinuePolling); + munit_assert_int(plan.type_to_read, ==, SeaderCredentialTypeNone); + munit_assert_size(plan.detected_type_count, ==, 0U); + return MUNIT_OK; +} + +static MunitResult test_single_detected_type_starts_read( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = {SeaderCredentialType14A}; + const SeaderHfReadPlan plan = + seader_hf_read_plan_build(SeaderCredentialTypeNone, detected_types, 1U); + + munit_assert_int(plan.decision, ==, SeaderHfReadDecisionStartRead); + munit_assert_int(plan.type_to_read, ==, SeaderCredentialType14A); + munit_assert_size(plan.detected_type_count, ==, 1U); + munit_assert_int(plan.detected_types[0], ==, SeaderCredentialType14A); + return MUNIT_OK; +} + +static MunitResult test_multiple_detected_types_requests_selection( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = { + SeaderCredentialType14A, + SeaderCredentialTypeMifareClassic, + SeaderCredentialTypePicopass, + }; + const SeaderHfReadPlan plan = + seader_hf_read_plan_build(SeaderCredentialTypeNone, detected_types, 3U); + + munit_assert_int(plan.decision, ==, SeaderHfReadDecisionSelectType); + munit_assert_int(plan.type_to_read, ==, SeaderCredentialTypeNone); + munit_assert_size(plan.detected_type_count, ==, 3U); + munit_assert_int(plan.detected_types[0], ==, SeaderCredentialType14A); + munit_assert_int(plan.detected_types[1], ==, SeaderCredentialTypeMifareClassic); + munit_assert_int(plan.detected_types[2], ==, SeaderCredentialTypePicopass); + return MUNIT_OK; +} + +static MunitResult test_detected_types_are_deduped_and_clamped( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = { + SeaderCredentialType14A, + SeaderCredentialTypeMifareClassic, + SeaderCredentialType14A, + SeaderCredentialTypeNone, + SeaderCredentialTypePicopass, + SeaderCredentialTypeConfig, + }; + const SeaderHfReadPlan plan = + seader_hf_read_plan_build(SeaderCredentialTypeNone, detected_types, 6U); + + munit_assert_int(plan.decision, ==, SeaderHfReadDecisionSelectType); + munit_assert_size(plan.detected_type_count, ==, 3U); + munit_assert_int(plan.detected_types[0], ==, SeaderCredentialType14A); + munit_assert_int(plan.detected_types[1], ==, SeaderCredentialTypeMifareClassic); + munit_assert_int(plan.detected_types[2], ==, SeaderCredentialTypePicopass); + return MUNIT_OK; +} + +static MunitTest test_hf_read_plan_cases[] = { + {(char*)"/selected-type", test_selected_type_starts_immediately, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/none-detected", test_no_detected_types_continues_polling, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/single-detected", test_single_detected_type_starts_read, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/multiple-detected", test_multiple_detected_types_requests_selection, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/dedupe-and-clamp", test_detected_types_are_deduped_and_clamped, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_hf_read_plan_suite = { + "", + test_hf_read_plan_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_main.c b/lib/host_tests/test_main.c index 32ec92a..1786ed3 100644 --- a/lib/host_tests/test_main.c +++ b/lib/host_tests/test_main.c @@ -8,6 +8,7 @@ extern MunitSuite test_t1_protocol_suite; extern MunitSuite test_snmp_suite; extern MunitSuite test_uhf_status_label_suite; extern MunitSuite test_credential_sio_label_suite; +extern MunitSuite test_hf_read_plan_suite; extern MunitSuite test_runtime_policy_suite; int main(int argc, char* argv[]) { @@ -20,6 +21,7 @@ int main(int argc, char* argv[]) { {"/snmp", test_snmp_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/uhf-status-label", test_uhf_status_label_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/credential-sio-label", test_credential_sio_label_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/hf-read-plan", test_hf_read_plan_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/runtime-policy", test_runtime_policy_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {NULL, NULL, NULL, 0, 0}, }; diff --git a/seader_hf_read_plan.c b/seader_hf_read_plan.c new file mode 100644 index 0000000..6e9dd69 --- /dev/null +++ b/seader_hf_read_plan.c @@ -0,0 +1,49 @@ +#include "seader_hf_read_plan.h" + +#define SEADER_HF_READ_PLAN_MAX_TYPES 3U + +static void seader_hf_read_plan_add_type( + SeaderHfReadPlan* plan, + SeaderCredentialType type) { + if(type == SeaderCredentialTypeNone) { + return; + } + + for(size_t i = 0; i < plan->detected_type_count; i++) { + if(plan->detected_types[i] == type) { + return; + } + } + + if(plan->detected_type_count < SEADER_HF_READ_PLAN_MAX_TYPES) { + plan->detected_types[plan->detected_type_count++] = type; + } +} + +SeaderHfReadPlan seader_hf_read_plan_build( + SeaderCredentialType selected_type, + const SeaderCredentialType* detected_types, + size_t detected_type_count) { + SeaderHfReadPlan plan = {0}; + + if(selected_type != SeaderCredentialTypeNone) { + plan.decision = SeaderHfReadDecisionStartRead; + plan.type_to_read = selected_type; + return plan; + } + + for(size_t i = 0; i < detected_type_count; i++) { + seader_hf_read_plan_add_type(&plan, detected_types[i]); + } + + if(plan.detected_type_count == 1U) { + plan.decision = SeaderHfReadDecisionStartRead; + plan.type_to_read = plan.detected_types[0]; + } else if(plan.detected_type_count > 1U) { + plan.decision = SeaderHfReadDecisionSelectType; + } else { + plan.decision = SeaderHfReadDecisionContinuePolling; + } + + return plan; +} diff --git a/seader_hf_read_plan.h b/seader_hf_read_plan.h new file mode 100644 index 0000000..380c7fe --- /dev/null +++ b/seader_hf_read_plan.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "seader_credential_type.h" + +typedef enum { + SeaderHfReadDecisionContinuePolling, + SeaderHfReadDecisionStartRead, + SeaderHfReadDecisionSelectType, +} SeaderHfReadDecision; + +typedef struct { + SeaderHfReadDecision decision; + SeaderCredentialType type_to_read; + SeaderCredentialType detected_types[3]; + size_t detected_type_count; +} SeaderHfReadPlan; + +SeaderHfReadPlan seader_hf_read_plan_build( + SeaderCredentialType selected_type, + const SeaderCredentialType* detected_types, + size_t detected_type_count); diff --git a/seader_worker.c b/seader_worker.c index 4922be9..344700a 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -1,4 +1,5 @@ #include "seader_worker_i.h" +#include "seader_hf_read_plan.h" #include "trace_log.h" #include @@ -490,6 +491,7 @@ void seader_worker_reading(Seader* seader) { bool detected = false; SeaderPollerEventType result_stage = SeaderPollerEventTypeFail; SeaderCredentialType type_to_read = seader_hf_mode_get_selected_read_type(seader); + SeaderHfReadPlan read_plan = {0}; FURI_LOG_D(TAG, "HF loop selected type=%d stage=%d", type_to_read, seader_worker->stage); if(type_to_read == SeaderCredentialTypeNone) { @@ -497,22 +499,23 @@ void seader_worker_reading(Seader* seader) { const size_t detected_type_count = seader->plugin_hf->detect_supported_types( seader->hf_plugin_ctx, detected_types, COUNT_OF(detected_types)); FURI_LOG_I(TAG, "HF plugin detected %u type(s)", detected_type_count); - - if(detected_type_count > 1) { - seader_hf_mode_set_detected_types(seader, detected_types, detected_type_count); - if(seader_worker->callback) { - seader_worker->callback( - SeaderWorkerEventSelectCardType, seader_worker->context); - } - break; - } else if(detected_type_count == 1) { - type_to_read = detected_types[0]; - } + read_plan = + seader_hf_read_plan_build(type_to_read, detected_types, detected_type_count); + } else { + read_plan = seader_hf_read_plan_build(type_to_read, NULL, 0U); } - 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(read_plan.decision == SeaderHfReadDecisionSelectType) { + seader_hf_mode_set_detected_types( + seader, read_plan.detected_types, read_plan.detected_type_count); + if(seader_worker->callback) { + seader_worker->callback(SeaderWorkerEventSelectCardType, seader_worker->context); + } + break; + } else if(read_plan.decision == SeaderHfReadDecisionStartRead) { + FURI_LOG_I(TAG, "HF start read for type=%d", read_plan.type_to_read); + detected = + seader->plugin_hf->start_read_for_type(seader->hf_plugin_ctx, read_plan.type_to_read); if(detected) { seader->hf_session_state = SeaderHfSessionStateActive; } From a4723220867aa9ebd7034cf516a84834acb559fc Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Wed, 25 Mar 2026 01:19:33 -0700 Subject: [PATCH 2/5] Reduce HF read RAM pressure --- apdu_runner.c | 69 +++++------ ccid.c | 30 ++++- hf_interface_fal/hf.c | 14 +-- scenes/seader_scene_credential_info.c | 7 +- scenes/seader_scene_read_card_success.c | 9 +- scenes/seader_scene_save_name.c | 16 ++- seader.c | 13 --- seader_i.h | 6 +- seader_worker.c | 146 ++++++++++++++++++------ seader_worker_i.h | 3 + t_1.c | 8 +- 11 files changed, 210 insertions(+), 111 deletions(-) diff --git a/apdu_runner.c b/apdu_runner.c index ae9c40a..0281018 100644 --- a/apdu_runner.c +++ b/apdu_runner.c @@ -9,6 +9,7 @@ // Max length of firmware upgrade: 731 bytes #define SEADER_APDU_MAX_LEN 732 +#define SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES 32U void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) { furi_check(seader); @@ -27,6 +28,28 @@ void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) { } } +static void seader_apdu_runner_log_hex(const char* prefix, const uint8_t* data, size_t len) { + if(!data || len == 0U) { + FURI_LOG_I(TAG, "%s: ", prefix); + return; + } + + const size_t display_len = + len > SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES ? SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES : len; + char hex[(SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES * 2U) + 1U]; + + for(size_t i = 0; i < display_len; i++) { + snprintf(hex + (i * 2U), sizeof(hex) - (i * 2U), "%02x", data[i]); + } + hex[display_len * 2U] = '\0'; + + if(display_len < len) { + FURI_LOG_I(TAG, "%s len=%u: %s...", prefix, (unsigned)len, hex); + } else { + FURI_LOG_I(TAG, "%s len=%u: %s", prefix, (unsigned)len, hex); + } +} + bool seader_apdu_runner_send_next_line(Seader* seader) { furi_check(seader); SeaderWorker* seader_worker = seader->worker; @@ -39,24 +62,19 @@ bool seader_apdu_runner_send_next_line(Seader* seader) { apdu_log_get_next_log_str(seader->apdu_log, line); size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes - uint8_t* apdu = malloc(len); - if(apdu == NULL) { - FURI_LOG_E(TAG, "Failed to allocate memory for APDU"); + if(len > SEADER_UART_RX_BUF_SIZE || len > SEADER_APDU_MAX_LEN) { + FURI_LOG_E(TAG, "APDU length is too long"); seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + furi_string_free(line); return false; } - if(len > SEADER_UART_RX_BUF_SIZE) { - FURI_LOG_E(TAG, "APDU length is too long"); - seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); - free(apdu); - return false; - } + uint8_t apdu[SEADER_APDU_MAX_LEN]; if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) { FURI_LOG_E(TAG, "Failed to convert line to number"); seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); - free(apdu); + furi_string_free(line); return false; } FURI_LOG_I( @@ -77,7 +95,6 @@ bool seader_apdu_runner_send_next_line(Seader* seader) { seader_ccid_XfrBlock(seader_uart, apdu, len); } furi_string_free(line); - free(apdu); return true; } @@ -120,23 +137,7 @@ bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) } if(r_len < SEADER_UART_RX_BUF_SIZE) { - char* display = malloc(r_len * 2 + 1); - if(display == NULL) { - FURI_LOG_E(TAG, "Failed to allocate memory for display"); - seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); - return false; - } - memset(display, 0, r_len * 2 + 1); - for(uint8_t i = 0; i < r_len; i++) { - snprintf(display + (i * 2), sizeof(display), "%02x", r_apdu[i]); - } - FURI_LOG_I( - TAG, - "APDU Runner <=: (%d/%d): %s", - apdu_runner_ctx->current_line + 1, - apdu_runner_ctx->total_lines, - display); - free(display); + seader_apdu_runner_log_hex("APDU Runner <=", r_apdu, r_len); } else { FURI_LOG_I(TAG, "APDU Runner <=: Response too long to display"); } @@ -148,22 +149,24 @@ bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) if(furi_string_size(line) % 2 == 1) { FURI_LOG_E(TAG, "APDU log file has odd number of characters"); seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + furi_string_free(line); return false; } size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes - uint8_t* apdu = malloc(len); - if(apdu == NULL) { - FURI_LOG_E(TAG, "Failed to allocate memory for APDU"); + if(len > SEADER_APDU_MAX_LEN) { + FURI_LOG_E(TAG, "Expected APDU length is too long"); seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); + furi_string_free(line); return false; } + uint8_t apdu[SEADER_APDU_MAX_LEN]; if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) { FURI_LOG_E(TAG, "Failed to convert line to byte array"); seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); - free(apdu); // TODO: Send failed event + furi_string_free(line); return false; } @@ -179,10 +182,8 @@ bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) apdu[len - 2], apdu[len - 1]); seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError); - free(apdu); return false; } - free(apdu); // Check if we are at the end of the log if(apdu_runner_ctx->current_line >= apdu_runner_ctx->total_lines) { diff --git a/ccid.c b/ccid.c index aef2a92..2235762 100644 --- a/ccid.c +++ b/ccid.c @@ -2,6 +2,7 @@ #include "ccid_logic.h" #define TAG "SeaderCCID" +#define SEADER_CCID_HEX_LOG_MAX_BYTES 32U const uint8_t SAM_ATR[] = {0x3b, 0x95, 0x96, 0x80, 0xb1, 0xfe, 0x55, 0x1f, 0xc7, 0x47, 0x72, 0x61, 0x63, 0x65, 0x13}; const uint8_t SAM_ATR2[] = {0x3b, 0x90, 0x96, 0x91, 0x81, 0xb1, 0xfe, 0x55, 0x1f, 0xc7, 0xd4}; @@ -37,6 +38,28 @@ static SeaderUartBridge* seader_ccid_active_uart(Seader* seader) { return seader->worker->uart; } +static void seader_ccid_log_hex(const char* prefix, const uint8_t* data, size_t len) { + if(!data || len == 0U) { + FURI_LOG_D(TAG, "%s: ", prefix); + return; + } + + const size_t display_len = len > SEADER_CCID_HEX_LOG_MAX_BYTES ? SEADER_CCID_HEX_LOG_MAX_BYTES : + len; + char hex[(SEADER_CCID_HEX_LOG_MAX_BYTES * 2U) + 1U]; + + for(size_t i = 0; i < display_len; i++) { + snprintf(hex + (i * 2U), sizeof(hex) - (i * 2U), "%02x", data[i]); + } + hex[display_len * 2U] = '\0'; + + if(display_len < len) { + FURI_LOG_D(TAG, "%s len=%u: %s...", prefix, (unsigned)len, hex); + } else { + FURI_LOG_D(TAG, "%s len=%u: %s", prefix, (unsigned)len, hex); + } +} + void seader_ccid_IccPowerOn(SeaderUartBridge* seader_uart, uint8_t slot) { SeaderCcidSlotState* slot_state = seader_ccid_slot_state(seader_uart, slot); if(slot_state->powered) { @@ -239,12 +262,7 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) { message.consumed = 0; SeaderCcidState* ccid_state = seader_ccid_state(seader_uart); - char* display = malloc(cmd_len * 2 + 1); - for(size_t i = 0; i < cmd_len; i++) { - snprintf(display + (i * 2), sizeof(display), "%02x", cmd[i]); - } - FURI_LOG_D(TAG, "seader_ccid_process %d: %s", cmd_len, display); - free(display); + seader_ccid_log_hex("seader_ccid_process", cmd, cmd_len); if(cmd_len == 2) { if(cmd[0] == CCID_MESSAGE_TYPE_RDR_TO_PC_NOTIFY_SLOT_CHANGE) { diff --git a/hf_interface_fal/hf.c b/hf_interface_fal/hf.c index 7d37260..317284b 100644 --- a/hf_interface_fal/hf.c +++ b/hf_interface_fal/hf.c @@ -26,6 +26,7 @@ #define HF_PLUGIN_POLLER_MAX_FWT (200000U) #define HF_PLUGIN_POLLER_MAX_BUFFER_SIZE (258U) +#define HF_PLUGIN_MAX_ATS_SIZE 33U // ATS bit definitions #define ISO14443_4A_ATS_T0_TA1 (1U << 4) @@ -477,13 +478,13 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v } uint8_t ats_len = 0; - uint8_t* ats = malloc(4 + t1_tk_size); - if(!ats) { - FURI_LOG_E(TAG, "Failed to allocate ATS buffer"); - ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); - return NfcCommandStop; - } + uint8_t ats[HF_PLUGIN_MAX_ATS_SIZE] = {0}; if(iso_data->ats_data.tl > 1) { + if(sizeof(ats) < 4U + t1_tk_size) { + FURI_LOG_E(TAG, "ATS buffer too small: %u", (unsigned)(4U + t1_tk_size)); + ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); + return NfcCommandStop; + } ats[ats_len++] = iso_data->ats_data.t0; if(iso_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1) ats[ats_len++] = iso_data->ats_data.ta_1; @@ -503,7 +504,6 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v ctx->api->send_card_detected( ctx->host_ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_len); FURI_LOG_D(TAG, "14A cardDetected delivered uid_len=%u ats_len=%u", uid_len, ats_len); - free(ats); ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation); } else if(stage == PluginHfStageConversation) { FURI_LOG_D(TAG, "14A enter conversation"); diff --git a/scenes/seader_scene_credential_info.c b/scenes/seader_scene_credential_info.c index 95d9448..6961c89 100644 --- a/scenes/seader_scene_credential_info.c +++ b/scenes/seader_scene_credential_info.c @@ -31,6 +31,7 @@ void seader_scene_credential_info_on_enter(void* context) { FuriString* bitlength_str = seader->temp_string2; FuriString* credential_str = seader->temp_string3; FuriString* sio_str = seader->temp_string4; + char sio_label[SEADER_TEXT_STORE_SIZE + 1] = {0}; furi_string_set(credential_str, ""); furi_string_set(bitlength_str, ""); @@ -80,9 +81,9 @@ void seader_scene_credential_info_on_enter(void* context) { credential->sio[0] == 0x30, seader_credential_is_picopass_sio_context(credential), credential->sio_start_block, - seader->text_store, - sizeof(seader->text_store))) { - furi_string_set(sio_str, seader->text_store); + sio_label, + sizeof(sio_label))) { + furi_string_set(sio_str, sio_label); widget_add_string_element( widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); } diff --git a/scenes/seader_scene_read_card_success.c b/scenes/seader_scene_read_card_success.c index b58b9ca..124e568 100644 --- a/scenes/seader_scene_read_card_success.c +++ b/scenes/seader_scene_read_card_success.c @@ -33,6 +33,7 @@ void seader_scene_read_card_success_on_enter(void* context) { FuriString* bitlength_str = seader->temp_string2; FuriString* credential_str = seader->temp_string3; FuriString* sio_str = seader->temp_string4; + char sio_label[SEADER_TEXT_STORE_SIZE + 1] = {0}; dolphin_deed(DolphinDeedNfcReadSuccess); @@ -117,12 +118,12 @@ void seader_scene_read_card_success_on_enter(void* context) { credential->sio[0] == 0x30, seader_credential_is_picopass_sio_context(credential), credential->sio_start_block, - seader->text_store, - sizeof(seader->text_store))) { - if(strcmp(seader->text_store, "+SIO(?)") == 0) { + sio_label, + sizeof(sio_label))) { + if(strcmp(sio_label, "+SIO(?)") == 0) { FURI_LOG_E(TAG, "Unknown SIO start block: %d", credential->sio_start_block); } - furi_string_set(sio_str, seader->text_store); + furi_string_set(sio_str, sio_label); widget_add_string_element( widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str)); } diff --git a/scenes/seader_scene_save_name.c b/scenes/seader_scene_save_name.c index 2be78fb..25a93ce 100644 --- a/scenes/seader_scene_save_name.c +++ b/scenes/seader_scene_save_name.c @@ -18,17 +18,20 @@ void seader_scene_save_name_on_enter(void* context) { TextInput* text_input = seader->text_input; bool cred_name_empty = false; if(!strcmp(seader->credential->name, "")) { - name_generator_make_random(seader->text_store, sizeof(seader->text_store)); + name_generator_make_random(seader->save_name_buf, sizeof(seader->save_name_buf)); cred_name_empty = true; } else { - seader_text_store_set(seader, seader->credential->name); + strlcpy( + seader->save_name_buf, + seader->credential->name, + sizeof(seader->save_name_buf)); } text_input_set_header_text(text_input, "Name the credential"); text_input_set_result_callback( text_input, seader_scene_save_name_text_input_callback, seader, - seader->text_store, + seader->save_name_buf, SEADER_CRED_NAME_MAX_LEN, cred_name_empty); @@ -60,8 +63,11 @@ bool seader_scene_save_name_on_event(void* context, SceneManagerEvent event) { FURI_LOG_D(TAG, "Delete existing named credential [%s]", seader->credential->name); seader_credential_delete(seader->credential, true); } - strlcpy(seader->credential->name, seader->text_store, strlen(seader->text_store) + 1); - if(seader_credential_save(seader->credential, seader->text_store)) { + strlcpy( + seader->credential->name, + seader->save_name_buf, + sizeof(seader->credential->name)); + if(seader_credential_save(seader->credential, seader->save_name_buf)) { scene_manager_next_scene(seader->scene_manager, SeaderSceneSaveSuccess); consumed = true; } else { diff --git a/seader.c b/seader.c index 4b5cc8f..69e031e 100644 --- a/seader.c +++ b/seader.c @@ -647,19 +647,6 @@ void seader_free(Seader* seader) { free(seader); } -void seader_text_store_set(Seader* seader, const char* text, ...) { - va_list args; - va_start(args, text); - - vsnprintf(seader->text_store, sizeof(seader->text_store), text, args); - - va_end(args); -} - -void seader_text_store_clear(Seader* seader) { - memset(seader->text_store, 0, sizeof(seader->text_store)); -} - static const NotificationSequence seader_sequence_blink_start_blue = { &message_blink_start_10, &message_blink_set_color_blue, diff --git a/seader_i.h b/seader_i.h index 8bfa5f9..e677048 100644 --- a/seader_i.h +++ b/seader_i.h @@ -158,7 +158,7 @@ struct Seader { SeaderScratch scratch; SeaderHfModeContext* hf_mode; - char text_store[SEADER_TEXT_STORE_SIZE + 1]; + char save_name_buf[SEADER_CRED_NAME_MAX_LEN + 1]; char read_error[SEADER_TEXT_STORE_SIZE + 1]; FuriString* text_box_store; @@ -214,10 +214,6 @@ typedef enum { SeaderViewUart, } SeaderView; -void seader_text_store_set(Seader* seader, const char* text, ...); - -void seader_text_store_clear(Seader* seader); - void seader_blink_start(Seader* seader); void seader_blink_stop(Seader* seader); diff --git a/seader_worker.c b/seader_worker.c index 344700a..e17ec46 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -9,6 +9,7 @@ #define APDU_HEADER_LEN 5 #define ASN1_PREFIX 6 +#define SEADER_HEX_LOG_MAX_BYTES 32U // #define ASN1_DEBUG true #define RFAL_PICOPASS_TXRX_FLAGS \ @@ -32,6 +33,51 @@ typedef struct { volatile bool detected; } SeaderPicopassDetectContext; +static void seader_worker_reset_apdu_slots(SeaderWorker* seader_worker) { + furi_assert(seader_worker); + memset(seader_worker->apdu_slot_in_use, 0, sizeof(seader_worker->apdu_slot_in_use)); + if(seader_worker->apdu_slots) { + memset( + seader_worker->apdu_slots, + 0, + sizeof(*seader_worker->apdu_slots) * SEADER_WORKER_APDU_SLOT_COUNT); + } +} + +static bool seader_worker_claim_apdu_slot(SeaderWorker* seader_worker, uint8_t* slot_index) { + furi_assert(seader_worker); + furi_assert(slot_index); + + for(uint8_t i = 0; i < SEADER_WORKER_APDU_SLOT_COUNT; i++) { + if(!seader_worker->apdu_slot_in_use[i]) { + seader_worker->apdu_slot_in_use[i] = true; + *slot_index = i; + return true; + } + } + + return false; +} + +static void seader_worker_release_apdu_slot(SeaderWorker* seader_worker, uint8_t slot_index) { + furi_assert(seader_worker); + furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT); + + seader_worker->apdu_slot_in_use[slot_index] = false; + if(seader_worker->apdu_slots) { + seader_worker->apdu_slots[slot_index].len = 0U; + } +} + +static bool seader_worker_dequeue_apdu( + SeaderWorker* seader_worker, + uint8_t* slot_index, + FuriWait timeout) { + furi_assert(seader_worker); + furi_assert(slot_index); + return furi_message_queue_get(seader_worker->messages, slot_index, timeout) == FuriStatusOk; +} + static void seader_worker_clear_active_card(Seader* seader, const char* reason) { if(!seader) { return; @@ -43,6 +89,27 @@ static void seader_worker_clear_active_card(Seader* seader, const char* reason) } } +static void seader_worker_log_hex(const char* prefix, const uint8_t* data, size_t len) { + if(!data || len == 0U) { + FURI_LOG_I(TAG, "%s: ", prefix); + return; + } + + const size_t display_len = len > SEADER_HEX_LOG_MAX_BYTES ? SEADER_HEX_LOG_MAX_BYTES : len; + char hex[(SEADER_HEX_LOG_MAX_BYTES * 2U) + 1U]; + + for(size_t i = 0; i < display_len; i++) { + snprintf(hex + (i * 2U), sizeof(hex) - (i * 2U), "%02x", data[i]); + } + hex[display_len * 2U] = '\0'; + + if(display_len < len) { + FURI_LOG_I(TAG, "%s len=%u: %s...", prefix, (unsigned)len, hex); + } else { + FURI_LOG_I(TAG, "%s len=%u: %s", prefix, (unsigned)len, hex); + } +} + static NfcCommand seader_worker_picopass_detect_callback(PicopassPollerEvent event, void* context) { SeaderPicopassDetectContext* detect_context = context; @@ -177,15 +244,20 @@ seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) { SeaderWorker* seader_worker_alloc() { SeaderWorker* seader_worker = calloc(1, sizeof(SeaderWorker)); + if(!seader_worker) { + return NULL; + } // Worker thread attributes seader_worker->thread = - furi_thread_alloc_ex("SeaderWorker", 8192, seader_worker_task, seader_worker); - seader_worker->messages = furi_message_queue_alloc(3, sizeof(SeaderAPDU)); + furi_thread_alloc_ex("SeaderWorker", 6144, seader_worker_task, seader_worker); + seader_worker->messages = furi_message_queue_alloc(2, sizeof(uint8_t)); + seader_worker->apdu_slots = calloc(SEADER_WORKER_APDU_SLOT_COUNT, sizeof(SeaderAPDU)); seader_worker->callback = NULL; seader_worker->context = NULL; seader_worker->storage = furi_record_open(RECORD_STORAGE); + seader_worker_reset_apdu_slots(seader_worker); seader_worker_change_state(seader_worker, SeaderWorkerStateReady); @@ -197,6 +269,7 @@ void seader_worker_free(SeaderWorker* seader_worker) { furi_thread_free(seader_worker->thread); furi_message_queue_free(seader_worker->messages); + free(seader_worker->apdu_slots); furi_record_close(RECORD_STORAGE); @@ -282,6 +355,7 @@ void seader_worker_reset_poller_session(SeaderWorker* seader_worker) { furi_message_queue_get_count(seader_worker->messages)); furi_message_queue_reset(seader_worker->messages); + seader_worker_reset_apdu_slots(seader_worker); seader_worker->stage = SeaderPollerEventTypeCardDetect; } @@ -323,12 +397,23 @@ bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len) seader_trace( TAG, "enqueue len=%d stage=%d sam=%d", len, seader_worker->stage, seader->samCommand); uint32_t space = furi_message_queue_get_space(seader_worker->messages); - if(space > 0) { - SeaderAPDU seaderApdu = {}; - seaderApdu.len = len; - memcpy(seaderApdu.buf, apdu, len); + if(space > 0 && len <= SEADER_POLLER_MAX_BUFFER_SIZE) { + uint8_t slot_index = 0U; + if(!seader_worker_claim_apdu_slot(seader_worker, &slot_index)) { + FURI_LOG_W(TAG, "No free APDU slot for len=%u", (unsigned)len); + return true; + } - furi_message_queue_put(seader_worker->messages, &seaderApdu, FuriWaitForever); + seader_worker->apdu_slots[slot_index].len = len; + memcpy(seader_worker->apdu_slots[slot_index].buf, apdu, len); + + if(furi_message_queue_put(seader_worker->messages, &slot_index, FuriWaitForever) != + FuriStatusOk) { + FURI_LOG_W(TAG, "Failed to queue APDU slot=%u", slot_index); + seader_worker_release_apdu_slot(seader_worker, slot_index); + } + } else if(len > SEADER_POLLER_MAX_BUFFER_SIZE) { + FURI_LOG_W(TAG, "Drop oversized SAM message len=%u", (unsigned)len); } } return true; @@ -348,12 +433,7 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l return seader_apdu_runner_response(seader, apdu, len); } - char* display = malloc(len * 2 + 1); - memset(display, 0, len * 2 + 1); - for(size_t i = 0; i < len; i++) { - snprintf(display + (i * 2), sizeof(display), "%02x", apdu[i]); - } - FURI_LOG_I(TAG, "APDU: %s", display); + seader_worker_log_hex("APDU", apdu, len); seader_trace( TAG, "sam apdu len=%lu stage=%d sam=%d state=%d intent=%d sw=%02x%02x", @@ -364,7 +444,6 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l seader->sam_intent, apdu[len - 2], apdu[len - 1]); - free(display); uint8_t SW1 = apdu[len - 2]; uint8_t SW2 = apdu[len - 1]; @@ -408,21 +487,23 @@ void seader_worker_virtual_credential(Seader* seader) { if(count > 0) { FURI_LOG_I(TAG, "Dequeue SAM message [%ld messages]", count); - SeaderAPDU seaderApdu = {}; - FuriStatus status = - furi_message_queue_get(seader_worker->messages, &seaderApdu, FuriWaitForever); - if(status != FuriStatusOk) { - FURI_LOG_W(TAG, "furi_message_queue_get fail %d", status); + uint8_t slot_index = 0U; + if(!seader_worker_dequeue_apdu(seader_worker, &slot_index, FuriWaitForever)) { + FURI_LOG_W(TAG, "furi_message_queue_get fail"); view_dispatcher_send_custom_event( seader->view_dispatcher, SeaderCustomEventWorkerExit); + continue; } + furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT); + SeaderAPDU* seaderApdu = &seader_worker->apdu_slots[slot_index]; if(seader_process_success_response_i( - seader, seaderApdu.buf, seaderApdu.len, true, NULL)) { + seader, seaderApdu->buf, seaderApdu->len, true, NULL)) { // no-op } else { FURI_LOG_I(TAG, "Response false"); running = false; } + seader_worker_release_apdu_slot(seader_worker, slot_index); } else { dead_loops--; running = (dead_loops > 0); @@ -564,14 +645,16 @@ void seader_worker_run_hf_conversation(Seader* seader) { The worker queue is the bridge between SAM APDUs and the poller callback thread. */ while(seader_worker->stage == SeaderPollerEventTypeConversation && seader_worker->state == SeaderWorkerStateReading) { - SeaderAPDU seaderApdu = {}; + uint8_t slot_index = 0U; // Short wait for SAM message - FuriStatus status = furi_message_queue_get(seader_worker->messages, &seaderApdu, 100); + FuriStatus status = furi_message_queue_get(seader_worker->messages, &slot_index, 100); if(status == FuriStatusOk) { - FURI_LOG_D(TAG, "Dequeue SAM message [%d bytes]", seaderApdu.len); + furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT); + SeaderAPDU* seaderApdu = &seader_worker->apdu_slots[slot_index]; + FURI_LOG_D(TAG, "Dequeue SAM message [%d bytes]", seaderApdu->len); if(seader_process_success_response_i( - seader, seaderApdu.buf, seaderApdu.len, true, NULL)) { + seader, seaderApdu->buf, seaderApdu->len, true, NULL)) { // message was processed, loop again to see if SAM has more to say } else { FURI_LOG_I(TAG, "Response false, ending conversation"); @@ -579,6 +662,7 @@ void seader_worker_run_hf_conversation(Seader* seader) { view_dispatcher_send_custom_event( seader->view_dispatcher, SeaderCustomEventWorkerExit); } + seader_worker_release_apdu_slot(seader_worker, slot_index); } else if(status == FuriStatusErrorTimeout) { // No message yet, keep looping to stay in callback // This is "properly idling" while waiting for SAM @@ -638,14 +722,14 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void } uint8_t ats_len = 0; - uint8_t* ats = malloc(4 + t1_tk_size); - if(!ats) { - FURI_LOG_E(TAG, "Failed to allocate host ATS buffer"); - seader_worker->stage = SeaderPollerEventTypeFail; - return NfcCommandStop; - } + uint8_t ats[SEADER_MAX_ATR_SIZE] = {0}; if(iso14443_4a_data->ats_data.tl > 1) { + if(sizeof(ats) < 4U + t1_tk_size) { + FURI_LOG_E(TAG, "Host ATS buffer too small: %u", (unsigned)(4U + t1_tk_size)); + seader_worker->stage = SeaderPollerEventTypeFail; + return NfcCommandStop; + } ats[ats_len++] = iso14443_4a_data->ats_data.t0; if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1) { ats[ats_len++] = iso14443_4a_data->ats_data.ta_1; @@ -672,8 +756,6 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void seader, sak, (uint8_t*)iso14443_3a_data->atqa, uid, uid_len, ats, ats_len); seader_trace(TAG, "14a card_detect sent uid_len=%d sak=%d", uid_len, sak); - free(ats); - if(seader_worker->state == SeaderWorkerStateReading) { seader_worker->stage = SeaderPollerEventTypeConversation; return NfcCommandContinue; diff --git a/seader_worker_i.h b/seader_worker_i.h index a5a539b..83fb58f 100644 --- a/seader_worker_i.h +++ b/seader_worker_i.h @@ -17,6 +17,7 @@ #define SEADER_POLLER_MAX_FWT (200000U) // Maximum basic rAPDU size is 256 bytes of data + 2 byte SW #define SEADER_POLLER_MAX_BUFFER_SIZE (258U) +#define SEADER_WORKER_APDU_SLOT_COUNT (2U) // ATS bit definitions #define ISO14443_4A_ATS_T0_TA1 (1U << 4) @@ -33,6 +34,8 @@ struct SeaderWorker { SeaderPollerEventType stage; SeaderWorkerState state; + struct SeaderAPDU* apdu_slots; + bool apdu_slot_in_use[SEADER_WORKER_APDU_SLOT_COUNT]; }; struct SeaderAPDU { diff --git a/t_1.c b/t_1.c index 50e0d8c..dad6d7f 100644 --- a/t_1.c +++ b/t_1.c @@ -7,6 +7,7 @@ #include "t_1_logic.h" #define TAG "Seader:T=1" +#define SEADER_T1_MAX_FRAME_LEN (3U + SEADER_T1_IFS_MAX + 1U) static SeaderT1State* seader_t1_state(SeaderUartBridge* seader_uart) { return &seader_uart->t1; @@ -136,9 +137,13 @@ static void seader_t_1_send_nak(Seader* seader) { void seader_send_t1_chunk(SeaderUartBridge* seader_uart, uint8_t pcb, uint8_t* chunk, size_t len) { SeaderT1State* t1 = seader_t1_state(seader_uart); - uint8_t* frame = malloc(3 + len + 1); + uint8_t frame[SEADER_T1_MAX_FRAME_LEN]; uint8_t frame_len = 0; + if(len > SEADER_T1_IFS_MAX) { + return; + } + frame[0] = t1->nad; frame[1] = pcb; frame[2] = len; @@ -151,7 +156,6 @@ void seader_send_t1_chunk(SeaderUartBridge* seader_uart, uint8_t pcb, uint8_t* c frame_len = seader_add_lrc(frame, frame_len); seader_ccid_XfrBlock(seader_uart, frame, frame_len); - free(frame); } void seader_send_t1_scratchpad( From 3af85b64562b30fb5d0b7ac11e32c8150060dc8e Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Wed, 25 Mar 2026 01:22:35 -0700 Subject: [PATCH 3/5] Reduce buffer size --- uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uart.c b/uart.c index fa8cdc4..fde4c49 100644 --- a/uart.c +++ b/uart.c @@ -165,7 +165,7 @@ SeaderUartBridge* seader_uart_enable(SeaderUartConfig* cfg, Seader* seader) { memcpy(&(seader_uart->cfg_new), cfg, sizeof(SeaderUartConfig)); seader_uart->thread = - furi_thread_alloc_ex("SeaderUartWorker", 5 * 1024, seader_uart_worker, seader); + furi_thread_alloc_ex("SeaderUartWorker", 4 * 1024, seader_uart_worker, seader); furi_thread_start(seader_uart->thread); return seader_uart; From 6ebb29f66d82b2be1ab875a3565f41c0ea7d81f4 Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Wed, 25 Mar 2026 01:26:54 -0700 Subject: [PATCH 4/5] Eliminate unnecessary buffer --- lib/host_tests/t_1_host_env.h | 1 - sam_api.c | 6 +++++- seader_bridge.h | 1 - uart.c | 18 ++++++++---------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/host_tests/t_1_host_env.h b/lib/host_tests/t_1_host_env.h index e589609..3602afa 100644 --- a/lib/host_tests/t_1_host_env.h +++ b/lib/host_tests/t_1_host_env.h @@ -32,7 +32,6 @@ typedef enum { SeaderWorkerEventSamPresent = 53 } SeaderWorkerEvent; typedef void (*SeaderWorkerCallback)(uint32_t event, void* context); struct SeaderUartBridge { - uint8_t rx_buf[SEADER_UART_RX_BUF_SIZE]; uint8_t tx_buf[SEADER_UART_RX_BUF_SIZE]; size_t tx_len; uint8_t T; diff --git a/sam_api.c b/sam_api.c index aa849f8..ff42cca 100644 --- a/sam_api.c +++ b/sam_api.c @@ -98,9 +98,13 @@ static void seader_reset_cached_sam_metadata(Seader* seader) { static bool seader_snmp_probe_send_next_request(Seader* seader) { SeaderUartBridge* seader_uart = seader_require_uart(seader); uint8_t* scratch = seader_uart->tx_buf + MAX_FRAME_HEADERS; - uint8_t* message = seader_uart->rx_buf; + uint8_t* message = seader_scratch_alloc(seader, SEADER_UART_RX_BUF_SIZE, _Alignof(uint8_t)); size_t message_len = 0U; + if(!message) { + return false; + } + if(!seader_uhf_snmp_probe_build_next_request( &seader->snmp_probe, scratch, diff --git a/seader_bridge.h b/seader_bridge.h index 8b58c2b..7d4afec 100644 --- a/seader_bridge.h +++ b/seader_bridge.h @@ -56,7 +56,6 @@ struct SeaderUartBridge { SeaderUartState st; - uint8_t rx_buf[SEADER_UART_RX_BUF_SIZE]; uint8_t tx_buf[SEADER_UART_RX_BUF_SIZE]; size_t tx_len; diff --git a/uart.c b/uart.c index fde4c49..8ccb604 100644 --- a/uart.c +++ b/uart.c @@ -119,26 +119,24 @@ int32_t seader_uart_worker(void* context) { break; } if(events & (WorkerEvtRxDone | WorkerEvtSamTxComplete)) { + if(cmd_len >= sizeof(cmd)) { + FURI_LOG_I(TAG, "RX buffer full, resetting"); + memset(cmd, 0, sizeof(cmd)); + cmd_len = 0; + } + size_t len = furi_stream_buffer_receive( - seader_uart->rx_stream, seader_uart->rx_buf, SEADER_UART_RX_BUF_SIZE, 0); + seader_uart->rx_stream, cmd + cmd_len, sizeof(cmd) - cmd_len, 0); if(len > 0) { furi_delay_ms(5); //WTF /* char display[SEADER_UART_RX_BUF_SIZE * 2 + 1] = {0}; for (uint8_t i = 0; i < len; i++) { - snprintf(display+(i*2), sizeof(display), "%02x", seader_uart->rx_buf[i]); + snprintf(display+(i*2), sizeof(display), "%02x", cmd[cmd_len + i]); } FURI_LOG_I(TAG, "RECV %d bytes: %s", len, display); */ - - if(cmd_len + len > SEADER_UART_RX_BUF_SIZE) { - FURI_LOG_I(TAG, "OVERFLOW: %d + %d", cmd_len, len); - memset(cmd, 0, cmd_len); - cmd_len = 0; - } - - memcpy(cmd + cmd_len, seader_uart->rx_buf, len); cmd_len += len; cmd_len = seader_uart_process_buffer(seader, cmd, cmd_len); } From 2dc6779a55775e437ee85733c5ab0604a0bcff73 Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Wed, 25 Mar 2026 01:59:59 -0700 Subject: [PATCH 5/5] Even more RAM reduction --- apdu_runner.c | 2 +- ccid.c | 6 ++-- hf_interface_fal/hf.c | 2 +- lib/host_tests/t_1_host_env.h | 2 +- sam_api.c | 20 ++++++++++-- scenes/seader_scene_credential_info.c | 43 ++++++++++++++++++++++++- scenes/seader_scene_formats.c | 21 ++++++++++++ scenes/seader_scene_read_card_success.c | 43 ++++++++++++++++++++++++- scenes/seader_scene_sam_info.c | 35 +++++++++++++++++++- scenes/seader_scene_save_name.c | 16 +++------ seader.c | 24 +++++++------- seader_bridge.h | 2 +- seader_hf_read_plan.c | 4 +-- seader_worker.c | 39 ++++++++++++---------- t_1.c | 2 +- 15 files changed, 205 insertions(+), 56 deletions(-) diff --git a/apdu_runner.c b/apdu_runner.c index 0281018..82618bc 100644 --- a/apdu_runner.c +++ b/apdu_runner.c @@ -8,7 +8,7 @@ #define TAG "APDU_Runner" // Max length of firmware upgrade: 731 bytes -#define SEADER_APDU_MAX_LEN 732 +#define SEADER_APDU_MAX_LEN 732 #define SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES 32U void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) { diff --git a/ccid.c b/ccid.c index 2235762..bd73213 100644 --- a/ccid.c +++ b/ccid.c @@ -1,7 +1,7 @@ #include "seader_i.h" #include "ccid_logic.h" -#define TAG "SeaderCCID" +#define TAG "SeaderCCID" #define SEADER_CCID_HEX_LOG_MAX_BYTES 32U const uint8_t SAM_ATR[] = {0x3b, 0x95, 0x96, 0x80, 0xb1, 0xfe, 0x55, 0x1f, 0xc7, 0x47, 0x72, 0x61, 0x63, 0x65, 0x13}; @@ -44,8 +44,8 @@ static void seader_ccid_log_hex(const char* prefix, const uint8_t* data, size_t return; } - const size_t display_len = len > SEADER_CCID_HEX_LOG_MAX_BYTES ? SEADER_CCID_HEX_LOG_MAX_BYTES : - len; + const size_t display_len = + len > SEADER_CCID_HEX_LOG_MAX_BYTES ? SEADER_CCID_HEX_LOG_MAX_BYTES : len; char hex[(SEADER_CCID_HEX_LOG_MAX_BYTES * 2U) + 1U]; for(size_t i = 0; i < display_len; i++) { diff --git a/hf_interface_fal/hf.c b/hf_interface_fal/hf.c index 317284b..5cfe60f 100644 --- a/hf_interface_fal/hf.c +++ b/hf_interface_fal/hf.c @@ -247,7 +247,7 @@ static void plugin_hf_iso14443a_transmit( return; } - BitBuffer* tx_buffer = bit_buffer_alloc(len + 1); + BitBuffer* tx_buffer = bit_buffer_alloc(len + 1U); BitBuffer* rx_buffer = bit_buffer_alloc(HF_PLUGIN_POLLER_MAX_BUFFER_SIZE); if(!tx_buffer || !rx_buffer) { FURI_LOG_E(TAG, "Failed to allocate 14A buffers"); diff --git a/lib/host_tests/t_1_host_env.h b/lib/host_tests/t_1_host_env.h index 3602afa..52ef0d6 100644 --- a/lib/host_tests/t_1_host_env.h +++ b/lib/host_tests/t_1_host_env.h @@ -12,7 +12,7 @@ #include "t_1_logic.h" /* Keep the host harness aligned with the production UART scratchpad size. */ -#define SEADER_UART_RX_BUF_SIZE (300) +#define SEADER_UART_RX_BUF_SIZE (272) #define FURI_LOG_W(tag, fmt, ...) ((void)0) #define furi_check(expr) \ do { \ diff --git a/sam_api.c b/sam_api.c index ff42cca..8287e25 100644 --- a/sam_api.c +++ b/sam_api.c @@ -355,8 +355,14 @@ PicopassError seader_worker_fake_epurse_update(BitBuffer* tx_buffer, BitBuffer* void seader_virtual_picopass_state_machine(Seader* seader, uint8_t* buffer, size_t len) { BitBuffer* tx_buffer = bit_buffer_alloc(len); - bit_buffer_append_bytes(tx_buffer, buffer, len); BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE); + if(!tx_buffer || !rx_buffer) { + FURI_LOG_E(TAG, "Failed to allocate virtual Picopass buffers"); + if(tx_buffer) bit_buffer_free(tx_buffer); + if(rx_buffer) bit_buffer_free(rx_buffer); + return; + } + bit_buffer_append_bytes(tx_buffer, buffer, len); uint8_t config[PICOPASS_BLOCK_LEN] = {0x12, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0xff, 0x3c}; uint8_t sr_aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xff, 0xff, 0xff, 0xFF, 0xFf, 0xff, 0xFF}; @@ -1208,9 +1214,18 @@ void seader_iso15693_transmit( BitBuffer* tx_buffer = bit_buffer_alloc(len); BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE); - PicopassError error = PicopassErrorNone; + if(!tx_buffer || !rx_buffer) { + FURI_LOG_E(TAG, "Failed to allocate Picopass tx/rx buffers"); + if(tx_buffer) bit_buffer_free(tx_buffer); + if(rx_buffer) bit_buffer_free(rx_buffer); + if(seader_worker) { + seader_worker->stage = SeaderPollerEventTypeFail; + } + return; + } + do { bit_buffer_append_bytes(tx_buffer, buffer, len); @@ -1238,7 +1253,6 @@ void seader_iso15693_transmit( bit_buffer_get_size_bytes(rx_buffer)); } while(false); - bit_buffer_free(tx_buffer); bit_buffer_free(rx_buffer); } diff --git a/scenes/seader_scene_credential_info.c b/scenes/seader_scene_credential_info.c index 6961c89..b766aee 100644 --- a/scenes/seader_scene_credential_info.c +++ b/scenes/seader_scene_credential_info.c @@ -4,6 +4,46 @@ #define TAG "SeaderCredentialInfoScene" +static void seader_scene_credential_info_alloc_strings(Seader* seader) { + furi_check(seader); + if(!seader->temp_string1) { + seader->temp_string1 = furi_string_alloc(); + furi_check(seader->temp_string1); + } + if(!seader->temp_string2) { + seader->temp_string2 = furi_string_alloc(); + furi_check(seader->temp_string2); + } + if(!seader->temp_string3) { + seader->temp_string3 = furi_string_alloc(); + furi_check(seader->temp_string3); + } + if(!seader->temp_string4) { + seader->temp_string4 = furi_string_alloc(); + furi_check(seader->temp_string4); + } +} + +static void seader_scene_credential_info_free_strings(Seader* seader) { + furi_check(seader); + if(seader->temp_string1) { + furi_string_free(seader->temp_string1); + seader->temp_string1 = NULL; + } + if(seader->temp_string2) { + furi_string_free(seader->temp_string2); + seader->temp_string2 = NULL; + } + if(seader->temp_string3) { + furi_string_free(seader->temp_string3); + seader->temp_string3 = NULL; + } + if(seader->temp_string4) { + furi_string_free(seader->temp_string4); + seader->temp_string4 = NULL; + } +} + static bool seader_credential_is_picopass_sio_context(const SeaderCredential* credential) { return credential && (credential->type == SeaderCredentialTypePicopass || (credential->has_pacs_media_type && @@ -26,7 +66,7 @@ void seader_scene_credential_info_on_enter(void* context) { seader_wiegand_plugin_acquire(seader); Widget* widget = seader->widget; - // Use reusable strings instead of allocating new ones + seader_scene_credential_info_alloc_strings(seader); FuriString* type_str = seader->temp_string1; FuriString* bitlength_str = seader->temp_string2; FuriString* credential_str = seader->temp_string3; @@ -119,5 +159,6 @@ void seader_scene_credential_info_on_exit(void* context) { // Clear views widget_reset(seader->widget); + seader_scene_credential_info_free_strings(seader); seader_wiegand_plugin_release(seader); } diff --git a/scenes/seader_scene_formats.c b/scenes/seader_scene_formats.c index 7b0dbf3..66f4e44 100644 --- a/scenes/seader_scene_formats.c +++ b/scenes/seader_scene_formats.c @@ -1,11 +1,24 @@ #include "../seader_i.h" #include +static void seader_scene_formats_alloc_strings(Seader* seader) { + furi_check(seader); + if(!seader->text_box_store) { + seader->text_box_store = furi_string_alloc(); + furi_check(seader->text_box_store); + } + if(!seader->temp_string1) { + seader->temp_string1 = furi_string_alloc(); + furi_check(seader->temp_string1); + } +} + void seader_scene_formats_on_enter(void* context) { Seader* seader = context; PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL; SeaderCredential* credential = seader->credential; + seader_scene_formats_alloc_strings(seader); FuriString* str = seader->text_box_store; furi_string_reset(str); @@ -51,5 +64,13 @@ void seader_scene_formats_on_exit(void* context) { // Clear views text_box_reset(seader->text_box); + if(seader->text_box_store) { + furi_string_free(seader->text_box_store); + seader->text_box_store = NULL; + } + if(seader->temp_string1) { + furi_string_free(seader->temp_string1); + seader->temp_string1 = NULL; + } seader_wiegand_plugin_release(seader); } diff --git a/scenes/seader_scene_read_card_success.c b/scenes/seader_scene_read_card_success.c index 124e568..4944186 100644 --- a/scenes/seader_scene_read_card_success.c +++ b/scenes/seader_scene_read_card_success.c @@ -4,6 +4,46 @@ #define TAG "SeaderSceneReadCardSuccess" +static void seader_scene_read_card_success_alloc_strings(Seader* seader) { + furi_check(seader); + if(!seader->temp_string1) { + seader->temp_string1 = furi_string_alloc(); + furi_check(seader->temp_string1); + } + if(!seader->temp_string2) { + seader->temp_string2 = furi_string_alloc(); + furi_check(seader->temp_string2); + } + if(!seader->temp_string3) { + seader->temp_string3 = furi_string_alloc(); + furi_check(seader->temp_string3); + } + if(!seader->temp_string4) { + seader->temp_string4 = furi_string_alloc(); + furi_check(seader->temp_string4); + } +} + +static void seader_scene_read_card_success_free_strings(Seader* seader) { + furi_check(seader); + if(seader->temp_string1) { + furi_string_free(seader->temp_string1); + seader->temp_string1 = NULL; + } + if(seader->temp_string2) { + furi_string_free(seader->temp_string2); + seader->temp_string2 = NULL; + } + if(seader->temp_string3) { + furi_string_free(seader->temp_string3); + seader->temp_string3 = NULL; + } + if(seader->temp_string4) { + furi_string_free(seader->temp_string4); + seader->temp_string4 = NULL; + } +} + static bool seader_credential_is_picopass_sio_context(const SeaderCredential* credential) { return credential && (credential->type == SeaderCredentialTypePicopass || (credential->has_pacs_media_type && @@ -28,7 +68,7 @@ void seader_scene_read_card_success_on_enter(void* context) { PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL; Widget* widget = seader->widget; - // Use reusable strings instead of allocating new ones + seader_scene_read_card_success_alloc_strings(seader); FuriString* type_str = seader->temp_string1; FuriString* bitlength_str = seader->temp_string2; FuriString* credential_str = seader->temp_string3; @@ -166,5 +206,6 @@ void seader_scene_read_card_success_on_exit(void* context) { // Clear view widget_reset(seader->widget); + seader_scene_read_card_success_free_strings(seader); seader_wiegand_plugin_release(seader); } diff --git a/scenes/seader_scene_sam_info.c b/scenes/seader_scene_sam_info.c index 6b14dcc..e4dbc36 100644 --- a/scenes/seader_scene_sam_info.c +++ b/scenes/seader_scene_sam_info.c @@ -3,6 +3,38 @@ #define TAG "SeaderSamInfoScene" +static void seader_scene_sam_info_alloc_strings(Seader* seader) { + furi_check(seader); + if(!seader->temp_string1) { + seader->temp_string1 = furi_string_alloc(); + furi_check(seader->temp_string1); + } + if(!seader->temp_string2) { + seader->temp_string2 = furi_string_alloc(); + furi_check(seader->temp_string2); + } + if(!seader->temp_string3) { + seader->temp_string3 = furi_string_alloc(); + furi_check(seader->temp_string3); + } +} + +static void seader_scene_sam_info_free_strings(Seader* seader) { + furi_check(seader); + if(seader->temp_string1) { + furi_string_free(seader->temp_string1); + seader->temp_string1 = NULL; + } + if(seader->temp_string2) { + furi_string_free(seader->temp_string2); + seader->temp_string2 = NULL; + } + if(seader->temp_string3) { + furi_string_free(seader->temp_string3); + seader->temp_string3 = NULL; + } +} + void seader_scene_sam_info_widget_callback(GuiButtonType result, InputType type, void* context) { Seader* seader = context; if(type == InputTypeShort) { @@ -14,7 +46,7 @@ void seader_scene_sam_info_on_enter(void* context) { Seader* seader = context; Widget* widget = seader->widget; - // Use reusable string instead of allocating new one + seader_scene_sam_info_alloc_strings(seader); FuriString* fw_str = seader->temp_string1; FuriString* info_str = seader->temp_string2; FuriString* uhf_str = seader->temp_string3; @@ -66,4 +98,5 @@ void seader_scene_sam_info_on_exit(void* context) { // Clear views widget_reset(seader->widget); + seader_scene_sam_info_free_strings(seader); } diff --git a/scenes/seader_scene_save_name.c b/scenes/seader_scene_save_name.c index 25a93ce..b7dcbe9 100644 --- a/scenes/seader_scene_save_name.c +++ b/scenes/seader_scene_save_name.c @@ -21,10 +21,7 @@ void seader_scene_save_name_on_enter(void* context) { name_generator_make_random(seader->save_name_buf, sizeof(seader->save_name_buf)); cred_name_empty = true; } else { - strlcpy( - seader->save_name_buf, - seader->credential->name, - sizeof(seader->save_name_buf)); + strlcpy(seader->save_name_buf, seader->credential->name, sizeof(seader->save_name_buf)); } text_input_set_header_text(text_input, "Name the credential"); text_input_set_result_callback( @@ -35,8 +32,8 @@ void seader_scene_save_name_on_enter(void* context) { SEADER_CRED_NAME_MAX_LEN, cred_name_empty); - // Use reusable string instead of allocating new one - FuriString* folder_path = seader->temp_string1; + FuriString* folder_path = furi_string_alloc(); + furi_check(folder_path); if(furi_string_end_with(seader->credential->load_path, SEADER_APP_EXTENSION)) { path_extract_dirname(furi_string_get_cstr(seader->credential->load_path), folder_path); } else { @@ -48,8 +45,7 @@ void seader_scene_save_name_on_enter(void* context) { text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewTextInput); - - // No need to free folder_path as it's reused from seader struct + furi_string_free(folder_path); } bool seader_scene_save_name_on_event(void* context, SceneManagerEvent event) { @@ -64,9 +60,7 @@ bool seader_scene_save_name_on_event(void* context, SceneManagerEvent event) { seader_credential_delete(seader->credential, true); } strlcpy( - seader->credential->name, - seader->save_name_buf, - sizeof(seader->credential->name)); + seader->credential->name, seader->save_name_buf, sizeof(seader->credential->name)); if(seader_credential_save(seader->credential, seader->save_name_buf)) { scene_manager_next_scene(seader->scene_manager, SeaderSceneSaveSuccess); consumed = true; diff --git a/seader.c b/seader.c index 69e031e..b12ef54 100644 --- a/seader.c +++ b/seader.c @@ -533,18 +533,18 @@ Seader* seader_alloc() { seader->text_box = text_box_alloc(); view_dispatcher_add_view( seader->view_dispatcher, SeaderViewTextBox, text_box_get_view(seader->text_box)); - seader->text_box_store = furi_string_alloc(); + seader->text_box_store = NULL; // Custom Widget seader->widget = widget_alloc(); view_dispatcher_add_view( seader->view_dispatcher, SeaderViewWidget, widget_get_view(seader->widget)); - // Allocate reusable strings for scene optimization - seader->temp_string1 = furi_string_alloc(); - seader->temp_string2 = furi_string_alloc(); - seader->temp_string3 = furi_string_alloc(); - seader->temp_string4 = furi_string_alloc(); + // Scene strings are allocated lazily by the scenes that need them. + seader->temp_string1 = NULL; + seader->temp_string2 = NULL; + seader->temp_string3 = NULL; + seader->temp_string4 = NULL; seader->plugin_manager = NULL; seader->plugin_wiegand = NULL; @@ -618,17 +618,19 @@ void seader_free(Seader* seader) { // TextBox view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextBox); text_box_free(seader->text_box); - furi_string_free(seader->text_box_store); + if(seader->text_box_store) { + furi_string_free(seader->text_box_store); + } // Custom Widget view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewWidget); widget_free(seader->widget); // Free reusable strings - furi_string_free(seader->temp_string1); - furi_string_free(seader->temp_string2); - furi_string_free(seader->temp_string3); - furi_string_free(seader->temp_string4); + if(seader->temp_string1) furi_string_free(seader->temp_string1); + if(seader->temp_string2) furi_string_free(seader->temp_string2); + if(seader->temp_string3) furi_string_free(seader->temp_string3); + if(seader->temp_string4) furi_string_free(seader->temp_string4); // View Dispatcher view_dispatcher_free(seader->view_dispatcher); diff --git a/seader_bridge.h b/seader_bridge.h index 7d4afec..ec0e669 100644 --- a/seader_bridge.h +++ b/seader_bridge.h @@ -12,7 +12,7 @@ #include "t_1_logic.h" // https://ww1.microchip.com/downloads/en/DeviceDoc/00001561C.pdf -#define SEADER_UART_RX_BUF_SIZE (300) +#define SEADER_UART_RX_BUF_SIZE (272) #define SEADER_CCID_SLOT_COUNT (2U) typedef struct BitBuffer BitBuffer; diff --git a/seader_hf_read_plan.c b/seader_hf_read_plan.c index 6e9dd69..4228ae5 100644 --- a/seader_hf_read_plan.c +++ b/seader_hf_read_plan.c @@ -2,9 +2,7 @@ #define SEADER_HF_READ_PLAN_MAX_TYPES 3U -static void seader_hf_read_plan_add_type( - SeaderHfReadPlan* plan, - SeaderCredentialType type) { +static void seader_hf_read_plan_add_type(SeaderHfReadPlan* plan, SeaderCredentialType type) { if(type == SeaderCredentialTypeNone) { return; } diff --git a/seader_worker.c b/seader_worker.c index e17ec46..bdc64bf 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -7,8 +7,8 @@ #define TAG "SeaderWorker" -#define APDU_HEADER_LEN 5 -#define ASN1_PREFIX 6 +#define APDU_HEADER_LEN 5 +#define ASN1_PREFIX 6 #define SEADER_HEX_LOG_MAX_BYTES 32U // #define ASN1_DEBUG true @@ -69,10 +69,8 @@ static void seader_worker_release_apdu_slot(SeaderWorker* seader_worker, uint8_t } } -static bool seader_worker_dequeue_apdu( - SeaderWorker* seader_worker, - uint8_t* slot_index, - FuriWait timeout) { +static bool + seader_worker_dequeue_apdu(SeaderWorker* seader_worker, uint8_t* slot_index, FuriWait timeout) { furi_assert(seader_worker); furi_assert(slot_index); return furi_message_queue_get(seader_worker->messages, slot_index, timeout) == FuriStatusOk; @@ -250,10 +248,22 @@ SeaderWorker* seader_worker_alloc() { // Worker thread attributes seader_worker->thread = - furi_thread_alloc_ex("SeaderWorker", 6144, seader_worker_task, seader_worker); + furi_thread_alloc_ex("SeaderWorker", 5120, seader_worker_task, seader_worker); seader_worker->messages = furi_message_queue_alloc(2, sizeof(uint8_t)); seader_worker->apdu_slots = calloc(SEADER_WORKER_APDU_SLOT_COUNT, sizeof(SeaderAPDU)); + if(!seader_worker->thread || !seader_worker->messages || !seader_worker->apdu_slots) { + if(seader_worker->thread) { + furi_thread_free(seader_worker->thread); + } + if(seader_worker->messages) { + furi_message_queue_free(seader_worker->messages); + } + free(seader_worker->apdu_slots); + free(seader_worker); + return NULL; + } + seader_worker->callback = NULL; seader_worker->context = NULL; seader_worker->storage = furi_record_open(RECORD_STORAGE); @@ -559,14 +569,9 @@ void seader_worker_reading(Seader* seader) { SeaderWorker* seader_worker = seader->worker; FURI_LOG_I(TAG, "Reading loop started"); - 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); - } - return; - } + furi_check(seader_hf_plugin_acquire(seader)); + furi_check(seader->plugin_hf); + furi_check(seader->hf_plugin_ctx); while(seader_worker->state == SeaderWorkerStateReading) { bool detected = false; @@ -595,8 +600,8 @@ void seader_worker_reading(Seader* seader) { break; } else if(read_plan.decision == SeaderHfReadDecisionStartRead) { FURI_LOG_I(TAG, "HF start read for type=%d", read_plan.type_to_read); - detected = - seader->plugin_hf->start_read_for_type(seader->hf_plugin_ctx, read_plan.type_to_read); + detected = seader->plugin_hf->start_read_for_type( + seader->hf_plugin_ctx, read_plan.type_to_read); if(detected) { seader->hf_session_state = SeaderHfSessionStateActive; } diff --git a/t_1.c b/t_1.c index dad6d7f..be46805 100644 --- a/t_1.c +++ b/t_1.c @@ -6,7 +6,7 @@ #include "t_1_logic.h" -#define TAG "Seader:T=1" +#define TAG "Seader:T=1" #define SEADER_T1_MAX_FRAME_LEN (3U + SEADER_T1_IFS_MAX + 1U) static SeaderT1State* seader_t1_state(SeaderUartBridge* seader_uart) {