1 #pragma once 2 3 #include "config.h" 4 5 #include "constants.hpp" 6 #include "event_logger.hpp" 7 #include "exceptions.hpp" 8 #include "logger.hpp" 9 #include "types.hpp" 10 11 #include <nlohmann/json.hpp> 12 #include <utility/common_utility.hpp> 13 #include <utility/dbus_utility.hpp> 14 15 #include <filesystem> 16 #include <fstream> 17 #include <regex> 18 #include <typeindex> 19 20 namespace vpd 21 { 22 namespace vpdSpecificUtility 23 { 24 /** 25 * @brief API to generate file name for bad VPD. 26 * 27 * For i2c eeproms - the pattern of the vpd-name will be 28 * i2c-<bus-number>-<eeprom-address>. 29 * For spi eeproms - the pattern of the vpd-name will be spi-<spi-number>. 30 * 31 * @param[in] i_vpdFilePath - file path of the vpd. 32 * 33 * @return On success, returns generated file name, otherwise returns empty 34 * string. 35 */ 36 inline std::string generateBadVPDFileName( 37 const std::string& i_vpdFilePath) noexcept 38 { 39 std::string l_badVpdFileName{BAD_VPD_DIR}; 40 try 41 { 42 if (i_vpdFilePath.find("i2c") != std::string::npos) 43 { 44 l_badVpdFileName += "i2c-"; 45 std::regex l_i2cPattern("(at24/)([0-9]+-[0-9]+)\\/"); 46 std::smatch l_match; 47 if (std::regex_search(i_vpdFilePath, l_match, l_i2cPattern)) 48 { 49 l_badVpdFileName += l_match.str(2); 50 } 51 } 52 else if (i_vpdFilePath.find("spi") != std::string::npos) 53 { 54 std::regex l_spiPattern("((spi)[0-9]+)(.0)"); 55 std::smatch l_match; 56 if (std::regex_search(i_vpdFilePath, l_match, l_spiPattern)) 57 { 58 l_badVpdFileName += l_match.str(1); 59 } 60 } 61 } 62 catch (const std::exception& l_ex) 63 { 64 l_badVpdFileName.clear(); 65 logging::logMessage("Failed to generate bad VPD file name for [" + 66 i_vpdFilePath + "]. Error: " + l_ex.what()); 67 } 68 return l_badVpdFileName; 69 } 70 71 /** 72 * @brief API which dumps the broken/bad vpd in a directory. 73 * When the vpd is bad, this API places the bad vpd file inside 74 * "/tmp/bad-vpd" in BMC, in order to collect bad VPD data as a part of user 75 * initiated BMC dump. 76 * 77 * 78 * @param[in] i_vpdFilePath - vpd file path 79 * @param[in] i_vpdVector - vpd vector 80 * 81 * @return On success returns 0, otherwise returns -1. 82 */ 83 inline int dumpBadVpd(const std::string& i_vpdFilePath, 84 const types::BinaryVector& i_vpdVector) noexcept 85 { 86 int l_rc{constants::FAILURE}; 87 try 88 { 89 std::filesystem::create_directory(BAD_VPD_DIR); 90 auto l_badVpdPath = generateBadVPDFileName(i_vpdFilePath); 91 92 if (l_badVpdPath.empty()) 93 { 94 throw std::runtime_error("Failed to generate bad VPD file name"); 95 } 96 97 if (std::filesystem::exists(l_badVpdPath)) 98 { 99 std::error_code l_ec; 100 std::filesystem::remove(l_badVpdPath, l_ec); 101 if (l_ec) // error code 102 { 103 const std::string l_errorMsg{ 104 "Error removing the existing broken vpd in " + 105 l_badVpdPath + 106 ". Error code : " + std::to_string(l_ec.value()) + 107 ". Error message : " + l_ec.message()}; 108 109 throw std::runtime_error(l_errorMsg); 110 } 111 } 112 113 std::ofstream l_badVpdFileStream(l_badVpdPath, std::ofstream::binary); 114 if (!l_badVpdFileStream.is_open()) 115 { 116 throw std::runtime_error( 117 "Failed to open bad vpd file path in /tmp/bad-vpd. " 118 "Unable to dump the broken/bad vpd file."); 119 } 120 121 l_badVpdFileStream.write( 122 reinterpret_cast<const char*>(i_vpdVector.data()), 123 i_vpdVector.size()); 124 125 l_rc = constants::SUCCESS; 126 } 127 catch (const std::exception& l_ex) 128 { 129 logging::logMessage("Failed to dump bad VPD for [" + i_vpdFilePath + 130 "]. Error: " + l_ex.what()); 131 } 132 return l_rc; 133 } 134 135 /** 136 * @brief An API to read value of a keyword. 137 * 138 * Note: Throws exception. Caller needs to handle. 139 * 140 * @param[in] kwdValueMap - A map having Kwd value pair. 141 * @param[in] kwd - keyword name. 142 * @param[out] kwdValue - Value of the keyword read from map. 143 */ 144 inline void getKwVal(const types::IPZKwdValueMap& kwdValueMap, 145 const std::string& kwd, std::string& kwdValue) 146 { 147 if (kwd.empty()) 148 { 149 logging::logMessage("Invalid parameters"); 150 throw std::runtime_error("Invalid parameters"); 151 } 152 153 auto itrToKwd = kwdValueMap.find(kwd); 154 if (itrToKwd != kwdValueMap.end()) 155 { 156 kwdValue = itrToKwd->second; 157 return; 158 } 159 160 throw std::runtime_error("Keyword not found"); 161 } 162 163 /** 164 * @brief An API to process encoding of a keyword. 165 * 166 * @param[in] i_keyword - Keyword to be processed. 167 * @param[in] i_encoding - Type of encoding. 168 * 169 * @return Value after being processed for encoded type. 170 */ 171 inline std::string encodeKeyword(const std::string& i_keyword, 172 const std::string& i_encoding) noexcept 173 { 174 // Default value is keyword value 175 std::string l_result(i_keyword.begin(), i_keyword.end()); 176 try 177 { 178 if (i_encoding == "MAC") 179 { 180 l_result.clear(); 181 size_t l_firstByte = i_keyword[0]; 182 l_result += commonUtility::toHex(l_firstByte >> 4); 183 l_result += commonUtility::toHex(l_firstByte & 0x0f); 184 for (size_t i = 1; i < i_keyword.size(); ++i) 185 { 186 l_result += ":"; 187 l_result += commonUtility::toHex(i_keyword[i] >> 4); 188 l_result += commonUtility::toHex(i_keyword[i] & 0x0f); 189 } 190 } 191 else if (i_encoding == "DATE") 192 { 193 // Date, represent as 194 // <year>-<month>-<day> <hour>:<min> 195 l_result.clear(); 196 static constexpr uint8_t skipPrefix = 3; 197 198 auto strItr = i_keyword.begin(); 199 advance(strItr, skipPrefix); 200 for_each(strItr, i_keyword.end(), 201 [&l_result](size_t c) { l_result += c; }); 202 203 l_result.insert(constants::BD_YEAR_END, 1, '-'); 204 l_result.insert(constants::BD_MONTH_END, 1, '-'); 205 l_result.insert(constants::BD_DAY_END, 1, ' '); 206 l_result.insert(constants::BD_HOUR_END, 1, ':'); 207 } 208 } 209 catch (const std::exception& l_ex) 210 { 211 l_result.clear(); 212 logging::logMessage("Failed to encode keyword [" + i_keyword + 213 "]. Error: " + l_ex.what()); 214 } 215 216 return l_result; 217 } 218 219 /** 220 * @brief Helper function to insert or merge in map. 221 * 222 * This method checks in an interface if the given interface exists. If the 223 * interface key already exists, property map is inserted corresponding to it. 224 * If the key does'nt exist then given interface and property map pair is newly 225 * created. If the property present in propertymap already exist in the 226 * InterfaceMap, then the new property value is ignored. 227 * 228 * @param[in,out] io_map - Interface map. 229 * @param[in] i_interface - Interface to be processed. 230 * @param[in] i_propertyMap - new property map that needs to be emplaced. 231 * 232 * @return On success returns 0, otherwise returns -1. 233 */ 234 inline int insertOrMerge(types::InterfaceMap& io_map, 235 const std::string& i_interface, 236 types::PropertyMap&& i_propertyMap) noexcept 237 { 238 int l_rc{constants::FAILURE}; 239 try 240 { 241 if (io_map.find(i_interface) != io_map.end()) 242 { 243 auto& l_prop = io_map.at(i_interface); 244 std::for_each(i_propertyMap.begin(), i_propertyMap.end(), 245 [&l_prop](auto l_keyValue) { 246 l_prop[l_keyValue.first] = l_keyValue.second; 247 }); 248 } 249 else 250 { 251 io_map.emplace(i_interface, i_propertyMap); 252 } 253 254 l_rc = constants::SUCCESS; 255 } 256 catch (const std::exception& l_ex) 257 { 258 // ToDo:: Log PEL 259 logging::logMessage( 260 "Inserting properties into interface[" + i_interface + 261 "] map failed, reason: " + std::string(l_ex.what())); 262 } 263 return l_rc; 264 } 265 266 /** 267 * @brief API to expand unpanded location code. 268 * 269 * Note: The API handles all the exception internally, in case of any error 270 * unexpanded location code will be returned as it is. 271 * 272 * @param[in] unexpandedLocationCode - Unexpanded location code. 273 * @param[in] parsedVpdMap - Parsed VPD map. 274 * @return Expanded location code. In case of any error, unexpanded is returned 275 * as it is. 276 */ 277 inline std::string getExpandedLocationCode( 278 const std::string& unexpandedLocationCode, 279 const types::VPDMapVariant& parsedVpdMap) 280 { 281 auto expanded{unexpandedLocationCode}; 282 283 try 284 { 285 // Expanded location code is formed by combining two keywords 286 // depending on type in unexpanded one. Second one is always "SE". 287 std::string kwd1, kwd2{constants::kwdSE}; 288 289 // interface to search for required keywords; 290 std::string kwdInterface; 291 292 // record which holds the required keywords. 293 std::string recordName; 294 295 auto pos = unexpandedLocationCode.find("fcs"); 296 if (pos != std::string::npos) 297 { 298 kwd1 = constants::kwdFC; 299 kwdInterface = constants::vcenInf; 300 recordName = constants::recVCEN; 301 } 302 else 303 { 304 pos = unexpandedLocationCode.find("mts"); 305 if (pos != std::string::npos) 306 { 307 kwd1 = constants::kwdTM; 308 kwdInterface = constants::vsysInf; 309 recordName = constants::recVSYS; 310 } 311 else 312 { 313 throw std::runtime_error( 314 "Error detecting type of unexpanded location code."); 315 } 316 } 317 318 std::string firstKwdValue, secondKwdValue; 319 320 if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap); 321 ipzVpdMap && (*ipzVpdMap).find(recordName) != (*ipzVpdMap).end()) 322 { 323 auto itrToVCEN = (*ipzVpdMap).find(recordName); 324 // The exceptions will be cautght at end. 325 getKwVal(itrToVCEN->second, kwd1, firstKwdValue); 326 getKwVal(itrToVCEN->second, kwd2, secondKwdValue); 327 } 328 else 329 { 330 std::array<const char*, 1> interfaceList = {kwdInterface.c_str()}; 331 332 types::MapperGetObject mapperRetValue = dbusUtility::getObjectMap( 333 std::string(constants::systemVpdInvPath), interfaceList); 334 335 if (mapperRetValue.empty()) 336 { 337 throw std::runtime_error("Mapper failed to get service"); 338 } 339 340 const std::string& serviceName = std::get<0>(mapperRetValue.at(0)); 341 342 auto retVal = dbusUtility::readDbusProperty( 343 serviceName, std::string(constants::systemVpdInvPath), 344 kwdInterface, kwd1); 345 346 if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal)) 347 { 348 firstKwdValue.assign( 349 reinterpret_cast<const char*>(kwdVal->data()), 350 kwdVal->size()); 351 } 352 else 353 { 354 throw std::runtime_error( 355 "Failed to read value of " + kwd1 + " from Bus"); 356 } 357 358 retVal = dbusUtility::readDbusProperty( 359 serviceName, std::string(constants::systemVpdInvPath), 360 kwdInterface, kwd2); 361 362 if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal)) 363 { 364 secondKwdValue.assign( 365 reinterpret_cast<const char*>(kwdVal->data()), 366 kwdVal->size()); 367 } 368 else 369 { 370 throw std::runtime_error( 371 "Failed to read value of " + kwd2 + " from Bus"); 372 } 373 } 374 375 if (unexpandedLocationCode.find("fcs") != std::string::npos) 376 { 377 // TODO: See if ND0 can be placed in the JSON 378 expanded.replace( 379 pos, 3, firstKwdValue.substr(0, 4) + ".ND0." + secondKwdValue); 380 } 381 else 382 { 383 replace(firstKwdValue.begin(), firstKwdValue.end(), '-', '.'); 384 expanded.replace(pos, 3, firstKwdValue + "." + secondKwdValue); 385 } 386 } 387 catch (const std::exception& ex) 388 { 389 logging::logMessage("Failed to expand location code with exception: " + 390 std::string(ex.what())); 391 } 392 393 return expanded; 394 } 395 396 /** 397 * @brief An API to get VPD in a vector. 398 * 399 * The vector is required by the respective parser to fill the VPD map. 400 * Note: API throws exception in case of failure. Caller needs to handle. 401 * 402 * @param[in] vpdFilePath - EEPROM path of the FRU. 403 * @param[out] vpdVector - VPD in vector form. 404 * @param[in] vpdStartOffset - Offset of VPD data in EEPROM. 405 */ 406 inline void getVpdDataInVector(const std::string& vpdFilePath, 407 types::BinaryVector& vpdVector, 408 size_t& vpdStartOffset) 409 { 410 try 411 { 412 std::fstream vpdFileStream; 413 vpdFileStream.exceptions( 414 std::ifstream::badbit | std::ifstream::failbit); 415 vpdFileStream.open(vpdFilePath, std::ios::in | std::ios::binary); 416 auto vpdSizeToRead = std::min(std::filesystem::file_size(vpdFilePath), 417 static_cast<uintmax_t>(65504)); 418 vpdVector.resize(vpdSizeToRead); 419 420 vpdFileStream.seekg(vpdStartOffset, std::ios_base::beg); 421 vpdFileStream.read(reinterpret_cast<char*>(&vpdVector[0]), 422 vpdSizeToRead); 423 424 vpdVector.resize(vpdFileStream.gcount()); 425 vpdFileStream.clear(std::ios_base::eofbit); 426 } 427 catch (const std::ifstream::failure& fail) 428 { 429 std::cerr << "Exception in file handling [" << vpdFilePath 430 << "] error : " << fail.what(); 431 throw; 432 } 433 } 434 435 /** 436 * @brief An API to get D-bus representation of given VPD keyword. 437 * 438 * @param[in] i_keywordName - VPD keyword name. 439 * 440 * @return D-bus representation of given keyword. 441 */ 442 inline std::string getDbusPropNameForGivenKw(const std::string& i_keywordName) 443 { 444 // Check for "#" prefixed VPD keyword. 445 if ((i_keywordName.size() == vpd::constants::TWO_BYTES) && 446 (i_keywordName.at(0) == constants::POUND_KW)) 447 { 448 // D-bus doesn't support "#". Replace "#" with "PD_" for those "#" 449 // prefixed keywords. 450 return (std::string(constants::POUND_KW_PREFIX) + 451 i_keywordName.substr(1)); 452 } 453 454 // Return the keyword name back, if D-bus representation is same as the VPD 455 // keyword name. 456 return i_keywordName; 457 } 458 459 /** 460 * @brief API to find CCIN in parsed VPD map. 461 * 462 * Few FRUs need some special handling. To identify those FRUs CCIN are used. 463 * The API will check from parsed VPD map if the FRU is the one with desired 464 * CCIN. 465 * 466 * @param[in] i_JsonObject - Any JSON which contains CCIN tag to match. 467 * @param[in] i_parsedVpdMap - Parsed VPD map. 468 * 469 * @return True if found, false otherwise. 470 */ 471 inline bool findCcinInVpd(const nlohmann::json& i_JsonObject, 472 const types::VPDMapVariant& i_parsedVpdMap) noexcept 473 { 474 bool l_rc{false}; 475 try 476 { 477 if (i_JsonObject.empty()) 478 { 479 throw std::runtime_error("Json object is empty. Can't find CCIN"); 480 } 481 482 if (auto l_ipzVPDMap = std::get_if<types::IPZVpdMap>(&i_parsedVpdMap)) 483 { 484 auto l_itrToRec = (*l_ipzVPDMap).find("VINI"); 485 if (l_itrToRec == (*l_ipzVPDMap).end()) 486 { 487 throw DataException( 488 "VINI record not found in parsed VPD. Can't find CCIN"); 489 } 490 491 std::string l_ccinFromVpd; 492 vpdSpecificUtility::getKwVal(l_itrToRec->second, "CC", 493 l_ccinFromVpd); 494 if (l_ccinFromVpd.empty()) 495 { 496 throw DataException( 497 "Empty CCIN value in VPD map. Can't find CCIN"); 498 } 499 500 transform(l_ccinFromVpd.begin(), l_ccinFromVpd.end(), 501 l_ccinFromVpd.begin(), ::toupper); 502 503 for (std::string l_ccinValue : i_JsonObject["ccin"]) 504 { 505 transform(l_ccinValue.begin(), l_ccinValue.end(), 506 l_ccinValue.begin(), ::toupper); 507 508 if (l_ccinValue.compare(l_ccinFromVpd) == 509 constants::STR_CMP_SUCCESS) 510 { 511 // CCIN found 512 l_rc = true; 513 } 514 } 515 516 if (!l_rc) 517 { 518 logging::logMessage("No match found for CCIN"); 519 } 520 } 521 else 522 { 523 logging::logMessage("VPD type not supported. Can't find CCIN"); 524 } 525 } 526 catch (const std::exception& l_ex) 527 { 528 const std::string l_errMsg{ 529 "Failed to find CCIN in VPD. Error : " + std::string(l_ex.what())}; 530 531 if (typeid(l_ex) == std::type_index(typeid(DataException))) 532 { 533 EventLogger::createSyncPel( 534 types::ErrorType::InvalidVpdMessage, 535 types::SeverityType::Informational, __FILE__, __FUNCTION__, 0, 536 l_errMsg, std::nullopt, std::nullopt, std::nullopt, 537 std::nullopt); 538 } 539 540 logging::logMessage(l_errMsg); 541 } 542 return l_rc; 543 } 544 545 /** 546 * @brief API to reset data of a FRU populated under PIM. 547 * 548 * This API resets the data for particular interfaces of a FRU under PIM. 549 * 550 * @param[in] i_objectPath - DBus object path of the FRU. 551 * @param[in] io_interfaceMap - Interface and its properties map. 552 */ 553 inline void resetDataUnderPIM(const std::string& i_objectPath, 554 types::InterfaceMap& io_interfaceMap) 555 { 556 try 557 { 558 std::array<const char*, 0> l_interfaces; 559 const types::MapperGetObject& l_getObjectMap = 560 dbusUtility::getObjectMap(i_objectPath, l_interfaces); 561 562 const std::vector<std::string>& l_vpdRelatedInterfaces{ 563 constants::operationalStatusInf, constants::inventoryItemInf, 564 constants::assetInf}; 565 566 for (const auto& [l_service, l_interfaceList] : l_getObjectMap) 567 { 568 if (l_service.compare(constants::pimServiceName) != 569 constants::STR_CMP_SUCCESS) 570 { 571 continue; 572 } 573 574 for (const auto& l_interface : l_interfaceList) 575 { 576 if ((l_interface.find(constants::ipzVpdInf) != 577 std::string::npos) || 578 ((std::find(l_vpdRelatedInterfaces.begin(), 579 l_vpdRelatedInterfaces.end(), l_interface)) != 580 l_vpdRelatedInterfaces.end())) 581 { 582 const types::PropertyMap& l_propertyValueMap = 583 dbusUtility::getPropertyMap(l_service, i_objectPath, 584 l_interface); 585 586 types::PropertyMap l_propertyMap; 587 588 for (const auto& l_aProperty : l_propertyValueMap) 589 { 590 const std::string& l_propertyName = l_aProperty.first; 591 const auto& l_propertyValue = l_aProperty.second; 592 593 if (std::holds_alternative<types::BinaryVector>( 594 l_propertyValue)) 595 { 596 l_propertyMap.emplace(l_propertyName, 597 types::BinaryVector{}); 598 } 599 else if (std::holds_alternative<std::string>( 600 l_propertyValue)) 601 { 602 l_propertyMap.emplace(l_propertyName, 603 std::string{}); 604 } 605 else if (std::holds_alternative<bool>(l_propertyValue)) 606 { 607 // ToDo -- Update the functional status property 608 // to true. 609 if (l_propertyName.compare("Present") == 610 constants::STR_CMP_SUCCESS) 611 { 612 l_propertyMap.emplace(l_propertyName, false); 613 } 614 } 615 } 616 io_interfaceMap.emplace(l_interface, 617 std::move(l_propertyMap)); 618 } 619 } 620 } 621 } 622 catch (const std::exception& l_ex) 623 { 624 logging::logMessage("Failed to remove VPD for FRU: " + i_objectPath + 625 " with error: " + std::string(l_ex.what())); 626 } 627 } 628 629 /** 630 * @brief API to detect pass1 planar type. 631 * 632 * Based on HW version and IM keyword, This API detects is it is a pass1 planar 633 * or not. 634 * 635 * @return True if pass 1 planar, false otherwise. 636 */ 637 inline bool isPass1Planar() noexcept 638 { 639 bool l_rc{false}; 640 try 641 { 642 auto l_retVal = dbusUtility::readDbusProperty( 643 constants::pimServiceName, constants::systemVpdInvPath, 644 constants::viniInf, constants::kwdHW); 645 646 auto l_hwVer = std::get_if<types::BinaryVector>(&l_retVal); 647 648 l_retVal = dbusUtility::readDbusProperty( 649 constants::pimServiceName, constants::systemInvPath, 650 constants::vsbpInf, constants::kwdIM); 651 652 auto l_imValue = std::get_if<types::BinaryVector>(&l_retVal); 653 654 if (l_hwVer && l_imValue) 655 { 656 if (l_hwVer->size() != constants::VALUE_2) 657 { 658 throw std::runtime_error("Invalid HW keyword length."); 659 } 660 661 if (l_imValue->size() != constants::VALUE_4) 662 { 663 throw std::runtime_error("Invalid IM keyword length."); 664 } 665 666 const types::BinaryVector l_everest{80, 00, 48, 00}; 667 const types::BinaryVector l_fuji{96, 00, 32, 00}; 668 669 if (((*l_imValue) == l_everest) || ((*l_imValue) == l_fuji)) 670 { 671 if ((*l_hwVer).at(1) < constants::VALUE_21) 672 { 673 l_rc = true; 674 } 675 } 676 else if ((*l_hwVer).at(1) < constants::VALUE_2) 677 { 678 l_rc = true; 679 } 680 } 681 } 682 catch (const std::exception& l_ex) 683 { 684 logging::logMessage("Failed to check for pass 1 planar. Error: " + 685 std::string(l_ex.what())); 686 } 687 688 return l_rc; 689 } 690 691 /** 692 * @brief API to detect if system configuration is that of PowerVS system. 693 * 694 * @param[in] i_imValue - IM value of the system. 695 * @return true if it is PowerVS configuration, false otherwise. 696 */ 697 inline bool isPowerVsConfiguration(const types::BinaryVector& i_imValue) 698 { 699 if (i_imValue.empty() || i_imValue.size() != constants::VALUE_4) 700 { 701 return false; 702 } 703 704 // Should be a 0x5000XX series system. 705 if (i_imValue.at(0) == constants::HEX_VALUE_50 && 706 i_imValue.at(1) == constants::HEX_VALUE_00) 707 { 708 std::string l_imagePrefix = dbusUtility::getImagePrefix(); 709 710 // Check image for 0x500030XX series. 711 if ((i_imValue.at(2) == constants::HEX_VALUE_30) && 712 ((l_imagePrefix == constants::powerVsImagePrefix_MY) || 713 (l_imagePrefix == constants::powerVsImagePrefix_NY))) 714 { 715 logging::logMessage("PowerVS configuration"); 716 return true; 717 } 718 719 // Check image for 0X500010XX series. 720 if ((i_imValue.at(2) == constants::HEX_VALUE_10) && 721 ((l_imagePrefix == constants::powerVsImagePrefix_MZ) || 722 (l_imagePrefix == constants::powerVsImagePrefix_NZ))) 723 { 724 logging::logMessage("PowerVS configuration"); 725 return true; 726 } 727 } 728 return false; 729 } 730 } // namespace vpdSpecificUtility 731 } // namespace vpd 732