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