Merge pull request #36 from cindersocket/feat-snmp

HF functionality extraction and Seader hardening
This commit is contained in:
Eric Betts
2026-03-24 19:58:56 -07:00
committed by GitHub
60 changed files with 3631 additions and 375 deletions
-3
View File
@@ -1,6 +1,3 @@
[submodule "plugin"]
path = plugin
url = https://gitlab.com/bettse/flipper-wiegand-plugin.git
[submodule "lib/host_tests/vendor/munit"]
path = lib/host_tests/vendor/munit
url = https://github.com/nemequ/munit.git
+49 -1
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,10 +24,13 @@ 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_credential_sio_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 \
ccid_logic.c \
credential_sio_label.c \
t_1_logic.c \
t_1.c \
sam_key_label.c \
@@ -34,9 +40,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
+18
View File
@@ -1,4 +1,9 @@
#include "apdu_runner.h"
#include "seader_i.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TAG "APDU_Runner"
@@ -6,7 +11,14 @@
#define SEADER_APDU_MAX_LEN 732
void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) {
furi_check(seader);
SeaderWorker* seader_worker = seader->worker;
if(!seader_worker) {
apdu_log_free(seader->apdu_log);
seader->apdu_log = NULL;
return;
}
seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
apdu_log_free(seader->apdu_log);
seader->apdu_log = NULL;
@@ -16,7 +28,10 @@ void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) {
}
bool seader_apdu_runner_send_next_line(Seader* seader) {
furi_check(seader);
SeaderWorker* seader_worker = seader->worker;
furi_check(seader_worker);
furi_check(seader_worker->uart);
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
@@ -86,6 +101,9 @@ void seader_apdu_runner_init(Seader* seader) {
}
bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) {
furi_check(seader);
furi_check(seader->worker);
furi_check(seader->worker->uart);
SeaderUartBridge* seader_uart = seader->worker->uart;
SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
uint8_t GET_RESPONSE[] = {0x00, 0xc0, 0x00, 0x00, 0xff};
+4 -1
View File
@@ -2,7 +2,10 @@
#pragma once
#include <lib/toolbox/hex.h>
#include "seader_i.h"
#include <stddef.h>
#include <stdint.h>
#include "seader.h"
#define SEADER_APDU_RUNNER_FILE_NAME APP_DATA_PATH("script.apdu")
+12 -2
View File
@@ -20,7 +20,8 @@ App(
sources=[
"*.c",
"aeabi_uldivmod.sx",
"!plugin/*.c",
"!hf_interface_fal/*.c",
"!wiegand_interface_fal/*.c",
],
fap_icon="icons/logo.png",
fap_category="NFC",
@@ -60,6 +61,15 @@ App(
apptype=FlipperAppType.PLUGIN,
entry_point="plugin_wiegand_ep",
requires=["seader"],
sources=["plugin/wiegand.c"],
sources=["wiegand_interface_fal/wiegand.c"],
fal_embedded=True,
)
App(
appid="plugin_hf",
apptype=FlipperAppType.PLUGIN,
entry_point="plugin_hf_ep",
requires=["seader"],
sources=["hf_interface_fal/hf.c"],
fal_embedded=True,
)
+82
View File
@@ -0,0 +1,82 @@
#include "card_details_builder.h"
#include <string.h>
#include <FrameProtocol.h>
/* Build the ASN.1-owned CardDetails payload used for cardDetected. Optional members
must be heap/ASN.1-owned because the caller always releases the structure through
ASN_STRUCT_FREE_CONTENTS_ONLY(). */
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) {
/* ISO14443-4A cards report ATS and SAK to the SAM. */
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) {
/* Picopass does not provide ATS/SAK in this path; uid_len==8 is the existing discriminator. */
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 {
/* MIFARE Classic still identifies as NFC here, but carries a one-byte SAK. */
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;
}
/* Release a builder result through the same ASN.1 ownership boundary used by production code. */
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
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);
+9 -3
View File
@@ -30,6 +30,13 @@ static uint8_t seader_ccid_next_sequence(SeaderUartBridge* seader_uart, uint8_t
return seader_ccid_sequence_advance(&slot_state->sequence);
}
static SeaderUartBridge* seader_ccid_active_uart(Seader* seader) {
furi_check(seader);
furi_check(seader->worker);
furi_check(seader->worker->uart);
return seader->worker->uart;
}
void seader_ccid_IccPowerOn(SeaderUartBridge* seader_uart, uint8_t slot) {
SeaderCcidSlotState* slot_state = seader_ccid_slot_state(seader_uart, slot);
if(slot_state->powered) {
@@ -92,8 +99,7 @@ void seader_ccid_GetSlotStatus(SeaderUartBridge* seader_uart, uint8_t slot) {
}
void seader_ccid_SetParameters(Seader* seader, uint8_t slot) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_ccid_active_uart(seader);
FURI_LOG_D(TAG, "seader_ccid_SetParameters(%d)", slot);
uint8_t payloadLen = 0;
@@ -227,8 +233,8 @@ void seader_ccid_XfrBlockToSlot(
}
size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
SeaderUartBridge* seader_uart = seader_ccid_active_uart(seader);
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
CCID_Message message;
message.consumed = 0;
SeaderCcidState* ccid_state = seader_ccid_state(seader_uart);
+36
View File
@@ -0,0 +1,36 @@
#include "credential_sio_label.h"
#include <stdio.h>
bool seader_sio_label_format(
bool has_sio,
bool is_picopass_sio_context,
uint8_t sio_start_block,
char* out,
size_t out_size) {
if(out && out_size > 0U) {
out[0] = '\0';
}
if(!out || out_size == 0U || !has_sio) {
return false;
}
if(!is_picopass_sio_context) {
snprintf(out, out_size, "+SIO");
return true;
}
/* Picopass/iClass-only SIO labeling. DESFire/other media do not use block-derived SR/SE labels. */
switch(sio_start_block) {
case 6:
snprintf(out, out_size, "+SIO(SE)");
return true;
case 10:
snprintf(out, out_size, "+SIO(SR)");
return true;
default:
snprintf(out, out_size, "+SIO(?)");
return true;
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "seader_credential_type.h"
bool seader_sio_label_format(
bool has_sio,
bool is_picopass_sio_context,
uint8_t sio_start_block,
char* out,
size_t out_size);
+9
View File
@@ -0,0 +1,9 @@
# Seader embedded HF plugin sources
This directory is part of the main Seader repository.
It contains the embedded HF `.fal` plugin sources used by Seader:
- `hf.c`
- `hf_interface.h`
The HF plugin source path in `application.fam` must point at `hf_interface_fal/hf.c`.
+830
View File
@@ -0,0 +1,830 @@
#include "hf_interface.h"
#include "../protocol/picopass_poller.h"
#include "../protocol/rfal_picopass.h"
#include <flipper_application/flipper_application.h>
#include <lib/bit_lib/bit_lib.h>
#include <lib/nfc/nfc.h>
#include <nfc/nfc_device.h>
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
#include <lib/nfc/protocols/mf_classic/mf_classic_poller.h>
#include <nfc/helpers/iso13239_crc.h>
#define TAG "PluginHF"
#ifdef HF_HARDEN_DIAG
#define HF_DIAG_D(...) FURI_LOG_D(TAG, __VA_ARGS__)
#define HF_DIAG_I(...) FURI_LOG_I(TAG, __VA_ARGS__)
#else
#define HF_DIAG_D(...) \
do { \
} while(0)
#define HF_DIAG_I(...) \
do { \
} while(0)
#endif
#define HF_PLUGIN_POLLER_MAX_FWT (200000U)
#define HF_PLUGIN_POLLER_MAX_BUFFER_SIZE (258U)
// ATS bit definitions
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
#define ISO14443_4A_ATS_T0_TB1 (1U << 5)
#define ISO14443_4A_ATS_T0_TC1 (1U << 6)
typedef struct {
const PluginHfHostApi* api;
void* host_ctx;
Nfc* nfc;
NfcDevice* nfc_device;
NfcPoller* poller;
Iso14443_4aPoller* iso14443_4a_poller;
MfClassicPoller* mfc_poller;
SeaderCredentialType active_type;
} PluginHfContext;
static const uint8_t plugin_hf_update_block2[] = {RFAL_PICOPASS_CMD_UPDATE, 0x02};
static const uint8_t plugin_hf_select_seos_app[] =
{0x00, 0xa4, 0x04, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x04, 0x40, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00};
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 bool plugin_hf_validate_host_api(const PluginHfHostApi* api) {
if(!api) {
FURI_LOG_E(TAG, "Missing HF host API");
return false;
}
#define HF_REQUIRE_API(field) \
do { \
if(!(api->field)) { \
FURI_LOG_E(TAG, "Missing host API: " #field); \
return false; \
} \
} 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(send_nfc_rx);
HF_REQUIRE_API(run_conversation);
HF_REQUIRE_API(set_stage);
HF_REQUIRE_API(get_stage);
HF_REQUIRE_API(set_credential_type);
HF_REQUIRE_API(get_credential_type);
HF_REQUIRE_API(get_desfire_ev2);
HF_REQUIRE_API(set_desfire_ev2);
HF_REQUIRE_API(append_picopass_sio);
HF_REQUIRE_API(set_14a_sio);
HF_REQUIRE_API(get_nfc);
HF_REQUIRE_API(get_nfc_device);
HF_REQUIRE_API(picopass_detect);
HF_REQUIRE_API(picopass_start);
HF_REQUIRE_API(picopass_stop);
HF_REQUIRE_API(picopass_get_csn);
HF_REQUIRE_API(picopass_transmit);
#undef HF_REQUIRE_API
return true;
}
static PluginHfContext* plugin_hf_require_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);
return ctx;
}
static void plugin_hf_cleanup_pollers(PluginHfContext* ctx) {
ctx = plugin_hf_require_ctx(ctx);
if(ctx->poller) {
nfc_poller_stop(ctx->poller);
nfc_poller_free(ctx->poller);
ctx->poller = NULL;
}
ctx->iso14443_4a_poller = NULL;
ctx->mfc_poller = NULL;
if(ctx->api->picopass_stop) {
ctx->api->picopass_stop(ctx->host_ctx);
}
}
static void plugin_hf_set_read_error(PluginHfContext* ctx, const char* text) {
ctx = plugin_hf_require_ctx(ctx);
if(ctx->api->set_read_error) {
ctx->api->set_read_error(ctx->host_ctx, text);
}
}
static void plugin_hf_add_detected_type(
SeaderCredentialType* detected_types,
size_t* detected_type_count,
size_t detected_capacity,
SeaderCredentialType type) {
for(size_t i = 0; i < *detected_type_count; i++) {
if(detected_types[i] == type) {
return;
}
}
if(*detected_type_count < detected_capacity) {
detected_types[*detected_type_count] = type;
(*detected_type_count)++;
}
}
static PicopassError plugin_hf_fake_epurse_update(BitBuffer* tx_buffer, BitBuffer* rx_buffer) {
const uint8_t* buffer = bit_buffer_get_data(tx_buffer);
uint8_t fake_response[8];
memset(fake_response, 0, sizeof(fake_response));
memcpy(fake_response + 0, buffer + 6, 4);
memcpy(fake_response + 4, buffer + 2, 4);
bit_buffer_append_bytes(rx_buffer, fake_response, sizeof(fake_response));
iso13239_crc_append(Iso13239CrcTypePicopass, rx_buffer);
return PicopassErrorNone;
}
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);
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);
if(!buffer || !rx_buffer_data || len == 0U) return;
if(ctx->api->get_credential_type(ctx->host_ctx) == SeaderCredentialTypePicopass) {
if(buffer[0] == RFAL_PICOPASS_CMD_READ4) {
uint8_t block_num = buffer[1];
ctx->api->append_picopass_sio(
ctx->host_ctx, block_num, rx_buffer_data, PICOPASS_BLOCK_LEN * 4);
}
} else if(ctx->api->get_credential_type(ctx->host_ctx) == SeaderCredentialType14A) {
uint8_t desfire_read[] = {0x90, 0xbd, 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x00};
if(len == 13 && memcmp(buffer, desfire_read, sizeof(desfire_read)) == 0 &&
rx_buffer_data[0] == 0x30) {
size_t sio_len = bit_buffer_get_size_bytes(rx_buffer) - 2;
ctx->api->set_14a_sio(ctx->host_ctx, rx_buffer_data, sio_len);
}
}
}
static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, size_t len) {
ctx = plugin_hf_require_ctx(ctx);
if(!buffer || len == 0U) {
FURI_LOG_W(TAG, "Skip picopass transmit invalid input");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return;
}
BitBuffer* tx_buffer = bit_buffer_alloc(len);
BitBuffer* rx_buffer = bit_buffer_alloc(HF_PLUGIN_POLLER_MAX_BUFFER_SIZE);
uint8_t rx_data[HF_PLUGIN_POLLER_MAX_BUFFER_SIZE];
size_t rx_len = 0U;
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate picopass buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return;
}
do {
bit_buffer_append_bytes(tx_buffer, buffer, len);
if(memcmp(buffer, plugin_hf_update_block2, sizeof(plugin_hf_update_block2)) == 0) {
if(plugin_hf_fake_epurse_update(tx_buffer, rx_buffer) != PicopassErrorNone) {
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
break;
}
} else {
if(!ctx->api->picopass_transmit || !ctx->api->picopass_transmit(
ctx->host_ctx,
buffer,
len,
rx_data,
sizeof(rx_data),
&rx_len,
HF_PLUGIN_POLLER_MAX_FWT)) {
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
break;
}
bit_buffer_append_bytes(rx_buffer, rx_data, rx_len);
}
plugin_hf_capture_sio(ctx, tx_buffer, rx_buffer);
ctx->api->send_nfc_rx(
ctx->host_ctx,
(uint8_t*)bit_buffer_get_data(rx_buffer),
bit_buffer_get_size_bytes(rx_buffer));
} while(false);
bit_buffer_free(tx_buffer);
bit_buffer_free(rx_buffer);
}
static void plugin_hf_iso14443a_transmit(
PluginHfContext* ctx,
uint8_t* buffer,
size_t len,
uint16_t timeout,
uint8_t format[3]) {
UNUSED(timeout);
UNUSED(format);
ctx = plugin_hf_require_ctx(ctx);
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);
return;
}
BitBuffer* tx_buffer = bit_buffer_alloc(len + 1);
BitBuffer* rx_buffer = bit_buffer_alloc(HF_PLUGIN_POLLER_MAX_BUFFER_SIZE);
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate 14A buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return;
}
do {
bit_buffer_append_bytes(tx_buffer, buffer, len);
if(ctx->api->get_desfire_ev2(ctx->host_ctx) &&
sizeof(plugin_hf_select_desfire_app_no_le) == len &&
memcmp(buffer, plugin_hf_select_desfire_app_no_le, len) == 0) {
bit_buffer_append_byte(tx_buffer, 0x00);
}
Iso14443_4aError error =
iso14443_4a_poller_send_block(ctx->iso14443_4a_poller, tx_buffer, rx_buffer);
if(error != Iso14443_4aErrorNone) {
FURI_LOG_W(TAG, "iso14443_4a_poller_send_block error %d", error);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
break;
}
if(sizeof(plugin_hf_select_seos_app) == len &&
memcmp(buffer, plugin_hf_select_seos_app, len) == 0 &&
bit_buffer_get_size_bytes(rx_buffer) == 38) {
const uint8_t ev2_select_reply_prefix[] = {0x6F, 0x22, 0x85, 0x20};
const uint8_t* rapdu = bit_buffer_get_data(rx_buffer);
if(memcmp(ev2_select_reply_prefix, rapdu, sizeof(ev2_select_reply_prefix)) == 0) {
ctx->api->set_desfire_ev2(ctx->host_ctx, true);
bit_buffer_reset(rx_buffer);
bit_buffer_append_bytes(
rx_buffer, plugin_hf_file_not_found, sizeof(plugin_hf_file_not_found));
}
}
plugin_hf_capture_sio(ctx, tx_buffer, rx_buffer);
ctx->api->send_nfc_rx(
ctx->host_ctx,
(uint8_t*)bit_buffer_get_data(rx_buffer),
bit_buffer_get_size_bytes(rx_buffer));
} while(false);
bit_buffer_free(tx_buffer);
bit_buffer_free(rx_buffer);
}
static void plugin_hf_mfc_transmit(
PluginHfContext* ctx,
uint8_t* buffer,
size_t len,
uint16_t timeout,
uint8_t format[3]) {
UNUSED(timeout);
ctx = plugin_hf_require_ctx(ctx);
if(!buffer || len == 0U || !ctx->mfc_poller) {
FURI_LOG_W(TAG, "Skip MFC transmit invalid state");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return;
}
BitBuffer* tx_buffer = bit_buffer_alloc(len);
BitBuffer* rx_buffer = bit_buffer_alloc(HF_PLUGIN_POLLER_MAX_BUFFER_SIZE);
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate MFC buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return;
}
do {
if(format[0] == 0x00 && format[1] == 0xC0 && format[2] == 0x00) {
bit_buffer_append_bytes(tx_buffer, buffer, len);
MfClassicError error =
mf_classic_poller_send_frame(ctx->mfc_poller, tx_buffer, rx_buffer, 60000);
if(error != MfClassicErrorNone) {
FURI_LOG_W(TAG, "mf_classic_poller_send_frame error %d", error);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
break;
}
} else if(
(format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x40) ||
(format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x24) ||
(format[0] == 0x00 && format[1] == 0x00 && format[2] == 0x44)) {
uint8_t tx_parity = 0;
uint8_t len_without_parity = len - 1;
for(size_t i = 0; i < len; i++) {
bit_lib_reverse_bits(buffer + i, 0, 8);
}
for(size_t i = 0; i < len_without_parity; i++) {
bool val = bit_lib_get_bit(buffer + i + 1, i);
bit_lib_set_bit(&tx_parity, i, val);
}
for(size_t i = 0; i < len_without_parity; i++) {
buffer[i] = (buffer[i] << i) | (buffer[i + 1] >> (8 - i));
}
bit_buffer_append_bytes(tx_buffer, buffer, len_without_parity);
for(size_t i = 0; i < len_without_parity; i++) {
bit_lib_reverse_bits(buffer + i, 0, 8);
bit_buffer_set_byte_with_parity(
tx_buffer, i, buffer[i], bit_lib_get_bit(&tx_parity, i));
}
MfClassicError error = mf_classic_poller_send_custom_parity_frame(
ctx->mfc_poller, tx_buffer, rx_buffer, 60000);
if(error != MfClassicErrorNone) {
if(error == MfClassicErrorTimeout &&
ctx->api->get_credential_type(ctx->host_ctx) ==
SeaderCredentialTypeMifareClassic) {
plugin_hf_set_read_error(
ctx, "Protected read timed out.\nNo supported data\nor wrong key.");
}
FURI_LOG_W(TAG, "mf_classic_poller_send_custom_parity_frame error %d", error);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
break;
}
size_t length = bit_buffer_get_size_bytes(rx_buffer);
const uint8_t* rx_parity = bit_buffer_get_parity(rx_buffer);
uint8_t with_parity[HF_PLUGIN_POLLER_MAX_BUFFER_SIZE];
memset(with_parity, 0, sizeof(with_parity));
for(size_t i = 0; i < length; i++) {
uint8_t b = bit_buffer_get_byte(rx_buffer, i);
bit_lib_reverse_bits(&b, 0, 8);
bit_buffer_set_byte(rx_buffer, i, b);
}
length = length + (length / 8) + 1;
uint8_t parts = 1 + length / 9;
for(size_t p = 0; p < parts; p++) {
uint8_t doffset = p * 9;
uint8_t soffset = p * 8;
for(size_t i = 0; i < 9; i++) {
with_parity[i + doffset] = bit_buffer_get_byte(rx_buffer, i + soffset) >> i;
if(i > 0) {
with_parity[i + doffset] |= bit_buffer_get_byte(rx_buffer, i + soffset - 1)
<< (9 - i);
}
if(i > 0) {
bool val = bit_lib_get_bit(rx_parity, i - 1);
bit_lib_set_bit(with_parity + i, i - 1, val);
}
}
}
for(size_t i = 0; i < length; i++) {
bit_lib_reverse_bits(with_parity + i, 0, 8);
}
bit_buffer_copy_bytes(rx_buffer, with_parity, length);
} else {
FURI_LOG_W(TAG, "Unhandled MFC format");
}
ctx->api->send_nfc_rx(
ctx->host_ctx,
(uint8_t*)bit_buffer_get_data(rx_buffer),
bit_buffer_get_size_bytes(rx_buffer));
} while(false);
bit_buffer_free(tx_buffer);
bit_buffer_free(rx_buffer);
}
static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
PluginHfContext* ctx = plugin_hf_require_ctx(context);
NfcCommand ret = NfcCommandContinue;
const Iso14443_4aPollerEvent* iso_event = event.event_data;
if(event.protocol != NfcProtocolIso14443_4a || !iso_event) {
FURI_LOG_W(TAG, "14A callback invalid event");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
PluginHfStage stage = ctx->api->get_stage(ctx->host_ctx);
ctx->iso14443_4a_poller = event.instance;
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;
}
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");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
nfc_device_set_data(ctx->nfc_device, NfcProtocolIso14443_4a, poller_data);
size_t uid_len = 0;
const uint8_t* uid = nfc_device_get_uid(ctx->nfc_device, &uid_len);
const Iso14443_4aData* iso_data =
nfc_device_get_data(ctx->nfc_device, NfcProtocolIso14443_4a);
if(!uid || !iso_data) {
FURI_LOG_E(TAG, "14A data unavailable uid=%p iso=%p", (void*)uid, (void*)iso_data);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
const Iso14443_3aData* iso3a = iso14443_4a_get_base_data(iso_data);
if(!iso3a) {
FURI_LOG_E(TAG, "14A base data unavailable");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
uint32_t t1_tk_size = 0;
if(iso_data->ats_data.t1_tk != NULL) {
t1_tk_size = simple_array_get_count(iso_data->ats_data.t1_tk);
if(t1_tk_size > 0xFF) {
t1_tk_size = 0;
}
}
uint8_t ats_len = 0;
uint8_t* ats = malloc(4 + t1_tk_size);
if(!ats) {
FURI_LOG_E(TAG, "Failed to allocate ATS buffer");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
if(iso_data->ats_data.tl > 1) {
ats[ats_len++] = iso_data->ats_data.t0;
if(iso_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1)
ats[ats_len++] = iso_data->ats_data.ta_1;
if(iso_data->ats_data.t0 & ISO14443_4A_ATS_T0_TB1)
ats[ats_len++] = iso_data->ats_data.tb_1;
if(iso_data->ats_data.t0 & ISO14443_4A_ATS_T0_TC1)
ats[ats_len++] = iso_data->ats_data.tc_1;
if(t1_tk_size != 0) {
memcpy(
ats + ats_len,
simple_array_cget_data(iso_data->ats_data.t1_tk),
t1_tk_size);
ats_len += t1_tk_size;
}
}
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);
free(ats);
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;
}
} else if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
} else if(iso_event->type == Iso14443_4aPollerEventTypeError) {
Iso14443_4aPollerEventData* data = iso_event->data;
if(data->error == Iso14443_4aErrorProtocol) {
ret = NfcCommandStop;
}
}
return ret;
}
static NfcCommand plugin_hf_poller_callback_mfc(NfcGenericEvent event, void* context) {
PluginHfContext* ctx = plugin_hf_require_ctx(context);
NfcCommand ret = NfcCommandContinue;
MfClassicPollerEvent* mfc_event = event.event_data;
if(event.protocol != NfcProtocolMfClassic || !mfc_event) {
FURI_LOG_W(TAG, "MFC callback invalid event");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
PluginHfStage stage = ctx->api->get_stage(ctx->host_ctx);
ctx->mfc_poller = event.instance;
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;
}
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");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
size_t uid_len = 0;
const uint8_t* uid = mf_classic_get_uid(mfc_data, &uid_len);
if(!uid) {
FURI_LOG_E(TAG, "MFC uid unavailable");
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);
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;
}
} else if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
} else if(mfc_event->type == MfClassicPollerEventTypeFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
return ret;
}
static NfcCommand plugin_hf_poller_callback_picopass(PicopassPollerEvent event, void* context) {
PluginHfContext* ctx = plugin_hf_require_ctx(context);
NfcCommand ret = NfcCommandContinue;
PluginHfStage stage = ctx->api->get_stage(ctx->host_ctx);
if(event.type == PicopassPollerEventTypeCardDetected) {
HF_DIAG_D("Picopass card detected");
ctx->api->set_stage(ctx->host_ctx, PluginHfStageCardDetect);
} 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");
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;
}
} else if(stage == PluginHfStageComplete) {
ret = NfcCommandStop;
} else if(stage == PluginHfStageFail) {
ctx->api->notify_worker_exit(ctx->host_ctx);
ret = NfcCommandStop;
}
} else if(event.type == PicopassPollerEventTypeFail) {
ret = NfcCommandStop;
}
return ret;
}
static void* plugin_hf_alloc(const PluginHfHostApi* api, void* host_ctx) {
PluginHfContext* ctx = calloc(1, sizeof(PluginHfContext));
if(!ctx) {
FURI_LOG_E(TAG, "Failed to allocate plugin context");
return NULL;
}
if(!host_ctx) {
FURI_LOG_E(TAG, "Missing HF host context");
free(ctx);
return NULL;
}
if(!plugin_hf_validate_host_api(api)) {
free(ctx);
return NULL;
}
ctx->api = api;
ctx->host_ctx = host_ctx;
ctx->nfc = api->get_nfc ? api->get_nfc(host_ctx) : NULL;
ctx->nfc_device = api->get_nfc_device ? api->get_nfc_device(host_ctx) : NULL;
if(!ctx->nfc || !ctx->nfc_device) {
FURI_LOG_E(
TAG,
"Host NFC objects unavailable nfc=%p device=%p",
(void*)ctx->nfc,
(void*)ctx->nfc_device);
free(ctx);
return NULL;
}
return ctx;
}
static void plugin_hf_free(void* plugin_ctx) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
plugin_hf_cleanup_pollers(ctx);
free(ctx);
}
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);
size_t detected_type_count = 0;
HF_DIAG_D("Detect supported HF types");
NfcPoller* poller_detect = nfc_poller_alloc(ctx->nfc, NfcProtocolIso14443_4a);
if(!poller_detect) {
FURI_LOG_W(TAG, "Failed to allocate 14A detect poller");
} else if(nfc_poller_detect(poller_detect)) {
plugin_hf_add_detected_type(
detected_types, &detected_type_count, detected_capacity, SeaderCredentialType14A);
}
if(poller_detect) nfc_poller_free(poller_detect);
poller_detect = nfc_poller_alloc(ctx->nfc, NfcProtocolMfClassic);
if(!poller_detect) {
FURI_LOG_W(TAG, "Failed to allocate MFC detect poller");
} else if(nfc_poller_detect(poller_detect)) {
plugin_hf_add_detected_type(
detected_types,
&detected_type_count,
detected_capacity,
SeaderCredentialTypeMifareClassic);
}
if(poller_detect) nfc_poller_free(poller_detect);
if(ctx->api->picopass_detect && ctx->api->picopass_detect(ctx->host_ctx)) {
plugin_hf_add_detected_type(
detected_types, &detected_type_count, detected_capacity, SeaderCredentialTypePicopass);
}
return detected_type_count;
}
static bool plugin_hf_start_read_for_type(void* plugin_ctx, SeaderCredentialType type) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
NfcPoller* poller_detect = NULL;
plugin_hf_cleanup_pollers(ctx);
ctx->active_type = type;
HF_DIAG_I("Start read type=%d", type);
if(type == SeaderCredentialType14A) {
poller_detect = nfc_poller_alloc(ctx->nfc, NfcProtocolIso14443_4a);
if(!poller_detect) {
FURI_LOG_E(TAG, "Failed to allocate 14A detect poller");
return false;
}
if(!nfc_poller_detect(poller_detect)) {
nfc_poller_free(poller_detect);
return false;
}
nfc_poller_free(poller_detect);
ctx->poller = nfc_poller_alloc(ctx->nfc, NfcProtocolIso14443_4a);
if(!ctx->poller) {
FURI_LOG_E(TAG, "Failed to allocate 14A poller");
return false;
}
ctx->api->set_credential_type(ctx->host_ctx, SeaderCredentialType14A);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageCardDetect);
nfc_poller_start(ctx->poller, plugin_hf_poller_callback_iso14443_4a, ctx);
return true;
} else if(type == SeaderCredentialTypeMifareClassic) {
poller_detect = nfc_poller_alloc(ctx->nfc, NfcProtocolMfClassic);
if(!poller_detect) {
FURI_LOG_E(TAG, "Failed to allocate MFC detect poller");
return false;
}
if(!nfc_poller_detect(poller_detect)) {
nfc_poller_free(poller_detect);
return false;
}
nfc_poller_free(poller_detect);
ctx->poller = nfc_poller_alloc(ctx->nfc, NfcProtocolMfClassic);
if(!ctx->poller) {
FURI_LOG_E(TAG, "Failed to allocate MFC poller");
return false;
}
ctx->api->set_credential_type(ctx->host_ctx, SeaderCredentialTypeMifareClassic);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageCardDetect);
nfc_poller_start(ctx->poller, plugin_hf_poller_callback_mfc, ctx);
return true;
} else if(type == SeaderCredentialTypePicopass) {
if(!ctx->api->picopass_detect || !ctx->api->picopass_detect(ctx->host_ctx)) {
return false;
}
ctx->api->set_credential_type(ctx->host_ctx, SeaderCredentialTypePicopass);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageCardDetect);
return ctx->api->picopass_start &&
ctx->api->picopass_start(ctx->host_ctx, plugin_hf_poller_callback_picopass, ctx);
}
return false;
}
static void plugin_hf_stop(void* plugin_ctx) {
PluginHfContext* ctx = plugin_hf_require_ctx(plugin_ctx);
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);
HF_DIAG_D("Handle action type=%d len=%u", action->type, action->len);
if(action->type == PluginHfActionTypePicopassTx) {
if(ctx->active_type != SeaderCredentialTypePicopass) return false;
plugin_hf_iso15693_transmit(ctx, action->data, action->len);
return true;
} else if(action->type == PluginHfActionTypeMfClassicTx) {
if(!ctx->poller) return false;
plugin_hf_mfc_transmit(
ctx, action->data, action->len, action->timeout, (uint8_t*)action->format);
return true;
} else if(action->type == PluginHfActionTypeIso14443Tx) {
if(!ctx->poller) return false;
plugin_hf_iso14443a_transmit(
ctx, action->data, action->len, action->timeout, (uint8_t*)action->format);
return true;
}
FURI_LOG_W(TAG, "Unhandled HF action %d", action->type);
return false;
}
static const PluginHf plugin_hf = {
.name = "Plugin HF",
.alloc = plugin_hf_alloc,
.free = plugin_hf_free,
.detect_supported_types = plugin_hf_detect_supported_types,
.start_read_for_type = plugin_hf_start_read_for_type,
.stop = plugin_hf_stop,
.handle_action = plugin_hf_handle_action,
};
static const FlipperAppPluginDescriptor plugin_hf_descriptor = {
.appid = HF_PLUGIN_APP_ID,
.ep_api_version = HF_PLUGIN_API_VERSION,
.entry_point = &plugin_hf,
};
const FlipperAppPluginDescriptor* plugin_hf_ep(void) {
return &plugin_hf_descriptor;
}
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "../protocol/picopass_poller.h"
#include "../seader_credential_type.h"
#include <lib/nfc/nfc.h>
#include <nfc/nfc_device.h>
#define HF_PLUGIN_APP_ID "plugin_hf"
#define HF_PLUGIN_API_VERSION 1
typedef enum {
PluginHfStageCardDetect = 0,
PluginHfStageConversation,
PluginHfStageComplete,
PluginHfStageSuccess,
PluginHfStageFail,
} PluginHfStage;
typedef enum {
PluginHfActionTypeIso14443Tx,
PluginHfActionTypeMfClassicTx,
PluginHfActionTypePicopassTx,
} PluginHfActionType;
typedef struct {
PluginHfActionType type;
uint8_t* data;
size_t len;
uint16_t timeout;
uint8_t format[3];
} PluginHfAction;
typedef struct {
/* Required runtime callbacks. A successful plugin alloc assumes these remain valid until free. */
void (*notify_card_detected)(void* host_ctx);
void (*notify_worker_exit)(void* host_ctx);
bool (*sam_can_accept_card)(void* host_ctx);
void (*send_card_detected)(
void* host_ctx,
uint8_t sak,
const uint8_t* uid,
uint8_t uid_len,
const uint8_t* ats,
uint8_t ats_len);
void (*send_nfc_rx)(void* host_ctx, uint8_t* buffer, size_t len);
void (*run_conversation)(void* host_ctx);
void (*set_stage)(void* host_ctx, PluginHfStage stage);
PluginHfStage (*get_stage)(void* host_ctx);
void (*set_credential_type)(void* host_ctx, SeaderCredentialType type);
SeaderCredentialType (*get_credential_type)(void* host_ctx);
bool (*get_desfire_ev2)(void* host_ctx);
void (*set_desfire_ev2)(void* host_ctx, bool is_desfire_ev2);
void (*append_picopass_sio)(void* host_ctx, uint8_t block_num, const uint8_t* data, size_t len);
void (*set_14a_sio)(void* host_ctx, const uint8_t* data, size_t len);
Nfc* (*get_nfc)(void* host_ctx);
NfcDevice* (*get_nfc_device)(void* host_ctx);
/* Required Picopass hooks. All Flippers expose Picopass through the HF host API. */
bool (*picopass_detect)(void* host_ctx);
bool (*picopass_start)(void* host_ctx, PicopassPollerCallback callback, void* callback_ctx);
void (*picopass_stop)(void* host_ctx);
uint8_t* (*picopass_get_csn)(void* host_ctx);
bool (*picopass_transmit)(
void* host_ctx,
const uint8_t* tx_data,
size_t tx_len,
uint8_t* rx_data,
size_t rx_capacity,
size_t* rx_len,
uint32_t fwt_fc);
/* Optional UX hook for richer read failure text. */
void (*set_read_error)(void* host_ctx, const char* text);
} PluginHfHostApi;
typedef struct {
const char* name;
void* (*alloc)(const PluginHfHostApi* api, void* host_ctx);
void (*free)(void* plugin_ctx);
size_t (*detect_supported_types)(
void* plugin_ctx,
SeaderCredentialType* detected_types,
size_t detected_capacity);
bool (*start_read_for_type)(void* plugin_ctx, SeaderCredentialType type);
void (*stop)(void* plugin_ctx);
bool (*handle_action)(void* plugin_ctx, const PluginHfAction* action);
} PluginHf;
+34
View File
@@ -0,0 +1,34 @@
#include "hf_release_sequence.h"
static void seader_hf_release_callback_invoke(SeaderHfReleaseCallback callback, void* context) {
if(callback) {
callback(context);
}
}
/* This is the one canonical HF release order used by production teardown paths and by
the runtime-integration tests. It mirrors the ownership documentation so teardown ordering can
be reviewed and exercised without duplicating the sequence in multiple call sites. */
void seader_hf_release_sequence_run(SeaderHfReleaseSequence* sequence) {
if(!sequence) {
return;
}
if(sequence->hf_session_state) {
*sequence->hf_session_state = SeaderHfSessionStateTearingDown;
}
/* Stop live I/O before freeing any HF-owned or host-owned runtime objects. */
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);
/* Reset worker-visible session state before publishing Unloaded/None. */
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
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
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.
+7
View File
@@ -14,6 +14,13 @@
/* Keep the host harness aligned with the production UART scratchpad size. */
#define SEADER_UART_RX_BUF_SIZE (300)
#define FURI_LOG_W(tag, fmt, ...) ((void)0)
#define furi_check(expr) \
do { \
if(!(expr)) { \
fprintf(stderr, "furi_check failed: %s (%s:%d)\n", #expr, __FILE__, __LINE__); \
abort(); \
} \
} while(0)
typedef struct BitBuffer BitBuffer;
typedef struct Seader Seader;
@@ -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,
};
+15
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);
}
@@ -0,0 +1,76 @@
#include "munit.h"
#include "credential_sio_label.h"
static MunitResult test_returns_false_without_sio(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
char label[16] = "unchanged";
munit_assert_false(
seader_sio_label_format(false, false, 0U, label, sizeof(label)));
munit_assert_string_equal(label, "");
return MUNIT_OK;
}
static MunitResult test_formats_generic_sio_for_desfire(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
char label[16] = {0};
munit_assert_true(
seader_sio_label_format(true, false, 0U, label, sizeof(label)));
munit_assert_string_equal(label, "+SIO");
return MUNIT_OK;
}
static MunitResult test_formats_sr_and_se_for_picopass(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
char label[16] = {0};
munit_assert_true(
seader_sio_label_format(true, true, 6U, label, sizeof(label)));
munit_assert_string_equal(label, "+SIO(SE)");
munit_assert_true(
seader_sio_label_format(true, true, 10U, label, sizeof(label)));
munit_assert_string_equal(label, "+SIO(SR)");
return MUNIT_OK;
}
static MunitResult test_formats_unknown_picopass_layout(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
char label[16] = {0};
munit_assert_true(
seader_sio_label_format(true, true, 0U, label, sizeof(label)));
munit_assert_string_equal(label, "+SIO(?)");
return MUNIT_OK;
}
static MunitTest test_credential_sio_label_cases[] = {
{(char*)"/no-sio", test_returns_false_without_sio, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/desfire-generic", test_formats_generic_sio_for_desfire, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/picopass-sr-se", test_formats_sr_and_se_for_picopass, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/picopass-unknown", test_formats_unknown_picopass_layout, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_credential_sio_label_suite = {
"",
test_credential_sio_label_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};
+108
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,
};
+4
View File
@@ -7,6 +7,8 @@ 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_credential_sio_label_suite;
extern MunitSuite test_runtime_policy_suite;
int main(int argc, char* argv[]) {
MunitSuite child_suites[] = {
@@ -17,6 +19,8 @@ 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},
{"/credential-sio-label", test_credential_sio_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 = {
@@ -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);
}
+139
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,
};
+61
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},
};
Submodule plugin deleted from dde6192486
+78
View File
@@ -0,0 +1,78 @@
#include "runtime_policy.h"
/* A newly accepted SAM must not inherit visible metadata from the previous card while
asynchronous version/serial/UHF maintenance responses are still in flight. */
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);
}
}
/* UHF maintenance is a mutually exclusive runtime mode. The probe may only start when
the SAM is present, HF is fully unloaded, and no other mode currently owns runtime. */
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;
}
/* Clear the narrow UHF probe runtime only when it currently owns mode_runtime. */
void seader_runtime_finish_uhf_probe(SeaderModeRuntime* mode_runtime) {
if(!mode_runtime) {
return;
}
if(*mode_runtime == SeaderModeRuntimeUHF) {
*mode_runtime = SeaderModeRuntimeNone;
}
}
/* Teardown publishes TearingDown before any runtime release so acquire paths and teardown
request paths can see that HF work is already shutting down. */
void seader_runtime_begin_hf_teardown(SeaderHfSessionState* hf_session_state) {
if(hf_session_state) {
*hf_session_state = SeaderHfSessionStateTearingDown;
}
}
/* Final state publication happens only after the caller has completed the release sequence. */
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
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);
+193 -106
View File
@@ -1,7 +1,11 @@
#include "sam_api.h"
#include "seader_i.h"
#include "protocol/rfal_picopass.h"
#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>
@@ -61,17 +65,42 @@ static void seader_update_uhf_status_label(Seader* seader) {
seader_publish_sam_status(seader);
}
static bool seader_snmp_probe_send_next_request(Seader* seader) {
SeaderWorker* seader_worker = seader ? seader->worker : NULL;
SeaderUartBridge* seader_uart = seader_worker ? seader_worker->uart : NULL;
uint8_t* scratch = seader_uart ? (seader_uart->tx_buf + MAX_FRAME_HEADERS) : NULL;
uint8_t* message = seader_uart ? seader_uart->rx_buf : NULL;
size_t message_len = 0U;
static SeaderWorker* seader_get_active_worker(Seader* seader) {
return seader ? seader->worker : NULL;
}
if(!seader || !scratch || !message) {
return false;
static SeaderUartBridge* seader_require_uart(Seader* seader) {
furi_check(seader);
furi_check(seader->uart);
return seader->uart;
}
static SeaderWorker* seader_require_worker(Seader* seader) {
furi_check(seader);
furi_check(seader->worker);
return seader->worker;
}
/* A newly inserted SAM should never inherit the previous card's cached firmware/UHF status
while maintenance probes for the new card are still pending. */
static void seader_reset_cached_sam_metadata(Seader* seader) {
if(!seader) {
return;
}
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) {
SeaderUartBridge* seader_uart = seader_require_uart(seader);
uint8_t* scratch = seader_uart->tx_buf + MAX_FRAME_HEADERS;
uint8_t* message = seader_uart->rx_buf;
size_t message_len = 0U;
if(!seader_uhf_snmp_probe_build_next_request(
&seader->snmp_probe,
scratch,
@@ -85,20 +114,32 @@ static bool seader_snmp_probe_send_next_request(Seader* seader) {
return seader_worker_send_process_snmp_message(seader, message, message_len);
}
/* Finishing the maintenance probe returns mode ownership to the normal app flow and leaves
the SAM state machine idle for the next command. */
static void seader_snmp_probe_finish(Seader* seader) {
if(!seader) {
return;
}
seader_runtime_finish_uhf_probe(&seader->mode_runtime);
seader_sam_set_state(seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
}
/* UHF maintenance is only legal when the SAM is present and HF runtime is fully unloaded.
The helper enforces that ownership boundary before any SNMP request is sent. */
static void seader_start_snmp_probe(Seader* seader) {
if(!seader || !seader->sam_present) {
return;
}
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,
@@ -258,7 +299,6 @@ void* calloc(size_t count, size_t size) {
}
// Forward declarations
void seader_send_nfc_rx(Seader* seader, uint8_t* buffer, size_t len);
static void seader_abort_active_read(Seader* seader);
static void seader_sam_set_state(
@@ -400,8 +440,7 @@ bool seader_send_apdu(
uint8_t* payload,
uint8_t payloadLen,
bool in_scratchpad) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_require_uart(seader);
bool extended = seader_uart->T == 1;
uint8_t header_len = extended ? 7 : 5;
@@ -486,8 +525,7 @@ void seader_send_payload(
uint8_t from,
uint8_t to,
uint8_t replyTo) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_require_uart(seader);
uint8_t* scratchpad = seader_uart->tx_buf + MAX_FRAME_HEADERS;
size_t scratchpad_size = SEADER_UART_RX_BUF_SIZE - MAX_FRAME_HEADERS;
@@ -634,6 +672,7 @@ void seader_worker_send_serial_number(Seader* seader) {
void seader_worker_send_version(Seader* seader) {
SamCommand_t samCommand = {0};
samCommand.present = SamCommand_PR_version;
seader_reset_cached_sam_metadata(seader);
seader->sam_present = true;
seader_update_sam_key_label(seader, NULL, 0U);
seader_sam_set_state(
@@ -651,7 +690,9 @@ bool seader_worker_send_process_snmp_message(
Seader* seader,
const uint8_t* message,
size_t message_len) {
if(!seader || !message || message_len == 0U || message_len > UINT16_MAX) return false;
furi_check(seader);
furi_check(message);
if(message_len == 0U || message_len > UINT16_MAX) return false;
SamCommand_t samCommand = {0};
samCommand.present = SamCommand_PR_processSNMPMessage;
@@ -668,6 +709,9 @@ bool seader_worker_send_process_snmp_message(
}
void seader_send_card_detected(Seader* seader, CardDetails_t* cardDetails) {
furi_check(seader);
furi_check(cardDetails);
furi_check(cardDetails->csn.buf);
CardDetected_t cardDetected = {
.detectedCardDetails = *cardDetails,
};
@@ -681,6 +725,13 @@ void seader_send_card_detected(Seader* seader, CardDetails_t* cardDetails) {
payload.choice.samCommand = samCommand;
seader_trace(
TAG, "send cardDetected state=%d intent=%d", seader->sam_state, seader->sam_intent);
FURI_LOG_D(
TAG,
"Send cardDetected csn_len=%zu has_sak=%d has_ats=%d protocol_len=%zu",
cardDetails->csn.size,
cardDetails->sak != NULL,
cardDetails->atsOrAtqbOrAtr != NULL,
cardDetails->protocol.size);
seader_send_payload(
seader, &payload, ExternalApplicationA, SAMInterface, ExternalApplicationA);
@@ -791,7 +842,7 @@ static bool seader_unpack_pacs2_bits(Seader* seader, const OCTET_STRING_t* pacs_
// ATR3:
// 800207358106793D81F9F385820104A51E8004000000018106053000000000820B323330353139313232395A830152
#define MAX_VERSION_SIZE 60
bool seader_parse_version(SeaderWorker* seader_worker, uint8_t* buf, size_t size) {
bool seader_parse_version(Seader* seader, uint8_t* buf, size_t size) {
bool rtn = false;
if(size > MAX_VERSION_SIZE) {
// Too large to handle now
@@ -820,12 +871,8 @@ bool seader_parse_version(SeaderWorker* seader_worker, uint8_t* buf, size_t size
}
#endif
if(version.version.size == 2) {
memcpy(seader_worker->sam_version, version.version.buf, version.version.size);
FURI_LOG_I(
TAG,
"SAM Version: %d.%d",
seader_worker->sam_version[0],
seader_worker->sam_version[1]);
memcpy(seader->sam_version, version.version.buf, version.version.size);
FURI_LOG_I(TAG, "SAM Version: %d.%d", seader->sam_version[0], seader->sam_version[1]);
}
rtn = true;
@@ -921,16 +968,19 @@ bool seader_parse_serial_number(Seader* seader, uint8_t* buf, size_t size) {
}
static void seader_abort_active_read(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
FURI_LOG_W(TAG, "Abort active read stage=%d sam=%d", seader_worker->stage, seader->samCommand);
SeaderWorker* seader_worker = seader_get_active_worker(seader);
const int stage = seader_worker ? (int)seader_worker->stage : -1;
FURI_LOG_W(TAG, "Abort active read stage=%d sam=%d", stage, seader->samCommand);
seader_trace(
TAG,
"abort stage=%d sam=%d state=%d intent=%d",
seader_worker->stage,
stage,
seader->samCommand,
seader->sam_state,
seader->sam_intent);
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
if(!seader_sam_has_active_card(seader) && seader->sam_state != SeaderSamStateClearPending) {
seader_sam_set_state(
seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
@@ -961,7 +1011,10 @@ bool seader_parse_sam_response2(Seader* seader, SamResponse2_t* samResponse) {
SeaderPacsMediaTypeUnknown;
if(seader_unpack_pacs2_bits(seader, pacs)) {
seader->worker->stage = SeaderPollerEventTypeComplete;
SeaderWorker* seader_worker = seader_get_active_worker(seader);
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeComplete;
}
seader_sam_set_state(
seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
} else {
@@ -982,14 +1035,16 @@ bool seader_parse_sam_response2(Seader* seader, SamResponse2_t* samResponse) {
}
bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) {
SeaderWorker* seader_worker = seader->worker;
SeaderWorker* seader_worker = seader_get_active_worker(seader);
switch(seader->sam_state) {
case SeaderSamStateConversation:
case SeaderSamStateFinishing:
if(seader->sam_intent == SeaderSamIntentConfig) {
FURI_LOG_I(TAG, "samResponse config");
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
seader_sam_set_state(
seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
} else {
@@ -999,7 +1054,7 @@ bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) {
break;
case SeaderSamStateVersionPending:
FURI_LOG_I(TAG, "samResponse version");
seader_parse_version(seader_worker, samResponse->buf, samResponse->size);
seader_parse_version(seader, samResponse->buf, samResponse->size);
seader_worker_send_serial_number(seader);
break;
case SeaderSamStateSerialPending:
@@ -1043,7 +1098,10 @@ bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) {
break;
case SeaderSamStateClearPending:
FURI_LOG_I(TAG, "samResponse clear-detected-card ack");
seader_trace(TAG, "cardDetected ack clear stage=%d", seader_worker->stage);
seader_trace(
TAG,
"cardDetected ack clear stage=%d",
seader_worker ? (int)seader_worker->stage : -1);
seader_sam_set_state(
seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING);
break;
@@ -1111,6 +1169,8 @@ void seader_capture_sio(BitBuffer* tx_buffer, BitBuffer* rx_buffer, SeaderCreden
if(buffer[0] == RFAL_PICOPASS_CMD_READ4) {
uint8_t block_num = buffer[1];
if(credential->sio_len == 0 && rxBuffer[0] == 0x30) {
/* Only Picopass uses block-derived SR/SE labeling, so remember where the
first ASN.1 SIO fragment was observed. */
credential->sio_start_block = block_num;
}
uint8_t offset = (block_num - credential->sio_start_block) * PICOPASS_BLOCK_LEN;
@@ -1118,10 +1178,9 @@ void seader_capture_sio(BitBuffer* tx_buffer, BitBuffer* rx_buffer, SeaderCreden
credential->sio_len += PICOPASS_BLOCK_LEN * 4;
}
} else if(credential->type == SeaderCredentialType14A) {
// Desfire EV1 passes SIO in the clear
// The desfire_read command is 13 bytes in total, but we deliberately don't check the read length as newer SAM
// firmware versions read 5 bytes first to determine the length of the SIO from the ASN.1 tag length then do a
// second read with just the required length to skip reading any additional bytes at the end of the file
/* DESFire exposes SIO as raw file data rather than as block-addressed Picopass reads.
Match the fixed read command body, but accept any response length that starts with
ASN.1 SEQUENCE data instead of expecting one exact returned payload size. */
uint8_t desfire_read[] = {0x90, 0xbd, 0x00, 0x00, 0x07, 0x0f, 0x00, 0x00, 0x00};
if(len == 13 && memcmp(buffer, desfire_read, sizeof(desfire_read)) == 0 &&
rxBuffer[0] == 0x30) {
@@ -1141,7 +1200,7 @@ void seader_iso15693_transmit(
PicopassPoller* picopass_poller,
uint8_t* buffer,
size_t len) {
SeaderWorker* seader_worker = seader->worker;
SeaderWorker* seader_worker = seader_get_active_worker(seader);
BitBuffer* tx_buffer = bit_buffer_alloc(len);
BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
@@ -1162,7 +1221,9 @@ void seader_iso15693_transmit(
}
if(error != PicopassErrorNone) {
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
break;
}
@@ -1189,15 +1250,22 @@ void seader_iso14443a_transmit(
UNUSED(timeout);
UNUSED(format);
furi_assert(seader);
furi_assert(buffer);
furi_assert(iso14443_4a_poller);
SeaderWorker* seader_worker = seader->worker;
furi_check(seader);
furi_check(buffer);
furi_check(iso14443_4a_poller);
SeaderWorker* seader_worker = seader_require_worker(seader);
SeaderCredential* credential = seader->credential;
BitBuffer* tx_buffer =
bit_buffer_alloc(len + 1); // extra byte to allow for appending a Le byte sometimes
BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate 14A tx/rx buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
if(seader_worker) seader_worker->stage = SeaderPollerEventTypeFail;
return;
}
do {
bit_buffer_append_bytes(tx_buffer, buffer, len);
@@ -1217,7 +1285,9 @@ void seader_iso14443a_transmit(
iso14443_4a_poller_send_block(iso14443_4a_poller, tx_buffer, rx_buffer);
if(error != Iso14443_4aErrorNone) {
FURI_LOG_W(TAG, "iso14443_4a_poller_send_block error %d", error);
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
break;
}
@@ -1261,13 +1331,20 @@ void seader_mfc_transmit(
uint8_t format[3]) {
UNUSED(timeout);
furi_assert(seader);
furi_assert(buffer);
furi_assert(mfc_poller);
SeaderWorker* seader_worker = seader->worker;
furi_check(seader);
furi_check(buffer);
furi_check(mfc_poller);
SeaderWorker* seader_worker = seader_require_worker(seader);
BitBuffer* tx_buffer = bit_buffer_alloc(len);
BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate MFC tx/rx buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
if(seader_worker) seader_worker->stage = SeaderPollerEventTypeFail;
return;
}
do {
seader_trace(
@@ -1292,7 +1369,9 @@ void seader_mfc_transmit(
if(error != MfClassicErrorNone) {
FURI_LOG_W(TAG, "mf_classic_poller_send_frame error %d", error);
seader_trace(TAG, "mfc send_frame error=%d", error);
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
break;
}
@@ -1358,7 +1437,9 @@ void seader_mfc_transmit(
sizeof(seader->read_error),
"Protected read timed out.\nNo supported data\nor wrong key.");
}
seader_worker->stage = SeaderPollerEventTypeFail;
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
break;
}
@@ -1440,43 +1521,48 @@ void seader_mfc_transmit(
bit_buffer_free(rx_buffer);
}
void seader_parse_nfc_command_transmit(
Seader* seader,
NFCSend_t* nfcSend,
SeaderPollerContainer* spc) {
long timeOut = nfcSend->timeOut;
Protocol_t protocol = nfcSend->protocol;
FrameProtocol_t frameProtocol = protocol.buf[1];
void seader_parse_nfc_command_transmit(Seader* seader, NFCSend_t* nfcSend) {
#ifdef ASN1_DEBUG
seader_log_hex_data(TAG, "Transmit data", nfcSend->data.buf, nfcSend->data.size);
#endif
PluginHfAction action = {
.data = nfcSend->data.buf,
.len = nfcSend->data.size,
.timeout = nfcSend->timeOut,
};
if(nfcSend->format) {
const size_t raw_format_len = (size_t)nfcSend->format->size;
const size_t format_len = raw_format_len < sizeof(action.format) ? raw_format_len :
sizeof(action.format);
memcpy(action.format, nfcSend->format->buf, format_len);
}
if(seader->credential->type == SeaderCredentialTypeVirtual) {
seader_virtual_picopass_state_machine(seader, nfcSend->data.buf, nfcSend->data.size);
} else if(frameProtocol == FrameProtocol_iclass) {
seader_iso15693_transmit(
seader, spc->picopass_poller, nfcSend->data.buf, nfcSend->data.size);
} else if(frameProtocol == FrameProtocol_nfc) {
if(spc->iso14443_4a_poller) {
seader_iso14443a_transmit(
seader,
spc->iso14443_4a_poller,
nfcSend->data.buf,
nfcSend->data.size,
(uint16_t)timeOut,
nfcSend->format->buf);
} else if(spc->mfc_poller) {
seader_mfc_transmit(
seader,
spc->mfc_poller,
nfcSend->data.buf,
nfcSend->data.size,
(uint16_t)timeOut,
nfcSend->format->buf);
} else if(seader->plugin_hf && seader->hf_plugin_ctx) {
if(seader->credential->type == SeaderCredentialTypePicopass) {
action.type = PluginHfActionTypePicopassTx;
} else if(seader->credential->type == SeaderCredentialTypeMifareClassic) {
action.type = PluginHfActionTypeMfClassicTx;
} else {
action.type = PluginHfActionTypeIso14443Tx;
}
FURI_LOG_D(
TAG,
"Dispatch HF action type=%d len=%u timeout=%lu",
action.type,
action.len,
(unsigned long)action.timeout);
if(!seader->plugin_hf->handle_action(seader->hf_plugin_ctx, &action)) {
FURI_LOG_W(TAG, "HF plugin failed to handle action");
SeaderWorker* seader_worker = seader_get_active_worker(seader);
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeFail;
}
}
} else {
FURI_LOG_W(TAG, "unknown frame protocol %lx", frameProtocol);
FURI_LOG_W(TAG, "No HF plugin available for nfcSend");
}
}
@@ -1502,13 +1588,15 @@ void seader_parse_nfc_off(Seader* seader) {
void seader_parse_nfc_command(Seader* seader, NFCCommand_t* nfcCommand, SeaderPollerContainer* spc) {
switch(nfcCommand->present) {
case NFCCommand_PR_nfcSend:
furi_assert(spc);
seader_parse_nfc_command_transmit(seader, &nfcCommand->choice.nfcSend, spc);
seader_parse_nfc_command_transmit(seader, &nfcCommand->choice.nfcSend);
break;
case NFCCommand_PR_nfcOff:
seader_parse_nfc_off(seader);
if(spc != NULL) {
seader->worker->stage = SeaderPollerEventTypeComplete;
SeaderWorker* seader_worker = seader_get_active_worker(seader);
if(seader_worker) {
seader_worker->stage = SeaderPollerEventTypeComplete;
}
}
break;
default:
@@ -1585,6 +1673,8 @@ bool seader_process_success_response_i(
Payload_t* payload_p = &payload;
bool processed = false;
/* Seader wraps each ASN.1 payload with a 6-byte application header
{from, to, replyTo, 0x00, 0x00, 0x00}. Skip that prefix before decoding. */
asn_dec_rval_t rval =
asn_decode(0, ATS_DER, &asn_DEF_Payload, (void**)&payload_p, apdu + 6, len - 6);
if(rval.code == RC_OK) {
@@ -1626,36 +1716,30 @@ NfcCommand seader_worker_card_detect(
uint8_t* ats,
uint8_t ats_len) {
UNUSED(atqa);
furi_check(seader);
furi_check(seader->credential);
furi_check(uid);
furi_check(uid_len > 0U);
SeaderCredential* credential = seader->credential;
CardDetails_t cardDetails = {0};
FURI_LOG_D(TAG, "Build card_detect sak=%02x uid_len=%u ats_len=%u", sak, uid_len, ats_len);
OCTET_STRING_fromBuf(&cardDetails.csn, (const char*)uid, uid_len);
OCTET_STRING_t sak_string = {.buf = &sak, .size = 1};
OCTET_STRING_t ats_string = {.buf = ats, .size = ats_len};
uint8_t protocol_bytes[] = {0x00, 0x00};
/* The UID is reused as the current diversifier seed for formats that need one. This is
not universal across all media, but it is the intentional behavior for the cards Seader
currently supports on this read path. */
size_t diversifier_len = uid_len;
if(diversifier_len > sizeof(credential->diversifier)) {
FURI_LOG_W(
TAG, "Clamp diversifier uid_len=%u to %zu", uid_len, sizeof(credential->diversifier));
diversifier_len = sizeof(credential->diversifier);
}
memcpy(credential->diversifier, uid, diversifier_len);
credential->diversifier_len = diversifier_len;
// 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
memcpy(credential->diversifier, uid, uid_len);
credential->diversifier_len = uid_len;
if(ats != NULL) { // type 4
protocol_bytes[1] = FrameProtocol_nfc;
OCTET_STRING_fromBuf(
&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
cardDetails.sak = &sak_string;
// TODO: Update asn1 to change atqa to ats
cardDetails.atsOrAtqbOrAtr = &ats_string;
} else if(uid_len == 8) { // picopass
protocol_bytes[1] = FrameProtocol_iclass;
OCTET_STRING_fromBuf(
&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
} else { // MFC
protocol_bytes[1] = FrameProtocol_nfc;
OCTET_STRING_fromBuf(
&cardDetails.protocol, (const char*)protocol_bytes, sizeof(protocol_bytes));
cardDetails.sak = &sak_string;
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(
@@ -1663,7 +1747,10 @@ NfcCommand seader_worker_card_detect(
SeaderSamStateDetectPending,
seader_sam_card_intent(seader),
SamCommand_PR_cardDetected);
/* cardDetails must remain valid until the SAM payload is encoded, then it can be released
through the ASN.1-owned reset helper. */
seader_send_card_detected(seader, &cardDetails);
FURI_LOG_D(TAG, "cardDetected sent");
// Print version information for app and firmware for later review in log
const Version* version = version_get();
FURI_LOG_I(
@@ -1673,6 +1760,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;
}
+10 -6
View File
@@ -1,17 +1,20 @@
#pragma once
#ifndef ASN_EMIT_DEBUG
#define ASN_EMIT_DEBUG 0
#endif
#include <nfc/helpers/iso13239_crc.h>
#include <optimized_ikeys.h>
#include <optimized_cipher.h>
#include <lib/nfc/nfc.h>
#include <stddef.h>
#include <stdint.h>
#include "seader_i.h"
#include "seader_credential.h"
#include "seader_bridge.h"
#include "seader_worker.h"
#include "protocol/rfal_picopass.h"
typedef struct Seader Seader;
typedef struct SeaderPollerContainer SeaderPollerContainer;
#include <Payload.h>
#include <SIO.h>
#define ExternalApplicationA 0x44
#define NFCInterface 0x14
@@ -26,6 +29,7 @@ NfcCommand seader_worker_card_detect(
uint8_t* ats,
uint8_t ats_len);
void seader_send_nfc_rx(Seader* seader, uint8_t* buffer, size_t len);
void seader_send_no_card_detected(Seader* seader);
bool seader_sam_can_accept_card(const Seader* seader);
bool seader_sam_has_active_card(const Seader* seader);
+2
View File
@@ -12,6 +12,7 @@ void seader_apdu_runner_worker_callback(uint32_t event, void* context) {
void seader_scene_apdu_runner_on_enter(void* context) {
Seader* seader = context;
seader_worker_acquire(seader);
// Setup view
Popup* popup = seader->popup;
popup_set_header(popup, "APDU Runner", 68, 30, AlignLeft, AlignTop);
@@ -73,4 +74,5 @@ void seader_scene_apdu_runner_on_exit(void* context) {
// Clear view
popup_reset(seader->popup);
seader_worker_release(seader);
}
+2 -3
View File
@@ -33,7 +33,7 @@ void seader_scene_card_menu_on_enter(void* context) {
SubmenuIndexSaveRFID,
seader_scene_card_menu_submenu_callback,
seader);
if(credential->sio[0] == 0x30 && credential->diversifier_len == RFAL_PICOPASS_UID_LEN) {
if(credential->sio[0] == 0x30 && credential->diversifier_len == PICOPASS_UID_LEN) {
submenu_add_item(
submenu,
"Save SR",
@@ -88,8 +88,7 @@ bool seader_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(
seader->scene_manager, SeaderSceneSamPresent);
consumed = scene_manager_previous_scene(seader->scene_manager);
}
return consumed;
+23 -13
View File
@@ -1,8 +1,15 @@
#include "../seader_i.h"
#include "../credential_sio_label.h"
#include <dolphin/dolphin.h>
#define TAG "SeaderCredentialInfoScene"
static bool seader_credential_is_picopass_sio_context(const SeaderCredential* credential) {
return credential && (credential->type == SeaderCredentialTypePicopass ||
(credential->has_pacs_media_type &&
credential->pacs_media_type == SeaderPacsMediaTypePicopass));
}
void seader_scene_credential_info_widget_callback(
GuiButtonType result,
InputType type,
@@ -16,7 +23,7 @@ void seader_scene_credential_info_widget_callback(
void seader_scene_credential_info_on_enter(void* context) {
Seader* seader = context;
SeaderCredential* credential = seader->credential;
PluginWiegand* plugin = seader->plugin_wiegand;
seader_wiegand_plugin_acquire(seader);
Widget* widget = seader->widget;
// Use reusable strings instead of allocating new ones
@@ -41,16 +48,13 @@ void seader_scene_credential_info_on_enter(void* context) {
seader_scene_credential_info_widget_callback,
seader);
if(plugin) {
size_t format_count = plugin->count(credential->bit_length, credential->credential);
if(format_count > 0) {
widget_add_button_element(
seader->widget,
GuiButtonTypeCenter,
"Parse",
seader_scene_credential_info_widget_callback,
seader);
}
if(credential->bit_length > 0) {
widget_add_button_element(
seader->widget,
GuiButtonTypeCenter,
"Parse",
seader_scene_credential_info_widget_callback,
seader);
}
widget_add_string_element(
@@ -72,8 +76,13 @@ void seader_scene_credential_info_on_enter(void* context) {
FontSecondary,
furi_string_get_cstr(credential_str));
if(credential->sio[0] == 0x30) {
furi_string_set(sio_str, "+SIO");
if(seader_sio_label_format(
credential->sio[0] == 0x30,
seader_credential_is_picopass_sio_context(credential),
credential->sio_start_block,
seader->text_store,
sizeof(seader->text_store))) {
furi_string_set(sio_str, seader->text_store);
widget_add_string_element(
widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
}
@@ -109,4 +118,5 @@ void seader_scene_credential_info_on_exit(void* context) {
// Clear views
widget_reset(seader->widget);
seader_wiegand_plugin_release(seader);
}
+7 -1
View File
@@ -3,7 +3,7 @@
void seader_scene_formats_on_enter(void* context) {
Seader* seader = context;
PluginWiegand* plugin = seader->plugin_wiegand;
PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL;
SeaderCredential* credential = seader->credential;
FuriString* str = seader->text_box_store;
@@ -19,7 +19,12 @@ void seader_scene_formats_on_enter(void* context) {
furi_string_cat_printf(str, "%s\n", furi_string_get_cstr(description));
}
if(format_count == 0) {
furi_string_set_str(str, "No known Wiegand formats matched.");
}
// No need to free description as it's reused from seader struct
} else {
furi_string_set_str(str, "Wiegand parser unavailable.");
}
text_box_set_font(seader->text_box, TextBoxFontHex);
@@ -46,4 +51,5 @@ void seader_scene_formats_on_exit(void* context) {
// Clear views
text_box_reset(seader->text_box);
seader_wiegand_plugin_release(seader);
}
+13 -11
View File
@@ -4,6 +4,8 @@
void seader_scene_read_on_enter(void* context) {
Seader* seader = context;
seader_hf_mode_activate(seader);
seader_worker_acquire(seader);
dolphin_deed(DolphinDeedNfcRead);
// Setup view
@@ -16,9 +18,8 @@ void seader_scene_read_on_enter(void* context) {
seader_scene_read_prepare(seader);
seader_credential_clear(seader->credential);
if(seader->selected_read_type == SeaderCredentialTypeNone) {
seader->detected_card_type_count = 0;
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
if(seader_hf_mode_get_selected_read_type(seader) == SeaderCredentialTypeNone) {
seader_hf_mode_clear_detected_types(seader);
}
seader_worker_start(
seader->worker,
@@ -38,6 +39,11 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) {
if(event.event == SeaderCustomEventWorkerExit) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
consumed = true;
} else if(event.event == SeaderWorkerEventFail) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
consumed = true;
} else if(event.event == SeaderWorkerEventHfTeardownComplete) {
consumed = seader_hf_finish_teardown_action(seader);
} else if(event.event == SeaderCustomEventPollerDetect) {
Popup* popup = seader->popup;
popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
@@ -50,12 +56,8 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) {
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
seader->selected_read_type = SeaderCredentialTypeNone;
seader->detected_card_type_count = 0;
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
scene_manager_search_and_switch_to_previous_scene(
seader->scene_manager, SeaderSceneSamPresent);
consumed = true;
seader_scene_read_abort_cleanup(seader);
consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent);
}
return consumed;
@@ -63,6 +65,6 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) {
void seader_scene_read_on_exit(void* context) {
Seader* seader = context;
seader_worker_stop(seader->worker);
seader_scene_read_cleanup(seader);
seader_scene_read_finish_cleanup(seader);
seader_worker_release(seader);
}
+44 -40
View File
@@ -1,8 +1,15 @@
#include "../seader_i.h"
#include "../credential_sio_label.h"
#include <dolphin/dolphin.h>
#define TAG "SeaderSceneReadCardSuccess"
static bool seader_credential_is_picopass_sio_context(const SeaderCredential* credential) {
return credential && (credential->type == SeaderCredentialTypePicopass ||
(credential->has_pacs_media_type &&
credential->pacs_media_type == SeaderPacsMediaTypePicopass));
}
void seader_scene_read_card_success_widget_callback(
GuiButtonType result,
InputType type,
@@ -18,11 +25,8 @@ void seader_scene_read_card_success_widget_callback(
void seader_scene_read_card_success_on_enter(void* context) {
Seader* seader = context;
SeaderCredential* credential = seader->credential;
PluginWiegand* plugin = seader->plugin_wiegand;
PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL;
Widget* widget = seader->widget;
seader->selected_read_type = SeaderCredentialTypeNone;
seader->detected_card_type_count = 0;
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
// Use reusable strings instead of allocating new ones
FuriString* type_str = seader->temp_string1;
@@ -46,10 +50,8 @@ void seader_scene_read_card_success_on_enter(void* context) {
furi_string_set(type_str, "Read error");
furi_string_set(bitlength_str, seader->read_error[0] ? seader->read_error : "Read failed");
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
seader_t_1_reset(seader->uart);
seader_ccid_check_for_sam(seader_uart);
seader_ccid_check_for_sam(seader->uart);
}
widget_add_button_element(
@@ -71,22 +73,25 @@ void seader_scene_read_card_success_on_enter(void* context) {
seader);
}
if(plugin && credential->bit_length > 0) {
size_t format_count = plugin->count(credential->bit_length, credential->credential);
FURI_LOG_D(
TAG,
"Plugin present, bit_length=%d, format_count=%zu",
credential->bit_length,
format_count);
if(format_count > 0) {
widget_add_button_element(
seader->widget,
GuiButtonTypeCenter,
"Parse",
seader_scene_read_card_success_widget_callback,
seader);
if(credential->bit_length > 0) {
if(plugin) {
size_t format_count = plugin->count(credential->bit_length, credential->credential);
FURI_LOG_D(
TAG,
"Plugin present, bit_length=%d, format_count=%zu",
credential->bit_length,
format_count);
} else {
FURI_LOG_D(
TAG, "Parse available without plugin bit_length=%d", credential->bit_length);
}
} else {
widget_add_button_element(
seader->widget,
GuiButtonTypeCenter,
"Parse",
seader_scene_read_card_success_widget_callback,
seader);
} else if(!plugin) {
FURI_LOG_D(TAG, "Plugin=%p, bit_length=%d", plugin, credential->bit_length);
}
@@ -108,19 +113,16 @@ void seader_scene_read_card_success_on_enter(void* context) {
AlignCenter,
FontSecondary,
furi_string_get_cstr(credential_str));
if(credential->sio[0] == 0x30) {
switch(credential->sio_start_block) {
case 6:
furi_string_set(sio_str, "+SIO(SE)");
break;
case 10:
furi_string_set(sio_str, "+SIO(SR)");
break;
default:
if(seader_sio_label_format(
credential->sio[0] == 0x30,
seader_credential_is_picopass_sio_context(credential),
credential->sio_start_block,
seader->text_store,
sizeof(seader->text_store))) {
if(strcmp(seader->text_store, "+SIO(?)") == 0) {
FURI_LOG_E(TAG, "Unknown SIO start block: %d", credential->sio_start_block);
furi_string_set(sio_str, "+SIO(?)");
break;
}
furi_string_set(sio_str, seader->text_store);
widget_add_string_element(
widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
}
@@ -136,23 +138,24 @@ bool seader_scene_read_card_success_on_event(void* context, SceneManagerEvent ev
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(seader->scene_manager);
consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionRestartRead);
} else if(event.event == GuiButtonTypeRight) {
if(seader->credential->bit_length > 0) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneCardMenu);
} else {
scene_manager_search_and_switch_to_previous_scene(
seader->scene_manager, SeaderSceneSamPresent);
consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent);
}
if(seader->credential->bit_length > 0) {
consumed = true;
}
consumed = true;
} else if(event.event == GuiButtonTypeCenter) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats);
consumed = true;
} else if(event.event == SeaderWorkerEventHfTeardownComplete) {
consumed = seader_hf_finish_teardown_action(seader);
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_search_and_switch_to_previous_scene(
seader->scene_manager, SeaderSceneSamPresent);
consumed = true;
consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent);
}
return consumed;
}
@@ -162,4 +165,5 @@ void seader_scene_read_card_success_on_exit(void* context) {
// Clear view
widget_reset(seader->widget);
seader_wiegand_plugin_release(seader);
}
+12 -13
View File
@@ -21,10 +21,12 @@ void seader_scene_read_card_type_submenu_callback(void* context, uint32_t index)
void seader_scene_read_card_type_on_enter(void* context) {
Seader* seader = context;
Submenu* submenu = seader->submenu;
const SeaderCredentialType* detected_types = seader_hf_mode_get_detected_types(seader);
const size_t detected_type_count = seader_hf_mode_get_detected_type_count(seader);
submenu_reset(submenu);
for(size_t i = 0; i < seader->detected_card_type_count; i++) {
const SeaderCredentialType type = seader->detected_card_types[i];
for(size_t i = 0; i < detected_type_count; i++) {
const SeaderCredentialType type = detected_types[i];
submenu_add_item(
submenu,
seader_scene_read_card_type_label(type),
@@ -42,21 +44,18 @@ bool seader_scene_read_card_type_on_event(void* context, SceneManagerEvent event
if(event.type == SceneManagerEventTypeCustom) {
const SeaderCredentialType type = event.event;
if(type == SeaderCredentialType14A || type == SeaderCredentialTypeMifareClassic ||
type == SeaderCredentialTypePicopass) {
seader->selected_read_type = type;
seader->detected_card_type_count = 0;
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
if(event.event == SeaderWorkerEventHfTeardownComplete) {
consumed = seader_hf_finish_teardown_action(seader);
} else if(
type == SeaderCredentialType14A || type == SeaderCredentialTypeMifareClassic ||
type == SeaderCredentialTypePicopass) {
seader_hf_mode_set_selected_read_type(seader, type);
seader_hf_mode_clear_detected_types(seader);
scene_manager_next_scene(seader->scene_manager, SeaderSceneRead);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
seader->selected_read_type = SeaderCredentialTypeNone;
seader->detected_card_type_count = 0;
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
scene_manager_search_and_switch_to_previous_scene(
seader->scene_manager, SeaderSceneSamPresent);
consumed = true;
consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionSamPresent);
}
return consumed;
+16 -3
View File
@@ -21,7 +21,6 @@ void seader_scene_read_prepare(Seader* seader) {
seader->samCommand = SamCommand_PR_NOTHING;
}
memset(seader->read_error, 0, sizeof(seader->read_error));
seader_worker_reset_poller_session(seader->worker);
}
void seader_scene_read_cleanup(Seader* seader) {
@@ -33,14 +32,28 @@ void seader_scene_read_cleanup(Seader* seader) {
seader->samCommand,
seader->sam_state,
seader->sam_intent);
seader_worker_cancel_poller_session(seader->worker);
seader_scene_read_abort_cleanup(seader);
seader_scene_read_finish_cleanup(seader);
}
void seader_scene_read_abort_cleanup(Seader* seader) {
furi_assert(seader);
FURI_LOG_D("SceneRead", "Abort cleanup session sam=%d", seader->samCommand);
if(seader_sam_has_active_card(seader)) {
seader_send_no_card_detected(seader);
}
popup_reset(seader->popup);
seader_worker_reset_poller_session(seader->worker);
if(seader->sam_state == SeaderSamStateIdle) {
seader->samCommand = SamCommand_PR_NOTHING;
}
seader_blink_stop(seader);
}
void seader_scene_read_finish_cleanup(Seader* seader) {
furi_assert(seader);
popup_reset(seader->popup);
if(seader->sam_state == SeaderSamStateIdle) {
seader->samCommand = SamCommand_PR_NOTHING;
}
+2
View File
@@ -6,4 +6,6 @@ typedef struct Seader Seader;
void seader_sam_check_worker_callback(uint32_t event, void* context);
void seader_scene_read_prepare(Seader* seader);
void seader_scene_read_abort_cleanup(Seader* seader);
void seader_scene_read_finish_cleanup(Seader* seader);
void seader_scene_read_cleanup(Seader* seader);
+9 -1
View File
@@ -10,6 +10,7 @@ void seader_read_config_card_worker_callback(uint32_t event, void* context) {
void seader_scene_read_config_card_on_enter(void* context) {
Seader* seader = context;
seader_worker_acquire(seader);
// Setup view
Popup* popup = seader->popup;
@@ -41,6 +42,10 @@ bool seader_scene_read_config_card_on_event(void* context, SceneManagerEvent eve
if(event.event == SeaderCustomEventWorkerExit || event.event == SeaderWorkerEventSuccess) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneReadConfigCardSuccess);
consumed = true;
} else if(event.event == SeaderWorkerEventFail) {
scene_manager_search_and_switch_to_previous_scene(
seader->scene_manager, SeaderSceneSamPresent);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_search_and_switch_to_previous_scene(
@@ -53,6 +58,9 @@ bool seader_scene_read_config_card_on_event(void* context, SceneManagerEvent eve
void seader_scene_read_config_card_on_exit(void* context) {
Seader* seader = context;
seader_worker_stop(seader->worker);
if(seader->worker) {
seader_worker_stop(seader->worker);
}
seader_scene_read_cleanup(seader);
seader_worker_release(seader);
}
+1 -3
View File
@@ -12,7 +12,6 @@ void seader_scene_sam_info_widget_callback(GuiButtonType result, InputType type,
void seader_scene_sam_info_on_enter(void* context) {
Seader* seader = context;
SeaderWorker* seader_worker = seader->worker;
Widget* widget = seader->widget;
// Use reusable string instead of allocating new one
@@ -24,8 +23,7 @@ void seader_scene_sam_info_on_enter(void* context) {
furi_string_reset(info_str);
furi_string_reset(uhf_str);
furi_string_cat_printf(
fw_str, "FW %d.%d", seader_worker->sam_version[0], seader_worker->sam_version[1]);
furi_string_cat_printf(fw_str, "FW %d.%d", seader->sam_version[0], seader->sam_version[1]);
furi_string_set_str(info_str, seader->sam_key_label);
furi_string_set_str(uhf_str, seader->uhf_status_label);
+1
View File
@@ -42,6 +42,7 @@ bool seader_scene_sam_missing_on_event(void* context, SceneManagerEvent event) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneFileSelect);
consumed = true;
} else if(event.event == SeaderWorkerEventSamPresent) {
seader->sam_present_menu_guard_active = true;
scene_manager_next_scene(seader->scene_manager, SeaderSceneSamPresent);
consumed = true;
}
+41 -35
View File
@@ -1,23 +1,17 @@
#include "../seader_i.h"
enum SubmenuIndex {
SubmenuIndexSamInfo,
SubmenuIndexRead,
SubmenuIndexSaved,
SubmenuIndexAPDURunner,
SubmenuIndexReadConfigCard,
SubmenuIndexSamInfo,
};
static uint8_t fwChecks = 3;
void seader_scene_sam_present_submenu_callback(void* context, uint32_t index) {
Seader* seader = context;
view_dispatcher_send_custom_event(seader->view_dispatcher, index);
}
void seader_scene_sam_present_on_update(void* context) {
Seader* seader = context;
SeaderWorker* seader_worker = seader->worker;
void seader_scene_sam_present_submenu_callback(void* context, uint32_t index);
static void seader_scene_sam_present_rebuild_menu(Seader* seader, uint32_t selected_item) {
Submenu* submenu = seader->submenu;
submenu_reset(submenu);
@@ -43,29 +37,32 @@ void seader_scene_sam_present_on_update(void* context) {
seader_scene_sam_present_submenu_callback,
seader);
}
if(seader_worker->sam_version[0] != 0 && seader_worker->sam_version[1] != 0) {
submenu_add_item(
submenu,
seader->sam_key_label,
SubmenuIndexSamInfo,
seader_scene_sam_present_submenu_callback,
seader);
submenu_add_item(
submenu,
seader->sam_key_label,
SubmenuIndexSamInfo,
seader_scene_sam_present_submenu_callback,
seader);
if(seader->sam_version[0] != 0 && seader->sam_version[1] != 0) {
fwChecks = 0;
} else {
submenu_add_item(
submenu,
seader->sam_key_label,
SubmenuIndexSamInfo,
seader_scene_sam_present_submenu_callback,
seader);
}
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(seader->scene_manager, SeaderSceneSamPresent));
submenu_set_selected_item(submenu, selected_item);
view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewMenu);
}
void seader_scene_sam_present_submenu_callback(void* context, uint32_t index) {
Seader* seader = context;
view_dispatcher_send_custom_event(seader->view_dispatcher, index);
}
void seader_scene_sam_present_on_update(void* context) {
Seader* seader = context;
seader_scene_sam_present_rebuild_menu(
seader, scene_manager_get_scene_state(seader->scene_manager, SeaderSceneSamPresent));
}
void seader_scene_sam_present_on_enter(void* context) {
seader_scene_sam_present_on_update(context);
}
@@ -75,7 +72,13 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexRead) {
if(seader->sam_present_menu_guard_active &&
(event.event == SubmenuIndexRead || event.event == SubmenuIndexSaved ||
event.event == SubmenuIndexAPDURunner || event.event == SubmenuIndexReadConfigCard ||
event.event == SubmenuIndexSamInfo)) {
seader->sam_present_menu_guard_active = false;
consumed = true;
} else if(event.event == SubmenuIndexRead) {
scene_manager_set_scene_state(
seader->scene_manager, SeaderSceneSamPresent, event.event);
scene_manager_next_scene(seader->scene_manager, SeaderSceneRead);
@@ -103,20 +106,23 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
seader->scene_manager, SeaderSceneSamPresent, event.event);
scene_manager_next_scene(seader->scene_manager, SeaderSceneAPDURunner);
consumed = true;
} else if(event.event == SeaderWorkerEventHfTeardownComplete) {
consumed = seader_hf_finish_teardown_action(seader);
} else if(event.event == SeaderCustomEventSamStatusUpdated) {
seader_scene_sam_present_on_update(context);
seader_scene_sam_present_rebuild_menu(
seader, submenu_get_selected_item(seader->submenu));
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_stop(seader->scene_manager);
view_dispatcher_stop(seader->view_dispatcher);
consumed = true;
consumed = seader_hf_request_teardown(seader, SeaderHfTeardownActionStopApp);
} else if(event.type == SceneManagerEventTypeTick) {
SeaderWorker* seader_worker = seader->worker;
if(fwChecks > 0 && seader_worker->sam_version[0] != 0 &&
seader_worker->sam_version[1] != 0) {
if(seader->sam_present_menu_guard_active) {
seader->sam_present_menu_guard_active = false;
}
if(fwChecks > 0 && seader->sam_version[0] != 0 && seader->sam_version[1] != 0) {
fwChecks--;
seader_scene_sam_present_on_update(context);
seader_scene_sam_present_rebuild_menu(
seader, submenu_get_selected_item(seader->submenu));
}
}
+8
View File
@@ -8,6 +8,9 @@ enum SubmenuIndex {
static void seader_scene_start_detect_callback(void* context) {
Seader* seader = context;
if(!seader || !seader->start_scene_active) {
return;
}
view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderWorkerEventSamMissing);
}
@@ -18,6 +21,8 @@ void seader_scene_start_submenu_callback(void* context, uint32_t index) {
void seader_scene_start_on_enter(void* context) {
Seader* seader = context;
seader_worker_acquire(seader);
seader->start_scene_active = true;
Popup* popup = seader->popup;
@@ -43,6 +48,7 @@ bool seader_scene_start_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SeaderWorkerEventSamPresent) {
seader->sam_present_menu_guard_active = true;
scene_manager_next_scene(seader->scene_manager, SeaderSceneSamPresent);
consumed = true;
} else if(event.event == SeaderWorkerEventSamMissing) {
@@ -67,5 +73,7 @@ bool seader_scene_start_on_event(void* context, SceneManagerEvent event) {
void seader_scene_start_on_exit(void* context) {
Seader* seader = context;
seader->start_scene_active = false;
popup_reset(seader->popup);
seader_worker_release(seader);
}
+2
View File
@@ -8,6 +8,7 @@ void seader_virtual_credential_worker_callback(uint32_t event, void* context) {
void seader_scene_virtual_credential_on_enter(void* context) {
Seader* seader = context;
seader_worker_acquire(seader);
// Setup view
Popup* popup = seader->popup;
@@ -52,4 +53,5 @@ void seader_scene_virtual_credential_on_exit(void* context) {
// Clear view
popup_reset(seader->popup);
seader_worker_release(seader);
}
+876 -39
View File
@@ -1,7 +1,435 @@
#include "seader_i.h"
#include "runtime_policy.h"
#include "hf_release_sequence.h"
#include "trace_log.h"
#define TAG "Seader"
#define TAG "Seader"
#define SEADER_PLUGIN_DIR APP_ASSETS_PATH("plugins")
#define SEADER_WIEGAND_PLUGIN_PATH APP_ASSETS_PATH("plugins/plugin_wiegand.fal")
#define SEADER_HF_PLUGIN_PATH APP_ASSETS_PATH("plugins/plugin_hf.fal")
typedef struct {
volatile bool done;
volatile bool detected;
} SeaderHfPicopassDetectContext;
static void seader_hf_worker_event_callback(uint32_t event, void* context);
static void seader_hf_teardown_blocking(Seader* seader);
static NfcCommand seader_hf_picopass_detect_callback(PicopassPollerEvent event, void* context) {
SeaderHfPicopassDetectContext* detect_context = context;
if(event.type == PicopassPollerEventTypeCardDetected ||
event.type == PicopassPollerEventTypeSuccess) {
detect_context->detected = true;
detect_context->done = true;
return NfcCommandStop;
} else if(event.type == PicopassPollerEventTypeFail) {
detect_context->done = true;
return NfcCommandStop;
}
return NfcCommandContinue;
}
static void seader_hf_plugin_notify_event(void* host_ctx, uint32_t event) {
Seader* seader = host_ctx;
if(!seader || !seader->view_dispatcher) {
FURI_LOG_W(TAG, "Drop HF plugin event %lu without dispatcher", event);
return;
}
view_dispatcher_send_custom_event(seader->view_dispatcher, event);
}
static void seader_hf_plugin_notify_card_detected(void* host_ctx) {
seader_hf_plugin_notify_event(host_ctx, SeaderCustomEventPollerDetect);
}
static void seader_hf_plugin_notify_worker_exit(void* host_ctx) {
seader_hf_plugin_notify_event(host_ctx, SeaderCustomEventWorkerExit);
}
static bool seader_hf_plugin_sam_can_accept_card(void* host_ctx) {
return seader_sam_can_accept_card(host_ctx);
}
static void seader_hf_plugin_send_card_detected(
void* host_ctx,
uint8_t sak,
const uint8_t* uid,
uint8_t uid_len,
const uint8_t* ats,
uint8_t ats_len) {
Seader* seader = host_ctx;
if(!seader || !seader->worker || !seader->credential || !uid || uid_len == 0U) {
FURI_LOG_E(
TAG,
"Drop HF cardDetected invalid state seader=%p worker=%p cred=%p uid=%p uid_len=%u",
(void*)seader,
seader ? (void*)seader->worker : NULL,
seader ? (void*)seader->credential : NULL,
(const void*)uid,
uid_len);
return;
}
FURI_LOG_D(
TAG,
"HF plugin cardDetected sak=%02x uid_len=%u ats_len=%u stage=%d",
sak,
uid_len,
ats_len,
seader->worker->stage);
seader_worker_card_detect(seader, sak, NULL, uid, uid_len, (uint8_t*)ats, ats_len);
}
static void seader_hf_plugin_send_nfc_rx(void* host_ctx, uint8_t* buffer, size_t len) {
Seader* seader = host_ctx;
seader_send_nfc_rx(seader, buffer, len);
}
static void seader_hf_plugin_run_conversation(void* host_ctx) {
Seader* seader = host_ctx;
if(!seader || !seader->worker) {
FURI_LOG_W(TAG, "Skip HF conversation without worker");
return;
}
FURI_LOG_D(TAG, "HF plugin run conversation stage=%d", seader->worker->stage);
seader_worker_run_hf_conversation(seader);
}
static void seader_hf_plugin_set_stage(void* host_ctx, PluginHfStage stage) {
Seader* seader = host_ctx;
if(seader->worker) {
switch(stage) {
case PluginHfStageCardDetect:
seader->worker->stage = SeaderPollerEventTypeCardDetect;
break;
case PluginHfStageConversation:
seader->worker->stage = SeaderPollerEventTypeConversation;
break;
case PluginHfStageComplete:
seader->worker->stage = SeaderPollerEventTypeComplete;
break;
case PluginHfStageSuccess:
seader->worker->stage = SeaderPollerEventTypeSuccess;
break;
case PluginHfStageFail:
default:
seader->worker->stage = SeaderPollerEventTypeFail;
break;
}
}
}
static PluginHfStage seader_hf_plugin_get_stage(void* host_ctx) {
Seader* seader = host_ctx;
if(!seader->worker) {
return PluginHfStageFail;
}
switch(seader->worker->stage) {
case SeaderPollerEventTypeCardDetect:
return PluginHfStageCardDetect;
case SeaderPollerEventTypeConversation:
return PluginHfStageConversation;
case SeaderPollerEventTypeComplete:
return PluginHfStageComplete;
case SeaderPollerEventTypeSuccess:
return PluginHfStageSuccess;
case SeaderPollerEventTypeFail:
default:
return PluginHfStageFail;
}
}
static void seader_hf_plugin_set_credential_type(void* host_ctx, SeaderCredentialType type) {
Seader* seader = host_ctx;
seader->credential->type = type;
seader->credential->sio_len = 0U;
seader->credential->sio_start_block = 0U;
seader->credential->isDesfireEV2 = false;
}
static SeaderCredentialType seader_hf_plugin_get_credential_type(void* host_ctx) {
Seader* seader = host_ctx;
return seader->credential->type;
}
static bool seader_hf_plugin_get_desfire_ev2(void* host_ctx) {
Seader* seader = host_ctx;
return seader->credential->isDesfireEV2;
}
static void seader_hf_plugin_set_desfire_ev2(void* host_ctx, bool is_desfire_ev2) {
Seader* seader = host_ctx;
seader->credential->isDesfireEV2 = is_desfire_ev2;
}
static void seader_hf_plugin_append_picopass_sio(
void* host_ctx,
uint8_t block_num,
const uint8_t* data,
size_t len) {
Seader* seader = host_ctx;
SeaderCredential* credential = seader->credential;
if(!data || len == 0U || credential->type != SeaderCredentialTypePicopass) {
return;
}
if(credential->sio_len == 0U && data[0] == 0x30U) {
credential->sio_start_block = block_num;
}
const size_t offset = (size_t)(block_num - credential->sio_start_block) * PICOPASS_BLOCK_LEN;
if(offset >= sizeof(credential->sio)) {
return;
}
const size_t copy_len = MIN(len, sizeof(credential->sio) - offset);
memcpy(credential->sio + offset, data, copy_len);
credential->sio_len = MAX(credential->sio_len, offset + copy_len);
}
static void seader_hf_plugin_set_14a_sio(void* host_ctx, const uint8_t* data, size_t len) {
Seader* seader = host_ctx;
SeaderCredential* credential = seader->credential;
if(!data || credential->type != SeaderCredentialType14A) {
return;
}
const size_t copy_len = MIN(len, sizeof(credential->sio));
memcpy(credential->sio, data, copy_len);
credential->sio_len = copy_len;
}
static Nfc* seader_hf_plugin_get_nfc(void* host_ctx) {
Seader* seader = host_ctx;
return seader ? seader->nfc : NULL;
}
static NfcDevice* seader_hf_plugin_get_nfc_device(void* host_ctx) {
Seader* seader = host_ctx;
return seader ? seader->nfc_device : NULL;
}
static bool seader_hf_plugin_picopass_detect(void* host_ctx) {
Seader* seader = host_ctx;
bool detected = false;
PicopassPoller* poller = picopass_poller_alloc(seader->nfc);
SeaderHfPicopassDetectContext detect_context = {0};
if(!poller) {
FURI_LOG_W(TAG, "Failed to allocate Picopass detect poller");
return false;
}
picopass_poller_start(poller, seader_hf_picopass_detect_callback, &detect_context);
for(uint8_t i = 0; i < 10 && !detect_context.done; i++) {
furi_delay_ms(10);
}
picopass_poller_stop(poller);
detected = detect_context.detected;
picopass_poller_free(poller);
return detected;
}
static bool seader_hf_plugin_picopass_start(
void* host_ctx,
PicopassPollerCallback callback,
void* callback_ctx) {
Seader* seader = host_ctx;
if(seader->picopass_poller) {
picopass_poller_stop(seader->picopass_poller);
picopass_poller_free(seader->picopass_poller);
seader->picopass_poller = NULL;
}
seader->picopass_poller = picopass_poller_alloc(seader->nfc);
if(!seader->picopass_poller) {
return false;
}
picopass_poller_start(seader->picopass_poller, callback, callback_ctx);
return true;
}
static void seader_hf_plugin_picopass_stop(void* host_ctx) {
Seader* seader = host_ctx;
if(seader->picopass_poller) {
picopass_poller_stop(seader->picopass_poller);
picopass_poller_free(seader->picopass_poller);
seader->picopass_poller = NULL;
}
}
static uint8_t* seader_hf_plugin_picopass_get_csn(void* host_ctx) {
Seader* seader = host_ctx;
if(!seader->picopass_poller) {
return NULL;
}
return picopass_poller_get_csn(seader->picopass_poller);
}
static bool seader_hf_plugin_picopass_transmit(
void* host_ctx,
const uint8_t* tx_data,
size_t tx_len,
uint8_t* rx_data,
size_t rx_capacity,
size_t* rx_len,
uint32_t fwt_fc) {
Seader* seader = host_ctx;
if(!seader->picopass_poller || !tx_data || !rx_data || !rx_len) {
return false;
}
BitBuffer* tx_buffer = bit_buffer_alloc(tx_len);
BitBuffer* rx_buffer = bit_buffer_alloc(rx_capacity);
bool success = false;
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate picopass host tx/rx buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
return false;
}
bit_buffer_append_bytes(tx_buffer, tx_data, tx_len);
PicopassError error =
picopass_poller_send_frame(seader->picopass_poller, tx_buffer, rx_buffer, fwt_fc);
if(error == PicopassErrorIncorrectCrc) {
error = PicopassErrorNone;
}
if(error == PicopassErrorNone) {
*rx_len = bit_buffer_get_size_bytes(rx_buffer);
memcpy(rx_data, bit_buffer_get_data(rx_buffer), *rx_len);
success = true;
}
bit_buffer_free(tx_buffer);
bit_buffer_free(rx_buffer);
return success;
}
static void seader_hf_plugin_set_read_error(void* host_ctx, const char* text) {
Seader* seader = host_ctx;
if(!text) {
seader->read_error[0] = '\0';
return;
}
strlcpy(seader->read_error, text, sizeof(seader->read_error));
}
static const PluginHfHostApi seader_hf_plugin_host_api = {
.notify_card_detected = seader_hf_plugin_notify_card_detected,
.notify_worker_exit = seader_hf_plugin_notify_worker_exit,
.sam_can_accept_card = seader_hf_plugin_sam_can_accept_card,
.send_card_detected = seader_hf_plugin_send_card_detected,
.send_nfc_rx = seader_hf_plugin_send_nfc_rx,
.run_conversation = seader_hf_plugin_run_conversation,
.set_stage = seader_hf_plugin_set_stage,
.get_stage = seader_hf_plugin_get_stage,
.set_credential_type = seader_hf_plugin_set_credential_type,
.get_credential_type = seader_hf_plugin_get_credential_type,
.get_desfire_ev2 = seader_hf_plugin_get_desfire_ev2,
.set_desfire_ev2 = seader_hf_plugin_set_desfire_ev2,
.append_picopass_sio = seader_hf_plugin_append_picopass_sio,
.set_14a_sio = seader_hf_plugin_set_14a_sio,
.get_nfc = seader_hf_plugin_get_nfc,
.get_nfc_device = seader_hf_plugin_get_nfc_device,
.picopass_detect = seader_hf_plugin_picopass_detect,
.picopass_start = seader_hf_plugin_picopass_start,
.picopass_stop = seader_hf_plugin_picopass_stop,
.picopass_get_csn = seader_hf_plugin_picopass_get_csn,
.picopass_transmit = seader_hf_plugin_picopass_transmit,
.set_read_error = seader_hf_plugin_set_read_error,
};
static void seader_hf_worker_event_callback(uint32_t event, void* context) {
Seader* seader = context;
if(!seader || !seader->view_dispatcher) {
return;
}
view_dispatcher_send_custom_event(seader->view_dispatcher, event);
}
static void seader_hf_session_force_unloaded(Seader* seader) {
if(!seader) {
return;
}
seader->hf_plugin_ctx = NULL;
seader->plugin_hf = NULL;
seader->hf_plugin_manager = NULL;
seader->poller = NULL;
seader->picopass_poller = NULL;
seader->hf_session_state = SeaderHfSessionStateUnloaded;
if(seader->mode_runtime == SeaderModeRuntimeHF) {
seader->mode_runtime = SeaderModeRuntimeNone;
}
}
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);
@@ -21,6 +449,10 @@ void seader_tick_event_callback(void* context) {
scene_manager_handle_tick_event(seader->scene_manager);
}
static bool seader_align_is_valid(size_t align) {
return align != 0U && ((align & (align - 1U)) == 0U);
}
Seader* seader_alloc() {
Seader* seader = malloc(sizeof(Seader));
seader_trace_reset();
@@ -34,14 +466,17 @@ Seader* seader_alloc() {
seader->sam_state = SeaderSamStateIdle;
seader->sam_intent = SeaderSamIntentNone;
seader->sam_present = false;
memset(seader->sam_version, 0, sizeof(seader->sam_version));
seader_sam_key_label_format(
false, NULL, 0U, seader->sam_key_label, sizeof(seader->sam_key_label));
seader_uhf_status_label_format(
false, false, false, false, seader->uhf_status_label, sizeof(seader->uhf_status_label));
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
seader->detected_card_type_count = 0;
seader->selected_read_type = SeaderCredentialTypeNone;
seader_uhf_snmp_probe_init(&seader->snmp_probe);
seader->nfc = nfc_alloc();
seader->nfc_device = seader->nfc ? nfc_device_alloc() : NULL;
seader->scratch.offset = 0U;
seader->scratch.high_water = 0U;
seader->hf_mode = NULL;
seader->worker = seader_worker_alloc();
seader->view_dispatcher = view_dispatcher_alloc();
@@ -58,8 +493,13 @@ Seader* seader_alloc() {
seader->credential = seader_credential_alloc();
seader->nfc = NULL;
seader->nfc_device = NULL;
if(!seader->nfc || !seader->nfc_device) {
FURI_LOG_W(
TAG,
"HF host NFC objects unavailable at startup nfc=%p device=%p",
seader->nfc,
seader->nfc_device);
}
// Open GUI record
seader->gui = furi_record_open(RECORD_GUI);
@@ -106,27 +546,20 @@ Seader* seader_alloc() {
seader->temp_string3 = furi_string_alloc();
seader->temp_string4 = furi_string_alloc();
seader->plugin_manager =
plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface);
seader->plugin_manager = NULL;
seader->plugin_wiegand = NULL;
FURI_LOG_I(TAG, "Loading plugins from %s", APP_ASSETS_PATH("plugins"));
if(plugin_manager_load_all(seader->plugin_manager, APP_ASSETS_PATH("plugins")) !=
PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load all libs");
} else {
uint32_t plugin_count = plugin_manager_get_count(seader->plugin_manager);
FURI_LOG_I(TAG, "Loaded %lu plugin(s)", plugin_count);
seader->hf_plugin_manager = NULL;
seader->plugin_hf = NULL;
seader->hf_plugin_ctx = NULL;
seader->mode_runtime = SeaderModeRuntimeNone;
seader->hf_session_state = SeaderHfSessionStateUnloaded;
seader->hf_teardown_action = SeaderHfTeardownActionNone;
seader->loading_popup_enabled = true;
seader->start_scene_active = false;
seader->sam_present_menu_guard_active = false;
for(uint32_t i = 0; i < plugin_count; i++) {
const PluginWiegand* plugin = plugin_manager_get_ep(seader->plugin_manager, i);
FURI_LOG_I(TAG, "plugin index %lu, name: %s", i, plugin->name);
if(strcmp(plugin->name, "Plugin Wiegand") == 0) {
FURI_LOG_I(TAG, "Wiegand plugin found and assigned");
// Have to cast to drop "const" qualifier
seader->plugin_wiegand = (PluginWiegand*)plugin;
}
}
if(seader->nfc_device) {
nfc_device_set_loading_callback(seader->nfc_device, seader_nfc_loading_callback, seader);
}
return seader;
@@ -139,21 +572,32 @@ void seader_free(Seader* seader) {
furi_hal_power_disable_otg();
}
seader_uart_free(seader->uart);
seader->uart = NULL;
seader->loading_popup_enabled = false;
seader_hf_teardown_blocking(seader);
seader_hf_mode_deactivate(seader);
seader_worker_release(seader);
if(seader->worker) {
seader_worker_free(seader->worker);
seader->worker = NULL;
}
seader_credential_free(seader->credential);
seader->credential = NULL;
seader_wiegand_plugin_release(seader);
if(seader->nfc_device) {
nfc_device_free(seader->nfc_device);
seader->nfc_device = NULL;
}
if(seader->nfc) {
nfc_free(seader->nfc);
seader->nfc = NULL;
}
if(seader->nfc_device) {
nfc_device_free(seader->nfc_device);
seader->nfc_device = NULL;
}
seader_uart_free(seader->uart);
seader->uart = NULL;
seader_credential_free(seader->credential);
seader->credential = NULL;
// Submenu
view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewMenu);
@@ -186,10 +630,6 @@ void seader_free(Seader* seader) {
furi_string_free(seader->temp_string3);
furi_string_free(seader->temp_string4);
// Worker
seader_worker_stop(seader->worker);
seader_worker_free(seader->worker);
// View Dispatcher
view_dispatcher_free(seader->view_dispatcher);
@@ -204,8 +644,6 @@ void seader_free(Seader* seader) {
furi_record_close(RECORD_NOTIFICATION);
seader->notifications = NULL;
plugin_manager_free(seader->plugin_manager);
free(seader);
}
@@ -242,8 +680,20 @@ void seader_blink_stop(Seader* seader) {
notification_message(seader->notifications, &seader_sequence_blink_stop);
}
void seader_nfc_loading_callback(void* context, bool show) {
Seader* seader = context;
if(!seader || !seader->loading_popup_enabled || !seader->view_dispatcher) {
return;
}
seader_show_loading_popup(seader, show);
}
void seader_show_loading_popup(void* context, bool show) {
Seader* seader = context;
if(!seader || !seader->loading_popup_enabled || !seader->view_dispatcher) {
return;
}
if(show) {
// Raise timer priority so that animations can play
@@ -255,6 +705,393 @@ void seader_show_loading_popup(void* context, bool show) {
}
}
bool seader_wiegand_plugin_acquire(Seader* seader) {
furi_assert(seader);
if(seader->plugin_wiegand) {
return true;
}
if(!seader->plugin_manager) {
seader->plugin_manager =
plugin_manager_alloc(PLUGIN_APP_ID, PLUGIN_API_VERSION, firmware_api_interface);
if(!seader->plugin_manager) {
FURI_LOG_E(TAG, "Failed to allocate plugin manager");
return false;
}
}
FURI_LOG_I(TAG, "Loading Wiegand plugin from %s", SEADER_WIEGAND_PLUGIN_PATH);
if(plugin_manager_load_single(seader->plugin_manager, SEADER_WIEGAND_PLUGIN_PATH) !=
PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load Wiegand plugin");
plugin_manager_free(seader->plugin_manager);
seader->plugin_manager = NULL;
return false;
}
if(plugin_manager_get_count(seader->plugin_manager) == 0) {
FURI_LOG_E(TAG, "Wiegand plugin manager is empty after load");
plugin_manager_free(seader->plugin_manager);
seader->plugin_manager = NULL;
return false;
}
seader->plugin_wiegand = (PluginWiegand*)plugin_manager_get_ep(seader->plugin_manager, 0);
if(!seader->plugin_wiegand) {
FURI_LOG_E(TAG, "Failed to resolve Wiegand plugin entry point");
plugin_manager_free(seader->plugin_manager);
seader->plugin_manager = NULL;
return false;
}
FURI_LOG_I(TAG, "Wiegand plugin loaded: %s", seader->plugin_wiegand->name);
return true;
}
void seader_wiegand_plugin_release(Seader* seader) {
furi_assert(seader);
if(!seader->plugin_manager) {
seader->plugin_wiegand = NULL;
return;
}
FURI_LOG_I(TAG, "Unloading Wiegand plugin");
seader->plugin_wiegand = NULL;
plugin_manager_free(seader->plugin_manager);
seader->plugin_manager = NULL;
}
bool seader_hf_plugin_acquire(Seader* seader) {
furi_assert(seader);
/* UHF maintenance and HF runtime are mutually exclusive mode owners. */
if(seader->mode_runtime == SeaderModeRuntimeUHF) {
FURI_LOG_W(TAG, "Reject HF plugin acquire while UHF runtime is active");
return false;
}
if(seader->hf_session_state == SeaderHfSessionStateTearingDown) {
FURI_LOG_W(TAG, "Reject HF plugin acquire during teardown");
return false;
}
/* Re-acquire is allowed only when the live runtime is already coherent. */
if(seader->plugin_hf && seader->hf_plugin_ctx) {
if(seader->hf_session_state == SeaderHfSessionStateUnloaded) {
seader->hf_session_state = SeaderHfSessionStateLoaded;
}
seader->mode_runtime = SeaderModeRuntimeHF;
return true;
}
/* Partial pointer state is always a bug; normalize through the single release path
instead of trying to reason about each damaged combination inline. */
if(seader->hf_plugin_manager || seader->plugin_hf || seader->hf_plugin_ctx) {
FURI_LOG_W(
TAG,
"Normalize partial HF session manager=%p plugin=%p ctx=%p state=%d",
(void*)seader->hf_plugin_manager,
(void*)seader->plugin_hf,
seader->hf_plugin_ctx,
seader->hf_session_state);
seader_hf_plugin_release(seader);
}
if(!seader->nfc || !seader->nfc_device) {
FURI_LOG_E(
TAG, "Host NFC objects unavailable nfc=%p device=%p", seader->nfc, seader->nfc_device);
return false;
}
if(!seader->hf_plugin_manager) {
seader->hf_plugin_manager =
plugin_manager_alloc(HF_PLUGIN_APP_ID, HF_PLUGIN_API_VERSION, firmware_api_interface);
if(!seader->hf_plugin_manager) {
FURI_LOG_E(TAG, "Failed to allocate HF plugin manager");
return false;
}
}
FURI_LOG_I(TAG, "Loading HF plugin from %s", SEADER_HF_PLUGIN_PATH);
if(plugin_manager_load_single(seader->hf_plugin_manager, SEADER_HF_PLUGIN_PATH) !=
PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load HF plugin");
plugin_manager_free(seader->hf_plugin_manager);
seader_hf_session_force_unloaded(seader);
return false;
}
seader->plugin_hf = (PluginHf*)plugin_manager_get_ep(seader->hf_plugin_manager, 0);
if(!seader->plugin_hf) {
FURI_LOG_E(TAG, "Failed to resolve HF plugin entry point");
plugin_manager_free(seader->hf_plugin_manager);
seader_hf_session_force_unloaded(seader);
return false;
}
seader->hf_plugin_ctx = seader->plugin_hf->alloc(&seader_hf_plugin_host_api, seader);
if(!seader->hf_plugin_ctx) {
FURI_LOG_E(TAG, "Failed to allocate HF plugin context");
plugin_manager_free(seader->hf_plugin_manager);
seader_hf_session_force_unloaded(seader);
return false;
}
seader->hf_session_state = SeaderHfSessionStateLoaded;
seader->mode_runtime = SeaderModeRuntimeHF;
FURI_LOG_I(TAG, "HF plugin loaded: %s", seader->plugin_hf->name);
return true;
}
static bool seader_hf_has_runtime(const Seader* seader) {
return seader && (seader->hf_plugin_manager || seader->plugin_hf || seader->hf_plugin_ctx ||
seader->poller || seader->picopass_poller);
}
/* App shutdown uses the same teardown primitive as normal navigation. The only difference
is that shutdown waits synchronously for the worker-owned teardown to finish. */
static void seader_hf_teardown_blocking(Seader* seader) {
if(!seader || !seader_hf_has_runtime(seader)) {
return;
}
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);
return;
}
seader_worker_stop(seader->worker);
FURI_LOG_I(TAG, "HF teardown blocking");
seader_worker_start(seader->worker, SeaderWorkerStateHfTeardown, seader->uart, NULL, seader);
seader_worker_join(seader->worker);
}
/* All HF runtime shutdown funnels through the canonical release sequence so stop/free/unload
order cannot silently diverge between code paths. */
void seader_hf_plugin_release(Seader* seader) {
furi_assert(seader);
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);
}
/* Teardown completion is the single place that collapses HF UI/runtime mode back into
ordinary app navigation. Scenes request teardown targets; they do not perform teardown. */
bool seader_hf_finish_teardown_action(Seader* seader) {
if(!seader) {
return false;
}
FURI_LOG_I(TAG, "HF teardown complete action=%d", seader->hf_teardown_action);
seader_show_loading_popup(seader, false);
seader_hf_mode_set_selected_read_type(seader, SeaderCredentialTypeNone);
seader_hf_mode_clear_detected_types(seader);
seader_hf_mode_deactivate(seader);
const SeaderHfTeardownAction action = seader->hf_teardown_action;
seader->hf_teardown_action = SeaderHfTeardownActionNone;
switch(action) {
case SeaderHfTeardownActionSamPresent:
return scene_manager_search_and_switch_to_another_scene(
seader->scene_manager, SeaderSceneSamPresent);
case SeaderHfTeardownActionRestartRead:
scene_manager_next_scene(seader->scene_manager, SeaderSceneRead);
return true;
case SeaderHfTeardownActionStopApp:
scene_manager_stop(seader->scene_manager);
view_dispatcher_stop(seader->view_dispatcher);
return true;
case SeaderHfTeardownActionNone:
default:
return false;
}
}
/* Requesting teardown is intentionally cheap: record the target, handle the no-runtime and
already-tearing-down fast paths, and hand off actual release work to the worker. */
bool seader_hf_request_teardown(Seader* seader, SeaderHfTeardownAction action) {
furi_assert(seader);
FURI_LOG_I(
TAG,
"HF teardown requested action=%d state=%d worker_state=%d",
action,
seader->hf_session_state,
seader->worker ? seader_worker_get_state(seader->worker) : -1);
seader->hf_teardown_action = action;
if(!seader_hf_has_runtime(seader)) {
seader->hf_session_state = SeaderHfSessionStateUnloaded;
return seader_hf_finish_teardown_action(seader);
}
if(!seader_worker_acquire(seader)) {
return seader_hf_finish_teardown_action(seader);
}
if(seader->hf_session_state == SeaderHfSessionStateTearingDown ||
(seader->worker &&
seader_worker_get_state(seader->worker) == SeaderWorkerStateHfTeardown)) {
return true;
}
seader->hf_session_state = SeaderHfSessionStateTearingDown;
seader_worker_stop(seader->worker);
seader_worker_start(
seader->worker,
SeaderWorkerStateHfTeardown,
seader->uart,
seader_hf_worker_event_callback,
seader);
return true;
}
bool seader_worker_acquire(Seader* seader) {
furi_assert(seader);
if(seader->worker) {
return true;
}
seader->worker = seader_worker_alloc();
return seader->worker != NULL;
}
void seader_worker_release(Seader* seader) {
furi_assert(seader);
if(!seader->worker) {
return;
}
seader_worker_stop(seader->worker);
seader->worker->callback = NULL;
seader->worker->context = NULL;
seader_worker_change_state(seader->worker, SeaderWorkerStateReady);
}
void seader_scratch_reset(Seader* seader) {
furi_assert(seader);
seader->scratch.offset = 0U;
}
void* seader_scratch_alloc(Seader* seader, size_t size, size_t align) {
furi_assert(seader);
furi_assert(seader_align_is_valid(align));
const size_t mask = align - 1U;
const size_t aligned_offset = (seader->scratch.offset + mask) & ~mask;
if(aligned_offset + size > sizeof(seader->scratch.arena)) {
FURI_LOG_E(TAG, "Scratch overflow: need=%zu offset=%zu", size, aligned_offset);
return NULL;
}
void* ptr = &seader->scratch.arena[aligned_offset];
memset(ptr, 0, size);
seader->scratch.offset = aligned_offset + size;
if(seader->scratch.offset > seader->scratch.high_water) {
seader->scratch.high_water = seader->scratch.offset;
}
return ptr;
}
bool seader_hf_mode_activate(Seader* seader) {
furi_assert(seader);
if(seader->hf_mode) {
return true;
}
seader_scratch_reset(seader);
seader->hf_mode =
seader_scratch_alloc(seader, sizeof(SeaderHfModeContext), _Alignof(SeaderHfModeContext));
if(!seader->hf_mode) {
return false;
}
seader->hf_mode->selected_read_type = SeaderCredentialTypeNone;
return true;
}
void seader_hf_mode_deactivate(Seader* seader) {
furi_assert(seader);
seader->hf_mode = NULL;
seader_scratch_reset(seader);
}
SeaderCredentialType seader_hf_mode_get_selected_read_type(const Seader* seader) {
return seader && seader->hf_mode ? seader->hf_mode->selected_read_type :
SeaderCredentialTypeNone;
}
void seader_hf_mode_set_selected_read_type(Seader* seader, SeaderCredentialType type) {
if(!seader || !seader->hf_mode) {
FURI_LOG_W(
TAG,
"Ignoring HF selected read type update without mode context seader=%p hf_mode=%p type=%d",
seader,
seader ? seader->hf_mode : NULL,
type);
return;
}
seader->hf_mode->selected_read_type = type;
}
void seader_hf_mode_set_detected_types(
Seader* seader,
const SeaderCredentialType* types,
size_t count) {
if(!seader || !seader->hf_mode) {
FURI_LOG_W(
TAG,
"Ignoring HF detected types update without mode context seader=%p hf_mode=%p count=%zu",
seader,
seader ? seader->hf_mode : NULL,
count);
return;
}
if(count > SEADER_MAX_DETECTED_CARD_TYPES) {
count = SEADER_MAX_DETECTED_CARD_TYPES;
}
memset(seader->hf_mode->detected_card_types, 0, sizeof(seader->hf_mode->detected_card_types));
if(types && count > 0) {
memcpy(seader->hf_mode->detected_card_types, types, count * sizeof(types[0]));
}
seader->hf_mode->detected_card_type_count = count;
}
size_t seader_hf_mode_get_detected_type_count(const Seader* seader) {
return seader && seader->hf_mode ? seader->hf_mode->detected_card_type_count : 0U;
}
const SeaderCredentialType* seader_hf_mode_get_detected_types(const Seader* seader) {
return seader && seader->hf_mode ? seader->hf_mode->detected_card_types : NULL;
}
void seader_hf_mode_clear_detected_types(Seader* seader) {
seader_hf_mode_set_detected_types(seader, NULL, 0U);
}
int32_t seader_app(void* p) {
UNUSED(p);
Seader* seader = seader_alloc();
+34
View File
@@ -1,4 +1,38 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
typedef struct Seader Seader;
typedef struct SeaderPollerContainer SeaderPollerContainer;
typedef enum {
SeaderHfSessionStateUnloaded,
SeaderHfSessionStateLoaded,
SeaderHfSessionStateActive,
SeaderHfSessionStateTearingDown,
} SeaderHfSessionState;
typedef enum {
SeaderModeRuntimeNone,
SeaderModeRuntimeHF,
SeaderModeRuntimeUHF,
} SeaderModeRuntime;
typedef enum {
SeaderHfTeardownActionNone,
SeaderHfTeardownActionSamPresent,
SeaderHfTeardownActionRestartRead,
SeaderHfTeardownActionStopApp,
} SeaderHfTeardownAction;
bool seader_worker_acquire(Seader* seader);
void seader_worker_release(Seader* seader);
void seader_scratch_reset(Seader* seader);
void* seader_scratch_alloc(Seader* seader, size_t size, size_t align);
bool seader_wiegand_plugin_acquire(Seader* seader);
void seader_wiegand_plugin_release(Seader* seader);
bool seader_hf_plugin_acquire(Seader* seader);
void seader_hf_plugin_release(Seader* seader);
bool seader_hf_request_teardown(Seader* seader, SeaderHfTeardownAction action);
bool seader_hf_finish_teardown_action(Seader* seader);
+1 -10
View File
@@ -5,6 +5,7 @@
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include "protocol/picopass_protocol.h"
#include "seader_credential_type.h"
#include <optimized_ikeys.h>
#include <optimized_cipher.h>
@@ -15,16 +16,6 @@
typedef void (*SeaderLoadingCallback)(void* context, bool state);
typedef enum {
SeaderCredentialTypeNone,
SeaderCredentialTypePicopass,
SeaderCredentialType14A,
// Might need to make 14a into "javacard" and add Desfire
SeaderCredentialTypeMifareClassic,
SeaderCredentialTypeVirtual,
SeaderCredentialTypeConfig,
} SeaderCredentialType;
typedef enum {
SeaderPacsMediaTypeUnknown = 0,
SeaderPacsMediaTypeDesfire = 1,
+10
View File
@@ -0,0 +1,10 @@
#pragma once
typedef enum {
SeaderCredentialTypeNone,
SeaderCredentialTypePicopass,
SeaderCredentialType14A,
SeaderCredentialTypeMifareClassic,
SeaderCredentialTypeVirtual,
SeaderCredentialTypeConfig,
} SeaderCredentialType;
+4
View File
@@ -0,0 +1,4 @@
#include "seader_i.h"
// The first RAM-focused step keeps HF mode state small and scratch-backed.
// Additional HF-specific session state can move here later without changing host ownership.
+40 -4
View File
@@ -39,7 +39,8 @@
#include <Payload.h>
#include <FrameProtocol.h>
#include "plugin/interface.h"
#include "wiegand_interface_fal/interface.h"
#include "hf_interface_fal/hf_interface.h"
#include <flipper_application/flipper_application.h>
#include <flipper_application/plugins/plugin_manager.h>
#include <loader/firmware_api/firmware_api.h>
@@ -68,6 +69,7 @@
#define SEADER_TEXT_STORE_SIZE 128
#define SEADER_MAX_ATR_SIZE 33
#define MAX_FRAME_HEADERS 32
#define SEADER_SCRATCH_SIZE 512
#define SEADER_MAX_DETECTED_CARD_TYPES 3
enum SeaderCustomEvent {
@@ -103,6 +105,18 @@ typedef struct {
uint16_t current_line;
} SeaderAPDURunnerContext;
typedef struct {
size_t offset;
size_t high_water;
uint8_t arena[SEADER_SCRATCH_SIZE];
} SeaderScratch;
typedef struct {
SeaderCredentialType detected_card_types[SEADER_MAX_DETECTED_CARD_TYPES];
size_t detected_card_type_count;
SeaderCredentialType selected_read_type;
} SeaderHfModeContext;
typedef enum {
SeaderSamStateIdle,
SeaderSamStateDetectPending,
@@ -135,11 +149,14 @@ struct Seader {
SeaderSamState sam_state;
SeaderSamIntent sam_intent;
bool sam_present;
uint8_t sam_version[2];
uint8_t ATR[SEADER_MAX_ATR_SIZE];
size_t ATR_len;
char sam_key_label[SEADER_SAM_KEY_LABEL_MAX_LEN];
char uhf_status_label[SEADER_UHF_STATUS_LABEL_MAX_LEN];
SeaderUhfSnmpProbe snmp_probe;
SeaderScratch scratch;
SeaderHfModeContext* hf_mode;
char text_store[SEADER_TEXT_STORE_SIZE + 1];
char read_error[SEADER_TEXT_STORE_SIZE + 1];
@@ -164,12 +181,18 @@ struct Seader {
PicopassPoller* picopass_poller;
NfcDevice* nfc_device;
SeaderCredentialType detected_card_types[SEADER_MAX_DETECTED_CARD_TYPES];
size_t detected_card_type_count;
SeaderCredentialType selected_read_type;
PluginManager* plugin_manager;
PluginWiegand* plugin_wiegand;
PluginManager* hf_plugin_manager;
PluginHf* plugin_hf;
void* hf_plugin_ctx;
SeaderModeRuntime mode_runtime;
SeaderHfSessionState hf_session_state;
SeaderHfTeardownAction hf_teardown_action;
bool loading_popup_enabled;
bool start_scene_active;
bool sam_present_menu_guard_active;
APDULog* apdu_log;
SeaderAPDURunnerContext apdu_runner_ctx;
@@ -199,4 +222,17 @@ void seader_blink_start(Seader* seader);
void seader_blink_stop(Seader* seader);
void seader_nfc_loading_callback(void* context, bool show);
void seader_show_loading_popup(void* context, bool show);
bool seader_hf_mode_activate(Seader* seader);
void seader_hf_mode_deactivate(Seader* seader);
SeaderCredentialType seader_hf_mode_get_selected_read_type(const Seader* seader);
void seader_hf_mode_set_selected_read_type(Seader* seader, SeaderCredentialType type);
void seader_hf_mode_set_detected_types(
Seader* seader,
const SeaderCredentialType* types,
size_t count);
size_t seader_hf_mode_get_detected_type_count(const Seader* seader);
const SeaderCredentialType* seader_hf_mode_get_detected_types(const Seader* seader);
void seader_hf_mode_clear_detected_types(Seader* seader);
+101 -53
View File
@@ -17,13 +17,31 @@
// Forward declaration
void seader_send_card_detected(SeaderUartBridge* seader_uart, CardDetails_t* cardDetails);
void seader_worker_reading(Seader* seader);
void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* spc);
static void seader_worker_release_hf_session(Seader* seader) {
if(!seader) {
return;
}
seader_hf_plugin_release(seader);
}
typedef struct {
bool done;
bool detected;
volatile bool done;
volatile bool detected;
} SeaderPicopassDetectContext;
static void seader_worker_clear_active_card(Seader* seader, const char* reason) {
if(!seader) {
return;
}
if(seader_sam_has_active_card(seader)) {
FURI_LOG_I(TAG, "Clear active SAM card (%s)", reason ? reason : "worker");
seader_send_no_card_detected(seader);
}
}
static NfcCommand
seader_worker_picopass_detect_callback(PicopassPollerEvent event, void* context) {
SeaderPicopassDetectContext* detect_context = context;
@@ -46,14 +64,19 @@ static bool seader_worker_detect_picopass(Nfc* nfc) {
PicopassPoller* poller = picopass_poller_alloc(nfc);
SeaderPicopassDetectContext detect_context = {0};
if(!poller) {
FURI_LOG_W(TAG, "Failed to allocate Picopass detect poller");
return false;
}
picopass_poller_start(poller, seader_worker_picopass_detect_callback, &detect_context);
for(uint8_t i = 0; i < 10 && !detect_context.done; i++) {
furi_delay_ms(10);
}
detected = detect_context.detected;
picopass_poller_stop(poller);
detected = detect_context.detected;
picopass_poller_free(poller);
return detected;
@@ -75,7 +98,7 @@ static void seader_worker_add_detected_type(
}
}
static size_t seader_worker_detect_supported_types(
static size_t __attribute__((unused)) seader_worker_detect_supported_types(
Seader* seader,
SeaderCredentialType* detected_types,
size_t detected_capacity) {
@@ -103,7 +126,8 @@ static size_t seader_worker_detect_supported_types(
return detected_type_count;
}
static bool seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) {
static bool __attribute__((unused))
seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) {
NfcPoller* poller_detect = NULL;
if(type == SeaderCredentialType14A) {
@@ -161,7 +185,6 @@ SeaderWorker* seader_worker_alloc() {
seader_worker->callback = NULL;
seader_worker->context = NULL;
seader_worker->storage = furi_record_open(RECORD_STORAGE);
memset(seader_worker->sam_version, 0, sizeof(seader_worker->sam_version));
seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
@@ -196,7 +219,9 @@ void seader_worker_start(
seader_worker_stop(seader_worker);
}
seader_worker->stage = SeaderPollerEventTypeCardDetect;
/* Worker startup owns queue/stage reset. Scene code must not pre-reset the live
poller session because the worker is the runtime owner for those objects. */
seader_worker_reset_poller_session(seader_worker);
seader_worker->callback = callback;
seader_worker->context = context;
seader_worker->uart = uart;
@@ -214,6 +239,15 @@ void seader_worker_stop(SeaderWorker* seader_worker) {
furi_thread_join(seader_worker->thread);
}
void seader_worker_join(SeaderWorker* seader_worker) {
furi_assert(seader_worker);
if(furi_thread_get_state(seader_worker->thread) == FuriThreadStateStopped) {
return;
}
furi_thread_join(seader_worker->thread);
}
void seader_worker_change_state(SeaderWorker* seader_worker, SeaderWorkerState state) {
seader_worker->state = state;
}
@@ -247,7 +281,6 @@ void seader_worker_reset_poller_session(SeaderWorker* seader_worker) {
furi_message_queue_get_count(seader_worker->messages));
furi_message_queue_reset(seader_worker->messages);
seader_worker->stage = SeaderPollerEventTypeCardDetect;
}
@@ -259,6 +292,8 @@ bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len)
if(seader_process_success_response_i(seader, apdu, len, false, NULL)) {
// no-op, message was processed
} else {
/* Outside an active conversation, an unhandled SAM message is stale noise from a
previous flow. Enqueueing it would let old maintenance/read traffic bleed forward. */
if(seader_worker->state != SeaderWorkerStateVirtualCredential &&
seader_worker->stage != SeaderPollerEventTypeConversation) {
FURI_LOG_I(
@@ -299,8 +334,11 @@ bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len)
}
bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t len) {
furi_check(seader);
SeaderWorker* seader_worker = seader->worker;
furi_check(seader_worker);
SeaderUartBridge* seader_uart = seader_worker->uart;
furi_check(seader_uart);
if(len < 2) {
return false;
}
@@ -420,6 +458,12 @@ int32_t seader_worker_task(void* context) {
FURI_LOG_D(TAG, "APDU Runner");
seader_apdu_runner_init(seader);
return 0;
} else if(seader_worker->state == SeaderWorkerStateHfTeardown) {
FURI_LOG_I(TAG, "HF teardown started");
seader_worker_release_hf_session(seader);
if(seader_worker->callback) {
seader_worker->callback(SeaderWorkerEventHfTeardownComplete, seader_worker->context);
}
} else if(seader_worker->state == SeaderWorkerStateReading) {
FURI_LOG_D(TAG, "Reading mode started");
seader_worker_reading(seader);
@@ -433,26 +477,29 @@ void seader_worker_reading(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
FURI_LOG_I(TAG, "Reading loop started");
seader->nfc = nfc_alloc();
seader->nfc_device = nfc_device_alloc();
nfc_device_set_loading_callback(seader->nfc_device, seader_show_loading_popup, seader);
if(!seader_hf_plugin_acquire(seader) || !seader->plugin_hf || !seader->hf_plugin_ctx) {
FURI_LOG_E(TAG, "HF plugin unavailable");
strlcpy(seader->read_error, "HF plugin unavailable", sizeof(seader->read_error));
if(seader_worker->callback) {
seader_worker->callback(SeaderWorkerEventFail, seader_worker->context);
}
return;
}
while(seader_worker->state == SeaderWorkerStateReading) {
bool detected = false;
SeaderPollerEventType result_stage = SeaderPollerEventTypeFail;
SeaderCredentialType type_to_read = seader->selected_read_type;
SeaderCredentialType type_to_read = seader_hf_mode_get_selected_read_type(seader);
FURI_LOG_D(TAG, "HF loop selected type=%d stage=%d", type_to_read, seader_worker->stage);
if(type_to_read == SeaderCredentialTypeNone) {
SeaderCredentialType detected_types[SEADER_MAX_DETECTED_CARD_TYPES] = {0};
const size_t detected_type_count = seader_worker_detect_supported_types(
seader, detected_types, COUNT_OF(detected_types));
const size_t detected_type_count = seader->plugin_hf->detect_supported_types(
seader->hf_plugin_ctx, detected_types, COUNT_OF(detected_types));
FURI_LOG_I(TAG, "HF plugin detected %u type(s)", detected_type_count);
if(detected_type_count > 1) {
memcpy(
seader->detected_card_types,
detected_types,
sizeof(seader->detected_card_types));
seader->detected_card_type_count = detected_type_count;
seader_hf_mode_set_detected_types(seader, detected_types, detected_type_count);
if(seader_worker->callback) {
seader_worker->callback(
SeaderWorkerEventSelectCardType, seader_worker->context);
@@ -464,7 +511,12 @@ void seader_worker_reading(Seader* seader) {
}
if(type_to_read != SeaderCredentialTypeNone) {
detected = seader_worker_start_read_for_type(seader, type_to_read);
FURI_LOG_I(TAG, "HF start read for type=%d", type_to_read);
detected = seader->plugin_hf->start_read_for_type(seader->hf_plugin_ctx, type_to_read);
if(detected) {
seader->hf_session_state = SeaderHfSessionStateActive;
}
FURI_LOG_I(TAG, "HF start read result=%d", detected);
}
if(detected) {
@@ -477,18 +529,11 @@ void seader_worker_reading(Seader* seader) {
furi_delay_ms(10);
}
result_stage = seader_worker->stage;
// Cleanup poller
if(seader->poller) {
nfc_poller_stop(seader->poller);
nfc_poller_free(seader->poller);
seader->poller = NULL;
}
if(seader->picopass_poller) {
picopass_poller_stop(seader->picopass_poller);
picopass_poller_free(seader->picopass_poller);
seader->picopass_poller = NULL;
}
/* SAM active-card state belongs to the read lifecycle, not to the success scene.
Clear it as soon as the poller conversation reaches a terminal stage. */
seader_worker_clear_active_card(
seader,
result_stage == SeaderPollerEventTypeComplete ? "read-complete" : "read-abort");
if(result_stage == SeaderPollerEventTypeComplete) {
// Notify UI of success
@@ -504,19 +549,16 @@ void seader_worker_reading(Seader* seader) {
}
}
nfc_free(seader->nfc);
seader->nfc = NULL;
nfc_device_free(seader->nfc_device);
seader->nfc_device = NULL;
FURI_LOG_I(TAG, "Reading loop stopped");
}
void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* spc) {
void seader_worker_run_hf_conversation(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
furi_thread_set_current_priority(FuriThreadPriorityHighest);
/* The NFC callback thread stays in this loop while the SAM drives the conversation.
The worker queue is the bridge between SAM APDUs and the poller callback thread. */
while(seader_worker->stage == SeaderPollerEventTypeConversation &&
seader_worker->state == SeaderWorkerStateReading) {
SeaderAPDU seaderApdu = {};
@@ -526,7 +568,7 @@ void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* sp
if(status == FuriStatusOk) {
FURI_LOG_D(TAG, "Dequeue SAM message [%d bytes]", seaderApdu.len);
if(seader_process_success_response_i(
seader, seaderApdu.buf, seaderApdu.len, true, spc)) {
seader, seaderApdu.buf, seaderApdu.len, true, NULL)) {
// message was processed, loop again to see if SAM has more to say
} else {
FURI_LOG_I(TAG, "Response false, ending conversation");
@@ -547,15 +589,17 @@ void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* sp
}
NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolIso14443_4a);
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;
SeaderPollerContainer spc = {.iso14443_4a_poller = event.instance};
if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
FURI_LOG_D(TAG, "14a stage CardDetect -> Conversation");
@@ -592,7 +636,11 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
uint8_t ats_len = 0;
uint8_t* ats = malloc(4 + t1_tk_size);
furi_assert(ats);
if(!ats) {
FURI_LOG_E(TAG, "Failed to allocate host ATS buffer");
seader_worker->stage = SeaderPollerEventTypeFail;
return NfcCommandStop;
}
if(iso14443_4a_data->ats_data.tl > 1) {
ats[ats_len++] = iso14443_4a_data->ats_data.t0;
@@ -633,7 +681,7 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
seader_worker->stage = SeaderPollerEventTypeConversation;
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
seader_trace(TAG, "14a ready in Conversation");
seader_worker_poller_conversation(seader, &spc);
seader_worker_run_hf_conversation(seader);
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
seader_trace(TAG, "14a ready in Complete");
ret = NfcCommandStop;
@@ -667,15 +715,17 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
}
NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfClassic);
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;
SeaderPollerContainer spc = {.mfc_poller = event.instance};
if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
if(seader_worker->stage == SeaderPollerEventTypeCardDetect) {
FURI_LOG_D(TAG, "MFC stage CardDetect -> Conversation");
@@ -706,7 +756,7 @@ NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* contex
furi_thread_set_current_priority(FuriThreadPriorityLowest);
seader_worker->stage = SeaderPollerEventTypeConversation;
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
seader_worker_poller_conversation(seader, &spc);
seader_worker_run_hf_conversation(seader);
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
ret = NfcCommandStop;
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
@@ -725,15 +775,13 @@ NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* contex
}
NfcCommand seader_worker_poller_callback_picopass(PicopassPollerEvent event, void* context) {
furi_assert(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;
SeaderPollerContainer spc = {.picopass_poller = instance};
if(event.type == PicopassPollerEventTypeCardDetected) {
seader_worker->stage = SeaderPollerEventTypeCardDetect;
} else if(event.type == PicopassPollerEventTypeSuccess) {
@@ -761,7 +809,7 @@ NfcCommand seader_worker_poller_callback_picopass(PicopassPollerEvent event, voi
furi_thread_set_current_priority(FuriThreadPriorityLowest);
seader_worker->stage = SeaderPollerEventTypeConversation;
} else if(seader_worker->stage == SeaderPollerEventTypeConversation) {
seader_worker_poller_conversation(seader, &spc);
seader_worker_run_hf_conversation(seader);
} else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
ret = NfcCommandStop;
} else if(seader_worker->stage == SeaderPollerEventTypeFail) {
+6
View File
@@ -3,6 +3,8 @@
#include <lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
#include <lib/nfc/protocols/mf_classic/mf_classic_poller.h>
#include "protocol/picopass_poller.h"
#include "seader.h"
#include "sam_api.h"
#include "seader_credential.h"
#include "seader_bridge.h"
@@ -22,6 +24,7 @@ typedef enum {
SeaderWorkerStateVirtualCredential,
SeaderWorkerStateAPDURunner,
SeaderWorkerStateReading,
SeaderWorkerStateHfTeardown,
// Transition
SeaderWorkerStateStop,
} SeaderWorkerState;
@@ -42,6 +45,7 @@ typedef enum {
SeaderWorkerEventAPDURunnerUpdate,
SeaderWorkerEventAPDURunnerSuccess,
SeaderWorkerEventAPDURunnerError,
SeaderWorkerEventHfTeardownComplete,
} SeaderWorkerEvent;
typedef enum {
@@ -69,10 +73,12 @@ void seader_worker_start(
void* context);
void seader_worker_stop(SeaderWorker* seader_worker);
void seader_worker_join(SeaderWorker* seader_worker);
bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t len);
void seader_worker_send_version(Seader* seader);
void seader_worker_cancel_poller_session(SeaderWorker* seader_worker);
void seader_worker_reset_poller_session(SeaderWorker* seader_worker);
void seader_worker_run_hf_conversation(Seader* seader);
NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void* context);
NfcCommand seader_worker_poller_callback_mfc(NfcGenericEvent event, void* context);
-1
View File
@@ -26,7 +26,6 @@
struct SeaderWorker {
FuriThread* thread;
Storage* storage;
uint8_t sam_version[2];
FuriMessageQueue* messages;
SeaderUartBridge* uart;
SeaderWorkerCallback callback;
+14 -13
View File
@@ -19,6 +19,13 @@ static uint8_t seader_next_dpcb(SeaderUartBridge* seader_uart) {
return t1->send_pcb;
}
static SeaderUartBridge* seader_t1_active_uart(Seader* seader) {
furi_check(seader);
furi_check(seader->worker);
furi_check(seader->worker->uart);
return seader->worker->uart;
}
void seader_t_1_reset(SeaderUartBridge* seader_uart) {
SeaderT1State* t1 = seader_t1_state(seader_uart);
t1->nad = 0x00;
@@ -31,8 +38,7 @@ void seader_t_1_reset(SeaderUartBridge* seader_uart) {
}
void seader_t_1_set_IFSD(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t frame[5];
uint8_t frame_len = 0;
@@ -51,8 +57,7 @@ void seader_t_1_set_IFSD(Seader* seader) {
}
static void seader_t_1_IFSD_response(Seader* seader, uint8_t ifs_value) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t frame[5];
uint8_t frame_len = 0;
@@ -68,8 +73,7 @@ static void seader_t_1_IFSD_response(Seader* seader, uint8_t ifs_value) {
}
static void seader_t_1_WTX_response(Seader* seader, uint8_t multiplier) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t frame[5];
uint8_t frame_len = 0;
@@ -85,8 +89,7 @@ static void seader_t_1_WTX_response(Seader* seader, uint8_t multiplier) {
}
static void seader_t_1_resynch_response(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t frame[4];
uint8_t frame_len = 0;
@@ -101,8 +104,7 @@ static void seader_t_1_resynch_response(Seader* seader) {
}
void seader_t_1_send_ack(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t frame[4];
uint8_t frame_len = 0;
@@ -117,8 +119,7 @@ void seader_t_1_send_ack(Seader* seader) {
}
static void seader_t_1_send_nak(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t frame[4];
uint8_t frame_len = 0;
@@ -211,8 +212,8 @@ void seader_send_t1(SeaderUartBridge* seader_uart, uint8_t* apdu, size_t len) {
}
bool seader_recv_t1(Seader* seader, CCID_Message* message) {
SeaderUartBridge* seader_uart = seader_t1_active_uart(seader);
SeaderWorker* seader_worker = seader->worker;
SeaderUartBridge* seader_uart = seader_worker->uart;
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t* apdu = NULL;
size_t apdu_len = 0;
+17 -2
View File
@@ -104,7 +104,15 @@ int32_t seader_uart_worker(void* context) {
while(1) {
uint32_t events =
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
furi_check(!(events & FuriFlagError));
if(events & FuriFlagError) {
FURI_LOG_E(
TAG,
"RX worker flag error events=0x%08lx thread=%p tx_thread=%p",
(unsigned long)events,
(void*)seader_uart->thread,
(void*)seader_uart->tx_thread);
break;
}
if(events & WorkerEvtStop) {
memset(cmd, 0, cmd_len);
cmd_len = 0;
@@ -171,7 +179,14 @@ int32_t seader_uart_tx_thread(void* context) {
while(1) {
uint32_t events =
furi_thread_flags_wait(WORKER_ALL_TX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
furi_check(!(events & FuriFlagError));
if(events & FuriFlagError) {
FURI_LOG_E(
TAG,
"TX worker flag error events=0x%08lx serial_handle=%p",
(unsigned long)events,
(void*)seader_uart->serial_handle);
break;
}
if(events & WorkerEvtTxStop) break;
if(events & WorkerEvtSamRx) {
if(seader_uart->tx_len > 0) {
+26 -7
View File
@@ -10,16 +10,35 @@ static size_t seader_uhf_append_family(
bool* wrote_any,
const char* name,
bool key_present) {
if(*wrote_any) {
pos += (size_t)snprintf(out + pos, out_size - pos, "/");
} else {
pos += (size_t)snprintf(out + pos, out_size - pos, "UHF: ");
*wrote_any = true;
int written = 0;
if(pos >= out_size) {
return out_size - 1U;
}
if(*wrote_any) {
written = snprintf(out + pos, out_size - pos, "/");
} else {
written = snprintf(out + pos, out_size - pos, "UHF: ");
*wrote_any = true;
}
pos += (size_t)written;
if(pos >= out_size) {
return out_size - 1U;
}
written = snprintf(out + pos, out_size - pos, "%s", name);
pos += (size_t)written;
if(pos >= out_size) {
return out_size - 1U;
}
pos += (size_t)snprintf(out + pos, out_size - pos, "%s", name);
if(!key_present) {
pos += (size_t)snprintf(out + pos, out_size - pos, " [no key]");
written = snprintf(out + pos, out_size - pos, " [no key]");
pos += (size_t)written;
if(pos >= out_size) {
return out_size - 1U;
}
}
return pos;
}
+9
View File
@@ -0,0 +1,9 @@
# Seader embedded Wiegand plugin sources
This directory is part of the main Seader repository.
It contains the embedded Wiegand `.fal` plugin sources used by Seader:
- `wiegand.c`
- `interface.h`
The Wiegand plugin source path in `application.fam` must point at `wiegand_interface_fal/wiegand.c`.
+20
View File
@@ -0,0 +1,20 @@
/**
* @file plugin_interface.h
* @brief Example plugin interface.
*
* Common interface between a plugin and host application
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <furi.h>
#define PLUGIN_APP_ID "plugin_wiegand"
#define PLUGIN_API_VERSION 1
typedef struct {
const char* name;
int (*count)(uint8_t, uint64_t);
void (*description)(uint8_t, uint64_t, size_t, FuriString*);
} PluginWiegand;
+228
View File
@@ -0,0 +1,228 @@
#include "interface.h"
#include <lib/bit_lib/bit_lib.h>
#include <flipper_application/flipper_application.h>
/*
* Huge thanks to the proxmark codebase:
* https://github.com/RfidResearchGroup/proxmark3/blob/master/client/src/wiegand_formats.c
*/
// Structure for packed wiegand messages
// Always align lowest value (last transmitted) bit to ordinal position 0 (lowest valued bit bottom)
typedef struct {
uint8_t Length; // Number of encoded bits in wiegand message (excluding headers and preamble)
uint32_t Top; // Bits in x<<64 positions
uint32_t Mid; // Bits in x<<32 positions
uint32_t Bot; // Lowest ordinal positions
} wiegand_message_t;
static inline uint8_t oddparity32(uint32_t x) {
return bit_lib_test_parity_32(x, BitLibParityOdd);
}
static inline uint8_t evenparity32(uint32_t x) {
return bit_lib_test_parity_32(x, BitLibParityEven);
}
uint8_t get_bit_by_position(wiegand_message_t* data, uint8_t pos) {
if(pos >= data->Length) return false;
pos = (data->Length - pos) -
1; // invert ordering; Indexing goes from 0 to 1. Subtract 1 for weight of bit.
uint8_t result = 0;
if(pos > 95)
result = 0;
else if(pos > 63)
result = (data->Top >> (pos - 64)) & 1;
else if(pos > 31)
result = (data->Mid >> (pos - 32)) & 1;
else
result = (data->Bot >> pos) & 1;
return result;
}
uint64_t get_linear_field(wiegand_message_t* data, uint8_t firstBit, uint8_t length) {
uint64_t result = 0;
for(uint8_t i = 0; i < length; i++) {
result = (result << 1) | get_bit_by_position(data, firstBit + i);
}
return result;
}
static int wiegand_C1k35s_parse(uint8_t bit_length, uint64_t bits, FuriString* description) {
wiegand_message_t value;
value.Length = bit_length;
value.Mid = bits >> 32;
value.Bot = bits;
wiegand_message_t* packed = &value;
if(packed->Length != 35) return false; // Wrong length? Stop here.
uint32_t cn = (packed->Bot >> 1) & 0x000FFFFF;
uint32_t fc = ((packed->Mid & 1) << 11) | ((packed->Bot >> 21));
bool valid = (evenparity32((packed->Mid & 0x1) ^ (packed->Bot & 0xB6DB6DB6)) ==
((packed->Mid >> 1) & 1)) &&
(oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0x6DB6DB6C)) ==
((packed->Bot >> 0) & 1)) &&
(oddparity32((packed->Mid & 0x3) ^ (packed->Bot & 0xFFFFFFFF)) ==
((packed->Mid >> 2) & 1));
if(valid) {
furi_string_cat_printf(description, "C1k35s\nFC: %ld CN: %ld\n", fc, cn);
return 1;
} else {
FURI_LOG_D(PLUGIN_APP_ID, "C1k35s invalid");
}
return 0;
}
static int wiegand_h10301_parse(uint8_t bit_length, uint64_t bits, FuriString* description) {
if(bit_length != 26) {
return 0;
}
//E XXXX XXXX XXXX
//XXXX XXXX XXXX O
uint32_t eBitMask = 0x02000000;
uint32_t oBitMask = 0x00000001;
uint32_t eParityMask = 0x01FFE000;
uint32_t oParityMask = 0x00001FFE;
uint8_t eBit = (eBitMask & bits) >> 25;
uint8_t oBit = (oBitMask & bits) >> 0;
bool eParity = bit_lib_test_parity_32((bits & eParityMask) >> 13, BitLibParityEven) ==
(eBit == 1);
bool oParity = bit_lib_test_parity_32((bits & oParityMask) >> 1, BitLibParityOdd) ==
(oBit == 1);
FURI_LOG_D(
PLUGIN_APP_ID,
"eBit: %d, oBit: %d, eParity: %d, oParity: %d",
eBit,
oBit,
eParity,
oParity);
if(eParity && oParity) {
uint32_t cnMask = 0x1FFFE;
uint16_t cn = ((bits & cnMask) >> 1);
uint32_t fcMask = 0x1FE0000;
uint16_t fc = ((bits & fcMask) >> 17);
furi_string_cat_printf(description, "H10301\nFC: %d CN: %d\n", fc, cn);
return 1;
} else {
FURI_LOG_D(PLUGIN_APP_ID, "H10301 invalid");
}
return 0;
}
static int wiegand_H10304_parse(uint8_t bit_length, uint64_t bits, FuriString* description) {
wiegand_message_t value;
value.Length = bit_length;
value.Mid = bits >> 32;
value.Bot = bits;
wiegand_message_t* packed = &value;
if(packed->Length != 37) return false; // Wrong length? Stop here.
uint32_t fc = get_linear_field(packed, 1, 16);
uint32_t cn = get_linear_field(packed, 17, 19);
bool valid =
(get_bit_by_position(packed, 0) == evenparity32(get_linear_field(packed, 1, 18))) &&
(get_bit_by_position(packed, 36) == oddparity32(get_linear_field(packed, 18, 18)));
if(valid) {
furi_string_cat_printf(description, "H10304\nFC: %ld CN: %ld\n", fc, cn);
return 1;
} else {
FURI_LOG_D(PLUGIN_APP_ID, "H10304 invalid");
}
return 0;
}
static int wiegand_H10302_parse(uint8_t bit_length, uint64_t bits, FuriString* description) {
wiegand_message_t value;
value.Length = bit_length;
value.Mid = bits >> 32;
value.Bot = bits;
wiegand_message_t* packed = &value;
if(packed->Length != 37) return false; // Wrong length? Stop here.
uint64_t cn = get_linear_field(packed, 1, 35);
bool valid =
(get_bit_by_position(packed, 0) == evenparity32(get_linear_field(packed, 1, 18))) &&
(get_bit_by_position(packed, 36) == oddparity32(get_linear_field(packed, 18, 18)));
if(valid) {
furi_string_cat_printf(description, "H10302\nCN: %lld\n", cn);
return 1;
} else {
FURI_LOG_D(PLUGIN_APP_ID, "H10302 invalid");
}
return 0;
}
static int wiegand_format_count(uint8_t bit_length, uint64_t bits) {
UNUSED(bit_length);
UNUSED(bits);
int count = 0;
FuriString* ignore = furi_string_alloc();
// NOTE: Always update the `total` and add to the wiegand_format_description function
// TODO: Make this into a function pointer array
count += wiegand_h10301_parse(bit_length, bits, ignore);
count += wiegand_C1k35s_parse(bit_length, bits, ignore);
count += wiegand_H10302_parse(bit_length, bits, ignore);
count += wiegand_H10304_parse(bit_length, bits, ignore);
int total = 4;
furi_string_free(ignore);
FURI_LOG_I(PLUGIN_APP_ID, "count: %i/%i", count, total);
return count;
}
static void wiegand_format_description(
uint8_t bit_length,
uint64_t bits,
size_t index,
FuriString* description) {
FURI_LOG_I(PLUGIN_APP_ID, "description %d", index);
// Turns out I did this wrong and trying to use the index means the results get repeated. Instead, just return the results for index == 0
if(index != 0) {
return;
}
wiegand_h10301_parse(bit_length, bits, description);
wiegand_C1k35s_parse(bit_length, bits, description);
wiegand_H10302_parse(bit_length, bits, description);
wiegand_H10304_parse(bit_length, bits, description);
}
/* Actual implementation of app<>plugin interface */
static const PluginWiegand plugin_wiegand = {
.name = "Plugin Wiegand",
.count = &wiegand_format_count,
.description = &wiegand_format_description,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor plugin_wiegand_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &plugin_wiegand,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* plugin_wiegand_ep(void) {
return &plugin_wiegand_descriptor;
}