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