tools: blob: implement layer above ipmi
Implement the layer above actual IPMI calls, such that we can verify the
behavior. There is a layer beneath this that'll compute CRCs before
passing the commands down, that will go in next.
Change-Id: I0b8e3aa93c171d829e32727c7bb1b411659d80bd
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/test/Makefile.am b/test/Makefile.am
index aac24a1..f6a9263 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -26,7 +26,8 @@
firmware_delete_unittest \
firmware_sessionstat_unittest \
firmware_commit_unittest \
- file_handler_unittest
+ file_handler_unittest \
+ tools_blob_unittest
TESTS = $(check_PROGRAMS)
@@ -65,3 +66,6 @@
file_handler_unittest_SOURCES = file_handler_unittest.cpp
file_handler_unittest_LDADD = $(top_builddir)/file_handler.o -lstdc++fs
+
+tools_blob_unittest_SOURCES = tools_blob_unittest.cpp
+tools_blob_unittest_LDADD = $(top_builddir)/tools/blob_handler.o
diff --git a/test/tools_blob_unittest.cpp b/test/tools_blob_unittest.cpp
new file mode 100644
index 0000000..020c2fc
--- /dev/null
+++ b/test/tools_blob_unittest.cpp
@@ -0,0 +1,96 @@
+#include "blob_handler.hpp"
+#include "ipmi_interface_mock.hpp"
+
+#include <gtest/gtest.h>
+
+std::uint16_t expectedCrc = 0;
+
+std::uint16_t generateCrc(const std::vector<std::uint8_t>& data)
+{
+ return expectedCrc;
+}
+
+using ::testing::Eq;
+using ::testing::Return;
+
+TEST(BlobHandler, getCountIpmiHappy)
+{
+ /* Verify returns the value specified by the IPMI response. */
+ IpmiInterfaceMock ipmiMock;
+ BlobHandler blob(&ipmiMock);
+ std::vector<std::uint8_t> request = {
+ 0xcf, 0xc2, 0x00, BlobHandler::BlobOEMCommands::bmcBlobGetCount};
+
+ /* return 1 blob count. */
+ std::vector<std::uint8_t> resp = {0xcf, 0xc2, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00};
+
+ EXPECT_CALL(ipmiMock, sendPacket(Eq(request))).WillOnce(Return(resp));
+ EXPECT_EQ(1, blob.getBlobCount());
+}
+
+TEST(BlobHandler, enumerateBlobIpmiHappy)
+{
+ /* Verify returns the name specified by the IPMI response. */
+ IpmiInterfaceMock ipmiMock;
+ BlobHandler blob(&ipmiMock);
+ std::vector<std::uint8_t> request = {
+ 0xcf, 0xc2, 0x00, BlobHandler::BlobOEMCommands::bmcBlobEnumerate,
+ 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00};
+
+ /* return value. */
+ std::vector<std::uint8_t> resp = {0xcf, 0xc2, 0x00, 0x00, 0x00,
+ 'a', 'b', 'c', 'd'};
+
+ EXPECT_CALL(ipmiMock, sendPacket(Eq(request))).WillOnce(Return(resp));
+ EXPECT_STREQ("abcd", blob.enumerateBlob(1).c_str());
+}
+
+TEST(BlobHandler, enumerateBlobIpmiNoBytes)
+{
+ /* Simulate a case where the IPMI command returns no data. */
+ IpmiInterfaceMock ipmiMock;
+ BlobHandler blob(&ipmiMock);
+ std::vector<std::uint8_t> request = {
+ 0xcf, 0xc2, 0x00, BlobHandler::BlobOEMCommands::bmcBlobEnumerate,
+ 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00};
+
+ /* return value. */
+ std::vector<std::uint8_t> resp = {};
+
+ EXPECT_CALL(ipmiMock, sendPacket(Eq(request))).WillOnce(Return(resp));
+ EXPECT_STREQ("", blob.enumerateBlob(1).c_str());
+}
+
+TEST(BlobHandler, getBlobListIpmiHappy)
+{
+ /* Verify returns the list built via the above two commands. */
+ IpmiInterfaceMock ipmiMock;
+ BlobHandler blob(&ipmiMock);
+
+ std::vector<std::uint8_t> request1 = {
+ 0xcf, 0xc2, 0x00, BlobHandler::BlobOEMCommands::bmcBlobGetCount};
+
+ /* return 1 blob count. */
+ std::vector<std::uint8_t> resp1 = {0xcf, 0xc2, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00};
+
+ EXPECT_CALL(ipmiMock, sendPacket(Eq(request1))).WillOnce(Return(resp1));
+
+ std::vector<std::uint8_t> request2 = {
+ 0xcf, 0xc2, 0x00, BlobHandler::BlobOEMCommands::bmcBlobEnumerate,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00};
+
+ /* return value. */
+ std::vector<std::uint8_t> resp2 = {0xcf, 0xc2, 0x00, 0x00, 0x00,
+ 'a', 'b', 'c', 'd'};
+
+ EXPECT_CALL(ipmiMock, sendPacket(Eq(request2))).WillOnce(Return(resp2));
+
+ std::vector<std::string> expectedList = {"abcd"};
+
+ EXPECT_EQ(expectedList, blob.getBlobList());
+}
diff --git a/tools/blob_handler.cpp b/tools/blob_handler.cpp
index a2fe9d3..e05da90 100644
--- a/tools/blob_handler.cpp
+++ b/tools/blob_handler.cpp
@@ -1,6 +1,151 @@
+/*
+ * 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 "crc.hpp"
+#include "ipmi_errors.hpp"
+
+#include <array>
+#include <cstring>
+
+const std::array<std::uint8_t, 3> ipmiPhosphorOen = {0xcf, 0xc2, 0x00};
+
+std::vector<std::uint8_t>
+ BlobHandler::sendIpmiPayload(BlobOEMCommands command,
+ const std::vector<std::uint8_t>& payload)
+{
+ std::vector<std::uint8_t> request, reply;
+
+ std::copy(ipmiPhosphorOen.begin(), ipmiPhosphorOen.end(),
+ std::back_inserter(request));
+ request.push_back(command);
+
+ if (payload.size() > 0)
+ {
+ /* Grow the vector to hold the bytes. */
+ request.reserve(request.size() + sizeof(std::uint16_t));
+
+ /* CRC required. */
+ std::uint16_t crc = generateCrc(payload);
+ std::uint8_t* src = reinterpret_cast<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(request);
+ }
+ catch (const IpmiException& e)
+ {
+ std::fprintf(stderr, "Received exception: %s\n", e.what());
+ return {};
+ }
+
+ /* IPMI_CC was OK, and it returned no bytes, so let's be happy with that for
+ * now.
+ */
+ if (reply.size() == 0)
+ {
+ return reply;
+ }
+
+ size_t headerSize = ipmiPhosphorOen.size() + sizeof(std::uint16_t);
+
+ /* This cannot be a response because it's smaller than the smallest
+ * response.
+ */
+ if (reply.size() < headerSize)
+ {
+ std::fprintf(stderr, "Invalid response length\n");
+ return {};
+ }
+
+ /* Verify the OEN. */
+ if (std::memcmp(ipmiPhosphorOen.data(), reply.data(),
+ ipmiPhosphorOen.size()) != 0)
+ {
+ std::fprintf(stderr, "Invalid OEN received\n");
+ return {};
+ }
+
+ /* Validate CRC. */
+ std::uint16_t crc;
+ auto ptr = reinterpret_cast<std::uint8_t*>(&crc);
+ std::memcpy(ptr, &reply[ipmiPhosphorOen.size()], sizeof(crc));
+
+ std::vector<std::uint8_t> bytes;
+ std::copy(&reply[headerSize], &reply[reply.size()],
+ std::back_inserter(bytes));
+
+ auto computed = generateCrc(bytes);
+ if (crc != computed)
+ {
+ std::fprintf(stderr, "Invalid CRC, received: 0x%x, computed: 0x%x\n",
+ crc, computed);
+ return {};
+ }
+
+ return bytes;
+}
+
+int BlobHandler::getBlobCount()
+{
+ std::uint32_t count;
+ 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));
+ return count;
+}
+
+std::string BlobHandler::enumerateBlob(std::uint32_t index)
+{
+ std::vector<std::uint8_t> payload;
+ std::uint8_t* data = reinterpret_cast<std::uint8_t*>(&index);
+ std::copy(data, data + sizeof(std::uint32_t), std::back_inserter(payload));
+
+ auto resp = sendIpmiPayload(BlobOEMCommands::bmcBlobEnumerate, payload);
+ std::string output;
+ std::copy(resp.begin(), resp.end(), std::back_inserter(output));
+ return output;
+}
+
std::vector<std::string> BlobHandler::getBlobList()
{
- return {};
+ 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;
}
diff --git a/tools/blob_handler.hpp b/tools/blob_handler.hpp
index 285bbf1..fb2dcb5 100644
--- a/tools/blob_handler.hpp
+++ b/tools/blob_handler.hpp
@@ -6,8 +6,50 @@
class BlobHandler : public BlobInterface
{
public:
+ enum BlobOEMCommands
+ {
+ bmcBlobGetCount = 0,
+ bmcBlobEnumerate = 1,
+ bmcBlobOpen = 2,
+ bmcBlobRead = 3,
+ bmcBlobWrite = 4,
+ bmcBlobCommit = 5,
+ bmcBlobClose = 6,
+ bmcBlobDelete = 7,
+ bmcBlobStat = 8,
+ bmcBlobSessionStat = 9,
+ bmcBlobWriteMeta = 10,
+ };
+
explicit BlobHandler(IpmiInterface* ipmi) : ipmi(ipmi){};
+ /**
+ * Send the contents of the payload to IPMI, this method handles wrapping
+ * with the OEN, subcommand and CRC.
+ *
+ * @param[in] command - the blob command.
+ * @param[in] payload - the payload bytes.
+ * @return the bytes returned from the ipmi interface.
+ */
+ std::vector<std::uint8_t>
+ sendIpmiPayload(BlobOEMCommands command,
+ const std::vector<std::uint8_t>& payload);
+
+ /**
+ * Retrieve the blob count.
+ *
+ * @return the number of blob_ids found (0 on failure).
+ */
+ int getBlobCount();
+
+ /**
+ * Given an index into the list of blobs, return the name.
+ *
+ * @param[in] index - the index into the list of blob ids.
+ * @return the name as a string or empty on failure.
+ */
+ std::string enumerateBlob(std::uint32_t index);
+
std::vector<std::string> getBlobList() override;
private: