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 "registry.hpp"
17 
18 #include "json_utils.hpp"
19 #include "pel_types.hpp"
20 #include "pel_values.hpp"
21 
22 #include <phosphor-logging/lg2.hpp>
23 
24 #include <algorithm>
25 #include <fstream>
26 
27 namespace openpower
28 {
29 namespace pels
30 {
31 namespace message
32 {
33 
34 namespace pv = pel_values;
35 namespace fs = std::filesystem;
36 
37 constexpr auto debugFilePath = "/etc/phosphor-logging/";
38 
39 namespace helper
40 {
41 
getSubsystem(const std::string & subsystemName)42 uint8_t getSubsystem(const std::string& subsystemName)
43 {
44     // Get the actual value to use in the PEL for the string name
45     auto ss = pv::findByName(subsystemName, pv::subsystemValues);
46     if (ss == pv::subsystemValues.end())
47     {
48         // Schema validation should be catching this.
49         lg2::error("Invalid subsystem name used in message registry: {SUBSYS}",
50                    "SUBSYS", subsystemName);
51 
52         throw std::runtime_error("Invalid subsystem used in message registry");
53     }
54 
55     return std::get<pv::fieldValuePos>(*ss);
56 }
57 
getSeverity(const std::string & severityName)58 uint8_t getSeverity(const std::string& severityName)
59 {
60     auto s = pv::findByName(severityName, pv::severityValues);
61     if (s == pv::severityValues.end())
62     {
63         // Schema validation should be catching this.
64         lg2::error("Invalid severity name used in message registry: {SEV}",
65                    "SEV", severityName);
66 
67         throw std::runtime_error("Invalid severity used in message registry");
68     }
69 
70     return std::get<pv::fieldValuePos>(*s);
71 }
72 
getSeverities(const nlohmann::json & severity)73 std::vector<RegistrySeverity> getSeverities(const nlohmann::json& severity)
74 {
75     std::vector<RegistrySeverity> severities;
76 
77     // The plain string value, like "unrecoverable"
78     if (severity.is_string())
79     {
80         RegistrySeverity s;
81         s.severity = getSeverity(severity.get<std::string>());
82         severities.push_back(std::move(s));
83     }
84     else
85     {
86         // An array, with an element like:
87         // {
88         //    "SevValue": "unrecoverable",
89         //    "System", "systemA"
90         // }
91         for (const auto& sev : severity)
92         {
93             RegistrySeverity s;
94             s.severity = getSeverity(sev["SevValue"].get<std::string>());
95 
96             if (sev.contains("System"))
97             {
98                 s.system = sev["System"].get<std::string>();
99             }
100 
101             severities.push_back(std::move(s));
102         }
103     }
104 
105     return severities;
106 }
107 
getActionFlags(const std::vector<std::string> & flags)108 uint16_t getActionFlags(const std::vector<std::string>& flags)
109 {
110     uint16_t actionFlags = 0;
111 
112     // Make the bitmask based on the array of flag names
113     for (const auto& flag : flags)
114     {
115         auto s = pv::findByName(flag, pv::actionFlagsValues);
116         if (s == pv::actionFlagsValues.end())
117         {
118             // Schema validation should be catching this.
119             lg2::error(
120                 "Invalid action flag name used in message registry: {FLAG}",
121                 "FLAG", flag);
122 
123             throw std::runtime_error(
124                 "Invalid action flag used in message registry");
125         }
126 
127         actionFlags |= std::get<pv::fieldValuePos>(*s);
128     }
129 
130     return actionFlags;
131 }
132 
getEventType(const std::string & eventTypeName)133 uint8_t getEventType(const std::string& eventTypeName)
134 {
135     auto t = pv::findByName(eventTypeName, pv::eventTypeValues);
136     if (t == pv::eventTypeValues.end())
137     {
138         lg2::error("Invalid event type used in message registry: {TYPE}",
139                    "TYPE", eventTypeName);
140 
141         throw std::runtime_error("Invalid event type used in message registry");
142     }
143     return std::get<pv::fieldValuePos>(*t);
144 }
145 
getEventScope(const std::string & eventScopeName)146 uint8_t getEventScope(const std::string& eventScopeName)
147 {
148     auto s = pv::findByName(eventScopeName, pv::eventScopeValues);
149     if (s == pv::eventScopeValues.end())
150     {
151         lg2::error("Invalid event scope used in registry: {SCOPE}", "SCOPE",
152                    eventScopeName);
153 
154         throw std::runtime_error(
155             "Invalid event scope used in message registry");
156     }
157     return std::get<pv::fieldValuePos>(*s);
158 }
159 
getSRCReasonCode(const nlohmann::json & src,const std::string & name)160 uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
161 {
162     std::string rc = src["ReasonCode"];
163     uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
164     if (reasonCode == 0)
165     {
166         lg2::error(
167             "Invalid reason code {RC} in message registry, error name = {ERROR}",
168             "RC", rc, "ERROR", name);
169 
170         throw std::runtime_error("Invalid reason code in message registry");
171     }
172     return reasonCode;
173 }
174 
getSRCType(const nlohmann::json & src,const std::string & name)175 uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
176 {
177     // Looks like: "22"
178     std::string srcType = src["Type"];
179     size_t type = strtoul(srcType.c_str(), nullptr, 16);
180     if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
181     {
182         lg2::error(
183             "Invalid SRC Type {TYPE} in message registry, error name = {ERROR}",
184             "TYPE", srcType, "ERROR", name);
185 
186         throw std::runtime_error("Invalid SRC Type in message registry");
187     }
188 
189     return type;
190 }
191 
getSRCDeconfigFlag(const nlohmann::json & src)192 bool getSRCDeconfigFlag(const nlohmann::json& src)
193 {
194     return src["DeconfigFlag"].get<bool>();
195 }
196 
getSRCCheckstopFlag(const nlohmann::json & src)197 bool getSRCCheckstopFlag(const nlohmann::json& src)
198 {
199     return src["CheckstopFlag"].get<bool>();
200 }
201 
202 std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
getSRCHexwordFields(const nlohmann::json & src,const std::string & name)203     getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
204 {
205     std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
206 
207     // Build the map of which AdditionalData fields to use for which SRC words
208 
209     // Like:
210     // {
211     //   "8":
212     //   {
213     //     "AdditionalDataPropSource": "TEST"
214     //   }
215     //
216     // }
217 
218     for (const auto& word : src["Words6To9"].items())
219     {
220         std::string num = word.key();
221         size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
222 
223         if (wordNum == 0)
224         {
225             lg2::error(
226                 "Invalid SRC word number {NUM} in message registry, error name = {ERROR}",
227                 "NUM", num, "ERROR", name);
228 
229             throw std::runtime_error("Invalid SRC word in message registry");
230         }
231 
232         auto attributes = word.value();
233 
234         // Use an empty string for the description if it does not exist.
235         auto itr = attributes.find("Description");
236         std::string desc = (attributes.end() != itr) ? *itr : "";
237 
238         std::tuple<std::string, std::string> adPropSourceDesc(
239             attributes["AdditionalDataPropSource"], desc);
240         hexwordFields[wordNum] = std::move(adPropSourceDesc);
241     }
242 
243     if (!hexwordFields.empty())
244     {
245         return hexwordFields;
246     }
247 
248     return std::nullopt;
249 }
250 std::optional<std::vector<SRC::WordNum>>
getSRCSymptomIDFields(const nlohmann::json & src,const std::string & name)251     getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
252 {
253     std::vector<SRC::WordNum> symptomIDFields;
254 
255     // Looks like:
256     // "SymptomIDFields": ["SRCWord3", "SRCWord6"],
257 
258     for (const std::string field : src["SymptomIDFields"])
259     {
260         // Just need the last digit off the end, e.g. SRCWord6.
261         // The schema enforces the format of these.
262         auto srcWordNum = field.substr(field.size() - 1);
263         size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
264         if (num == 0)
265         {
266             lg2::error(
267                 "Invalid symptom ID field {FIELD} in message registry, error name = {ERROR}",
268                 "FIELD", field, "ERROR", name);
269 
270             throw std::runtime_error("Invalid symptom ID in message registry");
271         }
272         symptomIDFields.push_back(num);
273     }
274     if (!symptomIDFields.empty())
275     {
276         return symptomIDFields;
277     }
278 
279     return std::nullopt;
280 }
281 
getComponentID(uint8_t srcType,uint16_t reasonCode,const nlohmann::json & pelEntry,const std::string & name)282 uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
283                         const nlohmann::json& pelEntry, const std::string& name)
284 {
285     uint16_t id = 0;
286 
287     // If the ComponentID field is there, use that.  Otherwise, if it's a
288     // 0xBD BMC error SRC, use the reasoncode.
289     if (pelEntry.contains("ComponentID"))
290     {
291         std::string componentID = pelEntry["ComponentID"];
292         id = strtoul(componentID.c_str(), nullptr, 16);
293     }
294     else
295     {
296         // On BMC error SRCs (BD), can just get the component ID from
297         // the first byte of the reason code.
298         if (srcType == static_cast<uint8_t>(SRCType::bmcError))
299         {
300             id = reasonCode & 0xFF00;
301         }
302         else
303         {
304             lg2::error(
305                 "Missing component ID field in message registry, error name = {ERROR}",
306                 "ERROR", name);
307 
308             throw std::runtime_error(
309                 "Missing component ID field in message registry");
310         }
311     }
312 
313     return id;
314 }
315 
316 /**
317  * @brief Says if the JSON is the format that contains AdditionalData keys
318  *        as in index into them.
319  *
320  * @param[in] json - The highest level callout JSON
321  *
322  * @return bool - If it is the AdditionalData format or not
323  */
calloutUsesAdditionalData(const nlohmann::json & json)324 bool calloutUsesAdditionalData(const nlohmann::json& json)
325 {
326     return (json.contains("ADName") &&
327             json.contains("CalloutsWithTheirADValues"));
328 }
329 
330 /**
331  * @brief Finds the callouts to use when there is no AdditionalData,
332  *        but the system type may be used as a key.
333  *
334  * A sample calloutList array looks like the following.  The System and Systems
335  * key are optional.
336  *
337  * System key - Value of the key will be the system name as a string. The
338  * callouts for a specific system can define under this key.
339  *
340  * Systems key - Value of the key will be an array of system names in the form
341  * of string. The callouts common to the systems mentioned in the array can
342  * define under this key.
343  *
344  * If both System and Systems not present it means that entry applies to every
345  * configuration that doesn't have another entry with a matching System and
346  * Systems key.
347  *
348  *    {
349  *        "System": "system1",
350  *        "CalloutList":
351  *        [
352  *            {
353  *                "Priority": "high",
354  *                "LocCode": "P1-C1"
355  *            },
356  *            {
357  *                "Priority": "low",
358  *                "LocCode": "P1"
359  *            }
360  *        ]
361  *    },
362  *    {
363  *        "Systems": ["system1", 'system2"],
364  *        "CalloutList":
365  *        [
366  *            {
367  *                "Priority": "high",
368  *                "LocCode": "P0-C1"
369  *            },
370  *            {
371  *                "Priority": "low",
372  *                "LocCode": "P0"
373  *            }
374  *        ]
375  *    }
376  *
377  * @param[in] json - The callout JSON
378  * @param[in] systemNames - List of compatible system type names
379  * @param[out] calloutLists - The JSON array which will hold the calloutlist to
380  * use specific to the system.
381  *
382  * @return - Throws runtime exception if json is not an array or if calloutLists
383  *           is empty.
384  */
findCalloutList(const nlohmann::json & json,const std::vector<std::string> & systemNames,nlohmann::json & calloutLists)385 static void findCalloutList(const nlohmann::json& json,
386                             const std::vector<std::string>& systemNames,
387                             nlohmann::json& calloutLists)
388 {
389     if (!json.is_array())
390     {
391         throw std::runtime_error{"findCalloutList was not passed a JSON array"};
392     }
393 
394     // Flag to indicate whether system specific callouts found or not
395     bool foundCallouts = false;
396 
397     for (const auto& callouts : json)
398     {
399         if (callouts.contains("System"))
400         {
401             if (std::ranges::find(systemNames,
402                                   callouts["System"].get<std::string>()) !=
403                 systemNames.end())
404             {
405                 calloutLists.insert(calloutLists.end(),
406                                     callouts["CalloutList"].begin(),
407                                     callouts["CalloutList"].end());
408                 foundCallouts = true;
409             }
410             continue;
411         }
412 
413         if (callouts.contains("Systems"))
414         {
415             std::vector<std::string> systems =
416                 callouts["Systems"].get<std::vector<std::string>>();
417             auto inSystemNames = [systemNames](const auto& system) {
418                 return (std::ranges::find(systemNames, system) !=
419                         systemNames.end());
420             };
421             if (std::ranges::any_of(systems, inSystemNames))
422             {
423                 calloutLists.insert(calloutLists.end(),
424                                     callouts["CalloutList"].begin(),
425                                     callouts["CalloutList"].end());
426                 foundCallouts = true;
427             }
428             continue;
429         }
430 
431         // Any entry if neither System/Systems key matches with system name
432         if (!foundCallouts)
433         {
434             calloutLists.insert(calloutLists.end(),
435                                 callouts["CalloutList"].begin(),
436                                 callouts["CalloutList"].end());
437         }
438     }
439     if (calloutLists.empty())
440     {
441         std::string types;
442         std::for_each(systemNames.begin(), systemNames.end(),
443                       [&types](const auto& t) { types += t + '|'; });
444         lg2::warning(
445             "No matching system name entry or default system name entry "
446             " for PEL callout list, names = {TYPES}",
447             "TYPES", types);
448 
449         throw std::runtime_error{
450             "Could not find a CalloutList JSON for this error and system name"};
451     }
452 }
453 
454 /**
455  * @brief Creates a RegistryCallout based on the input JSON.
456  *
457  * The JSON looks like:
458  *     {
459  *          "Priority": "high",
460  *          "LocCode": "E1"
461  *          ...
462  *     }
463  *
464  * Schema validation enforces what keys are present.
465  *
466  * @param[in] json - The JSON dictionary entry for a callout
467  *
468  * @return RegistryCallout - A filled in RegistryCallout
469  */
makeRegistryCallout(const nlohmann::json & json)470 RegistryCallout makeRegistryCallout(const nlohmann::json& json)
471 {
472     RegistryCallout callout;
473 
474     callout.priority = "high";
475     callout.useInventoryLocCode = false;
476 
477     if (json.contains("Priority"))
478     {
479         callout.priority = json["Priority"].get<std::string>();
480     }
481 
482     if (json.contains("LocCode"))
483     {
484         callout.locCode = json["LocCode"].get<std::string>();
485     }
486 
487     if (json.contains("Procedure"))
488     {
489         callout.procedure = json["Procedure"].get<std::string>();
490     }
491     else if (json.contains("SymbolicFRU"))
492     {
493         callout.symbolicFRU = json["SymbolicFRU"].get<std::string>();
494     }
495     else if (json.contains("SymbolicFRUTrusted"))
496     {
497         callout.symbolicFRUTrusted =
498             json["SymbolicFRUTrusted"].get<std::string>();
499     }
500 
501     if (json.contains("UseInventoryLocCode"))
502     {
503         callout.useInventoryLocCode = json["UseInventoryLocCode"].get<bool>();
504     }
505 
506     return callout;
507 }
508 
509 /**
510  * @brief Returns the callouts to use when an AdditionalData key is
511  *        required to find the correct entries.
512  *
513  *       The System property is used to find which CalloutList to use.
514  *       If System is missing, then that CalloutList is valid for
515  *       everything.
516  *
517  * The JSON looks like:
518  *    {
519  *        "System": "system1",
520  *        "CalloutList":
521  *        [
522  *            {
523  *                "Priority": "high",
524  *                "LocCode": "P1-C1"
525  *            },
526  *            {
527  *                "Priority": "low",
528  *                "LocCode": "P1"
529  *            }
530  *        ]
531  *    },
532  *    {
533  *        "Systems": ["system1", 'system2"],
534  *        "CalloutList":
535  *        [
536  *            {
537  *                "Priority": "high",
538  *                "LocCode": "P0-C1"
539  *            },
540  *            {
541  *                "Priority": "low",
542  *                "LocCode": "P0"
543  *            }
544  *        ]
545  *    }
546  *
547  * @param[in] json - The callout JSON
548  * @param[in] systemNames - List of compatible system type names
549  *
550  * @return std::vector<RegistryCallout> - The callouts to use
551  */
getCalloutsWithoutAD(const nlohmann::json & json,const std::vector<std::string> & systemNames)552 std::vector<RegistryCallout> getCalloutsWithoutAD(
553     const nlohmann::json& json, const std::vector<std::string>& systemNames)
554 {
555     std::vector<RegistryCallout> calloutEntries;
556 
557     nlohmann::json calloutLists = nlohmann::json::array();
558 
559     // Find the CalloutList to use based on the system type
560     findCalloutList(json, systemNames, calloutLists);
561 
562     // We finally found the callouts, make the objects.
563     for (const auto& callout : calloutLists)
564     {
565         calloutEntries.push_back(std::move(makeRegistryCallout(callout)));
566     }
567 
568     return calloutEntries;
569 }
570 
571 /**
572  * @brief Returns the callouts to use when an AdditionalData key is
573  *        required to find the correct entries.
574  *
575  * The JSON looks like:
576  *    {
577  *        "ADName": "PROC_NUM",
578  *        "CalloutsWithTheirADValues":
579  *        [
580  *            {
581  *                "ADValue": "0",
582  *                "Callouts":
583  *                [
584  *                    {
585  *                        "CalloutList":
586  *                        [
587  *                            {
588  *                                "Priority": "high",
589  *                                "LocCode": "P1-C5"
590  *                            }
591  *                        ]
592  *                    }
593  *                ]
594  *            }
595  *        ]
596  *     }
597  *
598  * Note that the "Callouts" entry above is the same as the top level
599  * entry used when there is no AdditionalData key.
600  *
601  * @param[in] json - The callout JSON
602  * @param[in] systemNames - List of compatible system type names
603  * @param[in] additionalData - The AdditionalData property
604  *
605  * @return std::vector<RegistryCallout> - The callouts to use
606  */
getCalloutsUsingAD(const nlohmann::json & json,const std::vector<std::string> & systemNames,const AdditionalData & additionalData)607 std::vector<RegistryCallout> getCalloutsUsingAD(
608     const nlohmann::json& json, const std::vector<std::string>& systemNames,
609     const AdditionalData& additionalData)
610 {
611     // This indicates which AD field we'll be using
612     auto keyName = json["ADName"].get<std::string>();
613 
614     // Get the actual value from the AD data
615     auto adValue = additionalData.getValue(keyName);
616 
617     if (!adValue)
618     {
619         // The AdditionalData did not contain the necessary key
620         lg2::warning("The PEL message registry callouts JSON "
621                      "said to use an AdditionalData key that isn't in the "
622                      "AdditionalData event log property, key = {KEY}",
623                      "KEY", keyName);
624         throw std::runtime_error{
625             "Missing AdditionalData entry for this callout"};
626     }
627 
628     const auto& callouts = json["CalloutsWithTheirADValues"];
629 
630     // find the entry with that AD value
631     auto it = std::find_if(
632         callouts.begin(), callouts.end(), [adValue](const nlohmann::json& j) {
633             return *adValue == j["ADValue"].get<std::string>();
634         });
635 
636     if (it == callouts.end())
637     {
638         // This can happen if not all possible values were in the
639         // message registry and that's fine.  There may be a
640         // "CalloutsWhenNoADMatch" section that contains callouts
641         // to use in this case.
642         if (json.contains("CalloutsWhenNoADMatch"))
643         {
644             return getCalloutsWithoutAD(json["CalloutsWhenNoADMatch"],
645                                         systemNames);
646         }
647         return std::vector<RegistryCallout>{};
648     }
649 
650     // Proceed to find the callouts possibly based on system type.
651     return getCalloutsWithoutAD((*it)["Callouts"], systemNames);
652 }
653 
654 /**
655  * @brief Returns the journal capture information
656  *
657  *  The JSON looks like:
658  *    "JournalCapture": {
659  *        "NumLines": 30
660  *    }
661  *
662  *    "JournalCapture":
663  *    {
664  *        "Sections": [
665  *            {
666  *                "SyslogID": "phosphor-log-manager",
667  *                "NumLines": 20
668  *            }
669  *        ]
670  *    }
671  *
672  * @param json - The journal capture JSON
673  * @return JournalCapture - The filled in variant
674  */
getJournalCapture(const nlohmann::json & json)675 JournalCapture getJournalCapture(const nlohmann::json& json)
676 {
677     JournalCapture capt;
678 
679     // Primary key is either NumLines or Sections.
680     if (json.contains("NumLines"))
681     {
682         capt = json.at("NumLines").get<size_t>();
683     }
684     else if (json.contains("Sections"))
685     {
686         AppCaptureList captures;
687         for (const auto& capture : json.at("Sections"))
688         {
689             AppCapture ac;
690             ac.syslogID = capture.at("SyslogID").get<std::string>();
691             ac.numLines = capture.at("NumLines").get<size_t>();
692             captures.push_back(std::move(ac));
693         }
694 
695         capt = captures;
696     }
697     else
698     {
699         lg2::error("JournalCapture section not the right format");
700         throw std::runtime_error{"JournalCapture section not the right format"};
701     }
702 
703     return capt;
704 }
705 
706 } // namespace helper
707 
lookup(const std::string & name,LookupType type,bool toCache)708 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
709                                       bool toCache)
710 {
711     std::optional<nlohmann::json> registryTmp;
712     auto& registryOpt = (_registry) ? _registry : registryTmp;
713     if (!registryOpt)
714     {
715         registryOpt = readRegistry(_registryFile);
716         if (!registryOpt)
717         {
718             return std::nullopt;
719         }
720         else if (toCache)
721         {
722             // Save message registry in memory for peltool
723             _registry = std::move(registryTmp);
724         }
725     }
726     auto& reg = (_registry) ? _registry : registryTmp;
727     const auto& registry = reg.value();
728     // Find an entry with this name in the PEL array.
729     auto e = std::find_if(
730         registry["PELs"].begin(), registry["PELs"].end(),
731         [&name, &type](const nlohmann::json& j) {
732             return ((name == j.at("Name").get<std::string>() &&
733                      type == LookupType::name) ||
734                     (name == j.at("SRC").at("ReasonCode").get<std::string>() &&
735                      type == LookupType::reasonCode));
736         });
737 
738     if (e != registry["PELs"].end())
739     {
740         // Fill in the Entry structure from the JSON.  Most, but not all, fields
741         // are optional.
742 
743         try
744         {
745             Entry entry;
746             entry.name = (*e)["Name"];
747 
748             if (e->contains("Subsystem"))
749             {
750                 entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
751             }
752 
753             if (e->contains("ActionFlags"))
754             {
755                 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
756             }
757 
758             if (e->contains("MfgActionFlags"))
759             {
760                 entry.mfgActionFlags =
761                     helper::getActionFlags((*e)["MfgActionFlags"]);
762             }
763 
764             if (e->contains("Severity"))
765             {
766                 entry.severity = helper::getSeverities((*e)["Severity"]);
767             }
768 
769             if (e->contains("MfgSeverity"))
770             {
771                 entry.mfgSeverity = helper::getSeverities((*e)["MfgSeverity"]);
772             }
773 
774             if (e->contains("EventType"))
775             {
776                 entry.eventType = helper::getEventType((*e)["EventType"]);
777             }
778 
779             if (e->contains("EventScope"))
780             {
781                 entry.eventScope = helper::getEventScope((*e)["EventScope"]);
782             }
783 
784             auto& src = (*e)["SRC"];
785             entry.src.reasonCode = helper::getSRCReasonCode(src, name);
786 
787             if (src.contains("Type"))
788             {
789                 entry.src.type = helper::getSRCType(src, name);
790             }
791             else
792             {
793                 entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
794             }
795 
796             // Now that we know the SRC type and reason code,
797             // we can get the component ID.
798             entry.componentID = helper::getComponentID(
799                 entry.src.type, entry.src.reasonCode, *e, name);
800 
801             if (src.contains("Words6To9"))
802             {
803                 entry.src.hexwordADFields =
804                     helper::getSRCHexwordFields(src, name);
805             }
806 
807             if (src.contains("SymptomIDFields"))
808             {
809                 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
810             }
811 
812             if (src.contains("DeconfigFlag"))
813             {
814                 entry.src.deconfigFlag = helper::getSRCDeconfigFlag(src);
815             }
816 
817             if (src.contains("CheckstopFlag"))
818             {
819                 entry.src.checkstopFlag = helper::getSRCCheckstopFlag(src);
820             }
821 
822             auto& doc = (*e)["Documentation"];
823             entry.doc.message = doc["Message"];
824             entry.doc.description = doc["Description"];
825             if (doc.contains("MessageArgSources"))
826             {
827                 entry.doc.messageArgSources = doc["MessageArgSources"];
828             }
829 
830             // If there are callouts defined, save the JSON for later
831             if (_loadCallouts)
832             {
833                 if (e->contains("Callouts"))
834                 {
835                     entry.callouts = (*e)["Callouts"];
836                 }
837                 else if (e->contains("CalloutsUsingAD"))
838                 {
839                     entry.callouts = (*e)["CalloutsUsingAD"];
840                 }
841             }
842 
843             if (e->contains("JournalCapture"))
844             {
845                 entry.journalCapture =
846                     helper::getJournalCapture((*e)["JournalCapture"]);
847             }
848 
849             return entry;
850         }
851         catch (const std::exception& ex)
852         {
853             lg2::error("Found invalid message registry field. Error: {ERROR}",
854                        "ERROR", ex);
855         }
856     }
857 
858     return std::nullopt;
859 }
860 
861 std::optional<nlohmann::json>
readRegistry(const std::filesystem::path & registryFile)862     Registry::readRegistry(const std::filesystem::path& registryFile)
863 {
864     // Look in /etc first in case someone put a test file there
865     fs::path debugFile{fs::path{debugFilePath} / registryFileName};
866     nlohmann::json registry;
867     std::ifstream file;
868 
869     if (fs::exists(debugFile))
870     {
871         lg2::info("Using debug PEL message registry");
872         file.open(debugFile);
873     }
874     else
875     {
876         file.open(registryFile);
877     }
878 
879     try
880     {
881         registry = nlohmann::json::parse(file);
882     }
883     catch (const std::exception& e)
884     {
885         lg2::error("Error parsing message registry JSON. Error: {ERROR}",
886                    "ERROR", e);
887         return std::nullopt;
888     }
889     return registry;
890 }
891 
892 std::vector<RegistryCallout>
getCallouts(const nlohmann::json & calloutJSON,const std::vector<std::string> & systemNames,const AdditionalData & additionalData)893     Registry::getCallouts(const nlohmann::json& calloutJSON,
894                           const std::vector<std::string>& systemNames,
895                           const AdditionalData& additionalData)
896 {
897     // The JSON may either use an AdditionalData key
898     // as an index, or not.
899     if (helper::calloutUsesAdditionalData(calloutJSON))
900     {
901         return helper::getCalloutsUsingAD(calloutJSON, systemNames,
902                                           additionalData);
903     }
904 
905     return helper::getCalloutsWithoutAD(calloutJSON, systemNames);
906 }
907 
908 } // namespace message
909 } // namespace pels
910 } // namespace openpower
911