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