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 "config.h"
17 
18 #include "pel.hpp"
19 
20 #include "bcd_time.hpp"
21 #include "extended_user_data.hpp"
22 #include "extended_user_header.hpp"
23 #include "failing_mtms.hpp"
24 #include "json_utils.hpp"
25 #include "log_id.hpp"
26 #include "pel_rules.hpp"
27 #include "pel_values.hpp"
28 #include "section_factory.hpp"
29 #include "src.hpp"
30 #include "stream.hpp"
31 #include "user_data_formats.hpp"
32 
33 #ifdef PEL_ENABLE_PHAL
34 #include "phal_service_actions.hpp"
35 #include "sbe_ffdc_handler.hpp"
36 #endif
37 
38 #include <fmt/format.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41 
42 #include <iostream>
43 #include <phosphor-logging/log.hpp>
44 
45 namespace openpower
46 {
47 namespace pels
48 {
49 namespace message = openpower::pels::message;
50 namespace pv = openpower::pels::pel_values;
51 using namespace phosphor::logging;
52 
53 constexpr auto unknownValue = "Unknown";
54 
55 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
56          phosphor::logging::Entry::Level severity,
57          const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn,
58          const DataInterfaceBase& dataIface)
59 {
60     // No changes in input, for non SBE error related requests
61     PelFFDC ffdcFiles = ffdcFilesIn;
62 
63 #ifdef PEL_ENABLE_PHAL
64     // Add sbe ffdc processed data into ffdcfiles.
65     namespace sbe = openpower::pels::sbe;
66     auto processReq =
67         std::any_of(ffdcFiles.begin(), ffdcFiles.end(), [](const auto& file) {
68             return file.format == UserDataFormat::custom &&
69                    file.subType == sbe::sbeFFDCSubType;
70         });
71     // sbeFFDC can't be destroyed until the end of the PEL constructor
72     // because it needs to keep around the FFDC Files to be used below.
73     std::unique_ptr<sbe::SbeFFDC> sbeFFDCPtr;
74     if (processReq)
75     {
76         sbeFFDCPtr =
77             std::make_unique<sbe::SbeFFDC>(additionalData, ffdcFilesIn);
78         const auto& sbeFFDCFiles = sbeFFDCPtr->getSbeFFDC();
79         ffdcFiles.insert(ffdcFiles.end(), sbeFFDCFiles.begin(),
80                          sbeFFDCFiles.end());
81     }
82 #endif
83 
84     std::map<std::string, std::vector<std::string>> debugData;
85     nlohmann::json callouts;
86 
87     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
88                                           timestamp);
89     _uh = std::make_unique<UserHeader>(regEntry, severity, additionalData,
90                                        dataIface);
91 
92     // Extract any callouts embedded in an FFDC file.
93     if (!ffdcFiles.empty())
94     {
95         try
96         {
97             callouts = getCalloutJSON(ffdcFiles);
98         }
99         catch (const std::exception& e)
100         {
101             debugData.emplace("FFDC file JSON callouts error",
102                               std::vector<std::string>{e.what()});
103         }
104     }
105 
106     auto src =
107         std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface);
108 
109     if (!src->getDebugData().empty())
110     {
111         // Something didn't go as planned
112         debugData.emplace("SRC", src->getDebugData());
113     }
114 
115     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src);
116 
117     _optionalSections.push_back(std::move(src));
118     _optionalSections.push_back(std::move(euh));
119 
120     auto mtms = std::make_unique<FailingMTMS>(dataIface);
121     _optionalSections.push_back(std::move(mtms));
122 
123     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
124     addUserDataSection(std::move(ud));
125 
126     //  Check for pel severity of type - 0x51 = critical error, system
127     //  termination and update terminate bit in SRC for pels
128     updateTerminateBitInSRCSection();
129 
130     // Create a UserData section from AdditionalData.
131     if (!additionalData.empty())
132     {
133         ud = util::makeADUserDataSection(additionalData);
134         addUserDataSection(std::move(ud));
135     }
136 
137     // Add any FFDC files into UserData sections
138     for (const auto& file : ffdcFiles)
139     {
140         ud = util::makeFFDCuserDataSection(regEntry.componentID, file);
141         if (!ud)
142         {
143             // Add this error into the debug data UserData section
144             std::ostringstream msg;
145             msg << "Could not make PEL FFDC UserData section from file"
146                 << std::hex << regEntry.componentID << " " << file.subType
147                 << " " << file.version;
148             if (debugData.count("FFDC File"))
149             {
150                 debugData.at("FFDC File").push_back(msg.str());
151             }
152             else
153             {
154                 debugData.emplace("FFDC File",
155                                   std::vector<std::string>{msg.str()});
156             }
157 
158             continue;
159         }
160 
161         addUserDataSection(std::move(ud));
162     }
163 
164 #ifdef PEL_ENABLE_PHAL
165     auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
166     openpower::pels::phal::createServiceActions(callouts, path, dataIface,
167                                                 plid());
168 #endif
169 
170     // Store in the PEL any important debug data created while
171     // building the PEL sections.
172     if (!debugData.empty())
173     {
174         nlohmann::json data;
175         data["PEL Internal Debug Data"] = debugData;
176         ud = util::makeJSONUserDataSection(data);
177 
178         addUserDataSection(std::move(ud));
179 
180         // Also put in the journal for debug
181         for (const auto& [name, data] : debugData)
182         {
183             for (const auto& message : data)
184             {
185                 std::string entry = name + ": " + message;
186                 log<level::INFO>(entry.c_str());
187             }
188         }
189     }
190 
191     _ph->setSectionCount(2 + _optionalSections.size());
192 
193     checkRulesAndFix();
194 }
195 
196 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0)
197 {
198 }
199 
200 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
201 {
202     populateFromRawData(data, obmcLogID);
203 }
204 
205 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
206 {
207     Stream pelData{data};
208     _ph = std::make_unique<PrivateHeader>(pelData);
209     if (obmcLogID != 0)
210     {
211         _ph->setOBMCLogID(obmcLogID);
212     }
213 
214     _uh = std::make_unique<UserHeader>(pelData);
215 
216     // Use the section factory to create the rest of the objects
217     for (size_t i = 2; i < _ph->sectionCount(); i++)
218     {
219         auto section = section_factory::create(pelData);
220         _optionalSections.push_back(std::move(section));
221     }
222 }
223 
224 bool PEL::valid() const
225 {
226     bool valid = _ph->valid();
227 
228     if (valid)
229     {
230         valid = _uh->valid();
231     }
232 
233     if (valid)
234     {
235         if (!std::all_of(_optionalSections.begin(), _optionalSections.end(),
236                          [](const auto& section) { return section->valid(); }))
237         {
238             valid = false;
239         }
240     }
241 
242     return valid;
243 }
244 
245 void PEL::setCommitTime()
246 {
247     auto now = std::chrono::system_clock::now();
248     _ph->setCommitTimestamp(getBCDTime(now));
249 }
250 
251 void PEL::assignID()
252 {
253     _ph->setID(generatePELID());
254 }
255 
256 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
257 {
258     Stream pelData{pelBuffer};
259 
260     if (!valid())
261     {
262         log<level::WARNING>("Unflattening an invalid PEL");
263     }
264 
265     _ph->flatten(pelData);
266     _uh->flatten(pelData);
267 
268     for (auto& section : _optionalSections)
269     {
270         section->flatten(pelData);
271     }
272 }
273 
274 std::vector<uint8_t> PEL::data() const
275 {
276     std::vector<uint8_t> pelData;
277     flatten(pelData);
278     return pelData;
279 }
280 
281 size_t PEL::size() const
282 {
283     size_t size = 0;
284 
285     if (_ph)
286     {
287         size += _ph->header().size;
288     }
289 
290     if (_uh)
291     {
292         size += _uh->header().size;
293     }
294 
295     for (const auto& section : _optionalSections)
296     {
297         size += section->header().size;
298     }
299 
300     return size;
301 }
302 
303 std::optional<SRC*> PEL::primarySRC() const
304 {
305     auto src = std::find_if(
306         _optionalSections.begin(), _optionalSections.end(), [](auto& section) {
307             return section->header().id ==
308                    static_cast<uint16_t>(SectionID::primarySRC);
309         });
310     if (src != _optionalSections.end())
311     {
312         return static_cast<SRC*>(src->get());
313     }
314 
315     return std::nullopt;
316 }
317 
318 void PEL::checkRulesAndFix()
319 {
320     // Only fix if the action flags are at their default value which
321     // means they weren't specified in the registry.  Otherwise
322     // assume the user knows what they are doing.
323     if (_uh->actionFlags() == actionFlagsDefault)
324     {
325         auto [actionFlags, eventType] =
326             pel_rules::check(0, _uh->eventType(), _uh->severity());
327 
328         _uh->setActionFlags(actionFlags);
329         _uh->setEventType(eventType);
330     }
331 }
332 
333 void PEL::printSectionInJSON(const Section& section, std::string& buf,
334                              std::map<uint16_t, size_t>& pluralSections,
335                              message::Registry& registry,
336                              const std::vector<std::string>& plugins,
337                              uint8_t creatorID) const
338 {
339     char tmpB[5];
340     uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
341                     static_cast<uint8_t>(section.header().id)};
342     sprintf(tmpB, "%c%c", id[0], id[1]);
343     std::string sectionID(tmpB);
344     std::string sectionName = pv::sectionTitles.count(sectionID)
345                                   ? pv::sectionTitles.at(sectionID)
346                                   : "Unknown Section";
347 
348     // Add a count if there are multiple of this type of section
349     auto count = pluralSections.find(section.header().id);
350     if (count != pluralSections.end())
351     {
352         sectionName += " " + std::to_string(count->second);
353         count->second++;
354     }
355 
356     if (section.valid())
357     {
358         std::optional<std::string> json;
359         if (sectionID == "PS" || sectionID == "SS")
360         {
361             json = section.getJSON(registry, plugins, creatorID);
362         }
363         else if ((sectionID == "UD") || (sectionID == "ED"))
364         {
365             json = section.getJSON(creatorID, plugins);
366         }
367         else
368         {
369             json = section.getJSON();
370         }
371 
372         buf += "\"" + sectionName + "\": {\n";
373 
374         if (json)
375         {
376             buf += *json + "\n},\n";
377         }
378         else
379         {
380             jsonInsert(buf, pv::sectionVer,
381                        getNumberString("%d", section.header().version), 1);
382             jsonInsert(buf, pv::subSection,
383                        getNumberString("%d", section.header().subType), 1);
384             jsonInsert(buf, pv::createdBy,
385                        getNumberString("0x%X", section.header().componentID),
386                        1);
387 
388             std::vector<uint8_t> data;
389             Stream s{data};
390             section.flatten(s);
391             std::string dstr =
392                 dumpHex(std::data(data) + SectionHeader::flattenedSize(),
393                         data.size() - SectionHeader::flattenedSize(), 2);
394             std::string jsonIndent(indentLevel, 0x20);
395             buf += jsonIndent + "\"Data\": [\n";
396             buf += dstr;
397             buf += jsonIndent + "]\n";
398             buf += "},\n";
399         }
400     }
401     else
402     {
403         buf += "\n\"Invalid Section\": [\n    \"invalid\"\n],\n";
404     }
405 }
406 
407 std::map<uint16_t, size_t> PEL::getPluralSections() const
408 {
409     std::map<uint16_t, size_t> sectionCounts;
410 
411     for (const auto& section : optionalSections())
412     {
413         if (sectionCounts.find(section->header().id) == sectionCounts.end())
414         {
415             sectionCounts[section->header().id] = 1;
416         }
417         else
418         {
419             sectionCounts[section->header().id]++;
420         }
421     }
422 
423     std::map<uint16_t, size_t> sections;
424     for (const auto& [id, count] : sectionCounts)
425     {
426         if (count > 1)
427         {
428             // Start with 0 here and printSectionInJSON()
429             // will increment it as it goes.
430             sections.emplace(id, 0);
431         }
432     }
433 
434     return sections;
435 }
436 
437 void PEL::toJSON(message::Registry& registry,
438                  const std::vector<std::string>& plugins) const
439 {
440     auto sections = getPluralSections();
441 
442     std::string buf = "{\n";
443     printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins);
444     printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins);
445     for (auto& section : this->optionalSections())
446     {
447         printSectionInJSON(*(section.get()), buf, sections, registry, plugins,
448                            _ph->creatorID());
449     }
450     buf += "}";
451     std::size_t found = buf.rfind(",");
452     if (found != std::string::npos)
453         buf.replace(found, 1, "");
454     std::cout << buf << std::endl;
455 }
456 
457 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData)
458 {
459     if (size() + userData->header().size > _maxPELSize)
460     {
461         if (userData->shrink(_maxPELSize - size()))
462         {
463             _optionalSections.push_back(std::move(userData));
464         }
465         else
466         {
467             log<level::WARNING>(
468                 "Could not shrink UserData section. Dropping",
469                 entry("SECTION_SIZE=%d\n", userData->header().size),
470                 entry("COMPONENT_ID=0x%02X", userData->header().componentID),
471                 entry("SUBTYPE=0x%X", userData->header().subType),
472                 entry("VERSION=0x%X", userData->header().version));
473             return false;
474         }
475     }
476     else
477     {
478         _optionalSections.push_back(std::move(userData));
479     }
480     return true;
481 }
482 
483 nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles)
484 {
485     nlohmann::json callouts;
486 
487     for (const auto& file : ffdcFiles)
488     {
489         if ((file.format == UserDataFormat::json) &&
490             (file.subType == jsonCalloutSubtype))
491         {
492             auto data = util::readFD(file.fd);
493             if (data.empty())
494             {
495                 throw std::runtime_error{
496                     "Could not get data from JSON callout file descriptor"};
497             }
498 
499             std::string jsonString{data.begin(), data.begin() + data.size()};
500 
501             callouts = nlohmann::json::parse(jsonString);
502             break;
503         }
504     }
505 
506     return callouts;
507 }
508 
509 bool PEL::isCalloutPresent() const
510 {
511     auto pSRC = primarySRC();
512     if (!pSRC)
513     {
514         return false;
515     }
516 
517     bool calloutPresent = false;
518     if ((*pSRC)->callouts())
519     {
520         for (auto& i : (*pSRC)->callouts()->callouts())
521         {
522             if (((*i).fruIdentity()))
523             {
524                 auto& fruId = (*i).fruIdentity();
525                 if ((*fruId).failingComponentType() != 0)
526                 {
527                     calloutPresent = true;
528                     break;
529                 }
530             }
531         }
532     }
533 
534     return calloutPresent;
535 }
536 
537 void PEL::updateSysInfoInExtendedUserDataSection(
538     const DataInterfaceBase& dataIface)
539 {
540     const AdditionalData additionalData;
541 
542     // Check for PEL from Hostboot
543     if (_ph->creatorID() == static_cast<uint8_t>(CreatorID::hostboot))
544     {
545         // Get the ED section from PEL
546         auto op = std::find_if(_optionalSections.begin(),
547                                _optionalSections.end(), [](auto& section) {
548                                    return section->header().id ==
549                                           static_cast<uint16_t>(
550                                               SectionID::extUserData);
551                                });
552 
553         // Check for ED section found and its not the last section of PEL
554         if (op != _optionalSections.end())
555         {
556             // Get the extended user data class mapped to found section
557             auto extUserData = static_cast<ExtendedUserData*>(op->get());
558 
559             // Check for the creator ID is for OpenBMC
560             if (extUserData->creatorID() ==
561                 static_cast<uint8_t>(CreatorID::openBMC))
562             {
563                 // Update subtype and component id
564                 auto subType = static_cast<uint8_t>(UserDataFormat::json);
565                 auto componentId =
566                     static_cast<uint16_t>(ComponentID::phosphorLogging);
567 
568                 // Update system data to ED section
569                 auto ud =
570                     util::makeSysInfoUserDataSection(additionalData, dataIface);
571                 extUserData->updateDataSection(subType, componentId,
572                                                ud->data());
573             }
574         }
575     }
576 }
577 
578 void PEL::updateTerminateBitInSRCSection()
579 {
580     //  Check for pel severity of type - 0x51 = critical error, system
581     //  termination
582     if (_uh->severity() == 0x51)
583     {
584         // Get the primary SRC section
585         auto pSRC = primarySRC();
586         if (pSRC)
587         {
588             (*pSRC)->setTerminateBit();
589         }
590     }
591 }
592 
593 namespace util
594 {
595 
596 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
597 {
598     auto jsonString = json.dump();
599     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
600 
601     // Pad to a 4 byte boundary
602     while ((jsonData.size() % 4) != 0)
603     {
604         jsonData.push_back(0);
605     }
606 
607     return std::make_unique<UserData>(
608         static_cast<uint16_t>(ComponentID::phosphorLogging),
609         static_cast<uint8_t>(UserDataFormat::json),
610         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
611 }
612 
613 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
614 {
615     assert(!ad.empty());
616     nlohmann::json json;
617 
618     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
619     if (ad.getValue("ESEL"))
620     {
621         auto newAD = ad;
622         newAD.remove("ESEL");
623         json = newAD.toJSON();
624     }
625     else
626     {
627         json = ad.toJSON();
628     }
629 
630     return makeJSONUserDataSection(json);
631 }
632 
633 void addProcessNameToJSON(nlohmann::json& json,
634                           const std::optional<std::string>& pid,
635                           const DataInterfaceBase& dataIface)
636 {
637     std::string name{unknownValue};
638 
639     try
640     {
641         if (pid)
642         {
643             auto n = dataIface.getProcessName(*pid);
644             if (n)
645             {
646                 name = *n;
647             }
648         }
649     }
650     catch (const std::exception& e)
651     {
652     }
653 
654     if (pid)
655     {
656         json["Process Name"] = std::move(name);
657     }
658 }
659 
660 void addBMCFWVersionIDToJSON(nlohmann::json& json,
661                              const DataInterfaceBase& dataIface)
662 {
663     auto id = dataIface.getBMCFWVersionID();
664     if (id.empty())
665     {
666         id = unknownValue;
667     }
668 
669     json["FW Version ID"] = std::move(id);
670 }
671 
672 std::string lastSegment(char separator, std::string data)
673 {
674     auto pos = data.find_last_of(separator);
675     if (pos != std::string::npos)
676     {
677         data = data.substr(pos + 1);
678     }
679 
680     return data;
681 }
682 
683 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface)
684 {
685     auto keyword = dataIface.getSystemIMKeyword();
686 
687     std::string value{};
688 
689     std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) {
690         value += fmt::format("{:02X}", byte);
691     });
692 
693     json["System IM"] = value;
694 }
695 
696 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
697 {
698     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
699     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
700     json["HostState"] = lastSegment('.', dataIface.getHostState());
701     json["BootState"] = lastSegment('.', dataIface.getBootState());
702 }
703 
704 std::unique_ptr<UserData>
705     makeSysInfoUserDataSection(const AdditionalData& ad,
706                                const DataInterfaceBase& dataIface)
707 {
708     nlohmann::json json;
709 
710     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
711     addBMCFWVersionIDToJSON(json, dataIface);
712     addIMKeyword(json, dataIface);
713     addStatesToJSON(json, dataIface);
714 
715     return makeJSONUserDataSection(json);
716 }
717 
718 std::vector<uint8_t> readFD(int fd)
719 {
720     std::vector<uint8_t> data;
721 
722     // Get the size
723     struct stat s;
724     int r = fstat(fd, &s);
725     if (r != 0)
726     {
727         auto e = errno;
728         log<level::ERR>("Could not get FFDC file size from FD",
729                         entry("ERRNO=%d", e));
730         return data;
731     }
732 
733     if (0 == s.st_size)
734     {
735         log<level::ERR>("FFDC file is empty");
736         return data;
737     }
738 
739     data.resize(s.st_size);
740 
741     // Make sure its at the beginning, as maybe another
742     // extension already used it.
743     r = lseek(fd, 0, SEEK_SET);
744     if (r == -1)
745     {
746         auto e = errno;
747         log<level::ERR>("Could not seek to beginning of FFDC file",
748                         entry("ERRNO=%d", e));
749         return data;
750     }
751 
752     r = read(fd, data.data(), s.st_size);
753     if (r == -1)
754     {
755         auto e = errno;
756         log<level::ERR>("Could not read FFDC file", entry("ERRNO=%d", e));
757     }
758     else if (r != s.st_size)
759     {
760         log<level::WARNING>("Could not read full FFDC file",
761                             entry("FILE_SIZE=%d", s.st_size),
762                             entry("SIZE_READ=%d", r));
763     }
764 
765     return data;
766 }
767 
768 std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
769                                                   const PelFFDCfile& file)
770 {
771     auto data = readFD(file.fd);
772 
773     if (data.empty())
774     {
775         return std::unique_ptr<UserData>();
776     }
777 
778     // The data needs 4 Byte alignment, and save amount padded for the
779     // CBOR case.
780     uint32_t pad = 0;
781     while (data.size() % 4)
782     {
783         data.push_back(0);
784         pad++;
785     }
786 
787     // For JSON, CBOR, and Text use our component ID, subType, and version,
788     // otherwise use the supplied ones.
789     uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging);
790     uint8_t subType{};
791     uint8_t version{};
792 
793     switch (file.format)
794     {
795         case UserDataFormat::json:
796             subType = static_cast<uint8_t>(UserDataFormat::json);
797             version = static_cast<uint8_t>(UserDataFormatVersion::json);
798             break;
799         case UserDataFormat::cbor:
800             subType = static_cast<uint8_t>(UserDataFormat::cbor);
801             version = static_cast<uint8_t>(UserDataFormatVersion::cbor);
802 
803             // The CBOR parser will fail on the extra pad bytes since they
804             // aren't CBOR.  Add the amount we padded to the end and other
805             // code will remove it all before parsing.
806             {
807                 data.resize(data.size() + 4);
808                 Stream stream{data};
809                 stream.offset(data.size() - 4);
810                 stream << pad;
811             }
812 
813             break;
814         case UserDataFormat::text:
815             subType = static_cast<uint8_t>(UserDataFormat::text);
816             version = static_cast<uint8_t>(UserDataFormatVersion::text);
817             break;
818         case UserDataFormat::custom:
819         default:
820             // Use the passed in values
821             compID = componentID;
822             subType = file.subType;
823             version = file.version;
824             break;
825     }
826 
827     return std::make_unique<UserData>(compID, subType, version, data);
828 }
829 
830 } // namespace util
831 
832 } // namespace pels
833 } // namespace openpower
834