| /* |
| * SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & |
| * AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "utils.hpp" |
| |
| #include "dBusAsyncUtils.hpp" |
| |
| #include <fcntl.h> // For ftruncate |
| #include <sys/mman.h> // for memfd_create |
| #include <sys/stat.h> // for fstat |
| #include <unistd.h> // for write and lseek |
| |
| #include <boost/regex.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| #include <xyz/openbmc_project/Logging/Entry/server.hpp> |
| #include <xyz/openbmc_project/Software/ExtendedVersion/server.hpp> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cctype> |
| #include <ctime> |
| #include <fstream> |
| #include <future> |
| #include <iostream> |
| #include <map> |
| #include <mutex> |
| #include <stdexcept> |
| #include <string> |
| #include <vector> |
| |
| namespace utils |
| { |
| |
| static const boost::regex invalidDBusNameSubString{"[^a-zA-Z0-9._/]+"}; |
| static const uint32_t INVALID_UINT32_VALUE = 0xFFFFFFFF; |
| uuid_t convertUUIDToString(const std::vector<uint8_t>& uuidIntArr) |
| { |
| if (uuidIntArr.size() != UUID_INT_SIZE) |
| { |
| lg2::error("UUID Conversion: Failed, integer UUID size is not {UUIDSZ}", |
| "UUIDSZ", UUID_INT_SIZE); |
| return ""; |
| } |
| uuid_t uuidStr(UUID_LEN + 1, 0); |
| |
| snprintf( |
| const_cast<char*>(uuidStr.data()), uuidStr.size(), |
| "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
| uuidIntArr[0], uuidIntArr[1], uuidIntArr[2], uuidIntArr[3], |
| uuidIntArr[4], uuidIntArr[5], uuidIntArr[6], uuidIntArr[7], |
| uuidIntArr[8], uuidIntArr[9], uuidIntArr[10], uuidIntArr[11], |
| uuidIntArr[12], uuidIntArr[13], uuidIntArr[14], uuidIntArr[15]); |
| |
| return uuidStr; |
| } |
| |
| std::string convertHexToString(const std::vector<uint8_t>& data, |
| const size_t dataSize) |
| { |
| std::string result(""); |
| if (dataSize == 0) |
| { |
| return result; |
| } |
| |
| std::vector<uint8_t> nvu8ArrVal(dataSize, 0); |
| memcpy(nvu8ArrVal.data(), data.data(), dataSize); |
| |
| for (const auto& token : nvu8ArrVal) |
| { |
| result += std::format("{:02x}", token); |
| } |
| return result; |
| } |
| |
| void printBuffer(bool isTx, const std::vector<uint8_t>& buffer, uint8_t tag, |
| eid_t eid) |
| { |
| if (!buffer.empty()) |
| { |
| // Length of "EID: 1d, TAG: 03, Tx: " |
| constexpr size_t headerSize = 22; |
| // Length of "89 " |
| constexpr size_t hexWithSpaceSize = 3; |
| std::string output(headerSize + buffer.size() * hexWithSpaceSize, '\0'); |
| sprintf(output.data(), "EID: %02x, TAG: %02x, %s: ", eid, tag, |
| isTx ? "Tx" : "Rx"); |
| for (size_t i = 0; i < buffer.size(); i++) |
| { |
| sprintf(&output[headerSize + i * hexWithSpaceSize], "%02x ", |
| buffer[i]); |
| } |
| // Changing last trailing space to string null terminator |
| output.back() = '\0'; |
| lg2::info("{OUTPUT}", "OUTPUT", output); |
| } |
| } |
| |
| bool isValidDbusString(std::string_view input) |
| { |
| const uint8_t* token = std::bit_cast<const uint8_t*>(input.data()); |
| const uint8_t* end = token + input.size(); |
| |
| while (token < end) |
| { |
| uint32_t codepoint = 0; |
| int numBytes = 0; |
| |
| // utf8 can be up to 4 bytes, check the leading byte to determine its |
| // size |
| if (*token < 0x80) // 1-byte - ascii |
| { |
| codepoint = *token; |
| numBytes = 1; |
| } |
| else if ((*token & 0xE0) == 0xC0) // 2-byte |
| { |
| codepoint = *token & 0x1F; |
| numBytes = 2; |
| } |
| else if ((*token & 0xF0) == 0xE0) // 3-byte |
| { |
| codepoint = *token & 0x0F; |
| numBytes = 3; |
| } |
| else if ((*token & 0xF8) == 0xF0) // 4-byte |
| { |
| codepoint = *token & 0x07; |
| numBytes = 4; |
| } |
| else |
| { |
| // the leading byte is invalid |
| return false; |
| } |
| |
| // check if it's overflow |
| if (token + numBytes > end) |
| return false; |
| |
| // check if the subsequent byte is 10xxxxxx and do OR to codepoint |
| for (int i = 1; i < numBytes; ++i) |
| { |
| if ((token[i] & 0xC0) != 0x80) |
| { |
| return false; |
| } |
| codepoint = (codepoint << 6) | (token[i] & 0x3F); |
| } |
| |
| // check if it's overlong |
| if ((numBytes == 2 && codepoint < 0x80) || |
| (numBytes == 3 && codepoint < 0x800) || |
| (numBytes == 4 && codepoint < 0x10000)) |
| { |
| return false; |
| } |
| |
| // it can't be 0x0000 or higher than 0x10FFFF based on the dbus spec. |
| if (codepoint > 0x10FFFF || codepoint == 0x0000) |
| return false; |
| |
| token += numBytes; |
| } |
| |
| return true; |
| } |
| |
| void printBuffer(bool isTx, const uint8_t* ptr, size_t bufferLen, uint8_t tag, |
| eid_t eid) |
| { |
| auto outBuffer = std::vector<uint8_t>(ptr, ptr + bufferLen); |
| printBuffer(isTx, outBuffer, tag, eid); |
| } |
| |
| std::vector<std::string> split(std::string_view srcStr, std::string_view delim, |
| std::string_view trimStr) |
| { |
| std::vector<std::string> out; |
| size_t start; |
| size_t end = 0; |
| |
| while ((start = srcStr.find_first_not_of(delim, end)) != std::string::npos) |
| { |
| end = srcStr.find(delim, start); |
| std::string_view dstStr = srcStr.substr(start, end - start); |
| if (!trimStr.empty()) |
| { |
| dstStr.remove_prefix(dstStr.find_first_not_of(trimStr)); |
| dstStr.remove_suffix(dstStr.size() - 1 - |
| dstStr.find_last_not_of(trimStr)); |
| } |
| |
| if (!dstStr.empty()) |
| { |
| out.push_back(std::string(dstStr)); |
| } |
| } |
| |
| return out; |
| } |
| |
| std::string getCurrentSystemTime() |
| { |
| using namespace std::chrono; |
| const time_point<system_clock> tp = system_clock::now(); |
| std::time_t tt = system_clock::to_time_t(tp); |
| auto ms = duration_cast<microseconds>(tp.time_since_epoch()) - |
| duration_cast<seconds>(tp.time_since_epoch()); |
| |
| std::stringstream ss; |
| ss << std::put_time(std::localtime(&tt), "%F %Z %T.") |
| << std::to_string(ms.count()); |
| return ss.str(); |
| } |
| |
| // assuming <uuid, eid, mctpMedium, mctpBinding> is unique |
| // assuming we only have a single MCTP network id 0 |
| // its safe to say if we are given a particular eid it will map to a particular |
| // uuid. A device can have multiple EIDs |
| std::optional<uuid_t> getUUIDFromEID( |
| const std::multimap<std::string, |
| std::tuple<eid_t, MctpMedium, MctpBinding>>& eidTable, |
| eid_t eid) |
| { |
| for (const auto& entry : eidTable) |
| { |
| // Accessing the EID in the tuple |
| if (std::get<0>(entry.second) == eid) |
| { |
| // Returning the UUID as an optional when found |
| return entry.first; |
| } |
| } |
| // Returning an empty optional if no UUID is found |
| return std::nullopt; |
| } |
| |
| uint64_t getCurrentSteadyClockTimestamp() |
| { |
| return std::chrono::duration_cast<std::chrono::milliseconds>( |
| std::chrono::steady_clock::now().time_since_epoch()) |
| .count(); |
| } |
| |
| eid_t getEidFromUUID( |
| const std::multimap<uuid_t, std::tuple<eid_t, MctpMedium, MctpBinding>>& |
| eidTable, |
| uuid_t uuid) |
| { |
| eid_t eid = std::numeric_limits<uint8_t>::max(); |
| for (const auto& entry : eidTable) |
| { |
| // TODO: as of now it is hard-coded for PCIe meidium, will handle in |
| // seperate MR for selecting the fasted bandwidth medium instead of hard |
| // coded value |
| // Assuming UUID_LEN is defined correctly and accessible here |
| if (std::string_view(entry.first) |
| .starts_with(std::string_view(uuid.data(), UUID_LEN))) |
| { |
| eid = std::get<0>( |
| entry.second); // Accessing the first element (eid) of the tuple |
| break; |
| } |
| } |
| return eid; |
| } |
| |
| std::string makeDBusNameValid(const std::string& name) |
| { |
| return boost::regex_replace(name, invalidDBusNameSubString, "_"); |
| } |
| |
| std::vector<Association> getAssociations(const std::string& objPath, |
| const std::string& interfaceSubStr) |
| { |
| auto mapperResponse = DBusHandler().getServiceMap(objPath.c_str()); |
| |
| std::vector<Association> associations; |
| |
| for (const auto& [service, interfaces] : mapperResponse) |
| { |
| for (const auto& interface : interfaces) |
| { |
| if (interface.find(interfaceSubStr) != std::string::npos) |
| { |
| associations.push_back({}); |
| auto& association = associations.back(); |
| |
| association.forward = |
| DBusHandler().getDbusProperty<std::string>( |
| objPath.c_str(), "Forward", interface.c_str()); |
| |
| association.backward = |
| DBusHandler().getDbusProperty<std::string>( |
| objPath.c_str(), "Backward", interface.c_str()); |
| |
| association.absolutePath = |
| DBusHandler().getDbusProperty<std::string>( |
| objPath.c_str(), "AbsolutePath", interface.c_str()); |
| association.absolutePath = |
| makeDBusNameValid(association.absolutePath); |
| } |
| } |
| } |
| |
| return associations; |
| } |
| |
| Associations getAssociations(const std::vector<Association>& associations) |
| { |
| Associations tuples; |
| for (auto& association : associations) |
| { |
| tuples.emplace_back(association.forward, association.backward, |
| association.absolutePath); |
| } |
| return tuples; |
| } |
| |
| void convertBitMaskToVector(std::vector<uint8_t>& data, |
| const bitfield8_t* value, uint8_t size) |
| { |
| for (uint8_t i = 0; i < size; i++) |
| { |
| if (value[i].bits.bit0) |
| { |
| data.push_back((i * 8) + 0); |
| } |
| if (value[i].bits.bit1) |
| { |
| data.push_back((i * 8) + 1); |
| } |
| if (value[i].bits.bit2) |
| { |
| data.push_back((i * 8) + 2); |
| } |
| if (value[i].bits.bit3) |
| { |
| data.push_back((i * 8) + 3); |
| } |
| if (value[i].bits.bit4) |
| { |
| data.push_back((i * 8) + 4); |
| } |
| if (value[i].bits.bit5) |
| { |
| data.push_back((i * 8) + 5); |
| } |
| if (value[i].bits.bit6) |
| { |
| data.push_back((i * 8) + 6); |
| } |
| if (value[i].bits.bit7) |
| { |
| data.push_back((i * 8) + 7); |
| } |
| } |
| } |
| |
| std::string getDeviceNameFromDeviceType(const uint8_t deviceType) |
| { |
| switch (deviceType) |
| { |
| case 0: |
| return "GPU"; |
| case 1: |
| return "SWITCH"; |
| case 2: |
| return "BRIDGE"; |
| case 3: |
| return "BASEBOARD"; |
| case 4: |
| return "EROT"; |
| case 5: |
| return "MCTPBRIDGE"; |
| default: |
| return "NSM_DEV_ID_UNKNOWN"; |
| } |
| } |
| |
| std::string getDeviceInstanceName(const uint8_t deviceType, |
| const uint8_t instanceNumber) |
| { |
| std::string deviceInstanceName = getDeviceNameFromDeviceType(deviceType); |
| deviceInstanceName += "_"; |
| deviceInstanceName += std::to_string(static_cast<int>(instanceNumber)); |
| return deviceInstanceName; |
| } |
| |
| requester::Coroutine coGetAssociations(const std::string& objPath, |
| const std::string& interfaceSubStr, |
| std::vector<Association>& associations) |
| { |
| auto mapperResponse = co_await coGetServiceMap(objPath, dbus::Interfaces{}); |
| |
| for (const auto& [service, interfaces] : mapperResponse) |
| { |
| for (const auto& interface : interfaces) |
| { |
| if (interface.find(interfaceSubStr) != std::string::npos) |
| { |
| associations.push_back({}); |
| auto& association = associations.back(); |
| |
| auto allCurrentIfaceProperties = co_await coGetAllDbusProperty( |
| entityManagerServiceStr, objPath.c_str(), |
| interface.c_str()); |
| |
| std::string forward{}; |
| if (allCurrentIfaceProperties.count("Forward")) |
| { |
| forward = std::get<std::string>( |
| allCurrentIfaceProperties.at("Forward")); |
| } |
| association.forward = forward; |
| |
| std::string backward{}; |
| if (allCurrentIfaceProperties.count("Backward")) |
| { |
| backward = std::get<std::string>( |
| allCurrentIfaceProperties.at("Backward")); |
| } |
| association.backward = backward; |
| |
| std::string absolutePath{}; |
| if (allCurrentIfaceProperties.count("AbsolutePath")) |
| { |
| absolutePath = std::get<std::string>( |
| allCurrentIfaceProperties.at("AbsolutePath")); |
| } |
| association.absolutePath = absolutePath; |
| association.absolutePath = |
| makeDBusNameValid(association.absolutePath); |
| } |
| } |
| } |
| // coverity[missing_return] |
| co_return NSM_SUCCESS; |
| } |
| // Function to convert bitfield256_t to bitmap |
| std::vector<uint8_t> bitfield256_tToBitMap(bitfield256_t bf) |
| { |
| std::vector<uint8_t> bitmap(32, 0); // 32 bytes = 256 bits |
| |
| // Iterate over each bitfield32_t in the bitfield256_t |
| for (int i = 0; i < 8; i++) |
| { |
| uint32_t byte = bf.fields[i].byte; |
| // Iterate over each bit in the bitfield32_t |
| for (int j = 0; j < 32; j++) |
| { |
| // If the bit is set, set the corresponding bit in the bitmap |
| // i * 4 accounts for the 4 bytes (32 bits) per bitfield32_t |
| // element j / 8 determines which byte within the 4 bytes the |
| // current bit belongs to. |
| if (byte & (1 << j)) |
| { |
| bitmap[i * 4 + j / 8] |= (1 << (j % 8)); |
| } |
| } |
| } |
| return bitmap; |
| } |
| |
| // Function to convert bitfield256_t to bitmap |
| std::vector<uint8_t> bitfield256_tToBitArray(bitfield256_t bf) |
| { |
| std::vector<uint8_t> bitmap(32, 0); // 32 bytes = 256 bits |
| |
| // Iterate over each bitfield32_t in the bitfield256_t |
| for (int i = 0; i < 8; i++) |
| { |
| uint32_t byte = bf.fields[i].byte; |
| bitmap[i * 4 + 0] = (byte >> 24) & 0xFF; |
| bitmap[i * 4 + 1] = (byte >> 16) & 0xFF; |
| bitmap[i * 4 + 2] = (byte >> 8) & 0xFF; |
| bitmap[i * 4 + 3] = (byte >> 0) & 0xFF; |
| } |
| return bitmap; |
| } |
| |
| std::pair<std::vector<uint8_t>, std::vector<uint8_t>> |
| bitmapToIndices(const std::vector<uint8_t>& bitmap) |
| { |
| std::vector<uint8_t> zeroIndices, oneIndices; |
| uint8_t index = 0; |
| for (auto byte : bitmap) |
| { |
| for (auto bit = 0; bit < 8; ++bit) |
| { |
| if (byte & 0x01) |
| { |
| oneIndices.emplace_back(index++); |
| } |
| else |
| { |
| zeroIndices.emplace_back(index++); |
| } |
| byte >>= 1; |
| } |
| } |
| |
| return std::make_pair(zeroIndices, oneIndices); |
| } |
| |
| std::vector<uint8_t> indicesToBitmap(const std::vector<uint8_t>& indices, |
| const size_t size) |
| { |
| constexpr const size_t maxBitmapSize = 8; // maximum size used by ERoT |
| if (size > maxBitmapSize) |
| { |
| throw std::invalid_argument( |
| "Requested bitmap size larger than maximum allowed value"); |
| } |
| if (indices.empty()) |
| { |
| return std::vector<uint8_t>(size, 0); |
| } |
| uint8_t maxIndex = *std::max_element(indices.begin(), indices.end()); |
| std::vector<uint8_t> bitmap; |
| if (size == 0) |
| { |
| bitmap.resize(maxIndex / 8 + 1, 0); |
| } |
| else if (maxIndex > size * 8 - 1) |
| { |
| throw std::invalid_argument("Index out of bounds for specified size"); |
| } |
| else |
| { |
| bitmap.resize(size, 0); |
| } |
| for (auto& index : indices) |
| { |
| size_t bitmapIndex = index / 8; |
| size_t bitmapBit = index % 8; |
| bitmap[bitmapIndex] |= 1 << bitmapBit; |
| } |
| |
| return bitmap; |
| } |
| |
| Bitfield256::Bitfield256() |
| { |
| clear(); |
| } |
| bool Bitfield256::setBit(const uint8_t& bitNumber) |
| { |
| uint8_t fieldIndex = bitNumber / 32; |
| uint8_t bitIndex = bitNumber % 32; |
| uint32_t& byte = fields[fieldIndex].byte; |
| bool wasSet = byte & (1 << bitIndex); |
| byte |= (1 << bitIndex); |
| return !wasSet; |
| } |
| |
| bool Bitfield256::isAnyBitSet() const |
| { |
| return std::any_of(std::begin(fields), std::end(fields), |
| [](const auto& field) { return field.byte != 0; }); |
| } |
| |
| std::string Bitfield256::getSetBits() const |
| { |
| std::ostringstream oss; |
| |
| for (size_t i = 0; i < 8; ++i) |
| { |
| uint32_t byte = fields[i].byte; |
| |
| while (byte > 0) |
| { |
| // Get the position of the least significant set bit |
| int position = __builtin_ctz(byte); |
| oss << (i * 32 + position) << ", "; |
| |
| // Remove the least significant set bit |
| byte &= (byte - 1); |
| } |
| } |
| |
| std::string result = oss.str(); |
| if (!result.empty()) |
| { |
| result.erase(result.size() - 2); // Remove trailing ", " |
| } |
| |
| return result; |
| } |
| void Bitfield256::clear() |
| { |
| memset(fields, 0, sizeof(fields)); |
| } |
| |
| std::vector<sdbusplus::common::xyz::openbmc_project::software::SecurityCommon:: |
| UpdateMethods> |
| updateMethodsBitfieldToList(bitfield32_t updateMethodBitfield) |
| { |
| using namespace sdbusplus::common::xyz::openbmc_project::software; |
| |
| std::vector<SecurityCommon::UpdateMethods> updateMethods; |
| if (updateMethodBitfield.bits.bit0) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::Automatic); |
| } |
| if (updateMethodBitfield.bits.bit2) |
| { |
| updateMethods.emplace_back( |
| SecurityCommon::UpdateMethods::MediumSpecificReset); |
| } |
| if (updateMethodBitfield.bits.bit3) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::SystemReboot); |
| } |
| if (updateMethodBitfield.bits.bit4) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::DCPowerCycle); |
| } |
| if (updateMethodBitfield.bits.bit5) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::ACPowerCycle); |
| } |
| if (updateMethodBitfield.bits.bit16) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::WarmReset); |
| } |
| if (updateMethodBitfield.bits.bit17) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::HotReset); |
| } |
| if (updateMethodBitfield.bits.bit18) |
| { |
| updateMethods.emplace_back(SecurityCommon::UpdateMethods::FLR); |
| } |
| |
| return updateMethods; |
| } |
| // Function to convert bitmap to bitfield256_t |
| bitfield256_t bitMapToBitfield256_t(const std::vector<uint8_t>& bitmap) |
| { |
| bitfield256_t bf = {0}; // Initialize all fields to 0 |
| |
| // Ensure the bitmap has the correct size |
| if (bitmap.size() != 32) |
| { |
| return bf; |
| } |
| |
| // Iterate over each group of 4 bytes in the bitmap |
| for (int i = 0; i < 8; i++) |
| { |
| uint32_t byte = 0; |
| byte |= static_cast<uint32_t>(bitmap[i * 4 + 0]) << 24; |
| byte |= static_cast<uint32_t>(bitmap[i * 4 + 1]) << 16; |
| byte |= static_cast<uint32_t>(bitmap[i * 4 + 2]) << 8; |
| byte |= static_cast<uint32_t>(bitmap[i * 4 + 3]); |
| bf.fields[i].byte = byte; |
| } |
| |
| return bf; |
| } |
| |
| std::string vectorTo256BitHexString(const std::vector<uint8_t>& value) |
| { |
| // Ensure the vector has exactly 32 bytes (256 bits) |
| if (value.size() != 32) |
| { |
| return "0x" + std::string(64, '0'); |
| } |
| |
| // Convert the vector to a hex string |
| std::stringstream ss; |
| ss << "0x"; |
| for (const auto& byte : value) |
| { |
| ss << std::hex << std::setw(2) << std::setfill('0') |
| << static_cast<int>(byte); |
| } |
| return ss.str(); |
| } |
| |
| void readFdToBuffer(int fd, std::vector<uint8_t>& buffer) |
| { |
| if (fd < 0) |
| { |
| throw std::runtime_error("readFdToBuffer - Invalid file descriptor"); |
| } |
| if (lseek(fd, 0, SEEK_SET) < 0) |
| { |
| throw std::runtime_error("readFdToBuffer - lseek failed"); |
| } |
| struct stat fileStat; |
| if (fstat(fd, &fileStat) < 0) |
| { |
| throw std::runtime_error("readFdToBuffer - fstat failed" + |
| std::string(strerror(errno))); |
| } |
| if (fileStat.st_size < 0) |
| { |
| throw std::runtime_error("readFdToBuffer - Invalid file size in fd"); |
| } |
| buffer.resize(fileStat.st_size); |
| ssize_t bytesRead = read(fd, buffer.data(), buffer.size()); |
| if (bytesRead < 0) |
| { |
| throw std::runtime_error("readFdToBuffer - Fd read failed" + |
| std::string(strerror(errno))); |
| } |
| else if (static_cast<size_t>(bytesRead) != buffer.size()) |
| { |
| throw std::runtime_error( |
| "readFdToBuffer - Read fewer bytes than expected"); |
| } |
| } |
| |
| void writeBufferToFd(int fd, const std::vector<uint8_t>& buffer) |
| { |
| if (fd < 0) |
| { |
| throw std::runtime_error("writeBufferToFd - Invalid file descriptor"); |
| } |
| if (lseek(fd, 0, SEEK_SET) < 0) |
| { |
| throw std::runtime_error("writeBufferToFd - lseek failed"); |
| } |
| size_t totalBytesWritten = 0; |
| while (totalBytesWritten < buffer.size()) |
| { |
| ssize_t bytesWritten = write(fd, buffer.data() + totalBytesWritten, |
| buffer.size() - totalBytesWritten); |
| if (bytesWritten < 0) |
| { |
| throw std::runtime_error("writeBufferToFd - write failed: " + |
| std::string(strerror(errno))); |
| } |
| totalBytesWritten += bytesWritten; |
| } |
| if (ftruncate(fd, buffer.size()) < 0) |
| { |
| throw std::runtime_error("writeBufferToFd - ftruncate failed: " + |
| std::string(strerror(errno))); |
| } |
| } |
| |
| void appendBufferToFd(int fd, const std::vector<uint8_t>& buffer) |
| { |
| if (fd < 0) |
| { |
| throw std::runtime_error("appendBufferToFd - Invalid file descriptor"); |
| } |
| size_t totalBytesWritten = 0; |
| while (totalBytesWritten < buffer.size()) |
| { |
| ssize_t bytesWritten = write(fd, buffer.data() + totalBytesWritten, |
| buffer.size() - totalBytesWritten); |
| if (bytesWritten < 0) |
| { |
| throw std::runtime_error("appendBufferToFd - write failed: " + |
| std::string(strerror(errno))); |
| } |
| totalBytesWritten += bytesWritten; |
| } |
| } |
| |
| std::string requestMsgToHexString(std::vector<uint8_t>& requestMsg) |
| { |
| std::ostringstream oss; |
| for (const auto& byte : requestMsg) |
| { |
| oss << std::setfill('0') << std::setw(2) << std::hex |
| << static_cast<int>(byte) << " "; |
| } |
| return oss.str(); |
| } |
| |
| double convertAndScaleDownUint32ToDouble(uint32_t value, double scaleFactor) |
| { |
| if (value == INVALID_UINT32_VALUE) |
| { |
| return static_cast<double>(INVALID_UINT32_VALUE); |
| } |
| else |
| { |
| return static_cast<double>(value) / scaleFactor; |
| } |
| } |
| |
| double uint64ToDoubleSafeConvert(uint64_t value) |
| { |
| if (value > MAX_SAFE_INTEGER_IN_DOUBLE) |
| { |
| lg2::error( |
| "Warning: Uint64 Value ({VAL}) exceeds safe range for double precision. Capping to maximum safe value.", |
| "VAL", value); |
| return static_cast<double>(MAX_SAFE_INTEGER_IN_DOUBLE); |
| } |
| return static_cast<double>(value); |
| } |
| |
| double int64ToDoubleSafeConvert(int64_t value) |
| { |
| if (value < 0) |
| { |
| if (static_cast<uint64_t>(-value) > MAX_SAFE_INTEGER_IN_DOUBLE) |
| { |
| lg2::error( |
| "Warning: Int64 Value ({VAL}) exceeds safe range for double precision. Capping to maximum safe value.", |
| "VAL", value); |
| return static_cast<double>(-MAX_SAFE_INTEGER_IN_DOUBLE); |
| } |
| } |
| else |
| { |
| if (static_cast<uint64_t>(value) > MAX_SAFE_INTEGER_IN_DOUBLE) |
| { |
| lg2::error( |
| "Warning: Int64 Value ({VAL}) exceeds safe range for double precision. Capping to maximum safe value.", |
| "VAL", value); |
| return static_cast<double>(MAX_SAFE_INTEGER_IN_DOUBLE); |
| } |
| } |
| |
| return static_cast<double>(value); |
| } |
| |
| uint16_t combineDeviceTypeAndRole(uint8_t deviceType, uint8_t deviceRole) |
| { |
| return (uint16_t)((deviceRole << 8) | |
| deviceType); // Role in high byte, Type in low byte |
| } |
| |
| void getDeviceTypeAndRole(uint16_t combined, uint8_t* deviceType, |
| uint8_t* deviceRole) |
| { |
| *deviceType = (uint8_t)(combined & 0xFF); // Type is in low byte |
| *deviceRole = (uint8_t)(combined >> 8); // Role is in high byte |
| } |
| |
| void convertMacAddressToString(const uint8_t* macAddress, |
| size_t macAddressDataLen, |
| std::string& macAddressString) |
| { |
| const static size_t MAC_FORMAT_LENGTH = sizeof("XX:XX:XX:XX:XX:XX"); |
| if (macAddressDataLen < MAC_ADDRESS_DATA_LEN) |
| { |
| lg2::error( |
| "convertMacAddressToString - Invalid mac address data length: {LEN}", |
| "LEN", macAddressDataLen); |
| macAddressString.clear(); |
| return; |
| } |
| char buffer[MAC_FORMAT_LENGTH]; |
| snprintf(buffer, MAC_FORMAT_LENGTH, "%02x:%02x:%02x:%02x:%02x:%02x", |
| macAddress[0], macAddress[1], macAddress[2], macAddress[3], |
| macAddress[4], macAddress[5]); |
| macAddressString = buffer; |
| } |
| |
| void convertGuid64ToString(uint64_t guid, std::string& guidString) |
| { |
| guidString = std::format("{:04X}-{:04X}-{:04X}-{:04X}", |
| (guid >> 48) & 0xFFFF, (guid >> 32) & 0xFFFF, |
| (guid >> 16) & 0xFFFF, guid & 0xFFFF); |
| } |
| // Single-flight pattern implementation for single-threaded async execution |
| // for EM configuration PDI properties |
| requester::Coroutine coGetCachedBaseProperties( |
| [[maybe_unused]] const std::string& objPath, |
| [[maybe_unused]] const std::string& baseInterface, |
| [[maybe_unused]] dbus::PropertyMap& cachedProperties) |
| { |
| #ifndef MOCK_DBUS_ASYNC_UTILS |
| static std::unordered_map< |
| std::string, std::unordered_map<std::string, dbus::PropertyMap>> |
| basePropertiesCache; |
| |
| static std::unordered_map< |
| std::string, |
| std::unordered_map<std::string, std::shared_future<dbus::PropertyMap>>> |
| pendingRequests; |
| |
| // Check if already cached |
| auto objPathIt = basePropertiesCache.find(objPath); |
| if (objPathIt != basePropertiesCache.end()) |
| { |
| auto interfaceIt = objPathIt->second.find(baseInterface); |
| if (interfaceIt != objPathIt->second.end()) |
| { |
| // "Cache hit: Using cached base properties for |
| // {OBJPATH}:{INTERFACE}", "OBJPATH", objPath, "INTERFACE", |
| // baseInterface); |
| cachedProperties = interfaceIt->second; |
| co_return NSM_SUCCESS; |
| } |
| } |
| |
| // Check if request is already pending |
| auto pendingObjIt = pendingRequests.find(objPath); |
| if (pendingObjIt != pendingRequests.end()) |
| { |
| auto pendingInterfaceIt = pendingObjIt->second.find(baseInterface); |
| if (pendingInterfaceIt != pendingObjIt->second.end()) |
| { |
| // "Request pending: Waiting for ongoing request for |
| // {OBJPATH}:{INTERFACE}", "OBJPATH", objPath, "INTERFACE", |
| // baseInterface); |
| |
| try |
| { |
| dbus::PropertyMap result = pendingInterfaceIt->second.get(); |
| cachedProperties = result; |
| co_return NSM_SUCCESS; |
| } |
| catch (...) |
| { |
| // If the future threw an exception, we'll try making our own |
| // request Continue to the request creation logic below |
| } |
| } |
| } |
| |
| // Create a promise for this request |
| std::promise<dbus::PropertyMap> promise; |
| std::shared_future<dbus::PropertyMap> future = promise.get_future().share(); |
| |
| // Store the pending request |
| pendingRequests[objPath][baseInterface] = future; |
| |
| // "Cache miss: First retrieval for {OBJPATH}:{INTERFACE}", |
| // "OBJPATH", objPath, "INTERFACE", baseInterface); |
| |
| try |
| { |
| // Make the actual D-Bus call |
| dbus::PropertyMap properties = co_await utils::coGetAllDbusProperty( |
| utils::entityManagerServiceStr, objPath, baseInterface); |
| |
| // Cache the result and clean up pending request |
| basePropertiesCache[objPath][baseInterface] = properties; |
| |
| // Remove from pending requests |
| auto pendingObjIt = pendingRequests.find(objPath); |
| if (pendingObjIt != pendingRequests.end()) |
| { |
| pendingObjIt->second.erase(baseInterface); |
| if (pendingObjIt->second.empty()) |
| { |
| pendingRequests.erase(objPath); |
| } |
| } |
| |
| // Set the promise result |
| promise.set_value(properties); |
| |
| cachedProperties = properties; |
| co_return NSM_SUCCESS; |
| } |
| catch (const std::exception& e) |
| { |
| // Clean up pending request on error |
| auto pendingObjIt = pendingRequests.find(objPath); |
| if (pendingObjIt != pendingRequests.end()) |
| { |
| pendingObjIt->second.erase(baseInterface); |
| if (pendingObjIt->second.empty()) |
| { |
| pendingRequests.erase(objPath); |
| } |
| } |
| |
| // Set the promise exception |
| promise.set_exception(std::current_exception()); |
| |
| lg2::error( |
| "Failed to fetch base properties for {OBJPATH}:{INTERFACE}:{ERROR}", |
| "OBJPATH", objPath, "INTERFACE", baseInterface, "ERROR", e.what()); |
| |
| co_return NSM_SW_ERROR; |
| } |
| #else |
| // In test mode, just copy the current propertyMap |
| auto& propertyMap = utils::MockDbusAsync::getPropertyMap(); |
| cachedProperties = propertyMap; |
| co_return NSM_SUCCESS; |
| #endif |
| } |
| |
| int parseStaticUuid(uuid_t& uuid, uint8_t& deviceType, uint8_t& instanceNumber, |
| uint8_t& deviceRole, std::string& remapPropName, |
| std::string& remapPropValue) |
| { |
| int n1 = -1; |
| int n2 = -1; |
| char propName[128] = {0}; |
| char propValue[128] = {0}; |
| |
| // Use sscanf with width specifiers to avoid buffer overflow |
| int numParsed = std::sscanf(uuid.c_str(), "STATIC:%d:%d:%127[^:]:%127s", |
| &n1, &n2, propName, propValue); |
| if (numParsed != 4) |
| { |
| return -3; // Parsing failed |
| } |
| |
| if (n1 < 0 || n1 > 0xffff) |
| { |
| return -1; |
| } |
| if (n2 < 0 || n2 > 0xff) |
| { |
| return -2; |
| } |
| |
| utils::getDeviceTypeAndRole(n1, &deviceType, &deviceRole); |
| instanceNumber = static_cast<uint8_t>(n2); |
| remapPropName = std::string(propName); |
| remapPropValue = std::string(propValue); |
| return 0; |
| } |
| |
| } // namespace utils |