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
57 std::unique_ptr<DataInterfaceMock> dataInterfaceMock;
58 DataInterfaceMock* dataInterfaceMockPtr;
59 std::unique_ptr<BufferImpl> bufferImpl;
60 };
61
TEST_F(BufferTest,BufferInitializeEraseFail)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 = 0x201;
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 queue size '513' is bigger than the BMC's allocated MMIO region of '512'");
78 throw;
79 },
80 std::runtime_error);
81 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
82
83 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
84 .WillOnce(Return(testRegionSize));
85 const std::vector<uint8_t> emptyArray(testQueueSize, 0);
86 // Return a smaller write than the intended testRegionSize to test the error
87 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
88 .WillOnce(Return(testQueueSize - 1));
89 EXPECT_THROW(
90 try {
91 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
92 testUeRegionSize, testMagicNumber);
93 } catch (const std::runtime_error& e) {
94 EXPECT_STREQ(e.what(), "[initialize] Only erased '511'");
95 throw;
96 },
97 std::runtime_error);
98 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
99
100 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
101 .WillOnce(Return(testRegionSize));
102 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
103 .WillOnce(Return(testQueueSize));
104 // Return a smaller write than the intended initializationHeader to test the
105 // error
106 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)).WillOnce(Return(0));
107 EXPECT_THROW(
108 try {
109 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
110 testUeRegionSize, testMagicNumber);
111 } catch (const std::runtime_error& e) {
112 EXPECT_STREQ(e.what(),
113 "[initialize] Only wrote '0' bytes of the header");
114 throw;
115 },
116 std::runtime_error);
117 EXPECT_NE(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
118 }
119
TEST_F(BufferTest,BufferInitializePass)120 TEST_F(BufferTest, BufferInitializePass)
121 {
122 InSequence s;
123 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
124 .WillOnce(Return(testRegionSize));
125 const std::vector<uint8_t> emptyArray(testQueueSize, 0);
126 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
127 .WillOnce(Return(testQueueSize));
128
129 uint8_t* testInitializationHeaderPtr =
130 reinterpret_cast<uint8_t*>(&testInitializationHeader);
131 EXPECT_CALL(*dataInterfaceMockPtr,
132 write(0, ElementsAreArray(testInitializationHeaderPtr,
133 bufferHeaderSize)))
134 .WillOnce(Return(bufferHeaderSize));
135 EXPECT_NO_THROW(
136 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
137 testUeRegionSize, testMagicNumber));
138 EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
139 }
140
TEST_F(BufferTest,BufferHeaderReadFail)141 TEST_F(BufferTest, BufferHeaderReadFail)
142 {
143 std::vector<std::uint8_t> testBytesRead{};
144 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
145 .WillOnce(Return(testBytesRead));
146 EXPECT_THROW(
147 try {
148 bufferImpl->readBufferHeader();
149 } catch (const std::runtime_error& e) {
150 EXPECT_STREQ(e.what(),
151 "Buffer header read only read '0', expected '48'");
152 throw;
153 },
154 std::runtime_error);
155 }
156
TEST_F(BufferTest,BufferHeaderReadPass)157 TEST_F(BufferTest, BufferHeaderReadPass)
158 {
159 uint8_t* testInitializationHeaderPtr =
160 reinterpret_cast<uint8_t*>(&testInitializationHeader);
161 std::vector<uint8_t> testInitializationHeaderVector(
162 testInitializationHeaderPtr,
163 testInitializationHeaderPtr + bufferHeaderSize);
164
165 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
166 .WillOnce(Return(testInitializationHeaderVector));
167 EXPECT_NO_THROW(bufferImpl->readBufferHeader());
168 EXPECT_EQ(bufferImpl->getCachedBufferHeader(), testInitializationHeader);
169 }
170
TEST_F(BufferTest,BufferUpdateReadPtrFail)171 TEST_F(BufferTest, BufferUpdateReadPtrFail)
172 {
173 // Return write size that is not 2 which is sizeof(little_uint16_t)
174 constexpr size_t wrongWriteSize = 1;
175 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
176 .WillOnce(Return(wrongWriteSize));
177 EXPECT_THROW(
178 try {
179 bufferImpl->updateReadPtr(0);
180 } catch (const std::runtime_error& e) {
181 EXPECT_STREQ(
182 e.what(),
183 "[updateReadPtr] Wrote '1' bytes, instead of expected '3'");
184 throw;
185 },
186 std::runtime_error);
187 }
188
TEST_F(BufferTest,BufferUpdateReadPtrPass)189 TEST_F(BufferTest, BufferUpdateReadPtrPass)
190 {
191 constexpr size_t expectedWriteSize = 3;
192 constexpr uint8_t expectedBmcReadPtrOffset = 0x21;
193 // Check that we truncate the highest 24bits
194 const uint32_t testNewReadPtr = 0x99881234;
195 const std::vector<uint8_t> expectedReadPtr{0x34, 0x12, 0x88};
196
197 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
198 ElementsAreArray(expectedReadPtr)))
199 .WillOnce(Return(expectedWriteSize));
200 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testNewReadPtr));
201
202 auto cachedHeader = bufferImpl->getCachedBufferHeader();
203 EXPECT_EQ(boost::endian::little_to_native(cachedHeader.bmcReadPtr),
204 0x881234);
205 }
206
TEST_F(BufferTest,BufferUpdateBmcFlagsFail)207 TEST_F(BufferTest, BufferUpdateBmcFlagsFail)
208 {
209 // Return write size that is not 4 which is sizeof(little_uint32_t)
210 constexpr size_t wrongWriteSize = 1;
211 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
212 .WillOnce(Return(wrongWriteSize));
213 EXPECT_THROW(
214 try {
215 bufferImpl->updateBmcFlags(static_cast<uint32_t>(BmcFlags::ready));
216 } catch (const std::runtime_error& e) {
217 EXPECT_STREQ(
218 e.what(),
219 "[updateBmcFlags] Wrote '1' bytes, instead of expected '4'");
220 throw;
221 },
222 std::runtime_error);
223 }
224
TEST_F(BufferTest,BufferUpdateBmcFlagsPass)225 TEST_F(BufferTest, BufferUpdateBmcFlagsPass)
226 {
227 constexpr size_t expectedWriteSize = 4;
228 constexpr uint8_t expectedBmcReadPtrOffset = 0x1d;
229 const std::vector<uint8_t> expectedNewBmcFlagsVector{0x04, 0x0, 0x0, 0x00};
230
231 EXPECT_CALL(*dataInterfaceMockPtr,
232 write(expectedBmcReadPtrOffset,
233 ElementsAreArray(expectedNewBmcFlagsVector)))
234 .WillOnce(Return(expectedWriteSize));
235 EXPECT_NO_THROW(
236 bufferImpl->updateBmcFlags(static_cast<uint32_t>(BmcFlags::ready)));
237
238 auto cachedHeader = bufferImpl->getCachedBufferHeader();
239 EXPECT_EQ(boost::endian::little_to_native(cachedHeader.bmcFlags),
240 static_cast<uint32_t>(BmcFlags::ready));
241 }
242
TEST_F(BufferTest,GetMaxOffsetQueueSizeFail)243 TEST_F(BufferTest, GetMaxOffsetQueueSizeFail)
244 {
245 InSequence s;
246 static constexpr size_t wrongQueueSize = testQueueSize - 1;
247 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
248 .WillOnce(Return(testRegionSize));
249 const std::vector<uint8_t> emptyArray(wrongQueueSize, 0);
250 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
251 .WillOnce(Return(wrongQueueSize));
252
253 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
254 .WillOnce(Return(bufferHeaderSize));
255 EXPECT_NO_THROW(
256 bufferImpl->initialize(testBmcInterfaceVersion, wrongQueueSize,
257 testUeRegionSize, testMagicNumber));
258 EXPECT_THROW(
259 try {
260 bufferImpl->getMaxOffset();
261 } catch (const std::runtime_error& e) {
262 EXPECT_STREQ(e.what(),
263 "[getMaxOffset] runtime queueSize '511' did not match "
264 "compile-time queueSize '512'. This indicates that the"
265 " buffer was corrupted");
266 throw;
267 },
268 std::runtime_error);
269 }
270
TEST_F(BufferTest,GetMaxOffsetUeRegionSizeFail)271 TEST_F(BufferTest, GetMaxOffsetUeRegionSizeFail)
272 {
273 InSequence s;
274 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
275 .WillOnce(Return(testRegionSize));
276 const std::vector<uint8_t> emptyArray(testQueueSize, 0);
277 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
278 .WillOnce(Return(testQueueSize));
279
280 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
281 .WillOnce(Return(bufferHeaderSize));
282 EXPECT_NO_THROW(
283 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
284 testUeRegionSize + 1, testMagicNumber));
285 EXPECT_THROW(
286 try {
287 bufferImpl->getMaxOffset();
288 } catch (const std::runtime_error& e) {
289 EXPECT_STREQ(
290 e.what(),
291 "[getMaxOffset] runtime ueRegionSize '81' did not match "
292 "compile-time ueRegionSize '80'. This indicates that the"
293 " buffer was corrupted");
294 throw;
295 },
296 std::runtime_error);
297 }
298
TEST_F(BufferTest,GetOffsetUeRegionSizeFail)299 TEST_F(BufferTest, GetOffsetUeRegionSizeFail)
300 {
301 InSequence s;
302 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
303 .WillOnce(Return(testRegionSize));
304 const std::vector<uint8_t> emptyArray(testQueueSize, 0);
305 EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
306 .WillOnce(Return(testQueueSize));
307
308 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
309 .WillOnce(Return(bufferHeaderSize));
310 EXPECT_NO_THROW(
311 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
312 testUeRegionSize - 1, testMagicNumber));
313 EXPECT_THROW(
314 try {
315 bufferImpl->getQueueOffset();
316 } catch (const std::runtime_error& e) {
317 EXPECT_STREQ(
318 e.what(),
319 "[getQueueOffset] runtime ueRegionSize '79' did not match "
320 "compile-time ueRegionSize '80'. This indicates that the"
321 " buffer was corrupted");
322 throw;
323 },
324 std::runtime_error);
325 }
326
327 class BufferWraparoundReadTest : public BufferTest
328 {
329 protected:
BufferWraparoundReadTest()330 BufferWraparoundReadTest()
331 {
332 initializeFuncMock();
333 }
initializeFuncMock()334 void initializeFuncMock()
335 {
336 // Initialize the memory and the cachedBufferHeader
337 InSequence s;
338 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
339 .WillOnce(Return(testRegionSize));
340 const std::vector<uint8_t> emptyArray(testQueueSize, 0);
341 EXPECT_CALL(*dataInterfaceMockPtr,
342 write(0, ElementsAreArray(emptyArray)))
343 .WillOnce(Return(testQueueSize));
344
345 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
346 .WillOnce(Return(bufferHeaderSize));
347 EXPECT_NO_THROW(
348 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
349 testUeRegionSize, testMagicNumber));
350 }
351 static constexpr size_t expectedWriteSize = 3;
352 static constexpr uint8_t expectedBmcReadPtrOffset = 0x21;
353 static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
354
355 static constexpr size_t testMaxOffset =
356 testQueueSize - testUeRegionSize - sizeof(struct CircularBufferHeader);
357 uint8_t* testInitializationHeaderPtr =
358 reinterpret_cast<uint8_t*>(&testInitializationHeader);
359 };
360
TEST_F(BufferWraparoundReadTest,GetMaxOffsetPassTest)361 TEST_F(BufferWraparoundReadTest, GetMaxOffsetPassTest)
362 {
363 EXPECT_EQ(bufferImpl->getMaxOffset(), testMaxOffset);
364 }
365
TEST_F(BufferWraparoundReadTest,GetQueueOffsetPassTest)366 TEST_F(BufferWraparoundReadTest, GetQueueOffsetPassTest)
367 {
368 EXPECT_EQ(bufferImpl->getQueueOffset(),
369 bufferHeaderSize + testUeRegionSize);
370 }
371
TEST_F(BufferWraparoundReadTest,ParamsTooBigFail)372 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail)
373 {
374 InSequence s;
375 size_t tooBigOffset = testMaxOffset + 1;
376 EXPECT_THROW(
377 try {
378 bufferImpl->wraparoundRead(tooBigOffset, /* length */ 1);
379 } catch (const std::runtime_error& e) {
380 EXPECT_STREQ(
381 e.what(),
382 "[wraparoundRead] relativeOffset '385' was bigger than maxOffset '384'");
383 throw;
384 },
385 std::runtime_error);
386
387 size_t tooBigLength = testMaxOffset + 1;
388 EXPECT_THROW(
389 try {
390 bufferImpl->wraparoundRead(/* relativeOffset */ 0, tooBigLength);
391 } catch (const std::runtime_error& e) {
392 EXPECT_STREQ(e.what(), "[wraparoundRead] length '385' was bigger "
393 "than maxOffset '384'");
394 throw;
395 },
396 std::runtime_error);
397 }
398
TEST_F(BufferWraparoundReadTest,NoWrapAroundReadFails)399 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadFails)
400 {
401 InSequence s;
402 size_t testLength = 0x10;
403 size_t testOffset = 0x20;
404
405 // Fail the first read
406 std::vector<std::uint8_t> shortTestBytesRead(testLength - 1);
407 EXPECT_CALL(*dataInterfaceMockPtr,
408 read(testOffset + expectedqueueOffset, testLength))
409 .WillOnce(Return(shortTestBytesRead));
410
411 EXPECT_THROW(
412 try {
413 bufferImpl->wraparoundRead(testOffset, testLength);
414 } catch (const std::runtime_error& e) {
415 EXPECT_STREQ(e.what(),
416 "[wraparoundRead] Read '15' which was not the "
417 "requested length of '16'");
418 throw;
419 },
420 std::runtime_error);
421 }
422
TEST_F(BufferWraparoundReadTest,NoWrapAroundReadPass)423 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass)
424 {
425 InSequence s;
426 size_t testLength = 0x10;
427 size_t testOffset = 0x20;
428
429 // Successfully read all the requested length without a wrap around
430 std::vector<std::uint8_t> testBytesRead(testLength);
431 EXPECT_CALL(*dataInterfaceMockPtr,
432 read(testOffset + expectedqueueOffset, testLength))
433 .WillOnce(Return(testBytesRead));
434
435 // Call to updateReadPtr is triggered
436 const std::vector<uint8_t> expectedReadPtr{
437 static_cast<uint8_t>(testOffset + testLength), 0x0, 0x0};
438 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
439 ElementsAreArray(expectedReadPtr)))
440 .WillOnce(Return(expectedWriteSize));
441
442 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
443 ElementsAreArray(testBytesRead));
444 struct CircularBufferHeader cachedBufferHeader =
445 bufferImpl->getCachedBufferHeader();
446 // The bmcReadPtr should have been updated
447 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
448 testOffset + testLength);
449 }
450
TEST_F(BufferWraparoundReadTest,WrapAroundReadFails)451 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails)
452 {
453 InSequence s;
454 size_t testBytesLeft = 3;
455 size_t testLength = 0x10;
456 size_t testOffset = testMaxOffset - (testLength - testBytesLeft);
457
458 // Read until the end of the queue
459 std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft);
460 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
461 testLength - testBytesLeft))
462 .WillOnce(Return(testBytesReadShort));
463
464 // Read 1 byte short after wraparound
465 std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1);
466 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
467 .WillOnce(Return(testBytesLeftReadShort));
468
469 EXPECT_THROW(
470 try {
471 bufferImpl->wraparoundRead(testOffset, testLength);
472 } catch (const std::runtime_error& e) {
473 EXPECT_STREQ(
474 e.what(),
475 "[wraparoundRead] Buffer wrapped around but read '2' which was "
476 "not the requested lenght of '3'");
477 throw;
478 },
479 std::runtime_error);
480 }
481
TEST_F(BufferWraparoundReadTest,WrapAroundReadPasses)482 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses)
483 {
484 InSequence s;
485 size_t testBytesLeft = 3;
486 size_t testLength = 0x10;
487 size_t testOffset = testMaxOffset - (testLength - testBytesLeft);
488
489 // Read to the end of the queue
490 std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10,
491 9, 8, 7, 6, 5, 4};
492 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
493 testLength - testBytesLeft))
494 .WillOnce(Return(testBytesReadFirst));
495
496 std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1};
497 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
498 .WillOnce(Return(testBytesReadSecond));
499
500 // Call to updateReadPtr is triggered
501 const std::vector<uint8_t> expectedReadPtr{
502 static_cast<uint8_t>(testBytesLeft), 0x0, 0x0};
503 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
504 ElementsAreArray(expectedReadPtr)))
505 .WillOnce(Return(expectedWriteSize));
506
507 std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9,
508 8, 7, 6, 5, 4, 3, 2, 1};
509 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
510 ElementsAreArray(expectedBytes));
511 struct CircularBufferHeader cachedBufferHeader =
512 bufferImpl->getCachedBufferHeader();
513 // The bmcReadPtr should have been updated to reflect the wraparound
514 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
515 testBytesLeft);
516 }
517
TEST_F(BufferWraparoundReadTest,WrapAroundCornerCasePass)518 TEST_F(BufferWraparoundReadTest, WrapAroundCornerCasePass)
519 {
520 InSequence s;
521 size_t testBytesLeft = 0;
522 size_t testLength = 4;
523 size_t testOffset = testMaxOffset - (testLength - testBytesLeft);
524
525 // Read to the very end of the queue
526 std::vector<std::uint8_t> testBytes{4, 3, 2, 1};
527 EXPECT_CALL(*dataInterfaceMockPtr,
528 read(testOffset + expectedqueueOffset, testLength))
529 .WillOnce(Return(testBytes));
530
531 // Call to updateReadPtr is triggered, since we read to the very end of the
532 // buffer, update the readPtr up around to 0
533 const std::vector<uint8_t> expectedReadPtr{0x0, 0x0, 0x0};
534 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
535 ElementsAreArray(expectedReadPtr)))
536 .WillOnce(Return(expectedWriteSize));
537
538 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
539 ElementsAreArray(testBytes));
540 struct CircularBufferHeader cachedBufferHeader =
541 bufferImpl->getCachedBufferHeader();
542 // The bmcReadPtr should have been updated to reflect the wraparound
543 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
544 0);
545 }
546
547 class BufferEntryTest : public BufferWraparoundReadTest
548 {
549 protected:
BufferEntryTest()550 BufferEntryTest()
551 {
552 testEntryHeader.sequenceId = testSequenceId;
553 testEntryHeader.entrySize = testEntrySize;
554 testEntryHeader.checksum = testChecksum;
555 testEntryHeader.rdeCommandType = testRdeCommandType;
556 }
557 ~BufferEntryTest() override = default;
558
wraparoundReadMock(const uint32_t relativeOffset,std::span<std::uint8_t> expetedBytesOutput)559 void wraparoundReadMock(const uint32_t relativeOffset,
560 std::span<std::uint8_t> expetedBytesOutput)
561 {
562 InSequence s;
563 const uint32_t queueSizeToQueueEnd = testMaxOffset - relativeOffset;
564
565 // This will wrap, split the read mocks in 2
566 if (expetedBytesOutput.size() > queueSizeToQueueEnd)
567 {
568 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
569 .WillOnce(Return(std::vector<std::uint8_t>(
570 expetedBytesOutput.begin(),
571 expetedBytesOutput.begin() + queueSizeToQueueEnd)));
572 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
573 .WillOnce(Return(std::vector<std::uint8_t>(
574 expetedBytesOutput.begin() + queueSizeToQueueEnd,
575 expetedBytesOutput.end())));
576 }
577 else
578 {
579 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
580 .WillOnce(Return(std::vector<std::uint8_t>(
581 expetedBytesOutput.begin(), expetedBytesOutput.end())));
582 }
583
584 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
585 .WillOnce(Return(expectedWriteSize));
586 }
587
588 static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
589 static constexpr uint16_t testSequenceId = 0;
590 static constexpr uint16_t testEntrySize = 0x20;
591 static constexpr uint8_t testRdeCommandType = 0x01;
592 // Calculated checksum for the header
593 static constexpr uint8_t testChecksum =
594 (testSequenceId ^ testEntrySize ^ testRdeCommandType);
595 size_t testOffset = 0x0;
596
597 struct QueueEntryHeader testEntryHeader
598 {};
599 };
600
TEST_F(BufferEntryTest,ReadEntryHeaderPass)601 TEST_F(BufferEntryTest, ReadEntryHeaderPass)
602 {
603 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
604 std::vector<uint8_t> testEntryHeaderVector(
605 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
606 wraparoundReadMock(testOffset, testEntryHeaderVector);
607 EXPECT_EQ(bufferImpl->readEntryHeader(), testEntryHeader);
608 // Check the bmcReadPtr
609 struct CircularBufferHeader cachedBufferHeader =
610 bufferImpl->getCachedBufferHeader();
611 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
612 testOffset + testEntryHeaderVector.size());
613 }
614
TEST_F(BufferEntryTest,ReadEntryChecksumFail)615 TEST_F(BufferEntryTest, ReadEntryChecksumFail)
616 {
617 InSequence s;
618 std::vector<uint8_t> testEntryVector(testEntrySize);
619 // Offset the checksum by 1
620 testEntryHeader.checksum += 1;
621 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
622 std::vector<uint8_t> testEntryHeaderVector(
623 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
624 wraparoundReadMock(testOffset, testEntryHeaderVector);
625 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
626 EXPECT_THROW(
627 try { bufferImpl->readEntry(); } catch (const std::runtime_error& e) {
628 // Calculation: testChecksum (0x21) XOR (0x22) = 3
629 EXPECT_STREQ(e.what(),
630 "[readEntry] Checksum was '3', expected '0'");
631 throw;
632 },
633 std::runtime_error);
634 }
635
TEST_F(BufferEntryTest,ReadEntryPassWraparound)636 TEST_F(BufferEntryTest, ReadEntryPassWraparound)
637 {
638 InSequence s;
639 // We expect this will bump checksum up by "testEntrySize" = 0xff ^ 0xff ...
640 // (20 times) = 0 therefore leave the checksum as is
641 std::vector<uint8_t> testEntryVector(testEntrySize, 0xff);
642 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
643 std::vector<uint8_t> testEntryHeaderVector(
644 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
645 // Set testOffset so that we can test the wraparound here at the header and
646 // update the readPtr
647 testOffset = testMaxOffset - 1;
648 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, _))
649 .WillOnce(Return(expectedWriteSize));
650 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testOffset));
651
652 wraparoundReadMock(testOffset, testEntryHeaderVector);
653 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
654
655 EntryPair testedEntryPair;
656 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry());
657 EXPECT_EQ(testedEntryPair.first, testEntryHeader);
658 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
659 struct CircularBufferHeader cachedBufferHeader =
660 bufferImpl->getCachedBufferHeader();
661 // The bmcReadPtr should have been updated to reflect the wraparound
662 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
663 entryHeaderSize + testEntrySize - 1);
664
665 // Set testOffset so that we can test the wraparound here as well on our
666 // second read for the entry (by 1 byte)
667 testOffset = testMaxOffset - entryHeaderSize - 1;
668 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, _))
669 .WillOnce(Return(expectedWriteSize));
670 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testOffset));
671
672 wraparoundReadMock(testOffset, testEntryHeaderVector);
673 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
674
675 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry());
676 EXPECT_EQ(testedEntryPair.first, testEntryHeader);
677 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
678 cachedBufferHeader = bufferImpl->getCachedBufferHeader();
679 // The bmcReadPtr should have been updated to reflect the wraparound
680 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
681 testEntrySize - 1);
682 }
683
684 class BufferReadErrorLogsTest : public BufferEntryTest
685 {
686 protected:
687 BufferReadErrorLogsTest() = default;
688
689 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
690 size_t entryAndHeaderSize = entryHeaderSize + testEntrySize;
691 };
692
TEST_F(BufferReadErrorLogsTest,PtrsTooBigFail)693 TEST_F(BufferReadErrorLogsTest, PtrsTooBigFail)
694 {
695 InSequence s;
696 // Set the biosWritePtr too big
697 testInitializationHeader.biosWritePtr =
698 boost::endian::native_to_little((testMaxOffset + 1));
699 initializeFuncMock();
700
701 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
702 .WillOnce(Return(std::vector<uint8_t>(
703 testInitializationHeaderPtr,
704 testInitializationHeaderPtr + bufferHeaderSize)));
705 EXPECT_THROW(
706 try {
707 bufferImpl->readErrorLogs();
708 } catch (const std::runtime_error& e) {
709 EXPECT_STREQ(e.what(),
710 "[readErrorLogs] currentBiosWritePtr was '385' "
711 "which was bigger than maxOffset '384'");
712 throw;
713 },
714 std::runtime_error);
715
716 // Reset the biosWritePtr and set the bmcReadPtr too big
717 testInitializationHeader.biosWritePtr = 0;
718 initializeFuncMock();
719 testInitializationHeader.bmcReadPtr =
720 boost::endian::native_to_little((testMaxOffset + 1));
721 initializeFuncMock();
722
723 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
724 .WillOnce(Return(std::vector<uint8_t>(
725 testInitializationHeaderPtr,
726 testInitializationHeaderPtr + bufferHeaderSize)));
727 EXPECT_THROW(
728 try {
729 bufferImpl->readErrorLogs();
730 } catch (const std::runtime_error& e) {
731 EXPECT_STREQ(e.what(), "[readErrorLogs] currentReadPtr was '385' "
732 "which was bigger than maxOffset '384'");
733 throw;
734 },
735 std::runtime_error);
736 }
737
TEST_F(BufferReadErrorLogsTest,IdenticalPtrsPass)738 TEST_F(BufferReadErrorLogsTest, IdenticalPtrsPass)
739 {
740 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
741 .WillOnce(Return(std::vector<uint8_t>(
742 testInitializationHeaderPtr,
743 testInitializationHeaderPtr + bufferHeaderSize)));
744 EXPECT_NO_THROW(bufferImpl->readErrorLogs());
745 }
746
TEST_F(BufferReadErrorLogsTest,NoWraparoundPass)747 TEST_F(BufferReadErrorLogsTest, NoWraparoundPass)
748 {
749 InSequence s;
750 // Set the biosWritePtr to 1 entryHeader + entry size
751 testInitializationHeader.biosWritePtr =
752 boost::endian::native_to_little((entryAndHeaderSize));
753 initializeFuncMock();
754 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
755 .WillOnce(Return(std::vector<uint8_t>(
756 testInitializationHeaderPtr,
757 testInitializationHeaderPtr + bufferHeaderSize)));
758 std::vector<uint8_t> testEntryHeaderVector(
759 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
760 std::vector<uint8_t> testEntryVector(testEntrySize);
761 wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
762 wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
763
764 std::vector<EntryPair> entryPairs;
765 EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
766
767 // Check that we only read one entryPair and that the content is correct
768 EXPECT_EQ(entryPairs.size(), 1U);
769 EXPECT_EQ(entryPairs[0].first, testEntryHeader);
770 EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
771 }
772
TEST_F(BufferReadErrorLogsTest,WraparoundMultiplEntryPass)773 TEST_F(BufferReadErrorLogsTest, WraparoundMultiplEntryPass)
774 {
775 InSequence s;
776 // Set the bmcReadPtr to 1 entryHeader + entry size from the "end" exactly
777 uint32_t entryAndHeaderSizeAwayFromEnd = testMaxOffset - entryAndHeaderSize;
778 testInitializationHeader.bmcReadPtr =
779 boost::endian::native_to_little(entryAndHeaderSizeAwayFromEnd);
780 // Set the biosWritePtr to 1 entryHeader + entry size from the "beginning"
781 testInitializationHeader.biosWritePtr = entryAndHeaderSize;
782 initializeFuncMock();
783 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
784 .WillOnce(Return(std::vector<uint8_t>(
785 testInitializationHeaderPtr,
786 testInitializationHeaderPtr + bufferHeaderSize)));
787
788 std::vector<uint8_t> testEntryHeaderVector(
789 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
790 std::vector<uint8_t> testEntryVector(testEntrySize);
791 wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd,
792 testEntryHeaderVector);
793 wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd +
794 entryHeaderSize,
795 testEntryVector);
796 wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize,
797 testEntryHeaderVector);
798 wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize +
799 entryHeaderSize,
800 testEntryVector);
801
802 std::vector<EntryPair> entryPairs;
803 EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
804
805 // Check that we only read one entryPair and that the content is correct
806 EXPECT_EQ(entryPairs.size(), 2);
807 EXPECT_EQ(entryPairs[0].first, testEntryHeader);
808 EXPECT_EQ(entryPairs[1].first, testEntryHeader);
809 EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
810 EXPECT_THAT(entryPairs[1].second, ElementsAreArray(testEntryVector));
811 }
812
TEST_F(BufferReadErrorLogsTest,WraparoundMismatchingPtrsFail)813 TEST_F(BufferReadErrorLogsTest, WraparoundMismatchingPtrsFail)
814 {
815 InSequence s;
816 testInitializationHeader.bmcReadPtr = boost::endian::native_to_little(0);
817 // Make the biosWritePtr intentially 1 smaller than expected
818 testInitializationHeader.biosWritePtr =
819 boost::endian::native_to_little(entryAndHeaderSize - 1);
820 initializeFuncMock();
821 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
822 .WillOnce(Return(std::vector<uint8_t>(
823 testInitializationHeaderPtr,
824 testInitializationHeaderPtr + bufferHeaderSize)));
825
826 std::vector<uint8_t> testEntryHeaderVector(
827 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
828 std::vector<uint8_t> testEntryVector(testEntrySize);
829 wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
830 wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
831
832 EXPECT_THROW(
833 try {
834 bufferImpl->readErrorLogs();
835 } catch (const std::runtime_error& e) {
836 EXPECT_STREQ(
837 e.what(),
838 "[readErrorLogs] biosWritePtr '37' and bmcReaddPtr '38' "
839 "are not identical after reading through all the logs");
840 throw;
841 },
842 std::runtime_error);
843 }
844
845 } // namespace
846 } // namespace bios_bmc_smm_error_logger
847