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 66 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 67 .WillOnce(Return(testRegionSize)); 68 const std::vector<uint8_t> emptyArray(testRegionSize, 0); 69 // Return a smaller write than the intended testRegionSize to test the error 70 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 71 .WillOnce(Return(testRegionSize - 1)); 72 EXPECT_THROW( 73 try { 74 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 75 testUeRegionSize, testMagicNumber); 76 } catch (const std::runtime_error& e) { 77 EXPECT_STREQ(e.what(), "Buffer initialization only erased '511'"); 78 throw; 79 }, 80 std::runtime_error); 81 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 82 83 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 84 .WillOnce(Return(testRegionSize)); 85 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 86 .WillOnce(Return(testRegionSize)); 87 // Return a smaller write than the intended initializationHeader to test the 88 // error 89 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)).WillOnce(Return(0)); 90 EXPECT_THROW( 91 try { 92 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize, 93 testUeRegionSize, testMagicNumber); 94 } catch (const std::runtime_error& e) { 95 EXPECT_STREQ( 96 e.what(), 97 "Buffer initialization buffer header write only wrote '0'"); 98 throw; 99 }, 100 std::runtime_error); 101 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 102 } 103 104 TEST_F(BufferTest, BufferInitializePass) 105 { 106 InSequence s; 107 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 108 .WillOnce(Return(testRegionSize)); 109 const std::vector<uint8_t> emptyArray(testRegionSize, 0); 110 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray))) 111 .WillOnce(Return(testRegionSize)); 112 113 uint8_t* testInitializationHeaderPtr = 114 reinterpret_cast<uint8_t*>(&testInitializationHeader); 115 EXPECT_CALL(*dataInterfaceMockPtr, 116 write(0, ElementsAreArray(testInitializationHeaderPtr, 117 bufferHeaderSize))) 118 .WillOnce(Return(bufferHeaderSize)); 119 EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion, 120 testQueueSize, testUeRegionSize, 121 testMagicNumber)); 122 EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 123 } 124 125 TEST_F(BufferTest, BufferHeaderReadFail) 126 { 127 std::vector<std::uint8_t> testBytesRead{}; 128 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 129 .WillOnce(Return(testBytesRead)); 130 EXPECT_THROW( 131 try { 132 bufferImpl->readBufferHeader(); 133 } catch (const std::runtime_error& e) { 134 EXPECT_STREQ(e.what(), 135 "Buffer header read only read '0', expected '48'"); 136 throw; 137 }, 138 std::runtime_error); 139 } 140 141 TEST_F(BufferTest, BufferHeaderReadPass) 142 { 143 uint8_t* testInitializationHeaderPtr = 144 reinterpret_cast<uint8_t*>(&testInitializationHeader); 145 std::vector<uint8_t> testInitializationHeaderVector( 146 testInitializationHeaderPtr, 147 testInitializationHeaderPtr + bufferHeaderSize); 148 149 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize)) 150 .WillOnce(Return(testInitializationHeaderVector)); 151 EXPECT_NO_THROW(bufferImpl->readBufferHeader()); 152 EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader); 153 } 154 155 TEST_F(BufferTest, BufferUpdateReadPtrFail) 156 { 157 // Return write size that is not 2 which is sizeof(little_uint16_t) 158 constexpr size_t wrongWriteSize = 1; 159 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 160 .WillOnce(Return(wrongWriteSize)); 161 EXPECT_THROW( 162 try { 163 bufferImpl->updateReadPtr(0); 164 } catch (const std::runtime_error& e) { 165 EXPECT_STREQ( 166 e.what(), 167 "[updateReadPtr] Wrote '1' bytes, instead of expected '2'"); 168 throw; 169 }, 170 std::runtime_error); 171 } 172 173 TEST_F(BufferTest, BufferUpdateReadPtrPass) 174 { 175 constexpr size_t expectedWriteSize = 2; 176 constexpr uint8_t expectedBmcReadPtrOffset = 0x20; 177 // Check that we truncate the highest 16bits 178 const uint32_t testNewReadPtr = 0x99881234; 179 const std::vector<uint8_t> expectedReadPtr{0x34, 0x12}; 180 181 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 182 ElementsAreArray(expectedReadPtr))) 183 .WillOnce(Return(expectedWriteSize)); 184 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testNewReadPtr)); 185 } 186 187 class BufferWraparoundReadTest : public BufferTest 188 { 189 protected: 190 BufferWraparoundReadTest() 191 { 192 // Initialize the memory and the cachedBufferHeader 193 InSequence s; 194 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 195 .WillOnce(Return(testRegionSize)); 196 const std::vector<uint8_t> emptyArray(testRegionSize, 0); 197 EXPECT_CALL(*dataInterfaceMockPtr, 198 write(0, ElementsAreArray(emptyArray))) 199 .WillOnce(Return(testRegionSize)); 200 201 uint8_t* testInitializationHeaderPtr = 202 reinterpret_cast<uint8_t*>(&testInitializationHeader); 203 EXPECT_CALL(*dataInterfaceMockPtr, 204 write(0, ElementsAreArray(testInitializationHeaderPtr, 205 bufferHeaderSize))) 206 .WillOnce(Return(bufferHeaderSize)); 207 EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion, 208 testQueueSize, testUeRegionSize, 209 testMagicNumber)); 210 } 211 static constexpr size_t expectedWriteSize = 2; 212 static constexpr uint8_t expectedBmcReadPtrOffset = 0x20; 213 static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize; 214 }; 215 216 TEST_F(BufferWraparoundReadTest, TooBigReadFail) 217 { 218 InSequence s; 219 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 220 .WillOnce(Return(testRegionSize)); 221 size_t tooBigLength = testRegionSize - expectedqueueOffset + 1; 222 EXPECT_THROW( 223 try { 224 bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength); 225 } catch (const std::runtime_error& e) { 226 EXPECT_STREQ(e.what(), 227 "[wraparoundRead] queueOffset '128' + length '385' + " 228 "additionalBoundaryCheck '0' + was " 229 "bigger than memoryRegionSize '512'"); 230 throw; 231 }, 232 std::runtime_error); 233 } 234 235 TEST_F(BufferWraparoundReadTest, TooBigReadWithAdditionalBoundaryCheckFail) 236 { 237 InSequence s; 238 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 239 .WillOnce(Return(testRegionSize)); 240 // Use additionalBoundaryCheck to still go over the memoryRegionSize by 1 241 size_t additionalBoundaryCheck = 10; 242 size_t tooBigLength = 243 testRegionSize - expectedqueueOffset - additionalBoundaryCheck + 1; 244 EXPECT_THROW( 245 try { 246 bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength, 247 additionalBoundaryCheck); 248 } catch (const std::runtime_error& e) { 249 EXPECT_STREQ(e.what(), 250 "[wraparoundRead] queueOffset '128' + length '375' + " 251 "additionalBoundaryCheck '10' + was " 252 "bigger than memoryRegionSize '512'"); 253 throw; 254 }, 255 std::runtime_error); 256 } 257 258 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass) 259 { 260 InSequence s; 261 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 262 .WillOnce(Return(testRegionSize)); 263 size_t testLength = 0x10; 264 size_t testOffset = 0x50; 265 266 // Successfully read all the requested length without a wrap around 267 std::vector<std::uint8_t> testBytesRead(testLength); 268 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength)) 269 .WillOnce(Return(testBytesRead)); 270 271 // Call to updateReadPtr is triggered 272 const std::vector<uint8_t> expectedReadPtr{ 273 static_cast<uint8_t>(testOffset + testLength), 0x0}; 274 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 275 ElementsAreArray(expectedReadPtr))) 276 .WillOnce(Return(expectedWriteSize)); 277 278 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 279 ElementsAreArray(testBytesRead)); 280 } 281 282 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails) 283 { 284 InSequence s; 285 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 286 .WillOnce(Return(testRegionSize)); 287 size_t testBytesLeft = 3; 288 size_t testLength = 0x10; 289 size_t testOffset = testRegionSize - (testLength - testBytesLeft); 290 291 // Read 3 bytes short 292 std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft); 293 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength)) 294 .WillOnce(Return(testBytesReadShort)); 295 296 // Read 1 byte short after wraparound 297 std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1); 298 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 299 .WillOnce(Return(testBytesLeftReadShort)); 300 301 EXPECT_THROW( 302 try { 303 bufferImpl->wraparoundRead(testOffset, testLength); 304 } catch (const std::runtime_error& e) { 305 EXPECT_STREQ(e.what(), 306 "[wraparoundRead] Buffer wrapped around but was not " 307 "able to read all of the requested info. " 308 "Bytes remaining to read '1' of '16'"); 309 throw; 310 }, 311 std::runtime_error); 312 } 313 314 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses) 315 { 316 InSequence s; 317 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 318 .WillOnce(Return(testRegionSize)); 319 size_t testBytesLeft = 3; 320 size_t testLength = 0x10; 321 size_t testOffset = testRegionSize - (testLength - testBytesLeft); 322 323 // Read 3 bytes short 324 std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10, 325 9, 8, 7, 6, 5, 4}; 326 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength)) 327 .WillOnce(Return(testBytesReadFirst)); 328 329 std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1}; 330 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft)) 331 .WillOnce(Return(testBytesReadSecond)); 332 333 // Call to updateReadPtr is triggered 334 const std::vector<uint8_t> expectedReadPtr{ 335 static_cast<uint8_t>(expectedqueueOffset + testBytesLeft), 0x0}; 336 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, 337 ElementsAreArray(expectedReadPtr))) 338 .WillOnce(Return(expectedWriteSize)); 339 340 std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9, 341 8, 7, 6, 5, 4, 3, 2, 1}; 342 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength), 343 ElementsAreArray(expectedBytes)); 344 } 345 346 class BufferEntryHeaderTest : public BufferWraparoundReadTest 347 { 348 protected: 349 BufferEntryHeaderTest() 350 { 351 testEntryHeader.sequenceId = testSequenceId; 352 testEntryHeader.entrySize = testEntrySize; 353 testEntryHeader.checksum = testChecksum; 354 testEntryHeader.rdeCommandType = testRdeCommandType; 355 } 356 ~BufferEntryHeaderTest() override = default; 357 358 void wraparoundReadMock(std::span<std::uint8_t> expetedBytesOutput) 359 { 360 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize()) 361 .WillOnce(Return(testRegionSize)); 362 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _)) 363 .WillOnce(Return(std::vector<std::uint8_t>( 364 expetedBytesOutput.begin(), expetedBytesOutput.end()))); 365 366 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _)) 367 .WillOnce(Return(expectedWriteSize)); 368 } 369 370 static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader); 371 static constexpr uint16_t testSequenceId = 0; 372 static constexpr uint16_t testEntrySize = 0x20; 373 static constexpr uint8_t testChecksum = 1; 374 static constexpr uint8_t testRdeCommandType = 0x01; 375 size_t testOffset = 0x50; 376 377 struct QueueEntryHeader testEntryHeader 378 {}; 379 }; 380 381 TEST_F(BufferEntryHeaderTest, ReadEntryHeaderPass) 382 { 383 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader); 384 std::vector<uint8_t> testEntryHeaderVector( 385 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize); 386 wraparoundReadMock(testEntryHeaderVector); 387 EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader); 388 } 389 390 } // namespace 391 } // namespace bios_bmc_smm_error_logger 392