blob: e9f15eb79a957d6bd9b05991d26bc8f0a4e5ff24 [file] [log] [blame]
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -05001#include "config.h"
2
3#include "vpd_tool.hpp"
4
5#include "tool_constants.hpp"
6#include "tool_types.hpp"
7#include "tool_utils.hpp"
8
9#include <iostream>
10#include <regex>
11#include <tuple>
12namespace vpd
13{
14int VpdTool::readKeyword(
15 const std::string& i_vpdPath, const std::string& i_recordName,
16 const std::string& i_keywordName, const bool i_onHardware,
17 const std::string& i_fileToSave)
18{
19 int l_rc = constants::FAILURE;
20 try
21 {
22 types::DbusVariantType l_keywordValue;
23 if (i_onHardware)
24 {
25 l_keywordValue = utils::readKeywordFromHardware(
26 i_vpdPath, std::make_tuple(i_recordName, i_keywordName));
27 }
28 else
29 {
30 std::string l_inventoryObjectPath(
31 constants::baseInventoryPath + i_vpdPath);
32
33 l_keywordValue = utils::readDbusProperty(
34 constants::inventoryManagerService, l_inventoryObjectPath,
35 constants::ipzVpdInfPrefix + i_recordName, i_keywordName);
36 }
37
38 if (const auto l_value =
39 std::get_if<types::BinaryVector>(&l_keywordValue);
40 l_value && !l_value->empty())
41 {
42 // ToDo: Print value in both ASCII and hex formats
43 const std::string& l_keywordStrValue =
44 utils::getPrintableValue(*l_value);
45
46 if (i_fileToSave.empty())
47 {
48 utils::displayOnConsole(i_vpdPath, i_keywordName,
49 l_keywordStrValue);
50 l_rc = constants::SUCCESS;
51 }
52 else
53 {
54 if (utils::saveToFile(i_fileToSave, l_keywordStrValue))
55 {
56 std::cout
57 << "Value read is saved on the file: " << i_fileToSave
58 << std::endl;
59 l_rc = constants::SUCCESS;
60 }
61 else
62 {
63 std::cerr
64 << "Error while saving the read value on the file: "
65 << i_fileToSave
66 << "\nDisplaying the read value on console"
67 << std::endl;
68 utils::displayOnConsole(i_vpdPath, i_keywordName,
69 l_keywordStrValue);
70 }
71 }
72 }
73 else
74 {
75 // TODO: Enable logging when verbose is enabled.
Anupama B Rd3539902025-01-20 05:10:00 -060076 std::cout << "Invalid data type or empty data received."
77 << std::endl;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050078 }
79 }
80 catch (const std::exception& l_ex)
81 {
82 // TODO: Enable logging when verbose is enabled.
Anupama B Rd3539902025-01-20 05:10:00 -060083 std::cerr << "Read keyword's value failed for path: " << i_vpdPath
84 << ", Record: " << i_recordName << ", Keyword: "
85 << i_keywordName << ", error: " << l_ex.what() << std::endl;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -050086 }
87 return l_rc;
88}
89
90int VpdTool::dumpObject(std::string i_fruPath) const noexcept
91{
92 int l_rc{constants::FAILURE};
93 try
94 {
95 // ToDo: For PFuture system take only full path from the user.
96 i_fruPath = constants::baseInventoryPath + i_fruPath;
97
98 nlohmann::json l_resultJsonArray = nlohmann::json::array({});
99 const nlohmann::json l_fruJson = getFruProperties(i_fruPath);
100 if (!l_fruJson.empty())
101 {
102 l_resultJsonArray += l_fruJson;
103
104 utils::printJson(l_resultJsonArray);
105 }
106 else
107 {
108 std::cout << "FRU [" << i_fruPath
109 << "] is not present in the system" << std::endl;
110 }
111 l_rc = constants::SUCCESS;
112 }
113 catch (std::exception& l_ex)
114 {
115 // TODO: Enable logging when verbose is enabled.
Souvik Roy549d0902025-01-22 02:37:37 -0600116 std::cerr << "Dump Object failed for FRU [" << i_fruPath
117 << "], Error: " << l_ex.what() << std::endl;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500118 }
119 return l_rc;
120}
121
Sunny Srivastavadf9e5542025-02-12 12:33:07 -0600122template <typename PropertyType>
123void VpdTool::populateInterfaceJson(const std::string& i_inventoryObjPath,
124 const std::string& i_infName,
125 const std::vector<std::string>& i_propList,
126 nlohmann::json& io_fruJsonObject) const
127{
128 nlohmann::json l_interfaceJsonObj = nlohmann::json::object({});
129
130 auto l_readProperties = [i_inventoryObjPath, &l_interfaceJsonObj, i_infName,
131 this](const std::string& i_property) {
132 const nlohmann::json l_propertyJsonObj =
133 getInventoryPropertyJson<PropertyType>(i_inventoryObjPath,
134 i_infName, i_property);
135 l_interfaceJsonObj.insert(l_propertyJsonObj.cbegin(),
136 l_propertyJsonObj.cend());
137 };
138
139 std::for_each(i_propList.cbegin(), i_propList.cend(), l_readProperties);
140
141 if (!l_interfaceJsonObj.empty())
142 {
143 io_fruJsonObject.insert(l_interfaceJsonObj.cbegin(),
144 l_interfaceJsonObj.cend());
145 }
146}
147
148void VpdTool::populateFruJson(
149 const std::string& i_inventoryObjPath, nlohmann::json& io_fruJsonObject,
150 const std::vector<std::string>& i_interfaceList) const
151{
152 for (const auto& l_interface : i_interfaceList)
153 {
154 if (l_interface == constants::inventoryItemInf)
155 {
156 const std::vector<std::string> l_properties = {"PrettyName"};
157 populateInterfaceJson<std::string>(i_inventoryObjPath,
158 constants::inventoryItemInf,
159 l_properties, io_fruJsonObject);
160 continue;
161 }
162
163 if (l_interface == constants::locationCodeInf)
164 {
165 const std::vector<std::string> l_properties = {"LocationCode"};
166 populateInterfaceJson<std::string>(i_inventoryObjPath,
167 constants::locationCodeInf,
168 l_properties, io_fruJsonObject);
169 continue;
170 }
171
172 if (l_interface == constants::viniInf)
173 {
174 const std::vector<std::string> l_properties = {"SN", "PN", "CC",
175 "FN", "DR"};
176 populateInterfaceJson<vpd::types::BinaryVector>(
177 i_inventoryObjPath, constants::viniInf, l_properties,
178 io_fruJsonObject);
179 continue;
180 }
181
182 if (l_interface == constants::assetInf)
183 {
184 if (std::find(i_interfaceList.begin(), i_interfaceList.end(),
185 constants::viniInf) != i_interfaceList.end())
186 {
187 // The value will be filled from VINI interface. Don't
188 // process asset interface.
189 continue;
190 }
191
192 const std::vector<std::string> l_properties = {
193 "Model", "SerialNumber", "SubModel"};
194
195 populateInterfaceJson<std::string>(i_inventoryObjPath,
196 constants::assetInf,
197 l_properties, io_fruJsonObject);
198 continue;
199 }
200
201 if (l_interface == constants::networkInf)
202 {
203 const std::vector<std::string> l_properties = {"MACAddress"};
204 populateInterfaceJson<std::string>(i_inventoryObjPath,
205 constants::networkInf,
206 l_properties, io_fruJsonObject);
207 continue;
208 }
209
210 if (l_interface == constants::pcieSlotInf)
211 {
212 const std::vector<std::string> l_properties = {"SlotType"};
213 populateInterfaceJson<std::string>(i_inventoryObjPath,
214 constants::pcieSlotInf,
215 l_properties, io_fruJsonObject);
216 continue;
217 }
218
219 if (l_interface == constants::slotNumInf)
220 {
221 const std::vector<std::string> l_properties = {"SlotNumber"};
222 populateInterfaceJson<uint32_t>(i_inventoryObjPath,
223 constants::slotNumInf, l_properties,
224 io_fruJsonObject);
225 continue;
226 }
227
228 if (l_interface == constants::i2cDeviceInf)
229 {
230 const std::vector<std::string> l_properties = {"Address", "Bus"};
231 populateInterfaceJson<uint32_t>(i_inventoryObjPath,
232 constants::i2cDeviceInf,
233 l_properties, io_fruJsonObject);
234 continue;
235 }
236 }
237}
238
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500239nlohmann::json VpdTool::getFruProperties(const std::string& i_objectPath) const
240{
241 // check if FRU is present in the system
242 if (!isFruPresent(i_objectPath))
243 {
244 return nlohmann::json::object_t();
245 }
246
247 nlohmann::json l_fruJson = nlohmann::json::object_t({});
248
Souvik Roy549d0902025-01-22 02:37:37 -0600249 // need to trim out the base inventory path in the FRU JSON.
250 const std::string l_displayObjectPath =
251 (i_objectPath.find(constants::baseInventoryPath) == std::string::npos)
252 ? i_objectPath
253 : i_objectPath.substr(strlen(constants::baseInventoryPath));
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500254
Souvik Roy549d0902025-01-22 02:37:37 -0600255 l_fruJson.emplace(l_displayObjectPath, nlohmann::json::object_t({}));
256
257 auto& l_fruObject = l_fruJson[l_displayObjectPath];
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500258
Sunny Srivastavadf9e5542025-02-12 12:33:07 -0600259 types::MapperGetObject l_mapperResp = utils::GetServiceInterfacesForObject(
260 i_objectPath, std::vector<std::string>{});
261
262 for (const auto& [l_service, l_interfaceList] : l_mapperResp)
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500263 {
Sunny Srivastavadf9e5542025-02-12 12:33:07 -0600264 if (l_service != constants::inventoryManagerService)
Souvik Roya8bb1662025-01-20 01:12:38 -0600265 {
Sunny Srivastavadf9e5542025-02-12 12:33:07 -0600266 continue;
Souvik Roya8bb1662025-01-20 01:12:38 -0600267 }
Sunny Srivastavadf9e5542025-02-12 12:33:07 -0600268 populateFruJson(i_objectPath, l_fruObject, l_interfaceList);
Souvik Roya8bb1662025-01-20 01:12:38 -0600269 }
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500270
271 const auto l_typePropertyJson = getFruTypeProperty(i_objectPath);
272 if (!l_typePropertyJson.empty())
273 {
274 l_fruObject.insert(l_typePropertyJson.cbegin(),
275 l_typePropertyJson.cend());
276 }
277
Souvik Roya8bb1662025-01-20 01:12:38 -0600278 // insert FRU "TYPE"
279 l_fruObject.emplace("TYPE", "FRU");
280
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500281 return l_fruJson;
282}
283
284template <typename PropertyType>
285nlohmann::json VpdTool::getInventoryPropertyJson(
286 const std::string& i_objectPath, const std::string& i_interface,
287 const std::string& i_propertyName) const noexcept
288{
289 nlohmann::json l_resultInJson = nlohmann::json::object({});
290 try
291 {
292 types::DbusVariantType l_keyWordValue;
293
294 l_keyWordValue =
295 utils::readDbusProperty(constants::inventoryManagerService,
296 i_objectPath, i_interface, i_propertyName);
297
298 if (const auto l_value = std::get_if<PropertyType>(&l_keyWordValue))
299 {
300 if constexpr (std::is_same<PropertyType, std::string>::value)
301 {
302 l_resultInJson.emplace(i_propertyName, *l_value);
303 }
304 else if constexpr (std::is_same<PropertyType, bool>::value)
305 {
306 l_resultInJson.emplace(i_propertyName,
307 *l_value ? "true" : "false");
308 }
309 else if constexpr (std::is_same<PropertyType,
310 types::BinaryVector>::value)
311 {
312 const std::string& l_keywordStrValue =
313 vpd::utils::getPrintableValue(*l_value);
314
315 l_resultInJson.emplace(i_propertyName, l_keywordStrValue);
316 }
Sunny Srivastavadf9e5542025-02-12 12:33:07 -0600317 else if constexpr (std::is_same<PropertyType, uint32_t>::value)
318 {
319 l_resultInJson.emplace(i_propertyName,
320 std::to_string(*l_value));
321 }
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500322 }
323 else
324 {
325 // TODO: Enable logging when verbose is enabled.
Anupama B Rd3539902025-01-20 05:10:00 -0600326 std::cout << "Invalid data type received." << std::endl;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500327 }
328 }
329 catch (const std::exception& l_ex)
330 {
331 // TODO: Enable logging when verbose is enabled.
Anupama B Rd3539902025-01-20 05:10:00 -0600332 std::cerr << "Read " << i_propertyName
333 << " value for FRU path: " << i_objectPath
334 << ", failed with exception: " << l_ex.what() << std::endl;
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500335 }
336 return l_resultInJson;
337}
338
339int VpdTool::fixSystemVpd() const noexcept
340{
341 int l_rc = constants::FAILURE;
342
343 nlohmann::json l_backupRestoreCfgJsonObj = getBackupRestoreCfgJsonObj();
344 if (!fetchKeywordInfo(l_backupRestoreCfgJsonObj))
345 {
346 return l_rc;
347 }
348
349 printSystemVpd(l_backupRestoreCfgJsonObj);
350
351 do
352 {
353 printFixSystemVpdOption(types::UserOption::UseBackupDataForAll);
354 printFixSystemVpdOption(
355 types::UserOption::UseSystemBackplaneDataForAll);
356 printFixSystemVpdOption(types::UserOption::MoreOptions);
357 printFixSystemVpdOption(types::UserOption::Exit);
358
359 int l_userSelectedOption = types::UserOption::Exit;
360 std::cin >> l_userSelectedOption;
361
362 std::cout << std::endl << std::string(191, '=') << std::endl;
363
364 if (types::UserOption::UseBackupDataForAll == l_userSelectedOption)
365 {
366 l_rc = updateAllKeywords(l_backupRestoreCfgJsonObj, true);
367 break;
368 }
369 else if (types::UserOption::UseSystemBackplaneDataForAll ==
370 l_userSelectedOption)
371 {
372 l_rc = updateAllKeywords(l_backupRestoreCfgJsonObj, false);
373 break;
374 }
375 else if (types::UserOption::MoreOptions == l_userSelectedOption)
376 {
377 l_rc = handleMoreOption(l_backupRestoreCfgJsonObj);
378 break;
379 }
380 else if (types::UserOption::Exit == l_userSelectedOption)
381 {
382 std::cout << "Exit successfully" << std::endl;
383 break;
384 }
385 else
386 {
387 std::cout << "Provide a valid option. Retry." << std::endl;
388 }
389 } while (true);
390
391 return l_rc;
392}
393
394int VpdTool::writeKeyword(
395 std::string i_vpdPath, const std::string& i_recordName,
396 const std::string& i_keywordName, const std::string& i_keywordValue,
397 const bool i_onHardware) noexcept
398{
399 int l_rc = constants::FAILURE;
400 try
401 {
402 if (i_vpdPath.empty() || i_recordName.empty() ||
403 i_keywordName.empty() || i_keywordValue.empty())
404 {
405 throw std::runtime_error("Received input is empty.");
406 }
407
408 auto l_paramsToWrite =
409 std::make_tuple(i_recordName, i_keywordName,
410 utils::convertToBinary(i_keywordValue));
411
412 if (i_onHardware)
413 {
414 l_rc = utils::writeKeywordOnHardware(i_vpdPath, l_paramsToWrite);
415 }
416 else
417 {
418 i_vpdPath = constants::baseInventoryPath + i_vpdPath;
419 l_rc = utils::writeKeyword(i_vpdPath, l_paramsToWrite);
420 }
421
422 if (l_rc > 0)
423 {
424 std::cout << "Data updated successfully " << std::endl;
425 l_rc = constants::SUCCESS;
426 }
427 }
428 catch (const std::exception& l_ex)
429 {
430 // TODO: Enable log when verbose is enabled.
431 std::cerr << "Write keyword's value for path: " << i_vpdPath
432 << ", Record: " << i_recordName
433 << ", Keyword: " << i_keywordName
434 << " is failed. Exception: " << l_ex.what() << std::endl;
435 }
436 return l_rc;
437}
438
439nlohmann::json VpdTool::getBackupRestoreCfgJsonObj() const noexcept
440{
441 nlohmann::json l_parsedBackupRestoreJson{};
442 try
443 {
444 nlohmann::json l_parsedSystemJson =
445 utils::getParsedJson(INVENTORY_JSON_SYM_LINK);
446
447 // check for mandatory fields at this point itself.
448 if (!l_parsedSystemJson.contains("backupRestoreConfigPath"))
449 {
450 throw std::runtime_error(
451 "backupRestoreConfigPath tag is missing from system config JSON : " +
452 std::string(INVENTORY_JSON_SYM_LINK));
453 }
454
455 l_parsedBackupRestoreJson =
456 utils::getParsedJson(l_parsedSystemJson["backupRestoreConfigPath"]);
457 }
458 catch (const std::exception& l_ex)
459 {
460 // TODO: Enable logging when verbose is enabled.
461 std::cerr << l_ex.what() << std::endl;
462 }
463
464 return l_parsedBackupRestoreJson;
465}
466
Souvik Roy7f749a62025-02-10 22:23:41 -0600467int VpdTool::cleanSystemVpd(bool i_syncBiosAttributesRequired) const noexcept
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500468{
469 try
470 {
Souvik Roy7f749a62025-02-10 22:23:41 -0600471 // In order to do syncBiosAttributes, we need BIOS Config Manager
472 // service up and running
473 if (i_syncBiosAttributesRequired &&
474 !utils::isServiceRunning(constants::biosConfigMgrService))
475 {
476 std::cerr
477 << "Cannot sync BIOS attributes as BIOS Config Manager service is not running."
478 << std::endl;
479 return constants::FAILURE;
480 }
Souvik Roy63089422025-01-23 02:48:21 -0600481
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500482 // get the keyword map from backup_restore json
483 // iterate through the keyword map get default value of
484 // l_keywordName.
485 // use writeKeyword API to update default value on hardware,
486 // backup and D - Bus.
487 const nlohmann::json l_parsedBackupRestoreJson =
488 getBackupRestoreCfgJsonObj();
489
490 // check for mandatory tags
491 if (l_parsedBackupRestoreJson.contains("source") &&
492 l_parsedBackupRestoreJson.contains("backupMap") &&
493 l_parsedBackupRestoreJson["source"].contains("hardwarePath") &&
494 l_parsedBackupRestoreJson["backupMap"].is_array())
495 {
496 // get the source hardware path
497 const auto& l_hardwarePath =
498 l_parsedBackupRestoreJson["source"]["hardwarePath"];
499
500 // iterate through the backup map
501 for (const auto& l_aRecordKwInfo :
502 l_parsedBackupRestoreJson["backupMap"])
503 {
504 // check if Manufacturing Reset is required for this entry
505 const bool l_isMfgCleanRequired =
506 l_aRecordKwInfo.value("isManufactureResetRequired", false);
507
508 if (l_isMfgCleanRequired)
509 {
510 // get the Record name and Keyword name
511 const std::string& l_srcRecordName =
512 l_aRecordKwInfo.value("sourceRecord", "");
513 const std::string& l_srcKeywordName =
514 l_aRecordKwInfo.value("sourceKeyword", "");
515
516 // validate the Record name, Keyword name and the
517 // defaultValue
518 if (!l_srcRecordName.empty() && !l_srcKeywordName.empty() &&
519 l_aRecordKwInfo.contains("defaultValue") &&
520 l_aRecordKwInfo["defaultValue"].is_array())
521 {
Souvik Roy7f749a62025-02-10 22:23:41 -0600522 // check if this keyword is used for backing up BIOS
523 // attribute
524 const bool l_isUsedForBiosAttributeBackup =
525 l_aRecordKwInfo.value("isBiosSyncRequired", false);
526
527 const types::BinaryVector l_keywordValueToUpdate =
528 (i_syncBiosAttributesRequired &&
529 l_isUsedForBiosAttributeBackup)
530 ? getVpdValueInBiosConfigManager(
531 l_srcRecordName, l_srcKeywordName)
532 : l_aRecordKwInfo["defaultValue"]
533 .get<types::BinaryVector>();
534
535 if (l_keywordValueToUpdate.empty())
536 {
537 std::cerr << "Failed to update " << l_srcRecordName
538 << ":" << l_srcKeywordName
539 << " . Keyword value to update is empty"
540 << std::endl;
541 continue;
542 }
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500543
544 // update the Keyword with default value, use D-Bus
545 // method UpdateKeyword exposed by vpd-manager.
546 // Note: writing to all paths (Primary EEPROM path,
547 // Secondary EEPROM path, D-Bus cache and Backup path)
548 // is the responsibility of vpd-manager's UpdateKeyword
549 // API
550 if (constants::FAILURE ==
551 utils::writeKeyword(
552 l_hardwarePath,
553 std::make_tuple(l_srcRecordName,
554 l_srcKeywordName,
Souvik Roy7f749a62025-02-10 22:23:41 -0600555 l_keywordValueToUpdate)))
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500556 {
557 // TODO: Enable logging when verbose
558 // is enabled.
559 std::cerr << "Failed to update " << l_srcRecordName
560 << ":" << l_srcKeywordName << std::endl;
561 }
562 }
563 else
564 {
565 std::cerr
566 << "Unrecognized Entry Record [" << l_srcRecordName
567 << "] Keyword [" << l_srcKeywordName
568 << "] in Backup Restore JSON backup map"
569 << std::endl;
570 }
571 } // mfgClean required check
572 } // keyword list loop
573 }
574 else // backupRestoreJson is not valid
575 {
576 std::cerr << "Backup Restore JSON is not valid" << std::endl;
577 }
578
579 // success/failure message
580 std::cout << "The critical keywords from system backplane VPD has "
581 "been reset successfully."
582 << std::endl;
583
584 } // try block end
585 catch (const std::exception& l_ex)
586 {
587 // TODO: Enable logging when verbose is enabled.
588 std::cerr
589 << "Manufacturing reset on system vpd keywords is unsuccessful. Error : "
590 << l_ex.what() << std::endl;
591 }
592 return constants::SUCCESS;
593}
594
595bool VpdTool::fetchKeywordInfo(nlohmann::json& io_parsedJsonObj) const noexcept
596{
597 bool l_returnValue = false;
598 try
599 {
600 if (io_parsedJsonObj.empty() || !io_parsedJsonObj.contains("source") ||
601 !io_parsedJsonObj.contains("destination") ||
602 !io_parsedJsonObj.contains("backupMap"))
603 {
604 throw std::runtime_error("Invalid JSON");
605 }
606
607 std::string l_srcVpdPath;
608 std::string l_dstVpdPath;
609
610 bool l_isSourceOnHardware = false;
611 if (l_srcVpdPath = io_parsedJsonObj["source"].value("hardwarePath", "");
612 !l_srcVpdPath.empty())
613 {
614 l_isSourceOnHardware = true;
615 }
616 else if (l_srcVpdPath =
617 io_parsedJsonObj["source"].value("inventoryPath", "");
618 l_srcVpdPath.empty())
619 {
620 throw std::runtime_error("Source path is empty in JSON");
621 }
622
623 bool l_isDestinationOnHardware = false;
624 if (l_dstVpdPath =
625 io_parsedJsonObj["destination"].value("hardwarePath", "");
626 !l_dstVpdPath.empty())
627 {
628 l_isDestinationOnHardware = true;
629 }
630 else if (l_dstVpdPath =
631 io_parsedJsonObj["destination"].value("inventoryPath", "");
632 l_dstVpdPath.empty())
633 {
634 throw std::runtime_error("Destination path is empty in JSON");
635 }
636
637 for (auto& l_aRecordKwInfo : io_parsedJsonObj["backupMap"])
638 {
639 const std::string& l_srcRecordName =
640 l_aRecordKwInfo.value("sourceRecord", "");
641 const std::string& l_srcKeywordName =
642 l_aRecordKwInfo.value("sourceKeyword", "");
643 const std::string& l_dstRecordName =
644 l_aRecordKwInfo.value("destinationRecord", "");
645 const std::string& l_dstKeywordName =
646 l_aRecordKwInfo.value("destinationKeyword", "");
647
648 if (l_srcRecordName.empty() || l_dstRecordName.empty() ||
649 l_srcKeywordName.empty() || l_dstKeywordName.empty())
650 {
651 // TODO: Enable logging when verbose is enabled.
652 std::cout << "Record or keyword not found in the JSON."
653 << std::endl;
654 continue;
655 }
656
657 types::DbusVariantType l_srcKeywordVariant;
658 if (l_isSourceOnHardware)
659 {
660 l_srcKeywordVariant = utils::readKeywordFromHardware(
661 l_srcVpdPath,
662 std::make_tuple(l_srcRecordName, l_srcKeywordName));
663 }
664 else
665 {
666 l_srcKeywordVariant = utils::readDbusProperty(
667 constants::inventoryManagerService, l_srcVpdPath,
668 constants::ipzVpdInfPrefix + l_srcRecordName,
669 l_srcKeywordName);
670 }
671
672 if (auto l_srcKeywordValue =
673 std::get_if<types::BinaryVector>(&l_srcKeywordVariant);
674 l_srcKeywordValue && !l_srcKeywordValue->empty())
675 {
676 l_aRecordKwInfo["sourcekeywordValue"] = *l_srcKeywordValue;
677 }
678 else
679 {
680 // TODO: Enable logging when verbose is enabled.
681 std::cout
682 << "Invalid data type or empty data received, for source record: "
683 << l_srcRecordName << ", keyword: " << l_srcKeywordName
684 << std::endl;
685 continue;
686 }
687
688 types::DbusVariantType l_dstKeywordVariant;
689 if (l_isDestinationOnHardware)
690 {
691 l_dstKeywordVariant = utils::readKeywordFromHardware(
692 l_dstVpdPath,
693 std::make_tuple(l_dstRecordName, l_dstKeywordName));
694 }
695 else
696 {
697 l_dstKeywordVariant = utils::readDbusProperty(
698 constants::inventoryManagerService, l_dstVpdPath,
699 constants::ipzVpdInfPrefix + l_dstRecordName,
700 l_dstKeywordName);
701 }
702
703 if (auto l_dstKeywordValue =
704 std::get_if<types::BinaryVector>(&l_dstKeywordVariant);
705 l_dstKeywordValue && !l_dstKeywordValue->empty())
706 {
707 l_aRecordKwInfo["destinationkeywordValue"] = *l_dstKeywordValue;
708 }
709 else
710 {
711 // TODO: Enable logging when verbose is enabled.
712 std::cout
713 << "Invalid data type or empty data received, for destination record: "
714 << l_dstRecordName << ", keyword: " << l_dstKeywordName
715 << std::endl;
716 continue;
717 }
718 }
719
720 l_returnValue = true;
721 }
722 catch (const std::exception& l_ex)
723 {
724 // TODO: Enable logging when verbose is enabled.
725 std::cerr << l_ex.what() << std::endl;
726 }
727
728 return l_returnValue;
729}
730
Patrick Williams43fedab2025-02-03 14:28:05 -0500731nlohmann::json VpdTool::getFruTypeProperty(
732 const std::string& i_objectPath) const noexcept
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -0500733{
734 nlohmann::json l_resultInJson = nlohmann::json::object({});
735 std::vector<std::string> l_pimInfList;
736
737 auto l_serviceInfMap = utils::GetServiceInterfacesForObject(
738 i_objectPath, std::vector<std::string>{constants::inventoryItemInf});
739 if (l_serviceInfMap.contains(constants::inventoryManagerService))
740 {
741 l_pimInfList = l_serviceInfMap[constants::inventoryManagerService];
742
743 // iterate through the list and find
744 // "xyz.openbmc_project.Inventory.Item.*"
745 for (const auto& l_interface : l_pimInfList)
746 {
747 if (l_interface.find(constants::inventoryItemInf) !=
748 std::string::npos &&
749 l_interface.length() >
750 std::string(constants::inventoryItemInf).length())
751 {
752 l_resultInJson.emplace("type", l_interface);
753 }
754 }
755 }
756 return l_resultInJson;
757}
758
759bool VpdTool::isFruPresent(const std::string& i_objectPath) const noexcept
760{
761 bool l_returnValue{false};
762 try
763 {
764 types::DbusVariantType l_keyWordValue;
765
766 l_keyWordValue = utils::readDbusProperty(
767 constants::inventoryManagerService, i_objectPath,
768 constants::inventoryItemInf, "Present");
769
770 if (const auto l_value = std::get_if<bool>(&l_keyWordValue))
771 {
772 l_returnValue = *l_value;
773 }
774 }
775 catch (const std::runtime_error& l_ex)
776 {
777 // TODO: Enable logging when verbose is enabled.
778 // std::cerr << "Failed to check \"Present\" property for FRU "
779 // << i_objectPath << " Error: " << l_ex.what() <<
780 // std::endl;
781 }
782 return l_returnValue;
783}
784
785void VpdTool::printFixSystemVpdOption(
786 const types::UserOption& i_option) const noexcept
787{
788 switch (i_option)
789 {
790 case types::UserOption::Exit:
791 std::cout << "Enter 0 => To exit successfully : ";
792 break;
793 case types::UserOption::UseBackupDataForAll:
794 std::cout << "Enter 1 => If you choose the data on backup for all "
795 "mismatching record-keyword pairs"
796 << std::endl;
797 break;
798 case types::UserOption::UseSystemBackplaneDataForAll:
799 std::cout << "Enter 2 => If you choose the data on primary for all "
800 "mismatching record-keyword pairs"
801 << std::endl;
802 break;
803 case types::UserOption::MoreOptions:
804 std::cout << "Enter 3 => If you wish to explore more options"
805 << std::endl;
806 break;
807 case types::UserOption::UseBackupDataForCurrent:
808 std::cout << "Enter 4 => If you choose the data on backup as the "
809 "right value"
810 << std::endl;
811 break;
812 case types::UserOption::UseSystemBackplaneDataForCurrent:
813 std::cout << "Enter 5 => If you choose the data on primary as the "
814 "right value"
815 << std::endl;
816 break;
817 case types::UserOption::NewValueOnBoth:
818 std::cout
819 << "Enter 6 => If you wish to enter a new value to update "
820 "both on backup and primary"
821 << std::endl;
822 break;
823 case types::UserOption::SkipCurrent:
824 std::cout << "Enter 7 => If you wish to skip the above "
825 "record-keyword pair"
826 << std::endl;
827 break;
828 }
829}
830
831int VpdTool::dumpInventory(bool i_dumpTable) const noexcept
832{
833 int l_rc{constants::FAILURE};
834
835 try
836 {
837 // get all object paths under PIM
838 const auto l_objectPaths = utils::GetSubTreePaths(
839 constants::baseInventoryPath, 0,
840 std::vector<std::string>{constants::inventoryItemInf});
841
842 if (!l_objectPaths.empty())
843 {
844 nlohmann::json l_resultInJson = nlohmann::json::array({});
845
846 std::for_each(l_objectPaths.begin(), l_objectPaths.end(),
847 [&](const auto& l_objectPath) {
848 const auto l_fruJson =
849 getFruProperties(l_objectPath);
850 if (!l_fruJson.empty())
851 {
852 if (l_resultInJson.empty())
853 {
854 l_resultInJson += l_fruJson;
855 }
856 else
857 {
858 l_resultInJson.at(0).insert(
859 l_fruJson.cbegin(), l_fruJson.cend());
860 }
861 }
862 });
863
864 if (i_dumpTable)
865 {
866 // create Table object
867 utils::Table l_inventoryTable{};
868
869 // columns to be populated in the Inventory table
870 const std::vector<types::TableColumnNameSizePair>
871 l_tableColumns = {
872 {"FRU", 100}, {"CC", 6}, {"DR", 20},
873 {"LocationCode", 32}, {"PN", 8}, {"PrettyName", 80},
874 {"SubModel", 10}, {"SN", 15}, {"type", 60}};
875
876 types::TableInputData l_tableData;
877
878 // First prepare the Table Columns
879 for (const auto& l_column : l_tableColumns)
880 {
881 if (constants::FAILURE ==
882 l_inventoryTable.AddColumn(l_column.first,
883 l_column.second))
884 {
885 // TODO: Enable logging when verbose is enabled.
886 std::cerr << "Failed to add column " << l_column.first
887 << " in Inventory Table." << std::endl;
888 }
889 }
890
891 // iterate through the json array
892 for (const auto& l_fruEntry : l_resultInJson[0].items())
893 {
894 // if object path ends in "unit([0-9][0-9]?)", skip adding
895 // the object path in the table
896 if (std::regex_search(l_fruEntry.key(),
897 std::regex("unit([0-9][0-9]?)")))
898 {
899 continue;
900 }
901
902 std::vector<std::string> l_row;
903 for (const auto& l_column : l_tableColumns)
904 {
905 const auto& l_fruJson = l_fruEntry.value();
906
907 if (l_column.first == "FRU")
908 {
909 l_row.push_back(l_fruEntry.key());
910 }
911 else
912 {
913 if (l_fruJson.contains(l_column.first))
914 {
915 l_row.push_back(l_fruJson[l_column.first]);
916 }
917 else
918 {
919 l_row.push_back("");
920 }
921 }
922 }
923
924 l_tableData.push_back(l_row);
925 }
926
927 l_rc = l_inventoryTable.Print(l_tableData);
928 }
929 else
930 {
931 // print JSON to console
932 utils::printJson(l_resultInJson);
933 l_rc = constants::SUCCESS;
934 }
935 }
936 }
937 catch (const std::exception& l_ex)
938 {
939 // TODO: Enable logging when verbose is enabled.
940 std::cerr << "Dump inventory failed. Error: " << l_ex.what()
941 << std::endl;
942 }
943 return l_rc;
944}
945
946void VpdTool::printSystemVpd(
947 const nlohmann::json& i_parsedJsonObj) const noexcept
948{
949 if (i_parsedJsonObj.empty() || !i_parsedJsonObj.contains("backupMap"))
950 {
951 // TODO: Enable logging when verbose is enabled.
952 std::cerr << "Invalid JSON to print system VPD" << std::endl;
953 }
954
955 std::string l_outline(191, '=');
956 std::cout << "\nRestorable record-keyword pairs and their data on backup & "
957 "primary.\n\n"
958 << l_outline << std::endl;
959
960 std::cout << std::left << std::setw(6) << "S.No" << std::left
961 << std::setw(8) << "Record" << std::left << std::setw(9)
962 << "Keyword" << std::left << std::setw(75) << "Data On Backup"
963 << std::left << std::setw(75) << "Data On Primary" << std::left
964 << std::setw(14) << "Data Mismatch\n"
965 << l_outline << std::endl;
966
967 uint8_t l_slNum = 0;
968
969 for (const auto& l_aRecordKwInfo : i_parsedJsonObj["backupMap"])
970 {
971 if (l_aRecordKwInfo.contains("sourceRecord") ||
972 l_aRecordKwInfo.contains("sourceKeyword") ||
973 l_aRecordKwInfo.contains("destinationkeywordValue") ||
974 l_aRecordKwInfo.contains("sourcekeywordValue"))
975 {
976 std::string l_mismatchFound{
977 (l_aRecordKwInfo["destinationkeywordValue"] !=
978 l_aRecordKwInfo["sourcekeywordValue"])
979 ? "YES"
980 : "NO"};
981
982 std::string l_splitLine(191, '-');
983
984 try
985 {
986 std::cout << std::left << std::setw(6)
987 << static_cast<int>(++l_slNum) << std::left
988 << std::setw(8)
989 << l_aRecordKwInfo.value("sourceRecord", "")
990 << std::left << std::setw(9)
991 << l_aRecordKwInfo.value("sourceKeyword", "")
992 << std::left << std::setw(75) << std::setfill(' ')
993 << utils::getPrintableValue(
994 l_aRecordKwInfo["destinationkeywordValue"])
995 << std::left << std::setw(75) << std::setfill(' ')
996 << utils::getPrintableValue(
997 l_aRecordKwInfo["sourcekeywordValue"])
998 << std::left << std::setw(14) << l_mismatchFound
999 << '\n'
1000 << l_splitLine << std::endl;
1001 }
1002 catch (const std::exception& l_ex)
1003 {
1004 // TODO: Enable logging when verbose is enabled.
1005 std::cerr << l_ex.what() << std::endl;
1006 }
1007 }
1008 }
1009}
1010
1011int VpdTool::updateAllKeywords(const nlohmann::json& i_parsedJsonObj,
1012 bool i_useBackupData) const noexcept
1013{
1014 int l_rc = constants::FAILURE;
1015
1016 if (i_parsedJsonObj.empty() || !i_parsedJsonObj.contains("source") ||
1017 !i_parsedJsonObj.contains("backupMap"))
1018 {
1019 // TODO: Enable logging when verbose is enabled.
1020 std::cerr << "Invalid JSON" << std::endl;
1021 return l_rc;
1022 }
1023
1024 std::string l_srcVpdPath;
1025 if (auto l_vpdPath = i_parsedJsonObj["source"].value("hardwarePath", "");
1026 !l_vpdPath.empty())
1027 {
1028 l_srcVpdPath = l_vpdPath;
1029 }
1030 else if (auto l_vpdPath =
1031 i_parsedJsonObj["source"].value("inventoryPath", "");
1032 !l_vpdPath.empty())
1033 {
1034 l_srcVpdPath = l_vpdPath;
1035 }
1036 else
1037 {
1038 // TODO: Enable logging when verbose is enabled.
1039 std::cerr << "source path information is missing in JSON" << std::endl;
1040 return l_rc;
1041 }
1042
1043 bool l_anyMismatchFound = false;
1044 for (const auto& l_aRecordKwInfo : i_parsedJsonObj["backupMap"])
1045 {
1046 if (!l_aRecordKwInfo.contains("sourceRecord") ||
1047 !l_aRecordKwInfo.contains("sourceKeyword") ||
1048 !l_aRecordKwInfo.contains("destinationkeywordValue") ||
1049 !l_aRecordKwInfo.contains("sourcekeywordValue"))
1050 {
1051 // TODO: Enable logging when verbose is enabled.
1052 std::cerr << "Missing required information in the JSON"
1053 << std::endl;
1054 continue;
1055 }
1056
1057 if (l_aRecordKwInfo["sourcekeywordValue"] !=
1058 l_aRecordKwInfo["destinationkeywordValue"])
1059 {
1060 l_anyMismatchFound = true;
1061
1062 auto l_keywordValue =
1063 i_useBackupData ? l_aRecordKwInfo["destinationkeywordValue"]
1064 : l_aRecordKwInfo["sourcekeywordValue"];
1065
1066 auto l_paramsToWrite = std::make_tuple(
1067 l_aRecordKwInfo["sourceRecord"],
1068 l_aRecordKwInfo["sourceKeyword"], l_keywordValue);
1069
1070 try
1071 {
1072 l_rc = utils::writeKeyword(l_srcVpdPath, l_paramsToWrite);
1073 if (l_rc > 0)
1074 {
1075 l_rc = constants::SUCCESS;
1076 }
1077 }
1078 catch (const std::exception& l_ex)
1079 {
1080 // TODO: Enable logging when verbose is enabled.
1081 std::cerr << "write keyword failed for record: "
1082 << l_aRecordKwInfo["sourceRecord"]
1083 << ", keyword: " << l_aRecordKwInfo["sourceKeyword"]
1084 << ", error: " << l_ex.what() << std::ends;
1085 }
1086 }
1087 }
1088
1089 std::string l_dataUsed =
1090 (i_useBackupData ? "data from backup" : "data from primary VPD");
1091 if (l_anyMismatchFound)
1092 {
1093 std::cout << "Data updated successfully for all mismatching "
1094 "record-keyword pairs by choosing their corresponding "
1095 << l_dataUsed << ". Exit successfully." << std::endl;
1096 }
1097 else
1098 {
1099 std::cout << "No mismatch found for any of the above mentioned "
1100 "record-keyword pair. Exit successfully."
1101 << std::endl;
1102 }
1103
1104 return l_rc;
1105}
1106
1107int VpdTool::handleMoreOption(
1108 const nlohmann::json& i_parsedJsonObj) const noexcept
1109{
1110 int l_rc = constants::FAILURE;
1111
1112 try
1113 {
1114 if (i_parsedJsonObj.empty() || !i_parsedJsonObj.contains("backupMap"))
1115 {
1116 throw std::runtime_error("Invalid JSON");
1117 }
1118
1119 std::string l_srcVpdPath;
1120
1121 if (auto l_vpdPath =
1122 i_parsedJsonObj["source"].value("hardwarePath", "");
1123 !l_vpdPath.empty())
1124 {
1125 l_srcVpdPath = l_vpdPath;
1126 }
1127 else if (auto l_vpdPath =
1128 i_parsedJsonObj["source"].value("inventoryPath", "");
1129 !l_vpdPath.empty())
1130 {
1131 l_srcVpdPath = l_vpdPath;
1132 }
1133 else
1134 {
1135 throw std::runtime_error(
1136 "source path information is missing in JSON");
1137 }
1138
1139 auto updateKeywordValue =
1140 [](std::string io_vpdPath, const std::string& i_recordName,
1141 const std::string& i_keywordName,
1142 const types::BinaryVector& i_keywordValue) -> int {
1143 int l_rc = constants::FAILURE;
1144
1145 try
1146 {
1147 auto l_paramsToWrite = std::make_tuple(
1148 i_recordName, i_keywordName, i_keywordValue);
1149 l_rc = utils::writeKeyword(io_vpdPath, l_paramsToWrite);
1150
1151 if (l_rc > 0)
1152 {
1153 std::cout << std::endl
1154 << "Data updated successfully." << std::endl;
1155 }
1156 }
1157 catch (const std::exception& l_ex)
1158 {
1159 // TODO: Enable log when verbose is enabled.
1160 std::cerr << l_ex.what() << std::endl;
1161 }
1162 return l_rc;
1163 };
1164
1165 do
1166 {
1167 int l_slNum = 0;
1168 bool l_exit = false;
1169
1170 for (const auto& l_aRecordKwInfo : i_parsedJsonObj["backupMap"])
1171 {
1172 if (!l_aRecordKwInfo.contains("sourceRecord") ||
1173 !l_aRecordKwInfo.contains("sourceKeyword") ||
1174 !l_aRecordKwInfo.contains("destinationkeywordValue") ||
1175 !l_aRecordKwInfo.contains("sourcekeywordValue"))
1176 {
1177 // TODO: Enable logging when verbose is enabled.
1178 std::cerr
1179 << "Source or destination information is missing in the JSON."
1180 << std::endl;
1181 continue;
1182 }
1183
1184 const std::string l_mismatchFound{
1185 (l_aRecordKwInfo["sourcekeywordValue"] !=
1186 l_aRecordKwInfo["destinationkeywordValue"])
1187 ? "YES"
1188 : "NO"};
1189
1190 std::cout << std::endl
1191 << std::left << std::setw(6) << "S.No" << std::left
1192 << std::setw(8) << "Record" << std::left
1193 << std::setw(9) << "Keyword" << std::left
1194 << std::setw(75) << std::setfill(' ') << "Backup Data"
1195 << std::left << std::setw(75) << std::setfill(' ')
1196 << "Primary Data" << std::left << std::setw(14)
1197 << "Data Mismatch" << std::endl;
1198
1199 std::cout << std::left << std::setw(6)
1200 << static_cast<int>(++l_slNum) << std::left
1201 << std::setw(8)
1202 << l_aRecordKwInfo.value("sourceRecord", "")
1203 << std::left << std::setw(9)
1204 << l_aRecordKwInfo.value("sourceKeyword", "")
1205 << std::left << std::setw(75) << std::setfill(' ')
1206 << utils::getPrintableValue(
1207 l_aRecordKwInfo["destinationkeywordValue"])
1208 << std::left << std::setw(75) << std::setfill(' ')
1209 << utils::getPrintableValue(
1210 l_aRecordKwInfo["sourcekeywordValue"])
1211 << std::left << std::setw(14) << l_mismatchFound
1212 << std::endl;
1213
1214 std::cout << std::string(191, '=') << std::endl;
1215
1216 if (constants::STR_CMP_SUCCESS ==
1217 l_mismatchFound.compare("YES"))
1218 {
1219 printFixSystemVpdOption(
1220 types::UserOption::UseBackupDataForCurrent);
1221 printFixSystemVpdOption(
1222 types::UserOption::UseSystemBackplaneDataForCurrent);
1223 printFixSystemVpdOption(types::UserOption::NewValueOnBoth);
1224 printFixSystemVpdOption(types::UserOption::SkipCurrent);
1225 printFixSystemVpdOption(types::UserOption::Exit);
1226 }
1227 else
1228 {
1229 std::cout << "No mismatch found." << std::endl << std::endl;
1230 printFixSystemVpdOption(types::UserOption::NewValueOnBoth);
1231 printFixSystemVpdOption(types::UserOption::SkipCurrent);
1232 printFixSystemVpdOption(types::UserOption::Exit);
1233 }
1234
1235 int l_userSelectedOption = types::UserOption::Exit;
1236 std::cin >> l_userSelectedOption;
1237
1238 if (types::UserOption::UseBackupDataForCurrent ==
1239 l_userSelectedOption)
1240 {
1241 l_rc = updateKeywordValue(
1242 l_srcVpdPath, l_aRecordKwInfo["sourceRecord"],
1243 l_aRecordKwInfo["sourceKeyword"],
1244 l_aRecordKwInfo["destinationkeywordValue"]);
1245 }
1246 else if (types::UserOption::UseSystemBackplaneDataForCurrent ==
1247 l_userSelectedOption)
1248 {
1249 l_rc = updateKeywordValue(
1250 l_srcVpdPath, l_aRecordKwInfo["sourceRecord"],
1251 l_aRecordKwInfo["sourceKeyword"],
1252 l_aRecordKwInfo["sourcekeywordValue"]);
1253 }
1254 else if (types::UserOption::NewValueOnBoth ==
1255 l_userSelectedOption)
1256 {
1257 std::string l_newValue;
1258 std::cout
1259 << std::endl
1260 << "Enter the new value to update on both "
1261 "primary & backup. Value should be in ASCII or "
1262 "in HEX(prefixed with 0x) : ";
1263 std::cin >> l_newValue;
1264 std::cout << std::endl
1265 << std::string(191, '=') << std::endl;
1266
1267 try
1268 {
1269 l_rc = updateKeywordValue(
1270 l_srcVpdPath, l_aRecordKwInfo["sourceRecord"],
1271 l_aRecordKwInfo["sourceKeyword"],
1272 utils::convertToBinary(l_newValue));
1273 }
1274 catch (const std::exception& l_ex)
1275 {
1276 // TODO: Enable logging when verbose is enabled.
1277 std::cerr << l_ex.what() << std::endl;
1278 }
1279 }
1280 else if (types::UserOption::SkipCurrent == l_userSelectedOption)
1281 {
1282 std::cout << std::endl
1283 << "Skipped the above record-keyword pair. "
1284 "Continue to the next available pair."
1285 << std::endl;
1286 }
1287 else if (types::UserOption::Exit == l_userSelectedOption)
1288 {
1289 std::cout << "Exit successfully" << std::endl;
1290 l_exit = true;
1291 break;
1292 }
1293 else
1294 {
1295 std::cout << "Provide a valid option. Retrying for the "
1296 "current record-keyword pair"
1297 << std::endl;
1298 }
1299 }
1300 if (l_exit)
1301 {
1302 l_rc = constants::SUCCESS;
1303 break;
1304 }
1305 } while (true);
1306 }
1307 catch (const std::exception& l_ex)
1308 {
1309 // TODO: Enable logging when verbose is enabled.
1310 std::cerr << l_ex.what() << std::endl;
1311 }
1312
1313 return l_rc;
1314}
1315
Anupama B R6be2c012025-01-23 02:59:27 -06001316int VpdTool::resetVpdOnDbus()
1317{
1318 // ToDo: Implementation needs to be added
1319 return constants::SUCCESS;
1320}
1321
Souvik Roy7f749a62025-02-10 22:23:41 -06001322types::BinaryVector VpdTool::getVpdValueInBiosConfigManager(
1323 [[maybe_unused]] const std::string& i_recordName,
1324 [[maybe_unused]] const std::string& i_keywordName) const
1325{
1326 types::BinaryVector l_result;
1327 // TODO: Use Record name, Keyword name to identify BIOS attribute.
1328 // Get BIOS attribute value from BIOS Config Manager.
1329 // Convert BIOS attribute value to VPD format value in binary.
1330 return l_result;
1331}
Sunny Srivastavafa5e4d32023-03-12 11:59:49 -05001332} // namespace vpd