blob: 7fde4e4a479247e837617cda7cd69e4a66391db9 [file] [log] [blame] [edit]
// Copied from
// https://github.com/openbmc/entity-manager/blob/master/test/test_fru-utils.cpp.
#include "fru_utils.hpp"
#include <algorithm>
#include <array>
#include <cstring>
#include <iterator>
#include "gtest/gtest.h"
TEST(ValidateHeaderTest, InvalidFruVersionReturnsFalse)
{
// Validates the FruVersion is checked for the only legal value.
constexpr std::array<uint8_t, SMBUS_BLOCK_MAX> fruHeader = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
EXPECT_FALSE(validateHeader(fruHeader));
}
TEST(ValidateHeaderTest, InvalidReservedReturnsFalse)
{
// Validates the reserved bit(7:4) of first bytes.
constexpr std::array<uint8_t, SMBUS_BLOCK_MAX> fruHeader = {
0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
EXPECT_FALSE(validateHeader(fruHeader));
}
TEST(ValidateHeaderTest, InvalidPaddingReturnsFalse)
{
// Validates the padding byte (7th byte).
constexpr std::array<uint8_t, SMBUS_BLOCK_MAX> fruHeader = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00};
EXPECT_FALSE(validateHeader(fruHeader));
}
TEST(ValidateHeaderTest, InvalidChecksumReturnsFalse)
{
// Validates the checksum, check for incorrect value.
constexpr std::array<uint8_t, SMBUS_BLOCK_MAX> fruHeader = {
0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00};
EXPECT_FALSE(validateHeader(fruHeader));
}
TEST(ValidateHeaderTest, ValidChecksumReturnsTrue)
{
// Validates the checksum, check for correct value.
constexpr std::array<uint8_t, SMBUS_BLOCK_MAX> fruHeader = {
0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5};
EXPECT_TRUE(validateHeader(fruHeader));
}
TEST(VerifyOffsetTest, EmptyFruDataReturnsFalse)
{
// Validates the FruData size is checked for non empty.
std::vector<uint8_t> fruData = {};
EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaChassis, 0));
}
TEST(VerifyOffsetTest, AreaOutOfRangeReturnsFalse)
{
// Validates the FruArea value, check if it is within range.
const std::vector<uint8_t> fruData = {0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
unsigned int areaOutOfRange = 8;
EXPECT_FALSE(
verifyOffset(fruData, static_cast<fruAreas>(areaOutOfRange), 0));
}
TEST(VerifyOffsetTest, OverlapNextAreaReturnsFalse)
{
// Validates the Overlap of offsets with overlapped values.
const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x02, 0x03,
0x04, 0x00, 0x00, 0x00};
EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaChassis, 2));
}
TEST(VerifyOffsetTest, OverlapPrevAreaReturnsFalse)
{
// Validates the Overlap of offsets with overlapped values.
const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x03, 0x02,
0x07, 0x00, 0x00, 0x00};
EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaProduct, 2));
}
TEST(VerifyOffsetTest, ValidInputDataNoOverlapReturnsTrue)
{
// Validates all inputs with expected value and no overlap.
const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x02, 0x03,
0x04, 0x00, 0x00, 0x00};
EXPECT_TRUE(verifyOffset(fruData, fruAreas::fruAreaChassis, 1));
}
TEST(VerifyChecksumTest, EmptyInput)
{
std::vector<uint8_t> data = {};
EXPECT_EQ(calculateChecksum(data), 0);
}
TEST(VerifyChecksumTest, SingleOneInput)
{
std::vector<uint8_t> data(1, 1);
EXPECT_EQ(calculateChecksum(data), 255);
}
TEST(VerifyChecksumTest, AllOneInput)
{
std::vector<uint8_t> data(256, 1);
EXPECT_EQ(calculateChecksum(data), 0);
}
TEST(VerifyChecksumTest, WrapBoundaryLow)
{
std::vector<uint8_t> data = {255, 0};
EXPECT_EQ(calculateChecksum(data), 1);
}
TEST(VerifyChecksumTest, WrapBoundaryExact)
{
std::vector<uint8_t> data = {255, 1};
EXPECT_EQ(calculateChecksum(data), 0);
}
TEST(VerifyChecksumTest, WrapBoundaryHigh)
{
std::vector<uint8_t> data = {255, 2};
EXPECT_EQ(calculateChecksum(data), 255);
}
int64_t getDataTempl(const std::vector<uint8_t>& data, off_t offset,
size_t length, uint8_t* outBuf)
{
if (offset >= static_cast<off_t>(data.size()))
{
return 0;
}
uint16_t idx = offset;
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for (; idx < std::min(static_cast<uint32_t>(data.size()),
static_cast<uint32_t>(offset + length));
++idx, ++outBuf)
{
*outBuf = data[idx];
}
return idx - offset;
}
TEST(FindFRUHeaderTest, InvalidHeader)
{
const std::vector<uint8_t> data = {255, 16};
off_t offset = 0;
std::array<uint8_t, SMBUS_BLOCK_MAX> blockData{};
auto getData = [&data](auto o, auto l, auto* b) {
return getDataTempl(data, o, l, b);
};
FRUReader reader(getData);
EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
}
TEST(FindFRUHeaderTest, NoData)
{
const std::vector<uint8_t> data = {};
off_t offset = 0;
std::array<uint8_t, SMBUS_BLOCK_MAX> blockData{};
auto getData = [&data](auto o, auto l, auto* b) {
return getDataTempl(data, o, l, b);
};
FRUReader reader(getData);
EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
}
TEST(FindFRUHeaderTest, ValidHeader)
{
const std::vector<uint8_t> data = {0x01, 0x00, 0x01, 0x02,
0x03, 0x04, 0x00, 0xf5};
off_t offset = 0;
std::array<uint8_t, SMBUS_BLOCK_MAX> blockData{};
auto getData = [&data](auto o, auto l, auto* b) {
return getDataTempl(data, o, l, b);
};
FRUReader reader(getData);
EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset));
EXPECT_EQ(0, offset);
}
TEST(FindFRUHeaderTest, TyanInvalidHeader)
{
std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
data.resize(0x6000 + SMBUS_BLOCK_MAX);
off_t offset = 0;
std::array<uint8_t, SMBUS_BLOCK_MAX> blockData{};
auto getData = [&data](auto o, auto l, auto* b) {
return getDataTempl(data, o, l, b);
};
FRUReader reader(getData);
EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
}
TEST(FindFRUHeaderTest, TyanNoData)
{
const std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
off_t offset = 0;
std::array<uint8_t, SMBUS_BLOCK_MAX> blockData{};
auto getData = [&data](auto o, auto l, auto* b) {
return getDataTempl(data, o, l, b);
};
FRUReader reader(getData);
EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset));
}
TEST(FindFRUHeaderTest, TyanValidHeader)
{
std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
data.resize(0x6000);
constexpr std::array<uint8_t, SMBUS_BLOCK_MAX> fruHeader = {
0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5};
copy(fruHeader.begin(), fruHeader.end(), back_inserter(data));
off_t offset = 0;
std::array<uint8_t, SMBUS_BLOCK_MAX> blockData{};
auto getData = [&data](auto o, auto l, auto* b) {
return getDataTempl(data, o, l, b);
};
FRUReader reader(getData);
EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset));
EXPECT_EQ(0x6000, offset);
}
TEST(FormatIPMIFRUTest, EmptyFruReturnsError)
{
std::vector<uint8_t> fruBytes = {};
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resErr);
}
TEST(FormatIPMIFRUTest, YCCapCaseSetsCxlDevice)
{
std::vector<uint8_t> fruBytes = {
// Common Header (8 bytes)
0x01, // Version
0x00, // Internal offset
0x00, // Chassis offset
0x00, // Board offset
0x01, // Product offset (*8 = 8)
0x00, // Multirecord offset
0x00, // Pad
0xfe, // Header checksum
// Product Area (starts at 8)
0x01, // Format version
0x05, // Area length (*8 = 40 bytes)
0x19, // Language code (english)
((3 << 6) | 3), 'F', 'O', 'O', // Manufacturer: "FOO"
((3 << 6) | 7), 'Y', '-', 'C', 'A', 'B', 'L',
'E', // Product Name: "Y-CABLE"
((3 << 6) | 3), '1', '2', '3', // Part Number
((3 << 6) | 3), '4', '5', '6', // Version
((3 << 6) | 3), '7', '8', '9', // Serial Number
((3 << 6) | 3), 'A', 'B', 'C', // Asset Tag
((3 << 6) | 3), 'D', 'E', 'F', // FRU Version ID
0xc1, // end of fields
0, 0, 0, // padding
0 // checksum placeholder
};
fruBytes.resize(48);
fruBytes[47] =
calculateChecksum(fruBytes.begin() + 8, fruBytes.begin() + 47);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["PRODUCT_PRODUCT_NAME"], "Y-CABLE");
EXPECT_EQ(result["cxl_device"], "true");
}
TEST(FormatIPMIFRUTest, YCSmallCaseSetsCxlDevice)
{
std::vector<uint8_t> fruBytes = {
// Common Header (8 bytes)
0x01, // Version
0x00, // Internal offset
0x00, // Chassis offset
0x00, // Board offset
0x01, // Product offset (*8 = 8)
0x00, // Multirecord offset
0x00, // Pad
0xfe, // Header checksum
// Product Area (starts at 8)
0x01, // Format version
0x05, // Area length (*8 = 40 bytes)
0x19, // Language code (english)
((3 << 6) | 3), 'F', 'O', 'O', // Manufacturer: "FOO"
((3 << 6) | 7), 'y', '-', 'c', 'a', 'b', 'l',
'e', // Product Name: "y-cable"
((3 << 6) | 3), '1', '2', '3', // Part Number
((3 << 6) | 3), '4', '5', '6', // Version
((3 << 6) | 3), '7', '8', '9', // Serial Number
((3 << 6) | 3), 'A', 'B', 'C', // Asset Tag
((3 << 6) | 3), 'D', 'E', 'F', // FRU Version ID
0xc1, // end of fields
0, 0, 0, // padding
0 // checksum placeholder
};
fruBytes.resize(48);
fruBytes[47] =
calculateChecksum(fruBytes.begin() + 8, fruBytes.begin() + 47);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["PRODUCT_PRODUCT_NAME"], "y-cable");
EXPECT_EQ(result["cxl_device"], "true");
}
TEST(FormatIPMIFRUTest, YCCamelCaseSetsCxlDevice)
{
std::vector<uint8_t> fruBytes = {
// Common Header (8 bytes)
0x01, // Version
0x00, // Internal offset
0x00, // Chassis offset
0x00, // Board offset
0x01, // Product offset (*8 = 8)
0x00, // Multirecord offset
0x00, // Pad
0xfe, // Header checksum
// Product Area (starts at 8)
0x01, // Format version
0x05, // Area length (*8 = 40 bytes)
0x19, // Language code (english)
((3 << 6) | 3), 'F', 'O', 'O', // Manufacturer: "FOO"
((3 << 6) | 7), 'Y', '-', 'C', 'a', 'b', 'l',
'e', // Product Name: "Y-Cable"
((3 << 6) | 3), '1', '2', '3', // Part Number
((3 << 6) | 3), '4', '5', '6', // Version
((3 << 6) | 3), '7', '8', '9', // Serial Number
((3 << 6) | 3), 'A', 'B', 'C', // Asset Tag
((3 << 6) | 3), 'D', 'E', 'F', // FRU Version ID
0xc1, // end of fields
0, 0, 0, // padding
0 // checksum placeholder
};
fruBytes.resize(48);
fruBytes[47] =
calculateChecksum(fruBytes.begin() + 8, fruBytes.begin() + 47);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["PRODUCT_PRODUCT_NAME"], "Y-Cable");
EXPECT_EQ(result["cxl_device"], "true");
}
TEST(FormatIPMIFRUTest, NormalProductNameDoesNotSetCxlDevice)
{
std::vector<uint8_t> fruBytes = {
// Common Header (8 bytes)
0x01, // Version
0x00, // Internal offset
0x00, // Chassis offset
0x00, // Board offset
0x01, // Product offset (*8 = 8)
0x00, // Multirecord offset
0x00, // Pad
0xfe, // Header checksum
// Product Area (starts at 8)
0x01, // Format version
0x05, // Area length (*8 = 40 bytes)
0x19, // Language code (english)
((3 << 6) | 3), 'F', 'O', 'O', // Manufacturer: "FOO"
((3 << 6) | 8), 'N', 'o', 'r', 'm', 'a', 'l', 'P',
'D', // Product Name: "NormalPD"
((3 << 6) | 3), '1', '2', '3', // Part Number
((3 << 6) | 3), '4', '5', '6', // Version
((3 << 6) | 3), '7', '8', '9', // Serial Number
((3 << 6) | 3), 'A', 'B', 'C', // Asset Tag
((3 << 6) | 3), 'D', 'E', 'F', // FRU Version ID
0xc1, // end of fields
0, 0, // padding
0 // checksum placeholder
};
fruBytes.resize(48);
fruBytes[47] =
calculateChecksum(fruBytes.begin() + 8, fruBytes.begin() + 47);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["PRODUCT_PRODUCT_NAME"], "NormalPD");
EXPECT_EQ(result["cxl_device"], "false");
}
TEST(FormatIPMIFRUTest, YCableP0P1SetCxlDevice)
{
const std::string_view product_names_under_test[] = {"P0", "P1"};
std::vector<uint8_t> fruBytes = {
// Common Header (8 bytes)
0x01, // Version
0x00, // Internal offset
0x00, // Chassis offset
0x01, // Board offset
0x00, // Product offset (*8 = 8)
0x00, // Multirecord offset
0x00, // Pad
0xfe, // Header checksum
// Board Area (starts at 8)
0x01, // Format version
0x04, // Area length (*8 = 32 bytes)
0x19, // Language code (english)
0x00, 0x00, 0x00, // MFG Date/Time
((3 << 6) | 3), 'F', 'O', 'O', // Manufacturer: "FOO"
((3 << 6) | 2), 'P', '0', // Product Name: "P0"/"P1"
((3 << 6) | 3), '7', '8', '9', // Serial Number
((3 << 6) | 3), '1', '2', '3', // Part Number
((3 << 6) | 3), 'D', 'E', 'F', // FRU Version ID
0xc1, // end of fields
0, 0, 0, 0, 0, // padding
0 // checksum placeholder
};
fruBytes.resize(40);
for (const auto& product_name : product_names_under_test)
{
fruBytes[20] = product_name[1];
fruBytes[39] =
calculateChecksum(fruBytes.begin() + 8, fruBytes.begin() + 39);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["BOARD_PRODUCT_NAME"], product_name);
EXPECT_EQ(result["cxl_device"], "true");
}
}
TEST(FormatIPMIFRUTest, YCableDcmioMxioSetCxlDevice)
{
const std::string_view product_names_under_test[] = {
"x16_GenZ_to_2x8_mxio_P0\0", "x16_GenZ_to_2x8_mxio_P1\0",
"x16_GenZ_to_2x8_dcmio_P0\0", "x16_GenZ_to_2x8_dcmio_P1\0"};
for (const auto& product_name : product_names_under_test)
{
const int product_name_offset = 19;
const int fru_size = 64;
std::vector<uint8_t> fruBytes = {
// Common Header (8 bytes)
0x01, // Version
0x00, // Internal offset
0x00, // Chassis offset
0x01, // Board offset
0x00, // Product offset (*8 = 8)
0x00, // Multirecord offset
0x00, // Pad
0xfe, // Header checksum
// Board Area (starts at 8)
0x01, // Format version
0x07, // Area length (*8 = 56 bytes)
0x19, // Language code (english)
0x00, 0x00, 0x00, // MFG Date/Time
((3 << 6) | 3), 'F', 'O', 'O', // Manufacturer: "FOO"
((3 << 6) | 25), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, // Product Name (placeholder)
((3 << 6) | 3), '7', '8', '9', // Serial Number
((3 << 6) | 3), '1', '2', '3', // Part Number
((3 << 6) | 3), 'D', 'E', 'F', // FRU Version ID
0xc1, // end of fields
0, 0, 0, 0, 0, 0, // padding
0 // checksum placeholder
};
fruBytes.resize(fru_size);
for (size_t idx = 0; idx < product_name.size(); idx++)
{
fruBytes[idx + product_name_offset] = product_name[idx];
}
fruBytes[fru_size - 1] = calculateChecksum(
fruBytes.begin() + 8, fruBytes.begin() + fru_size - 1);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["BOARD_PRODUCT_NAME"], product_name);
EXPECT_EQ(result["cxl_device"], "true");
}
}
TEST(FormatIPMIFRUTest, SupportBadAmphenolFruTest)
{
// This test specifically confirms support for known-bad Amphenol FRU
// information.
const int fru_size = 96;
std::vector<uint8_t> fruBytes = {
0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x0b, 0x19, 0x04,
0xf4, 0xef, 0xc9, 0x41, 0x6d, 0x70, 0x68, 0x65, 0x6e, 0x6f, 0x6c, 0x00,
0xc3, 0x50, 0x30, 0x00, 0xd6, 0x48, 0x47, 0x5a, 0x31, 0x36, 0x38, 0x2d,
0x31, 0x33, 0x34, 0x36, 0x20, 0x32, 0x35, 0x34, 0x36, 0x30, 0x30, 0x33,
0x31, 0x36, 0x00, 0xcb, 0x31, 0x31, 0x39, 0x36, 0x37, 0x37, 0x32, 0x2d,
0x30, 0x32, 0x00, 0xc0, 0xd7, 0x01, 0x50, 0x43, 0x49, 0x65, 0x2d, 0x62,
0x69, 0x66, 0x75, 0x72, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x78,
0x38, 0x78, 0x38, 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae};
fruBytes.resize(fru_size);
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resOK);
EXPECT_EQ(result["BOARD_PRODUCT_NAME"], "P0");
EXPECT_EQ(result["cxl_device"], "true");
}
TEST(FormatIPMIFRUTest, InvalidChecksumReturnsWarning)
{
std::vector<uint8_t> fruBytes = {
// Common Header
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xff,
// Chassis Info Area
0x01, 0x02, 0x03, 0xc1,
0x00, // Invalid checksum
};
std::map<std::string, std::string> result;
// The area is short, so it will cause a resErr instead of a resWarn.
// To properly test this, we need to provide a full area.
fruBytes.resize(8 + 2 * 8);
fruBytes[8 + 2 * 8 - 1] = 0; // set checksum to 0, which is likely wrong
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resWarn);
}
TEST(FormatIPMIFRUTest, TruncatedFruReturnsError)
{
const std::vector<uint8_t> fruBytes = {
// Common Header
0x01,
0x00,
0x01,
0x02,
0x03,
0x00,
0x00,
0xf9,
// Chassis Info Area (truncated)
0x01,
0x02,
0x03,
};
std::map<std::string, std::string> result;
EXPECT_EQ(formatIPMIFRU(fruBytes, result), resCodes::resErr);
}