From patchwork Wed Sep 3 13:36:16 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 204 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1756906662; bh=nETNOXmkbN7S9jJxFyRfN/HmdhnIm2cgB1BompGW4FE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=pb3Tqavr/363JIM7lnqVftnjKua1iBWka4wQIxkF5tUgXyI1DafSZmyZFgluq0zXo uAHs12T044VFatbSssPdig05wNeS1RuKmzm7eIllkvNQ8+fURL37epDiBsRnem4nnb xpsm2ZUslWSjuW9a5ZVbMWxcIloIvMaSeGTPL4IGexU6IL55goYzsQ8/QmUWwfGF38 dl2Bm+2geSWmk2wc4DSQpkPZ3E+riLEuLj0pC9VgFXxoDaa+6ztJTuQmiF2lVaH9Lf wf/oPLYnr+yzWElE6L7jqEvQgqyt09UHxu/9xgK/zH/b5NJAn/FnndEq5+m4LOBktT dSNkYitgWYG0w== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id F04E767920 for ; Wed, 3 Sep 2025 07:37:42 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 1TAeFGfOoQbq for ; Wed, 3 Sep 2025 07:37:42 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1756906662; bh=nETNOXmkbN7S9jJxFyRfN/HmdhnIm2cgB1BompGW4FE=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=pb3Tqavr/363JIM7lnqVftnjKua1iBWka4wQIxkF5tUgXyI1DafSZmyZFgluq0zXo uAHs12T044VFatbSssPdig05wNeS1RuKmzm7eIllkvNQ8+fURL37epDiBsRnem4nnb xpsm2ZUslWSjuW9a5ZVbMWxcIloIvMaSeGTPL4IGexU6IL55goYzsQ8/QmUWwfGF38 dl2Bm+2geSWmk2wc4DSQpkPZ3E+riLEuLj0pC9VgFXxoDaa+6ztJTuQmiF2lVaH9Lf wf/oPLYnr+yzWElE6L7jqEvQgqyt09UHxu/9xgK/zH/b5NJAn/FnndEq5+m4LOBktT dSNkYitgWYG0w== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DEF04678B2 for ; Wed, 3 Sep 2025 07:37:42 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1756906660; bh=+Ca+ChXYXPp5hzUfxKNkqG+dyC0WRAOor/FYHqH5hK0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=U2hnm1zZqGbrzdLrLwQhTEx4BsPy/XVux+YTiKcvjvfQu1gLUFU4WSa4pvqkKbiqj BtVOORX0VaX6pcdxqjGR11Th2JvlyxBW/TAmGaehIgbVcvb9nIunUTkNR6FbAjoZbL nP/PV/vt9wAJ1pIRCWe4LF58hl0/5CSZ6nTEqfqkP3l7DjgqsPNkq3m7leUYFrwok2 ngkS1Hm6DedO7sVLaPeBWuHGuIWvGISPchkcG0Fx2p6qExeN1IQSYEZLIT0DSf4a0r iKjPw5aVtbUNPR3S385fYM2Ynzycs47C8wcT3SSVY2fym4Z0h2KL+m0G+bN6OPYTP7 u4tSmWSfvqfPg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E2FF267915; Wed, 3 Sep 2025 07:37:40 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id VHz4M6ByWGPx; Wed, 3 Sep 2025 07:37:40 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1756906656; bh=pUzfI6lRTDgT+6AaKxiYogOtmeWR/v9aIHAXqGCV2UA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ucDtvK3ckFZArPYQuQpuzKZpFtvsSyLzPnIgwAv4j2Z+Nl1cRBUjeyYaHV1oSRyO4 HJeoX+xpubuafNYagzfAQ2a2v3NJ0+rrfrWkqvTgLWUoQDeC+nTVkZOUnotKn7dMN+ E/jP1mMdKDCh4ARkVVSfqjtweCDdx6hoi0ou/R33LYhZvlJ9ch5luGK7+2T0rl6wCi jVZ7ujoB1zBLUTmbiQ98f1/ilElZV/o4nBSZ3mVhEhTzzWQe1rkD9g5TUKTYi2wGHW rCPJ6MitKB5ZT6DeNFHe3n8E90RdMX2l4o8bcvzCZ/HrG3Sgom73De5Ip2eKw0HkiW UzXEkFUf6in5w== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id E5F825FE1B; Wed, 3 Sep 2025 07:37:35 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 3 Sep 2025 07:36:16 -0600 Message-ID: <20250903133639.3235920-17-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250903133639.3235920-1-sjg@u-boot.org> References: <20250903133639.3235920-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: SXLDSJER4EH5L35ZUI3MRW5QWSXOOM33 X-Message-ID-Hash: SXLDSJER4EH5L35ZUI3MRW5QWSXOOM33 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , Claude X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 16/25] chid: Support calculating values for each variant List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Provide a table of variants which indicates which fields are included in each. Use this to calculate the CHID for a variant. Add some tests to cover this. Provide a constant for the length of a uuid. Co-developed-by: Claude Signed-off-by: Simon Glass --- include/chid.h | 54 ++++++++++ lib/chid.c | 225 ++++++++++++++++++++++++++++++++++++++++++ test/lib/Makefile | 1 + test/lib/chid.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 526 insertions(+) create mode 100644 test/lib/chid.c diff --git a/include/chid.h b/include/chid.h index 5746ba88c32..0ea2053b08c 100644 --- a/include/chid.h +++ b/include/chid.h @@ -50,6 +50,31 @@ enum chid_field_t { CHID_COUNT, }; +/* + * enum chid_variant_id - Microsoft CHID hardware ID variants + * + * This covers HardwareID-00 through HardwareID-14 + */ +enum chid_variant_id { + CHID_00, /* Most specific */ + CHID_01, + CHID_02, + CHID_03, + CHID_04, + CHID_05, + CHID_06, + CHID_07, + CHID_08, + CHID_09, + CHID_10, + CHID_11, + CHID_12, + CHID_13, + CHID_14, /* Least specific */ + + CHID_VARIANT_COUNT +}; + /** * struct chid_variant - defines which fields are used in each CHID variant * @@ -103,4 +128,33 @@ struct chid_data { */ int chid_from_smbios(struct chid_data *chid); +/** + * chid_generate() - Generate a specific CHID variant + * + * @variant: Which CHID variant to generate (0-14) + * @data: SMBIOS data to use for generation + * @chid: Output buffer for the generated CHID (16 bytes) + * + * Return: 0 if OK, -ve error code on failure + */ +int chid_generate(int variant, const struct chid_data *data, u8 chid[16]); + +/** + * chid_get_field_name() - Get display name of a specific CHID field + * + * @field: Which CHID field + * + * Return: String containing the field name + */ +const char *chid_get_field_name(enum chid_field_t field); + +/** + * chid_get_variant_fields() - Get the fields mask for a CHID variant + * + * @variant: Which CHID variant (0-14) + * + * Return: Bitmask of fields used by this variant + */ +u32 chid_get_variant_fields(int variant); + #endif diff --git a/lib/chid.c b/lib/chid.c index 10e9b8fecae..1522ae04c79 100644 --- a/lib/chid.c +++ b/lib/chid.c @@ -9,6 +9,9 @@ * See: https://docs.microsoft.com/en-us/windows-hardware/drivers/install/specifying-hardware-ids-for-a-computer * * Copyright 2025 Simon Glass + * + * Credit: Richard Hughes + * https://blogs.gnome.org/hughsie/2017/04/25/reverse-engineering-computerhardwareids-exe-with-winedbg/ */ #define LOG_CATEGORY LOGC_ACPI @@ -17,9 +20,111 @@ #include #include #include +#include +#include +#include DECLARE_GLOBAL_DATA_PTR; +/* field names for display purposes */ +static const char *fields[CHID_COUNT] = { + [CHID_MANUF] = "Manufacturer", + [CHID_FAMILY] = "Family", + [CHID_PRODUCT_NAME] = "ProductName", + [CHID_PRODUCT_SKU] = "ProductSku", + [CHID_BOARD_MANUF] = "BaseboardManufacturer", + [CHID_BOARD_PRODUCT] = "BaseboardProduct", + [CHID_BIOS_VENDOR] = "BiosVendor", + [CHID_BIOS_VERSION] = "BiosVersion", + [CHID_BIOS_MAJOR] = "BiosMajorRelease", + [CHID_BIOS_MINOR] = "BiosMinorRelease", + [CHID_ENCLOSURE_TYPE] = "EnclosureKind", +}; + +/* + * Microsoft CHID variants table + * + * Each entry defines which SMBIOS fields are combined to create + * a specific Hardware ID variant. The variants are ordered from + * most specific (HardwareID-00) to least specific (HardwareID-14). + */ +static const struct chid_variant variants[CHID_VARIANT_COUNT] = {{ + /* HardwareID-00: Most specific - includes all identifying fields */ + .name = "HardwareID-00", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) | + BIT(CHID_PRODUCT_SKU) | BIT(CHID_BIOS_VENDOR) | + BIT(CHID_BIOS_VERSION) | BIT(CHID_BIOS_MAJOR) | + BIT(CHID_BIOS_MINOR), + }, { + /* HardwareID-01: Without SKU */ + .name = "HardwareID-01", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) | + BIT(CHID_BIOS_VENDOR) | BIT(CHID_BIOS_VERSION) | + BIT(CHID_BIOS_MAJOR) | BIT(CHID_BIOS_MINOR), + }, { + /* HardwareID-02: Without family */ + .name = "HardwareID-02", + .fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_NAME) | + BIT(CHID_BIOS_VENDOR) | BIT(CHID_BIOS_VERSION) | + BIT(CHID_BIOS_MAJOR) | BIT(CHID_BIOS_MINOR), + }, { + /* HardwareID-03: With baseboard info, no BIOS version */ + .name = "HardwareID-03", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) | + BIT(CHID_PRODUCT_SKU) | BIT(CHID_BOARD_MANUF) | + BIT(CHID_BOARD_PRODUCT), + }, { + /* HardwareID-04: Basic product identification */ + .name = "HardwareID-04", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME) | + BIT(CHID_PRODUCT_SKU), + }, { + /* HardwareID-05: Without SKU */ + .name = "HardwareID-05", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_PRODUCT_NAME), + }, { + /* HardwareID-06: SKU with baseboard */ + .name = "HardwareID-06", + .fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_SKU) | + BIT(CHID_BOARD_MANUF) | BIT(CHID_BOARD_PRODUCT), + }, { + /* HardwareID-07: Just manufacturer and SKU */ + .name = "HardwareID-07", + .fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_SKU), + }, { + /* HardwareID-08: Product name with baseboard */ + .name = "HardwareID-08", + .fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_NAME) | + BIT(CHID_BOARD_MANUF) | BIT(CHID_BOARD_PRODUCT), + }, { + /* HardwareID-09: Just manufacturer and product name */ + .name = "HardwareID-09", + .fields = BIT(CHID_MANUF) | BIT(CHID_PRODUCT_NAME), + }, { + /* HardwareID-10: Family with baseboard */ + .name = "HardwareID-10", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY) | BIT(CHID_BOARD_MANUF) | + BIT(CHID_BOARD_PRODUCT), + }, { + /* HardwareID-11: Just manufacturer and family */ + .name = "HardwareID-11", + .fields = BIT(CHID_MANUF) | BIT(CHID_FAMILY), + }, { + /* HardwareID-12: Manufacturer and enclosure type */ + .name = "HardwareID-12", + .fields = BIT(CHID_MANUF) | BIT(CHID_ENCLOSURE_TYPE), + }, { + /* HardwareID-13: Manufacturer with baseboard only */ + .name = "HardwareID-13", + .fields = BIT(CHID_MANUF) | BIT(CHID_BOARD_MANUF) | + BIT(CHID_BOARD_PRODUCT), + }, { + /* HardwareID-14: Least specific - manufacturer only */ + .name = "HardwareID-14", + .fields = BIT(CHID_MANUF), + } +}; + int chid_from_smbios(struct chid_data *chid) { const struct smbios_type0 *bios; @@ -71,3 +176,123 @@ int chid_from_smbios(struct chid_data *chid) return 0; } + +/** + * add_item() - Add a string to a buffer if the field is enabled + * + * Adds a string and then an '&', but only if the field is enabled in the mask + * + * @ptr: Current position in the buffer + * @end: Pointer to the end of the buffer (one past the last byte) + * @fields: Bitmask of enabled fields + * @field: Which field this is (CHID_xxx) + * @str: String to add, or NULL if none, in which case nothing is added + * Return: Pointer updated to after the written string and '&' (non-terminated), + * or @end if out of space + */ +static char *add_item(char *ptr, char *end, u32 fields, enum chid_field_t field, + const char *str) +{ + char trimmed[256]; + int len; + + if (!(fields & BIT(field)) || !str) + return ptr; + + /* Copy string to temporary buffer and trim spaces */ + strlcpy(trimmed, str, sizeof(trimmed)); + str = strim(trimmed); + if (!*str) + return ptr; + + len = strlen(str); + if (end - ptr <= len + 1) + return end; + memcpy(ptr, str, len); + ptr += len; + *ptr++ = '&'; + + return ptr; +} + +int chid_generate(int variant, const struct chid_data *data, u8 chid[16]) +{ + const struct chid_variant *var; + struct uuid namespace = { + .time_low = cpu_to_be32(0x70ffd812), + .time_mid = cpu_to_be16(0x4c7f), + .time_hi_and_version = cpu_to_be16(0x4c7d), + }; + __le16 utf16_data[1024]; + char *ptr, *end; + int utf16_chars; + char str[512]; + u32 fields; + + /* Validate input parameters */ + if (variant < 0 || variant >= CHID_VARIANT_COUNT || !data || !chid) + return -EINVAL; + + var = &variants[variant]; + fields = var->fields; + ptr = str; + end = str + sizeof(str) - 1; + + /* build the input string based on the variant's field mask */ + ptr = add_item(ptr, end, fields, CHID_MANUF, data->manuf); + ptr = add_item(ptr, end, fields, CHID_FAMILY, data->family); + ptr = add_item(ptr, end, fields, CHID_PRODUCT_NAME, data->product_name); + ptr = add_item(ptr, end, fields, CHID_PRODUCT_SKU, data->product_sku); + ptr = add_item(ptr, end, fields, CHID_BOARD_MANUF, data->board_manuf); + ptr = add_item(ptr, end, fields, CHID_BOARD_PRODUCT, + data->board_product); + ptr = add_item(ptr, end, fields, CHID_BIOS_VENDOR, data->bios_vendor); + ptr = add_item(ptr, end, fields, CHID_BIOS_VERSION, data->bios_version); + ptr = add_item(ptr, end, fields, CHID_BIOS_MAJOR, + simple_itoa(data->bios_major)); + ptr = add_item(ptr, end, fields, CHID_BIOS_MINOR, + simple_itoa(data->bios_minor)); + ptr = add_item(ptr, end, fields, CHID_ENCLOSURE_TYPE, + simple_itoa(data->enclosure_type)); + + /* Check if we ran out of buffer space */ + if (ptr == end) + return log_msg_ret("cgs", -ENOSPC); + + /* If no fields were added, we can't generate a CHID */ + if (ptr == str) + return log_msg_ret("cgn", -ENODATA); + + /* remove the trailing '&' and nul-terminate the string */ + ptr--; + *ptr = '\0'; + + /* + * convert to UTF-16LE and generate v5 UUID using Microsoft's namespace + * This matches Microsoft's ComputerHardwareIds.exe implementation + */ + utf16_chars = utf8_to_utf16le(str, utf16_data, ARRAY_SIZE(utf16_data)); + if (utf16_chars < 0) + return log_msg_ret("cgu", -ENOMEM); + + gen_v5_guid_be(&namespace, (struct efi_guid *)chid, utf16_data, + utf16_chars * 2, NULL); + + return 0; +} + +const char *chid_get_field_name(enum chid_field_t field) +{ + if (field >= CHID_COUNT) + return "Unknown"; + + return fields[field]; +} + +u32 chid_get_variant_fields(int variant) +{ + if (variant < 0 || variant >= CHID_VARIANT_COUNT) + return 0; + + return variants[variant].fields; +} diff --git a/test/lib/Makefile b/test/lib/Makefile index c53ceab955e..5c89421918b 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_UT_LIB_CRYPT) += test_crypt.o obj-$(CONFIG_UT_TIME) += time.o obj-$(CONFIG_$(PHASE_)UT_UNICODE) += unicode.o obj-$(CONFIG_LIB_UUID) += uuid.o +obj-$(CONFIG_CHID) += chid.o else obj-$(CONFIG_SANDBOX) += kconfig_spl.o endif diff --git a/test/lib/chid.c b/test/lib/chid.c new file mode 100644 index 00000000000..3250d4eb624 --- /dev/null +++ b/test/lib/chid.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for Computer Hardware Identifiers (Windows CHID) support + * + * Copyright 2025 Simon Glass + */ + +#include +#include +#include +#include +#include +#include + +static int chid_basic(struct unit_test_state *uts) +{ + struct chid_data data = { + .manuf = "Test Manufacturer", + .product_name = "Test Product", + .family = "Test Family", + .product_sku = "Test SKU", + .bios_vendor = "Test BIOS Vendor", + .bios_version = "1.0.0", + .bios_major = 1, + .bios_minor = 0, + }; + u8 chid[UUID_LEN]; + + /* Test HardwareID-00 (most specific) */ + ut_assertok(chid_generate(CHID_00, &data, chid)); + + /* The CHID should not be all zeros */ + u8 zero_chid[UUID_LEN] = {0}; + ut_assert(memcmp(chid, zero_chid, UUID_LEN)); + + return 0; +} +LIB_TEST(chid_basic, 0); + +static int chid_variants(struct unit_test_state *uts) +{ + struct chid_data data = { + .manuf = "Dell Inc.", + .product_name = "OptiPlex 7090", + .family = "OptiPlex", + .product_sku = "0A5C", + .bios_vendor = "Dell Inc.", + .bios_version = "1.12.0", + .bios_major = 1, + .bios_minor = 12, + .enclosure_type = 3, + }; + u8 chid0[UUID_LEN], chid1[UUID_LEN], chid14[UUID_LEN]; + + /* Test different variants produce different CHIDs */ + ut_assertok(chid_generate(CHID_00, &data, chid0)); + ut_assertok(chid_generate(CHID_01, &data, chid1)); + ut_assertok(chid_generate(CHID_14, &data, chid14)); + + /* All CHIDs should be different */ + ut_assert(memcmp(chid0, chid1, UUID_LEN)); + ut_assert(memcmp(chid0, chid14, UUID_LEN)); + ut_assert(memcmp(chid1, chid14, UUID_LEN)); + + return 0; +} +LIB_TEST(chid_variants, 0); + +static int chid_missing_fields(struct unit_test_state *uts) +{ + struct chid_data data = { + .manuf = "Test Manufacturer", + /* Missing other fields */ + }; + struct chid_data empty_data = {0}; + u8 chid[UUID_LEN]; + + /* Test HardwareID-14 (manufacturer only) should work */ + ut_assertok(chid_generate(CHID_14, &data, chid)); + + /* + * Test HardwareID-05 (requires string fields only) with completely + * empty data should fail + */ + ut_asserteq(-ENODATA, chid_generate(CHID_05, &empty_data, chid)); + + /* Test HardwareID-14 with empty data should also fail */ + ut_asserteq(-ENODATA, chid_generate(CHID_14, &empty_data, chid)); + + return 0; +} +LIB_TEST(chid_missing_fields, 0); + +static int chid_invalid_params(struct unit_test_state *uts) +{ + struct chid_data data = { + .manuf = "Test Manufacturer", + }; + u8 chid[UUID_LEN]; + + /* Test invalid variant number */ + ut_asserteq(-EINVAL, chid_generate(-1, &data, chid)); + ut_asserteq(-EINVAL, chid_generate(15, &data, chid)); + + /* Test NULL data */ + ut_asserteq(-EINVAL, chid_generate(CHID_00, NULL, chid)); + + /* Test NULL chid output buffer */ + ut_asserteq(-EINVAL, chid_generate(CHID_00, &data, NULL)); + + return 0; +} +LIB_TEST(chid_invalid_params, 0); + +static int chid_consistent(struct unit_test_state *uts) +{ + struct chid_data data = { + .manuf = "ACME Corp", + .product_name = "Widget Pro", + .bios_vendor = "ACME BIOS", + .bios_version = "2.1.0", + .bios_major = 2, + .bios_minor = 1, + }; + u8 chid1[UUID_LEN], chid2[UUID_LEN]; + char chid1_str[UUID_STR_LEN + 1], chid2_str[UUID_STR_LEN + 1]; + + /* Generate the same CHID twice - should be identical */ + ut_assertok(chid_generate(CHID_02, &data, chid1)); + ut_assertok(chid_generate(CHID_02, &data, chid2)); + + /* CHIDs should be identical for same input */ + uuid_bin_to_str(chid1, chid1_str, UUID_STR_FORMAT_STD); + uuid_bin_to_str(chid2, chid2_str, UUID_STR_FORMAT_STD); + ut_asserteq_str(chid1_str, chid2_str); + + return 0; +} +LIB_TEST(chid_consistent, 0); + +static int chid_numeric(struct unit_test_state *uts) +{ + struct chid_data data = { + .manuf = "Test Corp", + .bios_major = 255, + .bios_minor = 127, + .enclosure_type = 99, + }; + u8 zero_chid[UUID_LEN] = {0}; + u8 chid[UUID_LEN]; + + /* Test with numeric fields only (manufacturer + numeric values) */ + /* HardwareID-12: Manufacturer + Enclosure Type */ + ut_assertok(chid_generate(CHID_12, &data, chid)); + + /* CHID should be generated successfully */ + ut_assert(memcmp(chid, zero_chid, UUID_LEN)); + + return 0; +} +LIB_TEST(chid_numeric, 0); + +static int chid_real(struct unit_test_state *uts) +{ + /* + * Real data from Lenovo ThinkPad X13s Gen 1 (21BXCTO1WW) + * Test against actual CHIDs from Microsoft's ComputerHardwareIds.exe + * output + */ + struct chid_data data = { + .manuf = "LENOVO", + .family = "ThinkPad X13s Gen 1", + .product_name = "21BXCTO1WW", + .product_sku = "LENOVO_MT_21BX_BU_Think_FM_ThinkPad X13s Gen 1", + .board_manuf = "LENOVO", + .board_product = "21BXCTO1WW", + .bios_vendor = "LENOVO", + .bios_version = "N3HET88W (1.60 )", + .bios_major = 1, + .bios_minor = 60, + .enclosure_type = 0x0a, + }; + u8 chid[UUID_LEN]; + char chid_str[UUID_STR_LEN + 1]; + + /* Test HardwareID-14 (Manufacturer only) */ + ut_assertok(chid_generate(CHID_14, &data, chid)); + uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD); + ut_asserteq_str("6de5d951-d755-576b-bd09-c5cf66b27234", chid_str); + + /* Test HardwareID-11 (Manufacturer + Family) */ + ut_assertok(chid_generate(CHID_11, &data, chid)); + uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD); + ut_asserteq_str("f249803d-0d95-54f3-a28f-f26c14a03f3b", chid_str); + + /* Test HardwareID-12 (Manufacturer + EnclosureKind) */ + ut_assertok(chid_generate(CHID_12, &data, chid)); + uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD); + ut_asserteq_str("5e820764-888e-529d-a6f9-dfd12bacb160", chid_str); + + /* + * Test HardwareID-13 (Manufacturer + BaseboardManufacturer + + * BaseboardProduct) + */ + ut_assertok(chid_generate(CHID_13, &data, chid)); + uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD); + ut_asserteq_str("156c9b34-bedb-5bfd-ae1f-ef5d2a994967", chid_str); + + return 0; +} +LIB_TEST(chid_real, 0); + +static int chid_exact(struct unit_test_state *uts) +{ + /* + * Test exact CHID matching against Microsoft's ComputerHardwareIds.exe + * Using Lenovo ThinkPad X13s Gen 1 data from reference file + * Expected CHID for HardwareID-14 (Manufacturer only): + * {6de5d951-d755-576b-bd09-c5cf66b27234} + */ + struct chid_data data = { + .manuf = "LENOVO", + .family = "ThinkPad X13s Gen 1", + .product_name = "21BXCTO1WW", + .product_sku = "LENOVO_MT_21BX_BU_Think_FM_ThinkPad X13s Gen 1", + .board_manuf = "LENOVO", + .board_product = "21BXCTO1WW", + .bios_vendor = "LENOVO", + .bios_version = "N3HET88W (1.60 )", + .bios_major = 1, + .bios_minor = 60, + .enclosure_type = 0x0a, + }; + char chid_str[UUID_STR_LEN + 1]; + u8 chid[UUID_LEN]; + + /* Test HardwareID-14 (Manufacturer only) */ + ut_assertok(chid_generate(CHID_14, &data, chid)); + + /* Convert CHID to string and compare with expected GUID string */ + uuid_bin_to_str(chid, chid_str, UUID_STR_FORMAT_STD); + ut_asserteq_str("6de5d951-d755-576b-bd09-c5cf66b27234", chid_str); + + return 0; +} +LIB_TEST(chid_exact, 0);