Split host and integration test coverage

This commit is contained in:
CinderSocket
2026-03-24 16:17:08 -07:00
parent 9a2c5253fc
commit 862a06f9ed
17 changed files with 823 additions and 99 deletions

View File

@@ -9,9 +9,12 @@ asn1:
build:
ufbt
HOST_TEST_CFLAGS = -std=c11 -Wall -Wextra -Werror -DSEADER_HOST_TEST -Ilib/host_tests/vendor/munit -Ilib/host_tests -Ilib/asn1 -I.
ASN1_TEST_CFLAGS = -std=c11 -Wall -Wextra -Werror -Wno-error=unused-function -Wno-error=unused-parameter -DASN_DISABLE_PER_SUPPORT -DASN_DISABLE_OER_SUPPORT -DASN_DISABLE_XER_SUPPORT -DASN_DISABLE_RANDOM_FILL -Ilib/host_tests/vendor/munit -Ilib/host_tests -Ilib/asn1 -Ilib/asn1_skeletons -I.
test-host:
mkdir -p build/host_tests
cc -std=c11 -Wall -Wextra -Werror -DSEADER_HOST_TEST -Ilib/host_tests/vendor/munit -Ilib/host_tests -I. \
cc $(HOST_TEST_CFLAGS) \
lib/host_tests/vendor/munit/munit.c \
lib/host_tests/test_main.c \
lib/host_tests/test_lrc.c \
@@ -21,6 +24,7 @@ test-host:
lib/host_tests/test_t1_protocol.c \
lib/host_tests/test_snmp.c \
lib/host_tests/test_uhf_status_label.c \
lib/host_tests/test_runtime_policy.c \
lib/host_tests/t1_test_stubs.c \
lib/host_tests/bit_buffer_mock.c \
lrc.c \
@@ -34,9 +38,51 @@ test-host:
uhf_status_label.c \
uhf_tag_config_view.c \
uhf_snmp_probe.c \
runtime_policy.c \
-o build/host_tests/seader_tests
./build/host_tests/seader_tests
test-asn1-integration:
mkdir -p build/host_tests
cc $(ASN1_TEST_CFLAGS) \
lib/host_tests/vendor/munit/munit.c \
lib/host_tests/test_card_details_main.c \
lib/host_tests/test_card_details_builder.c \
card_details_builder.c \
lib/asn1/CardDetails.c \
lib/asn1/Protocol.c \
lib/asn1/FrameProtocol.c \
lib/asn1/RunTimerValue.c \
lib/asn1_skeletons/OCTET_STRING.c \
lib/asn1_skeletons/BOOLEAN.c \
lib/asn1_skeletons/NativeInteger.c \
lib/asn1_skeletons/NativeEnumerated.c \
lib/asn1_skeletons/INTEGER.c \
lib/asn1_skeletons/OPEN_TYPE.c \
lib/asn1_skeletons/constr_CHOICE.c \
lib/asn1_skeletons/constr_SEQUENCE.c \
lib/asn1_skeletons/constr_TYPE.c \
lib/asn1_skeletons/asn_application.c \
lib/asn1_skeletons/asn_codecs_prim.c \
lib/asn1_skeletons/ber_tlv_tag.c \
lib/asn1_skeletons/ber_tlv_length.c \
lib/asn1_skeletons/ber_decoder.c \
lib/asn1_skeletons/der_encoder.c \
lib/asn1_skeletons/constraints.c \
lib/asn1_skeletons/asn_internal.c \
-o build/host_tests/seader_card_details_tests
./build/host_tests/seader_card_details_tests
test-runtime-integration:
mkdir -p build/host_tests
cc $(HOST_TEST_CFLAGS) \
lib/host_tests/vendor/munit/munit.c \
lib/host_tests/test_runtime_integration_main.c \
lib/host_tests/test_hf_release_sequence.c \
hf_release_sequence.c \
-o build/host_tests/seader_runtime_integration_tests
./build/host_tests/seader_runtime_integration_tests
launch:
ufbt launch

78
card_details_builder.c Normal file
View File

