xref: /openbmc/phosphor-logging/test/openpower-pels/pel_test.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3 
4 #include "elog_entry.hpp"
5 #include "extensions/openpower-pels/generic.hpp"
6 #include "extensions/openpower-pels/pel.hpp"
7 #include "mocks.hpp"
8 #include "pel_utils.hpp"
9 
10 #include <filesystem>
11 #include <fstream>
12 #include <optional>
13 
14 #include <gtest/gtest.h>
15 
16 namespace fs = std::filesystem;
17 using namespace openpower::pels;
18 using ::testing::_;
19 using ::testing::DoAll;
20 using ::testing::NiceMock;
21 using ::testing::Return;
22 using ::testing::SetArgReferee;
23 
24 class PELTest : public CleanLogID
25 {};
26 
makeTempDir()27 fs::path makeTempDir()
28 {
29     char path[] = "/tmp/tempdirXXXXXX";
30     std::filesystem::path dir = mkdtemp(path);
31     return dir;
32 }
33 
writeFileAndGetFD(const fs::path & dir,const std::vector<uint8_t> & data)34 int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data)
35 {
36     static size_t count = 0;
37     fs::path path = dir / (std::string{"file"} + std::to_string(count));
38     std::ofstream stream{path};
39     count++;
40 
41     stream.write(reinterpret_cast<const char*>(data.data()), data.size());
42     stream.close();
43 
44     FILE* fp = fopen(path.c_str(), "r");
45     return fileno(fp);
46 }
47 
TEST_F(PELTest,FlattenTest)48 TEST_F(PELTest, FlattenTest)
49 {
50     auto data = pelDataFactory(TestPELType::pelSimple);
51     auto pel = std::make_unique<PEL>(data);
52 
53     // Check a few fields
54     EXPECT_TRUE(pel->valid());
55     EXPECT_EQ(pel->id(), 0x80818283);
56     EXPECT_EQ(pel->plid(), 0x50515253);
57     EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
58     EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
59 
60     // Test that data in == data out
61     auto flattenedData = pel->data();
62     EXPECT_EQ(data, flattenedData);
63     EXPECT_EQ(flattenedData.size(), pel->size());
64 }
65 
TEST_F(PELTest,CommitTimeTest)66 TEST_F(PELTest, CommitTimeTest)
67 {
68     auto data = pelDataFactory(TestPELType::pelSimple);
69     auto pel = std::make_unique<PEL>(data);
70 
71     auto origTime = pel->commitTime();
72     pel->setCommitTime();
73     auto newTime = pel->commitTime();
74 
75     EXPECT_NE(origTime, newTime);
76 
77     // Make a new PEL and check new value is still there
78     auto newData = pel->data();
79     auto newPel = std::make_unique<PEL>(newData);
80     EXPECT_EQ(newTime, newPel->commitTime());
81 }
82 
TEST_F(PELTest,AssignIDTest)83 TEST_F(PELTest, AssignIDTest)
84 {
85     auto data = pelDataFactory(TestPELType::pelSimple);
86     auto pel = std::make_unique<PEL>(data);
87 
88     auto origID = pel->id();
89     pel->assignID();
90     auto newID = pel->id();
91 
92     EXPECT_NE(origID, newID);
93 
94     // Make a new PEL and check new value is still there
95     auto newData = pel->data();
96     auto newPel = std::make_unique<PEL>(newData);
97     EXPECT_EQ(newID, newPel->id());
98 }
99 
TEST_F(PELTest,WithLogIDTest)100 TEST_F(PELTest, WithLogIDTest)
101 {
102     auto data = pelDataFactory(TestPELType::pelSimple);
103     auto pel = std::make_unique<PEL>(data, 0x42);
104 
105     EXPECT_TRUE(pel->valid());
106     EXPECT_EQ(pel->obmcLogID(), 0x42);
107 }
108 
TEST_F(PELTest,InvalidPELTest)109 TEST_F(PELTest, InvalidPELTest)
110 {
111     auto data = pelDataFactory(TestPELType::pelSimple);
112 
113     // Too small
114     data.resize(PrivateHeader::flattenedSize());
115 
116     auto pel = std::make_unique<PEL>(data);
117 
118     EXPECT_TRUE(pel->privateHeader().valid());
119     EXPECT_FALSE(pel->userHeader().valid());
120     EXPECT_FALSE(pel->valid());
121 
122     // Now corrupt the private header
123     data = pelDataFactory(TestPELType::pelSimple);
124     data.at(0) = 0;
125     pel = std::make_unique<PEL>(data);
126 
127     EXPECT_FALSE(pel->privateHeader().valid());
128     EXPECT_TRUE(pel->userHeader().valid());
129     EXPECT_FALSE(pel->valid());
130 }
131 
TEST_F(PELTest,EmptyDataTest)132 TEST_F(PELTest, EmptyDataTest)
133 {
134     std::vector<uint8_t> data;
135     auto pel = std::make_unique<PEL>(data);
136 
137     EXPECT_FALSE(pel->privateHeader().valid());
138     EXPECT_FALSE(pel->userHeader().valid());
139     EXPECT_FALSE(pel->valid());
140 }
141 
TEST_F(PELTest,CreateFromRegistryTest)142 TEST_F(PELTest, CreateFromRegistryTest)
143 {
144     message::Entry regEntry;
145     uint64_t timestamp = 5;
146 
147     regEntry.name = "test";
148     regEntry.subsystem = 5;
149     regEntry.actionFlags = 0xC000;
150     regEntry.src.type = 0xBD;
151     regEntry.src.reasonCode = 0x1234;
152 
153     std::map<std::string, std::string> data{{"KEY1", "VALUE1"}};
154     AdditionalData ad{data};
155     NiceMock<MockDataInterface> dataIface;
156     NiceMock<MockJournal> journal;
157     PelFFDC ffdc;
158 
159     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
160             ad,       ffdc, dataIface, journal};
161 
162     EXPECT_TRUE(pel.valid());
163     EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
164     EXPECT_EQ(pel.userHeader().severity(), 0x40);
165 
166     EXPECT_EQ(pel.primarySRC().value()->asciiString(),
167               "BD051234                        ");
168 
169     // Check that certain optional sections have been created
170     size_t mtmsCount = 0;
171     size_t euhCount = 0;
172     size_t udCount = 0;
173 
174     for (const auto& section : pel.optionalSections())
175     {
176         if (section->header().id ==
177             static_cast<uint16_t>(SectionID::failingMTMS))
178         {
179             mtmsCount++;
180         }
181         else if (section->header().id ==
182                  static_cast<uint16_t>(SectionID::extendedUserHeader))
183         {
184             euhCount++;
185         }
186         else if (section->header().id ==
187                  static_cast<uint16_t>(SectionID::userData))
188         {
189             udCount++;
190         }
191     }
192 
193     EXPECT_EQ(mtmsCount, 1);
194     EXPECT_EQ(euhCount, 1);
195     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
196     ASSERT_FALSE(pel.isHwCalloutPresent());
197 
198     {
199         // The same thing, but without the action flags specified
200         // in the registry, so the constructor should set them.
201         regEntry.actionFlags = std::nullopt;
202 
203         PEL pel2{regEntry,  42,
204                  timestamp, phosphor::logging::Entry::Level::Error,
205                  ad,        ffdc,
206                  dataIface, journal};
207 
208         EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
209     }
210 }
211 
212 // Test that when the AdditionalData size is over 16KB that
213 // the PEL that's created is exactly 16KB since the UserData
214 // section that contains all that data was pruned.
TEST_F(PELTest,CreateTooBigADTest)215 TEST_F(PELTest, CreateTooBigADTest)
216 {
217     message::Entry regEntry;
218     uint64_t timestamp = 5;
219 
220     regEntry.name = "test";
221     regEntry.subsystem = 5;
222     regEntry.actionFlags = 0xC000;
223     regEntry.src.type = 0xBD;
224     regEntry.src.reasonCode = 0x1234;
225     PelFFDC ffdc;
226 
227     // Over the 16KB max PEL size
228     std::map<std::string, std::string> data{{"KEY1", std::string(17000, 'G')}};
229     AdditionalData ad{data};
230     NiceMock<MockDataInterface> dataIface;
231     NiceMock<MockJournal> journal;
232 
233     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
234             ad,       ffdc, dataIface, journal};
235 
236     EXPECT_TRUE(pel.valid());
237     EXPECT_EQ(pel.size(), 16384);
238 
239     // Make sure that there are still 2 UD sections.
240     const auto& optSections = pel.optionalSections();
241     auto udCount = std::count_if(
242         optSections.begin(), optSections.end(), [](const auto& section) {
243             return section->header().id ==
244                    static_cast<uint16_t>(SectionID::userData);
245         });
246 
247     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
248 }
249 
250 // Test that we'll create Generic optional sections for sections that
251 // there aren't explicit classes for.
TEST_F(PELTest,GenericSectionTest)252 TEST_F(PELTest, GenericSectionTest)
253 {
254     auto data = pelDataFactory(TestPELType::pelSimple);
255 
256     std::vector<uint8_t> section1{
257         0x58, 0x58, // ID 'XX'
258         0x00, 0x18, // Size
259         0x01, 0x02, // version, subtype
260         0x03, 0x04, // comp ID
261 
262         // some data
263         0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
264         0x09, 0x22, 0x3A, 0x00};
265 
266     std::vector<uint8_t> section2{
267         0x59, 0x59, // ID 'YY'
268         0x00, 0x20, // Size
269         0x01, 0x02, // version, subtype
270         0x03, 0x04, // comp ID
271 
272         // some data
273         0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
274         0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
275 
276     // Add the new sections at the end
277     data.insert(data.end(), section1.begin(), section1.end());
278     data.insert(data.end(), section2.begin(), section2.end());
279 
280     // Increment the section count
281     data.at(27) += 2;
282     auto origData = data;
283 
284     PEL pel{data};
285 
286     const auto& sections = pel.optionalSections();
287 
288     bool foundXX = false;
289     bool foundYY = false;
290 
291     // Check that we can find these 2 Generic sections
292     for (const auto& section : sections)
293     {
294         if (section->header().id == 0x5858)
295         {
296             foundXX = true;
297             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
298         }
299         else if (section->header().id == 0x5959)
300         {
301             foundYY = true;
302             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
303         }
304     }
305 
306     EXPECT_TRUE(foundXX);
307     EXPECT_TRUE(foundYY);
308 
309     // Now flatten and check
310     auto newData = pel.data();
311 
312     EXPECT_EQ(origData, newData);
313 }
314 
315 // Test that an invalid section will still get a Generic object
TEST_F(PELTest,InvalidGenericTest)316 TEST_F(PELTest, InvalidGenericTest)
317 {
318     auto data = pelDataFactory(TestPELType::pelSimple);
319 
320     // Not a valid section
321     std::vector<uint8_t> section1{0x01, 0x02, 0x03};
322 
323     data.insert(data.end(), section1.begin(), section1.end());
324 
325     // Increment the section count
326     data.at(27) += 1;
327 
328     PEL pel{data};
329     EXPECT_FALSE(pel.valid());
330 
331     const auto& sections = pel.optionalSections();
332 
333     bool foundGeneric = false;
334     for (const auto& section : sections)
335     {
336         if (dynamic_cast<Generic*>(section.get()) != nullptr)
337         {
338             foundGeneric = true;
339             EXPECT_EQ(section->valid(), false);
340             break;
341         }
342     }
343 
344     EXPECT_TRUE(foundGeneric);
345 }
346 
347 // Create a UserData section out of AdditionalData
TEST_F(PELTest,MakeUDSectionTest)348 TEST_F(PELTest, MakeUDSectionTest)
349 {
350     std::map<std::string, std::string> ad{{"KEY1", "VALUE1"},
351                                           {"KEY2", "VALUE2"},
352                                           {"KEY3", "VALUE3"},
353                                           {"ESEL", "TEST"}};
354     AdditionalData additionalData{ad};
355 
356     auto ud = util::makeADUserDataSection(additionalData);
357 
358     EXPECT_TRUE(ud->valid());
359     EXPECT_EQ(ud->header().id, 0x5544);
360     EXPECT_EQ(ud->header().version, 0x01);
361     EXPECT_EQ(ud->header().subType, 0x01);
362     EXPECT_EQ(ud->header().componentID, 0x2000);
363 
364     const auto& d = ud->data();
365 
366     std::string jsonString{d.begin(), d.end()};
367 
368     std::string expectedJSON =
369         R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
370 
371     // The actual data is null padded to a 4B boundary.
372     std::vector<uint8_t> expectedData;
373     expectedData.resize(52, '\0');
374     memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
375 
376     EXPECT_EQ(d, expectedData);
377 
378     // Ensure we can read this as JSON
379     auto newJSON = nlohmann::json::parse(jsonString);
380     EXPECT_EQ(newJSON["KEY1"], "VALUE1");
381     EXPECT_EQ(newJSON["KEY2"], "VALUE2");
382     EXPECT_EQ(newJSON["KEY3"], "VALUE3");
383 }
384 
385 // Create the UserData section that contains system info
TEST_F(PELTest,SysInfoSectionTest)386 TEST_F(PELTest, SysInfoSectionTest)
387 {
388     MockDataInterface dataIface;
389 
390     EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
391     EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
392     EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
393     EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
394     EXPECT_CALL(dataIface, getBootState())
395         .WillOnce(Return("State.SystemInitComplete"));
396     EXPECT_CALL(dataIface, getSystemIMKeyword())
397         .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
398 
399     std::map<std::string, std::string> ad{{"_PID", std::to_string(getpid())}};
400     AdditionalData additionalData{ad};
401 
402     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
403 
404     EXPECT_TRUE(ud->valid());
405     EXPECT_EQ(ud->header().id, 0x5544);
406     EXPECT_EQ(ud->header().version, 0x01);
407     EXPECT_EQ(ud->header().subType, 0x01);
408     EXPECT_EQ(ud->header().componentID, 0x2000);
409 
410     // Pull out the JSON data and check it.
411     const auto& d = ud->data();
412     std::string jsonString{d.begin(), d.end()};
413     auto json = nlohmann::json::parse(jsonString);
414 
415     // Ensure the 'Process Name' entry contains the name of this test
416     // executable.
417     auto name = json["Process Name"].get<std::string>();
418     auto found = (name.find("pel_test") != std::string::npos) ||
419                  (name.find("test-openpower-pels-pel") != std::string::npos);
420     EXPECT_TRUE(found);
421     // @TODO(stwcx): remove 'pel_test' when removing autotools.
422 
423     auto version = json["FW Version ID"].get<std::string>();
424     EXPECT_EQ(version, "ABCD1234");
425 
426     auto state = json["BMCState"].get<std::string>();
427     EXPECT_EQ(state, "Ready");
428 
429     state = json["ChassisState"].get<std::string>();
430     EXPECT_EQ(state, "On");
431 
432     state = json["HostState"].get<std::string>();
433     EXPECT_EQ(state, "Off");
434 
435     state = json["BootState"].get<std::string>();
436     EXPECT_EQ(state, "SystemInitComplete");
437 
438     auto keyword = json["System IM"].get<std::string>();
439     EXPECT_EQ(keyword, "000155AA");
440 }
441 
442 // Test that the sections that override
443 //     virtual std::optional<std::string> Section::getJSON() const
444 // return valid JSON.
TEST_F(PELTest,SectionJSONTest)445 TEST_F(PELTest, SectionJSONTest)
446 {
447     auto data = pelDataFactory(TestPELType::pelSimple);
448     PEL pel{data};
449 
450     // Check that all JSON returned from the sections is
451     // parseable by nlohmann::json, which will throw an
452     // exception and fail the test if there is a problem.
453 
454     // The getJSON() response needs to be wrapped in a { } to make
455     // actual valid JSON (PEL::toJSON() usually handles that).
456 
457     auto jsonString = pel.privateHeader().getJSON('O');
458 
459     // PrivateHeader always prints JSON
460     ASSERT_TRUE(jsonString);
461     *jsonString = '{' + *jsonString + '}';
462     auto json = nlohmann::json::parse(*jsonString);
463 
464     jsonString = pel.userHeader().getJSON('O');
465 
466     // UserHeader always prints JSON
467     ASSERT_TRUE(jsonString);
468     *jsonString = '{' + *jsonString + '}';
469     json = nlohmann::json::parse(*jsonString);
470 
471     for (const auto& section : pel.optionalSections())
472     {
473         // The optional sections may or may not have implemented getJSON().
474         jsonString = section->getJSON('O');
475         if (jsonString)
476         {
477             *jsonString = '{' + *jsonString + '}';
478             auto json = nlohmann::json::parse(*jsonString);
479         }
480     }
481 }
482 
getJSONFFDC(const fs::path & dir)483 PelFFDCfile getJSONFFDC(const fs::path& dir)
484 {
485     PelFFDCfile ffdc;
486     ffdc.format = UserDataFormat::json;
487     ffdc.subType = 5;
488     ffdc.version = 42;
489 
490     auto inputJSON = R"({
491         "key1": "value1",
492         "key2": 42,
493         "key3" : [1, 2, 3, 4, 5],
494         "key4": {"key5": "value5"}
495     })"_json;
496 
497     // Write the JSON to a file and get its descriptor.
498     auto s = inputJSON.dump();
499     std::vector<uint8_t> data{s.begin(), s.end()};
500     ffdc.fd = writeFileAndGetFD(dir, data);
501 
502     return ffdc;
503 }
504 
TEST_F(PELTest,MakeJSONFileUDSectionTest)505 TEST_F(PELTest, MakeJSONFileUDSectionTest)
506 {
507     auto dir = makeTempDir();
508 
509     {
510         auto ffdc = getJSONFFDC(dir);
511 
512         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
513         close(ffdc.fd);
514         ASSERT_TRUE(ud);
515         ASSERT_TRUE(ud->valid());
516         EXPECT_EQ(ud->header().id, 0x5544);
517 
518         EXPECT_EQ(ud->header().version,
519                   static_cast<uint8_t>(UserDataFormatVersion::json));
520         EXPECT_EQ(ud->header().subType,
521                   static_cast<uint8_t>(UserDataFormat::json));
522         EXPECT_EQ(ud->header().componentID,
523                   static_cast<uint16_t>(ComponentID::phosphorLogging));
524 
525         // Pull the JSON back out of the the UserData section
526         const auto& d = ud->data();
527         std::string js{d.begin(), d.end()};
528         auto json = nlohmann::json::parse(js);
529 
530         EXPECT_EQ("value1", json["key1"].get<std::string>());
531         EXPECT_EQ(42, json["key2"].get<int>());
532 
533         std::vector<int> key3Values{1, 2, 3, 4, 5};
534         EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
535 
536         std::map<std::string, std::string> key4Values{{"key5", "value5"}};
537         auto actual = json["key4"].get<std::map<std::string, std::string>>();
538         EXPECT_EQ(key4Values, actual);
539     }
540 
541     {
542         // A bad FD
543         PelFFDCfile ffdc;
544         ffdc.format = UserDataFormat::json;
545         ffdc.subType = 5;
546         ffdc.version = 42;
547         ffdc.fd = 10000;
548 
549         // The section shouldn't get made
550         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
551         ASSERT_FALSE(ud);
552     }
553 
554     fs::remove_all(dir);
555 }
556 
getCBORFFDC(const fs::path & dir)557 PelFFDCfile getCBORFFDC(const fs::path& dir)
558 {
559     PelFFDCfile ffdc;
560     ffdc.format = UserDataFormat::cbor;
561     ffdc.subType = 5;
562     ffdc.version = 42;
563 
564     auto inputJSON = R"({
565         "key1": "value1",
566         "key2": 42,
567         "key3" : [1, 2, 3, 4, 5],
568         "key4": {"key5": "value5"}
569     })"_json;
570 
571     // Convert the JSON to CBOR and write it to a file
572     auto data = nlohmann::json::to_cbor(inputJSON);
573     ffdc.fd = writeFileAndGetFD(dir, data);
574 
575     return ffdc;
576 }
577 
TEST_F(PELTest,MakeCBORFileUDSectionTest)578 TEST_F(PELTest, MakeCBORFileUDSectionTest)
579 {
580     auto dir = makeTempDir();
581 
582     auto ffdc = getCBORFFDC(dir);
583     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
584     close(ffdc.fd);
585     ASSERT_TRUE(ud);
586     ASSERT_TRUE(ud->valid());
587     EXPECT_EQ(ud->header().id, 0x5544);
588 
589     EXPECT_EQ(ud->header().version,
590               static_cast<uint8_t>(UserDataFormatVersion::cbor));
591     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
592     EXPECT_EQ(ud->header().componentID,
593               static_cast<uint16_t>(ComponentID::phosphorLogging));
594 
595     // Pull the CBOR back out of the PEL section
596     // The number of pad bytes to make the section be 4B aligned
597     // was added at the end, read it and then remove it and the
598     // padding before parsing it.
599     auto data = ud->data();
600     Stream stream{data};
601     stream.offset(data.size() - 4);
602     uint32_t pad;
603     stream >> pad;
604 
605     data.resize(data.size() - 4 - pad);
606 
607     auto json = nlohmann::json::from_cbor(data);
608 
609     EXPECT_EQ("value1", json["key1"].get<std::string>());
610     EXPECT_EQ(42, json["key2"].get<int>());
611 
612     std::vector<int> key3Values{1, 2, 3, 4, 5};
613     EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
614 
615     std::map<std::string, std::string> key4Values{{"key5", "value5"}};
616     auto actual = json["key4"].get<std::map<std::string, std::string>>();
617     EXPECT_EQ(key4Values, actual);
618 
619     fs::remove_all(dir);
620 }
621 
getTextFFDC(const fs::path & dir)622 PelFFDCfile getTextFFDC(const fs::path& dir)
623 {
624     PelFFDCfile ffdc;
625     ffdc.format = UserDataFormat::text;
626     ffdc.subType = 5;
627     ffdc.version = 42;
628 
629     std::string text{"this is some text that will be used for FFDC"};
630     std::vector<uint8_t> data{text.begin(), text.end()};
631 
632     ffdc.fd = writeFileAndGetFD(dir, data);
633 
634     return ffdc;
635 }
636 
TEST_F(PELTest,MakeTextFileUDSectionTest)637 TEST_F(PELTest, MakeTextFileUDSectionTest)
638 {
639     auto dir = makeTempDir();
640 
641     auto ffdc = getTextFFDC(dir);
642     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
643     close(ffdc.fd);
644     ASSERT_TRUE(ud);
645     ASSERT_TRUE(ud->valid());
646     EXPECT_EQ(ud->header().id, 0x5544);
647 
648     EXPECT_EQ(ud->header().version,
649               static_cast<uint8_t>(UserDataFormatVersion::text));
650     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
651     EXPECT_EQ(ud->header().componentID,
652               static_cast<uint16_t>(ComponentID::phosphorLogging));
653 
654     // Get the text back out
655     std::string text{ud->data().begin(), ud->data().end()};
656     EXPECT_EQ(text, "this is some text that will be used for FFDC");
657 
658     fs::remove_all(dir);
659 }
660 
getCustomFFDC(const fs::path & dir,const std::vector<uint8_t> & data)661 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
662 {
663     PelFFDCfile ffdc;
664     ffdc.format = UserDataFormat::custom;
665     ffdc.subType = 5;
666     ffdc.version = 42;
667 
668     ffdc.fd = writeFileAndGetFD(dir, data);
669 
670     return ffdc;
671 }
672 
TEST_F(PELTest,MakeCustomFileUDSectionTest)673 TEST_F(PELTest, MakeCustomFileUDSectionTest)
674 {
675     auto dir = makeTempDir();
676 
677     {
678         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
679 
680         auto ffdc = getCustomFFDC(dir, data);
681         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
682         close(ffdc.fd);
683         ASSERT_TRUE(ud);
684         ASSERT_TRUE(ud->valid());
685         EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
686         EXPECT_EQ(ud->header().id, 0x5544);
687 
688         EXPECT_EQ(ud->header().version, 42);
689         EXPECT_EQ(ud->header().subType, 5);
690         EXPECT_EQ(ud->header().componentID, 0x2002);
691 
692         // Get the data back out
693         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
694         EXPECT_EQ(data, newData);
695     }
696 
697     // Do the same thing again, but make it be non 4B aligned
698     // so the data gets padded.
699     {
700         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
701 
702         auto ffdc = getCustomFFDC(dir, data);
703         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
704         close(ffdc.fd);
705         ASSERT_TRUE(ud);
706         ASSERT_TRUE(ud->valid());
707         EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
708         EXPECT_EQ(ud->header().id, 0x5544);
709 
710         EXPECT_EQ(ud->header().version, 42);
711         EXPECT_EQ(ud->header().subType, 5);
712         EXPECT_EQ(ud->header().componentID, 0x2002);
713 
714         // Get the data back out
715         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
716 
717         // pad the original to 12B so we can compare
718         data.push_back(0);
719         data.push_back(0);
720         data.push_back(0);
721 
722         EXPECT_EQ(data, newData);
723     }
724 
725     fs::remove_all(dir);
726 }
727 
728 // Test Adding FFDC from files to a PEL
TEST_F(PELTest,CreateWithFFDCTest)729 TEST_F(PELTest, CreateWithFFDCTest)
730 {
731     auto dir = makeTempDir();
732     message::Entry regEntry;
733     uint64_t timestamp = 5;
734 
735     regEntry.name = "test";
736     regEntry.subsystem = 5;
737     regEntry.actionFlags = 0xC000;
738     regEntry.src.type = 0xBD;
739     regEntry.src.reasonCode = 0x1234;
740 
741     std::map<std::string, std::string> additionalData{{"KEY1", "VALUE1"}};
742     AdditionalData ad{additionalData};
743     NiceMock<MockDataInterface> dataIface;
744     NiceMock<MockJournal> journal;
745     PelFFDC ffdc;
746 
747     std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
748 
749     // This will be trimmed when added
750     std::vector<uint8_t> hugeCustomData(17000, 0x42);
751 
752     ffdc.emplace_back(getJSONFFDC(dir));
753     ffdc.emplace_back(getCBORFFDC(dir));
754     ffdc.emplace_back(getTextFFDC(dir));
755     ffdc.emplace_back(getCustomFFDC(dir, customData));
756     ffdc.emplace_back(getCustomFFDC(dir, hugeCustomData));
757 
758     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
759             ad,       ffdc, dataIface, journal};
760 
761     EXPECT_TRUE(pel.valid());
762 
763     // Clipped to the max
764     EXPECT_EQ(pel.size(), 16384);
765 
766     // Check for the FFDC sections
767     size_t udCount = 0;
768     Section* ud = nullptr;
769 
770     for (const auto& section : pel.optionalSections())
771     {
772         if (section->header().id == static_cast<uint16_t>(SectionID::userData))
773         {
774             udCount++;
775             ud = section.get();
776         }
777     }
778 
779     EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
780 
781     // Check the last section was trimmed to
782     // something a bit less that 17000.
783     EXPECT_GT(ud->header().size, 14000);
784     EXPECT_LT(ud->header().size, 16000);
785 
786     fs::remove_all(dir);
787 }
788 
789 // Create a PEL with device callouts
TEST_F(PELTest,CreateWithDevCalloutsTest)790 TEST_F(PELTest, CreateWithDevCalloutsTest)
791 {
792     message::Entry regEntry;
793     uint64_t timestamp = 5;
794 
795     regEntry.name = "test";
796     regEntry.subsystem = 5;
797     regEntry.actionFlags = 0xC000;
798     regEntry.src.type = 0xBD;
799     regEntry.src.reasonCode = 0x1234;
800 
801     NiceMock<MockDataInterface> dataIface;
802     NiceMock<MockJournal> journal;
803     PelFFDC ffdc;
804 
805     const auto calloutJSON = R"(
806     {
807         "I2C":
808         {
809             "14":
810             {
811                 "114":
812                 {
813                     "Callouts":[
814                         {
815                         "Name": "/chassis/motherboard/cpu0",
816                         "LocationCode": "P1",
817                         "Priority": "H"
818                         }
819                     ],
820                     "Dest": "proc 0 target"
821                 }
822             }
823         }
824     })";
825 
826     std::vector<std::string> names{"systemA"};
827     EXPECT_CALL(dataIface, getSystemNames)
828         .Times(2)
829         .WillRepeatedly(Return(names));
830 
831     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
832         .Times(1)
833         .WillOnce(Return("UXXX-P1"));
834 
835     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
836         .WillOnce(Return(std::vector<std::string>{
837             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
838 
839     EXPECT_CALL(dataIface,
840                 getHWCalloutFields(
841                     "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
842                     _, _, _))
843         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
844                         SetArgReferee<3>("123456789ABC")));
845 
846     auto dataPath = getPELReadOnlyDataPath();
847     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
848     file << calloutJSON;
849     file.close();
850 
851     {
852         std::map<std::string, std::string> data{
853             {"CALLOUT_ERRNO", "5"},
854             {"CALLOUT_DEVICE_PATH",
855              "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
856 
857         AdditionalData ad{data};
858 
859         PEL pel{regEntry,  42,
860                 timestamp, phosphor::logging::Entry::Level::Error,
861                 ad,        ffdc,
862                 dataIface, journal};
863 
864         ASSERT_TRUE(pel.primarySRC().value()->callouts());
865         auto& callouts = pel.primarySRC().value()->callouts()->callouts();
866         ASSERT_EQ(callouts.size(), 1);
867         ASSERT_TRUE(pel.isHwCalloutPresent());
868 
869         EXPECT_EQ(callouts[0]->priority(), 'H');
870         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
871 
872         auto& fru = callouts[0]->fruIdentity();
873         EXPECT_EQ(fru->getPN().value(), "1234567");
874         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
875         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
876 
877         const auto& section = pel.optionalSections().back();
878 
879         ASSERT_EQ(section->header().id, 0x5544); // UD
880         auto ud = static_cast<UserData*>(section.get());
881 
882         // Check that there was a UserData section added that
883         // contains debug details about the device.
884         const auto& d = ud->data();
885         std::string jsonString{d.begin(), d.end()};
886         auto actualJSON = nlohmann::json::parse(jsonString);
887 
888         auto expectedJSON = R"(
889             {
890                 "PEL Internal Debug Data": {
891                     "SRC": [
892                       "I2C: bus: 14 address: 114 dest: proc 0 target"
893                     ]
894                 }
895             }
896         )"_json;
897 
898         EXPECT_TRUE(
899             actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
900         EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
901                   expectedJSON["PEL Internal Debug Data"]["SRC"]);
902     }
903 
904     {
905         // Device path not found (wrong i2c addr), so no callouts
906         std::map<std::string, std::string> data{
907             {"CALLOUT_ERRNO", "5"},
908             {"CALLOUT_DEVICE_PATH",
909              "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}};
910 
911         AdditionalData ad{data};
912 
913         PEL pel{regEntry,  42,
914                 timestamp, phosphor::logging::Entry::Level::Error,
915                 ad,        ffdc,
916                 dataIface, journal};
917 
918         // no callouts
919         EXPECT_FALSE(pel.primarySRC().value()->callouts());
920 
921         // Now check that there was a UserData section
922         // that contains the lookup error.
923         const auto& section = pel.optionalSections().back();
924 
925         ASSERT_EQ(section->header().id, 0x5544); // UD
926         auto ud = static_cast<UserData*>(section.get());
927 
928         const auto& d = ud->data();
929 
930         std::string jsonString{d.begin(), d.end()};
931 
932         auto actualJSON = nlohmann::json::parse(jsonString);
933 
934         auto expectedJSON =
935             "{\"PEL Internal Debug Data\":{\"SRC\":"
936             "[\"Problem looking up I2C callouts on 14 153: "
937             "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
938 
939         EXPECT_TRUE(
940             actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
941         EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
942                   expectedJSON["PEL Internal Debug Data"]["SRC"]);
943     }
944 
945     fs::remove_all(dataPath);
946 }
947 
948 // Test PELs when the callouts are passed in using a JSON file.
TEST_F(PELTest,CreateWithJSONCalloutsTest)949 TEST_F(PELTest, CreateWithJSONCalloutsTest)
950 {
951     PelFFDCfile ffdcFile;
952     ffdcFile.format = UserDataFormat::json;
953     ffdcFile.subType = 0xCA; // Callout JSON
954     ffdcFile.version = 1;
955 
956     // Write these callouts to a JSON file and pass it into
957     // the PEL as an FFDC file. Also has a duplicate that
958     // will be removed.
959     auto inputJSON = R"([
960         {
961             "Priority": "H",
962             "LocationCode": "P0-C1"
963         },
964         {
965             "Priority": "M",
966             "Procedure": "PROCEDURE"
967         },
968         {
969             "Priority": "L",
970             "Procedure": "PROCEDURE"
971         }
972     ])"_json;
973 
974     auto s = inputJSON.dump();
975     std::vector<uint8_t> data{s.begin(), s.end()};
976     auto dir = makeTempDir();
977     ffdcFile.fd = writeFileAndGetFD(dir, data);
978 
979     PelFFDC ffdc;
980     ffdc.push_back(std::move(ffdcFile));
981 
982     AdditionalData ad;
983     NiceMock<MockDataInterface> dataIface;
984     NiceMock<MockJournal> journal;
985 
986     EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
987         .Times(1)
988         .WillOnce(Return("UXXX-P0-C1"));
989     EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
990         .Times(1)
991         .WillOnce(Return(
992             std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
993     EXPECT_CALL(dataIface, getHWCalloutFields(
994                                "/inv/system/chassis/motherboard/bmc", _, _, _))
995         .Times(1)
996         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
997                         SetArgReferee<3>("123456789ABC")));
998 
999     message::Entry regEntry;
1000     regEntry.name = "test";
1001     regEntry.subsystem = 5;
1002     regEntry.actionFlags = 0xC000;
1003     regEntry.src.type = 0xBD;
1004     regEntry.src.reasonCode = 0x1234;
1005 
1006     PEL pel{regEntry, 42,   5,         phosphor::logging::Entry::Level::Error,
1007             ad,       ffdc, dataIface, journal};
1008 
1009     ASSERT_TRUE(pel.valid());
1010     ASSERT_TRUE(pel.primarySRC().value()->callouts());
1011     const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1012     ASSERT_EQ(callouts.size(), 2);
1013     ASSERT_TRUE(pel.isHwCalloutPresent());
1014 
1015     {
1016         EXPECT_EQ(callouts[0]->priority(), 'H');
1017         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1018 
1019         auto& fru = callouts[0]->fruIdentity();
1020         EXPECT_EQ(fru->getPN().value(), "1234567");
1021         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1022         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1023         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1024     }
1025     {
1026         EXPECT_EQ(callouts[1]->priority(), 'M');
1027         EXPECT_EQ(callouts[1]->locationCode(), "");
1028 
1029         auto& fru = callouts[1]->fruIdentity();
1030         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1031         EXPECT_EQ(fru->failingComponentType(),
1032                   src::FRUIdentity::maintenanceProc);
1033     }
1034     fs::remove_all(dir);
1035 }
1036 
1037 // Test PELs with symblic FRU callout.
TEST_F(PELTest,CreateWithJSONSymblicCalloutTest)1038 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1039 {
1040     PelFFDCfile ffdcFile;
1041     ffdcFile.format = UserDataFormat::json;
1042     ffdcFile.subType = 0xCA; // Callout JSON
1043     ffdcFile.version = 1;
1044 
1045     // Write these callouts to a JSON file and pass it into
1046     // the PEL as an FFDC file.
1047     auto inputJSON = R"([
1048         {
1049             "Priority": "M",
1050             "Procedure": "SVCDOCS"
1051         }
1052     ])"_json;
1053 
1054     auto s = inputJSON.dump();
1055     std::vector<uint8_t> data{s.begin(), s.end()};
1056     auto dir = makeTempDir();
1057     ffdcFile.fd = writeFileAndGetFD(dir, data);
1058 
1059     PelFFDC ffdc;
1060     ffdc.push_back(std::move(ffdcFile));
1061 
1062     AdditionalData ad;
1063     NiceMock<MockDataInterface> dataIface;
1064     NiceMock<MockJournal> journal;
1065 
1066     message::Entry regEntry;
1067     regEntry.name = "test";
1068     regEntry.subsystem = 5;
1069     regEntry.actionFlags = 0xC000;
1070     regEntry.src.type = 0xBD;
1071     regEntry.src.reasonCode = 0x1234;
1072 
1073     PEL pel{regEntry, 42,   5,         phosphor::logging::Entry::Level::Error,
1074             ad,       ffdc, dataIface, journal};
1075 
1076     ASSERT_TRUE(pel.valid());
1077     ASSERT_TRUE(pel.primarySRC().value()->callouts());
1078     const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1079     ASSERT_EQ(callouts.size(), 1);
1080     ASSERT_FALSE(pel.isHwCalloutPresent());
1081 
1082     {
1083         EXPECT_EQ(callouts[0]->priority(), 'M');
1084         EXPECT_EQ(callouts[0]->locationCode(), "");
1085 
1086         auto& fru = callouts[0]->fruIdentity();
1087         EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1088     }
1089     fs::remove_all(dir);
1090 }
1091 
TEST_F(PELTest,FlattenLinesTest)1092 TEST_F(PELTest, FlattenLinesTest)
1093 {
1094     std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1095 
1096     auto buffer = util::flattenLines(msgs);
1097 
1098     std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1099     std::vector<uint8_t> expected(string.begin(), string.end());
1100 
1101     EXPECT_EQ(buffer, expected);
1102 }
1103 
checkJournalSection(const std::unique_ptr<Section> & section,const std::string & expected)1104 void checkJournalSection(const std::unique_ptr<Section>& section,
1105                          const std::string& expected)
1106 {
1107     ASSERT_EQ(SectionID::userData,
1108               static_cast<SectionID>(section->header().id));
1109     ASSERT_EQ(UserDataFormat::text,
1110               static_cast<UserDataFormat>(section->header().subType));
1111     ASSERT_EQ(section->header().version,
1112               static_cast<uint8_t>(UserDataFormatVersion::text));
1113 
1114     auto ud = static_cast<UserData*>(section.get());
1115 
1116     std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1117 
1118     // PEL sections are 4B aligned so add padding before the compare
1119     while (expectedData.size() % 4 != 0)
1120     {
1121         expectedData.push_back('\0');
1122     }
1123 
1124     EXPECT_EQ(ud->data(), expectedData);
1125 }
1126 
TEST_F(PELTest,CaptureJournalTest)1127 TEST_F(PELTest, CaptureJournalTest)
1128 {
1129     message::Entry regEntry;
1130     uint64_t timestamp = 5;
1131 
1132     regEntry.name = "test";
1133     regEntry.subsystem = 5;
1134     regEntry.actionFlags = 0xC000;
1135     regEntry.src.type = 0xBD;
1136     regEntry.src.reasonCode = 0x1234;
1137 
1138     std::map<std::string, std::string> data{};
1139     AdditionalData ad{data};
1140     NiceMock<MockDataInterface> dataIface;
1141     NiceMock<MockJournal> journal;
1142     PelFFDC ffdc;
1143 
1144     size_t pelSectsWithOneUD{0};
1145 
1146     {
1147         // Capture 5 lines from the journal into a single UD section
1148         message::JournalCapture jc = size_t{5};
1149         regEntry.journalCapture = jc;
1150 
1151         std::vector<std::string> msgs{"test1 test2", "test3 test4",
1152                                       "test5 test6", "4", "5"};
1153 
1154         EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1155 
1156         PEL pel{regEntry,  42,
1157                 timestamp, phosphor::logging::Entry::Level::Error,
1158                 ad,        ffdc,
1159                 dataIface, journal};
1160 
1161         // Check the generated UserData section
1162         std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1163 
1164         checkJournalSection(pel.optionalSections().back(), expected);
1165 
1166         // Save for upcoming testcases
1167         pelSectsWithOneUD = pel.privateHeader().sectionCount();
1168     }
1169 
1170     {
1171         // Attempt to capture too many journal entries so the
1172         // section gets dropped.
1173         message::JournalCapture jc = size_t{1};
1174         regEntry.journalCapture = jc;
1175 
1176         EXPECT_CALL(journal, sync()).Times(1);
1177 
1178         // A 20000 byte line won't fit in a PEL
1179         EXPECT_CALL(journal, getMessages("", 1))
1180             .WillOnce(
1181                 Return(std::vector<std::string>{std::string(20000, 'x')}));
1182 
1183         PEL pel{regEntry,  42,
1184                 timestamp, phosphor::logging::Entry::Level::Error,
1185                 ad,        ffdc,
1186                 dataIface, journal};
1187 
1188         // Check for 1 fewer sections than in the previous PEL
1189         EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1190     }
1191 
1192     // Capture 3 different journal sections
1193     {
1194         message::AppCaptureList captureList{
1195             message::AppCapture{"app1", 3},
1196             message::AppCapture{"app2", 4},
1197             message::AppCapture{"app3", 1},
1198         };
1199         message::JournalCapture jc = captureList;
1200         regEntry.journalCapture = jc;
1201 
1202         std::vector<std::string> app1{"A B", "C D", "E F"};
1203         std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1204         std::vector<std::string> app3{"a b c"};
1205 
1206         std::string expected1{"A B\nC D\nE F\n"};
1207         std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1208         std::string expected3{"a b c\n"};
1209 
1210         EXPECT_CALL(journal, sync()).Times(1);
1211         EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1212         EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1213         EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1214 
1215         PEL pel{regEntry,  42,
1216                 timestamp, phosphor::logging::Entry::Level::Error,
1217                 ad,        ffdc,
1218                 dataIface, journal};
1219 
1220         // Two more sections than the 1 extra UD section in the first
1221         // testcase
1222         ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1223 
1224         const auto& optionalSections = pel.optionalSections();
1225         auto numOptSections = optionalSections.size();
1226 
1227         checkJournalSection(optionalSections[numOptSections - 3], expected1);
1228         checkJournalSection(optionalSections[numOptSections - 2], expected2);
1229         checkJournalSection(optionalSections[numOptSections - 1], expected3);
1230     }
1231 
1232     {
1233         // One section gets saved, and one is too big and gets dropped
1234         message::AppCaptureList captureList{
1235             message::AppCapture{"app4", 2},
1236             message::AppCapture{"app5", 1},
1237         };
1238         message::JournalCapture jc = captureList;
1239         regEntry.journalCapture = jc;
1240 
1241         std::vector<std::string> app4{"w x", "y z"};
1242         std::string expected4{"w x\ny z\n"};
1243 
1244         EXPECT_CALL(journal, sync()).Times(1);
1245 
1246         EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1247 
1248         // A 20000 byte line won't fit in a PEL
1249         EXPECT_CALL(journal, getMessages("app5", 1))
1250             .WillOnce(
1251                 Return(std::vector<std::string>{std::string(20000, 'x')}));
1252 
1253         PEL pel{regEntry,  42,
1254                 timestamp, phosphor::logging::Entry::Level::Error,
1255                 ad,        ffdc,
1256                 dataIface, journal};
1257 
1258         // The last section should have been dropped, so same as first TC
1259         ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1260 
1261         checkJournalSection(pel.optionalSections().back(), expected4);
1262     }
1263 }
1264 
1265 // API to collect and parse the User Data section of the PEL.
getDIMMInfo(const auto & pel)1266 nlohmann::json getDIMMInfo(const auto& pel)
1267 {
1268     nlohmann::json dimmInfo{};
1269     auto hasDIMMInfo = [&dimmInfo](const auto& optionalSection) {
1270         if (optionalSection->header().id !=
1271             static_cast<uint16_t>(SectionID::userData))
1272         {
1273             return false;
1274         }
1275         else
1276         {
1277             auto userData = static_cast<UserData*>(optionalSection.get());
1278 
1279             // convert the userdata section to string and then parse in to
1280             // json format
1281             std::string userDataString{userData->data().begin(),
1282                                        userData->data().end()};
1283             nlohmann::json userDataJson = nlohmann::json::parse(userDataString);
1284 
1285             if (userDataJson.contains("DIMMs Additional Info"))
1286             {
1287                 dimmInfo = userDataJson.at("DIMMs Additional Info");
1288             }
1289             else if (
1290                 userDataJson.contains(
1291                     "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer))
1292             {
1293                 dimmInfo = userDataJson.at(
1294                     "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer);
1295             }
1296             else
1297             {
1298                 return false;
1299             }
1300             return true;
1301         }
1302     };
1303     std::ranges::any_of(pel.optionalSections(), hasDIMMInfo);
1304 
1305     return dimmInfo;
1306 }
1307 
1308 // Test whether the DIMM callouts manufacturing info is getting added to the
1309 // SysInfo User Data section of the PEL
TEST_F(PELTest,TestDimmsCalloutInfo)1310 TEST_F(PELTest, TestDimmsCalloutInfo)
1311 {
1312     {
1313         message::Entry entry;
1314         uint64_t timestamp = 5;
1315         AdditionalData ad;
1316         NiceMock<MockDataInterface> dataIface;
1317         NiceMock<MockJournal> journal;
1318         PelFFDC ffdc;
1319 
1320         // When callouts contain DIMM callouts.
1321         entry.callouts = R"(
1322         [
1323             {
1324                 "CalloutList": [
1325                     {
1326                         "Priority": "high",
1327                         "LocCode": "P0-DIMM0"
1328                     },
1329                     {
1330                         "Priority": "low",
1331                         "LocCode": "P0-DIMM1"
1332                     }
1333                 ]
1334             }
1335         ]
1336         )"_json;
1337 
1338         EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1339             .WillOnce(Return("U98D-P0-DIMM0"));
1340         EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
1341             .WillOnce(Return("U98D-P0-DIMM1"));
1342 
1343         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1344             .WillOnce(Return(std::vector<std::string>{
1345                 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1346         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
1347             .WillOnce(Return(std::vector<std::string>{
1348                 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
1349 
1350         std::vector<uint8_t> diValue{128, 74};
1351         EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1352             .WillOnce(Return(diValue));
1353         EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM1"))
1354             .WillOnce(Return(diValue));
1355 
1356         // Add some location code in expanded format to DIMM cache memory
1357         dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1358         dataIface.addDIMMLocCode("U98D-P0-DIMM1", true);
1359 
1360         PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1361                 ad,    ffdc, dataIface, journal};
1362         nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1363 
1364         nlohmann::json expected_data = R"(
1365         [
1366             {
1367                 "Location Code": "U98D-P0-DIMM0",
1368                 "DRAM Manufacturer ID": [
1369                     "0x80",
1370                     "0x4a"
1371                 ]
1372             },
1373             {
1374                 "Location Code": "U98D-P0-DIMM1",
1375                 "DRAM Manufacturer ID": [
1376                     "0x80",
1377                     "0x4a"
1378                 ]
1379             }
1380         ]
1381         )"_json;
1382         EXPECT_EQ(expected_data, dimmInfoJson);
1383     }
1384 }
1385 
1386 // When PEL has FRU callouts but PHAL is not enabled.
TEST_F(PELTest,TestNoDimmsCallout)1387 TEST_F(PELTest, TestNoDimmsCallout)
1388 {
1389     message::Entry entry;
1390     uint64_t timestamp = 5;
1391     AdditionalData ad;
1392     NiceMock<MockDataInterface> dataIface;
1393     NiceMock<MockJournal> journal;
1394     PelFFDC ffdc;
1395 
1396     entry.callouts = R"(
1397         [
1398             {
1399                 "CalloutList": [
1400                     {
1401                         "Priority": "high",
1402                         "LocCode": "P0-PROC0"
1403                     }
1404                 ]
1405             }
1406         ]
1407         )"_json;
1408 
1409     EXPECT_CALL(dataIface, expandLocationCode("P0-PROC0", 0))
1410         .WillOnce(Return("U98D-P0-PROC0"));
1411 
1412     EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-PROC0", 0, false))
1413         .WillOnce(Return(std::vector<std::string>{
1414             "/xyz/openbmc_project/inventory/system/chassis/motherboard/dcm0/cpu0"}));
1415 
1416     // Add some location code in expanded format to DIMM cache memory
1417     dataIface.addDIMMLocCode("U98D-P0-PROC0", false);
1418 
1419     PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1420             ad,    ffdc, dataIface, journal};
1421 
1422     nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1423 
1424     nlohmann::json expected_data{};
1425 
1426     EXPECT_EQ(expected_data, dimmInfoJson);
1427 }
1428 
1429 // When the PEL doesn't contain any type of callouts
TEST_F(PELTest,TestDimmsCalloutInfoWithNoCallouts)1430 TEST_F(PELTest, TestDimmsCalloutInfoWithNoCallouts)
1431 {
1432     message::Entry entry;
1433     uint64_t timestamp = 5;
1434     AdditionalData ad;
1435     NiceMock<MockDataInterface> dataIface;
1436     NiceMock<MockJournal> journal;
1437     PelFFDC ffdc;
1438 
1439     PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1440             ad,    ffdc, dataIface, journal};
1441 
1442     nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1443 
1444     nlohmann::json expected_data{};
1445 
1446     EXPECT_EQ(expected_data, dimmInfoJson);
1447 }
1448 
1449 // When the PEL has DIMM callouts, but failed to fetch DI property value
TEST_F(PELTest,TestDimmsCalloutInfoDIFailure)1450 TEST_F(PELTest, TestDimmsCalloutInfoDIFailure)
1451 {
1452     {
1453         message::Entry entry;
1454         uint64_t timestamp = 5;
1455         AdditionalData ad;
1456         NiceMock<MockDataInterface> dataIface;
1457         NiceMock<MockJournal> journal;
1458         PelFFDC ffdc;
1459 
1460         entry.callouts = R"(
1461         [
1462             {
1463                 "CalloutList": [
1464                     {
1465                         "Priority": "high",
1466                         "LocCode": "P0-DIMM0"
1467                     }
1468                 ]
1469             }
1470         ]
1471         )"_json;
1472 
1473         EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1474             .WillOnce(Return("U98D-P0-DIMM0"));
1475 
1476         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1477             .WillOnce(Return(std::vector<std::string>{
1478                 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1479 
1480         EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1481             .WillOnce(Return(std::nullopt));
1482 
1483         // Add some location code in expanded format to DIMM cache memory
1484         dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1485 
1486         PEL pel{entry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1487                 ad,    ffdc, dataIface, journal};
1488 
1489         nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1490 
1491         nlohmann::json expected_data = R"(
1492             [
1493                 "Failed reading DI property from VINI Interface for the LocationCode:[U98D-P0-DIMM0]"
1494             ]
1495         )"_json;
1496 
1497         EXPECT_EQ(expected_data, dimmInfoJson);
1498     }
1499 }
1500