initial drop of phosphor-ipmi-blobs
This implements a majority of the OEM IPMI BLOBS protocol. The only
piece missing from this is the timed expiration of sessions.
Change-Id: I82c9d17b625c94fc3340edcfabbbf1ffeb5ad7ac
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..ea71ad6
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,99 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^[<"](gtest|gmock)'
+ Priority: 5
+ - Regex: '^"config.h"'
+ Priority: -1
+ - Regex: '^".*\.hpp"'
+ Priority: 1
+ - Regex: '^<.*\.h>'
+ Priority: 2
+ - Regex: '^<.*'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 4
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dbd5f0a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,64 @@
+# Template from:
+# https://github.com/github/gitignore/blob/master/Autotools.gitignore
+
+# http://www.gnu.org/software/automake
+
+Makefile.in
+/ar-lib
+/mdate-sh
+/py-compile
+/test-driver
+/ylwrap
+
+# http://www.gnu.org/software/autoconf
+
+/autom4te.cache
+/autoscan.log
+/autoscan-*.log
+/aclocal.m4
+/compile
+/config.guess
+/config.h.in
+/config.sub
+/configure
+/configure.scan
+/depcomp
+/install-sh
+/missing
+/stamp-h1
+
+# https://www.gnu.org/software/libtool/
+
+/ltmain.sh
+
+# http://www.gnu.org/software/texinfo
+
+/texinfo.tex
+
+# Repo Specific Items
+*.o
+/config.h
+/config.h.in~
+/config.log
+/config.status
+Makefile
+.deps
+.dirstamp
+/lib*
+.libs/
+/*-libtool
+/ipmid
+.project
+/test/*_unittest
+/test/*.log
+/test/*.trs
+
+# ignore vim swap files
+.*.sw*
+# failures from patch
+*.orig
+*.rej
+# backup files from some editors
+*~
+.cscope/
+build/
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..2f27cc0
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,46 @@
+How to use this list:
+ Find the most specific section entry (described below) that matches where
+ your change lives and add the reviewers (R) and maintainers (M) as
+ reviewers. You can use the same method to track down who knows a particular
+ code base best.
+
+ Your change/query may span multiple entries; that is okay.
+
+ If you do not find an entry that describes your request at all, someone
+ forgot to update this list; please at least file an issue or send an email
+ to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+ Section entries are structured according to the following scheme:
+
+ X: NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+ X: ...
+ .
+ .
+ .
+
+ Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+ organization; FILE_PATH is a file path within the repository, possibly with
+ wildcards; X is a tag of one of the following types:
+
+ M: Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ if omitted from an entry, assume one of the maintainers from the
+ MAINTAINERS entry.
+ R: Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ these people are to be added as reviewers for a change matching the repo
+ path.
+ F: Denotes forked from an external repository; has fields URL.
+
+ Line comments are to be denoted "# SOME COMMENT" (typical shell style
+ comment); it is important to follow the correct syntax and semantics as we
+ may want to use automated tools with this file in the future.
+
+ A change cannot be added to an OpenBMC repository without a MAINTAINER's
+ approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+M: Patrick Venture <venture@google.com> <venture!>
+M: Kun Yi <kunyi731@gmail.com> <kunyi!>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..624bbf2
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,16 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+libblobcmdsdir = ${libdir}/ipmid-providers
+libblobcmds_LTLIBRARIES = libblobcmds.la
+libblobcmds_la_SOURCES = main.cpp \
+ ipmi.cpp \
+ manager.cpp \
+ process.cpp \
+ crc.cpp
+
+libblobcmds_la_LDFLAGS = $(SYSTEMD_LIBS) \
+ -version-info 0:0:0 -shared
+
+libblobcmds_la_CXXFLAGS = $(SYSTEMD_CFLAGS)
+
+SUBDIRS = . test
diff --git a/blobs.hpp b/blobs.hpp
new file mode 100644
index 0000000..b6672b7
--- /dev/null
+++ b/blobs.hpp
@@ -0,0 +1,143 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+enum OpenFlags
+{
+ read = (1 << 0),
+ write = (1 << 1),
+ /* bits 3-7 reserved. */
+ /* bits 8-15 given blob-specific definitions */
+};
+
+enum StateFlags
+{
+ open_read = (1 << 0),
+ open_write = (1 << 1),
+ committing = (1 << 2),
+ committed = (1 << 3),
+ commit_error = (1 << 4),
+};
+
+struct BlobMeta
+{
+ uint16_t blobState;
+ uint32_t size;
+ std::vector<uint8_t> metadata;
+};
+
+/*
+ * All blob specific objects implement this interface.
+ */
+class GenericBlobInterface
+{
+ public:
+ virtual ~GenericBlobInterface() = default;
+
+ /**
+ * Checks if the handler will manage this file path.
+ *
+ * @param[in] blobId.
+ * @return bool whether it will manage the file path.
+ */
+ virtual bool canHandleBlob(const std::string& path) = 0;
+
+ /**
+ * Return the name(s) of the blob(s). Used during GetCount.
+ *
+ * @return List of blobIds this handler manages.
+ */
+ virtual std::vector<std::string> getBlobIds() = 0;
+
+ /**
+ * Attempt to delete the blob specified by the path.
+ *
+ * @param[in] path - the blobId to try and delete.
+ * @return bool - whether it was able to delete the blob.
+ */
+ virtual bool deleteBlob(const std::string& path) = 0;
+
+ /**
+ * Return metadata about the blob.
+ *
+ * @param[in] path - the blobId for metadata.
+ * @param[in,out] meta - a pointer to a blobmeta.
+ * @return bool - true if it was successful.
+ */
+ virtual bool stat(const std::string& path, struct BlobMeta* meta) = 0;
+
+ /* The methods below are per session. */
+
+ /**
+ * Attempt to open a session from this path.
+ *
+ * @param[in] session - the session id.
+ * @param[in] flags - the open flags.
+ * @param[in] path - the blob path.
+ * @return bool - was able to open the session.
+ */
+ virtual bool open(uint16_t session, uint16_t flags,
+ const std::string& path) = 0;
+
+ /**
+ * Attempt to read from a blob.
+ *
+ * @param[in] session - the session id.
+ * @param[in] offset - offset into the blob.
+ * @param[in] requestedSize - number of bytes to read.
+ * @return Bytes read back (0 length on error).
+ */
+ virtual std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize) = 0;
+
+ /**
+ * Attempt to write to a blob.
+ *
+ * @param[in] session - the session id.
+ * @param[in] offset - offset into the blob.
+ * @param[in] data - the data to write.
+ * @return bool - was able to write.
+ */
+ virtual bool write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) = 0;
+
+ /**
+ * Attempt to commit to a blob.
+ *
+ * @param[in] session - the session id.
+ * @param[in] data - optional commit data.
+ * @return bool - was able to start commit.
+ */
+ virtual bool commit(uint16_t session, const std::vector<uint8_t>& data) = 0;
+
+ /**
+ * Attempt to close your session.
+ *
+ * @param[in] session - the session id.
+ * @return bool - was able to close session.
+ */
+ virtual bool close(uint16_t session) = 0;
+
+ /**
+ * Attempt to return metadata for the session's view of the blob.
+ *
+ * @param[in] session - the session id.
+ * @param[in,out] meta - pointer to update with the BlobMeta.
+ * @return bool - wether it was successful.
+ */
+ virtual bool stat(uint16_t session, struct BlobMeta* meta) = 0;
+
+ /**
+ * Attempt to expire a session. This is called when a session has been
+ * inactive for at least 10 minutes.
+ *
+ * @param[in] session - the session id.
+ * @return bool - whether the session was able to be closed.
+ */
+ virtual bool expire(uint16_t session) = 0;
+};
+} // namespace blobs
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..50b75b7
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+ config.guess config.h.in config.sub configure depcomp install-sh \
+ ltmain.sh missing *libtool test-driver"
+
+case $1 in
+ clean)
+ test -f Makefile && make maintainer-clean
+ for file in ${AUTOCONF_FILES}; do
+ find -name "$file" | xargs -r rm -rf
+ done
+ exit 0
+ ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..e81c806
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,58 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([phosphor-ipmi-blobs], [1.0], [https://github.com/openbmc/phosphor-ipmi-blobs/issues])
+AC_LANG([C++])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+AM_SILENT_RULES([yes])
+
+# Checks for programs.
+AC_PROG_CXX
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_14([noext])
+AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221], [], [AC_MSG_ERROR(["systemd required and not found"])])
+AC_CHECK_HEADER([host-ipmid], [AC_MSG_ERROR(["phosphor-host-ipmid required and not found."])])
+AX_PTHREAD([], [AC_MSG_ERROR(["pthread required and not found"])])
+
+# Checks for library functions.
+LT_INIT # Required for systemd linking
+
+# Check/set gtest specific functions.
+PKG_CHECK_MODULES([GTEST], [gtest], [], [AC_MSG_NOTICE([gtest not found, tests will not build])])
+PKG_CHECK_MODULES([GMOCK], [gmock], [], [AC_MSG_NOTICE([gmock not found, tests will not build])])
+PKG_CHECK_MODULES([GTEST_MAIN], [gtest_main], [], [AC_MSG_NOTICE([gtest_main not found, tests will not build])])
+
+# Add --enable-oe-sdk flag to configure script
+AC_ARG_ENABLE([oe-sdk],
+ AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
+)
+
+# Check for OECORE_TARGET_SYSROOT in the environment.
+AC_ARG_VAR(OECORE_TARGET_SYSROOT,
+ [Path to the OE SDK SYSROOT])
+
+# Configure OESDK_TESTCASE_FLAGS environment variable, which will be later
+# used in test/Makefile.am
+AS_IF([test "x$enable_oe_sdk" == "xyes"],
+ AS_IF([test "x$OECORE_TARGET_SYSROOT" == "x"],
+ AC_MSG_ERROR([OECORE_TARGET_SYSROOT must be set with --enable-oe-sdk])
+ )
+ AC_MSG_NOTICE([Enabling OE-SDK at $OECORE_TARGET_SYSROOT])
+ [
+ testcase_flags="-Wl,-rpath,\${OECORE_TARGET_SYSROOT}/lib"
+ testcase_flags="${testcase_flags} -Wl,-rpath,\${OECORE_TARGET_SYSROOT}/usr/lib"
+ testcase_flags="${testcase_flags} -Wl,-dynamic-linker,`find \${OECORE_TARGET_SYSROOT}/lib/ld-*.so | sort -r -n | head -n1`"
+ ]
+ AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
+)
+
+# Create configured output
+AC_CONFIG_FILES([Makefile test/Makefile])
+AC_OUTPUT
diff --git a/crc.cpp b/crc.cpp
new file mode 100644
index 0000000..5fc4558
--- /dev/null
+++ b/crc.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 "crc.hpp"
+
+namespace blobs
+{
+
+void Crc16::clear()
+{
+ value = crc16Initial;
+}
+
+// Origin: security/crypta/ipmi/portable/ipmi_utils.c
+void Crc16::compute(const uint8_t* bytes, uint32_t length)
+{
+ if (!bytes)
+ {
+ return;
+ }
+
+ const int kExtraRounds = 2;
+ const uint16_t kLeftBit = 0x8000;
+ uint16_t crc = value;
+ size_t i, j;
+
+ for (i = 0; i < length + kExtraRounds; ++i)
+ {
+ for (j = 0; j < 8; ++j)
+ {
+ bool xor_flag = crc & kLeftBit;
+ crc <<= 1;
+ // If this isn't an extra round and the current byte's j'th bit
+ // from the left is set, increment the CRC.
+ if (i < length && bytes[i] & (1 << (7 - j)))
+ {
+ crc++;
+ }
+ if (xor_flag)
+ {
+ crc ^= crc16Ccitt;
+ }
+ }
+ }
+
+ value = crc;
+}
+
+uint16_t Crc16::get() const
+{
+ return value;
+}
+} // namespace blobs
diff --git a/crc.hpp b/crc.hpp
new file mode 100644
index 0000000..3793d9a
--- /dev/null
+++ b/crc.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <cstdint>
+
+namespace blobs
+{
+
+using std::size_t;
+using std::uint16_t;
+using std::uint32_t;
+using std::uint8_t;
+
+constexpr uint16_t crc16Ccitt = 0x1021;
+/* Value from: http://srecord.sourceforge.net/crc16-ccitt.html for
+ * implementation without explicit bit adding.
+ */
+constexpr uint16_t crc16Initial = 0xFFFF;
+
+class CrcInterface
+{
+ public:
+ virtual ~CrcInterface() = default;
+
+ /**
+ * Reset the crc.
+ */
+ virtual void clear() = 0;
+
+ /**
+ * Provide bytes against which to compute the crc. This method is
+ * meant to be only called once between clear() and get().
+ *
+ * @param[in] bytes - the data against which to compute.
+ * @param[in] length - the number of bytes.
+ */
+ virtual void compute(const uint8_t* bytes, uint32_t length) = 0;
+
+ /**
+ * Read back the current crc value.
+ *
+ * @return the crc16 value.
+ */
+ virtual uint16_t get() const = 0;
+};
+
+class Crc16 : public CrcInterface
+{
+ public:
+ Crc16() : poly(crc16Ccitt), value(crc16Initial){};
+ ~Crc16() = default;
+
+ void clear() override;
+ void compute(const uint8_t* bytes, uint32_t length) override;
+ uint16_t get() const override;
+
+ private:
+ uint16_t poly;
+ uint16_t value;
+};
+} // namespace blobs
diff --git a/ipmi.cpp b/ipmi.cpp
new file mode 100644
index 0000000..6942b11
--- /dev/null
+++ b/ipmi.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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 "ipmi.hpp"
+
+#include <cstring>
+#include <string>
+#include <unordered_map>
+
+namespace blobs
+{
+
+bool validateRequestLength(BlobOEMCommands command, size_t requestLen)
+{
+ /* The smallest string is one letter and the nul-terminator. */
+ static const int kMinStrLen = 2;
+
+ static const std::unordered_map<BlobOEMCommands, size_t> minimumLengths = {
+ {BlobOEMCommands::bmcBlobEnumerate, sizeof(struct BmcBlobEnumerateTx)},
+ {BlobOEMCommands::bmcBlobOpen,
+ sizeof(struct BmcBlobOpenTx) + kMinStrLen},
+ {BlobOEMCommands::bmcBlobClose, sizeof(struct BmcBlobCloseTx)},
+ {BlobOEMCommands::bmcBlobDelete,
+ sizeof(struct BmcBlobDeleteTx) + kMinStrLen},
+ {BlobOEMCommands::bmcBlobStat,
+ sizeof(struct BmcBlobStatTx) + kMinStrLen},
+ {BlobOEMCommands::bmcBlobSessionStat,
+ sizeof(struct BmcBlobSessionStatTx)},
+ {BlobOEMCommands::bmcBlobCommit, sizeof(struct BmcBlobCommitTx)},
+ {BlobOEMCommands::bmcBlobRead, sizeof(struct BmcBlobReadTx)},
+ {BlobOEMCommands::bmcBlobWrite,
+ sizeof(struct BmcBlobWriteTx) + sizeof(uint8_t)},
+ };
+
+ auto results = minimumLengths.find(command);
+ if (results == minimumLengths.end())
+ {
+ /* Valid length by default if we don't care. */
+ return true;
+ }
+
+ /* If the request is shorter than the minimum, it's invalid. */
+ if (requestLen < results->second)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+std::string stringFromBuffer(const char* start, size_t length)
+{
+ if (!start)
+ {
+ return "";
+ }
+
+ auto end = static_cast<const char*>(std::memchr(start, '\0', length));
+ if (end != &start[length - 1])
+ {
+ return "";
+ }
+
+ return (end == nullptr) ? std::string() : std::string(start, end);
+}
+
+ipmi_ret_t getBlobCount(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ struct BmcBlobCountRx resp;
+ resp.crc = 0;
+ resp.blobCount = mgr->buildBlobList();
+
+ /* Copy the response into the reply buffer */
+ std::memcpy(replyCmdBuf, &resp, sizeof(resp));
+ (*dataLen) = sizeof(resp);
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t enumerateBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ /* Verify datalen is >= sizeof(request) */
+ struct BmcBlobEnumerateTx request;
+ auto reply = reinterpret_cast<struct BmcBlobEnumerateRx*>(replyCmdBuf);
+
+ std::memcpy(&request, reqBuf, sizeof(request));
+
+ std::string blobId = mgr->getBlobId(request.blobIdx);
+ if (blobId == "")
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ /* TODO(venture): Need to do a hard-code check against the maximum
+ * reply buffer size. */
+ reply->crc = 0;
+ /* Explicilty copies the NUL-terminator. */
+ std::memcpy(&reply->blobId, blobId.c_str(), blobId.length() + 1);
+
+ (*dataLen) = sizeof(reply->crc) + blobId.length() + 1;
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t openBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ size_t requestLen = (*dataLen);
+ auto request = reinterpret_cast<const struct BmcBlobOpenTx*>(reqBuf);
+ uint16_t session;
+
+ std::string path = stringFromBuffer(
+ request->blobId, (requestLen - sizeof(struct BmcBlobOpenTx)));
+ if (path.empty())
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ /* Attempt to open. */
+ if (!mgr->open(request->flags, path, &session))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ struct BmcBlobOpenRx reply;
+ reply.crc = 0;
+ reply.sessionId = session;
+
+ std::memcpy(replyCmdBuf, &reply, sizeof(reply));
+ (*dataLen) = sizeof(reply);
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t closeBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ struct BmcBlobCloseTx request;
+ std::memcpy(&request, reqBuf, sizeof(request));
+
+ /* Attempt to close. */
+ if (!mgr->close(request.sessionId))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ (*dataLen) = 0;
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t deleteBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ size_t requestLen = (*dataLen);
+ auto request = reinterpret_cast<const struct BmcBlobDeleteTx*>(reqBuf);
+
+ std::string path = stringFromBuffer(
+ request->blobId, (requestLen - sizeof(struct BmcBlobDeleteTx)));
+ if (path.empty())
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ /* Attempt to delete. */
+ if (!mgr->deleteBlob(path))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ (*dataLen) = 0;
+ return IPMI_CC_OK;
+}
+
+static ipmi_ret_t returnStatBlob(struct BlobMeta* meta, uint8_t* replyCmdBuf,
+ size_t* dataLen)
+{
+ struct BmcBlobStatRx reply;
+ reply.crc = 0;
+ reply.blobState = meta->blobState;
+ reply.size = meta->size;
+ reply.metadataLen = meta->metadata.size();
+
+ std::memcpy(replyCmdBuf, &reply, sizeof(reply));
+
+ /* If there is metadata, copy it over. */
+ if (meta->metadata.size())
+ {
+ uint8_t* metadata = &replyCmdBuf[sizeof(reply)];
+ std::memcpy(metadata, meta->metadata.data(), reply.metadataLen);
+ }
+
+ (*dataLen) = sizeof(reply) + reply.metadataLen;
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t statBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ size_t requestLen = (*dataLen);
+ auto request = reinterpret_cast<const struct BmcBlobStatTx*>(reqBuf);
+
+ std::string path = stringFromBuffer(
+ request->blobId, (requestLen - sizeof(struct BmcBlobStatTx)));
+ if (path.empty())
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ /* Attempt to stat. */
+ struct BlobMeta meta;
+ if (!mgr->stat(path, &meta))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ return returnStatBlob(&meta, replyCmdBuf, dataLen);
+}
+
+ipmi_ret_t sessionStatBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ struct BmcBlobSessionStatTx request;
+ std::memcpy(&request, reqBuf, sizeof(request));
+
+ /* Attempt to stat. */
+ struct BlobMeta meta;
+
+ if (!mgr->stat(request.sessionId, &meta))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ return returnStatBlob(&meta, replyCmdBuf, dataLen);
+}
+
+ipmi_ret_t commitBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ size_t requestLen = (*dataLen);
+ auto request = reinterpret_cast<const struct BmcBlobCommitTx*>(reqBuf);
+
+ /* Sanity check the commitDataLen */
+ if (request->commitDataLen > (requestLen - sizeof(struct BmcBlobCommitTx)))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ std::vector<uint8_t> data(request->commitDataLen);
+ std::memcpy(data.data(), request->commitData, request->commitDataLen);
+
+ if (!mgr->commit(request->sessionId, data))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ (*dataLen) = 0;
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t readBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ struct BmcBlobReadTx request;
+ std::memcpy(&request, reqBuf, sizeof(request));
+
+ /* TODO(venture): Verify requestedSize can fit in a returned IPMI packet.
+ */
+
+ std::vector<uint8_t> result =
+ mgr->read(request.sessionId, request.offset, request.requestedSize);
+
+ /* If the Read fails, it returns success but with only the crc and 0 bytes
+ * of data.
+ * If there was data returned, copy into the reply buffer.
+ */
+ (*dataLen) = sizeof(struct BmcBlobReadRx);
+
+ if (result.size())
+ {
+ uint8_t* output = &replyCmdBuf[sizeof(struct BmcBlobReadRx)];
+ std::memcpy(output, result.data(), result.size());
+
+ (*dataLen) = sizeof(struct BmcBlobReadRx) + result.size();
+ }
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t writeBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ size_t requestLen = (*dataLen);
+ auto request = reinterpret_cast<const struct BmcBlobWriteTx*>(reqBuf);
+
+ uint32_t size = requestLen - sizeof(struct BmcBlobWriteTx);
+ std::vector<uint8_t> data(size);
+
+ std::memcpy(data.data(), request->data, size);
+
+ /* Attempt to write the bytes. */
+ if (!mgr->write(request->sessionId, request->offset, data))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ return IPMI_CC_OK;
+}
+
+} // namespace blobs
diff --git a/ipmi.hpp b/ipmi.hpp
new file mode 100644
index 0000000..c8b6c24
--- /dev/null
+++ b/ipmi.hpp
@@ -0,0 +1,227 @@
+#pragma once
+
+#include "manager.hpp"
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <string>
+
+namespace blobs
+{
+
+enum BlobOEMCommands
+{
+ bmcBlobGetCount = 0,
+ bmcBlobEnumerate = 1,
+ bmcBlobOpen = 2,
+ bmcBlobRead = 3,
+ bmcBlobWrite = 4,
+ bmcBlobCommit = 5,
+ bmcBlobClose = 6,
+ bmcBlobDelete = 7,
+ bmcBlobStat = 8,
+ bmcBlobSessionStat = 9,
+};
+
+/* Used by bmcBlobGetCount */
+struct BmcBlobCountTx
+{
+ uint8_t cmd; /* bmcBlobGetCount */
+} __attribute__((packed));
+
+struct BmcBlobCountRx
+{
+ uint16_t crc;
+ uint32_t blobCount;
+} __attribute__((packed));
+
+/* Used by bmcBlobEnumerate */
+struct BmcBlobEnumerateTx
+{
+ uint8_t cmd; /* bmcBlobEnumerate */
+ uint16_t crc;
+ uint32_t blobIdx;
+} __attribute__((packed));
+
+struct BmcBlobEnumerateRx
+{
+ uint16_t crc;
+ char blobId[];
+} __attribute__((packed));
+
+/* Used by bmcBlobOpen */
+struct BmcBlobOpenTx
+{
+ uint8_t cmd; /* bmcBlobOpen */
+ uint16_t crc;
+ uint16_t flags;
+ char blobId[]; /* Must correspond to a valid blob. */
+} __attribute__((packed));
+
+struct BmcBlobOpenRx
+{
+ uint16_t crc;
+ uint16_t sessionId;
+} __attribute__((packed));
+
+/* Used by bmcBlobClose */
+struct BmcBlobCloseTx
+{
+ uint8_t cmd; /* bmcBlobClose */
+ uint16_t crc;
+ uint16_t sessionId; /* Returned from BmcBlobOpen. */
+} __attribute__((packed));
+
+/* Used by bmcBlobDelete */
+struct BmcBlobDeleteTx
+{
+ uint8_t cmd; /* bmcBlobDelete */
+ uint16_t crc;
+ char blobId[];
+} __attribute__((packed));
+
+/* Used by bmcBlobStat */
+struct BmcBlobStatTx
+{
+ uint8_t cmd; /* bmcBlobStat */
+ uint16_t crc;
+ char blobId[];
+} __attribute__((packed));
+
+struct BmcBlobStatRx
+{
+ uint16_t crc;
+ uint16_t blobState;
+ uint32_t size; /* Size in bytes of the blob. */
+ uint8_t metadataLen;
+ uint8_t metadata[]; /* Optional blob-specific metadata. */
+} __attribute__((packed));
+
+/* Used by bmcBlobSessionStat */
+struct BmcBlobSessionStatTx
+{
+ uint8_t cmd; /* bmcBlobSessionStat */
+ uint16_t crc;
+ uint16_t sessionId;
+} __attribute__((packed));
+
+/* Used by bmcBlobCommit */
+struct BmcBlobCommitTx
+{
+ uint8_t cmd; /* bmcBlobCommit */
+ uint16_t crc;
+ uint16_t sessionId;
+ uint8_t commitDataLen;
+ uint8_t commitData[]; /* Optional blob-specific commit data. */
+} __attribute__((packed));
+
+/* Used by bmcBlobRead */
+struct BmcBlobReadTx
+{
+ uint8_t cmd; /* bmcBlobRead */
+ uint16_t crc;
+ uint16_t sessionId;
+ uint32_t offset; /* The byte sequence start, 0-based. */
+ uint32_t requestedSize; /* The number of bytes requested for reading. */
+} __attribute__((packed));
+
+struct BmcBlobReadRx
+{
+ uint16_t crc;
+ uint8_t data[];
+} __attribute__((packed));
+
+/* Used by bmcBlobWrite */
+struct BmcBlobWriteTx
+{
+ uint8_t cmd; /* bmcBlobWrite */
+ uint16_t crc;
+ uint16_t sessionId;
+ uint32_t offset; /* The byte sequence start, 0-based. */
+ uint8_t data[];
+} __attribute__((packed));
+
+/**
+ * Validate the minimum request length if there is one.
+ *
+ * @param[in] subcommand - the command
+ * @param[in] requestLength - the length of the request
+ * @return bool - true if valid.
+ */
+bool validateRequestLength(BlobOEMCommands command, size_t requestLen);
+
+/**
+ * Given a pointer into an IPMI request buffer and the length of the remaining
+ * buffer, builds a string. This does no string validation w.r.t content.
+ *
+ * @param[in] start - the start of the expected string.
+ * @param[in] length - the number of bytes remaining in the buffer.
+ * @return the string if valid otherwise an empty string.
+ */
+std::string stringFromBuffer(const char* start, size_t length);
+
+/**
+ * Writes out a BmcBlobCountRx structure and returns IPMI_OK.
+ */
+ipmi_ret_t getBlobCount(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Writes out a BmcBlobEnumerateRx in response to a BmcBlobEnumerateTx
+ * request. If the index does not correspond to a blob, then this will
+ * return failure.
+ *
+ * It will also return failure if the response buffer is of an invalid
+ * length.
+ */
+ipmi_ret_t enumerateBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempts to open the blobId specified and associate with a session id.
+ */
+ipmi_ret_t openBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempts to close the session specified.
+ */
+ipmi_ret_t closeBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempts to delete the blobId specified.
+ */
+ipmi_ret_t deleteBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempts to retrieve the Stat for the blobId specified.
+ */
+ipmi_ret_t statBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempts to retrieve the Stat for the session specified.
+ */
+ipmi_ret_t sessionStatBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempts to commit the data in the blob.
+ */
+ipmi_ret_t commitBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempt to read data from the blob.
+ */
+ipmi_ret_t readBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempt to write data to the blob.
+ */
+ipmi_ret_t writeBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+} // namespace blobs
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..e7c1247
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "config.h"
+
+#include "ipmi.hpp"
+#include "process.hpp"
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <cstdio>
+#include <host-ipmid/iana.hpp>
+#include <host-ipmid/oemrouter.hpp>
+#include <memory>
+
+/* TODO: Swap out once https://gerrit.openbmc-project.xyz/12743 is merged */
+namespace oem
+{
+constexpr auto blobTransferCmd = 128;
+} // namespace oem
+
+namespace blobs
+{
+
+static std::unique_ptr<BlobManager> manager;
+
+static ipmi_ret_t handleBlobCommand(ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ /* It's holding at least a sub-command. The OEN is trimmed from the bytes
+ * before this is called.
+ */
+ if ((*dataLen) < 1)
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ Crc16 crc;
+ IpmiBlobHandler command =
+ validateBlobCommand(&crc, reqBuf, replyCmdBuf, dataLen);
+ if (command == nullptr)
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ return processBlobCommand(command, manager.get(), &crc, reqBuf, replyCmdBuf,
+ dataLen);
+}
+
+void setupBlobGlobalHandler() __attribute__((constructor));
+
+void setupBlobGlobalHandler()
+{
+ oem::Router* oemRouter = oem::mutableRouter();
+ std::fprintf(stderr,
+ "Registering OEM:[%#08X], Cmd:[%#04X] for Blob Commands\n",
+ oem::obmcOemNumber, oem::blobTransferCmd);
+
+ oemRouter->registerHandler(oem::obmcOemNumber, oem::blobTransferCmd,
+ handleBlobCommand);
+
+ manager = std::make_unique<BlobManager>();
+}
+} // namespace blobs
diff --git a/manager.cpp b/manager.cpp
new file mode 100644
index 0000000..b6311b4
--- /dev/null
+++ b/manager.cpp
@@ -0,0 +1,347 @@
+/*
+ * 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 "manager.hpp"
+
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+void BlobManager::incrementOpen(const std::string& path)
+{
+ if (path.empty())
+ {
+ return;
+ }
+
+ openFiles[path] += 1;
+}
+
+void BlobManager::decrementOpen(const std::string& path)
+{
+ if (path.empty())
+ {
+ return;
+ }
+
+ /* TODO(venture): Check into the iterator from find, does it makes sense
+ * to just update it directly? */
+ auto entry = openFiles.find(path);
+ if (entry != openFiles.end())
+ {
+ /* found it, decrement it and remove it if 0. */
+ openFiles[path] -= 1;
+ if (openFiles[path] == 0)
+ {
+ openFiles.erase(path);
+ }
+ }
+}
+
+int BlobManager::getOpen(const std::string& path) const
+{
+ /* No need to input check on the read-only call. */
+ auto entry = openFiles.find(path);
+ if (entry != openFiles.end())
+ {
+ return entry->second;
+ }
+
+ return 0;
+}
+
+bool BlobManager::registerHandler(std::unique_ptr<GenericBlobInterface> handler)
+{
+ if (!handler)
+ {
+ return false;
+ }
+
+ handlers.push_back(std::move(handler));
+ return true;
+}
+
+uint32_t BlobManager::buildBlobList()
+{
+ /* Clear out the current list (IPMI handler is presently single-threaded).
+ */
+ ids.clear();
+
+ /* Grab the list of blobs and extend the local list */
+ for (auto& h : handlers)
+ {
+ std::vector<std::string> blobs = h->getBlobIds();
+ ids.insert(ids.end(), blobs.begin(), blobs.end());
+ }
+
+ return ids.size();
+}
+
+std::string BlobManager::getBlobId(uint32_t index)
+{
+ /* Range check. */
+ if (index >= ids.size())
+ {
+ return "";
+ }
+
+ return ids[index];
+}
+
+bool BlobManager::open(uint16_t flags, const std::string& path,
+ uint16_t* session)
+{
+ GenericBlobInterface* handler = getHandler(path);
+
+ /* No handler found. */
+ if (!handler)
+ {
+ return false;
+ }
+
+ /* No sessions availabe... */
+ if (!getSession(session))
+ {
+ return false;
+ }
+
+ /* Verify flags - must be at least read or write */
+ if (!(flags & (OpenFlags::read | OpenFlags::write)))
+ {
+ /* Neither read not write set, which means calls to Read/Write will
+ * reject. */
+ return false;
+ }
+
+ if (!handler->open(*session, flags, path))
+ {
+ return false;
+ }
+
+ /* Associate session with handler */
+ sessions[*session] = SessionInfo(path, handler, flags);
+ incrementOpen(path);
+ return true;
+}
+
+GenericBlobInterface* BlobManager::getHandler(const std::string& path)
+{
+ /* Find a handler. */
+ GenericBlobInterface* handler = nullptr;
+
+ for (auto& h : handlers)
+ {
+ if (h->canHandleBlob(path))
+ {
+ handler = h.get();
+ break;
+ }
+ }
+
+ return handler;
+}
+
+GenericBlobInterface* BlobManager::getHandler(uint16_t session)
+{
+ auto item = sessions.find(session);
+ if (item == sessions.end())
+ {
+ return nullptr;
+ }
+
+ return item->second.handler;
+}
+
+SessionInfo* BlobManager::getSessionInfo(uint16_t session)
+{
+ auto item = sessions.find(session);
+ if (item == sessions.end())
+ {
+ return nullptr;
+ }
+
+ /* If we go to multi-threaded, this pointer can be invalidated and this
+ * method will need to change.
+ */
+ return &item->second;
+}
+
+std::string BlobManager::getPath(uint16_t session) const
+{
+ auto item = sessions.find(session);
+ if (item == sessions.end())
+ {
+ return "";
+ }
+
+ return item->second.blobId;
+}
+
+bool BlobManager::stat(const std::string& path, struct BlobMeta* meta)
+{
+ /* meta should never be NULL. */
+ GenericBlobInterface* handler = getHandler(path);
+
+ /* No handler found. */
+ if (!handler)
+ {
+ return false;
+ }
+
+ return handler->stat(path, meta);
+}
+
+bool BlobManager::stat(uint16_t session, struct BlobMeta* meta)
+{
+ /* meta should never be NULL. */
+ GenericBlobInterface* handler = getHandler(session);
+
+ /* No handler found. */
+ if (!handler)
+ {
+ return false;
+ }
+
+ return handler->stat(session, meta);
+}
+
+bool BlobManager::commit(uint16_t session, const std::vector<uint8_t>& data)
+{
+ GenericBlobInterface* handler = getHandler(session);
+
+ /* No handler found. */
+ if (!handler)
+ {
+ return false;
+ }
+
+ return handler->commit(session, data);
+}
+
+bool BlobManager::close(uint16_t session)
+{
+ GenericBlobInterface* handler = getHandler(session);
+
+ /* No handler found. */
+ if (!handler)
+ {
+ return false;
+ }
+
+ /* Handler returns failure */
+ if (!handler->close(session))
+ {
+ return false;
+ }
+
+ sessions.erase(session);
+ decrementOpen(getPath(session));
+ return true;
+}
+
+std::vector<uint8_t> BlobManager::read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize)
+{
+ SessionInfo* info = getSessionInfo(session);
+
+ /* No session found. */
+ if (!info)
+ {
+ return std::vector<uint8_t>();
+ }
+
+ /* Check flags. */
+ if (!(info->flags & OpenFlags::read))
+ {
+ return std::vector<uint8_t>();
+ }
+
+ /* Try reading from it. */
+ return info->handler->read(session, offset, requestedSize);
+}
+
+bool BlobManager::write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data)
+{
+ SessionInfo* info = getSessionInfo(session);
+
+ /* No session found. */
+ if (!info)
+ {
+ return false;
+ }
+
+ /* Check flags. */
+ if (!(info->flags & OpenFlags::write))
+ {
+ return false;
+ }
+
+ /* Try writing to it. */
+ return info->handler->write(session, offset, data);
+}
+
+bool BlobManager::deleteBlob(const std::string& path)
+{
+ GenericBlobInterface* handler = getHandler(path);
+
+ /* No handler found. */
+ if (!handler)
+ {
+ return false;
+ }
+
+ /* Check if the file has any open handles. */
+ if (getOpen(path) > 0)
+ {
+ return false;
+ }
+
+ /* Try deleting it. */
+ return handler->deleteBlob(path);
+}
+
+bool BlobManager::getSession(uint16_t* sess)
+{
+ uint16_t tries = 0;
+ uint16_t lsess;
+
+ if (!sess)
+ {
+ return false;
+ }
+
+ /* This is not meant to fail as you have 64KiB values available. */
+
+ /* TODO(venture): We could just count the keys in the session map to know
+ * if it's full.
+ */
+ do
+ {
+ lsess = next++;
+ if (!sessions.count(lsess))
+ {
+ /* value not in use, return it. */
+ (*sess) = lsess;
+ return true;
+ }
+ } while (++tries < 0xffff);
+
+ return false;
+}
+} // namespace blobs
diff --git a/manager.hpp b/manager.hpp
new file mode 100644
index 0000000..cfa62f5
--- /dev/null
+++ b/manager.hpp
@@ -0,0 +1,254 @@
+#pragma once
+
+#include "blobs.hpp"
+
+#include <ctime>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace blobs
+{
+
+struct SessionInfo
+{
+ SessionInfo() = default;
+ SessionInfo(const std::string& path, GenericBlobInterface* handler,
+ uint16_t flags) :
+ blobId(path),
+ handler(handler), flags(flags)
+ {
+ }
+ ~SessionInfo() = default;
+
+ std::string blobId;
+ GenericBlobInterface* handler;
+ uint16_t flags;
+};
+
+class ManagerInterface
+{
+ public:
+ virtual ~ManagerInterface() = default;
+
+ virtual bool
+ registerHandler(std::unique_ptr<GenericBlobInterface> handler) = 0;
+
+ virtual uint32_t buildBlobList() = 0;
+
+ virtual std::string getBlobId(uint32_t index) = 0;
+
+ virtual bool open(uint16_t flags, const std::string& path,
+ uint16_t* session) = 0;
+
+ virtual bool stat(const std::string& path, struct BlobMeta* meta) = 0;
+
+ virtual bool stat(uint16_t session, struct BlobMeta* meta) = 0;
+
+ virtual bool commit(uint16_t session, const std::vector<uint8_t>& data) = 0;
+
+ virtual bool close(uint16_t session) = 0;
+
+ virtual std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize) = 0;
+
+ virtual bool write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) = 0;
+
+ virtual bool deleteBlob(const std::string& path) = 0;
+};
+
+/**
+ * Blob Manager used to store handlers and sessions.
+ */
+class BlobManager : public ManagerInterface
+{
+ public:
+ BlobManager()
+ {
+ next = static_cast<uint16_t>(std::time(nullptr));
+ };
+
+ ~BlobManager() = default;
+ /* delete copy constructor & assignment operator, only support move
+ * operations.
+ */
+ BlobManager(const BlobManager&) = delete;
+ BlobManager& operator=(const BlobManager&) = delete;
+ BlobManager(BlobManager&&) = default;
+ BlobManager& operator=(BlobManager&&) = default;
+
+ /**
+ * Register a handler. We own the pointer.
+ *
+ * @param[in] handler - a pointer to a blob handler.
+ * @return bool - true if registered.
+ */
+ bool
+ registerHandler(std::unique_ptr<GenericBlobInterface> handler) override;
+
+ /**
+ * Builds the blobId list for enumeration.
+ *
+ * @return lowest value returned is 0, otherwise the number of
+ * blobIds.
+ */
+ uint32_t buildBlobList() override;
+
+ /**
+ * Grabs the blobId for the indexed blobId.
+ *
+ * @param[in] index - the index into the blobId cache.
+ * @return string - the blobId or empty string on failure.
+ */
+ std::string getBlobId(uint32_t index) override;
+
+ /**
+ * Attempts to open the file specified and associates with a session.
+ *
+ * @param[in] flags - the flags to pass to open.
+ * @param[in] path - the file path to open.
+ * @param[in,out] session - pointer to store the session on success.
+ * @return bool - true if able to open.
+ */
+ bool open(uint16_t flags, const std::string& path,
+ uint16_t* session) override;
+
+ /**
+ * Attempts to retrieve a BlobMeta for the specified path.
+ *
+ * @param[in] path - the file path for stat().
+ * @param[in,out] meta - a pointer to store the metadata.
+ * @return bool - true if able to retrieve the information.
+ */
+ bool stat(const std::string& path, struct BlobMeta* meta) override;
+
+ /**
+ * Attempts to retrieve a BlobMeta for a given session.
+ *
+ * @param[in] session - the session for this command.
+ * @param[in,out] meta - a pointer to store the metadata.
+ * @return bool - true if able to retrieve the information.
+ */
+ bool stat(uint16_t session, struct BlobMeta* meta) override;
+
+ /**
+ * Attempt to commit a blob for a given session.
+ *
+ * @param[in] session - the session for this command.
+ * @param[in] data - an optional commit blob.
+ * @return bool - true if the commit succeeds.
+ */
+ bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
+
+ /**
+ * Attempt to close a session. If the handler returns a failure
+ * in closing, the session is kept open.
+ *
+ * @param[in] session - the session for this command.
+ * @return bool - true if the session was closed.
+ */
+ bool close(uint16_t session) override;
+
+ /**
+ * Attempt to read bytes from the blob. If there's a failure, such as
+ * an invalid offset it'll just return 0 bytes.
+ *
+ * @param[in] session - the session for this command.
+ * @param[in] offset - the offset from which to read.
+ * @param[in] requestedSize - the number of bytes to try and read.
+ * @return the bytes read.
+ */
+ std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize) override;
+
+ /**
+ * Attempt to write to a blob. The manager does not track whether
+ * the session opened the file for writing.
+ *
+ * @param[in] session - the session for this command.
+ * @param[in] offset - the offset into the blob to write.
+ * @param[in] data - the bytes to write to the blob.
+ * @return bool - true if the write succeeded.
+ */
+ bool write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+
+ /**
+ * Attempt to delete a blobId. This method will just call the
+ * handler, which will return failure if the blob doesn't support
+ * deletion. This command will also fail if there are any open
+ * sessions against the specific blob.
+ *
+ * In the case where they specify a folder, such as /blob/skm where
+ * the "real" blobIds are /blob/skm/1, or /blob/skm/2, the manager
+ * may see there are on open sessions to that specific path and will
+ * call the handler. In this case, the handler is responsible for
+ * handling any checks or logic.
+ *
+ * @param[in] path - the blobId path.
+ * @return bool - true if delete was successful.
+ */
+ bool deleteBlob(const std::string& path) override;
+
+ /**
+ * Attempts to return a valid unique session id.
+ *
+ * @param[in,out] - pointer to the session.
+ * @return bool - true if able to allocate.
+ */
+ bool getSession(uint16_t* session);
+
+ /**
+ * Given a file path will return first handler to answer that it owns
+ * it.
+ *
+ * @param[in] path - the file path.
+ * @return pointer to the handler or nullptr if not found.
+ */
+ GenericBlobInterface* getHandler(const std::string& path);
+
+ /**
+ * Given a session id will return associated handler.
+ *
+ * @param[in] session - the session.
+ * @return pointer to the handler or nullptr if not found.
+ */
+ GenericBlobInterface* getHandler(uint16_t session);
+
+ /**
+ * Given a session id will return associated metadata, including
+ * the handler and the flags passed into open.
+ *
+ * @param[in] session - the session.
+ * @return pointer to the information or nullptr if not found.
+ */
+ SessionInfo* getSessionInfo(uint16_t session);
+
+ /**
+ * Given a session id will return associated path.
+ *
+ * @param[in] session - the session.
+ * @return the path or "" on failure.
+ */
+ std::string getPath(uint16_t session) const;
+
+ private:
+ void incrementOpen(const std::string& path);
+ void decrementOpen(const std::string& path);
+ int getOpen(const std::string& path) const;
+
+ /* The next session ID to use */
+ uint16_t next;
+ /* Temporary list of blobIds used for enumeration. */
+ std::vector<std::string> ids;
+ /* List of Blob handler. */
+ std::vector<std::unique_ptr<GenericBlobInterface>> handlers;
+ /* Mapping of session ids to blob handlers and the path used with open.
+ */
+ std::unordered_map<uint16_t, SessionInfo> sessions;
+ /* Mapping of open blobIds */
+ std::unordered_map<std::string, int> openFiles;
+};
+} // namespace blobs
diff --git a/process.cpp b/process.cpp
new file mode 100644
index 0000000..595b5c2
--- /dev/null
+++ b/process.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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 "process.hpp"
+
+#include "ipmi.hpp"
+
+#include <cstring>
+#include <vector>
+
+namespace blobs
+{
+
+/* Used by all commands with data. */
+struct BmcRx
+{
+ uint8_t cmd;
+ uint16_t crc;
+ uint8_t data; /* one byte minimum of data. */
+} __attribute__((packed));
+
+IpmiBlobHandler validateBlobCommand(CrcInterface* crc, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ IpmiBlobHandler cmd;
+ size_t requestLength = (*dataLen);
+ /* We know dataLen is at least 1 already */
+ auto command = static_cast<BlobOEMCommands>(reqBuf[0]);
+
+ /* Validate it's at least well-formed. */
+ if (!validateRequestLength(command, requestLength))
+ {
+ return nullptr;
+ }
+
+ /* If there is a payload. */
+ if (requestLength > sizeof(uint8_t))
+ {
+ /* Verify the request includes: command, crc16, data */
+ if (requestLength < sizeof(struct BmcRx))
+ {
+ return nullptr;
+ }
+
+ /* We don't include the command byte at offset 0 as part of the crc
+ * payload area or the crc bytes at the beginning.
+ */
+ size_t requestBodyLen = requestLength - 3;
+
+ /* We start after the command byte. */
+ std::vector<uint8_t> bytes(requestBodyLen);
+
+ /* It likely has a well-formed payload. */
+ struct BmcRx request;
+ std::memcpy(&request, reqBuf, sizeof(request));
+ uint16_t crcValue = request.crc;
+
+ /* Set the in-place CRC to zero. */
+ std::memcpy(bytes.data(), &reqBuf[3], requestBodyLen);
+
+ crc->clear();
+ crc->compute(bytes.data(), bytes.size());
+
+ /* Crc expected but didn't match. */
+ if (crcValue != crc->get())
+ {
+ return nullptr;
+ }
+ }
+
+ /* Grab the corresponding handler for the command (could do map or array
+ * of function pointer lookup).
+ */
+ switch (command)
+ {
+ case BlobOEMCommands::bmcBlobGetCount:
+ cmd = getBlobCount;
+ break;
+ case BlobOEMCommands::bmcBlobEnumerate:
+ cmd = enumerateBlob;
+ break;
+ case BlobOEMCommands::bmcBlobOpen:
+ cmd = openBlob;
+ break;
+ case BlobOEMCommands::bmcBlobRead:
+ cmd = readBlob;
+ break;
+ case BlobOEMCommands::bmcBlobWrite:
+ cmd = writeBlob;
+ break;
+ case BlobOEMCommands::bmcBlobCommit:
+ cmd = commitBlob;
+ break;
+ case BlobOEMCommands::bmcBlobClose:
+ cmd = closeBlob;
+ break;
+ case BlobOEMCommands::bmcBlobDelete:
+ cmd = deleteBlob;
+ break;
+ case BlobOEMCommands::bmcBlobStat:
+ cmd = statBlob;
+ break;
+ case BlobOEMCommands::bmcBlobSessionStat:
+ cmd = sessionStatBlob;
+ break;
+ default:
+ return nullptr;
+ }
+
+ return cmd;
+}
+
+ipmi_ret_t processBlobCommand(IpmiBlobHandler cmd, ManagerInterface* mgr,
+ CrcInterface* crc, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)
+{
+ ipmi_ret_t result = cmd(mgr, reqBuf, replyCmdBuf, dataLen);
+ if (result != IPMI_CC_OK)
+ {
+ return result;
+ }
+
+ size_t replyLength = (*dataLen);
+
+ /* The command, whatever it was, returned success. */
+ if (replyLength == 0)
+ {
+ return result;
+ }
+
+ /* The response, if it has one byte, has three, to include the crc16. */
+ if (replyLength < (sizeof(uint16_t) + 1))
+ {
+ return IPMI_CC_INVALID;
+ }
+
+ /* The command, whatever it was, replied, so let's set the CRC. */
+ crc->clear();
+ replyCmdBuf[0] = 0x00;
+ replyCmdBuf[1] = 0x00;
+ crc->compute(replyCmdBuf, replyLength);
+
+ /* Copy the CRC into place. */
+ uint16_t crcValue = crc->get();
+ std::memcpy(replyCmdBuf, &crcValue, sizeof(crcValue));
+
+ return result;
+}
+} // namespace blobs
diff --git a/process.hpp b/process.hpp
new file mode 100644
index 0000000..e8a9906
--- /dev/null
+++ b/process.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "crc.hpp"
+#include "manager.hpp"
+
+#include <host-ipmid/ipmid-api.h>
+
+#include <functional>
+
+namespace blobs
+{
+
+using IpmiBlobHandler =
+ std::function<ipmi_ret_t(ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen)>;
+
+/**
+ * Validate the IPMI request and determine routing.
+ *
+ * @param[in] crc - a pointer to the crc interface.
+ * @param[in] reqBuf - a pointer to the ipmi request packet buffer.
+ * @param[in,out] replyCmdBuf - a pointer to the ipmi reply packet buffer.
+ * @param[in,out] dataLen - initially the request length, set to reply length
+ * on return.
+ * @return the ipmi command handler.
+ */
+IpmiBlobHandler validateBlobCommand(CrcInterface* crc, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Call the IPMI command and process the result, including running the CRC
+ * computation for the reply message if there is one.
+ *
+ * @param[in] cmd - a funtion pointer to the ipmi command to process.
+ * @param[in] mgr - a pointer to the manager interface.
+ * @param[in] crc - a pointer to the crc interface.
+ * @param[in] reqBuf - a pointer to the ipmi request packet buffer.
+ * @param[in,out] replyCmdBuf - a pointer to the ipmi reply packet buffer.
+ * @param[in,out] dataLen - initially the request length, set to reply length
+ * on return.
+ * @return the ipmi command result.
+ */
+ipmi_ret_t processBlobCommand(IpmiBlobHandler cmd, ManagerInterface* mgr,
+ CrcInterface* crc, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen);
+} // namespace blobs
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..29586bf
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,110 @@
+AM_CPPFLAGS = -I$(top_srcdir)/ \
+ $(GTEST_CFLAGS) \
+ $(GMOCK_CFLAGS)
+AM_CXXFLAGS = \
+ $(GTEST_MAIN_CFLAGS)
+AM_LDFLAGS = \
+ $(GMOCK_LIBS) \
+ $(GTEST_MAIN_LIBS) \
+ $(OESDK_TESTCASE_FLAGS)
+
+# Run all 'check' test programs
+check_PROGRAMS = \
+ ipmi_unittest \
+ ipmi_getcount_unittest \
+ ipmi_enumerate_unittest \
+ ipmi_open_unittest \
+ ipmi_close_unittest \
+ ipmi_delete_unittest \
+ ipmi_stat_unittest \
+ ipmi_sessionstat_unittest \
+ ipmi_commit_unittest \
+ ipmi_read_unittest \
+ ipmi_write_unittest \
+ ipmi_validate_unittest \
+ manager_unittest \
+ manager_getsession_unittest \
+ manager_open_unittest \
+ manager_stat_unittest \
+ manager_sessionstat_unittest \
+ manager_commit_unittest \
+ manager_close_unittest \
+ manager_delete_unittest \
+ manager_write_unittest \
+ manager_read_unittest \
+ process_unittest \
+ crc_unittest
+TESTS = $(check_PROGRAMS)
+
+ipmi_unittest_SOURCES = ipmi_unittest.cpp
+ipmi_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_getcount_unittest_SOURCES = ipmi_getcount_unittest.cpp
+ipmi_getcount_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_enumerate_unittest_SOURCES = ipmi_enumerate_unittest.cpp
+ipmi_enumerate_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_open_unittest_SOURCES = ipmi_open_unittest.cpp
+ipmi_open_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_close_unittest_SOURCES = ipmi_close_unittest.cpp
+ipmi_close_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_delete_unittest_SOURCES = ipmi_delete_unittest.cpp
+ipmi_delete_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_stat_unittest_SOURCES = ipmi_stat_unittest.cpp
+ipmi_stat_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_sessionstat_unittest_SOURCES = ipmi_sessionstat_unittest.cpp
+ipmi_sessionstat_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_commit_unittest_SOURCES = ipmi_commit_unittest.cpp
+ipmi_commit_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_read_unittest_SOURCES = ipmi_read_unittest.cpp
+ipmi_read_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_write_unittest_SOURCES = ipmi_write_unittest.cpp
+ipmi_write_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_validate_unittest_SOURCES = ipmi_validate_unittest.cpp
+ipmi_validate_unittest_LDADD = $(top_builddir)/ipmi.o
+
+manager_unittest_SOURCES = manager_unittest.cpp
+manager_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_getsession_unittest_SOURCES = manager_getsession_unittest.cpp
+manager_getsession_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_open_unittest_SOURCES = manager_open_unittest.cpp
+manager_open_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_stat_unittest_SOURCES = manager_stat_unittest.cpp
+manager_stat_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_sessionstat_unittest_SOURCES = manager_sessionstat_unittest.cpp
+manager_sessionstat_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_commit_unittest_SOURCES = manager_commit_unittest.cpp
+manager_commit_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_close_unittest_SOURCES = manager_close_unittest.cpp
+manager_close_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_delete_unittest_SOURCES = manager_delete_unittest.cpp
+manager_delete_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_write_unittest_SOURCES = manager_write_unittest.cpp
+manager_write_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_read_unittest_SOURCES = manager_read_unittest.cpp
+manager_read_unittest_LDADD = $(top_builddir)/manager.o
+
+process_unittest_SOURCES = process_unittest.cpp
+process_unittest_LDADD = $(top_builddir)/process.o $(top_builddir)/ipmi.o \
+ $(top_builddir)/crc.o
+
+crc_unittest_SOURCES = crc_unittest.cpp
+crc_unittest_LDADD = $(top_builddir)/crc.o
diff --git a/test/blob_mock.hpp b/test/blob_mock.hpp
new file mode 100644
index 0000000..6c21c65
--- /dev/null
+++ b/test/blob_mock.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "blobs.hpp"
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+
+class BlobMock : public GenericBlobInterface
+{
+ public:
+ virtual ~BlobMock() = default;
+
+ MOCK_METHOD1(canHandleBlob, bool(const std::string&));
+ MOCK_METHOD0(getBlobIds, std::vector<std::string>());
+ MOCK_METHOD1(deleteBlob, bool(const std::string&));
+ MOCK_METHOD2(stat, bool(const std::string&, struct BlobMeta*));
+ MOCK_METHOD3(open, bool(uint16_t, uint16_t, const std::string&));
+ MOCK_METHOD3(read, std::vector<uint8_t>(uint16_t, uint32_t, uint32_t));
+ MOCK_METHOD3(write, bool(uint16_t, uint32_t, const std::vector<uint8_t>&));
+ MOCK_METHOD2(commit, bool(uint16_t, const std::vector<uint8_t>&));
+ MOCK_METHOD1(close, bool(uint16_t));
+ MOCK_METHOD2(stat, bool(uint16_t, struct BlobMeta*));
+ MOCK_METHOD1(expire, bool(uint16_t));
+};
+} // namespace blobs
diff --git a/test/crc_mock.hpp b/test/crc_mock.hpp
new file mode 100644
index 0000000..1562200
--- /dev/null
+++ b/test/crc_mock.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "crc.hpp"
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+
+class CrcMock : public CrcInterface
+{
+ public:
+ virtual ~CrcMock() = default;
+
+ MOCK_METHOD0(clear, void());
+ MOCK_METHOD2(compute, void(const uint8_t*, uint32_t));
+ MOCK_CONST_METHOD0(get, uint16_t());
+};
+} // namespace blobs
diff --git a/test/crc_unittest.cpp b/test/crc_unittest.cpp
new file mode 100644
index 0000000..fb69cb4
--- /dev/null
+++ b/test/crc_unittest.cpp
@@ -0,0 +1,44 @@
+#include "crc.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+TEST(Crc16Test, VerifyCrcValue)
+{
+ // Verify the crc16 is producing the value we expect.
+
+ // Origin: security/crypta/ipmi/portable/ipmi_utils_test.cc
+ struct CrcTestVector
+ {
+ std::string input;
+ uint16_t output;
+ };
+
+ std::string longString =
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAA";
+
+ std::vector<CrcTestVector> vectors({{"", 0x1D0F},
+ {"A", 0x9479},
+ {"123456789", 0xE5CC},
+ {longString, 0xE938}});
+
+ Crc16 crc;
+
+ for (const CrcTestVector& testVector : vectors)
+ {
+ crc.clear();
+ auto data = reinterpret_cast<const uint8_t*>(testVector.input.data());
+ crc.compute(data, testVector.input.size());
+ EXPECT_EQ(crc.get(), testVector.output);
+ }
+}
+} // namespace blobs
diff --git a/test/ipmi_close_unittest.cpp b/test/ipmi_close_unittest.cpp
new file mode 100644
index 0000000..e34f731
--- /dev/null
+++ b/test/ipmi_close_unittest.cpp
@@ -0,0 +1,66 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobCloseTest, ManagerRejectsCloseReturnsFailure)
+{
+ // The session manager returned failure to close, which we need to pass on.
+
+ ManagerMock mgr;
+ uint16_t sessionId = 0x54;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ struct BmcBlobCloseTx req;
+
+ req.cmd = BlobOEMCommands::bmcBlobClose;
+ req.crc = 0;
+ req.sessionId = sessionId;
+
+ dataLen = sizeof(req);
+
+ std::memcpy(request, &req, sizeof(req));
+
+ EXPECT_CALL(mgr, close(sessionId)).WillOnce(Return(false));
+ EXPECT_EQ(IPMI_CC_INVALID, closeBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCloseTest, BlobClosedReturnsSuccess)
+{
+ // Verify that if all goes right, success is returned.
+
+ ManagerMock mgr;
+ uint16_t sessionId = 0x54;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ struct BmcBlobCloseTx req;
+
+ req.cmd = BlobOEMCommands::bmcBlobClose;
+ req.crc = 0;
+ req.sessionId = sessionId;
+
+ dataLen = sizeof(req);
+
+ std::memcpy(request, &req, sizeof(req));
+
+ EXPECT_CALL(mgr, close(sessionId)).WillOnce(Return(true));
+ EXPECT_EQ(IPMI_CC_OK, closeBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/ipmi_commit_unittest.cpp b/test/ipmi_commit_unittest.cpp
new file mode 100644
index 0000000..1cc47a4
--- /dev/null
+++ b/test/ipmi_commit_unittest.cpp
@@ -0,0 +1,112 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::ElementsAreArray;
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobCommitTest, InvalidCommitDataLengthReturnsFailure)
+{
+ // The commit command supports an optional commit blob. This test verifies
+ // we sanity check the length of that blob.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobCommit;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->commitDataLen =
+ 1; // It's one byte, but that's more than the packet size.
+
+ dataLen = sizeof(struct BmcBlobCommitTx);
+
+ EXPECT_EQ(IPMI_CC_INVALID, commitBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCommitTest, ValidCommitNoDataHandlerRejectsReturnsFailure)
+{
+ // The commit packet is valid and the manager's commit call returns failure.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobCommit;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->commitDataLen = 0;
+
+ dataLen = sizeof(struct BmcBlobCommitTx);
+
+ EXPECT_CALL(mgr, commit(req->sessionId, _)).WillOnce(Return(false));
+
+ EXPECT_EQ(IPMI_CC_INVALID, commitBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCommitTest, ValidCommitNoDataHandlerAcceptsReturnsSuccess)
+{
+ // Commit called with no data and everything returns success.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobCommit;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->commitDataLen = 0;
+
+ dataLen = sizeof(struct BmcBlobCommitTx);
+
+ EXPECT_CALL(mgr, commit(req->sessionId, _)).WillOnce(Return(true));
+
+ EXPECT_EQ(IPMI_CC_OK, commitBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCommitTest, ValidCommitWithDataHandlerAcceptsReturnsSuccess)
+{
+ // Commit called with extra data and everything returns success.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+ uint8_t expectedBlob[4] = {0x25, 0x33, 0x45, 0x67};
+
+ req->cmd = BlobOEMCommands::bmcBlobCommit;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->commitDataLen = sizeof(expectedBlob);
+ std::memcpy(req->commitData, &expectedBlob[0], sizeof(expectedBlob));
+
+ dataLen = sizeof(struct BmcBlobCommitTx) + sizeof(expectedBlob);
+
+ EXPECT_CALL(mgr,
+ commit(req->sessionId,
+ ElementsAreArray(expectedBlob, sizeof(expectedBlob))))
+ .WillOnce(Return(true));
+
+ EXPECT_EQ(IPMI_CC_OK, commitBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/ipmi_delete_unittest.cpp b/test/ipmi_delete_unittest.cpp
new file mode 100644
index 0000000..25fb06b
--- /dev/null
+++ b/test/ipmi_delete_unittest.cpp
@@ -0,0 +1,89 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobDeleteTest, InvalidRequestLengthReturnsFailure)
+{
+ // There is a minimum blobId length of one character, this test verifies
+ // we check that.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request);
+ std::string blobId = "abc";
+
+ req->cmd = BlobOEMCommands::bmcBlobDelete;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator.
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length();
+
+ EXPECT_EQ(IPMI_CC_INVALID, deleteBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobDeleteTest, RequestRejectedReturnsFailure)
+{
+ // The blobId is rejected for any reason.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request);
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobDelete;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length() + 1;
+
+ EXPECT_CALL(mgr, deleteBlob(StrEq(blobId))).WillOnce(Return(false));
+
+ EXPECT_EQ(IPMI_CC_INVALID, deleteBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobDeleteTest, BlobDeleteReturnsOk)
+{
+ // The boring case where the blobId is deleted.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request);
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobDelete;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length() + 1;
+
+ EXPECT_CALL(mgr, deleteBlob(StrEq(blobId))).WillOnce(Return(true));
+
+ EXPECT_EQ(IPMI_CC_OK, deleteBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/ipmi_enumerate_unittest.cpp b/test/ipmi_enumerate_unittest.cpp
new file mode 100644
index 0000000..232fe7a
--- /dev/null
+++ b/test/ipmi_enumerate_unittest.cpp
@@ -0,0 +1,65 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobEnumerateTest, VerifyIfRequestByIdInvalidReturnsFailure)
+{
+ // This tests to verify that if the index is invalid, it'll return failure.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ struct BmcBlobEnumerateTx req;
+ uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+
+ req.cmd = BlobOEMCommands::bmcBlobEnumerate;
+ req.blobIdx = 0;
+ dataLen = sizeof(struct BmcBlobEnumerateTx);
+
+ EXPECT_CALL(mgr, getBlobId(req.blobIdx)).WillOnce(Return(""));
+
+ EXPECT_EQ(IPMI_CC_INVALID, enumerateBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobEnumerateTest, BoringRequestByIdAndReceive)
+{
+ // This tests that if an index into the blob_id cache is valid, the command
+ // will return the blobId.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ struct BmcBlobEnumerateTx req;
+ struct BmcBlobEnumerateRx* rep;
+ uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+ std::string blobId = "/asdf";
+
+ req.cmd = BlobOEMCommands::bmcBlobEnumerate;
+ req.blobIdx = 0;
+ dataLen = sizeof(struct BmcBlobEnumerateTx);
+
+ EXPECT_CALL(mgr, getBlobId(req.blobIdx)).WillOnce(Return(blobId));
+
+ EXPECT_EQ(IPMI_CC_OK, enumerateBlob(&mgr, request, reply, &dataLen));
+
+ // We're expecting this as a response.
+ // blobId.length + 1 + sizeof(uint16_t);
+ EXPECT_EQ(blobId.length() + 1 + sizeof(uint16_t), dataLen);
+
+ rep = reinterpret_cast<struct BmcBlobEnumerateRx*>(reply);
+ EXPECT_EQ(0, std::memcmp(rep->blobId, blobId.c_str(), blobId.length() + 1));
+}
+} // namespace blobs
diff --git a/test/ipmi_getcount_unittest.cpp b/test/ipmi_getcount_unittest.cpp
new file mode 100644
index 0000000..c6d74e6
--- /dev/null
+++ b/test/ipmi_getcount_unittest.cpp
@@ -0,0 +1,72 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+// the request here is only the subcommand byte and therefore there's no invalid
+// length check, etc to handle within the method.
+
+TEST(BlobCountTest, ReturnsZeroBlobs)
+{
+ // Calling BmcBlobGetCount if there are no handlers registered should just
+ // return that there are 0 blobs.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ struct BmcBlobCountTx req;
+ struct BmcBlobCountRx rep;
+ uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+
+ req.cmd = BlobOEMCommands::bmcBlobGetCount;
+ dataLen = sizeof(req);
+
+ rep.crc = 0;
+ rep.blobCount = 0;
+
+ EXPECT_CALL(mgr, buildBlobList()).WillOnce(Return(0));
+
+ EXPECT_EQ(IPMI_CC_OK, getBlobCount(&mgr, request, reply, &dataLen));
+
+ EXPECT_EQ(sizeof(rep), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+
+TEST(BlobCountTest, ReturnsTwoBlobs)
+{
+ // Calling BmcBlobGetCount with one handler registered that knows of two
+ // blobs will return that it found two blobs.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ struct BmcBlobCountTx req;
+ struct BmcBlobCountRx rep;
+ uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+
+ req.cmd = BlobOEMCommands::bmcBlobGetCount;
+ dataLen = sizeof(req);
+
+ rep.crc = 0;
+ rep.blobCount = 2;
+
+ EXPECT_CALL(mgr, buildBlobList()).WillOnce(Return(2));
+
+ EXPECT_EQ(IPMI_CC_OK, getBlobCount(&mgr, request, reply, &dataLen));
+
+ EXPECT_EQ(sizeof(rep), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+} // namespace blobs
diff --git a/test/ipmi_open_unittest.cpp b/test/ipmi_open_unittest.cpp
new file mode 100644
index 0000000..db2a34f
--- /dev/null
+++ b/test/ipmi_open_unittest.cpp
@@ -0,0 +1,108 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobOpenTest, InvalidRequestLengthReturnsFailure)
+{
+ // There is a minimum blobId length of one character, this test verifies
+ // we check that.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request);
+ std::string blobId = "abc";
+
+ req->cmd = BlobOEMCommands::bmcBlobOpen;
+ req->crc = 0;
+ req->flags = 0;
+ // length() doesn't include the nul-terminator.
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length();
+
+ EXPECT_EQ(IPMI_CC_INVALID, openBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobOpenTest, RequestRejectedReturnsFailure)
+{
+ // The blobId is rejected for any reason.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request);
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobOpen;
+ req->crc = 0;
+ req->flags = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length() + 1;
+
+ EXPECT_CALL(mgr, open(req->flags, StrEq(blobId), _))
+ .WillOnce(Return(false));
+
+ EXPECT_EQ(IPMI_CC_INVALID, openBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobOpenTest, BlobOpenReturnsOk)
+{
+ // The boring case where the blobId opens.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request);
+ struct BmcBlobOpenRx rep;
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobOpen;
+ req->crc = 0;
+ req->flags = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length() + 1;
+ uint16_t returnedSession = 0x54;
+
+ EXPECT_CALL(mgr, open(req->flags, StrEq(blobId), NotNull()))
+ .WillOnce(Invoke(
+ [&](uint16_t flags, const std::string& path, uint16_t* session) {
+ (*session) = returnedSession;
+ return true;
+ }));
+
+ EXPECT_EQ(IPMI_CC_OK, openBlob(&mgr, request, reply, &dataLen));
+
+ rep.crc = 0;
+ rep.sessionId = returnedSession;
+
+ EXPECT_EQ(sizeof(rep), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+} // namespace blobs
diff --git a/test/ipmi_read_unittest.cpp b/test/ipmi_read_unittest.cpp
new file mode 100644
index 0000000..b6dab55
--- /dev/null
+++ b/test/ipmi_read_unittest.cpp
@@ -0,0 +1,78 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobReadTest, ManagerReturnsNoData)
+{
+ // Verify that if no data is returned the IPMI command reply has no
+ // payload. The manager, in all failures, will just return 0 bytes.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobReadTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobRead;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->offset = 0x100;
+ req->requestedSize = 0x10;
+
+ dataLen = sizeof(struct BmcBlobReadTx);
+
+ std::vector<uint8_t> data;
+
+ EXPECT_CALL(mgr, read(req->sessionId, req->offset, req->requestedSize))
+ .WillOnce(Return(data));
+
+ EXPECT_EQ(IPMI_CC_OK, readBlob(&mgr, request, reply, &dataLen));
+ EXPECT_EQ(sizeof(struct BmcBlobReadRx), dataLen);
+}
+
+TEST(BlobReadTest, ManagerReturnsData)
+{
+ // Verify that if data is returned, it's placed in the expected location.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobReadTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobRead;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->offset = 0x100;
+ req->requestedSize = 0x10;
+
+ dataLen = sizeof(struct BmcBlobReadTx);
+
+ std::vector<uint8_t> data = {0x02, 0x03, 0x05, 0x06};
+
+ EXPECT_CALL(mgr, read(req->sessionId, req->offset, req->requestedSize))
+ .WillOnce(Return(data));
+
+ EXPECT_EQ(IPMI_CC_OK, readBlob(&mgr, request, reply, &dataLen));
+ EXPECT_EQ(sizeof(struct BmcBlobReadRx) + data.size(), dataLen);
+ EXPECT_EQ(0, std::memcmp(&reply[sizeof(struct BmcBlobReadRx)], data.data(),
+ data.size()));
+}
+
+/* TODO(venture): We need a test that handles other checks such as if the size
+ * requested won't fit into a packet response.
+ */
+} // namespace blobs
diff --git a/test/ipmi_sessionstat_unittest.cpp b/test/ipmi_sessionstat_unittest.cpp
new file mode 100644
index 0000000..e1e1aad
--- /dev/null
+++ b/test/ipmi_sessionstat_unittest.cpp
@@ -0,0 +1,121 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Matcher;
+using ::testing::NotNull;
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobSessionStatTest, RequestRejectedByManagerReturnsFailure)
+{
+ // If the session ID is invalid, the request must fail.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request);
+ req->cmd = BlobOEMCommands::bmcBlobSessionStat;
+ req->crc = 0;
+ req->sessionId = 0x54;
+
+ dataLen = sizeof(struct BmcBlobSessionStatTx);
+
+ EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId),
+ Matcher<struct BlobMeta*>(_)))
+ .WillOnce(Return(false));
+
+ EXPECT_EQ(IPMI_CC_INVALID, sessionStatBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobSessionStatTest, RequestSucceedsNoMetadata)
+{
+ // Stat request succeeeds but there were no metadata bytes.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request);
+ req->cmd = BlobOEMCommands::bmcBlobSessionStat;
+ req->crc = 0;
+ req->sessionId = 0x54;
+
+ dataLen = sizeof(struct BmcBlobSessionStatTx);
+
+ struct BmcBlobStatRx rep;
+ rep.crc = 0x00;
+ rep.blobState = 0x01;
+ rep.size = 0x100;
+ rep.metadataLen = 0x00;
+
+ EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId),
+ Matcher<struct BlobMeta*>(NotNull())))
+ .WillOnce(Invoke([&](uint16_t session, struct BlobMeta* meta) {
+ meta->blobState = rep.blobState;
+ meta->size = rep.size;
+ return true;
+ }));
+
+ EXPECT_EQ(IPMI_CC_OK, sessionStatBlob(&mgr, request, reply, &dataLen));
+
+ EXPECT_EQ(sizeof(rep), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+
+TEST(BlobSessionStatTest, RequestSucceedsWithMetadata)
+{
+ // Stat request succeeds and there were metadata bytes.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request);
+ req->cmd = BlobOEMCommands::bmcBlobSessionStat;
+ req->crc = 0;
+ req->sessionId = 0x54;
+
+ dataLen = sizeof(struct BmcBlobSessionStatTx);
+
+ struct BlobMeta lmeta;
+ lmeta.blobState = 0x01;
+ lmeta.size = 0x100;
+ lmeta.metadata.push_back(0x01);
+ lmeta.metadata.push_back(0x02);
+ lmeta.metadata.push_back(0x03);
+ lmeta.metadata.push_back(0x04);
+
+ struct BmcBlobStatRx rep;
+ rep.crc = 0x00;
+ rep.blobState = lmeta.blobState;
+ rep.size = lmeta.size;
+ rep.metadataLen = lmeta.metadata.size();
+
+ EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId),
+ Matcher<struct BlobMeta*>(NotNull())))
+ .WillOnce(Invoke([&](uint16_t session, struct BlobMeta* meta) {
+ (*meta) = lmeta;
+ return true;
+ }));
+
+ EXPECT_EQ(IPMI_CC_OK, sessionStatBlob(&mgr, request, reply, &dataLen));
+
+ EXPECT_EQ(sizeof(rep) + lmeta.metadata.size(), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+ EXPECT_EQ(0, std::memcmp(reply + sizeof(rep), lmeta.metadata.data(),
+ lmeta.metadata.size()));
+}
+} // namespace blobs
diff --git a/test/ipmi_stat_unittest.cpp b/test/ipmi_stat_unittest.cpp
new file mode 100644
index 0000000..a6f1dfe
--- /dev/null
+++ b/test/ipmi_stat_unittest.cpp
@@ -0,0 +1,157 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Matcher;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobStatTest, InvalidRequestLengthReturnsFailure)
+{
+ // There is a minimum blobId length of one character, this test verifies
+ // we check that.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+ std::string blobId = "abc";
+
+ req->cmd = BlobOEMCommands::bmcBlobStat;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator.
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobStatTx) + blobId.length();
+
+ EXPECT_EQ(IPMI_CC_INVALID, statBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobStatTest, RequestRejectedReturnsFailure)
+{
+ // The blobId is rejected for any reason.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobStat;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1;
+
+ EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)),
+ Matcher<struct BlobMeta*>(_)))
+ .WillOnce(Return(false));
+
+ EXPECT_EQ(IPMI_CC_INVALID, statBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobStatTest, RequestSucceedsNoMetadata)
+{
+ // Stat request succeeeds but there were no metadata bytes.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobStat;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1;
+
+ struct BmcBlobStatRx rep;
+ rep.crc = 0x00;
+ rep.blobState = 0x01;
+ rep.size = 0x100;
+ rep.metadataLen = 0x00;
+
+ EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)),
+ Matcher<struct BlobMeta*>(NotNull())))
+ .WillOnce(Invoke([&](const std::string& path, struct BlobMeta* meta) {
+ meta->blobState = rep.blobState;
+ meta->size = rep.size;
+ return true;
+ }));
+
+ EXPECT_EQ(IPMI_CC_OK, statBlob(&mgr, request, reply, &dataLen));
+
+ EXPECT_EQ(sizeof(rep), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+
+TEST(BlobStatTest, RequestSucceedsWithMetadata)
+{
+ // Stat request succeeds and there were metadata bytes.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+ std::string blobId = "a";
+
+ req->cmd = BlobOEMCommands::bmcBlobStat;
+ req->crc = 0;
+ // length() doesn't include the nul-terminator, request buff is initialized
+ // to 0s
+ std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+ dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1;
+
+ struct BlobMeta lmeta;
+ lmeta.blobState = 0x01;
+ lmeta.size = 0x100;
+ lmeta.metadata.push_back(0x01);
+ lmeta.metadata.push_back(0x02);
+ lmeta.metadata.push_back(0x03);
+ lmeta.metadata.push_back(0x04);
+
+ struct BmcBlobStatRx rep;
+ rep.crc = 0x00;
+ rep.blobState = lmeta.blobState;
+ rep.size = lmeta.size;
+ rep.metadataLen = lmeta.metadata.size();
+
+ EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)),
+ Matcher<struct BlobMeta*>(NotNull())))
+ .WillOnce(Invoke([&](const std::string& path, struct BlobMeta* meta) {
+ (*meta) = lmeta;
+ return true;
+ }));
+
+ EXPECT_EQ(IPMI_CC_OK, statBlob(&mgr, request, reply, &dataLen));
+
+ EXPECT_EQ(sizeof(rep) + lmeta.metadata.size(), dataLen);
+ EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+ EXPECT_EQ(0, std::memcmp(reply + sizeof(rep), lmeta.metadata.data(),
+ lmeta.metadata.size()));
+}
+} // namespace blobs
diff --git a/test/ipmi_unittest.cpp b/test/ipmi_unittest.cpp
new file mode 100644
index 0000000..8f27ed7
--- /dev/null
+++ b/test/ipmi_unittest.cpp
@@ -0,0 +1,60 @@
+#include "ipmi.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(StringInputTest, NullPointerInput)
+{
+ // The method should verify it did receive a non-null input pointer.
+
+ EXPECT_STREQ("", stringFromBuffer(NULL, 5).c_str());
+}
+
+TEST(StringInputTest, ZeroBytesInput)
+{
+ // Verify that if the input length is 0 that it'll return the empty string.
+
+ const char* request = "asdf";
+ EXPECT_STREQ("", stringFromBuffer(request, 0).c_str());
+}
+
+TEST(StringInputTest, NulTerminatorNotFound)
+{
+ // Verify that if there isn't a nul-terminator found in an otherwise valid
+ // string, it'll return the emptry string.
+
+ char request[MAX_IPMI_BUFFER];
+ std::memset(request, 'a', sizeof(request));
+ EXPECT_STREQ("", stringFromBuffer(request, sizeof(request)).c_str());
+}
+
+TEST(StringInputTest, TwoNulsFound)
+{
+ // Verify it makes you use the entire data region for the string.
+ char request[MAX_IPMI_BUFFER];
+ request[0] = 'a';
+ request[1] = 0;
+ std::memset(&request[2], 'b', sizeof(request) - 2);
+ request[MAX_IPMI_BUFFER - 1] = 0;
+
+ // This case has two strings, and the last character is a nul-terminator.
+ EXPECT_STREQ("", stringFromBuffer(request, sizeof(request)).c_str());
+}
+
+TEST(StringInputTest, NulTerminatorFound)
+{
+ // Verify that if it's provided a valid nul-terminated string, it'll
+ // return it.
+
+ const char* request = "asdf";
+ EXPECT_STREQ("asdf", stringFromBuffer(request, 5).c_str());
+}
+} // namespace blobs
diff --git a/test/ipmi_validate_unittest.cpp b/test/ipmi_validate_unittest.cpp
new file mode 100644
index 0000000..6bf4200
--- /dev/null
+++ b/test/ipmi_validate_unittest.cpp
@@ -0,0 +1,44 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+TEST(IpmiValidateTest, VerifyCommandMinimumLengths)
+{
+
+ struct TestCase
+ {
+ BlobOEMCommands cmd;
+ size_t len;
+ bool expect;
+ };
+
+ std::vector<TestCase> tests = {
+ {BlobOEMCommands::bmcBlobClose, sizeof(struct BmcBlobCloseTx) - 1,
+ false},
+ {BlobOEMCommands::bmcBlobCommit, sizeof(struct BmcBlobCommitTx) - 1,
+ false},
+ {BlobOEMCommands::bmcBlobDelete, sizeof(struct BmcBlobDeleteTx) + 1,
+ false},
+ {BlobOEMCommands::bmcBlobEnumerate,
+ sizeof(struct BmcBlobEnumerateTx) - 1, false},
+ {BlobOEMCommands::bmcBlobOpen, sizeof(struct BmcBlobOpenTx) + 1, false},
+ {BlobOEMCommands::bmcBlobRead, sizeof(struct BmcBlobReadTx) - 1, false},
+ {BlobOEMCommands::bmcBlobSessionStat,
+ sizeof(struct BmcBlobSessionStatTx) - 1, false},
+ {BlobOEMCommands::bmcBlobStat, sizeof(struct BmcBlobStatTx) + 1, false},
+ {BlobOEMCommands::bmcBlobWrite, sizeof(struct BmcBlobWriteTx), false},
+ };
+
+ for (const auto& test : tests)
+ {
+ bool result = validateRequestLength(test.cmd, test.len);
+ EXPECT_EQ(result, test.expect);
+ }
+}
+} // namespace blobs
diff --git a/test/ipmi_write_unittest.cpp b/test/ipmi_write_unittest.cpp
new file mode 100644
index 0000000..55a1e3b
--- /dev/null
+++ b/test/ipmi_write_unittest.cpp
@@ -0,0 +1,73 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::ElementsAreArray;
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobWriteTest, ManagerReturnsFailureReturnsFailure)
+{
+ // This verifies a failure from the manager is passed back.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobWrite;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->offset = 0x100;
+
+ uint8_t expectedBytes[2] = {0x66, 0x67};
+ std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+ dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+ EXPECT_CALL(mgr,
+ write(req->sessionId, req->offset,
+ ElementsAreArray(expectedBytes, sizeof(expectedBytes))))
+ .WillOnce(Return(false));
+
+ EXPECT_EQ(IPMI_CC_INVALID, writeBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobWriteTest, ManagerReturnsTrueWriteSucceeds)
+{
+ // The case where everything works.
+
+ ManagerMock mgr;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+
+ req->cmd = BlobOEMCommands::bmcBlobWrite;
+ req->crc = 0;
+ req->sessionId = 0x54;
+ req->offset = 0x100;
+
+ uint8_t expectedBytes[2] = {0x66, 0x67};
+ std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+ dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+ EXPECT_CALL(mgr,
+ write(req->sessionId, req->offset,
+ ElementsAreArray(expectedBytes, sizeof(expectedBytes))))
+ .WillOnce(Return(true));
+
+ EXPECT_EQ(IPMI_CC_OK, writeBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/manager_close_unittest.cpp b/test/manager_close_unittest.cpp
new file mode 100644
index 0000000..47c9264
--- /dev/null
+++ b/test/manager_close_unittest.cpp
@@ -0,0 +1,66 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerCloseTest, CloseNoSessionReturnsFalse)
+{
+ // Calling Close on a session that doesn't exist should return false.
+
+ BlobManager mgr;
+ uint16_t sess = 1;
+
+ EXPECT_FALSE(mgr.close(sess));
+}
+
+TEST(ManagerCloseTest, CloseSessionFoundButHandlerReturnsFalse)
+{
+ // The handler was found but it returned failure.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ EXPECT_CALL(*m1ptr, close(sess)).WillOnce(Return(false));
+
+ EXPECT_FALSE(mgr.close(sess));
+
+ // TODO(venture): The session wasn't closed, need to verify. Could call
+ // public GetHandler method.
+}
+
+TEST(ManagerCloseTest, CloseSessionFoundAndHandlerReturnsSuccess)
+{
+ // The handler was found and returned success.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ EXPECT_CALL(*m1ptr, close(sess)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mgr.close(sess));
+}
+} // namespace blobs
diff --git a/test/manager_commit_unittest.cpp b/test/manager_commit_unittest.cpp
new file mode 100644
index 0000000..b1b3c8c
--- /dev/null
+++ b/test/manager_commit_unittest.cpp
@@ -0,0 +1,68 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerCommitTest, CommitNoSessionReturnsFalse)
+{
+ // Calling Commit on a session that doesn't exist should return false.
+
+ BlobManager mgr;
+ uint16_t sess = 1;
+ std::vector<uint8_t> data;
+
+ EXPECT_FALSE(mgr.commit(sess, data));
+}
+
+TEST(ManagerCommitTest, CommitSessionFoundButHandlerReturnsFalse)
+{
+ // The handler was found but it returned failure.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::write, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ std::vector<uint8_t> data;
+ EXPECT_CALL(*m1ptr, commit(sess, data)).WillOnce(Return(false));
+
+ EXPECT_FALSE(mgr.commit(sess, data));
+}
+
+TEST(ManagerCommitTest, CommitSessionFoundAndHandlerReturnsSuccess)
+{
+ // The handler was found and returned success.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::write, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ std::vector<uint8_t> data;
+ EXPECT_CALL(*m1ptr, commit(sess, data)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mgr.commit(sess, data));
+}
+} // namespace blobs
diff --git a/test/manager_delete_unittest.cpp b/test/manager_delete_unittest.cpp
new file mode 100644
index 0000000..9ad3afd
--- /dev/null
+++ b/test/manager_delete_unittest.cpp
@@ -0,0 +1,87 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerDeleteTest, FileIsOpenReturnsFailure)
+{
+ // The blob manager maintains a naive list of open files and will
+ // return failure if you try to delete an open file.
+
+ // Open the file.
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillRepeatedly(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ // Try to delete the file.
+ EXPECT_FALSE(mgr.deleteBlob(path));
+}
+
+TEST(ManagerDeleteTest, FileHasNoHandler)
+{
+ // The blob manager cannot find any handler.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false));
+
+ // Try to delete the file.
+ EXPECT_FALSE(mgr.deleteBlob(path));
+}
+
+TEST(ManagerDeleteTest, FileIsNotOpenButHandlerDeleteFails)
+{
+ // The Blob manager finds the handler but the handler returns failure
+ // on delete.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, deleteBlob(path)).WillOnce(Return(false));
+
+ // Try to delete the file.
+ EXPECT_FALSE(mgr.deleteBlob(path));
+}
+
+TEST(ManagerDeleteTest, FileIsNotOpenAndHandlerSucceeds)
+{
+ // The Blob manager finds the handler and the handler returns success.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, deleteBlob(path)).WillOnce(Return(true));
+
+ // Try to delete the file.
+ EXPECT_TRUE(mgr.deleteBlob(path));
+}
+} // namespace blobs
diff --git a/test/manager_getsession_unittest.cpp b/test/manager_getsession_unittest.cpp
new file mode 100644
index 0000000..e66729a
--- /dev/null
+++ b/test/manager_getsession_unittest.cpp
@@ -0,0 +1,24 @@
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+TEST(ManagerGetSessionTest, NextSessionReturned)
+{
+ // This test verifies the next session ID is returned.
+ BlobManager mgr;
+
+ uint16_t first, second;
+ EXPECT_TRUE(mgr.getSession(&first));
+ EXPECT_TRUE(mgr.getSession(&second));
+ EXPECT_FALSE(first == second);
+}
+
+TEST(ManagerGetSessionTest, SessionsCheckedAgainstList)
+{
+ // TODO(venture): Need a test that verifies the session ids are checked
+ // against open sessions.
+}
+} // namespace blobs
diff --git a/test/manager_mock.hpp b/test/manager_mock.hpp
new file mode 100644
index 0000000..41979ac
--- /dev/null
+++ b/test/manager_mock.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "blobs.hpp"
+#include "manager.hpp"
+
+#include <memory>
+#include <string>
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+
+class ManagerMock : public ManagerInterface
+{
+ public:
+ virtual ~ManagerMock() = default;
+
+ MOCK_METHOD1(registerHandler, bool(std::unique_ptr<GenericBlobInterface>));
+ MOCK_METHOD0(buildBlobList, uint32_t());
+ MOCK_METHOD1(getBlobId, std::string(uint32_t));
+ MOCK_METHOD3(open, bool(uint16_t, const std::string&, uint16_t*));
+ MOCK_METHOD2(stat, bool(const std::string&, struct BlobMeta*));
+ MOCK_METHOD2(stat, bool(uint16_t, struct BlobMeta*));
+ MOCK_METHOD2(commit, bool(uint16_t, const std::vector<uint8_t>&));
+ MOCK_METHOD1(close, bool(uint16_t));
+ MOCK_METHOD3(read, std::vector<uint8_t>(uint16_t, uint32_t, uint32_t));
+ MOCK_METHOD3(write, bool(uint16_t, uint32_t, const std::vector<uint8_t>&));
+ MOCK_METHOD1(deleteBlob, bool(const std::string&));
+};
+} // namespace blobs
diff --git a/test/manager_open_unittest.cpp b/test/manager_open_unittest.cpp
new file mode 100644
index 0000000..309d3f6
--- /dev/null
+++ b/test/manager_open_unittest.cpp
@@ -0,0 +1,85 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerOpenTest, OpenButNoHandler)
+{
+ // No handler claims to be able to open the file.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false));
+ EXPECT_FALSE(mgr.open(flags, path, &sess));
+}
+
+TEST(ManagerOpenTest, OpenButHandlerFailsOpen)
+{
+ // The handler is found but Open fails.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(false));
+ EXPECT_FALSE(mgr.open(flags, path, &sess));
+}
+
+TEST(ManagerOpenTest, OpenFailsMustSupplyAtLeastReadOrWriteFlag)
+{
+ // One must supply either read or write in the flags for the session to
+ // open.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = 0, sess;
+ std::string path = "/asdf/asdf";
+
+ /* It checks if someone can handle the blob before it checks the flags. */
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+
+ EXPECT_FALSE(mgr.open(flags, path, &sess));
+}
+
+TEST(ManagerOpenTest, OpenSucceeds)
+{
+ // The handler is found and Open succeeds.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ // TODO(venture): Need a way to verify the session is associated with it,
+ // maybe just call Read() or SessionStat()
+}
+} // namespace blobs
diff --git a/test/manager_read_unittest.cpp b/test/manager_read_unittest.cpp
new file mode 100644
index 0000000..1d40f5d
--- /dev/null
+++ b/test/manager_read_unittest.cpp
@@ -0,0 +1,78 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerReadTest, ReadNoSessionReturnsFalse)
+{
+ // Calling Read on a session that doesn't exist should return false.
+
+ BlobManager mgr;
+ uint16_t sess = 1;
+ uint32_t ofs = 0x54;
+ uint32_t requested = 0x100;
+
+ std::vector<uint8_t> result = mgr.read(sess, ofs, requested);
+ EXPECT_EQ(0, result.size());
+}
+
+TEST(ManagerReadTest, ReadFromWriteOnlyFails)
+{
+ // The session manager will not route a Read call to a blob if the session
+ // was opened as write-only.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t sess = 1;
+ uint32_t ofs = 0x54;
+ uint32_t requested = 0x100;
+ uint16_t flags = OpenFlags::write;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ std::vector<uint8_t> result = mgr.read(sess, ofs, requested);
+ EXPECT_EQ(0, result.size());
+}
+
+TEST(ManagerReadTest, ReadFromHandlerReturnsData)
+{
+ // There is no logic in this as it's just as a pass-thru command, however
+ // we want to verify this behavior doesn't change.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t sess = 1;
+ uint32_t ofs = 0x54;
+ uint32_t requested = 0x100;
+ uint16_t flags = OpenFlags::read;
+ std::string path = "/asdf/asdf";
+ std::vector<uint8_t> data = {0x12, 0x14, 0x15, 0x16};
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ EXPECT_CALL(*m1ptr, read(sess, ofs, requested)).WillOnce(Return(data));
+
+ std::vector<uint8_t> result = mgr.read(sess, ofs, requested);
+ EXPECT_EQ(data.size(), result.size());
+ EXPECT_EQ(result, data);
+}
+} // namespace blobs
diff --git a/test/manager_sessionstat_unittest.cpp b/test/manager_sessionstat_unittest.cpp
new file mode 100644
index 0000000..6ae27d3
--- /dev/null
+++ b/test/manager_sessionstat_unittest.cpp
@@ -0,0 +1,66 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerSessionStatTest, StatNoSessionReturnsFalse)
+{
+ // Calling Stat on a session that doesn't exist should return false.
+
+ BlobManager mgr;
+ struct BlobMeta meta;
+ uint16_t sess = 1;
+
+ EXPECT_FALSE(mgr.stat(sess, &meta));
+}
+
+TEST(ManagerSessionStatTest, StatSessionFoundButHandlerReturnsFalse)
+{
+ // The handler was found but it returned failure.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ struct BlobMeta meta;
+ EXPECT_CALL(*m1ptr, stat(sess, &meta)).WillOnce(Return(false));
+
+ EXPECT_FALSE(mgr.stat(sess, &meta));
+}
+
+TEST(ManagerSessionStatTest, StatSessionFoundAndHandlerReturnsSuccess)
+{
+ // The handler was found and returned success.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ struct BlobMeta meta;
+ EXPECT_CALL(*m1ptr, stat(sess, &meta)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mgr.stat(sess, &meta));
+}
+} // namespace blobs
diff --git a/test/manager_stat_unittest.cpp b/test/manager_stat_unittest.cpp
new file mode 100644
index 0000000..a13a66d
--- /dev/null
+++ b/test/manager_stat_unittest.cpp
@@ -0,0 +1,60 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+TEST(ManagerStatTest, StatNoHandler)
+{
+ // There is no handler for this path.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ struct BlobMeta meta;
+ std::string path = "/asdf/asdf";
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false));
+
+ EXPECT_FALSE(mgr.stat(path, &meta));
+}
+
+TEST(ManagerStatTest, StatHandlerFoundButFails)
+{
+ // There is a handler for this path but Stat fails.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ struct BlobMeta meta;
+ std::string path = "/asdf/asdf";
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, stat(path, &meta)).WillOnce(Return(false));
+
+ EXPECT_FALSE(mgr.stat(path, &meta));
+}
+
+TEST(ManagerStatTest, StatHandlerFoundAndSucceeds)
+{
+ // There is a handler and Stat succeeds.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ struct BlobMeta meta;
+ std::string path = "/asdf/asdf";
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, stat(path, &meta)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mgr.stat(path, &meta));
+}
+} // namespace blobs
diff --git a/test/manager_unittest.cpp b/test/manager_unittest.cpp
new file mode 100644
index 0000000..7d4d49e
--- /dev/null
+++ b/test/manager_unittest.cpp
@@ -0,0 +1,172 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+TEST(BlobsTest, RegisterNullPointerFails)
+{
+ // The only invalid pointer really is a null one.
+
+ BlobManager mgr;
+ EXPECT_FALSE(mgr.registerHandler(nullptr));
+}
+
+TEST(BlobsTest, RegisterNonNullPointerPasses)
+{
+ // Test that the valid pointer is boringly registered.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+}
+
+TEST(BlobsTest, GetCountNoBlobsRegistered)
+{
+ // Request the Blob Count when there are no blobs.
+
+ BlobManager mgr;
+ EXPECT_EQ(0, mgr.buildBlobList());
+}
+
+TEST(BlobsTest, GetCountBlobRegisteredReturnsOne)
+{
+ // Request the blob count and verify the list is of length one.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ std::vector<std::string> v = {"item"};
+
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ // We expect it to ask for the list.
+ EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v));
+
+ EXPECT_EQ(1, mgr.buildBlobList());
+}
+
+TEST(BlobsTest, GetCountBlobsRegisteredEachReturnsOne)
+{
+ // Request the blob count and verify the list is of length two.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ auto m2ptr = m2.get();
+ std::vector<std::string> v1, v2;
+
+ v1.push_back("asdf");
+ v2.push_back("ghjk");
+
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+ EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+ // We expect it to ask for the list.
+ EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+ EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+ EXPECT_EQ(2, mgr.buildBlobList());
+}
+
+TEST(BlobsTest, EnumerateBlobZerothEntry)
+{
+ // Validate that you can read back the 0th blobId.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ auto m2ptr = m2.get();
+ std::vector<std::string> v1, v2;
+
+ v1.push_back("asdf");
+ v2.push_back("ghjk");
+
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+ EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+ // We expect it to ask for the list.
+ EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+ EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+ EXPECT_EQ(2, mgr.buildBlobList());
+
+ std::string result = mgr.getBlobId(0);
+ // The exact order the blobIds is returned is not guaranteed to never
+ // change.
+ EXPECT_TRUE("asdf" == result || "ghjk" == result);
+}
+
+TEST(BlobsTest, EnumerateBlobFirstEntry)
+{
+ // Validate you can read back the two real entries.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ auto m2ptr = m2.get();
+ std::vector<std::string> v1, v2;
+
+ v1.push_back("asdf");
+ v2.push_back("ghjk");
+
+ // Presently the list of blobs is read and appended in a specific order,
+ // but I don't want to rely on that.
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+ EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+ // We expect it to ask for the list.
+ EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+ EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+ EXPECT_EQ(2, mgr.buildBlobList());
+
+ // Try to grab the two blobIds and verify they're in the list.
+ std::vector<std::string> results;
+ results.push_back(mgr.getBlobId(0));
+ results.push_back(mgr.getBlobId(1));
+ EXPECT_EQ(2, results.size());
+ EXPECT_TRUE(std::find(results.begin(), results.end(), "asdf") !=
+ results.end());
+ EXPECT_TRUE(std::find(results.begin(), results.end(), "ghjk") !=
+ results.end());
+}
+
+TEST(BlobTest, EnumerateBlobInvalidEntry)
+{
+ // Validate trying to read an invalid entry fails expectedly.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ auto m2ptr = m2.get();
+ std::vector<std::string> v1, v2;
+
+ v1.push_back("asdf");
+ v2.push_back("ghjk");
+
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+ EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+ // We expect it to ask for the list.
+ EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+ EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+ EXPECT_EQ(2, mgr.buildBlobList());
+
+ // Grabs the third entry which isn't valid.
+ EXPECT_STREQ("", mgr.getBlobId(2).c_str());
+}
+} // namespace blobs
diff --git a/test/manager_write_unittest.cpp b/test/manager_write_unittest.cpp
new file mode 100644
index 0000000..33c6d5a
--- /dev/null
+++ b/test/manager_write_unittest.cpp
@@ -0,0 +1,90 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace blobs
+{
+
+TEST(ManagerWriteTest, WriteNoSessionReturnsFalse)
+{
+ // Calling Write on a session that doesn't exist should return false.
+
+ BlobManager mgr;
+ uint16_t sess = 1;
+ uint32_t ofs = 0x54;
+ std::vector<uint8_t> data = {0x11, 0x22};
+
+ EXPECT_FALSE(mgr.write(sess, ofs, data));
+}
+
+TEST(ManagerWriteTest, WriteSessionFoundButHandlerReturnsFalse)
+{
+ // The handler was found but it returned failure.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::write, sess;
+ std::string path = "/asdf/asdf";
+ uint32_t ofs = 0x54;
+ std::vector<uint8_t> data = {0x11, 0x22};
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ EXPECT_CALL(*m1ptr, write(sess, ofs, data)).WillOnce(Return(false));
+
+ EXPECT_FALSE(mgr.write(sess, ofs, data));
+}
+
+TEST(ManagerWriteTest, WriteFailsBecauseFileOpenedReadOnly)
+{
+ // The manager will not route a write call to a file opened read-only.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::read, sess;
+ std::string path = "/asdf/asdf";
+ uint32_t ofs = 0x54;
+ std::vector<uint8_t> data = {0x11, 0x22};
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ EXPECT_FALSE(mgr.write(sess, ofs, data));
+}
+
+TEST(ManagerWriteTest, WriteSessionFoundAndHandlerReturnsSuccess)
+{
+ // The handler was found and returned success.
+
+ BlobManager mgr;
+ std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+ auto m1ptr = m1.get();
+ EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+ uint16_t flags = OpenFlags::write, sess;
+ std::string path = "/asdf/asdf";
+ uint32_t ofs = 0x54;
+ std::vector<uint8_t> data = {0x11, 0x22};
+
+ EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+ EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+ EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+ EXPECT_CALL(*m1ptr, write(sess, ofs, data)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mgr.write(sess, ofs, data));
+}
+} // namespace blobs
diff --git a/test/process_unittest.cpp b/test/process_unittest.cpp
new file mode 100644
index 0000000..2ffb023
--- /dev/null
+++ b/test/process_unittest.cpp
@@ -0,0 +1,290 @@
+#include "crc.hpp"
+#include "crc_mock.hpp"
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+#include "process.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+namespace
+{
+
+void EqualFunctions(IpmiBlobHandler lhs, IpmiBlobHandler rhs)
+{
+ EXPECT_FALSE(lhs == nullptr);
+ EXPECT_FALSE(rhs == nullptr);
+
+ ipmi_ret_t (*const* lPtr)(ManagerInterface*, const uint8_t*, uint8_t*,
+ size_t*) =
+ lhs.target<ipmi_ret_t (*)(ManagerInterface*, const uint8_t*, uint8_t*,
+ size_t*)>();
+
+ ipmi_ret_t (*const* rPtr)(ManagerInterface*, const uint8_t*, uint8_t*,
+ size_t*) =
+ rhs.target<ipmi_ret_t (*)(ManagerInterface*, const uint8_t*, uint8_t*,
+ size_t*)>();
+
+ EXPECT_TRUE(lPtr);
+ EXPECT_TRUE(rPtr);
+ EXPECT_EQ(*lPtr, *rPtr);
+ return;
+}
+
+} // namespace
+
+TEST(ValidateBlobCommandTest, InvalidCommandReturnsFailure)
+{
+ // Verify we handle an invalid command.
+
+ StrictMock<CrcMock> crc;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ request[0] = 0xff; // There is no command 0xff.
+ dataLen = sizeof(uint8_t); // There is no payload for CRC.
+
+ EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen));
+}
+
+TEST(ValidateBlobCommandTest, ValidCommandWithoutPayload)
+{
+ // Verify we handle a valid command that doesn't have a payload.
+
+ StrictMock<CrcMock> crc;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ request[0] = BlobOEMCommands::bmcBlobGetCount;
+ dataLen = sizeof(uint8_t); // There is no payload for CRC.
+
+ IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen);
+ EXPECT_FALSE(res == nullptr);
+ EqualFunctions(getBlobCount, res);
+}
+
+TEST(ValidateBlobCommandTest, WithPayloadMinimumLengthIs3VerifyChecks)
+{
+ // Verify that if there's a payload, it's at least one command byte and
+ // two bytes for the crc16 and then one data byte.
+
+ StrictMock<CrcMock> crc;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ request[0] = BlobOEMCommands::bmcBlobGetCount;
+ dataLen = sizeof(uint8_t) + sizeof(uint16_t);
+ // There is a payload, but there are insufficient bytes.
+
+ EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen));
+}
+
+TEST(ValidateBlobCommandTest, WithPayloadAndInvalidCrc)
+{
+ // Verify that the CRC is checked, and failure is reported.
+
+ StrictMock<CrcMock> crc;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+ req->cmd = BlobOEMCommands::bmcBlobWrite;
+ req->crc = 0x34;
+ req->sessionId = 0x54;
+ req->offset = 0x100;
+
+ uint8_t expectedBytes[2] = {0x66, 0x67};
+ std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+ dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+ // skip over cmd and crc.
+ size_t expectedLen = dataLen - 3;
+
+ EXPECT_CALL(crc, clear());
+ EXPECT_CALL(crc, compute(_, expectedLen))
+ .WillOnce(Invoke([&](const uint8_t* bytes, uint32_t length) {
+ EXPECT_EQ(0, std::memcmp(&request[3], bytes, length));
+ }));
+ EXPECT_CALL(crc, get()).WillOnce(Return(0x1234));
+
+ EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen));
+}
+
+TEST(ValidateBlobCommandTest, WithPayloadAndValidCrc)
+{
+ // Verify the CRC is checked and if it matches, return the handler.
+
+ StrictMock<CrcMock> crc;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+ req->cmd = BlobOEMCommands::bmcBlobWrite;
+ req->crc = 0x3412;
+ req->sessionId = 0x54;
+ req->offset = 0x100;
+
+ uint8_t expectedBytes[2] = {0x66, 0x67};
+ std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+ dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+ // skip over cmd and crc.
+ size_t expectedLen = dataLen - 3;
+
+ EXPECT_CALL(crc, clear());
+ EXPECT_CALL(crc, compute(_, expectedLen))
+ .WillOnce(Invoke([&](const uint8_t* bytes, uint32_t length) {
+ EXPECT_EQ(0, std::memcmp(&request[3], bytes, length));
+ }));
+ EXPECT_CALL(crc, get()).WillOnce(Return(0x3412));
+
+ IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen);
+ EXPECT_FALSE(res == nullptr);
+ EqualFunctions(writeBlob, res);
+}
+
+TEST(ValidateBlobCommandTest, InputIntegrationTest)
+{
+ // Given a request buffer generated by the host-side utility, verify it is
+ // properly routed.
+
+ Crc16 crc;
+ size_t dataLen;
+ uint8_t request[] = {0x02, 0x88, 0x21, 0x03, 0x00, 0x2f, 0x64, 0x65, 0x76,
+ 0x2f, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2f, 0x63, 0x6f,
+ 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73,
+ 0x73, 0x74, 0x68, 0x72, 0x75, 0x00};
+
+ // The above request to open a file for reading & writing named:
+ // "/dev/haven/command_passthru"
+
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ dataLen = sizeof(request);
+
+ IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen);
+ EXPECT_FALSE(res == nullptr);
+ EqualFunctions(openBlob, res);
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsNotOk)
+{
+ // Verify that if the IPMI command handler returns not OK that this is
+ // noticed and returned.
+
+ StrictMock<CrcMock> crc;
+ StrictMock<ManagerMock> manager;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf,
+ size_t* dataLen) { return IPMI_CC_INVALID; };
+
+ dataLen = sizeof(request);
+
+ EXPECT_EQ(IPMI_CC_INVALID,
+ processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsOkWithNoPayload)
+{
+ // Verify that if the IPMI command handler returns OK but without a payload
+ // it doesn't try to compute a CRC.
+
+ StrictMock<CrcMock> crc;
+ StrictMock<ManagerMock> manager;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen) {
+ (*dataLen) = 0;
+ return IPMI_CC_OK;
+ };
+
+ dataLen = sizeof(request);
+
+ EXPECT_EQ(IPMI_CC_OK,
+ processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsOkWithInvalidPayloadLength)
+{
+ // There is a minimum payload length of 3 bytes, this command returns a
+ // payload of 2 bytes.
+
+ StrictMock<CrcMock> crc;
+ StrictMock<ManagerMock> manager;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+ IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen) {
+ (*dataLen) = sizeof(uint16_t);
+ return IPMI_CC_OK;
+ };
+
+ dataLen = sizeof(request);
+
+ EXPECT_EQ(IPMI_CC_INVALID,
+ processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsOkWithValidPayloadLength)
+{
+ // There is a minimum payload length of 3 bytes, this command returns a
+ // payload of 3 bytes and the crc code is called to process the payload.
+
+ StrictMock<CrcMock> crc;
+ StrictMock<ManagerMock> manager;
+ size_t dataLen;
+ uint8_t request[MAX_IPMI_BUFFER] = {0};
+ uint8_t reply[MAX_IPMI_BUFFER] = {0};
+ uint32_t payloadLen = sizeof(uint16_t) + sizeof(uint8_t);
+
+ IpmiBlobHandler h = [payloadLen](ManagerInterface* mgr,
+ const uint8_t* reqBuf,
+ uint8_t* replyCmdBuf, size_t* dataLen) {
+ (*dataLen) = payloadLen;
+ replyCmdBuf[2] = 0x56;
+ return IPMI_CC_OK;
+ };
+
+ dataLen = sizeof(request);
+
+ EXPECT_CALL(crc, clear());
+ EXPECT_CALL(crc, compute(_, payloadLen));
+ EXPECT_CALL(crc, get()).WillOnce(Return(0x3412));
+
+ EXPECT_EQ(IPMI_CC_OK,
+ processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+ EXPECT_EQ(dataLen, payloadLen);
+
+ uint8_t expectedBytes[3] = {0x12, 0x34, 0x56};
+ EXPECT_EQ(0, std::memcmp(expectedBytes, reply, sizeof(expectedBytes)));
+}
+} // namespace blobs