@@ -0,0 +1,78 @@
#include "card_details_builder.h"
#include <string.h>
#include <FrameProtocol.h>
bool seader_card_details_build(
CardDetails_t* card_details,
uint8_t sak,
const uint8_t* uid,
uint8_t uid_len,
const uint8_t* ats,
uint8_t ats_len) {
if(!card_details || !uid || uid_len == 0U) {
return false;
}
memset(card_details, 0, sizeof(*card_details));
if(OCTET_STRING_fromBuf(&card_details->csn, (const char*)uid, uid_len) != 0) {
return false;
}
uint8_t protocol_bytes[] = {0x00, 0x00};
if(ats != NULL) {
protocol_bytes[1] = FrameProtocol_nfc;
if(OCTET_STRING_fromBuf(
&card_details->protocol,
(const char*)protocol_bytes,
sizeof(protocol_bytes)) != 0) {
seader_card_details_reset(card_details);
return false;
}
card_details->sak = calloc(1, sizeof(*card_details->sak));
card_details->atsOrAtqbOrAtr = calloc(1, sizeof(*card_details->atsOrAtqbOrAtr));
if(!card_details->sak || !card_details->atsOrAtqbOrAtr ||
OCTET_STRING_fromBuf(card_details->sak, (const char*)&sak, 1) != 0 ||
OCTET_STRING_fromBuf(card_details->atsOrAtqbOrAtr, (const char*)ats, ats_len) != 0) {
seader_card_details_reset(card_details);
return false;
}
} else if(uid_len == 8U) {
protocol_bytes[1] = FrameProtocol_iclass;
if(OCTET_STRING_fromBuf(
&card_details->protocol,
(const char*)protocol_bytes,
sizeof(protocol_bytes)) != 0) {
seader_card_details_reset(card_details);
return false;
}
} else {
protocol_bytes[1] = FrameProtocol_nfc;
if(OCTET_STRING_fromBuf(
&card_details->protocol,
(const char*)protocol_bytes,
sizeof(protocol_bytes)) != 0) {
seader_card_details_reset(card_details);
return false;
}
card_details->sak = calloc(1, sizeof(*card_details->sak));
if(!card_details->sak ||
OCTET_STRING_fromBuf(card_details->sak, (const char*)&sak, 1) != 0) {
seader_card_details_reset(card_details);
return false;
}
}
return true;
}
void seader_card_details_reset(CardDetails_t* card_details) {
if(!card_details) {
return;
}
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, card_details);
memset(card_details, 0, sizeof(*card_details));
}

21
card_details_builder.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifndef ASN_EMIT_DEBUG
#define ASN_EMIT_DEBUG 0
#endif
#include <CardDetails.h>
bool seader_card_details_build(
CardDetails_t* card_details,
uint8_t sak,
const uint8_t* uid,
uint8_t uid_len,
const uint8_t* ats,
uint8_t ats_len);
void seader_card_details_reset(CardDetails_t* card_details);

31
hf_release_sequence.c Normal file
View File

@@ -0,0 +1,31 @@
#include "hf_release_sequence.h"
static void seader_hf_release_callback_invoke(
SeaderHfReleaseCallback callback,
void* context) {
if(callback) {
callback(context);
}
}
void seader_hf_release_sequence_run(SeaderHfReleaseSequence* sequence) {
if(!sequence) {
return;
}
if(sequence->hf_session_state) {
*sequence->hf_session_state = SeaderHfSessionStateTearingDown;
}
seader_hf_release_callback_invoke(sequence->plugin_stop, sequence->context);
seader_hf_release_callback_invoke(sequence->host_poller_release, sequence->context);
seader_hf_release_callback_invoke(sequence->host_picopass_release, sequence->context);
seader_hf_release_callback_invoke(sequence->plugin_free, sequence->context);
seader_hf_release_callback_invoke(sequence->plugin_manager_unload, sequence->context);
seader_hf_release_callback_invoke(sequence->worker_reset, sequence->context);
if(sequence->hf_session_state) {
*sequence->hf_session_state = SeaderHfSessionStateUnloaded;
}
if(sequence->mode_runtime && *sequence->mode_runtime == SeaderModeRuntimeHF) {
*sequence->mode_runtime = SeaderModeRuntimeNone;
}
}

19
hf_release_sequence.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "seader.h"
typedef void (*SeaderHfReleaseCallback)(void* context);
typedef struct {
void* context;
SeaderHfSessionState* hf_session_state;
SeaderModeRuntime* mode_runtime;
SeaderHfReleaseCallback plugin_stop;
SeaderHfReleaseCallback host_poller_release;
SeaderHfReleaseCallback host_picopass_release;
SeaderHfReleaseCallback plugin_free;
SeaderHfReleaseCallback plugin_manager_unload;
SeaderHfReleaseCallback worker_reset;
} SeaderHfReleaseSequence;
void seader_hf_release_sequence_run(SeaderHfReleaseSequence* sequence);

