mirror of
https://github.com/bettse/seader.git
synced 2026-05-14 19:05:28 +00:00
Merge pull request #36 from cindersocket/feat-snmp
HF functionality extraction and Seader hardening
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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`.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
-1
Submodule plugin deleted from dde6192486
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
SeaderCredentialTypeNone,
|
||||
SeaderCredentialTypePicopass,
|
||||
SeaderCredentialType14A,
|
||||
SeaderCredentialTypeMifareClassic,
|
||||
SeaderCredentialTypeVirtual,
|
||||
SeaderCredentialTypeConfig,
|
||||
} SeaderCredentialType;
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
struct SeaderWorker {
|
||||
FuriThread* thread;
|
||||
Storage* storage;
|
||||
uint8_t sam_version[2];
|
||||
FuriMessageQueue* messages;
|
||||
SeaderUartBridge* uart;
|
||||
SeaderWorkerCallback callback;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user