| // Copyright 2024 Google LLC |
| // |
| // 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 "hoth_skmhss.hpp" |
| |
| #include "config.hpp" |
| |
| #include <fmt/format.h> |
| #include <google3/host_commands.h> |
| |
| #include <blobs-ipmid/blobs.hpp> |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <stdplus/cancel.hpp> |
| #include <xyz/openbmc_project/Control/Hoth/error.hpp> |
| |
| #include <algorithm> |
| #include <charconv> |
| #include <cstring> |
| #include <fstream> |
| #include <memory> |
| #include <optional> |
| #include <span> |
| #include <string> |
| #include <vector> |
| |
| using phosphor::logging::entry; |
| using phosphor::logging::log; |
| |
| using level = phosphor::logging::level; |
| using SdBusError = sdbusplus::exception::SdBusError; |
| using CommandFailure = |
| sdbusplus::xyz::openbmc_project::Control::Hoth::Error::CommandFailure; |
| using ResponseFailure = |
| sdbusplus::xyz::openbmc_project::Control::Hoth::Error::ResponseFailure; |
| using json = nlohmann::json; |
| |
| namespace ipmi_hoth |
| { |
| |
| namespace internal |
| { |
| |
| /** |
| * @brief: Get baseId from a blob id string |
| * @param blobId: Input blob id which is expected to only contain alphanumerical |
| * characters and '/'. |
| * @returns: The baseId containing the blobId, stripping all contents from the |
| * last '/'. If no '/' is present, an empty string is returned. |
| */ |
| static std::string_view getBaseFromID(std::string_view blobId) |
| { |
| return blobId.substr(0, blobId.find_last_of('/') + 1); |
| } |
| |
| /** |
| * @brief: Get the blob identifer under the baseId |
| * @param blobId: Input blob id which is expected to only contain alphanumerical |
| * characters and '/'. |
| * @returns: The truncated blobId, by deleting the baseId prefix |
| * in the input blobId. |
| */ |
| static std::string_view stripBaseFromID(std::string_view blobId) |
| { |
| return blobId.substr(blobId.find_last_of('/') + 1); |
| } |
| |
| /** |
| * @brief: Get the slot number from a blob id string |
| * @param blobId: Input blob id which is expected to only contain alphanumerical |
| * characters and '/'. |
| * @returns: The slot number if the blob id has a tailing integer; otherwise |
| * returns a null pointer; |
| */ |
| static std::optional<uint32_t> extractSlotFromID(std::string_view blobId) |
| { |
| std::string_view pathId = internal::stripBaseFromID(blobId); |
| uint32_t slot; |
| const auto res = |
| std::from_chars(pathId.data(), pathId.data() + pathId.size(), slot); |
| |
| if (res.ec != std::errc() || res.ptr != pathId.data() + pathId.size()) |
| { |
| return std::nullopt; |
| } |
| |
| return slot; |
| } |
| |
| } // namespace internal |
| |
| void HothSKMHSSBlobHandler::loadHSS() |
| { |
| readBasePathConfig(); |
| for (auto& [basePath, hothId] : hothIdBasePaths) |
| { |
| slotPopulated[basePath] = {}; |
| inits[basePath] = {}; |
| dels[basePath] = {}; |
| /* Iterate all slots and find valid HSS copies */ |
| for (uint32_t slot = 0; slot < maxNumHSSSlot; slot++) |
| { |
| auto& init = inits[basePath][slot]; |
| init = readSKMHSSSlot( |
| basePath, slot, hothId, |
| [&](std::span<const uint8_t>) noexcept { init.reset(); }); |
| } |
| } |
| } |
| |
| /* read the hothid_basepaths.config and store it in hothIdBasePaths*/ |
| /** |
| * @brief: Read the hothid_basepaths.config and store it in a bimap. |
| * It populates basePath <--> hothId mapping |
| * @param : |
| * @returns: |
| */ |
| void HothSKMHSSBlobHandler::readBasePathConfig() |
| { |
| std::ifstream configFile(HOTHID_CONFIG); |
| |
| // Check if the config file exists |
| if (configFile.fail()) |
| { |
| log<level::ERR>( |
| "config file does not exist, populate map with defaults"); |
| |
| // There is no config file, |
| // So, populate with default values |
| hothIdBasePaths.insert({"/skm/hss-backup/", ""}); |
| return; |
| } |
| |
| try |
| { |
| json data = json::parse(configFile); |
| for (const auto& [basePath, hothId] : data.items()) |
| { |
| hothIdBasePaths.insert({basePath, hothId}); |
| } |
| } |
| catch (json::parse_error& ex) |
| { |
| log<level::ERR>("config parse error at ", entry("ERR_MSG=%d", ex.byte)); |
| } |
| } |
| |
| /** |
| * @brief: Get the hothId from a blob id string |
| * @param blobId: Input blob id which is expected to only contain alphanumerical |
| * characters and '/'. |
| * @returns: The hothId from the map hothIdBasePaths if it exists; otherwise |
| * empty string |
| */ |
| std::string_view |
| HothSKMHSSBlobHandler::getHothIdFromPath(std::string_view blobId) |
| { |
| auto base = internal::getBaseFromID(blobId); |
| auto it = hothIdBasePaths.left.find(static_cast<std::string>(base)); |
| |
| if (it != hothIdBasePaths.left.end()) |
| { |
| return it->second; |
| } |
| return ""; |
| } |
| |
| /** |
| * @brief: Get the base path from a hothId string |
| * @param hothId: Input hothId string |
| * @returns: The hothId from the bimap hothIdBasePaths if it exists; otherwise |
| * empty string |
| */ |
| std::string_view |
| HothSKMHSSBlobHandler::getBaseFromHothId(std::string_view hothId) |
| { |
| auto it = hothIdBasePaths.right.find(static_cast<std::string>(hothId)); |
| if (it != hothIdBasePaths.right.end()) |
| { |
| return it->second; |
| } |
| return ""; |
| } |
| |
| stdplus::Cancel HothSKMHSSBlobHandler::readSKMHSSSlot( |
| std::string_view base, uint32_t slot, std::string_view hothId, |
| fu2::unique_function<void(std::span<const uint8_t>)>&& cb) |
| { |
| auto req = hothUtil_->readSKMHSS(slot); |
| return dbus_->SendHostCommand( |
| hothId, req, |
| [this, base, slot, cb = std::move(cb)]( |
| std::optional<std::vector<uint8_t>> rsp) mutable noexcept { |
| try |
| { |
| auto data = hothUtil_->payloadECResponse(rsp.value()); |
| this->slotPopulated[base][slot] = |
| data.size() == SKM_HSS_STRUCT_SIZE; |
| cb(data); |
| return; |
| } |
| catch (const ResponseFailure& e) |
| { |
| log<level::ERR>("Invalid Hoth response", |
| entry("ERR_MSG=%s", e.what())); |
| } |
| catch (...) |
| {} |
| cb({}); |
| }); |
| } |
| |
| bool HothSKMHSSBlobHandler::canHandleBlob(const std::string& path) |
| { |
| auto base = internal::getBaseFromID(path); |
| if (hothIdBasePaths.left.find(static_cast<std::string>(base)) == |
| hothIdBasePaths.left.end()) |
| { |
| return false; |
| } |
| |
| auto hothId = getHothIdFromPath(path); |
| if (!dbus_->pingHothd(hothId)) |
| { |
| return false; |
| } |
| |
| /* The HSS path is expected to be "/skm/hss-backup/{0,1,2,...}" */ |
| std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path); |
| if (!slotPtr) |
| { |
| return false; |
| } |
| return *slotPtr < maxNumHSSSlot; |
| } |
| |
| std::vector<std::string> HothSKMHSSBlobHandler::getBlobIds() |
| { |
| std::vector<std::string> result; |
| for (auto& [basePath, hothId] : hothIdBasePaths) |
| { |
| if (!dbus_->pingHothd(hothId)) |
| { |
| continue; |
| } |
| |
| result.emplace_back(basePath); |
| for (size_t i = 0; i < slotPopulated[basePath].size(); ++i) |
| { |
| if (slotPopulated[basePath][i]) |
| { |
| result.emplace_back(fmt::format("{}{}", basePath, i)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| bool HothSKMHSSBlobHandler::stat(const std::string& path, blobs::BlobMeta* meta) |
| { |
| std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path); |
| auto base = internal::getBaseFromID(path); |
| if (!slotPtr) |
| { |
| return false; |
| } |
| *meta = {}; |
| return slotPopulated[base][*slotPtr]; |
| } |
| |
| bool HothSKMHSSBlobHandler::open(uint16_t session, uint16_t flags, |
| const std::string& path) |
| { |
| std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path); |
| if (!slotPtr) |
| { |
| return false; |
| } |
| std::string_view hothId = getHothIdFromPath(path); |
| if (!HothBlobHandler::open(session, flags, hothIdToPath(hothId))) |
| { |
| return false; |
| } |
| |
| std::string_view base = internal::getBaseFromID(path); |
| sessionSlot[base].emplace(session, *slotPtr); |
| HothBlob* blobIt = getSession(session); |
| |
| if (!(blobIt->state & blobs::StateFlags::open_read)) |
| { |
| return true; |
| } |
| auto finalState = blobIt->state; |
| blobIt->state = blobs::StateFlags::committing; |
| blobIt->outstanding = readSKMHSSSlot( |
| base, *slotPtr, hothId, |
| [blobIt, finalState](std::span<const uint8_t> data) noexcept { |
| auto outstanding = std::move(blobIt->outstanding); |
| if (data.size() == SKM_HSS_STRUCT_SIZE) |
| { |
| /* Set committed to denote that our buffer is consistent with |
| * the remote copy, making commits a no-op until a write changes |
| * it. |
| */ |
| blobIt->state = finalState | blobs::StateFlags::committed; |
| blobIt->buffer = std::vector<uint8_t>(data.begin(), data.end()); |
| return; |
| } |
| blobIt->state = blobs::StateFlags::commit_error; |
| if (data.data() != nullptr && |
| finalState & blobs::StateFlags::open_write) |
| { |
| /* If the call succeeded, but we were uninitialized we want to |
| * allow a write to continue accessing the blob. This requires |
| * us to populate the open_* flags that were originally set on |
| * the blob. We don't do this for RO blobs since they can't |
| * fill in valid data to read back. |
| */ |
| blobIt->state |= finalState; |
| } |
| }); |
| return true; |
| } |
| |
| bool HothSKMHSSBlobHandler::commit(uint16_t session, |
| const std::vector<uint8_t>& data) |
| { |
| if (!data.empty()) |
| { |
| log<level::ERR>("Unexpected data provided to commit call"); |
| return false; |
| } |
| |
| HothBlob* blobIt = getSession(session); |
| if (!blobIt) |
| { |
| return false; |
| } |
| if (blobIt->state & |
| (blobs::StateFlags::committed | blobs::StateFlags::committing)) |
| { |
| return true; |
| } |
| |
| std::string_view base = getBaseFromHothId(blobIt->hothId); |
| /* Clear remaining commit bits */ |
| blobIt->state &= ~blobs::StateFlags::commit_error; |
| uint32_t slot = sessionSlot[base][session]; |
| std::vector<uint8_t> req; |
| try |
| { |
| req = hothUtil_->writeSKMHSS(slot, blobIt->buffer); |
| } |
| catch (const CommandFailure& e) |
| { |
| blobIt->state |= blobs::StateFlags::commit_error; |
| log<level::ERR>("Hoth command failed", entry("ERR_MSG=%s", e.what())); |
| return false; |
| } |
| |
| blobIt->state |= blobs::StateFlags::committing; |
| blobIt->outstanding = dbus_->SendHostCommand( |
| blobIt->hothId, req, |
| [this, base, slot, |
| blobIt](std::optional<std::vector<uint8_t>> rsp) noexcept { |
| auto outstanding = std::move(blobIt->outstanding); |
| blobIt->state &= ~blobs::StateFlags::committing; |
| try |
| { |
| hothUtil_->payloadECResponse(rsp.value()); |
| blobIt->state |= blobs::StateFlags::committed; |
| slotPopulated[base][slot] = true; |
| return; |
| } |
| catch (const ResponseFailure& e) |
| { |
| log<level::ERR>("Invalid Hoth response", |
| entry("ERR_MSG=%s", e.what())); |
| } |
| catch (...) |
| {} |
| blobIt->state |= blobs::StateFlags::commit_error; |
| }); |
| return true; |
| } |
| |
| bool HothSKMHSSBlobHandler::stat(uint16_t session, blobs::BlobMeta* meta) |
| { |
| HothBlob* blobIt = getSession(session); |
| if (!blobIt) |
| { |
| return false; |
| } |
| |
| meta->size = blobIt->buffer.size(); |
| meta->blobState = blobIt->state; |
| return true; |
| } |
| |
| bool HothSKMHSSBlobHandler::deleteBlob(const std::string& path) |
| { |
| std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path); |
| std::string_view hothId = getHothIdFromPath(path); |
| auto base = internal::getBaseFromID(path); |
| if (!slotPtr) |
| { |
| return false; |
| } |
| auto& del = dels[base][*slotPtr]; |
| if (del) |
| { |
| return true; |
| } |
| auto& pop = slotPopulated[base][*slotPtr]; |
| del = dbus_->SendHostCommand( |
| hothId, hothUtil_->deleteSKMHSS(*slotPtr), |
| [&pop, &del, this](std::optional<std::vector<uint8_t>> rsp) noexcept { |
| auto adel = std::move(del); |
| try |
| { |
| hothUtil_->payloadECResponse(rsp.value()); |
| pop = false; |
| return; |
| } |
| catch (const ResponseFailure& e) |
| { |
| log<level::ERR>("Invalid Hoth response", |
| entry("ERR_MSG=%s", e.what())); |
| } |
| catch (...) |
| {} |
| }); |
| return true; |
| } |
| |
| bool HothSKMHSSBlobHandler::close(uint16_t session) |
| { |
| HothBlob* blobIt = getSession(session); |
| if (!blobIt) |
| { |
| return false; |
| } |
| |
| std::string_view base = getBaseFromHothId(blobIt->hothId); |
| if (HothBlobHandler::close(session)) |
| { |
| sessionSlot[base].erase(session); |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace ipmi_hoth |