From a4940efeb8d555735c8ddbfa67c041e06473a26b Mon Sep 17 00:00:00 2001 From: CinderSocket Date: Mon, 9 Mar 2026 10:25:40 -0700 Subject: [PATCH] Integrate SNMP probe into SAM API --- Makefile | 4 + lib/host_tests/test_main.c | 4 + lib/host_tests/test_sam_key_label.c | 80 ++++++++++++ lib/host_tests/test_uhf_status_label.c | 34 +++++ sam_api.c | 170 ++++++++++++++++++++++++- sam_api.h | 5 + sam_key_label.c | 56 ++++++++ sam_key_label.h | 14 ++ seader.c | 6 + seader_i.h | 9 ++ uhf_status_label.c | 57 +++++++++ uhf_status_label.h | 14 ++ 12 files changed, 446 insertions(+), 7 deletions(-) create mode 100644 lib/host_tests/test_sam_key_label.c create mode 100644 lib/host_tests/test_uhf_status_label.c create mode 100644 sam_key_label.c create mode 100644 sam_key_label.h create mode 100644 uhf_status_label.c create mode 100644 uhf_status_label.h diff --git a/Makefile b/Makefile index 3482b23..8de0b2a 100644 --- a/Makefile +++ b/Makefile @@ -15,19 +15,23 @@ test-host: lib/host_tests/vendor/munit/munit.c \ lib/host_tests/test_main.c \ lib/host_tests/test_lrc.c \ + lib/host_tests/test_sam_key_label.c \ lib/host_tests/test_ccid_logic.c \ lib/host_tests/test_t1_existing.c \ lib/host_tests/test_t1_protocol.c \ lib/host_tests/test_snmp.c \ + lib/host_tests/test_uhf_status_label.c \ lib/host_tests/t1_test_stubs.c \ lib/host_tests/bit_buffer_mock.c \ lrc.c \ ccid_logic.c \ t_1_logic.c \ t_1.c \ + sam_key_label.c \ snmp_ber_view.c \ snmp_codec.c \ snmp_response_view.c \ + uhf_status_label.c \ uhf_tag_config_view.c \ uhf_snmp_probe.c \ -o build/host_tests/seader_tests diff --git a/lib/host_tests/test_main.c b/lib/host_tests/test_main.c index ae93b93..e5fbb55 100644 --- a/lib/host_tests/test_main.c +++ b/lib/host_tests/test_main.c @@ -2,17 +2,21 @@ extern MunitSuite test_lrc_suite; extern MunitSuite test_ccid_logic_suite; +extern MunitSuite test_sam_key_label_suite; extern MunitSuite test_t1_existing_suite; extern MunitSuite test_t1_protocol_suite; extern MunitSuite test_snmp_suite; +extern MunitSuite test_uhf_status_label_suite; int main(int argc, char* argv[]) { MunitSuite child_suites[] = { {"/lrc", test_lrc_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/sam-key-label", test_sam_key_label_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/t1", test_t1_existing_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/t1", test_t1_protocol_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/ccid", test_ccid_logic_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {"/snmp", test_snmp_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, + {"/uhf-status-label", test_uhf_status_label_suite.tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}, {NULL, NULL, NULL, 0, 0}, }; MunitSuite main_suite = { diff --git a/lib/host_tests/test_sam_key_label.c b/lib/host_tests/test_sam_key_label.c new file mode 100644 index 0000000..0be5a55 --- /dev/null +++ b/lib/host_tests/test_sam_key_label.c @@ -0,0 +1,80 @@ +#include "munit.h" +#include "sam_key_label.h" + +static MunitResult test_formats_no_sam(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_SAM_KEY_LABEL_MAX_LEN] = {0}; + + seader_sam_key_label_format(false, NULL, 0U, label, sizeof(label)); + munit_assert_string_equal(label, "NO SAM"); + return MUNIT_OK; +} + +static MunitResult test_formats_standard_key_for_missing_value( + const MunitParameter params[], + void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_SAM_KEY_LABEL_MAX_LEN] = {0}; + const uint8_t zeros[] = {0x00, 0x00, 0x00}; + + seader_sam_key_label_format(true, NULL, 0U, label, sizeof(label)); + munit_assert_string_equal(label, "SAM: Standard Key"); + + seader_sam_key_label_format(true, zeros, sizeof(zeros), label, sizeof(label)); + munit_assert_string_equal(label, "SAM: Standard Key"); + return MUNIT_OK; +} + +static MunitResult test_formats_standard_key_for_eight_zero_bytes( + 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, zero64, sizeof(zero64), label, sizeof(label)); + munit_assert_string_equal(label, "SAM: Standard Key"); + return MUNIT_OK; +} + +static MunitResult test_formats_ascii_ice_value(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_SAM_KEY_LABEL_MAX_LEN] = {0}; + const uint8_t ice[] = {'I', 'C', 'E', '1', '8', '0', '3'}; + + seader_sam_key_label_format(true, ice, sizeof(ice), label, sizeof(label)); + munit_assert_string_equal(label, "SAM: ICE1803"); + return MUNIT_OK; +} + +static MunitResult test_sanitizes_non_printable_bytes(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_SAM_KEY_LABEL_MAX_LEN] = {0}; + const uint8_t mixed[] = {'A', 0x00, 0x1F, 'Z'}; + + seader_sam_key_label_format(true, mixed, sizeof(mixed), label, sizeof(label)); + munit_assert_string_equal(label, "SAM: A??Z"); + return MUNIT_OK; +} + +static MunitTest test_sam_key_label_cases[] = { + {(char*)"/no-sam", test_formats_no_sam, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/standard-key", test_formats_standard_key_for_missing_value, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/standard-key-zero64", test_formats_standard_key_for_eight_zero_bytes, 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}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_sam_key_label_suite = { + "", + test_sam_key_label_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/lib/host_tests/test_uhf_status_label.c b/lib/host_tests/test_uhf_status_label.c new file mode 100644 index 0000000..0bf5d64 --- /dev/null +++ b/lib/host_tests/test_uhf_status_label.c @@ -0,0 +1,34 @@ +#include "munit.h" +#include "uhf_status_label.h" + +static MunitResult test_formats_none(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_UHF_STATUS_LABEL_MAX_LEN] = {0}; + seader_uhf_status_label_format(false, false, false, false, label, sizeof(label)); + munit_assert_string_equal(label, "UHF: none"); + return MUNIT_OK; +} + +static MunitResult test_formats_supported_key_states(const MunitParameter params[], void* fixture) { + (void)params; + (void)fixture; + char label[SEADER_UHF_STATUS_LABEL_MAX_LEN] = {0}; + seader_uhf_status_label_format(true, false, true, true, label, sizeof(label)); + munit_assert_string_equal(label, "UHF: Monza 4QT [no key]/Higgs 3"); + return MUNIT_OK; +} + +static MunitTest test_uhf_status_label_cases[] = { + {(char*)"/none", test_formats_none, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {(char*)"/supported-key-states", test_formats_supported_key_states, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, 0, NULL}, +}; + +MunitSuite test_uhf_status_label_suite = { + "", + test_uhf_status_label_cases, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; diff --git a/sam_api.c b/sam_api.c index dbe0c5e..a469f52 100644 --- a/sam_api.c +++ b/sam_api.c @@ -1,5 +1,8 @@ #include "sam_api.h" +#include "sam_key_label.h" #include "trace_log.h" +#include "uhf_snmp_probe.h" +#include "uhf_status_label.h" #include #include #include @@ -16,6 +19,99 @@ 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 seader_sam_set_state( + Seader* seader, + SeaderSamState state, + SeaderSamIntent intent, + SamCommand_PR command); + +static void seader_publish_sam_status(Seader* seader) { + if(seader && seader->view_dispatcher) { + view_dispatcher_send_custom_event( + seader->view_dispatcher, SeaderCustomEventSamStatusUpdated); + } +} + +static void seader_update_sam_key_label(Seader* seader, const uint8_t* value, size_t value_len) { + if(!seader) { + return; + } + + seader_sam_key_label_format( + seader->sam_present, + value, + value_len, + seader->sam_key_label, + sizeof(seader->sam_key_label)); + seader_publish_sam_status(seader); +} + +static void seader_update_uhf_status_label(Seader* seader) { + if(!seader) { + return; + } + + seader_uhf_status_label_format( + seader->snmp_probe.has_monza4qt, + seader->snmp_probe.monza4qt_key_present, + seader->snmp_probe.has_higgs3, + seader->snmp_probe.higgs3_key_present, + seader->uhf_status_label, + sizeof(seader->uhf_status_label)); + seader_publish_sam_status(seader); +} + +static bool seader_snmp_probe_send_next_request(Seader* seader) { + SeaderWorker* seader_worker = seader ? seader->worker : NULL; + SeaderUartBridge* seader_uart = seader_worker ? seader_worker->uart : NULL; + uint8_t* scratch = seader_uart ? (seader_uart->tx_buf + MAX_FRAME_HEADERS) : NULL; + uint8_t* message = seader_uart ? seader_uart->rx_buf : NULL; + size_t message_len = 0U; + + if(!seader || !scratch || !message) { + return false; + } + + if(!seader_uhf_snmp_probe_build_next_request( + &seader->snmp_probe, + scratch, + SEADER_UART_RX_BUF_SIZE - MAX_FRAME_HEADERS, + message, + SEADER_UART_RX_BUF_SIZE, + &message_len)) { + return false; + } + + return seader_worker_send_process_snmp_message(seader, message, message_len); +} + +static void seader_snmp_probe_finish(Seader* seader) { + if(!seader) { + return; + } + + seader_sam_set_state(seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING); +} + +static void seader_start_snmp_probe(Seader* seader) { + if(!seader || !seader->sam_present) { + return; + } + + seader_uhf_snmp_probe_init(&seader->snmp_probe); + seader_uhf_status_label_format( + false, false, false, false, seader->uhf_status_label, sizeof(seader->uhf_status_label)); + seader_sam_set_state( + seader, + SeaderSamStateCapabilityPending, + SeaderSamIntentMaintenance, + SamCommand_PR_processSNMPMessage); + + if(!seader_snmp_probe_send_next_request(seader)) { + seader_snmp_probe_finish(seader); + } +} + #ifdef ASN1_DEBUG char asn1_log[SEADER_UART_RX_BUF_SIZE] = {0}; #endif @@ -539,6 +635,8 @@ 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->sam_present = true; + seader_update_sam_key_label(seader, NULL, 0U); seader_sam_set_state( seader, SeaderSamStateVersionPending, SeaderSamIntentMaintenance, samCommand.present); @@ -550,6 +648,26 @@ void seader_worker_send_version(Seader* seader) { seader, &payload, ExternalApplicationA, SAMInterface, ExternalApplicationA); } +bool seader_worker_send_process_snmp_message( + Seader* seader, + const uint8_t* message, + size_t message_len) { + if(!seader || !message || message_len == 0U || message_len > UINT16_MAX) return false; + + SamCommand_t samCommand = {0}; + samCommand.present = SamCommand_PR_processSNMPMessage; + samCommand.choice.processSNMPMessage.buf = (uint8_t*)message; + samCommand.choice.processSNMPMessage.size = message_len; + + Payload_t payload = {0}; + payload.present = Payload_PR_samCommand; + payload.choice.samCommand = samCommand; + + seader_send_payload( + seader, &payload, ExternalApplicationA, SAMInterface, ExternalApplicationA); + return true; +} + void seader_send_card_detected(Seader* seader, CardDetails_t* cardDetails) { CardDetected_t cardDetected = { .detectedCardDetails = *cardDetails, @@ -792,7 +910,7 @@ bool seader_sam_save_serial_QR(Seader* seader, char* serial) { bool seader_parse_serial_number(Seader* seader, uint8_t* buf, size_t size) { // Create hex string for QR code (needs to be persistent) char hex_string[size * 2 + 1]; - for(uint8_t i = 0; i < size; i++) { + for(size_t i = 0; i < size; i++) { snprintf(hex_string + (i * 2), sizeof(hex_string) - (i * 2), "%02x", buf[i]); } hex_string[size * 2] = '\0'; @@ -888,8 +1006,30 @@ bool seader_parse_sam_response(Seader* seader, SamResponse_t* samResponse) { case SeaderSamStateSerialPending: FURI_LOG_I(TAG, "samResponse serial"); seader_parse_serial_number(seader, samResponse->buf, samResponse->size); - seader_sam_set_state( - seader, SeaderSamStateIdle, SeaderSamIntentNone, SamCommand_PR_NOTHING); + seader_start_snmp_probe(seader); + break; + case SeaderSamStateCapabilityPending: + FURI_LOG_I(TAG, "samResponse processSNMPMessage"); + if(!seader_uhf_snmp_probe_consume_response( + &seader->snmp_probe, samResponse->buf, samResponse->size)) { + seader_update_sam_key_label(seader, NULL, 0U); + seader_snmp_probe_finish(seader); + break; + } + + if(seader->snmp_probe.stage >= SeaderUhfSnmpProbeStageReadTagConfig) { + 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.stage == SeaderUhfSnmpProbeStageFailed || + !seader_snmp_probe_send_next_request(seader)) { + seader_snmp_probe_finish(seader); + } break; case SeaderSamStateDetectPending: FURI_LOG_I(TAG, "samResponse cardDetected"); @@ -1199,7 +1339,7 @@ void seader_mfc_transmit( size_t tx_size = bit_buffer_get_size_bytes(tx_buffer); uint8_t* tx_data = malloc(tx_size); if(tx_data) { - for(uint8_t i = 0; i < tx_size; i++) { + for(size_t i = 0; i < tx_size; i++) { tx_data[i] = bit_buffer_get_byte(tx_buffer, i); } seader_log_hex_data(TAG, "NFC Send without parity", tx_data, tx_size); @@ -1230,7 +1370,7 @@ void seader_mfc_transmit( // Log the BitBuffer contents efficiently uint8_t* rx_data = malloc(length); if(rx_data) { - for(uint8_t i = 0; i < length; i++) { + for(size_t i = 0; i < length; i++) { rx_data[i] = bit_buffer_get_byte(rx_buffer, i); } seader_log_hex_data(TAG, "NFC Response without parity", rx_data, length); @@ -1277,7 +1417,7 @@ void seader_mfc_transmit( // Log the BitBuffer contents efficiently uint8_t* rx_data_parity = malloc(length); if(rx_data_parity) { - for(uint8_t i = 0; i < length; i++) { + for(size_t i = 0; i < length; i++) { rx_data_parity[i] = bit_buffer_get_byte(rx_buffer, i); } seader_log_hex_data(TAG, "NFC Response with parity", rx_data_parity, length); @@ -1410,7 +1550,23 @@ bool seader_worker_state_machine( case Payload_PR_errorResponse: FURI_LOG_W(TAG, "Payload_PR_errorResponse"); processed = true; - view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit); + if(seader->sam_state == SeaderSamStateCapabilityPending) { + ErrorResponse_t* err = &payload->choice.errorResponse; + if(seader_uhf_snmp_probe_consume_error( + &seader->snmp_probe, err->errorCode, err->data.buf, err->data.size)) { + 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_snmp_probe_finish(seader); + } + } else { + seader_snmp_probe_finish(seader); + } + } else { + view_dispatcher_send_custom_event( + seader->view_dispatcher, SeaderCustomEventWorkerExit); + } break; default: FURI_LOG_W(TAG, "unhandled payload"); diff --git a/sam_api.h b/sam_api.h index b8bc75e..db6da1a 100644 --- a/sam_api.h +++ b/sam_api.h @@ -36,3 +36,8 @@ bool seader_process_success_response_i( size_t len, bool online, SeaderPollerContainer* spc); + +bool seader_worker_send_process_snmp_message( + Seader* seader, + const uint8_t* message, + size_t message_len); diff --git a/sam_key_label.c b/sam_key_label.c new file mode 100644 index 0000000..566be02 --- /dev/null +++ b/sam_key_label.c @@ -0,0 +1,56 @@ +#include "sam_key_label.h" + +#include +#include +#include + +static bool seader_sam_key_label_is_missing(const uint8_t* value, size_t value_len) { + if(!value || value_len == 0U) { + return true; + } + + for(size_t i = 0; i < value_len; i++) { + if(value[i] != 0x00U) { + return false; + } + } + + return true; +} + +void seader_sam_key_label_format( + bool sam_present, + const uint8_t* elite_ice_value, + size_t elite_ice_value_len, + char* out, + size_t out_size) { + if(!out || out_size == 0U) { + return; + } + + out[0] = '\0'; + + if(!sam_present) { + snprintf(out, out_size, "NO SAM"); + return; + } + + if(seader_sam_key_label_is_missing(elite_ice_value, elite_ice_value_len)) { + snprintf(out, out_size, "SAM: Standard Key"); + return; + } + + size_t pos = 0U; + pos += (size_t)snprintf(out + pos, out_size - pos, "SAM: "); + if(pos >= out_size) { + out[out_size - 1U] = '\0'; + return; + } + + for(size_t i = 0; i < elite_ice_value_len && pos + 1U < out_size; i++) { + unsigned char ch = elite_ice_value[i]; + out[pos++] = isprint(ch) ? (char)ch : '?'; + } + + out[pos] = '\0'; +} diff --git a/sam_key_label.h b/sam_key_label.h new file mode 100644 index 0000000..7433b4e --- /dev/null +++ b/sam_key_label.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +#define SEADER_SAM_KEY_LABEL_MAX_LEN 32U + +void seader_sam_key_label_format( + bool sam_present, + const uint8_t* elite_ice_value, + size_t elite_ice_value_len, + char* out, + size_t out_size); diff --git a/seader.c b/seader.c index f45f73a..e6f5fb8 100644 --- a/seader.c +++ b/seader.c @@ -33,9 +33,15 @@ Seader* seader_alloc() { seader->samCommand = SamCommand_PR_NOTHING; seader->sam_state = SeaderSamStateIdle; seader->sam_intent = SeaderSamIntentNone; + seader->sam_present = false; + seader_sam_key_label_format( + false, NULL, 0U, seader->sam_key_label, sizeof(seader->sam_key_label)); + seader_uhf_status_label_format( + false, false, false, false, seader->uhf_status_label, sizeof(seader->uhf_status_label)); memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); seader->detected_card_type_count = 0; seader->selected_read_type = SeaderCredentialTypeNone; + seader_uhf_snmp_probe_init(&seader->snmp_probe); seader->worker = seader_worker_alloc(); seader->view_dispatcher = view_dispatcher_alloc(); diff --git a/seader_i.h b/seader_i.h index 1648374..2cb883e 100644 --- a/seader_i.h +++ b/seader_i.h @@ -56,6 +56,9 @@ #include "seader_worker.h" #include "seader_credential.h" #include "apdu_log.h" +#include "sam_key_label.h" +#include "uhf_snmp_probe.h" +#include "uhf_status_label.h" #define WORKER_ALL_RX_EVENTS \ (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ @@ -78,6 +81,7 @@ enum SeaderCustomEvent { SeaderCustomEventPollerDetect, SeaderCustomEventPollerSuccess, + SeaderCustomEventSamStatusUpdated, }; typedef enum { @@ -107,6 +111,7 @@ typedef enum { SeaderSamStateClearPending, SeaderSamStateVersionPending, SeaderSamStateSerialPending, + SeaderSamStateCapabilityPending, } SeaderSamState; typedef enum { @@ -129,8 +134,12 @@ struct Seader { SamCommand_PR samCommand; SeaderSamState sam_state; SeaderSamIntent sam_intent; + bool sam_present; uint8_t ATR[SEADER_MAX_ATR_SIZE]; size_t ATR_len; + char sam_key_label[SEADER_SAM_KEY_LABEL_MAX_LEN]; + char uhf_status_label[SEADER_UHF_STATUS_LABEL_MAX_LEN]; + SeaderUhfSnmpProbe snmp_probe; char text_store[SEADER_TEXT_STORE_SIZE + 1]; char read_error[SEADER_TEXT_STORE_SIZE + 1]; diff --git a/uhf_status_label.c b/uhf_status_label.c new file mode 100644 index 0000000..e4919fe --- /dev/null +++ b/uhf_status_label.c @@ -0,0 +1,57 @@ +#include "uhf_status_label.h" + +#include +#include + +static size_t seader_uhf_append_family( + char* out, + size_t out_size, + size_t pos, + bool* wrote_any, + const char* name, + bool key_present) { + if(*wrote_any) { + pos += (size_t)snprintf(out + pos, out_size - pos, "/"); + } else { + pos += (size_t)snprintf(out + pos, out_size - pos, "UHF: "); + *wrote_any = true; + } + + pos += (size_t)snprintf(out + pos, out_size - pos, "%s", name); + if(!key_present) { + pos += (size_t)snprintf(out + pos, out_size - pos, " [no key]"); + } + return pos; +} + +void seader_uhf_status_label_format( + bool has_monza4qt, + bool monza4qt_key_present, + bool has_higgs3, + bool higgs3_key_present, + char* out, + size_t out_size) { + bool wrote_any = false; + size_t pos = 0U; + + if(!out || out_size == 0U) { + return; + } + + out[0] = '\0'; + + if(has_monza4qt) { + pos = seader_uhf_append_family( + out, out_size, pos, &wrote_any, "Monza 4QT", monza4qt_key_present); + } + if(has_higgs3) { + pos = seader_uhf_append_family( + out, out_size, pos, &wrote_any, "Higgs 3", higgs3_key_present); + } + + if(!wrote_any) { + snprintf(out, out_size, "UHF: none"); + } else if(pos >= out_size) { + out[out_size - 1U] = '\0'; + } +} diff --git a/uhf_status_label.h b/uhf_status_label.h new file mode 100644 index 0000000..2976a83 --- /dev/null +++ b/uhf_status_label.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#define SEADER_UHF_STATUS_LABEL_MAX_LEN 64U + +void seader_uhf_status_label_format( + bool has_monza4qt, + bool monza4qt_key_present, + bool has_higgs3, + bool higgs3_key_present, + char* out, + size_t out_size);