diff options
44 files changed, 4369 insertions, 0 deletions
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 @@ -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 @@ -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 |