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; }