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