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, TooBigReadFail) 240 { 241 InSequence s; 242 size_t tooBigLength = testQueueSize + 1; 243 EXPECT_THROW( 244 try { 245 bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength); 246 } catch (const std::runtime_error& e) { 247 EXPECT_STREQ( 248 e.what(), 249 "[wraparoundRead] length '257' + additionalBoundaryCheck '0' " 250 "was bigger than queueSize '256'"); 251 throw; 252 }, 253 std::runtime_error); 254 } 255 256 TEST_F(BufferWraparoundReadTest, TooBigReadWithAdditionalBoundaryCheckFail) 257 { 258 InSequence s; 259 // Use additionalBoundaryCheck to still go over the queueSize by 1 260 size_t additionalBoundaryCheck = 10; 261 size_t tooBigLength = testQueueSize - additionalBoundaryCheck + 1; 262 EXPECT_THROW( 263 try { 264 bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength, 265 additionalBoundaryCheck); 266 } catch (const std::runtime_error& e) { 267 EXPECT_STREQ( 268 e.what(), 269 "[wraparoundRead] length '247' + additionalBoundaryCheck '10' " 270 "was bigger than queueSize '256'"); 271 throw; 272 }, 273 std::runtime_error); 274 } 275 276 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass) 277 { 278 InSequence s; 279 size_t testLength = 0x10; 280 size_t testOffset = 0x50; 281 282 // Successfully read all the requested length without a wrap around 283 std::vector<std::uint8_t> testBytesRead(testLength); 284 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength)) 285 .WillOnce(Return(testBytesRead)); 286 287 // Call to updateReadPtr is triggered 288 const std::vector<uint8_t> expectedReadPtr{ 289 static_cast<uint8_t>(testOffset + testLength), 0x0}; 290 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 291 ElementsAreArray(expectedReadPtr))) 292 .WillOnce(Return(expectedWriteSize)); 293 294 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 295 ElementsAreArray(testBytesRead)); 296 } 297 298 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails) 299 { 300 InSequence s; 301 size_t testBytesLeft = 3; 302 size_t testLength = 0x10; 303 size_t testOffset = testQueueSize - (testLength - testBytesLeft); 304 305 // Read 3 bytes short 306 std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft); 307 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength)) 308 .WillOnce(Return(testBytesReadShort)); 309 310 // Read 1 byte short after wraparound 311 std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1); 312 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 313 .WillOnce(Return(testBytesLeftReadShort)); 314 315 EXPECT_THROW( 316 try { 317 bufferImpl->wraparoundRead(testOffset, testLength); 318 } catch (const std::runtime_error& e) { 319 EXPECT_STREQ(e.what(), 320 "[wraparoundRead] Buffer wrapped around but was not " 321 "able to read all of the requested info. " 322 "Bytes remaining to read '1' of '16'"); 323 throw; 324 }, 325 std::runtime_error); 326 } 327 328 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses) 329 { 330 InSequence s; 331 size_t testBytesLeft = 3; 332 size_t testLength = 0x10; 333 size_t testOffset = testQueueSize - (testLength - testBytesLeft); 334 335 // Read 3 bytes short 336 std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10, 337 9, 8, 7, 6, 5, 4}; 338 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength)) 339 .WillOnce(Return(testBytesReadFirst)); 340 341 std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1}; 342 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 343 .WillOnce(Return(testBytesReadSecond)); 344 345 // Call to updateReadPtr is triggered 346 const std::vector<uint8_t> expectedReadPtr{ 347 static_cast<uint8_t>(testBytesLeft), 0x0}; 348 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 349 ElementsAreArray(expectedReadPtr))) 350 .WillOnce(Return(expectedWriteSize)); 351 352 std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9, 353 8, 7, 6, 5, 4, 3, 2, 1}; 354 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 355 ElementsAreArray(expectedBytes)); 356 } 357 358 class BufferEntryTest : public BufferWraparoundReadTest 359 { 360 protected: 361 BufferEntryTest() 362 { 363 testEntryHeader.sequenceId = testSequenceId; 364 testEntryHeader.entrySize = testEntrySize; 365 testEntryHeader.checksum = testChecksum; 366 testEntryHeader.rdeCommandType = testRdeCommandType; 367 } 368 ~BufferEntryTest() override = default; 369 370 void wraparoundReadMock(std::span<std::uint8_t> expetedBytesOutput) 371 { 372 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 373 .WillOnce(Return(std::vector<std::uint8_t>( 374 expetedBytesOutput.begin(), expetedBytesOutput.end()))); 375 376 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 377 .WillOnce(Return(expectedWriteSize)); 378 } 379 380 static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader); 381 static constexpr uint16_t testSequenceId = 0; 382 static constexpr uint16_t testEntrySize = 0x20; 383 // Calculated checksum for the header (0x100 - 0 - 0x20 - 0x01) & 0xff 384 static constexpr uint8_t testChecksum = 0xdf; 385 static constexpr uint8_t testRdeCommandType = 0x01; 386 size_t testOffset = 0x50; 387 388 struct QueueEntryHeader testEntryHeader 389 {}; 390 }; 391 392 TEST_F(BufferEntryTest, ReadEntryHeaderPass) 393 { 394 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 395 std::vector<uint8_t> testEntryHeaderVector( 396 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 397 wraparoundReadMock(testEntryHeaderVector); 398 EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader); 399 } 400 401 TEST_F(BufferEntryTest, ReadEntryChecksumFail) 402 { 403 InSequence s; 404 // We expect this will bump checksum up by "testEntrySize" = 0x20 405 std::vector<uint8_t> testEntryVector(testEntrySize, 1); 406 // Offset the checksum by 1 407 testEntryHeader.checksum -= (0x20 - 1); 408 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 409 std::vector<uint8_t> testEntryHeaderVector( 410 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 411 wraparoundReadMock(testEntryHeaderVector); 412 413 wraparoundReadMock(testEntryVector); 414 EXPECT_THROW( 415 try { 416 bufferImpl->readEntry(testOffset); 417 } catch (const std::runtime_error& e) { 418 EXPECT_STREQ(e.what(), 419 "[readEntry] Checksum was '1', expected '0'"); 420 throw; 421 }, 422 std::runtime_error); 423 } 424 425 TEST_F(BufferEntryTest, ReadEntryPass) 426 { 427 InSequence s; 428 // We expect this will bump checksum up by "testEntrySize" = 0x40 429 std::vector<uint8_t> testEntryVector(testEntrySize, 2); 430 testEntryHeader.checksum -= (0x40); 431 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 432 std::vector<uint8_t> testEntryHeaderVector( 433 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 434 wraparoundReadMock(testEntryHeaderVector); 435 wraparoundReadMock(testEntryVector); 436 437 EntryPair testedEntryPair; 438 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry(testOffset)); 439 EXPECT_EQ(testedEntryPair.first, testEntryHeader); 440 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector)); 441 } 442 443 } // namespace 444 } // namespace bios_bmc_smm_error_logger 445