root/usr/src/test/util-tests/tests/smbios/smbios_test_addinfo.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2024 Oxide Computer Company
 */

/*
 * Test SMBIOS Type 40 Additional Information. We try to cover a variety of
 * cases with and without entries, entries with and without additional data, and
 * several invalid length entries. Nothing currently checks that handles are
 * meaningful beyond that they are replicated.
 */

#include "smbios_test.h"

static const uint16_t smbios_addinfo_ent0_hdl = 0x7777;
static const uint8_t smbios_addinfo_ent0_off = 0x97;
static const char *smbios_addinfo_ent0_str = "Sephiroth";
static const uint32_t smbios_addinfo_ent0_data = 9999;
static const uint16_t smbios_addinfo_ent1_hdl = 0x1234;
static const uint8_t smbios_addinfo_ent1_off = 4;
static const char *smbios_addinfo_ent1_str = "Himmel";
static const uint16_t smbios_addinfo_ent2_hdl = 0x4321;
static const uint8_t smbios_addinfo_ent2_off = 0xfe;
static const char *smbios_addinfo_ent2_str = "Knights of the Round";
static const char *smbios_addinfo_ent2_data = "Galahad, Gawain, Lancelot";

static boolean_t
smbios_test_addinfo_verify_base(smbios_hdl_t *hdl, smbios_struct_t *sp,
    uint_t exp)
{
        uint_t nents;
        boolean_t ret = B_TRUE;
        smbios_addinfo_ent_t *ent;

        if (smbios_lookup_type(hdl, SMB_TYPE_ADDINFO, sp) == -1) {
                warnx("failed to lookup SMBIOS addinfo: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (smbios_info_addinfo_nents(hdl, sp->smbstr_id, &nents) != 0) {
                warnx("failed to get additional information entry count: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (nents != exp) {
                warnx("additional information entry mismatch: expected 0x%x, "
                    "found 0x%x", exp, nents);
                ret = B_FALSE;
        }

        if (smbios_info_addinfo_ent(hdl, sp->smbstr_id, exp, &ent) != -1) {
                warnx("incorrectly parsed non-existent entity");
                smbios_info_addinfo_ent_free(hdl, ent);
                ret = B_FALSE;
        } else if (smbios_errno(hdl) != ESMB_REQVAL) {
                warnx("encountered wrong error for addinfo ent, expected: "
                    "0x%x, found: 0x%x", ESMB_REQVAL, smbios_errno(hdl));
                ret = B_FALSE;
        }

        return (ret);
}

/*
 * Basic entry without valid entries. Strictly speaking this may be illegal per
 * the spec.
 */
boolean_t
smbios_test_addinfo_mktable_noent(smbios_test_table_t *table)
{
        smb_addinfo_t add;

        add.smbai_hdr.smbh_type = SMB_TYPE_ADDINFO;
        add.smbai_hdr.smbh_len = sizeof (add);
        add.smbai_nents = 0;

        (void) smbios_test_table_append(table, &add, sizeof (add));
        smbios_test_table_append_eot(table);

        return (B_TRUE);
}

boolean_t
smbios_test_addinfo_verify_noent(smbios_hdl_t *hdl)
{
        smbios_struct_t sp;

        return (smbios_test_addinfo_verify_base(hdl, &sp, 0));
}

/*
 * Complex case with three entries, each with varying data and strings.
 */
boolean_t
smbios_test_addinfo_mktable_ents(smbios_test_table_t *table)
{
        smb_addinfo_t add;
        smb_addinfo_ent_t ent0, ent1, ent2;
        size_t slen;

        add.smbai_hdr.smbh_type = SMB_TYPE_ADDINFO;
        add.smbai_hdr.smbh_len = sizeof (add);
        add.smbai_nents = 3;

        ent0.smbaie_len = sizeof (smb_addinfo_ent_t) +
            sizeof (smbios_addinfo_ent0_data);
        ent0.smbaie_rhdl = htole16(smbios_addinfo_ent0_hdl);
        ent0.smbaie_off = smbios_addinfo_ent0_off;
        ent0.smbaie_str = 1;
        add.smbai_hdr.smbh_len += ent0.smbaie_len;

        ent1.smbaie_len = sizeof (smb_addinfo_ent_t);
        ent1.smbaie_rhdl = htole16(smbios_addinfo_ent1_hdl);
        ent1.smbaie_off = smbios_addinfo_ent1_off;
        ent1.smbaie_str = 2;
        add.smbai_hdr.smbh_len += ent1.smbaie_len;

        slen = strlen(smbios_addinfo_ent2_data) + 1;
        ent2.smbaie_len = sizeof (smb_addinfo_ent_t) + slen;
        ent2.smbaie_rhdl = htole16(smbios_addinfo_ent2_hdl);
        ent2.smbaie_off = smbios_addinfo_ent2_off;
        ent2.smbaie_str = 3;
        add.smbai_hdr.smbh_len += ent2.smbaie_len;

        (void) smbios_test_table_append(table, &add, sizeof (add));
        (void) smbios_test_table_append_raw(table, &ent0, sizeof (ent0));
        (void) smbios_test_table_append_raw(table, &smbios_addinfo_ent0_data,
            sizeof (smbios_addinfo_ent0_data));
        (void) smbios_test_table_append_raw(table, &ent1, sizeof (ent1));
        (void) smbios_test_table_append_raw(table, &ent2, sizeof (ent2));
        (void) smbios_test_table_append_raw(table, smbios_addinfo_ent2_data,
            slen);
        smbios_test_table_append_string(table, smbios_addinfo_ent0_str);
        smbios_test_table_append_string(table, smbios_addinfo_ent1_str);
        smbios_test_table_append_string(table, smbios_addinfo_ent2_str);
        smbios_test_table_str_fini(table);
        smbios_test_table_append_eot(table);

        return (B_TRUE);
}

boolean_t
smbios_test_addinfo_verify_ents(smbios_hdl_t *hdl)
{
        smbios_struct_t sp;
        boolean_t ret = B_TRUE;
        smbios_addinfo_ent_t *ent;

        if (!smbios_test_addinfo_verify_base(hdl, &sp, 3)) {
                return (B_FALSE);
        }

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 0, &ent) != 0) {
                warnx("failed to lookup additional entry 0: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (ent->smbai_ref != smbios_addinfo_ent0_hdl) {
                warnx("entry 0 mismatch, found unexpected reference handle: "
                    "0x%" _PRIxID, ent->smbai_ref);
                ret = B_FALSE;
        }
        if (ent->smbai_ref_off != smbios_addinfo_ent0_off) {
                warnx("entry 0 mismatch, found unexpected reference offset: "
                    "0x%x", ent->smbai_ref_off);
                ret = B_FALSE;
        }
        if (ent->smbai_dlen != sizeof (smbios_addinfo_ent0_data)) {
                warnx("entry 0 mismatch, found unexpected data length: 0x%x",
                    ent->smbai_dlen);
                ret = B_FALSE;
        }
        if (memcmp(ent->smbai_data, &smbios_addinfo_ent0_data,
            ent->smbai_dlen) != 0) {
                warnx("entry 0 mismatch, additional data mismatched");
                ret = B_FALSE;
        }
        smbios_info_addinfo_ent_free(hdl, ent);

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 1, &ent) != 0) {
                warnx("failed to lookup additional entry 1: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (ent->smbai_ref != smbios_addinfo_ent1_hdl) {
                warnx("entry 1 mismatch, found unexpected reference handle: "
                    "0x%" _PRIxID, ent->smbai_ref);
                ret = B_FALSE;
        }
        if (ent->smbai_ref_off != smbios_addinfo_ent1_off) {
                warnx("entry 1 mismatch, found unexpected reference offset: "
                    "0x%x", ent->smbai_ref_off);
                ret = B_FALSE;
        }
        if (ent->smbai_dlen != 0) {
                warnx("entry 1 mismatch, found unexpected data length: 0x%x",
                    ent->smbai_dlen);
                ret = B_FALSE;
        }
        if (ent->smbai_data != NULL) {
                warnx("entry 1 mismatch, found unexpected data pointer");
                ret = B_FALSE;
        }
        smbios_info_addinfo_ent_free(hdl, ent);

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 2, &ent) != 0) {
                warnx("failed to lookup additional entry 2: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (ent->smbai_ref != smbios_addinfo_ent2_hdl) {
                warnx("entry 2 mismatch, found unexpected reference handle: "
                    "0x%" _PRIxID, ent->smbai_ref);
                ret = B_FALSE;
        }
        if (ent->smbai_ref_off != smbios_addinfo_ent2_off) {
                warnx("entry 2 mismatch, found unexpected reference offset: "
                    "0x%x", ent->smbai_ref_off);
                ret = B_FALSE;
        }
        if (ent->smbai_dlen != strlen(smbios_addinfo_ent2_data) + 1) {
                warnx("entry 2 mismatch, found unexpected data length: 0x%x",
                    ent->smbai_dlen);
                ret = B_FALSE;
        }
        if (memcmp(ent->smbai_data, smbios_addinfo_ent2_data,
            ent->smbai_dlen) != 0) {
                warnx("entry 2 mismatch, additional data mismatched");
                ret = B_FALSE;
        }
        smbios_info_addinfo_ent_free(hdl, ent);

        return (ret);
}

