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, TooBigReadFail)
240 {
241     InSequence s;
242     size_t tooBigLength = testQueueSize + 1;
243     EXPECT_THROW(
244         try {
245             bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength);
246         } catch (const std::runtime_error& e) {
247             EXPECT_STREQ(
248                 e.what(),
249                 "[wraparoundRead] length '257' + additionalBoundaryCheck '0' "
250                 "was bigger than queueSize '256'");
251             throw;
252         },
253         std::runtime_error);
254 }
255 
256 TEST_F(BufferWraparoundReadTest, TooBigReadWithAdditionalBoundaryCheckFail)
257 {
258     InSequence s;
259     // Use additionalBoundaryCheck to still go over the queueSize by 1
260     size_t additionalBoundaryCheck = 10;
261     size_t tooBigLength = testQueueSize - additionalBoundaryCheck + 1;
262     EXPECT_THROW(
263         try {
264             bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength,
265                                        additionalBoundaryCheck);
266         } catch (const std::runtime_error& e) {
267             EXPECT_STREQ(
268                 e.what(),
269                 "[wraparoundRead] length '247' + additionalBoundaryCheck '10' "
270                 "was bigger than queueSize '256'");
271             throw;
272         },
273         std::runtime_error);
274 }
275 
276 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass)
277 {
278     InSequence s;
279     size_t testLength = 0x10;
280     size_t testOffset = 0x50;
281 
282     // Successfully read all the requested length without a wrap around
283     std::vector<std::uint8_t> testBytesRead(testLength);
284     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength))
285         .WillOnce(Return(testBytesRead));
286 
287     // Call to updateReadPtr is triggered
288     const std::vector<uint8_t> expectedReadPtr{
289         static_cast<uint8_t>(testOffset + testLength), 0x0};
290     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
291                                              ElementsAreArray(expectedReadPtr)))
292         .WillOnce(Return(expectedWriteSize));
293 
294     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
295                 ElementsAreArray(testBytesRead));
296 }
297 
298 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails)
299 {
300     InSequence s;
301     size_t testBytesLeft = 3;
302     size_t testLength = 0x10;
303     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
304 
305     // Read 3 bytes short
306     std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft);
307     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength))
308         .WillOnce(Return(testBytesReadShort));
309 
310     // Read 1 byte short after wraparound
311     std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1);
312     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
313         .WillOnce(Return(testBytesLeftReadShort));
314 
315     EXPECT_THROW(
316         try {
317             bufferImpl->wraparoundRead(testOffset, testLength);
318         } catch (const std::runtime_error& e) {
319             EXPECT_STREQ(e.what(),
320                          "[wraparoundRead] Buffer wrapped around but was not "
321                          "able to read all of the requested info. "
322                          "Bytes remaining to read '1' of '16'");
323             throw;
324         },
325         std::runtime_error);
326 }
327 
328 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses)
329 {
330     InSequence s;
331     size_t testBytesLeft = 3;
332     size_t testLength = 0x10;
333     size_t testOffset = testQueueSize - (testLength - testBytesLeft);
334 
335     // Read 3 bytes short
336     std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10,
337                                                  9,  8,  7,  6,  5,  4};
338     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength))
339         .WillOnce(Return(testBytesReadFirst));
340 
341     std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1};
342     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
343         .WillOnce(Return(testBytesReadSecond));
344 
345     // Call to updateReadPtr is triggered
346     const std::vector<uint8_t> expectedReadPtr{
347         static_cast<uint8_t>(testBytesLeft), 0x0};
348     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
349                                              ElementsAreArray(expectedReadPtr)))
350         .WillOnce(Return(expectedWriteSize));
351 
352     std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9,
353                                                8,  7,  6,  5,  4,  3,  2,  1};
354     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
355                 ElementsAreArray(expectedBytes));
356 }
357 
358 class BufferEntryTest : public BufferWraparoundReadTest
359 {
360   protected:
361     BufferEntryTest()
362     {
363         testEntryHeader.sequenceId = testSequenceId;
364         testEntryHeader.entrySize = testEntrySize;
365         testEntryHeader.checksum = testChecksum;
366         testEntryHeader.rdeCommandType = testRdeCommandType;
367     }
368     ~BufferEntryTest() override = default;
369 
370     void wraparoundReadMock(std::span<std::uint8_t> expetedBytesOutput)
371     {
372         EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
373             .WillOnce(Return(std::vector<std::uint8_t>(
374                 expetedBytesOutput.begin(), expetedBytesOutput.end())));
375 
376         EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
377             .WillOnce(Return(expectedWriteSize));
378     }
379 
380     static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
381     static constexpr uint16_t testSequenceId = 0;
382     static constexpr uint16_t testEntrySize = 0x20;
383     // Calculated checksum for the header (0x100 - 0 - 0x20 - 0x01) & 0xff
384     static constexpr uint8_t testChecksum = 0xdf;
385     static constexpr uint8_t testRdeCommandType = 0x01;
386     size_t testOffset = 0x50;
387 
388     struct QueueEntryHeader testEntryHeader
389     {};
390 };
391 
392 TEST_F(BufferEntryTest, ReadEntryHeaderPass)
393 {
394     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
395     std::vector<uint8_t> testEntryHeaderVector(
396         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
397     wraparoundReadMock(testEntryHeaderVector);
398     EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader);
399 }
400 
401 TEST_F(BufferEntryTest, ReadEntryChecksumFail)
402 {
403     InSequence s;
404     // We expect this will bump checksum up by "testEntrySize" = 0x20
405     std::vector<uint8_t> testEntryVector(testEntrySize, 1);
406     // Offset the checksum by 1
407     testEntryHeader.checksum -= (0x20 - 1);
408     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
409     std::vector<uint8_t> testEntryHeaderVector(
410         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
411     wraparoundReadMock(testEntryHeaderVector);
412 
413     wraparoundReadMock(testEntryVector);
414     EXPECT_THROW(
415         try {
416             bufferImpl->readEntry(testOffset);
417         } catch (const std::runtime_error& e) {
418             EXPECT_STREQ(e.what(),
419                          "[readEntry] Checksum was '1', expected '0'");
420             throw;
421         },
422         std::runtime_error);
423 }
424 
425 TEST_F(BufferEntryTest, ReadEntryPass)
426 {
427     InSequence s;
428     // We expect this will bump checksum up by "testEntrySize" = 0x40
429     std::vector<uint8_t> testEntryVector(testEntrySize, 2);
430     testEntryHeader.checksum -= (0x40);
431     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
432     std::vector<uint8_t> testEntryHeaderVector(
433         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
434     wraparoundReadMock(testEntryHeaderVector);
435     wraparoundReadMock(testEntryVector);
436 
437     EntryPair testedEntryPair;
438     EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry(testOffset));
439     EXPECT_EQ(testedEntryPair.first, testEntryHeader);
440     EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
441 }
442 
443 } // namespace
444 } // namespace bios_bmc_smm_error_logger
445