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