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 "src.hpp"
17 
18 #include "device_callouts.hpp"
19 #include "json_utils.hpp"
20 #include "paths.hpp"
21 #include "pel_values.hpp"
22 #ifdef PELTOOL
23 #include <Python.h>
24 
25 #include <nlohmann/json.hpp>
26 
27 #include <sstream>
28 #endif
29 #include <phosphor-logging/lg2.hpp>
30 
31 #include <format>
32 
33 namespace openpower
34 {
35 namespace pels
36 {
37 namespace pv = openpower::pels::pel_values;
38 namespace rg = openpower::pels::message;
39 using namespace std::string_literals;
40 
41 constexpr size_t ccinSize = 4;
42 
43 #ifdef PELTOOL
44 using orderedJSON = nlohmann::ordered_json;
45 
46 void pyDecRef(PyObject* pyObj)
47 {
48     Py_XDECREF(pyObj);
49 }
50 
51 /**
52  * @brief Returns a JSON string to append to SRC section.
53  *
54  * The returning string will contain a JSON object, but without
55  * the outer {}.  If the input JSON isn't a JSON object (dict), then
56  * one will be created with the input added to a 'SRC Details' key.
57  *
58  * @param[in] json - The JSON to convert to a string
59  *
60  * @return std::string - The JSON string
61  */
62 std::string prettyJSON(const orderedJSON& json)
63 {
64     orderedJSON output;
65     if (!json.is_object())
66     {
67         output["SRC Details"] = json;
68     }
69     else
70     {
71         for (const auto& [key, value] : json.items())
72         {
73             output["SRC Details"][key] = value;
74         }
75     }
76 
77     // Let nlohmann do the pretty printing.
78     std::stringstream stream;
79     stream << std::setw(4) << output;
80 
81     auto jsonString = stream.str();
82 
83     // Now it looks like:
84     // {
85     //     "Key": "Value",
86     //     ...
87     // }
88 
89     // Replace the { and the following newline, and the } and its
90     // preceeding newline.
91     jsonString.erase(0, 2);
92 
93     auto pos = jsonString.find_last_of('}');
94     jsonString.erase(pos - 1);
95 
96     return jsonString;
97 }
98 
99 /**
100  * @brief Call Python modules to parse the data into a JSON string
101  *
102  * The module to call is based on the Creator Subsystem ID under the namespace
103  * "srcparsers". For example: "srcparsers.xsrc.xsrc" where "x" is the Creator
104  * Subsystem ID in ASCII lowercase.
105  *
106  * All modules must provide the following:
107  * Function: parseSRCToJson
108  * Argument list:
109  *    1. (str) ASCII string (Hex Word 1)
110  *    2. (str) Hex Word 2
111  *    3. (str) Hex Word 3
112  *    4. (str) Hex Word 4
113  *    5. (str) Hex Word 5
114  *    6. (str) Hex Word 6
115  *    7. (str) Hex Word 7
116  *    8. (str) Hex Word 8
117  *    9. (str) Hex Word 9
118  *-Return data:
119  *    1. (str) JSON string
120  *
121  * @param[in] hexwords - Vector of strings of Hexwords 1-9
122  * @param[in] creatorID - The creatorID from the Private Header section
123  * @return std::optional<std::string> - The JSON string if it could be created,
124  *                                      else std::nullopt
125  */
126 std::optional<std::string> getPythonJSON(std::vector<std::string>& hexwords,
127                                          uint8_t creatorID)
128 {
129     PyObject *pName, *pModule, *eType, *eValue, *eTraceback;
130     std::string pErrStr;
131     std::string module = getNumberString("%c", tolower(creatorID)) + "src";
132     pName = PyUnicode_FromString(
133         std::string("srcparsers." + module + "." + module).c_str());
134     std::unique_ptr<PyObject, decltype(&pyDecRef)> modNamePtr(pName, &pyDecRef);
135     pModule = PyImport_Import(pName);
136     if (pModule == NULL)
137     {
138         pErrStr = "No error string found";
139         PyErr_Fetch(&eType, &eValue, &eTraceback);
140         if (eType)
141         {
142             Py_XDECREF(eType);
143         }
144         if (eTraceback)
145         {
146             Py_XDECREF(eTraceback);
147         }
148         if (eValue)
149         {
150             PyObject* pStr = PyObject_Str(eValue);
151             Py_XDECREF(eValue);
152             if (pStr)
153             {
154                 pErrStr = PyUnicode_AsUTF8(pStr);
155                 Py_XDECREF(pStr);
156             }
157         }
158     }
159     else
160     {
161         std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(
162             pModule, &pyDecRef);
163         std::string funcToCall = "parseSRCToJson";
164         PyObject* pKey = PyUnicode_FromString(funcToCall.c_str());
165         std::unique_ptr<PyObject, decltype(&pyDecRef)> keyPtr(pKey, &pyDecRef);
166         PyObject* pDict = PyModule_GetDict(pModule);
167         Py_INCREF(pDict);
168         if (!PyDict_Contains(pDict, pKey))
169         {
170             Py_DECREF(pDict);
171             lg2::error(
172                 "Python module error.  Function missing: {FUNC}, SRC = {SRC}, module = {MODULE}",
173                 "FUNC", funcToCall, "SRC", hexwords.front(), "MODULE", module);
174             return std::nullopt;
175         }
176         PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
177         Py_DECREF(pDict);
178         Py_INCREF(pFunc);
179         if (PyCallable_Check(pFunc))
180         {
181             PyObject* pArgs = PyTuple_New(9);
182             std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(
183                 pArgs, &pyDecRef);
184             for (size_t i = 0; i < 9; i++)
185             {
186                 std::string arg{"00000000"};
187                 if (i < hexwords.size())
188                 {
189                     arg = hexwords[i];
190                 }
191                 PyTuple_SetItem(pArgs, i, Py_BuildValue("s", arg.c_str()));
192             }
193             PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
194             Py_DECREF(pFunc);
195             if (pResult)
196             {
197                 std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
198                     pResult, &pyDecRef);
199                 PyObject* pBytes =
200                     PyUnicode_AsEncodedString(pResult, "utf-8", "~E~");
201                 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
202                     pBytes, &pyDecRef);
203                 const char* output = PyBytes_AS_STRING(pBytes);
204                 try
205                 {
206                     orderedJSON json = orderedJSON::parse(output);
207                     if ((json.is_object() && !json.empty()) ||
208                         (json.is_array() && json.size() > 0) ||
209                         (json.is_string() && json != ""))
210                     {
211                         return prettyJSON(json);
212                     }
213                 }
214                 catch (const std::exception& e)
215                 {
216                     lg2::error(
217                         "Bad JSON from parser. Error = {ERROR}, SRC = {SRC}, module = {MODULE}",
218                         "ERROR", e, "SRC", hexwords.front(), "MODULE", module);
219                     return std::nullopt;
220                 }
221             }
222             else
223             {
224                 pErrStr = "No error string found";
225                 PyErr_Fetch(&eType, &eValue, &eTraceback);
226                 if (eType)
227                 {
228                     Py_XDECREF(eType);
229                 }
230                 if (eTraceback)
231                 {
232                     Py_XDECREF(eTraceback);
233                 }
234                 if (eValue)
235                 {
236                     PyObject* pStr = PyObject_Str(eValue);
237                     Py_XDECREF(eValue);
238                     if (pStr)
239                     {
240                         pErrStr = PyUnicode_AsUTF8(pStr);
241                         Py_XDECREF(pStr);
242                     }
243                 }
244             }
245         }
246     }
247     if (!pErrStr.empty())
248     {
249         lg2::debug("Python exception thrown by parser. Error = {ERROR}, "
250                    "SRC = {SRC}, module = {MODULE}",
251                    "ERROR", pErrStr, "SRC", hexwords.front(), "MODULE", module);
252     }
253     return std::nullopt;
254 }
255 #endif
256 
257 void SRC::unflatten(Stream& stream)
258 {
259     stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
260         _reserved2B >> _size;
261 
262     for (auto& word : _hexData)
263     {
264         stream >> word;
265     }
266 
267     _asciiString = std::make_unique<src::AsciiString>(stream);
268 
269     if (hasAdditionalSections())
270     {
271         // The callouts section is currently the only extra subsection type
272         _callouts = std::make_unique<src::Callouts>(stream);
273     }
274 }
275 
276 void SRC::flatten(Stream& stream) const
277 {
278     stream << _header << _version << _flags << _reserved1B << _wordCount
279            << _reserved2B << _size;
280 
281     for (auto& word : _hexData)
282     {
283         stream << word;
284     }
285 
286     _asciiString->flatten(stream);
287 
288     if (_callouts)
289     {
290         _callouts->flatten(stream);
291     }
292 }
293 
294 SRC::SRC(Stream& pel)
295 {
296     try
297     {
298         unflatten(pel);
299         validate();
300     }
301     catch (const std::exception& e)
302     {
303         lg2::error("Cannot unflatten SRC, error = {ERROR}", "ERROR", e);
304         _valid = false;
305     }
306 }
307 
308 SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
309          const nlohmann::json& jsonCallouts, const DataInterfaceBase& dataIface)
310 {
311     _header.id = static_cast<uint16_t>(SectionID::primarySRC);
312     _header.version = srcSectionVersion;
313     _header.subType = srcSectionSubtype;
314     _header.componentID = regEntry.componentID;
315 
316     _version = srcVersion;
317 
318     _flags = 0;
319 
320     _reserved1B = 0;
321 
322     _wordCount = numSRCHexDataWords + 1;
323 
324     _reserved2B = 0;
325 
326     // There are multiple fields encoded in the hex data words.
327     std::for_each(_hexData.begin(), _hexData.end(),
328                   [](auto& word) { word = 0; });
329 
330     // Hex Word 2 Nibbles:
331     //   MIGVEPFF
332     //   M: Partition dump status = 0
333     //   I: System boot state = TODO
334     //   G: Partition Boot type = 0
335     //   V: BMC dump status
336     //   E: Platform boot mode = 0 (side = temporary, speed = fast)
337     //   P: Platform dump status
338     //  FF: SRC format, set below
339 
340     setProgressCode(dataIface);
341     setBMCFormat();
342     setBMCPosition();
343     setMotherboardCCIN(dataIface);
344 
345     if (regEntry.src.checkstopFlag)
346     {
347         setErrorStatusFlag(ErrorStatusFlags::hwCheckstop);
348     }
349 
350     if (regEntry.src.deconfigFlag)
351     {
352         setErrorStatusFlag(ErrorStatusFlags::deconfigured);
353     }
354 
355     // Fill in the last 4 words from the AdditionalData property contents.
356     setUserDefinedHexWords(regEntry, additionalData);
357 
358     _asciiString = std::make_unique<src::AsciiString>(regEntry);
359 
360     // Check for additional data - PEL_SUBSYSTEM
361     auto ss = additionalData.getValue("PEL_SUBSYSTEM");
362     if (ss)
363     {
364         auto eventSubsystem = std::stoul(*ss, NULL, 16);
365         std::string subsystem =
366             pv::getValue(eventSubsystem, pel_values::subsystemValues);
367         if (subsystem == "invalid")
368         {
369             lg2::warning("SRC: Invalid SubSystem value: {VAL}", "VAL", lg2::hex,
370                          eventSubsystem);
371         }
372         else
373         {
374             _asciiString->setByte(2, eventSubsystem);
375         }
376     }
377 
378     addCallouts(regEntry, additionalData, jsonCallouts, dataIface);
379 
380     _size = baseSRCSize;
381     _size += _callouts ? _callouts->flattenedSize() : 0;
382     _header.size = Section::flattenedSize() + _size;
383 
384     _valid = true;
385 }
386 
387 void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
388                                  const AdditionalData& ad)
389 {
390     if (!regEntry.src.hexwordADFields)
391     {
392         return;
393     }
394 
395     // Save the AdditionalData value corresponding to the first element of
396     // adName tuple into _hexData[wordNum].
397     for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
398     {
399         // Can only set words 6 - 9
400         if (!isUserDefinedWord(wordNum))
401         {
402             std::string msg =
403                 "SRC user data word out of range: " + std::to_string(wordNum);
404             addDebugData(msg);
405             continue;
406         }
407 
408         auto value = ad.getValue(std::get<0>(adName));
409         if (value)
410         {
411             _hexData[getWordIndexFromWordNum(wordNum)] =
412                 std::strtoul(value.value().c_str(), nullptr, 0);
413         }
414         else
415         {
416             std::string msg = "Source for user data SRC word not found: " +
417                               std::get<0>(adName);
418             addDebugData(msg);
419         }
420     }
421 }
422 
423 void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
424 {
425     uint32_t ccin = 0;
426     auto ccinString = dataIface.getMotherboardCCIN();
427 
428     try
429     {
430         if (ccinString.size() == ccinSize)
431         {
432             ccin = std::stoi(ccinString, 0, 16);
433         }
434     }
435     catch (const std::exception& e)
436     {
437         lg2::warning("Could not convert motherboard CCIN {CCIN} to a number",
438                      "CCIN", ccinString);
439         return;
440     }
441 
442     // Set the first 2 bytes
443     _hexData[1] |= ccin << 16;
444 }
445 
446 void SRC::validate()
447 {
448     bool failed = false;
449 
450     if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
451         (header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
452     {
453         lg2::error("Invalid SRC section ID: {ID}", "ID", lg2::hex, header().id);
454         failed = true;
455     }
456 
457     // Check the version in the SRC, not in the header
458     if (_version != srcVersion)
459     {
460         lg2::error("Invalid SRC version: {VERSION}", "VERSION", lg2::hex,
461                    header().version);
462         failed = true;
463     }
464 
465     _valid = failed ? false : true;
466 }
467 
468 bool SRC::isBMCSRC() const
469 {
470     auto as = asciiString();
471     if (as.length() >= 2)
472     {
473         uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
474         return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
475                 errorType == static_cast<uint8_t>(SRCType::powerError));
476     }
477     return false;
478 }
479 
480 bool SRC::isHostbootSRC() const
481 {
482     auto as = asciiString();
483     if (as.length() >= 2)
484     {
485         uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
486         return errorType == static_cast<uint8_t>(SRCType::hostbootError);
487     }
488     return false;
489 }
490 
491 std::optional<std::string> SRC::getErrorDetails(
492     message::Registry& registry, DetailLevel type, bool toCache) const
493 {
494     const std::string jsonIndent(indentLevel, 0x20);
495     std::string errorOut;
496     if (isBMCSRC())
497     {
498         auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
499                                      rg::LookupType::reasonCode, toCache);
500         if (entry)
501         {
502             errorOut.append(jsonIndent + "\"Error Details\": {\n");
503             auto errorMsg = getErrorMessage(*entry);
504             if (errorMsg)
505             {
506                 if (type == DetailLevel::message)
507                 {
508                     return errorMsg.value();
509                 }
510                 else
511                 {
512                     jsonInsert(errorOut, "Message", errorMsg.value(), 2);
513                 }
514             }
515             if (entry->src.hexwordADFields)
516             {
517                 std::map<size_t, std::tuple<std::string, std::string>>
518                     adFields = entry->src.hexwordADFields.value();
519                 for (const auto& hexwordMap : adFields)
520                 {
521                     auto srcValue = getNumberString(
522                         "0x%X",
523                         _hexData[getWordIndexFromWordNum(hexwordMap.first)]);
524 
525                     auto srcKey = std::get<0>(hexwordMap.second);
526                     auto srcDesc = std::get<1>(hexwordMap.second);
527 
528                     // Only include this hex word in the error details if the
529                     // description exists.
530                     if (!srcDesc.empty())
531                     {
532                         std::vector<std::string> valueDescr;
533                         valueDescr.push_back(srcValue);
534                         valueDescr.push_back(srcDesc);
535                         jsonInsertArray(errorOut, srcKey, valueDescr, 2);
536                     }
537                 }
538             }
539             errorOut.erase(errorOut.size() - 2);
540             errorOut.append("\n");
541             errorOut.append(jsonIndent + "},\n");
542             return errorOut;
543         }
544     }
545     return std::nullopt;
546 }
547 
548 std::optional<std::string>
549     SRC::getErrorMessage(const message::Entry& regEntry) const
550 {
551     try
552     {
553         if (regEntry.doc.messageArgSources)
554         {
555             std::vector<uint32_t> argSourceVals;
556             std::string message;
557             const auto& argValues = regEntry.doc.messageArgSources.value();
558             for (size_t i = 0; i < argValues.size(); ++i)
559             {
560                 argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
561                     argValues[i].back() - '0')]);
562             }
563 
564             auto it = std::begin(regEntry.doc.message);
565             auto it_end = std::end(regEntry.doc.message);
566 
567             while (it != it_end)
568             {
569                 if (*it == '%')
570                 {
571                     ++it;
572 
573                     size_t wordIndex = *it - '0';
574                     if (isdigit(*it) && wordIndex >= 1 &&
575                         static_cast<uint16_t>(wordIndex) <=
576                             argSourceVals.size())
577                     {
578                         message.append(getNumberString(
579                             "0x%08X", argSourceVals[wordIndex - 1]));
580                     }
581                     else
582                     {
583                         message.append("%" + std::string(1, *it));
584                     }
585                 }
586                 else
587                 {
588                     message.push_back(*it);
589                 }
590                 ++it;
591             }
592 
593             return message;
594         }
595         else
596         {
597             return regEntry.doc.message;
598         }
599     }
600     catch (const std::exception& e)
601     {
602         lg2::error(
603             "Cannot get error message from registry entry, error = {ERROR}",
604             "ERROR", e);
605     }
606     return std::nullopt;
607 }
608 
609 std::optional<std::string> SRC::getCallouts() const
610 {
611     if (!_callouts)
612     {
613         return std::nullopt;
614     }
615     std::string printOut;
616     const std::string jsonIndent(indentLevel, 0x20);
617     const auto& callout = _callouts->callouts();
618     const auto& compDescrp = pv::failingComponentType;
619     printOut.append(jsonIndent + "\"Callout Section\": {\n");
620     jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
621     printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
622     for (auto& entry : callout)
623     {
624         printOut.append("{\n");
625         if (entry->fruIdentity())
626         {
627             jsonInsert(
628                 printOut, "FRU Type",
629                 compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
630             jsonInsert(printOut, "Priority",
631                        pv::getValue(entry->priority(),
632                                     pel_values::calloutPriorityValues),
633                        3);
634             if (!entry->locationCode().empty())
635             {
636                 jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
637             }
638             if (entry->fruIdentity()->getPN().has_value())
639             {
640                 jsonInsert(printOut, "Part Number",
641                            entry->fruIdentity()->getPN().value(), 3);
642             }
643             if (entry->fruIdentity()->getMaintProc().has_value())
644             {
645                 jsonInsert(printOut, "Procedure",
646                            entry->fruIdentity()->getMaintProc().value(), 3);
647                 if (pv::procedureDesc.find(
648                         entry->fruIdentity()->getMaintProc().value()) !=
649                     pv::procedureDesc.end())
650                 {
651                     jsonInsert(
652                         printOut, "Description",
653                         pv::procedureDesc.at(
654                             entry->fruIdentity()->getMaintProc().value()),
655                         3);
656                 }
657             }
658             if (entry->fruIdentity()->getCCIN().has_value())
659             {
660                 jsonInsert(printOut, "CCIN",
661                            entry->fruIdentity()->getCCIN().value(), 3);
662             }
663             if (entry->fruIdentity()->getSN().has_value())
664             {
665                 jsonInsert(printOut, "Serial Number",
666                            entry->fruIdentity()->getSN().value(), 3);
667             }
668         }
669         if (entry->pceIdentity())
670         {
671             const auto& pceIdentMtms = entry->pceIdentity()->mtms();
672             if (!pceIdentMtms.machineTypeAndModel().empty())
673             {
674                 jsonInsert(printOut, "PCE MTMS",
675                            pceIdentMtms.machineTypeAndModel() + "_" +
676                                pceIdentMtms.machineSerialNumber(),
677                            3);
678             }
679             if (!entry->pceIdentity()->enclosureName().empty())
680             {
681                 jsonInsert(printOut, "PCE Name",
682                            entry->pceIdentity()->enclosureName(), 3);
683             }
684         }
685         if (entry->mru())
686         {
687             const auto& mruCallouts = entry->mru()->mrus();
688             std::string mruId;
689             for (auto& element : mruCallouts)
690             {
691                 if (!mruId.empty())
692                 {
693                     mruId.append(", " + getNumberString("%08X", element.id));
694                 }
695                 else
696                 {
697                     mruId.append(getNumberString("%08X", element.id));
698                 }
699             }
700             jsonInsert(printOut, "MRU Id", mruId, 3);
701         }
702         printOut.erase(printOut.size() - 2);
703         printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
704     };
705     printOut.erase(printOut.size() - 2);
706     printOut.append("]\n" + jsonIndent + "}");
707     return printOut;
708 }
709 
710 std::optional<std::string>
711     SRC::getJSON(message::Registry& registry,
712                  const std::vector<std::string>& plugins [[maybe_unused]],
713                  uint8_t creatorID) const
714 {
715     std::string ps;
716     std::vector<std::string> hexwords;
717     jsonInsert(ps, pv::sectionVer, getNumberString("%d", _header.version), 1);
718     jsonInsert(ps, pv::subSection, getNumberString("%d", _header.subType), 1);
719     jsonInsert(ps, pv::createdBy,
720                getComponentName(_header.componentID, creatorID), 1);
721     jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
722     jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
723                1);
724     jsonInsert(ps, "Virtual Progress SRC",
725                pv::boolString.at(_flags & virtualProgressSRC), 1);
726     jsonInsert(ps, "I5/OS Service Event Bit",
727                pv::boolString.at(_flags & i5OSServiceEventBit), 1);
728     jsonInsert(ps, "Hypervisor Dump Initiated",
729                pv::boolString.at(_flags & hypDumpInit), 1);
730 
731     if (isBMCSRC())
732     {
733         std::string ccinString;
734         uint32_t ccin = _hexData[1] >> 16;
735 
736         if (ccin)
737         {
738             ccinString = getNumberString("%04X", ccin);
739         }
740         // The PEL spec calls it a backplane, so call it that here.
741         jsonInsert(ps, "Backplane CCIN", ccinString, 1);
742 
743         jsonInsert(ps, "Terminate FW Error",
744                    pv::boolString.at(
745                        _hexData[3] &
746                        static_cast<uint32_t>(ErrorStatusFlags::terminateFwErr)),
747                    1);
748     }
749 
750     if (isBMCSRC() || isHostbootSRC())
751     {
752         jsonInsert(ps, "Deconfigured",
753                    pv::boolString.at(
754                        _hexData[3] &
755                        static_cast<uint32_t>(ErrorStatusFlags::deconfigured)),
756                    1);
757 
758         jsonInsert(
759             ps, "Guarded",
760             pv::boolString.at(
761                 _hexData[3] & static_cast<uint32_t>(ErrorStatusFlags::guarded)),
762             1);
763     }
764 
765     auto errorDetails = getErrorDetails(registry, DetailLevel::json, true);
766     if (errorDetails)
767     {
768         ps.append(errorDetails.value());
769     }
770     jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
771                1);
772     std::string refcode = asciiString();
773     hexwords.push_back(refcode);
774     std::string extRefcode;
775     size_t pos = refcode.find(0x20);
776     if (pos != std::string::npos)
777     {
778         size_t nextPos = refcode.find_first_not_of(0x20, pos);
779         if (nextPos != std::string::npos)
780         {
781             extRefcode = trimEnd(refcode.substr(nextPos));
782         }
783         refcode.erase(pos);
784     }
785     jsonInsert(ps, "Reference Code", refcode, 1);
786     if (!extRefcode.empty())
787     {
788         jsonInsert(ps, "Extended Reference Code", extRefcode, 1);
789     }
790     for (size_t i = 2; i <= _wordCount; i++)
791     {
792         std::string tmpWord =
793             getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]);
794         jsonInsert(ps, "Hex Word " + std::to_string(i), tmpWord, 1);
795         hexwords.push_back(tmpWord);
796     }
797     auto calloutJson = getCallouts();
798     if (calloutJson)
799     {
800         ps.append(calloutJson.value());
801         ps.append(",\n");
802     }
803     std::string subsystem = getNumberString("%c", tolower(creatorID));
804     bool srcDetailExists = false;
805 #ifdef PELTOOL
806     if (std::find(plugins.begin(), plugins.end(), subsystem + "src") !=
807         plugins.end())
808     {
809         auto pyJson = getPythonJSON(hexwords, creatorID);
810         if (pyJson)
811         {
812             ps.append(pyJson.value());
813             srcDetailExists = true;
814         }
815     }
816 #endif
817     if (!srcDetailExists)
818     {
819         ps.erase(ps.size() - 2);
820     }
821     return ps;
822 }
823 
824 void SRC::addCallouts(const message::Entry& regEntry,
825                       const AdditionalData& additionalData,
826                       const nlohmann::json& jsonCallouts,
827                       const DataInterfaceBase& dataIface)
828 {
829     auto registryCallouts =
830         getRegistryCallouts(regEntry, additionalData, dataIface);
831 
832     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
833     auto priority = additionalData.getValue("CALLOUT_PRIORITY");
834 
835     std::optional<CalloutPriority> calloutPriority;
836 
837     // Only  H, M or L priority values.
838     if (priority && !(*priority).empty())
839     {
840         uint8_t p = (*priority)[0];
841         if (p == 'H' || p == 'M' || p == 'L')
842         {
843             calloutPriority = static_cast<CalloutPriority>(p);
844         }
845     }
846     // If the first registry callout says to use the passed in inventory
847     // path to get the location code for a symbolic FRU callout with a
848     // trusted location code, then do not add the inventory path as a
849     // normal FRU callout.
850     bool useInvForSymbolicFRULocCode =
851         !registryCallouts.empty() && registryCallouts[0].useInventoryLocCode &&
852         !registryCallouts[0].symbolicFRUTrusted.empty();
853 
854     if (item && !useInvForSymbolicFRULocCode)
855     {
856         addInventoryCallout(*item, calloutPriority, std::nullopt, dataIface);
857     }
858 
859     addDevicePathCallouts(additionalData, dataIface);
860 
861     addRegistryCallouts(registryCallouts, dataIface,
862                         (useInvForSymbolicFRULocCode) ? item : std::nullopt);
863 
864     if (!jsonCallouts.empty())
865     {
866         addJSONCallouts(jsonCallouts, dataIface);
867     }
868 }
869 
870 void SRC::addInventoryCallout(const std::string& inventoryPath,
871                               const std::optional<CalloutPriority>& priority,
872                               const std::optional<std::string>& locationCode,
873                               const DataInterfaceBase& dataIface,
874                               const std::vector<src::MRU::MRUCallout>& mrus)
875 {
876     std::string locCode;
877     std::string fn;
878     std::string ccin;
879     std::string sn;
880     std::unique_ptr<src::Callout> callout;
881 
882     try
883     {
884         // Use the passed in location code if there otherwise look it up
885         if (locationCode)
886         {
887             locCode = *locationCode;
888         }
889         else
890         {
891             locCode = dataIface.getLocationCode(inventoryPath);
892         }
893 
894         try
895         {
896             dataIface.getHWCalloutFields(inventoryPath, fn, ccin, sn);
897 
898             CalloutPriority p =
899                 priority ? priority.value() : CalloutPriority::high;
900 
901             callout =
902                 std::make_unique<src::Callout>(p, locCode, fn, ccin, sn, mrus);
903         }
904         catch (const sdbusplus::exception_t& e)
905         {
906             std::string msg =
907                 "No VPD found for " + inventoryPath + ": " + e.what();
908             addDebugData(msg);
909 
910             // Just create the callout with empty FRU fields
911             callout = std::make_unique<src::Callout>(
912                 CalloutPriority::high, locCode, fn, ccin, sn, mrus);
913         }
914     }
915     catch (const sdbusplus::exception_t& e)
916     {
917         std::string msg = "Could not get location code for " + inventoryPath +
918                           ": " + e.what();
919         addDebugData(msg);
920 
921         // Don't add a callout in this case, because:
922         // 1) With how the inventory is primed, there is no case where
923         //    a location code is expected to be missing.  This implies
924         //    the caller is passing in something invalid.
925         // 2) The addDebugData call above will put the passed in path into
926         //    a user data section that can be seen by development for debug.
927         // 3) Even if we wanted to do a 'no_vpd_for_fru' sort of maint.
928         //    procedure, we don't have a good way to indicate to the user
929         //    anything about the intended callout (they won't see user data).
930         // 4) Creating a new standalone event log for this problem isn't
931         //    possible from inside a PEL section.
932     }
933 
934     if (callout)
935     {
936         createCalloutsObject();
937         _callouts->addCallout(std::move(callout));
938     }
939 }
940 
941 std::vector<message::RegistryCallout> SRC::getRegistryCallouts(
942     const message::Entry& regEntry, const AdditionalData& additionalData,
943     const DataInterfaceBase& dataIface)
944 {
945     std::vector<message::RegistryCallout> registryCallouts;
946 
947     if (regEntry.callouts)
948     {
949         std::vector<std::string> systemNames;
950 
951         try
952         {
953             systemNames = dataIface.getSystemNames();
954         }
955         catch (const std::exception& e)
956         {
957             // Compatible interface not available yet
958         }
959 
960         try
961         {
962             registryCallouts = message::Registry::getCallouts(
963                 regEntry.callouts.value(), systemNames, additionalData);
964         }
965         catch (const std::exception& e)
966         {
967             addDebugData(std::format(
968                 "Error parsing PEL message registry callout JSON: {}",
969                 e.what()));
970         }
971     }
972 
973     return registryCallouts;
974 }
975 
976 void SRC::addRegistryCallouts(
977     const std::vector<message::RegistryCallout>& callouts,
978     const DataInterfaceBase& dataIface,
979     std::optional<std::string> trustedSymbolicFRUInvPath)
980 {
981     try
982     {
983         for (const auto& callout : callouts)
984         {
985             addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
986 
987             // Only the first callout gets the inventory path
988             if (trustedSymbolicFRUInvPath)
989             {
990                 trustedSymbolicFRUInvPath = std::nullopt;
991             }
992         }
993     }
994     catch (const std::exception& e)
995     {
996         std::string msg =
997             "Error parsing PEL message registry callout JSON: "s + e.what();
998         addDebugData(msg);
999     }
1000 }
1001 
1002 void SRC::addRegistryCallout(
1003     const message::RegistryCallout& regCallout,
1004     const DataInterfaceBase& dataIface,
1005     const std::optional<std::string>& trustedSymbolicFRUInvPath)
1006 {
1007     std::unique_ptr<src::Callout> callout;
1008     auto locCode = regCallout.locCode;
1009 
1010     if (!locCode.empty())
1011     {
1012         try
1013         {
1014             locCode = dataIface.expandLocationCode(locCode, 0);
1015         }
1016         catch (const std::exception& e)
1017         {
1018             auto msg = "Unable to expand location code " + locCode + ": " +
1019                        e.what();
1020             addDebugData(msg);
1021             return;
1022         }
1023     }
1024 
1025     // Via the PEL values table, get the priority enum.
1026     // The schema will have validated the priority was a valid value.
1027     auto priorityIt =
1028         pv::findByName(regCallout.priority, pv::calloutPriorityValues);
1029     assert(priorityIt != pv::calloutPriorityValues.end());
1030     auto priority =
1031         static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
1032 
1033     if (!regCallout.procedure.empty())
1034     {
1035         // Procedure callout
1036         callout = std::make_unique<src::Callout>(priority, regCallout.procedure,
1037                                                  src::CalloutValueType::raw);
1038     }
1039     else if (!regCallout.symbolicFRU.empty())
1040     {
1041         // Symbolic FRU callout
1042         callout = std::make_unique<src::Callout>(
1043             priority, regCallout.symbolicFRU, locCode, false);
1044     }
1045     else if (!regCallout.symbolicFRUTrusted.empty())
1046     {
1047         // Symbolic FRU with trusted location code callout
1048 
1049         // Use the location code from the inventory path if there is one.
1050         if (trustedSymbolicFRUInvPath)
1051         {
1052             try
1053             {
1054                 locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
1055             }
1056             catch (const std::exception& e)
1057             {
1058                 addDebugData(
1059                     std::format("Could not get location code for {}: {}",
1060                                 *trustedSymbolicFRUInvPath, e.what()));
1061                 locCode.clear();
1062             }
1063         }
1064 
1065         // The registry wants it to be trusted, but that requires a valid
1066         // location code for it to actually be.
1067         callout = std::make_unique<src::Callout>(
1068             priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
1069     }
1070     else
1071     {
1072         // A hardware callout
1073         std::vector<std::string> inventoryPaths;
1074 
1075         try
1076         {
1077             // Get the inventory item from the unexpanded location code
1078             inventoryPaths =
1079                 dataIface.getInventoryFromLocCode(regCallout.locCode, 0, false);
1080         }
1081         catch (const std::exception& e)
1082         {
1083             std::string msg =
1084                 "Unable to get inventory path from location code: " + locCode +
1085                 ": " + e.what();
1086             addDebugData(msg);
1087             return;
1088         }
1089 
1090         // Just use first path returned since they all point to the same FRU.
1091         addInventoryCallout(inventoryPaths[0], priority, locCode, dataIface);
1092     }
1093 
1094     if (callout)
1095     {
1096         createCalloutsObject();
1097         _callouts->addCallout(std::move(callout));
1098     }
1099 }
1100 
1101 void SRC::addDevicePathCallouts(const AdditionalData& additionalData,
1102                                 const DataInterfaceBase& dataIface)
1103 {
1104     std::vector<device_callouts::Callout> callouts;
1105     auto i2cBus = additionalData.getValue("CALLOUT_IIC_BUS");
1106     auto i2cAddr = additionalData.getValue("CALLOUT_IIC_ADDR");
1107     auto devPath = additionalData.getValue("CALLOUT_DEVICE_PATH");
1108 
1109     // A device callout contains either:
1110     // * CALLOUT_ERRNO, CALLOUT_DEVICE_PATH
1111     // * CALLOUT_ERRNO, CALLOUT_IIC_BUS, CALLOUT_IIC_ADDR
1112     // We don't care about the errno.
1113 
1114     if (devPath)
1115     {
1116         try
1117         {
1118             callouts = device_callouts::getCallouts(*devPath,
1119                                                     dataIface.getSystemNames());
1120         }
1121         catch (const std::exception& e)
1122         {
1123             addDebugData(e.what());
1124             callouts.clear();
1125         }
1126     }
1127     else if (i2cBus && i2cAddr)
1128     {
1129         size_t bus;
1130         uint8_t address;
1131 
1132         try
1133         {
1134             // If /dev/i2c- is prepended, remove it
1135             if (i2cBus->find("/dev/i2c-") != std::string::npos)
1136             {
1137                 *i2cBus = i2cBus->substr(9);
1138             }
1139 
1140             bus = stoul(*i2cBus, nullptr, 0);
1141             address = stoul(*i2cAddr, nullptr, 0);
1142         }
1143         catch (const std::exception& e)
1144         {
1145             std::string msg =
1146                 "Invalid CALLOUT_IIC_BUS " + *i2cBus + " or CALLOUT_IIC_ADDR " +
1147                 *i2cAddr + " in AdditionalData property";
1148             addDebugData(msg);
1149             return;
1150         }
1151 
1152         try
1153         {
1154             callouts = device_callouts::getI2CCallouts(
1155                 bus, address, dataIface.getSystemNames());
1156         }
1157         catch (const std::exception& e)
1158         {
1159             addDebugData(e.what());
1160             callouts.clear();
1161         }
1162     }
1163 
1164     for (const auto& callout : callouts)
1165     {
1166         // The priority shouldn't be invalid, but check just in case.
1167         CalloutPriority priority = CalloutPriority::high;
1168 
1169         if (!callout.priority.empty())
1170         {
1171             auto p = pel_values::findByValue(
1172                 static_cast<uint32_t>(callout.priority[0]),
1173                 pel_values::calloutPriorityValues);
1174 
1175             if (p != pel_values::calloutPriorityValues.end())
1176             {
1177                 priority = static_cast<CalloutPriority>(callout.priority[0]);
1178             }
1179             else
1180             {
1181                 std::string msg =
1182                     "Invalid priority found in dev callout JSON: " +
1183                     callout.priority[0];
1184                 addDebugData(msg);
1185             }
1186         }
1187 
1188         std::optional<std::string> locCode;
1189 
1190         try
1191         {
1192             locCode = dataIface.expandLocationCode(callout.locationCode, 0);
1193         }
1194         catch (const std::exception& e)
1195         {
1196             auto msg = std::format("Unable to expand location code {}: {}",
1197                                    callout.locationCode, e.what());
1198             addDebugData(msg);
1199         }
1200 
1201         try
1202         {
1203             auto inventoryPaths = dataIface.getInventoryFromLocCode(
1204                 callout.locationCode, 0, false);
1205 
1206             // Just use first path returned since they all
1207             // point to the same FRU.
1208             addInventoryCallout(inventoryPaths[0], priority, locCode,
1209                                 dataIface);
1210         }
1211         catch (const std::exception& e)
1212         {
1213             std::string msg =
1214                 "Unable to get inventory path from location code: " +
1215                 callout.locationCode + ": " + e.what();
1216             addDebugData(msg);
1217         }
1218 
1219         // Until the code is there to convert these MRU value strings to
1220         // the official MRU values in the callout objects, just store
1221         // the MRU name in the debug UserData section.
1222         if (!callout.mru.empty())
1223         {
1224             std::string msg = "MRU: " + callout.mru;
1225             addDebugData(msg);
1226         }
1227 
1228         // getCallouts() may have generated some debug data it stored
1229         // in a callout object.  Save it as well.
1230         if (!callout.debug.empty())
1231         {
1232             addDebugData(callout.debug);
1233         }
1234     }
1235 }
1236 
1237 void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
1238                           const DataInterfaceBase& dataIface)
1239 {
1240     if (jsonCallouts.empty())
1241     {
1242         return;
1243     }
1244 
1245     if (!jsonCallouts.is_array())
1246     {
1247         addDebugData("Callout JSON isn't an array");
1248         return;
1249     }
1250 
1251     for (const auto& callout : jsonCallouts)
1252     {
1253         try
1254         {
1255             addJSONCallout(callout, dataIface);
1256         }
1257         catch (const std::exception& e)
1258         {
1259             addDebugData(std::format(
1260                 "Failed extracting callout data from JSON: {}", e.what()));
1261         }
1262     }
1263 }
1264 
1265 void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
1266                          const DataInterfaceBase& dataIface)
1267 {
1268     auto priority = getPriorityFromJSON(jsonCallout);
1269     std::string locCode;
1270     std::string unexpandedLocCode;
1271     std::unique_ptr<src::Callout> callout;
1272 
1273     // Expand the location code if it's there
1274     if (jsonCallout.contains("LocationCode"))
1275     {
1276         unexpandedLocCode = jsonCallout.at("LocationCode").get<std::string>();
1277 
1278         try
1279         {
1280             locCode = dataIface.expandLocationCode(unexpandedLocCode, 0);
1281         }
1282         catch (const std::exception& e)
1283         {
1284             addDebugData(std::format("Unable to expand location code {}: {}",
1285                                      unexpandedLocCode, e.what()));
1286             // Use the value from the JSON so at least there's something
1287             locCode = unexpandedLocCode;
1288         }
1289     }
1290 
1291     // Create either a procedure, symbolic FRU, or normal FRU callout.
1292     if (jsonCallout.contains("Procedure"))
1293     {
1294         auto procedure = jsonCallout.at("Procedure").get<std::string>();
1295 
1296         // If it's the registry name instead of the raw name, convert.
1297         if (pv::maintenanceProcedures.find(procedure) !=
1298             pv::maintenanceProcedures.end())
1299         {
1300             procedure = pv::maintenanceProcedures.at(procedure);
1301         }
1302 
1303         callout = std::make_unique<src::Callout>(
1304             static_cast<CalloutPriority>(priority), procedure,
1305             src::CalloutValueType::raw);
1306     }
1307     else if (jsonCallout.contains("SymbolicFRU"))
1308     {
1309         auto fru = jsonCallout.at("SymbolicFRU").get<std::string>();
1310 
1311         // If it's the registry name instead of the raw name, convert.
1312         if (pv::symbolicFRUs.find(fru) != pv::symbolicFRUs.end())
1313         {
1314             fru = pv::symbolicFRUs.at(fru);
1315         }
1316 
1317         bool trusted = false;
1318         if (jsonCallout.contains("TrustedLocationCode") && !locCode.empty())
1319         {
1320             trusted = jsonCallout.at("TrustedLocationCode").get<bool>();
1321         }
1322 
1323         callout = std::make_unique<src::Callout>(
1324             static_cast<CalloutPriority>(priority), fru,
1325             src::CalloutValueType::raw, locCode, trusted);
1326     }
1327     else
1328     {
1329         // A hardware FRU
1330         std::string inventoryPath;
1331         std::vector<src::MRU::MRUCallout> mrus;
1332 
1333         if (jsonCallout.contains("InventoryPath"))
1334         {
1335             inventoryPath = jsonCallout.at("InventoryPath").get<std::string>();
1336         }
1337         else
1338         {
1339             if (unexpandedLocCode.empty())
1340             {
1341                 throw std::runtime_error{"JSON callout needs either an "
1342                                          "inventory path or location code"};
1343             }
1344 
1345             try
1346             {
1347                 auto inventoryPaths = dataIface.getInventoryFromLocCode(
1348                     unexpandedLocCode, 0, false);
1349                 // Just use first path returned since they all
1350                 // point to the same FRU.
1351                 inventoryPath = inventoryPaths[0];
1352             }
1353             catch (const std::exception& e)
1354             {
1355                 throw std::runtime_error{
1356                     std::format("Unable to get inventory path from "
1357                                 "location code: {}: {}",
1358                                 unexpandedLocCode, e.what())};
1359             }
1360         }
1361 
1362         if (jsonCallout.contains("MRUs"))
1363         {
1364             mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
1365         }
1366 
1367         // If the location code was also passed in, use that here too
1368         // so addInventoryCallout doesn't have to look it up.
1369         std::optional<std::string> lc;
1370         if (!locCode.empty())
1371         {
1372             lc = locCode;
1373         }
1374 
1375         addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
1376 
1377         if (jsonCallout.contains("Deconfigured"))
1378         {
1379             if (jsonCallout.at("Deconfigured").get<bool>())
1380             {
1381                 setErrorStatusFlag(ErrorStatusFlags::deconfigured);
1382             }
1383         }
1384 
1385         if (jsonCallout.contains("Guarded"))
1386         {
1387             if (jsonCallout.at("Guarded").get<bool>())
1388             {
1389                 setErrorStatusFlag(ErrorStatusFlags::guarded);
1390             }
1391         }
1392     }
1393 
1394     if (callout)
1395     {
1396         createCalloutsObject();
1397         _callouts->addCallout(std::move(callout));
1398     }
1399 }
1400 
1401 CalloutPriority SRC::getPriorityFromJSON(const nlohmann::json& json)
1402 {
1403     // Looks like:
1404     // {
1405     //     "Priority": "H"
1406     // }
1407     auto p = json.at("Priority").get<std::string>();
1408     if (p.empty())
1409     {
1410         throw std::runtime_error{"Priority field in callout is empty"};
1411     }
1412 
1413     auto priority = static_cast<CalloutPriority>(p.front());
1414 
1415     // Validate it
1416     auto priorityIt = pv::findByValue(static_cast<uint32_t>(priority),
1417                                       pv::calloutPriorityValues);
1418     if (priorityIt == pv::calloutPriorityValues.end())
1419     {
1420         throw std::runtime_error{
1421             std::format("Invalid priority '{}' found in JSON callout", p)};
1422     }
1423 
1424     return priority;
1425 }
1426 
1427 std::vector<src::MRU::MRUCallout>
1428     SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
1429 {
1430     std::vector<src::MRU::MRUCallout> mrus;
1431 
1432     // Looks like:
1433     // [
1434     //     {
1435     //         "ID": 100,
1436     //         "Priority": "H"
1437     //     }
1438     // ]
1439     if (!mruJSON.is_array())
1440     {
1441         addDebugData("MRU callout JSON is not an array");
1442         return mrus;
1443     }
1444 
1445     for (const auto& mruCallout : mruJSON)
1446     {
1447         try
1448         {
1449             auto priority = getPriorityFromJSON(mruCallout);
1450             auto id = mruCallout.at("ID").get<uint32_t>();
1451 
1452             src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
1453             mrus.push_back(std::move(mru));
1454         }
1455         catch (const std::exception& e)
1456         {
1457             addDebugData(std::format("Invalid MRU entry in JSON: {}: {}",
1458                                      mruCallout.dump(), e.what()));
1459         }
1460     }
1461 
1462     return mrus;
1463 }
1464 
1465 std::vector<uint8_t> SRC::getSrcStruct()
1466 {
1467     std::vector<uint8_t> data;
1468     Stream stream{data};
1469 
1470     //------ Ref section 4.3 in PEL doc---
1471     //------ SRC Structure 40 bytes-------
1472     // Byte-0 | Byte-1 | Byte-2 | Byte-3 |
1473     // -----------------------------------
1474     //   02   |   08   |   00   |   09   | ==> Header
1475     //   00   |   00   |   00   |   48   | ==> Header
1476     //   00   |   00   |   00   |   00   | ==> Hex data word-2
1477     //   00   |   00   |   00   |   00   | ==> Hex data word-3
1478     //   00   |   00   |   00   |   00   | ==> Hex data word-4
1479     //   20   |   00   |   00   |   00   | ==> Hex data word-5
1480     //   00   |   00   |   00   |   00   | ==> Hex data word-6
1481     //   00   |   00   |   00   |   00   | ==> Hex data word-7
1482     //   00   |   00   |   00   |   00   | ==> Hex data word-8
1483     //   00   |   00   |   00   |   00   | ==> Hex data word-9
1484     // -----------------------------------
1485     //   ASCII string - 8 bytes          |
1486     // -----------------------------------
1487     //   ASCII space NULL - 24 bytes     |
1488     // -----------------------------------
1489     //_size = Base SRC struct: 8 byte header + hex data section + ASCII string
1490 
1491     uint8_t flags = (_flags | postOPPanel);
1492 
1493     stream << _version << flags << _reserved1B << _wordCount << _reserved2B
1494            << _size;
1495 
1496     for (auto& word : _hexData)
1497     {
1498         stream << word;
1499     }
1500 
1501     _asciiString->flatten(stream);
1502 
1503     return data;
1504 }
1505 
1506 void SRC::setProgressCode(const DataInterfaceBase& dataIface)
1507 {
1508     std::vector<uint8_t> progressSRC;
1509 
1510     try
1511     {
1512         progressSRC = dataIface.getRawProgressSRC();
1513     }
1514     catch (const std::exception& e)
1515     {
1516         lg2::error("Error getting progress code: {ERROR}", "ERROR", e);
1517         return;
1518     }
1519 
1520     _hexData[2] = getProgressCode(progressSRC);
1521 }
1522 
1523 uint32_t SRC::getProgressCode(std::vector<uint8_t>& rawProgressSRC)
1524 {
1525     uint32_t progressCode = 0;
1526 
1527     // A valid progress SRC is at least 72 bytes
1528     if (rawProgressSRC.size() < 72)
1529     {
1530         return progressCode;
1531     }
1532 
1533     try
1534     {
1535         // The ASCII string field in progress SRCs starts at offset 40.
1536         // Take the first 8 characters to put in the uint32:
1537         //   "CC009189" -> 0xCC009189
1538         Stream stream{rawProgressSRC, 40};
1539         src::AsciiString aString{stream};
1540         auto progressCodeString = aString.get().substr(0, 8);
1541 
1542         if (std::all_of(progressCodeString.begin(), progressCodeString.end(),
1543                         [](char c) {
1544                             return std::isxdigit(static_cast<unsigned char>(c));
1545                         }))
1546         {
1547             progressCode = std::stoul(progressCodeString, nullptr, 16);
1548         }
1549     }
1550     catch (const std::exception& e)
1551     {}
1552 
1553     return progressCode;
1554 }
1555 
1556 } // namespace pels
1557 } // namespace openpower
1558