mirror of
https://github.com/bettse/seader.git
synced 2026-08-28 19:28:21 +00:00
Fix HF bridge synchronization and SAM key diagnostics
This commit is contained in:
+4
-2
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,6 +9,7 @@ typedef enum {
|
||||
SeaderUhfProbeStatusUnknown = 0,
|
||||
SeaderUhfProbeStatusSuccess,
|
||||
SeaderUhfProbeStatusFailed,
|
||||
SeaderUhfProbeStatusHidden,
|
||||
} SeaderUhfProbeStatus;
|
||||
|
||||
void seader_uhf_status_label_format(
|
||||
|
||||
Reference in New Issue
Block a user