/* * Copyright (C) 2021 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; If not, see * . */ #include #include "internal.h" #include "testutils.h" #include "virpcivpd.h" #define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW #include "virpcivpdpriv.h" #include "virlog.h" #define VIR_FROM_THIS VIR_FROM_NONE #ifdef __linux__ VIR_LOG_INIT("tests.vpdtest"); typedef struct _TestPCIVPDKeywordValue { const char *keyword; const char *value; char **actual; } TestPCIVPDKeywordValue; static int testPCIVPDResourceBasic(const void *data G_GNUC_UNUSED) { size_t i = 0; g_autoptr(virPCIVPDResourceRO) ro = virPCIVPDResourceRONew(); g_autoptr(virPCIVPDResourceRW) rw = virPCIVPDResourceRWNew(); /* Note: when the same keyword is updated multiple times the * virPCIVPDResourceUpdateKeyword function is expected to free the * previous value whether it is a fixed keyword or a custom one. * */ const TestPCIVPDKeywordValue readOnlyCases[] = { {.keyword = "EC", .value = "level1", .actual = &ro->change_level}, {.keyword = "EC", .value = "level2", .actual = &ro->change_level}, {.keyword = "change_level", .value = "level3", .actual = &ro->change_level}, {.keyword = "PN", .value = "number1", .actual = &ro->part_number}, {.keyword = "PN", .value = "number2", .actual = &ro->part_number}, {.keyword = "part_number", .value = "number3", .actual = &ro->part_number}, {.keyword = "MN", .value = "id1", .actual = &ro->manufacture_id}, {.keyword = "MN", .value = "id2", .actual = &ro->manufacture_id}, {.keyword = "manufacture_id", .value = "id3", &ro->manufacture_id}, {.keyword = "SN", .value = "serial1", .actual = &ro->serial_number}, {.keyword = "SN", .value = "serial2", .actual = &ro->serial_number}, {.keyword = "serial_number", .value = "serial3", .actual = &ro->serial_number}, }; const TestPCIVPDKeywordValue readWriteCases[] = { {.keyword = "YA", .value = "tag1", .actual = &ro->change_level}, {.keyword = "YA", .value = "tag2", .actual = &ro->change_level}, {.keyword = "asset_tag", .value = "tag3", .actual = &ro->change_level}, }; const TestPCIVPDKeywordValue unsupportedFieldCases[] = { {.keyword = "FG", .value = "42", .actual = NULL}, {.keyword = "LC", .value = "42", .actual = NULL}, {.keyword = "PG", .value = "42", .actual = NULL}, {.keyword = "CP", .value = "42", .actual = NULL}, {.keyword = "EX", .value = "42", .actual = NULL}, }; size_t numROCases = G_N_ELEMENTS(readOnlyCases); size_t numRWCases = G_N_ELEMENTS(readWriteCases); size_t numUnsupportedCases = G_N_ELEMENTS(unsupportedFieldCases); g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1); virPCIVPDResourceCustom *custom = NULL; g_autofree char *val = g_strdup("testval"); res->name = g_steal_pointer(&val); /* RO has not been initialized - make sure updates fail. */ for (i = 0; i < numROCases; ++i) { if (virPCIVPDResourceUpdateKeyword(res, true, readOnlyCases[i].keyword, readOnlyCases[i].value)) return -1; } /* RW has not been initialized - make sure updates fail. */ for (i = 0; i < numRWCases; ++i) { if (virPCIVPDResourceUpdateKeyword(res, false, readWriteCases[i].keyword, readWriteCases[i].value)) return -1; } /* Initialize RO */ res->ro = g_steal_pointer(&ro); /* Update keywords one by one and compare actual values with the expected ones. */ for (i = 0; i < numROCases; ++i) { if (!virPCIVPDResourceUpdateKeyword(res, true, readOnlyCases[i].keyword, readOnlyCases[i].value)) return -1; if (STRNEQ(readOnlyCases[i].value, *readOnlyCases[i].actual)) return -1; } /* Do a basic vendor field check. */ if (!virPCIVPDResourceUpdateKeyword(res, true, "V0", "vendor0")) return -1; if (res->ro->vendor_specific->len != 1) return -1; custom = g_ptr_array_index(res->ro->vendor_specific, 0); if (custom->idx != '0' || STRNEQ(custom->value, "vendor0")) return -1; /* Make sure unsupported RO keyword updates are not fatal. */ for (i = 0; i < numUnsupportedCases; ++i) { if (!virPCIVPDResourceUpdateKeyword(res, true, unsupportedFieldCases[i].keyword, unsupportedFieldCases[i].value)) return -1; } /* Check that RW updates fail if RW has not been initialized. */ if (virPCIVPDResourceUpdateKeyword(res, false, "YA", "tag1")) return -1; if (virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", "tag1")) return -1; /* Initialize RW */ res->rw = g_steal_pointer(&rw); if (!virPCIVPDResourceUpdateKeyword(res, false, "YA", "tag1") || STRNEQ(res->rw->asset_tag, "tag1")) return -1; if (!virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", "tag2") || STRNEQ(res->rw->asset_tag, "tag2")) return -1; /* Do a basic system field check. */ if (!virPCIVPDResourceUpdateKeyword(res, false, "Y0", "system0")) return -1; if (res->rw->system_specific->len != 1) return -1; custom = g_ptr_array_index(res->rw->system_specific, 0); if (custom->idx != '0' || STRNEQ(custom->value, "system0")) return -1; /* Make sure unsupported RW keyword updates are not fatal. */ for (i = 0; i < numUnsupportedCases; ++i) { if (!virPCIVPDResourceUpdateKeyword(res, false, unsupportedFieldCases[i].keyword, unsupportedFieldCases[i].value)) return -1; } /* Just make sure the name has not been changed during keyword updates. */ if (!STREQ_NULLABLE(res->name, "testval")) return -1; return 0; } static int testPCIVPDResourceCustomCompareIndex(const void *data G_GNUC_UNUSED) { g_autoptr(virPCIVPDResourceCustom) a = NULL; g_autoptr(virPCIVPDResourceCustom) b = NULL; /* Both are NULL */ if (!virPCIVPDResourceCustomCompareIndex(a, b)) return -1; /* a is not NULL */ a = g_new0(virPCIVPDResourceCustom, 1); if (virPCIVPDResourceCustomCompareIndex(a, b)) return -1; /* Reverse */ if (virPCIVPDResourceCustomCompareIndex(b, a)) return -1; /* Same index, different strings */ b = g_new0(virPCIVPDResourceCustom, 1); a->idx = 'z'; a->value = g_strdup("42"); b->idx = 'z'; b->value = g_strdup("24"); if (!virPCIVPDResourceCustomCompareIndex(b, a)) return -1; /* Different index, different strings */ a->idx = 'a'; if (virPCIVPDResourceCustomCompareIndex(b, a)) return -1; virPCIVPDResourceCustomFree(a); virPCIVPDResourceCustomFree(b); a = g_new0(virPCIVPDResourceCustom, 1); b = g_new0(virPCIVPDResourceCustom, 1); /* Same index, same strings */ a->idx = 'z'; a->value = g_strdup("42"); b->idx = 'z'; b->value = g_strdup("42"); if (!virPCIVPDResourceCustomCompareIndex(b, a)) return -1; /* Different index, same strings */ a->idx = 'a'; if (virPCIVPDResourceCustomCompareIndex(b, a)) return -1; /* Different index, same value pointers */ g_free(b->value); b->value = a->value; if (virPCIVPDResourceCustomCompareIndex(b, a)) { b->value = NULL; return -1; } b->value = NULL; return 0; } static int testPCIVPDResourceCustomUpsertValue(const void *data G_GNUC_UNUSED) { g_autoptr(GPtrArray) arr = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree); virPCIVPDResourceCustom *custom = NULL; if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testval")) return -1; if (arr->len != 1) return -1; custom = g_ptr_array_index(arr, 0); if (custom == NULL || custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "testval")) return -1; /* Idempotency */ if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testval")) return -1; if (arr->len != 1) return -1; custom = g_ptr_array_index(arr, 0); if (custom == NULL || custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "testval")) return -1; /* Existing value updates. */ if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testvalnew")) return -1; if (arr->len != 1) return -1; custom = g_ptr_array_index(arr, 0); if (custom == NULL || custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "testvalnew")) return -1; /* Inserting multiple values */ if (!virPCIVPDResourceCustomUpsertValue(arr, '1', "42")) return -1; if (arr->len != 2) return -1; custom = g_ptr_array_index(arr, 1); if (custom == NULL || custom->idx != '1' || STRNEQ_NULLABLE(custom->value, "42")) return -1; return 0; } typedef struct _TestPCIVPDExpectedString { const char *keyword; bool expected; } TestPCIVPDExpectedString; /* * testPCIVPDIsValidTextValue: * * Test expected text value validation. Static metadata about possible values is taken * from the PCI(e) standards and based on some real-world hardware examples. * */ static int testPCIVPDIsValidTextValue(const void *data G_GNUC_UNUSED) { size_t i = 0; const TestPCIVPDExpectedString textValueCases[] = { /* Numbers */ {"42", true}, /* Alphanumeric */ {"DCM1001008FC52101008FC53201008FC54301008FC5", true}, /* Dots */ {"DSV1028VPDR.VER1.0", true}, /* Whitespace presence */ {"NMVIntel Corp", true}, /* Comma and spaces */ {"BlueField-2 DPU 25GbE Dual-Port SFP56, Tall Bracket", true}, /* Equal signs and colons. */ {"MLX:MN=MLNX:CSKU=V2:UUID=V3:PCI=V0:MODL=BF2H332A", true}, /* Dashes */ {"MBF2H332A-AEEOT", true}, {"under_score_example", true}, {"", true}, {";", true}, {"\\42", true}, {"N/A", true}, /* The first and last code points are outside ASCII (multi-byte in UTF-8). */ {"гbl🐧", false}, }; for (i = 0; i < G_N_ELEMENTS(textValueCases); ++i) { if (virPCIVPDResourceIsValidTextValue(textValueCases[i].keyword) != textValueCases[i].expected) return -1; } return 0; } /* * testPCIVPDGetFieldValueFormat: * * A simple test to assess the functionality of the * virPCIVPDResourceGetFieldValueFormat function. * */ static int testPCIVPDGetFieldValueFormat(const void *data G_GNUC_UNUSED) { typedef struct _TestPCIVPDExpectedFieldValueFormat { const char *keyword; virPCIVPDResourceFieldValueFormat expected; } TestPCIVPDExpectedFieldValueFormat; size_t i = 0; const TestPCIVPDExpectedFieldValueFormat valueFormatCases[] = { {"SN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"EC", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"MN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"PN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"RV", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD}, {"RW", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR}, {"VA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"YA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"YZ", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, {"CP", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY}, /* Invalid keywords. */ {"", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"sn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"ec", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"mn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"pn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"4", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"42", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"Y", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"V", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"v", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"vA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"va", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"ya", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, {"Ya", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, /* 2 bytes but not present in the spec. */ {"EX", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, /* Many numeric bytes. */ {"4242", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, /* Many letters. */ {"EXAMPLE", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, }; for (i = 0; i < G_N_ELEMENTS(valueFormatCases); ++i) { if (virPCIVPDResourceGetFieldValueFormat(valueFormatCases[i].keyword) != valueFormatCases[i].expected) return -1; } return 0; } # define VPD_STRING_RESOURCE_EXAMPLE_HEADER \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x08, 0x00 # define VPD_STRING_RESOURCE_EXAMPLE_DATA \ 't', 'e', 's', 't', 'n', 'a', 'm', 'e' # define VPD_R_FIELDS_EXAMPLE_HEADER \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x16, 0x00 # define VPD_R_EXAMPLE_VALID_RV_FIELD \ 'R', 'V', 0x02, 0x31, 0x00 # define VPD_R_EXAMPLE_INVALID_RV_FIELD \ 'R', 'V', 0x02, 0xFF, 0x00 # define VPD_R_EXAMPLE_FIELDS \ 'P', 'N', 0x02, '4', '2', \ 'E', 'C', 0x04, '4', '2', '4', '2', \ 'V', 'A', 0x02, 'E', 'X' # define VPD_R_FIELDS_EXAMPLE_DATA \ VPD_R_EXAMPLE_FIELDS, \ VPD_R_EXAMPLE_VALID_RV_FIELD # define VPD_W_FIELDS_EXAMPLE_HEADER \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG, 0x19, 0x00 # define VPD_W_EXAMPLE_FIELDS \ 'V', 'Z', 0x02, '4', '2', \ 'Y', 'A', 0x04, 'I', 'D', '4', '2', \ 'Y', 'F', 0x02, 'E', 'X', \ 'Y', 'E', 0x00, \ 'R', 'W', 0x02, 0x00, 0x00 static int testVirPCIVPDReadVPDBytes(const void *opaque G_GNUC_UNUSED) { int fd = -1; g_autofree uint8_t *buf = NULL; uint8_t csum = 0; size_t readBytes = 0; size_t dataLen = 0; /* An example of a valid VPD record with one VPD-R resource and 2 fields. */ uint8_t fullVPDExample[] = { VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA, VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, PCI_VPD_RESOURCE_END_VAL }; dataLen = G_N_ELEMENTS(fullVPDExample) - 2; buf = g_malloc0(dataLen); if ((fd = virCreateAnonymousFile(fullVPDExample, dataLen)) < 0) return -1; readBytes = virPCIVPDReadVPDBytes(fd, buf, dataLen, 0, &csum); if (readBytes != dataLen) { virReportError(VIR_ERR_INTERNAL_ERROR, "The number of bytes read %zu is lower than expected %zu ", readBytes, dataLen); return -1; } if (csum) { virReportError(VIR_ERR_INTERNAL_ERROR, "The sum of all VPD bytes up to and including the checksum byte" "is equal to zero: 0x%02x", csum); return -1; } return 0; } static int testVirPCIVPDParseVPDStringResource(const void *opaque G_GNUC_UNUSED) { int fd = -1; uint8_t csum = 0; size_t dataLen = 0; bool result = false; g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1); const char *expectedValue = "testname"; const uint8_t stringResExample[] = { VPD_STRING_RESOURCE_EXAMPLE_DATA }; dataLen = G_N_ELEMENTS(stringResExample); if ((fd = virCreateAnonymousFile(stringResExample, dataLen)) < 0) return -1; result = virPCIVPDParseVPDLargeResourceString(fd, 0, dataLen, &csum, res); VIR_FORCE_CLOSE(fd); if (!result) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Could not parse the example resource."); return -1; } if (STRNEQ(expectedValue, res->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, "Unexpected string resource value: %s, expected: %s", res->name, expectedValue); return -1; } return 0; } static int testVirPCIVPDValidateExampleReadOnlyFields(virPCIVPDResource *res) { const char *expectedName = "testname"; virPCIVPDResourceCustom *custom = NULL; if (STRNEQ(res->name, expectedName)) { virReportError(VIR_ERR_INTERNAL_ERROR, "Unexpected string resource value: %s, expected: %s", res->name, expectedName); return -1; } if (!res->ro) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-only keywords are missing from the VPD resource."); return -1; } if (STRNEQ_NULLABLE(res->ro->part_number, "42")) { return -1; } else if (STRNEQ_NULLABLE(res->ro->change_level, "4242")) { return -1; } if (!res->ro->vendor_specific) return -1; custom = g_ptr_array_index(res->ro->vendor_specific, 0); if (custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "EX")) return -1; return 0; } static int testVirPCIVPDParseFullVPD(const void *opaque G_GNUC_UNUSED) { int fd = -1; size_t dataLen = 0; g_autoptr(virPCIVPDResource) res = NULL; /* Note: Custom fields are supposed to be freed by the resource cleanup code. */ virPCIVPDResourceCustom *custom = NULL; const uint8_t fullVPDExample[] = { VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA, VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, VPD_W_FIELDS_EXAMPLE_HEADER, VPD_W_EXAMPLE_FIELDS, PCI_VPD_RESOURCE_END_VAL }; dataLen = G_N_ELEMENTS(fullVPDExample); if ((fd = virCreateAnonymousFile(fullVPDExample, dataLen)) < 0) return -1; res = virPCIVPDParse(fd); VIR_FORCE_CLOSE(fd); if (!res) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The resource pointer is NULL after parsing which is unexpected"); return -1; } if (!res->ro) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-only keywords are missing from the VPD resource."); return -1; } else if (!res->rw) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-write keywords are missing from the VPD resource."); return -1; } if (testVirPCIVPDValidateExampleReadOnlyFields(res)) return -1; if (STRNEQ_NULLABLE(res->rw->asset_tag, "ID42")) return -1; if (!res->rw->vendor_specific) return -1; custom = g_ptr_array_index(res->rw->vendor_specific, 0); if (custom->idx != 'Z' || STRNEQ_NULLABLE(custom->value, "42")) return -1; if (!res->rw->system_specific) return -1; custom = g_ptr_array_index(res->rw->system_specific, 0); if (custom->idx != 'F' || STRNEQ_NULLABLE(custom->value, "EX")) return -1; custom = g_ptr_array_index(res->rw->system_specific, 1); if (custom->idx != 'E' || STRNEQ_NULLABLE(custom->value, "")) return -1; custom = NULL; return 0; } static int testVirPCIVPDParseZeroLengthRW(const void *opaque G_GNUC_UNUSED) { int fd = -1; size_t dataLen = 0; g_autoptr(virPCIVPDResource) res = NULL; virPCIVPDResourceCustom *custom = NULL; /* The RW field has a zero length which means there is no more RW space left. */ const uint8_t fullVPDExample[] = { VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA, VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG, 0x08, 0x00, 'V', 'Z', 0x02, '4', '2', 'R', 'W', 0x00, PCI_VPD_RESOURCE_END_VAL }; dataLen = G_N_ELEMENTS(fullVPDExample); if ((fd = virCreateAnonymousFile(fullVPDExample, dataLen)) < 0) return -1; res = virPCIVPDParse(fd); VIR_FORCE_CLOSE(fd); if (!res) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The resource pointer is NULL after parsing which is unexpected"); return -1; } if (!res->ro) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-only keywords are missing from the VPD resource."); return -1; } else if (!res->rw) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-write keywords are missing from the VPD resource."); return -1; } if (testVirPCIVPDValidateExampleReadOnlyFields(res)) return -1; custom = g_ptr_array_index(res->rw->vendor_specific, 0); if (custom->idx != 'Z' || STRNEQ_NULLABLE(custom->value, "42")) return -1; custom = NULL; return 0; } static int testVirPCIVPDParseNoRW(const void *opaque G_GNUC_UNUSED) { int fd = -1; size_t dataLen = 0; g_autoptr(virPCIVPDResource) res = NULL; virPCIVPDResourceCustom *custom = NULL; /* The RW field has a zero length which means there is no more RW space left. */ const uint8_t fullVPDExample[] = { VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA, VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG, 0x05, 0x00, 'V', 'Z', 0x02, '4', '2', PCI_VPD_RESOURCE_END_VAL }; dataLen = G_N_ELEMENTS(fullVPDExample); if ((fd = virCreateAnonymousFile(fullVPDExample, dataLen)) < 0) return -1; res = virPCIVPDParse(fd); VIR_FORCE_CLOSE(fd); if (!res) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The resource pointer is NULL after parsing which is unexpected"); return -1; } if (!res->ro) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-only keywords are missing from the VPD resource."); return -1; } else if (!res->rw) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Read-write keywords are missing from the VPD resource."); return -1; } if (testVirPCIVPDValidateExampleReadOnlyFields(res)) return -1; custom = g_ptr_array_index(res->rw->vendor_specific, 0); if (custom->idx != 'Z' || STRNEQ_NULLABLE(custom->value, "42")) return -1; custom = NULL; return 0; } static int testVirPCIVPDParseFullVPDSkipInvalidKeywords(const void *opaque G_GNUC_UNUSED) { int fd = -1; size_t dataLen = 0; g_autoptr(virPCIVPDResource) res = NULL; const uint8_t fullVPDExample[] = { VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA, PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x25, 0x00, VPD_R_EXAMPLE_FIELDS, /* The keywords below (except for "RV") are invalid but will be skipped by the parser */ 0x07, 'A', 0x02, 0x00, 0x00, 'V', 0x07, 0x02, 0x00, 0x00, 'e', 'x', 0x02, 0x00, 0x00, 'R', 'V', 0x02, 0x9A, 0x00, PCI_VPD_RESOURCE_END_VAL }; dataLen = G_N_ELEMENTS(fullVPDExample); if ((fd = virCreateAnonymousFile(fullVPDExample, dataLen)) < 0) return -1; res = virPCIVPDParse(fd); VIR_FORCE_CLOSE(fd); if (!res) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The resource pointer is NULL after parsing which is unexpected."); return -1; } if (!res->ro) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The RO portion of the VPD resource is NULL."); return -1; } if (testVirPCIVPDValidateExampleReadOnlyFields(res)) return -1; return 0; } static int testVirPCIVPDParseFullVPDSkipInvalidValues(const void *opaque G_GNUC_UNUSED) { int fd = -1; size_t dataLen = 0; size_t i = 0; virPCIVPDResourceCustom *custom = NULL; g_autoptr(virPCIVPDResource) res = NULL; /* This example is based on real-world hardware which was programmed by the vendor with * invalid field values in both the RO section and RW section. The RO section contains * fields that are not valid per the spec but accepted by Libvirt as printable ASCII * characters. The RW field has a 0 length which means there is no more space in the * RW section. */ const uint8_t fullVPDExample[] = { 0x82, 0x23, 0x00, 0x48, 0x50, 0x20, 0x45, 0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x31, 0x47, 0x62, 0x20, 0x32, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x33, 0x36, 0x31, 0x69, 0x20, 0x41, 0x64, 0x61, 0x70, 0x74, 0x65, 0x72, 0x90, 0x42, 0x00, 0x50, 0x4e, 0x03, 0x4e, 0x2f, 0x41, 0x45, 0x43, 0x03, 0x4e, 0x2f, 0x41, 0x53, 0x4e, 0x03, 0x4e, 0x2f, 0x41, 0x56, 0x30, 0x29, 0x34, 0x57, 0x2f, 0x31, 0x57, 0x20, 0x50, 0x43, 0x49, 0x65, 0x47, 0x32, 0x78, 0x34, 0x20, 0x32, 0x70, 0x20, 0x31, 0x47, 0x62, 0x45, 0x20, 0x52, 0x4a, 0x34, 0x35, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x6c, 0x20, 0x69, 0x33, 0x35, 0x30, 0x20, 0x20, 0x20, 0x52, 0x56, 0x01, 0x63, 0x91, 0x47, 0x00, 0x56, 0x31, 0x06, 0x35, 0x2e, 0x37, 0x2e, 0x30, 0x36, 0x56, 0x33, 0x06, 0x32, 0x2e, 0x38, 0x2e, 0x32, 0x30, 0x56, 0x36, 0x06, 0x31, 0x2e, 0x35, 0x2e, 0x33, 0x35, 0x59, 0x41, 0x03, 0x4e, 0x2f, 0x41, 0x59, 0x42, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x59, 0x43, 0x0D, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 'R', 'W', 0x00, 0x78, }; dataLen = G_N_ELEMENTS(fullVPDExample); if ((fd = virCreateAnonymousFile(fullVPDExample, dataLen)) < 0) return -1; res = virPCIVPDParse(fd); VIR_FORCE_CLOSE(fd); if (!res) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The resource pointer is NULL after parsing which is unexpected."); return -1; } /* Some values in the read-write section are invalid but parsing should succeed * considering the parser is implemented to be graceful about invalid keywords and * values. */ if (!res->ro) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The RO section consisting of only invalid fields got parsed successfully"); return -1; } if (!res->rw) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Could not successfully parse an RW section with some invalid fields"); return -1; } if (!STREQ_NULLABLE(res->ro->change_level, "N/A")) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Could not parse a change level field with acceptable contents"); return -1; } if (!STREQ_NULLABLE(res->ro->part_number, "N/A")) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Could not parse a part number field with acceptable contents"); return -1; } if (!STREQ_NULLABLE(res->ro->serial_number, "N/A")) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Could not parse a serial number with acceptable contents"); return -1; } if (!STREQ_NULLABLE(res->rw->asset_tag, "N/A")) { /* The asset tag has an invalid value in this case so it should be NULL. */ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Could not parse an asset tag with acceptable contents"); return -1; } if (res->rw->vendor_specific->len != 3) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "The number of parsed vendor fields is not equal to the expected number."); return -1; } if (res->rw->system_specific->len > 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Successfully parsed some systems-specific fields while none are valid"); return -1; } for (i = 0; i < res->rw->vendor_specific->len; ++i) { custom = ((virPCIVPDResourceCustom*)g_ptr_array_index(res->rw->vendor_specific, i)); if (custom->idx == '1') { if (STRNEQ(custom->value, "5.7.06")) { return -1; } } else if (custom->idx == '3') { if (STRNEQ(custom->value, "2.8.20")) { return -1; } } else if (custom->idx == '6') { if (STRNEQ(custom->value, "1.5.35")) { return -1; } } } return 0; } static int testVirPCIVPDParseFullVPDInvalid(const void *opaque G_GNUC_UNUSED) { int fd = -1; size_t dataLen = 0; # define VPD_INVALID_ZERO_BYTE \ 0x00 # define VPD_INVALID_STRING_HEADER_DATA_LONG \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x04, 0x00, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x05, 0x00, \ 'R', 'V', 0x02, 0xDA, 0x00, \ PCI_VPD_RESOURCE_END_VAL # define VPD_INVALID_STRING_HEADER_DATA_SHORT \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x0A, 0x00, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x05, 0x00, \ 'R', 'V', 0x02, 0xD4, 0x00, \ PCI_VPD_RESOURCE_END_VAL # define VPD_NO_VPD_R \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ PCI_VPD_RESOURCE_END_VAL # define VPD_R_NO_RV \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ VPD_R_FIELDS_EXAMPLE_HEADER, \ VPD_R_EXAMPLE_FIELDS, \ PCI_VPD_RESOURCE_END_VAL # define VPD_R_INVALID_RV \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ VPD_R_FIELDS_EXAMPLE_HEADER, \ VPD_R_EXAMPLE_FIELDS, \ VPD_R_EXAMPLE_INVALID_RV_FIELD, \ PCI_VPD_RESOURCE_END_VAL # define VPD_R_INVALID_RV_ZERO_LENGTH \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x14, 0x00, \ VPD_R_EXAMPLE_FIELDS, \ 'R', 'V', 0x00, \ PCI_VPD_RESOURCE_END_VAL /* The RW key is not expected in a VPD-R record. */ # define VPD_R_UNEXPECTED_RW_IN_VPD_R_KEY \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ VPD_STRING_RESOURCE_EXAMPLE_DATA, \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x1B, 0x00, \ VPD_R_EXAMPLE_FIELDS, \ 'R', 'W', 0x02, 0x00, 0x00, \ 'R', 'V', 0x02, 0x81, 0x00, \ PCI_VPD_RESOURCE_END_VAL # define VPD_INVALID_STRING_RESOURCE_VALUE \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ 't', 0x03, 's', 't', 'n', 'a', 'm', 'e', \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x0A, 0x00, \ 'S', 'N', 0x02, 0x04, 0x02, \ 'R', 'V', 0x02, 0x8A, 0x00, \ PCI_VPD_RESOURCE_END_VAL /* The SN field has a length field that goes past the resource boundaries. */ # define VPD_INVALID_SN_FIELD_LENGTH \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ 't', 'e', 's', 't', 'n', 'a', 'm', 'e', \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x0A, 0x00, \ 'S', 'N', 0x42, 0x04, 0x02, \ 'R', 'V', 0x02, 0xE8, 0x00, \ PCI_VPD_RESOURCE_END_VAL /* The RV field is not the last one in VPD-R while the checksum is valid. */ # define VPD_INVALID_RV_NOT_LAST \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ 't', 'e', 's', 't', 'n', 'a', 'm', 'e', \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x0A, 0x00, \ 'R', 'V', 0x02, 0xD1, 0x00, \ 'S', 'N', 0x02, 0x04, 0x02, \ PCI_VPD_RESOURCE_END_VAL # define VPD_INVALID_RW_NOT_LAST \ VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA, \ VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, \ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG, 0x08, 0x00, \ 'R', 'W', 0x00, \ 'V', 'Z', 0x02, '4', '2', \ PCI_VPD_RESOURCE_END_VAL # define TEST_INVALID_VPD(invalidVPD) \ do { \ g_autoptr(virPCIVPDResource) res = NULL; \ const uint8_t testCase[] = { invalidVPD }; \ dataLen = G_N_ELEMENTS(testCase); \ if ((fd = virCreateAnonymousFile(testCase, dataLen)) < 0) \ return -1; \ if ((res = virPCIVPDParse(fd))) { \ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", \ "Successfully parsed an invalid VPD - this is not expected"); \ return -1; \ } \ VIR_FORCE_CLOSE(fd); \ } while (0); TEST_INVALID_VPD(VPD_INVALID_ZERO_BYTE); TEST_INVALID_VPD(VPD_INVALID_STRING_HEADER_DATA_SHORT); TEST_INVALID_VPD(VPD_INVALID_STRING_HEADER_DATA_LONG); TEST_INVALID_VPD(VPD_NO_VPD_R); TEST_INVALID_VPD(VPD_R_NO_RV); TEST_INVALID_VPD(VPD_R_INVALID_RV); TEST_INVALID_VPD(VPD_R_INVALID_RV_ZERO_LENGTH); TEST_INVALID_VPD(VPD_R_UNEXPECTED_RW_IN_VPD_R_KEY); TEST_INVALID_VPD(VPD_INVALID_STRING_RESOURCE_VALUE); TEST_INVALID_VPD(VPD_INVALID_SN_FIELD_LENGTH); TEST_INVALID_VPD(VPD_INVALID_RV_NOT_LAST); TEST_INVALID_VPD(VPD_INVALID_RW_NOT_LAST); return 0; } static int mymain(void) { int ret = 0; if (virTestRun("Basic functionality of virPCIVPDResource ", testPCIVPDResourceBasic, NULL) < 0) ret = -1; if (virTestRun("Custom field index comparison", testPCIVPDResourceCustomCompareIndex, NULL) < 0) ret = -1; if (virTestRun("Custom field value insertion and updates ", testPCIVPDResourceCustomUpsertValue, NULL) < 0) ret = -1; if (virTestRun("Valid text values ", testPCIVPDIsValidTextValue, NULL) < 0) ret = -1; if (virTestRun("Determining a field value format by a key ", testPCIVPDGetFieldValueFormat, NULL) < 0) ret = -1; if (virTestRun("Reading VPD bytes ", testVirPCIVPDReadVPDBytes, NULL) < 0) ret = -1; if (virTestRun("Parsing VPD string resources ", testVirPCIVPDParseVPDStringResource, NULL) < 0) ret = -1; if (virTestRun("Parsing a VPD resource with a zero-length RW ", testVirPCIVPDParseZeroLengthRW, NULL) < 0) ret = -1; if (virTestRun("Parsing a VPD resource without an RW ", testVirPCIVPDParseNoRW, NULL) < 0) ret = -1; if (virTestRun("Parsing a VPD resource with an invalid values ", testVirPCIVPDParseFullVPDSkipInvalidValues, NULL) < 0) ret = -1; if (virTestRun("Parsing a VPD resource with an invalid keyword ", testVirPCIVPDParseFullVPDSkipInvalidKeywords, NULL) < 0) ret = -1; if (virTestRun("Parsing VPD resources from a full VPD ", testVirPCIVPDParseFullVPD, NULL) < 0) ret = -1; if (virTestRun("Parsing invalid VPD records ", testVirPCIVPDParseFullVPDInvalid, NULL) < 0) ret = -1; return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } VIR_TEST_MAIN(mymain) #else /* ! __linux__ */ int main(void) { return EXIT_AM_SKIP; } #endif /* ! __linux__ */