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