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     auto cachedHeader = bufferImpl->getCachedBufferHeader();
208     EXPECT_EQ(boost::endian::little_to_native(cachedHeader.bmcReadPtr), 0x1234);
209 }
210 
211 TEST_F(BufferTest, BufferUpdateBmcFlagsFail)
212 {
213     // Return write size that is not 4 which is sizeof(little_uint32_t)
214     constexpr size_t wrongWriteSize = 1;
215     EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
216         .WillOnce(Return(wrongWriteSize));
217     EXPECT_THROW(
218         try {
219             bufferImpl->updateBmcFlags(static_cast<uint32_t>(BmcFlags::ready));
220         } catch (const std::runtime_error& e) {
221             EXPECT_STREQ(
222                 e.what(),
223                 "[updateBmcFlags] Wrote '1' bytes, instead of expected '4'");
224             throw;
225         },
226         std::runtime_error);
227 }
228 
229 TEST_F(BufferTest, BufferUpdateBmcFlagsPass)
230 {
231     constexpr size_t expectedWriteSize = 4;
232     constexpr uint8_t expectedBmcReadPtrOffset = 0x1c;
233     const std::vector<uint8_t> expectedNewBmcFlagsVector{0x04, 0x0, 0x0, 0x00};
234 
235     EXPECT_CALL(*dataInterfaceMockPtr,
236                 write(expectedBmcReadPtrOffset,
237                       ElementsAreArray(expectedNewBmcFlagsVector)))
238         .WillOnce(Return(expectedWriteSize));
239     EXPECT_NO_THROW(
240         bufferImpl->updateBmcFlags(static_cast<uint32_t>(BmcFlags::ready)));
241 
242     auto cachedHeader = bufferImpl->getCachedBufferHeader();
243     EXPECT_EQ(boost::endian::little_to_native(cachedHeader.bmcFlags),
244               static_cast<uint32_t>(BmcFlags::ready));
245 }
246 
247 class BufferWraparoundReadTest : public BufferTest
248 {
249   protected:
250     BufferWraparoundReadTest()
251     {
252         initializeFuncMock();
253     }
254     void initializeFuncMock()
255     {
256         // Initialize the memory and the cachedBufferHeader
257         InSequence s;
258         EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
259             .WillOnce(Return(testRegionSize));
260         size_t testProposedBufferSize = sizeof(struct CircularBufferHeader) +
261                                         testUeRegionSize + testQueueSize;
262         const std::vector<uint8_t> emptyArray(testProposedBufferSize, 0);
263         EXPECT_CALL(*dataInterfaceMockPtr,
264                     write(0, ElementsAreArray(emptyArray)))
265             .WillOnce(Return(testProposedBufferSize));
266 
267         EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
268             .WillOnce(Return(bufferHeaderSize));
269         EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
270                                                testQueueSize, testUeRegionSize,
271                                                testMagicNumber));
272     }
273     static constexpr size_t expectedWriteSize = 2;
274     static constexpr uint8_t expectedBmcReadPtrOffset = 0x20;
275     static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
276 
277     uint8_t* testInitializationHeaderPtr =
278         reinterpret_cast<uint8_t*>(&testInitializationHeader);
279 };
280 
281 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail)
282 {
283     InSequence s;
284 
285     size_t tooBigOffset = testQueueSize + 1;
286     EXPECT_THROW(
287         try {
288             bufferImpl->wraparoundRead(tooBigOffset, /* length */ 1);
289         } catch (const std::runtime_error& e) {
290             EXPECT_STREQ(
291                 e.what(),
292                 "[wraparoundRead] relativeOffset '257' was bigger than queueSize '256'");
293             throw;
294         },
295         std::runtime_error);
296 
297     size_t tooBigLength = testQueueSize + 1;
298     EXPECT_THROW(
299         try {
300             bufferImpl->wraparoundRead(/* relativeOffset */ 0, tooBigLength);
301         } catch (const std::runtime_error& e) {
302             EXPECT_STREQ(e.what(), "[wraparoundRead] length '257' was bigger "
303                                    "than queueSize '256'");
304             throw;
305         },
306         std::runtime_error);
307 }
308 
309 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadFails)
310 {
311     InSequence s;
312     size_t testLength = 0x10;
313     size_t testOffset = 0x20;
314 
315     // Fail the first read
316     std::vector<std::uint8_t> shortTestBytesRead(testLength - 1);
317     EXPECT_CALL(*dataInterfaceMockPtr,
318                 read(testOffset + expectedqueueOffset, testLength))
319         .WillOnce(Return(shortTestBytesRead));
320 
321     EXPECT_THROW(
322         try {
323             bufferImpl->wraparoundRead(testOffset, testLength);
324         } catch (const std::runtime_error& e) {
325             EXPECT_STREQ(e.what(),
326                          "[wraparoundRead] Read '15' which was not the "
327                          "requested length of '16'");
328             throw;
329         },
330         std::runtime_error);
331 }
332 
333 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass)
334 {
335     InSequence s;
336     size_t testLength = 0x10;
337     size_t testOffset = 0x20;
338 
339     // Successfully read all the requested length without a wrap around
340     std::vector<std::uint8_t> testBytesRead(testLength);
341     EXPECT_CALL(*dataInterfaceMockPtr,
342                 read(testOffset + expectedqueueOffset, testLength))
343         .WillOnce(Return(testBytesRead));
344 
345     // Call to updateReadPtr is triggered
346     const std::vector<uint8_t> expectedReadPtr{
347         static_cast<uint8_t>(testOffset + testLength), 0x0};
348     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
349                                              ElementsAreArray(expectedReadPtr)))
350         .WillOnce(Return(expectedWriteSize));
351 
352     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
353                 ElementsAreArray(testBytesRead));
354     struct CircularBufferHeader cachedBufferHeader =
355         bufferImpl->getCachedBufferHeader();
356     // The bmcReadPtr should have been updated
357     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
358               testOffset + testLength);
359 }
360 
361 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails)
362 {
363     InSequence s;
364     size_t testBytesLeft = 3;
365     size_t testLength = 0x10;
366     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
367 
368     // Read until the end of the queue
369     std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft);
370     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
371                                             testLength - testBytesLeft))
372         .WillOnce(Return(testBytesReadShort));
373 
374     // Read 1 byte short after wraparound
375     std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1);
376     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
377         .WillOnce(Return(testBytesLeftReadShort));
378 
379     EXPECT_THROW(
380         try {
381             bufferImpl->wraparoundRead(testOffset, testLength);
382         } catch (const std::runtime_error& e) {
383             EXPECT_STREQ(
384                 e.what(),
385                 "[wraparoundRead] Buffer wrapped around but read '2' which was "
386                 "not the requested lenght of '3'");
387             throw;
388         },
389         std::runtime_error);
390 }
391 
392 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses)
393 {
394     InSequence s;
395     size_t testBytesLeft = 3;
396     size_t testLength = 0x10;
397     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
398 
399     // Read to the end of the queue
400     std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10,
401                                                  9,  8,  7,  6,  5,  4};
402     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
403                                             testLength - testBytesLeft))
404         .WillOnce(Return(testBytesReadFirst));
405 
406     std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1};
407     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
408         .WillOnce(Return(testBytesReadSecond));
409 
410     // Call to updateReadPtr is triggered
411     const std::vector<uint8_t> expectedReadPtr{
412         static_cast<uint8_t>(testBytesLeft), 0x0};
413     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
414                                              ElementsAreArray(expectedReadPtr)))
415         .WillOnce(Return(expectedWriteSize));
416 
417     std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9,
418                                                8,  7,  6,  5,  4,  3,  2,  1};
419     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
420                 ElementsAreArray(expectedBytes));
421     struct CircularBufferHeader cachedBufferHeader =
422         bufferImpl->getCachedBufferHeader();
423     // The bmcReadPtr should have been updated to reflect the wraparound
424     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
425               testBytesLeft);
426 }
427 
428 TEST_F(BufferWraparoundReadTest, WrapAroundCornerCasePass)
429 {
430     InSequence s;
431     size_t testBytesLeft = 0;
432     size_t testLength = 4;
433     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
434 
435     // Read to the very end of the queue
436     std::vector<std::uint8_t> testBytes{4, 3, 2, 1};
437     EXPECT_CALL(*dataInterfaceMockPtr,
438                 read(testOffset + expectedqueueOffset, testLength))
439         .WillOnce(Return(testBytes));
440 
441     // Call to updateReadPtr is triggered, since we read to the very end of the
442     // buffer, update the readPtr up around to 0
443     const std::vector<uint8_t> expectedReadPtr{0x0, 0x0};
444     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
445                                              ElementsAreArray(expectedReadPtr)))
446         .WillOnce(Return(expectedWriteSize));
447 
448     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
449                 ElementsAreArray(testBytes));
450     struct CircularBufferHeader cachedBufferHeader =
451         bufferImpl->getCachedBufferHeader();
452     // The bmcReadPtr should have been updated to reflect the wraparound
453     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
454               0);
455 }
456 
457 class BufferEntryTest : public BufferWraparoundReadTest
458 {
459   protected:
460     BufferEntryTest()
461     {
462         testEntryHeader.sequenceId = testSequenceId;
463         testEntryHeader.entrySize = testEntrySize;
464         testEntryHeader.checksum = testChecksum;
465         testEntryHeader.rdeCommandType = testRdeCommandType;
466     }
467     ~BufferEntryTest() override = default;
468 
469     void wraparoundReadMock(const uint32_t relativeOffset,
470                             std::span<std::uint8_t> expetedBytesOutput)
471     {
472         InSequence s;
473         const uint32_t queueSizeToQueueEnd = testQueueSize - relativeOffset;
474 
475         // This will wrap, split the read mocks in 2
476         if (expetedBytesOutput.size() > queueSizeToQueueEnd)
477         {
478             EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
479                 .WillOnce(Return(std::vector<std::uint8_t>(
480                     expetedBytesOutput.begin(),
481                     expetedBytesOutput.begin() + queueSizeToQueueEnd)));
482             EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
483                 .WillOnce(Return(std::vector<std::uint8_t>(
484                     expetedBytesOutput.begin() + queueSizeToQueueEnd,
485                     expetedBytesOutput.end())));
486         }
487         else
488         {
489             EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
490                 .WillOnce(Return(std::vector<std::uint8_t>(
491                     expetedBytesOutput.begin(), expetedBytesOutput.end())));
492         }
493 
494         EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
495             .WillOnce(Return(expectedWriteSize));
496     }
497 
498     static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
499     static constexpr uint16_t testSequenceId = 0;
500     static constexpr uint16_t testEntrySize = 0x20;
501     static constexpr uint8_t testRdeCommandType = 0x01;
502     // Calculated checksum for the header
503     static constexpr uint8_t testChecksum =
504         (testSequenceId ^ testEntrySize ^ testRdeCommandType);
505     size_t testOffset = 0x20;
506 
507     struct QueueEntryHeader testEntryHeader
508     {};
509 };
510 
511 TEST_F(BufferEntryTest, ReadEntryHeaderPass)
512 {
513     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
514     std::vector<uint8_t> testEntryHeaderVector(
515         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
516     wraparoundReadMock(testOffset, testEntryHeaderVector);
517     EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader);
518     // Check the bmcReadPtr
519     struct CircularBufferHeader cachedBufferHeader =
520         bufferImpl->getCachedBufferHeader();
521     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
522               testOffset + testEntryHeaderVector.size());
523 }
524 
525 TEST_F(BufferEntryTest, ReadEntryChecksumFail)
526 {
527     InSequence s;
528     std::vector<uint8_t> testEntryVector(testEntrySize);
529     // Offset the checksum by 1
530     testEntryHeader.checksum += 1;
531     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
532     std::vector<uint8_t> testEntryHeaderVector(
533         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
534     wraparoundReadMock(testOffset, testEntryHeaderVector);
535     wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
536     EXPECT_THROW(
537         try {
538             bufferImpl->readEntry(testOffset);
539         } catch (const std::runtime_error& e) {
540             // Calculation: testChecksum (0x21) XOR (0x22) = 3
541             EXPECT_STREQ(e.what(),
542                          "[readEntry] Checksum was '3', expected '0'");
543             throw;
544         },
545         std::runtime_error);
546 }
547 
548 TEST_F(BufferEntryTest, ReadEntryPass)
549 {
550     InSequence s;
551     // We expect this will bump checksum up by "testEntrySize" = 0xff ^ 0xff ...
552     // (20 times) = 0 therefore leave the checksum as is
553     std::vector<uint8_t> testEntryVector(testEntrySize, 0xff);
554     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
555     std::vector<uint8_t> testEntryHeaderVector(
556         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
557     // Set testOffset so that we can test the wraparound here as well on our
558     // second read for the entry (by 1 byte)
559     testOffset = testQueueSize - entryHeaderSize - 1;
560     wraparoundReadMock(testOffset, testEntryHeaderVector);
561     wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
562 
563     EntryPair testedEntryPair;
564     EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry(testOffset));
565     EXPECT_EQ(testedEntryPair.first, testEntryHeader);
566     EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
567     struct CircularBufferHeader cachedBufferHeader =
568         bufferImpl->getCachedBufferHeader();
569     // The bmcReadPtr should have been updated to reflect the wraparound
570     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
571               testEntrySize - 1);
572 }
573 
574 class BufferReadErrorLogsTest : public BufferEntryTest
575 {
576   protected:
577     BufferReadErrorLogsTest() = default;
578 
579     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
580     size_t entryAndHeaderSize = entryHeaderSize + testEntrySize;
581 };
582 
583 TEST_F(BufferReadErrorLogsTest, PtrsTooBigFail)
584 {
585     InSequence s;
586     // Set the biosWritePtr too big
587     testInitializationHeader.biosWritePtr =
588         boost::endian::native_to_little((testQueueSize + 1));
589     initializeFuncMock();
590 
591     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
592         .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
593                                               testInitializationHeaderPtr +
594                                                   bufferHeaderSize)));
595     EXPECT_THROW(
596         try {
597             bufferImpl->readErrorLogs();
598         } catch (const std::runtime_error& e) {
599             EXPECT_STREQ(e.what(),
600                          "[readErrorLogs] currentBiosWritePtr was '257' "
601                          "which was bigger than queueSize '256'");
602             throw;
603         },
604         std::runtime_error);
605 
606     // Reset the biosWritePtr and set the bmcReadPtr too big
607     testInitializationHeader.biosWritePtr = 0;
608     initializeFuncMock();
609     testInitializationHeader.bmcReadPtr =
610         boost::endian::native_to_little((testQueueSize + 1));
611     initializeFuncMock();
612 
613     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
614         .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
615                                               testInitializationHeaderPtr +
616                                                   bufferHeaderSize)));
617     EXPECT_THROW(
618         try {
619             bufferImpl->readErrorLogs();
620         } catch (const std::runtime_error& e) {
621             EXPECT_STREQ(e.what(), "[readErrorLogs] currentReadPtr was '257' "
622                                    "which was bigger than queueSize '256'");
623             throw;
624         },
625         std::runtime_error);
626 }
627 
628 TEST_F(BufferReadErrorLogsTest, IdenticalPtrsPass)
629 {
630     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
631         .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
632                                               testInitializationHeaderPtr +
633                                                   bufferHeaderSize)));
634     EXPECT_NO_THROW(bufferImpl->readErrorLogs());
635 }
636 
637 TEST_F(BufferReadErrorLogsTest, NoWraparoundPass)
638 {
639     InSequence s;
640     // Set the biosWritePtr to 1 entryHeader + entry size
641     testInitializationHeader.biosWritePtr =
642         boost::endian::native_to_little((entryAndHeaderSize));
643     initializeFuncMock();
644     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
645         .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
646                                               testInitializationHeaderPtr +
647                                                   bufferHeaderSize)));
648     std::vector<uint8_t> testEntryHeaderVector(
649         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
650     std::vector<uint8_t> testEntryVector(testEntrySize);
651     wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
652     wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
653 
654     std::vector<EntryPair> entryPairs;
655     EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
656 
657     // Check that we only read one entryPair and that the content is correct
658     EXPECT_EQ(entryPairs.size(), 1);
659     EXPECT_EQ(entryPairs[0].first, testEntryHeader);
660     EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
661 }
662 
663 TEST_F(BufferReadErrorLogsTest, WraparoundMultiplEntryPass)
664 {
665     InSequence s;
666     // Set the bmcReadPtr to 1 entryHeader + entry size from the "end" exactly
667     uint32_t entryAndHeaderSizeAwayFromEnd = testQueueSize - entryAndHeaderSize;
668     testInitializationHeader.bmcReadPtr =
669         boost::endian::native_to_little(entryAndHeaderSizeAwayFromEnd);
670     // Set the biosWritePtr to 1 entryHeader + entry size from the "beginning"
671     testInitializationHeader.biosWritePtr = entryAndHeaderSize;
672     initializeFuncMock();
673     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
674         .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
675                                               testInitializationHeaderPtr +
676                                                   bufferHeaderSize)));
677 
678     std::vector<uint8_t> testEntryHeaderVector(
679         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
680     std::vector<uint8_t> testEntryVector(testEntrySize);
681     wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd,
682                        testEntryHeaderVector);
683     wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd +
684                            entryHeaderSize,
685                        testEntryVector);
686     wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize,
687                        testEntryHeaderVector);
688     wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize +
689                            entryHeaderSize,
690                        testEntryVector);
691 
692     std::vector<EntryPair> entryPairs;
693     EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
694 
695     // Check that we only read one entryPair and that the content is correct
696     EXPECT_EQ(entryPairs.size(), 2);
697     EXPECT_EQ(entryPairs[0].first, testEntryHeader);
698     EXPECT_EQ(entryPairs[1].first, testEntryHeader);
699     EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
700     EXPECT_THAT(entryPairs[1].second, ElementsAreArray(testEntryVector));
701 }
702 
703 TEST_F(BufferReadErrorLogsTest, WraparoundMismatchingPtrsFail)
704 {
705     InSequence s;
706     testInitializationHeader.bmcReadPtr = boost::endian::native_to_little(0);
707     // Make the biosWritePtr intentially 1 smaller than expected
708     testInitializationHeader.biosWritePtr =
709         boost::endian::native_to_little(entryAndHeaderSize - 1);
710     initializeFuncMock();
711     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
712         .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
713                                               testInitializationHeaderPtr +
714                                                   bufferHeaderSize)));
715 
716     std::vector<uint8_t> testEntryHeaderVector(
717         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
718     std::vector<uint8_t> testEntryVector(testEntrySize);
719     wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
720     wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
721 
722     EXPECT_THROW(
723         try {
724             bufferImpl->readErrorLogs();
725         } catch (const std::runtime_error& e) {
726             EXPECT_STREQ(
727                 e.what(),
728                 "[readErrorLogs] biosWritePtr '37' and bmcReaddPtr '38' "
729                 "are not identical after reading through all the logs");
730             throw;
731         },
732         std::runtime_error);
733 }
734 
735 } // namespace
736 } // namespace bios_bmc_smm_error_logger
737