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