mirror of
https://github.com/bettse/seader.git
synced 2026-03-29 08:00:07 +00:00
Harden HF startup failure handling
This commit is contained in:
@@ -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
50
hf_read_lifecycle.c
Normal 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
39
hf_read_lifecycle.h
Normal 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);
|
||||
97
lib/host_tests/test_hf_read_lifecycle.c
Normal file
97
lib/host_tests/test_hf_read_lifecycle.c
Normal 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,
|
||||
};
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
381
seader_worker.c
381
seader_worker.c
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user