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 
66     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
67         .WillOnce(Return(testRegionSize));
68     const std::vector<uint8_t> emptyArray(testRegionSize, 0);
69     // Return a smaller write than the intended testRegionSize to test the error
70     EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
71         .WillOnce(Return(testRegionSize - 1));
72     EXPECT_THROW(
73         try {
74             bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
75                                    testUeRegionSize, testMagicNumber);
76         } catch (const std::runtime_error& e) {
77             EXPECT_STREQ(e.what(), "Buffer initialization only erased '511'");
78             throw;
79         },
80         std::runtime_error);
81     EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
82 
83     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
84         .WillOnce(Return(testRegionSize));
85     EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
86         .WillOnce(Return(testRegionSize));
87     // Return a smaller write than the intended initializationHeader to test the
88     // error
89     EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)).WillOnce(Return(0));
90     EXPECT_THROW(
91         try {
92             bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
93                                    testUeRegionSize, testMagicNumber);
94         } catch (const std::runtime_error& e) {
95             EXPECT_STREQ(
96                 e.what(),
97                 "Buffer initialization buffer header write only wrote '0'");
98             throw;
99         },
100         std::runtime_error);
101     EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
102 }
103 
104 TEST_F(BufferTest, BufferInitializePass)
105 {
106     InSequence s;
107     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
108         .WillOnce(Return(testRegionSize));
109     const std::vector<uint8_t> emptyArray(testRegionSize, 0);
110     EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
111         .WillOnce(Return(testRegionSize));
112 
113     uint8_t* testInitializationHeaderPtr =
114         reinterpret_cast<uint8_t*>(&testInitializationHeader);
115     EXPECT_CALL(*dataInterfaceMockPtr,
116                 write(0, ElementsAreArray(testInitializationHeaderPtr,
117                                           bufferHeaderSize)))
118         .WillOnce(Return(bufferHeaderSize));
119     EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
120                                            testQueueSize, testUeRegionSize,
121                                            testMagicNumber));
122     EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
123 }
124 
125 TEST_F(BufferTest, BufferHeaderReadFail)
126 {
127     std::vector<std::uint8_t> testBytesRead{};
128     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
129         .WillOnce(Return(testBytesRead));
130     EXPECT_THROW(
131         try {
132             bufferImpl->readBufferHeader();
133         } catch (const std::runtime_error& e) {
134             EXPECT_STREQ(e.what(),
135                          "Buffer header read only read '0', expected '48'");
136             throw;
137         },
138         std::runtime_error);
139 }
140 
141 TEST_F(BufferTest, BufferHeaderReadPass)
142 {
143     uint8_t* testInitializationHeaderPtr =
144         reinterpret_cast<uint8_t*>(&testInitializationHeader);
145     std::vector<uint8_t> testInitializationHeaderVector(
146         testInitializationHeaderPtr,
147         testInitializationHeaderPtr + bufferHeaderSize);
148 
149     EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
150         .WillOnce(Return(testInitializationHeaderVector));
151     EXPECT_NO_THROW(bufferImpl->readBufferHeader());
152     EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
153 }
154 
155 TEST_F(BufferTest, BufferUpdateReadPtrFail)
156 {
157     // Return write size that is not 2 which is sizeof(little_uint16_t)
158     constexpr size_t wrongWriteSize = 1;
159     EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
160         .WillOnce(Return(wrongWriteSize));
161     EXPECT_THROW(
162         try {
163             bufferImpl->updateReadPtr(0);
164         } catch (const std::runtime_error& e) {
165             EXPECT_STREQ(
166                 e.what(),
167                 "[updateReadPtr] Wrote '1' bytes, instead of expected '2'");
168             throw;
169         },
170         std::runtime_error);
171 }
172 
173 TEST_F(BufferTest, BufferUpdateReadPtrPass)
174 {
175     constexpr size_t expectedWriteSize = 2;
176     constexpr uint8_t expectedBmcReadPtrOffset = 0x20;
177     // Check that we truncate the highest 16bits
178     const uint32_t testNewReadPtr = 0x99881234;
179     const std::vector<uint8_t> expectedReadPtr{0x34, 0x12};
180 
181     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
182                                              ElementsAreArray(expectedReadPtr)))
183         .WillOnce(Return(expectedWriteSize));
184     EXPECT_NO_THROW(bufferImpl->updateReadPtr(testNewReadPtr));
185 }
186 
187 class BufferWraparoundReadTest : public BufferTest
188 {
189   protected:
190     BufferWraparoundReadTest()
191     {
192         // Initialize the memory and the cachedBufferHeader
193         InSequence s;
194         EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
195             .WillOnce(Return(testRegionSize));
196         const std::vector<uint8_t> emptyArray(testRegionSize, 0);
197         EXPECT_CALL(*dataInterfaceMockPtr,
198                     write(0, ElementsAreArray(emptyArray)))
199             .WillOnce(Return(testRegionSize));
200 
201         uint8_t* testInitializationHeaderPtr =
202             reinterpret_cast<uint8_t*>(&testInitializationHeader);
203         EXPECT_CALL(*dataInterfaceMockPtr,
204                     write(0, ElementsAreArray(testInitializationHeaderPtr,
205                                               bufferHeaderSize)))
206             .WillOnce(Return(bufferHeaderSize));
207         EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
208                                                testQueueSize, testUeRegionSize,
209                                                testMagicNumber));
210     }
211     static constexpr size_t expectedWriteSize = 2;
212     static constexpr uint8_t expectedBmcReadPtrOffset = 0x20;
213     static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
214 };
215 
216 TEST_F(BufferWraparoundReadTest, TooBigReadFail)
217 {
218     InSequence s;
219     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
220         .WillOnce(Return(testRegionSize));
221     size_t tooBigLength = testRegionSize - expectedqueueOffset + 1;
222     EXPECT_THROW(
223         try {
224             bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength);
225         } catch (const std::runtime_error& e) {
226             EXPECT_STREQ(e.what(),
227                          "[wraparoundRead] queueOffset '128' + length '385' + "
228                          "additionalBoundaryCheck '0' + was "
229                          "bigger than memoryRegionSize '512'");
230             throw;
231         },
232         std::runtime_error);
233 }
234 
235 TEST_F(BufferWraparoundReadTest, TooBigReadWithAdditionalBoundaryCheckFail)
236 {
237     InSequence s;
238     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
239         .WillOnce(Return(testRegionSize));
240     // Use additionalBoundaryCheck to still go over the memoryRegionSize by 1
241     size_t additionalBoundaryCheck = 10;
242     size_t tooBigLength =
243         testRegionSize - expectedqueueOffset - additionalBoundaryCheck + 1;
244     EXPECT_THROW(
245         try {
246             bufferImpl->wraparoundRead(/* offset */ 0, tooBigLength,
247                                        additionalBoundaryCheck);
248         } catch (const std::runtime_error& e) {
249             EXPECT_STREQ(e.what(),
250                          "[wraparoundRead] queueOffset '128' + length '375' + "
251                          "additionalBoundaryCheck '10' + was "
252                          "bigger than memoryRegionSize '512'");
253             throw;
254         },
255         std::runtime_error);
256 }
257 
258 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass)
259 {
260     InSequence s;
261     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
262         .WillOnce(Return(testRegionSize));
263     size_t testLength = 0x10;
264     size_t testOffset = 0x50;
265 
266     // Successfully read all the requested length without a wrap around
267     std::vector<std::uint8_t> testBytesRead(testLength);
268     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength))
269         .WillOnce(Return(testBytesRead));
270 
271     // Call to updateReadPtr is triggered
272     const std::vector<uint8_t> expectedReadPtr{
273         static_cast<uint8_t>(testOffset + testLength), 0x0};
274     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
275                                              ElementsAreArray(expectedReadPtr)))
276         .WillOnce(Return(expectedWriteSize));
277 
278     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
279                 ElementsAreArray(testBytesRead));
280 }
281 
282 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails)
283 {
284     InSequence s;
285     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
286         .WillOnce(Return(testRegionSize));
287     size_t testBytesLeft = 3;
288     size_t testLength = 0x10;
289     size_t testOffset = testRegionSize - (testLength - testBytesLeft);
290 
291     // Read 3 bytes short
292     std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft);
293     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength))
294         .WillOnce(Return(testBytesReadShort));
295 
296     // Read 1 byte short after wraparound
297     std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1);
298     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
299         .WillOnce(Return(testBytesLeftReadShort));
300 
301     EXPECT_THROW(
302         try {
303             bufferImpl->wraparoundRead(testOffset, testLength);
304         } catch (const std::runtime_error& e) {
305             EXPECT_STREQ(e.what(),
306                          "[wraparoundRead] Buffer wrapped around but was not "
307                          "able to read all of the requested info. "
308                          "Bytes remaining to read '1' of '16'");
309             throw;
310         },
311         std::runtime_error);
312 }
313 
314 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses)
315 {
316     InSequence s;
317     EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
318         .WillOnce(Return(testRegionSize));
319     size_t testBytesLeft = 3;
320     size_t testLength = 0x10;
321     size_t testOffset = testRegionSize - (testLength - testBytesLeft);
322 
323     // Read 3 bytes short
324     std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10,
325                                                  9,  8,  7,  6,  5,  4};
326     EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset, testLength))
327         .WillOnce(Return(testBytesReadFirst));
328 
329     std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1};
330     EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
331         .WillOnce(Return(testBytesReadSecond));
332 
333     // Call to updateReadPtr is triggered
334     const std::vector<uint8_t> expectedReadPtr{
335         static_cast<uint8_t>(expectedqueueOffset + testBytesLeft), 0x0};
336     EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
337                                              ElementsAreArray(expectedReadPtr)))
338         .WillOnce(Return(expectedWriteSize));
339 
340     std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9,
341                                                8,  7,  6,  5,  4,  3,  2,  1};
342     EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
343                 ElementsAreArray(expectedBytes));
344 }
345 
346 class BufferEntryHeaderTest : public BufferWraparoundReadTest
347 {
348   protected:
349     BufferEntryHeaderTest()
350     {
351         testEntryHeader.sequenceId = testSequenceId;
352         testEntryHeader.entrySize = testEntrySize;
353         testEntryHeader.checksum = testChecksum;
354         testEntryHeader.rdeCommandType = testRdeCommandType;
355     }
356     ~BufferEntryHeaderTest() override = default;
357 
358     void wraparoundReadMock(std::span<std::uint8_t> expetedBytesOutput)
359     {
360         EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
361             .WillOnce(Return(testRegionSize));
362         EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
363             .WillOnce(Return(std::vector<std::uint8_t>(
364                 expetedBytesOutput.begin(), expetedBytesOutput.end())));
365 
366         EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
367             .WillOnce(Return(expectedWriteSize));
368     }
369 
370     static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
371     static constexpr uint16_t testSequenceId = 0;
372     static constexpr uint16_t testEntrySize = 0x20;
373     static constexpr uint8_t testChecksum = 1;
374     static constexpr uint8_t testRdeCommandType = 0x01;
375     size_t testOffset = 0x50;
376 
377     struct QueueEntryHeader testEntryHeader
378     {};
379 };
380 
381 TEST_F(BufferEntryHeaderTest, ReadEntryHeaderPass)
382 {
383     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
384     std::vector<uint8_t> testEntryHeaderVector(
385         testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
386     wraparoundReadMock(testEntryHeaderVector);
387     EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader);
388 }
389 
390 } // namespace
391 } // namespace bios_bmc_smm_error_logger
392