blob: fb797ff8c2bd42409051043ed081de9ff3b82bb3 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* 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 "blob_handler.hpp"
#include "blob_errors.hpp"
#include "crc.hpp"
#include "ipmi_errors.hpp"
#include "ipmi_interface.hpp"
#include <array>
#include <cinttypes>
#include <cstring>
#include <iterator>
#include <limits>
#include <memory>
namespace ipmiblob
{
namespace
{
const std::array<std::uint8_t, 3> ipmiPhosphorOen = {0xcf, 0xc2, 0x00};
}
std::unique_ptr<BlobInterface>
BlobHandler::CreateBlobHandler(std::unique_ptr<IpmiInterface> ipmi)
{
return std::make_unique<BlobHandler>(std::move(ipmi));
}
std::vector<std::uint8_t>
BlobHandler::sendIpmiPayload(BlobOEMCommands command,
const std::vector<std::uint8_t>& payload)
{
std::vector<std::uint8_t> request, reply, bytes;
std::copy(ipmiPhosphorOen.begin(), ipmiPhosphorOen.end(),
std::back_inserter(request));
request.push_back(static_cast<std::uint8_t>(command));
if (!payload.empty())
{
/* Grow the vector to hold the bytes. */
request.reserve(request.size() + sizeof(std::uint16_t));
/* CRC required. */
std::uint16_t crc = generateCrc(payload);
auto src = reinterpret_cast<const std::uint8_t*>(&crc);
std::copy(src, src + sizeof(crc), std::back_inserter(request));
/* Copy the payload. */
std::copy(payload.begin(), payload.end(), std::back_inserter(request));
}
try
{
reply = ipmi->sendPacket(ipmiOEMNetFn, ipmiOEMBlobCmd, request);
}
catch (const IpmiException& e)
{
throw BlobException(e.what());
}
/* IPMI_CC was OK, and it returned no bytes, so let's be happy with that for
* now.
*/
if (reply.empty())
{
return reply;
}
/* This cannot be a response because it's smaller than the smallest
* response.
*/
if (reply.size() < ipmiPhosphorOen.size())
{
throw BlobException("Invalid response length");
}
/* Verify the OEN. */
if (std::memcmp(ipmiPhosphorOen.data(), reply.data(),
ipmiPhosphorOen.size()) != 0)
{
throw BlobException("Invalid OEN received");
}
/* In this case there was no data, as there was no CRC. */
std::size_t headerSize = ipmiPhosphorOen.size() + sizeof(std::uint16_t);
if (reply.size() < headerSize)
{
return {};
}
/* Validate CRC. */
std::uint16_t crc;
auto ptr = reinterpret_cast<std::uint8_t*>(&crc);
std::memcpy(ptr, &reply[ipmiPhosphorOen.size()], sizeof(crc));
bytes.insert(bytes.begin(), reply.begin() + headerSize, reply.end());
auto computed = generateCrc(bytes);
if (crc != computed)
{
std::fprintf(stderr, "Invalid CRC, received: 0x%x, computed: 0x%x\n",
crc, computed);
throw BlobException("Invalid CRC on received data.");
}
return bytes;
}
int BlobHandler::getBlobCount()
{
std::uint32_t count;
try
{
auto resp = sendIpmiPayload(BlobOEMCommands::bmcBlobGetCount, {});
if (resp.size() != sizeof(count))
{
return 0;
}
/* LE to LE (need to make this portable as some point. */
std::memcpy(&count, resp.data(), sizeof(count));
}
catch (const BlobException& b)
{
return 0;
}
return count;
}
std::string BlobHandler::enumerateBlob(std::uint32_t index)
{
std::vector<std::uint8_t> payload;
auto data = reinterpret_cast<const std::uint8_t*>(&index);
std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload));
try
{
auto resp = sendIpmiPayload(BlobOEMCommands::bmcBlobEnumerate, payload);
return (resp.empty()) ? ""
: std::string(&resp[0], &resp[resp.size() - 1]);
}
catch (const BlobException& b)
{
return "";
}
}
void BlobHandler::commit(std::uint16_t session,
const std::vector<std::uint8_t>& bytes)
{
std::vector<std::uint8_t> request;
auto addrSession = reinterpret_cast<const std::uint8_t*>(&session);
std::copy(addrSession, addrSession + sizeof(session),
std::back_inserter(request));
/* You have one byte to describe the length. */
if (bytes.size() > std::numeric_limits<std::uint8_t>::max())
{
throw BlobException("Commit data length greater than 8-bit limit\n");
}
std::uint8_t length = static_cast<std::uint8_t>(bytes.size());
auto addrLength = reinterpret_cast<const std::uint8_t*>(&length);
std::copy(addrLength, addrLength + sizeof(length),
std::back_inserter(request));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(request));
sendIpmiPayload(BlobOEMCommands::bmcBlobCommit, request);
}
void BlobHandler::writeGeneric(BlobOEMCommands command, std::uint16_t session,
std::uint32_t offset,
const std::vector<std::uint8_t>& bytes)
{
std::vector<std::uint8_t> payload;
payload.reserve(sizeof(std::uint16_t) + sizeof(std::uint32_t) +
bytes.size());
auto data = reinterpret_cast<const std::uint8_t*>(&session);
std::copy(data, data + sizeof(std::uint16_t), std::back_inserter(payload));
data = reinterpret_cast<const std::uint8_t*>(&offset);
std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
sendIpmiPayload(command, payload);
}
void BlobHandler::writeMeta(std::uint16_t session, std::uint32_t offset,
const std::vector<std::uint8_t>& bytes)
{
writeGeneric(BlobOEMCommands::bmcBlobWriteMeta, session, offset, bytes);
}
void BlobHandler::writeBytes(std::uint16_t session, std::uint32_t offset,
const std::vector<std::uint8_t>& bytes)
{
writeGeneric(BlobOEMCommands::bmcBlobWrite, session, offset, bytes);
}
std::vector<std::string> BlobHandler::getBlobList()
{
std::vector<std::string> list;
int blobCount = getBlobCount();
for (int i = 0; i < blobCount; i++)
{
auto name = enumerateBlob(i);
/* Currently ignore failures. */
if (!name.empty())
{
list.push_back(name);
}
}
return list;
}
StatResponse BlobHandler::statGeneric(BlobOEMCommands command,
const std::vector<std::uint8_t>& request)
{
StatResponse meta;
static constexpr std::size_t blobStateSize = sizeof(meta.blob_state);
static constexpr std::size_t metaSize = sizeof(meta.size);
static constexpr std::size_t metaOffset = blobStateSize + metaSize;
static constexpr std::size_t minRespSize = metaOffset +
sizeof(std::uint8_t);
std::vector<std::uint8_t> resp;
try
{
resp = sendIpmiPayload(command, request);
}
catch (const BlobException& b)
{
throw;
}
// Avoid out of bounds memcpy below
if (resp.size() < minRespSize)
{
std::fprintf(stderr,
"Invalid response length, Got %zu which is less than "
"minRespSize %zu\n",
resp.size(), minRespSize);
throw BlobException("Invalid response length");
}
std::memcpy(&meta.blob_state, &resp[0], blobStateSize);
std::memcpy(&meta.size, &resp[blobStateSize], metaSize);
std::uint8_t len = resp[metaOffset];
auto metaDataLength = resp.size() - minRespSize;
if (metaDataLength != len)
{
std::fprintf(stderr,
"Metadata length did not match actual length, Got %zu "
"which does not equal expected length %" PRIu8 "\n",
metaDataLength, len);
throw BlobException("Metadata length did not match actual length");
}
if (len > 0)
{
meta.metadata.resize(len);
std::copy(resp.begin() + minRespSize, resp.end(),
meta.metadata.begin());
}
return meta;
}
StatResponse BlobHandler::getStat(const std::string& id)
{
std::vector<std::uint8_t> name;
std::copy(id.begin(), id.end(), std::back_inserter(name));
name.push_back(0x00); /* need to add nul-terminator. */
return statGeneric(BlobOEMCommands::bmcBlobStat, name);
}
StatResponse BlobHandler::getStat(std::uint16_t session)
{
std::vector<std::uint8_t> request;
auto addrSession = reinterpret_cast<const std::uint8_t*>(&session);
std::copy(addrSession, addrSession + sizeof(session),
std::back_inserter(request));
return statGeneric(BlobOEMCommands::bmcBlobSessionStat, request);
}
std::uint16_t BlobHandler::openBlob(const std::string& id,
std::uint16_t handlerFlags)
{
std::uint16_t session;
std::vector<std::uint8_t> request, resp;
auto addrFlags = reinterpret_cast<const std::uint8_t*>(&handlerFlags);
std::copy(addrFlags, addrFlags + sizeof(handlerFlags),
std::back_inserter(request));
std::copy(id.begin(), id.end(), std::back_inserter(request));
request.push_back(0x00); /* need to add nul-terminator. */
try
{
resp = sendIpmiPayload(BlobOEMCommands::bmcBlobOpen, request);
}
catch (const BlobException& b)
{
throw;
}
if (resp.size() != sizeof(session))
{
throw BlobException("Did not receive session.");
}
std::memcpy(&session, resp.data(), sizeof(session));
return session;
}
void BlobHandler::closeBlob(std::uint16_t session)
{
std::vector<std::uint8_t> request;
auto addrSession = reinterpret_cast<const std::uint8_t*>(&session);
std::copy(addrSession, addrSession + sizeof(session),
std::back_inserter(request));
try
{
sendIpmiPayload(BlobOEMCommands::bmcBlobClose, request);
}
catch (const BlobException& b)
{
std::fprintf(stderr, "Received failure on close: %s\n", b.what());
}
}
bool BlobHandler::deleteBlob(const std::string& id)
{
std::vector<std::uint8_t> name;
std::copy(id.begin(), id.end(), std::back_inserter(name));
name.push_back(0x00); /* need to add nul-terminator. */
try
{
sendIpmiPayload(BlobOEMCommands::bmcBlobDelete, name);
return true;
}
catch (const BlobException& b)
{
std::fprintf(stderr, "Received failure on delete: %s\n", b.what());
}
return false;
}
std::vector<std::uint8_t> BlobHandler::readBytes(std::uint16_t session,
std::uint32_t offset,
std::uint32_t length)
{
std::vector<std::uint8_t> payload;
payload.reserve(sizeof(std::uint16_t) + sizeof(std::uint32_t) +
sizeof(std::uint32_t));
auto data = reinterpret_cast<const std::uint8_t*>(&session);
std::copy(data, data + sizeof(std::uint16_t), std::back_inserter(payload));
data = reinterpret_cast<const std::uint8_t*>(&offset);
std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload));
data = reinterpret_cast<const std::uint8_t*>(&length);
std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload));
return sendIpmiPayload(BlobOEMCommands::bmcBlobRead, payload);
}
} // namespace ipmiblob