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