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 
39 fs::path makeTempDir()
40 {
41     char path[] = "/tmp/tempdirXXXXXX";
42     std::filesystem::path dir = mkdtemp(path);
43     return dir;
44 }
45 
46 int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data)
47 {
48     static size_t count = 0;
49     fs::path path = dir / (std::string{"file"} + std::to_string(count));
50     std::ofstream stream{path};
51     count++;
52 
53     stream.write(reinterpret_cast<const char*>(data.data()), data.size());
54     stream.close();
55 
56     FILE* fp = fopen(path.c_str(), "r");
57     return fileno(fp);
58 }
59 
60 TEST_F(PELTest, FlattenTest)
61 {
62     auto data = pelDataFactory(TestPELType::pelSimple);
63     auto pel = std::make_unique<PEL>(data);
64 
65     // Check a few fields
66     EXPECT_TRUE(pel->valid());
67     EXPECT_EQ(pel->id(), 0x80818283);
68     EXPECT_EQ(pel->plid(), 0x50515253);
69     EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
70     EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
71 
72     // Test that data in == data out
73     auto flattenedData = pel->data();
74     EXPECT_EQ(data, flattenedData);
75     EXPECT_EQ(flattenedData.size(), pel->size());
76 }
77 
78 TEST_F(PELTest, CommitTimeTest)
79 {
80     auto data = pelDataFactory(TestPELType::pelSimple);
81     auto pel = std::make_unique<PEL>(data);
82 
83     auto origTime = pel->commitTime();
84     pel->setCommitTime();
85     auto newTime = pel->commitTime();
86 
87     EXPECT_NE(origTime, newTime);
88 
89     // Make a new PEL and check new value is still there
90     auto newData = pel->data();
91     auto newPel = std::make_unique<PEL>(newData);
92     EXPECT_EQ(newTime, newPel->commitTime());
93 }
94 
95 TEST_F(PELTest, AssignIDTest)
96 {
97     auto data = pelDataFactory(TestPELType::pelSimple);
98     auto pel = std::make_unique<PEL>(data);
99 
100     auto origID = pel->id();
101     pel->assignID();
102     auto newID = pel->id();
103 
104     EXPECT_NE(origID, newID);
105 
106     // Make a new PEL and check new value is still there
107     auto newData = pel->data();
108     auto newPel = std::make_unique<PEL>(newData);
109     EXPECT_EQ(newID, newPel->id());
110 }
111 
112 TEST_F(PELTest, WithLogIDTest)
113 {
114     auto data = pelDataFactory(TestPELType::pelSimple);
115     auto pel = std::make_unique<PEL>(data, 0x42);
116 
117     EXPECT_TRUE(pel->valid());
118     EXPECT_EQ(pel->obmcLogID(), 0x42);
119 }
120 
121 TEST_F(PELTest, InvalidPELTest)
122 {
123     auto data = pelDataFactory(TestPELType::pelSimple);
124 
125     // Too small
126     data.resize(PrivateHeader::flattenedSize());
127 
128     auto pel = std::make_unique<PEL>(data);
129 
130     EXPECT_TRUE(pel->privateHeader().valid());
131     EXPECT_FALSE(pel->userHeader().valid());
132     EXPECT_FALSE(pel->valid());
133 
134     // Now corrupt the private header
135     data = pelDataFactory(TestPELType::pelSimple);
136     data.at(0) = 0;
137     pel = std::make_unique<PEL>(data);
138 
139     EXPECT_FALSE(pel->privateHeader().valid());
140     EXPECT_TRUE(pel->userHeader().valid());
141     EXPECT_FALSE(pel->valid());
142 }
143 
144 TEST_F(PELTest, EmptyDataTest)
145 {
146     std::vector<uint8_t> data;
147     auto pel = std::make_unique<PEL>(data);
148 
149     EXPECT_FALSE(pel->privateHeader().valid());
150     EXPECT_FALSE(pel->userHeader().valid());
151     EXPECT_FALSE(pel->valid());
152 }
153 
154 TEST_F(PELTest, CreateFromRegistryTest)
155 {
156     message::Entry regEntry;
157     uint64_t timestamp = 5;
158 
159     regEntry.name = "test";
160     regEntry.subsystem = 5;
161     regEntry.actionFlags = 0xC000;
162     regEntry.src.type = 0xBD;
163     regEntry.src.reasonCode = 0x1234;
164 
165     std::vector<std::string> data{"KEY1=VALUE1"};
166     AdditionalData ad{data};
167     NiceMock<MockDataInterface> dataIface;
168     PelFFDC ffdc;
169 
170     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
171             ad,       ffdc, dataIface};
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.isCalloutPresent());
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{
215             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
216             ad,       ffdc, dataIface};
217 
218         EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
219     }
220 }
221 
222 // Test that when the AdditionalData size is over 16KB that
223 // the PEL that's created is exactly 16KB since the UserData
224 // section that contains all that data was pruned.
225 TEST_F(PELTest, CreateTooBigADTest)
226 {
227     message::Entry regEntry;
228     uint64_t timestamp = 5;
229 
230     regEntry.name = "test";
231     regEntry.subsystem = 5;
232     regEntry.actionFlags = 0xC000;
233     regEntry.src.type = 0xBD;
234     regEntry.src.reasonCode = 0x1234;
235     PelFFDC ffdc;
236 
237     // Over the 16KB max PEL size
238     std::string bigAD{"KEY1="};
239     bigAD += std::string(17000, 'G');
240 
241     std::vector<std::string> data{bigAD};
242     AdditionalData ad{data};
243     NiceMock<MockDataInterface> dataIface;
244 
245     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
246             ad,       ffdc, dataIface};
247 
248     EXPECT_TRUE(pel.valid());
249     EXPECT_EQ(pel.size(), 16384);
250 
251     // Make sure that there are still 2 UD sections.
252     size_t udCount = 0;
253     for (const auto& section : pel.optionalSections())
254     {
255         if (section->header().id == static_cast<uint16_t>(SectionID::userData))
256         {
257             udCount++;
258         }
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{0x58, 0x58, // ID 'XX'
271                                   0x00, 0x18, // Size
272                                   0x01, 0x02, // version, subtype
273                                   0x03, 0x04, // comp ID
274 
275                                   // some data
276                                   0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,
277                                   0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
278                                   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, getSystemIMKeyword())
407         .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
408 
409     std::string pid = "_PID=" + std::to_string(getpid());
410     std::vector<std::string> ad{pid};
411     AdditionalData additionalData{ad};
412 
413     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
414 
415     EXPECT_TRUE(ud->valid());
416     EXPECT_EQ(ud->header().id, 0x5544);
417     EXPECT_EQ(ud->header().version, 0x01);
418     EXPECT_EQ(ud->header().subType, 0x01);
419     EXPECT_EQ(ud->header().componentID, 0x2000);
420 
421     // Pull out the JSON data and check it.
422     const auto& d = ud->data();
423     std::string jsonString{d.begin(), d.end()};
424     auto json = nlohmann::json::parse(jsonString);
425 
426     // Ensure the 'Process Name' entry contains the name of this test
427     // executable.
428     auto name = json["Process Name"].get<std::string>();
429     auto found = (name.find("pel_test") != std::string::npos) ||
430                  (name.find("test-openpower-pels-pel") != std::string::npos);
431     EXPECT_TRUE(found);
432     // @TODO(stwcx): remove 'pel_test' when removing autotools.
433 
434     auto version = json["FW Version ID"].get<std::string>();
435     EXPECT_EQ(version, "ABCD1234");
436 
437     auto state = json["BMCState"].get<std::string>();
438     EXPECT_EQ(state, "Ready");
439 
440     state = json["ChassisState"].get<std::string>();
441     EXPECT_EQ(state, "On");
442 
443     state = json["HostState"].get<std::string>();
444     EXPECT_EQ(state, "Off");
445 
446     auto keyword = json["System IM"].get<std::string>();
447     EXPECT_EQ(keyword, "000155AA");
448 }
449 
450 // Test that the sections that override
451 //     virtual std::optional<std::string> Section::getJSON() const
452 // return valid JSON.
453 TEST_F(PELTest, SectionJSONTest)
454 {
455     auto data = pelDataFactory(TestPELType::pelSimple);
456     PEL pel{data};
457 
458     // Check that all JSON returned from the sections is
459     // parseable by nlohmann::json, which will throw an
460     // exception and fail the test if there is a problem.
461 
462     // The getJSON() response needs to be wrapped in a { } to make
463     // actual valid JSON (PEL::toJSON() usually handles that).
464 
465     auto jsonString = pel.privateHeader().getJSON();
466 
467     // PrivateHeader always prints JSON
468     ASSERT_TRUE(jsonString);
469     *jsonString = '{' + *jsonString + '}';
470     auto json = nlohmann::json::parse(*jsonString);
471 
472     jsonString = pel.userHeader().getJSON();
473 
474     // UserHeader always prints JSON
475     ASSERT_TRUE(jsonString);
476     *jsonString = '{' + *jsonString + '}';
477     json = nlohmann::json::parse(*jsonString);
478 
479     for (const auto& section : pel.optionalSections())
480     {
481         // The optional sections may or may not have implemented getJSON().
482         jsonString = section->getJSON();
483         if (jsonString)
484         {
485             *jsonString = '{' + *jsonString + '}';
486             auto json = nlohmann::json::parse(*jsonString);
487         }
488     }
489 }
490 
491 PelFFDCfile getJSONFFDC(const fs::path& dir)
492 {
493     PelFFDCfile ffdc;
494     ffdc.format = UserDataFormat::json;
495     ffdc.subType = 5;
496     ffdc.version = 42;
497 
498     auto inputJSON = R"({
499         "key1": "value1",
500         "key2": 42,
501         "key3" : [1, 2, 3, 4, 5],
502         "key4": {"key5": "value5"}
503     })"_json;
504 
505     // Write the JSON to a file and get its descriptor.
506     auto s = inputJSON.dump();
507     std::vector<uint8_t> data{s.begin(), s.end()};
508     ffdc.fd = writeFileAndGetFD(dir, data);
509 
510     return ffdc;
511 }
512 
513 TEST_F(PELTest, MakeJSONFileUDSectionTest)
514 {
515     auto dir = makeTempDir();
516 
517     {
518         auto ffdc = getJSONFFDC(dir);
519 
520         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
521         close(ffdc.fd);
522         ASSERT_TRUE(ud);
523         ASSERT_TRUE(ud->valid());
524         EXPECT_EQ(ud->header().id, 0x5544);
525 
526         EXPECT_EQ(ud->header().version,
527                   static_cast<uint8_t>(UserDataFormatVersion::json));
528         EXPECT_EQ(ud->header().subType,
529                   static_cast<uint8_t>(UserDataFormat::json));
530         EXPECT_EQ(ud->header().componentID,
531                   static_cast<uint16_t>(ComponentID::phosphorLogging));
532 
533         // Pull the JSON back out of the the UserData section
534         const auto& d = ud->data();
535         std::string js{d.begin(), d.end()};
536         auto json = nlohmann::json::parse(js);
537 
538         EXPECT_EQ("value1", json["key1"].get<std::string>());
539         EXPECT_EQ(42, json["key2"].get<int>());
540 
541         std::vector<int> key3Values{1, 2, 3, 4, 5};
542         EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
543 
544         std::map<std::string, std::string> key4Values{{"key5", "value5"}};
545         auto actual = json["key4"].get<std::map<std::string, std::string>>();
546         EXPECT_EQ(key4Values, actual);
547     }
548 
549     {
550         // A bad FD
551         PelFFDCfile ffdc;
552         ffdc.format = UserDataFormat::json;
553         ffdc.subType = 5;
554         ffdc.version = 42;
555         ffdc.fd = 10000;
556 
557         // The section shouldn't get made
558         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
559         ASSERT_FALSE(ud);
560     }
561 
562     fs::remove_all(dir);
563 }
564 
565 PelFFDCfile getCBORFFDC(const fs::path& dir)
566 {
567     PelFFDCfile ffdc;
568     ffdc.format = UserDataFormat::cbor;
569     ffdc.subType = 5;
570     ffdc.version = 42;
571 
572     auto inputJSON = R"({
573         "key1": "value1",
574         "key2": 42,
575         "key3" : [1, 2, 3, 4, 5],
576         "key4": {"key5": "value5"}
577     })"_json;
578 
579     // Convert the JSON to CBOR and write it to a file
580     auto data = nlohmann::json::to_cbor(inputJSON);
581     ffdc.fd = writeFileAndGetFD(dir, data);
582 
583     return ffdc;
584 }
585 
586 TEST_F(PELTest, MakeCBORFileUDSectionTest)
587 {
588     auto dir = makeTempDir();
589 
590     auto ffdc = getCBORFFDC(dir);
591     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
592     close(ffdc.fd);
593     ASSERT_TRUE(ud);
594     ASSERT_TRUE(ud->valid());
595     EXPECT_EQ(ud->header().id, 0x5544);
596 
597     EXPECT_EQ(ud->header().version,
598               static_cast<uint8_t>(UserDataFormatVersion::cbor));
599     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
600     EXPECT_EQ(ud->header().componentID,
601               static_cast<uint16_t>(ComponentID::phosphorLogging));
602 
603     // Pull the CBOR back out of the PEL section
604     // The number of pad bytes to make the section be 4B aligned
605     // was added at the end, read it and then remove it and the
606     // padding before parsing it.
607     auto data = ud->data();
608     Stream stream{data};
609     stream.offset(data.size() - 4);
610     uint32_t pad;
611     stream >> pad;
612 
613     data.resize(data.size() - 4 - pad);
614 
615     auto json = nlohmann::json::from_cbor(data);
616 
617     EXPECT_EQ("value1", json["key1"].get<std::string>());
618     EXPECT_EQ(42, json["key2"].get<int>());
619 
620     std::vector<int> key3Values{1, 2, 3, 4, 5};
621     EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
622 
623     std::map<std::string, std::string> key4Values{{"key5", "value5"}};
624     auto actual = json["key4"].get<std::map<std::string, std::string>>();
625     EXPECT_EQ(key4Values, actual);
626 
627     fs::remove_all(dir);
628 }
629 
630 PelFFDCfile getTextFFDC(const fs::path& dir)
631 {
632     PelFFDCfile ffdc;
633     ffdc.format = UserDataFormat::text;
634     ffdc.subType = 5;
635     ffdc.version = 42;
636 
637     std::string text{"this is some text that will be used for FFDC"};
638     std::vector<uint8_t> data{text.begin(), text.end()};
639 
640     ffdc.fd = writeFileAndGetFD(dir, data);
641 
642     return ffdc;
643 }
644 
645 TEST_F(PELTest, MakeTextFileUDSectionTest)
646 {
647     auto dir = makeTempDir();
648 
649     auto ffdc = getTextFFDC(dir);
650     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
651     close(ffdc.fd);
652     ASSERT_TRUE(ud);
653     ASSERT_TRUE(ud->valid());
654     EXPECT_EQ(ud->header().id, 0x5544);
655 
656     EXPECT_EQ(ud->header().version,
657               static_cast<uint8_t>(UserDataFormatVersion::text));
658     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
659     EXPECT_EQ(ud->header().componentID,
660               static_cast<uint16_t>(ComponentID::phosphorLogging));
661 
662     // Get the text back out
663     std::string text{ud->data().begin(), ud->data().end()};
664     EXPECT_EQ(text, "this is some text that will be used for FFDC");
665 
666     fs::remove_all(dir);
667 }
668 
669 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
670 {
671     PelFFDCfile ffdc;
672     ffdc.format = UserDataFormat::custom;
673     ffdc.subType = 5;
674     ffdc.version = 42;
675 
676     ffdc.fd = writeFileAndGetFD(dir, data);
677 
678     return ffdc;
679 }
680 
681 TEST_F(PELTest, MakeCustomFileUDSectionTest)
682 {
683     auto dir = makeTempDir();
684 
685     {
686         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
687 
688         auto ffdc = getCustomFFDC(dir, data);
689         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
690         close(ffdc.fd);
691         ASSERT_TRUE(ud);
692         ASSERT_TRUE(ud->valid());
693         EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
694         EXPECT_EQ(ud->header().id, 0x5544);
695 
696         EXPECT_EQ(ud->header().version, 42);
697         EXPECT_EQ(ud->header().subType, 5);
698         EXPECT_EQ(ud->header().componentID, 0x2002);
699 
700         // Get the data back out
701         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
702         EXPECT_EQ(data, newData);
703     }
704 
705     // Do the same thing again, but make it be non 4B aligned
706     // so the data gets padded.
707     {
708         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
709 
710         auto ffdc = getCustomFFDC(dir, data);
711         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
712         close(ffdc.fd);
713         ASSERT_TRUE(ud);
714         ASSERT_TRUE(ud->valid());
715         EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
716         EXPECT_EQ(ud->header().id, 0x5544);
717 
718         EXPECT_EQ(ud->header().version, 42);
719         EXPECT_EQ(ud->header().subType, 5);
720         EXPECT_EQ(ud->header().componentID, 0x2002);
721 
722         // Get the data back out
723         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
724 
725         // pad the original to 12B so we can compare
726         data.push_back(0);
727         data.push_back(0);
728         data.push_back(0);
729 
730         EXPECT_EQ(data, newData);
731     }
732 
733     fs::remove_all(dir);
734 }
735 
736 // Test Adding FFDC from files to a PEL
737 TEST_F(PELTest, CreateWithFFDCTest)
738 {
739     auto dir = makeTempDir();
740     message::Entry regEntry;
741     uint64_t timestamp = 5;
742 
743     regEntry.name = "test";
744     regEntry.subsystem = 5;
745     regEntry.actionFlags = 0xC000;
746     regEntry.src.type = 0xBD;
747     regEntry.src.reasonCode = 0x1234;
748 
749     std::vector<std::string> additionalData{"KEY1=VALUE1"};
750     AdditionalData ad{additionalData};
751     NiceMock<MockDataInterface> dataIface;
752     PelFFDC ffdc;
753 
754     std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
755 
756     // This will be trimmed when added
757     std::vector<uint8_t> hugeCustomData(17000, 0x42);
758 
759     ffdc.emplace_back(std::move(getJSONFFDC(dir)));
760     ffdc.emplace_back(std::move(getCBORFFDC(dir)));
761     ffdc.emplace_back(std::move(getTextFFDC(dir)));
762     ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
763     ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
764 
765     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
766             ad,       ffdc, dataIface};
767 
768     EXPECT_TRUE(pel.valid());
769 
770     // Clipped to the max
771     EXPECT_EQ(pel.size(), 16384);
772 
773     // Check for the FFDC sections
774     size_t udCount = 0;
775     Section* ud = nullptr;
776 
777     for (const auto& section : pel.optionalSections())
778     {
779         if (section->header().id == static_cast<uint16_t>(SectionID::userData))
780         {
781             udCount++;
782             ud = section.get();
783         }
784     }
785 
786     EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
787 
788     // Check the last section was trimmed to
789     // something a bit less that 17000.
790     EXPECT_GT(ud->header().size, 14000);
791     EXPECT_LT(ud->header().size, 16000);
792 
793     fs::remove_all(dir);
794 }
795 
796 // Create a PEL with device callouts
797 TEST_F(PELTest, CreateWithDevCalloutsTest)
798 {
799     message::Entry regEntry;
800     uint64_t timestamp = 5;
801 
802     regEntry.name = "test";
803     regEntry.subsystem = 5;
804     regEntry.actionFlags = 0xC000;
805     regEntry.src.type = 0xBD;
806     regEntry.src.reasonCode = 0x1234;
807 
808     NiceMock<MockDataInterface> dataIface;
809     PelFFDC ffdc;
810 
811     const auto calloutJSON = R"(
812     {
813         "I2C":
814         {
815             "14":
816             {
817                 "114":
818                 {
819                     "Callouts":[
820                         {
821                         "Name": "/chassis/motherboard/cpu0",
822                         "LocationCode": "P1",
823                         "Priority": "H"
824                         }
825                     ],
826                     "Dest": "proc 0 target"
827                 }
828             }
829         }
830     })";
831 
832     std::vector<std::string> names{"systemA"};
833     EXPECT_CALL(dataIface, getSystemNames)
834         .Times(2)
835         .WillRepeatedly(Return(names));
836 
837     EXPECT_CALL(dataIface,
838                 getLocationCode(
839                     "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"))
840         .WillOnce(Return("UXXX-P1"));
841 
842     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
843         .WillOnce(
844             Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
845 
846     EXPECT_CALL(
847         dataIface,
848         getHWCalloutFields(
849             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
850         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
851                         SetArgReferee<3>("123456789ABC")));
852 
853     auto dataPath = getPELReadOnlyDataPath();
854     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
855     file << calloutJSON;
856     file.close();
857 
858     {
859         std::vector<std::string> data{
860             "CALLOUT_ERRNO=5",
861             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
862             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
863 
864         AdditionalData ad{data};
865 
866         PEL pel{
867             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
868             ad,       ffdc, dataIface};
869 
870         ASSERT_TRUE(pel.primarySRC().value()->callouts());
871         auto& callouts = pel.primarySRC().value()->callouts()->callouts();
872         ASSERT_EQ(callouts.size(), 1);
873         ASSERT_TRUE(pel.isCalloutPresent());
874 
875         EXPECT_EQ(callouts[0]->priority(), 'H');
876         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
877 
878         auto& fru = callouts[0]->fruIdentity();
879         EXPECT_EQ(fru->getPN().value(), "1234567");
880         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
881         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
882 
883         const auto& section = pel.optionalSections().back();
884 
885         ASSERT_EQ(section->header().id, 0x5544); // UD
886         auto ud = static_cast<UserData*>(section.get());
887 
888         // Check that there was a UserData section added that
889         // contains debug details about the device.
890         const auto& d = ud->data();
891         std::string jsonString{d.begin(), d.end()};
892         auto actualJSON = nlohmann::json::parse(jsonString);
893 
894         auto expectedJSON = R"(
895             {
896                 "PEL Internal Debug Data": {
897                     "SRC": [
898                       "I2C: bus: 14 address: 114 dest: proc 0 target"
899                     ]
900                 }
901             }
902         )"_json;
903 
904         EXPECT_EQ(actualJSON, expectedJSON);
905     }
906 
907     {
908         // Device path not found (wrong i2c addr), so no callouts
909         std::vector<std::string> data{
910             "CALLOUT_ERRNO=5",
911             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
912             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
913 
914         AdditionalData ad{data};
915 
916         PEL pel{
917             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
918             ad,       ffdc, dataIface};
919 
920         // no callouts
921         EXPECT_FALSE(pel.primarySRC().value()->callouts());
922 
923         // Now check that there was a UserData section
924         // that contains the lookup error.
925         const auto& section = pel.optionalSections().back();
926 
927         ASSERT_EQ(section->header().id, 0x5544); // UD
928         auto ud = static_cast<UserData*>(section.get());
929 
930         const auto& d = ud->data();
931 
932         std::string jsonString{d.begin(), d.end()};
933 
934         auto actualJSON = nlohmann::json::parse(jsonString);
935 
936         auto expectedJSON =
937             "{\"PEL Internal Debug Data\":{\"SRC\":"
938             "[\"Problem looking up I2C callouts on 14 153: "
939             "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
940 
941         EXPECT_EQ(actualJSON, expectedJSON);
942     }
943 
944     fs::remove_all(dataPath);
945 }
946 
947 // Test PELs when the callouts are passed in using a JSON file.
948 TEST_F(PELTest, CreateWithJSONCalloutsTest)
949 {
950     PelFFDCfile ffdcFile;
951     ffdcFile.format = UserDataFormat::json;
952     ffdcFile.subType = 0xCA; // Callout JSON
953     ffdcFile.version = 1;
954 
955     // Write these callouts to a JSON file and pass it into
956     // the PEL as an FFDC file.
957     auto inputJSON = R"([
958         {
959             "Priority": "H",
960             "LocationCode": "P0-C1"
961         },
962         {
963             "Priority": "M",
964             "Procedure": "PROCEDURE"
965         }
966     ])"_json;
967 
968     auto s = inputJSON.dump();
969     std::vector<uint8_t> data{s.begin(), s.end()};
970     auto dir = makeTempDir();
971     ffdcFile.fd = writeFileAndGetFD(dir, data);
972 
973     PelFFDC ffdc;
974     ffdc.push_back(std::move(ffdcFile));
975 
976     AdditionalData ad;
977     NiceMock<MockDataInterface> dataIface;
978 
979     EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
980         .Times(1)
981         .WillOnce(Return("UXXX-P0-C1"));
982     EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
983         .Times(1)
984         .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
985     EXPECT_CALL(dataIface, getHWCalloutFields(
986                                "/inv/system/chassis/motherboard/bmc", _, _, _))
987         .Times(1)
988         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
989                         SetArgReferee<3>("123456789ABC")));
990 
991     message::Entry regEntry;
992     regEntry.name = "test";
993     regEntry.subsystem = 5;
994     regEntry.actionFlags = 0xC000;
995     regEntry.src.type = 0xBD;
996     regEntry.src.reasonCode = 0x1234;
997 
998     PEL pel{regEntry, 42,   5,        phosphor::logging::Entry::Level::Error,
999             ad,       ffdc, dataIface};
1000 
1001     ASSERT_TRUE(pel.valid());
1002     ASSERT_TRUE(pel.primarySRC().value()->callouts());
1003     const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1004     ASSERT_EQ(callouts.size(), 2);
1005 
1006     {
1007         EXPECT_EQ(callouts[0]->priority(), 'H');
1008         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1009 
1010         auto& fru = callouts[0]->fruIdentity();
1011         EXPECT_EQ(fru->getPN().value(), "1234567");
1012         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1013         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1014         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1015     }
1016     {
1017         EXPECT_EQ(callouts[1]->priority(), 'M');
1018         EXPECT_EQ(callouts[1]->locationCode(), "");
1019 
1020         auto& fru = callouts[1]->fruIdentity();
1021         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1022         EXPECT_EQ(fru->failingComponentType(),
1023                   src::FRUIdentity::maintenanceProc);
1024     }
1025     fs::remove_all(dir);
1026 }
1027