1 #include "fru_device/fru_utils.hpp" 2 3 #include <algorithm> 4 #include <array> 5 #include <iterator> 6 7 #include "gmock/gmock.h" 8 #include "gtest/gtest.h" 9 10 using ::testing::Pair; 11 using ::testing::UnorderedElementsAre; 12 13 extern "C" 14 { 15 // Include for I2C_SMBUS_BLOCK_MAX 16 #include <linux/i2c.h> 17 } 18 19 static constexpr size_t blockSize = I2C_SMBUS_BLOCK_MAX; 20 21 TEST(ValidateHeaderTest, InvalidFruVersionReturnsFalse) 22 { 23 // Validates the FruVersion is checked for the only legal value. 24 constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = { 25 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 26 27 EXPECT_FALSE(validateHeader(fruHeader)); 28 } 29 30 TEST(ValidateHeaderTest, InvalidReservedReturnsFalse) 31 { 32 // Validates the reserved bit(7:4) of first bytes. 33 constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = { 34 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 35 36 EXPECT_FALSE(validateHeader(fruHeader)); 37 } 38 39 TEST(ValidateHeaderTest, InvalidPaddingReturnsFalse) 40 { 41 // Validates the padding byte (7th byte). 42 constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = { 43 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}; 44 45 EXPECT_FALSE(validateHeader(fruHeader)); 46 } 47 48 TEST(ValidateHeaderTest, InvalidChecksumReturnsFalse) 49 { 50 // Validates the checksum, check for incorrect value. 51 constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = { 52 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00}; 53 54 EXPECT_FALSE(validateHeader(fruHeader)); 55 } 56 57 TEST(ValidateHeaderTest, ValidChecksumReturnsTrue) 58 { 59 // Validates the checksum, check for correct value. 60 constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = { 61 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5}; 62 63 EXPECT_TRUE(validateHeader(fruHeader)); 64 } 65 66 TEST(VerifyOffsetTest, EmptyFruDataReturnsFalse) 67 { 68 // Validates the FruData size is checked for non empty. 69 std::vector<uint8_t> fruData = {}; 70 71 EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaChassis, 0)); 72 } 73 74 TEST(VerifyOffsetTest, AreaOutOfRangeReturnsFalse) 75 { 76 // Validates the FruArea value, check if it is within range. 77 const std::vector<uint8_t> fruData = {0x01, 0x00, 0x00, 0x00, 0x00, 78 0x00, 0x00, 0x00, 0x00}; 79 80 unsigned int areaOutOfRange = 8; 81 EXPECT_FALSE( 82 verifyOffset(fruData, static_cast<fruAreas>(areaOutOfRange), 0)); 83 } 84 85 TEST(VerifyOffsetTest, OverlapNextAreaReturnsFalse) 86 { 87 // Validates the Overlap of offsets with overlapped values. 88 const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x02, 0x03, 89 0x04, 0x00, 0x00, 0x00}; 90 91 EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaChassis, 2)); 92 } 93 94 TEST(VerifyOffsetTest, OverlapPrevAreaReturnsFalse) 95 { 96 // Validates the Overlap of offsets with overlapped values. 97 const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x03, 0x02, 98 0x07, 0x00, 0x00, 0x00}; 99 100 EXPECT_FALSE(verifyOffset(fruData, fruAreas::fruAreaProduct, 2)); 101 } 102 103 TEST(VerifyOffsetTest, ValidInputDataNoOverlapReturnsTrue) 104 { 105 // Validates all inputs with expected value and no overlap. 106 const std::vector<uint8_t> fruData = {0x01, 0x00, 0x01, 0x02, 0x03, 107 0x04, 0x00, 0x00, 0x00}; 108 109 EXPECT_TRUE(verifyOffset(fruData, fruAreas::fruAreaChassis, 1)); 110 } 111 112 TEST(VerifyChecksumTest, EmptyInput) 113 { 114 std::vector<uint8_t> data = {}; 115 116 EXPECT_EQ(calculateChecksum(data), 0); 117 } 118 119 TEST(VerifyChecksumTest, SingleOneInput) 120 { 121 std::vector<uint8_t> data(1, 1); 122 123 EXPECT_EQ(calculateChecksum(data), 255); 124 } 125 126 TEST(VerifyChecksumTest, AllOneInput) 127 { 128 std::vector<uint8_t> data(256, 1); 129 130 EXPECT_EQ(calculateChecksum(data), 0); 131 } 132 133 TEST(VerifyChecksumTest, WrapBoundaryLow) 134 { 135 std::vector<uint8_t> data = {255, 0}; 136 137 EXPECT_EQ(calculateChecksum(data), 1); 138 } 139 140 TEST(VerifyChecksumTest, WrapBoundaryExact) 141 { 142 std::vector<uint8_t> data = {255, 1}; 143 144 EXPECT_EQ(calculateChecksum(data), 0); 145 } 146 147 TEST(VerifyChecksumTest, WrapBoundaryHigh) 148 { 149 std::vector<uint8_t> data = {255, 2}; 150 151 EXPECT_EQ(calculateChecksum(data), 255); 152 } 153 154 int64_t getDataTempl(const std::vector<uint8_t>& data, off_t offset, 155 size_t length, uint8_t* outBuf) 156 { 157 if (offset >= static_cast<off_t>(data.size())) 158 { 159 return 0; 160 } 161 162 uint16_t idx = offset; 163 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 164 for (; idx < std::min(data.size(), offset + length); ++idx, ++outBuf) 165 { 166 *outBuf = data[idx]; 167 } 168 169 return idx - offset; 170 } 171 172 TEST(FRUReaderTest, ReadData) 173 { 174 std::vector<uint8_t> data = {}; 175 data.reserve(blockSize * 2); 176 for (size_t i = 0; i < blockSize * 2; i++) 177 { 178 data.push_back(i); 179 } 180 std::array<uint8_t, blockSize * 2> rdbuf{}; 181 auto getData = [&data](auto o, auto l, auto* b) { 182 return getDataTempl(data, o, l, b); 183 }; 184 FRUReader reader(getData); 185 186 EXPECT_EQ(reader.read(0, data.size(), rdbuf.data()), 187 static_cast<ssize_t>(data.size())); 188 EXPECT_TRUE(std::equal(rdbuf.begin(), rdbuf.end(), data.begin())); 189 for (size_t i = 0; i < blockSize * 2; i++) 190 { 191 EXPECT_EQ(reader.read(i, 1, rdbuf.data()), 1); 192 EXPECT_EQ(rdbuf[i], i); 193 } 194 EXPECT_EQ(reader.read(blockSize - 1, 2, rdbuf.data()), 2); 195 EXPECT_EQ(rdbuf[0], blockSize - 1); 196 EXPECT_EQ(rdbuf[1], blockSize); 197 } 198 199 TEST(FRUReaderTest, StartPastUnknownEOF) 200 { 201 const std::vector<uint8_t> data = {}; 202 auto getData = [&data](auto o, auto l, auto* b) { 203 return getDataTempl(data, o, l, b); 204 }; 205 FRUReader reader(getData); 206 207 EXPECT_EQ(reader.read(1, 1, nullptr), 0); 208 } 209 210 TEST(FRUReaderTest, StartPastKnownEOF) 211 { 212 std::vector<uint8_t> data = {}; 213 data.resize(blockSize / 2); 214 std::array<uint8_t, blockSize> blockData{}; 215 auto getData = [&data](auto o, auto l, auto* b) { 216 return getDataTempl(data, o, l, b); 217 }; 218 FRUReader reader(getData); 219 220 EXPECT_EQ(reader.read(0, blockSize, blockData.data()), 221 static_cast<ssize_t>(data.size())); 222 EXPECT_EQ(reader.read(data.size(), 1, nullptr), 0); 223 EXPECT_EQ(reader.read(data.size() + 1, 1, nullptr), 0); 224 EXPECT_EQ(reader.read(blockSize, 1, nullptr), 0); 225 EXPECT_EQ(reader.read(blockSize + 1, 1, nullptr), 0); 226 } 227 228 TEST(FRUReaderTest, DecreasingEOF) 229 { 230 const std::vector<uint8_t> data = {}; 231 auto getData = [&data](auto o, auto l, auto* b) { 232 return getDataTempl(data, o, l, b); 233 }; 234 FRUReader reader(getData); 235 236 EXPECT_EQ(reader.read(blockSize * 2, 1, nullptr), 0); 237 EXPECT_EQ(reader.read(blockSize + (blockSize / 2), 1, nullptr), 0); 238 EXPECT_EQ(reader.read(blockSize, 1, nullptr), 0); 239 EXPECT_EQ(reader.read(blockSize / 2, 1, nullptr), 0); 240 EXPECT_EQ(reader.read(0, 1, nullptr), 0); 241 } 242 243 TEST(FRUReaderTest, CacheHit) 244 { 245 std::vector<uint8_t> data = {'X'}; 246 std::array<uint8_t, blockSize> read1{}; 247 std::array<uint8_t, blockSize> read2{}; 248 auto getData = [&data](auto o, auto l, auto* b) { 249 return getDataTempl(data, o, l, b); 250 }; 251 FRUReader reader(getData); 252 253 // cache hit should return the same data for the second read even if we 254 // change it behind the FRUReader's back after the first 255 EXPECT_EQ(reader.read(0, blockSize, read1.data()), 1); 256 data[0] = 'Y'; 257 EXPECT_EQ(reader.read(0, blockSize, read2.data()), 1); 258 EXPECT_EQ(read1[0], read2[0]); 259 } 260 261 TEST(FRUReaderTest, ReadPastKnownEnd) 262 { 263 const std::vector<uint8_t> data = {'X', 'Y'}; 264 std::array<uint8_t, blockSize> rdbuf{}; 265 auto getData = [&data](auto o, auto l, auto* b) { 266 return getDataTempl(data, o, l, b); 267 }; 268 FRUReader reader(getData); 269 270 EXPECT_EQ(reader.read(0, data.size(), rdbuf.data()), 271 static_cast<ssize_t>(data.size())); 272 EXPECT_EQ(rdbuf[0], 'X'); 273 EXPECT_EQ(rdbuf[1], 'Y'); 274 EXPECT_EQ(reader.read(1, data.size(), rdbuf.data()), 275 static_cast<ssize_t>(data.size() - 1)); 276 EXPECT_EQ(rdbuf[0], 'Y'); 277 } 278 279 TEST(FRUReaderTest, MultiBlockRead) 280 { 281 std::vector<uint8_t> data = {}; 282 data.resize(blockSize, 'X'); 283 data.resize(2 * blockSize, 'Y'); 284 std::array<uint8_t, 2 * blockSize> rdbuf{}; 285 auto getData = [&data](auto o, auto l, auto* b) { 286 return getDataTempl(data, o, l, b); 287 }; 288 FRUReader reader(getData); 289 290 EXPECT_EQ(reader.read(0, 2 * blockSize, rdbuf.data()), 291 static_cast<ssize_t>(2 * blockSize)); 292 EXPECT_TRUE(std::equal(rdbuf.begin(), rdbuf.end(), data.begin())); 293 } 294 295 TEST(FRUReaderTest, ShrinkingEEPROM) 296 { 297 std::vector<uint8_t> data = {}; 298 data.resize(3 * blockSize, 'X'); 299 std::array<uint8_t, blockSize> rdbuf{}; 300 auto getData = [&data](auto o, auto l, auto* b) { 301 return getDataTempl(data, o, l, b); 302 }; 303 FRUReader reader(getData); 304 305 EXPECT_EQ(reader.read(data.size() - 1, 2, rdbuf.data()), 1); 306 data.resize(blockSize); 307 EXPECT_EQ(reader.read(data.size() - 1, 2, rdbuf.data()), 1); 308 } 309 310 TEST(FindFRUHeaderTest, InvalidHeader) 311 { 312 const std::vector<uint8_t> data = {255, 16}; 313 off_t offset = 0; 314 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 315 auto getData = [&data](auto o, auto l, auto* b) { 316 return getDataTempl(data, o, l, b); 317 }; 318 FRUReader reader(getData); 319 320 EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset)); 321 } 322 323 TEST(FindFRUHeaderTest, NoData) 324 { 325 const std::vector<uint8_t> data = {}; 326 off_t offset = 0; 327 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 328 auto getData = [&data](auto o, auto l, auto* b) { 329 return getDataTempl(data, o, l, b); 330 }; 331 FRUReader reader(getData); 332 333 EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset)); 334 } 335 336 TEST(FindFRUHeaderTest, ValidHeader) 337 { 338 const std::vector<uint8_t> data = {0x01, 0x00, 0x01, 0x02, 339 0x03, 0x04, 0x00, 0xf5}; 340 off_t offset = 0; 341 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 342 auto getData = [&data](auto o, auto l, auto* b) { 343 return getDataTempl(data, o, l, b); 344 }; 345 FRUReader reader(getData); 346 347 EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset)); 348 EXPECT_EQ(0, offset); 349 } 350 351 TEST(FindFRUHeaderTest, TyanInvalidHeader) 352 { 353 std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0}; 354 data.resize(0x6000 + I2C_SMBUS_BLOCK_MAX); 355 off_t offset = 0; 356 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 357 auto getData = [&data](auto o, auto l, auto* b) { 358 return getDataTempl(data, o, l, b); 359 }; 360 FRUReader reader(getData); 361 362 EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset)); 363 } 364 365 TEST(FindFRUHeaderTest, TyanNoData) 366 { 367 const std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0}; 368 off_t offset = 0; 369 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 370 auto getData = [&data](auto o, auto l, auto* b) { 371 return getDataTempl(data, o, l, b); 372 }; 373 FRUReader reader(getData); 374 375 EXPECT_FALSE(findFRUHeader(reader, "error", blockData, offset)); 376 } 377 378 TEST(FindFRUHeaderTest, TyanValidHeader) 379 { 380 std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0}; 381 data.resize(0x6000); 382 constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = { 383 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5}; 384 copy(fruHeader.begin(), fruHeader.end(), back_inserter(data)); 385 386 off_t offset = 0; 387 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 388 auto getData = [&data](auto o, auto l, auto* b) { 389 return getDataTempl(data, o, l, b); 390 }; 391 FRUReader reader(getData); 392 393 EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset)); 394 EXPECT_EQ(0x6000, offset); 395 } 396 397 TEST(formatIPMIFRU, FullDecode) 398 { 399 const std::array<uint8_t, 176> bmcFru = { 400 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a, 0x19, 0x1f, 401 0x0f, 0xe6, 0xc6, 0x4e, 0x56, 0x49, 0x44, 0x49, 0x41, 0xc5, 0x50, 0x33, 402 0x38, 0x30, 0x39, 0xcd, 0x31, 0x35, 0x38, 0x33, 0x33, 0x32, 0x34, 0x38, 403 0x30, 0x30, 0x31, 0x35, 0x30, 0xd2, 0x36, 0x39, 0x39, 0x2d, 0x31, 0x33, 404 0x38, 0x30, 0x39, 0x2d, 0x30, 0x34, 0x30, 0x34, 0x2d, 0x36, 0x30, 0x30, 405 0xc0, 0x01, 0x01, 0xd6, 0x4d, 0x41, 0x43, 0x3a, 0x20, 0x33, 0x43, 0x3a, 406 0x36, 0x44, 0x3a, 0x36, 0x36, 0x3a, 0x31, 0x34, 0x3a, 0x43, 0x38, 0x3a, 407 0x37, 0x41, 0xc1, 0x3b, 0x01, 0x09, 0x19, 0xc6, 0x4e, 0x56, 0x49, 0x44, 408 0x49, 0x41, 0xc9, 0x50, 0x33, 0x38, 0x30, 0x39, 0x2d, 0x42, 0x4d, 0x43, 409 0xd2, 0x36, 0x39, 0x39, 0x2d, 0x31, 0x33, 0x38, 0x30, 0x39, 0x2d, 0x30, 410 0x34, 0x30, 0x34, 0x2d, 0x36, 0x30, 0x30, 0xc4, 0x41, 0x45, 0x2e, 0x31, 411 0xcd, 0x31, 0x35, 0x38, 0x33, 0x33, 0x32, 0x34, 0x38, 0x30, 0x30, 0x31, 412 0x35, 0x30, 0xc0, 0xc4, 0x76, 0x30, 0x2e, 0x31, 0xc1, 0x00, 0x00, 0x00, 413 0x00, 0x00, 0x00, 0xb4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 414 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; 415 boost::container::flat_map<std::string, std::string> result; 416 ASSERT_EQ(formatIPMIFRU(bmcFru, result), resCodes::resOK); 417 418 EXPECT_THAT( 419 result, 420 UnorderedElementsAre( 421 Pair("BOARD_FRU_VERSION_ID", ""), Pair("BOARD_INFO_AM1", "01"), 422 Pair("BOARD_INFO_AM2", "MAC: 3C:6D:66:14:C8:7A"), 423 Pair("BOARD_LANGUAGE_CODE", "25"), 424 Pair("BOARD_MANUFACTURER", "NVIDIA"), 425 Pair("BOARD_MANUFACTURE_DATE", "20240831T055100Z"), 426 Pair("BOARD_PART_NUMBER", "699-13809-0404-600"), 427 Pair("BOARD_PRODUCT_NAME", "P3809"), 428 Pair("BOARD_SERIAL_NUMBER", "1583324800150"), 429 Pair("Common_Format_Version", "1"), Pair("PRODUCT_ASSET_TAG", ""), 430 Pair("MAC_BOARD_INFO_AM2", "3C:6D:66:14:C8:7A"), 431 Pair("PRODUCT_FRU_VERSION_ID", "v0.1"), 432 Pair("PRODUCT_LANGUAGE_CODE", "25"), 433 Pair("PRODUCT_MANUFACTURER", "NVIDIA"), 434 Pair("PRODUCT_PART_NUMBER", "699-13809-0404-600"), 435 Pair("PRODUCT_PRODUCT_NAME", "P3809-BMC"), 436 Pair("PRODUCT_SERIAL_NUMBER", "1583324800150"), 437 Pair("PRODUCT_VERSION", "AE.1"))); 438 } 439