diff --git a/Makefile b/Makefile index 1f0992f..f5f71dd 100644 --- a/Makefile +++ b/Makefile @@ -17,26 +17,40 @@ test-host: cc $(HOST_TEST_CFLAGS) \ lib/host_tests/vendor/munit/munit.c \ lib/host_tests/test_main.c \ + lib/host_tests/test_board_identity.c \ lib/host_tests/test_lrc.c \ lib/host_tests/test_board_power_lifecycle.c \ lib/host_tests/test_hf_read_lifecycle.c \ + lib/host_tests/test_hf_buffer_pool.c \ lib/host_tests/test_sam_startup_ui.c \ lib/host_tests/test_sam_key_label.c \ lib/host_tests/test_ccid_logic.c \ + lib/host_tests/test_uart_tx_logic.c \ + lib/host_tests/test_uart_rx_logic.c \ lib/host_tests/test_t1_existing.c \ 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_hf_read_plan.c \ + lib/host_tests/test_hf_bridge_policy.c \ + lib/host_tests/test_hf_14a_session.c \ + lib/host_tests/test_hf_sam_response_view.c \ lib/host_tests/test_wiegand_plugin.c \ lib/host_tests/test_runtime_policy.c \ + lib/host_tests/test_ui_memory_policy.c \ lib/host_tests/t1_test_stubs.c \ lib/host_tests/bit_buffer_mock.c \ lrc.c \ + board_identity.c \ board_power_lifecycle.c \ sam_startup_ui.c \ ccid_logic.c \ + hf_buffer_pool.c \ + uart_tx_logic.c \ + uart_rx_logic.c \ + allocation_policy.c \ + worker_loop_policy.c \ credential_sio_label.c \ t_1_logic.c \ t_1.c \ @@ -48,9 +62,13 @@ test-host: uhf_tag_config_view.c \ uhf_snmp_probe.c \ hf_read_lifecycle.c \ + hf_bridge_policy.c \ + hf_14a_session.c \ + hf_sam_response_view.c \ seader_hf_read_plan.c \ wiegand_interface_fal/wiegand.c \ runtime_policy.c \ + ui_memory_policy.c \ -o build/host_tests/seader_tests ./build/host_tests/seader_tests diff --git a/allocation_policy.c b/allocation_policy.c new file mode 100644 index 0000000..518a29b --- /dev/null +++ b/allocation_policy.c @@ -0,0 +1,16 @@ +#include "allocation_policy.h" + +#include + +bool seader_size_multiply_checked(size_t count, size_t size, size_t* out) { + if(!out) { + return false; + } + + if(count != 0U && size > SIZE_MAX / count) { + return false; + } + + *out = count * size; + return true; +} diff --git a/allocation_policy.h b/allocation_policy.h new file mode 100644 index 0000000..634a098 --- /dev/null +++ b/allocation_policy.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +bool seader_size_multiply_checked(size_t count, size_t size, size_t* out); diff --git a/application.fam b/application.fam index 87ef211..11de608 100644 --- a/application.fam +++ b/application.fam @@ -11,6 +11,7 @@ App( "ASN_DISABLE_OER_SUPPORT", "ASN_DISABLE_XER_SUPPORT", "ASN_DISABLE_RANDOM_FILL", + "ASN_DISABLE_PRINT_SUPPORT", ], requires=[ "gui", "storage", "nfc", @@ -45,12 +46,13 @@ App( "-DASN_DISABLE_OER_SUPPORT", "-DASN_DISABLE_XER_SUPPORT", "-DASN_DISABLE_RANDOM_FILL", + "-DASN_DISABLE_PRINT_SUPPORT", "-Os", ], ), Lib( name="loclass", - cflags=["-O3"], + cflags=["-Os"], ), ], fap_weburl="https://seader.ericbetts.dev", @@ -71,6 +73,6 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="plugin_hf_ep", requires=["seader"], - sources=["hf_interface_fal/hf.c"], + sources=["hf_interface_fal/hf.c", "hf_bridge_policy.c", "hf_buffer_pool.c", "hf_14a_session.c"], fal_embedded=True, ) diff --git a/board_identity.c b/board_identity.c new file mode 100644 index 0000000..91b2539 --- /dev/null +++ b/board_identity.c @@ -0,0 +1,17 @@ +#include "board_identity.h" + +SeaderBoardClass seader_board_classify(bool pa4_high, bool pc1_high, bool pc0_high) { + if(pa4_high) { + return SeaderBoardClassUhfCarrier; + } + + if(pc1_high || pc0_high) { + return SeaderBoardClassSamOnly; + } + + return SeaderBoardClassNone; +} + +bool seader_board_class_supports_uhf(SeaderBoardClass board_class) { + return board_class == SeaderBoardClassUhfCarrier; +} diff --git a/board_identity.h b/board_identity.h new file mode 100644 index 0000000..7f612a6 --- /dev/null +++ b/board_identity.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef enum { + SeaderBoardClassUnknown = 0, + SeaderBoardClassNone, + SeaderBoardClassSamOnly, + SeaderBoardClassUhfCarrier, +} SeaderBoardClass; + +SeaderBoardClass seader_board_classify(bool pa4_high, bool pc1_high, bool pc0_high); +bool seader_board_class_supports_uhf(SeaderBoardClass board_class); diff --git a/ccid.c b/ccid.c index db01e3d..35759af 100644 --- a/ccid.c +++ b/ccid.c @@ -31,6 +31,17 @@ static uint8_t seader_ccid_next_sequence(SeaderUartBridge* seader_uart, uint8_t return seader_ccid_sequence_advance(&slot_state->sequence); } +static void + seader_ccid_publish_tx_frame(SeaderUartBridge* seader_uart, const uint8_t* frame, size_t len) { + if(!seader_uart_tx_enqueue(seader_uart, frame, len)) { + FURI_LOG_E(TAG, "Failed to queue CCID frame len=%u", (unsigned)len); + } +} + +static size_t seader_ccid_build_control_frame_lrc(uint8_t* frame, size_t frame_len) { + return seader_add_lrc(frame, frame_len); +} + static SeaderUartBridge* seader_ccid_active_uart(Seader* seader) { furi_check(seader); furi_check(seader->worker); @@ -46,33 +57,35 @@ void seader_ccid_IccPowerOn(SeaderUartBridge* seader_uart, uint8_t slot) { slot_state->powered = true; SEADER_VERBOSE_D(TAG, "Sending Power On (%d)", slot); - memset(seader_uart->tx_buf, 0, SEADER_UART_RX_BUF_SIZE); - seader_uart->tx_buf[0] = SYNC; - seader_uart->tx_buf[1] = CTRL; - seader_uart->tx_buf[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON; + uint8_t frame[2 + 10 + 1] = {0}; + frame[0] = SYNC; + frame[1] = CTRL; + frame[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON; - seader_uart->tx_buf[2 + 5] = slot; - seader_uart->tx_buf[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); - seader_uart->tx_buf[2 + 7] = 1; //power + frame[2 + 5] = slot; + frame[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); + frame[2 + 7] = 1; //power - seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10); - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + seader_uart->tx_len = + seader_ccid_build_control_frame_lrc(frame, seader_ccid_control_frame_size(0U)); + seader_ccid_publish_tx_frame(seader_uart, frame, seader_uart->tx_len); } void seader_ccid_IccPowerOff(SeaderUartBridge* seader_uart, uint8_t slot) { seader_ccid_slot_state(seader_uart, slot)->powered = false; SEADER_VERBOSE_D(TAG, "Sending Power Off (%d)", slot); - memset(seader_uart->tx_buf, 0, SEADER_UART_RX_BUF_SIZE); - seader_uart->tx_buf[0] = SYNC; - seader_uart->tx_buf[1] = CTRL; - seader_uart->tx_buf[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF; + uint8_t frame[2 + 10 + 1] = {0}; + frame[0] = SYNC; + frame[1] = CTRL; + frame[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF; - seader_uart->tx_buf[2 + 5] = slot; - seader_uart->tx_buf[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); + frame[2 + 5] = slot; + frame[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); - seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10); - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + seader_uart->tx_len = + seader_ccid_build_control_frame_lrc(frame, seader_ccid_control_frame_size(0U)); + seader_ccid_publish_tx_frame(seader_uart, frame, seader_uart->tx_len); } void seader_ccid_check_for_sam(SeaderUartBridge* seader_uart) { @@ -88,15 +101,16 @@ void seader_ccid_check_for_sam(SeaderUartBridge* seader_uart) { void seader_ccid_GetSlotStatus(SeaderUartBridge* seader_uart, uint8_t slot) { SEADER_VERBOSE_D(TAG, "seader_ccid_GetSlotStatus(%d)", slot); - memset(seader_uart->tx_buf, 0, SEADER_UART_RX_BUF_SIZE); - seader_uart->tx_buf[0] = SYNC; - seader_uart->tx_buf[1] = CTRL; - seader_uart->tx_buf[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_GET_SLOT_STATUS; - seader_uart->tx_buf[2 + 5] = slot; - seader_uart->tx_buf[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); - - seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10); - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + uint8_t frame[2 + 10 + 1] = {0}; + frame[0] = SYNC; + frame[1] = CTRL; + frame[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_GET_SLOT_STATUS; + frame[2 + 5] = slot; + frame[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); + + seader_uart->tx_len = + seader_ccid_build_control_frame_lrc(frame, seader_ccid_control_frame_size(0U)); + seader_ccid_publish_tx_frame(seader_uart, frame, seader_uart->tx_len); } void seader_ccid_SetParameters(Seader* seader, uint8_t slot) { @@ -109,65 +123,66 @@ void seader_ccid_SetParameters(Seader* seader, uint8_t slot) { } else if(seader_uart->T == 1) { payloadLen = 7; } - memset(seader_uart->tx_buf, 0, SEADER_UART_RX_BUF_SIZE); - seader_uart->tx_buf[0] = SYNC; - seader_uart->tx_buf[1] = CTRL; - seader_uart->tx_buf[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_SET_PARAMETERS; - seader_uart->tx_buf[2 + 1] = payloadLen; - seader_uart->tx_buf[2 + 5] = slot; - seader_uart->tx_buf[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); - seader_uart->tx_buf[2 + 7] = seader_uart->T; - seader_uart->tx_buf[2 + 8] = 0; - seader_uart->tx_buf[2 + 9] = 0; + uint8_t frame[2 + 10 + 7 + 1] = {0}; + frame[0] = SYNC; + frame[1] = CTRL; + frame[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_SET_PARAMETERS; + frame[2 + 1] = payloadLen; + frame[2 + 5] = slot; + frame[2 + 6] = seader_ccid_next_sequence(seader_uart, slot); + frame[2 + 7] = seader_uart->T; + frame[2 + 8] = 0; + frame[2 + 9] = 0; uint8_t* atr = seader->ATR; seader_uart->t1.ifsc = atr[5]; if(seader_uart->T == 0) { // I'm leaving this here for completeness, but it was actually causing ICC_MUTE on the first apdu. - seader_uart->tx_buf[2 + 10] = 0x11; //atr[2]; //bmFindexDindex - seader_uart->tx_buf[2 + 11] = 0x00; //bmTCCKST1 - seader_uart->tx_buf[2 + 12] = 0x00; //bGuardTimeT0 - seader_uart->tx_buf[2 + 13] = 0x0a; //bWaitingIntegerT0 - seader_uart->tx_buf[2 + 14] = 0x00; //bClockStop + frame[2 + 10] = 0x11; //atr[2]; //bmFindexDindex + frame[2 + 11] = 0x00; //bmTCCKST1 + frame[2 + 12] = 0x00; //bGuardTimeT0 + frame[2 + 13] = 0x0a; //bWaitingIntegerT0 + frame[2 + 14] = 0x00; //bClockStop } else if(atr[4] == 0xB1 && seader_uart->T == 1) { - seader_uart->tx_buf[2 + 10] = atr[2]; //bmFindexDindex - seader_uart->tx_buf[2 + 11] = 0x10; //bmTCCKST1 - seader_uart->tx_buf[2 + 12] = 0xfe; //bGuardTimeT1 - seader_uart->tx_buf[2 + 13] = atr[6]; //bWaitingIntegerT1 - seader_uart->tx_buf[2 + 14] = atr[8]; //bClockStop - seader_uart->tx_buf[2 + 15] = seader_uart->t1.ifsc; //bIFSC - seader_uart->tx_buf[2 + 16] = 0x00; //bNadValue + frame[2 + 10] = atr[2]; //bmFindexDindex + frame[2 + 11] = 0x10; //bmTCCKST1 + frame[2 + 12] = 0xfe; //bGuardTimeT1 + frame[2 + 13] = atr[6]; //bWaitingIntegerT1 + frame[2 + 14] = atr[8]; //bClockStop + frame[2 + 15] = seader_uart->t1.ifsc; //bIFSC + frame[2 + 16] = 0x00; //bNadValue } else if(seader_uart->T == 1) { - seader_uart->tx_buf[2 + 10] = 0x11; //atr[2]; //bmFindexDindex - seader_uart->tx_buf[2 + 11] = 0x10; //bmTCCKST1 - seader_uart->tx_buf[2 + 12] = 0x00; //bGuardTimeT1 - seader_uart->tx_buf[2 + 13] = 0x4d; //atr[6]; //bWaitingIntegerT1 - seader_uart->tx_buf[2 + 14] = 0x00; //atr[8]; //bClockStop - seader_uart->tx_buf[2 + 15] = seader_uart->t1.ifsc; //bIFSC - seader_uart->tx_buf[2 + 16] = 0x00; //bNadValue + frame[2 + 10] = 0x11; //atr[2]; //bmFindexDindex + frame[2 + 11] = 0x10; //bmTCCKST1 + frame[2 + 12] = 0x00; //bGuardTimeT1 + frame[2 + 13] = 0x4d; //atr[6]; //bWaitingIntegerT1 + frame[2 + 14] = 0x00; //atr[8]; //bClockStop + frame[2 + 15] = seader_uart->t1.ifsc; //bIFSC + frame[2 + 16] = 0x00; //bNadValue } - seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10 + payloadLen); - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + seader_uart->tx_len = + seader_ccid_build_control_frame_lrc(frame, seader_ccid_control_frame_size(payloadLen)); + seader_ccid_publish_tx_frame(seader_uart, frame, seader_uart->tx_len); } void seader_ccid_GetParameters(SeaderUartBridge* seader_uart) { - memset(seader_uart->tx_buf, 0, SEADER_UART_RX_BUF_SIZE); - seader_uart->tx_buf[0] = SYNC; - seader_uart->tx_buf[1] = CTRL; - seader_uart->tx_buf[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_GET_PARAMETERS; - seader_uart->tx_buf[2 + 1] = 0; - seader_uart->tx_buf[2 + 5] = seader_ccid_current_slot(seader_uart); - seader_uart->tx_buf[2 + 6] = - seader_ccid_next_sequence(seader_uart, seader_ccid_current_slot(seader_uart)); - seader_uart->tx_buf[2 + 7] = 0; - seader_uart->tx_buf[2 + 8] = 0; - seader_uart->tx_buf[2 + 9] = 0; - - seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10); - - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + uint8_t frame[2 + 10 + 1] = {0}; + frame[0] = SYNC; + frame[1] = CTRL; + frame[2 + 0] = CCID_MESSAGE_TYPE_PC_TO_RDR_GET_PARAMETERS; + frame[2 + 1] = 0; + frame[2 + 5] = seader_ccid_current_slot(seader_uart); + frame[2 + 6] = seader_ccid_next_sequence(seader_uart, seader_ccid_current_slot(seader_uart)); + frame[2 + 7] = 0; + frame[2 + 8] = 0; + frame[2 + 9] = 0; + + seader_uart->tx_len = + seader_ccid_build_control_frame_lrc(frame, seader_ccid_control_frame_size(0U)); + + seader_ccid_publish_tx_frame(seader_uart, frame, seader_uart->tx_len); } void seader_ccid_XfrBlock(SeaderUartBridge* seader_uart, uint8_t* data, size_t len) { @@ -221,16 +236,7 @@ void seader_ccid_XfrBlockToSlot( seader_uart->tx_len = seader_add_lrc(frame, seader_uart->tx_len); - /* - char* display = malloc(seader_uart->tx_len * 2 + 1); - for(uint8_t i = 0; i < seader_uart->tx_len; i++) { - snprintf(display + (i * 2), sizeof(display), "%02x", frame[i]); - } - SEADER_VERBOSE_D(TAG, "seader_ccid_XfrBlockToSlot(%d) %d: %s", slot, seader_uart->tx_len, display); - free(display); - */ - - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + seader_ccid_publish_tx_frame(seader_uart, frame, seader_uart->tx_len); } size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) { @@ -320,7 +326,7 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) { if(cmd_len > 12 && cmd[0] == SYNC && cmd[1] == CTRL) { uint8_t* ccid = cmd + 2; message.bMessageType = ccid[0]; - message.dwLength = *((uint32_t*)(ccid + 1)); + message.dwLength = seader_ccid_decode_le32(ccid + 1); message.bSlot = ccid[5]; message.bSeq = ccid[6]; message.bStatus = ccid[7]; @@ -423,7 +429,8 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) { SEADER_VERBOSE_D(TAG, "Discarding message on non-sam slot"); } } else { - if(memcmp(SAM_ATR, message.payload, sizeof(SAM_ATR)) == 0) { + if(seader_ccid_payload_matches_exact( + message.payload, message.dwLength, SAM_ATR, sizeof(SAM_ATR))) { SEADER_VERBOSE_I(TAG, "SAM ATR!"); ccid_state->has_sam = true; ccid_state->sam_slot = message.bSlot; @@ -434,19 +441,21 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) { } else if(seader_uart->T == 1) { seader_ccid_SetParameters(seader, ccid_state->sam_slot); } - } else if(memcmp(SAM_ATR2, message.payload, sizeof(SAM_ATR2)) == 0) { + } else if(seader_ccid_payload_matches_exact( + message.payload, message.dwLength, SAM_ATR2, sizeof(SAM_ATR2))) { SEADER_VERBOSE_I(TAG, "SAM ATR2!"); ccid_state->has_sam = true; ccid_state->sam_slot = message.bSlot; - seader->ATR_len = sizeof(SAM_ATR); + seader->ATR_len = sizeof(SAM_ATR2); memcpy(seader->ATR, message.payload, seader->ATR_len); // I don't have an ATR2 to test with seader_ccid_GetParameters(seader_uart); - } else if(memcmp(SAM_ATR3, message.payload, sizeof(SAM_ATR3)) == 0) { + } else if(seader_ccid_payload_matches_exact( + message.payload, message.dwLength, SAM_ATR3, sizeof(SAM_ATR3))) { SEADER_VERBOSE_I(TAG, "SAM ATR3!"); ccid_state->has_sam = true; ccid_state->sam_slot = message.bSlot; - seader->ATR_len = sizeof(SAM_ATR); + seader->ATR_len = sizeof(SAM_ATR3); memcpy(seader->ATR, message.payload, seader->ATR_len); if(seader_uart->T == 0) { seader_ccid_GetParameters(seader_uart); diff --git a/ccid_logic.c b/ccid_logic.c index 955652f..9f88479 100644 --- a/ccid_logic.c +++ b/ccid_logic.c @@ -1,5 +1,7 @@ #include "ccid_logic.h" +#include + uint8_t seader_ccid_sequence_advance(uint8_t* sequence) { return (*sequence)++; } @@ -33,6 +35,11 @@ SeaderCcidStatus seader_ccid_decode_status(uint8_t status) { return decoded; } +uint32_t seader_ccid_decode_le32(const uint8_t bytes[4]) { + return ((uint32_t)bytes[0]) | ((uint32_t)bytes[1] << 8U) | ((uint32_t)bytes[2] << 16U) | + ((uint32_t)bytes[3] << 24U); +} + bool seader_ccid_response_matches_pending(bool pending, uint8_t expected_seq, uint8_t response_seq) { return !pending || (expected_seq == response_seq); } @@ -92,3 +99,19 @@ SeaderCcidDataRoute seader_ccid_route_data_block( return SeaderCcidDataRouteSamT1; } + +bool seader_ccid_payload_matches_exact( + const uint8_t* payload, + size_t payload_len, + const uint8_t* expected, + size_t expected_len) { + if(!payload || !expected || payload_len != expected_len) { + return false; + } + + return memcmp(payload, expected, expected_len) == 0; +} + +size_t seader_ccid_control_frame_size(size_t payload_len) { + return 2U + 10U + payload_len; +} diff --git a/ccid_logic.h b/ccid_logic.h index f5d6c07..1e1bc53 100644 --- a/ccid_logic.h +++ b/ccid_logic.h @@ -31,6 +31,7 @@ bool seader_ccid_data_in_scratchpad( const uint8_t* data, size_t payload_len); SeaderCcidStatus seader_ccid_decode_status(uint8_t status); +uint32_t seader_ccid_decode_le32(const uint8_t bytes[4]); 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, @@ -48,3 +49,9 @@ SeaderCcidDataRoute seader_ccid_route_data_block( uint8_t sam_slot, uint8_t message_slot, uint8_t protocol_t); +bool seader_ccid_payload_matches_exact( + const uint8_t* payload, + size_t payload_len, + const uint8_t* expected, + size_t expected_len); +size_t seader_ccid_control_frame_size(size_t payload_len); diff --git a/hf_14a_session.c b/hf_14a_session.c new file mode 100644 index 0000000..d8ae3a1 --- /dev/null +++ b/hf_14a_session.c @@ -0,0 +1,59 @@ +#include "hf_14a_session.h" + +#include + +bool seader_hf_14a_build_ats( + const SeaderHf14aAtsSource* source, + uint8_t* out, + size_t out_size, + size_t* out_len) { + if(out_len) { + *out_len = 0U; + } + + if(!source || !out || !out_len) { + return false; + } + + if(source->tl <= 1U) { + return true; + } + + size_t required = 1U + source->t1_tk_size; + if(source->t0 & SEADER_HF_14A_ATS_T0_TA1) { + required++; + } + if(source->t0 & SEADER_HF_14A_ATS_T0_TB1) { + required++; + } + if(source->t0 & SEADER_HF_14A_ATS_T0_TC1) { + required++; + } + + if(source->t1_tk_size && !source->t1_tk) { + return false; + } + + if(required > out_size || required > UINT8_MAX) { + return false; + } + + size_t len = 0U; + out[len++] = source->t0; + if(source->t0 & SEADER_HF_14A_ATS_T0_TA1) { + out[len++] = source->ta_1; + } + if(source->t0 & SEADER_HF_14A_ATS_T0_TB1) { + out[len++] = source->tb_1; + } + if(source->t0 & SEADER_HF_14A_ATS_T0_TC1) { + out[len++] = source->tc_1; + } + if(source->t1_tk_size) { + memcpy(out + len, source->t1_tk, source->t1_tk_size); + len += source->t1_tk_size; + } + + *out_len = len; + return true; +} diff --git a/hf_14a_session.h b/hf_14a_session.h new file mode 100644 index 0000000..0e9702d --- /dev/null +++ b/hf_14a_session.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#define SEADER_HF_14A_ATS_T0_TA1 (1U << 4) +#define SEADER_HF_14A_ATS_T0_TB1 (1U << 5) +#define SEADER_HF_14A_ATS_T0_TC1 (1U << 6) + +typedef struct { + uint8_t tl; + uint8_t t0; + uint8_t ta_1; + uint8_t tb_1; + uint8_t tc_1; + const uint8_t* t1_tk; + size_t t1_tk_size; +} SeaderHf14aAtsSource; + +bool seader_hf_14a_build_ats( + const SeaderHf14aAtsSource* source, + uint8_t* out, + size_t out_size, + size_t* out_len); diff --git a/hf_bridge_policy.c b/hf_bridge_policy.c new file mode 100644 index 0000000..cf941c3 --- /dev/null +++ b/hf_bridge_policy.c @@ -0,0 +1,72 @@ +#include "hf_bridge_policy.h" + +int seader_hf_bridge_begin_conversation( + void* context, + const SeaderHfBridgeConversationOps* ops, + int stop_command) { + if(!ops || !ops->set_conversation || !ops->begin_card_session || !ops->set_fail || + !ops->run_conversation) { + return stop_command; + } + + ops->set_conversation(context); + if(!ops->begin_card_session(context)) { + ops->set_fail(context); + return stop_command; + } + + return ops->run_conversation(context); +} + +uint16_t seader_hf_bridge_rf_status_code(SeaderHfBridgeRfStatus status) { + switch(status) { + case SeaderHfBridgeRfStatusSuccess: + return 0x0000U; + case SeaderHfBridgeRfStatusTimeout: + return 0x0020U; + case SeaderHfBridgeRfStatusCrc: + case SeaderHfBridgeRfStatusProtocol: + default: + return 0x0004U; + } +} + +void seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatus status, uint8_t bytes[2]) { + if(!bytes) { + return; + } + + const uint16_t code = seader_hf_bridge_rf_status_code(status); + bytes[0] = (uint8_t)((code >> 8) & 0xFFU); + bytes[1] = (uint8_t)(code & 0xFFU); +} + +SeaderHfBridgeApduDecision seader_hf_bridge_apdu_decision( + bool virtual_credential, + bool conversation_stage, + size_t len, + size_t max_len, + bool queue_has_space) { + if(!virtual_credential && !conversation_stage) { + return SeaderHfBridgeApduDecisionDiscardStale; + } + + if(len > max_len || !queue_has_space) { + return SeaderHfBridgeApduDecisionFailProtocol; + } + + return SeaderHfBridgeApduDecisionQueue; +} + +uint32_t seader_hf_bridge_timeout_us_to_fwt_fc(uint32_t timeout_us) { + if(timeout_us == 0U) { + return 0U; + } + + const uint64_t fwt_fc = ((uint64_t)timeout_us * 1356U + 99U) / 100U; + if(fwt_fc > UINT32_MAX) { + return UINT32_MAX; + } + + return (uint32_t)fwt_fc; +} diff --git a/hf_bridge_policy.h b/hf_bridge_policy.h new file mode 100644 index 0000000..8beb0e6 --- /dev/null +++ b/hf_bridge_policy.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +typedef enum { + SeaderHfBridgeRfStatusSuccess = 0, + SeaderHfBridgeRfStatusTimeout, + SeaderHfBridgeRfStatusCrc, + SeaderHfBridgeRfStatusProtocol, +} SeaderHfBridgeRfStatus; + +typedef enum { + SeaderHfBridgeApduDecisionDiscardStale = 0, + SeaderHfBridgeApduDecisionQueue, + SeaderHfBridgeApduDecisionFailProtocol, +} SeaderHfBridgeApduDecision; + +typedef struct { + void (*set_conversation)(void* context); + bool (*begin_card_session)(void* context); + void (*set_fail)(void* context); + int (*run_conversation)(void* context); +} SeaderHfBridgeConversationOps; + +int seader_hf_bridge_begin_conversation( + void* context, + const SeaderHfBridgeConversationOps* ops, + int stop_command); + +uint16_t seader_hf_bridge_rf_status_code(SeaderHfBridgeRfStatus status); +void seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatus status, uint8_t bytes[2]); +SeaderHfBridgeApduDecision seader_hf_bridge_apdu_decision( + bool virtual_credential, + bool conversation_stage, + size_t len, + size_t max_len, + bool queue_has_space); + +/* SAM nfcSend.timeOut is microseconds; Flipper NFC uses 13.56 MHz carrier cycles. */ +uint32_t seader_hf_bridge_timeout_us_to_fwt_fc(uint32_t timeout_us); diff --git a/hf_buffer_pool.c b/hf_buffer_pool.c new file mode 100644 index 0000000..02a4a85 --- /dev/null +++ b/hf_buffer_pool.c @@ -0,0 +1,51 @@ +#include "hf_buffer_pool.h" + +static void seader_hf_buffer_pair_release_buffers(SeaderHfBufferPair* pair) { + if(pair->tx) { + bit_buffer_free(pair->tx); + pair->tx = NULL; + } + if(pair->rx) { + bit_buffer_free(pair->rx); + pair->rx = NULL; + } + pair->tx_capacity = 0U; + pair->rx_capacity = 0U; +} + +bool seader_hf_buffer_pair_prepare( + SeaderHfBufferPair* pair, + size_t tx_capacity, + size_t rx_capacity, + size_t required_tx_len) { + if(!pair || required_tx_len > tx_capacity || tx_capacity == 0U || rx_capacity == 0U) { + return false; + } + + if(pair->tx && pair->rx && pair->tx_capacity == tx_capacity && + pair->rx_capacity == rx_capacity) { + bit_buffer_reset(pair->tx); + bit_buffer_reset(pair->rx); + return true; + } + + seader_hf_buffer_pair_release_buffers(pair); + pair->tx = bit_buffer_alloc(tx_capacity); + pair->rx = bit_buffer_alloc(rx_capacity); + if(!pair->tx || !pair->rx) { + seader_hf_buffer_pair_release_buffers(pair); + return false; + } + + pair->tx_capacity = tx_capacity; + pair->rx_capacity = rx_capacity; + return true; +} + +void seader_hf_buffer_pair_free(SeaderHfBufferPair* pair) { + if(!pair) { + return; + } + + seader_hf_buffer_pair_release_buffers(pair); +} diff --git a/hf_buffer_pool.h b/hf_buffer_pool.h new file mode 100644 index 0000000..4799213 --- /dev/null +++ b/hf_buffer_pool.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#ifdef SEADER_HOST_TEST +#include "lib/host_tests/bit_buffer.h" +#else +#include +#endif + +typedef struct { + BitBuffer* tx; + BitBuffer* rx; + size_t tx_capacity; + size_t rx_capacity; +} SeaderHfBufferPair; + +bool seader_hf_buffer_pair_prepare( + SeaderHfBufferPair* pair, + size_t tx_capacity, + size_t rx_capacity, + size_t required_tx_len); +void seader_hf_buffer_pair_free(SeaderHfBufferPair* pair); diff --git a/hf_interface_fal/hf.c b/hf_interface_fal/hf.c index cad0912..e294986 100644 --- a/hf_interface_fal/hf.c +++ b/hf_interface_fal/hf.c @@ -1,5 +1,8 @@ #include "hf_interface.h" #include "../trace_log.h" +#include "../hf_buffer_pool.h" +#include "../hf_14a_session.h" +#include "../seader_hf_read_plan.h" #include "../protocol/picopass_poller.h" #include "../protocol/rfal_picopass.h" @@ -33,7 +36,10 @@ typedef struct { NfcPoller* poller; Iso14443_4aPoller* iso14443_4a_poller; MfClassicPoller* mfc_poller; + SeaderHfBufferPair buffers; SeaderCredentialType active_type; + SeaderCredentialType detected_types[3]; + size_t detected_type_count; } PluginHfContext; static const uint8_t plugin_hf_update_block2[] = {RFAL_PICOPASS_CMD_UPDATE, 0x02}; @@ -65,6 +71,102 @@ static NfcCommand plugin_hf_run_conversation(PluginHfContext* ctx) { return NfcCommandContinue; } +typedef struct { + PluginHfContext* ctx; + uint8_t sak; + const uint8_t* uid; + uint8_t uid_len; + const uint8_t* ats; + uint8_t ats_len; +} PluginHfBeginConversationContext; + +static void plugin_hf_bridge_set_conversation(void* context) { + PluginHfBeginConversationContext* begin_ctx = context; + begin_ctx->ctx->api->set_stage(begin_ctx->ctx->host_ctx, PluginHfStageConversation); +} + +static bool plugin_hf_bridge_begin_card_session(void* context) { + PluginHfBeginConversationContext* begin_ctx = context; + return begin_ctx->ctx->api->begin_card_session( + begin_ctx->ctx->host_ctx, + begin_ctx->sak, + begin_ctx->uid, + begin_ctx->uid_len, + begin_ctx->ats, + begin_ctx->ats_len); +} + +static void plugin_hf_bridge_set_fail(void* context) { + PluginHfBeginConversationContext* begin_ctx = context; + begin_ctx->ctx->api->set_stage(begin_ctx->ctx->host_ctx, PluginHfStageFail); +} + +static int plugin_hf_bridge_run_conversation(void* context) { + PluginHfBeginConversationContext* begin_ctx = context; + return plugin_hf_run_conversation(begin_ctx->ctx); +} + +static NfcCommand plugin_hf_begin_conversation( + PluginHfContext* ctx, + uint8_t sak, + const uint8_t* uid, + uint8_t uid_len, + const uint8_t* ats, + uint8_t ats_len) { + PluginHfBeginConversationContext begin_ctx = { + .ctx = ctx, + .sak = sak, + .uid = uid, + .uid_len = uid_len, + .ats = ats, + .ats_len = ats_len, + }; + const SeaderHfBridgeConversationOps ops = { + .set_conversation = plugin_hf_bridge_set_conversation, + .begin_card_session = plugin_hf_bridge_begin_card_session, + .set_fail = plugin_hf_bridge_set_fail, + .run_conversation = plugin_hf_bridge_run_conversation, + }; + + return (NfcCommand)seader_hf_bridge_begin_conversation(&begin_ctx, &ops, NfcCommandStop); +} + +static void plugin_hf_send_error_status(PluginHfContext* ctx, SeaderHfBridgeRfStatus status) { + if(!ctx || !ctx->api || !ctx->api->send_nfc_rx_status) { + return; + } + + ctx->api->send_nfc_rx_status(ctx->host_ctx, NULL, 0U, status); +} + +static SeaderHfBridgeRfStatus plugin_hf_iso14443_4a_status(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return SeaderHfBridgeRfStatusSuccess; + case Iso14443_4aErrorNotPresent: + case Iso14443_4aErrorTimeout: + return SeaderHfBridgeRfStatusTimeout; + case Iso14443_4aErrorProtocol: + default: + return SeaderHfBridgeRfStatusProtocol; + } +} + +static SeaderHfBridgeRfStatus plugin_hf_mf_classic_status(MfClassicError error) { + switch(error) { + case MfClassicErrorNone: + return SeaderHfBridgeRfStatusSuccess; + case MfClassicErrorNotPresent: + case MfClassicErrorTimeout: + return SeaderHfBridgeRfStatusTimeout; + case MfClassicErrorProtocol: + case MfClassicErrorAuth: + case MfClassicErrorPartialRead: + default: + return SeaderHfBridgeRfStatusProtocol; + } +} + static bool plugin_hf_validate_host_api(const PluginHfHostApi* api) { if(!api) { FURI_LOG_E(TAG, "Missing HF host API"); @@ -82,6 +184,7 @@ static bool plugin_hf_validate_host_api(const PluginHfHostApi* api) { HF_REQUIRE_API(notify_worker_exit); HF_REQUIRE_API(begin_card_session); HF_REQUIRE_API(send_nfc_rx); + HF_REQUIRE_API(send_nfc_rx_status); HF_REQUIRE_API(run_conversation); HF_REQUIRE_API(set_stage); HF_REQUIRE_API(get_stage); @@ -203,7 +306,16 @@ static void } } -static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, size_t len) { +static uint32_t plugin_hf_sam_timeout_fwt(uint32_t timeout_us) { + const uint32_t fwt_fc = seader_hf_bridge_timeout_us_to_fwt_fc(timeout_us); + return fwt_fc != 0U ? fwt_fc : HF_PLUGIN_POLLER_MAX_FWT; +} + +static void plugin_hf_iso15693_transmit( + PluginHfContext* ctx, + uint8_t* buffer, + size_t len, + uint32_t timeout) { ctx = plugin_hf_get_ctx(ctx); if(!ctx) { return; @@ -213,17 +325,20 @@ static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, s 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); + if(!seader_hf_buffer_pair_prepare( + &ctx->buffers, + HF_PLUGIN_POLLER_MAX_BUFFER_SIZE + 1U, + HF_PLUGIN_POLLER_MAX_BUFFER_SIZE, + len)) { + FURI_LOG_E(TAG, "Failed to prepare picopass buffers"); ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return; } + BitBuffer* tx_buffer = ctx->buffers.tx; + BitBuffer* rx_buffer = ctx->buffers.rx; + uint8_t rx_data[HF_PLUGIN_POLLER_MAX_BUFFER_SIZE]; + size_t rx_len = 0U; + SeaderHfBridgeRfStatus rx_status = SeaderHfBridgeRfStatusTimeout; do { bit_buffer_append_bytes(tx_buffer, buffer, len); @@ -241,8 +356,9 @@ static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, s rx_data, sizeof(rx_data), &rx_len, - HF_PLUGIN_POLLER_MAX_FWT)) { - ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); + plugin_hf_sam_timeout_fwt(timeout), + &rx_status)) { + plugin_hf_send_error_status(ctx, rx_status); break; } bit_buffer_append_bytes(rx_buffer, rx_data, rx_len); @@ -254,16 +370,13 @@ static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, s (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, + uint32_t timeout, uint8_t format[3]) { UNUSED(timeout); UNUSED(format); @@ -278,15 +391,17 @@ static void plugin_hf_iso14443a_transmit( return; } - 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"); - if(tx_buffer) bit_buffer_free(tx_buffer); - if(rx_buffer) bit_buffer_free(rx_buffer); + if(!seader_hf_buffer_pair_prepare( + &ctx->buffers, + HF_PLUGIN_POLLER_MAX_BUFFER_SIZE + 1U, + HF_PLUGIN_POLLER_MAX_BUFFER_SIZE, + len + 1U)) { + FURI_LOG_E(TAG, "Failed to prepare 14A buffers"); ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return; } + BitBuffer* tx_buffer = ctx->buffers.tx; + BitBuffer* rx_buffer = ctx->buffers.rx; do { bit_buffer_append_bytes(tx_buffer, buffer, len); @@ -301,7 +416,7 @@ static void plugin_hf_iso14443a_transmit( 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); + plugin_hf_send_error_status(ctx, plugin_hf_iso14443_4a_status(error)); break; } @@ -324,19 +439,14 @@ static void plugin_hf_iso14443a_transmit( (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, + uint32_t timeout, uint8_t format[3]) { - UNUSED(timeout); - ctx = plugin_hf_get_ctx(ctx); if(!ctx) { return; @@ -347,24 +457,28 @@ static void plugin_hf_mfc_transmit( 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); + if(!seader_hf_buffer_pair_prepare( + &ctx->buffers, + HF_PLUGIN_POLLER_MAX_BUFFER_SIZE + 1U, + HF_PLUGIN_POLLER_MAX_BUFFER_SIZE, + len)) { + FURI_LOG_E(TAG, "Failed to prepare MFC buffers"); ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return; } + BitBuffer* tx_buffer = ctx->buffers.tx; + BitBuffer* rx_buffer = ctx->buffers.rx; + + const uint32_t mfc_fwt_fc = plugin_hf_sam_timeout_fwt(timeout); 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); + mf_classic_poller_send_frame(ctx->mfc_poller, tx_buffer, rx_buffer, mfc_fwt_fc); if(error != MfClassicErrorNone) { FURI_LOG_W(TAG, "mf_classic_poller_send_frame error %d", error); - ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); + plugin_hf_send_error_status(ctx, plugin_hf_mf_classic_status(error)); break; } } else if( @@ -395,7 +509,7 @@ static void plugin_hf_mfc_transmit( } MfClassicError error = mf_classic_poller_send_custom_parity_frame( - ctx->mfc_poller, tx_buffer, rx_buffer, 60000); + ctx->mfc_poller, tx_buffer, rx_buffer, mfc_fwt_fc); if(error != MfClassicErrorNone) { if(error == MfClassicErrorTimeout && ctx->api->get_credential_type(ctx->host_ctx) == @@ -404,7 +518,7 @@ static void plugin_hf_mfc_transmit( 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); + plugin_hf_send_error_status(ctx, plugin_hf_mf_classic_status(error)); break; } @@ -445,6 +559,8 @@ static void plugin_hf_mfc_transmit( bit_buffer_copy_bytes(rx_buffer, with_parity, length); } else { FURI_LOG_W(TAG, "Unhandled MFC format"); + plugin_hf_send_error_status(ctx, SeaderHfBridgeRfStatusProtocol); + break; } ctx->api->send_nfc_rx( @@ -452,9 +568,6 @@ static void plugin_hf_mfc_transmit( (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) { @@ -475,29 +588,22 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v if(iso_event->type == Iso14443_4aPollerEventTypeReady) { HF_DIAG_D("14A ready stage=%d", stage); if(stage == PluginHfStageCardDetect) { - if(!ctx->poller || !ctx->nfc_device) { - FURI_LOG_E( - TAG, - "14A detect without poller/device poller=%p device=%p", - (void*)ctx->poller, - (void*)ctx->nfc_device); + if(!ctx->poller) { + FURI_LOG_E(TAG, "14A detect without poller poller=%p", (void*)ctx->poller); ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return NfcCommandStop; } - const void* poller_data = nfc_poller_get_data(ctx->poller); - if(!poller_data) { + const Iso14443_4aData* iso_data = nfc_poller_get_data(ctx->poller); + if(!iso_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); + const uint8_t* uid = iso14443_4a_get_uid(iso_data, &uid_len); + if(!uid) { + FURI_LOG_E(TAG, "14A uid unavailable"); ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return NfcCommandStop; } @@ -508,44 +614,34 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v return NfcCommandStop; } - uint32_t t1_tk_size = 0; + size_t t1_tk_size = 0; + const uint8_t* t1_tk = NULL; 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; + if(t1_tk_size) { + t1_tk = simple_array_cget_data(iso_data->ats_data.t1_tk); } } - uint8_t ats_len = 0; + size_t ats_size = 0U; 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; - 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; - } - } - - if(!ctx->api->begin_card_session( - ctx->host_ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_len)) { + const SeaderHf14aAtsSource ats_source = { + .tl = iso_data->ats_data.tl, + .t0 = iso_data->ats_data.t0, + .ta_1 = iso_data->ats_data.ta_1, + .tb_1 = iso_data->ats_data.tb_1, + .tc_1 = iso_data->ats_data.tc_1, + .t1_tk = t1_tk, + .t1_tk_size = t1_tk_size, + }; + if(!seader_hf_14a_build_ats(&ats_source, ats, sizeof(ats), &ats_size)) { + FURI_LOG_E(TAG, "14A ATS unavailable size=%u", (unsigned)t1_tk_size); ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return NfcCommandStop; } - ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation); + + ret = plugin_hf_begin_conversation( + ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_size); } else if(stage == PluginHfStageConversation) { SEADER_VERBOSE_D(TAG, "14A enter conversation"); ret = plugin_hf_run_conversation(ctx); @@ -601,17 +697,8 @@ static NfcCommand plugin_hf_poller_callback_mfc(NfcGenericEvent event, void* con ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return NfcCommandStop; } - if(!ctx->api->begin_card_session( - ctx->host_ctx, - iso14443_3a_get_sak(mfc_data->iso14443_3a_data), - uid, - uid_len, - NULL, - 0)) { - ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); - return NfcCommandStop; - } - ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation); + ret = plugin_hf_begin_conversation( + ctx, iso14443_3a_get_sak(mfc_data->iso14443_3a_data), uid, uid_len, NULL, 0); } else if(stage == PluginHfStageConversation) { SEADER_VERBOSE_D(TAG, "MFC enter conversation"); ret = plugin_hf_run_conversation(ctx); @@ -649,12 +736,7 @@ static NfcCommand plugin_hf_poller_callback_picopass(PicopassPollerEvent event, ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); return NfcCommandStop; } - if(!ctx->api->begin_card_session( - ctx->host_ctx, 0, csn, sizeof(PicopassSerialNum), NULL, 0)) { - ctx->api->set_stage(ctx->host_ctx, PluginHfStageFail); - return NfcCommandStop; - } - ctx->api->set_stage(ctx->host_ctx, PluginHfStageConversation); + ret = plugin_hf_begin_conversation(ctx, 0, csn, sizeof(PicopassSerialNum), NULL, 0); } else if(stage == PluginHfStageConversation) { SEADER_VERBOSE_D(TAG, "Picopass enter conversation"); ret = plugin_hf_run_conversation(ctx); @@ -709,6 +791,7 @@ static void plugin_hf_free(void* plugin_ctx) { return; } plugin_hf_cleanup_pollers(ctx); + seader_hf_buffer_pair_free(&ctx->buffers); free(ctx); } @@ -723,6 +806,7 @@ static size_t plugin_hf_detect_supported_types( } size_t detected_type_count = 0; HF_DIAG_D("Detect supported HF types"); + ctx->detected_type_count = 0U; NfcPoller* poller_detect = nfc_poller_alloc(ctx->nfc, NfcProtocolIso14443_4a); if(!poller_detect) { FURI_LOG_W(TAG, "Failed to allocate 14A detect poller"); @@ -749,6 +833,13 @@ static size_t plugin_hf_detect_supported_types( detected_types, &detected_type_count, detected_capacity, SeaderCredentialTypePicopass); } + ctx->detected_type_count = detected_type_count; + const size_t cached_type_count = detected_type_count < COUNT_OF(ctx->detected_types) ? + detected_type_count : + COUNT_OF(ctx->detected_types); + memcpy( + ctx->detected_types, detected_types, cached_type_count * sizeof(ctx->detected_types[0])); + return detected_type_count; } @@ -762,18 +853,22 @@ static bool plugin_hf_start_read_for_type(void* plugin_ctx, SeaderCredentialType plugin_hf_cleanup_pollers(ctx); ctx->active_type = type; HF_DIAG_I("Start read type=%d", type); + const bool verify_start_type = seader_hf_read_plan_should_verify_start_type( + type, ctx->detected_types, ctx->detected_type_count); 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)) { + if(verify_start_type) { + 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); - 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"); @@ -784,16 +879,18 @@ static bool plugin_hf_start_read_for_type(void* plugin_ctx, SeaderCredentialType 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)) { + if(verify_start_type) { + 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); - 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"); @@ -835,7 +932,7 @@ static bool plugin_hf_handle_action(void* plugin_ctx, const PluginHfAction* acti if(action->type == PluginHfActionTypePicopassTx) { if(ctx->active_type != SeaderCredentialTypePicopass) return false; - plugin_hf_iso15693_transmit(ctx, action->data, action->len); + plugin_hf_iso15693_transmit(ctx, action->data, action->len, action->timeout); return true; } else if(action->type == PluginHfActionTypeMfClassicTx) { if(!ctx->poller) return false; diff --git a/hf_interface_fal/hf_interface.h b/hf_interface_fal/hf_interface.h index a51ec5b..42f8f46 100644 --- a/hf_interface_fal/hf_interface.h +++ b/hf_interface_fal/hf_interface.h @@ -4,13 +4,14 @@ #include #include +#include "../hf_bridge_policy.h" #include "../protocol/picopass_poller.h" #include "../seader_credential_type.h" #include #include #define HF_PLUGIN_APP_ID "plugin_hf" -#define HF_PLUGIN_API_VERSION 1 +#define HF_PLUGIN_API_VERSION 2 typedef enum { PluginHfStageCardDetect = 0, @@ -30,7 +31,7 @@ typedef struct { PluginHfActionType type; uint8_t* data; size_t len; - uint16_t timeout; + uint32_t timeout; uint8_t format[3]; } PluginHfAction; @@ -45,6 +46,11 @@ typedef struct { const uint8_t* ats, uint8_t ats_len); void (*send_nfc_rx)(void* host_ctx, uint8_t* buffer, size_t len); + void (*send_nfc_rx_status)( + void* host_ctx, + uint8_t* buffer, + size_t len, + SeaderHfBridgeRfStatus status); void (*run_conversation)(void* host_ctx); void (*set_stage)(void* host_ctx, PluginHfStage stage); PluginHfStage (*get_stage)(void* host_ctx); @@ -69,7 +75,8 @@ typedef struct { uint8_t* rx_data, size_t rx_capacity, size_t* rx_len, - uint32_t fwt_fc); + uint32_t fwt_fc, + SeaderHfBridgeRfStatus* status); /* Optional UX hook for richer read failure text. */ void (*set_read_error)(void* host_ctx, const char* text); diff --git a/hf_read_lifecycle.c b/hf_read_lifecycle.c index 3d79a7c..603e202 100644 --- a/hf_read_lifecycle.c +++ b/hf_read_lifecycle.c @@ -1,5 +1,7 @@ #include "hf_read_lifecycle.h" +#include + SeaderHfCardSessionDecision seader_hf_read_on_card_detect(SeaderHfReadState state, bool sam_can_accept_card) { if(state != SeaderHfReadStateDetecting) { @@ -43,8 +45,94 @@ const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason) return "Protocol error"; case SeaderHfReadFailureReasonInternalState: return "Read state error"; + case SeaderHfReadFailureReasonSamKeysMissing: + return "SAM missing keys"; + case SeaderHfReadFailureReasonResourceExhausted: + return "SAM exchange memory error"; case SeaderHfReadFailureReasonNone: default: return "Read failed"; } } + +void seader_hf_read_prepare_context( + SeaderHfReadFailureReason* failure_reason, + char* read_error, + size_t read_error_size) { + if(failure_reason) { + *failure_reason = SeaderHfReadFailureReasonNone; + } + + if(read_error && read_error_size > 0U) { + read_error[0] = '\0'; + } +} + +bool seader_pacs2_indicates_sam_keys_missing( + bool has_media_type, + const uint8_t* pacs_bits, + size_t pacs_bits_size) { + if(!has_media_type) { + return false; + } + + return !pacs_bits || pacs_bits_size < 2U; +} + +static const char* seader_hf_read_media_type_label(SeaderHfPacsMediaType media_type) { + switch(media_type) { + case SeaderHfPacsMediaTypeDesfire: + return "DESFire"; + case SeaderHfPacsMediaTypeMifare: + return "MIFARE"; + case SeaderHfPacsMediaTypePicopass: + return "PicoPass"; + case SeaderHfPacsMediaTypeMifarePlus: + return "MIFARE Plus"; + case SeaderHfPacsMediaTypeSeos: + return "Seos"; + case SeaderHfPacsMediaTypeUnknown: + default: + return NULL; + } +} + +void seader_hf_read_format_sam_keys_missing_error( + bool has_media_type, + SeaderHfPacsMediaType media_type, + bool standard_pacs_keys_probed, + bool standard_pacs_keys_present, + char* out, + size_t out_size) { + if(!out || out_size == 0U) { + return; + } + + out[0] = '\0'; + + const char* media_label = has_media_type ? seader_hf_read_media_type_label(media_type) : NULL; + const bool standard_keys_missing = standard_pacs_keys_probed && !standard_pacs_keys_present; + + if(media_label && standard_keys_missing) { + snprintf( + out, + out_size, + "%s recognized.\nUnable to read keys.\nSAM missing standard keys.", + media_label); + return; + } + + if(media_label) { + snprintf( + out, out_size, "%s recognized.\nUnable to read keys.\nCheck SAM Info.", media_label); + return; + } + + if(standard_keys_missing) { + snprintf( + out, out_size, "Unable to read keys.\nSAM missing standard\nkeys. Check SAM Info."); + return; + } + + snprintf(out, out_size, "Unable to read keys.\nCheck SAM Info."); +} diff --git a/hf_read_lifecycle.h b/hf_read_lifecycle.h index 5352633..d07a7f9 100644 --- a/hf_read_lifecycle.h +++ b/hf_read_lifecycle.h @@ -1,7 +1,19 @@ #pragma once +#include "sam_key_label.h" + #include #include +#include + +typedef enum { + SeaderHfPacsMediaTypeUnknown = 0, + SeaderHfPacsMediaTypeDesfire = 1, + SeaderHfPacsMediaTypeMifare = 2, + SeaderHfPacsMediaTypePicopass = 3, + SeaderHfPacsMediaTypeMifarePlus = 6, + SeaderHfPacsMediaTypeSeos = 7, +} SeaderHfPacsMediaType; typedef enum { SeaderHfReadStateIdle = 0, @@ -21,6 +33,8 @@ typedef enum { SeaderHfReadFailureReasonBoardMissing, SeaderHfReadFailureReasonProtocolError, SeaderHfReadFailureReasonInternalState, + SeaderHfReadFailureReasonSamKeysMissing, + SeaderHfReadFailureReasonResourceExhausted, } SeaderHfReadFailureReason; typedef enum { @@ -37,3 +51,18 @@ bool seader_hf_read_should_timeout( uint32_t elapsed_ms, uint32_t timeout_ms); const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason); +void seader_hf_read_prepare_context( + SeaderHfReadFailureReason* failure_reason, + char* read_error, + size_t read_error_size); +bool seader_pacs2_indicates_sam_keys_missing( + bool has_media_type, + const uint8_t* pacs_bits, + size_t pacs_bits_size); +void seader_hf_read_format_sam_keys_missing_error( + bool has_media_type, + SeaderHfPacsMediaType media_type, + bool standard_pacs_keys_probed, + bool standard_pacs_keys_present, + char* out, + size_t out_size); diff --git a/hf_release_sequence.c b/hf_release_sequence.c index 409bc62..675d97e 100644 --- a/hf_release_sequence.c +++ b/hf_release_sequence.c @@ -23,6 +23,7 @@ void seader_hf_release_sequence_run(SeaderHfReleaseSequence* sequence) { 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); + seader_hf_release_callback_invoke(sequence->host_nfc_release, 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) { diff --git a/hf_release_sequence.h b/hf_release_sequence.h index 015f8f5..408d069 100644 --- a/hf_release_sequence.h +++ b/hf_release_sequence.h @@ -13,6 +13,7 @@ typedef struct { SeaderHfReleaseCallback host_picopass_release; SeaderHfReleaseCallback plugin_free; SeaderHfReleaseCallback plugin_manager_unload; + SeaderHfReleaseCallback host_nfc_release; SeaderHfReleaseCallback worker_reset; } SeaderHfReleaseSequence; diff --git a/hf_sam_response_view.c b/hf_sam_response_view.c new file mode 100644 index 0000000..a0af489 --- /dev/null +++ b/hf_sam_response_view.c @@ -0,0 +1,154 @@ +#include "hf_sam_response_view.h" + +#include + +#define SEADER_ARTEMIS_HEADER_LEN (6U) +#define SEADER_ASN_TAG_PAYLOAD_NFC_COMMAND (0xA1U) +#define SEADER_ASN_TAG_NFC_SEND (0xA1U) +#define SEADER_ASN_TAG_NFC_SEND_DATA (0x80U) +#define SEADER_ASN_TAG_NFC_SEND_PROTOCOL (0x81U) +#define SEADER_ASN_TAG_NFC_SEND_TIMEOUT (0x82U) +#define SEADER_ASN_TAG_NFC_SEND_FORMAT (0x85U) + +typedef struct { + uint8_t tag; + const uint8_t* value; + size_t len; + const uint8_t* next; +} SeaderBerTlv; + +static bool seader_ber_read_tlv(const uint8_t* cursor, const uint8_t* end, SeaderBerTlv* tlv) { + if(!cursor || !end || !tlv || cursor >= end) { + return false; + } + + tlv->tag = *cursor++; + if(cursor >= end) { + return false; + } + + uint8_t len_byte = *cursor++; + size_t len = 0U; + if((len_byte & 0x80U) == 0U) { + len = len_byte; + } else { + size_t len_len = len_byte & 0x7FU; + if(len_len == 0U || len_len > sizeof(size_t) || (size_t)(end - cursor) < len_len) { + return false; + } + + for(size_t i = 0U; i < len_len; i++) { + len = (len << 8) | cursor[i]; + } + cursor += len_len; + } + + if((size_t)(end - cursor) < len) { + return false; + } + + tlv->value = cursor; + tlv->len = len; + tlv->next = cursor + len; + return true; +} + +static bool seader_read_be_u16(const uint8_t* value, size_t len, uint16_t* out) { + if(!value || !out || len == 0U || len > 2U) { + return false; + } + + uint16_t result = 0U; + for(size_t i = 0U; i < len; i++) { + result = (uint16_t)((result << 8) | value[i]); + } + *out = result; + return true; +} + +static bool seader_read_be_u32(const uint8_t* value, size_t len, uint32_t* out) { + if(!value || !out || len == 0U || len > 4U) { + return false; + } + + uint32_t result = 0U; + for(size_t i = 0U; i < len; i++) { + result = (result << 8) | value[i]; + } + *out = result; + return true; +} + +bool seader_hf_sam_response_view_parse_nfc_send( + const uint8_t* response, + size_t response_len, + SeaderHfSamNfcSendView* out) { + if(!response || !out || response_len <= SEADER_ARTEMIS_HEADER_LEN) { + return false; + } + + SeaderHfSamNfcSendView view = {0}; + const uint8_t* end = response + response_len; + const uint8_t* cursor = response + SEADER_ARTEMIS_HEADER_LEN; + SeaderBerTlv payload_tlv = {0}; + SeaderBerTlv nfc_command_tlv = {0}; + + if(!seader_ber_read_tlv(cursor, end, &payload_tlv) || + payload_tlv.tag != SEADER_ASN_TAG_PAYLOAD_NFC_COMMAND || payload_tlv.next != end) { + return false; + } + + if(!seader_ber_read_tlv( + payload_tlv.value, payload_tlv.value + payload_tlv.len, &nfc_command_tlv) || + nfc_command_tlv.tag != SEADER_ASN_TAG_NFC_SEND || + nfc_command_tlv.next != payload_tlv.value + payload_tlv.len) { + return false; + } + + bool has_data = false; + bool has_protocol = false; + bool has_timeout = false; + cursor = nfc_command_tlv.value; + const uint8_t* nfc_send_end = nfc_command_tlv.value + nfc_command_tlv.len; + while(cursor < nfc_send_end) { + SeaderBerTlv field = {0}; + if(!seader_ber_read_tlv(cursor, nfc_send_end, &field)) { + return false; + } + + switch(field.tag) { + case SEADER_ASN_TAG_NFC_SEND_DATA: + view.data = field.value; + view.data_len = field.len; + has_data = true; + break; + case SEADER_ASN_TAG_NFC_SEND_PROTOCOL: + if(!seader_read_be_u16(field.value, field.len, &view.protocol)) { + return false; + } + has_protocol = true; + break; + case SEADER_ASN_TAG_NFC_SEND_TIMEOUT: + if(!seader_read_be_u32(field.value, field.len, &view.timeout_us)) { + return false; + } + has_timeout = true; + break; + case SEADER_ASN_TAG_NFC_SEND_FORMAT: + view.format = field.value; + view.format_len = field.len; + break; + default: + break; + } + + cursor = field.next; + } + + if(!has_data || !has_protocol || !has_timeout) { + return false; + } + + memcpy(out, &view, sizeof(view)); + return true; +} diff --git a/hf_sam_response_view.h b/hf_sam_response_view.h new file mode 100644 index 0000000..d5da2e5 --- /dev/null +++ b/hf_sam_response_view.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +typedef struct { + const uint8_t* data; + size_t data_len; + uint16_t protocol; + uint32_t timeout_us; + const uint8_t* format; + size_t format_len; +} SeaderHfSamNfcSendView; + +bool seader_hf_sam_response_view_parse_nfc_send( + const uint8_t* response, + size_t response_len, + SeaderHfSamNfcSendView* out); diff --git a/lib/host_tests/bit_buffer.h b/lib/host_tests/bit_buffer.h index 8af0ff0..20be7cb 100644 --- a/lib/host_tests/bit_buffer.h +++ b/lib/host_tests/bit_buffer.h @@ -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); diff --git a/lib/host_tests/bit_buffer_mock.c b/lib/host_tests/bit_buffer_mock.c index fd34458..777427a 100644 --- a/lib/host_tests/bit_buffer_mock.c +++ b/lib/host_tests/bit_buffer_mock.c @@ -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); diff --git a/lib/host_tests/t1_test_stubs.c b/lib/host_tests/t1_test_stubs.c index da69ec0..4f98e45 100644 --- a/lib/host_tests/t1_test_stubs.c +++ b/lib/host_tests/t1_test_stubs.c @@ -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); + } +} diff --git a/lib/host_tests/t_1_host_env.h b/lib/host_tests/t_1_host_env.h index 52ef0d6..d60d464 100644 --- a/lib/host_tests/t_1_host_env.h +++ b/lib/host_tests/t_1_host_env.h @@ -8,6 +8,7 @@ #include #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; diff --git a/lib/host_tests/test_board_identity.c b/lib/host_tests/test_board_identity.c new file mode 100644 index 0000000..1075510 --- /dev/null +++ b/lib/host_tests/test_board_identity.c @@ -0,0 +1,46 @@ +#include "munit.h" + +#include "board_identity.h" + +static MunitResult test_classifies_strap_states(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_int( + seader_board_classify(false, false, false), ==, SeaderBoardClassNone); + munit_assert_int( + seader_board_classify(false, true, false), ==, SeaderBoardClassSamOnly); + munit_assert_int( + seader_board_classify(false, false, true), ==, SeaderBoardClassSamOnly); + munit_assert_int( + seader_board_classify(true, false, false), ==, SeaderBoardClassUhfCarrier); + munit_assert_int( + seader_board_classify(true, true, true), ==, SeaderBoardClassUhfCarrier); + return MUNIT_OK; +} + +static MunitResult test_uhf_support_policy(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_false(seader_board_class_supports_uhf(SeaderBoardClassUnknown)); + munit_assert_false(seader_board_class_supports_uhf(SeaderBoardClassNone)); + munit_assert_false(seader_board_class_supports_uhf(SeaderBoardClassSamOnly)); + munit_assert_true(seader_board_class_supports_uhf(SeaderBoardClassUhfCarrier)); + return MUNIT_OK; +} + +static MunitTest test_board_identity_cases[] = { + {(char*)"/strap-classifier", test_classifies_strap_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/uhf-support-policy", test_uhf_support_policy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_board_identity_suite = { + "", + test_board_identity_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; + diff --git a/lib/host_tests/test_ccid_logic.c b/lib/host_tests/test_ccid_logic.c index 52fde88..f8eb720 100644 --- a/lib/host_tests/test_ccid_logic.c +++ b/lib/host_tests/test_ccid_logic.c @@ -91,6 +91,17 @@ static MunitResult test_status_decode_time_extension(const MunitParameter params return MUNIT_OK; } +static MunitResult test_decode_le32_accepts_unaligned_bytes( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const uint8_t frame[] = {0xAA, 0x78, 0x56, 0x34, 0x12, 0xBB}; + munit_assert_uint32(seader_ccid_decode_le32(frame + 1), ==, 0x12345678U); + return MUNIT_OK; +} + static MunitResult test_find_start_skips_nak_triplet(const MunitParameter params[], void* fixture) { (void)params; (void)fixture; @@ -152,6 +163,7 @@ static MunitResult test_data_block_route(const MunitParameter params[], void* fi /* CCID says "bSlot identifies which ICC slot is being addressed"; protocol 00h is T=0 and 01h is T=1. */ 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(true, 1, 1, 1), ==, SeaderCcidDataRouteSamT1); munit_assert_int( seader_ccid_route_data_block(false, 0, 0, 1), ==, SeaderCcidDataRouteAtrRecognition); munit_assert_int( @@ -159,6 +171,45 @@ static MunitResult test_data_block_route(const MunitParameter params[], void* fi return MUNIT_OK; } +static MunitResult test_payload_match_requires_exact_length( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const uint8_t expected[] = {0x3b, 0x90, 0x96, 0x91}; + const uint8_t exact[] = {0x3b, 0x90, 0x96, 0x91}; + const uint8_t short_payload_with_matching_prefix[] = {0x3b, 0x90, 0x96, 0x91}; + const uint8_t long_payload[] = {0x3b, 0x90, 0x96, 0x91, 0x00}; + const uint8_t wrong_payload[] = {0x3b, 0x90, 0x96, 0x92}; + + munit_assert_true(seader_ccid_payload_matches_exact( + exact, sizeof(exact), expected, sizeof(expected))); + munit_assert_false(seader_ccid_payload_matches_exact( + short_payload_with_matching_prefix, sizeof(short_payload_with_matching_prefix) - 1U, expected, sizeof(expected))); + munit_assert_false(seader_ccid_payload_matches_exact( + long_payload, sizeof(long_payload), expected, sizeof(expected))); + munit_assert_false(seader_ccid_payload_matches_exact( + wrong_payload, sizeof(wrong_payload), expected, sizeof(expected))); + munit_assert_false( + seader_ccid_payload_matches_exact(NULL, sizeof(exact), expected, sizeof(expected))); + munit_assert_false( + seader_ccid_payload_matches_exact(exact, sizeof(exact), NULL, sizeof(expected))); + return MUNIT_OK; +} + +static MunitResult test_control_frame_size_includes_transport_header( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_size(seader_ccid_control_frame_size(0U), ==, 12U); + munit_assert_size(seader_ccid_control_frame_size(5U), ==, 17U); + munit_assert_size(seader_ccid_control_frame_size(7U), ==, 19U); + return MUNIT_OK; +} + static MunitTest test_ccid_cases[] = { {(char*)"/sequence/advance-wraps-through-ff", test_sequence_advance_wraps, @@ -197,6 +248,12 @@ static MunitTest test_ccid_cases[] = { NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/frame/decode-le32-unaligned", + test_decode_le32_accepts_unaligned_bytes, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, {(char*)"/frame/find-start-skips-nak-triplet", test_find_start_skips_nak_triplet, NULL, @@ -221,6 +278,18 @@ static MunitTest test_ccid_cases[] = { NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/payload/match-requires-exact-length", + test_payload_match_requires_exact_length, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/frame/control-size-includes-header", + test_control_frame_size_includes_transport_header, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, {NULL, NULL, NULL, NULL, 0, NULL}, }; diff --git a/lib/host_tests/test_hf_14a_session.c b/lib/host_tests/test_hf_14a_session.c new file mode 100644 index 0000000..ad87cab --- /dev/null +++ b/lib/host_tests/test_hf_14a_session.c @@ -0,0 +1,124 @@ +#include "hf_14a_session.h" +#include "munit.h" + +static MunitResult test_build_ats_empty_when_tl_has_no_ats( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderHf14aAtsSource source = {.tl = 1U}; + uint8_t ats[8] = {0xffU}; + size_t ats_len = 99U; + + munit_assert_true(seader_hf_14a_build_ats(&source, ats, sizeof(ats), &ats_len)); + munit_assert_size(ats_len, ==, 0U); + return MUNIT_OK; +} + +static MunitResult test_build_ats_packs_declared_interface_bytes( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderHf14aAtsSource source = { + .tl = 4U, + .t0 = SEADER_HF_14A_ATS_T0_TA1 | SEADER_HF_14A_ATS_T0_TC1 | 0x05U, + .ta_1 = 0x11U, + .tb_1 = 0x22U, + .tc_1 = 0x33U, + }; + uint8_t ats[8] = {0}; + size_t ats_len = 0U; + + munit_assert_true(seader_hf_14a_build_ats(&source, ats, sizeof(ats), &ats_len)); + munit_assert_size(ats_len, ==, 3U); + munit_assert_uint8(ats[0], ==, source.t0); + munit_assert_uint8(ats[1], ==, source.ta_1); + munit_assert_uint8(ats[2], ==, source.tc_1); + return MUNIT_OK; +} + +static MunitResult test_build_ats_appends_historical_bytes( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const uint8_t historical[] = {0xaaU, 0xbbU, 0xccU}; + const SeaderHf14aAtsSource source = { + .tl = 5U, + .t0 = SEADER_HF_14A_ATS_T0_TB1, + .tb_1 = 0x44U, + .t1_tk = historical, + .t1_tk_size = sizeof(historical), + }; + uint8_t ats[8] = {0}; + size_t ats_len = 0U; + + munit_assert_true(seader_hf_14a_build_ats(&source, ats, sizeof(ats), &ats_len)); + munit_assert_size(ats_len, ==, 5U); + munit_assert_uint8(ats[0], ==, source.t0); + munit_assert_uint8(ats[1], ==, source.tb_1); + munit_assert_memory_equal(sizeof(historical), ats + 2U, historical); + return MUNIT_OK; +} + +static MunitResult test_build_ats_rejects_oversized_output( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const uint8_t historical[] = {0x01U, 0x02U, 0x03U}; + const SeaderHf14aAtsSource source = { + .tl = 5U, + .t0 = SEADER_HF_14A_ATS_T0_TA1, + .ta_1 = 0x11U, + .t1_tk = historical, + .t1_tk_size = sizeof(historical), + }; + uint8_t ats[4] = {0}; + size_t ats_len = 99U; + + munit_assert_false(seader_hf_14a_build_ats(&source, ats, sizeof(ats), &ats_len)); + munit_assert_size(ats_len, ==, 0U); + return MUNIT_OK; +} + +static MunitTest test_hf_14a_session_cases[] = { + {(char*)"/build-ats/no-ats", + test_build_ats_empty_when_tl_has_no_ats, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/build-ats/interface-bytes", + test_build_ats_packs_declared_interface_bytes, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/build-ats/historical-bytes", + test_build_ats_appends_historical_bytes, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/build-ats/oversized", + test_build_ats_rejects_oversized_output, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_hf_14a_session_suite = { + "", + test_hf_14a_session_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_hf_bridge_policy.c b/lib/host_tests/test_hf_bridge_policy.c new file mode 100644 index 0000000..6240a2c --- /dev/null +++ b/lib/host_tests/test_hf_bridge_policy.c @@ -0,0 +1,171 @@ +#include "munit.h" + +#include "hf_bridge_policy.h" + +typedef struct { + unsigned index; + const char* calls[4]; + bool begin_result; + int run_result; +} BridgeRecorder; + +static void record_call(BridgeRecorder* recorder, const char* name) { + if(recorder && recorder->index < (sizeof(recorder->calls) / sizeof(recorder->calls[0]))) { + recorder->calls[recorder->index++] = name; + } +} + +static void record_set_conversation(void* context) { + record_call(context, "set-conversation"); +} + +static bool record_begin_card_session(void* context) { + BridgeRecorder* recorder = context; + record_call(recorder, "begin-card-session"); + return recorder->begin_result; +} + +static void record_set_fail(void* context) { + record_call(context, "set-fail"); +} + +static int record_run_conversation(void* context) { + BridgeRecorder* recorder = context; + record_call(recorder, "run-conversation"); + return recorder->run_result; +} + +static const SeaderHfBridgeConversationOps bridge_ops = { + .set_conversation = record_set_conversation, + .begin_card_session = record_begin_card_session, + .set_fail = record_set_fail, + .run_conversation = record_run_conversation, +}; + +static MunitResult test_begin_conversation_sets_stage_before_card_session( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + BridgeRecorder recorder = { + .begin_result = true, + .run_result = 42, + }; + + const int result = seader_hf_bridge_begin_conversation(&recorder, &bridge_ops, -1); + + munit_assert_int(result, ==, 42); + munit_assert_uint(recorder.index, ==, 3); + munit_assert_string_equal(recorder.calls[0], "set-conversation"); + munit_assert_string_equal(recorder.calls[1], "begin-card-session"); + munit_assert_string_equal(recorder.calls[2], "run-conversation"); + return MUNIT_OK; +} + +static MunitResult test_begin_conversation_fails_without_running_conversation( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + BridgeRecorder recorder = { + .begin_result = false, + .run_result = 42, + }; + + const int result = seader_hf_bridge_begin_conversation(&recorder, &bridge_ops, -1); + + munit_assert_int(result, ==, -1); + munit_assert_uint(recorder.index, ==, 3); + munit_assert_string_equal(recorder.calls[0], "set-conversation"); + munit_assert_string_equal(recorder.calls[1], "begin-card-session"); + munit_assert_string_equal(recorder.calls[2], "set-fail"); + return MUNIT_OK; +} + +static MunitResult test_rf_status_mapping(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + uint8_t bytes[2] = {0xFF, 0xFF}; + + seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusSuccess, bytes); + munit_assert_uint8(bytes[0], ==, 0x00); + munit_assert_uint8(bytes[1], ==, 0x00); + + seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusTimeout, bytes); + munit_assert_uint8(bytes[0], ==, 0x00); + munit_assert_uint8(bytes[1], ==, 0x20); + + seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusCrc, bytes); + munit_assert_uint8(bytes[0], ==, 0x00); + munit_assert_uint8(bytes[1], ==, 0x04); + + seader_hf_bridge_rf_status_bytes(SeaderHfBridgeRfStatusProtocol, bytes); + munit_assert_uint8(bytes[0], ==, 0x00); + munit_assert_uint8(bytes[1], ==, 0x04); + return MUNIT_OK; +} + +static MunitResult test_apdu_decision_discards_stale_messages( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_int( + seader_hf_bridge_apdu_decision(false, false, 16U, 258U, true), + ==, + SeaderHfBridgeApduDecisionDiscardStale); + munit_assert_int( + seader_hf_bridge_apdu_decision(true, false, 16U, 258U, true), + ==, + SeaderHfBridgeApduDecisionQueue); + return MUNIT_OK; +} + +static MunitResult test_apdu_decision_fails_overflow( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_int( + seader_hf_bridge_apdu_decision(false, true, 259U, 258U, true), + ==, + SeaderHfBridgeApduDecisionFailProtocol); + munit_assert_int( + seader_hf_bridge_apdu_decision(false, true, 16U, 258U, false), + ==, + SeaderHfBridgeApduDecisionFailProtocol); + return MUNIT_OK; +} + +static MunitResult test_timeout_us_to_fwt_fc(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_uint32(seader_hf_bridge_timeout_us_to_fwt_fc(0U), ==, 0U); + munit_assert_uint32(seader_hf_bridge_timeout_us_to_fwt_fc(1000U), ==, 13560U); + munit_assert_uint32(seader_hf_bridge_timeout_us_to_fwt_fc(20000U), ==, 271200U); + return MUNIT_OK; +} + +static MunitTest test_hf_bridge_policy_cases[] = { + {(char*)"/begin-conversation-order", test_begin_conversation_sets_stage_before_card_session, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/begin-conversation-fail", test_begin_conversation_fails_without_running_conversation, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/rf-status-mapping", test_rf_status_mapping, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/apdu-discard-stale", test_apdu_decision_discards_stale_messages, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/apdu-overflow-fails", test_apdu_decision_fails_overflow, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/timeout-us-to-fwt", test_timeout_us_to_fwt_fc, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_hf_bridge_policy_suite = { + "", + test_hf_bridge_policy_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_hf_buffer_pool.c b/lib/host_tests/test_hf_buffer_pool.c new file mode 100644 index 0000000..f8034d1 --- /dev/null +++ b/lib/host_tests/test_hf_buffer_pool.c @@ -0,0 +1,112 @@ +#include "hf_buffer_pool.h" +#include "munit.h" + +static MunitResult test_prepare_allocates_and_resets_buffers( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderHfBufferPair pair = {0}; + const uint8_t tx_data[] = {0x01U, 0x02U}; + const uint8_t rx_data[] = {0x03U}; + + munit_assert_true(seader_hf_buffer_pair_prepare(&pair, 8U, 8U, sizeof(tx_data))); + munit_assert_not_null(pair.tx); + munit_assert_not_null(pair.rx); + + bit_buffer_append_bytes(pair.tx, tx_data, sizeof(tx_data)); + bit_buffer_append_bytes(pair.rx, rx_data, sizeof(rx_data)); + munit_assert_size(bit_buffer_get_size_bytes(pair.tx), ==, sizeof(tx_data)); + munit_assert_size(bit_buffer_get_size_bytes(pair.rx), ==, sizeof(rx_data)); + + munit_assert_true(seader_hf_buffer_pair_prepare(&pair, 8U, 8U, sizeof(tx_data))); + munit_assert_size(bit_buffer_get_size_bytes(pair.tx), ==, 0U); + munit_assert_size(bit_buffer_get_size_bytes(pair.rx), ==, 0U); + + seader_hf_buffer_pair_free(&pair); + return MUNIT_OK; +} + +static MunitResult test_prepare_reuses_existing_buffers( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderHfBufferPair pair = {0}; + munit_assert_true(seader_hf_buffer_pair_prepare(&pair, 8U, 8U, 1U)); + BitBuffer* first_tx = pair.tx; + BitBuffer* first_rx = pair.rx; + + munit_assert_true(seader_hf_buffer_pair_prepare(&pair, 8U, 8U, 1U)); + munit_assert_ptr(pair.tx, ==, first_tx); + munit_assert_ptr(pair.rx, ==, first_rx); + + seader_hf_buffer_pair_free(&pair); + return MUNIT_OK; +} + +static MunitResult test_prepare_rejects_oversized_tx( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderHfBufferPair pair = {0}; + munit_assert_false(seader_hf_buffer_pair_prepare(&pair, 8U, 8U, 9U)); + munit_assert_null(pair.tx); + munit_assert_null(pair.rx); + return MUNIT_OK; +} + +static MunitResult test_free_clears_pair(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + SeaderHfBufferPair pair = {0}; + munit_assert_true(seader_hf_buffer_pair_prepare(&pair, 8U, 8U, 1U)); + + seader_hf_buffer_pair_free(&pair); + munit_assert_null(pair.tx); + munit_assert_null(pair.rx); + munit_assert_size(pair.tx_capacity, ==, 0U); + munit_assert_size(pair.rx_capacity, ==, 0U); + return MUNIT_OK; +} + +static MunitTest test_hf_buffer_pool_cases[] = { + {(char*)"/prepare/allocates-and-resets", + test_prepare_allocates_and_resets_buffers, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/prepare/reuses-existing", + test_prepare_reuses_existing_buffers, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/prepare/rejects-oversized-tx", + test_prepare_rejects_oversized_tx, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/free/clears-pair", + test_free_clears_pair, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_hf_buffer_pool_suite = { + "", + test_hf_buffer_pool_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_hf_read_lifecycle.c b/lib/host_tests/test_hf_read_lifecycle.c index 5434815..0cea6d0 100644 --- a/lib/host_tests/test_hf_read_lifecycle.c +++ b/lib/host_tests/test_hf_read_lifecycle.c @@ -62,6 +62,75 @@ static MunitResult test_failure_reason_texts_are_stable( seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonSamTimeout), "SAM timeout"); munit_assert_string_equal( seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonBoardMissing), "Reader lost"); + 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; +} + +static MunitResult test_prepare_context_clears_stale_failure( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderHfReadFailureReason failure_reason = SeaderHfReadFailureReasonSamTimeout; + char read_error[32] = "previous timeout"; + + seader_hf_read_prepare_context(&failure_reason, read_error, sizeof(read_error)); + + munit_assert_int(failure_reason, ==, SeaderHfReadFailureReasonNone); + munit_assert_char(read_error[0], ==, '\0'); + return MUNIT_OK; +} + +static MunitResult test_empty_pacs2_detects_sam_keys_missing( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_false(seader_pacs2_indicates_sam_keys_missing(false, NULL, 0U)); + munit_assert_true(seader_pacs2_indicates_sam_keys_missing(true, NULL, 0U)); + munit_assert_true(seader_pacs2_indicates_sam_keys_missing(true, NULL, 1U)); + munit_assert_false( + seader_pacs2_indicates_sam_keys_missing(true, (const uint8_t[]){0x00U, 0x10U}, 2U)); + return MUNIT_OK; +} + +static MunitResult test_sam_keys_missing_error_texts_fit_storage( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + char label[97] = {0}; + + seader_hf_read_format_sam_keys_missing_error( + true, SeaderHfPacsMediaTypePicopass, true, false, label, sizeof(label)); + munit_assert_string_equal( + label, "PicoPass recognized.\nUnable to read keys.\nSAM missing standard keys."); + munit_assert_size(strlen(label), <, 96U); + + seader_hf_read_format_sam_keys_missing_error( + true, SeaderHfPacsMediaTypeMifarePlus, true, true, label, sizeof(label)); + munit_assert_string_equal( + label, "MIFARE Plus recognized.\nUnable to read keys.\nCheck SAM Info."); + munit_assert_size(strlen(label), <, 96U); + + seader_hf_read_format_sam_keys_missing_error( + false, SeaderHfPacsMediaTypeUnknown, true, false, label, sizeof(label)); + munit_assert_string_equal( + label, "Unable to read keys.\nSAM missing standard\nkeys. Check SAM Info."); + munit_assert_size(strlen(label), <, 96U); + + seader_hf_read_format_sam_keys_missing_error( + false, SeaderHfPacsMediaTypeUnknown, false, false, label, sizeof(label)); + munit_assert_string_equal(label, "Unable to read keys.\nCheck SAM Info."); + munit_assert_size(strlen(label), <, 96U); return MUNIT_OK; } @@ -77,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; } @@ -84,7 +157,10 @@ static MunitTest test_hf_read_lifecycle_cases[] = { {(char*)"/card-detect-gating", test_card_detect_starts_only_from_detecting_when_sam_idle, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/timeout-policy", test_waiting_states_and_timeout_policy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/failure-text", test_failure_reason_texts_are_stable, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/prepare-context", test_prepare_context_clears_stale_failure, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/failure-text-fits", test_error_texts_fit_read_error_storage, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/empty-pacs2", test_empty_pacs2_detects_sam_keys_missing, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/sam-keys-missing-text", test_sam_keys_missing_error_texts_fit_storage, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {NULL, NULL, NULL, NULL, 0, NULL}, }; diff --git a/lib/host_tests/test_hf_read_plan.c b/lib/host_tests/test_hf_read_plan.c index c839b66..c9fc35c 100644 --- a/lib/host_tests/test_hf_read_plan.c +++ b/lib/host_tests/test_hf_read_plan.c @@ -100,12 +100,93 @@ static MunitResult test_detected_types_are_deduped_and_clamped( return MUNIT_OK; } +static MunitResult test_skips_start_verify_for_single_matching_detected_type( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = {SeaderCredentialType14A}; + + munit_assert_false(seader_hf_read_plan_should_verify_start_type( + SeaderCredentialType14A, detected_types, sizeof(detected_types) / sizeof(detected_types[0]))); + return MUNIT_OK; +} + +static MunitResult test_verifies_start_for_prompted_multi_type_selection( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = { + SeaderCredentialType14A, + SeaderCredentialTypeMifareClassic, + }; + + munit_assert_true(seader_hf_read_plan_should_verify_start_type( + SeaderCredentialTypeMifareClassic, + detected_types, + sizeof(detected_types) / sizeof(detected_types[0]))); + return MUNIT_OK; +} + +static MunitResult test_verifies_start_for_manual_selected_type( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_true(seader_hf_read_plan_should_verify_start_type( + SeaderCredentialTypePicopass, NULL, 0U)); + return MUNIT_OK; +} + +static MunitResult test_verifies_start_for_stale_non_matching_detected_type( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + const SeaderCredentialType detected_types[] = {SeaderCredentialType14A}; + + munit_assert_true(seader_hf_read_plan_should_verify_start_type( + SeaderCredentialTypeMifareClassic, + detected_types, + sizeof(detected_types) / sizeof(detected_types[0]))); + 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}, + {(char*)"/start-verify/single-match", + test_skips_start_verify_for_single_matching_detected_type, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/start-verify/multi-type-selection", + test_verifies_start_for_prompted_multi_type_selection, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/start-verify/manual-selected-type", + test_verifies_start_for_manual_selected_type, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/start-verify/non-matching", + test_verifies_start_for_stale_non_matching_detected_type, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, {NULL, NULL, NULL, NULL, 0, NULL}, }; diff --git a/lib/host_tests/test_hf_release_sequence.c b/lib/host_tests/test_hf_release_sequence.c index ba24631..0f02bd3 100644 --- a/lib/host_tests/test_hf_release_sequence.c +++ b/lib/host_tests/test_hf_release_sequence.c @@ -33,6 +33,10 @@ static void record_manager_unload(void* context) { record_call(context, "plugin-manager-unload"); } +static void record_host_nfc_release(void* context) { + record_call(context, "host-nfc-release"); +} + static void record_worker_reset(void* context) { record_call(context, "worker-reset"); } @@ -55,6 +59,7 @@ static MunitResult test_release_sequence_orders_operations_and_finalizes_state( .host_picopass_release = record_picopass_release, .plugin_free = record_plugin_free, .plugin_manager_unload = record_manager_unload, + .host_nfc_release = record_host_nfc_release, .worker_reset = record_worker_reset, }; @@ -62,13 +67,14 @@ static MunitResult test_release_sequence_orders_operations_and_finalizes_state( munit_assert_int(hf_state, ==, SeaderHfSessionStateUnloaded); munit_assert_int(mode_runtime, ==, SeaderModeRuntimeNone); - munit_assert_uint(recorder.index, ==, 6); + munit_assert_uint(recorder.index, ==, 7); 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"); + munit_assert_string_equal(recorder.calls[5], "host-nfc-release"); + munit_assert_string_equal(recorder.calls[6], "worker-reset"); return MUNIT_OK; } diff --git a/lib/host_tests/test_hf_sam_response_view.c b/lib/host_tests/test_hf_sam_response_view.c new file mode 100644 index 0000000..1875c01 --- /dev/null +++ b/lib/host_tests/test_hf_sam_response_view.c @@ -0,0 +1,201 @@ +#include +#include + +#include "hf_sam_response_view.h" +#include "munit.h" + +static size_t test_hex_to_bytes(const char* hex, uint8_t* out, size_t out_size) { + size_t len = 0U; + int high_nibble = -1; + + for(const char* p = hex; *p; ++p) { + int value = -1; + if(*p >= '0' && *p <= '9') value = *p - '0'; + else if(*p >= 'A' && *p <= 'F') value = *p - 'A' + 10; + else if(*p >= 'a' && *p <= 'f') value = *p - 'a' + 10; + else if(isspace((unsigned char)*p)) continue; + else munit_error("invalid hex character"); + + if(high_nibble < 0) { + high_nibble = value; + } else { + if(len >= out_size) munit_error("hex output buffer too small"); + out[len++] = (uint8_t)((high_nibble << 4) | value); + high_nibble = -1; + } + } + + if(high_nibble >= 0) munit_error("odd-length hex string"); + return len; +} + +static void assert_nfc_send_vector( + const char* response_hex, + const char* expected_data_hex, + uint16_t expected_protocol, + uint32_t expected_timeout_us, + const char* expected_format_hex) { + uint8_t response[128] = {0}; + uint8_t expected_data[32] = {0}; + uint8_t expected_format[8] = {0}; + SeaderHfSamNfcSendView view = {0}; + size_t response_len = test_hex_to_bytes(response_hex, response, sizeof(response)); + size_t expected_data_len = + test_hex_to_bytes(expected_data_hex, expected_data, sizeof(expected_data)); + size_t expected_format_len = + test_hex_to_bytes(expected_format_hex, expected_format, sizeof(expected_format)); + + munit_assert_true( + seader_hf_sam_response_view_parse_nfc_send(response, response_len, &view)); + munit_assert_size(view.data_len, ==, expected_data_len); + munit_assert_memory_equal(expected_data_len, view.data, expected_data); + munit_assert_uint16(view.protocol, ==, expected_protocol); + munit_assert_uint32(view.timeout_us, ==, expected_timeout_us); + munit_assert_size(view.format_len, ==, expected_format_len); + if(expected_format_len > 0U) { + munit_assert_memory_equal(expected_format_len, view.format, expected_format); + } else { + munit_assert_null(view.format); + } +} + +static MunitResult test_parses_live_seos_nfc_send_vector( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + assert_nfc_send_vector( + "0A140A000000A120A11E800E0A0000A4040007D2760000850100810200028203017995850306C000", + "0A0000A4040007D2760000850100", + 0x0002U, + 96661U, + "06C000"); + return MUNIT_OK; +} + +static MunitResult test_parses_older_seos_nfc_send_vector( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + assert_nfc_send_vector( + "0A140A000000A129A12780110200A404000AA000000440000101000100810200028203012E11830102840102850306C000", + "0200A404000AA000000440000101000100", + 0x0002U, + 77329U, + "06C000"); + return MUNIT_OK; +} + +static MunitResult test_parses_live_mfc_nfc_send_vector( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + assert_nfc_send_vector( + "0A140A000000A115A11380046000F57B8102000282022710850300C000", + "6000F57B", + 0x0002U, + 10000U, + "00C000"); + return MUNIT_OK; +} + +static MunitResult test_parses_live_picopass_nfc_send_vector( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + assert_nfc_send_vector( + "0A140A000000A110A10E80040C05DE6481020004820201F4", + "0C05DE64", + 0x0004U, + 500U, + ""); + return MUNIT_OK; +} + +static MunitResult test_rejects_error_response_vector(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[32] = {0}; + SeaderHfSamNfcSendView view = {0}; + size_t response_len = + test_hex_to_bytes("0A4400000000BE0780013D81020015", response, sizeof(response)); + + munit_assert_false( + seader_hf_sam_response_view_parse_nfc_send(response, response_len, &view)); + return MUNIT_OK; +} + +static MunitResult test_rejects_truncated_header(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[] = {0x0A, 0x14, 0x0A, 0x00, 0x00}; + SeaderHfSamNfcSendView view = {0}; + + munit_assert_false( + seader_hf_sam_response_view_parse_nfc_send(response, sizeof(response), &view)); + return MUNIT_OK; +} + +static MunitResult test_rejects_missing_timeout(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[64] = {0}; + SeaderHfSamNfcSendView view = {0}; + size_t response_len = + test_hex_to_bytes("0A140A000000A109A1078002600081020002", response, sizeof(response)); + + munit_assert_false( + seader_hf_sam_response_view_parse_nfc_send(response, response_len, &view)); + return MUNIT_OK; +} + +static MunitResult test_rejects_malformed_nested_length( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + uint8_t response[64] = {0}; + SeaderHfSamNfcSendView view = {0}; + size_t response_len = test_hex_to_bytes( + "0A140A000000A10EA11380046000F57B8102000282022710", response, sizeof(response)); + + munit_assert_false( + seader_hf_sam_response_view_parse_nfc_send(response, response_len, &view)); + return MUNIT_OK; +} + +static MunitResult test_ignores_unknown_optional_tags(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + assert_nfc_send_vector( + "0A140A000000A119A1178002600081020002820227108301AA8601BB850300C000", + "6000", + 0x0002U, + 10000U, + "00C000"); + return MUNIT_OK; +} + +static MunitTest tests[] = { + {(char*)"/live-seos-vector", test_parses_live_seos_nfc_send_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/older-seos-vector", test_parses_older_seos_nfc_send_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/live-mfc-vector", test_parses_live_mfc_nfc_send_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/live-picopass-vector", test_parses_live_picopass_nfc_send_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/reject-error-response", test_rejects_error_response_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/reject-truncated-header", test_rejects_truncated_header, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/reject-missing-timeout", test_rejects_missing_timeout, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/reject-malformed-nested-length", test_rejects_malformed_nested_length, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/ignore-unknown-optional-tags", test_ignores_unknown_optional_tags, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, +}; + +MunitSuite test_hf_sam_response_view_suite = { + (char*)"", + tests, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_main.c b/lib/host_tests/test_main.c index 081cc3c..d836fd6 100644 --- a/lib/host_tests/test_main.c +++ b/lib/host_tests/test_main.c @@ -1,10 +1,14 @@ #include "munit.h" extern MunitSuite test_lrc_suite; +extern MunitSuite test_board_identity_suite; extern MunitSuite test_board_power_lifecycle_suite; extern MunitSuite test_hf_read_lifecycle_suite; +extern MunitSuite test_hf_buffer_pool_suite; extern MunitSuite test_sam_startup_ui_suite; extern MunitSuite test_ccid_logic_suite; +extern MunitSuite test_uart_tx_logic_suite; +extern MunitSuite test_uart_rx_logic_suite; extern MunitSuite test_sam_key_label_suite; extern MunitSuite test_t1_existing_suite; extern MunitSuite test_t1_protocol_suite; @@ -12,12 +16,17 @@ 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_hf_bridge_policy_suite; extern MunitSuite test_runtime_policy_suite; extern MunitSuite test_wiegand_plugin_suite; +extern MunitSuite test_ui_memory_policy_suite; +extern MunitSuite test_hf_14a_session_suite; +extern MunitSuite test_hf_sam_response_view_suite; int main(int argc, char* argv[]) { MunitSuite child_suites[] = { {"/lrc", test_lrc_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/board-identity", test_board_identity_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/board-power-lifecycle", test_board_power_lifecycle_suite.tests, NULL, @@ -28,6 +37,7 @@ int main(int argc, char* argv[]) { NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/hf-buffer-pool", test_hf_buffer_pool_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/sam-startup-ui", test_sam_startup_ui_suite.tests, NULL, @@ -37,12 +47,26 @@ int main(int argc, char* argv[]) { {"/t1", test_t1_existing_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/t1", test_t1_protocol_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/ccid", test_ccid_logic_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/uart-tx", test_uart_tx_logic_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/uart-rx", test_uart_rx_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}, {"/hf-read-plan", test_hf_read_plan_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/hf-bridge-policy", test_hf_bridge_policy_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/hf-14a-session", test_hf_14a_session_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/hf-sam-response-view", + test_hf_sam_response_view_suite.tests, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE}, {"/runtime-policy", test_runtime_policy_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/wiegand-plugin", test_wiegand_plugin_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/ui-memory-policy", + test_ui_memory_policy_suite.tests, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE}, {NULL, NULL, NULL, 0, 0}, }; MunitSuite main_suite = { diff --git a/lib/host_tests/test_runtime_policy.c b/lib/host_tests/test_runtime_policy.c index d0016ac..3bd9c9b 100644 --- a/lib/host_tests/test_runtime_policy.c +++ b/lib/host_tests/test_runtime_policy.c @@ -1,7 +1,11 @@ #include +#include #include "munit.h" +#include "allocation_policy.h" #include "runtime_policy.h" +#include "seader_hf_read_plan.h" +#include "worker_loop_policy.h" static MunitResult test_reset_cached_sam_metadata_clears_all_fields( const MunitParameter params[], @@ -237,6 +241,81 @@ static MunitResult test_reset_hf_mode_clears_selection_and_detected_types( return MUNIT_OK; } +static MunitResult test_cancel_hf_type_prompt_resets_future_read_to_full_polling( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + bool hf_mode_active = true; + SeaderCredentialType selected_read_type = SeaderCredentialTypePicopass; + SeaderCredentialType detected_types[3] = { + SeaderCredentialType14A, + SeaderCredentialTypePicopass, + SeaderCredentialTypeNone, + }; + size_t detected_type_count = 2U; + + seader_runtime_cancel_hf_type_prompt( + &hf_mode_active, + &selected_read_type, + detected_types, + 3U, + &detected_type_count); + + munit_assert_false(hf_mode_active); + munit_assert_int(selected_read_type, ==, SeaderCredentialTypeNone); + munit_assert_size(detected_type_count, ==, 0U); + for(size_t i = 0; i < 3U; i++) { + munit_assert_int(detected_types[i], ==, SeaderCredentialTypeNone); + } + + const SeaderHfReadPlan next_plan = + seader_hf_read_plan_build(selected_read_type, detected_types, detected_type_count); + munit_assert_int(next_plan.decision, ==, SeaderHfReadDecisionContinuePolling); + munit_assert_int(next_plan.type_to_read, ==, SeaderCredentialTypeNone); + munit_assert_size(next_plan.detected_type_count, ==, 0U); + return MUNIT_OK; +} + +static MunitResult test_virtual_credential_loop_terminal_policy( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_true( + seader_worker_virtual_credential_should_continue(true, true, false, false, 1U)); + munit_assert_false( + seader_worker_virtual_credential_should_continue(false, true, false, false, 1U)); + munit_assert_false( + seader_worker_virtual_credential_should_continue(true, false, false, false, 1U)); + munit_assert_false( + seader_worker_virtual_credential_should_continue(true, true, true, false, 1U)); + munit_assert_false( + seader_worker_virtual_credential_should_continue(true, true, false, true, 1U)); + munit_assert_false( + seader_worker_virtual_credential_should_continue(true, true, false, false, 0U)); + return MUNIT_OK; +} + +static MunitResult test_checked_size_multiply_rejects_overflow( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + size_t total_size = 0U; + munit_assert_true(seader_size_multiply_checked(4U, 8U, &total_size)); + munit_assert_size(total_size, ==, 32U); + + total_size = 123U; + munit_assert_false(seader_size_multiply_checked(SIZE_MAX, 2U, &total_size)); + munit_assert_size(total_size, ==, 123U); + munit_assert_false(seader_size_multiply_checked(1U, 1U, NULL)); + 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}, @@ -248,6 +327,9 @@ static MunitTest test_runtime_policy_cases[] = { {(char*)"/begin-board-auto-recover", test_begin_board_auto_recover_sets_pending_and_target, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/begin-board-auto-recover-invalid", test_begin_board_auto_recover_rejects_invalid_or_duplicate_state, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/reset-hf-mode", test_reset_hf_mode_clears_selection_and_detected_types, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/cancel-hf-type-prompt", test_cancel_hf_type_prompt_resets_future_read_to_full_polling, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/virtual-credential-terminal-policy", test_virtual_credential_loop_terminal_policy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/checked-size-multiply", test_checked_size_multiply_rejects_overflow, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {NULL, NULL, NULL, NULL, 0, NULL}, }; diff --git a/lib/host_tests/test_sam_key_label.c b/lib/host_tests/test_sam_key_label.c index 88d42f5..1768971 100644 --- a/lib/host_tests/test_sam_key_label.c +++ b/lib/host_tests/test_sam_key_label.c @@ -1,3 +1,5 @@ +#include + #include "munit.h" #include "sam_key_label.h" @@ -7,7 +9,7 @@ static MunitResult test_formats_no_sam(const MunitParameter params[], void* fixt char label[SEADER_SAM_KEY_LABEL_MAX_LEN] = {0}; seader_sam_key_label_format( - false, SeaderSamKeyProbeStatusUnknown, NULL, 0U, label, sizeof(label)); + false, SeaderSamKeyProbeStatusUnknown, NULL, 0U, false, false, label, sizeof(label)); munit_assert_string_equal(label, "NO SAM"); return MUNIT_OK; } @@ -21,11 +23,11 @@ static MunitResult test_formats_unknown_for_missing_value( const uint8_t zeros[] = {0x00, 0x00, 0x00}; seader_sam_key_label_format( - true, SeaderSamKeyProbeStatusUnknown, NULL, 0U, label, sizeof(label)); + true, SeaderSamKeyProbeStatusUnknown, NULL, 0U, false, false, label, sizeof(label)); munit_assert_string_equal(label, "SAM: Key Unknown"); seader_sam_key_label_format( - true, SeaderSamKeyProbeStatusUnknown, zeros, sizeof(zeros), label, sizeof(label)); + true, SeaderSamKeyProbeStatusUnknown, zeros, sizeof(zeros), false, false, label, sizeof(label)); munit_assert_string_equal(label, "SAM: Key Unknown"); return MUNIT_OK; } @@ -43,6 +45,8 @@ static MunitResult test_formats_standard_key_for_successful_zero_value( SeaderSamKeyProbeStatusVerifiedStandard, zero64, sizeof(zero64), + true, + true, label, sizeof(label)); munit_assert_string_equal(label, "SAM: Standard Key"); @@ -59,7 +63,7 @@ static MunitResult test_probe_failure_never_formats_standard( const uint8_t zero64[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; seader_sam_key_label_format( - true, SeaderSamKeyProbeStatusProbeFailed, NULL, 0U, label, sizeof(label)); + true, SeaderSamKeyProbeStatusProbeFailed, NULL, 0U, false, false, label, sizeof(label)); munit_assert_string_equal(label, "SAM: Probe Failed"); seader_sam_key_label_format( @@ -67,6 +71,8 @@ static MunitResult test_probe_failure_never_formats_standard( SeaderSamKeyProbeStatusProbeFailed, zero64, sizeof(zero64), + false, + false, label, sizeof(label)); munit_assert_string_equal(label, "SAM: Probe Failed"); @@ -80,11 +86,57 @@ static MunitResult test_formats_ascii_ice_value(const MunitParameter params[], v const uint8_t ice[] = {'I', 'C', 'E', '1', '8', '0', '3'}; seader_sam_key_label_format( - true, SeaderSamKeyProbeStatusVerifiedValue, ice, sizeof(ice), label, sizeof(label)); + true, SeaderSamKeyProbeStatusVerifiedValue, ice, sizeof(ice), true, true, label, sizeof(label)); munit_assert_string_equal(label, "SAM: ICE1803"); return MUNIT_OK; } +static MunitResult test_formats_missing_standard_key(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_SAM_KEY_LABEL_MAX_LEN] = {0}; + const uint8_t zero64[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + seader_sam_key_label_format( + true, + SeaderSamKeyProbeStatusVerifiedStandard, + zero64, + sizeof(zero64), + true, + false, + label, + sizeof(label)); + munit_assert_string_equal(label, "MISSING STANDARD KEYS"); + munit_assert_size(strlen(label), <, SEADER_SAM_KEY_LABEL_MAX_LEN); + return MUNIT_OK; +} + +static MunitResult test_probe_status_from_snmp_result(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + const uint8_t zero64[] = {0x00, 0x00, 0x00, 0x00}; + const uint8_t ice[] = {'I', 'C', 'E'}; + + munit_assert_int( + seader_sam_key_probe_status_from_snmp_result(false, zero64, sizeof(zero64)), + ==, + SeaderSamKeyProbeStatusProbeFailed); + munit_assert_int( + seader_sam_key_probe_status_from_snmp_result(true, zero64, sizeof(zero64)), + ==, + SeaderSamKeyProbeStatusVerifiedStandard); + munit_assert_int( + seader_sam_key_probe_status_from_snmp_result(true, ice, sizeof(ice)), + ==, + SeaderSamKeyProbeStatusVerifiedValue); + munit_assert_int( + seader_sam_key_probe_status_from_snmp_result(true, NULL, 0U), + ==, + SeaderSamKeyProbeStatusUnknown); + return MUNIT_OK; +} + static MunitResult test_sanitizes_non_printable_bytes(const MunitParameter params[], void* fixture) { (void)params; (void)fixture; @@ -92,7 +144,7 @@ static MunitResult test_sanitizes_non_printable_bytes(const MunitParameter param const uint8_t mixed[] = {'A', 0x00, 0x1F, 'Z'}; seader_sam_key_label_format( - true, SeaderSamKeyProbeStatusVerifiedValue, mixed, sizeof(mixed), label, sizeof(label)); + true, SeaderSamKeyProbeStatusVerifiedValue, mixed, sizeof(mixed), true, true, label, sizeof(label)); munit_assert_string_equal(label, "SAM: A??Z"); return MUNIT_OK; } @@ -101,6 +153,8 @@ static MunitTest test_sam_key_label_cases[] = { {(char*)"/no-sam", test_formats_no_sam, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/unknown", test_formats_unknown_for_missing_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/standard-key-zero64", test_formats_standard_key_for_successful_zero_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/missing-standard-key", test_formats_missing_standard_key, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-status-from-snmp", test_probe_status_from_snmp_result, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/probe-failed", test_probe_failure_never_formats_standard, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/ascii", test_formats_ascii_ice_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/sanitize", test_sanitizes_non_printable_bytes, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, diff --git a/lib/host_tests/test_snmp.c b/lib/host_tests/test_snmp.c index a621de6..c3624e0 100644 --- a/lib/host_tests/test_snmp.c +++ b/lib/host_tests/test_snmp.c @@ -13,8 +13,14 @@ static const char* snmp_discovery_response_hex = "308200A80201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F0400040030820051041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F040D2B0601040181E438010104080FA08200210201000201000201003082001430820010060A2B060106030F0101040002020141"; static const char* snmp_ice_response_hex = "308200A80201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020102040D2B0601040181E438010104080F0400040030820051041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F040D2B0601040181E438010104080FA2820021020100020100020100308200143082001006050301070138040749434531383033"; +static const char* snmp_live_ice_response_hex = + "308200A80201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8D8088CDA2A99DCFC392D087FF7F020104020102040D2B0601040181E438010104080F0400040030820051041B2B0601040181E438010103050F8D8088CDA2A99DCFC392D087FF7F040D2B0601040181E438010104080FA2820021020100020100020100308200143082001006050301070138040700000000000000"; static const char* snmp_uhf_config_response_hex = "308200F40201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020103040D2B0601040181E438010104080F040004003082009D041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F040D2B0601040181E438010104080FA282006D020100020100020100308200603082005C0606030107030B00045204E2003412112B0601040181E438010102012201010101112B0601040181E43801010201220101020104E2801105112B0601040181E438010102011E01010101112B0601040181E438010102011E01010201"; +static const char* snmp_new_sam_ice_response_hex = + "308200A80201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A2ADC582D2B8C3FF7F020104020102040D2B0601040181E438010104080F0400040030820051041B2B0601040181E438010103050F8C9088CDA2A2ADC582D2B8C3FF7F040D2B0601040181E438010104080FA2820021020100020100020100308200143082001006050301070138040749434531383033"; +static const char* snmp_new_sam_uhf_config_response_hex = + "308200F40201033082000E02010002010004020000020201010482003A30820036041B2B0601040181E438010103050F8C9088CDA2A2ADC582D2B8C3FF7F020104020103040D2B0601040181E438010104080F040004003082009D041B2B0601040181E438010103050F8C9088CDA2A2ADC582D2B8C3FF7F040D2B0601040181E438010104080FA282006D020100020100020100308200603082005C0606030107030B00045204E2003412112B0601040181E438010102012201010101112B0601040181E43801010201220101020104E2801105112B0601040181E438010102011E01010101112B0601040181E438010102011E01010201"; static const char* snmp_ice_request_hex = "307A020103300E020100020202F40401040202010104383036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F04000400302B04000400A025020100020100020100301A30180604030003060410300E0605030107013802010002020100"; static const char* snmp_monza_request_hex = @@ -23,11 +29,27 @@ static const char* snmp_higgs_request_hex = "308186020103300E020100020202F40401040202010104383036041B2B0601040181E438010103050F8C9088CDA2A8D885C0B298D7FF7F020105020101040D2B0601040181E438010104080F04000400303704000400A03102010002010002010030263024060403000306041C301A06112B0601040181E43801010201220101010102010002020100"; static const uint8_t oid_elite_ice[] = {0x03, 0x01, 0x07, 0x01, 0x38}; +static const uint8_t oid_standard_encryption_key[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x0C, 0x03, 0x01}; +static const uint8_t oid_standard_signature_key[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x0C, 0x02, 0x01}; static const uint8_t oid_uhf_tags_config[] = {0x03, 0x01, 0x07, 0x03, 0x0B, 0x00}; static const uint8_t oid_monza4qt_access_key[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x1E, 0x01, 0x01, 0x01, 0x01}; static const uint8_t oid_higgs3_access_key[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x22, 0x01, 0x01, 0x01, 0x01}; + +static void test_probe_advance_standard_pacs_keys(SeaderUhfSnmpProbe* probe) { + munit_assert_int(probe->stage, ==, SeaderUhfSnmpProbeStageReadStandardEncryptionKey); + munit_assert_true(seader_uhf_snmp_probe_consume_error( + probe, 0x06U, (const uint8_t*)"\x69\x82", 2U)); + munit_assert_int(probe->stage, ==, SeaderUhfSnmpProbeStageReadStandardSignatureKey); + munit_assert_true(seader_uhf_snmp_probe_consume_error( + probe, 0x06U, (const uint8_t*)"\x69\x82", 2U)); + munit_assert_true(probe->standard_pacs_keys_probed); + munit_assert_true(probe->standard_encryption_key_present); + munit_assert_true(probe->standard_signature_key_present); +} static const uint8_t live_engine_id[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x03, 0x05, 0x0F, 0x8C, 0x90, 0x88, 0xCD, 0xA2, 0xA8, 0xD8, 0x85, 0xC0, @@ -60,6 +82,52 @@ static size_t test_hex_to_bytes(const char* hex, uint8_t* out, size_t out_size) return len; } +static bool test_bytes_contain( + const uint8_t* haystack, + size_t haystack_len, + const uint8_t* needle, + size_t needle_len) { + if(!haystack || !needle || needle_len == 0U || needle_len > haystack_len) { + return false; + } + + for(size_t i = 0U; i + needle_len <= haystack_len; i++) { + if(memcmp(haystack + i, needle, needle_len) == 0) { + return true; + } + } + + return false; +} + +static void test_make_snmp_pdu_error(uint8_t* response, size_t response_len) { + const uint8_t pdu_header[] = { + 0xA2, + 0x82, + 0x00, + 0x21, + 0x02, + 0x01, + 0x00, + 0x02, + 0x01, + 0x00, + 0x02, + 0x01, + 0x00, + }; + + for(size_t i = 0U; i + sizeof(pdu_header) <= response_len; i++) { + if(memcmp(response + i, pdu_header, sizeof(pdu_header)) == 0) { + response[i + 9U] = 0x02U; + response[i + 12U] = 0x01U; + return; + } + } + + munit_error("SNMP PDU header not found"); +} + static MunitResult test_build_discovery_request_matches_live_vector(const MunitParameter params[], void* fixture) { (void)params; (void)fixture; @@ -108,6 +176,58 @@ static MunitResult test_build_get_data_requests_match_live_vectors(const MunitPa return MUNIT_OK; } +static MunitResult test_build_standard_pacs_key_requests_include_mandatory_oids( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + uint8_t message[512] = {0}; + uint8_t scratch[512] = {0}; + size_t message_len = 0U; + + munit_assert_true(seader_snmp_build_get_data_request( + live_engine_id, + sizeof(live_engine_id), + live_username, + sizeof(live_username), + 5U, + 1U, + oid_standard_encryption_key, + sizeof(oid_standard_encryption_key), + scratch, + sizeof(scratch), + message, + sizeof(message), + &message_len)); + munit_assert_true(test_bytes_contain( + message, message_len, oid_standard_encryption_key, sizeof(oid_standard_encryption_key))); + munit_assert_false(test_bytes_contain( + message, message_len, oid_standard_signature_key, sizeof(oid_standard_signature_key))); + + memset(message, 0, sizeof(message)); + memset(scratch, 0, sizeof(scratch)); + munit_assert_true(seader_snmp_build_get_data_request( + live_engine_id, + sizeof(live_engine_id), + live_username, + sizeof(live_username), + 5U, + 1U, + oid_standard_signature_key, + sizeof(oid_standard_signature_key), + scratch, + sizeof(scratch), + message, + sizeof(message), + &message_len)); + munit_assert_true(test_bytes_contain( + message, message_len, oid_standard_signature_key, sizeof(oid_standard_signature_key))); + munit_assert_false(test_bytes_contain( + message, message_len, oid_standard_encryption_key, sizeof(oid_standard_encryption_key))); + + return MUNIT_OK; +} + static MunitResult test_parse_response_and_zero_copy_views(const MunitParameter params[], void* fixture) { (void)params; (void)fixture; @@ -176,9 +296,11 @@ static MunitResult test_probe_stages(const MunitParameter params[], void* fixtur response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); - munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadTagConfig); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadStandardEncryptionKey); munit_assert_size(probe.ice_value_len, ==, 7); munit_assert_memory_equal(7, probe.ice_value_storage, "ICE1803"); + test_probe_advance_standard_pacs_keys(&probe); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadTagConfig); response_len = test_hex_to_bytes(snmp_uhf_config_response_hex, response, sizeof(response)); munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); @@ -224,6 +346,7 @@ static MunitResult test_probe_full_sequence_succeeds_with_runtime_sized_buffers( &probe, scratch, sizeof(scratch), message, sizeof(message), &message_len)); response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + test_probe_advance_standard_pacs_keys(&probe); memset(message, 0, sizeof(message)); memset(scratch, 0, sizeof(scratch)); @@ -321,12 +444,233 @@ static MunitResult test_response_rejects_truncated_length(const MunitParameter p return MUNIT_OK; } +static MunitResult test_parse_live_standard_ice_value(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_live_ice_response_hex, response, sizeof(response)); + SeaderSnmpResponseView view = {0}; + SeaderBytesView value = {0}; + + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &view)); + munit_assert_true(seader_snmp_find_varbind_octet_value( + view.varbind_sequence, (SeaderBytesView){oid_elite_ice, sizeof(oid_elite_ice)}, &value)); + munit_assert_size(value.len, ==, 7); + + // Assert all 7 bytes are 0x00 (represents the standard key) + const uint8_t standard_key_val[] = {0, 0, 0, 0, 0, 0, 0}; + munit_assert_memory_equal(7, value.ptr, standard_key_val); + return MUNIT_OK; +} + +static MunitResult test_parse_new_sam_ice_value(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_new_sam_ice_response_hex, response, sizeof(response)); + SeaderSnmpResponseView view = {0}; + SeaderBytesView value = {0}; + + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &view)); + munit_assert_uint32(view.error_status, ==, 0U); + munit_assert_true(seader_snmp_find_varbind_octet_value( + view.varbind_sequence, (SeaderBytesView){oid_elite_ice, sizeof(oid_elite_ice)}, &value)); + munit_assert_size(value.len, ==, 7); + munit_assert_memory_equal(7, value.ptr, "ICE1803"); + + static const uint8_t new_sam_engine_id[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x03, + 0x05, 0x0F, 0x8C, 0x90, 0x88, 0xCD, 0xA2, 0xA2, 0xAD, 0xC5, 0x82, + 0xD2, 0xB8, 0xC3, 0xFF, 0x7F + }; + munit_assert_memory_equal(sizeof(new_sam_engine_id), view.usm_engine_id.ptr, new_sam_engine_id); + munit_assert_uint32(view.usm_engine_boots, ==, 4U); + munit_assert_uint32(view.usm_engine_time, ==, 2U); + return MUNIT_OK; +} + +static MunitResult test_probe_no_uhf(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_discovery_response_hex, response, sizeof(response)); + SeaderUhfSnmpProbe probe = {0}; + uint8_t message[512] = {0}; + uint8_t scratch[512] = {0}; + size_t message_len = 0U; + + seader_uhf_snmp_probe_init(&probe); + probe.supports_uhf = false; + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDiscovery); + munit_assert_true(seader_uhf_snmp_probe_build_next_request( + &probe, scratch, sizeof(scratch), message, sizeof(message), &message_len)); + munit_assert_size(message_len, >, 0); + + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadIce); + munit_assert_true(seader_uhf_snmp_probe_build_next_request( + &probe, scratch, sizeof(scratch), message, sizeof(message), &message_len)); + + response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + test_probe_advance_standard_pacs_keys(&probe); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDone); + munit_assert_size(probe.ice_value_len, ==, 7); + munit_assert_memory_equal(7, probe.ice_value_storage, "ICE1803"); + + return MUNIT_OK; +} + +static MunitResult test_probe_missing_standard_pacs_keys(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + SeaderUhfSnmpProbe probe = {0}; + + seader_uhf_snmp_probe_init(&probe); + probe.stage = SeaderUhfSnmpProbeStageReadStandardEncryptionKey; + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x11U, (const uint8_t*)"\x2E\x00", 2U)); + munit_assert_false(probe.standard_encryption_key_present); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadStandardSignatureKey); + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x11U, (const uint8_t*)"\x39\x00", 2U)); + munit_assert_false(probe.standard_signature_key_present); + munit_assert_true(probe.standard_pacs_keys_probed); + munit_assert_false(seader_uhf_snmp_probe_standard_pacs_keys_present(&probe)); + return MUNIT_OK; +} + +static MunitResult test_probe_access_denied_means_standard_pacs_keys_present( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + SeaderUhfSnmpProbe probe = {0}; + + seader_uhf_snmp_probe_init(&probe); + probe.stage = SeaderUhfSnmpProbeStageReadStandardEncryptionKey; + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x06U, (const uint8_t*)"\x69\x82", 2U)); + munit_assert_true(probe.standard_encryption_key_present); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadStandardSignatureKey); + + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x06U, (const uint8_t*)"\x69\x82", 2U)); + munit_assert_true(probe.standard_signature_key_present); + munit_assert_true(probe.standard_pacs_keys_probed); + munit_assert_true(seader_uhf_snmp_probe_standard_pacs_keys_present(&probe)); + return MUNIT_OK; +} + +static MunitResult test_probe_storage_error_marks_standard_pacs_keys_bad( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + SeaderUhfSnmpProbe probe = {0}; + + seader_uhf_snmp_probe_init(&probe); + probe.stage = SeaderUhfSnmpProbeStageReadStandardEncryptionKey; + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x11U, (const uint8_t*)"\x37\x00", 2U)); + munit_assert_false(probe.standard_encryption_key_present); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadStandardSignatureKey); + munit_assert_true(seader_uhf_snmp_probe_consume_error( + &probe, 0x11U, (const uint8_t*)"\x37\x00", 2U)); + munit_assert_false(probe.standard_signature_key_present); + munit_assert_true(probe.standard_pacs_keys_probed); + munit_assert_false(seader_uhf_snmp_probe_standard_pacs_keys_present(&probe)); + return MUNIT_OK; +} + +static MunitResult test_probe_pdu_error_marks_standard_pacs_keys_missing( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + SeaderUhfSnmpProbe probe = {0}; + size_t response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); + + seader_uhf_snmp_probe_init(&probe); + probe.stage = SeaderUhfSnmpProbeStageReadStandardEncryptionKey; + test_make_snmp_pdu_error(response, response_len); + + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_false(probe.standard_encryption_key_present); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadStandardSignatureKey); + + response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); + test_make_snmp_pdu_error(response, response_len); + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_false(probe.standard_signature_key_present); + munit_assert_true(probe.standard_pacs_keys_probed); + munit_assert_false(seader_uhf_snmp_probe_standard_pacs_keys_present(&probe)); + + return MUNIT_OK; +} + +static MunitResult test_probe_carrier_no_module(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_discovery_response_hex, response, sizeof(response)); + SeaderUhfSnmpProbe probe = {0}; + + seader_uhf_snmp_probe_init(&probe); + probe.supports_uhf = true; + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageDiscovery); + + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadIce); + + response_len = test_hex_to_bytes(snmp_ice_response_hex, response, sizeof(response)); + munit_assert_true(seader_uhf_snmp_probe_consume_response(&probe, response, response_len)); + test_probe_advance_standard_pacs_keys(&probe); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageReadTagConfig); + + // Consume error at ReadTagConfig stage (e.g. carrier board present but module missing) + munit_assert_true(seader_uhf_snmp_probe_consume_error(&probe, 0x11U, (const uint8_t*)"\x2E\x00", 2U)); + munit_assert_int(probe.stage, ==, SeaderUhfSnmpProbeStageFailed); + + return MUNIT_OK; +} + +static MunitResult test_parse_new_sam_uhf_config_value(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + uint8_t response[512] = {0}; + size_t response_len = test_hex_to_bytes(snmp_new_sam_uhf_config_response_hex, response, sizeof(response)); + SeaderSnmpResponseView snmp = {0}; + SeaderBytesView config_payload = {0}; + SeaderUhfTagConfigView view = {0}; + + munit_assert_true(seader_snmp_parse_response_view(response, response_len, &snmp)); + munit_assert_true(seader_snmp_find_varbind_octet_value( + snmp.varbind_sequence, (SeaderBytesView){oid_uhf_tags_config, sizeof(oid_uhf_tags_config)}, &config_payload)); + munit_assert_true(seader_uhf_tag_config_parse(config_payload, &view)); + munit_assert_true(view.has_higgs3); + munit_assert_true(view.has_monza4qt); + munit_assert_size(view.entry_count, ==, 4); + return MUNIT_OK; +} + static MunitTest test_snmp_cases[] = { {(char*)"/build-discovery", test_build_discovery_request_matches_live_vector, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/build-get-data", test_build_get_data_requests_match_live_vectors, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/build-standard-pacs-keys", test_build_standard_pacs_key_requests_include_mandatory_oids, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/parse-response", test_parse_response_and_zero_copy_views, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/parse-values", test_parse_ice_and_tag_config_values, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/parse-live-standard-ice", test_parse_live_standard_ice_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/parse-new-sam-ice", test_parse_new_sam_ice_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/parse-new-sam-uhf-config", test_parse_new_sam_uhf_config_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/probe", test_probe_stages, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-missing-standard-pacs", test_probe_missing_standard_pacs_keys, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-access-denied-standard-pacs-present", test_probe_access_denied_means_standard_pacs_keys_present, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-storage-error-standard-pacs-bad", test_probe_storage_error_marks_standard_pacs_keys_bad, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-pdu-error-standard-pacs", test_probe_pdu_error_marks_standard_pacs_keys_missing, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-no-uhf", test_probe_no_uhf, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/probe-carrier-no-module", test_probe_carrier_no_module, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/probe-runtime-buffers", test_probe_full_sequence_succeeds_with_runtime_sized_buffers, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/bounded-get-data", test_get_data_request_fits_bounded_transport_buffer, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/tag-config", test_tag_config_view_extracts_known_entries, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, diff --git a/lib/host_tests/test_t1_protocol.c b/lib/host_tests/test_t1_protocol.c index 59b6222..c61072b 100644 --- a/lib/host_tests/test_t1_protocol.c +++ b/lib/host_tests/test_t1_protocol.c @@ -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; @@ -308,6 +337,48 @@ static MunitResult test_recv_live_uhf_config_chained_blocks( return MUNIT_OK; } +static MunitResult test_recv_live_sam_card_detected( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + static const char* expected_apdu_hex = + "0A4400000000BD028A009000"; + + SeaderUartBridge uart = {0}; + SeaderWorker worker = {0}; + Seader seader = make_test_seader(&uart, &worker); + uint8_t rx_block[64] = {0}; + uint8_t expected_apdu[64] = {0}; + CCID_Message message = {0}; + size_t expected_apdu_len = 0U; + const size_t inf_len = 12; + + t1_host_test_reset(); + uart.t1.ifsd = 0xFE; + uart.t1.recv_pcb = 0x00; + + expected_apdu_len = + test_hex_to_bytes(expected_apdu_hex, expected_apdu, sizeof(expected_apdu)); + munit_assert_size(expected_apdu_len, ==, inf_len); + + rx_block[0] = 0x00; + rx_block[1] = 0x00; + rx_block[2] = (uint8_t)inf_len; + memcpy(rx_block + 3, expected_apdu, inf_len); + seader_add_lrc(rx_block, 3 + inf_len); + + message.payload = rx_block; + message.dwLength = 3 + inf_len + 1; + munit_assert_true(seader_recv_t1(&seader, &message)); + munit_assert_size(g_t1_host_test_state.process_call_count, ==, 1); + munit_assert_size(g_t1_host_test_state.last_apdu_len, ==, expected_apdu_len); + munit_assert_memory_equal( + expected_apdu_len, g_t1_host_test_state.last_apdu, expected_apdu); + return MUNIT_OK; +} + static MunitTest test_t1_regression_cases[] = { {(char*)"/recv/wtx-request-responds", test_recv_wtx_request_responds, @@ -351,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, @@ -369,6 +446,12 @@ static MunitTest test_t1_regression_cases[] = { NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/recv/live-sam-card-detected", + test_recv_live_sam_card_detected, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, {NULL, NULL, NULL, NULL, 0, NULL}, }; diff --git a/lib/host_tests/test_uart_rx_logic.c b/lib/host_tests/test_uart_rx_logic.c new file mode 100644 index 0000000..4b1f1c3 --- /dev/null +++ b/lib/host_tests/test_uart_rx_logic.c @@ -0,0 +1,112 @@ +#include "munit.h" +#include "uart_rx_logic.h" + +#include + +static MunitResult test_rx_chunk_processes_without_artificial_delay( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_uint32(seader_uart_rx_inter_chunk_delay_ms(1U), ==, 0U); + munit_assert_uint32(seader_uart_rx_inter_chunk_delay_ms(64U), ==, 0U); + munit_assert_uint32(seader_uart_rx_inter_chunk_delay_ms(272U), ==, 0U); + return MUNIT_OK; +} + +static MunitResult test_empty_rx_chunk_does_not_delay(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + munit_assert_uint32(seader_uart_rx_inter_chunk_delay_ms(0U), ==, 0U); + return MUNIT_OK; +} + +static MunitResult test_discard_consumed_noops_when_nothing_consumed( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + uint8_t buffer[] = {0x01U, 0x02U, 0x03U}; + const uint8_t expected[] = {0x01U, 0x02U, 0x03U}; + + size_t remaining = seader_uart_rx_discard_consumed(buffer, sizeof(buffer), 0U); + + munit_assert_size(remaining, ==, sizeof(expected)); + munit_assert_memory_equal(sizeof(expected), buffer, expected); + return MUNIT_OK; +} + +static MunitResult test_discard_consumed_compacts_unparsed_tail( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + uint8_t buffer[] = {0x10U, 0x20U, 0x30U, 0x40U, 0x50U}; + const uint8_t expected[] = {0x30U, 0x40U, 0x50U}; + + size_t remaining = seader_uart_rx_discard_consumed(buffer, sizeof(buffer), 2U); + + munit_assert_size(remaining, ==, sizeof(expected)); + munit_assert_memory_equal(sizeof(expected), buffer, expected); + return MUNIT_OK; +} + +static MunitResult test_discard_consumed_clears_length_when_all_consumed( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + uint8_t buffer[] = {0xAAU, 0xBBU}; + + size_t remaining = seader_uart_rx_discard_consumed(buffer, sizeof(buffer), sizeof(buffer)); + + munit_assert_size(remaining, ==, 0U); + return MUNIT_OK; +} + +static MunitTest test_uart_rx_logic_cases[] = { + {(char*)"/delay/positive-chunk-is-immediate", + test_rx_chunk_processes_without_artificial_delay, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/delay/empty-chunk-is-immediate", + test_empty_rx_chunk_does_not_delay, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/discard/noop-with-zero-consumed", + test_discard_consumed_noops_when_nothing_consumed, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/discard/compacts-tail", + test_discard_consumed_compacts_unparsed_tail, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/discard/all-consumed", + test_discard_consumed_clears_length_when_all_consumed, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_uart_rx_logic_suite = { + "", + test_uart_rx_logic_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_uart_tx_logic.c b/lib/host_tests/test_uart_tx_logic.c new file mode 100644 index 0000000..d303b3d --- /dev/null +++ b/lib/host_tests/test_uart_tx_logic.c @@ -0,0 +1,114 @@ +#include + +#include "munit.h" +#include "uart_tx_logic.h" + +static MunitResult test_tx_frame_copy_rejects_invalid_inputs( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartTxFrame frame = {0}; + const uint8_t data[] = {0x01U}; + + munit_assert_false(seader_uart_tx_frame_copy(NULL, data, sizeof(data), sizeof(data))); + munit_assert_false(seader_uart_tx_frame_copy(&frame, NULL, sizeof(data), sizeof(data))); + munit_assert_false(seader_uart_tx_frame_copy(&frame, data, 0U, sizeof(data))); + munit_assert_false(seader_uart_tx_frame_copy(&frame, data, sizeof(data), 0U)); + return MUNIT_OK; +} + +static MunitResult test_tx_frame_copy_preserves_frame_bytes( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartTxFrame frame = {0}; + const uint8_t data[] = {0x01U, 0x02U, 0x03U}; + + munit_assert_true(seader_uart_tx_frame_copy(&frame, data, sizeof(data), sizeof(frame.data))); + munit_assert_size(frame.len, ==, sizeof(data)); + munit_assert_memory_equal(sizeof(data), frame.data, data); + return MUNIT_OK; +} + +static MunitResult test_tx_frame_copy_is_immutable_after_source_reuse( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartTxFrame frame = {0}; + uint8_t scratch[] = {0xAAU, 0xBBU, 0xCCU}; + const uint8_t expected[] = {0xAAU, 0xBBU, 0xCCU}; + + munit_assert_true( + seader_uart_tx_frame_copy(&frame, scratch, sizeof(scratch), sizeof(frame.data))); + memset(scratch, 0x11, sizeof(scratch)); + + munit_assert_size(frame.len, ==, sizeof(expected)); + munit_assert_memory_equal(sizeof(expected), frame.data, expected); + return MUNIT_OK; +} + +static MunitResult test_back_to_back_copies_preserve_distinct_frames( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + SeaderUartTxFrame first = {0}; + SeaderUartTxFrame second = {0}; + uint8_t scratch[] = {0x10U, 0x20U}; + const uint8_t first_expected[] = {0x10U, 0x20U}; + const uint8_t second_expected[] = {0x30U, 0x40U}; + + munit_assert_true( + seader_uart_tx_frame_copy(&first, scratch, sizeof(scratch), sizeof(first.data))); + scratch[0] = 0x30U; + scratch[1] = 0x40U; + munit_assert_true( + seader_uart_tx_frame_copy(&second, scratch, sizeof(scratch), sizeof(second.data))); + + munit_assert_memory_equal(sizeof(first_expected), first.data, first_expected); + munit_assert_memory_equal(sizeof(second_expected), second.data, second_expected); + return MUNIT_OK; +} + +static MunitTest test_uart_tx_logic_cases[] = { + {(char*)"/copy/rejects-invalid", + test_tx_frame_copy_rejects_invalid_inputs, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/copy/preserves-bytes", + test_tx_frame_copy_preserves_frame_bytes, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/copy/immutable-after-source-reuse", + test_tx_frame_copy_is_immutable_after_source_reuse, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/copy/back-to-back-distinct", + test_back_to_back_copies_preserve_distinct_frames, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_uart_tx_logic_suite = { + "", + test_uart_tx_logic_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_uhf_status_label.c b/lib/host_tests/test_uhf_status_label.c index a9b206f..b98f959 100644 --- a/lib/host_tests/test_uhf_status_label.c +++ b/lib/host_tests/test_uhf_status_label.c @@ -13,6 +13,17 @@ static MunitResult test_formats_none(const MunitParameter params[], void* fixtur return MUNIT_OK; } +static MunitResult test_formats_hidden_as_empty(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + + char label[SEADER_UHF_STATUS_LABEL_MAX_LEN] = {'X'}; + seader_uhf_status_label_format( + SeaderUhfProbeStatusHidden, true, true, true, true, label, sizeof(label)); + munit_assert_string_equal(label, ""); + return MUNIT_OK; +} + static MunitResult test_formats_probing_and_failed_states( const MunitParameter params[], void* fixture) { @@ -116,6 +127,7 @@ static MunitResult test_small_buffer_for_none_is_safe(const MunitParameter param static MunitTest test_uhf_status_label_cases[] = { {(char*)"/none", test_formats_none, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/hidden", test_formats_hidden_as_empty, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/probing-failed", test_formats_probing_and_failed_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/supported-key-states", test_formats_supported_key_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {(char*)"/longest-fits", test_longest_supported_label_fits_buffer, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, diff --git a/lib/host_tests/test_ui_memory_policy.c b/lib/host_tests/test_ui_memory_policy.c new file mode 100644 index 0000000..13f12d2 --- /dev/null +++ b/lib/host_tests/test_ui_memory_policy.c @@ -0,0 +1,81 @@ +#include "munit.h" + +#include "ui_memory_policy.h" + +static MunitResult test_keeps_submenu_in_normal_ui_phase( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_false(seader_ui_memory_should_release_submenu(SeaderUiMemoryPhaseNormal)); + return MUNIT_OK; +} + +static MunitResult test_releases_submenu_during_hf_read( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_true(seader_ui_memory_should_release_submenu(SeaderUiMemoryPhaseHfReadActive)); + return MUNIT_OK; +} + +static MunitResult test_keeps_inactive_lazy_views_in_normal_ui_phase( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_false( + seader_ui_memory_should_release_inactive_lazy_views(SeaderUiMemoryPhaseNormal)); + return MUNIT_OK; +} + +static MunitResult test_releases_inactive_lazy_views_during_hf_read( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + + munit_assert_true( + seader_ui_memory_should_release_inactive_lazy_views(SeaderUiMemoryPhaseHfReadActive)); + return MUNIT_OK; +} + +static MunitTest test_ui_memory_policy_cases[] = { + {(char*)"/normal-keeps-submenu", + test_keeps_submenu_in_normal_ui_phase, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/hf-read-releases-submenu", + test_releases_submenu_during_hf_read, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/normal-keeps-inactive-lazy-views", + test_keeps_inactive_lazy_views_in_normal_ui_phase, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {(char*)"/hf-read-releases-inactive-lazy-views", + test_releases_inactive_lazy_views_during_hf_read, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_ui_memory_policy_suite = { + "", + test_ui_memory_policy_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/runtime_policy.c b/runtime_policy.c index 920eef9..2ea6b43 100644 --- a/runtime_policy.c +++ b/runtime_policy.c @@ -162,3 +162,13 @@ void seader_runtime_reset_hf_mode( *hf_mode_active = false; } } + +void seader_runtime_cancel_hf_type_prompt( + bool* hf_mode_active, + SeaderCredentialType* selected_read_type, + SeaderCredentialType detected_types[], + size_t detected_capacity, + size_t* detected_type_count) { + seader_runtime_reset_hf_mode( + hf_mode_active, selected_read_type, detected_types, detected_capacity, detected_type_count); +} diff --git a/runtime_policy.h b/runtime_policy.h index d175912..b531acb 100644 --- a/runtime_policy.h +++ b/runtime_policy.h @@ -50,3 +50,9 @@ void seader_runtime_reset_hf_mode( SeaderCredentialType detected_types[], size_t detected_capacity, size_t* detected_type_count); +void seader_runtime_cancel_hf_type_prompt( + bool* hf_mode_active, + SeaderCredentialType* selected_read_type, + SeaderCredentialType detected_types[], + size_t detected_capacity, + size_t* detected_type_count); diff --git a/sam_api.c b/sam_api.c index 7a181f1..e314839 100644 --- a/sam_api.c +++ b/sam_api.c @@ -1,4 +1,6 @@ #include "sam_api.h" +#include "hf_read_lifecycle.h" +#include "hf_sam_response_view.h" #include "seader_i.h" #include "protocol/rfal_picopass.h" #include "sam_key_label.h" @@ -6,6 +8,7 @@ #include "uhf_snmp_probe.h" #include "card_details_builder.h" #include "uhf_status_label.h" +#include "allocation_policy.h" #include #include #include @@ -23,11 +26,22 @@ const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, const uint8_t seader_oid[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x04}; +static void log_hex(const char* prefix, const uint8_t* data, size_t len) { + char hex[256]; + size_t i; + for(i = 0; i < len && i < 120; i++) { + snprintf(hex + (i * 2), sizeof(hex) - (i * 2), "%02X", data[i]); + } + hex[i * 2] = '\0'; + FURI_LOG_W(TAG, "%s len=%zu: %s", prefix, len, hex); +} + static void seader_sam_set_state( Seader* seader, SeaderSamState state, SeaderSamIntent intent, SamCommand_PR command); +static void seader_snmp_probe_finish(Seader* seader); static const char* seader_snmp_probe_stage_name(SeaderUhfSnmpProbeStage stage) { switch(stage) { @@ -35,6 +49,10 @@ static const char* seader_snmp_probe_stage_name(SeaderUhfSnmpProbeStage stage) { return "discovery"; case SeaderUhfSnmpProbeStageReadIce: return "read_ice"; + case SeaderUhfSnmpProbeStageReadStandardEncryptionKey: + return "read_std_enc_key"; + case SeaderUhfSnmpProbeStageReadStandardSignatureKey: + return "read_std_sig_key"; case SeaderUhfSnmpProbeStageReadTagConfig: return "read_tag_config"; case SeaderUhfSnmpProbeStageReadMonza4QtKey: @@ -67,6 +85,8 @@ static void seader_update_sam_key_label(Seader* seader, const uint8_t* value, si seader->sam_key_probe_status, value, value_len, + seader->snmp_probe.standard_pacs_keys_probed, + seader_uhf_snmp_probe_standard_pacs_keys_present(&seader->snmp_probe), seader->sam_key_label, sizeof(seader->sam_key_label)); seader_publish_sam_status(seader); @@ -77,8 +97,11 @@ static void seader_update_uhf_status_label(Seader* seader) { return; } + const SeaderUhfProbeStatus probe_status = + seader_board_class_supports_uhf(seader->board_class) ? seader->uhf_probe_status : + SeaderUhfProbeStatusHidden; seader_uhf_status_label_format( - seader->uhf_probe_status, + probe_status, seader->snmp_probe.has_monza4qt, seader->snmp_probe.monza4qt_key_present, seader->snmp_probe.has_higgs3, @@ -88,22 +111,30 @@ static void seader_update_uhf_status_label(Seader* seader) { seader_publish_sam_status(seader); } -static SeaderWorker* seader_get_active_worker(Seader* seader) { - return seader ? seader->worker : NULL; +static void seader_set_sam_key_probe_status_from_snmp(Seader* seader, bool probe_succeeded) { + if(!seader) { + return; + } + + seader->sam_key_probe_status = seader_sam_key_probe_status_from_snmp_result( + probe_succeeded, seader->snmp_probe.ice_value_storage, seader->snmp_probe.ice_value_len); } -static bool seader_ice_value_is_standard(const uint8_t* value, size_t value_len) { - if(!value || value_len == 0U) { - return false; +static void seader_finish_failed_snmp_probe(Seader* seader) { + if(!seader) { + return; } - for(size_t i = 0; i < value_len; i++) { - if(value[i] != 0x00U) { - return false; - } - } + seader->uhf_probe_status = SeaderUhfProbeStatusFailed; + seader_update_uhf_status_label(seader); + seader_set_sam_key_probe_status_from_snmp(seader, false); + seader_update_sam_key_label( + seader, seader->snmp_probe.ice_value_storage, seader->snmp_probe.ice_value_len); + seader_snmp_probe_finish(seader); +} - return true; +static SeaderWorker* seader_get_active_worker(Seader* seader) { + return seader ? seader->worker : NULL; } static SeaderUartBridge* seader_require_uart(Seader* seader) { @@ -126,11 +157,14 @@ static void seader_reset_cached_sam_metadata(Seader* seader) { } seader->sam_key_probe_status = SeaderSamKeyProbeStatusUnknown; - seader->uhf_probe_status = SeaderUhfProbeStatusUnknown; + seader->uhf_probe_status = seader_board_class_supports_uhf(seader->board_class) ? + SeaderUhfProbeStatusUnknown : + SeaderUhfProbeStatusHidden; seader->sam_version[0] = 0U; seader->sam_version[1] = 0U; seader->uhf_status_label[0] = '\0'; seader_uhf_snmp_probe_init(&seader->snmp_probe); + seader->snmp_probe.supports_uhf = seader_board_class_supports_uhf(seader->board_class); } static bool seader_snmp_probe_send_next_request(Seader* seader) { @@ -179,10 +213,17 @@ static void seader_start_snmp_probe(Seader* seader) { } seader->mode_runtime = SeaderModeRuntimeUHF; seader_uhf_snmp_probe_init(&seader->snmp_probe); - seader->sam_key_probe_status = SeaderSamKeyProbeStatusUnknown; - seader->uhf_probe_status = SeaderUhfProbeStatusUnknown; - seader_update_sam_key_label(seader, NULL, 0U); - seader_update_uhf_status_label(seader); + seader->snmp_probe.supports_uhf = seader_board_class_supports_uhf(seader->board_class); + if(seader->sam_key_probe_status != SeaderSamKeyProbeStatusVerifiedStandard && + seader->sam_key_probe_status != SeaderSamKeyProbeStatusVerifiedValue) { + seader->sam_key_probe_status = SeaderSamKeyProbeStatusUnknown; + seader_update_sam_key_label(seader, NULL, 0U); + } + if(seader->uhf_probe_status != SeaderUhfProbeStatusSuccess) { + seader->uhf_probe_status = seader->snmp_probe.supports_uhf ? SeaderUhfProbeStatusUnknown : + SeaderUhfProbeStatusHidden; + seader_update_uhf_status_label(seader); + } seader_sam_set_state( seader, SeaderSamStateCapabilityPending, @@ -190,9 +231,8 @@ static void seader_start_snmp_probe(Seader* seader) { SamCommand_PR_processSNMPMessage); if(!seader_snmp_probe_send_next_request(seader)) { - seader->sam_key_probe_status = SeaderSamKeyProbeStatusProbeFailed; - seader->uhf_probe_status = SeaderUhfProbeStatusFailed; - seader_update_sam_key_label(seader, NULL, 0U); + seader->uhf_probe_status = seader->snmp_probe.supports_uhf ? SeaderUhfProbeStatusFailed : + SeaderUhfProbeStatusHidden; seader_update_uhf_status_label(seader); seader_snmp_probe_finish(seader); } @@ -306,9 +346,14 @@ uint8_t select_desfire_app_no_le[] = uint8_t FILE_NOT_FOUND[] = {0x6a, 0x82}; void* calloc(size_t count, size_t size) { - void* ptr = malloc(count * size); + size_t total_size = 0U; + if(!seader_size_multiply_checked(count, size, &total_size)) { + return NULL; + } + + void* ptr = malloc(total_size); if(ptr) { - memset(ptr, 0, count * size); + memset(ptr, 0, total_size); } return ptr; } @@ -487,7 +532,7 @@ bool seader_send_apdu( uint8_t length = header_len + payloadLen; uint8_t* apdu; - bool must_free = false; + uint8_t local_apdu_buf[262]; uintptr_t tx_start = (uintptr_t)seader_uart->tx_buf; uintptr_t tx_end = tx_start + SEADER_UART_RX_BUF_SIZE; uintptr_t payload_addr = (uintptr_t)payload; @@ -502,13 +547,8 @@ bool seader_send_apdu( if(scratchpad_payload) { apdu = (uint8_t*)(payload_addr - header_len); } else { - apdu = malloc(length); - if(!apdu) { - FURI_LOG_E(TAG, "Failed to allocate memory for apdu in seader_send_apdu"); - return false; - } + apdu = local_apdu_buf; memcpy(apdu + header_len, payload, payloadLen); - must_free = true; } apdu[0] = CLA; @@ -524,7 +564,7 @@ bool seader_send_apdu( apdu[4] = payloadLen; } - SEADER_VERBOSE_HEX(FuriLogLevelDebug, TAG, "seader_send_apdu", apdu, length); + log_hex("RAW TX APDU", apdu, length); if(seader_uart->T == 1) { seader_send_t1(seader_uart, apdu, length); @@ -532,10 +572,6 @@ bool seader_send_apdu( seader_ccid_XfrBlock(seader_uart, apdu, length); } - if(must_free) { - free(apdu); - } - return true; } @@ -566,16 +602,13 @@ void seader_send_payload( size_t max_der_len = UINT8_MAX - ASN1_PREFIX; uint8_t* payload_buf = scratchpad; bool payload_in_scratchpad = true; + uint8_t fallback_buf[255]; asn_enc_rval_t er = der_encode_to_buffer( &asn_DEF_Payload, payload, scratchpad + ASN1_PREFIX, scratchpad_size - ASN1_PREFIX); if(er.encoded < 0 || ((size_t)er.encoded + ASN1_PREFIX) > UINT8_MAX) { - payload_buf = malloc(ASN1_PREFIX + max_der_len); - if(!payload_buf) { - FURI_LOG_E(TAG, "Failed to allocate DER fallback buffer"); - return; - } + payload_buf = fallback_buf; payload_in_scratchpad = false; er = der_encode_to_buffer( @@ -584,18 +617,12 @@ void seader_send_payload( if(er.encoded < 0) { FURI_LOG_E(TAG, "Failed to encode payload"); - if(!payload_in_scratchpad) { - free(payload_buf); - } return; } size_t apdu_payload_len = ASN1_PREFIX + (size_t)er.encoded; if(apdu_payload_len > UINT8_MAX) { FURI_LOG_E(TAG, "Encoded payload too large for APDU: %d", (int)apdu_payload_len); - if(!payload_in_scratchpad) { - free(payload_buf); - } return; } @@ -631,10 +658,6 @@ void seader_send_payload( payload_buf, (uint8_t)apdu_payload_len, payload_in_scratchpad); - - if(!payload_in_scratchpad) { - free(payload_buf); - } } void seader_send_process_config_card(Seader* seader) { @@ -706,10 +729,12 @@ 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); + if(!seader->sam_present) { + seader_reset_cached_sam_metadata(seader); + seader->sam_key_probe_status = SeaderSamKeyProbeStatusUnknown; + seader_update_sam_key_label(seader, NULL, 0U); + } seader->sam_present = true; - seader->sam_key_probe_status = SeaderSamKeyProbeStatusUnknown; - seader_update_sam_key_label(seader, NULL, 0U); seader_sam_set_state( seader, SeaderSamStateVersionPending, SeaderSamIntentMaintenance, samCommand.present); @@ -817,7 +842,15 @@ static bool seader_store_pacs_bits( static bool seader_unpack_pacs2_bits(Seader* seader, const OCTET_STRING_t* pacs_bits) { SeaderCredential* seader_credential = seader->credential; if(!pacs_bits || !pacs_bits->buf || pacs_bits->size < 2) { - FURI_LOG_W(TAG, "Malformed pacs2 bits"); + FURI_LOG_W(TAG, "Malformed pacs2 bits: pacs_bits=%p", (void*)pacs_bits); + if(pacs_bits) { + FURI_LOG_W(TAG, " buf=%p, size=%zu", (void*)pacs_bits->buf, pacs_bits->size); + if(pacs_bits->buf) { + for(size_t i = 0; i < pacs_bits->size && i < 16; i++) { + FURI_LOG_W(TAG, " byte[%zu] = 0x%02x", i, pacs_bits->buf[i]); + } + } + } return false; } @@ -1004,7 +1037,10 @@ 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(Seader* seader) { +void seader_abort_active_read_with_reason( + Seader* seader, + SeaderHfReadFailureReason reason, + const char* detail) { 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); @@ -1015,6 +1051,17 @@ static void seader_abort_active_read(Seader* seader) { seader->samCommand, seader->sam_state, seader->sam_intent); + if(reason != SeaderHfReadFailureReasonNone) { + seader->hf_read_failure_reason = reason; + if(detail && detail[0] != '\0') { + strlcpy(seader->read_error, detail, sizeof(seader->read_error)); + } else { + strlcpy( + seader->read_error, + seader_hf_read_failure_reason_text(reason), + sizeof(seader->read_error)); + } + } if(seader_worker) { seader_worker->stage = SeaderPollerEventTypeFail; } @@ -1025,6 +1072,10 @@ static void seader_abort_active_read(Seader* seader) { view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit); } +static void seader_abort_active_read(Seader* seader) { + seader_abort_active_read_with_reason(seader, SeaderHfReadFailureReasonNone, NULL); +} + bool seader_parse_sam_response2(Seader* seader, SamResponse2_t* samResponse) { switch(samResponse->present) { case SamResponse2_PR_pacs: @@ -1047,6 +1098,29 @@ bool seader_parse_sam_response2(Seader* seader, SamResponse2_t* samResponse) { seader->credential->pacs_media_type = pacs2.type ? (SeaderPacsMediaType)(*pacs2.type) : SeaderPacsMediaTypeUnknown; + const bool sam_keys_missing = seader_pacs2_indicates_sam_keys_missing( + seader->credential->has_pacs_media_type, + pacs ? pacs->buf : NULL, + pacs ? pacs->size : 0U); + if(sam_keys_missing) { + char read_error[SEADER_TEXT_STORE_SIZE + 1] = {0}; + seader_hf_read_format_sam_keys_missing_error( + seader->credential->has_pacs_media_type, + (SeaderHfPacsMediaType)seader->credential->pacs_media_type, + seader->snmp_probe.standard_pacs_keys_probed, + seader_uhf_snmp_probe_standard_pacs_keys_present(&seader->snmp_probe), + read_error, + sizeof(read_error)); + FURI_LOG_W( + TAG, + "Empty PACS2 after card read: media=%d probe=%d", + seader->credential->pacs_media_type, + seader->sam_key_probe_status); + seader_abort_active_read_with_reason( + seader, SeaderHfReadFailureReasonSamKeysMissing, read_error); + break; + } + if(seader_unpack_pacs2_bits(seader, pacs)) { SeaderWorker* seader_worker = seader_get_active_worker(seader); if(seader_worker) { @@ -1056,7 +1130,8 @@ bool seader_parse_sam_response2(Seader* seader, SamResponse2_t* samResponse) { seader_sam_set_state( seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING); } else { - seader_abort_active_read(seader); + seader_abort_active_read_with_reason( + seader, SeaderHfReadFailureReasonProtocolError, NULL); } break; case SamResponse2_PR_NOTHING: @@ -1104,26 +1179,24 @@ bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) { SEADER_VERBOSE_I(TAG, "samResponse processSNMPMessage"); if(!seader_uhf_snmp_probe_consume_response( &seader->snmp_probe, samResponse->buf, samResponse->size)) { - seader->sam_key_probe_status = SeaderSamKeyProbeStatusProbeFailed; - seader->uhf_probe_status = SeaderUhfProbeStatusFailed; - seader_update_sam_key_label(seader, NULL, 0U); - seader_update_uhf_status_label(seader); - seader_snmp_probe_finish(seader); + seader_finish_failed_snmp_probe(seader); break; } if(seader->snmp_probe.ice_value_len > 0U) { - seader->sam_key_probe_status = - seader_ice_value_is_standard( - seader->snmp_probe.ice_value_storage, seader->snmp_probe.ice_value_len) ? - SeaderSamKeyProbeStatusVerifiedStandard : - SeaderSamKeyProbeStatusVerifiedValue; + seader_set_sam_key_probe_status_from_snmp(seader, true); } - if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadTagConfig) { - seader->uhf_probe_status = SeaderUhfProbeStatusSuccess; + if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadStandardEncryptionKey || + seader->snmp_probe.stage == SeaderUhfSnmpProbeStageDone) { seader_update_sam_key_label( seader, seader->snmp_probe.ice_value_storage, seader->snmp_probe.ice_value_len); + } + if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadTagConfig || + seader->snmp_probe.stage == SeaderUhfSnmpProbeStageDone) { + if(seader->snmp_probe.supports_uhf) { + seader->uhf_probe_status = SeaderUhfProbeStatusSuccess; + } seader_update_uhf_status_label(seader); } @@ -1132,11 +1205,7 @@ bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) { } else if( seader->snmp_probe.stage == SeaderUhfSnmpProbeStageFailed || !seader_snmp_probe_send_next_request(seader)) { - seader->sam_key_probe_status = SeaderSamKeyProbeStatusProbeFailed; - seader->uhf_probe_status = SeaderUhfProbeStatusFailed; - seader_update_sam_key_label(seader, NULL, 0U); - seader_update_uhf_status_label(seader); - seader_snmp_probe_finish(seader); + seader_finish_failed_snmp_probe(seader); } break; case SeaderSamStateDetectPending: @@ -1188,14 +1257,21 @@ bool seader_parse_response(Seader* seader, Response_t* response) { return false; } -void seader_send_nfc_rx(Seader* seader, uint8_t* buffer, size_t len) { +void seader_send_nfc_rx_status( + Seader* seader, + uint8_t* buffer, + size_t len, + SeaderHfBridgeRfStatus status) { OCTET_STRING_t rxData = {.buf = buffer, .size = len}; - uint8_t status[] = {0x00, 0x00}; - RfStatus_t rfStatus = {.buf = status, .size = 2}; + uint8_t status_bytes[2] = {0}; + seader_hf_bridge_rf_status_bytes(status, status_bytes); + RfStatus_t rfStatus = {.buf = status_bytes, .size = sizeof(status_bytes)}; NFCRx_t nfcRx = {0}; nfcRx.rfStatus = rfStatus; - nfcRx.data = &rxData; + if(buffer && len > 0U) { + nfcRx.data = &rxData; + } NFCResponse_t nfcResponse = {0}; nfcResponse.present = NFCResponse_PR_nfcRx; @@ -1208,6 +1284,10 @@ void seader_send_nfc_rx(Seader* seader, uint8_t* buffer, size_t len) { seader_send_response(seader, &response, NFCInterface, SAMInterface, 0x0); } +void seader_send_nfc_rx(Seader* seader, uint8_t* buffer, size_t len) { + seader_send_nfc_rx_status(seader, buffer, len, SeaderHfBridgeRfStatusSuccess); +} + void seader_capture_sio(BitBuffer* tx_buffer, BitBuffer* rx_buffer, SeaderCredential* credential) { const uint8_t* buffer = bit_buffer_get_data(tx_buffer); size_t len = bit_buffer_get_size_bytes(tx_buffer); @@ -1476,6 +1556,7 @@ void seader_mfc_transmit( } seader_trace_mfc_bitbuffer("mfc tx bitbuffer", tx_buffer, true); +#if SEADER_VERBOSE_LOG || defined(SEADER_ENABLE_TRACE_LOG) // Log the BitBuffer contents efficiently size_t tx_size = bit_buffer_get_size_bytes(tx_buffer); uint8_t* tx_data = malloc(tx_size); @@ -1488,6 +1569,7 @@ void seader_mfc_transmit( seader_trace_hex(TAG, "mfc tx no parity", tx_data, tx_size); free(tx_data); } +#endif MfClassicError error = mf_classic_poller_send_custom_parity_frame( mfc_poller, tx_buffer, rx_buffer, MF_CLASSIC_FWT_FC); @@ -1511,6 +1593,7 @@ void seader_mfc_transmit( const uint8_t* rx_parity = bit_buffer_get_parity(rx_buffer); seader_trace_mfc_bitbuffer("mfc rx bitbuffer", rx_buffer, true); +#if SEADER_VERBOSE_LOG || defined(SEADER_ENABLE_TRACE_LOG) // Log the BitBuffer contents efficiently uint8_t* rx_data = malloc(length); if(rx_data) { @@ -1522,6 +1605,7 @@ void seader_mfc_transmit( seader_trace_hex(TAG, "mfc rx no parity", rx_data, length); free(rx_data); } +#endif uint8_t with_parity[SEADER_POLLER_MAX_BUFFER_SIZE]; memset(with_parity, 0, sizeof(with_parity)); @@ -1559,6 +1643,7 @@ void seader_mfc_transmit( bit_buffer_copy_bytes(rx_buffer, with_parity, length); +#if SEADER_VERBOSE_LOG || defined(SEADER_ENABLE_TRACE_LOG) // Log the BitBuffer contents efficiently uint8_t* rx_data_parity = malloc(length); if(rx_data_parity) { @@ -1570,6 +1655,7 @@ void seader_mfc_transmit( seader_trace_hex(TAG, "mfc rx parity", rx_data_parity, length); free(rx_data_parity); } +#endif } else { FURI_LOG_W(TAG, "UNHANDLED FORMAT"); @@ -1587,26 +1673,26 @@ void seader_mfc_transmit( bit_buffer_free(rx_buffer); } -void seader_parse_nfc_command_transmit(Seader* seader, NFCSend_t* nfcSend) { -#ifdef ASN1_DEBUG - SEADER_VERBOSE_HEX( - FuriLogLevelDebug, TAG, "Transmit data", nfcSend->data.buf, nfcSend->data.size); -#endif - +static void seader_dispatch_nfc_send( + Seader* seader, + uint8_t* data, + size_t data_len, + uint32_t timeout_us, + const uint8_t* format, + size_t format_len) { PluginHfAction action = { - .data = nfcSend->data.buf, - .len = nfcSend->data.size, - .timeout = nfcSend->timeOut, + .data = data, + .len = data_len, + .timeout = timeout_us, }; - 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(format) { + const size_t copied_format_len = + format_len < sizeof(action.format) ? format_len : sizeof(action.format); + memcpy(action.format, format, copied_format_len); } if(seader->credential->type == SeaderCredentialTypeVirtual) { - seader_virtual_picopass_state_machine(seader, nfcSend->data.buf, nfcSend->data.size); + seader_virtual_picopass_state_machine(seader, data, data_len); } else if(seader->plugin_hf && seader->hf_plugin_ctx) { if(seader->credential->type == SeaderCredentialTypePicopass) { action.type = PluginHfActionTypePicopassTx; @@ -1633,6 +1719,21 @@ void seader_parse_nfc_command_transmit(Seader* seader, NFCSend_t* nfcSend) { } } +void seader_parse_nfc_command_transmit(Seader* seader, NFCSend_t* nfcSend) { +#ifdef ASN1_DEBUG + SEADER_VERBOSE_HEX( + FuriLogLevelDebug, TAG, "Transmit data", nfcSend->data.buf, nfcSend->data.size); +#endif + + const long sam_timeout_us = nfcSend->timeOut; + const uint32_t timeout_us = sam_timeout_us > 0L ? (uint32_t)sam_timeout_us : 0U; + const uint8_t* format = nfcSend->format ? nfcSend->format->buf : NULL; + const size_t format_len = nfcSend->format ? (size_t)nfcSend->format->size : 0U; + + seader_dispatch_nfc_send( + seader, nfcSend->data.buf, nfcSend->data.size, timeout_us, format, format_len); +} + void seader_parse_nfc_off(Seader* seader) { SEADER_VERBOSE_D(TAG, "Set Field Off"); seader_trace(TAG, "nfcOff state=%d intent=%d", seader->sam_state, seader->sam_intent); @@ -1719,28 +1820,26 @@ bool seader_worker_state_machine( err->data.size > 1U ? err->data.buf[1] : 0U, err->data.size); if(seader->snmp_probe.ice_value_len > 0U) { - seader->sam_key_probe_status = seader_ice_value_is_standard( - seader->snmp_probe.ice_value_storage, - seader->snmp_probe.ice_value_len) ? - SeaderSamKeyProbeStatusVerifiedStandard : - SeaderSamKeyProbeStatusVerifiedValue; + seader_set_sam_key_probe_status_from_snmp(seader, true); } - if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadTagConfig) { - seader->uhf_probe_status = SeaderUhfProbeStatusSuccess; + if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadStandardEncryptionKey || + seader->snmp_probe.stage == SeaderUhfSnmpProbeStageDone) { + seader_update_sam_key_label( + seader, + seader->snmp_probe.ice_value_storage, + seader->snmp_probe.ice_value_len); + } + if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadTagConfig || + seader->snmp_probe.stage == SeaderUhfSnmpProbeStageDone) { + if(seader->snmp_probe.supports_uhf) { + seader->uhf_probe_status = SeaderUhfProbeStatusSuccess; + } + seader_update_uhf_status_label(seader); } - seader_update_sam_key_label( - seader, - seader->snmp_probe.ice_value_storage, - seader->snmp_probe.ice_value_len); - seader_update_uhf_status_label(seader); if(seader->snmp_probe.stage == SeaderUhfSnmpProbeStageDone) { seader_snmp_probe_finish(seader); } else if(!seader_snmp_probe_send_next_request(seader)) { - seader->sam_key_probe_status = SeaderSamKeyProbeStatusProbeFailed; - seader->uhf_probe_status = SeaderUhfProbeStatusFailed; - seader_update_sam_key_label(seader, NULL, 0U); - seader_update_uhf_status_label(seader); - seader_snmp_probe_finish(seader); + seader_finish_failed_snmp_probe(seader); } } else { FURI_LOG_W( @@ -1751,11 +1850,7 @@ bool seader_worker_state_machine( err->data.size > 0U ? err->data.buf[0] : 0U, err->data.size > 1U ? err->data.buf[1] : 0U, err->data.size); - seader->sam_key_probe_status = SeaderSamKeyProbeStatusProbeFailed; - seader->uhf_probe_status = SeaderUhfProbeStatusFailed; - seader_update_sam_key_label(seader, NULL, 0U); - seader_update_uhf_status_label(seader); - seader_snmp_probe_finish(seader); + seader_finish_failed_snmp_probe(seader); } } else { FURI_LOG_W(TAG, "Payload_PR_errorResponse"); @@ -1777,9 +1872,33 @@ bool seader_process_success_response_i( size_t len, bool online, SeaderPollerContainer* spc) { + log_hex("RAW RX APDU", apdu, len); Payload_t payload = {0}; Payload_t* payload_p = &payload; bool processed = false; + SeaderHfSamNfcSendView nfc_send_view = {0}; + + if(seader_hf_sam_response_view_parse_nfc_send(apdu, len, &nfc_send_view)) { + if(online) { + seader_dispatch_nfc_send( + seader, + (uint8_t*)nfc_send_view.data, + nfc_send_view.data_len, + nfc_send_view.timeout_us, + nfc_send_view.format, + nfc_send_view.format_len); + return true; + } + + seader_trace( + TAG, "defer offline nfcSend state=%d intent=%d", seader->sam_state, seader->sam_intent); + return false; + } + + if(len < ASN1_PREFIX) { + SEADER_VERBOSE_HEX(FuriLogLevelDebug, TAG, "Short APDU payload", apdu, len); + return false; + } /* Seader wraps each ASN.1 payload with a 6-byte application header {from, to, replyTo, 0x00, 0x00, 0x00}. Skip that prefix before decoding. */ @@ -1808,7 +1927,6 @@ bool seader_process_success_response_i( processed = seader_worker_state_machine(seader, &payload, online, spc); } else { SEADER_VERBOSE_HEX(FuriLogLevelDebug, TAG, "Failed to decode APDU payload", apdu, len); - seader_abort_active_read(seader); } ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_Payload, &payload); diff --git a/sam_api.h b/sam_api.h index 755c820..f995079 100644 --- a/sam_api.h +++ b/sam_api.h @@ -11,6 +11,9 @@ #include #include +#include "hf_bridge_policy.h" +#include "hf_read_lifecycle.h" + typedef struct Seader Seader; typedef struct SeaderPollerContainer SeaderPollerContainer; @@ -30,10 +33,19 @@ NfcCommand seader_worker_card_detect( uint8_t ats_len); void seader_send_nfc_rx(Seader* seader, uint8_t* buffer, size_t len); +void seader_send_nfc_rx_status( + Seader* seader, + uint8_t* buffer, + size_t len, + SeaderHfBridgeRfStatus status); 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, diff --git a/sam_key_label.c b/sam_key_label.c index fff637a..1fe6059 100644 --- a/sam_key_label.c +++ b/sam_key_label.c @@ -18,11 +18,30 @@ static bool seader_sam_key_label_is_missing(const uint8_t* value, size_t value_l return true; } +SeaderSamKeyProbeStatus seader_sam_key_probe_status_from_snmp_result( + bool probe_succeeded, + const uint8_t* elite_ice_value, + size_t elite_ice_value_len) { + if(!probe_succeeded) { + return SeaderSamKeyProbeStatusProbeFailed; + } + + if(!elite_ice_value || elite_ice_value_len == 0U) { + return SeaderSamKeyProbeStatusUnknown; + } + + return seader_sam_key_label_is_missing(elite_ice_value, elite_ice_value_len) ? + SeaderSamKeyProbeStatusVerifiedStandard : + SeaderSamKeyProbeStatusVerifiedValue; +} + void seader_sam_key_label_format( bool sam_present, SeaderSamKeyProbeStatus probe_status, const uint8_t* elite_ice_value, size_t elite_ice_value_len, + bool standard_pacs_keys_probed, + bool standard_pacs_keys_present, char* out, size_t out_size) { if(!out || out_size == 0U) { @@ -36,6 +55,11 @@ void seader_sam_key_label_format( return; } + if(standard_pacs_keys_probed && !standard_pacs_keys_present) { + snprintf(out, out_size, "MISSING STANDARD KEYS"); + return; + } + if(probe_status == SeaderSamKeyProbeStatusUnknown) { snprintf(out, out_size, "SAM: Key Unknown"); return; diff --git a/sam_key_label.h b/sam_key_label.h index 2fd293c..4e72dcd 100644 --- a/sam_key_label.h +++ b/sam_key_label.h @@ -13,10 +13,16 @@ typedef enum { SeaderSamKeyProbeStatusProbeFailed, } SeaderSamKeyProbeStatus; +SeaderSamKeyProbeStatus seader_sam_key_probe_status_from_snmp_result( + bool probe_succeeded, + const uint8_t* elite_ice_value, + size_t elite_ice_value_len); void seader_sam_key_label_format( bool sam_present, SeaderSamKeyProbeStatus probe_status, const uint8_t* elite_ice_value, size_t elite_ice_value_len, + bool standard_pacs_keys_probed, + bool standard_pacs_keys_present, char* out, size_t out_size); diff --git a/scenes/seader_scene_card_menu.c b/scenes/seader_scene_card_menu.c index 0c7915e..702c199 100644 --- a/scenes/seader_scene_card_menu.c +++ b/scenes/seader_scene_card_menu.c @@ -17,7 +17,10 @@ void seader_scene_card_menu_submenu_callback(void* context, uint32_t index) { void seader_scene_card_menu_on_enter(void* context) { Seader* seader = context; SeaderCredential* credential = seader->credential; - Submenu* submenu = seader->submenu; + Submenu* submenu = seader_get_submenu(seader); + if(!submenu) { + return; + } submenu_add_item( submenu, "Save", SubmenuIndexSave, seader_scene_card_menu_submenu_callback, seader); @@ -45,8 +48,7 @@ void seader_scene_card_menu_on_enter(void* context) { submenu, "Save MFC", SubmenuIndexSaveMFC, seader_scene_card_menu_submenu_callback, seader); submenu_set_selected_item( - seader->submenu, - scene_manager_get_scene_state(seader->scene_manager, SeaderSceneCardMenu)); + submenu, scene_manager_get_scene_state(seader->scene_manager, SeaderSceneCardMenu)); view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewMenu); } @@ -92,5 +94,7 @@ bool seader_scene_card_menu_on_event(void* context, SceneManagerEvent event) { void seader_scene_card_menu_on_exit(void* context) { Seader* seader = context; - submenu_reset(seader->submenu); + if(seader->submenu) { + submenu_reset(seader->submenu); + } } diff --git a/scenes/seader_scene_read.c b/scenes/seader_scene_read.c index c1b1d0a..eb4c320 100644 --- a/scenes/seader_scene_read.c +++ b/scenes/seader_scene_read.c @@ -1,4 +1,5 @@ #include "../seader_i.h" +#include "../ui_memory_policy.h" #include "seader_scene_read_common.h" #include @@ -19,6 +20,9 @@ void seader_scene_read_on_enter(void* context) { // Start worker view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewPopup); + if(seader_ui_memory_should_release_inactive_lazy_views(SeaderUiMemoryPhaseHfReadActive)) { + seader_release_inactive_lazy_views(seader); + } view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventBeginRead); seader_blink_start(seader); diff --git a/scenes/seader_scene_read_card_success.c b/scenes/seader_scene_read_card_success.c index ded202a..2deb162 100644 --- a/scenes/seader_scene_read_card_success.c +++ b/scenes/seader_scene_read_card_success.c @@ -56,11 +56,19 @@ void seader_scene_read_card_success_on_enter(void* context) { furi_string_cat_printf(credential_str, "0x%llX", credential->credential); furi_string_set(type_str, seader_credential_get_type_label(credential)); } else { - furi_string_set(type_str, "Read error"); + if(seader->hf_read_failure_reason == SeaderHfReadFailureReasonSamKeysMissing && + credential->has_pacs_media_type) { + furi_string_set(type_str, seader_credential_get_type_label(credential)); + } else { + furi_string_set(type_str, "Read error"); + } furi_string_set(bitlength_str, seader->read_error[0] ? seader->read_error : "Read failed"); - seader_t_1_reset(seader->uart); - seader_ccid_check_for_sam(seader->uart); + if(seader->hf_read_failure_reason == SeaderHfReadFailureReasonSamTimeout || + seader->hf_read_failure_reason == SeaderHfReadFailureReasonProtocolError) { + seader_t_1_reset(seader->uart); + seader_ccid_check_for_sam(seader->uart); + } } widget_add_button_element( diff --git a/scenes/seader_scene_read_card_type.c b/scenes/seader_scene_read_card_type.c index b514f5c..0c885b4 100644 --- a/scenes/seader_scene_read_card_type.c +++ b/scenes/seader_scene_read_card_type.c @@ -20,7 +20,10 @@ 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; + Submenu* submenu = seader_get_submenu(seader); + if(!submenu) { + return; + } 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); @@ -63,5 +66,7 @@ bool seader_scene_read_card_type_on_event(void* context, SceneManagerEvent event void seader_scene_read_card_type_on_exit(void* context) { Seader* seader = context; - submenu_reset(seader->submenu); + if(seader->submenu) { + submenu_reset(seader->submenu); + } } diff --git a/scenes/seader_scene_read_common.c b/scenes/seader_scene_read_common.c index db35e97..e173070 100644 --- a/scenes/seader_scene_read_common.c +++ b/scenes/seader_scene_read_common.c @@ -20,7 +20,8 @@ void seader_scene_read_prepare(Seader* seader) { if(seader->sam_state == SeaderSamStateIdle) { seader->samCommand = SamCommand_PR_NOTHING; } - memset(seader->read_error, 0, sizeof(seader->read_error)); + seader_hf_read_prepare_context( + &seader->hf_read_failure_reason, seader->read_error, sizeof(seader->read_error)); } void seader_scene_read_cleanup(Seader* seader) { diff --git a/scenes/seader_scene_read_config_card.c b/scenes/seader_scene_read_config_card.c index 55da4e7..6464ec0 100644 --- a/scenes/seader_scene_read_config_card.c +++ b/scenes/seader_scene_read_config_card.c @@ -1,4 +1,5 @@ #include "../seader_i.h" +#include "../ui_memory_policy.h" #include "seader_scene_read_common.h" #include @@ -19,6 +20,9 @@ void seader_scene_read_config_card_on_enter(void* context) { // Start worker view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewPopup); + if(seader_ui_memory_should_release_inactive_lazy_views(SeaderUiMemoryPhaseHfReadActive)) { + seader_release_inactive_lazy_views(seader); + } seader_scene_read_prepare(seader); seader_credential_clear(seader->credential); diff --git a/scenes/seader_scene_sam_info.c b/scenes/seader_scene_sam_info.c index 4bb2bc2..be39948 100644 --- a/scenes/seader_scene_sam_info.c +++ b/scenes/seader_scene_sam_info.c @@ -32,11 +32,15 @@ void seader_scene_sam_info_on_enter(void* context) { 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_printf( - status_str, - "%s\n%s", - seader_board_status_label(seader->board_status), - seader->uhf_status_label); + if(seader->uhf_status_label[0] != '\0') { + furi_string_printf( + status_str, + "%s\n%s", + seader_board_status_label(seader->board_status), + seader->uhf_status_label); + } else { + furi_string_set_str(status_str, seader_board_status_label(seader->board_status)); + } widget_add_button_element( seader->widget, GuiButtonTypeLeft, "Back", seader_scene_sam_info_widget_callback, seader); diff --git a/scenes/seader_scene_sam_present.c b/scenes/seader_scene_sam_present.c index 42d1857..fa362dc 100644 --- a/scenes/seader_scene_sam_present.c +++ b/scenes/seader_scene_sam_present.c @@ -12,7 +12,10 @@ static uint8_t fwChecks = 3; 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* submenu = seader_get_submenu(seader); + if(!submenu) { + return; + } submenu_reset(submenu); submenu_add_item( @@ -110,8 +113,10 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SeaderWorkerEventHfTeardownComplete) { consumed = seader_hf_finish_teardown_action(seader); } else if(event.event == SeaderCustomEventSamStatusUpdated) { - seader_scene_sam_present_rebuild_menu( - seader, submenu_get_selected_item(seader->submenu)); + Submenu* submenu = seader_get_submenu(seader); + if(submenu) { + seader_scene_sam_present_rebuild_menu(seader, submenu_get_selected_item(submenu)); + } consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { @@ -122,8 +127,10 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) { } if(fwChecks > 0 && seader->sam_version[0] != 0 && seader->sam_version[1] != 0) { fwChecks--; - seader_scene_sam_present_rebuild_menu( - seader, submenu_get_selected_item(seader->submenu)); + Submenu* submenu = seader_get_submenu(seader); + if(submenu) { + seader_scene_sam_present_rebuild_menu(seader, submenu_get_selected_item(submenu)); + } } } @@ -132,5 +139,7 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) { void seader_scene_sam_present_on_exit(void* context) { Seader* seader = context; - submenu_reset(seader->submenu); + if(seader->submenu) { + submenu_reset(seader->submenu); + } } diff --git a/scenes/seader_scene_saved_menu.c b/scenes/seader_scene_saved_menu.c index e94657d..742a4b4 100644 --- a/scenes/seader_scene_saved_menu.c +++ b/scenes/seader_scene_saved_menu.c @@ -15,7 +15,10 @@ void seader_scene_saved_menu_submenu_callback(void* context, uint32_t index) { void seader_scene_saved_menu_on_enter(void* context) { Seader* seader = context; SeaderCredential* credential = seader->credential; - Submenu* submenu = seader->submenu; + Submenu* submenu = seader_get_submenu(seader); + if(!submenu) { + return; + } submenu_add_item( submenu, "Info", SubmenuIndexInfo, seader_scene_saved_menu_submenu_callback, seader); @@ -32,8 +35,7 @@ void seader_scene_saved_menu_on_enter(void* context) { } submenu_set_selected_item( - seader->submenu, - scene_manager_get_scene_state(seader->scene_manager, SeaderSceneSavedMenu)); + submenu, scene_manager_get_scene_state(seader->scene_manager, SeaderSceneSavedMenu)); view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewMenu); } @@ -63,5 +65,7 @@ bool seader_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { void seader_scene_saved_menu_on_exit(void* context) { Seader* seader = context; - submenu_reset(seader->submenu); + if(seader->submenu) { + submenu_reset(seader->submenu); + } } diff --git a/scenes/seader_scene_start.c b/scenes/seader_scene_start.c index 0d9e793..b63915f 100644 --- a/scenes/seader_scene_start.c +++ b/scenes/seader_scene_start.c @@ -113,6 +113,8 @@ bool seader_scene_start_on_event(void* context, SceneManagerEvent event) { SeaderSamKeyProbeStatusUnknown, NULL, 0U, + false, + false, seader->sam_key_label, sizeof(seader->sam_key_label)); scene_manager_next_scene(seader->scene_manager, SeaderSceneSamMissing); @@ -126,6 +128,8 @@ bool seader_scene_start_on_event(void* context, SceneManagerEvent event) { SeaderSamKeyProbeStatusUnknown, NULL, 0U, + false, + false, seader->sam_key_label, sizeof(seader->sam_key_label)); scene_manager_next_scene(seader->scene_manager, SeaderSceneSamWrong); diff --git a/seader.c b/seader.c index e65f9b9..c2dbf1f 100644 --- a/seader.c +++ b/seader.c @@ -4,6 +4,7 @@ #include "hf_read_lifecycle.h" #include "sam_startup_ui.h" #include "trace_log.h" +#include "ui_memory_policy.h" #define TAG "Seader" #define SEADER_PLUGIN_DIR APP_ASSETS_PATH("plugins") @@ -75,6 +76,66 @@ void seader_temp_strings_release(Seader* seader, size_t count) { } } +Submenu* seader_get_submenu(Seader* seader) { + if(!seader) { + return NULL; + } + + if(!seader->submenu) { + seader->submenu = submenu_alloc(); + if(!seader->submenu) { + FURI_LOG_E(TAG, "Failed to allocate submenu view"); + return NULL; + } + + view_dispatcher_add_view( + seader->view_dispatcher, SeaderViewMenu, submenu_get_view(seader->submenu)); + } + + return seader->submenu; +} + +void seader_release_submenu(Seader* seader) { + if(!seader || !seader->submenu) { + return; + } + + view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewMenu); + submenu_free(seader->submenu); + seader->submenu = NULL; +} + +void seader_release_inactive_lazy_views(Seader* seader) { + if(!seader) { + return; + } + + seader_release_submenu(seader); + + if(seader->text_input) { + view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextInput); + text_input_free(seader->text_input); + seader->text_input = NULL; + } + + if(seader->text_box) { + view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextBox); + text_box_free(seader->text_box); + seader->text_box = NULL; + } + + if(seader->text_box_store) { + furi_string_free(seader->text_box_store); + seader->text_box_store = NULL; + } + + if(seader->widget) { + view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewWidget); + widget_free(seader->widget); + seader->widget = NULL; + } +} + TextInput* seader_get_text_input(Seader* seader) { if(!seader) { return NULL; @@ -148,11 +209,22 @@ static void seader_board_prepare_missing_state(Seader* seader, SeaderBoardStatus seader->board_status = status; seader->sam_present = false; + seader->uhf_probe_status = SeaderUhfProbeStatusHidden; + seader_uhf_status_label_format( + seader->uhf_probe_status, + false, + false, + false, + false, + seader->uhf_status_label, + sizeof(seader->uhf_status_label)); seader_sam_key_label_format( false, SeaderSamKeyProbeStatusUnknown, NULL, 0U, + false, + false, seader->sam_key_label, sizeof(seader->sam_key_label)); } @@ -341,6 +413,27 @@ static void seader_board_set_enable_pin(bool enabled) { furi_hal_gpio_write(&gpio_ext_pc3, enabled); } +static bool seader_board_probe_pin_pulldown_high(const GpioPin* pin) { + furi_hal_gpio_init(pin, GpioModeInput, GpioPullDown, GpioSpeedLow); + furi_delay_ms(1U); + const bool high = furi_hal_gpio_read(pin); + furi_hal_gpio_init(pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + return high; +} + +static void seader_board_refresh_class(Seader* seader) { + if(!seader) { + return; + } + + const bool pa4 = seader_board_probe_pin_pulldown_high(&gpio_ext_pa4); + const bool pc1 = seader_board_probe_pin_pulldown_high(&gpio_ext_pc1); + const bool pc0 = seader_board_probe_pin_pulldown_high(&gpio_ext_pc0); + seader->board_class = seader_board_classify(pa4, pc1, pc0); + FURI_LOG_I( + TAG, "Board class=%u straps pa4=%u pc1=%u pc0=%u", seader->board_class, pa4, pc1, pc0); +} + void seader_start_popup_set_stage(Seader* seader, SeaderStartupStage stage) { if(!seader || !seader->popup) { return; @@ -384,6 +477,7 @@ static void seader_board_power_fail(Seader* seader, SeaderBoardStatus status) { } seader->board_power_enabled = false; seader->board_power_owned = false; + seader->board_class = SeaderBoardClassUnknown; seader->board_status = status; } @@ -445,6 +539,7 @@ static bool seader_board_power_on(Seader* seader) { } seader->board_power_enabled = true; seader->board_status = SeaderBoardStatusPowerReadyPendingValidation; + seader_board_refresh_class(seader); return true; } @@ -460,6 +555,7 @@ static void seader_board_power_off(Seader* seader) { } seader->board_power_enabled = false; seader->board_power_owned = false; + seader->board_class = SeaderBoardClassUnknown; } bool seader_board_retry_power_cycle(Seader* seader) { @@ -556,6 +652,15 @@ static void seader_hf_plugin_send_nfc_rx(void* host_ctx, uint8_t* buffer, size_t seader_send_nfc_rx(seader, buffer, len); } +static void seader_hf_plugin_send_nfc_rx_status( + void* host_ctx, + uint8_t* buffer, + size_t len, + SeaderHfBridgeRfStatus status) { + Seader* seader = host_ctx; + seader_send_nfc_rx_status(seader, buffer, len, status); +} + static void seader_hf_plugin_run_conversation(void* host_ctx) { Seader* seader = host_ctx; if(!seader || !seader->worker) { @@ -753,25 +858,48 @@ static bool seader_hf_plugin_picopass_transmit( uint8_t* rx_data, size_t rx_capacity, size_t* rx_len, - uint32_t fwt_fc) { + uint32_t fwt_fc, + SeaderHfBridgeRfStatus* status) { Seader* seader = host_ctx; if(!seader->picopass_poller || !tx_data || !rx_data || !rx_len) { + if(status) { + *status = SeaderHfBridgeRfStatusProtocol; + } 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) { + if(!seader_hf_buffer_pair_prepare( + &seader->picopass_host_buffers, tx_len, rx_capacity, tx_len)) { 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); + if(status) { + *status = SeaderHfBridgeRfStatusProtocol; + } return false; } + BitBuffer* tx_buffer = seader->picopass_host_buffers.tx; + BitBuffer* rx_buffer = seader->picopass_host_buffers.rx; + bool success = 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(status) { + switch(error) { + case PicopassErrorNone: + *status = SeaderHfBridgeRfStatusSuccess; + break; + case PicopassErrorTimeout: + *status = SeaderHfBridgeRfStatusTimeout; + break; + case PicopassErrorIncorrectCrc: + *status = SeaderHfBridgeRfStatusCrc; + break; + case PicopassErrorProtocol: + default: + *status = SeaderHfBridgeRfStatusProtocol; + break; + } + } if(error == PicopassErrorIncorrectCrc) { error = PicopassErrorNone; } @@ -782,8 +910,6 @@ static bool seader_hf_plugin_picopass_transmit( success = true; } - bit_buffer_free(tx_buffer); - bit_buffer_free(rx_buffer); return success; } @@ -800,6 +926,7 @@ static const PluginHfHostApi seader_hf_plugin_host_api = { .notify_worker_exit = seader_hf_plugin_notify_worker_exit, .begin_card_session = seader_hf_plugin_begin_card_session, .send_nfc_rx = seader_hf_plugin_send_nfc_rx, + .send_nfc_rx_status = seader_hf_plugin_send_nfc_rx_status, .run_conversation = seader_hf_plugin_run_conversation, .set_stage = seader_hf_plugin_set_stage, .get_stage = seader_hf_plugin_get_stage, @@ -828,6 +955,71 @@ static void seader_hf_worker_event_callback(uint32_t event, void* context) { view_dispatcher_send_custom_event(seader->view_dispatcher, event); } +static bool seader_hf_host_nfc_acquire(Seader* seader) { + if(!seader) { + return false; + } + + if(seader->nfc && seader->nfc_device) { + return true; + } + + if(seader->nfc || seader->nfc_device) { + FURI_LOG_W( + TAG, + "Normalize partial host NFC state nfc=%p device=%p", + (void*)seader->nfc, + (void*)seader->nfc_device); + if(seader->nfc_device) { + nfc_device_free(seader->nfc_device); + seader->nfc_device = NULL; + } + if(seader->nfc) { + nfc_free(seader->nfc); + seader->nfc = NULL; + } + } + + seader->nfc = nfc_alloc(); + seader->nfc_device = seader->nfc ? nfc_device_alloc() : NULL; + if(!seader->nfc || !seader->nfc_device) { + FURI_LOG_E( + TAG, + "Failed to allocate host NFC objects nfc=%p device=%p", + (void*)seader->nfc, + (void*)seader->nfc_device); + if(seader->nfc_device) { + nfc_device_free(seader->nfc_device); + seader->nfc_device = NULL; + } + if(seader->nfc) { + nfc_free(seader->nfc); + seader->nfc = NULL; + } + return false; + } + + nfc_device_set_loading_callback(seader->nfc_device, seader_nfc_loading_callback, seader); + return true; +} + +static void seader_hf_host_nfc_release(void* context) { + Seader* seader = context; + if(!seader) { + return; + } + + if(seader->nfc_device) { + nfc_device_free(seader->nfc_device); + seader->nfc_device = NULL; + } + + if(seader->nfc) { + nfc_free(seader->nfc); + seader->nfc = NULL; + } +} + static void seader_hf_session_force_unloaded(Seader* seader) { if(!seader) { return; @@ -869,6 +1061,9 @@ static void seader_hf_release_host_picopass(void* context) { picopass_poller_free(seader->picopass_poller); seader->picopass_poller = NULL; } + if(seader) { + seader_hf_buffer_pair_free(&seader->picopass_host_buffers); + } } static void seader_hf_release_plugin_free(void* context) { @@ -930,6 +1125,7 @@ Seader* seader_alloc() { seader->board_power_enabled = false; seader->board_power_owned = false; seader->expansion_disabled = false; + seader->board_class = SeaderBoardClassUnknown; seader->board_status = SeaderBoardStatusUnknown; seader->startup_stage = SeaderStartupStageNone; seader->board_retry_remaining = 0U; @@ -951,12 +1147,14 @@ Seader* seader_alloc() { seader->sam_present = false; memset(seader->sam_version, 0, sizeof(seader->sam_version)); seader->sam_key_probe_status = SeaderSamKeyProbeStatusUnknown; - seader->uhf_probe_status = SeaderUhfProbeStatusUnknown; + seader->uhf_probe_status = SeaderUhfProbeStatusHidden; seader_sam_key_label_format( false, seader->sam_key_probe_status, NULL, 0U, + false, + false, seader->sam_key_label, sizeof(seader->sam_key_label)); seader_uhf_status_label_format( @@ -968,8 +1166,9 @@ Seader* seader_alloc() { seader->uhf_status_label, sizeof(seader->uhf_status_label)); seader_uhf_snmp_probe_init(&seader->snmp_probe); - seader->nfc = nfc_alloc(); - seader->nfc_device = seader->nfc ? nfc_device_alloc() : NULL; + seader->nfc = NULL; + seader->nfc_device = NULL; + seader->picopass_host_buffers = (SeaderHfBufferPair){0}; memset(&seader->hf_mode_ctx, 0, sizeof(seader->hf_mode_ctx)); seader->hf_mode_active = false; @@ -988,14 +1187,6 @@ Seader* seader_alloc() { seader->credential = seader_credential_alloc(); - 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); view_dispatcher_attach_to_gui( @@ -1004,10 +1195,8 @@ Seader* seader_alloc() { // Open Notification record seader->notifications = furi_record_open(RECORD_NOTIFICATION); - // Submenu - seader->submenu = submenu_alloc(); - view_dispatcher_add_view( - seader->view_dispatcher, SeaderViewMenu, submenu_get_view(seader->submenu)); + // Submenu is allocated lazily by menu scenes and can be released during HF reads. + seader->submenu = NULL; // Popup seader->popup = popup_alloc(); @@ -1044,10 +1233,6 @@ Seader* seader_alloc() { seader->start_scene_active = false; seader->sam_present_menu_guard_active = false; - if(seader->nfc_device) { - nfc_device_set_loading_callback(seader->nfc_device, seader_nfc_loading_callback, seader); - } - if(seader->is_debug_enabled) { FURI_LOG_D( TAG, @@ -1073,15 +1258,8 @@ void seader_free(Seader* seader) { 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; - } + seader_hf_host_nfc_release(seader); + seader_hf_buffer_pair_free(&seader->picopass_host_buffers); seader_uart_free(seader->uart); seader->uart = NULL; @@ -1102,8 +1280,7 @@ void seader_free(Seader* seader) { seader->credential = NULL; // Submenu - view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewMenu); - submenu_free(seader->submenu); + seader_release_submenu(seader); // Popup view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewPopup); @@ -1113,26 +1290,7 @@ void seader_free(Seader* seader) { view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewLoading); loading_free(seader->loading); - // TextInput - if(seader->text_input) { - view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextInput); - text_input_free(seader->text_input); - } - - // TextBox - if(seader->text_box) { - view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewTextBox); - text_box_free(seader->text_box); - } - if(seader->text_box_store) { - furi_string_free(seader->text_box_store); - } - - // Custom Widget - if(seader->widget) { - view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewWidget); - widget_free(seader->widget); - } + seader_release_inactive_lazy_views(seader); // Free reusable strings if(seader->temp_string1) furi_string_free(seader->temp_string1); @@ -1276,7 +1434,7 @@ bool seader_hf_plugin_acquire(Seader* seader) { } /* Re-acquire is allowed only when the live runtime is already coherent. */ - if(seader->plugin_hf && seader->hf_plugin_ctx) { + if(seader->plugin_hf && seader->hf_plugin_ctx && seader->nfc && seader->nfc_device) { if(seader->hf_session_state == SeaderHfSessionStateUnloaded) { seader->hf_session_state = SeaderHfSessionStateLoaded; } @@ -1286,20 +1444,21 @@ bool seader_hf_plugin_acquire(Seader* seader) { /* 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) { + if(seader->hf_plugin_manager || seader->plugin_hf || seader->hf_plugin_ctx || seader->nfc || + seader->nfc_device) { FURI_LOG_W( TAG, - "Normalize partial HF session manager=%p plugin=%p ctx=%p state=%d", + "Normalize partial HF session manager=%p plugin=%p ctx=%p nfc=%p device=%p state=%d", (void*)seader->hf_plugin_manager, (void*)seader->plugin_hf, seader->hf_plugin_ctx, + (void*)seader->nfc, + (void*)seader->nfc_device, 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); + if(!seader_hf_host_nfc_acquire(seader)) { return false; } @@ -1308,6 +1467,7 @@ bool seader_hf_plugin_acquire(Seader* seader) { 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"); + seader_hf_host_nfc_release(seader); return false; } } @@ -1318,6 +1478,7 @@ bool seader_hf_plugin_acquire(Seader* seader) { FURI_LOG_E(TAG, "Failed to load HF plugin"); plugin_manager_free(seader->hf_plugin_manager); seader_hf_session_force_unloaded(seader); + seader_hf_host_nfc_release(seader); return false; } @@ -1327,6 +1488,7 @@ bool seader_hf_plugin_acquire(Seader* seader) { FURI_LOG_E(TAG, "Failed to resolve HF plugin entry point"); plugin_manager_free(seader->hf_plugin_manager); seader_hf_session_force_unloaded(seader); + seader_hf_host_nfc_release(seader); return false; } @@ -1335,6 +1497,7 @@ bool seader_hf_plugin_acquire(Seader* seader) { FURI_LOG_E(TAG, "Failed to allocate HF plugin context"); plugin_manager_free(seader->hf_plugin_manager); seader_hf_session_force_unloaded(seader); + seader_hf_host_nfc_release(seader); return false; } @@ -1345,8 +1508,9 @@ bool seader_hf_plugin_acquire(Seader* seader) { } 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); + return seader && + (seader->hf_plugin_manager || seader->plugin_hf || seader->hf_plugin_ctx || + seader->poller || seader->picopass_poller || seader->nfc || seader->nfc_device); } /* App shutdown uses the same teardown primitive as normal navigation. The only difference @@ -1382,6 +1546,7 @@ void seader_hf_plugin_release(Seader* seader) { .host_picopass_release = seader_hf_release_host_picopass, .plugin_free = seader_hf_release_plugin_free, .plugin_manager_unload = seader_hf_release_plugin_manager, + .host_nfc_release = seader_hf_host_nfc_release, .worker_reset = seader_hf_release_worker_reset, }; seader_hf_release_sequence_run(&release_sequence); diff --git a/seader_bridge.h b/seader_bridge.h index ec0e669..83e9755 100644 --- a/seader_bridge.h +++ b/seader_bridge.h @@ -10,6 +10,7 @@ #include #include "t_1_logic.h" +#include "uart_tx_logic.h" // https://ww1.microchip.com/downloads/en/DeviceDoc/00001561C.pdf #define SEADER_UART_RX_BUF_SIZE (272) @@ -50,10 +51,9 @@ struct SeaderUartBridge { FuriThread* tx_thread; FuriStreamBuffer* rx_stream; + FuriMessageQueue* tx_queue; FuriHalSerialHandle* serial_handle; - FuriSemaphore* tx_sem; - SeaderUartState st; uint8_t tx_buf[SEADER_UART_RX_BUF_SIZE]; diff --git a/seader_credential.c b/seader_credential.c index eae19a5..55e1a17 100644 --- a/seader_credential.c +++ b/seader_credential.c @@ -517,7 +517,23 @@ bool seader_credential_save_rfid(SeaderCredential* cred, const char* name) { FURI_LOG_D(TAG, "LFRFID (%d): %016llx", cred->bit_length, target); size_t data_size = protocol_dict_get_data_size(dict, protocol); - uint8_t* data = malloc(data_size); + uint8_t stack_data[32]; + uint8_t* data = NULL; + bool must_free = false; + if(data_size <= sizeof(stack_data)) { + data = stack_data; + memset(data, 0, data_size); + } else { + data = malloc(data_size); + if(!data) { + FURI_LOG_E(TAG, "Failed to allocate LFRFID data buffer"); + protocol_dict_free(dict); + furi_string_free(file_path); + return false; + } + must_free = true; + } + if(data_size < 8) { memcpy(data, (void*)&target, data_size); } else { @@ -525,7 +541,9 @@ bool seader_credential_save_rfid(SeaderCredential* cred, const char* name) { memcpy(data + 4, (void*)&target, 8); } protocol_dict_set_data(dict, protocol, data, data_size); - free(data); + if(must_free) { + free(data); + } result = lfrfid_dict_file_save(dict, protocol, furi_string_get_cstr(file_path)); diff --git a/seader_hf_read_plan.h b/seader_hf_read_plan.h index 380c7fe..808fdf9 100644 --- a/seader_hf_read_plan.h +++ b/seader_hf_read_plan.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "seader_credential_type.h" @@ -21,3 +22,17 @@ SeaderHfReadPlan seader_hf_read_plan_build( SeaderCredentialType selected_type, const SeaderCredentialType* detected_types, size_t detected_type_count); +static inline bool seader_hf_read_plan_should_verify_start_type( + SeaderCredentialType type_to_read, + const SeaderCredentialType* detected_types, + size_t detected_type_count) { + if(type_to_read == SeaderCredentialTypeNone) { + return true; + } + + if(!detected_types || detected_type_count != 1U) { + return true; + } + + return detected_types[0] != type_to_read; +} diff --git a/seader_i.h b/seader_i.h index cc8ffe2..763e7ca 100644 --- a/seader_i.h +++ b/seader_i.h @@ -62,10 +62,12 @@ #include "seader_credential.h" #include "apdu_log.h" #include "board_power_lifecycle.h" +#include "board_identity.h" #include "sam_startup_ui.h" #include "sam_key_label.h" #include "uhf_snmp_probe.h" #include "uhf_status_label.h" +#include "hf_buffer_pool.h" #define WORKER_ALL_RX_EVENTS \ (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ @@ -142,6 +144,7 @@ struct Seader { bool board_power_enabled; bool board_power_owned; bool expansion_disabled; + SeaderBoardClass board_class; SeaderBoardStatus board_status; SeaderStartupStage startup_stage; uint8_t board_retry_remaining; @@ -197,6 +200,7 @@ struct Seader { Nfc* nfc; NfcPoller* poller; PicopassPoller* picopass_poller; + SeaderHfBufferPair picopass_host_buffers; NfcDevice* nfc_device; @@ -241,6 +245,10 @@ void seader_blink_stop(Seader* seader); void seader_nfc_loading_callback(void* context, bool show); +Submenu* seader_get_submenu(Seader* seader); +void seader_release_submenu(Seader* seader); +void seader_release_inactive_lazy_views(Seader* seader); + TextInput* seader_get_text_input(Seader* seader); TextBox* seader_get_text_box(Seader* seader); diff --git a/seader_worker.c b/seader_worker.c index aebfe44..df0ce17 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -1,6 +1,8 @@ #include "seader_worker_i.h" #include "seader_hf_read_plan.h" #include "hf_read_lifecycle.h" +#include "hf_bridge_policy.h" +#include "worker_loop_policy.h" #include "trace_log.h" #include @@ -11,7 +13,7 @@ #define APDU_HEADER_LEN 5 #define ASN1_PREFIX 6 #define SEADER_HF_CONVERSATION_TIMEOUT_MS 3000U -#define SEADER_WORKER_STACK_SIZE 2048U +#define SEADER_WORKER_STACK_SIZE 4096U // #define ASN1_DEBUG true #define RFAL_PICOPASS_TXRX_FLAGS \ @@ -63,12 +65,7 @@ typedef struct { 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); - } + memset(seader_worker->apdu_slots, 0, sizeof(seader_worker->apdu_slots)); } static bool seader_worker_claim_apdu_slot(SeaderWorker* seader_worker, uint8_t* slot_index) { @@ -91,9 +88,7 @@ static void seader_worker_release_apdu_slot(SeaderWorker* seader_worker, uint8_t 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; - } + seader_worker->apdu_slots[slot_index].len = 0U; } static bool @@ -103,6 +98,23 @@ static bool return furi_message_queue_get(seader_worker->messages, slot_index, timeout) == FuriStatusOk; } +static void seader_worker_fail_protocol(Seader* seader, const char* detail) { + if(!seader || !seader->worker) { + return; + } + + FURI_LOG_W(TAG, "%s", detail ? detail : "HF protocol failure"); + seader->hf_read_state = SeaderHfReadStateTerminalFail; + seader->hf_read_failure_reason = SeaderHfReadFailureReasonProtocolError; + strlcpy( + seader->read_error, + seader_hf_read_failure_reason_text(SeaderHfReadFailureReasonProtocolError), + sizeof(seader->read_error)); + seader_sam_force_idle_for_recovery(seader); + seader->worker->stage = SeaderPollerEventTypeFail; + view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit); +} + static void seader_worker_clear_active_card(Seader* seader, const char* reason) { if(!seader) { return; @@ -209,17 +221,16 @@ SeaderWorker* seader_worker_alloc() { // Worker thread attributes seader_worker->thread = furi_thread_alloc_ex( "SeaderWorker", SEADER_WORKER_STACK_SIZE, 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)); + seader_worker->messages = + furi_message_queue_alloc(SEADER_WORKER_APDU_SLOT_COUNT, sizeof(uint8_t)); - if(!seader_worker->thread || !seader_worker->messages || !seader_worker->apdu_slots) { + if(!seader_worker->thread || !seader_worker->messages) { 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; } @@ -239,7 +250,6 @@ 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); @@ -337,10 +347,15 @@ 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) { + const uint32_t space = furi_message_queue_get_space(seader_worker->messages); + const SeaderHfBridgeApduDecision apdu_decision = seader_hf_bridge_apdu_decision( + seader_worker->state == SeaderWorkerStateVirtualCredential, + seader_worker->stage == SeaderPollerEventTypeConversation, + len, + SEADER_POLLER_MAX_BUFFER_SIZE, + space > 0U); + + if(apdu_decision == SeaderHfBridgeApduDecisionDiscardStale) { SEADER_VERBOSE_I( TAG, "Discard stale SAM message outside active conversation, %d bytes, stage=%d, sam=%d", @@ -366,11 +381,11 @@ bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len) seader->samCommand); 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 && len <= SEADER_POLLER_MAX_BUFFER_SIZE) { + if(apdu_decision == SeaderHfBridgeApduDecisionQueue) { 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); + seader_worker_fail_protocol(seader, "No free APDU slot"); return true; } @@ -381,9 +396,14 @@ bool seader_process_success_response(Seader* seader, uint8_t* apdu, size_t len) FuriStatusOk) { FURI_LOG_W(TAG, "Failed to queue APDU slot=%u", slot_index); seader_worker_release_apdu_slot(seader_worker, slot_index); + seader_worker_fail_protocol(seader, "Failed to queue SAM APDU"); } } else if(len > SEADER_POLLER_MAX_BUFFER_SIZE) { - FURI_LOG_W(TAG, "Drop oversized SAM message len=%u", (unsigned)len); + FURI_LOG_W(TAG, "Oversized SAM message len=%u", (unsigned)len); + seader_worker_fail_protocol(seader, "Oversized SAM APDU"); + } else { + FURI_LOG_W(TAG, "No SAM APDU queue space for len=%u", (unsigned)len); + seader_worker_fail_protocol(seader, "No SAM APDU queue space"); } } return true; @@ -449,6 +469,7 @@ void seader_worker_virtual_credential(Seader* seader) { seader, 0, NULL, seader->credential->diversifier, sizeof(PicopassSerialNum), NULL, 0); bool running = true; + bool processing_ok = true; // Max times the loop will run with no message to process uint8_t dead_loops = 20; @@ -471,17 +492,29 @@ void seader_worker_virtual_credential(Seader* seader) { // no-op } else { SEADER_VERBOSE_I(TAG, "Response false"); - running = false; + processing_ok = false; } seader_worker_release_apdu_slot(seader_worker, slot_index); } else { - dead_loops--; - running = (dead_loops > 0); + if(dead_loops > 0U) { + dead_loops--; + } + running = seader_worker_virtual_credential_should_continue( + processing_ok, + seader_worker->state == SeaderWorkerStateVirtualCredential, + seader_worker->stage == SeaderPollerEventTypeComplete, + seader_worker->stage == SeaderPollerEventTypeFail, + dead_loops); SEADER_VERBOSE_D( TAG, "Dead loops: %d -> Running: %s", dead_loops, running ? "true" : "false"); if(running) furi_delay_ms(10); // Don't tight loop if empty } - running = (seader_worker->stage != SeaderPollerEventTypeComplete); + running = seader_worker_virtual_credential_should_continue( + processing_ok, + seader_worker->state == SeaderWorkerStateVirtualCredential, + seader_worker->stage == SeaderPollerEventTypeComplete, + seader_worker->stage == SeaderPollerEventTypeFail, + dead_loops); } if(dead_loops > 0 && seader_worker->stage == SeaderPollerEventTypeComplete) { diff --git a/seader_worker_i.h b/seader_worker_i.h index 83fb58f..45ec52e 100644 --- a/seader_worker_i.h +++ b/seader_worker_i.h @@ -17,13 +17,18 @@ #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) +#define SEADER_WORKER_APDU_SLOT_COUNT (4U) // 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) +struct SeaderAPDU { + size_t len; + uint8_t buf[SEADER_POLLER_MAX_BUFFER_SIZE]; +}; + struct SeaderWorker { FuriThread* thread; Storage* storage; @@ -34,15 +39,10 @@ struct SeaderWorker { SeaderPollerEventType stage; SeaderWorkerState state; - struct SeaderAPDU* apdu_slots; + SeaderAPDU apdu_slots[SEADER_WORKER_APDU_SLOT_COUNT]; bool apdu_slot_in_use[SEADER_WORKER_APDU_SLOT_COUNT]; }; -struct SeaderAPDU { - size_t len; - uint8_t buf[SEADER_POLLER_MAX_BUFFER_SIZE]; -}; - void seader_worker_change_state(SeaderWorker* seader_worker, SeaderWorkerState state); int32_t seader_worker_task(void* context); diff --git a/t_1.c b/t_1.c index be46805..2d25218 100644 --- a/t_1.c +++ b/t_1.c @@ -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; diff --git a/t_1_logic.c b/t_1_logic.c index 8b27664..50719db 100644 --- a/t_1_logic.c +++ b/t_1_logic.c @@ -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; diff --git a/t_1_logic.h b/t_1_logic.h index c19875c..b2f6d29 100644 --- a/t_1_logic.h +++ b/t_1_logic.h @@ -29,6 +29,7 @@ typedef enum { SeaderT1ActionSendMoreData, SeaderT1ActionRetransmit, SeaderT1ActionSendNak, + SeaderT1ActionResourceExhausted, SeaderT1ActionError, } SeaderT1Action; diff --git a/uart.c b/uart.c index ffa3340..73c848d 100644 --- a/uart.c +++ b/uart.c @@ -1,10 +1,12 @@ #include "seader_i.h" #include "trace_log.h" +#include "uart_rx_logic.h" #define TAG "SeaderUART" #define BAUDRATE_DEFAULT 115200 #define SEADER_UART_WORKER_STACK_SIZE (3U * 1024U) #define SEADER_UART_TX_WORKER_STACK_SIZE (1024U) +#define SEADER_UART_TX_QUEUE_DEPTH (2U) static void seader_uart_on_irq_rx_dma_cb( FuriHalSerialHandle* handle, @@ -62,19 +64,7 @@ size_t seader_uart_process_buffer(Seader* seader, uint8_t* cmd, size_t cmd_len) consumed = seader_ccid_process(seader, cmd, cmd_len); if(consumed > 0) { - memset(cmd, 0, consumed); - cmd_len -= consumed; - if(cmd_len > 0) { - memmove(cmd, cmd + consumed, cmd_len); - } - - /* - memset(display, 0, SEADER_UART_RX_BUF_SIZE); - for (uint8_t i = 0; i < cmd_len; i++) { - snprintf(display+(i*2), sizeof(display), "%02x", cmd[i]); - } - FURI_LOG_I(TAG, "cmd is now %d bytes: %s", cmd_len, display); - */ + cmd_len = seader_uart_rx_discard_consumed(cmd, cmd_len, consumed); } } while(consumed > 0 && cmd_len > 0); return cmd_len; @@ -88,8 +78,8 @@ int32_t seader_uart_worker(void* context) { memcpy(&seader_uart->cfg, &seader_uart->cfg_new, sizeof(SeaderUartConfig)); seader_uart->rx_stream = furi_stream_buffer_alloc(SEADER_UART_RX_BUF_SIZE, 1); - - seader_uart->tx_sem = furi_semaphore_alloc(1, 1); + seader_uart->tx_queue = + furi_message_queue_alloc(SEADER_UART_TX_QUEUE_DEPTH, sizeof(SeaderUartTxFrame)); seader_uart->tx_thread = furi_thread_alloc_ex( "SeaderUartTxWorker", SEADER_UART_TX_WORKER_STACK_SIZE, seader_uart_tx_thread, seader); @@ -97,8 +87,6 @@ int32_t seader_uart_worker(void* context) { seader_uart_serial_init(seader_uart, seader_uart->cfg.uart_ch); furi_hal_serial_set_br(seader_uart->serial_handle, seader_uart->cfg.baudrate); - furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); - furi_thread_start(seader_uart->tx_thread); uint8_t cmd[SEADER_UART_RX_BUF_SIZE]; @@ -131,15 +119,11 @@ int32_t seader_uart_worker(void* context) { size_t len = furi_stream_buffer_receive( 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", cmd[cmd_len + i]); + uint32_t delay_ms = seader_uart_rx_inter_chunk_delay_ms(len); + if(delay_ms > 0U) { + furi_delay_ms(delay_ms); } - FURI_LOG_I(TAG, "RECV %d bytes: %s", len, display); - */ + cmd_len += len; cmd_len = seader_uart_process_buffer(seader, cmd, cmd_len); } @@ -152,10 +136,28 @@ int32_t seader_uart_worker(void* context) { furi_thread_free(seader_uart->tx_thread); furi_stream_buffer_free(seader_uart->rx_stream); - furi_semaphore_free(seader_uart->tx_sem); + furi_message_queue_free(seader_uart->tx_queue); return 0; } +bool seader_uart_tx_enqueue(SeaderUartBridge* seader_uart, const uint8_t* data, size_t len) { + if(!seader_uart || !seader_uart->tx_queue || !seader_uart->tx_thread) { + return false; + } + + SeaderUartTxFrame frame = {0}; + if(!seader_uart_tx_frame_copy(&frame, data, len, SEADER_UART_RX_BUF_SIZE)) { + return false; + } + + if(furi_message_queue_put(seader_uart->tx_queue, &frame, FuriWaitForever) != FuriStatusOk) { + return false; + } + + furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx); + return true; +} + SeaderUartBridge* seader_uart_enable(SeaderUartConfig* cfg, Seader* seader) { SeaderUartBridge* seader_uart = calloc(1, sizeof(SeaderUartBridge)); @@ -190,9 +192,10 @@ int32_t seader_uart_tx_thread(void* context) { } if(events & WorkerEvtTxStop) break; if(events & WorkerEvtSamRx) { - if(seader_uart->tx_len > 0) { - furi_hal_serial_tx( - seader_uart->serial_handle, seader_uart->tx_buf, seader_uart->tx_len); + SeaderUartTxFrame frame = {0}; + while(seader_uart->tx_queue && + furi_message_queue_get(seader_uart->tx_queue, &frame, 0) == FuriStatusOk) { + furi_hal_serial_tx(seader_uart->serial_handle, frame.data, frame.len); } } } diff --git a/uart.h b/uart.h index 41a3c47..7c649eb 100644 --- a/uart.h +++ b/uart.h @@ -7,6 +7,7 @@ void seader_uart_on_irq_cb(uint8_t data, void* context); void seader_uart_serial_init(SeaderUartBridge* seader_uart, uint8_t uart_ch); void seader_uart_serial_deinit(SeaderUartBridge* seader_uart); int32_t seader_uart_worker(void* context); +bool seader_uart_tx_enqueue(SeaderUartBridge* seader_uart, const uint8_t* data, size_t len); SeaderUartBridge* seader_uart_enable(SeaderUartConfig* cfg, Seader* seader); void seader_uart_disable(SeaderUartBridge* seader_uart); diff --git a/uart_rx_logic.c b/uart_rx_logic.c new file mode 100644 index 0000000..b266fa5 --- /dev/null +++ b/uart_rx_logic.c @@ -0,0 +1,24 @@ +#include "uart_rx_logic.h" + +#include + +uint32_t seader_uart_rx_inter_chunk_delay_ms(size_t received_len) { + (void)received_len; + return 0U; +} + +size_t seader_uart_rx_discard_consumed(uint8_t* buffer, size_t len, size_t consumed) { + if(!buffer) { + return 0U; + } + if(consumed == 0U) { + return len; + } + if(consumed >= len) { + return 0U; + } + + size_t remaining = len - consumed; + memmove(buffer, buffer + consumed, remaining); + return remaining; +} diff --git a/uart_rx_logic.h b/uart_rx_logic.h new file mode 100644 index 0000000..4c4a27e --- /dev/null +++ b/uart_rx_logic.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +uint32_t seader_uart_rx_inter_chunk_delay_ms(size_t received_len); +size_t seader_uart_rx_discard_consumed(uint8_t* buffer, size_t len, size_t consumed); diff --git a/uart_tx_logic.c b/uart_tx_logic.c new file mode 100644 index 0000000..ca437d7 --- /dev/null +++ b/uart_tx_logic.c @@ -0,0 +1,17 @@ +#include "uart_tx_logic.h" + +#include + +bool seader_uart_tx_frame_copy( + SeaderUartTxFrame* out, + const uint8_t* data, + size_t len, + size_t max_len) { + if(!out || !data || len == 0U || len > max_len || len > sizeof(out->data)) { + return false; + } + + out->len = len; + memcpy(out->data, data, len); + return true; +} diff --git a/uart_tx_logic.h b/uart_tx_logic.h new file mode 100644 index 0000000..1721a98 --- /dev/null +++ b/uart_tx_logic.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#define SEADER_UART_TX_FRAME_MAX_SIZE (272U) + +typedef struct { + size_t len; + uint8_t data[SEADER_UART_TX_FRAME_MAX_SIZE]; +} SeaderUartTxFrame; + +bool seader_uart_tx_frame_copy( + SeaderUartTxFrame* out, + const uint8_t* data, + size_t len, + size_t max_len); diff --git a/uhf_snmp_probe.c b/uhf_snmp_probe.c index f2e23a0..1b75f83 100644 --- a/uhf_snmp_probe.c +++ b/uhf_snmp_probe.c @@ -4,6 +4,10 @@ #include static const uint8_t oid_elite_ice[] = {0x03, 0x01, 0x07, 0x01, 0x38}; +static const uint8_t oid_standard_encryption_key[] = + {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x0C, 0x03, 0x01}; +static const uint8_t oid_standard_signature_key[] = + {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x0C, 0x02, 0x01}; static const uint8_t oid_uhf_tags_config[] = {0x03, 0x01, 0x07, 0x03, 0x0B, 0x00}; static const uint8_t oid_monza4qt_access_key[] = { 0x2B, @@ -42,6 +46,47 @@ static const uint8_t oid_higgs3_access_key[] = { 0x01, 0x01}; +static void seader_uhf_snmp_probe_advance_after_standard_signature(SeaderUhfSnmpProbe* probe) { + probe->standard_pacs_keys_probed = true; + if(probe->supports_uhf) { + probe->stage = SeaderUhfSnmpProbeStageReadTagConfig; + } else { + probe->stage = SeaderUhfSnmpProbeStageDone; + } +} + +static bool seader_uhf_snmp_probe_mark_standard_key_missing(SeaderUhfSnmpProbe* probe) { + if(probe->stage == SeaderUhfSnmpProbeStageReadStandardEncryptionKey) { + probe->standard_encryption_key_present = false; + probe->stage = SeaderUhfSnmpProbeStageReadStandardSignatureKey; + return true; + } + + if(probe->stage == SeaderUhfSnmpProbeStageReadStandardSignatureKey) { + probe->standard_signature_key_present = false; + seader_uhf_snmp_probe_advance_after_standard_signature(probe); + return true; + } + + return false; +} + +static bool seader_uhf_snmp_probe_key_missing_error( + uint32_t error_code, + const uint8_t* data, + size_t data_len) { + return error_code == 0x11U && data_len >= 2U && + ((data[0] == 0x2EU && data[1] == 0x00U) || (data[0] == 0x37U && data[1] == 0x00U) || + (data[0] == 0x39U && data[1] == 0x00U)); +} + +static bool seader_uhf_snmp_probe_key_present_error( + uint32_t error_code, + const uint8_t* data, + size_t data_len) { + return error_code == 0x06U && data_len >= 2U && data[0] == 0x69U && data[1] == 0x82U; +} + static void seader_uhf_snmp_probe_advance_after_tag_config(SeaderUhfSnmpProbe* probe) { if(probe->has_monza4qt) { probe->stage = SeaderUhfSnmpProbeStageReadMonza4QtKey; @@ -64,6 +109,7 @@ void seader_uhf_snmp_probe_init(SeaderUhfSnmpProbe* probe) { if(!probe) return; memset(probe, 0, sizeof(*probe)); probe->stage = SeaderUhfSnmpProbeStageDiscovery; + probe->supports_uhf = true; } bool seader_uhf_snmp_probe_build_next_request( @@ -94,6 +140,36 @@ bool seader_uhf_snmp_probe_build_next_request( message, message_capacity, message_len); + case SeaderUhfSnmpProbeStageReadStandardEncryptionKey: + return seader_snmp_build_get_data_request( + probe->usm_engine_id_storage, + probe->usm_engine_id_len, + probe->usm_username_storage, + probe->usm_username_len, + probe->usm_engine_boots, + probe->usm_engine_time, + oid_standard_encryption_key, + sizeof(oid_standard_encryption_key), + scratch, + scratch_capacity, + message, + message_capacity, + message_len); + case SeaderUhfSnmpProbeStageReadStandardSignatureKey: + return seader_snmp_build_get_data_request( + probe->usm_engine_id_storage, + probe->usm_engine_id_len, + probe->usm_username_storage, + probe->usm_username_len, + probe->usm_engine_boots, + probe->usm_engine_time, + oid_standard_signature_key, + sizeof(oid_standard_signature_key), + scratch, + scratch_capacity, + message, + message_capacity, + message_len); case SeaderUhfSnmpProbeStageReadTagConfig: return seader_snmp_build_get_data_request( probe->usm_engine_id_storage, @@ -156,6 +232,9 @@ bool seader_uhf_snmp_probe_consume_response( return false; } if(view.error_status != 0U) { + if(seader_uhf_snmp_probe_mark_standard_key_missing(probe)) { + return true; + } probe->stage = SeaderUhfSnmpProbeStageFailed; return false; } @@ -193,7 +272,25 @@ bool seader_uhf_snmp_probe_consume_response( return false; } memcpy(probe->ice_value_storage, value.ptr, probe->ice_value_len); - probe->stage = SeaderUhfSnmpProbeStageReadTagConfig; + probe->stage = SeaderUhfSnmpProbeStageReadStandardEncryptionKey; + return true; + case SeaderUhfSnmpProbeStageReadStandardEncryptionKey: + probe->standard_encryption_key_present = + seader_snmp_find_varbind_octet_value( + view.varbind_sequence, + (SeaderBytesView){oid_standard_encryption_key, sizeof(oid_standard_encryption_key)}, + &value) && + value.len > 0U; + probe->stage = SeaderUhfSnmpProbeStageReadStandardSignatureKey; + return true; + case SeaderUhfSnmpProbeStageReadStandardSignatureKey: + probe->standard_signature_key_present = + seader_snmp_find_varbind_octet_value( + view.varbind_sequence, + (SeaderBytesView){oid_standard_signature_key, sizeof(oid_standard_signature_key)}, + &value) && + value.len > 0U; + seader_uhf_snmp_probe_advance_after_standard_signature(probe); return true; case SeaderUhfSnmpProbeStageReadTagConfig: if(!seader_snmp_find_varbind_octet_value( @@ -247,8 +344,21 @@ bool seader_uhf_snmp_probe_consume_error( return false; } - if(error_code == 0x06U && data_len >= 2U && data[0] == 0x69U && data[1] == 0x82U) { - if(probe->stage == SeaderUhfSnmpProbeStageReadMonza4QtKey) { + if(probe->stage == SeaderUhfSnmpProbeStageReadTagConfig) { + probe->stage = SeaderUhfSnmpProbeStageFailed; + return true; + } + + if(seader_uhf_snmp_probe_key_present_error(error_code, data, data_len)) { + if(probe->stage == SeaderUhfSnmpProbeStageReadStandardEncryptionKey) { + probe->standard_encryption_key_present = true; + probe->stage = SeaderUhfSnmpProbeStageReadStandardSignatureKey; + return true; + } else if(probe->stage == SeaderUhfSnmpProbeStageReadStandardSignatureKey) { + probe->standard_signature_key_present = true; + seader_uhf_snmp_probe_advance_after_standard_signature(probe); + return true; + } else if(probe->stage == SeaderUhfSnmpProbeStageReadMonza4QtKey) { probe->monza4qt_key_present = true; seader_uhf_snmp_probe_advance_after_monza(probe); return true; @@ -259,9 +369,10 @@ bool seader_uhf_snmp_probe_consume_error( } } - if(error_code == 0x11U && data_len >= 2U && - ((data[0] == 0x2EU && data[1] == 0x00U) || (data[0] == 0x39U && data[1] == 0x00U))) { - if(probe->stage == SeaderUhfSnmpProbeStageReadMonza4QtKey) { + if(seader_uhf_snmp_probe_key_missing_error(error_code, data, data_len)) { + if(seader_uhf_snmp_probe_mark_standard_key_missing(probe)) { + return true; + } else if(probe->stage == SeaderUhfSnmpProbeStageReadMonza4QtKey) { probe->monza4qt_key_present = false; seader_uhf_snmp_probe_advance_after_monza(probe); return true; @@ -275,3 +386,8 @@ bool seader_uhf_snmp_probe_consume_error( probe->stage = SeaderUhfSnmpProbeStageFailed; return false; } + +bool seader_uhf_snmp_probe_standard_pacs_keys_present(const SeaderUhfSnmpProbe* probe) { + return probe && probe->standard_encryption_key_present && + probe->standard_signature_key_present; +} diff --git a/uhf_snmp_probe.h b/uhf_snmp_probe.h index f7cc56b..c218075 100644 --- a/uhf_snmp_probe.h +++ b/uhf_snmp_probe.h @@ -11,6 +11,8 @@ typedef enum { SeaderUhfSnmpProbeStageIdle = 0, SeaderUhfSnmpProbeStageDiscovery, SeaderUhfSnmpProbeStageReadIce, + SeaderUhfSnmpProbeStageReadStandardEncryptionKey, + SeaderUhfSnmpProbeStageReadStandardSignatureKey, SeaderUhfSnmpProbeStageReadTagConfig, SeaderUhfSnmpProbeStageReadMonza4QtKey, SeaderUhfSnmpProbeStageReadHiggs3Key, @@ -22,6 +24,9 @@ typedef struct { SeaderUhfSnmpProbeStage stage; uint32_t usm_engine_boots; uint32_t usm_engine_time; + bool standard_pacs_keys_probed; + bool standard_encryption_key_present; + bool standard_signature_key_present; bool has_monza4qt; bool has_higgs3; bool monza4qt_key_present; @@ -32,6 +37,7 @@ typedef struct { size_t usm_username_len; uint8_t ice_value_storage[SEADER_UHF_SNMP_MAX_VALUE_LEN]; size_t ice_value_len; + bool supports_uhf; } SeaderUhfSnmpProbe; void seader_uhf_snmp_probe_init(SeaderUhfSnmpProbe* probe); @@ -54,3 +60,5 @@ bool seader_uhf_snmp_probe_consume_error( uint32_t error_code, const uint8_t* data, size_t data_len); + +bool seader_uhf_snmp_probe_standard_pacs_keys_present(const SeaderUhfSnmpProbe* probe); diff --git a/uhf_status_label.c b/uhf_status_label.c index 0cf6430..ed7f741 100644 --- a/uhf_status_label.c +++ b/uhf_status_label.c @@ -60,6 +60,10 @@ void seader_uhf_status_label_format( out[0] = '\0'; + if(probe_status == SeaderUhfProbeStatusHidden) { + return; + } + if(probe_status == SeaderUhfProbeStatusUnknown) { snprintf(out, out_size, "UHF: probing..."); return; diff --git a/uhf_status_label.h b/uhf_status_label.h index 4ba0ef8..52ab60f 100644 --- a/uhf_status_label.h +++ b/uhf_status_label.h @@ -9,6 +9,7 @@ typedef enum { SeaderUhfProbeStatusUnknown = 0, SeaderUhfProbeStatusSuccess, SeaderUhfProbeStatusFailed, + SeaderUhfProbeStatusHidden, } SeaderUhfProbeStatus; void seader_uhf_status_label_format( diff --git a/ui_memory_policy.c b/ui_memory_policy.c new file mode 100644 index 0000000..7c973bc --- /dev/null +++ b/ui_memory_policy.c @@ -0,0 +1,9 @@ +#include "ui_memory_policy.h" + +bool seader_ui_memory_should_release_submenu(SeaderUiMemoryPhase phase) { + return phase == SeaderUiMemoryPhaseHfReadActive; +} + +bool seader_ui_memory_should_release_inactive_lazy_views(SeaderUiMemoryPhase phase) { + return phase == SeaderUiMemoryPhaseHfReadActive; +} diff --git a/ui_memory_policy.h b/ui_memory_policy.h new file mode 100644 index 0000000..c3d7d35 --- /dev/null +++ b/ui_memory_policy.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +typedef enum { + SeaderUiMemoryPhaseNormal = 0, + SeaderUiMemoryPhaseHfReadActive, +} SeaderUiMemoryPhase; + +bool seader_ui_memory_should_release_submenu(SeaderUiMemoryPhase phase); +bool seader_ui_memory_should_release_inactive_lazy_views(SeaderUiMemoryPhase phase); diff --git a/worker_loop_policy.c b/worker_loop_policy.c new file mode 100644 index 0000000..4ab1b93 --- /dev/null +++ b/worker_loop_policy.c @@ -0,0 +1,11 @@ +#include "worker_loop_policy.h" + +bool seader_worker_virtual_credential_should_continue( + bool processing_ok, + bool worker_active, + bool stage_complete, + bool stage_fail, + uint8_t empty_loops_remaining) { + return processing_ok && worker_active && !stage_complete && !stage_fail && + empty_loops_remaining > 0U; +} diff --git a/worker_loop_policy.h b/worker_loop_policy.h new file mode 100644 index 0000000..7d0424b --- /dev/null +++ b/worker_loop_policy.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +bool seader_worker_virtual_credential_should_continue( + bool processing_ok, + bool worker_active, + bool stage_complete, + bool stage_fail, + uint8_t empty_loops_remaining);