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