/*
 * Generate a table that's too short to get basic info.
 */
boolean_t
smbios_test_addinfo_mktable_invlen_base(smbios_test_table_t *table)
{
        smb_header_t hdr;

        hdr.smbh_type = SMB_TYPE_ADDINFO;
        hdr.smbh_len = sizeof (hdr);

        (void) smbios_test_table_append(table, &hdr, sizeof (hdr));
        smbios_test_table_append_eot(table);

        return (B_TRUE);
}

boolean_t
smbios_test_addinfo_verify_invlen_base(smbios_hdl_t *hdl)
{
        smbios_struct_t sp;
        uint_t nents;

        if (smbios_lookup_type(hdl, SMB_TYPE_ADDINFO, &sp) == -1) {
                warnx("failed to lookup SMBIOS addinfo: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (smbios_info_addinfo_nents(hdl, sp.smbstr_id, &nents) != -1) {
                warnx("accidentally parsed invalid addinfo information as "
                    "valid");
                return (B_FALSE);
        }

        if (smbios_errno(hdl) != ESMB_SHORT) {
                warnx("encountered wrong error for addinfo, expected: "
                    "0x%x, found: 0x%x", ESMB_SHORT, smbios_errno(hdl));
                return (B_FALSE);
        }

        return (B_TRUE);
}

/*
 * A table that's long enough to have valid entries, but too short for the first
 * entry.
 */
boolean_t
smbios_test_addinfo_mktable_invlen_ent(smbios_test_table_t *table)
{
        smb_addinfo_t add;
        smb_addinfo_ent_t ent = { 0 };
        size_t entoff = offsetof(smb_addinfo_ent_t, smbaie_rhdl);

        add.smbai_hdr.smbh_type = SMB_TYPE_ADDINFO;
        add.smbai_hdr.smbh_len = sizeof (add) + entoff;
        add.smbai_nents = 1;

        (void) smbios_test_table_append(table, &add, sizeof (add));
        (void) smbios_test_table_append_raw(table, &ent, entoff);
        smbios_test_table_append_eot(table);

        return (B_TRUE);
}

boolean_t
smbios_test_addinfo_verify_invlen_ent(smbios_hdl_t *hdl)
{
        smbios_struct_t sp;
        smbios_addinfo_ent_t *ent;
        boolean_t ret = B_TRUE;

        if (!smbios_test_addinfo_verify_base(hdl, &sp, 1)) {
                return (B_FALSE);
        }

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 0, &ent) != -1) {
                warnx("incorrectly parsed additional information entry 0: "
                    "expected bad length");
                ret = B_FALSE;
        } else if (smbios_errno(hdl) != ESMB_SHORT) {
                warnx("encountered wrong error for addinfo ent, expected: "
                    "0x%x, found: 0x%x", ESMB_SHORT, smbios_errno(hdl));
                ret = B_FALSE;
        }

        return (ret);
}

