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
TEST_F(BufferTest,ReadUeLog_NoUeRegionConfigured)326 TEST_F(BufferTest, ReadUeLog_NoUeRegionConfigured)
327 {
328 struct CircularBufferHeader header = testInitializationHeader;
329 header.ueRegionSize =
330 boost::endian::native_to_little<uint16_t>(0); // No UE region
331
332 uint8_t* headerPtr = reinterpret_cast<uint8_t*>(&header);
333 std::vector<uint8_t> headerBytes(headerPtr, headerPtr + bufferHeaderSize);
334 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
335 .WillOnce(Return(headerBytes));
336
337 auto result = bufferImpl->readUeLogFromReservedRegion();
338 EXPECT_TRUE(result.empty());
339 }
340
TEST_F(BufferTest,ReadUeLog_NotPresentDueToFlags)341 TEST_F(BufferTest, ReadUeLog_NotPresentDueToFlags)
342 {
343 struct CircularBufferHeader header = testInitializationHeader;
344 header.ueRegionSize = boost::endian::native_to_little<uint16_t>(0x20);
345 // Flags are the same, so no new UE log
346 header.biosFlags = boost::endian::native_to_little<uint32_t>(
347 static_cast<uint32_t>((BufferFlags::ueSwitch)));
348 header.bmcFlags = boost::endian::native_to_little<uint32_t>(
349 static_cast<uint32_t>(BufferFlags::ueSwitch));
350
351 uint8_t* headerPtr = reinterpret_cast<uint8_t*>(&header);
352 std::vector<uint8_t> headerBytes(headerPtr, headerPtr + bufferHeaderSize);
353 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
354 .WillOnce(Return(headerBytes));
355
356 auto result = bufferImpl->readUeLogFromReservedRegion();
357 EXPECT_TRUE(result.empty());
358 }
359
TEST_F(BufferTest,ReadUeLog_PresentAndSuccessfullyRead)360 TEST_F(BufferTest, ReadUeLog_PresentAndSuccessfullyRead)
361 {
362 struct CircularBufferHeader header = testInitializationHeader;
363 uint16_t ueSize = 0x20;
364 header.ueRegionSize = boost::endian::native_to_little(ueSize);
365 header.biosFlags = boost::endian::native_to_little<uint32_t>(
366 static_cast<uint32_t>(BufferFlags::ueSwitch));
367 header.bmcFlags =
368 boost::endian::native_to_little<uint32_t>(0); // BIOS set, BMC not yet
369
370 uint8_t* headerPtr = reinterpret_cast<uint8_t*>(&header);
371 std::vector<uint8_t> headerBytes(headerPtr, headerPtr + bufferHeaderSize);
372 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
373 .WillOnce(Return(headerBytes));
374
375 size_t ueRegionOffset = bufferHeaderSize;
376 std::vector<uint8_t> ueData(ueSize, 0xAA);
377 EXPECT_CALL(*dataInterfaceMockPtr, read(ueRegionOffset, ueSize))
378 .WillOnce(Return(ueData));
379
380 auto result = bufferImpl->readUeLogFromReservedRegion();
381 ASSERT_FALSE(result.empty());
382 EXPECT_THAT(result, ElementsAreArray(ueData));
383
384 // The initial bmcFlags (0) should remain unchanged in the cache
385 struct CircularBufferHeader cachedHeaderAfterRead =
386 bufferImpl->getCachedBufferHeader();
387 EXPECT_EQ(boost::endian::little_to_native(cachedHeaderAfterRead.bmcFlags),
388 0);
389 }
390
TEST_F(BufferTest,ReadUeLog_PresentButReadFails)391 TEST_F(BufferTest, ReadUeLog_PresentButReadFails)
392 {
393 struct CircularBufferHeader header = testInitializationHeader;
394 uint16_t ueSize = 0x20;
395 header.ueRegionSize = boost::endian::native_to_little(ueSize);
396 header.biosFlags = boost::endian::native_to_little<uint32_t>(
397 static_cast<uint32_t>(BufferFlags::ueSwitch));
398 header.bmcFlags = boost::endian::native_to_little<uint32_t>(0);
399
400 uint8_t* headerPtr = reinterpret_cast<uint8_t*>(&header);
401 std::vector<uint8_t> headerBytes(headerPtr, headerPtr + bufferHeaderSize);
402 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
403 .WillOnce(Return(headerBytes));
404
405 size_t ueRegionOffset = bufferHeaderSize;
406 std::vector<uint8_t> shortUeData(ueSize - 1, 0xAA); // Short read
407 EXPECT_CALL(*dataInterfaceMockPtr, read(ueRegionOffset, ueSize))
408 .WillOnce(Return(shortUeData));
409
410 // Expect an exception due to short read, which is treated as corruption for
411 // UE log
412 EXPECT_THROW(
413 try {
414 bufferImpl->readUeLogFromReservedRegion();
415 } catch (const std::runtime_error& e) {
416 EXPECT_THAT(e.what(),
417 ::testing::HasSubstr("Failed to read full UE log"));
418 throw;
419 },
420 std::runtime_error);
421 }
422
TEST_F(BufferTest,CheckOverflow_NotPresentDueToFlags)423 TEST_F(BufferTest, CheckOverflow_NotPresentDueToFlags)
424 {
425 struct CircularBufferHeader header = testInitializationHeader;
426 // Flags are the same, so no new overflow
427 header.biosFlags = boost::endian::native_to_little<uint32_t>(
428 static_cast<uint32_t>(BufferFlags::overflow));
429 header.bmcFlags = boost::endian::native_to_little<uint32_t>(
430 static_cast<uint32_t>(BufferFlags::overflow));
431
432 uint8_t* headerPtr = reinterpret_cast<uint8_t*>(&header);
433 std::vector<uint8_t> headerBytes(headerPtr, headerPtr + bufferHeaderSize);
434 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
435 .WillOnce(Return(headerBytes));
436
437 bool overflowDetected = bufferImpl->checkForOverflowAndAcknowledge();
438 ASSERT_FALSE(overflowDetected);
439 }
440
TEST_F(BufferTest,CheckOverflow_PresentAndAcknowledged)441 TEST_F(BufferTest, CheckOverflow_PresentAndAcknowledged)
442 {
443 struct CircularBufferHeader header = testInitializationHeader;
444 header.biosFlags = boost::endian::native_to_little<uint32_t>(
445 static_cast<uint32_t>(BufferFlags::overflow));
446 header.bmcFlags =
447 boost::endian::native_to_little<uint32_t>(0); // BIOS set, BMC not yet
448
449 uint8_t* headerPtr = reinterpret_cast<uint8_t*>(&header);
450 std::vector<uint8_t> headerBytes(headerPtr, headerPtr + bufferHeaderSize);
451 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
452 .WillOnce(Return(headerBytes));
453
454 uint32_t expectedNewBmcFlags =
455 static_cast<uint32_t>(BufferFlags::overflow); // BMC toggles its bit
456 little_uint32_t littleExpectedNewBmcFlags =
457 boost::endian::native_to_little(expectedNewBmcFlags);
458 uint8_t* flagPtr = reinterpret_cast<uint8_t*>(&littleExpectedNewBmcFlags);
459 std::vector<uint8_t> expectedFlagWrite(flagPtr,
460 flagPtr + sizeof(little_uint32_t));
461 constexpr uint8_t bmcFlagsOffset =
462 offsetof(struct CircularBufferHeader, bmcFlags);
463
464 EXPECT_CALL(*dataInterfaceMockPtr,
465 write(bmcFlagsOffset, ElementsAreArray(expectedFlagWrite)))
466 .WillOnce(Return(sizeof(little_uint32_t)));
467
468 bool overflowDetected = bufferImpl->checkForOverflowAndAcknowledge();
469 ASSERT_TRUE(overflowDetected);
470
471 struct CircularBufferHeader updatedCachedHeader =
472 bufferImpl->getCachedBufferHeader();
473 EXPECT_EQ(boost::endian::little_to_native(updatedCachedHeader.bmcFlags),
474 expectedNewBmcFlags);
475 }
476
477 class BufferWraparoundReadTest : public BufferTest
478 {
479 protected:
BufferWraparoundReadTest()480 BufferWraparoundReadTest()
481 {
482 initializeFuncMock();
483 }
initializeFuncMock()484 void initializeFuncMock()
485 {
486 // Initialize the memory and the cachedBufferHeader
487 InSequence s;
488 EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
489 .WillOnce(Return(testRegionSize));
490 const std::vector<uint8_t> emptyArray(testQueueSize, 0);
491 EXPECT_CALL(*dataInterfaceMockPtr,
492 write(0, ElementsAreArray(emptyArray)))
493 .WillOnce(Return(testQueueSize));
494
495 EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
496 .WillOnce(Return(bufferHeaderSize));
497 EXPECT_NO_THROW(
498 bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
499 testUeRegionSize, testMagicNumber));
500 }
501 static constexpr size_t expectedWriteSize = 3;
502 static constexpr uint8_t expectedBmcReadPtrOffset = 0x21;
503 static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
504
505 static constexpr size_t testMaxOffset =
506 testQueueSize - testUeRegionSize - sizeof(struct CircularBufferHeader);
507 uint8_t* testInitializationHeaderPtr =
508 reinterpret_cast<uint8_t*>(&testInitializationHeader);
509 };
510
TEST_F(BufferWraparoundReadTest,GetMaxOffsetPassTest)511 TEST_F(BufferWraparoundReadTest, GetMaxOffsetPassTest)
512 {
513 EXPECT_EQ(bufferImpl->getMaxOffset(), testMaxOffset);
514 }
515
TEST_F(BufferWraparoundReadTest,GetQueueOffsetPassTest)516 TEST_F(BufferWraparoundReadTest, GetQueueOffsetPassTest)
517 {
518 EXPECT_EQ(bufferImpl->getQueueOffset(),
519 bufferHeaderSize + testUeRegionSize);
520 }
521
TEST_F(BufferWraparoundReadTest,ParamsTooBigFail)522 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail)
523 {
524 InSequence s;
525 size_t tooBigOffset = testMaxOffset + 1;
526 EXPECT_THROW(
527 try {
528 bufferImpl->wraparoundRead(tooBigOffset, /* length */ 1);
529 } catch (const std::runtime_error& e) {
530 EXPECT_STREQ(
531 e.what(),
532 "[wraparoundRead] relativeOffset '385' was bigger than maxOffset '384'");
533 throw;
534 },
535 std::runtime_error);
536
537 size_t tooBigLength = testMaxOffset + 1;
538 EXPECT_THROW(
539 try {
540 bufferImpl->wraparoundRead(/* relativeOffset */ 0, tooBigLength);
541 } catch (const std::runtime_error& e) {
542 EXPECT_STREQ(e.what(), "[wraparoundRead] length '385' was bigger "
543 "than maxOffset '384'");
544 throw;
545 },
546 std::runtime_error);
547 }
548
TEST_F(BufferWraparoundReadTest,NoWrapAroundReadFails)549 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadFails)
550 {
551 InSequence s;
552 size_t testLength = 0x10;
553 size_t testOffset = 0x20;
554
555 // Fail the first read
556 std::vector<std::uint8_t> shortTestBytesRead(testLength - 1);
557 EXPECT_CALL(*dataInterfaceMockPtr,
558 read(testOffset + expectedqueueOffset, testLength))
559 .WillOnce(Return(shortTestBytesRead));
560
561 EXPECT_THROW(
562 try {
563 bufferImpl->wraparoundRead(testOffset, testLength);
564 } catch (const std::runtime_error& e) {
565 EXPECT_STREQ(e.what(),
566 "[wraparoundRead] Read '15' which was not the "
567 "requested length of '16'");
568 throw;
569 },
570 std::runtime_error);
571 }
572
TEST_F(BufferWraparoundReadTest,NoWrapAroundReadPass)573 TEST_F(BufferWraparoundReadTest, NoWrapAroundReadPass)
574 {
575 InSequence s;
576 size_t testLength = 0x10;
577 size_t testOffset = 0x20;
578
579 // Successfully read all the requested length without a wrap around
580 std::vector<std::uint8_t> testBytesRead(testLength);
581 EXPECT_CALL(*dataInterfaceMockPtr,
582 read(testOffset + expectedqueueOffset, testLength))
583 .WillOnce(Return(testBytesRead));
584
585 // Call to updateReadPtr is triggered
586 const std::vector<uint8_t> expectedReadPtr{
587 static_cast<uint8_t>(testOffset + testLength), 0x0, 0x0};
588 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
589 ElementsAreArray(expectedReadPtr)))
590 .WillOnce(Return(expectedWriteSize));
591
592 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
593 ElementsAreArray(testBytesRead));
594 struct CircularBufferHeader cachedBufferHeader =
595 bufferImpl->getCachedBufferHeader();
596 // The bmcReadPtr should have been updated
597 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
598 testOffset + testLength);
599 }
600
TEST_F(BufferWraparoundReadTest,WrapAroundReadFails)601 TEST_F(BufferWraparoundReadTest, WrapAroundReadFails)
602 {
603 InSequence s;
604 size_t testBytesLeft = 3;
605 size_t testLength = 0x10;
606 size_t testOffset = testMaxOffset - (testLength - testBytesLeft);
607
608 // Read until the end of the queue
609 std::vector<std::uint8_t> testBytesReadShort(testLength - testBytesLeft);
610 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
611 testLength - testBytesLeft))
612 .WillOnce(Return(testBytesReadShort));
613
614 // Read 1 byte short after wraparound
615 std::vector<std::uint8_t> testBytesLeftReadShort(testBytesLeft - 1);
616 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
617 .WillOnce(Return(testBytesLeftReadShort));
618
619 EXPECT_THROW(
620 try {
621 bufferImpl->wraparoundRead(testOffset, testLength);
622 } catch (const std::runtime_error& e) {
623 EXPECT_STREQ(
624 e.what(),
625 "[wraparoundRead] Buffer wrapped around but read '2' which was "
626 "not the requested lenght of '3'");
627 throw;
628 },
629 std::runtime_error);
630 }
631
TEST_F(BufferWraparoundReadTest,WrapAroundReadPasses)632 TEST_F(BufferWraparoundReadTest, WrapAroundReadPasses)
633 {
634 InSequence s;
635 size_t testBytesLeft = 3;
636 size_t testLength = 0x10;
637 size_t testOffset = testMaxOffset - (testLength - testBytesLeft);
638
639 // Read to the end of the queue
640 std::vector<std::uint8_t> testBytesReadFirst{16, 15, 14, 13, 12, 11, 10,
641 9, 8, 7, 6, 5, 4};
642 EXPECT_CALL(*dataInterfaceMockPtr, read(testOffset + expectedqueueOffset,
643 testLength - testBytesLeft))
644 .WillOnce(Return(testBytesReadFirst));
645
646 std::vector<std::uint8_t> testBytesReadSecond{3, 2, 1};
647 EXPECT_CALL(*dataInterfaceMockPtr, read(expectedqueueOffset, testBytesLeft))
648 .WillOnce(Return(testBytesReadSecond));
649
650 // Call to updateReadPtr is triggered
651 const std::vector<uint8_t> expectedReadPtr{
652 static_cast<uint8_t>(testBytesLeft), 0x0, 0x0};
653 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
654 ElementsAreArray(expectedReadPtr)))
655 .WillOnce(Return(expectedWriteSize));
656
657 std::vector<std::uint8_t> expectedBytes = {16, 15, 14, 13, 12, 11, 10, 9,
658 8, 7, 6, 5, 4, 3, 2, 1};
659 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
660 ElementsAreArray(expectedBytes));
661 struct CircularBufferHeader cachedBufferHeader =
662 bufferImpl->getCachedBufferHeader();
663 // The bmcReadPtr should have been updated to reflect the wraparound
664 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
665 testBytesLeft);
666 }
667
TEST_F(BufferWraparoundReadTest,WrapAroundCornerCasePass)668 TEST_F(BufferWraparoundReadTest, WrapAroundCornerCasePass)
669 {
670 InSequence s;
671 size_t testBytesLeft = 0;
672 size_t testLength = 4;
673 size_t testOffset = testMaxOffset - (testLength - testBytesLeft);
674
675 // Read to the very end of the queue
676 std::vector<std::uint8_t> testBytes{4, 3, 2, 1};
677 EXPECT_CALL(*dataInterfaceMockPtr,
678 read(testOffset + expectedqueueOffset, testLength))
679 .WillOnce(Return(testBytes));
680
681 // Call to updateReadPtr is triggered, since we read to the very end of the
682 // buffer, update the readPtr up around to 0
683 const std::vector<uint8_t> expectedReadPtr{0x0, 0x0, 0x0};
684 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
685 ElementsAreArray(expectedReadPtr)))
686 .WillOnce(Return(expectedWriteSize));
687
688 EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
689 ElementsAreArray(testBytes));
690 struct CircularBufferHeader cachedBufferHeader =
691 bufferImpl->getCachedBufferHeader();
692 // The bmcReadPtr should have been updated to reflect the wraparound
693 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
694 0);
695 }
696
697 class BufferEntryTest : public BufferWraparoundReadTest
698 {
699 protected:
BufferEntryTest()700 BufferEntryTest()
701 {
702 testEntryHeader.sequenceId = testSequenceId;
703 testEntryHeader.entrySize = testEntrySize;
704 testEntryHeader.checksum = testChecksum;
705 testEntryHeader.rdeCommandType = testRdeCommandType;
706 }
707 ~BufferEntryTest() override = default;
708
wraparoundReadMock(const uint32_t relativeOffset,std::span<std::uint8_t> expetedBytesOutput)709 void wraparoundReadMock(const uint32_t relativeOffset,
710 std::span<std::uint8_t> expetedBytesOutput)
711 {
712 InSequence s;
713 const uint32_t queueSizeToQueueEnd = testMaxOffset - relativeOffset;
714
715 // This will wrap, split the read mocks in 2
716 if (expetedBytesOutput.size() > queueSizeToQueueEnd)
717 {
718 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
719 .WillOnce(Return(std::vector<std::uint8_t>(
720 expetedBytesOutput.begin(),
721 expetedBytesOutput.begin() + queueSizeToQueueEnd)));
722 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
723 .WillOnce(Return(std::vector<std::uint8_t>(
724 expetedBytesOutput.begin() + queueSizeToQueueEnd,
725 expetedBytesOutput.end())));
726 }
727 else
728 {
729 EXPECT_CALL(*dataInterfaceMockPtr, read(_, _))
730 .WillOnce(Return(std::vector<std::uint8_t>(
731 expetedBytesOutput.begin(), expetedBytesOutput.end())));
732 }
733
734 EXPECT_CALL(*dataInterfaceMockPtr, write(_, _))
735 .WillOnce(Return(expectedWriteSize));
736 }
737
738 static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
739 static constexpr uint16_t testSequenceId = 0;
740 static constexpr uint16_t testEntrySize = 0x20;
741 static constexpr uint8_t testRdeCommandType = 0x01;
742 // Calculated checksum for the header
743 static constexpr uint8_t testChecksum =
744 (testSequenceId ^ testEntrySize ^ testRdeCommandType);
745 size_t testOffset = 0x0;
746
747 struct QueueEntryHeader testEntryHeader{};
748 };
749
TEST_F(BufferEntryTest,ReadEntryHeaderPass)750 TEST_F(BufferEntryTest, ReadEntryHeaderPass)
751 {
752 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
753 std::vector<uint8_t> testEntryHeaderVector(
754 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
755 wraparoundReadMock(testOffset, testEntryHeaderVector);
756 EXPECT_EQ(bufferImpl->readEntryHeader(), testEntryHeader);
757 // Check the bmcReadPtr
758 struct CircularBufferHeader cachedBufferHeader =
759 bufferImpl->getCachedBufferHeader();
760 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
761 testOffset + testEntryHeaderVector.size());
762 }
763
TEST_F(BufferEntryTest,ReadEntryChecksumFail)764 TEST_F(BufferEntryTest, ReadEntryChecksumFail)
765 {
766 InSequence s;
767 std::vector<uint8_t> testEntryVector(testEntrySize);
768 // Offset the checksum by 1
769 testEntryHeader.checksum += 1;
770 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
771 std::vector<uint8_t> testEntryHeaderVector(
772 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
773 wraparoundReadMock(testOffset, testEntryHeaderVector);
774 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
775 EXPECT_THROW(
776 try { bufferImpl->readEntry(); } catch (const std::runtime_error& e) {
777 // Calculation: testChecksum (0x21) XOR (0x22) = 3
778 EXPECT_STREQ(e.what(),
779 "[readEntry] Checksum was '3', expected '0'");
780 throw;
781 },
782 std::runtime_error);
783 }
784
TEST_F(BufferEntryTest,ReadEntryPassWraparound)785 TEST_F(BufferEntryTest, ReadEntryPassWraparound)
786 {
787 InSequence s;
788 // We expect this will bump checksum up by "testEntrySize" = 0xff ^ 0xff ...
789 // (20 times) = 0 therefore leave the checksum as is
790 std::vector<uint8_t> testEntryVector(testEntrySize, 0xff);
791 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
792 std::vector<uint8_t> testEntryHeaderVector(
793 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
794 // Set testOffset so that we can test the wraparound here at the header and
795 // update the readPtr
796 testOffset = testMaxOffset - 1;
797 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, _))
798 .WillOnce(Return(expectedWriteSize));
799 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testOffset));
800
801 wraparoundReadMock(testOffset, testEntryHeaderVector);
802 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
803
804 EntryPair testedEntryPair;
805 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry());
806 EXPECT_EQ(testedEntryPair.first, testEntryHeader);
807 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
808 struct CircularBufferHeader cachedBufferHeader =
809 bufferImpl->getCachedBufferHeader();
810 // The bmcReadPtr should have been updated to reflect the wraparound
811 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
812 entryHeaderSize + testEntrySize - 1);
813
814 // Set testOffset so that we can test the wraparound here as well on our
815 // second read for the entry (by 1 byte)
816 testOffset = testMaxOffset - entryHeaderSize - 1;
817 EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset, _))
818 .WillOnce(Return(expectedWriteSize));
819 EXPECT_NO_THROW(bufferImpl->updateReadPtr(testOffset));
820
821 wraparoundReadMock(testOffset, testEntryHeaderVector);
822 wraparoundReadMock(testOffset + entryHeaderSize, testEntryVector);
823
824 EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry());
825 EXPECT_EQ(testedEntryPair.first, testEntryHeader);
826 EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
827 cachedBufferHeader = bufferImpl->getCachedBufferHeader();
828 // The bmcReadPtr should have been updated to reflect the wraparound
829 EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
830 testEntrySize - 1);
831 }
832
833 class BufferReadErrorLogsTest : public BufferEntryTest
834 {
835 protected:
836 BufferReadErrorLogsTest() = default;
837
838 uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
839 size_t entryAndHeaderSize = entryHeaderSize + testEntrySize;
840 };
841
TEST_F(BufferReadErrorLogsTest,PtrsTooBigFail)842 TEST_F(BufferReadErrorLogsTest, PtrsTooBigFail)
843 {
844 InSequence s;
845 // Set the biosWritePtr too big
846 testInitializationHeader.biosWritePtr =
847 boost::endian::native_to_little((testMaxOffset + 1));
848 initializeFuncMock();
849
850 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
851 .WillOnce(Return(std::vector<uint8_t>(
852 testInitializationHeaderPtr,
853 testInitializationHeaderPtr + bufferHeaderSize)));
854 EXPECT_THROW(
855 try {
856 bufferImpl->readErrorLogs();
857 } catch (const std::runtime_error& e) {
858 EXPECT_STREQ(e.what(),
859 "[readErrorLogs] currentBiosWritePtr was '385' "
860 "which was bigger than maxOffset '384'");
861 throw;
862 },
863 std::runtime_error);
864
865 // Reset the biosWritePtr and set the bmcReadPtr too big
866 testInitializationHeader.biosWritePtr = 0;
867 initializeFuncMock();
868 testInitializationHeader.bmcReadPtr =
869 boost::endian::native_to_little((testMaxOffset + 1));
870 initializeFuncMock();
871
872 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
873 .WillOnce(Return(std::vector<uint8_t>(
874 testInitializationHeaderPtr,
875 testInitializationHeaderPtr + bufferHeaderSize)));
876 EXPECT_THROW(
877 try {
878 bufferImpl->readErrorLogs();
879 } catch (const std::runtime_error& e) {
880 EXPECT_STREQ(e.what(), "[readErrorLogs] currentReadPtr was '385' "
881 "which was bigger than maxOffset '384'");
882 throw;
883 },
884 std::runtime_error);
885 }
886
TEST_F(BufferReadErrorLogsTest,IdenticalPtrsPass)887 TEST_F(BufferReadErrorLogsTest, IdenticalPtrsPass)
888 {
889 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
890 .WillOnce(Return(std::vector<uint8_t>(
891 testInitializationHeaderPtr,
892 testInitializationHeaderPtr + bufferHeaderSize)));
893 EXPECT_NO_THROW(bufferImpl->readErrorLogs());
894 }
895
TEST_F(BufferReadErrorLogsTest,NoWraparoundPass)896 TEST_F(BufferReadErrorLogsTest, NoWraparoundPass)
897 {
898 InSequence s;
899 // Set the biosWritePtr to 1 entryHeader + entry size
900 testInitializationHeader.biosWritePtr =
901 boost::endian::native_to_little((entryAndHeaderSize));
902 initializeFuncMock();
903 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
904 .WillOnce(Return(std::vector<uint8_t>(
905 testInitializationHeaderPtr,
906 testInitializationHeaderPtr + bufferHeaderSize)));
907 std::vector<uint8_t> testEntryHeaderVector(
908 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
909 std::vector<uint8_t> testEntryVector(testEntrySize);
910 wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
911 wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
912
913 std::vector<EntryPair> entryPairs;
914 EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
915
916 // Check that we only read one entryPair and that the content is correct
917 EXPECT_EQ(entryPairs.size(), 1U);
918 EXPECT_EQ(entryPairs[0].first, testEntryHeader);
919 EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
920 }
921
TEST_F(BufferReadErrorLogsTest,WraparoundMultiplEntryPass)922 TEST_F(BufferReadErrorLogsTest, WraparoundMultiplEntryPass)
923 {
924 InSequence s;
925 // Set the bmcReadPtr to 1 entryHeader + entry size from the "end" exactly
926 uint32_t entryAndHeaderSizeAwayFromEnd = testMaxOffset - entryAndHeaderSize;
927 testInitializationHeader.bmcReadPtr =
928 boost::endian::native_to_little(entryAndHeaderSizeAwayFromEnd);
929 // Set the biosWritePtr to 1 entryHeader + entry size from the "beginning"
930 testInitializationHeader.biosWritePtr = entryAndHeaderSize;
931 initializeFuncMock();
932 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
933 .WillOnce(Return(std::vector<uint8_t>(
934 testInitializationHeaderPtr,
935 testInitializationHeaderPtr + bufferHeaderSize)));
936
937 std::vector<uint8_t> testEntryHeaderVector(
938 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
939 std::vector<uint8_t> testEntryVector(testEntrySize);
940 wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd,
941 testEntryHeaderVector);
942 wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd +
943 entryHeaderSize,
944 testEntryVector);
945 wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize,
946 testEntryHeaderVector);
947 wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize +
948 entryHeaderSize,
949 testEntryVector);
950
951 std::vector<EntryPair> entryPairs;
952 EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
953
954 // Check that we only read one entryPair and that the content is correct
955 EXPECT_EQ(entryPairs.size(), 2);
956 EXPECT_EQ(entryPairs[0].first, testEntryHeader);
957 EXPECT_EQ(entryPairs[1].first, testEntryHeader);
958 EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
959 EXPECT_THAT(entryPairs[1].second, ElementsAreArray(testEntryVector));
960 }
961
TEST_F(BufferReadErrorLogsTest,WraparoundMismatchingPtrsFail)962 TEST_F(BufferReadErrorLogsTest, WraparoundMismatchingPtrsFail)
963 {
964 InSequence s;
965 testInitializationHeader.bmcReadPtr = boost::endian::native_to_little(0);
966 // Make the biosWritePtr intentially 1 smaller than expected
967 testInitializationHeader.biosWritePtr =
968 boost::endian::native_to_little(entryAndHeaderSize - 1);
969 initializeFuncMock();
970 EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
971 .WillOnce(Return(std::vector<uint8_t>(
972 testInitializationHeaderPtr,
973 testInitializationHeaderPtr + bufferHeaderSize)));
974
975 std::vector<uint8_t> testEntryHeaderVector(
976 testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
977 std::vector<uint8_t> testEntryVector(testEntrySize);
978 wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
979 wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
980
981 EXPECT_THROW(
982 try {
983 bufferImpl->readErrorLogs();
984 } catch (const std::runtime_error& e) {
985 EXPECT_STREQ(
986 e.what(),
987 "[readErrorLogs] biosWritePtr '37' and bmcReaddPtr '38' "
988 "are not identical after reading through all the logs");
989 throw;
990 },
991 std::runtime_error);
992 }
993
994 } // namespace
995 } // namespace bios_bmc_smm_error_logger
996