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