7
lib/host_tests/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Host Test Layers
- `make test-host`: fast deterministic unit/policy tests. Keep this limited to Seader-owned helpers, formatting, parsing, and ownership-policy logic.
- `make test-asn1-integration`: narrow integration coverage for real ASN.1 ownership/free behavior.
- `make test-runtime-integration`: narrow mock-based integration coverage for HF release ordering and final state publication.
Do not add generated ASN.1 or firmware-heavy runtime dependencies to `make test-host` unless they are already a supported part of that surface.

View File

@@ -0,0 +1,93 @@
#include "munit.h"
#include "card_details_builder.h"
static MunitResult test_builds_type4_with_owned_optional_fields(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const uint8_t uid[] = {0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
const uint8_t ats[] = {0x75, 0x77, 0x81, 0x02};
CardDetails_t card_details = {0};
munit_assert_true(
seader_card_details_build(&card_details, 0x20U, uid, sizeof(uid), ats, sizeof(ats)));
munit_assert_size(card_details.csn.size, ==, sizeof(uid));
munit_assert_not_null(card_details.sak);
munit_assert_not_null(card_details.atsOrAtqbOrAtr);
munit_assert_size(card_details.sak->size, ==, 1U);
munit_assert_size(card_details.atsOrAtqbOrAtr->size, ==, sizeof(ats));
munit_assert_memory_equal(sizeof(uid), card_details.csn.buf, uid);
munit_assert_memory_equal(sizeof(ats), card_details.atsOrAtqbOrAtr->buf, ats);
seader_card_details_reset(&card_details);
munit_assert_ptr_null(card_details.sak);
munit_assert_ptr_null(card_details.atsOrAtqbOrAtr);
munit_assert_ptr_null(card_details.csn.buf);
return MUNIT_OK;
}
static MunitResult test_builds_picopass_without_optional_fields(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const uint8_t uid[] = {1, 2, 3, 4, 5, 6, 7, 8};
CardDetails_t card_details = {0};
munit_assert_true(seader_card_details_build(&card_details, 0U, uid, sizeof(uid), NULL, 0U));
munit_assert_ptr_null(card_details.sak);
munit_assert_ptr_null(card_details.atsOrAtqbOrAtr);
seader_card_details_reset(&card_details);
return MUNIT_OK;
}
static MunitResult test_builds_mfc_with_owned_sak(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const uint8_t uid[] = {0xDE, 0xAD, 0xBE, 0xEF};
CardDetails_t card_details = {0};
munit_assert_true(seader_card_details_build(&card_details, 0x08U, uid, sizeof(uid), NULL, 0U));
munit_assert_not_null(card_details.sak);
munit_assert_size(card_details.sak->size, ==, 1U);
munit_assert_ptr_null(card_details.atsOrAtqbOrAtr);
seader_card_details_reset(&card_details);
munit_assert_ptr_null(card_details.sak);
return MUNIT_OK;
}
static MunitResult test_rejects_invalid_input(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
CardDetails_t card_details = {0};
munit_assert_false(seader_card_details_build(&card_details, 0U, NULL, 0U, NULL, 0U));
munit_assert_ptr_null(card_details.csn.buf);
return MUNIT_OK;
}
static MunitTest test_card_details_builder_cases[] = {
{(char*)"/type4-owned-optional-fields", test_builds_type4_with_owned_optional_fields, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/picopass-no-optional-fields", test_builds_picopass_without_optional_fields, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/mfc-owned-sak", test_builds_mfc_with_owned_sak, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/invalid-input", test_rejects_invalid_input, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_card_details_builder_suite = {
"",
test_card_details_builder_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};

View File

@@ -0,0 +1,15 @@
#include "munit.h"
extern MunitSuite test_card_details_builder_suite;
int main(int argc, char* argv[]) {
MunitSuite main_suite = {
"/card-details",
test_card_details_builder_suite.tests,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};
return munit_suite_main(&main_suite, NULL, argc, argv);
}

View File

@@ -0,0 +1,108 @@
#include "munit.h"
#include "hf_release_sequence.h"
typedef struct {
unsigned index;
const char* calls[8];
} ReleaseRecorder;
static void record_call(ReleaseRecorder* recorder, const char* name) {
if(recorder && recorder->index < (sizeof(recorder->calls) / sizeof(recorder->calls[0]))) {
recorder->calls[recorder->index++] = name;
}
}
static void record_plugin_stop(void* context) {
record_call(context, "plugin-stop");
}
static void record_host_poller_release(void* context) {
record_call(context, "host-poller-release");
}
static void record_picopass_release(void* context) {
record_call(context, "picopass-release");
}
static void record_plugin_free(void* context) {
record_call(context, "plugin-free");
}
static void record_manager_unload(void* context) {
record_call(context, "plugin-manager-unload");
}
static void record_worker_reset(void* context) {
record_call(context, "worker-reset");
}
static MunitResult test_release_sequence_orders_operations_and_finalizes_state(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
ReleaseRecorder recorder = {0};
SeaderHfSessionState hf_state = SeaderHfSessionStateActive;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeHF;
SeaderHfReleaseSequence sequence = {
.context = &recorder,
.hf_session_state = &hf_state,
.mode_runtime = &mode_runtime,
.plugin_stop = record_plugin_stop,
.host_poller_release = record_host_poller_release,
.host_picopass_release = record_picopass_release,
.plugin_free = record_plugin_free,
.plugin_manager_unload = record_manager_unload,
.worker_reset = record_worker_reset,
};
seader_hf_release_sequence_run(&sequence);
munit_assert_int(hf_state, ==, SeaderHfSessionStateUnloaded);
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
munit_assert_uint(recorder.index, ==, 6);
munit_assert_string_equal(recorder.calls[0], "plugin-stop");
munit_assert_string_equal(recorder.calls[1], "host-poller-release");
munit_assert_string_equal(recorder.calls[2], "picopass-release");
munit_assert_string_equal(recorder.calls[3], "plugin-free");
munit_assert_string_equal(recorder.calls[4], "plugin-manager-unload");
munit_assert_string_equal(recorder.calls[5], "worker-reset");
return MUNIT_OK;
}
static MunitResult test_release_sequence_tolerates_missing_callbacks(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderHfSessionState hf_state = SeaderHfSessionStateLoaded;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeHF;
SeaderHfReleaseSequence sequence = {
.hf_session_state = &hf_state,
.mode_runtime = &mode_runtime,
.worker_reset = record_worker_reset,
};
seader_hf_release_sequence_run(&sequence);
munit_assert_int(hf_state, ==, SeaderHfSessionStateUnloaded);
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
return MUNIT_OK;
}
static MunitTest test_hf_release_sequence_cases[] = {
{(char*)"/ordering", test_release_sequence_orders_operations_and_finalizes_state, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/missing-callbacks", test_release_sequence_tolerates_missing_callbacks, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_hf_release_sequence_suite = {
"",
test_hf_release_sequence_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};

View File

@@ -7,6 +7,7 @@ extern MunitSuite test_t1_existing_suite;
extern MunitSuite test_t1_protocol_suite;
extern MunitSuite test_snmp_suite;
extern MunitSuite test_uhf_status_label_suite;
extern MunitSuite test_runtime_policy_suite;
int main(int argc, char* argv[]) {
MunitSuite child_suites[] = {
@@ -17,6 +18,7 @@ int main(int argc, char* argv[]) {
{"/ccid", test_ccid_logic_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{"/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},
{"/runtime-policy", test_runtime_policy_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE},
{NULL, NULL, NULL, 0, 0},
};
MunitSuite main_suite = {

View File

@@ -0,0 +1,15 @@
#include "munit.h"
extern MunitSuite test_hf_release_sequence_suite;
int main(int argc, char* argv[]) {
MunitSuite main_suite = {
"/runtime-integration",
test_hf_release_sequence_suite.tests,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};
return munit_suite_main(&main_suite, NULL, argc, argv);
}

View File

@@ -0,0 +1,139 @@
#include <string.h>
#include "munit.h"
#include "runtime_policy.h"
static MunitResult test_reset_cached_sam_metadata_clears_all_fields(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
uint8_t sam_version[2] = {1U, 99U};
char uhf_status_label[16];
memset(uhf_status_label, 'X', sizeof(uhf_status_label));
SeaderUhfSnmpProbe probe = {
.stage = SeaderUhfSnmpProbeStageDone,
.has_monza4qt = true,
.has_higgs3 = true,
.monza4qt_key_present = true,
.higgs3_key_present = true,
.ice_value_len = 7U,
};
seader_runtime_reset_cached_sam_metadata(
sam_version, uhf_status_label, sizeof(uhf_status_label), &probe);
munit_assert_uint8(sam_version[0], ==, 0U);
munit_assert_uint8(sam_version[1], ==, 0U);
munit_assert_char(uhf_status_label[0], ==, '\0');
munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDiscovery);
munit_assert_false(probe.has_monza4qt);
munit_assert_false(probe.has_higgs3);
munit_assert_false(probe.monza4qt_key_present);
munit_assert_false(probe.higgs3_key_present);
munit_assert_size(probe.ice_value_len, ==, 0U);
return MUNIT_OK;
}
static MunitResult test_begin_uhf_probe_sets_runtime_and_initializes_probe(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeNone;
SeaderUhfSnmpProbe probe = {.stage = SeaderUhfSnmpProbeStageDone};
munit_assert_true(seader_runtime_begin_uhf_probe(
true, &mode_runtime, SeaderHfSessionStateUnloaded, &probe));
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeUHF);
munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDiscovery);
return MUNIT_OK;
}
static MunitResult test_begin_uhf_probe_rejects_invalid_states(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeNone;
SeaderUhfSnmpProbe probe = {0};
munit_assert_false(seader_runtime_begin_uhf_probe(
false, &mode_runtime, SeaderHfSessionStateUnloaded, &probe));
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
munit_assert_false(seader_runtime_begin_uhf_probe(
true, &mode_runtime, SeaderHfSessionStateActive, &probe));
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
mode_runtime = SeaderModeRuntimeHF;
munit_assert_false(seader_runtime_begin_uhf_probe(
true, &mode_runtime, SeaderHfSessionStateUnloaded, &probe));
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeHF);
return MUNIT_OK;
}
static MunitResult test_finish_uhf_probe_restores_none(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeUHF;
seader_runtime_finish_uhf_probe(&mode_runtime);
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
mode_runtime = SeaderModeRuntimeHF;
seader_runtime_finish_uhf_probe(&mode_runtime);
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeHF);
return MUNIT_OK;
}
static MunitResult test_finalize_hf_release_sets_terminal_state(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderHfSessionState hf_state = SeaderHfSessionStateTearingDown;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeHF;
seader_runtime_finalize_hf_release(&hf_state, &mode_runtime);
munit_assert_int(hf_state, ==, SeaderHfSessionStateUnloaded);
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
return MUNIT_OK;
}
static MunitResult test_begin_hf_teardown_sets_state(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderHfSessionState hf_state = SeaderHfSessionStateActive;
seader_runtime_begin_hf_teardown(&hf_state);
munit_assert_int(hf_state, ==, SeaderHfSessionStateTearingDown);
return MUNIT_OK;
}
static MunitTest test_runtime_policy_cases[] = {
{(char*)"/reset-sam-metadata", test_reset_cached_sam_metadata_clears_all_fields, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/begin-uhf-probe", test_begin_uhf_probe_sets_runtime_and_initializes_probe, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/begin-uhf-probe-invalid", test_begin_uhf_probe_rejects_invalid_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/finish-uhf-probe", test_finish_uhf_probe_restores_none, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/begin-hf-teardown", test_begin_hf_teardown_sets_state, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/finalize-hf-release", test_finalize_hf_release_sets_terminal_state, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_runtime_policy_suite = {
"",
test_runtime_policy_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};

View File

@@ -1,3 +1,5 @@
#include <string.h>
#include "munit.h"
#include "uhf_status_label.h"
@@ -19,9 +21,68 @@ static MunitResult test_formats_supported_key_states(const MunitParameter params
return MUNIT_OK;
}
static MunitResult test_handles_null_and_zero_sized_output(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
seader_uhf_status_label_format(true, true, false, false, NULL, 0U);
char label[4] = {'X', 'Y', 'Z', 'W'};
seader_uhf_status_label_format(true, true, false, false, label, 0U);
munit_assert_memory_equal(sizeof(label), label, ((char[]){'X', 'Y', 'Z', 'W'}));
return MUNIT_OK;
}
static MunitResult test_nul_terminates_single_byte_output(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
char label[1] = {'X'};
seader_uhf_status_label_format(true, false, true, false, label, sizeof(label));
munit_assert_char(label[0], ==, '\0');
return MUNIT_OK;
}
static MunitResult test_truncates_safely_for_small_buffers(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
char label[8];
memset(label, 'Z', sizeof(label));
seader_uhf_status_label_format(true, false, true, false, label, sizeof(label));
munit_assert_char(label[sizeof(label) - 1], ==, '\0');
munit_assert_char(label[0], ==, 'U');
munit_assert_char(label[1], ==, 'H');
return MUNIT_OK;
}
static MunitResult test_small_buffer_for_none_is_safe(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
char label[4];
memset(label, 'Q', sizeof(label));
seader_uhf_status_label_format(false, false, false, false, label, sizeof(label));
munit_assert_char(label[sizeof(label) - 1], ==, '\0');
munit_assert_char(label[0], ==, 'U');
return MUNIT_OK;
}
static MunitTest test_uhf_status_label_cases[] = {
{(char*)"/none", test_formats_none, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/supported-key-states", test_formats_supported_key_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/null-zero-output", test_handles_null_and_zero_sized_output, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/single-byte-output", test_nul_terminates_single_byte_output, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/small-buffer-truncation", test_truncates_safely_for_small_buffers, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/small-buffer-none", test_small_buffer_for_none_is_safe, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};

70
runtime_policy.c Normal file
View File

@@ -0,0 +1,70 @@
#include "runtime_policy.h"
void seader_runtime_reset_cached_sam_metadata(
uint8_t sam_version[2],
char* uhf_status_label,
size_t label_size,
SeaderUhfSnmpProbe* probe) {
if(sam_version) {
sam_version[0] = 0U;
sam_version[1] = 0U;
}
if(uhf_status_label && label_size > 0U) {
uhf_status_label[0] = '\0';
}
if(probe) {
seader_uhf_snmp_probe_init(probe);
}
}
bool seader_runtime_begin_uhf_probe(
bool sam_present,
SeaderModeRuntime* mode_runtime,
SeaderHfSessionState hf_session_state,
SeaderUhfSnmpProbe* probe) {
if(!sam_present || !mode_runtime || !probe) {
return false;
}
if(hf_session_state != SeaderHfSessionStateUnloaded) {
return false;
}
if(*mode_runtime != SeaderModeRuntimeNone) {
return false;
}
*mode_runtime = SeaderModeRuntimeUHF;
seader_uhf_snmp_probe_init(probe);
return true;
}
void seader_runtime_finish_uhf_probe(SeaderModeRuntime* mode_runtime) {
if(!mode_runtime) {
return;
}
if(*mode_runtime == SeaderModeRuntimeUHF) {
*mode_runtime = SeaderModeRuntimeNone;
}
}
void seader_runtime_begin_hf_teardown(SeaderHfSessionState* hf_session_state) {
if(hf_session_state) {
*hf_session_state = SeaderHfSessionStateTearingDown;
}
}
void seader_runtime_finalize_hf_release(
SeaderHfSessionState* hf_session_state,
SeaderModeRuntime* mode_runtime) {
if(hf_session_state) {
*hf_session_state = SeaderHfSessionStateUnloaded;
}
if(mode_runtime && *mode_runtime == SeaderModeRuntimeHF) {
*mode_runtime = SeaderModeRuntimeNone;
}
}

28
runtime_policy.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "seader.h"
#include "uhf_snmp_probe.h"
void seader_runtime_reset_cached_sam_metadata(
uint8_t sam_version[2],
char* uhf_status_label,
size_t label_size,
SeaderUhfSnmpProbe* probe);
bool seader_runtime_begin_uhf_probe(
bool sam_present,
SeaderModeRuntime* mode_runtime,
SeaderHfSessionState hf_session_state,
SeaderUhfSnmpProbe* probe);
void seader_runtime_finish_uhf_probe(SeaderModeRuntime* mode_runtime);
void seader_runtime_begin_hf_teardown(SeaderHfSessionState* hf_session_state);
void seader_runtime_finalize_hf_release(
SeaderHfSessionState* hf_session_state,
SeaderModeRuntime* mode_runtime);

View File

@@ -4,6 +4,8 @@
#include "sam_key_label.h"
#include "trace_log.h"
#include "uhf_snmp_probe.h"
#include "runtime_policy.h"
#include "card_details_builder.h"
#include "uhf_status_label.h"
#include <toolbox/path.h>
#include <toolbox/version.h>
@@ -76,10 +78,11 @@ static void seader_reset_cached_sam_metadata(Seader* seader) {
return;
}
seader->sam_version[0] = 0U;
seader->sam_version[1] = 0U;
seader->uhf_status_label[0] = '\0';
seader_uhf_snmp_probe_init(&seader->snmp_probe);
seader_runtime_reset_cached_sam_metadata(
seader->sam_version,
seader->uhf_status_label,
sizeof(seader->uhf_status_label),
&seader->snmp_probe);
}
static bool seader_snmp_probe_send_next_request(Seader* seader) {
@@ -110,9 +113,7 @@ static void seader_snmp_probe_finish(Seader* seader) {
return;
}
if(seader->mode_runtime == SeaderModeRuntimeUHF) {
seader->mode_runtime = SeaderModeRuntimeNone;
}
seader_runtime_finish_uhf_probe(&seader->mode_runtime);
seader_sam_set_state(seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
}
@@ -121,8 +122,14 @@ static void seader_start_snmp_probe(Seader* seader) {
return;
}
seader->mode_runtime = SeaderModeRuntimeUHF;
seader_uhf_snmp_probe_init(&seader->snmp_probe);
if(!seader_runtime_begin_uhf_probe(
seader->sam_present,
&seader->mode_runtime,
seader->hf_session_state,
&seader->snmp_probe)) {
seader_snmp_probe_finish(seader);
return;
}
seader_update_uhf_status_label(seader);
seader_sam_set_state(
seader,
@@ -1725,12 +1732,6 @@ NfcCommand seader_worker_card_detect(
CardDetails_t cardDetails = {0};
FURI_LOG_D(TAG, "Build card_detect sak=%02x uid_len=%u ats_len=%u", sak, uid_len, ats_len);
if(OCTET_STRING_fromBuf(&cardDetails.csn, (const char*)uid, uid_len) != 0) {
FURI_LOG_E(TAG, "Failed to encode CSN");
return NfcCommandStop;
}
uint8_t protocol_bytes[] = {0x00, 0x00};
// this won't hold true for Seos cards, but then we won't see the SIO from Seos cards anyway
// so it doesn't really matter
size_t diversifier_len = uid_len;
@@ -1742,46 +1743,9 @@ NfcCommand seader_worker_card_detect(
memcpy(credential->diversifier, uid, diversifier_len);
credential->diversifier_len = diversifier_len;
if(ats != NULL) { // type 4
protocol_bytes[1] = FrameProtocol_nfc;
if(OCTET_STRING_fromBuf(
&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes)) != 0) {
FURI_LOG_E(TAG, "Failed to encode 14A protocol");
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails);
return NfcCommandStop;
}
cardDetails.sak = calloc(1, sizeof(*cardDetails.sak));
cardDetails.atsOrAtqbOrAtr = calloc(1, sizeof(*cardDetails.atsOrAtqbOrAtr));
if(!cardDetails.sak || !cardDetails.atsOrAtqbOrAtr ||
OCTET_STRING_fromBuf(cardDetails.sak, (const char*)&sak, 1) != 0 ||
OCTET_STRING_fromBuf(cardDetails.atsOrAtqbOrAtr, (const char*)ats, ats_len) != 0) {
FURI_LOG_E(TAG, "Failed to encode 14A optional card details");
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails);
return NfcCommandStop;
}
// TODO: Update asn1 to change atqa to ats
} else if(uid_len == 8) { // picopass
protocol_bytes[1] = FrameProtocol_iclass;
if(OCTET_STRING_fromBuf(
&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes)) != 0) {
FURI_LOG_E(TAG, "Failed to encode picopass protocol");
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails);
return NfcCommandStop;
}
} else { // MFC
protocol_bytes[1] = FrameProtocol_nfc;
if(OCTET_STRING_fromBuf(
&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes)) != 0) {
FURI_LOG_E(TAG, "Failed to encode MFC protocol");
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails);
return NfcCommandStop;
}
cardDetails.sak = calloc(1, sizeof(*cardDetails.sak));
if(!cardDetails.sak || OCTET_STRING_fromBuf(cardDetails.sak, (const char*)&sak, 1) != 0) {
FURI_LOG_E(TAG, "Failed to encode MFC SAK");
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails);
return NfcCommandStop;
}
if(!seader_card_details_build(&cardDetails, sak, uid, uid_len, ats, ats_len)) {
FURI_LOG_E(TAG, "Failed to build card details");
return NfcCommandStop;
}
seader_sam_set_state(
@@ -1800,6 +1764,6 @@ NfcCommand seader_worker_card_detect(
version_get_version(version),
FAP_VERSION);
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_CardDetails, &cardDetails);
seader_card_details_reset(&cardDetails);
return NfcCommandContinue;
}

111
seader.c
View File

@@ -1,4 +1,6 @@
#include "seader_i.h"
#include "runtime_policy.h"
#include "hf_release_sequence.h"
#include "trace_log.h"
#define TAG "Seader"
@@ -375,6 +377,60 @@ static void seader_hf_session_force_unloaded(Seader* seader) {
}
}
static void seader_hf_release_plugin_stop(void* context) {
Seader* seader = context;
if(seader && seader->plugin_hf && seader->hf_plugin_ctx) {
seader->plugin_hf->stop(seader->hf_plugin_ctx);
}
}
static void seader_hf_release_host_poller(void* context) {
Seader* seader = context;
if(seader && seader->poller) {
FURI_LOG_I(TAG, "Stopping host NFC poller");
nfc_poller_stop(seader->poller);
nfc_poller_free(seader->poller);
seader->poller = NULL;
}
}
static void seader_hf_release_host_picopass(void* context) {
Seader* seader = context;
if(seader && seader->picopass_poller) {
FURI_LOG_I(TAG, "Stopping host Picopass poller");
picopass_poller_stop(seader->picopass_poller);
picopass_poller_free(seader->picopass_poller);
seader->picopass_poller = NULL;
}
}
static void seader_hf_release_plugin_free(void* context) {
Seader* seader = context;
if(seader && seader->plugin_hf && seader->hf_plugin_ctx) {
seader->plugin_hf->free(seader->hf_plugin_ctx);
}
if(seader) {
seader->hf_plugin_ctx = NULL;
seader->plugin_hf = NULL;
}
}
static void seader_hf_release_plugin_manager(void* context) {
Seader* seader = context;
if(seader && seader->hf_plugin_manager) {
FURI_LOG_I(TAG, "Unloading HF plugin");
plugin_manager_free(seader->hf_plugin_manager);
seader->hf_plugin_manager = NULL;
}
}
static void seader_hf_release_worker_reset(void* context) {
Seader* seader = context;
if(seader && seader->worker) {
seader_worker_reset_poller_session(seader->worker);
}
}
bool seader_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Seader* seader = context;
@@ -795,7 +851,7 @@ static void seader_hf_teardown_blocking(Seader* seader) {
return;
}
seader->hf_session_state = SeaderHfSessionStateTearingDown;
seader_runtime_begin_hf_teardown(&seader->hf_session_state);
if(!seader_worker_acquire(seader) || !seader->worker || !seader->uart) {
FURI_LOG_W(TAG, "HF blocking teardown fallback");
seader_hf_plugin_release(seader);
@@ -810,47 +866,18 @@ static void seader_hf_teardown_blocking(Seader* seader) {
void seader_hf_plugin_release(Seader* seader) {
furi_assert(seader);
seader->hf_session_state = SeaderHfSessionStateTearingDown;
if(seader->plugin_hf && seader->hf_plugin_ctx) {
seader->plugin_hf->stop(seader->hf_plugin_ctx);
}
if(seader->poller) {
FURI_LOG_I(TAG, "Stopping host NFC poller");
nfc_poller_stop(seader->poller);
nfc_poller_free(seader->poller);
seader->poller = NULL;
}
if(seader->picopass_poller) {
FURI_LOG_I(TAG, "Stopping host Picopass poller");
picopass_poller_stop(seader->picopass_poller);
picopass_poller_free(seader->picopass_poller);
seader->picopass_poller = NULL;
}
if(seader->plugin_hf && seader->hf_plugin_ctx) {
seader->plugin_hf->free(seader->hf_plugin_ctx);
}
seader->hf_plugin_ctx = NULL;
seader->plugin_hf = NULL;
if(seader->hf_plugin_manager) {
FURI_LOG_I(TAG, "Unloading HF plugin");
plugin_manager_free(seader->hf_plugin_manager);
seader->hf_plugin_manager = NULL;
}
if(seader->worker) {
seader_worker_reset_poller_session(seader->worker);
}
if(seader->mode_runtime == SeaderModeRuntimeHF) {
seader->mode_runtime = SeaderModeRuntimeNone;
}
seader->hf_session_state = SeaderHfSessionStateUnloaded;
SeaderHfReleaseSequence release_sequence = {
.context = seader,
.hf_session_state = &seader->hf_session_state,
.mode_runtime = &seader->mode_runtime,
.plugin_stop = seader_hf_release_plugin_stop,
.host_poller_release = seader_hf_release_host_poller,
.host_picopass_release = seader_hf_release_host_picopass,
.plugin_free = seader_hf_release_plugin_free,
.plugin_manager_unload = seader_hf_release_plugin_manager,
.worker_reset = seader_hf_release_worker_reset,
};
seader_hf_release_sequence_run(&release_sequence);
}
bool seader_hf_finish_teardown_action(Seader* seader) {