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