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