xref: /openbmc/bios-bmc-smm-error-logger/test/buffer_test.cpp (revision c20d1216540b3c7668b2b62732ac78cad671c149)
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