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::NiceMock;
31 using ::testing::Return;
32 using ::testing::SetArgReferee;
33 
34 class PELTest : public CleanLogID
35 {
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     PelFFDC ffdc;
168 
169     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
170             ad,       ffdc, dataIface};
171 
172     EXPECT_TRUE(pel.valid());
173     EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
174     EXPECT_EQ(pel.userHeader().severity(), 0x40);
175 
176     EXPECT_EQ(pel.primarySRC().value()->asciiString(),
177               "BD051234                        ");
178 
179     // Check that certain optional sections have been created
180     size_t mtmsCount = 0;
181     size_t euhCount = 0;
182     size_t udCount = 0;
183 
184     for (const auto& section : pel.optionalSections())
185     {
186         if (section->header().id ==
187             static_cast<uint16_t>(SectionID::failingMTMS))
188         {
189             mtmsCount++;
190         }
191         else if (section->header().id ==
192                  static_cast<uint16_t>(SectionID::extendedUserHeader))
193         {
194             euhCount++;
195         }
196         else if (section->header().id ==
197                  static_cast<uint16_t>(SectionID::userData))
198         {
199             udCount++;
200         }
201     }
202 
203     EXPECT_EQ(mtmsCount, 1);
204     EXPECT_EQ(euhCount, 1);
205     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
206     ASSERT_FALSE(pel.isCalloutPresent());
207 
208     {
209         // The same thing, but without the action flags specified
210         // in the registry, so the constructor should set them.
211         regEntry.actionFlags = std::nullopt;
212 
213         PEL pel2{
214             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
215             ad,       ffdc, dataIface};
216 
217         EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
218     }
219 }
220 
221 // Test that when the AdditionalData size is over 16KB that
222 // the PEL that's created is exactly 16KB since the UserData
223 // section that contains all that data was pruned.
224 TEST_F(PELTest, CreateTooBigADTest)
225 {
226     message::Entry regEntry;
227     uint64_t timestamp = 5;
228 
229     regEntry.name = "test";
230     regEntry.subsystem = 5;
231     regEntry.actionFlags = 0xC000;
232     regEntry.src.type = 0xBD;
233     regEntry.src.reasonCode = 0x1234;
234     PelFFDC ffdc;
235 
236     // Over the 16KB max PEL size
237     std::string bigAD{"KEY1="};
238     bigAD += std::string(17000, 'G');
239 
240     std::vector<std::string> data{bigAD};
241     AdditionalData ad{data};
242     NiceMock<MockDataInterface> dataIface;
243 
244     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
245             ad,       ffdc, dataIface};
246 
247     EXPECT_TRUE(pel.valid());
248     EXPECT_EQ(pel.size(), 16384);
249 
250     // Make sure that there are still 2 UD sections.
251     size_t udCount = 0;
252     for (const auto& section : pel.optionalSections())
253     {
254         if (section->header().id == static_cast<uint16_t>(SectionID::userData))
255         {
256             udCount++;
257         }
258     }
259 
260     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
261 }
262 
263 // Test that we'll create Generic optional sections for sections that
264 // there aren't explicit classes for.
265 TEST_F(PELTest, GenericSectionTest)
266 {
267     auto data = pelDataFactory(TestPELType::pelSimple);
268 
269     std::vector<uint8_t> section1{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,
276                                   0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
277                                   0x00};
278 
279     std::vector<uint8_t> section2{
280         0x59, 0x59, // ID 'YY'
281         0x00, 0x20, // Size
282         0x01, 0x02, // version, subtype
283         0x03, 0x04, // comp ID
284 
285         // some data
286         0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
287         0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
288 
289     // Add the new sections at the end
290     data.insert(data.end(), section1.begin(), section1.end());
291     data.insert(data.end(), section2.begin(), section2.end());
292 
293     // Increment the section count
294     data.at(27) += 2;
295     auto origData = data;
296 
297     PEL pel{data};
298 
299     const auto& sections = pel.optionalSections();
300 
301     bool foundXX = false;
302     bool foundYY = false;
303 
304     // Check that we can find these 2 Generic sections
305     for (const auto& section : sections)
306     {
307         if (section->header().id == 0x5858)
308         {
309             foundXX = true;
310             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
311         }
312         else if (section->header().id == 0x5959)
313         {
314             foundYY = true;
315             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
316         }
317     }
318 
319     EXPECT_TRUE(foundXX);
320     EXPECT_TRUE(foundYY);
321 
322     // Now flatten and check
323     auto newData = pel.data();
324 
325     EXPECT_EQ(origData, newData);
326 }
327 
328 // Test that an invalid section will still get a Generic object
329 TEST_F(PELTest, InvalidGenericTest)
330 {
331     auto data = pelDataFactory(TestPELType::pelSimple);
332 
333     // Not a valid section
334     std::vector<uint8_t> section1{0x01, 0x02, 0x03};
335 
336     data.insert(data.end(), section1.begin(), section1.end());
337 
338     // Increment the section count
339     data.at(27) += 1;
340 
341     PEL pel{data};
342     EXPECT_FALSE(pel.valid());
343 
344     const auto& sections = pel.optionalSections();
345 
346     bool foundGeneric = false;
347     for (const auto& section : sections)
348     {
349         if (dynamic_cast<Generic*>(section.get()) != nullptr)
350         {
351             foundGeneric = true;
352             EXPECT_EQ(section->valid(), false);
353             break;
354         }
355     }
356 
357     EXPECT_TRUE(foundGeneric);
358 }
359 
360 // Create a UserData section out of AdditionalData
361 TEST_F(PELTest, MakeUDSectionTest)
362 {
363     std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
364                                 "ESEL=TEST"};
365     AdditionalData additionalData{ad};
366 
367     auto ud = util::makeADUserDataSection(additionalData);
368 
369     EXPECT_TRUE(ud->valid());
370     EXPECT_EQ(ud->header().id, 0x5544);
371     EXPECT_EQ(ud->header().version, 0x01);
372     EXPECT_EQ(ud->header().subType, 0x01);
373     EXPECT_EQ(ud->header().componentID, 0x2000);
374 
375     const auto& d = ud->data();
376 
377     std::string jsonString{d.begin(), d.end()};
378 
379     std::string expectedJSON =
380         R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
381 
382     // The actual data is null padded to a 4B boundary.
383     std::vector<uint8_t> expectedData;
384     expectedData.resize(52, '\0');
385     memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
386 
387     EXPECT_EQ(d, expectedData);
388 
389     // Ensure we can read this as JSON
390     auto newJSON = nlohmann::json::parse(jsonString);
391     EXPECT_EQ(newJSON["KEY1"], "VALUE1");
392     EXPECT_EQ(newJSON["KEY2"], "VALUE2");
393     EXPECT_EQ(newJSON["KEY3"], "VALUE3");
394 }
395 
396 // Create the UserData section that contains system info
397 TEST_F(PELTest, SysInfoSectionTest)
398 {
399     MockDataInterface dataIface;
400 
401     EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
402     EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
403     EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
404     EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
405 
406     std::string pid = "_PID=" + std::to_string(getpid());
407     std::vector<std::string> ad{pid};
408     AdditionalData additionalData{ad};
409 
410     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
411 
412     EXPECT_TRUE(ud->valid());
413     EXPECT_EQ(ud->header().id, 0x5544);
414     EXPECT_EQ(ud->header().version, 0x01);
415     EXPECT_EQ(ud->header().subType, 0x01);
416     EXPECT_EQ(ud->header().componentID, 0x2000);
417 
418     // Pull out the JSON data and check it.
419     const auto& d = ud->data();
420     std::string jsonString{d.begin(), d.end()};
421     auto json = nlohmann::json::parse(jsonString);
422 
423     // Ensure the 'Process Name' entry contains the name of this test
424     // executable.
425     auto name = json["Process Name"].get<std::string>();
426     auto found = (name.find("pel_test") != std::string::npos) ||
427                  (name.find("test-openpower-pels-pel") != std::string::npos);
428     EXPECT_TRUE(found);
429     // @TODO(stwcx): remove 'pel_test' when removing autotools.
430 
431     auto version = json["BMC Version ID"].get<std::string>();
432     EXPECT_EQ(version, "ABCD1234");
433 
434     auto state = json["BMCState"].get<std::string>();
435     EXPECT_EQ(state, "Ready");
436 
437     state = json["ChassisState"].get<std::string>();
438     EXPECT_EQ(state, "On");
439 
440     state = json["HostState"].get<std::string>();
441     EXPECT_EQ(state, "Off");
442 }
443 
444 // Test that the sections that override
445 //     virtual std::optional<std::string> Section::getJSON() const
446 // return valid JSON.
447 TEST_F(PELTest, SectionJSONTest)
448 {
449     auto data = pelDataFactory(TestPELType::pelSimple);
450     PEL pel{data};
451 
452     // Check that all JSON returned from the sections is
453     // parseable by nlohmann::json, which will throw an
454     // exception and fail the test if there is a problem.
455 
456     // The getJSON() response needs to be wrapped in a { } to make
457     // actual valid JSON (PEL::toJSON() usually handles that).
458 
459     auto jsonString = pel.privateHeader().getJSON();
460 
461     // PrivateHeader always prints JSON
462     ASSERT_TRUE(jsonString);
463     *jsonString = '{' + *jsonString + '}';
464     auto json = nlohmann::json::parse(*jsonString);
465 
466     jsonString = pel.userHeader().getJSON();
467 
468     // UserHeader always prints JSON
469     ASSERT_TRUE(jsonString);
470     *jsonString = '{' + *jsonString + '}';
471     json = nlohmann::json::parse(*jsonString);
472 
473     for (const auto& section : pel.optionalSections())
474     {
475         // The optional sections may or may not have implemented getJSON().
476         jsonString = section->getJSON();
477         if (jsonString)
478         {
479             *jsonString = '{' + *jsonString + '}';
480             auto json = nlohmann::json::parse(*jsonString);
481         }
482     }
483 }
484 
485 PelFFDCfile getJSONFFDC(const fs::path& dir)
486 {
487     PelFFDCfile ffdc;
488     ffdc.format = UserDataFormat::json;
489     ffdc.subType = 5;
490     ffdc.version = 42;
491 
492     auto inputJSON = R"({
493         "key1": "value1",
494         "key2": 42,
495         "key3" : [1, 2, 3, 4, 5],
496         "key4": {"key5": "value5"}
497     })"_json;
498 
499     // Write the JSON to a file and get its descriptor.
500     auto s = inputJSON.dump();
501     std::vector<uint8_t> data{s.begin(), s.end()};
502     ffdc.fd = writeFileAndGetFD(dir, data);
503 
504     return ffdc;
505 }
506 
507 TEST_F(PELTest, MakeJSONFileUDSectionTest)
508 {
509     auto dir = makeTempDir();
510 
511     {
512         auto ffdc = getJSONFFDC(dir);
513 
514         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
515         close(ffdc.fd);
516         ASSERT_TRUE(ud);
517         ASSERT_TRUE(ud->valid());
518         EXPECT_EQ(ud->header().id, 0x5544);
519 
520         EXPECT_EQ(ud->header().version,
521                   static_cast<uint8_t>(UserDataFormatVersion::json));
522         EXPECT_EQ(ud->header().subType,
523                   static_cast<uint8_t>(UserDataFormat::json));
524         EXPECT_EQ(ud->header().componentID,
525                   static_cast<uint16_t>(ComponentID::phosphorLogging));
526 
527         // Pull the JSON back out of the the UserData section
528         const auto& d = ud->data();
529         std::string js{d.begin(), d.end()};
530         auto json = nlohmann::json::parse(js);
531 
532         EXPECT_EQ("value1", json["key1"].get<std::string>());
533         EXPECT_EQ(42, json["key2"].get<int>());
534 
535         std::vector<int> key3Values{1, 2, 3, 4, 5};
536         EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
537 
538         std::map<std::string, std::string> key4Values{{"key5", "value5"}};
539         auto actual = json["key4"].get<std::map<std::string, std::string>>();
540         EXPECT_EQ(key4Values, actual);
541     }
542 
543     {
544         // A bad FD
545         PelFFDCfile ffdc;
546         ffdc.format = UserDataFormat::json;
547         ffdc.subType = 5;
548         ffdc.version = 42;
549         ffdc.fd = 10000;
550 
551         // The section shouldn't get made
552         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
553         ASSERT_FALSE(ud);
554     }
555 
556     fs::remove_all(dir);
557 }
558 
559 PelFFDCfile getCBORFFDC(const fs::path& dir)
560 {
561     PelFFDCfile ffdc;
562     ffdc.format = UserDataFormat::cbor;
563     ffdc.subType = 5;
564     ffdc.version = 42;
565 
566     auto inputJSON = R"({
567         "key1": "value1",
568         "key2": 42,
569         "key3" : [1, 2, 3, 4, 5],
570         "key4": {"key5": "value5"}
571     })"_json;
572 
573     // Convert the JSON to CBOR and write it to a file
574     auto data = nlohmann::json::to_cbor(inputJSON);
575     ffdc.fd = writeFileAndGetFD(dir, data);
576 
577     return ffdc;
578 }
579 
580 TEST_F(PELTest, MakeCBORFileUDSectionTest)
581 {
582     auto dir = makeTempDir();
583 
584     auto ffdc = getCBORFFDC(dir);
585     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
586     close(ffdc.fd);
587     ASSERT_TRUE(ud);
588     ASSERT_TRUE(ud->valid());
589     EXPECT_EQ(ud->header().id, 0x5544);
590 
591     EXPECT_EQ(ud->header().version,
592               static_cast<uint8_t>(UserDataFormatVersion::cbor));
593     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
594     EXPECT_EQ(ud->header().componentID,
595               static_cast<uint16_t>(ComponentID::phosphorLogging));
596 
597     // Pull the CBOR back out of the PEL section
598     // The number of pad bytes to make the section be 4B aligned
599     // was added at the end, read it and then remove it and the
600     // padding before parsing it.
601     auto data = ud->data();
602     Stream stream{data};
603     stream.offset(data.size() - 4);
604     uint32_t pad;
605     stream >> pad;
606 
607     data.resize(data.size() - 4 - pad);
608 
609     auto json = nlohmann::json::from_cbor(data);
610 
611     EXPECT_EQ("value1", json["key1"].get<std::string>());
612     EXPECT_EQ(42, json["key2"].get<int>());
613 
614     std::vector<int> key3Values{1, 2, 3, 4, 5};
615     EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
616 
617     std::map<std::string, std::string> key4Values{{"key5", "value5"}};
618     auto actual = json["key4"].get<std::map<std::string, std::string>>();
619     EXPECT_EQ(key4Values, actual);
620 
621     fs::remove_all(dir);
622 }
623 
624 PelFFDCfile getTextFFDC(const fs::path& dir)
625 {
626     PelFFDCfile ffdc;
627     ffdc.format = UserDataFormat::text;
628     ffdc.subType = 5;
629     ffdc.version = 42;
630 
631     std::string text{"this is some text that will be used for FFDC"};
632     std::vector<uint8_t> data{text.begin(), text.end()};
633 
634     ffdc.fd = writeFileAndGetFD(dir, data);
635 
636     return ffdc;
637 }
638 
639 TEST_F(PELTest, MakeTextFileUDSectionTest)
640 {
641     auto dir = makeTempDir();
642 
643     auto ffdc = getTextFFDC(dir);
644     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
645     close(ffdc.fd);
646     ASSERT_TRUE(ud);
647     ASSERT_TRUE(ud->valid());
648     EXPECT_EQ(ud->header().id, 0x5544);
649 
650     EXPECT_EQ(ud->header().version,
651               static_cast<uint8_t>(UserDataFormatVersion::text));
652     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
653     EXPECT_EQ(ud->header().componentID,
654               static_cast<uint16_t>(ComponentID::phosphorLogging));
655 
656     // Get the text back out
657     std::string text{ud->data().begin(), ud->data().end()};
658     EXPECT_EQ(text, "this is some text that will be used for FFDC");
659 
660     fs::remove_all(dir);
661 }
662 
663 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
664 {
665     PelFFDCfile ffdc;
666     ffdc.format = UserDataFormat::custom;
667     ffdc.subType = 5;
668     ffdc.version = 42;
669 
670     ffdc.fd = writeFileAndGetFD(dir, data);
671 
672     return ffdc;
673 }
674 
675 TEST_F(PELTest, MakeCustomFileUDSectionTest)
676 {
677     auto dir = makeTempDir();
678 
679     {
680         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
681 
682         auto ffdc = getCustomFFDC(dir, data);
683         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
684         close(ffdc.fd);
685         ASSERT_TRUE(ud);
686         ASSERT_TRUE(ud->valid());
687         EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
688         EXPECT_EQ(ud->header().id, 0x5544);
689 
690         EXPECT_EQ(ud->header().version, 42);
691         EXPECT_EQ(ud->header().subType, 5);
692         EXPECT_EQ(ud->header().componentID, 0x2002);
693 
694         // Get the data back out
695         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
696         EXPECT_EQ(data, newData);
697     }
698 
699     // Do the same thing again, but make it be non 4B aligned
700     // so the data gets padded.
701     {
702         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
703 
704         auto ffdc = getCustomFFDC(dir, data);
705         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
706         close(ffdc.fd);
707         ASSERT_TRUE(ud);
708         ASSERT_TRUE(ud->valid());
709         EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
710         EXPECT_EQ(ud->header().id, 0x5544);
711 
712         EXPECT_EQ(ud->header().version, 42);
713         EXPECT_EQ(ud->header().subType, 5);
714         EXPECT_EQ(ud->header().componentID, 0x2002);
715 
716         // Get the data back out
717         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
718 
719         // pad the original to 12B so we can compare
720         data.push_back(0);
721         data.push_back(0);
722         data.push_back(0);
723 
724         EXPECT_EQ(data, newData);
725     }
726 
727     fs::remove_all(dir);
728 }
729 
730 // Test Adding FFDC from files to a PEL
731 TEST_F(PELTest, CreateWithFFDCTest)
732 {
733     auto dir = makeTempDir();
734     message::Entry regEntry;
735     uint64_t timestamp = 5;
736 
737     regEntry.name = "test";
738     regEntry.subsystem = 5;
739     regEntry.actionFlags = 0xC000;
740     regEntry.src.type = 0xBD;
741     regEntry.src.reasonCode = 0x1234;
742 
743     std::vector<std::string> additionalData{"KEY1=VALUE1"};
744     AdditionalData ad{additionalData};
745     NiceMock<MockDataInterface> dataIface;
746     PelFFDC ffdc;
747 
748     std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
749 
750     // This will be trimmed when added
751     std::vector<uint8_t> hugeCustomData(17000, 0x42);
752 
753     ffdc.emplace_back(std::move(getJSONFFDC(dir)));
754     ffdc.emplace_back(std::move(getCBORFFDC(dir)));
755     ffdc.emplace_back(std::move(getTextFFDC(dir)));
756     ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
757     ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
758 
759     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
760             ad,       ffdc, dataIface};
761 
762     EXPECT_TRUE(pel.valid());
763 
764     // Clipped to the max
765     EXPECT_EQ(pel.size(), 16384);
766 
767     // Check for the FFDC sections
768     size_t udCount = 0;
769     Section* ud = nullptr;
770 
771     for (const auto& section : pel.optionalSections())
772     {
773         if (section->header().id == static_cast<uint16_t>(SectionID::userData))
774         {
775             udCount++;
776             ud = section.get();
777         }
778     }
779 
780     EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
781 
782     // Check the last section was trimmed to
783     // something a bit less that 17000.
784     EXPECT_GT(ud->header().size, 14000);
785     EXPECT_LT(ud->header().size, 16000);
786 
787     fs::remove_all(dir);
788 }
789 
790 // Create a PEL with device callouts
791 TEST_F(PELTest, CreateWithDevCalloutsTest)
792 {
793     message::Entry regEntry;
794     uint64_t timestamp = 5;
795 
796     regEntry.name = "test";
797     regEntry.subsystem = 5;
798     regEntry.actionFlags = 0xC000;
799     regEntry.src.type = 0xBD;
800     regEntry.src.reasonCode = 0x1234;
801 
802     NiceMock<MockDataInterface> dataIface;
803     PelFFDC ffdc;
804 
805     const auto calloutJSON = R"(
806     {
807         "I2C":
808         {
809             "14":
810             {
811                 "114":
812                 {
813                     "Callouts":[
814                         {
815                         "Name": "/chassis/motherboard/cpu0",
816                         "LocationCode": "P1",
817                         "Priority": "H"
818                         }
819                     ],
820                     "Dest": "proc 0 target"
821                 }
822             }
823         }
824     })";
825 
826     std::vector<std::string> names{"systemA"};
827     EXPECT_CALL(dataIface, getSystemNames)
828         .Times(2)
829         .WillRepeatedly(Return(names));
830 
831     EXPECT_CALL(dataIface,
832                 getLocationCode(
833                     "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"))
834         .WillOnce(Return("UXXX-P1"));
835 
836     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
837         .WillOnce(
838             Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
839 
840     EXPECT_CALL(
841         dataIface,
842         getHWCalloutFields(
843             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
844         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
845                         SetArgReferee<3>("123456789ABC")));
846 
847     auto dataPath = getPELReadOnlyDataPath();
848     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
849     file << calloutJSON;
850     file.close();
851 
852     {
853         std::vector<std::string> data{
854             "CALLOUT_ERRNO=5",
855             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
856             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
857 
858         AdditionalData ad{data};
859 
860         PEL pel{
861             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
862             ad,       ffdc, dataIface};
863 
864         ASSERT_TRUE(pel.primarySRC().value()->callouts());
865         auto& callouts = pel.primarySRC().value()->callouts()->callouts();
866         ASSERT_EQ(callouts.size(), 1);
867         ASSERT_TRUE(pel.isCalloutPresent());
868 
869         EXPECT_EQ(callouts[0]->priority(), 'H');
870         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
871 
872         auto& fru = callouts[0]->fruIdentity();
873         EXPECT_EQ(fru->getPN().value(), "1234567");
874         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
875         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
876 
877         const auto& section = pel.optionalSections().back();
878 
879         ASSERT_EQ(section->header().id, 0x5544); // UD
880         auto ud = static_cast<UserData*>(section.get());
881 
882         // Check that there was a UserData section added that
883         // contains debug details about the device.
884         const auto& d = ud->data();
885         std::string jsonString{d.begin(), d.end()};
886         auto actualJSON = nlohmann::json::parse(jsonString);
887 
888         auto expectedJSON = R"(
889             {
890                 "PEL Internal Debug Data": {
891                     "SRC": [
892                       "I2C: bus: 14 address: 114 dest: proc 0 target"
893                     ]
894                 }
895             }
896         )"_json;
897 
898         EXPECT_EQ(actualJSON, expectedJSON);
899     }
900 
901     {
902         // Device path not found (wrong i2c addr), so no callouts
903         std::vector<std::string> data{
904             "CALLOUT_ERRNO=5",
905             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
906             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
907 
908         AdditionalData ad{data};
909 
910         PEL pel{
911             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
912             ad,       ffdc, dataIface};
913 
914         // no callouts
915         EXPECT_FALSE(pel.primarySRC().value()->callouts());
916 
917         // Now check that there was a UserData section
918         // that contains the lookup error.
919         const auto& section = pel.optionalSections().back();
920 
921         ASSERT_EQ(section->header().id, 0x5544); // UD
922         auto ud = static_cast<UserData*>(section.get());
923 
924         const auto& d = ud->data();
925 
926         std::string jsonString{d.begin(), d.end()};
927 
928         auto actualJSON = nlohmann::json::parse(jsonString);
929 
930         auto expectedJSON =
931             "{\"PEL Internal Debug Data\":{\"SRC\":"
932             "[\"Problem looking up I2C callouts on 14 153: "
933             "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
934 
935         EXPECT_EQ(actualJSON, expectedJSON);
936     }
937 
938     fs::remove_all(dataPath);
939 }
940 
941 // Test PELs when the callouts are passed in using a JSON file.
942 TEST_F(PELTest, CreateWithJSONCalloutsTest)
943 {
944     PelFFDCfile ffdcFile;
945     ffdcFile.format = UserDataFormat::json;
946     ffdcFile.subType = 0xCA; // Callout JSON
947     ffdcFile.version = 1;
948 
949     // Write these callouts to a JSON file and pass it into
950     // the PEL as an FFDC file.
951     auto inputJSON = R"([
952         {
953             "Priority": "H",
954             "LocationCode": "P0-C1"
955         },
956         {
957             "Priority": "M",
958             "Procedure": "PROCEDURE"
959         }
960     ])"_json;
961 
962     auto s = inputJSON.dump();
963     std::vector<uint8_t> data{s.begin(), s.end()};
964     auto dir = makeTempDir();
965     ffdcFile.fd = writeFileAndGetFD(dir, data);
966 
967     PelFFDC ffdc;
968     ffdc.push_back(std::move(ffdcFile));
969 
970     AdditionalData ad;
971     NiceMock<MockDataInterface> dataIface;
972 
973     EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
974         .Times(1)
975         .WillOnce(Return("UXXX-P0-C1"));
976     EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
977         .Times(1)
978         .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
979     EXPECT_CALL(dataIface, getHWCalloutFields(
980                                "/inv/system/chassis/motherboard/bmc", _, _, _))
981         .Times(1)
982         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
983                         SetArgReferee<3>("123456789ABC")));
984 
985     message::Entry regEntry;
986     regEntry.name = "test";
987     regEntry.subsystem = 5;
988     regEntry.actionFlags = 0xC000;
989     regEntry.src.type = 0xBD;
990     regEntry.src.reasonCode = 0x1234;
991 
992     PEL pel{regEntry, 42,   5,        phosphor::logging::Entry::Level::Error,
993             ad,       ffdc, dataIface};
994 
995     ASSERT_TRUE(pel.valid());
996     ASSERT_TRUE(pel.primarySRC().value()->callouts());
997     const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
998     ASSERT_EQ(callouts.size(), 2);
999 
1000     {
1001         EXPECT_EQ(callouts[0]->priority(), 'H');
1002         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1003 
1004         auto& fru = callouts[0]->fruIdentity();
1005         EXPECT_EQ(fru->getPN().value(), "1234567");
1006         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1007         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1008         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1009     }
1010     {
1011         EXPECT_EQ(callouts[1]->priority(), 'M');
1012         EXPECT_EQ(callouts[1]->locationCode(), "");
1013 
1014         auto& fru = callouts[1]->fruIdentity();
1015         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1016         EXPECT_EQ(fru->failingComponentType(),
1017                   src::FRUIdentity::maintenanceProc);
1018     }
1019     fs::remove_all(dir);
1020 }
1021