/*
 * Make sure even if we parse the first entity correctly, we fail on the second
 * one being too short.
 */
boolean_t
smbios_test_addinfo_mktable_invlen_multient(smbios_test_table_t *table)
{
        smb_addinfo_t add;
        smb_addinfo_ent_t ent0, ent1 = { 0 };
        size_t entoff = offsetof(smb_addinfo_ent_t, smbaie_rhdl);

        add.smbai_hdr.smbh_type = SMB_TYPE_ADDINFO;
        add.smbai_hdr.smbh_len = sizeof (add);
        add.smbai_nents = 2;

        ent0.smbaie_len = sizeof (smb_addinfo_ent_t) +
            sizeof (smbios_addinfo_ent0_data);
        ent0.smbaie_rhdl = htole16(smbios_addinfo_ent0_hdl);
        ent0.smbaie_off = smbios_addinfo_ent0_off;
        ent0.smbaie_str = 1;
        add.smbai_hdr.smbh_len += ent0.smbaie_len;

        ent1.smbaie_len = sizeof (smb_addinfo_ent_t);
        add.smbai_hdr.smbh_len += entoff;

        (void) smbios_test_table_append(table, &add, sizeof (add));
        (void) smbios_test_table_append_raw(table, &ent0, sizeof (ent0));
        (void) smbios_test_table_append_raw(table, &smbios_addinfo_ent0_data,
            sizeof (smbios_addinfo_ent0_data));
        (void) smbios_test_table_append_raw(table, &ent1, entoff);

        smbios_test_table_append_string(table, smbios_addinfo_ent0_str);
        smbios_test_table_str_fini(table);
        smbios_test_table_append_eot(table);


        (void) smbios_test_table_append(table, &add, sizeof (add));
        (void) smbios_test_table_append_raw(table, &ent1, entoff);
        smbios_test_table_append_eot(table);

        return (B_TRUE);
}

