| /* |
| * 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 <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 |