blob: d3918e775ceb259999c0fd868f0032d55a261371 [file] [log] [blame]
// 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.hpp"
#include <ipmid/api-types.hpp>
#include <ipmid/handler.hpp>
#include <stdplus/util/string.hpp>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace ipmi_hoth
{
std::string_view HothBlobHandler::pathToHothId(std::string_view path)
{
if (path.starts_with(hothPathPrefix) && path.ends_with(pathSuffix()))
{
auto id = path.substr(
hothPathPrefix.size(),
path.size() - pathSuffix().size() - hothPathPrefix.size());
auto count = std::count(id.begin(), id.end(), '/');
// There can only be prefix + pathSuffix or
// prefix + id/pathSuffix.
// prefix already has one `/`
if (count == 1)
{
return id.substr(0, id.size() - 1);
}
else if (count > 1)
{
return "";
}
return id;
}
return "";
}
std::string HothBlobHandler::hothIdToPath(std::string_view hothId)
{
if (hothId.empty())
{
return stdplus::util::strCat(hothPathPrefix, pathSuffix());
}
return stdplus::util::strCat(hothPathPrefix, hothId, "/", pathSuffix());
}
HothBlob* HothBlobHandler::getSession(uint16_t id)
{
auto search = sessions.find(id);
if (search == sessions.end())
{
return nullptr;
}
/* Not thread-safe, however, the blob handler deliberately assumes serial
* execution. */
return search->second.get();
}
std::optional<uint16_t> HothBlobHandler::getOnlySession(std::string_view hothId)
{
/* This method is only valid if we only allow 1 session */
if (maxSessions() != 1)
{
return std::nullopt;
}
for (auto& session : pathSessions[std::string(hothId)])
{
return session;
}
return std::nullopt;
}
bool HothBlobHandler::canHandleBlob(const std::string& path)
{
if (path.starts_with(hothPathPrefix) && path.ends_with(pathSuffix()))
{
// There can only be hothPathPrefix + pathSuffix or
// hothPathPrefix + id/pathSuffix
// hothPathPrefix already has one `/`
return std::count(path.begin() + hothPathPrefix.size(),
path.end() - pathSuffix().size(), '/') <= 1;
}
return false;
}
std::vector<std::string> HothBlobHandler::getBlobIds()
{
std::vector<std::string> ret;
for (const auto& obj : dbus().getHothdMapping())
{
std::string_view objView = obj.first;
std::string_view objPrefix = "/xyz/openbmc_project/Control/Hoth";
if (objView.substr(0, objPrefix.size()) != objPrefix)
{
continue;
}
objView.remove_prefix(objPrefix.size());
if (objView.empty())
{
ret.push_back(hothIdToPath(""));
continue;
}
if (objView[0] != '/')
{
continue;
}
objView.remove_prefix(1);
auto sep = objView.find('/');
if (sep != std::string_view::npos)
{
continue;
}
ret.push_back(hothIdToPath(objView));
}
return ret;
}
bool HothBlobHandler::deleteBlob(const std::string&)
{
/* Hoth blob handler does not support a blob delete. */
return false;
}
bool HothBlobHandler::open(uint16_t session, uint16_t flags,
const std::string& path)
{
/* We require both flags set. */
if ((flags & requiredFlags()) != requiredFlags())
{
/* Both flags not set. */
return false;
}
auto findSess = sessions.find(session);
if (findSess != sessions.end())
{
/* This session is already active. */
return false;
}
auto hothId = std::string(pathToHothId(path));
auto pathSession = pathSessions.find(hothId);
if (pathSession != pathSessions.end() &&
pathSession->second.size() >= maxSessions())
{
return false;
}
// Prevent host from adding lots of bad entries to the table by verifying
// the hoth exists.
if (pathSession == pathSessions.end() && !dbus().pingHothd(hothId))
{
return false;
}
pathSessions[hothId].emplace(session);
sessions.emplace(session,
std::make_unique<HothBlob>(session, std::move(hothId),
flags, maxBufferSize()));
return true;
}
std::vector<uint8_t> HothBlobHandler::read(uint16_t session, uint32_t offset,
uint32_t requestedSize)
{
HothBlob* sess = getSession(session);
if (!sess || !(sess->state & blobs::StateFlags::open_read) ||
offset > sess->buffer.size())
{
throw ipmi::HandlerCompletion(ipmi::ccUnspecifiedError);
}
if (sess->buffer.size() > offset)
{
std::vector<uint8_t> ret(
std::min<size_t>(requestedSize, sess->buffer.size() - offset));
std::memcpy(ret.data(), sess->buffer.data() + offset, ret.size());
return ret;
}
return {};
}
bool HothBlobHandler::write(uint16_t session, uint32_t offset,
const std::vector<uint8_t>& data)
{
uint32_t newBufferSize = data.size() + offset;
HothBlob* sess = getSession(session);
if (!sess || !(sess->state & blobs::StateFlags::open_write) ||
newBufferSize > maxBufferSize())
{
return false;
}
/* Resize the buffer if what we're writing will go over the size */
if (newBufferSize > sess->buffer.size())
{
sess->buffer.resize(newBufferSize);
sess->state &= ~blobs::StateFlags::committed;
}
/* Clear the comitted bit if our data isn't identical to existing data */
if (std::memcmp(sess->buffer.data() + offset, data.data(), data.size()))
{
sess->state &= ~blobs::StateFlags::committed;
}
if (!data.empty())
{
std::memcpy(sess->buffer.data() + offset, data.data(), data.size());
}
return true;
}
bool HothBlobHandler::writeMeta(uint16_t, uint32_t, const std::vector<uint8_t>&)
{
/* Hoth blob handler does not support meta write. */
return false;
}
bool HothBlobHandler::close(uint16_t session)
{
auto session_it = sessions.find(session);
if (session_it == sessions.end())
{
return false;
}
auto path_it = pathSessions.find(session_it->second->hothId);
path_it->second.erase(session);
if (path_it->second.empty())
{
pathSessions.erase(path_it);
}
sessions.erase(session_it);
return true;
}
bool HothBlobHandler::expire(uint16_t session)
{
return close(session);
}
} // namespace ipmi_hoth