mirror of
https://github.com/bettse/seader.git
synced 2026-10-15 06:50:37 +00:00
Add host-tested board and HF bridge policies
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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},
|
||||
|
||||
Reference in New Issue
Block a user