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