begin work on multi tech

This commit is contained in:
CinderSocket
2026-03-25 00:55:52 -07:00
parent aa86e95df7
commit 9c8b24bb41
6 changed files with 211 additions and 14 deletions

View File

@@ -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

View File

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

View File

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

49
seader_hf_read_plan.c Normal file
View File

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

23
seader_hf_read_plan.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stddef.h>
#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);

View File

@@ -1,4 +1,5 @@
#include "seader_worker_i.h"
#include "seader_hf_read_plan.h"
#include "trace_log.h"
#include <flipper_format/flipper_format.h>
@@ -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;
}