#include "config.h" #include "ipz_parser.hpp" #include "vpdecc/vpdecc.h" #include "constants.hpp" #include "exceptions.hpp" #include <nlohmann/json.hpp> #include <typeindex> namespace vpd { // Offset of different entries in VPD data. enum Offset { VHDR = 17, VHDR_TOC_ENTRY = 29, VTOC_PTR = 35, VTOC_REC_LEN = 37, VTOC_ECC_OFF = 39, VTOC_ECC_LEN = 41, VTOC_DATA = 13, VHDR_ECC = 0, VHDR_RECORD = 11 }; // Length of some specific entries w.r.t VPD data. enum Length { RECORD_NAME = 4, KW_NAME = 2, RECORD_OFFSET = 2, RECORD_MIN = 44, RECORD_LENGTH = 2, RECORD_ECC_OFFSET = 2, VHDR_ECC_LENGTH = 11, VHDR_RECORD_LENGTH = 44, RECORD_TYPE = 2, SKIP_A_RECORD_IN_PT = 14, JUMP_TO_RECORD_NAME = 6 }; // enum Length /** * @brief API to read 2 bytes LE data. * * @param[in] iterator - iterator to VPD vector. * @return read bytes. */ static uint16_t readUInt16LE(types::BinaryVector::const_iterator iterator) { uint16_t lowByte = *iterator; uint16_t highByte = *(iterator + 1); lowByte |= (highByte << 8); return lowByte; } bool IpzVpdParser::vhdrEccCheck() { auto vpdPtr = m_vpdVector.cbegin(); auto l_status = vpdecc_check_data( const_cast<uint8_t*>(&vpdPtr[Offset::VHDR_RECORD]), Length::VHDR_RECORD_LENGTH, const_cast<uint8_t*>(&vpdPtr[Offset::VHDR_ECC]), Length::VHDR_ECC_LENGTH); if (l_status == VPD_ECC_CORRECTABLE_DATA) { try { if (m_vpdFileStream.is_open()) { m_vpdFileStream.seekp(m_vpdStartOffset + Offset::VHDR_RECORD, std::ios::beg); m_vpdFileStream.write(reinterpret_cast<const char*>( &m_vpdVector[Offset::VHDR_RECORD]), Length::VHDR_RECORD_LENGTH); } else { logging::logMessage("File not open"); return false; } } catch (const std::fstream::failure& e) { logging::logMessage( "Error while operating on file with exception: " + std::string(e.what())); return false; } } else if (l_status != VPD_ECC_OK) { return false; } return true; } bool IpzVpdParser::vtocEccCheck() { auto vpdPtr = m_vpdVector.cbegin(); std::advance(vpdPtr, Offset::VTOC_PTR); // The offset to VTOC could be 1 or 2 bytes long auto vtocOffset = readUInt16LE(vpdPtr); // Get the VTOC Length std::advance(vpdPtr, sizeof(types::RecordOffset)); auto vtocLength = readUInt16LE(vpdPtr); // Get the ECC Offset std::advance(vpdPtr, sizeof(types::RecordLength)); auto vtocECCOffset = readUInt16LE(vpdPtr); // Get the ECC length std::advance(vpdPtr, sizeof(types::ECCOffset)); auto vtocECCLength = readUInt16LE(vpdPtr); // Reset pointer to start of the vpd, // so that Offset will point to correct address vpdPtr = m_vpdVector.cbegin(); auto l_status = vpdecc_check_data( const_cast<uint8_t*>(&m_vpdVector[vtocOffset]), vtocLength, const_cast<uint8_t*>(&m_vpdVector[vtocECCOffset]), vtocECCLength); if (l_status == VPD_ECC_CORRECTABLE_DATA) { try { if (m_vpdFileStream.is_open()) { m_vpdFileStream.seekp(m_vpdStartOffset + vtocOffset, std::ios::beg); m_vpdFileStream.write( reinterpret_cast<const char*>(&m_vpdVector[vtocOffset]), vtocLength); } else { logging::logMessage("File not open"); return false; } } catch (const std::fstream::failure& e) { logging::logMessage( "Error while operating on file with exception " + std::string(e.what())); return false; } } else if (l_status != VPD_ECC_OK) { return false; } return true; } bool IpzVpdParser::recordEccCheck(types::BinaryVector::const_iterator iterator) { auto recordOffset = readUInt16LE(iterator); std::advance(iterator, sizeof(types::RecordOffset)); auto recordLength = readUInt16LE(iterator); if (recordOffset == 0 || recordLength == 0) { throw(DataException("Invalid record offset or length")); } std::advance(iterator, sizeof(types::RecordLength)); auto eccOffset = readUInt16LE(iterator); std::advance(iterator, sizeof(types::ECCOffset)); auto eccLength = readUInt16LE(iterator); if (eccLength == 0 || eccOffset == 0) { throw(EccException("Invalid ECC length or offset.")); } auto vpdPtr = m_vpdVector.cbegin(); if (vpdecc_check_data( const_cast<uint8_t*>(&vpdPtr[recordOffset]), recordLength, const_cast<uint8_t*>(&vpdPtr[eccOffset]), eccLength) == VPD_ECC_OK) { return true; } return false; } void IpzVpdParser::checkHeader(types::BinaryVector::const_iterator itrToVPD) { if (m_vpdVector.empty() || (Length::RECORD_MIN > m_vpdVector.size())) { throw(DataException("Malformed VPD")); } std::advance(itrToVPD, Offset::VHDR); auto stop = std::next(itrToVPD, Length::RECORD_NAME); std::string record(itrToVPD, stop); if ("VHDR" != record) { throw(DataException("VHDR record not found")); } if (!vhdrEccCheck()) { throw(EccException("ERROR: VHDR ECC check Failed")); } } auto IpzVpdParser::readTOC(types::BinaryVector::const_iterator& itrToVPD) { // The offset to VTOC could be 1 or 2 bytes long uint16_t vtocOffset = readUInt16LE((itrToVPD + Offset::VTOC_PTR)); // itrToVPD); // Got the offset to VTOC, skip past record header and keyword header // to get to the record name. std::advance(itrToVPD, vtocOffset + sizeof(types::RecordId) + sizeof(types::RecordSize) + // Skip past the RT keyword, which contains // the record name. Length::KW_NAME + sizeof(types::KwSize)); std::string record(itrToVPD, std::next(itrToVPD, Length::RECORD_NAME)); if ("VTOC" != record) { throw(DataException("VTOC record not found")); } if (!vtocEccCheck()) { throw(EccException("ERROR: VTOC ECC check Failed")); } // VTOC record name is good, now read through the TOC, stored in the PT // PT keyword; vpdBuffer is now pointing at the first character of the // name 'VTOC', jump to PT data. // Skip past record name and KW name, 'PT' std::advance(itrToVPD, Length::RECORD_NAME + Length::KW_NAME); // Note size of PT auto ptLen = *itrToVPD; // Skip past PT size std::advance(itrToVPD, sizeof(types::KwSize)); // length of PT keyword return ptLen; } types::RecordOffsetList IpzVpdParser::readPT( types::BinaryVector::const_iterator& itrToPT, auto ptLength) { types::RecordOffsetList recordOffsets; auto end = itrToPT; std::advance(end, ptLength); // Look at each entry in the PT keyword. In the entry, // we care only about the record offset information. while (itrToPT < end) { std::string recordName(itrToPT, itrToPT + Length::RECORD_NAME); // Skip record name and record type std::advance(itrToPT, Length::RECORD_NAME + sizeof(types::RecordType)); // Get record offset recordOffsets.push_back(readUInt16LE(itrToPT)); try { // Verify the ECC for this Record if (!recordEccCheck(itrToPT)) { throw(EccException("ERROR: ECC check failed")); } } catch (const EccException& ex) { logging::logMessage(ex.what()); /*TODO: uncomment when PEL code goes in */ /*std::string errMsg = std::string{ex.what()} + " Record: " + recordName; inventory::PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", errMsg); additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath); createPEL(additionalData, PelSeverity::WARNING, errIntfForEccCheckFail, nullptr);*/ } catch (const DataException& ex) { logging::logMessage(ex.what()); /*TODO: uncomment when PEL code goes in */ /*std::string errMsg = std::string{ex.what()} + " Record: " + recordName; inventory::PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", errMsg); additionalData.emplace("CALLOUT_INVENTORY_PATH", inventoryPath); createPEL(additionalData, PelSeverity::WARNING, errIntfForInvalidVPD, nullptr);*/ } // Jump record size, record length, ECC offset and ECC length std::advance(itrToPT, sizeof(types::RecordOffset) + sizeof(types::RecordLength) + sizeof(types::ECCOffset) + sizeof(types::ECCLength)); } return recordOffsets; } types::IPZVpdMap::mapped_type IpzVpdParser::readKeywords(types::BinaryVector::const_iterator& itrToKwds) { types::IPZVpdMap::mapped_type kwdValueMap{}; while (true) { // Note keyword name std::string kwdName(itrToKwds, itrToKwds + Length::KW_NAME); if (constants::LAST_KW == kwdName) { // We're done break; } // Check if the Keyword is '#kw' char kwNameStart = *itrToKwds; // Jump past keyword name std::advance(itrToKwds, Length::KW_NAME); std::size_t kwdDataLength; std::size_t lengthHighByte; if (constants::POUND_KW == kwNameStart) { // Note keyword data length kwdDataLength = *itrToKwds; lengthHighByte = *(itrToKwds + 1); kwdDataLength |= (lengthHighByte << 8); // Jump past 2Byte keyword length std::advance(itrToKwds, sizeof(types::PoundKwSize)); } else { // Note keyword data length kwdDataLength = *itrToKwds; // Jump past keyword length std::advance(itrToKwds, sizeof(types::KwSize)); } // support all the Keywords auto stop = std::next(itrToKwds, kwdDataLength); std::string kwdata(itrToKwds, stop); kwdValueMap.emplace(std::move(kwdName), std::move(kwdata)); // Jump past keyword data length std::advance(itrToKwds, kwdDataLength); } return kwdValueMap; } void IpzVpdParser::processRecord(auto recordOffset) { // Jump to record name auto recordNameOffset = recordOffset + sizeof(types::RecordId) + sizeof(types::RecordSize) + // Skip past the RT keyword, which contains // the record name. Length::KW_NAME + sizeof(types::KwSize); // Get record name auto itrToVPDStart = m_vpdVector.cbegin(); std::advance(itrToVPDStart, recordNameOffset); std::string recordName(itrToVPDStart, itrToVPDStart + Length::RECORD_NAME); // proceed to find contained keywords and their values. std::advance(itrToVPDStart, Length::RECORD_NAME); // Reverse back to RT Kw, in ipz vpd, to Read RT KW & value std::advance(itrToVPDStart, -(Length::KW_NAME + sizeof(types::KwSize) + Length::RECORD_NAME)); // Add entry for this record (and contained keyword:value pairs) // to the parsed vpd output. m_parsedVPDMap.emplace(std::move(recordName), std::move(readKeywords(itrToVPDStart))); } types::VPDMapVariant IpzVpdParser::parse() { try { auto itrToVPD = m_vpdVector.cbegin(); // Check vaidity of VHDR record checkHeader(itrToVPD); // Read the table of contents auto ptLen = readTOC(itrToVPD); // Read the table of contents record, to get offsets // to other records. auto recordOffsets = readPT(itrToVPD, ptLen); for (const auto& offset : recordOffsets) { processRecord(offset); } return m_parsedVPDMap; } catch (const std::exception& e) { logging::logMessage(e.what()); throw e; } } types::BinaryVector IpzVpdParser::getKeywordValueFromRecord( const types::Record& i_recordName, const types::Keyword& i_keywordName, const types::RecordOffset& i_recordDataOffset) { auto l_iterator = m_vpdVector.cbegin(); // Go to the record name in the given record's offset std::ranges::advance(l_iterator, i_recordDataOffset + Length::JUMP_TO_RECORD_NAME, m_vpdVector.cend()); // Check if the record is present in the given record's offset if (i_recordName != std::string(l_iterator, std::ranges::next(l_iterator, Length::RECORD_NAME, m_vpdVector.cend()))) { throw std::runtime_error( "Given record is not present in the offset provided"); } std::ranges::advance(l_iterator, Length::RECORD_NAME, m_vpdVector.cend()); std::string l_kwName = std::string( l_iterator, std::ranges::next(l_iterator, Length::KW_NAME, m_vpdVector.cend())); // Iterate through the keywords until the last keyword PF is found. while (l_kwName != constants::LAST_KW) { // First character required for #D keyword check char l_kwNameStart = *l_iterator; std::ranges::advance(l_iterator, Length::KW_NAME, m_vpdVector.cend()); // Get the keyword's data length auto l_kwdDataLength = 0; if (constants::POUND_KW == l_kwNameStart) { l_kwdDataLength = readUInt16LE(l_iterator); std::ranges::advance(l_iterator, sizeof(types::PoundKwSize), m_vpdVector.cend()); } else { l_kwdDataLength = *l_iterator; std::ranges::advance(l_iterator, sizeof(types::KwSize), m_vpdVector.cend()); } if (l_kwName == i_keywordName) { // Return keyword's value to the caller return types::BinaryVector( l_iterator, std::ranges::next(l_iterator, l_kwdDataLength, m_vpdVector.cend())); } // next keyword search std::ranges::advance(l_iterator, l_kwdDataLength, m_vpdVector.cend()); // next keyword name l_kwName = std::string( l_iterator, std::ranges::next(l_iterator, Length::KW_NAME, m_vpdVector.cend())); } // Keyword not found throw std::runtime_error("Given keyword not found."); } types::RecordData IpzVpdParser::getRecordDetailsFromVTOC( const types::Record& i_recordName, const types::RecordOffset& i_vtocOffset) { // Get VTOC's PT keyword value. const auto l_vtocPTKwValue = getKeywordValueFromRecord("VTOC", "PT", i_vtocOffset); // Parse through VTOC PT keyword value to find the record which we are // interested in. auto l_vtocPTItr = l_vtocPTKwValue.cbegin(); types::RecordData l_recordData; while (l_vtocPTItr < l_vtocPTKwValue.cend()) { if (i_recordName == std::string(l_vtocPTItr, l_vtocPTItr + Length::RECORD_NAME)) { // Record found in VTOC PT keyword. Get offset std::ranges::advance(l_vtocPTItr, Length::RECORD_NAME + Length::RECORD_TYPE, l_vtocPTKwValue.cend()); const auto l_recordOffset = readUInt16LE(l_vtocPTItr); std::ranges::advance(l_vtocPTItr, Length::RECORD_OFFSET, l_vtocPTKwValue.cend()); const auto l_recordLength = readUInt16LE(l_vtocPTItr); std::ranges::advance(l_vtocPTItr, Length::RECORD_LENGTH, l_vtocPTKwValue.cend()); const auto l_eccOffset = readUInt16LE(l_vtocPTItr); std::ranges::advance(l_vtocPTItr, Length::RECORD_ECC_OFFSET, l_vtocPTKwValue.cend()); const auto l_eccLength = readUInt16LE(l_vtocPTItr); l_recordData = std::make_tuple(l_recordOffset, l_recordLength, l_eccOffset, l_eccLength); break; } std::ranges::advance(l_vtocPTItr, Length::SKIP_A_RECORD_IN_PT, l_vtocPTKwValue.cend()); } return l_recordData; } types::DbusVariantType IpzVpdParser::readKeywordFromHardware( const types::ReadVpdParams i_paramsToReadData) { // Extract record and keyword from i_paramsToReadData types::Record l_record; types::Keyword l_keyword; if (const types::IpzType* l_ipzData = std::get_if<types::IpzType>(&i_paramsToReadData)) { l_record = std::get<0>(*l_ipzData); l_keyword = std::get<1>(*l_ipzData); } else { logging::logMessage( "Input parameter type provided isn't compatible with the given VPD type."); throw types::DbusInvalidArgument(); } // Read keyword's value from vector auto l_itrToVPD = m_vpdVector.cbegin(); if (l_record == "VHDR") { // Disable providing a way to read keywords from VHDR for the time being. #if 0 std::ranges::advance(l_itrToVPD, Offset::VHDR_RECORD, m_vpdVector.cend()); return types::DbusVariantType{getKeywordValueFromRecord( l_record, l_keyword, Offset::VHDR_RECORD)}; #endif logging::logMessage("Read cannot be performed on VHDR record."); throw types::DbusInvalidArgument(); } // Get VTOC offset std::ranges::advance(l_itrToVPD, Offset::VTOC_PTR, m_vpdVector.cend()); auto l_vtocOffset = readUInt16LE(l_itrToVPD); if (l_record == "VTOC") { // Disable providing a way to read keywords from VTOC for the time // being. #if 0 return types::DbusVariantType{ getKeywordValueFromRecord(l_record, l_keyword, l_vtocOffset)}; #endif logging::logMessage("Read cannot be performed on VTOC record."); throw types::DbusInvalidArgument(); } // Get record offset from VTOC's PT keyword value. auto l_recordData = getRecordDetailsFromVTOC(l_record, l_vtocOffset); const auto l_recordOffset = std::get<0>(l_recordData); if (l_recordOffset == 0) { throw std::runtime_error("Record not found in VTOC PT keyword."); } // Get the given keyword's value return types::DbusVariantType{ getKeywordValueFromRecord(l_record, l_keyword, l_recordOffset)}; } void IpzVpdParser::updateRecordECC( const auto& i_recordDataOffset, const auto& i_recordDataLength, const auto& i_recordECCOffset, size_t i_recordECCLength, types::BinaryVector& io_vpdVector) { auto l_recordDataBegin = std::next(io_vpdVector.begin(), i_recordDataOffset); auto l_recordECCBegin = std::next(io_vpdVector.begin(), i_recordECCOffset); auto l_eccStatus = vpdecc_create_ecc( const_cast<uint8_t*>(&l_recordDataBegin[0]), i_recordDataLength, const_cast<uint8_t*>(&l_recordECCBegin[0]), &i_recordECCLength); if (l_eccStatus != VPD_ECC_OK) { throw(EccException("ECC update failed with error " + l_eccStatus)); } auto l_recordECCEnd = std::next(l_recordECCBegin, i_recordECCLength); m_vpdFileStream.seekp(m_vpdStartOffset + i_recordECCOffset, std::ios::beg); std::copy(l_recordECCBegin, l_recordECCEnd, std::ostreambuf_iterator<char>(m_vpdFileStream)); } int IpzVpdParser::setKeywordValueInRecord( const types::Record& i_recordName, const types::Keyword& i_keywordName, const types::BinaryVector& i_keywordData, const types::RecordOffset& i_recordDataOffset, types::BinaryVector& io_vpdVector) { auto l_iterator = io_vpdVector.begin(); // Go to the record name in the given record's offset std::ranges::advance(l_iterator, i_recordDataOffset + Length::JUMP_TO_RECORD_NAME, io_vpdVector.end()); const std::string l_recordFound( l_iterator, std::ranges::next(l_iterator, Length::RECORD_NAME, io_vpdVector.end())); // Check if the record is present in the given record's offset if (i_recordName != l_recordFound) { throw(DataException("Given record found at the offset " + std::to_string(i_recordDataOffset) + " is : " + l_recordFound + " and not " + i_recordName)); } std::ranges::advance(l_iterator, Length::RECORD_NAME, io_vpdVector.end()); std::string l_kwName = std::string( l_iterator, std::ranges::next(l_iterator, Length::KW_NAME, io_vpdVector.end())); // Iterate through the keywords until the last keyword PF is found. while (l_kwName != constants::LAST_KW) { // First character required for #D keyword check char l_kwNameStart = *l_iterator; std::ranges::advance(l_iterator, Length::KW_NAME, io_vpdVector.end()); // Find the keyword's data length size_t l_kwdDataLength = 0; if (constants::POUND_KW == l_kwNameStart) { l_kwdDataLength = readUInt16LE(l_iterator); std::ranges::advance(l_iterator, sizeof(types::PoundKwSize), io_vpdVector.end()); } else { l_kwdDataLength = *l_iterator; std::ranges::advance(l_iterator, sizeof(types::KwSize), io_vpdVector.end()); } if (l_kwName == i_keywordName) { // Before writing the keyword's value, get the maximum size that can // be updated. const auto l_lengthToUpdate = i_keywordData.size() <= l_kwdDataLength ? i_keywordData.size() : l_kwdDataLength; // Set the keyword's value on vector. This is required to update the // record's ECC based on the new value set. const auto i_keywordDataEnd = std::ranges::next( i_keywordData.cbegin(), l_lengthToUpdate, i_keywordData.cend()); std::copy(i_keywordData.cbegin(), i_keywordDataEnd, l_iterator); // Set the keyword's value on hardware const auto l_kwdDataOffset = std::distance(io_vpdVector.begin(), l_iterator); m_vpdFileStream.seekp(m_vpdStartOffset + l_kwdDataOffset, std::ios::beg); std::copy(i_keywordData.cbegin(), i_keywordDataEnd, std::ostreambuf_iterator<char>(m_vpdFileStream)); // return no of bytes set return l_lengthToUpdate; } // next keyword search std::ranges::advance(l_iterator, l_kwdDataLength, io_vpdVector.end()); // next keyword name l_kwName = std::string( l_iterator, std::ranges::next(l_iterator, Length::KW_NAME, io_vpdVector.end())); } // Keyword not found throw(DataException( "Keyword " + i_keywordName + " not found in record " + i_recordName)); } int IpzVpdParser::writeKeywordOnHardware( const types::WriteVpdParams i_paramsToWriteData) { int l_sizeWritten = -1; try { types::Record l_recordName; types::Keyword l_keywordName; types::BinaryVector l_keywordData; // Extract record, keyword and value from i_paramsToWriteData if (const types::IpzData* l_ipzData = std::get_if<types::IpzData>(&i_paramsToWriteData)) { l_recordName = std::get<0>(*l_ipzData); l_keywordName = std::get<1>(*l_ipzData); l_keywordData = std::get<2>(*l_ipzData); } else { logging::logMessage( "Input parameter type provided isn't compatible with the given FRU's VPD type."); throw types::DbusInvalidArgument(); } if (l_recordName == "VHDR" || l_recordName == "VTOC") { logging::logMessage( "Write operation not allowed on the given record : " + l_recordName); throw types::DbusNotAllowed(); } if (l_keywordData.size() == 0) { logging::logMessage( "Write operation not allowed as the given keyword's data length is 0."); throw types::DbusInvalidArgument(); } auto l_vpdBegin = m_vpdVector.begin(); // Get VTOC offset std::ranges::advance(l_vpdBegin, Offset::VTOC_PTR, m_vpdVector.end()); auto l_vtocOffset = readUInt16LE(l_vpdBegin); // Get the details of user given record from VTOC const types::RecordData& l_inputRecordDetails = getRecordDetailsFromVTOC(l_recordName, l_vtocOffset); const auto& l_inputRecordOffset = std::get<0>(l_inputRecordDetails); if (l_inputRecordOffset == 0) { throw(DataException("Record not found in VTOC PT keyword.")); } // Create a local copy of m_vpdVector to perform keyword update and ecc // update on filestream. types::BinaryVector l_vpdVector = m_vpdVector; // write keyword's value on hardware l_sizeWritten = setKeywordValueInRecord(l_recordName, l_keywordName, l_keywordData, l_inputRecordOffset, l_vpdVector); if (l_sizeWritten <= 0) { throw(DataException("Unable to set value on " + l_recordName + ":" + l_keywordName)); } // Update the record's ECC updateRecordECC(l_inputRecordOffset, std::get<1>(l_inputRecordDetails), std::get<2>(l_inputRecordDetails), std::get<3>(l_inputRecordDetails), l_vpdVector); logging::logMessage(std::to_string(l_sizeWritten) + " bytes updated successfully on hardware for " + l_recordName + ":" + l_keywordName); } catch (const std::exception& l_exception) { throw; } return l_sizeWritten; } } // namespace vpd