From 2d60989beecec25f3423a2dbba9e49842d358195 Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Sun, 8 Mar 2026 19:03:30 -0700 Subject: [PATCH] Add CCID helpers and regression host tests --- Makefile | 3 + ccid.c | 16 +- ccid_logic.c | 94 ++++++++ ccid_logic.h | 50 +++++ lib/host_tests/t_1_host_env.h | 7 + lib/host_tests/test_ccid_logic.c | 222 +++++++++++++++++++ lib/host_tests/test_main.c | 4 + lib/host_tests/test_t1_regressions.c | 308 +++++++++++++++++++++++++++ 8 files changed, 693 insertions(+), 11 deletions(-) create mode 100644 ccid_logic.c create mode 100644 ccid_logic.h create mode 100644 lib/host_tests/test_ccid_logic.c create mode 100644 lib/host_tests/test_t1_regressions.c diff --git a/Makefile b/Makefile index 9723f34..d5c4d6b 100644 --- a/Makefile +++ b/Makefile @@ -15,10 +15,13 @@ test-host: lib/host_tests/vendor/munit/munit.c \ lib/host_tests/test_main.c \ lib/host_tests/test_lrc.c \ + lib/host_tests/test_ccid_logic.c \ lib/host_tests/test_t1_existing.c \ + lib/host_tests/test_t1_regressions.c \ lib/host_tests/t1_test_stubs.c \ lib/host_tests/bit_buffer_mock.c \ lrc.c \ + ccid_logic.c \ t_1.c \ -o build/host_tests/seader_tests ./build/host_tests/seader_tests diff --git a/ccid.c b/ccid.c index 5d52055..1e6cd1f 100644 --- a/ccid.c +++ b/ccid.c @@ -1,4 +1,5 @@ #include "seader_i.h" +#include "ccid_logic.h" #define TAG "SeaderCCID" const uint8_t SAM_ATR[] = @@ -26,10 +27,7 @@ static void seader_ccid_reset_slot_sequence(SeaderUartBridge* seader_uart, uint8 static uint8_t seader_ccid_next_sequence(SeaderUartBridge* seader_uart, uint8_t slot) { SeaderCcidSlotState* slot_state = seader_ccid_slot_state(seader_uart, slot); - if(slot_state->sequence > 254) { - slot_state->sequence = 0; - } - return slot_state->sequence++; + return seader_ccid_sequence_advance(&slot_state->sequence); } void seader_ccid_IccPowerOn(SeaderUartBridge* seader_uart, uint8_t slot) { @@ -175,19 +173,15 @@ void seader_ccid_XfrBlockToSlot( uint8_t* data, size_t len) { uint8_t header_len = 2 + 10; - if(len > ((size_t)SEADER_UART_RX_BUF_SIZE - header_len)) { + if(!seader_ccid_payload_fits_frame(len, SEADER_UART_RX_BUF_SIZE, header_len)) { FURI_LOG_E(TAG, "CCID frame too long: %d", (int)(header_len + len)); return; } uint8_t* tx_start = (uint8_t*)seader_uart->tx_buf; - uint8_t* tx_end = tx_start + SEADER_UART_RX_BUF_SIZE; uint8_t* data_addr = (uint8_t*)data; - bool in_scratchpad = false; - if(data_addr >= tx_start + header_len && data_addr <= tx_end) { - size_t available = (size_t)(tx_end - data_addr); - in_scratchpad = len <= available; - } + bool in_scratchpad = seader_ccid_data_in_scratchpad( + tx_start, SEADER_UART_RX_BUF_SIZE, header_len, data_addr, len); uint8_t* frame; if(in_scratchpad) { diff --git a/ccid_logic.c b/ccid_logic.c new file mode 100644 index 0000000..955652f --- /dev/null +++ b/ccid_logic.c @@ -0,0 +1,94 @@ +#include "ccid_logic.h" + +uint8_t seader_ccid_sequence_advance(uint8_t* sequence) { + return (*sequence)++; +} + +bool seader_ccid_payload_fits_frame(size_t payload_len, size_t uart_buf_size, size_t header_len) { + if(header_len > uart_buf_size) { + return false; + } + return payload_len <= (uart_buf_size - header_len); +} + +bool seader_ccid_data_in_scratchpad( + const uint8_t* tx_buf, + size_t tx_buf_size, + size_t header_len, + const uint8_t* data, + size_t payload_len) { + if(data < tx_buf + header_len || data > tx_buf + tx_buf_size) { + return false; + } + + size_t available = (size_t)((tx_buf + tx_buf_size) - data); + return payload_len <= available; +} + +SeaderCcidStatus seader_ccid_decode_status(uint8_t status) { + SeaderCcidStatus decoded = { + .icc_status = status & 0x03, + .command_status = (SeaderCcidDecodedCommandStatus)((status >> 6) & 0x03), + }; + return decoded; +} + +bool seader_ccid_response_matches_pending(bool pending, uint8_t expected_seq, uint8_t response_seq) { + return !pending || (expected_seq == response_seq); +} + +size_t seader_ccid_find_frame_start( + const uint8_t* data, + size_t len, + uint8_t sync, + uint8_t ctrl, + uint8_t nak) { + size_t i = 0; + + while(i + 1 < len) { + if(i + 2 < len && data[i] == sync && data[i + 1] == nak) { + i += 3; + continue; + } + + if(data[i] == sync && data[i + 1] == ctrl) { + return i; + } + + i++; + } + + return len; +} + +bool seader_ccid_pending_timed_out( + bool pending, + uint32_t pending_since_tick, + uint32_t now_tick, + uint32_t timeout_ticks) { + if(!pending || timeout_ticks == 0 || pending_since_tick == 0) { + return false; + } + + return (now_tick - pending_since_tick) > timeout_ticks; +} + +SeaderCcidDataRoute seader_ccid_route_data_block( + bool has_sam, + uint8_t sam_slot, + uint8_t message_slot, + uint8_t protocol_t) { + if(!has_sam) { + return SeaderCcidDataRouteAtrRecognition; + } + + if(message_slot != sam_slot) { + return SeaderCcidDataRouteWrongSlotError; + } + + if(protocol_t == 0) { + return SeaderCcidDataRouteSamT0; + } + + return SeaderCcidDataRouteSamT1; +} diff --git a/ccid_logic.h b/ccid_logic.h new file mode 100644 index 0000000..f5d6c07 --- /dev/null +++ b/ccid_logic.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +typedef enum { + SeaderCcidDecodedCommandStatusProcessed = 0, + SeaderCcidDecodedCommandStatusFailed = 1, + SeaderCcidDecodedCommandStatusTimeExtension = 2, +} SeaderCcidDecodedCommandStatus; + +typedef struct { + uint8_t icc_status; + SeaderCcidDecodedCommandStatus command_status; +} SeaderCcidStatus; + +typedef enum { + SeaderCcidDataRouteSamT0 = 0, + SeaderCcidDataRouteSamT1 = 1, + SeaderCcidDataRouteAtrRecognition = 2, + SeaderCcidDataRouteWrongSlotError = 3, +} SeaderCcidDataRoute; + +uint8_t seader_ccid_sequence_advance(uint8_t* sequence); +bool seader_ccid_payload_fits_frame(size_t payload_len, size_t uart_buf_size, size_t header_len); +bool seader_ccid_data_in_scratchpad( + const uint8_t* tx_buf, + size_t tx_buf_size, + size_t header_len, + const uint8_t* data, + size_t payload_len); +SeaderCcidStatus seader_ccid_decode_status(uint8_t status); +bool seader_ccid_response_matches_pending(bool pending, uint8_t expected_seq, uint8_t response_seq); +size_t seader_ccid_find_frame_start( + const uint8_t* data, + size_t len, + uint8_t sync, + uint8_t ctrl, + uint8_t nak); +bool seader_ccid_pending_timed_out( + bool pending, + uint32_t pending_since_tick, + uint32_t now_tick, + uint32_t timeout_ticks); +SeaderCcidDataRoute seader_ccid_route_data_block( + bool has_sam, + uint8_t sam_slot, + uint8_t message_slot, + uint8_t protocol_t); diff --git a/lib/host_tests/t_1_host_env.h b/lib/host_tests/t_1_host_env.h index 2bc0333..ba0cb99 100644 --- a/lib/host_tests/t_1_host_env.h +++ b/lib/host_tests/t_1_host_env.h @@ -31,16 +31,23 @@ typedef enum { SEADER_T1_PCB_R_BLOCK = 0x80, SEADER_T1_PCB_S_BLOCK = 0xC0, SEADER_T1_R_BLOCK_SEQUENCE_MASK = 0x10, + SEADER_T1_S_BLOCK_RESPONSE_BIT = 0x20, + SEADER_T1_S_BLOCK_RESYNCH = 0x00, SEADER_T1_S_BLOCK_IFS = 0x01, + SEADER_T1_S_BLOCK_ABORT = 0x02, + SEADER_T1_S_BLOCK_WTX = 0x03, } SeaderT1Constant; typedef struct { uint8_t ifsc; + uint8_t ifsd; + uint8_t ifsd_pending; uint8_t nad; uint8_t send_pcb; uint8_t recv_pcb; BitBuffer* tx_buffer; size_t tx_buffer_offset; + size_t last_tx_len; BitBuffer* rx_buffer; } SeaderT1State; diff --git a/lib/host_tests/test_ccid_logic.c b/lib/host_tests/test_ccid_logic.c new file mode 100644 index 0000000..14d15ad --- /dev/null +++ b/lib/host_tests/test_ccid_logic.c @@ -0,0 +1,222 @@ +#include + +#include "ccid_logic.h" +#include "munit.h" + +static MunitResult test_sequence_advance_wraps(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + uint8_t seq = 0; + munit_assert_uint8(seader_ccid_sequence_advance(&seq), ==, 0); + munit_assert_uint8(seq, ==, 1); + + seq = 254; + munit_assert_uint8(seader_ccid_sequence_advance(&seq), ==, 254); + munit_assert_uint8(seq, ==, 255); + + seq = 255; + munit_assert_uint8(seader_ccid_sequence_advance(&seq), ==, 255); + munit_assert_uint8(seq, ==, 0); + return MUNIT_OK; +} + +static MunitResult test_payload_fits_buffer(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_true(seader_ccid_payload_fits_frame(10, 300, 12)); + munit_assert_false(seader_ccid_payload_fits_frame(289, 300, 12)); + munit_assert_true(seader_ccid_payload_fits_frame(288, 300, 12)); + return MUNIT_OK; +} + +static MunitResult test_data_in_scratchpad(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + uint8_t tx[64] = {0}; + munit_assert_true(seader_ccid_data_in_scratchpad(tx, sizeof(tx), 12, tx + 12, 1)); + munit_assert_true(seader_ccid_data_in_scratchpad(tx, sizeof(tx), 12, tx + 63, 1)); + munit_assert_false(seader_ccid_data_in_scratchpad(tx, sizeof(tx), 12, tx + 11, 1)); + munit_assert_false(seader_ccid_data_in_scratchpad(tx, sizeof(tx), 12, tx + 60, 8)); + return MUNIT_OK; +} + +static MunitResult test_response_seq_match(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_true(seader_ccid_response_matches_pending(false, 0x00, 0xff)); + munit_assert_true(seader_ccid_response_matches_pending(true, 0x12, 0x12)); + munit_assert_false(seader_ccid_response_matches_pending(true, 0x12, 0x13)); + return MUNIT_OK; +} + +static MunitResult test_status_decode_ok(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderCcidStatus status_ok = seader_ccid_decode_status(0x00); + munit_assert_uint8(status_ok.icc_status, ==, 0); + munit_assert_int(status_ok.command_status, ==, SeaderCcidDecodedCommandStatusProcessed); + return MUNIT_OK; +} + +static MunitResult test_status_decode_failed(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderCcidStatus status_fail = seader_ccid_decode_status(0x41); + munit_assert_uint8(status_fail.icc_status, ==, 1); + munit_assert_int(status_fail.command_status, ==, SeaderCcidDecodedCommandStatusFailed); + return MUNIT_OK; +} + +static MunitResult test_status_decode_time_extension(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderCcidStatus status_wait = seader_ccid_decode_status(0x80); + munit_assert_uint8(status_wait.icc_status, ==, 0); + munit_assert_int( + status_wait.command_status, ==, SeaderCcidDecodedCommandStatusTimeExtension); + return MUNIT_OK; +} + +static MunitResult test_find_start_skips_nak_triplet(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + const uint8_t sync = 0x03; + const uint8_t ctrl = 0x06; + const uint8_t nak = 0x15; + const uint8_t frame_stream[] = { + 0x03, + 0x15, + 0x16, + 0x99, + 0x03, + 0x06, + 0x80, + }; + + munit_assert_size( + seader_ccid_find_frame_start(frame_stream, sizeof(frame_stream), sync, ctrl, nak), ==, 4); + return MUNIT_OK; +} + +static MunitResult test_find_start_noise_only(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + const uint8_t sync = 0x03; + const uint8_t ctrl = 0x06; + const uint8_t nak = 0x15; + const uint8_t noise_only[] = {0x01, 0x02, 0x03}; + + munit_assert_size( + seader_ccid_find_frame_start(noise_only, sizeof(noise_only), sync, ctrl, nak), + ==, + sizeof(noise_only)); + return MUNIT_OK; +} + +static MunitResult test_pending_timeout_helper(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_false(seader_ccid_pending_timed_out(false, 100, 200, 50)); + munit_assert_false(seader_ccid_pending_timed_out(true, 0, 200, 50)); + munit_assert_false(seader_ccid_pending_timed_out(true, 100, 149, 50)); + munit_assert_false(seader_ccid_pending_timed_out(true, 100, 150, 50)); + munit_assert_true(seader_ccid_pending_timed_out(true, 100, 151, 50)); + munit_assert_false(seader_ccid_pending_timed_out(true, 100, 200, 0)); + return MUNIT_OK; +} + +static MunitResult test_data_block_route(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_int(seader_ccid_route_data_block(true, 0, 0, 0), ==, SeaderCcidDataRouteSamT0); + munit_assert_int(seader_ccid_route_data_block(true, 0, 0, 1), ==, SeaderCcidDataRouteSamT1); + munit_assert_int( + seader_ccid_route_data_block(false, 0, 0, 1), ==, SeaderCcidDataRouteAtrRecognition); + munit_assert_int( + seader_ccid_route_data_block(true, 0, 1, 1), ==, SeaderCcidDataRouteWrongSlotError); + return MUNIT_OK; +} + +static MunitTest test_ccid_cases[] = { + {(char*)"/sequence/advance-wraps-through-ff", + test_sequence_advance_wraps, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/frame/payload-fits-buffer", + test_payload_fits_buffer, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/frame/data-in-scratchpad", + test_data_in_scratchpad, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/pending/response-seq-match", + test_response_seq_match, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/status/decode-ok", test_status_decode_ok, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/status/decode-failed", + test_status_decode_failed, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/status/decode-time-extension", + test_status_decode_time_extension, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/frame/find-start-skips-nak-triplet", + test_find_start_skips_nak_triplet, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/frame/find-start-noise-only", + test_find_start_noise_only, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/pending/timeout-helper", + test_pending_timeout_helper, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/routing/data-block-route", + test_data_block_route, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_ccid_logic_suite = { + "", + test_ccid_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_main.c b/lib/host_tests/test_main.c index 504c5a8..3cef2f0 100644 --- a/lib/host_tests/test_main.c +++ b/lib/host_tests/test_main.c @@ -1,12 +1,16 @@ #include "munit.h" extern MunitSuite test_lrc_suite; +extern MunitSuite test_ccid_logic_suite; extern MunitSuite test_t1_existing_suite; +extern MunitSuite test_t1_regressions_suite; int main(int argc, char* argv[]) { MunitSuite child_suites[] = { {"/lrc", test_lrc_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/t1", test_t1_existing_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/t1-future", test_t1_regressions_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/ccid", test_ccid_logic_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {NULL, NULL, NULL, 0, 0}, }; MunitSuite main_suite = { diff --git a/lib/host_tests/test_t1_regressions.c b/lib/host_tests/test_t1_regressions.c new file mode 100644 index 0000000..0515786 --- /dev/null +++ b/lib/host_tests/test_t1_regressions.c @@ -0,0 +1,308 @@ +#include + +#include "munit.h" +#include "t_1_host_env.h" + +static void test_callback(uint32_t event, void* context) { + (void)context; + g_t1_host_test_state.callback_call_count++; + g_t1_host_test_state.last_callback_event = event; +} + +static Seader make_test_seader(SeaderUartBridge* uart, SeaderWorker* worker) { + memset(uart, 0, sizeof(*uart)); + memset(worker, 0, sizeof(*worker)); + worker->uart = uart; + worker->callback = test_callback; + + Seader seader = {.worker = worker}; + return seader; +} + +static MunitResult test_recv_wtx_request_responds(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uint8_t s_wtx_req[] = {0x00, 0xC3, 0x01, 0x02, 0x00}; + seader_add_lrc(s_wtx_req, 4); + CCID_Message message = {.payload = s_wtx_req, .dwLength = 5}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[1], ==, 0xE3); + return MUNIT_OK; +} + +static MunitResult test_recv_resynch_request_responds(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uint8_t s_resync_req[] = {0x00, 0xC0, 0x00, 0x00}; + seader_add_lrc(s_resync_req, 3); + CCID_Message message = {.payload = s_resync_req, .dwLength = 4}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[1], ==, 0xE0); + return MUNIT_OK; +} + +static MunitResult test_recv_malformed_wtx_rejected(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uint8_t s_wtx_bad[] = {0x00, 0xC3, 0x00, 0x00}; + seader_add_lrc(s_wtx_bad, 3); + CCID_Message message = {.payload = s_wtx_bad, .dwLength = 4}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[1], ==, 0x81); + return MUNIT_OK; +} + +static MunitResult test_recv_malformed_ifs_rejected(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uint8_t s_ifs_bad[] = {0x00, 0xC1, 0x00, 0x00}; + seader_add_lrc(s_ifs_bad, 3); + CCID_Message message = {.payload = s_ifs_bad, .dwLength = 4}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[1], ==, 0x81); + return MUNIT_OK; +} + +static MunitResult test_recv_ifs_request_updates_ifsc(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uart.t1.ifsc = 0x20; + uint8_t s_ifs_req[] = {0x00, 0xC1, 0x01, 0x40, 0x00}; + seader_add_lrc(s_ifs_req, 4); + CCID_Message message = {.payload = s_ifs_req, .dwLength = 5}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[3], ==, 0x40); + return MUNIT_OK; +} + +static MunitResult test_recv_ifs_response_applies_pending_ifsd( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uart.t1.ifsd = 0x10; + uart.t1.ifsd_pending = 0x20; + uint8_t s_ifs_res[] = {0x00, 0xE1, 0x01, 0x20, 0x00}; + seader_add_lrc(s_ifs_res, 4); + CCID_Message message = {.payload = s_ifs_res, .dwLength = 5}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_uint8(uart.t1.ifsd, ==, 0x20); + munit_assert_uint8(uart.t1.ifsd_pending, ==, 0x00); + return MUNIT_OK; +} + +static MunitResult test_recv_ifs_response_mismatch_rejected( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uart.t1.ifsd_pending = 0x20; + uint8_t s_ifs_res_bad[] = {0x00, 0xE1, 0x01, 0x30, 0x00}; + seader_add_lrc(s_ifs_res_bad, 4); + CCID_Message message = {.payload = s_ifs_res_bad, .dwLength = 5}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.send_version_call_count, ==, 0); + munit_assert_size(g_t1_host_test_state.callback_call_count, ==, 0); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[1], ==, 0x81); + return MUNIT_OK; +} + +static MunitResult test_recv_i_block_too_large_rejected(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uart.t1.ifsd = 2; + uart.t1.recv_pcb = 0x00; + uint8_t i_block[] = {0x00, 0x00, 0x03, 0xAA, 0xBB, 0xCC, 0x00}; + seader_add_lrc(i_block, 6); + CCID_Message message = {.payload = i_block, .dwLength = 7}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.process_call_count, ==, 0); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(g_t1_host_test_state.last_frame[1], ==, 0x81); + return MUNIT_OK; +} + +static MunitResult test_recv_r_block_nack_retransmits(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uart.t1.send_pcb = 0x00; + uart.t1.ifsc = 4; + uart.t1.tx_buffer = bit_buffer_alloc(8); + bit_buffer_copy_bytes(uart.t1.tx_buffer, (const uint8_t*)"\xA0\xA1\xA2", 3); + uart.t1.tx_buffer_offset = 2; + uart.t1.last_tx_len = 2; + uint8_t r_block[] = {0x00, 0x80, 0x00, 0x00}; + seader_add_lrc(r_block, 3); + CCID_Message message = {.payload = r_block, .dwLength = 4}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 1); + munit_assert_uint8(uart.t1.send_pcb, ==, SEADER_T1_PCB_SEQUENCE_BIT); + bit_buffer_free(uart.t1.tx_buffer); + return MUNIT_OK; +} + +static MunitResult test_recv_r_block_invalid_retransmit_state_errors( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + + t1_host_test_reset(); + uart.t1.send_pcb = 0x00; + uart.t1.tx_buffer = bit_buffer_alloc(8); + uart.t1.tx_buffer_offset = 0; + uart.t1.last_tx_len = 2; + uint8_t r_block[] = {0x00, 0x91, 0x00, 0x00}; + seader_add_lrc(r_block, 3); + CCID_Message message = {.payload = r_block, .dwLength = 4}; + + munit_assert_false(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.xfrblock_call_count, ==, 0); + bit_buffer_free(uart.t1.tx_buffer); + return MUNIT_OK; +} + +static MunitTest test_t1_regression_cases[] = { + {(char*)"/recv/wtx-request-responds", + test_recv_wtx_request_responds, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/resynch-request-responds", + test_recv_resynch_request_responds, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/malformed-wtx-len-zero-rejected", + test_recv_malformed_wtx_rejected, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/malformed-ifs-len-zero-rejected", + test_recv_malformed_ifs_rejected, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/ifs-request-updates-ifsc", + test_recv_ifs_request_updates_ifsc, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/ifs-response-applies-pending-ifsd", + test_recv_ifs_response_applies_pending_ifsd, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/ifs-response-mismatch-rejected", + test_recv_ifs_response_mismatch_rejected, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/i-block-too-large-rejected", + test_recv_i_block_too_large_rejected, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/r-block-nack-retransmits", + test_recv_r_block_nack_retransmits, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/recv/r-block-invalid-retransmit-state-errors", + test_recv_r_block_invalid_retransmit_state_errors, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_t1_regressions_suite = { + "", + test_t1_regression_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +};