diff --git a/application.fam b/application.fam index 87ef211..2f88e48 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"], fal_embedded=True, ) diff --git a/hf_interface_fal/hf.c b/hf_interface_fal/hf.c index cad0912..a7f8c0e 100644 --- a/hf_interface_fal/hf.c +++ b/hf_interface_fal/hf.c @@ -65,6 +65,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 +178,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 +300,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; @@ -217,6 +323,7 @@ static void plugin_hf_iso15693_transmit(PluginHfContext* ctx, uint8_t* buffer, s 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; + SeaderHfBridgeRfStatus rx_status = SeaderHfBridgeRfStatusTimeout; if(!tx_buffer || !rx_buffer) { FURI_LOG_E(TAG, "Failed to allocate picopass buffers"); if(tx_buffer) bit_buffer_free(tx_buffer); @@ -241,8 +348,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); @@ -263,7 +371,7 @@ 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); @@ -301,7 +409,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; } @@ -333,10 +441,8 @@ 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; @@ -357,14 +463,16 @@ static void plugin_hf_mfc_transmit( return; } + 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 +503,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 +512,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 +553,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( @@ -540,12 +650,8 @@ static NfcCommand plugin_hf_poller_callback_iso14443_4a(NfcGenericEvent event, v } } - if(!ctx->api->begin_card_session( - ctx->host_ctx, iso14443_3a_get_sak(iso3a), uid, uid_len, ats, ats_len)) { - 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_len); } else if(stage == PluginHfStageConversation) { SEADER_VERBOSE_D(TAG, "14A enter conversation"); ret = plugin_hf_run_conversation(ctx); @@ -601,17 +707,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 +746,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); @@ -835,7 +927,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..8004bd6 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,86 @@ 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 SeaderHfReadFailureReasonNone: default: return "Read failed"; } } + +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..b6f87aa 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,7 @@ typedef enum { SeaderHfReadFailureReasonBoardMissing, SeaderHfReadFailureReasonProtocolError, SeaderHfReadFailureReasonInternalState, + SeaderHfReadFailureReasonSamKeysMissing, } SeaderHfReadFailureReason; typedef enum { @@ -37,3 +50,14 @@ bool seader_hf_read_should_timeout( uint32_t elapsed_ms, uint32_t timeout_ms); const char* seader_hf_read_failure_reason_text(SeaderHfReadFailureReason reason); +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/lib/host_tests/test_ccid_logic.c b/lib/host_tests/test_ccid_logic.c index 52fde88..a042a8c 100644 --- a/lib/host_tests/test_ccid_logic.c +++ b/lib/host_tests/test_ccid_logic.c @@ -152,6 +152,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( diff --git a/lib/host_tests/test_hf_read_lifecycle.c b/lib/host_tests/test_hf_read_lifecycle.c index 5434815..5b74efc 100644 --- a/lib/host_tests/test_hf_read_lifecycle.c +++ b/lib/host_tests/test_hf_read_lifecycle.c @@ -62,6 +62,56 @@ 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"); + 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; } @@ -85,6 +135,8 @@ static MunitTest test_hf_read_lifecycle_cases[] = { {(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*)"/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_sam_key_label.c b/lib/host_tests/test_sam_key_label.c index 88d42f5..a6380f8 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,31 @@ 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_sanitizes_non_printable_bytes(const MunitParameter params[], void* fixture) { (void)params; (void)fixture; @@ -92,7 +118,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 +127,7 @@ 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-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..df97f55 100644 --- a/lib/host_tests/test_t1_protocol.c +++ b/lib/host_tests/test_t1_protocol.c @@ -308,6 +308,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, @@ -369,6 +411,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_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/sam_api.c b/sam_api.c index 7a181f1..89e309b 100644 --- a/sam_api.c +++ b/sam_api.c @@ -1,4 +1,5 @@ #include "sam_api.h" +#include "hf_read_lifecycle.h" #include "seader_i.h" #include "protocol/rfal_picopass.h" #include "sam_key_label.h" @@ -23,6 +24,16 @@ 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, @@ -35,6 +46,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 +82,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 +94,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, @@ -126,11 +146,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 +202,16 @@ 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 +219,7 @@ 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); } @@ -315,6 +342,10 @@ void* calloc(size_t count, size_t size) { // Forward declarations static void seader_abort_active_read(Seader* seader); +static void seader_abort_active_read_with_reason( + Seader* seader, + SeaderHfReadFailureReason reason, + const char* detail); static void seader_sam_set_state( Seader* seader, @@ -487,7 +518,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 +533,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 +550,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 +558,6 @@ bool seader_send_apdu( seader_ccid_XfrBlock(seader_uart, apdu, length); } - if(must_free) { - free(apdu); - } - return true; } @@ -566,16 +588,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 +603,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 +644,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 +715,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 +828,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 +1023,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) { +static 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 +1037,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 +1058,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 +1084,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 +1116,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,9 +1165,7 @@ 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); break; @@ -1120,10 +1179,16 @@ bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) { SeaderSamKeyProbeStatusVerifiedValue; } - 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,9 +1197,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); } @@ -1188,14 +1251,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 +1278,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 +1550,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 +1563,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 +1587,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 +1599,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 +1637,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 +1649,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"); @@ -1593,10 +1673,13 @@ void seader_parse_nfc_command_transmit(Seader* seader, NFCSend_t* nfcSend) { 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; + PluginHfAction action = { .data = nfcSend->data.buf, .len = nfcSend->data.size, - .timeout = nfcSend->timeOut, + .timeout = timeout_us, }; if(nfcSend->format) { const size_t raw_format_len = (size_t)nfcSend->format->size; @@ -1725,20 +1808,24 @@ bool seader_worker_state_machine( SeaderSamKeyProbeStatusVerifiedStandard : SeaderSamKeyProbeStatusVerifiedValue; } - 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); } @@ -1751,9 +1838,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); } @@ -1777,6 +1862,7 @@ 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; @@ -1808,7 +1894,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..7e773ee 100644 --- a/sam_api.h +++ b/sam_api.h @@ -11,6 +11,8 @@ #include #include +#include "hf_bridge_policy.h" + typedef struct Seader Seader; typedef struct SeaderPollerContainer SeaderPollerContainer; @@ -30,6 +32,11 @@ 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); diff --git a/sam_key_label.c b/sam_key_label.c index fff637a..79c2bc6 100644 --- a/sam_key_label.c +++ b/sam_key_label.c @@ -23,6 +23,8 @@ void seader_sam_key_label_format( 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 +38,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..0a6064c 100644 --- a/sam_key_label.h +++ b/sam_key_label.h @@ -18,5 +18,7 @@ void seader_sam_key_label_format( 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_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_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_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..4c9e03a 100644 --- a/seader.c +++ b/seader.c @@ -148,11 +148,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 +352,32 @@ 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 +421,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 +483,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 +499,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 +596,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,9 +802,13 @@ 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; } @@ -772,6 +825,23 @@ static bool seader_hf_plugin_picopass_transmit( 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; } @@ -800,6 +870,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, @@ -930,6 +1001,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 +1023,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( 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_i.h b/seader_i.h index cc8ffe2..2fad87d 100644 --- a/seader_i.h +++ b/seader_i.h @@ -62,6 +62,7 @@ #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" @@ -142,6 +143,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; diff --git a/seader_worker.c b/seader_worker.c index aebfe44..ffcd12f 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -1,6 +1,7 @@ #include "seader_worker_i.h" #include "seader_hf_read_plan.h" #include "hf_read_lifecycle.h" +#include "hf_bridge_policy.h" #include "trace_log.h" #include @@ -11,7 +12,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 +64,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 +87,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 +97,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 +220,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 +249,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 +346,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 +380,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 +395,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; diff --git a/seader_worker_i.h b/seader_worker_i.h index 83fb58f..4414751 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,14 +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); diff --git a/uhf_snmp_probe.c b/uhf_snmp_probe.c index f2e23a0..2540eff 100644 --- a/uhf_snmp_probe.c +++ b/uhf_snmp_probe.c @@ -4,6 +4,38 @@ #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 +74,42 @@ 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 +132,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 +163,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 +255,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 +295,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 +367,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 +392,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 +409,7 @@ 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(