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