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 
208 class BufferWraparoundReadTest : public BufferTest
209 {
210   protected:
211     BufferWraparoundReadTest()
212     {
213         // Initialize the memory and the cachedBufferHeader
214         InSequence s;
215         EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
216             .WillOnce(Return(testRegionSize));
217         size_t testProposedBufferSize = sizeof(struct CircularBufferHeader) +
218                                         testUeRegionSize + testQueueSize;
219         const std::vector<uint8_t> emptyArray(testProposedBufferSize, 0);
220         EXPECT_CALL(*dataInterfaceMockPtr,
221                     write(0, ElementsAreArray(emptyArray)))
222             .WillOnce(Return(testProposedBufferSize));
223 
224         uint8_t* testInitializationHeaderPtr =
225             reinterpret_cast<uint8_t*>(&testInitializationHeader);
226         EXPECT_CALL(*dataInterfaceMockPtr,
227                     write(0, ElementsAreArray(testInitializationHeaderPtr,
228                                               bufferHeaderSize)))
229             .WillOnce(Return(bufferHeaderSize));
230         EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
231                                                testQueueSize, testUeRegionSize,
232                                                testMagicNumber));
233     }
234     static constexpr size_t expectedWriteSize = 2;
235     static constexpr uint8_t expectedBmcReadPtrOffset = 0x20;
236     static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
237 };
238 
239 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail)
240 {
241     InSequence s;
242 
243     size_t tooBigOffset = testQueueSize + 1;
244     EXPECT_THROW(
245         try {
246             bufferImpl->wraparoundRead(tooBigOffset, /* length */ 1);
247         } catch (const std::runtime_error& e) {
248             EXPECT_STREQ(
249                 e.what(),
250                 "[wraparoundRead] relativeOffset '257' was bigger than queueSize '256'");
251             throw;
252         },
253         std::runtime_error);
254 
255     size_t tooBigLength = testQueueSize + 1;
256     EXPECT_THROW(
257         try {
258             bufferImpl->wraparoundRead(/* relativeOffset */ 0, tooBigLength);
259         } catch (const std::runtime_error& e) {
260             EXPECT_STREQ(e.what(), "[wraparoundRead] length '257' was bigger "
261                                    "than queueSize '256'");
262             throw;
263         },
264         std::runtime_error);
265 }
266 
267 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadFails)
268 {
269     InSequence s;
270     size_t testLength = 0x10;
271     size_t testOffset = 0x20;
272 
273     // Fail the first read
274     std::vector<std::uint8_t> shortTestBytesRead(testLength - 1);
275     EXPECT_CALL(*dataInterfaceMockPtr,
276                 read(testOffset + expectedqueueOffset, testLength))
277         .WillOnce(Return(shortTestBytesRead));
278 
279     EXPECT_THROW(
280         try {
281             bufferImpl->wraparoundRead(testOffset, testLength);
282         } catch (const std::runtime_error& e) {
283             EXPECT_STREQ(e.what(),
284                          "[wraparoundRead] Read '15' which was not the "
285                          "requested length of '16'");
286             throw;
287         },
288         std::runtime_error);
289 }
290 
291 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass)
292 {
293     InSequence s;
294     size_t testLength = 0x10;
295     size_t testOffset = 0x20;
296 
297     // Successfully read all the requested length without a wrap around
298     std::vector<std::uint8_t> testBytesRead(testLength);
299     EXPECT_CALL(*dataInterfaceMockPtr,
300                 read(testOffset + expectedqueueOffset, testLength))
301         .WillOnce(Return(testBytesRead));
302 
303     // Call to updateReadPtr is triggered
304     const std::vector<uint8_t> expectedReadPtr{
305         static_cast<uint8_t>(testOffset + testLength), 0x0};
306     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
307                                              ElementsAreArray(expectedReadPtr)))
308         .WillOnce(Return(expectedWriteSize));
309 
310     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
311                 ElementsAreArray(testBytesRead));
312     struct CircularBufferHeader cachedBufferHeader =
313         bufferImpl->getCachedBufferHeader();
314     // The bmcReadPtr should have been updated
315     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
316               testOffset + testLength);
317 }
318 
319 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails)
320 {
321     InSequence s;
322     size_t testBytesLeft = 3;
323     size_t testLength = 0x10;
324     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
325 
326     // Read until the end of the queue
327     std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft);
328     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
329                                             testLength - testBytesLeft))
330         .WillOnce(Return(testBytesReadShort));
331 
332     // Read 1 byte short after wraparound
333     std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1);
334     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
335         .WillOnce(Return(testBytesLeftReadShort));
336 
337     EXPECT_THROW(
338         try {
339             bufferImpl->wraparoundRead(testOffset, testLength);
340         } catch (const std::runtime_error& e) {
341             EXPECT_STREQ(
342                 e.what(),
343                 "[wraparoundRead] Buffer wrapped around but read '2' which was "
344                 "not the requested lenght of '3'");
345             throw;
346         },
347         std::runtime_error);
348 }
349 
350 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses)
351 {
352     InSequence s;
353     size_t testBytesLeft = 3;
354     size_t testLength = 0x10;
355     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
356 
357     // Read to the end of the queue
358     std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10,
359                                                  9,  8,  7,  6,  5,  4};
360     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
361                                             testLength - testBytesLeft))
362         .WillOnce(Return(testBytesReadFirst));
363 
364     std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1};
365     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
366         .WillOnce(Return(testBytesReadSecond));
367 
368     // Call to updateReadPtr is triggered
369     const std::vector<uint8_t> expectedReadPtr{
370         static_cast<uint8_t>(testBytesLeft), 0x0};
371     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
372                                              ElementsAreArray(expectedReadPtr)))
373         .WillOnce(Return(expectedWriteSize));
374 
375     std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9,
376                                                8,  7,  6,  5,  4,  3,  2,  1};
377     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
378                 ElementsAreArray(expectedBytes));
379     struct CircularBufferHeader cachedBufferHeader =
380         bufferImpl->getCachedBufferHeader();
381     // The bmcReadPtr should have been updated to reflect the wraparound
382     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
383               testBytesLeft);
384 }
385 
386 class BufferEntryTest : public BufferWraparoundReadTest
387 {
388   protected:
389     BufferEntryTest()
390     {
391         testEntryHeader.sequenceId = testSequenceId;
392         testEntryHeader.entrySize = testEntrySize;
393         testEntryHeader.checksum = testChecksum;
394         testEntryHeader.rdeCommandType = testRdeCommandType;
395     }
396     ~BufferEntryTest() override = default;
397 
398     void wraparoundReadMock(const uint32_t relativeOffset,
399                             std::span<std::uint8_t> expetedBytesOutput)
400     {
401         InSequence s;
402         const uint32_t queueSizeToQueueEnd = testQueueSize - relativeOffset;
403 
404         // This will wrap, split the read mocks in 2
405         if (expetedBytesOutput.size() > queueSizeToQueueEnd)
406         {
407             EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
408                 .WillOnce(Return(std::vector<std::uint8_t>(
409                     expetedBytesOutput.begin(),
410                     expetedBytesOutput.begin() + queueSizeToQueueEnd)));
411             EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
412                 .WillOnce(Return(std::vector<std::uint8_t>(
413                     expetedBytesOutput.begin() + queueSizeToQueueEnd,
414                     expetedBytesOutput.end())));
415         }
416         else
417         {
418             EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
419                 .WillOnce(Return(std::vector<std::uint8_t>(
420                     expetedBytesOutput.begin(), expetedBytesOutput.end())));
421         }
422 
423         EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
424             .WillOnce(Return(expectedWriteSize));
425     }
426 
427     static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
428     static constexpr uint16_t testSequenceId = 0;
429     static constexpr uint16_t testEntrySize = 0x20;
430     // Calculated checksum for the header (0x100 - 0 - 0x20 - 0x01) & 0xff
431     static constexpr uint8_t testChecksum = 0xdf;
432     static constexpr uint8_t testRdeCommandType = 0x01;
433     size_t testOffset = 0x20;
434 
435     struct QueueEntryHeader testEntryHeader
436     {};
437 };
438 
439 TEST_F(BufferEntryTest, ReadEntryHeaderPass)
440 {
441     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
442     std::vector<uint8_t> testEntryHeaderVector(
443         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
444     wraparoundReadMock(testOffset, testEntryHeaderVector);
445     EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader);
446     // Check the bmcReadPtr
447     struct CircularBufferHeader cachedBufferHeader =
448         bufferImpl->getCachedBufferHeader();
449     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
450               testOffset + testEntryHeaderVector.size());
451 }
452 
453 TEST_F(BufferEntryTest, ReadEntryChecksumFail)
454 {
455     InSequence s;
456     // We expect this will bump checksum up by "testEntrySize" = 0x20
457     std::vector<uint8_t> testEntryVector(testEntrySize, 1);
458     // Offset the checksum by 1
459     testEntryHeader.checksum -= (0x20 - 1);
460     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
461     std::vector<uint8_t> testEntryHeaderVector(
462         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
463     wraparoundReadMock(testOffset, testEntryHeaderVector);
464     wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
465     EXPECT_THROW(
466         try {
467             bufferImpl->readEntry(testOffset);
468         } catch (const std::runtime_error& e) {
469             EXPECT_STREQ(e.what(),
470                          "[readEntry] Checksum was '1', expected '0'");
471             throw;
472         },
473         std::runtime_error);
474 }
475 
476 TEST_F(BufferEntryTest, ReadEntryPass)
477 {
478     InSequence s;
479     // We expect this will bump checksum up by "testEntrySize" = 0xff * 0x20 =
480     // 0x1fe0 -> 0x1fe0 & 0xff = 0xe0.
481     std::vector<uint8_t> testEntryVector(testEntrySize, 0xff);
482     testEntryHeader.checksum -= (0xe0);
483     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
484     std::vector<uint8_t> testEntryHeaderVector(
485         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
486     // Set testOffset so that we can test the wraparound here as well on our
487     // second read for the entry (by 1 byte)
488     testOffset = testQueueSize - entryHeaderSize - 1;
489     wraparoundReadMock(testOffset, testEntryHeaderVector);
490     wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
491 
492     EntryPair testedEntryPair;
493     EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry(testOffset));
494     EXPECT_EQ(testedEntryPair.first, testEntryHeader);
495     EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
496     struct CircularBufferHeader cachedBufferHeader =
497         bufferImpl->getCachedBufferHeader();
498     // The bmcReadPtr should have been updated to reflect the wraparound
499     EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
500               testEntrySize - 1);
501 }
502 
503 } // namespace
504 } // namespace bios_bmc_smm_error_logger
505