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