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