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:
BufferTest()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
TEST_F(BufferTest,BufferInitializeEraseFail)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
TEST_F(BufferTest,BufferInitializePass)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
TEST_F(BufferTest,BufferHeaderReadFail)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
TEST_F(BufferTest,BufferHeaderReadPass)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
TEST_F(BufferTest,BufferUpdateReadPtrFail)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
TEST_F(BufferTest,BufferUpdateReadPtrPass)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
TEST_F(BufferTest,BufferUpdateBmcFlagsFail)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
TEST_F(BufferTest,BufferUpdateBmcFlagsPass)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
TEST_F(BufferTest,GetMaxOffsetQueueSizeFail)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
TEST_F(BufferTest,GetMaxOffsetUeRegionSizeFail)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
TEST_F(BufferTest,GetOffsetUeRegionSizeFail)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:
BufferWraparoundReadTest()329 BufferWraparoundReadTest()
330 {
331 initializeFuncMock();
332 }
initializeFuncMock()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
TEST_F(BufferWraparoundReadTest,GetMaxOffsetPassTest)360 TEST_F(BufferWraparoundReadTest, GetMaxOffsetPassTest)
361 {
362 EXPECT_EQ(bufferImpl->getMaxOffset(), testMaxOffset);
363 }
364
TEST_F(BufferWraparoundReadTest,GetQueueOffsetPassTest)365 TEST_F(BufferWraparoundReadTest, GetQueueOffsetPassTest)
366 {
367 EXPECT_EQ(bufferImpl->getQueueOffset(),
368 bufferHeaderSize + testUeRegionSize);
369 }
370
TEST_F(BufferWraparoundReadTest,ParamsTooBigFail)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
TEST_F(BufferWraparoundReadTest,NoWrapAroundReadFails)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
TEST_F(BufferWraparoundReadTest,NoWrapAroundReadPass)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
TEST_F(BufferWraparoundReadTest,WrapAroundReadFails)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
TEST_F(BufferWraparoundReadTest,WrapAroundReadPasses)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
TEST_F(BufferWraparoundReadTest,WrapAroundCornerCasePass)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:
BufferEntryTest()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
wraparoundReadMock(const uint32_t relativeOffset,std::span<std::uint8_t> expetedBytesOutput)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
TEST_F(BufferEntryTest,ReadEntryHeaderPass)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
TEST_F(BufferEntryTest,ReadEntryChecksumFail)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
TEST_F(BufferEntryTest,ReadEntryPassWraparound)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
TEST_F(BufferReadErrorLogsTest,PtrsTooBigFail)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
TEST_F(BufferReadErrorLogsTest,IdenticalPtrsPass)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
TEST_F(BufferReadErrorLogsTest,NoWraparoundPass)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
TEST_F(BufferReadErrorLogsTest,WraparoundMultiplEntryPass)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
TEST_F(BufferReadErrorLogsTest,WraparoundMismatchingPtrsFail)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