Add host-tested board and HF bridge policies

This commit is contained in:
CinderSocket
2026-06-17 15:24:33 -07:00
parent bf10c834eb
commit 148ecd7a72
8 changed files with 372 additions and 0 deletions
+4
View File
@@ -17,6 +17,7 @@ test-host:
cc $(HOST_TEST_CFLAGS) \
lib/host_tests/vendor/munit/munit.c \
lib/host_tests/test_main.c \
lib/host_tests/test_board_identity.c \
lib/host_tests/test_lrc.c \
lib/host_tests/test_board_power_lifecycle.c \
lib/host_tests/test_hf_read_lifecycle.c \
@@ -29,11 +30,13 @@ test-host:
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_hf_bridge_policy.c \
lib/host_tests/test_wiegand_plugin.c \
lib/host_tests/test_runtime_policy.c \
lib/host_tests/t1_test_stubs.c \
lib/host_tests/bit_buffer_mock.c \
lrc.c \
board_identity.c \
board_power_lifecycle.c \
sam_startup_ui.c \
ccid_logic.c \
@@ -48,6 +51,7 @@ test-host:
uhf_tag_config_view.c \
uhf_snmp_probe.c \
hf_read_lifecycle.c \
hf_bridge_policy.c \
seader_hf_read_plan.c \
wiegand_interface_fal/wiegand.c \
runtime_policy.c \
+18
View File
@@ -0,0 +1,18 @@
#include "board_identity.h"
SeaderBoardClass seader_board_classify(bool pa4_high, bool pc1_high, bool pc0_high) {
if(pa4_high) {
return SeaderBoardClassUhfCarrier;
}
if(pc1_high || pc0_high) {
return SeaderBoardClassSamOnly;
}
return SeaderBoardClassNone;
}
bool seader_board_class_supports_uhf(SeaderBoardClass board_class) {
return board_class == SeaderBoardClassUhfCarrier;
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
typedef enum {
SeaderBoardClassUnknown = 0,
SeaderBoardClassNone,
SeaderBoardClassSamOnly,
SeaderBoardClassUhfCarrier,
} SeaderBoardClass;
SeaderBoardClass seader_board_classify(bool pa4_high, bool pc1_high, bool pc0_high);
bool seader_board_class_supports_uhf(SeaderBoardClass board_class);
+73
View File
@@ -0,0 +1,73 @@
#include "hf_bridge_policy.h"
int seader_hf_bridge_begin_conversation(
void* context,
const SeaderHfBridgeConversationOps* ops,
int stop_command) {
if(!ops || !ops->set_conversation || !ops->begin_card_session || !ops->set_fail ||
!ops->run_conversation) {
return stop_command;
}
ops->set_conversation(context);
if(!ops->begin_card_session(context)) {
ops->set_fail(context);
return stop_command;
}
return ops->run_conversation(context);
}
uint16_t seader_hf_bridge_rf_status_code(SeaderHfBridgeRfStatus status) {
switch(status) {
case SeaderHfBridgeRfStatusSuccess:
return 0x0000U;
case SeaderHfBridgeRfStatusTimeout:
return 0x0020U;
case SeaderHfBridgeRfStatusCrc:
case SeaderHfBridgeRfStatusProtocol:
default:
return 0x0004U;
}
}
void seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatus status, uint8_t bytes[2]) {
if(!bytes) {
return;
}
const uint16_t code = seader_hf_bridge_rf_status_code(status);
bytes[0] = (uint8_t)((code >> 8) & 0xFFU);
bytes[1] = (uint8_t)(code & 0xFFU);
}
SeaderHfBridgeApduDecision seader_hf_bridge_apdu_decision(
bool virtual_credential,
bool conversation_stage,
size_t len,
size_t max_len,
bool queue_has_space) {
if(!virtual_credential && !conversation_stage) {
return SeaderHfBridgeApduDecisionDiscardStale;
}
if(len > max_len || !queue_has_space) {
return SeaderHfBridgeApduDecisionFailProtocol;
}
return SeaderHfBridgeApduDecisionQueue;
}
uint32_t seader_hf_bridge_timeout_us_to_fwt_fc(uint32_t timeout_us) {
if(timeout_us == 0U) {
return 0U;
}
const uint64_t fwt_fc = ((uint64_t)timeout_us * 1356U + 99U) / 100U;
if(fwt_fc > UINT32_MAX) {
return UINT32_MAX;
}
return (uint32_t)fwt_fc;
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum {
SeaderHfBridgeRfStatusSuccess = 0,
SeaderHfBridgeRfStatusTimeout,
SeaderHfBridgeRfStatusCrc,
SeaderHfBridgeRfStatusProtocol,
} SeaderHfBridgeRfStatus;
typedef enum {
SeaderHfBridgeApduDecisionDiscardStale = 0,
SeaderHfBridgeApduDecisionQueue,
SeaderHfBridgeApduDecisionFailProtocol,
} SeaderHfBridgeApduDecision;
typedef struct {
void (*set_conversation)(void* context);
bool (*begin_card_session)(void* context);
void (*set_fail)(void* context);
int (*run_conversation)(void* context);
} SeaderHfBridgeConversationOps;
int seader_hf_bridge_begin_conversation(
void* context,
const SeaderHfBridgeConversationOps* ops,
int stop_command);
uint16_t seader_hf_bridge_rf_status_code(SeaderHfBridgeRfStatus status);
void seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatus status, uint8_t bytes[2]);
SeaderHfBridgeApduDecision seader_hf_bridge_apdu_decision(
bool virtual_credential,
bool conversation_stage,
size_t len,
size_t max_len,
bool queue_has_space);
/* SAM nfcSend.timeOut is microseconds; Flipper NFC uses 13.56 MHz carrier cycles. */
uint32_t seader_hf_bridge_timeout_us_to_fwt_fc(uint32_t timeout_us);
+46
View File
@@ -0,0 +1,46 @@
#include "munit.h"
#include "board_identity.h"
static MunitResult test_classifies_strap_states(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
munit_assert_int(
seader_board_classify(false, false, false), ==, SeaderBoardClassNone);
munit_assert_int(
seader_board_classify(false, true, false), ==, SeaderBoardClassSamOnly);
munit_assert_int(
seader_board_classify(false, false, true), ==, SeaderBoardClassSamOnly);
munit_assert_int(
seader_board_classify(true, false, false), ==, SeaderBoardClassUhfCarrier);
munit_assert_int(
seader_board_classify(true, true, true), ==, SeaderBoardClassUhfCarrier);
return MUNIT_OK;
}
static MunitResult test_uhf_support_policy(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
munit_assert_false(seader_board_class_supports_uhf(SeaderBoardClassUnknown));
munit_assert_false(seader_board_class_supports_uhf(SeaderBoardClassNone));
munit_assert_false(seader_board_class_supports_uhf(SeaderBoardClassSamOnly));
munit_assert_true(seader_board_class_supports_uhf(SeaderBoardClassUhfCarrier));
return MUNIT_OK;
}
static MunitTest test_board_identity_cases[] = {
{(char*)"/strap-classifier", test_classifies_strap_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/uhf-support-policy", test_uhf_support_policy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_board_identity_suite = {
"",
test_board_identity_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};
+171
View File
@@ -0,0 +1,171 @@
#include "munit.h"
#include "hf_bridge_policy.h"
typedef struct {
unsigned index;
const char* calls[4];
bool begin_result;
int run_result;
} BridgeRecorder;
static void record_call(BridgeRecorder* recorder, const char* name) {
if(recorder && recorder->index < (sizeof(recorder->calls) / sizeof(recorder->calls[0]))) {
recorder->calls[recorder->index++] = name;
}
}
static void record_set_conversation(void* context) {
record_call(context, "set-conversation");
}
static bool record_begin_card_session(void* context) {
BridgeRecorder* recorder = context;
record_call(recorder, "begin-card-session");
return recorder->begin_result;
}
static void record_set_fail(void* context) {
record_call(context, "set-fail");
}
static int record_run_conversation(void* context) {
BridgeRecorder* recorder = context;
record_call(recorder, "run-conversation");
return recorder->run_result;
}
static const SeaderHfBridgeConversationOps bridge_ops = {
.set_conversation = record_set_conversation,
.begin_card_session = record_begin_card_session,
.set_fail = record_set_fail,
.run_conversation = record_run_conversation,
};
static MunitResult test_begin_conversation_sets_stage_before_card_session(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
BridgeRecorder recorder = {
.begin_result = true,
.run_result = 42,
};
const int result = seader_hf_bridge_begin_conversation(&recorder, &bridge_ops, -1);
munit_assert_int(result, ==, 42);
munit_assert_uint(recorder.index, ==, 3);
munit_assert_string_equal(recorder.calls[0], "set-conversation");
munit_assert_string_equal(recorder.calls[1], "begin-card-session");
munit_assert_string_equal(recorder.calls[2], "run-conversation");
return MUNIT_OK;
}
static MunitResult test_begin_conversation_fails_without_running_conversation(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
BridgeRecorder recorder = {
.begin_result = false,
.run_result = 42,
};
const int result = seader_hf_bridge_begin_conversation(&recorder, &bridge_ops, -1);
munit_assert_int(result, ==, -1);
munit_assert_uint(recorder.index, ==, 3);
munit_assert_string_equal(recorder.calls[0], "set-conversation");
munit_assert_string_equal(recorder.calls[1], "begin-card-session");
munit_assert_string_equal(recorder.calls[2], "set-fail");
return MUNIT_OK;
}
static MunitResult test_rf_status_mapping(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
uint8_t bytes[2] = {0xFF, 0xFF};
seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusSuccess, bytes);
munit_assert_uint8(bytes[0], ==, 0x00);
munit_assert_uint8(bytes[1], ==, 0x00);
seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusTimeout, bytes);
munit_assert_uint8(bytes[0], ==, 0x00);
munit_assert_uint8(bytes[1], ==, 0x20);
seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusCrc, bytes);
munit_assert_uint8(bytes[0], ==, 0x00);
munit_assert_uint8(bytes[1], ==, 0x04);
seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusProtocol, bytes);
munit_assert_uint8(bytes[0], ==, 0x00);
munit_assert_uint8(bytes[1], ==, 0x04);
return MUNIT_OK;
}
static MunitResult test_apdu_decision_discards_stale_messages(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
munit_assert_int(
seader_hf_bridge_apdu_decision(false, false, 16U, 258U, true),
==,
SeaderHfBridgeApduDecisionDiscardStale);
munit_assert_int(
seader_hf_bridge_apdu_decision(true, false, 16U, 258U, true),
==,
SeaderHfBridgeApduDecisionQueue);
return MUNIT_OK;
}
static MunitResult test_apdu_decision_fails_overflow(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
munit_assert_int(
seader_hf_bridge_apdu_decision(false, true, 259U, 258U, true),
==,
SeaderHfBridgeApduDecisionFailProtocol);
munit_assert_int(
seader_hf_bridge_apdu_decision(false, true, 16U, 258U, false),
==,
SeaderHfBridgeApduDecisionFailProtocol);
return MUNIT_OK;
}
static MunitResult test_timeout_us_to_fwt_fc(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
munit_assert_uint32(seader_hf_bridge_timeout_us_to_fwt_fc(0U), ==, 0U);
munit_assert_uint32(seader_hf_bridge_timeout_us_to_fwt_fc(1000U), ==, 13560U);
munit_assert_uint32(seader_hf_bridge_timeout_us_to_fwt_fc(20000U), ==, 271200U);
return MUNIT_OK;
}
static MunitTest test_hf_bridge_policy_cases[] = {
{(char*)"/begin-conversation-order", test_begin_conversation_sets_stage_before_card_session, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/begin-conversation-fail", test_begin_conversation_fails_without_running_conversation, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/rf-status-mapping", test_rf_status_mapping, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/apdu-discard-stale", test_apdu_decision_discards_stale_messages, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/apdu-overflow-fails", test_apdu_decision_fails_overflow, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/timeout-us-to-fwt", test_timeout_us_to_fwt_fc, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_hf_bridge_policy_suite = {
"",
test_hf_bridge_policy_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};
+4
View File
@@ -1,6 +1,7 @@
#include "munit.h"
extern MunitSuite test_lrc_suite;
extern MunitSuite test_board_identity_suite;
extern MunitSuite test_board_power_lifecycle_suite;
extern MunitSuite test_hf_read_lifecycle_suite;
extern MunitSuite test_sam_startup_ui_suite;
@@ -12,12 +13,14 @@ 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_hf_bridge_policy_suite;
extern MunitSuite test_runtime_policy_suite;
extern MunitSuite test_wiegand_plugin_suite;
int main(int argc, char* argv[]) {
MunitSuite child_suites[] = {
{"/lrc", test_lrc_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{"/board-identity", test_board_identity_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{"/board-power-lifecycle",
test_board_power_lifecycle_suite.tests,
NULL,
@@ -41,6 +44,7 @@ int main(int argc, char* argv[]) {
{"/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},
{"/hf-bridge-policy", test_hf_bridge_policy_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{"/runtime-policy", test_runtime_policy_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{"/wiegand-plugin", test_wiegand_plugin_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{NULL, NULL, NULL, 0, 0},