1 #include "buffer.hpp" 2 #include "data_interface_mock.hpp" 3 4 #include <boost/endian/arithmetic.hpp> 5 #include <boost/endian/conversion.hpp> 6 7 #include <algorithm> 8 #include <array> 9 #include <cstdint> 10 #include <memory> 11 12 #include <gmock/gmock.h> 13 #include <gtest/gtest.h> 14 15 namespace bios_bmc_smm_error_logger 16 { 17 namespace 18 { 19 20 using ::testing::_; 21 using ::testing::ElementsAreArray; 22 using ::testing::InSequence; 23 using ::testing::Return; 24 25 class BufferTest : public ::testing::Test 26 { 27 protected: 28 BufferTest() : 29 dataInterfaceMock(std::make_unique<DataInterfaceMock>()), 30 dataInterfaceMockPtr(dataInterfaceMock.get()) 31 { 32 bufferImpl = std::make_unique<BufferImpl>(std::move(dataInterfaceMock)); 33 testInitializationHeader.bmcInterfaceVersion = testBmcInterfaceVersion; 34 testInitializationHeader.queueSize = testQueueSize; 35 testInitializationHeader.ueRegionSize = testUeRegionSize; 36 std::transform(testMagicNumber.begin(), testMagicNumber.end(), 37 testInitializationHeader.magicNumber.begin(), 38 [](uint32_t number) -> little_uint32_t { 39 return boost::endian::native_to_little(number); 40 }); 41 } 42 ~BufferTest() override = default; 43 44 // CircularBufferHeader size is 0x30, ensure the test region is bigger 45 static constexpr size_t testRegionSize = 0x200; 46 static constexpr uint32_t testBmcInterfaceVersion = 123; 47 static constexpr uint32_t testQueueSize = 0x200; 48 static constexpr uint16_t testUeRegionSize = 0x50; 49 static constexpr std::array<uint32_t, 4> testMagicNumber = { 50 0x12345678, 0x22345678, 0x32345678, 0x42345678}; 51 static constexpr size_t bufferHeaderSize = 52 sizeof(struct CircularBufferHeader); 53 54 struct CircularBufferHeader testInitializationHeader 55 {}; 56 57 std::unique_ptr<DataInterfaceMock> dataInterfaceMock; 58 DataInterfaceMock* dataInterfaceMockPtr; 59 std::unique_ptr<BufferImpl> bufferImpl; 60 }; 61 62 TEST_F(BufferTest, BufferInitializeEraseFail) 63 { 64 InSequence s; 65 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 66 .WillOnce(Return(testRegionSize)); 67 EXPECT_THROW( 68 try { 69 // Test too big of a proposed buffer compared to the memori size 70 uint16_t bigQueueSize = 0x201; 71 uint16_t bigUeRegionSize = 0x50; 72 bufferImpl->initialize(testBmcInterfaceVersion, bigQueueSize, 73 bigUeRegionSize, testMagicNumber); 74 } catch (const std::runtime_error& e) { 75 EXPECT_STREQ( 76 e.what(), 77 "[initialize] Proposed queue size '513' is bigger than the BMC's allocated MMIO region of '512'"); 78 throw; 79 }, 80 std::runtime_error); 81 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 82 83 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 84 .WillOnce(Return(testRegionSize)); 85 const std::vector<uint8_t> emptyArray(testQueueSize, 0); 86 // Return a smaller write than the intended testRegionSize to test the error 87 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 88 .WillOnce(Return(testQueueSize - 1)); 89 EXPECT_THROW( 90 try { 91 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 92 testUeRegionSize, testMagicNumber); 93 } catch (const std::runtime_error& e) { 94 EXPECT_STREQ(e.what(), "[initialize] Only erased '511'"); 95 throw; 96 }, 97 std::runtime_error); 98 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 99 100 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 101 .WillOnce(Return(testRegionSize)); 102 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 103 .WillOnce(Return(testQueueSize)); 104 // Return a smaller write than the intended initializationHeader to test the 105 // error 106 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)).WillOnce(Return(0)); 107 EXPECT_THROW( 108 try { 109 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 110 testUeRegionSize, testMagicNumber); 111 } catch (const std::runtime_error& e) { 112 EXPECT_STREQ(e.what(), 113 "[initialize] Only wrote '0' bytes of the header"); 114 throw; 115 }, 116 std::runtime_error); 117 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 118 } 119 120 TEST_F(BufferTest, BufferInitializePass) 121 { 122 InSequence s; 123 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 124 .WillOnce(Return(testRegionSize)); 125 const std::vector<uint8_t> emptyArray(testQueueSize, 0); 126 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 127 .WillOnce(Return(testQueueSize)); 128 129 uint8_t* testInitializationHeaderPtr = 130 reinterpret_cast<uint8_t*>(&testInitializationHeader); 131 EXPECT_CALL(*dataInterfaceMockPtr, 132 write(0, ElementsAreArray(testInitializationHeaderPtr, 133 bufferHeaderSize))) 134 .WillOnce(Return(bufferHeaderSize)); 135 EXPECT_NO_THROW( 136 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 137 testUeRegionSize, testMagicNumber)); 138 EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 139 } 140 141 TEST_F(BufferTest, BufferHeaderReadFail) 142 { 143 std::vector<std::uint8_t> testBytesRead{}; 144 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 145 .WillOnce(Return(testBytesRead)); 146 EXPECT_THROW( 147 try { 148 bufferImpl->readBufferHeader(); 149 } catch (const std::runtime_error& e) { 150 EXPECT_STREQ(e.what(), 151 "Buffer header read only read '0', expected '48'"); 152 throw; 153 }, 154 std::runtime_error); 155 } 156 157 TEST_F(BufferTest, BufferHeaderReadPass) 158 { 159 uint8_t* testInitializationHeaderPtr = 160 reinterpret_cast<uint8_t*>(&testInitializationHeader); 161 std::vector<uint8_t> testInitializationHeaderVector( 162 testInitializationHeaderPtr, 163 testInitializationHeaderPtr + bufferHeaderSize); 164 165 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 166 .WillOnce(Return(testInitializationHeaderVector)); 167 EXPECT_NO_THROW(bufferImpl->readBufferHeader()); 168 EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 169 } 170 171 TEST_F(BufferTest, BufferUpdateReadPtrFail) 172 { 173 // Return write size that is not 2 which is sizeof(little_uint16_t) 174 constexpr size_t wrongWriteSize = 1; 175 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 176 .WillOnce(Return(wrongWriteSize)); 177 EXPECT_THROW( 178 try { 179 bufferImpl->updateReadPtr(0); 180 } catch (const std::runtime_error& e) { 181 EXPECT_STREQ( 182 e.what(), 183 "[updateReadPtr] Wrote '1' bytes, instead of expected '3'"); 184 throw; 185 }, 186 std::runtime_error); 187 } 188 189 TEST_F(BufferTest, BufferUpdateReadPtrPass) 190 { 191 constexpr size_t expectedWriteSize = 3; 192 constexpr uint8_t expectedBmcReadPtrOffset = 0x21; 193 // Check that we truncate the highest 24bits 194 const uint32_t testNewReadPtr = 0x99881234; 195 const std::vector<uint8_t> expectedReadPtr{0x34, 0x12, 0x88}; 196 197 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 198 ElementsAreArray(expectedReadPtr))) 199 .WillOnce(Return(expectedWriteSize)); 200 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testNewReadPtr)); 201 202 auto cachedHeader = bufferImpl->getCachedBufferHeader(); 203 EXPECT_EQ(boost::endian::little_to_native(cachedHeader.bmcReadPtr), 204 0x881234); 205 } 206 207 TEST_F(BufferTest, BufferUpdateBmcFlagsFail) 208 { 209 // Return write size that is not 4 which is sizeof(little_uint32_t) 210 constexpr size_t wrongWriteSize = 1; 211 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 212 .WillOnce(Return(wrongWriteSize)); 213 EXPECT_THROW( 214 try { 215 bufferImpl->updateBmcFlags(static_cast<uint32_t>(BmcFlags::ready)); 216 } catch (const std::runtime_error& e) { 217 EXPECT_STREQ( 218 e.what(), 219 "[updateBmcFlags] Wrote '1' bytes, instead of expected '4'"); 220 throw; 221 }, 222 std::runtime_error); 223 } 224 225 TEST_F(BufferTest, BufferUpdateBmcFlagsPass) 226 { 227 constexpr size_t expectedWriteSize = 4; 228 constexpr uint8_t expectedBmcReadPtrOffset = 0x1d; 229 const std::vector<uint8_t> expectedNewBmcFlagsVector{0x04, 0x0, 0x0, 0x00}; 230 231 EXPECT_CALL(*dataInterfaceMockPtr, 232 write(expectedBmcReadPtrOffset, 233 ElementsAreArray(expectedNewBmcFlagsVector))) 234 .WillOnce(Return(expectedWriteSize)); 235 EXPECT_NO_THROW( 236 bufferImpl->updateBmcFlags(static_cast<uint32_t>(BmcFlags::ready))); 237 238 auto cachedHeader = bufferImpl->getCachedBufferHeader(); 239 EXPECT_EQ(boost::endian::little_to_native(cachedHeader.bmcFlags), 240 static_cast<uint32_t>(BmcFlags::ready)); 241 } 242 243 TEST_F(BufferTest, GetMaxOffsetQueueSizeFail) 244 { 245 InSequence s; 246 static constexpr size_t wrongQueueSize = testQueueSize - 1; 247 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 248 .WillOnce(Return(testRegionSize)); 249 const std::vector<uint8_t> emptyArray(wrongQueueSize, 0); 250 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 251 .WillOnce(Return(wrongQueueSize)); 252 253 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)) 254 .WillOnce(Return(bufferHeaderSize)); 255 EXPECT_NO_THROW( 256 bufferImpl->initialize(testBmcInterfaceVersion, wrongQueueSize, 257 testUeRegionSize, testMagicNumber)); 258 EXPECT_THROW( 259 try { 260 bufferImpl->getMaxOffset(); 261 } catch (const std::runtime_error& e) { 262 EXPECT_STREQ(e.what(), 263 "[getMaxOffset] runtime queueSize '511' did not match " 264 "compile-time queueSize '512'. This indicates that the" 265 " buffer was corrupted"); 266 throw; 267 }, 268 std::runtime_error); 269 } 270 271 TEST_F(BufferTest, GetMaxOffsetUeRegionSizeFail) 272 { 273 InSequence s; 274 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 275 .WillOnce(Return(testRegionSize)); 276 const std::vector<uint8_t> emptyArray(testQueueSize, 0); 277 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 278 .WillOnce(Return(testQueueSize)); 279 280 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)) 281 .WillOnce(Return(bufferHeaderSize)); 282 EXPECT_NO_THROW( 283 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 284 testUeRegionSize + 1, testMagicNumber)); 285 EXPECT_THROW( 286 try { 287 bufferImpl->getMaxOffset(); 288 } catch (const std::runtime_error& e) { 289 EXPECT_STREQ( 290 e.what(), 291 "[getMaxOffset] runtime ueRegionSize '81' did not match " 292 "compile-time ueRegionSize '80'. This indicates that the" 293 " buffer was corrupted"); 294 throw; 295 }, 296 std::runtime_error); 297 } 298 299 TEST_F(BufferTest, GetOffsetUeRegionSizeFail) 300 { 301 InSequence s; 302 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 303 .WillOnce(Return(testRegionSize)); 304 const std::vector<uint8_t> emptyArray(testQueueSize, 0); 305 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 306 .WillOnce(Return(testQueueSize)); 307 308 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)) 309 .WillOnce(Return(bufferHeaderSize)); 310 EXPECT_NO_THROW( 311 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 312 testUeRegionSize - 1, testMagicNumber)); 313 EXPECT_THROW( 314 try { 315 bufferImpl->getQueueOffset(); 316 } catch (const std::runtime_error& e) { 317 EXPECT_STREQ( 318 e.what(), 319 "[getQueueOffset] runtime ueRegionSize '79' did not match " 320 "compile-time ueRegionSize '80'. This indicates that the" 321 " buffer was corrupted"); 322 throw; 323 }, 324 std::runtime_error); 325 } 326 327 class BufferWraparoundReadTest : public BufferTest 328 { 329 protected: 330 BufferWraparoundReadTest() 331 { 332 initializeFuncMock(); 333 } 334 void initializeFuncMock() 335 { 336 // Initialize the memory and the cachedBufferHeader 337 InSequence s; 338 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 339 .WillOnce(Return(testRegionSize)); 340 const std::vector<uint8_t> emptyArray(testQueueSize, 0); 341 EXPECT_CALL(*dataInterfaceMockPtr, 342 write(0, ElementsAreArray(emptyArray))) 343 .WillOnce(Return(testQueueSize)); 344 345 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)) 346 .WillOnce(Return(bufferHeaderSize)); 347 EXPECT_NO_THROW( 348 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 349 testUeRegionSize, testMagicNumber)); 350 } 351 static constexpr size_t expectedWriteSize = 3; 352 static constexpr uint8_t expectedBmcReadPtrOffset = 0x21; 353 static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize; 354 355 static constexpr size_t testMaxOffset = 356 testQueueSize - testUeRegionSize - sizeof(struct CircularBufferHeader); 357 uint8_t* testInitializationHeaderPtr = 358 reinterpret_cast<uint8_t*>(&testInitializationHeader); 359 }; 360 361 TEST_F(BufferWraparoundReadTest, GetMaxOffsetPassTest) 362 { 363 EXPECT_EQ(bufferImpl->getMaxOffset(), testMaxOffset); 364 } 365 366 TEST_F(BufferWraparoundReadTest, GetQueueOffsetPassTest) 367 { 368 EXPECT_EQ(bufferImpl->getQueueOffset(), 369 bufferHeaderSize + testUeRegionSize); 370 } 371 372 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail) 373 { 374 InSequence s; 375 size_t tooBigOffset = testMaxOffset + 1; 376 EXPECT_THROW( 377 try { 378 bufferImpl->wraparoundRead(tooBigOffset, /* length */ 1); 379 } catch (const std::runtime_error& e) { 380 EXPECT_STREQ( 381 e.what(), 382 "[wraparoundRead] relativeOffset '385' was bigger than maxOffset '384'"); 383 throw; 384 }, 385 std::runtime_error); 386 387 size_t tooBigLength = testMaxOffset + 1; 388 EXPECT_THROW( 389 try { 390 bufferImpl->wraparoundRead(/* relativeOffset */ 0, tooBigLength); 391 } catch (const std::runtime_error& e) { 392 EXPECT_STREQ(e.what(), "[wraparoundRead] length '385' was bigger " 393 "than maxOffset '384'"); 394 throw; 395 }, 396 std::runtime_error); 397 } 398 399 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadFails) 400 { 401 InSequence s; 402 size_t testLength = 0x10; 403 size_t testOffset = 0x20; 404 405 // Fail the first read 406 std::vector<std::uint8_t> shortTestBytesRead(testLength - 1); 407 EXPECT_CALL(*dataInterfaceMockPtr, 408 read(testOffset + expectedqueueOffset, testLength)) 409 .WillOnce(Return(shortTestBytesRead)); 410 411 EXPECT_THROW( 412 try { 413 bufferImpl->wraparoundRead(testOffset, testLength); 414 } catch (const std::runtime_error& e) { 415 EXPECT_STREQ(e.what(), 416 "[wraparoundRead] Read '15' which was not the " 417 "requested length of '16'"); 418 throw; 419 }, 420 std::runtime_error); 421 } 422 423 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass) 424 { 425 InSequence s; 426 size_t testLength = 0x10; 427 size_t testOffset = 0x20; 428 429 // Successfully read all the requested length without a wrap around 430 std::vector<std::uint8_t> testBytesRead(testLength); 431 EXPECT_CALL(*dataInterfaceMockPtr, 432 read(testOffset + expectedqueueOffset, testLength)) 433 .WillOnce(Return(testBytesRead)); 434 435 // Call to updateReadPtr is triggered 436 const std::vector<uint8_t> expectedReadPtr{ 437 static_cast<uint8_t>(testOffset + testLength), 0x0, 0x0}; 438 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 439 ElementsAreArray(expectedReadPtr))) 440 .WillOnce(Return(expectedWriteSize)); 441 442 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 443 ElementsAreArray(testBytesRead)); 444 struct CircularBufferHeader cachedBufferHeader = 445 bufferImpl->getCachedBufferHeader(); 446 // The bmcReadPtr should have been updated 447 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 448 testOffset + testLength); 449 } 450 451 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails) 452 { 453 InSequence s; 454 size_t testBytesLeft = 3; 455 size_t testLength = 0x10; 456 size_t testOffset = testMaxOffset - (testLength - testBytesLeft); 457 458 // Read until the end of the queue 459 std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft); 460 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset, 461 testLength - testBytesLeft)) 462 .WillOnce(Return(testBytesReadShort)); 463 464 // Read 1 byte short after wraparound 465 std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1); 466 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 467 .WillOnce(Return(testBytesLeftReadShort)); 468 469 EXPECT_THROW( 470 try { 471 bufferImpl->wraparoundRead(testOffset, testLength); 472 } catch (const std::runtime_error& e) { 473 EXPECT_STREQ( 474 e.what(), 475 "[wraparoundRead] Buffer wrapped around but read '2' which was " 476 "not the requested lenght of '3'"); 477 throw; 478 }, 479 std::runtime_error); 480 } 481 482 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses) 483 { 484 InSequence s; 485 size_t testBytesLeft = 3; 486 size_t testLength = 0x10; 487 size_t testOffset = testMaxOffset - (testLength - testBytesLeft); 488 489 // Read to the end of the queue 490 std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10, 491 9, 8, 7, 6, 5, 4}; 492 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset, 493 testLength - testBytesLeft)) 494 .WillOnce(Return(testBytesReadFirst)); 495 496 std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1}; 497 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 498 .WillOnce(Return(testBytesReadSecond)); 499 500 // Call to updateReadPtr is triggered 501 const std::vector<uint8_t> expectedReadPtr{ 502 static_cast<uint8_t>(testBytesLeft), 0x0, 0x0}; 503 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 504 ElementsAreArray(expectedReadPtr))) 505 .WillOnce(Return(expectedWriteSize)); 506 507 std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9, 508 8, 7, 6, 5, 4, 3, 2, 1}; 509 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 510 ElementsAreArray(expectedBytes)); 511 struct CircularBufferHeader cachedBufferHeader = 512 bufferImpl->getCachedBufferHeader(); 513 // The bmcReadPtr should have been updated to reflect the wraparound 514 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 515 testBytesLeft); 516 } 517 518 TEST_F(BufferWraparoundReadTest, WrapAroundCornerCasePass) 519 { 520 InSequence s; 521 size_t testBytesLeft = 0; 522 size_t testLength = 4; 523 size_t testOffset = testMaxOffset - (testLength - testBytesLeft); 524 525 // Read to the very end of the queue 526 std::vector<std::uint8_t> testBytes{4, 3, 2, 1}; 527 EXPECT_CALL(*dataInterfaceMockPtr, 528 read(testOffset + expectedqueueOffset, testLength)) 529 .WillOnce(Return(testBytes)); 530 531 // Call to updateReadPtr is triggered, since we read to the very end of the 532 // buffer, update the readPtr up around to 0 533 const std::vector<uint8_t> expectedReadPtr{0x0, 0x0, 0x0}; 534 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 535 ElementsAreArray(expectedReadPtr))) 536 .WillOnce(Return(expectedWriteSize)); 537 538 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 539 ElementsAreArray(testBytes)); 540 struct CircularBufferHeader cachedBufferHeader = 541 bufferImpl->getCachedBufferHeader(); 542 // The bmcReadPtr should have been updated to reflect the wraparound 543 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 544 0); 545 } 546 547 class BufferEntryTest : public BufferWraparoundReadTest 548 { 549 protected: 550 BufferEntryTest() 551 { 552 testEntryHeader.sequenceId = testSequenceId; 553 testEntryHeader.entrySize = testEntrySize; 554 testEntryHeader.checksum = testChecksum; 555 testEntryHeader.rdeCommandType = testRdeCommandType; 556 } 557 ~BufferEntryTest() override = default; 558 559 void wraparoundReadMock(const uint32_t relativeOffset, 560 std::span<std::uint8_t> expetedBytesOutput) 561 { 562 InSequence s; 563 const uint32_t queueSizeToQueueEnd = testMaxOffset - relativeOffset; 564 565 // This will wrap, split the read mocks in 2 566 if (expetedBytesOutput.size() > queueSizeToQueueEnd) 567 { 568 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 569 .WillOnce(Return(std::vector<std::uint8_t>( 570 expetedBytesOutput.begin(), 571 expetedBytesOutput.begin() + queueSizeToQueueEnd))); 572 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 573 .WillOnce(Return(std::vector<std::uint8_t>( 574 expetedBytesOutput.begin() + queueSizeToQueueEnd, 575 expetedBytesOutput.end()))); 576 } 577 else 578 { 579 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 580 .WillOnce(Return(std::vector<std::uint8_t>( 581 expetedBytesOutput.begin(), expetedBytesOutput.end()))); 582 } 583 584 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 585 .WillOnce(Return(expectedWriteSize)); 586 } 587 588 static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader); 589 static constexpr uint16_t testSequenceId = 0; 590 static constexpr uint16_t testEntrySize = 0x20; 591 static constexpr uint8_t testRdeCommandType = 0x01; 592 // Calculated checksum for the header 593 static constexpr uint8_t testChecksum = 594 (testSequenceId ^ testEntrySize ^ testRdeCommandType); 595 size_t testOffset = 0x0; 596 597 struct QueueEntryHeader testEntryHeader 598 {}; 599 }; 600 601 TEST_F(BufferEntryTest, ReadEntryHeaderPass) 602 { 603 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 604 std::vector<uint8_t> testEntryHeaderVector( 605 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 606 wraparoundReadMock(testOffset, testEntryHeaderVector); 607 EXPECT_EQ(bufferImpl->readEntryHeader(), testEntryHeader); 608 // Check the bmcReadPtr 609 struct CircularBufferHeader cachedBufferHeader = 610 bufferImpl->getCachedBufferHeader(); 611 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 612 testOffset + testEntryHeaderVector.size()); 613 } 614 615 TEST_F(BufferEntryTest, ReadEntryChecksumFail) 616 { 617 InSequence s; 618 std::vector<uint8_t> testEntryVector(testEntrySize); 619 // Offset the checksum by 1 620 testEntryHeader.checksum += 1; 621 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 622 std::vector<uint8_t> testEntryHeaderVector( 623 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 624 wraparoundReadMock(testOffset, testEntryHeaderVector); 625 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector); 626 EXPECT_THROW( 627 try { bufferImpl->readEntry(); } catch (const std::runtime_error& e) { 628 // Calculation: testChecksum (0x21) XOR (0x22) = 3 629 EXPECT_STREQ(e.what(), 630 "[readEntry] Checksum was '3', expected '0'"); 631 throw; 632 }, 633 std::runtime_error); 634 } 635 636 TEST_F(BufferEntryTest, ReadEntryPassWraparound) 637 { 638 InSequence s; 639 // We expect this will bump checksum up by "testEntrySize" = 0xff ^ 0xff ... 640 // (20 times) = 0 therefore leave the checksum as is 641 std::vector<uint8_t> testEntryVector(testEntrySize, 0xff); 642 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 643 std::vector<uint8_t> testEntryHeaderVector( 644 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 645 // Set testOffset so that we can test the wraparound here at the header and 646 // update the readPtr 647 testOffset = testMaxOffset - 1; 648 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, _)) 649 .WillOnce(Return(expectedWriteSize)); 650 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testOffset)); 651 652 wraparoundReadMock(testOffset, testEntryHeaderVector); 653 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector); 654 655 EntryPair testedEntryPair; 656 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry()); 657 EXPECT_EQ(testedEntryPair.first, testEntryHeader); 658 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector)); 659 struct CircularBufferHeader cachedBufferHeader = 660 bufferImpl->getCachedBufferHeader(); 661 // The bmcReadPtr should have been updated to reflect the wraparound 662 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 663 entryHeaderSize + testEntrySize - 1); 664 665 // Set testOffset so that we can test the wraparound here as well on our 666 // second read for the entry (by 1 byte) 667 testOffset = testMaxOffset - entryHeaderSize - 1; 668 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, _)) 669 .WillOnce(Return(expectedWriteSize)); 670 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testOffset)); 671 672 wraparoundReadMock(testOffset, testEntryHeaderVector); 673 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector); 674 675 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry()); 676 EXPECT_EQ(testedEntryPair.first, testEntryHeader); 677 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector)); 678 cachedBufferHeader = bufferImpl->getCachedBufferHeader(); 679 // The bmcReadPtr should have been updated to reflect the wraparound 680 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 681 testEntrySize - 1); 682 } 683 684 class BufferReadErrorLogsTest : public BufferEntryTest 685 { 686 protected: 687 BufferReadErrorLogsTest() = default; 688 689 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 690 size_t entryAndHeaderSize = entryHeaderSize + testEntrySize; 691 }; 692 693 TEST_F(BufferReadErrorLogsTest, PtrsTooBigFail) 694 { 695 InSequence s; 696 // Set the biosWritePtr too big 697 testInitializationHeader.biosWritePtr = 698 boost::endian::native_to_little((testMaxOffset + 1)); 699 initializeFuncMock(); 700 701 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 702 .WillOnce(Return(std::vector<uint8_t>( 703 testInitializationHeaderPtr, 704 testInitializationHeaderPtr + bufferHeaderSize))); 705 EXPECT_THROW( 706 try { 707 bufferImpl->readErrorLogs(); 708 } catch (const std::runtime_error& e) { 709 EXPECT_STREQ(e.what(), 710 "[readErrorLogs] currentBiosWritePtr was '385' " 711 "which was bigger than maxOffset '384'"); 712 throw; 713 }, 714 std::runtime_error); 715 716 // Reset the biosWritePtr and set the bmcReadPtr too big 717 testInitializationHeader.biosWritePtr = 0; 718 initializeFuncMock(); 719 testInitializationHeader.bmcReadPtr = 720 boost::endian::native_to_little((testMaxOffset + 1)); 721 initializeFuncMock(); 722 723 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 724 .WillOnce(Return(std::vector<uint8_t>( 725 testInitializationHeaderPtr, 726 testInitializationHeaderPtr + bufferHeaderSize))); 727 EXPECT_THROW( 728 try { 729 bufferImpl->readErrorLogs(); 730 } catch (const std::runtime_error& e) { 731 EXPECT_STREQ(e.what(), "[readErrorLogs] currentReadPtr was '385' " 732 "which was bigger than maxOffset '384'"); 733 throw; 734 }, 735 std::runtime_error); 736 } 737 738 TEST_F(BufferReadErrorLogsTest, IdenticalPtrsPass) 739 { 740 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 741 .WillOnce(Return(std::vector<uint8_t>( 742 testInitializationHeaderPtr, 743 testInitializationHeaderPtr + bufferHeaderSize))); 744 EXPECT_NO_THROW(bufferImpl->readErrorLogs()); 745 } 746 747 TEST_F(BufferReadErrorLogsTest, NoWraparoundPass) 748 { 749 InSequence s; 750 // Set the biosWritePtr to 1 entryHeader + entry size 751 testInitializationHeader.biosWritePtr = 752 boost::endian::native_to_little((entryAndHeaderSize)); 753 initializeFuncMock(); 754 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 755 .WillOnce(Return(std::vector<uint8_t>( 756 testInitializationHeaderPtr, 757 testInitializationHeaderPtr + bufferHeaderSize))); 758 std::vector<uint8_t> testEntryHeaderVector( 759 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 760 std::vector<uint8_t> testEntryVector(testEntrySize); 761 wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector); 762 wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector); 763 764 std::vector<EntryPair> entryPairs; 765 EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs()); 766 767 // Check that we only read one entryPair and that the content is correct 768 EXPECT_EQ(entryPairs.size(), 1U); 769 EXPECT_EQ(entryPairs[0].first, testEntryHeader); 770 EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector)); 771 } 772 773 TEST_F(BufferReadErrorLogsTest, WraparoundMultiplEntryPass) 774 { 775 InSequence s; 776 // Set the bmcReadPtr to 1 entryHeader + entry size from the "end" exactly 777 uint32_t entryAndHeaderSizeAwayFromEnd = testMaxOffset - entryAndHeaderSize; 778 testInitializationHeader.bmcReadPtr = 779 boost::endian::native_to_little(entryAndHeaderSizeAwayFromEnd); 780 // Set the biosWritePtr to 1 entryHeader + entry size from the "beginning" 781 testInitializationHeader.biosWritePtr = entryAndHeaderSize; 782 initializeFuncMock(); 783 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 784 .WillOnce(Return(std::vector<uint8_t>( 785 testInitializationHeaderPtr, 786 testInitializationHeaderPtr + bufferHeaderSize))); 787 788 std::vector<uint8_t> testEntryHeaderVector( 789 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 790 std::vector<uint8_t> testEntryVector(testEntrySize); 791 wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd, 792 testEntryHeaderVector); 793 wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd + 794 entryHeaderSize, 795 testEntryVector); 796 wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize, 797 testEntryHeaderVector); 798 wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize + 799 entryHeaderSize, 800 testEntryVector); 801 802 std::vector<EntryPair> entryPairs; 803 EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs()); 804 805 // Check that we only read one entryPair and that the content is correct 806 EXPECT_EQ(entryPairs.size(), 2); 807 EXPECT_EQ(entryPairs[0].first, testEntryHeader); 808 EXPECT_EQ(entryPairs[1].first, testEntryHeader); 809 EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector)); 810 EXPECT_THAT(entryPairs[1].second, ElementsAreArray(testEntryVector)); 811 } 812 813 TEST_F(BufferReadErrorLogsTest, WraparoundMismatchingPtrsFail) 814 { 815 InSequence s; 816 testInitializationHeader.bmcReadPtr = boost::endian::native_to_little(0); 817 // Make the biosWritePtr intentially 1 smaller than expected 818 testInitializationHeader.biosWritePtr = 819 boost::endian::native_to_little(entryAndHeaderSize - 1); 820 initializeFuncMock(); 821 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 822 .WillOnce(Return(std::vector<uint8_t>( 823 testInitializationHeaderPtr, 824 testInitializationHeaderPtr + bufferHeaderSize))); 825 826 std::vector<uint8_t> testEntryHeaderVector( 827 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 828 std::vector<uint8_t> testEntryVector(testEntrySize); 829 wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector); 830 wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector); 831 832 EXPECT_THROW( 833 try { 834 bufferImpl->readErrorLogs(); 835 } catch (const std::runtime_error& e) { 836 EXPECT_STREQ( 837 e.what(), 838 "[readErrorLogs] biosWritePtr '37' and bmcReaddPtr '38' " 839 "are not identical after reading through all the logs"); 840 throw; 841 }, 842 std::runtime_error); 843 } 844 845 } // namespace 846 } // namespace bios_bmc_smm_error_logger 847