t1: surface chained response allocation failures

This commit is contained in:
CinderSocket
2026-06-17 16:41:56 -07:00
parent d27641e87a
commit 7e3ed5dda0
13 changed files with 99 additions and 5 deletions
+2
View File
@@ -47,6 +47,8 @@ const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason)
return "Read state error";
case SeaderHfReadFailureReasonSamKeysMissing:
return "SAM missing keys";
case SeaderHfReadFailureReasonResourceExhausted:
return "SAM exchange memory error";
case SeaderHfReadFailureReasonNone:
default:
return "Read failed";
+1
View File
@@ -34,6 +34,7 @@ typedef enum {
SeaderHfReadFailureReasonProtocolError,
SeaderHfReadFailureReasonInternalState,
SeaderHfReadFailureReasonSamKeysMissing,
SeaderHfReadFailureReasonResourceExhausted,
} SeaderHfReadFailureReason;
typedef enum {
+1
View File
@@ -7,6 +7,7 @@
typedef struct BitBuffer BitBuffer;
BitBuffer* bit_buffer_alloc(size_t capacity_bytes);
void bit_buffer_test_fail_next_alloc(bool fail);
void bit_buffer_free(BitBuffer* buf);
void bit_buffer_reset(BitBuffer* buf);
void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes);
+11
View File
@@ -9,7 +9,14 @@ struct BitBuffer {
size_t capacity_bytes;
};
static bool bit_buffer_fail_next_alloc = false;
BitBuffer* bit_buffer_alloc(size_t capacity_bytes) {
if(bit_buffer_fail_next_alloc) {
bit_buffer_fail_next_alloc = false;
return NULL;
}
BitBuffer* buf = malloc(sizeof(BitBuffer));
buf->data = calloc(1, capacity_bytes);
buf->size_bits = 0;
@@ -17,6 +24,10 @@ BitBuffer* bit_buffer_alloc(size_t capacity_bytes) {
return buf;
}
void bit_buffer_test_fail_next_alloc(bool fail) {
bit_buffer_fail_next_alloc = fail;
}
void bit_buffer_free(BitBuffer* buf) {
if(buf) {
free(buf->data);
+16
View File
@@ -29,3 +29,19 @@ void seader_worker_send_version(Seader* seader) {
(void)seader;
g_t1_host_test_state.send_version_call_count++;
}
void seader_abort_active_read_with_reason(
Seader* seader,
SeaderHfReadFailureReason reason,
const char* detail) {
g_t1_host_test_state.abort_call_count++;
seader->hf_read_failure_reason = reason;
if(detail && detail[0] != '\0') {
strncpy(seader->read_error, detail, sizeof(seader->read_error) - 1U);
} else {
strncpy(
seader->read_error,
seader_hf_read_failure_reason_text(reason),
sizeof(seader->read_error) - 1U);
}
}
+8
View File
@@ -8,6 +8,7 @@
#include <string.h>
#include "bit_buffer.h"
#include "hf_read_lifecycle.h"
#include "lrc.h"
#include "t_1_logic.h"
@@ -46,6 +47,8 @@ struct SeaderWorker {
struct Seader {
SeaderWorker* worker;
SeaderHfReadFailureReason hf_read_failure_reason;
char read_error[97];
};
typedef struct CCID_Message {
@@ -62,6 +65,10 @@ typedef struct CCID_Message {
void seader_ccid_XfrBlock(SeaderUartBridge* seader_uart, uint8_t* data, size_t len);
bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t len);
void seader_worker_send_version(Seader* seader);
void seader_abort_active_read_with_reason(
Seader* seader,
SeaderHfReadFailureReason reason,
const char* detail);
typedef struct {
/* Captured outbound CCID payload emitted by the T=1 implementation. */
@@ -77,6 +84,7 @@ typedef struct {
size_t send_version_call_count;
size_t callback_call_count;
uint32_t last_callback_event;
size_t abort_call_count;
} T1HostTestState;
extern T1HostTestState g_t1_host_test_state;
+7
View File
@@ -65,6 +65,9 @@ static MunitResult test_failure_reason_texts_are_stable(
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonSamKeysMissing),
"SAM missing keys");
munit_assert_string_equal(
seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonResourceExhausted),
"SAM exchange memory error");
return MUNIT_OK;
}
@@ -143,6 +146,10 @@ static MunitResult test_error_texts_fit_read_error_storage(
munit_assert_size(strlen(protected_read_timeout), <, 96U);
munit_assert_size(
strlen(seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonInternalState)), <, 96U);
munit_assert_size(
strlen(seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonResourceExhausted)),
<,
96U);
return MUNIT_OK;
}
+35
View File
@@ -196,6 +196,35 @@ static MunitResult test_recv_i_block_too_large_rejected(const MunitParameter par
return MUNIT_OK;
}
static MunitResult test_recv_chained_i_block_oom_returns_error(
const MunitParameter params[],
void* fixture) {
(void)params;
(void)fixture;
SeaderUartBridge uart = {0};
SeaderWorker worker = {0};
Seader seader = make_test_seader(&uart, &worker);
uint8_t i_block_more[] = {0x00, 0x20, 0x02, 0xAA, 0xBB, 0x00};
CCID_Message message = {.payload = i_block_more, .dwLength = sizeof(i_block_more)};
t1_host_test_reset();
uart.t1.ifsd = SEADER_T1_IFS_DEFAULT;
uart.t1.recv_pcb = 0x00;
seader_add_lrc(i_block_more, sizeof(i_block_more) - 1U);
bit_buffer_test_fail_next_alloc(true);
munit_assert_false(seader_recv_t1(&seader, &message));
munit_assert_null(uart.t1.rx_buffer);
munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 0);
munit_assert_size(g_t1_host_test_state.process_call_count, ==, 0);
munit_assert_size(g_t1_host_test_state.abort_call_count, ==, 1);
munit_assert_int(
seader.hf_read_failure_reason, ==, SeaderHfReadFailureReasonResourceExhausted);
munit_assert_string_equal(seader.read_error, "SAM exchange memory error");
return MUNIT_OK;
}
static MunitResult test_recv_r_block_nack_retransmits(const MunitParameter params[], void* fixture) {
(void)params;
(void)fixture;
@@ -393,6 +422,12 @@ static MunitTest test_t1_regression_cases[] = {
NULL,
MUNIT_TEST_OPTION_NONE,
NULL},
{(char*)"/recv/chained-i-block-oom-errors",
test_recv_chained_i_block_oom_returns_error,
NULL,
NULL,
MUNIT_TEST_OPTION_NONE,
NULL},
{(char*)"/recv/r-block-nack-retransmits",
test_recv_r_block_nack_retransmits,
NULL,
+1 -5
View File
@@ -357,10 +357,6 @@ void* calloc(size_t count, size_t size) {
// Forward declarations
static void seader_abort_active_read(Seader* seader);
static void seader_abort_active_read_with_reason(
Seader* seader,
SeaderHfReadFailureReason reason,
const char* detail);
static void seader_sam_set_state(
Seader* seader,
@@ -1038,7 +1034,7 @@ bool seader_parse_serial_number(Seader* seader, uint8_t* buf, size_t size) {
return seader_sam_save_serial(seader, buf, size);
}
static void seader_abort_active_read_with_reason(
void seader_abort_active_read_with_reason(
Seader* seader,
SeaderHfReadFailureReason reason,
const char* detail) {
+5
View File
@@ -12,6 +12,7 @@
#include <stdint.h>
#include "hf_bridge_policy.h"
#include "hf_read_lifecycle.h"
typedef struct Seader Seader;
typedef struct SeaderPollerContainer SeaderPollerContainer;
@@ -41,6 +42,10 @@ 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);
void seader_sam_force_idle_for_recovery(Seader* seader);
void seader_abort_active_read_with_reason(
Seader* seader,
SeaderHfReadFailureReason reason,
const char* detail);
bool seader_process_success_response_i(
Seader* seader,
+8
View File
@@ -1,6 +1,7 @@
#ifdef SEADER_HOST_TEST
#include "lib/host_tests/t_1_host_env.h"
#else
#include "sam_api.h"
#include "t_1.h"
#endif
@@ -279,6 +280,13 @@ bool seader_recv_t1(Seader* seader, CCID_Message* message) {
seader_send_t1(seader_uart, NULL, 0);
return false;
case SeaderT1ActionResourceExhausted:
FURI_LOG_W(TAG, "T=1 chained APDU allocation failed");
seader_t1_reset_link_state(t1);
seader_abort_active_read_with_reason(
seader, SeaderHfReadFailureReasonResourceExhausted, NULL);
return false;
case SeaderT1ActionNone:
return true;
+3
View File
@@ -164,6 +164,9 @@ SeaderT1Action seader_t1_handle_block(
t1->recv_pcb = seader_t1_next_pcb(t1->recv_pcb);
if(t1->rx_buffer == NULL) {
t1->rx_buffer = bit_buffer_alloc(512);
if(t1->rx_buffer == NULL) {
return SeaderT1ActionResourceExhausted;
}
}
bit_buffer_append_bytes(t1->rx_buffer, payload + 3, LEN);
return SeaderT1ActionSendAck;
+1
View File
@@ -29,6 +29,7 @@ typedef enum {
SeaderT1ActionSendMoreData,
SeaderT1ActionRetransmit,
SeaderT1ActionSendNak,
SeaderT1ActionResourceExhausted,
SeaderT1ActionError,
} SeaderT1Action;