Merge pull request #37 from cindersocket/feat-multi-card-v2

Re-enable multi-card selection, and reduce RAM pressure
This commit is contained in:
CinderSocket
2026-03-26 23:55:08 -07:00
committed by GitHub
22 changed files with 621 additions and 176 deletions

View File

@@ -25,6 +25,7 @@ test-host:
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_hf_read_plan.c \
lib/host_tests/test_runtime_policy.c \
lib/host_tests/t1_test_stubs.c \
lib/host_tests/bit_buffer_mock.c \
@@ -40,6 +41,7 @@ test-host:
uhf_status_label.c \
uhf_tag_config_view.c \
uhf_snmp_probe.c \
seader_hf_read_plan.c \
runtime_policy.c \
-o build/host_tests/seader_tests
./build/host_tests/seader_tests

View File

@@ -8,7 +8,8 @@
#define TAG "APDU_Runner"
// Max length of firmware upgrade: 731 bytes
#define SEADER_APDU_MAX_LEN 732
#define SEADER_APDU_MAX_LEN 732
#define SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES 32U
void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) {
furi_check(seader);
@@ -27,6 +28,28 @@ void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) {
}
}
static void seader_apdu_runner_log_hex(const char* prefix, const uint8_t* data, size_t len) {
if(!data || len == 0U) {
FURI_LOG_I(TAG, "%s: <empty>", prefix);
return;
}
const size_t display_len =
len > SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES ? SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES : len;
char hex[(SEADER_APDU_RUNNER_HEX_LOG_MAX_BYTES * 2U) + 1U];
for(size_t i = 0; i < display_len; i++) {
snprintf(hex + (i * 2U), sizeof(hex) - (i * 2U), "%02x", data[i]);
}
hex[display_len * 2U] = '\0';
if(display_len < len) {
FURI_LOG_I(TAG, "%s len=%u: %s...", prefix, (unsigned)len, hex);
} else {
FURI_LOG_I(TAG, "%s len=%u: %s", prefix, (unsigned)len, hex);
}
}
bool seader_apdu_runner_send_next_line(Seader* seader) {
furi_check(seader);
SeaderWorker* seader_worker = seader->worker;
@@ -39,24 +62,19 @@ bool seader_apdu_runner_send_next_line(Seader* seader) {
apdu_log_get_next_log_str(seader->apdu_log, line);
size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes
uint8_t* apdu = malloc(len);
if(apdu == NULL) {
FURI_LOG_E(TAG, "Failed to allocate memory for APDU");
if(len > SEADER_UART_RX_BUF_SIZE || len > SEADER_APDU_MAX_LEN) {
FURI_LOG_E(TAG, "APDU length is too long");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
furi_string_free(line);
return false;
}
if(len > SEADER_UART_RX_BUF_SIZE) {
FURI_LOG_E(TAG, "APDU length is too long");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
free(apdu);
return false;
}
uint8_t apdu[SEADER_APDU_MAX_LEN];
if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) {
FURI_LOG_E(TAG, "Failed to convert line to number");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
free(apdu);
furi_string_free(line);
return false;
}
FURI_LOG_I(
@@ -77,7 +95,6 @@ bool seader_apdu_runner_send_next_line(Seader* seader) {
seader_ccid_XfrBlock(seader_uart, apdu, len);
}
furi_string_free(line);
free(apdu);
return true;
}
@@ -120,23 +137,7 @@ bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len)
}
if(r_len < SEADER_UART_RX_BUF_SIZE) {
char* display = malloc(r_len * 2 + 1);
if(display == NULL) {
FURI_LOG_E(TAG, "Failed to allocate memory for display");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
return false;
}
memset(display, 0, r_len * 2 + 1);
for(uint8_t i = 0; i < r_len; i++) {
snprintf(display + (i * 2), sizeof(display), "%02x", r_apdu[i]);
}
FURI_LOG_I(
TAG,
"APDU Runner <=: (%d/%d): %s",
apdu_runner_ctx->current_line + 1,
apdu_runner_ctx->total_lines,
display);
free(display);
seader_apdu_runner_log_hex("APDU Runner <=", r_apdu, r_len);
} else {
FURI_LOG_I(TAG, "APDU Runner <=: Response too long to display");
}
@@ -148,22 +149,24 @@ bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len)
if(furi_string_size(line) % 2 == 1) {
FURI_LOG_E(TAG, "APDU log file has odd number of characters");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
furi_string_free(line);
return false;
}
size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes
uint8_t* apdu = malloc(len);
if(apdu == NULL) {
FURI_LOG_E(TAG, "Failed to allocate memory for APDU");
if(len > SEADER_APDU_MAX_LEN) {
FURI_LOG_E(TAG, "Expected APDU length is too long");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
furi_string_free(line);
return false;
}
uint8_t apdu[SEADER_APDU_MAX_LEN];
if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) {
FURI_LOG_E(TAG, "Failed to convert line to byte array");
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
free(apdu);
// TODO: Send failed event
furi_string_free(line);
return false;
}
@@ -179,10 +182,8 @@ bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len)
apdu[len - 2],
apdu[len - 1]);
seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
free(apdu);
return false;
}
free(apdu);
// Check if we are at the end of the log
if(apdu_runner_ctx->current_line >= apdu_runner_ctx->total_lines) {

32
ccid.c
View File

@@ -1,7 +1,8 @@
#include "seader_i.h"
#include "ccid_logic.h"
#define TAG "SeaderCCID"
#define TAG "SeaderCCID"
#define SEADER_CCID_HEX_LOG_MAX_BYTES 32U
const uint8_t SAM_ATR[] =
{0x3b, 0x95, 0x96, 0x80, 0xb1, 0xfe, 0x55, 0x1f, 0xc7, 0x47, 0x72, 0x61, 0x63, 0x65, 0x13};
const uint8_t SAM_ATR2[] = {0x3b, 0x90, 0x96, 0x91, 0x81, 0xb1, 0xfe, 0x55, 0x1f, 0xc7, 0xd4};
@@ -37,6 +38,28 @@ static SeaderUartBridge* seader_ccid_active_uart(Seader* seader) {
return seader->worker->uart;
}
static void seader_ccid_log_hex(const char* prefix, const uint8_t* data, size_t len) {
if(!data || len == 0U) {
FURI_LOG_D(TAG, "%s: <empty>", prefix);
return;
}
const size_t display_len =
len > SEADER_CCID_HEX_LOG_MAX_BYTES ? SEADER_CCID_HEX_LOG_MAX_BYTES : len;
char hex[(SEADER_CCID_HEX_LOG_MAX_BYTES * 2U) + 1U];
for(size_t i = 0; i < display_len; i++) {
snprintf(hex + (i * 2U), sizeof(hex) - (i * 2U), "%02x", data[i]);
}
hex[display_len * 2U] = '\0';
if(display_len < len) {
FURI_LOG_D(TAG, "%s len=%u: %s...", prefix, (unsigned)len, hex);
} else {
FURI_LOG_D(TAG, "%s len=%u: %s", prefix, (unsigned)len, hex);
}
}
void seader_ccid_IccPowerOn(SeaderUartBridge* seader_uart, uint8_t slot) {
SeaderCcidSlotState* slot_state = seader_ccid_slot_state(seader_uart, slot);
if(slot_state->powered) {
@@ -239,12 +262,7 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
message.consumed = 0;
SeaderCcidState* ccid_state = seader_ccid_state(seader_uart);
char* display = malloc(cmd_len * 2 + 1);
for(size_t i = 0; i < cmd_len; i++) {
snprintf(display + (i * 2), sizeof(display), "%02x", cmd[i]);
}
FURI_LOG_D(TAG, "seader_ccid_process %d: %s", cmd_len, display);
free(display);
seader_ccid_log_hex("seader_ccid_process", cmd, cmd_len);
if(cmd_len == 2) {
if(cmd[0] == CCID_MESSAGE_TYPE_RDR_TO_PC_NOTIFY_SLOT_CHANGE) {

View File

@@ -26,6 +26,7 @@
#define HF_PLUGIN_POLLER_MAX_FWT (200000U)
#define HF_PLUGIN_POLLER_MAX_BUFFER_SIZE (258U)
#define HF_PLUGIN_MAX_ATS_SIZE 33U
// ATS bit definitions
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
@@ -246,7 +247,7 @@ static void plugin_hf_iso14443a_transmit(
return;
}
BitBuffer* tx_buffer = bit_buffer_alloc(len + 1);
BitBuffer* tx_buffer = bit_buffer_alloc(len + 1U);
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");
@@ -477,13 +478,13 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v
}
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;
}
uint8_t ats[HF_PLUGIN_MAX_ATS_SIZE] = {0};
if(iso_data->ats_data.tl > 1) {
if(sizeof(ats) < 4U + t1_tk_size) {
FURI_LOG_E(TAG, "ATS buffer too small: %u", (unsigned)(4U + t1_tk_size));
ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail);
return NfcCommandStop;
}
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;
@@ -503,7 +504,6 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v
ctx->api->send_card_detected(
ctx->host_ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_len);
FURI_LOG_D(TAG, "14A cardDetected delivered uid_len=%u ats_len=%u", uid_len, ats_len);
free(ats);
ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation);
} else if(stage == PluginHfStageConversation) {
FURI_LOG_D(TAG, "14A enter conversation");

View File

@@ -12,7 +12,7 @@
#include "t_1_logic.h"
/* Keep the host harness aligned with the production UART scratchpad size. */
#define SEADER_UART_RX_BUF_SIZE (300)
#define SEADER_UART_RX_BUF_SIZE (272)
#define FURI_LOG_W(tag, fmt, ...) ((void)0)
#define furi_check(expr) \
do { \
@@ -32,7 +32,6 @@ typedef enum { SeaderWorkerEventSamPresent = 53 } SeaderWorkerEvent;
typedef void (*SeaderWorkerCallback)(uint32_t event, void* context);
struct SeaderUartBridge {
uint8_t rx_buf[SEADER_UART_RX_BUF_SIZE];
uint8_t tx_buf[SEADER_UART_RX_BUF_SIZE];
size_t tx_len;
uint8_t T;

View File

@@ -0,0 +1,118 @@
#include "munit.h"
#include "seader_hf_read_plan.h"
static MunitResult test_selected_type_starts_immediately(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const SeaderCredentialType detected_types[] = {
SeaderCredentialType14A,
SeaderCredentialTypeMifareClassic,
};
const SeaderHfReadPlan plan = seader_hf_read_plan_build(
SeaderCredentialTypePicopass, detected_types, 2U);
munit_assert_int(plan.decision, ==, SeaderHfReadDecisionStartRead);
munit_assert_int(plan.type_to_read, ==, SeaderCredentialTypePicopass);
munit_assert_size(plan.detected_type_count, ==, 0U);
return MUNIT_OK;
}
static MunitResult test_no_detected_types_continues_polling(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const SeaderHfReadPlan plan = seader_hf_read_plan_build(SeaderCredentialTypeNone, NULL, 0U);
munit_assert_int(plan.decision, ==, SeaderHfReadDecisionContinuePolling);
munit_assert_int(plan.type_to_read, ==, SeaderCredentialTypeNone);
munit_assert_size(plan.detected_type_count, ==, 0U);
return MUNIT_OK;
}
static MunitResult test_single_detected_type_starts_read(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const SeaderCredentialType detected_types[] = {SeaderCredentialType14A};
const SeaderHfReadPlan plan =
seader_hf_read_plan_build(SeaderCredentialTypeNone, detected_types, 1U);
munit_assert_int(plan.decision, ==, SeaderHfReadDecisionStartRead);
munit_assert_int(plan.type_to_read, ==, SeaderCredentialType14A);
munit_assert_size(plan.detected_type_count, ==, 1U);
munit_assert_int(plan.detected_types[0], ==, SeaderCredentialType14A);
return MUNIT_OK;
}
static MunitResult test_multiple_detected_types_requests_selection(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const SeaderCredentialType detected_types[] = {
SeaderCredentialType14A,
SeaderCredentialTypeMifareClassic,
SeaderCredentialTypePicopass,
};
const SeaderHfReadPlan plan =
seader_hf_read_plan_build(SeaderCredentialTypeNone, detected_types, 3U);
munit_assert_int(plan.decision, ==, SeaderHfReadDecisionSelectType);
munit_assert_int(plan.type_to_read, ==, SeaderCredentialTypeNone);
munit_assert_size(plan.detected_type_count, ==, 3U);
munit_assert_int(plan.detected_types[0], ==, SeaderCredentialType14A);
munit_assert_int(plan.detected_types[1], ==, SeaderCredentialTypeMifareClassic);
munit_assert_int(plan.detected_types[2], ==, SeaderCredentialTypePicopass);
return MUNIT_OK;
}
static MunitResult test_detected_types_are_deduped_and_clamped(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
const SeaderCredentialType detected_types[] = {
SeaderCredentialType14A,
SeaderCredentialTypeMifareClassic,
SeaderCredentialType14A,
SeaderCredentialTypeNone,
SeaderCredentialTypePicopass,
SeaderCredentialTypeConfig,
};
const SeaderHfReadPlan plan =
seader_hf_read_plan_build(SeaderCredentialTypeNone, detected_types, 6U);
munit_assert_int(plan.decision, ==, SeaderHfReadDecisionSelectType);
munit_assert_size(plan.detected_type_count, ==, 3U);
munit_assert_int(plan.detected_types[0], ==, SeaderCredentialType14A);
munit_assert_int(plan.detected_types[1], ==, SeaderCredentialTypeMifareClassic);
munit_assert_int(plan.detected_types[2], ==, SeaderCredentialTypePicopass);
return MUNIT_OK;
}
static MunitTest test_hf_read_plan_cases[] = {
{(char*)"/selected-type", test_selected_type_starts_immediately, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/none-detected", test_no_detected_types_continues_polling, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/single-detected", test_single_detected_type_starts_read, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/multiple-detected", test_multiple_detected_types_requests_selection, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{(char*)"/dedupe-and-clamp", test_detected_types_are_deduped_and_clamped, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL},
{NULL, NULL, NULL, NULL, 0, NULL},
};
MunitSuite test_hf_read_plan_suite = {
"",
test_hf_read_plan_cases,
NULL,
1,
MUNIT_SUITE_OPTION_NONE,
};

View File

@@ -8,6 +8,7 @@ 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_hf_read_plan_suite;
extern MunitSuite test_runtime_policy_suite;
int main(int argc, char* argv[]) {
@@ -20,6 +21,7 @@ int main(int argc, char* argv[]) {
{"/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},
{"/hf-read-plan", test_hf_read_plan_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},
};

View File

@@ -98,9 +98,13 @@ static void seader_reset_cached_sam_metadata(Seader* seader) {
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;
uint8_t* message = seader_scratch_alloc(seader, SEADER_UART_RX_BUF_SIZE, _Alignof(uint8_t));
size_t message_len = 0U;
if(!message) {
return false;
}
if(!seader_uhf_snmp_probe_build_next_request(
&seader->snmp_probe,
scratch,
@@ -351,8 +355,14 @@ PicopassError seader_worker_fake_epurse_update(BitBuffer* tx_buffer, BitBuffer*
void seader_virtual_picopass_state_machine(Seader* seader, uint8_t* buffer, size_t len) {
BitBuffer* tx_buffer = bit_buffer_alloc(len);
bit_buffer_append_bytes(tx_buffer, buffer, len);
BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate virtual Picopass buffers");
if(tx_buffer) bit_buffer_free(tx_buffer);
if(rx_buffer) bit_buffer_free(rx_buffer);
return;
}
bit_buffer_append_bytes(tx_buffer, buffer, len);
uint8_t config[PICOPASS_BLOCK_LEN] = {0x12, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0xff, 0x3c};
uint8_t sr_aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xff, 0xff, 0xff, 0xFF, 0xFf, 0xff, 0xFF};
@@ -1204,9 +1214,18 @@ void seader_iso15693_transmit(
BitBuffer* tx_buffer = bit_buffer_alloc(len);
BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
PicopassError error = PicopassErrorNone;
if(!tx_buffer || !rx_buffer) {
FURI_LOG_E(TAG, "Failed to allocate Picopass 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);
@@ -1234,7 +1253,6 @@ void seader_iso15693_transmit(
bit_buffer_get_size_bytes(rx_buffer));
} while(false);
bit_buffer_free(tx_buffer);
bit_buffer_free(rx_buffer);
}

View File

@@ -4,6 +4,46 @@
#define TAG "SeaderCredentialInfoScene"
static void seader_scene_credential_info_alloc_strings(Seader* seader) {
furi_check(seader);
if(!seader->temp_string1) {
seader->temp_string1 = furi_string_alloc();
furi_check(seader->temp_string1);
}
if(!seader->temp_string2) {
seader->temp_string2 = furi_string_alloc();
furi_check(seader->temp_string2);
}
if(!seader->temp_string3) {
seader->temp_string3 = furi_string_alloc();
furi_check(seader->temp_string3);
}
if(!seader->temp_string4) {
seader->temp_string4 = furi_string_alloc();
furi_check(seader->temp_string4);
}
}
static void seader_scene_credential_info_free_strings(Seader* seader) {
furi_check(seader);
if(seader->temp_string1) {
furi_string_free(seader->temp_string1);
seader->temp_string1 = NULL;
}
if(seader->temp_string2) {
furi_string_free(seader->temp_string2);
seader->temp_string2 = NULL;
}
if(seader->temp_string3) {
furi_string_free(seader->temp_string3);
seader->temp_string3 = NULL;
}
if(seader->temp_string4) {
furi_string_free(seader->temp_string4);
seader->temp_string4 = NULL;
}
}
static bool seader_credential_is_picopass_sio_context(const SeaderCredential* credential) {
return credential && (credential->type == SeaderCredentialTypePicopass ||
(credential->has_pacs_media_type &&
@@ -26,11 +66,12 @@ void seader_scene_credential_info_on_enter(void* context) {
seader_wiegand_plugin_acquire(seader);
Widget* widget = seader->widget;
// Use reusable strings instead of allocating new ones
seader_scene_credential_info_alloc_strings(seader);
FuriString* type_str = seader->temp_string1;
FuriString* bitlength_str = seader->temp_string2;
FuriString* credential_str = seader->temp_string3;
FuriString* sio_str = seader->temp_string4;
char sio_label[SEADER_TEXT_STORE_SIZE + 1] = {0};
furi_string_set(credential_str, "");
furi_string_set(bitlength_str, "");
@@ -80,9 +121,9 @@ void seader_scene_credential_info_on_enter(void* context) {
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);
sio_label,
sizeof(sio_label))) {
furi_string_set(sio_str, sio_label);
widget_add_string_element(
widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
}
@@ -118,5 +159,6 @@ void seader_scene_credential_info_on_exit(void* context) {
// Clear views
widget_reset(seader->widget);
seader_scene_credential_info_free_strings(seader);
seader_wiegand_plugin_release(seader);
}

View File

@@ -1,11 +1,24 @@
#include "../seader_i.h"
#include <dolphin/dolphin.h>
static void seader_scene_formats_alloc_strings(Seader* seader) {
furi_check(seader);
if(!seader->text_box_store) {
seader->text_box_store = furi_string_alloc();
furi_check(seader->text_box_store);
}
if(!seader->temp_string1) {
seader->temp_string1 = furi_string_alloc();
furi_check(seader->temp_string1);
}
}
void seader_scene_formats_on_enter(void* context) {
Seader* seader = context;
PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL;
SeaderCredential* credential = seader->credential;
seader_scene_formats_alloc_strings(seader);
FuriString* str = seader->text_box_store;
furi_string_reset(str);
@@ -51,5 +64,13 @@ void seader_scene_formats_on_exit(void* context) {
// Clear views
text_box_reset(seader->text_box);
if(seader->text_box_store) {
furi_string_free(seader->text_box_store);
seader->text_box_store = NULL;
}
if(seader->temp_string1) {
furi_string_free(seader->temp_string1);
seader->temp_string1 = NULL;
}
seader_wiegand_plugin_release(seader);
}

View File

@@ -4,6 +4,46 @@
#define TAG "SeaderSceneReadCardSuccess"
static void seader_scene_read_card_success_alloc_strings(Seader* seader) {
furi_check(seader);
if(!seader->temp_string1) {
seader->temp_string1 = furi_string_alloc();
furi_check(seader->temp_string1);
}
if(!seader->temp_string2) {
seader->temp_string2 = furi_string_alloc();
furi_check(seader->temp_string2);
}
if(!seader->temp_string3) {
seader->temp_string3 = furi_string_alloc();
furi_check(seader->temp_string3);
}
if(!seader->temp_string4) {
seader->temp_string4 = furi_string_alloc();
furi_check(seader->temp_string4);
}
}
static void seader_scene_read_card_success_free_strings(Seader* seader) {
furi_check(seader);
if(seader->temp_string1) {
furi_string_free(seader->temp_string1);
seader->temp_string1 = NULL;
}
if(seader->temp_string2) {
furi_string_free(seader->temp_string2);
seader->temp_string2 = NULL;
}
if(seader->temp_string3) {
furi_string_free(seader->temp_string3);
seader->temp_string3 = NULL;
}
if(seader->temp_string4) {
furi_string_free(seader->temp_string4);
seader->temp_string4 = NULL;
}
}
static bool seader_credential_is_picopass_sio_context(const SeaderCredential* credential) {
return credential && (credential->type == SeaderCredentialTypePicopass ||
(credential->has_pacs_media_type &&
@@ -28,11 +68,12 @@ void seader_scene_read_card_success_on_enter(void* context) {
PluginWiegand* plugin = seader_wiegand_plugin_acquire(seader) ? seader->plugin_wiegand : NULL;
Widget* widget = seader->widget;
// Use reusable strings instead of allocating new ones
seader_scene_read_card_success_alloc_strings(seader);
FuriString* type_str = seader->temp_string1;
FuriString* bitlength_str = seader->temp_string2;
FuriString* credential_str = seader->temp_string3;
FuriString* sio_str = seader->temp_string4;
char sio_label[SEADER_TEXT_STORE_SIZE + 1] = {0};
dolphin_deed(DolphinDeedNfcReadSuccess);
@@ -117,12 +158,12 @@ void seader_scene_read_card_success_on_enter(void* context) {
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) {
sio_label,
sizeof(sio_label))) {
if(strcmp(sio_label, "+SIO(?)") == 0) {
FURI_LOG_E(TAG, "Unknown SIO start block: %d", credential->sio_start_block);
}
furi_string_set(sio_str, seader->text_store);
furi_string_set(sio_str, sio_label);
widget_add_string_element(
widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
}
@@ -165,5 +206,6 @@ void seader_scene_read_card_success_on_exit(void* context) {
// Clear view
widget_reset(seader->widget);
seader_scene_read_card_success_free_strings(seader);
seader_wiegand_plugin_release(seader);
}

View File

@@ -3,6 +3,38 @@
#define TAG "SeaderSamInfoScene"
static void seader_scene_sam_info_alloc_strings(Seader* seader) {
furi_check(seader);
if(!seader->temp_string1) {
seader->temp_string1 = furi_string_alloc();
furi_check(seader->temp_string1);
}
if(!seader->temp_string2) {
seader->temp_string2 = furi_string_alloc();
furi_check(seader->temp_string2);
}
if(!seader->temp_string3) {
seader->temp_string3 = furi_string_alloc();
furi_check(seader->temp_string3);
}
}
static void seader_scene_sam_info_free_strings(Seader* seader) {
furi_check(seader);
if(seader->temp_string1) {
furi_string_free(seader->temp_string1);
seader->temp_string1 = NULL;
}
if(seader->temp_string2) {
furi_string_free(seader->temp_string2);
seader->temp_string2 = NULL;
}
if(seader->temp_string3) {
furi_string_free(seader->temp_string3);
seader->temp_string3 = NULL;
}
}
void seader_scene_sam_info_widget_callback(GuiButtonType result, InputType type, void* context) {
Seader* seader = context;
if(type == InputTypeShort) {
@@ -14,7 +46,7 @@ void seader_scene_sam_info_on_enter(void* context) {
Seader* seader = context;
Widget* widget = seader->widget;
// Use reusable string instead of allocating new one
seader_scene_sam_info_alloc_strings(seader);
FuriString* fw_str = seader->temp_string1;
FuriString* info_str = seader->temp_string2;
FuriString* uhf_str = seader->temp_string3;
@@ -66,4 +98,5 @@ void seader_scene_sam_info_on_exit(void* context) {
// Clear views
widget_reset(seader->widget);
seader_scene_sam_info_free_strings(seader);
}

View File

@@ -18,22 +18,22 @@ void seader_scene_save_name_on_enter(void* context) {
TextInput* text_input = seader->text_input;
bool cred_name_empty = false;
if(!strcmp(seader->credential->name, "")) {
name_generator_make_random(seader->text_store, sizeof(seader->text_store));
name_generator_make_random(seader->save_name_buf, sizeof(seader->save_name_buf));
cred_name_empty = true;
} else {
seader_text_store_set(seader, seader->credential->name);
strlcpy(seader->save_name_buf, seader->credential->name, sizeof(seader->save_name_buf));
}
text_input_set_header_text(text_input, "Name the credential");
text_input_set_result_callback(
text_input,
seader_scene_save_name_text_input_callback,
seader,
seader->text_store,
seader->save_name_buf,
SEADER_CRED_NAME_MAX_LEN,
cred_name_empty);
// Use reusable string instead of allocating new one
FuriString* folder_path = seader->temp_string1;
FuriString* folder_path = furi_string_alloc();
furi_check(folder_path);
if(furi_string_end_with(seader->credential->load_path, SEADER_APP_EXTENSION)) {
path_extract_dirname(furi_string_get_cstr(seader->credential->load_path), folder_path);
} else {
@@ -45,8 +45,7 @@ void seader_scene_save_name_on_enter(void* context) {
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewTextInput);
// No need to free folder_path as it's reused from seader struct
furi_string_free(folder_path);
}
bool seader_scene_save_name_on_event(void* context, SceneManagerEvent event) {
@@ -60,8 +59,9 @@ bool seader_scene_save_name_on_event(void* context, SceneManagerEvent event) {
FURI_LOG_D(TAG, "Delete existing named credential [%s]", seader->credential->name);
seader_credential_delete(seader->credential, true);
}
strlcpy(seader->credential->name, seader->text_store, strlen(seader->text_store) + 1);
if(seader_credential_save(seader->credential, seader->text_store)) {
strlcpy(
seader->credential->name, seader->save_name_buf, sizeof(seader->credential->name));
if(seader_credential_save(seader->credential, seader->save_name_buf)) {
scene_manager_next_scene(seader->scene_manager, SeaderSceneSaveSuccess);
consumed = true;
} else {

View File

@@ -533,18 +533,18 @@ Seader* seader_alloc() {
seader->text_box = text_box_alloc();
view_dispatcher_add_view(
seader->view_dispatcher, SeaderViewTextBox, text_box_get_view(seader->text_box));
seader->text_box_store = furi_string_alloc();
seader->text_box_store = NULL;
// Custom Widget
seader->widget = widget_alloc();
view_dispatcher_add_view(
seader->view_dispatcher, SeaderViewWidget, widget_get_view(seader->widget));
// Allocate reusable strings for scene optimization
seader->temp_string1 = furi_string_alloc();
seader->temp_string2 = furi_string_alloc();
seader->temp_string3 = furi_string_alloc();
seader->temp_string4 = furi_string_alloc();
// Scene strings are allocated lazily by the scenes that need them.
seader->temp_string1 = NULL;
seader->temp_string2 = NULL;
seader->temp_string3 = NULL;
seader->temp_string4 = NULL;
seader->plugin_manager = NULL;
seader->plugin_wiegand = NULL;
@@ -618,17 +618,19 @@ void seader_free(Seader* seader) {
// TextBox
view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextBox);
text_box_free(seader->text_box);
furi_string_free(seader->text_box_store);
if(seader->text_box_store) {
furi_string_free(seader->text_box_store);
}
// Custom Widget
view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewWidget);
widget_free(seader->widget);
// Free reusable strings
furi_string_free(seader->temp_string1);
furi_string_free(seader->temp_string2);
furi_string_free(seader->temp_string3);
furi_string_free(seader->temp_string4);
if(seader->temp_string1) furi_string_free(seader->temp_string1);
if(seader->temp_string2) furi_string_free(seader->temp_string2);
if(seader->temp_string3) furi_string_free(seader->temp_string3);
if(seader->temp_string4) furi_string_free(seader->temp_string4);
// View Dispatcher
view_dispatcher_free(seader->view_dispatcher);
@@ -647,19 +649,6 @@ void seader_free(Seader* seader) {
free(seader);
}
void seader_text_store_set(Seader* seader, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(seader->text_store, sizeof(seader->text_store), text, args);
va_end(args);
}
void seader_text_store_clear(Seader* seader) {
memset(seader->text_store, 0, sizeof(seader->text_store));
}
static const NotificationSequence seader_sequence_blink_start_blue = {
&message_blink_start_10,
&message_blink_set_color_blue,

View File

@@ -12,7 +12,7 @@
#include "t_1_logic.h"
// https://ww1.microchip.com/downloads/en/DeviceDoc/00001561C.pdf
#define SEADER_UART_RX_BUF_SIZE (300)
#define SEADER_UART_RX_BUF_SIZE (272)
#define SEADER_CCID_SLOT_COUNT (2U)
typedef struct BitBuffer BitBuffer;
@@ -56,7 +56,6 @@ struct SeaderUartBridge {
SeaderUartState st;
uint8_t rx_buf[SEADER_UART_RX_BUF_SIZE];
uint8_t tx_buf[SEADER_UART_RX_BUF_SIZE];
size_t tx_len;

47
seader_hf_read_plan.c Normal file
View File

@@ -0,0 +1,47 @@
#include "seader_hf_read_plan.h"
#define SEADER_HF_READ_PLAN_MAX_TYPES 3U
static void seader_hf_read_plan_add_type(SeaderHfReadPlan* plan, SeaderCredentialType type) {
if(type == SeaderCredentialTypeNone) {
return;
}
for(size_t i = 0; i < plan->detected_type_count; i++) {
if(plan->detected_types[i] == type) {
return;
}
}
if(plan->detected_type_count < SEADER_HF_READ_PLAN_MAX_TYPES) {
plan->detected_types[plan->detected_type_count++] = type;
}
}
SeaderHfReadPlan seader_hf_read_plan_build(
SeaderCredentialType selected_type,
const SeaderCredentialType* detected_types,
size_t detected_type_count) {
SeaderHfReadPlan plan = {0};
if(selected_type != SeaderCredentialTypeNone) {
plan.decision = SeaderHfReadDecisionStartRead;
plan.type_to_read = selected_type;
return plan;
}
for(size_t i = 0; i < detected_type_count; i++) {
seader_hf_read_plan_add_type(&plan, detected_types[i]);
}
if(plan.detected_type_count == 1U) {
plan.decision = SeaderHfReadDecisionStartRead;
plan.type_to_read = plan.detected_types[0];
} else if(plan.detected_type_count > 1U) {
plan.decision = SeaderHfReadDecisionSelectType;
} else {
plan.decision = SeaderHfReadDecisionContinuePolling;
}
return plan;
}

23
seader_hf_read_plan.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stddef.h>
#include "seader_credential_type.h"
typedef enum {
SeaderHfReadDecisionContinuePolling,
SeaderHfReadDecisionStartRead,
SeaderHfReadDecisionSelectType,
} SeaderHfReadDecision;
typedef struct {
SeaderHfReadDecision decision;
SeaderCredentialType type_to_read;
SeaderCredentialType detected_types[3];
size_t detected_type_count;
} SeaderHfReadPlan;
SeaderHfReadPlan seader_hf_read_plan_build(
SeaderCredentialType selected_type,
const SeaderCredentialType* detected_types,
size_t detected_type_count);

View File

@@ -158,7 +158,7 @@ struct Seader {
SeaderScratch scratch;
SeaderHfModeContext* hf_mode;
char text_store[SEADER_TEXT_STORE_SIZE + 1];
char save_name_buf[SEADER_CRED_NAME_MAX_LEN + 1];
char read_error[SEADER_TEXT_STORE_SIZE + 1];
FuriString* text_box_store;
@@ -214,10 +214,6 @@ typedef enum {
SeaderViewUart,
} SeaderView;
void seader_text_store_set(Seader* seader, const char* text, ...);
void seader_text_store_clear(Seader* seader);
void seader_blink_start(Seader* seader);
void seader_blink_stop(Seader* seader);

View File

@@ -1,4 +1,5 @@
#include "seader_worker_i.h"
#include "seader_hf_read_plan.h"
#include "trace_log.h"
#include <flipper_format/flipper_format.h>
@@ -6,8 +7,9 @@
#define TAG "SeaderWorker"
#define APDU_HEADER_LEN 5
#define ASN1_PREFIX 6
#define APDU_HEADER_LEN 5
#define ASN1_PREFIX 6
#define SEADER_HEX_LOG_MAX_BYTES 32U
// #define ASN1_DEBUG true
#define RFAL_PICOPASS_TXRX_FLAGS \
@@ -31,6 +33,49 @@ typedef struct {
volatile bool detected;
} SeaderPicopassDetectContext;
static void seader_worker_reset_apdu_slots(SeaderWorker* seader_worker) {
furi_assert(seader_worker);
memset(seader_worker->apdu_slot_in_use, 0, sizeof(seader_worker->apdu_slot_in_use));
if(seader_worker->apdu_slots) {
memset(
seader_worker->apdu_slots,
0,
sizeof(*seader_worker->apdu_slots) * SEADER_WORKER_APDU_SLOT_COUNT);
}
}
static bool seader_worker_claim_apdu_slot(SeaderWorker* seader_worker, uint8_t* slot_index) {
furi_assert(seader_worker);
furi_assert(slot_index);
for(uint8_t i = 0; i < SEADER_WORKER_APDU_SLOT_COUNT; i++) {
if(!seader_worker->apdu_slot_in_use[i]) {
seader_worker->apdu_slot_in_use[i] = true;
*slot_index = i;
return true;
}
}
return false;
}
static void seader_worker_release_apdu_slot(SeaderWorker* seader_worker, uint8_t slot_index) {
furi_assert(seader_worker);
furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT);
seader_worker->apdu_slot_in_use[slot_index] = false;
if(seader_worker->apdu_slots) {
seader_worker->apdu_slots[slot_index].len = 0U;
}
}
static bool
seader_worker_dequeue_apdu(SeaderWorker* seader_worker, uint8_t* slot_index, FuriWait timeout) {
furi_assert(seader_worker);
furi_assert(slot_index);
return furi_message_queue_get(seader_worker->messages, slot_index, timeout) == FuriStatusOk;
}
static void seader_worker_clear_active_card(Seader* seader, const char* reason) {
if(!seader) {
return;
@@ -42,6 +87,27 @@ static void seader_worker_clear_active_card(Seader* seader, const char* reason)
}
}
static void seader_worker_log_hex(const char* prefix, const uint8_t* data, size_t len) {
if(!data || len == 0U) {
FURI_LOG_I(TAG, "%s: <empty>", prefix);
return;
}
const size_t display_len = len > SEADER_HEX_LOG_MAX_BYTES ? SEADER_HEX_LOG_MAX_BYTES : len;
char hex[(SEADER_HEX_LOG_MAX_BYTES * 2U) + 1U];
for(size_t i = 0; i < display_len; i++) {
snprintf(hex + (i * 2U), sizeof(hex) - (i * 2U), "%02x", data[i]);
}
hex[display_len * 2U] = '\0';
if(display_len < len) {
FURI_LOG_I(TAG, "%s len=%u: %s...", prefix, (unsigned)len, hex);
} else {
FURI_LOG_I(TAG, "%s len=%u: %s", prefix, (unsigned)len, hex);
}
}
static NfcCommand
seader_worker_picopass_detect_callback(PicopassPollerEvent event, void* context) {
SeaderPicopassDetectContext* detect_context = context;
@@ -176,15 +242,32 @@ seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) {
SeaderWorker* seader_worker_alloc() {
SeaderWorker* seader_worker = calloc(1, sizeof(SeaderWorker));
if(!seader_worker) {
return NULL;
}
// Worker thread attributes
seader_worker->thread =
furi_thread_alloc_ex("SeaderWorker", 8192, seader_worker_task, seader_worker);
seader_worker->messages = furi_message_queue_alloc(3, sizeof(SeaderAPDU));
furi_thread_alloc_ex("SeaderWorker", 5120, seader_worker_task, seader_worker);
seader_worker->messages = furi_message_queue_alloc(2, sizeof(uint8_t));
seader_worker->apdu_slots = calloc(SEADER_WORKER_APDU_SLOT_COUNT, sizeof(SeaderAPDU));
if(!seader_worker->thread || !seader_worker->messages || !seader_worker->apdu_slots) {
if(seader_worker->thread) {
furi_thread_free(seader_worker->thread);
}
if(seader_worker->messages) {
furi_message_queue_free(seader_worker->messages);
}
free(seader_worker->apdu_slots);
free(seader_worker);
return NULL;
}
seader_worker->callback = NULL;
seader_worker->context = NULL;
seader_worker->storage = furi_record_open(RECORD_STORAGE);
seader_worker_reset_apdu_slots(seader_worker);
seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
@@ -196,6 +279,7 @@ void seader_worker_free(SeaderWorker* seader_worker) {
furi_thread_free(seader_worker->thread);
furi_message_queue_free(seader_worker->messages);
free(seader_worker->apdu_slots);
furi_record_close(RECORD_STORAGE);
@@ -281,6 +365,7 @@ 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_reset_apdu_slots(seader_worker);
seader_worker->stage = SeaderPollerEventTypeCardDetect;
}
@@ -322,12 +407,23 @@ bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len)
seader_trace(
TAG, "enqueue len=%d stage=%d sam=%d", len, seader_worker->stage, seader->samCommand);
uint32_t space = furi_message_queue_get_space(seader_worker->messages);
if(space > 0) {
SeaderAPDU seaderApdu = {};
seaderApdu.len = len;
memcpy(seaderApdu.buf, apdu, len);
if(space > 0 && len <= SEADER_POLLER_MAX_BUFFER_SIZE) {
uint8_t slot_index = 0U;
if(!seader_worker_claim_apdu_slot(seader_worker, &slot_index)) {
FURI_LOG_W(TAG, "No free APDU slot for len=%u", (unsigned)len);
return true;
}
furi_message_queue_put(seader_worker->messages, &seaderApdu, FuriWaitForever);
seader_worker->apdu_slots[slot_index].len = len;
memcpy(seader_worker->apdu_slots[slot_index].buf, apdu, len);
if(furi_message_queue_put(seader_worker->messages, &slot_index, FuriWaitForever) !=
FuriStatusOk) {
FURI_LOG_W(TAG, "Failed to queue APDU slot=%u", slot_index);
seader_worker_release_apdu_slot(seader_worker, slot_index);
}
} else if(len > SEADER_POLLER_MAX_BUFFER_SIZE) {
FURI_LOG_W(TAG, "Drop oversized SAM message len=%u", (unsigned)len);
}
}
return true;
@@ -347,12 +443,7 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l
return seader_apdu_runner_response(seader, apdu, len);
}
char* display = malloc(len * 2 + 1);
memset(display, 0, len * 2 + 1);
for(size_t i = 0; i < len; i++) {
snprintf(display + (i * 2), sizeof(display), "%02x", apdu[i]);
}
FURI_LOG_I(TAG, "APDU: %s", display);
seader_worker_log_hex("APDU", apdu, len);
seader_trace(
TAG,
"sam apdu len=%lu stage=%d sam=%d state=%d intent=%d sw=%02x%02x",
@@ -363,7 +454,6 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l
seader->sam_intent,
apdu[len - 2],
apdu[len - 1]);
free(display);
uint8_t SW1 = apdu[len - 2];
uint8_t SW2 = apdu[len - 1];
@@ -407,21 +497,23 @@ void seader_worker_virtual_credential(Seader* seader) {
if(count > 0) {
FURI_LOG_I(TAG, "Dequeue SAM message [%ld messages]", count);
SeaderAPDU seaderApdu = {};
FuriStatus status =
furi_message_queue_get(seader_worker->messages, &seaderApdu, FuriWaitForever);
if(status != FuriStatusOk) {
FURI_LOG_W(TAG, "furi_message_queue_get fail %d", status);
uint8_t slot_index = 0U;
if(!seader_worker_dequeue_apdu(seader_worker, &slot_index, FuriWaitForever)) {
FURI_LOG_W(TAG, "furi_message_queue_get fail");
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
continue;
}
furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT);
SeaderAPDU* seaderApdu = &seader_worker->apdu_slots[slot_index];
if(seader_process_success_response_i(
seader, seaderApdu.buf, seaderApdu.len, true, NULL)) {
seader, seaderApdu->buf, seaderApdu->len, true, NULL)) {
// no-op
} else {
FURI_LOG_I(TAG, "Response false");
running = false;
}
seader_worker_release_apdu_slot(seader_worker, slot_index);
} else {
dead_loops--;
running = (dead_loops > 0);
@@ -477,19 +569,15 @@ void seader_worker_reading(Seader* seader) {
SeaderWorker* seader_worker = seader->worker;
FURI_LOG_I(TAG, "Reading loop started");
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;
}
furi_check(seader_hf_plugin_acquire(seader));
furi_check(seader->plugin_hf);
furi_check(seader->hf_plugin_ctx);
while(seader_worker->state == SeaderWorkerStateReading) {
bool detected = false;
SeaderPollerEventType result_stage = SeaderPollerEventTypeFail;
SeaderCredentialType type_to_read = seader_hf_mode_get_selected_read_type(seader);
SeaderHfReadPlan read_plan = {0};
FURI_LOG_D(TAG, "HF loop selected type=%d stage=%d", type_to_read, seader_worker->stage);
if(type_to_read == SeaderCredentialTypeNone) {
@@ -497,22 +585,23 @@ void seader_worker_reading(Seader* seader) {
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) {
seader_hf_mode_set_detected_types(seader, detected_types, detected_type_count);
if(seader_worker->callback) {
seader_worker->callback(
SeaderWorkerEventSelectCardType, seader_worker->context);
}
break;
} else if(detected_type_count == 1) {
type_to_read = detected_types[0];
}
read_plan =
seader_hf_read_plan_build(type_to_read, detected_types, detected_type_count);
} else {
read_plan = seader_hf_read_plan_build(type_to_read, NULL, 0U);
}
if(type_to_read != SeaderCredentialTypeNone) {
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(read_plan.decision == SeaderHfReadDecisionSelectType) {
seader_hf_mode_set_detected_types(
seader, read_plan.detected_types, read_plan.detected_type_count);
if(seader_worker->callback) {
seader_worker->callback(SeaderWorkerEventSelectCardType, seader_worker->context);
}
break;
} else if(read_plan.decision == SeaderHfReadDecisionStartRead) {
FURI_LOG_I(TAG, "HF start read for type=%d", read_plan.type_to_read);
detected = seader->plugin_hf->start_read_for_type(
seader->hf_plugin_ctx, read_plan.type_to_read);
if(detected) {
seader->hf_session_state = SeaderHfSessionStateActive;
}
@@ -561,14 +650,16 @@ void seader_worker_run_hf_conversation(Seader* seader) {
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 = {};
uint8_t slot_index = 0U;
// Short wait for SAM message
FuriStatus status = furi_message_queue_get(seader_worker->messages, &seaderApdu, 100);
FuriStatus status = furi_message_queue_get(seader_worker->messages, &slot_index, 100);
if(status == FuriStatusOk) {
FURI_LOG_D(TAG, "Dequeue SAM message [%d bytes]", seaderApdu.len);
furi_assert(slot_index < SEADER_WORKER_APDU_SLOT_COUNT);
SeaderAPDU* seaderApdu = &seader_worker->apdu_slots[slot_index];
FURI_LOG_D(TAG, "Dequeue SAM message [%d bytes]", seaderApdu->len);
if(seader_process_success_response_i(
seader, seaderApdu.buf, seaderApdu.len, true, NULL)) {
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");
@@ -576,6 +667,7 @@ void seader_worker_run_hf_conversation(Seader* seader) {
view_dispatcher_send_custom_event(
seader->view_dispatcher, SeaderCustomEventWorkerExit);
}
seader_worker_release_apdu_slot(seader_worker, slot_index);
} else if(status == FuriStatusErrorTimeout) {
// No message yet, keep looping to stay in callback
// This is "properly idling" while waiting for SAM
@@ -635,14 +727,14 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
}
uint8_t ats_len = 0;
uint8_t* ats = malloc(4 + t1_tk_size);
if(!ats) {
FURI_LOG_E(TAG, "Failed to allocate host ATS buffer");
seader_worker->stage = SeaderPollerEventTypeFail;
return NfcCommandStop;
}
uint8_t ats[SEADER_MAX_ATR_SIZE] = {0};
if(iso14443_4a_data->ats_data.tl > 1) {
if(sizeof(ats) < 4U + t1_tk_size) {
FURI_LOG_E(TAG, "Host ATS buffer too small: %u", (unsigned)(4U + t1_tk_size));
seader_worker->stage = SeaderPollerEventTypeFail;
return NfcCommandStop;
}
ats[ats_len++] = iso14443_4a_data->ats_data.t0;
if(iso14443_4a_data->ats_data.t0 & ISO14443_4A_ATS_T0_TA1) {
ats[ats_len++] = iso14443_4a_data->ats_data.ta_1;
@@ -669,8 +761,6 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
seader, sak, (uint8_t*)iso14443_3a_data->atqa, uid, uid_len, ats, ats_len);
seader_trace(TAG, "14a card_detect sent uid_len=%d sak=%d", uid_len, sak);
free(ats);
if(seader_worker->state == SeaderWorkerStateReading) {
seader_worker->stage = SeaderPollerEventTypeConversation;
return NfcCommandContinue;

View File

@@ -17,6 +17,7 @@
#define SEADER_POLLER_MAX_FWT (200000U)
// Maximum basic rAPDU size is 256 bytes of data + 2 byte SW
#define SEADER_POLLER_MAX_BUFFER_SIZE (258U)
#define SEADER_WORKER_APDU_SLOT_COUNT (2U)
// ATS bit definitions
#define ISO14443_4A_ATS_T0_TA1 (1U << 4)
@@ -33,6 +34,8 @@ struct SeaderWorker {
SeaderPollerEventType stage;
SeaderWorkerState state;
struct SeaderAPDU* apdu_slots;
bool apdu_slot_in_use[SEADER_WORKER_APDU_SLOT_COUNT];
};
struct SeaderAPDU {

10
t_1.c
View File

@@ -6,7 +6,8 @@
#include "t_1_logic.h"
#define TAG "Seader:T=1"
#define TAG "Seader:T=1"
#define SEADER_T1_MAX_FRAME_LEN (3U + SEADER_T1_IFS_MAX + 1U)
static SeaderT1State* seader_t1_state(SeaderUartBridge* seader_uart) {
return &seader_uart->t1;
@@ -136,9 +137,13 @@ static void seader_t_1_send_nak(Seader* seader) {
void seader_send_t1_chunk(SeaderUartBridge* seader_uart, uint8_t pcb, uint8_t* chunk, size_t len) {
SeaderT1State* t1 = seader_t1_state(seader_uart);
uint8_t* frame = malloc(3 + len + 1);
uint8_t frame[SEADER_T1_MAX_FRAME_LEN];
uint8_t frame_len = 0;
if(len > SEADER_T1_IFS_MAX) {
return;
}
frame[0] = t1->nad;
frame[1] = pcb;
frame[2] = len;
@@ -151,7 +156,6 @@ void seader_send_t1_chunk(SeaderUartBridge* seader_uart, uint8_t pcb, uint8_t* c
frame_len = seader_add_lrc(frame, frame_len);
seader_ccid_XfrBlock(seader_uart, frame, frame_len);
free(frame);
}
void seader_send_t1_scratchpad(

20
uart.c
View File

@@ -119,26 +119,24 @@ int32_t seader_uart_worker(void* context) {
break;
}
if(events & (WorkerEvtRxDone | WorkerEvtSamTxComplete)) {
if(cmd_len >= sizeof(cmd)) {
FURI_LOG_I(TAG, "RX buffer full, resetting");
memset(cmd, 0, sizeof(cmd));
cmd_len = 0;
}
size_t len = furi_stream_buffer_receive(
seader_uart->rx_stream, seader_uart->rx_buf, SEADER_UART_RX_BUF_SIZE, 0);
seader_uart->rx_stream, cmd + cmd_len, sizeof(cmd) - cmd_len, 0);
if(len > 0) {
furi_delay_ms(5); //WTF
/*
char display[SEADER_UART_RX_BUF_SIZE * 2 + 1] = {0};
for (uint8_t i = 0; i < len; i++) {
snprintf(display+(i*2), sizeof(display), "%02x", seader_uart->rx_buf[i]);
snprintf(display+(i*2), sizeof(display), "%02x", cmd[cmd_len + i]);
}
FURI_LOG_I(TAG, "RECV %d bytes: %s", len, display);
*/
if(cmd_len + len > SEADER_UART_RX_BUF_SIZE) {
FURI_LOG_I(TAG, "OVERFLOW: %d + %d", cmd_len, len);
memset(cmd, 0, cmd_len);
cmd_len = 0;
}
memcpy(cmd + cmd_len, seader_uart->rx_buf, len);
cmd_len += len;
cmd_len = seader_uart_process_buffer(seader, cmd, cmd_len);
}
@@ -165,7 +163,7 @@ SeaderUartBridge* seader_uart_enable(SeaderUartConfig* cfg, Seader* seader) {
memcpy(&(seader_uart->cfg_new), cfg, sizeof(SeaderUartConfig));
seader_uart->thread =
furi_thread_alloc_ex("SeaderUartWorker", 5 * 1024, seader_uart_worker, seader);
furi_thread_alloc_ex("SeaderUartWorker", 4 * 1024, seader_uart_worker, seader);
furi_thread_start(seader_uart->thread);
return seader_uart;