Fix HF bridge synchronization and SAM key diagnostics

This commit is contained in:
CinderSocket
2026-06-17 15:24:51 -07:00
parent 148ecd7a72
commit 85f3bb4940
27 changed files with 1230 additions and 159 deletions
+4 -2
View File
@@ -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,
)
+128 -36
View File
@@ -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;
+10 -3
View File
@@ -4,13 +4,14 @@
#include <stddef.h>
#include <stdint.h>
#include "../hf_bridge_policy.h"
#include "../protocol/picopass_poller.h"
#include "../seader_credential_type.h"
#include <lib/nfc/nfc.h>
#include <nfc/nfc_device.h>
#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);
+80
View File
@@ -1,5 +1,7 @@
#include "hf_read_lifecycle.h"
#include <stdio.h>
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.");
}
+24
View File
@@ -1,7 +1,19 @@
#pragma once
#include "sam_key_label.h"
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
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);
+1
View File
@@ -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(
+52
View File
@@ -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},
};
+33 -6
View File
@@ -1,3 +1,5 @@
#include <string.h>
#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},
+345 -1
View File
@@ -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},
+48
View File
@@ -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},
};
+12
View File
@@ -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},
+150 -65
View File
@@ -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);
+7
View File
@@ -11,6 +11,8 @@
#include <stddef.h>
#include <stdint.h>
#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);
+7
View File
@@ -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;
+2
View File
@@ -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);
+11 -3
View File
@@ -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(
+9 -5
View File
@@ -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);
+4
View File
@@ -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);
+76 -2
View File
@@ -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(
+20 -2
View File
@@ -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));
+2
View File
@@ -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;
+41 -22
View File
@@ -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 <flipper_format/flipper_format.h>
@@ -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;
+7 -6
View File
@@ -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);
+144 -6
View File
@@ -4,6 +4,38 @@
#include <string.h>
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;
}
+8
View File
@@ -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);
+4
View File
@@ -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;
+1
View File
@@ -9,6 +9,7 @@ typedef enum {
SeaderUhfProbeStatusUnknown = 0,
SeaderUhfProbeStatusSuccess,
SeaderUhfProbeStatusFailed,
SeaderUhfProbeStatusHidden,
} SeaderUhfProbeStatus;
void seader_uhf_status_label_format(