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 uint16_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 '2'"); 189 throw; 190 }, 191 std::runtime_error); 192 } 193 194 TEST_F(BufferTest, BufferUpdateReadPtrPass) 195 { 196 constexpr size_t expectedWriteSize = 2; 197 constexpr uint8_t expectedBmcReadPtrOffset = 0x20; 198 // Check that we truncate the highest 16bits 199 const uint32_t testNewReadPtr = 0x99881234; 200 const std::vector<uint8_t> expectedReadPtr{0x34, 0x12}; 201 202 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 203 ElementsAreArray(expectedReadPtr))) 204 .WillOnce(Return(expectedWriteSize)); 205 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testNewReadPtr)); 206 } 207 208 class BufferWraparoundReadTest : public BufferTest 209 { 210 protected: 211 BufferWraparoundReadTest() 212 { 213 // Initialize the memory and the cachedBufferHeader 214 InSequence s; 215 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 216 .WillOnce(Return(testRegionSize)); 217 size_t testProposedBufferSize = sizeof(struct CircularBufferHeader) + 218 testUeRegionSize + testQueueSize; 219 const std::vector<uint8_t> emptyArray(testProposedBufferSize, 0); 220 EXPECT_CALL(*dataInterfaceMockPtr, 221 write(0, ElementsAreArray(emptyArray))) 222 .WillOnce(Return(testProposedBufferSize)); 223 224 uint8_t* testInitializationHeaderPtr = 225 reinterpret_cast<uint8_t*>(&testInitializationHeader); 226 EXPECT_CALL(*dataInterfaceMockPtr, 227 write(0, ElementsAreArray(testInitializationHeaderPtr, 228 bufferHeaderSize))) 229 .WillOnce(Return(bufferHeaderSize)); 230 EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion, 231 testQueueSize, testUeRegionSize, 232 testMagicNumber)); 233 } 234 static constexpr size_t expectedWriteSize = 2; 235 static constexpr uint8_t expectedBmcReadPtrOffset = 0x20; 236 static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize; 237 }; 238 239 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail) 240 { 241 InSequence s; 242 243 size_t tooBigOffset = testQueueSize + 1; 244 EXPECT_THROW( 245 try { 246 bufferImpl->wraparoundRead(tooBigOffset, /* length */ 1); 247 } catch (const std::runtime_error& e) { 248 EXPECT_STREQ( 249 e.what(), 250 "[wraparoundRead] relativeOffset '257' was bigger than queueSize '256'"); 251 throw; 252 }, 253 std::runtime_error); 254 255 size_t tooBigLength = testQueueSize + 1; 256 EXPECT_THROW( 257 try { 258 bufferImpl->wraparoundRead(/* relativeOffset */ 0, tooBigLength); 259 } catch (const std::runtime_error& e) { 260 EXPECT_STREQ(e.what(), "[wraparoundRead] length '257' was bigger " 261 "than queueSize '256'"); 262 throw; 263 }, 264 std::runtime_error); 265 } 266 267 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadFails) 268 { 269 InSequence s; 270 size_t testLength = 0x10; 271 size_t testOffset = 0x20; 272 273 // Fail the first read 274 std::vector<std::uint8_t> shortTestBytesRead(testLength - 1); 275 EXPECT_CALL(*dataInterfaceMockPtr, 276 read(testOffset + expectedqueueOffset, testLength)) 277 .WillOnce(Return(shortTestBytesRead)); 278 279 EXPECT_THROW( 280 try { 281 bufferImpl->wraparoundRead(testOffset, testLength); 282 } catch (const std::runtime_error& e) { 283 EXPECT_STREQ(e.what(), 284 "[wraparoundRead] Read '15' which was not the " 285 "requested length of '16'"); 286 throw; 287 }, 288 std::runtime_error); 289 } 290 291 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass) 292 { 293 InSequence s; 294 size_t testLength = 0x10; 295 size_t testOffset = 0x20; 296 297 // Successfully read all the requested length without a wrap around 298 std::vector<std::uint8_t> testBytesRead(testLength); 299 EXPECT_CALL(*dataInterfaceMockPtr, 300 read(testOffset + expectedqueueOffset, testLength)) 301 .WillOnce(Return(testBytesRead)); 302 303 // Call to updateReadPtr is triggered 304 const std::vector<uint8_t> expectedReadPtr{ 305 static_cast<uint8_t>(testOffset + testLength), 0x0}; 306 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 307 ElementsAreArray(expectedReadPtr))) 308 .WillOnce(Return(expectedWriteSize)); 309 310 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 311 ElementsAreArray(testBytesRead)); 312 struct CircularBufferHeader cachedBufferHeader = 313 bufferImpl->getCachedBufferHeader(); 314 // The bmcReadPtr should have been updated 315 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 316 testOffset + testLength); 317 } 318 319 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails) 320 { 321 InSequence s; 322 size_t testBytesLeft = 3; 323 size_t testLength = 0x10; 324 size_t testOffset = testQueueSize - (testLength - testBytesLeft); 325 326 // Read until the end of the queue 327 std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft); 328 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset, 329 testLength - testBytesLeft)) 330 .WillOnce(Return(testBytesReadShort)); 331 332 // Read 1 byte short after wraparound 333 std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1); 334 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 335 .WillOnce(Return(testBytesLeftReadShort)); 336 337 EXPECT_THROW( 338 try { 339 bufferImpl->wraparoundRead(testOffset, testLength); 340 } catch (const std::runtime_error& e) { 341 EXPECT_STREQ( 342 e.what(), 343 "[wraparoundRead] Buffer wrapped around but read '2' which was " 344 "not the requested lenght of '3'"); 345 throw; 346 }, 347 std::runtime_error); 348 } 349 350 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses) 351 { 352 InSequence s; 353 size_t testBytesLeft = 3; 354 size_t testLength = 0x10; 355 size_t testOffset = testQueueSize - (testLength - testBytesLeft); 356 357 // Read to the end of the queue 358 std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10, 359 9, 8, 7, 6, 5, 4}; 360 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset, 361 testLength - testBytesLeft)) 362 .WillOnce(Return(testBytesReadFirst)); 363 364 std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1}; 365 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 366 .WillOnce(Return(testBytesReadSecond)); 367 368 // Call to updateReadPtr is triggered 369 const std::vector<uint8_t> expectedReadPtr{ 370 static_cast<uint8_t>(testBytesLeft), 0x0}; 371 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 372 ElementsAreArray(expectedReadPtr))) 373 .WillOnce(Return(expectedWriteSize)); 374 375 std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9, 376 8, 7, 6, 5, 4, 3, 2, 1}; 377 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 378 ElementsAreArray(expectedBytes)); 379 struct CircularBufferHeader cachedBufferHeader = 380 bufferImpl->getCachedBufferHeader(); 381 // The bmcReadPtr should have been updated to reflect the wraparound 382 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 383 testBytesLeft); 384 } 385 386 class BufferEntryTest : public BufferWraparoundReadTest 387 { 388 protected: 389 BufferEntryTest() 390 { 391 testEntryHeader.sequenceId = testSequenceId; 392 testEntryHeader.entrySize = testEntrySize; 393 testEntryHeader.checksum = testChecksum; 394 testEntryHeader.rdeCommandType = testRdeCommandType; 395 } 396 ~BufferEntryTest() override = default; 397 398 void wraparoundReadMock(const uint32_t relativeOffset, 399 std::span<std::uint8_t> expetedBytesOutput) 400 { 401 InSequence s; 402 const uint32_t queueSizeToQueueEnd = testQueueSize - relativeOffset; 403 404 // This will wrap, split the read mocks in 2 405 if (expetedBytesOutput.size() > queueSizeToQueueEnd) 406 { 407 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 408 .WillOnce(Return(std::vector<std::uint8_t>( 409 expetedBytesOutput.begin(), 410 expetedBytesOutput.begin() + queueSizeToQueueEnd))); 411 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 412 .WillOnce(Return(std::vector<std::uint8_t>( 413 expetedBytesOutput.begin() + queueSizeToQueueEnd, 414 expetedBytesOutput.end()))); 415 } 416 else 417 { 418 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 419 .WillOnce(Return(std::vector<std::uint8_t>( 420 expetedBytesOutput.begin(), expetedBytesOutput.end()))); 421 } 422 423 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 424 .WillOnce(Return(expectedWriteSize)); 425 } 426 427 static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader); 428 static constexpr uint16_t testSequenceId = 0; 429 static constexpr uint16_t testEntrySize = 0x20; 430 // Calculated checksum for the header (0x100 - 0 - 0x20 - 0x01) & 0xff 431 static constexpr uint8_t testChecksum = 0xdf; 432 static constexpr uint8_t testRdeCommandType = 0x01; 433 size_t testOffset = 0x20; 434 435 struct QueueEntryHeader testEntryHeader 436 {}; 437 }; 438 439 TEST_F(BufferEntryTest, ReadEntryHeaderPass) 440 { 441 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 442 std::vector<uint8_t> testEntryHeaderVector( 443 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 444 wraparoundReadMock(testOffset, testEntryHeaderVector); 445 EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader); 446 // Check the bmcReadPtr 447 struct CircularBufferHeader cachedBufferHeader = 448 bufferImpl->getCachedBufferHeader(); 449 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 450 testOffset + testEntryHeaderVector.size()); 451 } 452 453 TEST_F(BufferEntryTest, ReadEntryChecksumFail) 454 { 455 InSequence s; 456 // We expect this will bump checksum up by "testEntrySize" = 0x20 457 std::vector<uint8_t> testEntryVector(testEntrySize, 1); 458 // Offset the checksum by 1 459 testEntryHeader.checksum -= (0x20 - 1); 460 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 461 std::vector<uint8_t> testEntryHeaderVector( 462 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 463 wraparoundReadMock(testOffset, testEntryHeaderVector); 464 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector); 465 EXPECT_THROW( 466 try { 467 bufferImpl->readEntry(testOffset); 468 } catch (const std::runtime_error& e) { 469 EXPECT_STREQ(e.what(), 470 "[readEntry] Checksum was '1', expected '0'"); 471 throw; 472 }, 473 std::runtime_error); 474 } 475 476 TEST_F(BufferEntryTest, ReadEntryPass) 477 { 478 InSequence s; 479 // We expect this will bump checksum up by "testEntrySize" = 0xff * 0x20 = 480 // 0x1fe0 -> 0x1fe0 & 0xff = 0xe0. 481 std::vector<uint8_t> testEntryVector(testEntrySize, 0xff); 482 testEntryHeader.checksum -= (0xe0); 483 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 484 std::vector<uint8_t> testEntryHeaderVector( 485 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 486 // Set testOffset so that we can test the wraparound here as well on our 487 // second read for the entry (by 1 byte) 488 testOffset = testQueueSize - entryHeaderSize - 1; 489 wraparoundReadMock(testOffset, testEntryHeaderVector); 490 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector); 491 492 EntryPair testedEntryPair; 493 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry(testOffset)); 494 EXPECT_EQ(testedEntryPair.first, testEntryHeader); 495 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector)); 496 struct CircularBufferHeader cachedBufferHeader = 497 bufferImpl->getCachedBufferHeader(); 498 // The bmcReadPtr should have been updated to reflect the wraparound 499 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr), 500 testEntrySize - 1); 501 } 502 503 } // namespace 504 } // namespace bios_bmc_smm_error_logger 505