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 
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 
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 
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 
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 
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 
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 
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 
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 
192 bool getSRCDeconfigFlag(const nlohmann::json& src)
193 {
194     return src["DeconfigFlag"].get<bool>();
195 }
196 
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>>
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>>
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 
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  */
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  */
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  */
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  */
552 std::vector<RegistryCallout>
553     getCalloutsWithoutAD(const nlohmann::json& json,
554                          const std::vector<std::string>& systemNames)
555 {
556     std::vector<RegistryCallout> calloutEntries;
557 
558     nlohmann::json calloutLists = nlohmann::json::array();
559 
560     // Find the CalloutList to use based on the system type
561     findCalloutList(json, systemNames, calloutLists);
562 
563     // We finally found the callouts, make the objects.
564     for (const auto& callout : calloutLists)
565     {
566         calloutEntries.push_back(std::move(makeRegistryCallout(callout)));
567     }
568 
569     return calloutEntries;
570 }
571 
572 /**
573  * @brief Returns the callouts to use when an AdditionalData key is
574  *        required to find the correct entries.
575  *
576  * The JSON looks like:
577  *    {
578  *        "ADName": "PROC_NUM",
579  *        "CalloutsWithTheirADValues":
580  *        [
581  *            {
582  *                "ADValue": "0",
583  *                "Callouts":
584  *                [
585  *                    {
586  *                        "CalloutList":
587  *                        [
588  *                            {
589  *                                "Priority": "high",
590  *                                "LocCode": "P1-C5"
591  *                            }
592  *                        ]
593  *                    }
594  *                ]
595  *            }
596  *        ]
597  *     }
598  *
599  * Note that the "Callouts" entry above is the same as the top level
600  * entry used when there is no AdditionalData key.
601  *
602  * @param[in] json - The callout JSON
603  * @param[in] systemNames - List of compatible system type names
604  * @param[in] additionalData - The AdditionalData property
605  *
606  * @return std::vector<RegistryCallout> - The callouts to use
607  */
608 std::vector<RegistryCallout>
609     getCalloutsUsingAD(const nlohmann::json& json,
610                        const std::vector<std::string>& systemNames,
611                        const AdditionalData& additionalData)
612 {
613     // This indicates which AD field we'll be using
614     auto keyName = json["ADName"].get<std::string>();
615 
616     // Get the actual value from the AD data
617     auto adValue = additionalData.getValue(keyName);
618 
619     if (!adValue)
620     {
621         // The AdditionalData did not contain the necessary key
622         lg2::warning("The PEL message registry callouts JSON "
623                      "said to use an AdditionalData key that isn't in the "
624                      "AdditionalData event log property, key = {KEY}",
625                      "KEY", keyName);
626         throw std::runtime_error{
627             "Missing AdditionalData entry for this callout"};
628     }
629 
630     const auto& callouts = json["CalloutsWithTheirADValues"];
631 
632     // find the entry with that AD value
633     auto it = std::find_if(callouts.begin(), callouts.end(),
634                            [adValue](const nlohmann::json& j) {
635         return *adValue == j["ADValue"].get<std::string>();
636     });
637 
638     if (it == callouts.end())
639     {
640         // This can happen if not all possible values were in the
641         // message registry and that's fine.  There may be a
642         // "CalloutsWhenNoADMatch" section that contains callouts
643         // to use in this case.
644         if (json.contains("CalloutsWhenNoADMatch"))
645         {
646             return getCalloutsWithoutAD(json["CalloutsWhenNoADMatch"],
647                                         systemNames);
648         }
649         return std::vector<RegistryCallout>{};
650     }
651 
652     // Proceed to find the callouts possibly based on system type.
653     return getCalloutsWithoutAD((*it)["Callouts"], systemNames);
654 }
655 
656 /**
657  * @brief Returns the journal capture information
658  *
659  *  The JSON looks like:
660  *    "JournalCapture": {
661  *        "NumLines": 30
662  *    }
663  *
664  *    "JournalCapture":
665  *    {
666  *        "Sections": [
667  *            {
668  *                "SyslogID": "phosphor-log-manager",
669  *                "NumLines": 20
670  *            }
671  *        ]
672  *    }
673  *
674  * @param json - The journal capture JSON
675  * @return JournalCapture - The filled in variant
676  */
677 JournalCapture getJournalCapture(const nlohmann::json& json)
678 {
679     JournalCapture capt;
680 
681     // Primary key is either NumLines or Sections.
682     if (json.contains("NumLines"))
683     {
684         capt = json.at("NumLines").get<size_t>();
685     }
686     else if (json.contains("Sections"))
687     {
688         AppCaptureList captures;
689         for (const auto& capture : json.at("Sections"))
690         {
691             AppCapture ac;
692             ac.syslogID = capture.at("SyslogID").get<std::string>();
693             ac.numLines = capture.at("NumLines").get<size_t>();
694             captures.push_back(std::move(ac));
695         }
696 
697         capt = captures;
698     }
699     else
700     {
701         lg2::error("JournalCapture section not the right format");
702         throw std::runtime_error{"JournalCapture section not the right format"};
703     }
704 
705     return capt;
706 }
707 
708 } // namespace helper
709 
710 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
711                                       bool toCache)
712 {
713     std::optional<nlohmann::json> registryTmp;
714     auto& registryOpt = (_registry) ? _registry : registryTmp;
715     if (!registryOpt)
716     {
717         registryOpt = readRegistry(_registryFile);
718         if (!registryOpt)
719         {
720             return std::nullopt;
721         }
722         else if (toCache)
723         {
724             // Save message registry in memory for peltool
725             _registry = std::move(registryTmp);
726         }
727     }
728     auto& reg = (_registry) ? _registry : registryTmp;
729     const auto& registry = reg.value();
730     // Find an entry with this name in the PEL array.
731     auto e = std::find_if(registry["PELs"].begin(), registry["PELs"].end(),
732                           [&name, &type](const nlohmann::json& j) {
733         return ((name == j.at("Name").get<std::string>() &&
734                  type == LookupType::name) ||
735                 (name == j.at("SRC").at("ReasonCode").get<std::string>() &&
736                  type == LookupType::reasonCode));
737     });
738 
739     if (e != registry["PELs"].end())
740     {
741         // Fill in the Entry structure from the JSON.  Most, but not all, fields
742         // are optional.
743 
744         try
745         {
746             Entry entry;
747             entry.name = (*e)["Name"];
748 
749             if (e->contains("Subsystem"))
750             {
751                 entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
752             }
753 
754             if (e->contains("ActionFlags"))
755             {
756                 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
757             }
758 
759             if (e->contains("MfgActionFlags"))
760             {
761                 entry.mfgActionFlags =
762                     helper::getActionFlags((*e)["MfgActionFlags"]);
763             }
764 
765             if (e->contains("Severity"))
766             {
767                 entry.severity = helper::getSeverities((*e)["Severity"]);
768             }
769 
770             if (e->contains("MfgSeverity"))
771             {
772                 entry.mfgSeverity = helper::getSeverities((*e)["MfgSeverity"]);
773             }
774 
775             if (e->contains("EventType"))
776             {
777                 entry.eventType = helper::getEventType((*e)["EventType"]);
778             }
779 
780             if (e->contains("EventScope"))
781             {
782                 entry.eventScope = helper::getEventScope((*e)["EventScope"]);
783             }
784 
785             auto& src = (*e)["SRC"];
786             entry.src.reasonCode = helper::getSRCReasonCode(src, name);
787 
788             if (src.contains("Type"))
789             {
790                 entry.src.type = helper::getSRCType(src, name);
791             }
792             else
793             {
794                 entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
795             }
796 
797             // Now that we know the SRC type and reason code,
798             // we can get the component ID.
799             entry.componentID = helper::getComponentID(
800                 entry.src.type, entry.src.reasonCode, *e, name);
801 
802             if (src.contains("Words6To9"))
803             {
804                 entry.src.hexwordADFields = helper::getSRCHexwordFields(src,
805                                                                         name);
806             }
807 
808             if (src.contains("SymptomIDFields"))
809             {
810                 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
811             }
812 
813             if (src.contains("DeconfigFlag"))
814             {
815                 entry.src.deconfigFlag = helper::getSRCDeconfigFlag(src);
816             }
817 
818             if (src.contains("CheckstopFlag"))
819             {
820                 entry.src.checkstopFlag = helper::getSRCCheckstopFlag(src);
821             }
822 
823             auto& doc = (*e)["Documentation"];
824             entry.doc.message = doc["Message"];
825             entry.doc.description = doc["Description"];
826             if (doc.contains("MessageArgSources"))
827             {
828                 entry.doc.messageArgSources = doc["MessageArgSources"];
829             }
830 
831             // If there are callouts defined, save the JSON for later
832             if (_loadCallouts)
833             {
834                 if (e->contains("Callouts"))
835                 {
836                     entry.callouts = (*e)["Callouts"];
837                 }
838                 else if (e->contains("CalloutsUsingAD"))
839                 {
840                     entry.callouts = (*e)["CalloutsUsingAD"];
841                 }
842             }
843 
844             if (e->contains("JournalCapture"))
845             {
846                 entry.journalCapture =
847                     helper::getJournalCapture((*e)["JournalCapture"]);
848             }
849 
850             return entry;
851         }
852         catch (const std::exception& ex)
853         {
854             lg2::error("Found invalid message registry field. Error: {ERROR}",
855                        "ERROR", ex);
856         }
857     }
858 
859     return std::nullopt;
860 }
861 
862 std::optional<nlohmann::json>
863     Registry::readRegistry(const std::filesystem::path& registryFile)
864 {
865     // Look in /etc first in case someone put a test file there
866     fs::path debugFile{fs::path{debugFilePath} / registryFileName};
867     nlohmann::json registry;
868     std::ifstream file;
869 
870     if (fs::exists(debugFile))
871     {
872         lg2::info("Using debug PEL message registry");
873         file.open(debugFile);
874     }
875     else
876     {
877         file.open(registryFile);
878     }
879 
880     try
881     {
882         registry = nlohmann::json::parse(file);
883     }
884     catch (const std::exception& e)
885     {
886         lg2::error("Error parsing message registry JSON. Error: {ERROR}",
887                    "ERROR", e);
888         return std::nullopt;
889     }
890     return registry;
891 }
892 
893 std::vector<RegistryCallout>
894     Registry::getCallouts(const nlohmann::json& calloutJSON,
895                           const std::vector<std::string>& systemNames,
896                           const AdditionalData& additionalData)
897 {
898     // The JSON may either use an AdditionalData key
899     // as an index, or not.
900     if (helper::calloutUsesAdditionalData(calloutJSON))
901     {
902         return helper::getCalloutsUsingAD(calloutJSON, systemNames,
903                                           additionalData);
904     }
905 
906     return helper::getCalloutsWithoutAD(calloutJSON, systemNames);
907 }
908 
909 } // namespace message
910 } // namespace pels
911 } // namespace openpower
912