1 #pragma once
2
3 #include "config.h"
4
5 #include "constants.hpp"
6 #include "exceptions.hpp"
7 #include "logger.hpp"
8 #include "types.hpp"
9
10 #include <nlohmann/json.hpp>
11 #include <utility/common_utility.hpp>
12 #include <utility/dbus_utility.hpp>
13
14 #include <filesystem>
15 #include <fstream>
16 #include <regex>
17
18 namespace vpd
19 {
20 namespace vpdSpecificUtility
21 {
22 /**
23 * @brief API to generate file name for bad VPD.
24 *
25 * For i2c eeproms - the pattern of the vpd-name will be
26 * i2c-<bus-number>-<eeprom-address>.
27 * For spi eeproms - the pattern of the vpd-name will be spi-<spi-number>.
28 *
29 * @param[in] vpdFilePath - file path of the vpd.
30 * @return Generated file name.
31 */
generateBadVPDFileName(const std::string & vpdFilePath)32 inline std::string generateBadVPDFileName(const std::string& vpdFilePath)
33 {
34 std::string badVpdFileName = BAD_VPD_DIR;
35 if (vpdFilePath.find("i2c") != std::string::npos)
36 {
37 badVpdFileName += "i2c-";
38 std::regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
39 std::smatch match;
40 if (std::regex_search(vpdFilePath, match, i2cPattern))
41 {
42 badVpdFileName += match.str(2);
43 }
44 }
45 else if (vpdFilePath.find("spi") != std::string::npos)
46 {
47 std::regex spiPattern("((spi)[0-9]+)(.0)");
48 std::smatch match;
49 if (std::regex_search(vpdFilePath, match, spiPattern))
50 {
51 badVpdFileName += match.str(1);
52 }
53 }
54 return badVpdFileName;
55 }
56
57 /**
58 * @brief API which dumps the broken/bad vpd in a directory.
59 * When the vpd is bad, this API places the bad vpd file inside
60 * "/tmp/bad-vpd" in BMC, in order to collect bad VPD data as a part of user
61 * initiated BMC dump.
62 *
63 * Note: Throws exception in case of any failure.
64 *
65 * @param[in] vpdFilePath - vpd file path
66 * @param[in] vpdVector - vpd vector
67 */
dumpBadVpd(const std::string & vpdFilePath,const types::BinaryVector & vpdVector)68 inline void dumpBadVpd(const std::string& vpdFilePath,
69 const types::BinaryVector& vpdVector)
70 {
71 std::filesystem::create_directory(BAD_VPD_DIR);
72 auto badVpdPath = generateBadVPDFileName(vpdFilePath);
73
74 if (std::filesystem::exists(badVpdPath))
75 {
76 std::error_code ec;
77 std::filesystem::remove(badVpdPath, ec);
78 if (ec) // error code
79 {
80 std::string error = "Error removing the existing broken vpd in ";
81 error += badVpdPath;
82 error += ". Error code : ";
83 error += ec.value();
84 error += ". Error message : ";
85 error += ec.message();
86 throw std::runtime_error(error);
87 }
88 }
89
90 std::ofstream badVpdFileStream(badVpdPath, std::ofstream::binary);
91 if (badVpdFileStream.is_open())
92 {
93 throw std::runtime_error(
94 "Failed to open bad vpd file path in /tmp/bad-vpd. "
95 "Unable to dump the broken/bad vpd file.");
96 }
97
98 badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()),
99 vpdVector.size());
100 }
101
102 /**
103 * @brief An API to read value of a keyword.
104 *
105 * Note: Throws exception. Caller needs to handle.
106 *
107 * @param[in] kwdValueMap - A map having Kwd value pair.
108 * @param[in] kwd - keyword name.
109 * @param[out] kwdValue - Value of the keyword read from map.
110 */
getKwVal(const types::IPZKwdValueMap & kwdValueMap,const std::string & kwd,std::string & kwdValue)111 inline void getKwVal(const types::IPZKwdValueMap& kwdValueMap,
112 const std::string& kwd, std::string& kwdValue)
113 {
114 if (kwd.empty())
115 {
116 logging::logMessage("Invalid parameters");
117 throw std::runtime_error("Invalid parameters");
118 }
119
120 auto itrToKwd = kwdValueMap.find(kwd);
121 if (itrToKwd != kwdValueMap.end())
122 {
123 kwdValue = itrToKwd->second;
124 return;
125 }
126
127 throw std::runtime_error("Keyword not found");
128 }
129
130 /**
131 * @brief An API to process encoding of a keyword.
132 *
133 * @param[in] keyword - Keyword to be processed.
134 * @param[in] encoding - Type of encoding.
135 * @return Value after being processed for encoded type.
136 */
encodeKeyword(const std::string & keyword,const std::string & encoding)137 inline std::string encodeKeyword(const std::string& keyword,
138 const std::string& encoding)
139 {
140 // Default value is keyword value
141 std::string result(keyword.begin(), keyword.end());
142
143 if (encoding == "MAC")
144 {
145 result.clear();
146 size_t firstByte = keyword[0];
147 result += commonUtility::toHex(firstByte >> 4);
148 result += commonUtility::toHex(firstByte & 0x0f);
149 for (size_t i = 1; i < keyword.size(); ++i)
150 {
151 result += ":";
152 result += commonUtility::toHex(keyword[i] >> 4);
153 result += commonUtility::toHex(keyword[i] & 0x0f);
154 }
155 }
156 else if (encoding == "DATE")
157 {
158 // Date, represent as
159 // <year>-<month>-<day> <hour>:<min>
160 result.clear();
161 static constexpr uint8_t skipPrefix = 3;
162
163 auto strItr = keyword.begin();
164 advance(strItr, skipPrefix);
165 for_each(strItr, keyword.end(), [&result](size_t c) { result += c; });
166
167 result.insert(constants::BD_YEAR_END, 1, '-');
168 result.insert(constants::BD_MONTH_END, 1, '-');
169 result.insert(constants::BD_DAY_END, 1, ' ');
170 result.insert(constants::BD_HOUR_END, 1, ':');
171 }
172
173 return result;
174 }
175
176 /**
177 * @brief Helper function to insert or merge in map.
178 *
179 * This method checks in an interface if the given interface exists. If the
180 * interface key already exists, property map is inserted corresponding to it.
181 * If the key does'nt exist then given interface and property map pair is newly
182 * created. If the property present in propertymap already exist in the
183 * InterfaceMap, then the new property value is ignored.
184 *
185 * @param[in,out] map - Interface map.
186 * @param[in] interface - Interface to be processed.
187 * @param[in] propertyMap - new property map that needs to be emplaced.
188 */
insertOrMerge(types::InterfaceMap & map,const std::string & interface,types::PropertyMap && propertyMap)189 inline void insertOrMerge(types::InterfaceMap& map,
190 const std::string& interface,
191 types::PropertyMap&& propertyMap)
192 {
193 if (map.find(interface) != map.end())
194 {
195 try
196 {
197 auto& prop = map.at(interface);
198 std::for_each(propertyMap.begin(), propertyMap.end(),
199 [&prop](auto l_keyValue) {
200 prop[l_keyValue.first] = l_keyValue.second;
201 });
202 }
203 catch (const std::exception& l_ex)
204 {
205 // ToDo:: Log PEL
206 logging::logMessage(
207 "Inserting properties into interface[" + interface +
208 "] map is failed, reason: " + std::string(l_ex.what()));
209 }
210 }
211 else
212 {
213 map.emplace(interface, propertyMap);
214 }
215 }
216
217 /**
218 * @brief API to expand unpanded location code.
219 *
220 * Note: The API handles all the exception internally, in case of any error
221 * unexpanded location code will be returned as it is.
222 *
223 * @param[in] unexpandedLocationCode - Unexpanded location code.
224 * @param[in] parsedVpdMap - Parsed VPD map.
225 * @return Expanded location code. In case of any error, unexpanded is returned
226 * as it is.
227 */
228 inline std::string
getExpandedLocationCode(const std::string & unexpandedLocationCode,const types::VPDMapVariant & parsedVpdMap)229 getExpandedLocationCode(const std::string& unexpandedLocationCode,
230 const types::VPDMapVariant& parsedVpdMap)
231 {
232 auto expanded{unexpandedLocationCode};
233
234 try
235 {
236 // Expanded location code is formed by combining two keywords
237 // depending on type in unexpanded one. Second one is always "SE".
238 std::string kwd1, kwd2{constants::kwdSE};
239
240 // interface to search for required keywords;
241 std::string kwdInterface;
242
243 // record which holds the required keywords.
244 std::string recordName;
245
246 auto pos = unexpandedLocationCode.find("fcs");
247 if (pos != std::string::npos)
248 {
249 kwd1 = constants::kwdFC;
250 kwdInterface = constants::vcenInf;
251 recordName = constants::recVCEN;
252 }
253 else
254 {
255 pos = unexpandedLocationCode.find("mts");
256 if (pos != std::string::npos)
257 {
258 kwd1 = constants::kwdTM;
259 kwdInterface = constants::vsysInf;
260 recordName = constants::recVSYS;
261 }
262 else
263 {
264 throw std::runtime_error(
265 "Error detecting type of unexpanded location code.");
266 }
267 }
268
269 std::string firstKwdValue, secondKwdValue;
270
271 if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap);
272 ipzVpdMap && (*ipzVpdMap).find(recordName) != (*ipzVpdMap).end())
273 {
274 auto itrToVCEN = (*ipzVpdMap).find(recordName);
275 // The exceptions will be cautght at end.
276 getKwVal(itrToVCEN->second, kwd1, firstKwdValue);
277 getKwVal(itrToVCEN->second, kwd2, secondKwdValue);
278 }
279 else
280 {
281 std::array<const char*, 1> interfaceList = {kwdInterface.c_str()};
282
283 types::MapperGetObject mapperRetValue = dbusUtility::getObjectMap(
284 std::string(constants::systemVpdInvPath), interfaceList);
285
286 if (mapperRetValue.empty())
287 {
288 throw std::runtime_error("Mapper failed to get service");
289 }
290
291 const std::string& serviceName = std::get<0>(mapperRetValue.at(0));
292
293 auto retVal = dbusUtility::readDbusProperty(
294 serviceName, std::string(constants::systemVpdInvPath),
295 kwdInterface, kwd1);
296
297 if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
298 {
299 firstKwdValue.assign(
300 reinterpret_cast<const char*>(kwdVal->data()),
301 kwdVal->size());
302 }
303 else
304 {
305 throw std::runtime_error(
306 "Failed to read value of " + kwd1 + " from Bus");
307 }
308
309 retVal = dbusUtility::readDbusProperty(
310 serviceName, std::string(constants::systemVpdInvPath),
311 kwdInterface, kwd2);
312
313 if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
314 {
315 secondKwdValue.assign(
316 reinterpret_cast<const char*>(kwdVal->data()),
317 kwdVal->size());
318 }
319 else
320 {
321 throw std::runtime_error(
322 "Failed to read value of " + kwd2 + " from Bus");
323 }
324 }
325
326 if (unexpandedLocationCode.find("fcs") != std::string::npos)
327 {
328 // TODO: See if ND0 can be placed in the JSON
329 expanded.replace(
330 pos, 3, firstKwdValue.substr(0, 4) + ".ND0." + secondKwdValue);
331 }
332 else
333 {
334 replace(firstKwdValue.begin(), firstKwdValue.end(), '-', '.');
335 expanded.replace(pos, 3, firstKwdValue + "." + secondKwdValue);
336 }
337 }
338 catch (const std::exception& ex)
339 {
340 logging::logMessage("Failed to expand location code with exception: " +
341 std::string(ex.what()));
342 }
343
344 return expanded;
345 }
346
347 /**
348 * @brief An API to get VPD in a vector.
349 *
350 * The vector is required by the respective parser to fill the VPD map.
351 * Note: API throws exception in case of failure. Caller needs to handle.
352 *
353 * @param[in] vpdFilePath - EEPROM path of the FRU.
354 * @param[out] vpdVector - VPD in vector form.
355 * @param[in] vpdStartOffset - Offset of VPD data in EEPROM.
356 */
getVpdDataInVector(const std::string & vpdFilePath,types::BinaryVector & vpdVector,size_t & vpdStartOffset)357 inline void getVpdDataInVector(const std::string& vpdFilePath,
358 types::BinaryVector& vpdVector,
359 size_t& vpdStartOffset)
360 {
361 try
362 {
363 std::fstream vpdFileStream;
364 vpdFileStream.exceptions(
365 std::ifstream::badbit | std::ifstream::failbit);
366 vpdFileStream.open(vpdFilePath, std::ios::in | std::ios::binary);
367 auto vpdSizeToRead = std::min(std::filesystem::file_size(vpdFilePath),
368 static_cast<uintmax_t>(65504));
369 vpdVector.resize(vpdSizeToRead);
370
371 vpdFileStream.seekg(vpdStartOffset, std::ios_base::beg);
372 vpdFileStream.read(reinterpret_cast<char*>(&vpdVector[0]),
373 vpdSizeToRead);
374
375 vpdVector.resize(vpdFileStream.gcount());
376 vpdFileStream.clear(std::ios_base::eofbit);
377 }
378 catch (const std::ifstream::failure& fail)
379 {
380 std::cerr << "Exception in file handling [" << vpdFilePath
381 << "] error : " << fail.what();
382 throw;
383 }
384 }
385
386 /**
387 * @brief An API to get D-bus representation of given VPD keyword.
388 *
389 * @param[in] i_keywordName - VPD keyword name.
390 *
391 * @return D-bus representation of given keyword.
392 */
getDbusPropNameForGivenKw(const std::string & i_keywordName)393 inline std::string getDbusPropNameForGivenKw(const std::string& i_keywordName)
394 {
395 // Check for "#" prefixed VPD keyword.
396 if ((i_keywordName.size() == vpd::constants::TWO_BYTES) &&
397 (i_keywordName.at(0) == constants::POUND_KW))
398 {
399 // D-bus doesn't support "#". Replace "#" with "PD_" for those "#"
400 // prefixed keywords.
401 return (std::string(constants::POUND_KW_PREFIX) +
402 i_keywordName.substr(1));
403 }
404
405 // Return the keyword name back, if D-bus representation is same as the VPD
406 // keyword name.
407 return i_keywordName;
408 }
409
410 /**
411 * @brief API to find CCIN in parsed VPD map.
412 *
413 * Few FRUs need some special handling. To identify those FRUs CCIN are used.
414 * The API will check from parsed VPD map if the FRU is the one with desired
415 * CCIN.
416 *
417 * @throw std::runtime_error
418 * @throw DataException
419 *
420 * @param[in] i_JsonObject - Any JSON which contains CCIN tag to match.
421 * @param[in] i_parsedVpdMap - Parsed VPD map.
422 * @return True if found, false otherwise.
423 */
findCcinInVpd(const nlohmann::json & i_JsonObject,const types::VPDMapVariant & i_parsedVpdMap)424 inline bool findCcinInVpd(const nlohmann::json& i_JsonObject,
425 const types::VPDMapVariant& i_parsedVpdMap)
426 {
427 if (i_JsonObject.empty())
428 {
429 throw std::runtime_error("Json object is empty. Can't find CCIN");
430 }
431
432 if (auto l_ipzVPDMap = std::get_if<types::IPZVpdMap>(&i_parsedVpdMap))
433 {
434 auto l_itrToRec = (*l_ipzVPDMap).find("VINI");
435 if (l_itrToRec == (*l_ipzVPDMap).end())
436 {
437 throw DataException(
438 "VINI record not found in parsed VPD. Can't find CCIN");
439 }
440
441 std::string l_ccinFromVpd;
442 vpdSpecificUtility::getKwVal(l_itrToRec->second, "CC", l_ccinFromVpd);
443 if (l_ccinFromVpd.empty())
444 {
445 throw DataException("Empty CCIN value in VPD map. Can't find CCIN");
446 }
447
448 transform(l_ccinFromVpd.begin(), l_ccinFromVpd.end(),
449 l_ccinFromVpd.begin(), ::toupper);
450
451 for (std::string l_ccinValue : i_JsonObject["ccin"])
452 {
453 transform(l_ccinValue.begin(), l_ccinValue.end(),
454 l_ccinValue.begin(), ::toupper);
455
456 if (l_ccinValue.compare(l_ccinFromVpd) ==
457 constants::STR_CMP_SUCCESS)
458 {
459 // CCIN found
460 return true;
461 }
462 }
463
464 logging::logMessage("No match found for CCIN");
465 return false;
466 }
467
468 logging::logMessage("VPD type not supported. Can't find CCIN");
469 return false;
470 }
471
472 /**
473 * @brief API to reset data of a FRU populated under PIM.
474 *
475 * This API resets the data for particular interfaces of a FRU under PIM.
476 *
477 * @param[in] i_objectPath - DBus object path of the FRU.
478 * @param[in] io_interfaceMap - Interface and its properties map.
479 */
resetDataUnderPIM(const std::string & i_objectPath,types::InterfaceMap & io_interfaceMap)480 inline void resetDataUnderPIM(const std::string& i_objectPath,
481 types::InterfaceMap& io_interfaceMap)
482 {
483 try
484 {
485 std::array<const char*, 0> l_interfaces;
486 const types::MapperGetObject& l_getObjectMap =
487 dbusUtility::getObjectMap(i_objectPath, l_interfaces);
488
489 const std::vector<std::string>& l_vpdRelatedInterfaces{
490 constants::operationalStatusInf, constants::inventoryItemInf,
491 constants::assetInf};
492
493 for (const auto& [l_service, l_interfaceList] : l_getObjectMap)
494 {
495 if (l_service.compare(constants::pimServiceName) !=
496 constants::STR_CMP_SUCCESS)
497 {
498 continue;
499 }
500
501 for (const auto& l_interface : l_interfaceList)
502 {
503 if ((l_interface.find(constants::ipzVpdInf) !=
504 std::string::npos) ||
505 ((std::find(l_vpdRelatedInterfaces.begin(),
506 l_vpdRelatedInterfaces.end(), l_interface)) !=
507 l_vpdRelatedInterfaces.end()))
508 {
509 const types::PropertyMap& l_propertyValueMap =
510 dbusUtility::getPropertyMap(l_service, i_objectPath,
511 l_interface);
512
513 types::PropertyMap l_propertyMap;
514
515 for (const auto& l_aProperty : l_propertyValueMap)
516 {
517 const std::string& l_propertyName = l_aProperty.first;
518 const auto& l_propertyValue = l_aProperty.second;
519
520 if (std::holds_alternative<types::BinaryVector>(
521 l_propertyValue))
522 {
523 l_propertyMap.emplace(l_propertyName,
524 types::BinaryVector{});
525 }
526 else if (std::holds_alternative<std::string>(
527 l_propertyValue))
528 {
529 l_propertyMap.emplace(l_propertyName,
530 std::string{});
531 }
532 else if (std::holds_alternative<bool>(l_propertyValue))
533 {
534 // ToDo -- Update the functional status property
535 // to true.
536 if (l_propertyName.compare("Present") ==
537 constants::STR_CMP_SUCCESS)
538 {
539 l_propertyMap.emplace(l_propertyName, false);
540 }
541 }
542 }
543 io_interfaceMap.emplace(l_interface,
544 std::move(l_propertyMap));
545 }
546 }
547 }
548 }
549 catch (const std::exception& l_ex)
550 {
551 logging::logMessage("Failed to remove VPD for FRU: " + i_objectPath +
552 " with error: " + std::string(l_ex.what()));
553 }
554 }
555 } // namespace vpdSpecificUtility
556 } // namespace vpd
557