boolean_t
smbios_test_addinfo_verify_invlen_multient(smbios_hdl_t *hdl)
{
        smbios_struct_t sp;
        smbios_addinfo_ent_t *ent;
        boolean_t ret = B_TRUE;

        if (!smbios_test_addinfo_verify_base(hdl, &sp, 2)) {
                return (B_FALSE);
        }

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 1, &ent) != -1) {
                warnx("incorrectly parsed additional information entry 1: "
                    "expected bad length");
                ret = B_FALSE;
        } else if (smbios_errno(hdl) != ESMB_SHORT) {
                warnx("encountered wrong error for addinfo ent, expected: "
                    "0x%x, found: 0x%x", ESMB_SHORT, smbios_errno(hdl));
                ret = B_FALSE;
        }

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 0, &ent) != 0) {
                warnx("failed to lookup additional entry 0: %s",
                    smbios_errmsg(smbios_errno(hdl)));
                return (B_FALSE);
        }

        if (ent->smbai_ref != smbios_addinfo_ent0_hdl) {
                warnx("entry 0 mismatch, found unexpected reference handle: "
                    "0x%" _PRIxID, ent->smbai_ref);
                ret = B_FALSE;
        }
        if (ent->smbai_ref_off != smbios_addinfo_ent0_off) {
                warnx("entry 0 mismatch, found unexpected reference offset: "
                    "0x%x", ent->smbai_ref_off);
                ret = B_FALSE;
        }
        if (ent->smbai_dlen != sizeof (smbios_addinfo_ent0_data)) {
                warnx("entry 0 mismatch, found unexpected data length: 0x%x",
                    ent->smbai_dlen);
                ret = B_FALSE;
        }
        if (memcmp(ent->smbai_data, &smbios_addinfo_ent0_data,
            ent->smbai_dlen) != 0) {
                warnx("entry 0 mismatch, additional data mismatched");
                ret = B_FALSE;
        }
        smbios_info_addinfo_ent_free(hdl, ent);

        return (ret);
}

/*
 * Make sure we get the case where the length of the entity is longer than the
 * table.
 */
boolean_t
smbios_test_addinfo_mktable_invlen_entdata(smbios_test_table_t *table)
{
        smb_addinfo_t add;
        smb_addinfo_ent_t ent;

        add.smbai_hdr.smbh_type = SMB_TYPE_ADDINFO;
        add.smbai_hdr.smbh_len = sizeof (add) + sizeof (ent);
        add.smbai_nents = 1;

        (void) memset(&ent, 0, sizeof (ent));
        ent.smbaie_len = sizeof (ent) + 3;

        (void) smbios_test_table_append(table, &add, sizeof (add));
        (void) smbios_test_table_append_raw(table, &ent, sizeof (ent));
        smbios_test_table_append_eot(table);

        return (B_TRUE);
}

boolean_t
smbios_test_addinfo_verify_invlen_entdata(smbios_hdl_t *hdl)
{
        smbios_struct_t sp;
        smbios_addinfo_ent_t *ent;
        boolean_t ret = B_TRUE;

        if (!smbios_test_addinfo_verify_base(hdl, &sp, 1)) {
                return (B_FALSE);
        }

        if (smbios_info_addinfo_ent(hdl, sp.smbstr_id, 0, &ent) != -1) {
                warnx("incorrectly parsed additional information entry 0: "
                    "expected bad length");
                ret = B_FALSE;
        } else if (smbios_errno(hdl) != ESMB_CORRUPT) {
                warnx("encountered wrong error for addinfo ent, expected: "
                    "0x%x, found: 0x%x", ESMB_CORRUPT, smbios_errno(hdl));
                ret = B_FALSE;
        }

        return (ret);
}