Harden HF startup failure handling

This commit is contained in:
CinderSocket
2026-03-27 18:05:10 -07:00
parent 2dc6779a55
commit 1ee80b21a3
8 changed files with 576 additions and 375 deletions

View File

@@ -51,6 +51,28 @@ static const uint8_t plugin_hf_select_desfire_app_no_le[] =
{0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x00};
static const uint8_t plugin_hf_file_not_found[] = {0x6a, 0x82};
static NfcCommand plugin_hf_run_conversation(PluginHfContext* ctx) {
if(!ctx || !ctx->api) {
FURI_LOG_E(TAG, "Cannot run HF conversation without valid context");
return NfcCommandStop;
}
furi_thread_set_current_priority(FuriThreadPriorityLowest);
ctx->api->run_conversation(ctx->host_ctx);
PluginHfStage stage = ctx->api->get_stage(ctx->host_ctx);
if(stage == PluginHfStageComplete) {
return NfcCommandStop;
}
if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
return NfcCommandStop;
}
return NfcCommandContinue;
}
static bool plugin_hf_validate_host_api(const PluginHfHostApi* api) {
if(!api) {
FURI_LOG_E(TAG, "Missing HF host API");
@@ -65,10 +87,8 @@ static bool plugin_hf_validate_host_api(const PluginHfHostApi* api) {
} \
} while(false)
HF_REQUIRE_API(notify_card_detected);
HF_REQUIRE_API(notify_worker_exit);
HF_REQUIRE_API(sam_can_accept_card);
HF_REQUIRE_API(send_card_detected);
HF_REQUIRE_API(begin_card_session);
HF_REQUIRE_API(send_nfc_rx);
HF_REQUIRE_API(run_conversation);
HF_REQUIRE_API(set_stage);
@@ -91,18 +111,27 @@ static bool plugin_hf_validate_host_api(const PluginHfHostApi* api) {
return true;
}
static PluginHfContext* plugin_hf_require_ctx(void* plugin_ctx) {
static PluginHfContext* plugin_hf_get_ctx(void* plugin_ctx) {
PluginHfContext* ctx = plugin_ctx;
furi_check(ctx);
furi_check(ctx->api);
furi_check(ctx->host_ctx);
furi_check(ctx->nfc);
furi_check(ctx->nfc_device);
if(!ctx || !ctx->api || !ctx->host_ctx || !ctx->nfc || !ctx->nfc_device) {
FURI_LOG_W(
TAG,
"Invalid HF plugin context ctx=%p api=%p host=%p nfc=%p device=%p",
(void*)ctx,
ctx ? (void*)ctx->api : NULL,
ctx ? ctx->host_ctx : NULL,
ctx ? (void*)ctx->nfc : NULL,
ctx ? (void*)ctx->nfc_device : NULL);
return NULL;
}
return ctx;
}
static void plugin_hf_cleanup_pollers(PluginHfContext* ctx) {
ctx = plugin_hf_require_ctx(ctx);
ctx = plugin_hf_get_ctx(ctx);
if(!ctx) {
return;
}
if(ctx->poller) {
nfc_poller_stop(ctx->poller);
nfc_poller_free(ctx->poller);
@@ -116,7 +145,10 @@ static void plugin_hf_cleanup_pollers(PluginHfContext* ctx) {
}
static void plugin_hf_set_read_error(PluginHfContext* ctx, const char* text) {
ctx = plugin_hf_require_ctx(ctx);
ctx = plugin_hf_get_ctx(ctx);
if(!ctx) {
return;
}
if(ctx->api->set_read_error) {
ctx->api->set_read_error(ctx->host_ctx, text);
}
@@ -154,9 +186,10 @@ static PicopassError plugin_hf_fake_epurse_update(BitBuffer* tx_buffer, BitBuffe
static void
plugin_hf_capture_sio(PluginHfContext* ctx, BitBuffer* tx_buffer, BitBuffer* rx_buffer) {
ctx = plugin_hf_require_ctx(ctx);
furi_check(tx_buffer);
furi_check(rx_buffer);
ctx = plugin_hf_get_ctx(ctx);
if(!ctx || !tx_buffer || !rx_buffer) {
return;
}
const uint8_t* buffer = bit_buffer_get_data(tx_buffer);
size_t len = bit_buffer_get_size_bytes(tx_buffer);
const uint8_t* rx_buffer_data = bit_buffer_get_data(rx_buffer);
@@ -179,7 +212,10 @@ static void
}
static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, size_t len) {
ctx = plugin_hf_require_ctx(ctx);
ctx = plugin_hf_get_ctx(ctx);
if(!ctx) {
return;
}
if(!buffer || len == 0U) {
FURI_LOG_W(TAG, "Skip picopass transmit invalid input");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
@@ -240,7 +276,10 @@ static void plugin_hf_iso14443a_transmit(
UNUSED(timeout);
UNUSED(format);
ctx = plugin_hf_require_ctx(ctx);
ctx = plugin_hf_get_ctx(ctx);
if(!ctx) {
return;
}
if(!buffer || len == 0U || !ctx->iso14443_4a_poller) {
FURI_LOG_W(TAG, "Skip 14A transmit invalid state");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
@@ -306,7 +345,10 @@ static void plugin_hf_mfc_transmit(
uint8_t format[3]) {
UNUSED(timeout);
ctx = plugin_hf_require_ctx(ctx);
ctx = plugin_hf_get_ctx(ctx);
if(!ctx) {
return;
}
if(!buffer || len == 0U || !ctx->mfc_poller) {
FURI_LOG_W(TAG, "Skip MFC transmit invalid state");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
@@ -424,7 +466,10 @@ static void plugin_hf_mfc_transmit(
}
static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
PluginHfContext* ctx = plugin_hf_require_ctx(context);
PluginHfContext* ctx = plugin_hf_get_ctx(context);
if(!ctx) {
return NfcCommandStop;
}
NfcCommand ret = NfcCommandContinue;
const Iso14443_4aPollerEvent* iso_event = event.event_data;
if(event.protocol != NfcProtocolIso14443_4a || !iso_event) {
@@ -438,13 +483,15 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v
if(iso_event->type == Iso14443_4aPollerEventTypeReady) {
HF_DIAG_D("14A ready stage=%d", stage);
if(stage == PluginHfStageCardDetect) {
ctx->api->notify_card_detected(ctx->host_ctx);
if(!ctx->api->sam_can_accept_card(ctx->host_ctx)) {
return NfcCommandContinue;
if(!ctx->poller || !ctx->nfc_device) {
FURI_LOG_E(
TAG,
"14A detect without poller/device poller=%p device=%p",
(void*)ctx->poller,
(void*)ctx->nfc_device);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
furi_check(ctx->poller);
furi_check(ctx->nfc_device);
const void* poller_data = nfc_poller_get_data(ctx->poller);
if(!poller_data) {
FURI_LOG_E(TAG, "14A ready without poller data");
@@ -501,20 +548,15 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v
}
}
ctx->api->send_card_detected(
ctx->host_ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_len);
FURI_LOG_D(TAG, "14A cardDetected delivered uid_len=%u ats_len=%u", uid_len, ats_len);
if(!ctx->api->begin_card_session(
ctx->host_ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_len)) {
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation);
} else if(stage == PluginHfStageConversation) {
FURI_LOG_D(TAG, "14A enter conversation");
ctx->api->run_conversation(ctx->host_ctx);
stage = ctx->api->get_stage(ctx->host_ctx);
if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
ret = plugin_hf_run_conversation(ctx);
} else if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
@@ -532,7 +574,10 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v
}
static NfcCommand plugin_hf_poller_callback_mfc(NfcGenericEvent event, void* context) {
PluginHfContext* ctx = plugin_hf_require_ctx(context);
PluginHfContext* ctx = plugin_hf_get_ctx(context);
if(!ctx) {
return NfcCommandStop;
}
NfcCommand ret = NfcCommandContinue;
MfClassicPollerEvent* mfc_event = event.event_data;
if(event.protocol != NfcProtocolMfClassic || !mfc_event) {
@@ -546,12 +591,11 @@ static NfcCommand plugin_hf_poller_callback_mfc(NfcGenericEvent event, void* con
if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
HF_DIAG_D("MFC success stage=%d", stage);
if(stage == PluginHfStageCardDetect) {
ctx->api->notify_card_detected(ctx->host_ctx);
if(!ctx->api->sam_can_accept_card(ctx->host_ctx)) {
return NfcCommandContinue;
if(!ctx->poller) {
FURI_LOG_E(TAG, "MFC detect without poller");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
furi_check(ctx->poller);
const MfClassicData* mfc_data = nfc_poller_get_data(ctx->poller);
if(!mfc_data || !mfc_data->iso14443_3a_data) {
FURI_LOG_E(TAG, "MFC data unavailable");
@@ -565,25 +609,20 @@ static NfcCommand plugin_hf_poller_callback_mfc(NfcGenericEvent event, void* con
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
ctx->api->send_card_detected(
ctx->host_ctx,
iso14443_3a_get_sak(mfc_data->iso14443_3a_data),
uid,
uid_len,
NULL,
0);
FURI_LOG_D(TAG, "MFC cardDetected delivered uid_len=%u", uid_len);
if(!ctx->api->begin_card_session(
ctx->host_ctx,
iso14443_3a_get_sak(mfc_data->iso14443_3a_data),
uid,
uid_len,
NULL,
0)) {
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation);
} else if(stage == PluginHfStageConversation) {
FURI_LOG_D(TAG, "MFC enter conversation");
ctx->api->run_conversation(ctx->host_ctx);
stage = ctx->api->get_stage(ctx->host_ctx);
if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
ret = plugin_hf_run_conversation(ctx);
} else if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
@@ -599,7 +638,10 @@ static NfcCommand plugin_hf_poller_callback_mfc(NfcGenericEvent event, void* con
}
static NfcCommand plugin_hf_poller_callback_picopass(PicopassPollerEvent event, void* context) {
PluginHfContext* ctx = plugin_hf_require_ctx(context);
PluginHfContext* ctx = plugin_hf_get_ctx(context);
if(!ctx) {
return NfcCommandStop;
}
NfcCommand ret = NfcCommandContinue;
PluginHfStage stage = ctx->api->get_stage(ctx->host_ctx);
@@ -609,26 +651,21 @@ static NfcCommand plugin_hf_poller_callback_picopass(PicopassPollerEvent event,
} else if(event.type == PicopassPollerEventTypeSuccess) {
HF_DIAG_D("Picopass success stage=%d", stage);
if(stage == PluginHfStageCardDetect) {
ctx->api->notify_card_detected(ctx->host_ctx);
if(!ctx->api->sam_can_accept_card(ctx->host_ctx)) {
return NfcCommandContinue;
}
uint8_t* csn = ctx->api->picopass_get_csn(ctx->host_ctx);
furi_check(csn);
ctx->api->send_card_detected(
ctx->host_ctx, 0, csn, sizeof(PicopassSerialNum), NULL, 0);
FURI_LOG_D(TAG, "Picopass cardDetected delivered");
if(!csn) {
FURI_LOG_E(TAG, "Picopass CSN unavailable");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
if(!ctx->api->begin_card_session(
ctx->host_ctx, 0, csn, sizeof(PicopassSerialNum), NULL, 0)) {
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation);
} else if(stage == PluginHfStageConversation) {
FURI_LOG_D(TAG, "Picopass enter conversation");
ctx->api->run_conversation(ctx->host_ctx);
stage = ctx->api->get_stage(ctx->host_ctx);
if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
ret = plugin_hf_run_conversation(ctx);
} else if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
@@ -674,7 +711,11 @@ static void* plugin_hf_alloc(const PluginHfHostApi* api, void* host_ctx) {
}
static void plugin_hf_free(void* plugin_ctx) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
PluginHfContext* ctx = plugin_hf_get_ctx(plugin_ctx);
if(!ctx) {
free(plugin_ctx);
return;
}
plugin_hf_cleanup_pollers(ctx);
free(ctx);
}
@@ -683,9 +724,11 @@ static size_t plugin_hf_detect_supported_types(
void* plugin_ctx,
SeaderCredentialType* detected_types,
size_t detected_capacity) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
furi_check(detected_types);
furi_check(detected_capacity > 0U);
PluginHfContext* ctx = plugin_hf_get_ctx(plugin_ctx);
if(!ctx || !detected_types || detected_capacity == 0U) {
FURI_LOG_W(TAG, "HF detect called with invalid state");
return 0U;
}
size_t detected_type_count = 0;
HF_DIAG_D("Detect supported HF types");
NfcPoller* poller_detect = nfc_poller_alloc(ctx->nfc, NfcProtocolIso14443_4a);
@@ -718,7 +761,10 @@ static size_t plugin_hf_detect_supported_types(
}
static bool plugin_hf_start_read_for_type(void* plugin_ctx, SeaderCredentialType type) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
PluginHfContext* ctx = plugin_hf_get_ctx(plugin_ctx);
if(!ctx) {
return false;
}
NfcPoller* poller_detect = NULL;
plugin_hf_cleanup_pollers(ctx);
@@ -779,14 +825,20 @@ static bool plugin_hf_start_read_for_type(void* plugin_ctx, SeaderCredentialType
}
static void plugin_hf_stop(void* plugin_ctx) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
PluginHfContext* ctx = plugin_hf_get_ctx(plugin_ctx);
if(!ctx) {
return;
}
plugin_hf_cleanup_pollers(ctx);
ctx->active_type = SeaderCredentialTypeNone;
}
static bool plugin_hf_handle_action(void* plugin_ctx, const PluginHfAction* action) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
furi_check(action);
PluginHfContext* ctx = plugin_hf_get_ctx(plugin_ctx);
if(!ctx || !action) {
FURI_LOG_W(TAG, "HF action called with invalid state");
return false;
}
HF_DIAG_D("Handle action type=%d len=%u", action->type, action->len);
if(action->type == PluginHfActionTypePicopassTx) {

50
hf_read_lifecycle.c Normal file
View File

@@ -0,0 +1,50 @@
#include "hf_read_lifecycle.h"
SeaderHfCardSessionDecision
seader_hf_read_on_card_detect(SeaderHfReadState state, bool sam_can_accept_card) {
if(state != SeaderHfReadStateDetecting) {
return SeaderHfCardSessionDecisionAbort;
}
if(!sam_can_accept_card) {
return SeaderHfCardSessionDecisionAbort;
}
return SeaderHfCardSessionDecisionStartConversation;
}
bool seader_hf_read_is_waiting_for_progress(SeaderHfReadState state) {
return state == SeaderHfReadStateConversationStarting ||
state == SeaderHfReadStateConversationActive || state == SeaderHfReadStateFinishing;
}
bool seader_hf_read_should_timeout(
SeaderHfReadState state,
uint32_t elapsed_ms,
uint32_t timeout_ms) {
if(!seader_hf_read_is_waiting_for_progress(state)) {
return false;
}
return elapsed_ms >= timeout_ms;
}
const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason) {
switch(reason) {
case SeaderHfReadFailureReasonUnavailable:
return "HF unavailable";
case SeaderHfReadFailureReasonSamBusy:
return "SAM not idle";
case SeaderHfReadFailureReasonSamTimeout:
return "SAM timeout";
case SeaderHfReadFailureReasonBoardMissing:
return "Reader lost";
case SeaderHfReadFailureReasonProtocolError:
return "Protocol error";
case SeaderHfReadFailureReasonInternalState:
return "Read state error";
case SeaderHfReadFailureReasonNone:
default:
return "Read failed";
}
}

39
hf_read_lifecycle.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef enum {
SeaderHfReadStateIdle = 0,
SeaderHfReadStateDetecting,
SeaderHfReadStateConversationStarting,
SeaderHfReadStateConversationActive,
SeaderHfReadStateFinishing,
SeaderHfReadStateTerminalSuccess,
SeaderHfReadStateTerminalFail,
} SeaderHfReadState;
typedef enum {
SeaderHfReadFailureReasonNone = 0,
SeaderHfReadFailureReasonUnavailable,
SeaderHfReadFailureReasonSamBusy,
SeaderHfReadFailureReasonSamTimeout,
SeaderHfReadFailureReasonBoardMissing,
SeaderHfReadFailureReasonProtocolError,
SeaderHfReadFailureReasonInternalState,
} SeaderHfReadFailureReason;
typedef enum {
SeaderHfCardSessionDecisionIgnore = 0,
SeaderHfCardSessionDecisionStartConversation,
SeaderHfCardSessionDecisionAbort,
} SeaderHfCardSessionDecision;
SeaderHfCardSessionDecision
seader_hf_read_on_card_detect(SeaderHfReadState state, bool sam_can_accept_card);
bool seader_hf_read_is_waiting_for_progress(SeaderHfReadState state);
bool seader_hf_read_should_timeout(
SeaderHfReadState state,
uint32_t elapsed_ms,
uint32_t timeout_ms);
const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason);

View File

@@ -0,0 +1,97 @@
#include <string.h>
#include "munit.h"
#include "hf_read_lifecycle.h"
static MunitResult test_card_detect_starts_only_from_detecting_when_sam_idle(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
munit_assert_int(
seader_hf_read_on_card_detect(SeaderHfReadStateDetecting, true),
==,
SeaderHfCardSessionDecisionStartConversation);
munit_assert_int(
seader_hf_read_on_card_detect(SeaderHfReadStateIdle, true),
==,
SeaderHfCardSessionDecisionAbort);
munit_assert_int(
seader_hf_read_on_card_detect(SeaderHfReadStateDetecting, false),
==,
SeaderHfCardSessionDecisionAbort);
return MUNIT_OK;
}
static MunitResult test_waiting_states_and_timeout_policy(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
munit_assert_true(seader_hf_read_is_waiting_for_progress(SeaderHfReadStateConversationStarting));
munit_assert_true(seader_hf_read_is_waiting_for_progress(SeaderHfReadStateConversationActive));
munit_assert_true(seader_hf_read_is_waiting_for_progress(SeaderHfReadStateFinishing));
munit_assert_false(seader_hf_read_is_waiting_for_progress(SeaderHfReadStateDetecting));
munit_assert_false(
seader_hf_read_should_timeout(SeaderHfReadStateConversationActive, 2999U, 3000U));
munit_assert_true(
seader_hf_read_should_timeout(SeaderHfReadStateConversationActive, 3000U, 3000U));
munit_assert_false(seader_hf_read_should_timeout(SeaderHfReadStateIdle, 99999U, 3000U));
return MUNIT_OK;
}
static MunitResult test_failure_reason_texts_are_stable(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonUnavailable),
"HF unavailable");
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonSamBusy), "SAM not idle");
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonInternalState),
"Read state error");
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonSamTimeout), "SAM timeout");
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonBoardMissing), "Reader lost");
return MUNIT_OK;
}
static MunitResult test_error_texts_fit_read_error_storage(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
static const char* protected_read_timeout =
"Protected read timed out.\nNo supported data\nor wrong key.";
munit_assert_size(strlen(protected_read_timeout), <, 96U);
munit_assert_size(
strlen(seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonInternalState)), <, 96U);
return MUNIT_OK;
}
static MunitTest test_hf_read_lifecycle_cases[] = {
{(char*)"/card-detect-gating", test_card_detect_starts_only_from_detecting_when_sam_idle, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/timeout-policy", test_waiting_states_and_timeout_policy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/failure-text", test_failure_reason_texts_are_stable, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/failure-text-fits", test_error_texts_fit_read_error_storage, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_hf_read_lifecycle_suite = {
"",
test_hf_read_lifecycle_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};

View File

@@ -120,13 +120,102 @@ static MunitResult test_begin_hf_teardown_sets_state(
return MUNIT_OK;
}
static MunitResult test_fail_hf_startup_clears_runtime_and_sets_failure(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderHfReadState read_state = SeaderHfReadStateDetecting;
SeaderHfReadFailureReason failure_reason = SeaderHfReadFailureReasonNone;
uint32_t last_progress_tick = 1234U;
SeaderHfSessionState hf_state = SeaderHfSessionStateLoaded;
SeaderModeRuntime mode_runtime = SeaderModeRuntimeHF;
seader_runtime_fail_hf_startup(
&read_state, &failure_reason, &last_progress_tick, &hf_state, &mode_runtime);
munit_assert_int(read_state, ==, SeaderHfReadStateTerminalFail);
munit_assert_int(failure_reason, ==, SeaderHfReadFailureReasonUnavailable);
munit_assert_uint32(last_progress_tick, ==, 0U);
munit_assert_int(hf_state, ==, SeaderHfSessionStateUnloaded);
munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone);
return MUNIT_OK;
}
static MunitResult test_begin_board_auto_recover_sets_pending_and_target(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
bool pending = false;
bool resume_read = false;
SeaderCredentialType preserved_read_type = SeaderCredentialTypeNone;
munit_assert_true(seader_runtime_begin_board_auto_recover(
true,
true,
SeaderCredentialTypeMifareClassic,
&pending,
&resume_read,
&preserved_read_type));
munit_assert_true(pending);
munit_assert_true(resume_read);
munit_assert_int(preserved_read_type, ==, SeaderCredentialTypeMifareClassic);
seader_runtime_finish_board_auto_recover(&pending, &resume_read, &preserved_read_type);
munit_assert_false(pending);
munit_assert_false(resume_read);
munit_assert_int(preserved_read_type, ==, SeaderCredentialTypeNone);
return MUNIT_OK;
}
static MunitResult test_begin_board_auto_recover_rejects_invalid_or_duplicate_state(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
bool pending = false;
bool resume_read = false;
SeaderCredentialType preserved_read_type = SeaderCredentialTypeNone;
munit_assert_false(
seader_runtime_begin_board_auto_recover(
false,
true,
SeaderCredentialType14A,
&pending,
&resume_read,
&preserved_read_type));
munit_assert_false(pending);
munit_assert_false(resume_read);
munit_assert_int(preserved_read_type, ==, SeaderCredentialTypeNone);
pending = true;
munit_assert_false(
seader_runtime_begin_board_auto_recover(
true,
false,
SeaderCredentialType14A,
&pending,
&resume_read,
&preserved_read_type));
munit_assert_true(pending);
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*)"/fail-hf-startup", test_fail_hf_startup_clears_runtime_and_sets_failure, 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},
{(char*)"/begin-board-auto-recover", test_begin_board_auto_recover_sets_pending_and_target, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/begin-board-auto-recover-invalid", test_begin_board_auto_recover_rejects_invalid_or_duplicate_state, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};

View File

@@ -76,3 +76,64 @@ void seader_runtime_finalize_hf_release(
*mode_runtime = SeaderModeRuntimeNone;
}
}
void seader_runtime_fail_hf_startup(
SeaderHfReadState* hf_read_state,
SeaderHfReadFailureReason* failure_reason,
uint32_t* last_progress_tick,
SeaderHfSessionState* hf_session_state,
SeaderModeRuntime* mode_runtime) {
if(hf_read_state) {
*hf_read_state = SeaderHfReadStateTerminalFail;
}
if(failure_reason) {
*failure_reason = SeaderHfReadFailureReasonUnavailable;
}
if(last_progress_tick) {
*last_progress_tick = 0U;
}
if(hf_session_state) {
*hf_session_state = SeaderHfSessionStateUnloaded;
}
if(mode_runtime && *mode_runtime == SeaderModeRuntimeHF) {
*mode_runtime = SeaderModeRuntimeNone;
}
}
bool seader_runtime_begin_board_auto_recover(
bool sam_present,
bool hf_runtime_active,
SeaderCredentialType selected_read_type,
bool* pending,
bool* resume_read,
SeaderCredentialType* preserved_read_type) {
if(!sam_present || !pending || !resume_read || !preserved_read_type || *pending) {
return false;
}
*pending = true;
*resume_read = hf_runtime_active;
*preserved_read_type = hf_runtime_active ? selected_read_type : SeaderCredentialTypeNone;
return true;
}
void seader_runtime_finish_board_auto_recover(
bool* pending,
bool* resume_read,
SeaderCredentialType* preserved_read_type) {
if(pending) {
*pending = false;
}
if(resume_read) {
*resume_read = false;
}
if(preserved_read_type) {
*preserved_read_type = SeaderCredentialTypeNone;
}
}

View File

@@ -5,6 +5,7 @@
#include <stdbool.h>
#include "seader.h"
#include "seader_credential_type.h"
#include "uhf_snmp_probe.h"
void seader_runtime_reset_cached_sam_metadata(
@@ -26,3 +27,20 @@ void seader_runtime_begin_hf_teardown(SeaderHfSessionState* hf_session_state);
void seader_runtime_finalize_hf_release(
SeaderHfSessionState* hf_session_state,
SeaderModeRuntime* mode_runtime);
void seader_runtime_fail_hf_startup(
SeaderHfReadState* hf_read_state,
SeaderHfReadFailureReason* failure_reason,
uint32_t* last_progress_tick,
SeaderHfSessionState* hf_session_state,
SeaderModeRuntime* mode_runtime);
bool seader_runtime_begin_board_auto_recover(
bool sam_present,
bool hf_runtime_active,
SeaderCredentialType selected_read_type,
bool* pending,
bool* resume_read,
SeaderCredentialType* preserved_read_type);
void seader_runtime_finish_board_auto_recover(
bool* pending,
bool* resume_read,
SeaderCredentialType* preserved_read_type);

View File

@@ -1,5 +1,7 @@
#include "seader_worker_i.h"
#include "seader_hf_read_plan.h"
#include "hf_read_lifecycle.h"
#include "runtime_policy.h"
#include "trace_log.h"
#include <flipper_format/flipper_format.h>
@@ -7,9 +9,11 @@
#define TAG "SeaderWorker"
#define APDU_HEADER_LEN 5
#define ASN1_PREFIX 6
#define SEADER_HEX_LOG_MAX_BYTES 32U
#define APDU_HEADER_LEN 5
#define ASN1_PREFIX 6
#define SEADER_HEX_LOG_MAX_BYTES 32U
#define SEADER_HF_CONVERSATION_TIMEOUT_MS 3000U
#define SEADER_WORKER_STACK_SIZE 4096U
// #define ASN1_DEBUG true
#define RFAL_PICOPASS_TXRX_FLAGS \
@@ -28,6 +32,30 @@ static void seader_worker_release_hf_session(Seader* seader) {
seader_hf_plugin_release(seader);
}
static void seader_worker_fail_hf_startup(Seader* seader, const char* detail) {
if(!seader || !seader->worker) {
return;
}
SeaderWorker* seader_worker = seader->worker;
seader_hf_plugin_release(seader);
seader_runtime_fail_hf_startup(
&seader->hf_read_state,
&seader->hf_read_failure_reason,
&seader->hf_read_last_progress_tick,
&seader->hf_session_state,
&seader->mode_runtime);
strlcpy(
seader->read_error,
detail ? detail : seader_hf_read_failure_reason_text(seader->hf_read_failure_reason),
sizeof(seader->read_error));
seader_sam_force_idle_for_recovery(seader);
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker->callback) {
seader_worker->callback(SeaderWorkerEventFail, seader_worker->context);
}
}
typedef struct {
volatile bool done;
volatile bool detected;
@@ -192,52 +220,6 @@ static size_t __attribute__((unused)) seader_worker_detect_supported_types(
return detected_type_count;
}
static bool __attribute__((unused))
seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) {
NfcPoller* poller_detect = NULL;
if(type == SeaderCredentialType14A) {
poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
if(!nfc_poller_detect(poller_detect)) {
nfc_poller_free(poller_detect);
return false;
}
FURI_LOG_I(TAG, "Detected ISO14443-4A card");
nfc_poller_free(poller_detect);
seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
seader->worker->stage = SeaderPollerEventTypeCardDetect;
seader->credential->type = SeaderCredentialType14A;
nfc_poller_start(seader->poller, seader_worker_poller_callback_iso14443_4a, seader);
return true;
} else if(type == SeaderCredentialTypeMifareClassic) {
poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
if(!nfc_poller_detect(poller_detect)) {
nfc_poller_free(poller_detect);
return false;
}
FURI_LOG_I(TAG, "Detected Mifare Classic card");
nfc_poller_free(poller_detect);
seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
seader->worker->stage = SeaderPollerEventTypeCardDetect;
seader->credential->type = SeaderCredentialTypeMifareClassic;
nfc_poller_start(seader->poller, seader_worker_poller_callback_mfc, seader);
return true;
} else if(type == SeaderCredentialTypePicopass) {
if(!seader_worker_detect_picopass(seader->nfc)) {
return false;
}
FURI_LOG_I(TAG, "Detected Picopass card");
seader->picopass_poller = picopass_poller_alloc(seader->nfc);
seader->worker->stage = SeaderPollerEventTypeCardDetect;
seader->credential->type = SeaderCredentialTypePicopass;
picopass_poller_start(
seader->picopass_poller, seader_worker_poller_callback_picopass, seader);
return true;
}
return false;
}
/***************************** Seader Worker API *******************************/
SeaderWorker* seader_worker_alloc() {
@@ -247,8 +229,8 @@ SeaderWorker* seader_worker_alloc() {
}
// Worker thread attributes
seader_worker->thread =
furi_thread_alloc_ex("SeaderWorker", 5120, seader_worker_task, seader_worker);
seader_worker->thread = furi_thread_alloc_ex(
"SeaderWorker", SEADER_WORKER_STACK_SIZE, seader_worker_task, seader_worker);
seader_worker->messages = furi_message_queue_alloc(2, sizeof(uint8_t));
seader_worker->apdu_slots = calloc(SEADER_WORKER_APDU_SLOT_COUNT, sizeof(SeaderAPDU));
@@ -560,6 +542,12 @@ int32_t seader_worker_task(void* context) {
FURI_LOG_D(TAG, "Reading mode started");
seader_worker_reading(seader);
}
if(seader && seader->is_debug_enabled) {
FURI_LOG_D(
TAG,
"Worker thread stack watermark free=%lu",
(unsigned long)furi_thread_get_stack_space(furi_thread_get_current_id()));
}
seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
return 0;
@@ -569,15 +557,34 @@ void seader_worker_reading(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
FURI_LOG_I(TAG, "Reading loop started");
furi_check(seader_hf_plugin_acquire(seader));
furi_check(seader->plugin_hf);
furi_check(seader->hf_plugin_ctx);
if(!seader_hf_plugin_acquire(seader) || !seader->plugin_hf || !seader->hf_plugin_ctx) {
FURI_LOG_E(
TAG,
"HF plugin unavailable acquire=%d plugin=%p ctx=%p",
seader->plugin_hf != NULL && seader->hf_plugin_ctx != NULL,
(void*)seader->plugin_hf,
seader->hf_plugin_ctx);
seader_worker_fail_hf_startup(seader, "HF unavailable");
return;
}
while(seader_worker->state == SeaderWorkerStateReading) {
bool detected = false;
SeaderPollerEventType result_stage = SeaderPollerEventTypeFail;
SeaderCredentialType type_to_read = seader_hf_mode_get_selected_read_type(seader);
SeaderHfReadPlan read_plan = {0};
if(!seader_sam_can_accept_card(seader) || seader->hf_read_state != SeaderHfReadStateIdle) {
FURI_LOG_W(
TAG,
"Recover stale HF read state=%d sam=%d intent=%d",
seader->hf_read_state,
seader->sam_state,
seader->sam_intent);
seader_sam_force_idle_for_recovery(seader);
seader->hf_read_state = SeaderHfReadStateIdle;
seader->hf_read_failure_reason = SeaderHfReadFailureReasonNone;
seader->hf_read_last_progress_tick = 0U;
}
FURI_LOG_D(TAG, "HF loop selected type=%d stage=%d", type_to_read, seader_worker->stage);
if(type_to_read == SeaderCredentialTypeNone) {
@@ -600,6 +607,9 @@ void seader_worker_reading(Seader* seader) {
break;
} else if(read_plan.decision == SeaderHfReadDecisionStartRead) {
FURI_LOG_I(TAG, "HF start read for type=%d", read_plan.type_to_read);
seader->hf_read_state = SeaderHfReadStateDetecting;
seader->hf_read_failure_reason = SeaderHfReadFailureReasonNone;
seader->hf_read_last_progress_tick = furi_get_tick();
detected = seader->plugin_hf->start_read_for_type(
seader->hf_plugin_ctx, read_plan.type_to_read);
if(detected) {
@@ -655,6 +665,8 @@ void seader_worker_run_hf_conversation(Seader* seader) {
FuriStatus status = furi_message_queue_get(seader_worker->messages, &slot_index, 100);
if(status == FuriStatusOk) {
seader->hf_read_state = SeaderHfReadStateConversationActive;
seader->hf_read_last_progress_tick = furi_get_tick();
furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT);
SeaderAPDU* seaderApdu = &seader_worker->apdu_slots[slot_index];
FURI_LOG_D(TAG, "Dequeue SAM message [%d bytes]", seaderApdu->len);
@@ -669,250 +681,33 @@ void seader_worker_run_hf_conversation(Seader* seader) {
}
seader_worker_release_apdu_slot(seader_worker, slot_index);
} else if(status == FuriStatusErrorTimeout) {
// No message yet, keep looping to stay in callback
// This is "properly idling" while waiting for SAM
const uint32_t elapsed = furi_get_tick() - seader->hf_read_last_progress_tick;
if(seader_hf_read_should_timeout(
seader->hf_read_state, elapsed, SEADER_HF_CONVERSATION_TIMEOUT_MS)) {
FURI_LOG_W(TAG, "HF conversation timeout after %lu ms", elapsed);
seader->hf_read_state = SeaderHfReadStateTerminalFail;
seader->hf_read_failure_reason = SeaderHfReadFailureReasonSamTimeout;
strlcpy(
seader->read_error,
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonSamTimeout),
sizeof(seader->read_error));
seader_sam_force_idle_for_recovery(seader);
seader_worker->stage = SeaderPollerEventTypeFail;
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
}
} else {
FURI_LOG_W(TAG, "furi_message_queue_get fail %d", status);
seader->hf_read_state = SeaderHfReadStateTerminalFail;
seader->hf_read_failure_reason = SeaderHfReadFailureReasonProtocolError;
strlcpy(
seader->read_error,
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonProtocolError),
sizeof(seader->read_error));
seader_sam_force_idle_for_recovery(seader);
seader_worker->stage = SeaderPollerEventTypeFail;
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
}
}
}
NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
if(event.protocol != NfcProtocolIso14443_4a || !event.event_data) {
FURI_LOG_W(TAG, "Ignore invalid host 14A callback");
return NfcCommandStop;
}
furi_check(context);
NfcCommand ret = NfcCommandContinue;
Seader* seader = context;
SeaderWorker* seader_worker = seader->worker;
const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
FURI_LOG_D(TAG, "14a stage CardDetect -> Conversation");
seader_trace(TAG, "14a CardDetect->Conversation");
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventPollerDetect);
if(!seader_sam_can_accept_card(seader)) {
seader_trace(
TAG,
"14a defer detect sam_state=%d intent=%d",
seader->sam_state,
seader->sam_intent);
return NfcCommandContinue;
}
nfc_device_set_data(
seader->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(seader->poller));
size_t uid_len;
const uint8_t* uid = nfc_device_get_uid(seader->nfc_device, &uid_len);
const Iso14443_4aData* iso14443_4a_data =
nfc_device_get_data(seader->nfc_device, NfcProtocolIso14443_4a);
const Iso14443_3aData* iso14443_3a_data = iso14443_4a_get_base_data(iso14443_4a_data);
uint32_t t1_tk_size = 0;
if(iso14443_4a_data->ats_data.t1_tk != NULL) {
t1_tk_size = simple_array_get_count(iso14443_4a_data->ats_data.t1_tk);
if(t1_tk_size > 0xFF) {
t1_tk_size = 0;
}
}
uint8_t ats_len = 0;
uint8_t ats[SEADER_MAX_ATR_SIZE] = {0};
if(iso14443_4a_data->ats_data.tl > 1) {
if(sizeof(ats) < 4U + t1_tk_size) {
FURI_LOG_E(TAG, "Host ATS buffer too small: %u", (unsigned)(4U + t1_tk_size));
seader_worker->stage = SeaderPollerEventTypeFail;
return NfcCommandStop;
}
ats[ats_len++] = iso14443_4a_data->ats_data.t0;
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1) {
ats[ats_len++] = iso14443_4a_data->ats_data.ta_1;
}
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1) {
ats[ats_len++] = iso14443_4a_data->ats_data.tb_1;
}
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TC1) {
ats[ats_len++] = iso14443_4a_data->ats_data.tc_1;
}
if(t1_tk_size != 0) {
memcpy(
ats + ats_len,
simple_array_cget_data(iso14443_4a_data->ats_data.t1_tk),
t1_tk_size);
ats_len += t1_tk_size;
}
}
uint8_t sak = iso14443_3a_get_sak(iso14443_3a_data);
seader_worker_card_detect(
seader, sak, (uint8_t*)iso14443_3a_data->atqa, uid, uid_len, ats, ats_len);
seader_trace(TAG, "14a card_detect sent uid_len=%d sak=%d", uid_len, sak);
if(seader_worker->state == SeaderWorkerStateReading) {
seader_worker->stage = SeaderPollerEventTypeConversation;
return NfcCommandContinue;
}
// nfc_set_fdt_poll_fc(event.instance, SEADER_POLLER_MAX_FWT);
furi_thread_set_current_priority(FuriThreadPriorityLowest);
seader_worker->stage = SeaderPollerEventTypeConversation;
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
seader_trace(TAG, "14a ready in Conversation");
seader_worker_run_hf_conversation(seader);
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
seader_trace(TAG, "14a ready in Complete");
ret = NfcCommandStop;
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
seader_trace(TAG, "14a ready in Fail");
ret = NfcCommandStop;
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
FURI_LOG_W(TAG, "SeaderPollerEventTypeFail");
}
} else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
Iso14443_4aPollerEventData* data = iso14443_4a_event->data;
Iso14443_4aError error = data->error;
FURI_LOG_W(TAG, "Iso14443_4aError %i", error);
seader_trace(TAG, "14a error=%d stage=%d", error, seader_worker->stage);
// I was hoping to catch MFC here, but it seems to be treated the same (None) as no card being present.
switch(error) {
case Iso14443_4aErrorNone:
break;
case Iso14443_4aErrorNotPresent:
break;
case Iso14443_4aErrorProtocol:
ret = NfcCommandStop;
break;
case Iso14443_4aErrorTimeout:
break;
}
}
return ret;
}
NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* context) {
if(event.protocol != NfcProtocolMfClassic || !event.event_data) {
FURI_LOG_W(TAG, "Ignore invalid host MFC callback");
return NfcCommandStop;
}
furi_check(context);
NfcCommand ret = NfcCommandContinue;
Seader* seader = context;
SeaderWorker* seader_worker = seader->worker;
MfClassicPollerEvent* mfc_event = event.event_data;
if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
FURI_LOG_D(TAG, "MFC stage CardDetect -> Conversation");
seader_trace(TAG, "mfc CardDetect->Conversation");
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventPollerDetect);
if(!seader_sam_can_accept_card(seader)) {
seader_trace(
TAG,
"mfc defer detect sam_state=%d intent=%d",
seader->sam_state,
seader->sam_intent);
return NfcCommandContinue;
}
const MfClassicData* mfc_data = nfc_poller_get_data(seader->poller);
uint8_t sak = iso14443_3a_get_sak(mfc_data->iso14443_3a_data);
size_t uid_len = 0;
const uint8_t* uid = mf_classic_get_uid(mfc_data, &uid_len);
seader_worker_card_detect(seader, sak, NULL, uid, uid_len, NULL, 0);
if(seader_worker->state == SeaderWorkerStateReading) {
seader_worker->stage = SeaderPollerEventTypeConversation;
return NfcCommandContinue;
}
furi_thread_set_current_priority(FuriThreadPriorityLowest);
seader_worker->stage = SeaderPollerEventTypeConversation;
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
seader_worker_run_hf_conversation(seader);
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
ret = NfcCommandStop;
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
seader_trace(TAG, "mfc ready in Fail");
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
ret = NfcCommandStop;
}
} else if(mfc_event->type == MfClassicPollerEventTypeFail) {
seader_trace(TAG, "mfc poller event fail");
view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit);
ret = NfcCommandStop;
}
return ret;
}
NfcCommand seader_worker_poller_callback_picopass(PicopassPollerEvent event, void* context) {
furi_check(context);
NfcCommand ret = NfcCommandContinue;
Seader* seader = context;
SeaderWorker* seader_worker = seader->worker;
// I know this is is passing the same thing that is on seader all the way down, but I prefer the symmetry between the 15a and iso15 stuff
PicopassPoller* instance = seader->picopass_poller;
if(event.type == PicopassPollerEventTypeCardDetected) {
seader_worker->stage = SeaderPollerEventTypeCardDetect;
} else if(event.type == PicopassPollerEventTypeSuccess) {
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
FURI_LOG_D(TAG, "Picopass stage CardDetect -> Conversation");
seader_trace(TAG, "picopass CardDetect->Conversation");
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventPollerDetect);
if(!seader_sam_can_accept_card(seader)) {
seader_trace(
TAG,
"picopass defer detect sam_state=%d intent=%d",
seader->sam_state,
seader->sam_intent);
return NfcCommandContinue;
}
uint8_t* csn = picopass_poller_get_csn(instance);
seader_worker_card_detect(seader, 0, NULL, csn, sizeof(PicopassSerialNum), NULL, 0);
if(seader_worker->state == SeaderWorkerStateReading) {
seader_worker->stage = SeaderPollerEventTypeConversation;
return NfcCommandContinue;
}
furi_thread_set_current_priority(FuriThreadPriorityLowest);
seader_worker->stage = SeaderPollerEventTypeConversation;
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
seader_worker_run_hf_conversation(seader);
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
ret = NfcCommandStop;
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
ret = NfcCommandStop;
}
} else if(event.type == PicopassPollerEventTypeFail) {
ret = NfcCommandStop;
FURI_LOG_W(TAG, "PicopassPollerEventTypeFail");
} else {
FURI_LOG_D(TAG, "picopass event type %x", event.type);
}
return ret;
}