import phosphor-net-ipmid as rmcpbridge
Change-Id: I4e62b33c178c9ee02d623579ba174c1359720525
diff --git a/transport/rmcpbridge/.clang-format b/transport/rmcpbridge/.clang-format
new file mode 100644
index 0000000..e5530e6
--- /dev/null
+++ b/transport/rmcpbridge/.clang-format
@@ -0,0 +1,136 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands: Align
+AlignTrailingComments:
+ Kind: Always
+ OverEmptyLines: 1
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: true
+BinPackParameters: true
+BitFieldColonSpacing: None
+BraceWrapping:
+ AfterCaseLabel: true
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterExternBlock: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakAfterAttributes: Never
+BreakAfterReturnType: Automatic
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+BreakInheritanceList: AfterColon
+BreakStringLiterals: false
+BreakTemplateDeclarations: Yes
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^[<"](gtest|gmock)'
+ Priority: 7
+ - Regex: '^"config.h"'
+ Priority: -1
+ - Regex: '^".*\.h"'
+ Priority: 1
+ - Regex: '^".*\.hpp"'
+ Priority: 2
+ - Regex: '^<.*\.h>'
+ Priority: 3
+ - Regex: '^<.*\.hpp>'
+ Priority: 4
+ - Regex: '^<.*'
+ Priority: 5
+ - Regex: '.*'
+ Priority: 6
+IndentCaseLabels: true
+IndentExternBlock: NoIndent
+IndentRequiresClause: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+InsertNewlineAtEOF: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+LineEnding: LF
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PackConstructorInitializers: BinPack
+PenaltyBreakAssignment: 25
+PenaltyBreakBeforeFirstCallParameter: 50
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 150
+PenaltyIndentedWhitespace: 1
+PointerAlignment: Left
+QualifierAlignment: Left
+ReferenceAlignment: Left
+ReflowComments: true
+RequiresClausePosition: OwnLine
+RequiresExpressionIndentation: Keyword
+SortIncludes: CaseSensitive
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Latest
+TabWidth: 4
+UseTab: Never
+...
+
diff --git a/transport/rmcpbridge/.clang-tidy b/transport/rmcpbridge/.clang-tidy
new file mode 100644
index 0000000..4b6eca0
--- /dev/null
+++ b/transport/rmcpbridge/.clang-tidy
@@ -0,0 +1,7 @@
+Checks: '
+ -*,
+ bugprone-unchecked-optional-access,
+ readability-identifier-naming
+'
+WarningsAsErrors: '*'
+HeaderFilterRegex: '(?!^subprojects).*'
diff --git a/transport/rmcpbridge/.gitignore b/transport/rmcpbridge/.gitignore
new file mode 100644
index 0000000..51ef08e
--- /dev/null
+++ b/transport/rmcpbridge/.gitignore
@@ -0,0 +1,3 @@
+/build*/
+/subprojects/*
+!subprojects/*.wrap
diff --git a/transport/rmcpbridge/LICENSE b/transport/rmcpbridge/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/transport/rmcpbridge/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
diff --git a/transport/rmcpbridge/OWNERS b/transport/rmcpbridge/OWNERS
new file mode 100644
index 0000000..05f2bbe
--- /dev/null
+++ b/transport/rmcpbridge/OWNERS
@@ -0,0 +1,48 @@
+# OWNERS
+# ------
+#
+# The OWNERS file maintains the list of individuals responsible for various
+# parts of this repository, including code review and approval. We use the
+# Gerrit 'owners' plugin, which consumes this file, along with some extra
+# keywords for our own purposes and tooling.
+#
+# For details on the configuration used by 'owners' see:
+# https://gerrit.googlesource.com/plugins/owners/+/refs/heads/master/owners/src/main/resources/Documentation/config.md
+#
+# An OWNERS file must be in the root of a repository but may also be present
+# in any subdirectory. The contents of the subdirectory OWNERS file are
+# combined with parent directories unless 'inherit: false' is set.
+#
+# The owners file is YAML and has [up to] 4 top-level keywords.
+# * owners: A list of individuals who have approval authority on the
+# repository.
+#
+# * reviewers: A list of individuals who have requested review notification
+# on the repository.
+#
+# * matchers: A list of specific file/path matchers for granular 'owners' and
+# 'reviewers'. See 'owners' plugin documentation.
+#
+# * openbmc: A list of openbmc-specific meta-data about owners and reviewers.
+# - name: preferred name of the individual.
+# - email: preferred email address of the individual.
+# - discord: Discord nickname of the individual.
+#
+# It is expected that these 4 sections will be listed in the order above and
+# data within them will be kept sorted.
+
+owners:
+- rushtotom@gmail.com
+- vernon.mauery@gmail.com
+
+reviewers:
+
+matchers:
+
+openbmc:
+- name: Tom Joseph
+ email: rushtotom@gmail.com
+ discord: tomjose
+- name: Vernon Mauery
+ email: vernon.mauery@gmail.com
+ discord: vmauery
diff --git a/transport/rmcpbridge/README.md b/transport/rmcpbridge/README.md
new file mode 100644
index 0000000..89aba20
--- /dev/null
+++ b/transport/rmcpbridge/README.md
@@ -0,0 +1,13 @@
+# phosphor-net-ipmid
+
+## To Build
+
+To build this package, do the following steps:
+
+```sh
+1. ./bootstrap.sh
+2. ./configure ${CONFIGURE_FLAGS}
+3. make
+```
+
+To clean the repository run `./bootstrap.sh clean`.
diff --git a/transport/rmcpbridge/auth_algo.cpp b/transport/rmcpbridge/auth_algo.cpp
new file mode 100644
index 0000000..23cefda
--- /dev/null
+++ b/transport/rmcpbridge/auth_algo.cpp
@@ -0,0 +1,90 @@
+#include "auth_algo.hpp"
+
+#include <error.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+#include <string.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace cipher
+{
+
+namespace rakp_auth
+{
+
+std::vector<uint8_t> AlgoSHA1::generateHMAC(
+ const std::vector<uint8_t>& input) const
+{
+ std::vector<uint8_t> output(SHA_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+
+ if (HMAC(EVP_sha1(), userKey.data(), userKey.size(), input.data(),
+ input.size(), output.data(), &mdLen) == NULL)
+ {
+ lg2::error("Generate HMAC failed: {ERROR}", "ERROR", strerror(errno));
+ output.resize(0);
+ }
+
+ return output;
+}
+
+std::vector<uint8_t> AlgoSHA1::generateICV(
+ const std::vector<uint8_t>& input) const
+{
+ std::vector<uint8_t> output(SHA_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+
+ if (HMAC(EVP_sha1(), sessionIntegrityKey.data(), SHA_DIGEST_LENGTH,
+ input.data(), input.size(), output.data(), &mdLen) == NULL)
+ {
+ lg2::error("Generate Session Integrity Key failed: {ERROR}", "ERROR",
+ strerror(errno));
+ output.resize(0);
+ }
+ output.resize(integrityCheckValueLength);
+
+ return output;
+}
+
+std::vector<uint8_t> AlgoSHA256::generateHMAC(
+ const std::vector<uint8_t>& input) const
+{
+ std::vector<uint8_t> output(SHA256_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+
+ if (HMAC(EVP_sha256(), userKey.data(), userKey.size(), input.data(),
+ input.size(), output.data(), &mdLen) == NULL)
+ {
+ lg2::error("Generate HMAC_SHA256 failed: {ERROR}", "ERROR",
+ strerror(errno));
+ output.resize(0);
+ }
+
+ return output;
+}
+
+std::vector<uint8_t> AlgoSHA256::generateICV(
+ const std::vector<uint8_t>& input) const
+{
+ std::vector<uint8_t> output(SHA256_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+
+ if (HMAC(EVP_sha256(), sessionIntegrityKey.data(),
+ sessionIntegrityKey.size(), input.data(), input.size(),
+ output.data(), &mdLen) == NULL)
+ {
+ lg2::error(
+ "Generate HMAC_SHA256_128 Integrity Check Value failed: {ERROR}",
+ "ERROR", strerror(errno));
+ output.resize(0);
+ }
+ output.resize(integrityCheckValueLength);
+
+ return output;
+}
+
+} // namespace rakp_auth
+
+} // namespace cipher
diff --git a/transport/rmcpbridge/auth_algo.hpp b/transport/rmcpbridge/auth_algo.hpp
new file mode 100644
index 0000000..e179d72
--- /dev/null
+++ b/transport/rmcpbridge/auth_algo.hpp
@@ -0,0 +1,219 @@
+#pragma once
+
+#include "crypt_algo.hpp"
+#include "integrity_algo.hpp"
+
+#include <array>
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace cipher
+{
+namespace rakp_auth
+{
+constexpr size_t USER_KEY_MAX_LENGTH = 20;
+constexpr size_t BMC_RANDOM_NUMBER_LEN = 16;
+constexpr size_t REMOTE_CONSOLE_RANDOM_NUMBER_LEN = 16;
+
+/**
+ * @enum RAKP Authentication Algorithms
+ *
+ * RMCP+ Authenticated Key-Exchange Protocol (RAKP)
+ *
+ * RAKP-None is not supported as per the following recommendation
+ * (https://www.us-cert.gov/ncas/alerts/TA13-207A)
+ * ("cipher 0" is an option enabled by default on many IPMI enabled devices that
+ * allows authentication to be bypassed. Disable "cipher 0" to prevent
+ * attackers from bypassing authentication and sending arbitrary IPMI commands.)
+ */
+enum class Algorithms : uint8_t
+{
+ RAKP_NONE = 0, // Mandatory (implemented, not supported)
+ RAKP_HMAC_SHA1, // Mandatory (implemented, default choice in ipmitool)
+ RAKP_HMAC_MD5, // Optional (not implemented)
+ RAKP_HMAC_SHA256, // Optional (implemented, best available)
+ // Reserved used to indicate an invalid authentication algorithm
+ RAKP_HMAC_INVALID = 0xB0
+};
+
+/**
+ * @class Interface
+ *
+ * Interface is the base class for the Authentication Algorithms.
+ * The Authentication Algorithm specifies the type of authentication “handshake”
+ * process that is used and identifies any particular variations of hashing or
+ * signature algorithm that is used as part of the process.
+ *
+ */
+class Interface
+{
+ public:
+ explicit Interface(integrity::Algorithms intAlgo,
+ crypt::Algorithms cryptAlgo) :
+ intAlgo(intAlgo), cryptAlgo(cryptAlgo)
+ {}
+
+ Interface() = delete;
+ virtual ~Interface() = default;
+ Interface(const Interface&) = default;
+ Interface& operator=(const Interface&) = default;
+ Interface(Interface&&) = default;
+ Interface& operator=(Interface&&) = default;
+
+ /**
+ * @brief Generate the Hash Message Authentication Code
+ *
+ * This API is invoked to generate the Key Exchange Authentication Code
+ * in the RAKP2 and RAKP4 sequence and for generating the Session
+ * Integrity Key.
+ *
+ * @param input message
+ *
+ * @return hash output
+ *
+ * @note The user key which is the secret key for the hash operation
+ * needs to be set before this operation.
+ */
+ std::vector<uint8_t> virtual generateHMAC(
+ const std::vector<uint8_t>& input) const = 0;
+
+ /**
+ * @brief Generate the Integrity Check Value
+ *
+ * This API is invoked in the RAKP4 sequence for generating the
+ * Integrity Check Value.
+ *
+ * @param input message
+ *
+ * @return hash output
+ *
+ * @note The session integrity key which is the secret key for the
+ * hash operation needs to be set before this operation.
+ */
+ std::vector<uint8_t> virtual generateICV(
+ const std::vector<uint8_t>& input) const = 0;
+
+ /**
+ * @brief Check if the Authentication algorithm is supported
+ *
+ * @param[in] algo - authentication algorithm
+ *
+ * @return true if algorithm is supported else false
+ *
+ */
+ static bool isAlgorithmSupported(Algorithms algo)
+ {
+ if (algo == Algorithms::RAKP_HMAC_SHA256)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // User Key is hardcoded to PASSW0RD till the IPMI User account
+ // management is in place.
+ std::array<uint8_t, USER_KEY_MAX_LENGTH> userKey = {"0penBmc"};
+
+ // Managed System Random Number
+ std::array<uint8_t, BMC_RANDOM_NUMBER_LEN> bmcRandomNum;
+
+ // Remote Console Random Number
+ std::array<uint8_t, REMOTE_CONSOLE_RANDOM_NUMBER_LEN> rcRandomNum;
+
+ // Session Integrity Key
+ std::vector<uint8_t> sessionIntegrityKey;
+
+ /**
+ * Integrity Algorithm is activated and set in the session data only
+ * once the session setup is succeeded in the RAKP34 command. But the
+ * integrity algorithm is negotiated in the Open Session Request command
+ * . So the integrity algorithm successfully negotiated is stored
+ * in the authentication algorithm's instance.
+ */
+ integrity::Algorithms intAlgo;
+
+ /**
+ * Confidentiality Algorithm is activated and set in the session data
+ * only once the session setup is succeeded in the RAKP34 command. But
+ * the confidentiality algorithm is negotiated in the Open Session
+ * Request command. So the confidentiality algorithm successfully
+ * negotiated is stored in the authentication algorithm's instance.
+ */
+ crypt::Algorithms cryptAlgo;
+};
+
+/**
+ * @class AlgoSHA1
+ *
+ * RAKP-HMAC-SHA1 specifies the use of RAKP messages for the key exchange
+ * portion of establishing the session, and that HMAC-SHA1 (per [RFC2104]) is
+ * used to create 20-byte Key Exchange Authentication Code fields in RAKP
+ * Message 2 and RAKP Message 3. HMAC-SHA1-96(per [RFC2404]) is used for
+ * generating a 12-byte Integrity Check Value field for RAKP Message 4.
+ */
+
+class AlgoSHA1 : public Interface
+{
+ public:
+ static constexpr size_t integrityCheckValueLength = 12;
+
+ explicit AlgoSHA1(integrity::Algorithms intAlgo,
+ crypt::Algorithms cryptAlgo) :
+ Interface(intAlgo, cryptAlgo)
+ {}
+
+ AlgoSHA1() = delete;
+ ~AlgoSHA1() = default;
+ AlgoSHA1(const AlgoSHA1&) = default;
+ AlgoSHA1& operator=(const AlgoSHA1&) = default;
+ AlgoSHA1(AlgoSHA1&&) = default;
+ AlgoSHA1& operator=(AlgoSHA1&&) = default;
+
+ std::vector<uint8_t> generateHMAC(
+ const std::vector<uint8_t>& input) const override;
+
+ std::vector<uint8_t> generateICV(
+ const std::vector<uint8_t>& input) const override;
+};
+
+/**
+ * @class AlgoSHA256
+ *
+ * RAKP-HMAC-SHA256 specifies the use of RAKP messages for the key exchange
+ * portion of establishing the session, and that HMAC-SHA256 (per [FIPS 180-2]
+ * and [RFC4634] and is used to create a 32-byte Key Exchange Authentication
+ * Code fields in RAKP Message 2 and RAKP Message 3. HMAC-SHA256-128 (per
+ * [RFC4868]) is used for generating a 16-byte Integrity Check Value field for
+ * RAKP Message 4.
+ */
+
+class AlgoSHA256 : public Interface
+{
+ public:
+ static constexpr size_t integrityCheckValueLength = 16;
+
+ explicit AlgoSHA256(integrity::Algorithms intAlgo,
+ crypt::Algorithms cryptAlgo) :
+ Interface(intAlgo, cryptAlgo)
+ {}
+
+ ~AlgoSHA256() = default;
+ AlgoSHA256(const AlgoSHA256&) = default;
+ AlgoSHA256& operator=(const AlgoSHA256&) = default;
+ AlgoSHA256(AlgoSHA256&&) = default;
+ AlgoSHA256& operator=(AlgoSHA256&&) = default;
+
+ std::vector<uint8_t> generateHMAC(
+ const std::vector<uint8_t>& input) const override;
+
+ std::vector<uint8_t> generateICV(
+ const std::vector<uint8_t>& input) const override;
+};
+
+} // namespace rakp_auth
+
+} // namespace cipher
diff --git a/transport/rmcpbridge/comm_module.cpp b/transport/rmcpbridge/comm_module.cpp
new file mode 100644
index 0000000..e34ab26
--- /dev/null
+++ b/transport/rmcpbridge/comm_module.cpp
@@ -0,0 +1,72 @@
+#include "comm_module.hpp"
+
+#include "command/channel_auth.hpp"
+#include "command/open_session.hpp"
+#include "command/rakp12.hpp"
+#include "command/rakp34.hpp"
+#include "command/session_cmds.hpp"
+#include "command_table.hpp"
+#include "session.hpp"
+
+#include <algorithm>
+#include <cstring>
+#include <iomanip>
+
+namespace command
+{
+
+void sessionSetupCommands()
+{
+ static const command::CmdDetails commands[] = {
+ // Open Session Request/Response
+ {{(static_cast<uint32_t>(message::PayloadType::OPEN_SESSION_REQUEST)
+ << 16)},
+ &openSession,
+ session::Privilege::HIGHEST_MATCHING,
+ true},
+ // RAKP1 & RAKP2 Message
+ {{(static_cast<uint32_t>(message::PayloadType::RAKP1) << 16)},
+ &RAKP12,
+ session::Privilege::HIGHEST_MATCHING,
+ true},
+ // RAKP3 & RAKP4 Message
+ {{(static_cast<uint32_t>(message::PayloadType::RAKP3) << 16)},
+ &RAKP34,
+ session::Privilege::HIGHEST_MATCHING,
+ true},
+ // Get Channel Authentication Capabilities Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(command::NetFns::APP) | 0x38},
+ &GetChannelCapabilities,
+ session::Privilege::HIGHEST_MATCHING,
+ true},
+ // Get Channel Cipher Suites Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(::command::NetFns::APP) | 0x54},
+ &getChannelCipherSuites,
+ session::Privilege::HIGHEST_MATCHING,
+ true},
+ // Set Session Privilege Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(command::NetFns::APP) | 0x3B},
+ &setSessionPrivilegeLevel,
+ session::Privilege::USER,
+ false},
+ // Close Session Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(command::NetFns::APP) | 0x3C},
+ &closeSession,
+ session::Privilege::CALLBACK,
+ false},
+ };
+
+ for (auto& iter : commands)
+ {
+ command::Table::get().registerCommand(
+ iter.command,
+ std::make_unique<command::NetIpmidEntry>(
+ iter.command, iter.functor, iter.privilege, iter.sessionless));
+ }
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/comm_module.hpp b/transport/rmcpbridge/comm_module.hpp
new file mode 100644
index 0000000..c67b413
--- /dev/null
+++ b/transport/rmcpbridge/comm_module.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <cstdint>
+
+namespace command
+{
+
+/**
+ * @brief RMCP+ and RAKP Message Status Codes
+ */
+enum class RAKP_ReturnCode : uint8_t
+{
+ NO_ERROR, //!< No errors
+ INSUFFICIENT_RESOURCE, //!< Insufficient resources to create a session
+ INVALID_SESSION_ID, //!< Invalid Session ID
+ INVALID_PAYLOAD_TYPE, //!< Invalid payload type
+ INVALID_AUTH_ALGO, //!< Invalid authentication algorithm
+ INVALID_INTEGRITY_ALGO, //!< Invalid integrity algorithm
+ NO_MATCH_AUTH_PAYLOAD, //!< No matching authentication payload
+ NO_MATCH_INTEGRITY_PAYLOAD, //!< No matching integrity payload
+ INACTIVE_SESSIONID, //!< Inactive Session ID
+ INACTIVE_ROLE, //!< Invalid role
+ UNAUTH_ROLE_PRIV, //!< Unauthorized role or privilege requested
+ INSUFFICIENT_RESOURCES_ROLE, //!< Insufficient resources to create a session
+ INVALID_NAME_LENGTH, //!< Invalid name length
+ UNAUTH_NAME, //!< Unauthorized name
+ UNAUTH_GUID, //!< Unauthorized GUID
+ INVALID_INTEGRITY_VALUE, //!< Invalid integrity check value
+ INVALID_CONF_ALGO, //!< Invalid confidentiality algorithm
+ NO_CIPHER_SUITE_MATCH, //!< No Cipher Suite match with security algos
+ ILLEGAL_PARAMETER, //!< Illegal or unrecognized parameter
+};
+
+/**
+ * @brief Register Session Setup commands to the Command Table
+ */
+void sessionSetupCommands();
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/channel_auth.cpp b/transport/rmcpbridge/command/channel_auth.cpp
new file mode 100644
index 0000000..ba101e4
--- /dev/null
+++ b/transport/rmcpbridge/command/channel_auth.cpp
@@ -0,0 +1,288 @@
+#include "channel_auth.hpp"
+
+#include <errno.h>
+#include <ipmid/api.h>
+
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <user_channel/channel_layer.hpp>
+#include <user_channel/user_layer.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <fstream>
+#include <set>
+#include <string>
+
+namespace command
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using Json = nlohmann::json;
+
+std::vector<uint8_t> GetChannelCapabilities(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ auto request =
+ reinterpret_cast<const GetChannelCapabilitiesReq*>(inPayload.data());
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+ constexpr unsigned int channelMask = 0x0f;
+ uint8_t chNum = ipmi::convertCurrentChannelNum(
+ request->channelNumber & channelMask, getInterfaceIndex());
+
+ if (!ipmi::isValidChannel(chNum) ||
+ (ipmi::EChannelSessSupported::none ==
+ ipmi::getChannelSessionSupport(chNum)) ||
+ !ipmi::isValidPrivLimit(request->reqMaxPrivLevel))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_INVALID_FIELD_REQUEST};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(GetChannelCapabilitiesResp));
+ auto response =
+ reinterpret_cast<GetChannelCapabilitiesResp*>(outPayload.data());
+
+ // A canned response, since there is no user and channel management.
+ response->completionCode = IPMI_CC_OK;
+
+ response->channelNumber = chNum;
+
+ response->ipmiVersion = 1; // IPMI v2.0 extended capabilities available.
+ response->reserved1 = 0;
+ response->oem = 0;
+ response->straightKey = 0;
+ response->reserved2 = 0;
+ response->md5 = 0;
+ response->md2 = 0;
+
+ response->reserved3 = 0;
+ response->KGStatus = 0; // KG is set to default
+ response->perMessageAuth = 0; // Per-message Authentication is enabled
+ response->userAuth = 0; // User Level Authentication is enabled
+ uint8_t maxChUsers = 0;
+ uint8_t enabledUsers = 0;
+ uint8_t fixedUsers = 0;
+ ipmi::ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
+
+ response->nonNullUsers = enabledUsers > 0 ? 1 : 0; // Non-null usernames
+ response->nullUsers = 0; // Null usernames disabled
+ response->anonymousLogin = 0; // Anonymous Login disabled
+
+ response->reserved4 = 0;
+ response->extCapabilities = 0x2; // Channel supports IPMI v2.0 connections
+
+ response->oemID[0] = 0;
+ response->oemID[1] = 0;
+ response->oemID[2] = 0;
+ response->oemAuxillary = 0;
+ return outPayload;
+}
+
+static constexpr const char* configFile =
+ "/usr/share/ipmi-providers/cipher_list.json";
+static constexpr const char* cipher = "cipher";
+static constexpr uint8_t stdCipherSuite = 0xC0;
+static constexpr uint8_t oemCipherSuite = 0xC1;
+static constexpr const char* oem = "oemiana";
+static constexpr const char* auth = "authentication";
+static constexpr const char* integrity = "integrity";
+static constexpr uint8_t integrityTag = 0x40;
+static constexpr const char* conf = "confidentiality";
+static constexpr uint8_t confTag = 0x80;
+
+/** @brief Get the supported Cipher records
+ *
+ * The cipher records are read from the JSON file and converted into
+ * 1. cipher suite record format mentioned in the IPMI specification. The
+ * records can be either OEM or standard cipher. Each json entry is parsed and
+ * converted into the cipher record format and pushed into the vector.
+ * 2. Algorithms listed in vector format
+ *
+ * @return pair of vector containing 1. all the cipher suite records. 2.
+ * Algorithms supported
+ *
+ */
+static std::pair<std::vector<uint8_t>, std::vector<uint8_t>> getCipherRecords()
+{
+ std::vector<uint8_t> cipherRecords;
+ std::vector<uint8_t> supportedAlgorithmRecords;
+ // create set to get the unique supported algorithms
+ std::set<uint8_t> supportedAlgorithmSet;
+
+ std::ifstream jsonFile(configFile);
+ if (!jsonFile.is_open())
+ {
+ lg2::error("Channel Cipher suites file not found: {ERROR}", "ERROR",
+ strerror(errno));
+ elog<InternalFailure>();
+ }
+
+ auto data = Json::parse(jsonFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ lg2::error("Parsing channel cipher suites JSON failed: {ERROR}",
+ "ERROR", strerror(errno));
+ elog<InternalFailure>();
+ }
+
+ for (const auto& record : data)
+ {
+ if (record.find(oem) != record.end())
+ {
+ // OEM cipher suite - 0xC1
+ cipherRecords.push_back(oemCipherSuite);
+ // Cipher Suite ID
+ cipherRecords.push_back(record.value(cipher, 0));
+ // OEM IANA - 3 bytes
+ cipherRecords.push_back(record.value(oem, 0));
+ cipherRecords.push_back(record.value(oem, 0) >> 8);
+ cipherRecords.push_back(record.value(oem, 0) >> 16);
+ }
+ else
+ {
+ // Standard cipher suite - 0xC0
+ cipherRecords.push_back(stdCipherSuite);
+ // Cipher Suite ID
+ cipherRecords.push_back(record.value(cipher, 0));
+ }
+
+ // Authentication algorithm number
+ cipherRecords.push_back(record.value(auth, 0));
+ supportedAlgorithmSet.insert(record.value(auth, 0));
+
+ // Integrity algorithm number
+ cipherRecords.push_back(record.value(integrity, 0) | integrityTag);
+ supportedAlgorithmSet.insert(record.value(integrity, 0) | integrityTag);
+
+ // Confidentiality algorithm number
+ cipherRecords.push_back(record.value(conf, 0) | confTag);
+ supportedAlgorithmSet.insert(record.value(conf, 0) | confTag);
+ }
+
+ // copy the set to supportedAlgorithmRecord which is vector based.
+ std::copy(supportedAlgorithmSet.begin(), supportedAlgorithmSet.end(),
+ std::back_inserter(supportedAlgorithmRecords));
+
+ return std::make_pair(cipherRecords, supportedAlgorithmRecords);
+}
+
+/** @brief this command is used to look up what authentication, integrity,
+ * confidentiality algorithms are supported.
+ *
+ * @ param inPayload - vector of input data
+ * @ param handler - pointer to handler
+ *
+ * @returns ipmi completion code plus response data
+ * - vector of response data: cc, channel, record data
+ **/
+std::vector<uint8_t> getChannelCipherSuites(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ const auto errorResponse = [](uint8_t cc) {
+ std::vector<uint8_t> rsp(1);
+ rsp[0] = cc;
+ return rsp;
+ };
+
+ static constexpr size_t getChannelCipherSuitesReqLen = 3;
+ if (inPayload.size() != getChannelCipherSuitesReqLen)
+ {
+ return errorResponse(IPMI_CC_REQ_DATA_LEN_INVALID);
+ }
+
+ static constexpr uint8_t channelMask = 0x0f;
+ uint8_t channelNumber = inPayload[0] & channelMask;
+ if (channelNumber != inPayload[0])
+ {
+ return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST);
+ }
+ static constexpr uint8_t payloadMask = 0x3f;
+ uint8_t payloadType = inPayload[1] & payloadMask;
+ if (payloadType != inPayload[1])
+ {
+ return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST);
+ }
+ static constexpr uint8_t indexMask = 0x3f;
+ uint8_t listIndex = inPayload[2] & indexMask;
+ static constexpr uint8_t algoSelectShift = 7;
+ uint8_t algoSelectBit = inPayload[2] >> algoSelectShift;
+ if ((listIndex | (algoSelectBit << algoSelectShift)) != inPayload[2])
+ {
+ return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST);
+ }
+
+ static std::vector<uint8_t> cipherRecords;
+ static std::vector<uint8_t> supportedAlgorithms;
+ static bool recordInit = false;
+
+ uint8_t rspChannel =
+ ipmi::convertCurrentChannelNum(channelNumber, getInterfaceIndex());
+
+ if (!ipmi::isValidChannel(rspChannel))
+ {
+ return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST);
+ }
+ if (!ipmi::isValidPayloadType(static_cast<ipmi::PayloadType>(payloadType)))
+ {
+ lg2::debug("Get channel cipher suites - Invalid payload type: {ERROR}",
+ "ERROR", strerror(errno));
+ constexpr uint8_t ccPayloadTypeNotSupported = 0x80;
+ return errorResponse(ccPayloadTypeNotSupported);
+ }
+
+ if (!recordInit)
+ {
+ try
+ {
+ std::tie(cipherRecords, supportedAlgorithms) = getCipherRecords();
+ recordInit = true;
+ }
+ catch (const std::exception& e)
+ {
+ return errorResponse(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+ }
+
+ const std::vector<uint8_t>& records =
+ algoSelectBit ? cipherRecords : supportedAlgorithms;
+ static constexpr auto respSize = 16;
+
+ // Session support is available in active LAN channels.
+ if ((ipmi::getChannelSessionSupport(rspChannel) ==
+ ipmi::EChannelSessSupported::none) ||
+ !(ipmi::doesDeviceExist(rspChannel)))
+ {
+ lg2::debug("Get channel cipher suites - Device does not exist:{ERROR}",
+ "ERROR", strerror(errno));
+ return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST);
+ }
+
+ // List index(00h-3Fh), 0h selects the first set of 16, 1h selects the next
+ // set of 16 and so on.
+
+ // Calculate the number of record data bytes to be returned.
+ auto start =
+ std::min(static_cast<size_t>(listIndex) * respSize, records.size());
+ auto end = std::min((static_cast<size_t>(listIndex) * respSize) + respSize,
+ records.size());
+ auto size = end - start;
+
+ std::vector<uint8_t> rsp;
+ rsp.push_back(IPMI_CC_OK);
+ rsp.push_back(rspChannel);
+ std::copy_n(records.data() + start, size, std::back_inserter(rsp));
+
+ return rsp;
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/channel_auth.hpp b/transport/rmcpbridge/command/channel_auth.hpp
new file mode 100644
index 0000000..059d0c7
--- /dev/null
+++ b/transport/rmcpbridge/command/channel_auth.hpp
@@ -0,0 +1,143 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <vector>
+
+namespace command
+{
+
+/**
+ * @struct GetChannelCapabilitiesReq
+ *
+ * IPMI Request data for Get Channel Authentication Capabilities command
+ */
+struct GetChannelCapabilitiesReq
+{
+ uint8_t channelNumber;
+ uint8_t reqMaxPrivLevel;
+} __attribute__((packed));
+
+/**
+ * @struct GetChannelCapabilitiesResp
+ *
+ * IPMI Response data for Get Channel Authentication Capabilities command
+ */
+struct GetChannelCapabilitiesResp
+{
+ uint8_t completionCode; // Completion Code
+
+ uint8_t channelNumber; // Channel number that the request was
+ // received on
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t none:1;
+ uint8_t md2:1;
+ uint8_t md5:1;
+ uint8_t reserved2:1;
+ uint8_t straightKey:1; // Straight password/key support
+ // Support OEM identified by the IANA OEM ID in RMCP+ ping response
+ uint8_t oem:1;
+ uint8_t reserved1:1;
+ uint8_t ipmiVersion:1; // 0b = IPMIV1.5 support only, 1B = IPMI V2.0
+ // support
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t ipmiVersion:1; // 0b = IPMIV1.5 support only, 1B = IPMI V2.0
+ // support
+ uint8_t reserved1:1;
+ // Support OEM identified by the IANA OEM ID in RMCP+ ping response
+ uint8_t oem:1;
+ uint8_t straightKey:1; // Straight password/key support
+ uint8_t reserved2:1;
+ uint8_t md5:1;
+ uint8_t md2:1;
+ uint8_t none:1;
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ // Anonymous login status for anonymous login enabled/disabled
+ uint8_t anonymousLogin:1;
+ // Anonymous login status for null usernames enabled/disabled
+ uint8_t nullUsers:1;
+ // Anonymous login status for non-null usernames enabled/disabled
+ uint8_t nonNullUsers:1;
+ uint8_t userAuth:1; // User level authentication status
+ uint8_t perMessageAuth:1; // Per-message authentication support
+ // Two key login status . only for IPMI V2.0 RMCP+ RAKP
+ uint8_t KGStatus:1;
+ uint8_t reserved3:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved3:2;
+ // Two key login status . only for IPMI V2.0 RMCP+ RAKP
+ uint8_t KGStatus:1;
+ uint8_t perMessageAuth:1; // Per-message authentication support
+ uint8_t userAuth:1; // User level authentication status
+ // Anonymous login status for non-null usernames enabled/disabled
+ uint8_t nonNullUsers:1;
+ // Anonymous login status for null usernames enabled/disabled
+ uint8_t nullUsers:1;
+ // Anonymous login status for anonymous login enabled/disabled
+ uint8_t anonymousLogin:1;
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ // Extended capabilities will be present only if IPMI version is V2.0
+ uint8_t extCapabilities:2; // Channel support for IPMI V2.0 connections
+ uint8_t reserved4:6;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ // Extended capabilities will be present only if IPMI version is V2.0
+ uint8_t reserved4:6;
+ uint8_t extCapabilities:2; // Channel support for IPMI V2.0 connections
+#endif
+
+ // Below 4 bytes will all the 0's if no OEM authentication type available.
+ uint8_t oemID[3]; // IANA enterprise number for OEM/organization
+ uint8_t oemAuxillary; // Addition OEM specific information..
+} __attribute__((packed));
+
+/**
+ * @brief Get Channel Authentication Capabilities
+ *
+ * This message exchange provides a way for a remote console to discover what
+ * IPMI version is supported i.e. whether or not the BMC supports the IPMI
+ * v2.0 / RMCP+ packet format. It also provides information that the remote
+ * console can use to determine whether anonymous, “one-key”, or “two-key”
+ * logins are used.This information can guide a remote console in how it
+ * presents queries to users for username and password information. This is a
+ * ‘session-less’ command that the BMC accepts in both IPMI v1.5 and v2.0/RMCP+
+ * packet formats.
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> GetChannelCapabilities(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+/**
+ * @brief Get Channel Cipher Suites
+ *
+ * This command is used to look up what authentication, integrity, and
+ * confidentiality algorithms are supported. The algorithms are used in
+ * combination as ‘Cipher Suites’. This command only applies to implementations
+ * that support IPMI v2.0/RMCP+ sessions. This command can be executed prior to
+ * establishing a session with the BMC.
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> getChannelCipherSuites(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/guid.cpp b/transport/rmcpbridge/command/guid.cpp
new file mode 100644
index 0000000..485d6b6
--- /dev/null
+++ b/transport/rmcpbridge/command/guid.cpp
@@ -0,0 +1,153 @@
+#include "guid.hpp"
+
+#include <ipmid/api.h>
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <sstream>
+#include <string>
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+static std::optional<command::Guid> guid;
+
+namespace command
+{
+
+std::unique_ptr<sdbusplus::bus::match_t> matchPtr(nullptr);
+
+static constexpr auto propInterface = "xyz.openbmc_project.Common.UUID";
+static constexpr auto uuidProperty = "UUID";
+static constexpr auto subtreePath = "/xyz/openbmc_project/inventory";
+
+static void rfcToGuid(std::string rfc4122, Guid& uuid)
+{
+ using Argument = xyz::openbmc_project::Common::InvalidArgument;
+ // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
+ // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte
+ // order
+ // Ex: 0x2332fc2c40e66298e511f2782395a361
+ constexpr size_t uuidHexLength = (2 * BMC_GUID_LEN);
+ constexpr size_t uuidRfc4122Length = (uuidHexLength + 4);
+
+ if (rfc4122.size() == uuidRfc4122Length)
+ {
+ rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'),
+ rfc4122.end());
+ }
+ if (rfc4122.size() != uuidHexLength)
+ {
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+ Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+ }
+ for (size_t ind = 0; ind < uuidHexLength; ind += 2)
+ {
+ long b;
+ try
+ {
+ b = std::stoul(rfc4122.substr(ind, 2), nullptr, 16);
+ }
+ catch (const std::exception& e)
+ {
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+ Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+ }
+
+ uuid[BMC_GUID_LEN - (ind / 2) - 1] = static_cast<uint8_t>(b);
+ }
+ return;
+}
+
+// Canned System GUID for when the Chassis DBUS object is not populated
+static constexpr Guid fakeGuid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
+ 0x0D, 0x0E, 0x0F, 0x10};
+const Guid& getSystemGUID()
+{
+ if (guid.has_value())
+ {
+ return guid.value();
+ }
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ ipmi::Value propValue;
+ try
+ {
+ const auto& [objPath, service] =
+ ipmi::getDbusObject(bus, propInterface, subtreePath);
+ // Read UUID property value from bmcObject
+ // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
+ propValue = ipmi::getDbusProperty(bus, service, objPath, propInterface,
+ uuidProperty);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed in reading BMC UUID property: {ERROR}", "ERROR", e);
+ return fakeGuid;
+ }
+
+ std::string rfc4122Uuid = std::get<std::string>(propValue);
+ try
+ {
+ // convert to IPMI format
+ Guid tmpGuid{};
+ rfcToGuid(rfc4122Uuid, tmpGuid);
+ guid = tmpGuid;
+ }
+ catch (const InvalidArgument& e)
+ {
+ lg2::error("Failed in parsing BMC UUID property: {VALUE}", "VALUE",
+ rfc4122Uuid.c_str());
+ return fakeGuid;
+ }
+ return guid.value();
+}
+
+void registerGUIDChangeCallback()
+{
+ if (matchPtr == nullptr)
+ {
+ using namespace sdbusplus::bus::match::rules;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ try
+ {
+ matchPtr = std::make_unique<sdbusplus::bus::match_t>(
+ bus, propertiesChangedNamespace(subtreePath, propInterface),
+ [](sdbusplus::message_t& m) {
+ try
+ {
+ std::string iface{};
+ std::map<std::string, ipmi::Value> pdict{};
+ m.read(iface, pdict);
+ if (iface != propInterface)
+ {
+ return;
+ }
+ auto guidStr = std::get<std::string>(pdict.at("UUID"));
+ Guid tmpGuid{};
+ rfcToGuid(guidStr, tmpGuid);
+ guid = tmpGuid;
+ }
+ catch (const std::exception& e)
+ {
+ // signal contained invalid guid; ignore it
+ lg2::error(
+ "Failed to parse propertiesChanged signal: {ERROR}",
+ "ERROR", e);
+ }
+ });
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to create dbus match: {ERROR}", "ERROR", e);
+ }
+ }
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/guid.hpp b/transport/rmcpbridge/command/guid.hpp
new file mode 100644
index 0000000..3bd8597
--- /dev/null
+++ b/transport/rmcpbridge/command/guid.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "comm_module.hpp"
+
+#include <sdbusplus/bus/match.hpp>
+
+#include <cstddef>
+#include <vector>
+
+namespace command
+{
+
+constexpr size_t BMC_GUID_LEN = 16;
+
+using Guid = std::array<uint8_t, BMC_GUID_LEN>;
+
+/**
+ * @brief Get System GUID
+ *
+ * @return If UUID is successfully read from the Chassis DBUS object, then the
+ * GUID is returned, else a canned GUID is returned
+ */
+const Guid& getSystemGUID();
+
+/**
+ * @brief Register the callback to update the cache when the GUID changes
+ */
+void registerGUIDChangeCallback();
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/open_session.cpp b/transport/rmcpbridge/command/open_session.cpp
new file mode 100644
index 0000000..9d5fc49
--- /dev/null
+++ b/transport/rmcpbridge/command/open_session.cpp
@@ -0,0 +1,118 @@
+#include "open_session.hpp"
+
+#include "comm_module.hpp"
+#include "endian.hpp"
+#include "sessions_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace command
+{
+
+std::vector<uint8_t> openSession(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ auto request =
+ reinterpret_cast<const OpenSessionRequest*>(inPayload.data());
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(OpenSessionResponse));
+ auto response = reinterpret_cast<OpenSessionResponse*>(outPayload.data());
+
+ // Per the IPMI Spec, messageTag and remoteConsoleSessionID are always
+ // returned
+ response->messageTag = request->messageTag;
+ response->remoteConsoleSessionID = request->remoteConsoleSessionID;
+
+ // Check for valid Authentication Algorithms
+ if (!cipher::rakp_auth::Interface::isAlgorithmSupported(
+ static_cast<cipher::rakp_auth::Algorithms>(request->authAlgo)))
+ {
+ response->status_code =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_AUTH_ALGO);
+ return outPayload;
+ }
+
+ // Check for valid Integrity Algorithms
+ if (!cipher::integrity::Interface::isAlgorithmSupported(
+ static_cast<cipher::integrity::Algorithms>(request->intAlgo)))
+ {
+ response->status_code =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_INTEGRITY_ALGO);
+ return outPayload;
+ }
+
+ session::Privilege priv;
+
+ // 0h in the requested maximum privilege role field indicates highest level
+ // matching proposed algorithms. The maximum privilege level the session
+ // can take is set to Administrator level. In the RAKP12 command sequence
+ // the session maximum privilege role is set again based on the user's
+ // permitted privilege level.
+ if (!request->maxPrivLevel)
+ {
+ priv = session::Privilege::ADMIN;
+ }
+ else
+ {
+ priv = static_cast<session::Privilege>(request->maxPrivLevel);
+ }
+
+ // Check for valid Confidentiality Algorithms
+ if (!cipher::crypt::Interface::isAlgorithmSupported(
+ static_cast<cipher::crypt::Algorithms>(request->confAlgo)))
+ {
+ response->status_code =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_CONF_ALGO);
+ return outPayload;
+ }
+
+ std::shared_ptr<session::Session> session;
+ try
+ {
+ // Start an IPMI session
+ session = session::Manager::get().startSession(
+ endian::from_ipmi<>(request->remoteConsoleSessionID), priv,
+ static_cast<cipher::rakp_auth::Algorithms>(request->authAlgo),
+ static_cast<cipher::integrity::Algorithms>(request->intAlgo),
+ static_cast<cipher::crypt::Algorithms>(request->confAlgo));
+ }
+ catch (const std::exception& e)
+ {
+ response->status_code =
+ static_cast<uint8_t>(RAKP_ReturnCode::INSUFFICIENT_RESOURCE);
+ lg2::error("openSession : Problem opening a session: {ERROR}", "ERROR",
+ e);
+ return outPayload;
+ }
+
+ response->status_code = static_cast<uint8_t>(RAKP_ReturnCode::NO_ERROR);
+ response->maxPrivLevel = static_cast<uint8_t>(session->reqMaxPrivLevel);
+ response->managedSystemSessionID =
+ endian::to_ipmi<>(session->getBMCSessionID());
+
+ response->authPayload = request->authPayload;
+ response->authPayloadLen = request->authPayloadLen;
+ response->authAlgo = request->authAlgo;
+
+ response->intPayload = request->intPayload;
+ response->intPayloadLen = request->intPayloadLen;
+ response->intAlgo = request->intAlgo;
+
+ response->confPayload = request->confPayload;
+ response->confPayloadLen = request->confPayloadLen;
+ response->confAlgo = request->confAlgo;
+
+ session->updateLastTransactionTime();
+
+ // Session state is Setup in progress
+ session->state(static_cast<uint8_t>(session::State::setupInProgress));
+ return outPayload;
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/open_session.hpp b/transport/rmcpbridge/command/open_session.hpp
new file mode 100644
index 0000000..2e17bf3
--- /dev/null
+++ b/transport/rmcpbridge/command/open_session.hpp
@@ -0,0 +1,180 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <vector>
+
+namespace command
+{
+
+/**
+ * @struct OpenSessionRequest
+ *
+ * IPMI Payload for RMCP+ Open Session Request
+ */
+struct OpenSessionRequest
+{
+ uint8_t messageTag; // Message tag from request buffer
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t maxPrivLevel:4; // Requested maximum privilege level
+ uint8_t reserved1:4; // Reserved for future definition
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved1:4; // Reserved for future definition
+ uint8_t maxPrivLevel:4; // Requested maximum privilege level
+
+#endif
+
+ uint16_t reserved2;
+ uint32_t remoteConsoleSessionID;
+
+ uint8_t authPayload;
+ uint16_t reserved3;
+ uint8_t authPayloadLen;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t authAlgo:6;
+ uint8_t reserved4:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved4:2;
+ uint8_t authAlgo:6;
+#endif
+
+ uint8_t reserved5;
+ uint16_t reserved6;
+
+ uint8_t intPayload;
+ uint16_t reserved7;
+ uint8_t intPayloadLen;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t intAlgo:6;
+ uint8_t reserved8:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved8:2;
+ uint8_t intAlgo:6;
+#endif
+
+ uint8_t reserved9;
+ uint16_t reserved10;
+
+ uint8_t confPayload;
+ uint16_t reserved11;
+ uint8_t confPayloadLen;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t confAlgo:6;
+ uint8_t reserved12:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved12:2;
+ uint8_t confAlgo:6;
+#endif
+
+ uint8_t reserved13;
+ uint16_t reserved14;
+} __attribute__((packed));
+
+/**
+ * @struct OpenSessionResponse
+ *
+ * IPMI Payload for RMCP+ Open Session Response
+ */
+struct OpenSessionResponse
+{
+ uint8_t messageTag;
+ uint8_t status_code;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t maxPrivLevel:4;
+ uint8_t reserved1:4;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved1:4;
+ uint8_t maxPrivLevel:4;
+#endif
+
+ uint8_t reserved2;
+ uint32_t remoteConsoleSessionID;
+ uint32_t managedSystemSessionID;
+
+ uint8_t authPayload;
+ uint16_t reserved3;
+ uint8_t authPayloadLen;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t authAlgo:6;
+ uint8_t reserved4:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved4:2;
+ uint8_t authAlgo:6;
+#endif
+
+ uint8_t reserved5;
+ uint16_t reserved6;
+
+ uint8_t intPayload;
+ uint16_t reserved7;
+ uint8_t intPayloadLen;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t intAlgo:6;
+ uint8_t reserved8:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved8:2;
+ uint8_t intAlgo:6;
+
+#endif
+
+ uint8_t reserved9;
+ uint16_t reserved10;
+
+ uint8_t confPayload;
+ uint16_t reserved11;
+ uint8_t confPayloadLen;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t confAlgo:6;
+ uint8_t reserved12:2;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved12:2;
+ uint8_t confAlgo:6;
+#endif
+
+ uint8_t reserved13;
+ uint16_t reserved14;
+} __attribute__((packed));
+
+/**
+ * @brief RMCP+ Open Session Request, RMCP+ Open Session Response
+ *
+ * The RMCP+ Open Session request and response messages are used to enable a
+ * remote console to discover what Cipher Suite(s) can be used for establishing
+ * a session at a requested maximum privilege level. These messages are also
+ * used for transferring the sessions IDs that the remote console and BMC wish
+ * to for the session once it’s been activated, and to track each party during
+ * the exchange of messages used for establishing the session.
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> openSession(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/payload_cmds.cpp b/transport/rmcpbridge/command/payload_cmds.cpp
new file mode 100644
index 0000000..ea517e8
--- /dev/null
+++ b/transport/rmcpbridge/command/payload_cmds.cpp
@@ -0,0 +1,283 @@
+#include "payload_cmds.hpp"
+
+#include "sessions_manager.hpp"
+#include "sol/sol_manager.hpp"
+#include "sol_cmds.hpp"
+
+#include <ipmid/api.h>
+
+#include <ipmid/api-types.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+namespace sol
+{
+
+namespace command
+{
+
+std::vector<uint8_t> activatePayload(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler)
+{
+ auto request =
+ reinterpret_cast<const ActivatePayloadRequest*>(inPayload.data());
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(ActivatePayloadResponse));
+ auto response =
+ reinterpret_cast<ActivatePayloadResponse*>(outPayload.data());
+
+ response->completionCode = IPMI_CC_OK;
+
+ // SOL is the payload currently supported for activation.
+ if (static_cast<uint8_t>(message::PayloadType::SOL) != request->payloadType)
+ {
+ response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST;
+ return outPayload;
+ }
+
+ sol::Manager::get().updateSOLParameter(ipmi::convertCurrentChannelNum(
+ ipmi::currentChNum, getInterfaceIndex()));
+ if (!sol::Manager::get().enable)
+ {
+ response->completionCode = IPMI_CC_PAYLOAD_TYPE_DISABLED;
+ return outPayload;
+ }
+
+ // Only one instance of SOL is currently supported.
+ if (request->payloadInstance != 1)
+ {
+ response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST;
+ return outPayload;
+ }
+
+ auto session = session::Manager::get().getSession(handler->sessionID);
+
+ if (!request->encryption && session->isCryptAlgoEnabled())
+ {
+ response->completionCode = IPMI_CC_PAYLOAD_WITHOUT_ENCRYPTION;
+ return outPayload;
+ }
+
+ if (session->currentPrivilege() <
+ static_cast<uint8_t>(sol::Manager::get().solMinPrivilege))
+ {
+ response->completionCode = IPMI_CC_INSUFFICIENT_PRIVILEGE;
+ return outPayload;
+ }
+
+ // Is SOL Payload enabled for this user & channel.
+ auto userId = ipmi::ipmiUserGetUserId(session->userName);
+ ipmi::PayloadAccess payloadAccess = {};
+ if ((ipmi::ipmiUserGetUserPayloadAccess(session->channelNum(), userId,
+ payloadAccess) != IPMI_CC_OK) ||
+ !(payloadAccess.stdPayloadEnables1[static_cast<uint8_t>(
+ message::PayloadType::SOL)]))
+ {
+ response->completionCode = IPMI_CC_PAYLOAD_TYPE_DISABLED;
+ return outPayload;
+ }
+
+ auto status = sol::Manager::get().isPayloadActive(request->payloadInstance);
+ if (status)
+ {
+ response->completionCode = IPMI_CC_PAYLOAD_ALREADY_ACTIVE;
+ return outPayload;
+ }
+
+ // Set the current command's socket channel to the session
+ handler->setChannelInSession();
+
+ // Start the SOL payload
+ try
+ {
+ sol::Manager::get().startPayloadInstance(request->payloadInstance,
+ handler->sessionID);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to start SOL payload: {ERROR}", "ERROR", e);
+ response->completionCode = IPMI_CC_UNSPECIFIED_ERROR;
+ return outPayload;
+ }
+
+ response->inPayloadSize = endian::to_ipmi<uint16_t>(MAX_PAYLOAD_SIZE);
+ response->outPayloadSize = endian::to_ipmi<uint16_t>(MAX_PAYLOAD_SIZE);
+ response->portNum = endian::to_ipmi<uint16_t>(IPMI_STD_PORT);
+
+ // VLAN addressing is not used
+ response->vlanNum = 0xFFFF;
+
+ return outPayload;
+}
+
+std::vector<uint8_t> deactivatePayload(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler)
+{
+ auto request =
+ reinterpret_cast<const DeactivatePayloadRequest*>(inPayload.data());
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(DeactivatePayloadResponse));
+ auto response =
+ reinterpret_cast<DeactivatePayloadResponse*>(outPayload.data());
+ response->completionCode = IPMI_CC_OK;
+
+ // SOL is the payload currently supported for deactivation
+ if (static_cast<uint8_t>(message::PayloadType::SOL) != request->payloadType)
+ {
+ response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST;
+ return outPayload;
+ }
+
+ // Only one instance of SOL is supported
+ if (request->payloadInstance != 1)
+ {
+ response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST;
+ return outPayload;
+ }
+
+ auto status = sol::Manager::get().isPayloadActive(request->payloadInstance);
+ if (!status)
+ {
+ response->completionCode = IPMI_CC_PAYLOAD_DEACTIVATED;
+ return outPayload;
+ }
+
+ auto currentSession =
+ session::Manager::get().getSession(handler->sessionID);
+ auto solSessionID =
+ sol::Manager::get().getContext(request->payloadInstance).sessionID;
+ auto solActiveSession =
+ sol::Manager::get().getContext(request->payloadInstance).session;
+ // The session owner or the ADMIN could deactivate the session
+ if (currentSession->userName != solActiveSession->userName &&
+ currentSession->currentPrivilege() !=
+ static_cast<uint8_t>(session::Privilege::ADMIN))
+ {
+ response->completionCode = IPMI_CC_INSUFFICIENT_PRIVILEGE;
+ return outPayload;
+ }
+
+ try
+ {
+ sol::Manager::get().stopPayloadInstance(request->payloadInstance);
+
+ try
+ {
+ activating(request->payloadInstance, solSessionID);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info("Failed to call the activating method: {ERROR}", "ERROR",
+ e);
+ /*
+ * In case session has been closed (like in the case of inactivity
+ * timeout), then activating function would throw an exception,
+ * since solSessionID is not found. As session is already closed,
+ * returning IPMI status code for Payload already deactivated
+ * as BMC automatically deactivates all active payloads when
+ * session is terminated.
+ */
+ response->completionCode = IPMI_CC_PAYLOAD_DEACTIVATED;
+ return outPayload;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to call the getContext method: {ERROR}", "ERROR", e);
+ response->completionCode = IPMI_CC_UNSPECIFIED_ERROR;
+ return outPayload;
+ }
+
+ return outPayload;
+}
+
+std::vector<uint8_t> getPayloadStatus(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ auto request =
+ reinterpret_cast<const GetPayloadStatusRequest*>(inPayload.data());
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(GetPayloadStatusResponse));
+ auto response =
+ reinterpret_cast<GetPayloadStatusResponse*>(outPayload.data());
+
+ // SOL is the payload currently supported for payload status
+ if (static_cast<uint8_t>(message::PayloadType::SOL) != request->payloadType)
+ {
+ response->completionCode = IPMI_CC_UNSPECIFIED_ERROR;
+ return outPayload;
+ }
+
+ response->completionCode = IPMI_CC_OK;
+
+ constexpr size_t maxSolPayloadInstances = 1;
+ response->capacity = maxSolPayloadInstances;
+
+ // Currently we support only one SOL session
+ response->instance1 = sol::Manager::get().isPayloadActive(1);
+
+ return outPayload;
+}
+
+std::vector<uint8_t> getPayloadInfo(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ auto request =
+ reinterpret_cast<const GetPayloadInfoRequest*>(inPayload.data());
+
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(GetPayloadInfoResponse));
+ auto response =
+ reinterpret_cast<GetPayloadInfoResponse*>(outPayload.data());
+
+ // SOL is the payload currently supported for payload status & only one
+ // instance of SOL is supported.
+ if (static_cast<uint8_t>(message::PayloadType::SOL) !=
+ request->payloadType ||
+ request->payloadInstance != 1)
+ {
+ response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST;
+ return outPayload;
+ }
+ auto status = sol::Manager::get().isPayloadActive(request->payloadInstance);
+
+ if (status)
+ {
+ auto& context =
+ sol::Manager::get().getContext(request->payloadInstance);
+ response->sessionID = context.sessionID;
+ }
+ else
+ {
+ // No active payload - return session id as 0
+ response->sessionID = 0;
+ }
+ response->completionCode = IPMI_CC_OK;
+ return outPayload;
+}
+
+} // namespace command
+
+} // namespace sol
diff --git a/transport/rmcpbridge/command/payload_cmds.hpp b/transport/rmcpbridge/command/payload_cmds.hpp
new file mode 100644
index 0000000..37e071f
--- /dev/null
+++ b/transport/rmcpbridge/command/payload_cmds.hpp
@@ -0,0 +1,296 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <vector>
+
+namespace sol
+{
+
+namespace command
+{
+
+constexpr uint8_t IPMI_CC_PAYLOAD_ALREADY_ACTIVE = 0x80;
+constexpr uint8_t IPMI_CC_PAYLOAD_TYPE_DISABLED = 0x81;
+constexpr uint8_t IPMI_CC_PAYLOAD_ACTIVATION_LIMIT = 0x82;
+constexpr uint8_t IPMI_CC_PAYLOAD_WITH_ENCRYPTION = 0x83;
+constexpr uint8_t IPMI_CC_PAYLOAD_WITHOUT_ENCRYPTION = 0x84;
+
+/** @struct ActivatePayloadRequest
+ *
+ * IPMI payload for Activate Payload command request.
+ */
+struct ActivatePayloadRequest
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t payloadType:6; //!< Payload type.
+ uint8_t reserved1:2; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved1:2; //!< Payload type.
+ uint8_t payloadType:6; //!< Payload type.
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t payloadInstance:4; //!< Payload instance.
+ uint8_t reserved2:4; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved2:4; //!< Reserved.
+ uint8_t payloadInstance:4; //!< Payload instance.
+#endif
+
+ /** @brief The following Auxiliary Request Data applies only for payload
+ * SOL only.
+ */
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t reserved4:1; //!< Reserved.
+ uint8_t handshake:1; //!< SOL startup handshake.
+ uint8_t alert:2; //!< Shared serial alert behavior.
+ uint8_t reserved3:1; //!< Reserved.
+ uint8_t testMode:1; //!< Test mode.
+ uint8_t auth:1; //!< If true, activate payload with authentication.
+ uint8_t encryption:1; //!< If true, activate payload with encryption.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t encryption:1; //!< If true, activate payload with encryption.
+ uint8_t auth:1; //!< If true, activate payload with authentication.
+ uint8_t testMode:1; //!< Test mode.
+ uint8_t reserved3:1; //!< Reserved.
+ uint8_t alert:2; //!< Shared serial alert behavior.
+ uint8_t handshake:1; //!< SOL startup handshake.
+ uint8_t reserved4:1; //!< Reserved.
+#endif
+
+ uint8_t reserved5; //!< Reserved.
+ uint8_t reserved6; //!< Reserved.
+ uint8_t reserved7; //!< Reserved.
+} __attribute__((packed));
+
+/** @struct ActivatePayloadResponse
+ *
+ * IPMI payload for Activate Payload command response.
+ */
+struct ActivatePayloadResponse
+{
+ uint8_t completionCode; //!< Completion code.
+ uint8_t reserved1; //!< Reserved.
+ uint8_t reserved2; //!< Reserved.
+ uint8_t reserved3; //!< Reserved.
+
+ // Test Mode
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t testMode:1; //!< Test mode.
+ uint8_t reserved4:7; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved4:7; //!< Reserved.
+ uint8_t testMode:1; //!< Test mode.
+#endif
+
+ uint16_t inPayloadSize; //!< Inbound payload size
+ uint16_t outPayloadSize; //!< Outbound payload size.
+ uint16_t portNum; //!< Payload UDP port number.
+ uint16_t vlanNum; //!< Payload VLAN number.
+} __attribute__((packed));
+
+/** @brief Activate Payload Command.
+ *
+ * This command is used for activating and deactivating a payload type under a
+ * given IPMI session. The UDP Port number for SOL is the same as the port that
+ * was used to establish the IPMI session.
+ *
+ * @param[in] inPayload - Request data for the command.
+ * @param[in] handler - Reference to the message handler.
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> activatePayload(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+constexpr uint8_t IPMI_CC_PAYLOAD_DEACTIVATED = 0x80;
+
+/** @struct DeactivatePayloadRequest
+ *
+ * IPMI payload for Deactivate Payload command request.
+ */
+struct DeactivatePayloadRequest
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t payloadType:6; //!< Payload type.
+ uint8_t reserved1:2; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved1:2; //!< Payload type.
+ uint8_t payloadType:6; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t payloadInstance:4; //!< Payload instance.
+ uint8_t reserved2:4; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved2:4; //!< Reserved.
+ uint8_t payloadInstance:4; //!< Payload instance.
+#endif
+
+ /** @brief No auxiliary data for payload type SOL */
+ uint8_t auxData1; //!< Auxiliary data 1
+ uint8_t auxData2; //!< Auxiliary data 2
+ uint8_t auxData3; //!< Auxiliary data 3
+ uint8_t auxData4; //!< Auxiliary data 4
+} __attribute__((packed));
+
+/** @struct DeactivatePayloadResponse
+ *
+ * IPMI payload for Deactivate Payload Command response.
+ */
+struct DeactivatePayloadResponse
+{
+ uint8_t completionCode; //!< Completion code
+} __attribute__((packed));
+
+/** @brief Deactivate Payload Command.
+ *
+ * This command is used to terminate use of a given payload on an IPMI session.
+ * This type of traffic then becomes freed for activation by another session,
+ * or for possible re-activation under the present session.The Deactivate
+ * Payload command does not cause the session to be terminated. The Close
+ * Session command should be used for that purpose. A remote console
+ * terminating a application does not need to explicitly deactivate payload(s)
+ * prior to session. When a session terminates all payloads that were active
+ * under that session are automatically deactivated by the BMC.
+ *
+ * @param[in] inPayload - Request data for the command.
+ * @param[in] handler - Reference to the message handler.
+ *
+ * @return Response data for the command.
+ */
+std::vector<uint8_t> deactivatePayload(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+/** @struct GetPayloadStatusRequest
+ *
+ * IPMI payload for Get Payload Activation Status command request.
+ */
+struct GetPayloadStatusRequest
+{
+ uint8_t payloadType; //!< Payload type
+} __attribute__((packed));
+
+/** @struct GetPayloadStatusResponse
+ *
+ * IPMI payload for Get Payload Activation Status command response.
+ */
+struct GetPayloadStatusResponse
+{
+ uint8_t completionCode; //!< Completion code.
+
+ uint8_t capacity; //!< Instance capacity.
+
+ /* @brief Activation Status. */
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t instance1:1; //!< If true, Instance 1 is activated.
+ uint8_t instance2:1; //!< If true, Instance 2 is activated.
+ uint8_t instance3:1; //!< If true, Instance 3 is activated.
+ uint8_t instance4:1; //!< If true, Instance 4 is activated.
+ uint8_t instance5:1; //!< If true, Instance 5 is activated.
+ uint8_t instance6:1; //!< If true, Instance 6 is activated.
+ uint8_t instance7:1; //!< If true, Instance 7 is activated.
+ uint8_t instance8:1; //!< If true, Instance 8 is activated.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t instance8:1; //!< If true, Instance 8 is activated.
+ uint8_t instance7:1; //!< If true, Instance 7 is activated.
+ uint8_t instance6:1; //!< If true, Instance 6 is activated.
+ uint8_t instance5:1; //!< If true, Instance 5 is activated.
+ uint8_t instance4:1; //!< If true, Instance 4 is activated.
+ uint8_t instance3:1; //!< If true, Instance 3 is activated.
+ uint8_t instance2:1; //!< If true, Instance 2 is activated.
+ uint8_t instance1:1; //!< If true, Instance 1 is activated.
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t instance9:1; //!< If true, Instance 9 is activated.
+ uint8_t instance10:1; //!< If true, Instance 10 is activated.
+ uint8_t instance11:1; //!< If true, Instance 11 is activated.
+ uint8_t instance12:1; //!< If true, Instance 12 is activated.
+ uint8_t instance13:1; //!< If true, Instance 13 is activated.
+ uint8_t instance14:1; //!< If true, Instance 14 is activated.
+ uint8_t instance15:1; //!< If true, Instance 15 is activated.
+ uint8_t instance16:1; //!< If true, Instance 16 is activated.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t instance16:1; //!< If true, Instance 16 is activated.
+ uint8_t instance15:1; //!< If true, Instance 15 is activated.
+ uint8_t instance14:1; //!< If true, Instance 14 is activated.
+ uint8_t instance13:1; //!< If true, Instance 13 is activated.
+ uint8_t instance12:1; //!< If true, Instance 12 is activated.
+ uint8_t instance11:1; //!< If true, Instance 11 is activated.
+ uint8_t instance10:1; //!< If true, Instance 10 is activated.
+ uint8_t instance9:1; //!< If true, Instance 9 is activated.
+#endif
+} __attribute__((packed));
+
+/** @brief Get Payload Activation Status Command.
+ *
+ * This command returns how many instances of a given payload type are
+ * presently activated, and how many total instances can be activated.
+ *
+ * @param[in] inPayload - Request Data for the command.
+ * @param[in] handler - Reference to the Message Handler.
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> getPayloadStatus(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+/** @struct GetPayloadInfoRequest
+ *
+ * IPMI payload for Get Payload Instance info command request.
+ */
+struct GetPayloadInfoRequest
+{
+ uint8_t payloadType; //!< Payload type
+ uint8_t payloadInstance; //!< Payload instance
+} __attribute__((packed));
+
+/** @struct GetPayloadInfoResponse
+ *
+ * IPMI payload for Get Payload Instance info command response.
+ */
+struct GetPayloadInfoResponse
+{
+ uint8_t completionCode; //!< Completion code.
+ uint32_t sessionID; //!< Session ID
+ uint8_t portNumber; //!< Port number
+ uint8_t reserved[7]; //!< Reserved
+} __attribute__((packed));
+
+/** @brief Get Payload Instance Info Command.
+ *
+ * This command returns information about a specific instance of a payload
+ * type. Session ID is returned by this command
+ *
+ * @param[in] inPayload - Request Data for the command.
+ * @param[in] handler - Reference to the Message Handler.
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> getPayloadInfo(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+} // namespace command
+
+} // namespace sol
diff --git a/transport/rmcpbridge/command/rakp12.cpp b/transport/rmcpbridge/command/rakp12.cpp
new file mode 100644
index 0000000..5c674ac
--- /dev/null
+++ b/transport/rmcpbridge/command/rakp12.cpp
@@ -0,0 +1,331 @@
+#include "config.h"
+
+#include "rakp12.hpp"
+
+#include "comm_module.hpp"
+#include "endian.hpp"
+#include "guid.hpp"
+#include "sessions_manager.hpp"
+
+#include <openssl/rand.h>
+
+#include <ipmid/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <cstring>
+#include <iomanip>
+
+namespace command
+{
+
+bool isChannelAccessModeEnabled(const uint8_t accessMode)
+{
+ return accessMode !=
+ static_cast<uint8_t>(ipmi::EChannelAccessMode::disabled);
+}
+
+void logInvalidLoginRedfishEvent(const std::string& journalMsg,
+ const std::optional<std::string>& messageArgs)
+{
+ static constexpr std::string_view openBMCMessageRegistryVersion = "0.1.";
+ std::string messageID =
+ "OpenBMC." + std::string(openBMCMessageRegistryVersion) +
+ "InvalidLoginAttempted";
+ lg2::error(
+ "message: {MSG}, id: {REDFISH_MESSAGE_ID}, args: {REDFISH_MESSAGE_ARGS}",
+ "MSG", journalMsg, "REDFISH_MESSAGE_ID", messageID,
+ "REDFISH_MESSAGE_ARGS", messageArgs.value_or(std::string{}));
+}
+std::vector<uint8_t> RAKP12(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ auto request = reinterpret_cast<const RAKP1request*>(inPayload.data());
+ // verify inPayload minimum size
+ if (inPayload.size() < (sizeof(*request) - userNameMaxLen))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(RAKP2response));
+ auto response = reinterpret_cast<RAKP2response*>(outPayload.data());
+
+ // Session ID zero is reserved for Session Setup
+ if (endian::from_ipmi(request->managedSystemSessionID) ==
+ session::sessionZero)
+ {
+ lg2::info("RAKP12: BMC invalid Session ID");
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_SESSION_ID);
+ return outPayload;
+ }
+
+ std::shared_ptr<session::Session> session;
+ try
+ {
+ session = session::Manager::get().getSession(
+ endian::from_ipmi(request->managedSystemSessionID));
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("RAKP12 : session not found: {ERROR}", "ERROR", e);
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_SESSION_ID);
+ return outPayload;
+ }
+
+ auto rakp1Size =
+ sizeof(RAKP1request) - (userNameMaxLen - request->user_name_len);
+
+ std::string message = "Invalid login attempted via RCMPP interface ";
+ // Validate user name length in the message
+ if (request->user_name_len > userNameMaxLen ||
+ inPayload.size() != rakp1Size)
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_NAME_LENGTH);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+
+ session->userName.assign(request->user_name, request->user_name_len);
+
+ // Update transaction time
+ session->updateLastTransactionTime();
+
+ auto rcSessionID = endian::to_ipmi(session->getRCSessionID());
+ auto bmcSessionID = endian::to_ipmi(session->getBMCSessionID());
+ auto authAlgo = session->getAuthAlgo();
+
+ /*
+ * Generate Key Authentication Code - RAKP 2
+ *
+ * 1) Remote Console Session ID - 4 bytes
+ * 2) Managed System Session ID - 4 bytes
+ * 3) Remote Console Random Number - 16 bytes
+ * 4) Managed System Random Number - 16 bytes
+ * 5) Managed System GUID - 16 bytes
+ * 6) Requested Privilege Level - 1 byte
+ * 7) User Name Length Byte - 1 byte (0 for 'null' username)
+ * 8) User Name - variable (absent for 'null' username)
+ */
+
+ std::vector<uint8_t> input;
+ input.resize(sizeof(rcSessionID) + sizeof(bmcSessionID) +
+ cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN +
+ cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN + BMC_GUID_LEN +
+ sizeof(request->req_max_privilege_level) +
+ sizeof(request->user_name_len) + session->userName.size());
+
+ auto iter = input.begin();
+
+ // Remote Console Session ID
+ std::copy_n(reinterpret_cast<uint8_t*>(&rcSessionID), sizeof(rcSessionID),
+ iter);
+ std::advance(iter, sizeof(rcSessionID));
+
+ // Managed System Session ID
+ std::copy_n(reinterpret_cast<uint8_t*>(&bmcSessionID), sizeof(bmcSessionID),
+ iter);
+ std::advance(iter, sizeof(bmcSessionID));
+
+ // Copy the Remote Console Random Number from the RAKP1 request to the
+ // Authentication Algorithm
+ std::copy_n(
+ reinterpret_cast<const uint8_t*>(request->remote_console_random_number),
+ cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN,
+ authAlgo->rcRandomNum.begin());
+
+ std::copy(authAlgo->rcRandomNum.begin(), authAlgo->rcRandomNum.end(), iter);
+ std::advance(iter, cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN);
+
+ // Generate the Managed System Random Number
+ if (!RAND_bytes(input.data() + sizeof(rcSessionID) + sizeof(bmcSessionID) +
+ cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN,
+ cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN))
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INSUFFICIENT_RESOURCE);
+ return outPayload;
+ }
+ // As stated in Set Session Privilege Level command in IPMI Spec, when
+ // creating a session through Activate command / RAKP 1 message, it must
+ // be established with USER privilege as well as all other sessions are
+ // initially set to USER privilege, regardless of the requested maximum
+ // privilege.
+ if (!(static_cast<session::Privilege>(
+ request->req_max_privilege_level & session::reqMaxPrivMask) >
+ session::Privilege::CALLBACK))
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_ROLE_PRIV);
+ return outPayload;
+ }
+ session->currentPrivilege(static_cast<uint8_t>(session::Privilege::USER));
+
+ session->reqMaxPrivLevel =
+ static_cast<session::Privilege>(request->req_max_privilege_level);
+ if (request->user_name_len == 0)
+ {
+ // Bail out, if user name is not specified.
+ // Yes, NULL user name is not supported for security reasons.
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+
+ // Perform user name based lookup
+ std::string userName(request->user_name, request->user_name_len);
+ ipmi::SecureString passwd;
+ uint8_t userId = ipmi::ipmiUserGetUserId(userName);
+ if (userId == ipmi::invalidUserId)
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+ // check user is enabled before proceeding.
+ bool userEnabled = false;
+ ipmi::ipmiUserCheckEnabled(userId, userEnabled);
+ if (!userEnabled)
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+ // Get the user password for RAKP message authenticate
+ passwd = ipmi::ipmiUserGetPassword(userName);
+ if (passwd.empty())
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+#ifdef PAM_AUTHENTICATE
+ // Check whether user is already locked for failed attempts
+ if (!ipmi::ipmiUserPamAuthenticate(userName, passwd))
+ {
+ lg2::error(
+ "Authentication failed - user already locked out, user id: {ID}",
+ "ID", userId);
+
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+#endif
+
+ uint8_t chNum = static_cast<uint8_t>(getInterfaceIndex());
+ // Get channel based access information
+ if ((ipmi::ipmiUserGetPrivilegeAccess(
+ userId, chNum, session->sessionUserPrivAccess) != IPMI_CC_OK) ||
+ (ipmi::getChannelAccessData(chNum, session->sessionChannelAccess) !=
+ IPMI_CC_OK))
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+ if (!isChannelAccessModeEnabled(session->sessionChannelAccess.accessMode))
+ {
+ lg2::error("Channel access mode disabled.");
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+ if (session->sessionUserPrivAccess.privilege >
+ static_cast<uint8_t>(session::Privilege::OEM))
+ {
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+ session->channelNum(chNum);
+ session->userID(userId);
+ // minimum privilege of Channel / User / session::privilege::USER
+ // has to be used as session current privilege level
+ uint8_t minPriv = 0;
+ if (session->sessionChannelAccess.privLimit <
+ session->sessionUserPrivAccess.privilege)
+ {
+ minPriv = session->sessionChannelAccess.privLimit;
+ }
+ else
+ {
+ minPriv = session->sessionUserPrivAccess.privilege;
+ }
+ if (session->currentPrivilege() > minPriv)
+ {
+ session->currentPrivilege(static_cast<uint8_t>(minPriv));
+ }
+ // For username / privilege lookup, fail with UNAUTH_NAME, if requested
+ // max privilege does not match user privilege
+ if (((request->req_max_privilege_level & userNameOnlyLookupMask) ==
+ userNamePrivLookup) &&
+ ((request->req_max_privilege_level & session::reqMaxPrivMask) !=
+ session->sessionUserPrivAccess.privilege))
+ {
+ lg2::info("Username/Privilege lookup failed for requested privilege");
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+
+ logInvalidLoginRedfishEvent(message);
+ return outPayload;
+ }
+
+ std::fill(authAlgo->userKey.data(),
+ authAlgo->userKey.data() + authAlgo->userKey.size(), 0);
+ std::copy_n(passwd.c_str(), passwd.size(), authAlgo->userKey.data());
+
+ // Copy the Managed System Random Number to the Authentication Algorithm
+ std::copy_n(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN,
+ authAlgo->bmcRandomNum.begin());
+ std::advance(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN);
+
+ // Managed System GUID
+ const Guid& guid = command::getSystemGUID();
+ std::copy_n(guid.data(), guid.size(), iter);
+ std::advance(iter, BMC_GUID_LEN);
+
+ // Requested Privilege Level
+ std::copy_n(&(request->req_max_privilege_level),
+ sizeof(request->req_max_privilege_level), iter);
+ std::advance(iter, sizeof(request->req_max_privilege_level));
+
+ // User Name Length Byte
+ std::copy_n(&(request->user_name_len), sizeof(request->user_name_len),
+ iter);
+ std::advance(iter, sizeof(request->user_name_len));
+
+ std::copy_n(session->userName.data(), session->userName.size(), iter);
+
+ // Generate Key Exchange Authentication Code - RAKP2
+ auto output = authAlgo->generateHMAC(input);
+
+ response->messageTag = request->messageTag;
+ response->rmcpStatusCode = static_cast<uint8_t>(RAKP_ReturnCode::NO_ERROR);
+ response->reserved = 0;
+ response->remoteConsoleSessionID = rcSessionID;
+
+ // Copy Managed System Random Number to the Response
+ std::copy(authAlgo->bmcRandomNum.begin(), authAlgo->bmcRandomNum.end(),
+ response->managed_system_random_number);
+
+ // Copy System GUID to the Response
+ std::copy_n(guid.data(), guid.size(), response->managed_system_guid);
+
+ // Insert the HMAC output into the payload
+ outPayload.insert(outPayload.end(), output.begin(), output.end());
+ return outPayload;
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/rakp12.hpp b/transport/rmcpbridge/command/rakp12.hpp
new file mode 100644
index 0000000..e2eebc2
--- /dev/null
+++ b/transport/rmcpbridge/command/rakp12.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "comm_module.hpp"
+#include "message_handler.hpp"
+
+#include <cstddef>
+#include <vector>
+
+namespace command
+{
+
+constexpr size_t userNameMaxLen = 16;
+
+constexpr uint8_t userNameOnlyLookupMask = 0x10;
+constexpr uint8_t userNameOnlyLookup = 0x10;
+constexpr uint8_t userNamePrivLookup = 0x0;
+
+/**
+ * @struct RAKP1request
+ *
+ * IPMI Payload for RAKP Message 1
+ */
+struct RAKP1request
+{
+ uint8_t messageTag;
+ uint8_t reserved1;
+ uint16_t reserved2;
+ uint32_t managedSystemSessionID;
+ uint8_t remote_console_random_number[16];
+ uint8_t req_max_privilege_level;
+ uint16_t reserved3;
+ uint8_t user_name_len;
+ char user_name[userNameMaxLen];
+} __attribute__((packed));
+
+/**
+ * @struct RAKP2response
+ *
+ * IPMI Payload for RAKP Message 2
+ */
+struct RAKP2response
+{
+ uint8_t messageTag;
+ uint8_t rmcpStatusCode;
+ uint16_t reserved;
+ uint32_t remoteConsoleSessionID;
+ uint8_t managed_system_random_number[16];
+ uint8_t managed_system_guid[16];
+} __attribute__((packed));
+
+/**
+ * @brief RAKP Message 1, RAKP Message 2
+ *
+ * These messages are used to exchange random number and identification
+ * information between the BMC and the remote console that are, in effect,
+ * mutual challenges for a challenge/response. (Unlike IPMI v1.5, the v2.0/RMCP+
+ * challenge/response is symmetric. I.e. the remote console and BMC both issues
+ * challenges,and both need to provide valid responses for the session to be
+ * activated.)
+ *
+ * The remote console request (RAKP Message 1) passes a random number and
+ * username/privilege information that the BMC will later use to ‘sign’ a
+ * response message based on key information associated with the user and the
+ * Authentication Algorithm negotiated in the Open Session Request/Response
+ * exchange. The BMC responds with RAKP Message 2 and passes a random number and
+ * GUID (globally unique ID) for the managed system that the remote console
+ * uses according the Authentication Algorithm to sign a response back to the
+ * BMC.
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> RAKP12(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+/**
+ *@brief Log Redfish event for invalid login attempted on RMCPP interface
+ *
+ * @param[in] journalMsg - Show journal Debug Message in journal logs
+ * @param[in] redfishMsg - Log Redfish Event Message
+ *
+ */
+void logInvalidLoginRedfishEvent(
+ const std::string& journalMsg,
+ const std::optional<std::string>& messageArgs = "RMCPP");
+} // namespace command
diff --git a/transport/rmcpbridge/command/rakp34.cpp b/transport/rmcpbridge/command/rakp34.cpp
new file mode 100644
index 0000000..7735d8d
--- /dev/null
+++ b/transport/rmcpbridge/command/rakp34.cpp
@@ -0,0 +1,273 @@
+#include "rakp34.hpp"
+
+#include "comm_module.hpp"
+#include "endian.hpp"
+#include "guid.hpp"
+#include "rmcp.hpp"
+#include "sessions_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <cstring>
+
+namespace command
+{
+
+void applyIntegrityAlgo(const uint32_t bmcSessionID)
+{
+ auto session = session::Manager::get().getSession(bmcSessionID);
+
+ auto authAlgo = session->getAuthAlgo();
+
+ switch (authAlgo->intAlgo)
+ {
+ case cipher::integrity::Algorithms::HMAC_SHA1_96:
+ {
+ session->setIntegrityAlgo(
+ std::make_unique<cipher::integrity::AlgoSHA1>(
+ authAlgo->sessionIntegrityKey));
+ break;
+ }
+ case cipher::integrity::Algorithms::HMAC_SHA256_128:
+ {
+ session->setIntegrityAlgo(
+ std::make_unique<cipher::integrity::AlgoSHA256>(
+ authAlgo->sessionIntegrityKey));
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void applyCryptAlgo(const uint32_t bmcSessionID)
+{
+ auto session = session::Manager::get().getSession(bmcSessionID);
+
+ auto authAlgo = session->getAuthAlgo();
+
+ switch (authAlgo->cryptAlgo)
+ {
+ case cipher::crypt::Algorithms::AES_CBC_128:
+ {
+ auto intAlgo = session->getIntegrityAlgo();
+ auto k2 = intAlgo->generateKn(authAlgo->sessionIntegrityKey,
+ rmcp::const_2);
+ session->setCryptAlgo(
+ std::make_unique<cipher::crypt::AlgoAES128>(k2));
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+std::vector<uint8_t> RAKP34(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& /* handler */)
+{
+ std::vector<uint8_t> outPayload(sizeof(RAKP4response));
+ auto request = reinterpret_cast<const RAKP3request*>(inPayload.data());
+ auto response = reinterpret_cast<RAKP4response*>(outPayload.data());
+
+ // Check if the RAKP3 Payload Length is as expected
+ if (inPayload.size() < sizeof(RAKP3request))
+ {
+ lg2::info("RAKP34: Invalid RAKP3 request");
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_INTEGRITY_VALUE);
+ return outPayload;
+ }
+
+ // Session ID zero is reserved for Session Setup
+ if (endian::from_ipmi(request->managedSystemSessionID) ==
+ session::sessionZero)
+ {
+ lg2::info("RAKP34: BMC invalid Session ID");
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_SESSION_ID);
+ return outPayload;
+ }
+
+ std::shared_ptr<session::Session> session;
+ try
+ {
+ session =
+ session::Manager::get().getSession(request->managedSystemSessionID);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("RAKP12 : session not found: {ERROR}", "ERROR", e);
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_SESSION_ID);
+ return outPayload;
+ }
+
+ session->updateLastTransactionTime();
+
+ auto authAlgo = session->getAuthAlgo();
+ /*
+ * Key Authentication Code - RAKP 3
+ *
+ * 1) Managed System Random Number - 16 bytes
+ * 2) Remote Console Session ID - 4 bytes
+ * 3) Session Privilege Level - 1 byte
+ * 4) User Name Length Byte - 1 byte (0 for 'null' username)
+ * 5) User Name - variable (absent for 'null' username)
+ */
+
+ // Remote Console Session ID
+ auto rcSessionID = endian::to_ipmi(session->getRCSessionID());
+
+ // Session Privilege Level
+ auto sessPrivLevel = static_cast<uint8_t>(session->reqMaxPrivLevel);
+
+ // User Name Length Byte
+ auto userLength = static_cast<uint8_t>(session->userName.size());
+
+ std::vector<uint8_t> input;
+ input.resize(cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN +
+ sizeof(rcSessionID) + sizeof(sessPrivLevel) +
+ sizeof(userLength) + userLength);
+
+ auto iter = input.begin();
+
+ // Managed System Random Number
+ std::copy(authAlgo->bmcRandomNum.begin(), authAlgo->bmcRandomNum.end(),
+ iter);
+ std::advance(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN);
+
+ // Remote Console Session ID
+ std::copy_n(reinterpret_cast<uint8_t*>(&rcSessionID), sizeof(rcSessionID),
+ iter);
+ std::advance(iter, sizeof(rcSessionID));
+
+ // Session Privilege Level
+ std::copy_n(reinterpret_cast<uint8_t*>(&sessPrivLevel),
+ sizeof(sessPrivLevel), iter);
+ std::advance(iter, sizeof(sessPrivLevel));
+
+ // User Name Length Byte
+ std::copy_n(&userLength, sizeof(userLength), iter);
+ std::advance(iter, sizeof(userLength));
+
+ std::copy_n(session->userName.data(), userLength, iter);
+
+ // Generate Key Exchange Authentication Code - RAKP2
+ auto output = authAlgo->generateHMAC(input);
+
+ if (inPayload.size() != (sizeof(RAKP3request) + output.size()) ||
+ std::memcmp(output.data(), request + 1, output.size()))
+ {
+ lg2::info("Mismatch in HMAC sent by remote console");
+
+ response->messageTag = request->messageTag;
+ response->rmcpStatusCode =
+ static_cast<uint8_t>(RAKP_ReturnCode::INVALID_INTEGRITY_VALUE);
+ response->reserved = 0;
+ response->remoteConsoleSessionID = rcSessionID;
+
+ // close the session
+ session::Manager::get().stopSession(session->getBMCSessionID());
+
+ return outPayload;
+ }
+
+ /*
+ * Session Integrity Key
+ *
+ * 1) Remote Console Random Number - 16 bytes
+ * 2) Managed System Random Number - 16 bytes
+ * 3) Session Privilege Level - 1 byte
+ * 4) User Name Length Byte - 1 byte (0 for 'null' username)
+ * 5) User Name - variable (absent for 'null' username)
+ */
+
+ input.clear();
+
+ input.resize(cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN +
+ cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN +
+ sizeof(sessPrivLevel) + sizeof(userLength) + userLength);
+ iter = input.begin();
+
+ // Remote Console Random Number
+ std::copy(authAlgo->rcRandomNum.begin(), authAlgo->rcRandomNum.end(), iter);
+ std::advance(iter, cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN);
+
+ // Managed Console Random Number
+ std::copy(authAlgo->bmcRandomNum.begin(), authAlgo->bmcRandomNum.end(),
+ iter);
+ std::advance(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN);
+
+ // Session Privilege Level
+ std::copy_n(reinterpret_cast<uint8_t*>(&sessPrivLevel),
+ sizeof(sessPrivLevel), iter);
+ std::advance(iter, sizeof(sessPrivLevel));
+
+ // User Name Length Byte
+ std::copy_n(&userLength, sizeof(userLength), iter);
+ std::advance(iter, sizeof(userLength));
+
+ std::copy_n(session->userName.data(), userLength, iter);
+
+ // Generate Session Integrity Key
+ auto sikOutput = authAlgo->generateHMAC(input);
+
+ // Update the SIK in the Authentication Algo Interface
+ authAlgo->sessionIntegrityKey.insert(authAlgo->sessionIntegrityKey.begin(),
+ sikOutput.begin(), sikOutput.end());
+
+ /*
+ * Integrity Check Value
+ *
+ * 1) Remote Console Random Number - 16 bytes
+ * 2) Managed System Session ID - 4 bytes
+ * 3) Managed System GUID - 16 bytes
+ */
+
+ // Get Managed System Session ID
+ auto bmcSessionID = endian::to_ipmi(session->getBMCSessionID());
+
+ input.clear();
+
+ input.resize(cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN +
+ sizeof(bmcSessionID) + BMC_GUID_LEN);
+ iter = input.begin();
+
+ // Remote Console Random Number
+ std::copy(authAlgo->rcRandomNum.begin(), authAlgo->rcRandomNum.end(), iter);
+ std::advance(iter, cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN);
+
+ // Managed System Session ID
+ std::copy_n(reinterpret_cast<uint8_t*>(&bmcSessionID), sizeof(bmcSessionID),
+ iter);
+ std::advance(iter, sizeof(bmcSessionID));
+
+ // Managed System GUID
+ const Guid& guid = command::getSystemGUID();
+ std::copy_n(guid.data(), guid.size(), iter);
+
+ // Integrity Check Value
+ auto icv = authAlgo->generateICV(input);
+
+ outPayload.resize(sizeof(RAKP4response));
+
+ response->messageTag = request->messageTag;
+ response->rmcpStatusCode = static_cast<uint8_t>(RAKP_ReturnCode::NO_ERROR);
+ response->reserved = 0;
+ response->remoteConsoleSessionID = rcSessionID;
+
+ // Insert the HMAC output into the payload
+ outPayload.insert(outPayload.end(), icv.begin(), icv.end());
+
+ // Set the Integrity Algorithm
+ applyIntegrityAlgo(session->getBMCSessionID());
+
+ // Set the Confidentiality Algorithm
+ applyCryptAlgo(session->getBMCSessionID());
+
+ session->state(static_cast<uint8_t>(session::State::active));
+ return outPayload;
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/rakp34.hpp b/transport/rmcpbridge/command/rakp34.hpp
new file mode 100644
index 0000000..7b29d70
--- /dev/null
+++ b/transport/rmcpbridge/command/rakp34.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "comm_module.hpp"
+#include "message_handler.hpp"
+
+#include <vector>
+
+namespace command
+{
+
+/**
+ * @struct RAKP3request
+ *
+ * IPMI Payload for RAKP Message 3
+ */
+struct RAKP3request
+{
+ uint8_t messageTag;
+ uint8_t rmcpStatusCode;
+ uint16_t reserved;
+ uint32_t managedSystemSessionID;
+} __attribute__((packed));
+
+/**
+ * @struct RAKP4response
+ *
+ * IPMI Payload for RAKP Message 4
+ */
+struct RAKP4response
+{
+ uint8_t messageTag;
+ uint8_t rmcpStatusCode;
+ uint16_t reserved;
+ uint32_t remoteConsoleSessionID;
+} __attribute__((packed));
+
+/**
+ * @brief RAKP Message 3, RAKP Message 4
+ *
+ * The session activation process is completed by the remote console and BMC
+ * exchanging messages that are signed according to the Authentication Algorithm
+ * that was negotiated, and the parameters that were passed in the earlier
+ * messages. RAKP Message 3 is the signed message from the remote console to the
+ * BMC. After receiving RAKP Message 3, the BMC returns RAKP Message 4 - a
+ * signed message from BMC to the remote console.
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> RAKP34(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/session_cmds.cpp b/transport/rmcpbridge/command/session_cmds.cpp
new file mode 100644
index 0000000..5ee7f8f
--- /dev/null
+++ b/transport/rmcpbridge/command/session_cmds.cpp
@@ -0,0 +1,328 @@
+#include "session_cmds.hpp"
+
+#include "endian.hpp"
+#include "sessions_manager.hpp"
+
+#include <ipmid/api.h>
+
+#include <ipmid/sessionhelper.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <chrono>
+
+using namespace std::chrono_literals;
+
+namespace command
+{
+
+std::vector<uint8_t> setSessionPrivilegeLevel(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler)
+{
+ auto request =
+ reinterpret_cast<const SetSessionPrivLevelReq*>(inPayload.data());
+ if (inPayload.size() != sizeof(*request))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+ if (request->reserved != 0)
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_INVALID_FIELD_REQUEST};
+ return errorPayload;
+ }
+
+ std::vector<uint8_t> outPayload(sizeof(SetSessionPrivLevelResp));
+ auto response =
+ reinterpret_cast<SetSessionPrivLevelResp*>(outPayload.data());
+ response->completionCode = IPMI_CC_OK;
+ uint8_t reqPrivilegeLevel = request->reqPrivLevel;
+
+ auto session = session::Manager::get().getSession(handler->sessionID);
+
+ if (reqPrivilegeLevel == 0) // Just return present privilege level
+ {
+ response->newPrivLevel = session->currentPrivilege();
+ return outPayload;
+ }
+ if (reqPrivilegeLevel ==
+ static_cast<uint8_t>(session::Privilege::CALLBACK) ||
+ reqPrivilegeLevel > static_cast<uint8_t>(session::Privilege::OEM))
+ {
+ response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST;
+ return outPayload;
+ }
+
+ if (reqPrivilegeLevel > (static_cast<uint8_t>(session->reqMaxPrivLevel) &
+ session::reqMaxPrivMask))
+ {
+ // Requested level exceeds Channel and/or User Privilege Limit
+ response->completionCode = IPMI_CC_EXCEEDS_USER_PRIV;
+ return outPayload;
+ }
+ // Use the minimum privilege of user or channel
+ uint8_t minPriv = 0;
+ if (session->sessionChannelAccess.privLimit <
+ session->sessionUserPrivAccess.privilege)
+ {
+ minPriv = session->sessionChannelAccess.privLimit;
+ }
+ else
+ {
+ minPriv = session->sessionUserPrivAccess.privilege;
+ }
+ if (reqPrivilegeLevel > minPriv)
+ {
+ // Requested level exceeds Channel and/or User Privilege Limit
+ response->completionCode = IPMI_CC_EXCEEDS_USER_PRIV;
+ }
+ else
+ {
+ // update current privilege of the session.
+ session->currentPrivilege(static_cast<uint8_t>(reqPrivilegeLevel));
+ response->newPrivLevel = reqPrivilegeLevel;
+ }
+
+ return outPayload;
+}
+
+/**
+ * @brief set the session state as teardown
+ *
+ * This function is to set the session state to tear down in progress if the
+ * state is active.
+ *
+ * @param[in] busp - Dbus obj
+ * @param[in] service - service name
+ * @param[in] obj - object path
+ *
+ * @return success completion code if it sets the session state to
+ * tearDownInProgress else return the corresponding error completion code.
+ **/
+uint8_t setSessionState(std::shared_ptr<sdbusplus::asio::connection>& busp,
+ const std::string& service, const std::string& obj)
+{
+ try
+ {
+ uint8_t sessionState = std::get<uint8_t>(ipmi::getDbusProperty(
+ *busp, service, obj, session::sessionIntf, "State"));
+
+ if (sessionState == static_cast<uint8_t>(session::State::active))
+ {
+ ipmi::setDbusProperty(
+ *busp, service, obj, session::sessionIntf, "State",
+ static_cast<uint8_t>(session::State::tearDownInProgress));
+ return ipmi::ccSuccess;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed in getting session state property: {SERVICE}, {PATH}, {INTERFACE}",
+ "SERVICE", service, "PATH", obj, "INTERFACE", session::sessionIntf);
+ return ipmi::ccUnspecifiedError;
+ }
+
+ return ipmi::ccInvalidFieldRequest;
+}
+
+uint8_t closeOtherNetInstanceSession(const uint32_t reqSessionId,
+ const uint8_t reqSessionHandle,
+ const uint8_t currentSessionPriv)
+{
+ auto busp = getSdBus();
+
+ try
+ {
+ ipmi::ObjectTree objectTree = ipmi::getAllDbusObjects(
+ *busp, session::sessionManagerRootPath, session::sessionIntf);
+
+ for (auto& objectTreeItr : objectTree)
+ {
+ const std::string obj = objectTreeItr.first;
+
+ if (isSessionObjectMatched(obj, reqSessionId, reqSessionHandle))
+ {
+ auto& serviceMap = objectTreeItr.second;
+
+ if (serviceMap.size() != 1)
+ {
+ return ipmi::ccUnspecifiedError;
+ }
+
+ auto itr = serviceMap.begin();
+ const std::string service = itr->first;
+ uint8_t closeSessionPriv = std::get<uint8_t>(
+ ipmi::getDbusProperty(*busp, service, obj,
+ session::sessionIntf,
+ "CurrentPrivilege"));
+
+ if (currentSessionPriv < closeSessionPriv)
+ {
+ return ipmi::ccInsufficientPrivilege;
+ }
+ return setSessionState(busp, service, obj);
+ }
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error(
+ "Failed to fetch object from dbus, interface: {INTERFACE}, error: {ERROR}",
+ "INTERFACE", session::sessionIntf, "ERROR", e);
+ return ipmi::ccUnspecifiedError;
+ }
+
+ return ipmi::ccInvalidFieldRequest;
+}
+
+uint8_t closeMyNetInstanceSession(uint32_t reqSessionId,
+ uint8_t reqSessionHandle,
+ const uint8_t currentSessionPriv)
+{
+ bool status = false;
+
+ try
+ {
+ if (reqSessionId == session::sessionZero)
+ {
+ reqSessionId = session::Manager::get().getSessionIDbyHandle(
+ reqSessionHandle & session::multiIntfaceSessionHandleMask);
+ if (!reqSessionId)
+ {
+ return session::ccInvalidSessionHandle;
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to get session manager instance or sessionID by sessionHandle: {ERROR}",
+ "ERROR", e);
+ return session::ccInvalidSessionHandle;
+ }
+
+ try
+ {
+ auto closeSessionInstance =
+ session::Manager::get().getSession(reqSessionId);
+ uint8_t closeSessionPriv = closeSessionInstance->currentPrivilege();
+
+ if (currentSessionPriv < closeSessionPriv)
+ {
+ return ipmi::ccInsufficientPrivilege;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to get session manager instance or sessionID: {ERROR}",
+ "ERROR", e);
+ return session::ccInvalidSessionId;
+ }
+
+ try
+ {
+ status = session::Manager::get().stopSession(reqSessionId);
+
+ if (!status)
+ {
+ return session::ccInvalidSessionId;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to get session manager instance or stop session: {ERROR}",
+ "ERROR", e);
+ return ipmi::ccUnspecifiedError;
+ }
+
+ return ipmi::ccSuccess;
+}
+
+std::vector<uint8_t> closeSession(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler)
+{
+ // minimum inPayload size is reqSessionId (uint32_t)
+ // maximum inPayload size is struct CloseSessionRequest
+ if (inPayload.size() != sizeof(uint32_t) &&
+ inPayload.size() != sizeof(CloseSessionRequest))
+ {
+ std::vector<uint8_t> errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID};
+ return errorPayload;
+ }
+
+ auto request =
+ reinterpret_cast<const CloseSessionRequest*>(inPayload.data());
+
+ std::vector<uint8_t> outPayload(sizeof(CloseSessionResponse));
+ auto response = reinterpret_cast<CloseSessionResponse*>(outPayload.data());
+ uint32_t reqSessionId = request->sessionID;
+ uint8_t ipmiNetworkInstance = 0;
+ uint8_t currentSessionPriv = 0;
+ uint8_t reqSessionHandle = session::invalidSessionHandle;
+
+ if (inPayload.size() == sizeof(CloseSessionRequest))
+ {
+ reqSessionHandle = request->sessionHandle;
+ }
+
+ if (reqSessionId == session::sessionZero &&
+ reqSessionHandle == session::invalidSessionHandle)
+ {
+ response->completionCode = session::ccInvalidSessionHandle;
+ return outPayload;
+ }
+
+ if (inPayload.size() == sizeof(reqSessionId) &&
+ reqSessionId == session::sessionZero)
+ {
+ response->completionCode = session::ccInvalidSessionId;
+ return outPayload;
+ }
+
+ if (reqSessionId != session::sessionZero &&
+ inPayload.size() != sizeof(reqSessionId))
+ {
+ response->completionCode = ipmi::ccInvalidFieldRequest;
+ return outPayload;
+ }
+
+ try
+ {
+ ipmiNetworkInstance = session::Manager::get().getNetworkInstance();
+ auto currentSession =
+ session::Manager::get().getSession(handler->sessionID);
+ currentSessionPriv = currentSession->currentPrivilege();
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error(
+ "Failed to fetch object from dbus, interface: {INTERFACE}, error: {ERROR}",
+ "INTERFACE", session::sessionIntf, "ERROR", e);
+ response->completionCode = ipmi::ccUnspecifiedError;
+ return outPayload;
+ }
+
+ if (reqSessionId >> myNetInstanceSessionIdShiftMask ==
+ ipmiNetworkInstance ||
+ (reqSessionId == session::sessionZero &&
+ (reqSessionHandle >> myNetInstanceSessionHandleShiftMask ==
+ ipmiNetworkInstance)))
+ {
+ response->completionCode = closeMyNetInstanceSession(
+ reqSessionId, reqSessionHandle, currentSessionPriv);
+ session::Manager::get().scheduleSessionCleaner(100us);
+ }
+ else
+ {
+ response->completionCode = closeOtherNetInstanceSession(
+ reqSessionId, reqSessionHandle, currentSessionPriv);
+ }
+
+ return outPayload;
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/session_cmds.hpp b/transport/rmcpbridge/command/session_cmds.hpp
new file mode 100644
index 0000000..b94143e
--- /dev/null
+++ b/transport/rmcpbridge/command/session_cmds.hpp
@@ -0,0 +1,119 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <vector>
+
+namespace command
+{
+
+constexpr uint8_t IPMI_CC_INVALID_PRIV_LEVEL = 0x80;
+constexpr uint8_t IPMI_CC_EXCEEDS_USER_PRIV = 0x81;
+// bits 30 & 31 (MSB) hold the instanceID, hence shifting by 30 bits
+constexpr uint8_t myNetInstanceSessionIdShiftMask = 30;
+// bits 6 & 7 (MSB) hold the instanceID, hence shifting by 6 bits
+constexpr uint8_t myNetInstanceSessionHandleShiftMask = 6;
+
+/**
+ * @struct SetSessionPrivLevelReq
+ *
+ * IPMI Request data for Set Session Privilege Level command
+ */
+struct SetSessionPrivLevelReq
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t reqPrivLevel:4;
+ uint8_t reserved:4;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved:4;
+ uint8_t reqPrivLevel:4;
+#endif
+
+} __attribute__((packed));
+
+/**
+ * @struct SetSessionPrivLevelResp
+ *
+ * IPMI Response data for Set Session Privilege Level command
+ */
+struct SetSessionPrivLevelResp
+{
+ uint8_t completionCode;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t newPrivLevel:4;
+ uint8_t reserved:4;
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved:4;
+ uint8_t newPrivLevel:4;
+#endif
+
+} __attribute__((packed));
+
+/**
+ * @brief Set Session Privilege Command
+ *
+ * This command is sent in authenticated format. When a session is activated,
+ * the session is set to an initial privilege level. A session that is
+ * activated at a maximum privilege level of Callback is set to an initial
+ * privilege level of Callback and cannot be changed. All other sessions are
+ * initially set to USER level, regardless of the maximum privilege level
+ * requested in the RAKP Message 1.
+ *
+ * This command cannot be used to set a privilege level higher than the lowest
+ * of the privilege level set for the user(via the Set User Access command) and
+ * the privilege limit for the channel that was set via the Set Channel Access
+ * command.
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> setSessionPrivilegeLevel(
+ const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+/**
+ * @struct CloseSessionRequest
+ *
+ * IPMI Request data for Close Session command
+ */
+struct CloseSessionRequest
+{
+ uint32_t sessionID;
+ uint8_t sessionHandle;
+} __attribute__((packed));
+
+/**
+ * @struct CloseSessionResponse
+ *
+ * IPMI Response data for Close Session command
+ */
+struct CloseSessionResponse
+{
+ uint8_t completionCode;
+} __attribute__((packed));
+
+/**
+ * @brief Close Session Command
+ *
+ * This command is used to immediately terminate a session in progress. It is
+ * typically used to close the session that the user is communicating over,
+ * though it can be used to other terminate sessions in progress (provided that
+ * the user is operating at the appropriate privilege level, or the command is
+ * executed over a local channel - e.g. the system interface). Closing
+ * sessionless session ( session zero) is restricted in this command
+ *
+ * @param[in] inPayload - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+std::vector<uint8_t> closeSession(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+} // namespace command
diff --git a/transport/rmcpbridge/command/sol_cmds.cpp b/transport/rmcpbridge/command/sol_cmds.cpp
new file mode 100644
index 0000000..9bb92ce
--- /dev/null
+++ b/transport/rmcpbridge/command/sol_cmds.cpp
@@ -0,0 +1,74 @@
+#include "sol_cmds.hpp"
+
+#include "sessions_manager.hpp"
+#include "sol/sol_context.hpp"
+#include "sol/sol_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace sol
+{
+
+namespace command
+{
+
+using namespace phosphor::logging;
+
+std::vector<uint8_t> payloadHandler(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler)
+{
+ // Check inPayload size is at least Payload
+ if (inPayload.size() < sizeof(Payload))
+ {
+ return std::vector<uint8_t>();
+ }
+
+ auto request = reinterpret_cast<const Payload*>(inPayload.data());
+ auto solDataSize = inPayload.size() - sizeof(Payload);
+
+ std::vector<uint8_t> charData(solDataSize);
+ if (solDataSize > 0)
+ {
+ std::copy_n(inPayload.data() + sizeof(Payload), solDataSize,
+ charData.begin());
+ }
+
+ try
+ {
+ auto& context = sol::Manager::get().getContext(handler->sessionID);
+
+ context.processInboundPayload(
+ request->packetSeqNum, request->packetAckSeqNum,
+ request->acceptedCharCount, request->inOperation.ack,
+ request->inOperation.generateBreak, charData);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to call the getContext method: {ERROR}", "ERROR", e);
+ return std::vector<uint8_t>();
+ }
+
+ return std::vector<uint8_t>();
+}
+
+void activating(uint8_t payloadInstance, uint32_t sessionID)
+{
+ std::vector<uint8_t> outPayload(sizeof(ActivatingRequest));
+
+ auto request = reinterpret_cast<ActivatingRequest*>(outPayload.data());
+
+ request->sessionState = 0;
+ request->payloadInstance = payloadInstance;
+ request->majorVersion = MAJOR_VERSION;
+ request->minorVersion = MINOR_VERSION;
+
+ auto session = session::Manager::get().getSession(sessionID);
+
+ message::Handler msgHandler(session->channelPtr, sessionID);
+
+ msgHandler.sendUnsolicitedIPMIPayload(netfnTransport, solActivatingCmd,
+ outPayload);
+}
+} // namespace command
+
+} // namespace sol
diff --git a/transport/rmcpbridge/command/sol_cmds.hpp b/transport/rmcpbridge/command/sol_cmds.hpp
new file mode 100644
index 0000000..b18b292
--- /dev/null
+++ b/transport/rmcpbridge/command/sol_cmds.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <vector>
+
+namespace sol
+{
+
+namespace command
+{
+
+/** @brief SOL Payload Handler
+ *
+ * This command is used for activating and deactivating a payload type under a
+ * given IPMI session. The UDP Port number for SOL is the same as the port that
+ * was used to establish the IPMI session.
+ *
+ * @param[in] inPayload - Request data for the command.
+ * @param[in] handler - Reference to the message handler.
+ *
+ * @return Response data for the command.
+ */
+std::vector<uint8_t> payloadHandler(const std::vector<uint8_t>& inPayload,
+ std::shared_ptr<message::Handler>& handler);
+
+constexpr uint8_t netfnTransport = 0x0C;
+constexpr uint8_t solActivatingCmd = 0x20;
+
+/** @struct ActivatingRequest
+ *
+ * IPMI payload for SOL Activating command.
+ */
+struct ActivatingRequest
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t sessionState:4; //!< SOL session state.
+ uint8_t reserved:4; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved:4; //!< Reserved.
+ uint8_t sessionState:4; //!< SOL session state.
+#endif
+
+ uint8_t payloadInstance; //!< Payload instance.
+ uint8_t majorVersion; //!< SOL format major version
+ uint8_t minorVersion; //!< SOL format minor version
+} __attribute__((packed));
+
+/** @brief SOL Activating Command.
+ *
+ * This command provides a mechanism for the BMC to notify a remote application
+ * that a SOL payload is activating on another channel.The request message is a
+ * message that is asynchronously generated by the BMC. The BMC will not wait
+ * for a response from the remote console before dropping the serial connection
+ * to proceed with SOL, therefore the remote console does not need to respond
+ * to this command.
+ *
+ * @param[in] payloadInstance - SOL payload instance.
+ * @param[in] sessionID - IPMI session ID.
+ */
+void activating(uint8_t payloadInstance, uint32_t sessionID);
+
+} // namespace command
+
+} // namespace sol
diff --git a/transport/rmcpbridge/command_table.cpp b/transport/rmcpbridge/command_table.cpp
new file mode 100644
index 0000000..9880bc0
--- /dev/null
+++ b/transport/rmcpbridge/command_table.cpp
@@ -0,0 +1,161 @@
+#include "command_table.hpp"
+
+#include "main.hpp"
+#include "message_handler.hpp"
+#include "message_parsers.hpp"
+#include "sessions_manager.hpp"
+
+#include <ipmid/types.hpp>
+#include <main.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <user_channel/user_layer.hpp>
+
+#include <iomanip>
+
+namespace command
+{
+
+void Table::registerCommand(CommandID inCommand, std::unique_ptr<Entry>&& entry)
+{
+ auto& command = commandTable[inCommand.command];
+
+ if (command)
+ {
+ lg2::debug("Already Registered: {COMMAND}", "COMMAND",
+ inCommand.command);
+ return;
+ }
+
+ command = std::move(entry);
+}
+
+void Table::executeCommand(uint32_t inCommand,
+ std::vector<uint8_t>& commandData,
+ std::shared_ptr<message::Handler> handler)
+{
+ using namespace std::chrono_literals;
+
+ auto iterator = commandTable.find(inCommand);
+
+ if (iterator == commandTable.end())
+ {
+ CommandID command(inCommand);
+
+ // Do not forward any session zero commands to ipmid
+ if (handler->sessionID == session::sessionZero)
+ {
+ lg2::info(
+ "Table: refuse to forward session-zero command: lun: {LUN}, netFn: {NETFN}, command: {COMMAND}",
+ "LUN", command.lun(), "NETFN", command.netFn(), "COMMAND",
+ command.cmd());
+ return;
+ }
+ std::shared_ptr<session::Session> session =
+ session::Manager::get().getSession(handler->sessionID);
+
+ // Ignore messages that are not part of an active session
+ auto state = static_cast<session::State>(session->state());
+ if (state != session::State::active)
+ {
+ return;
+ }
+
+ auto bus = getSdBus();
+ // forward the request onto the main ipmi queue
+ using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
+ std::vector<uint8_t>>;
+ uint8_t lun = command.lun();
+ uint8_t netFn = command.netFn();
+ uint8_t cmd = command.cmd();
+
+ std::map<std::string, ipmi::Value> options = {
+ {"userId", ipmi::Value(static_cast<int>(
+ ipmi::ipmiUserGetUserId(session->userName)))},
+ {"privilege",
+ ipmi::Value(static_cast<int>(session->currentPrivilege()))},
+ {"currentSessionId",
+ ipmi::Value(static_cast<uint32_t>(session->getBMCSessionID()))},
+ };
+ bus->async_method_call(
+ [handler](const boost::system::error_code& ec,
+ const IpmiDbusRspType& response) {
+ if (!ec)
+ {
+ const uint8_t& cc = std::get<3>(response);
+ const std::vector<uint8_t>& responseData =
+ std::get<4>(response);
+ std::vector<uint8_t> payload;
+ payload.reserve(1 + responseData.size());
+ payload.push_back(cc);
+ payload.insert(payload.end(), responseData.begin(),
+ responseData.end());
+ handler->outPayload = std::move(payload);
+ }
+ else
+ {
+ std::vector<uint8_t> payload;
+ payload.push_back(IPMI_CC_UNSPECIFIED_ERROR);
+ handler->outPayload = std::move(payload);
+ }
+ },
+ "xyz.openbmc_project.Ipmi.Host", "/xyz/openbmc_project/Ipmi",
+ "xyz.openbmc_project.Ipmi.Server", "execute", netFn, lun, cmd,
+ commandData, options);
+ }
+ else
+ {
+ auto start = std::chrono::steady_clock::now();
+
+ // Ignore messages that are not part of an active/pre-active session
+ if (handler->sessionID != session::sessionZero)
+ {
+ std::shared_ptr<session::Session> session =
+ session::Manager::get().getSession(handler->sessionID);
+ auto state = static_cast<session::State>(session->state());
+ if ((state != session::State::setupInProgress) &&
+ (state != session::State::active))
+ {
+ return;
+ }
+ }
+
+ handler->outPayload =
+ iterator->second->executeCommand(commandData, handler);
+
+ auto end = std::chrono::steady_clock::now();
+
+ std::chrono::duration<size_t> elapsedSeconds =
+ std::chrono::duration_cast<std::chrono::seconds>(end - start);
+
+ // If command time execution time exceeds 2 seconds, log a time
+ // exceeded message
+ if (elapsedSeconds > 2s)
+ {
+ lg2::error("IPMI command timed out: {DELAY}", "DELAY",
+ elapsedSeconds.count());
+ }
+ }
+}
+
+std::vector<uint8_t> NetIpmidEntry::executeCommand(
+ std::vector<uint8_t>& commandData,
+ std::shared_ptr<message::Handler> handler)
+{
+ std::vector<uint8_t> errResponse;
+
+ // Check if the command qualifies to be run prior to establishing a session
+ if (!sessionless && (handler->sessionID == session::sessionZero))
+ {
+ errResponse.resize(1);
+ errResponse[0] = IPMI_CC_INSUFFICIENT_PRIVILEGE;
+ lg2::info(
+ "Table: Insufficient privilege for command: lun: {LUN}, netFn: {NETFN}, command: {COMMAND}",
+ "LUN", command.lun(), "NETFN", command.netFn(), "COMMAND",
+ command.cmd());
+ return errResponse;
+ }
+
+ return functor(commandData, handler);
+}
+
+} // namespace command
diff --git a/transport/rmcpbridge/command_table.hpp b/transport/rmcpbridge/command_table.hpp
new file mode 100644
index 0000000..3a5e344
--- /dev/null
+++ b/transport/rmcpbridge/command_table.hpp
@@ -0,0 +1,276 @@
+#pragma once
+
+#include "message_handler.hpp"
+
+#include <ipmid/api.h>
+
+#include <cstddef>
+#include <functional>
+#include <map>
+
+namespace command
+{
+
+struct CommandID
+{
+ static constexpr size_t lunBits = 2;
+ CommandID(uint32_t command) : command(command) {}
+
+ uint8_t netFnLun() const
+ {
+ return static_cast<uint8_t>(command >> CHAR_BIT);
+ }
+ uint8_t netFn() const
+ {
+ return netFnLun() >> lunBits;
+ }
+ uint8_t lun() const
+ {
+ return netFnLun() & ((1 << (lunBits + 1)) - 1);
+ }
+ uint8_t cmd() const
+ {
+ return static_cast<uint8_t>(command);
+ }
+ uint32_t command;
+};
+
+/**
+ * CommandFunctor is the functor register for commands defined in
+ * phosphor-net-ipmid. This would take the request part of the command as a
+ * vector and a reference to the message handler. The response part of the
+ * command is returned as a vector.
+ */
+using CommandFunctor = std::function<std::vector<uint8_t>(
+ const std::vector<uint8_t>&, std::shared_ptr<message::Handler>&)>;
+
+/**
+ * @struct CmdDetails
+ *
+ * Command details is used to register commands supported in phosphor-net-ipmid.
+ */
+struct CmdDetails
+{
+ CommandID command;
+ CommandFunctor functor;
+ session::Privilege privilege;
+ bool sessionless;
+};
+
+/**
+ * @enum NetFns
+ *
+ * A field that identifies the functional class of the message. The Network
+ * Function clusters IPMI commands into different sets.
+ */
+enum class NetFns
+{
+ CHASSIS = (0x00 << 10),
+ CHASSIS_RESP = (0x01 << 10),
+
+ BRIDGE = (0x02 << 10),
+ BRIDGE_RESP = (0x03 << 10),
+
+ SENSOR = (0x04 << 10),
+ SENSOR_RESP = (0x05 << 10),
+ EVENT = (0x04 << 10),
+ EVENT_RESP = (0x05 << 10),
+
+ APP = (0x06 << 10),
+ APP_RESP = (0x07 << 10),
+
+ FIRMWARE = (0x08 << 10),
+ FIRMWARE_RESP = (0x09 << 10),
+
+ STORAGE = (0x0A << 10),
+ STORAGE_RESP = (0x0B << 10),
+
+ TRANSPORT = (0x0C << 10),
+ TRANSPORT_RESP = (0x0D << 10),
+
+ //>>
+ RESERVED_START = (0x0E << 10),
+ RESERVED_END = (0x2B << 10),
+ //<<
+
+ GROUP_EXTN = (0x2C << 10),
+ GROUP_EXTN_RESP = (0x2D << 10),
+
+ OEM = (0x2E << 10),
+ OEM_RESP = (0x2F << 10),
+};
+
+/**
+ * @class Entry
+ *
+ * This is the base class for registering IPMI commands. There are two ways of
+ * registering commands to phosphor-net-ipmid, the session related commands and
+ * provider commands
+ *
+ * Every commands has a privilege level which mentions the minimum session
+ * privilege level needed to execute the command
+ */
+
+class Entry
+{
+ public:
+ Entry(CommandID command, session::Privilege privilege) :
+ command(command), privilege(privilege)
+ {}
+
+ /**
+ * @brief Execute the command
+ *
+ * Execute the command
+ *
+ * @param[in] commandData - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+ virtual std::vector<uint8_t> executeCommand(
+ std::vector<uint8_t>& commandData,
+ std::shared_ptr<message::Handler> handler) = 0;
+
+ auto getCommand() const
+ {
+ return command;
+ }
+
+ auto getPrivilege() const
+ {
+ return privilege;
+ }
+
+ virtual ~Entry() = default;
+ Entry(const Entry&) = default;
+ Entry& operator=(const Entry&) = default;
+ Entry(Entry&&) = default;
+ Entry& operator=(Entry&&) = default;
+
+ protected:
+ CommandID command;
+
+ // Specifies the minimum privilege level required to execute this command
+ session::Privilege privilege;
+};
+
+/**
+ * @class NetIpmidEntry
+ *
+ * NetIpmidEntry is used to register commands that are consumed only in
+ * phosphor-net-ipmid. The RAKP commands, session commands and user management
+ * commands are examples of this.
+ *
+ * There are certain IPMI commands that can be executed before session can be
+ * established like Get System GUID, Get Channel Authentication Capabilities
+ * and RAKP commands.
+ */
+class NetIpmidEntry final : public Entry
+{
+ public:
+ NetIpmidEntry(CommandID command, CommandFunctor functor,
+ session::Privilege privilege, bool sessionless) :
+ Entry(command, privilege), functor(functor), sessionless(sessionless)
+ {}
+
+ /**
+ * @brief Execute the command
+ *
+ * Execute the command
+ *
+ * @param[in] commandData - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ * @return Response data for the command
+ */
+ std::vector<uint8_t> executeCommand(
+ std::vector<uint8_t>& commandData,
+ std::shared_ptr<message::Handler> handler) override;
+
+ virtual ~NetIpmidEntry() = default;
+ NetIpmidEntry(const NetIpmidEntry&) = default;
+ NetIpmidEntry& operator=(const NetIpmidEntry&) = default;
+ NetIpmidEntry(NetIpmidEntry&&) = default;
+ NetIpmidEntry& operator=(NetIpmidEntry&&) = default;
+
+ private:
+ CommandFunctor functor;
+
+ bool sessionless;
+};
+
+/**
+ * @class Table
+ *
+ * Table keeps the IPMI command entries as a sorted associative container with
+ * Command ID as the unique key. It has interfaces for registering commands
+ * and executing a command.
+ */
+class Table
+{
+ private:
+ struct Private
+ {};
+
+ public:
+ explicit Table(const Private&) {}
+ Table() = delete;
+ ~Table() = default;
+ // Command Table is a singleton so copy, copy-assignment, move and
+ // move assignment is deleted
+ Table(const Table&) = delete;
+ Table& operator=(const Table&) = delete;
+ Table(Table&&) = default;
+ Table& operator=(Table&&) = default;
+
+ /**
+ * @brief Get a reference to the singleton Table
+ *
+ * @return Table reference
+ */
+ static Table& get()
+ {
+ static std::shared_ptr<Table> ptr = nullptr;
+ if (!ptr)
+ {
+ ptr = std::make_shared<Table>(Private());
+ }
+ return *ptr;
+ }
+
+ using CommandTable = std::map<uint32_t, std::unique_ptr<Entry>>;
+
+ /**
+ * @brief Register a command
+ *
+ * Register a command with the command table
+ *
+ * @param[in] inCommand - Command ID
+ * @param[in] entry - Command Entry
+ *
+ * @return: None
+ *
+ * @note: Duplicate registrations will be rejected.
+ *
+ */
+ void registerCommand(CommandID inCommand, std::unique_ptr<Entry>&& entry);
+
+ /**
+ * @brief Execute the command
+ *
+ * Execute the command for the corresponding CommandID
+ *
+ * @param[in] inCommand - Command ID to execute.
+ * @param[in] commandData - Request Data for the command
+ * @param[in] handler - Reference to the Message Handler
+ *
+ */
+ void executeCommand(uint32_t inCommand, std::vector<uint8_t>& commandData,
+ std::shared_ptr<message::Handler> handler);
+
+ private:
+ CommandTable commandTable;
+};
+
+} // namespace command
diff --git a/transport/rmcpbridge/crypt_algo.cpp b/transport/rmcpbridge/crypt_algo.cpp
new file mode 100644
index 0000000..69c333d
--- /dev/null
+++ b/transport/rmcpbridge/crypt_algo.cpp
@@ -0,0 +1,213 @@
+#include "crypt_algo.hpp"
+
+#include "message_parsers.hpp"
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+
+#include <algorithm>
+#include <numeric>
+
+namespace cipher
+{
+
+namespace crypt
+{
+
+constexpr std::array<uint8_t, AlgoAES128::AESCBC128BlockSize - 1>
+ AlgoAES128::confPadBytes;
+
+std::vector<uint8_t> AlgoAES128::decryptPayload(
+ const std::vector<uint8_t>& packet, const size_t sessHeaderLen,
+ const size_t payloadLen) const
+{
+ // verify packet size minimal: sessHeaderLen + payloadLen
+ // and payloadLen is more than AESCBC128ConfHeader
+ if (packet.size() < (sessHeaderLen + payloadLen) ||
+ payloadLen < AESCBC128ConfHeader)
+ {
+ throw std::runtime_error("Invalid data length");
+ }
+
+ auto plainPayload =
+ decryptData(packet.data() + sessHeaderLen,
+ packet.data() + sessHeaderLen + AESCBC128ConfHeader,
+ payloadLen - AESCBC128ConfHeader);
+
+ /*
+ * The confidentiality pad length is the last byte in the payload, it would
+ * tell the number of pad bytes in the payload. We added a condition, so
+ * that buffer overrun doesn't happen.
+ */
+ size_t confPadLength = plainPayload.back();
+ auto padLength = std::min(plainPayload.size() - 1, confPadLength);
+
+ auto plainPayloadLen = plainPayload.size() - padLength - 1;
+
+ // Additional check if the confidentiality pad bytes are as expected
+ if (!std::equal(plainPayload.begin() + plainPayloadLen,
+ plainPayload.begin() + plainPayloadLen + padLength,
+ confPadBytes.begin()))
+ {
+ throw std::runtime_error("Confidentiality pad bytes check failed");
+ }
+
+ plainPayload.resize(plainPayloadLen);
+
+ return plainPayload;
+}
+
+std::vector<uint8_t> AlgoAES128::encryptPayload(
+ std::vector<uint8_t>& payload) const
+{
+ auto payloadLen = payload.size();
+
+ /*
+ * The following logic calculates the number of padding bytes to be added to
+ * the payload data. This would ensure that the length is a multiple of the
+ * block size of algorithm being used. For the AES algorithm, the block size
+ * is 16 bytes.
+ */
+ auto paddingLen = AESCBC128BlockSize - ((payloadLen + 1) & 0xF);
+
+ /*
+ * The additional field is for the Confidentiality Pad Length field. For the
+ * AES algorithm, this number will range from 0 to 15 bytes. This field is
+ * mandatory.
+ */
+ payload.resize(payloadLen + paddingLen + 1);
+
+ /*
+ * If no Confidentiality Pad bytes are required, the Confidentiality Pad
+ * Length field is set to 00h. If present, the value of the first byte of
+ * Confidentiality Pad shall be one (01h) and all subsequent bytes shall
+ * have a monotonically increasing value (e.g., 02h, 03h, 04h, etc).
+ */
+ if (0 != paddingLen)
+ {
+ std::iota(payload.begin() + payloadLen,
+ payload.begin() + payloadLen + paddingLen, 1);
+ }
+
+ payload.back() = paddingLen;
+
+ return encryptData(payload.data(), payload.size());
+}
+
+std::vector<uint8_t> AlgoAES128::decryptData(
+ const uint8_t* iv, const uint8_t* input, const int inputLen) const
+{
+ // Initializes Cipher context
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+
+ auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };
+
+ std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(
+ ctx, cleanupFunc);
+
+ /*
+ * EVP_DecryptInit_ex sets up cipher context ctx for encryption with type
+ * AES-CBC-128. ctx must be initialized before calling this function. K2 is
+ * the symmetric key used and iv is the initialization vector used.
+ */
+ if (!EVP_DecryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
+ iv))
+ {
+ throw std::runtime_error("EVP_DecryptInit_ex failed for type "
+ "AES-CBC-128");
+ }
+
+ /*
+ * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
+ * parameter is zero then no padding is performed. This function always
+ * returns 1.
+ */
+ EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);
+
+ std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);
+
+ int outputLen = 0;
+
+ /*
+ * If padding is disabled then EVP_DecryptFinal_ex() will not encrypt any
+ * more data and it will return an error if any data remains in a partial
+ * block: that is if the total data length is not a multiple of the block
+ * size. Since AES-CBC-128 encrypted payload format adds padding bytes and
+ * ensures that payload is a multiple of block size, we are not making the
+ * call to EVP_DecryptFinal_ex().
+ */
+ if (!EVP_DecryptUpdate(ctxPtr.get(), output.data(), &outputLen, input,
+ inputLen))
+ {
+ throw std::runtime_error("EVP_DecryptUpdate failed");
+ }
+
+ output.resize(outputLen);
+
+ return output;
+}
+
+std::vector<uint8_t> AlgoAES128::encryptData(const uint8_t* input,
+ const int inputLen) const
+{
+ std::vector<uint8_t> output(inputLen + AESCBC128BlockSize);
+
+ // Generate the initialization vector
+ if (!RAND_bytes(output.data(), AESCBC128ConfHeader))
+ {
+ throw std::runtime_error("RAND_bytes failed");
+ }
+
+ // Initializes Cipher context
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+
+ auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); };
+
+ std::unique_ptr<EVP_CIPHER_CTX, decltype(cleanupFunc)> ctxPtr(
+ ctx, cleanupFunc);
+
+ /*
+ * EVP_EncryptInit_ex sets up cipher context ctx for encryption with type
+ * AES-CBC-128. ctx must be initialized before calling this function. K2 is
+ * the symmetric key used and iv is the initialization vector used.
+ */
+ if (!EVP_EncryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(),
+ output.data()))
+ {
+ throw std::runtime_error("EVP_EncryptInit_ex failed for type "
+ "AES-CBC-128");
+ }
+
+ /*
+ * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad
+ * parameter is zero then no padding is performed. This function always
+ * returns 1.
+ */
+ EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0);
+
+ int outputLen = 0;
+
+ /*
+ * If padding is disabled then EVP_EncryptFinal_ex() will not encrypt any
+ * more data and it will return an error if any data remains in a partial
+ * block: that is if the total data length is not a multiple of the block
+ * size. Since we are adding padding bytes and ensures that payload is a
+ * multiple of block size, we are not making the call to
+ * EVP_DecryptFinal_ex()
+ */
+ if (!EVP_EncryptUpdate(ctxPtr.get(), output.data() + AESCBC128ConfHeader,
+ &outputLen, input, inputLen))
+ {
+ throw std::runtime_error("EVP_EncryptUpdate failed for type "
+ "AES-CBC-128");
+ }
+
+ output.resize(AESCBC128ConfHeader + outputLen);
+
+ return output;
+}
+
+} // namespace crypt
+
+} // namespace cipher
diff --git a/transport/rmcpbridge/crypt_algo.hpp b/transport/rmcpbridge/crypt_algo.hpp
new file mode 100644
index 0000000..534f1a0
--- /dev/null
+++ b/transport/rmcpbridge/crypt_algo.hpp
@@ -0,0 +1,204 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+namespace cipher
+{
+
+namespace crypt
+{
+
+/**
+ * @enum Confidentiality Algorithms
+ *
+ * The Confidentiality Algorithm Number specifies the encryption/decryption
+ * algorithm field that is used for encrypted payload data under the session.
+ * The ‘encrypted’ bit in the payload type field being set identifies packets
+ * with payloads that include data that is encrypted per this specification.
+ * When payload data is encrypted, there may be additional “Confidentiality
+ * Header” and/or “Confidentiality Trailer” fields that are included within the
+ * payload. The size and definition of those fields is specific to the
+ * particular confidentiality algorithm. Based on security recommendations
+ * encrypting IPMI traffic is preferred, so NONE is not supported.
+ */
+enum class Algorithms : uint8_t
+{
+ NONE, /**< No encryption (mandatory , not supported) */
+ AES_CBC_128, /**< AES-CBC-128 Algorithm (mandatory option) */
+ xRC4_128, /**< xRC4-128 Algorithm (optional option) */
+ xRC4_40, /**< xRC4-40 Algorithm (optional option) */
+};
+
+/**
+ * @class Interface
+ *
+ * Interface is the base class for the Confidentiality Algorithms.
+ */
+class Interface
+{
+ public:
+ /**
+ * @brief Constructor for Interface
+ */
+ explicit Interface(const std::vector<uint8_t>& k2) : k2(k2) {}
+
+ Interface() = delete;
+ virtual ~Interface() = default;
+ Interface(const Interface&) = default;
+ Interface& operator=(const Interface&) = default;
+ Interface(Interface&&) = default;
+ Interface& operator=(Interface&&) = default;
+
+ /**
+ * @brief Decrypt the incoming payload
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] sessHeaderLen - Length of the IPMI Session Header
+ * @param[in] payloadLen - Length of the encrypted IPMI payload
+ *
+ * @return decrypted payload if the operation is successful
+ */
+ virtual std::vector<uint8_t> decryptPayload(
+ const std::vector<uint8_t>& packet, const size_t sessHeaderLen,
+ const size_t payloadLen) const = 0;
+
+ /**
+ * @brief Encrypt the outgoing payload
+ *
+ * @param[in] payload - plain payload for the outgoing IPMI packet
+ *
+ * @return encrypted payload if the operation is successful
+ *
+ */
+ virtual std::vector<uint8_t> encryptPayload(
+ std::vector<uint8_t>& payload) const = 0;
+
+ /**
+ * @brief Check if the Confidentiality algorithm is supported
+ *
+ * @param[in] algo - confidentiality algorithm
+ *
+ * @return true if algorithm is supported else false
+ *
+ */
+ static bool isAlgorithmSupported(Algorithms algo)
+ {
+ if (algo == Algorithms::AES_CBC_128)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ protected:
+ /**
+ * @brief The Cipher Key is the first 128-bits of key “K2”, K2 is
+ * generated by processing a pre-defined constant keyed by Session
+ * Integrity Key (SIK) that was created during session activation.
+ */
+ std::vector<uint8_t> k2;
+};
+
+/**
+ * @class AlgoAES128
+ *
+ * @brief Implementation of the AES-CBC-128 Confidentiality algorithm
+ *
+ * AES-128 uses a 128-bit Cipher Key. The Cipher Key is the first 128-bits of
+ * key “K2”.Once the Cipher Key has been generated it is used to encrypt
+ * the payload data. The payload data is padded to make it an integral numbers
+ * of blocks in length (a block is 16 bytes for AES). The payload is then
+ * encrypted one block at a time from the lowest data offset to the highest
+ * using Cipher_Key as specified in AES.
+ */
+class AlgoAES128 final : public Interface
+{
+ public:
+ static constexpr size_t AESCBC128ConfHeader = 16;
+ static constexpr size_t AESCBC128BlockSize = 16;
+
+ /**
+ * If confidentiality bytes are present, the value of the first byte is
+ * one (01h). and all subsequent bytes shall have a monotonically
+ * increasing value (e.g., 02h, 03h, 04h, etc). The receiver, as an
+ * additional check for proper decryption, shall check the value of each
+ * byte of Confidentiality Pad. For AES algorithm, the pad bytes will
+ * range from 0 to 15 bytes. This predefined array would help in
+ * doing the additional check.
+ */
+ static constexpr std::array<uint8_t, AESCBC128BlockSize - 1> confPadBytes =
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
+
+ /**
+ * @brief Constructor for AlgoAES128
+ *
+ * @param[in] - Session Integrity key
+ */
+ explicit AlgoAES128(const std::vector<uint8_t>& k2) : Interface(k2) {}
+
+ AlgoAES128() = delete;
+ ~AlgoAES128() = default;
+ AlgoAES128(const AlgoAES128&) = default;
+ AlgoAES128& operator=(const AlgoAES128&) = default;
+ AlgoAES128(AlgoAES128&&) = default;
+ AlgoAES128& operator=(AlgoAES128&&) = default;
+
+ /**
+ * @brief Decrypt the incoming payload
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] sessHeaderLen - Length of the IPMI Session Header
+ * @param[in] payloadLen - Length of the encrypted IPMI payload
+ *
+ * @return decrypted payload if the operation is successful
+ */
+ std::vector<uint8_t> decryptPayload(const std::vector<uint8_t>& packet,
+ const size_t sessHeaderLen,
+ const size_t payloadLen) const override;
+
+ /**
+ * @brief Encrypt the outgoing payload
+ *
+ * @param[in] payload - plain payload for the outgoing IPMI packet
+ *
+ * @return encrypted payload if the operation is successful
+ *
+ */
+ std::vector<uint8_t> encryptPayload(
+ std::vector<uint8_t>& payload) const override;
+
+ private:
+ /**
+ * @brief Decrypt the passed data
+ *
+ * @param[in] iv - Initialization vector
+ * @param[in] input - Pointer to input data
+ * @param[in] inputLen - Length of input data
+ *
+ * @return decrypted data if the operation is successful
+ */
+ std::vector<uint8_t> decryptData(const uint8_t* iv, const uint8_t* input,
+ const int inputLen) const;
+
+ /**
+ * @brief Encrypt the passed data
+ *
+ * @param[in] input - Pointer to input data
+ * @param[in] inputLen - Length of input data
+ *
+ * @return encrypted data if the operation is successful
+ */
+ std::vector<uint8_t> encryptData(const uint8_t* input,
+ const int inputLen) const;
+};
+
+} // namespace crypt
+
+} // namespace cipher
diff --git a/transport/rmcpbridge/endian.hpp b/transport/rmcpbridge/endian.hpp
new file mode 100644
index 0000000..446e879
--- /dev/null
+++ b/transport/rmcpbridge/endian.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <endian.h>
+#include <stdint.h>
+
+namespace endian
+{
+namespace details
+{
+template <typename T>
+struct convert
+{
+ static T to_ipmi(T) = delete;
+ static T from_ipmi(T) = delete;
+ static T to_network(T) = delete;
+ static T from_network(T) = delete;
+};
+
+template <>
+struct convert<uint16_t>
+{
+ static uint16_t to_ipmi(uint16_t i)
+ {
+ return htole16(i);
+ };
+ static uint16_t from_ipmi(uint16_t i)
+ {
+ return le16toh(i);
+ };
+ static uint16_t to_network(uint16_t i)
+ {
+ return htobe16(i);
+ };
+ static uint16_t from_network(uint16_t i)
+ {
+ return be16toh(i);
+ };
+};
+
+template <>
+struct convert<uint32_t>
+{
+ static uint32_t to_ipmi(uint32_t i)
+ {
+ return htole32(i);
+ };
+ static uint32_t from_ipmi(uint32_t i)
+ {
+ return le32toh(i);
+ };
+ static uint32_t to_network(uint32_t i)
+ {
+ return htobe32(i);
+ };
+ static uint32_t from_network(uint32_t i)
+ {
+ return be32toh(i);
+ };
+};
+} // namespace details
+
+template <typename T>
+T to_ipmi(T i)
+{
+ return details::convert<T>::to_ipmi(i);
+}
+
+template <typename T>
+T from_ipmi(T i)
+{
+ return details::convert<T>::from_ipmi(i);
+}
+
+template <typename T>
+T to_network(T i)
+{
+ return details::convert<T>::to_network(i);
+}
+template <typename T>
+T from_network(T i)
+{
+ return details::convert<T>::from_network(i);
+}
+} // namespace endian
diff --git a/transport/rmcpbridge/integrity_algo.cpp b/transport/rmcpbridge/integrity_algo.cpp
new file mode 100644
index 0000000..3110fff
--- /dev/null
+++ b/transport/rmcpbridge/integrity_algo.cpp
@@ -0,0 +1,149 @@
+#include "integrity_algo.hpp"
+
+#include "message_parsers.hpp"
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+namespace cipher
+{
+
+namespace integrity
+{
+
+AlgoSHA1::AlgoSHA1(const std::vector<uint8_t>& sik) :
+ Interface(SHA1_96_AUTHCODE_LENGTH)
+{
+ k1 = generateKn(sik, rmcp::const_1);
+}
+
+std::vector<uint8_t> AlgoSHA1::generateHMAC(const uint8_t* input,
+ const size_t len) const
+{
+ std::vector<uint8_t> output(SHA_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+
+ if (HMAC(EVP_sha1(), k1.data(), k1.size(), input, len, output.data(),
+ &mdLen) == NULL)
+ {
+ throw std::runtime_error("Generating integrity data failed");
+ }
+
+ // HMAC generates Message Digest to the size of SHA_DIGEST_LENGTH, the
+ // AuthCode field length is based on the integrity algorithm. So we are
+ // interested only in the AuthCode field length of the generated Message
+ // digest.
+ output.resize(authCodeLength);
+
+ return output;
+}
+
+bool AlgoSHA1::verifyIntegrityData(
+ const std::vector<uint8_t>& packet, const size_t length,
+ std::vector<uint8_t>::const_iterator integrityDataBegin,
+ std::vector<uint8_t>::const_iterator integrityDataEnd) const
+{
+ auto output = generateHMAC(
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length);
+
+ // Verify if the generated integrity data for the packet and the received
+ // integrity data matches.
+ return (std::equal(output.begin(), output.end(), integrityDataBegin,
+ integrityDataEnd));
+}
+
+std::vector<uint8_t> AlgoSHA1::generateIntegrityData(
+ const std::vector<uint8_t>& packet) const
+{
+ return generateHMAC(
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE,
+ packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE);
+}
+
+std::vector<uint8_t> AlgoSHA1::generateKn(const std::vector<uint8_t>& sik,
+ const rmcp::Const_n& const_n) const
+{
+ unsigned int mdLen = 0;
+ std::vector<uint8_t> Kn(sik.size());
+
+ // Generated Kn for the integrity algorithm with the additional key keyed
+ // with SIK.
+ if (HMAC(EVP_sha1(), sik.data(), sik.size(), const_n.data(), const_n.size(),
+ Kn.data(), &mdLen) == NULL)
+ {
+ throw std::runtime_error("Generating KeyN for integrity "
+ "algorithm failed");
+ }
+ return Kn;
+}
+
+AlgoSHA256::AlgoSHA256(const std::vector<uint8_t>& sik) :
+ Interface(SHA256_128_AUTHCODE_LENGTH)
+{
+ k1 = generateKn(sik, rmcp::const_1);
+}
+
+std::vector<uint8_t> AlgoSHA256::generateHMAC(const uint8_t* input,
+ const size_t len) const
+{
+ std::vector<uint8_t> output(SHA256_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+
+ if (HMAC(EVP_sha256(), k1.data(), k1.size(), input, len, output.data(),
+ &mdLen) == NULL)
+ {
+ throw std::runtime_error("Generating HMAC_SHA256_128 failed");
+ }
+
+ // HMAC generates Message Digest to the size of SHA256_DIGEST_LENGTH, the
+ // AuthCode field length is based on the integrity algorithm. So we are
+ // interested only in the AuthCode field length of the generated Message
+ // digest.
+ output.resize(authCodeLength);
+
+ return output;
+}
+
+bool AlgoSHA256::verifyIntegrityData(
+ const std::vector<uint8_t>& packet, const size_t length,
+ std::vector<uint8_t>::const_iterator integrityDataBegin,
+ std::vector<uint8_t>::const_iterator integrityDataEnd) const
+{
+ auto output = generateHMAC(
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length);
+
+ // Verify if the generated integrity data for the packet and the received
+ // integrity data matches.
+ return (std::equal(output.begin(), output.end(), integrityDataBegin,
+ integrityDataEnd));
+}
+
+std::vector<uint8_t> AlgoSHA256::generateIntegrityData(
+ const std::vector<uint8_t>& packet) const
+{
+ return generateHMAC(
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE,
+ packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE);
+}
+
+std::vector<uint8_t> AlgoSHA256::generateKn(const std::vector<uint8_t>& sik,
+ const rmcp::Const_n& const_n) const
+{
+ unsigned int mdLen = 0;
+ std::vector<uint8_t> Kn(sik.size());
+
+ // Generated Kn for the integrity algorithm with the additional key keyed
+ // with SIK.
+ if (HMAC(EVP_sha256(), sik.data(), sik.size(), const_n.data(),
+ const_n.size(), Kn.data(), &mdLen) == NULL)
+ {
+ throw std::runtime_error("Generating KeyN for integrity "
+ "algorithm HMAC_SHA256 failed");
+ }
+ return Kn;
+}
+
+} // namespace integrity
+
+} // namespace cipher
diff --git a/transport/rmcpbridge/integrity_algo.hpp b/transport/rmcpbridge/integrity_algo.hpp
new file mode 100644
index 0000000..4d6674f
--- /dev/null
+++ b/transport/rmcpbridge/integrity_algo.hpp
@@ -0,0 +1,321 @@
+#pragma once
+
+#include "rmcp.hpp"
+
+#include <array>
+#include <cstddef>
+#include <vector>
+
+namespace cipher
+{
+
+namespace integrity
+{
+
+/**
+ * @enum Integrity Algorithms
+ *
+ * The Integrity Algorithm Number specifies the algorithm used to generate the
+ * contents for the AuthCode “signature” field that accompanies authenticated
+ * IPMI v2.0/RMCP+ messages once the session has been established. If the
+ * Integrity Algorithm is none the AuthCode value is not calculated and the
+ * AuthCode field in the message is not present. Based on security
+ * recommendations NONE will not be supported.
+ */
+enum class Algorithms : uint8_t
+{
+ NONE, // Mandatory (implemented, not supported)
+ HMAC_SHA1_96, // Mandatory (implemented, default choice in ipmitool)
+ HMAC_MD5_128, // Optional (not implemented)
+ MD5_128, // Optional (not implemented)
+ HMAC_SHA256_128, // Optional (implemented, best available)
+};
+
+/**
+ * @class Interface
+ *
+ * Interface is the base class for the Integrity Algorithms.
+ * Unless otherwise specified, the integrity algorithm is applied to the packet
+ * data starting with the AuthType/Format field up to and including the field
+ * that immediately precedes the AuthCode field itself.
+ */
+class Interface
+{
+ public:
+ /**
+ * @brief Constructor for Interface
+ *
+ * @param[in] - AuthCode length
+ */
+ explicit Interface(size_t authLength) : authCodeLength(authLength) {}
+
+ Interface() = delete;
+ virtual ~Interface() = default;
+ Interface(const Interface&) = default;
+ Interface& operator=(const Interface&) = default;
+ Interface(Interface&&) = default;
+ Interface& operator=(Interface&&) = default;
+
+ /**
+ * @brief Verify the integrity data of the packet
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] packetLen - Packet length excluding authCode
+ * @param[in] integrityDataBegin - Begin iterator to the authCode in the
+ * packet
+ * @param[in] integrityDataEnd - End to the authCode in the packet
+ *
+ * @return true if authcode in the packet is equal to one generated
+ * using integrity algorithm on the packet data, false otherwise
+ */
+ bool virtual verifyIntegrityData(
+ const std::vector<uint8_t>& packet, const size_t packetLen,
+ std::vector<uint8_t>::const_iterator integrityDataBegin,
+ std::vector<uint8_t>::const_iterator integrityDataEnd) const = 0;
+
+ /**
+ * @brief Generate integrity data for the outgoing IPMI packet
+ *
+ * @param[in] input - Outgoing IPMI packet
+ *
+ * @return authcode for the outgoing IPMI packet
+ *
+ */
+ std::vector<uint8_t> virtual generateIntegrityData(
+ const std::vector<uint8_t>& input) const = 0;
+
+ /**
+ * @brief Check if the Integrity algorithm is supported
+ *
+ * @param[in] algo - integrity algorithm
+ *
+ * @return true if algorithm is supported else false
+ *
+ */
+ static bool isAlgorithmSupported(Algorithms algo)
+ {
+ if (algo == Algorithms::HMAC_SHA256_128)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * @brief Generate additional keying material based on SIK
+ *
+ * @note
+ * The IPMI 2.0 spec only states that the additional keying material is
+ * generated by running HMAC(constN) using SIK as the key. It does not
+ * state whether this is the integrity algorithm or the authentication
+ * algorithm. Other implementations of the RMCP+ algorithm (ipmitool
+ * and ipmiutil) are not consistent on this matter. But it does not
+ * really matter because based on any of the defined cipher suites, the
+ * integrity and authentication algorithms are both based on the same
+ * digest method (integrity::Algorithms::HMAC_SHA1_96 uses SHA1 and
+ * rakp_auth::Algorithms::RAKP_HMAC_SHA1 uses SHA1). None of the
+ * defined cipher suites mix and match digests for integrity and
+ * authentication. Generating Kn belongs in either the integrity or
+ * authentication classes, so in this implementation, integrity has
+ * been chosen.
+ *
+ * @param[in] sik - session integrity key
+ * @param[in] data - 20-byte Const_n
+ *
+ * @return on success returns the Kn based on this integrity class
+ *
+ */
+ std::vector<uint8_t> virtual generateKn(
+ const std::vector<uint8_t>& sik, const rmcp::Const_n& data) const = 0;
+
+ /** @brief Authcode field
+ *
+ * AuthCode field length varies based on the integrity algorithm, for
+ * HMAC-SHA1-96 the authcode field is 12 bytes. For HMAC-SHA256-128 and
+ * HMAC-MD5-128 the authcode field is 16 bytes.
+ */
+ size_t authCodeLength;
+
+ protected:
+ /** @brief K1 key used to generated the integrity data. */
+ std::vector<uint8_t> k1;
+};
+
+/**
+ * @class AlgoSHA1
+ *
+ * @brief Implementation of the HMAC-SHA1-96 Integrity algorithm
+ *
+ * HMAC-SHA1-96 take the Session Integrity Key and use it to generate K1. K1 is
+ * then used as the key for use in HMAC to produce the AuthCode field.
+ * For “one-key” logins, the user’s key (password) is used in the creation of
+ * the Session Integrity Key. When the HMAC-SHA1-96 Integrity Algorithm is used
+ * the resulting AuthCode field is 12 bytes (96 bits).
+ */
+class AlgoSHA1 final : public Interface
+{
+ public:
+ static constexpr size_t SHA1_96_AUTHCODE_LENGTH = 12;
+
+ /**
+ * @brief Constructor for AlgoSHA1
+ *
+ * @param[in] - Session Integrity Key
+ */
+ explicit AlgoSHA1(const std::vector<uint8_t>& sik);
+
+ AlgoSHA1() = delete;
+ ~AlgoSHA1() = default;
+ AlgoSHA1(const AlgoSHA1&) = default;
+ AlgoSHA1& operator=(const AlgoSHA1&) = default;
+ AlgoSHA1(AlgoSHA1&&) = default;
+ AlgoSHA1& operator=(AlgoSHA1&&) = default;
+
+ /**
+ * @brief Verify the integrity data of the packet
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] length - Length of the data in the packet to calculate
+ * the integrity data
+ * @param[in] integrityDataBegin - Begin iterator to the authCode in the
+ * packet
+ * @param[in] integrityDataEnd - End to the authCode in the packet
+ *
+ * @return true if authcode in the packet is equal to one generated
+ * using integrity algorithm on the packet data, false otherwise
+ */
+ bool verifyIntegrityData(
+ const std::vector<uint8_t>& packet, const size_t length,
+ std::vector<uint8_t>::const_iterator integrityDataBegin,
+ std::vector<uint8_t>::const_iterator integrityDataEnd) const override;
+
+ /**
+ * @brief Generate integrity data for the outgoing IPMI packet
+ *
+ * @param[in] input - Outgoing IPMI packet
+ *
+ * @return on success return the integrity data for the outgoing IPMI
+ * packet
+ */
+ std::vector<uint8_t> generateIntegrityData(
+ const std::vector<uint8_t>& packet) const override;
+
+ /**
+ * @brief Generate additional keying material based on SIK
+ *
+ * @param[in] sik - session integrity key
+ * @param[in] data - 20-byte Const_n
+ *
+ * @return on success returns the Kn based on HMAC-SHA1
+ *
+ */
+ std::vector<uint8_t> generateKn(
+ const std::vector<uint8_t>& sik,
+ const rmcp::Const_n& const_n) const override;
+
+ private:
+ /**
+ * @brief Generate HMAC based on HMAC-SHA1-96 algorithm
+ *
+ * @param[in] input - pointer to the message
+ * @param[in] length - length of the message
+ *
+ * @return on success returns the message authentication code
+ *
+ */
+ std::vector<uint8_t> generateHMAC(const uint8_t* input,
+ const size_t len) const;
+};
+
+/**
+ * @class AlgoSHA256
+ *
+ * @brief Implementation of the HMAC-SHA256-128 Integrity algorithm
+ *
+ * HMAC-SHA256-128 take the Session Integrity Key and use it to generate K1. K1
+ * is then used as the key for use in HMAC to produce the AuthCode field. For
+ * “one-key” logins, the user’s key (password) is used in the creation of the
+ * Session Integrity Key. When the HMAC-SHA256-128 Integrity Algorithm is used
+ * the resulting AuthCode field is 16 bytes (128 bits).
+ */
+class AlgoSHA256 final : public Interface
+{
+ public:
+ static constexpr size_t SHA256_128_AUTHCODE_LENGTH = 16;
+
+ /**
+ * @brief Constructor for AlgoSHA256
+ *
+ * @param[in] - Session Integrity Key
+ */
+ explicit AlgoSHA256(const std::vector<uint8_t>& sik);
+
+ AlgoSHA256() = delete;
+ ~AlgoSHA256() = default;
+ AlgoSHA256(const AlgoSHA256&) = default;
+ AlgoSHA256& operator=(const AlgoSHA256&) = default;
+ AlgoSHA256(AlgoSHA256&&) = default;
+ AlgoSHA256& operator=(AlgoSHA256&&) = default;
+
+ /**
+ * @brief Verify the integrity data of the packet
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] length - Length of the data in the packet to calculate
+ * the integrity data
+ * @param[in] integrityDataBegin - Begin iterator to the authCode in the
+ * packet
+ * @param[in] integrityDataEnd - End to the authCode in the packet
+ *
+ * @return true if authcode in the packet is equal to one generated
+ * using integrity algorithm on the packet data, false otherwise
+ */
+ bool verifyIntegrityData(
+ const std::vector<uint8_t>& packet, const size_t length,
+ std::vector<uint8_t>::const_iterator integrityDataBegin,
+ std::vector<uint8_t>::const_iterator integrityDataEnd) const override;
+
+ /**
+ * @brief Generate integrity data for the outgoing IPMI packet
+ *
+ * @param[in] packet - Outgoing IPMI packet
+ *
+ * @return on success return the integrity data for the outgoing IPMI
+ * packet
+ */
+ std::vector<uint8_t> generateIntegrityData(
+ const std::vector<uint8_t>& packet) const override;
+
+ /**
+ * @brief Generate additional keying material based on SIK
+ *
+ * @param[in] sik - session integrity key
+ * @param[in] data - 20-byte Const_n
+ *
+ * @return on success returns the Kn based on HMAC-SHA256
+ *
+ */
+ std::vector<uint8_t> generateKn(
+ const std::vector<uint8_t>& sik,
+ const rmcp::Const_n& const_n) const override;
+
+ private:
+ /**
+ * @brief Generate HMAC based on HMAC-SHA256-128 algorithm
+ *
+ * @param[in] input - pointer to the message
+ * @param[in] len - length of the message
+ *
+ * @return on success returns the message authentication code
+ *
+ */
+ std::vector<uint8_t> generateHMAC(const uint8_t* input,
+ const size_t len) const;
+};
+
+} // namespace integrity
+
+} // namespace cipher
diff --git a/transport/rmcpbridge/main.cpp b/transport/rmcpbridge/main.cpp
new file mode 100644
index 0000000..a1427ca
--- /dev/null
+++ b/transport/rmcpbridge/main.cpp
@@ -0,0 +1,123 @@
+#include "main.hpp"
+
+#include "comm_module.hpp"
+#include "command/guid.hpp"
+#include "command_table.hpp"
+#include "message.hpp"
+#include "message_handler.hpp"
+#include "sd_event_loop.hpp"
+#include "sessions_manager.hpp"
+#include "socket_channel.hpp"
+#include "sol_module.hpp"
+
+#include <assert.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <ipmid/api.h>
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-event.h>
+#include <unistd.h>
+
+#include <CLI/CLI.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <user_channel/channel_layer.hpp>
+
+#include <tuple>
+
+static auto io = std::make_shared<boost::asio::io_context>();
+std::shared_ptr<boost::asio::io_context> getIo()
+{
+ return io;
+}
+
+sd_bus* bus = nullptr;
+
+std::shared_ptr<sdbusplus::asio::connection> sdbusp;
+
+/*
+ * @brief Required by apphandler IPMI Provider Library
+ */
+sd_bus* ipmid_get_sd_bus_connection()
+{
+ return bus;
+}
+
+/*
+ * @brief mechanism to get at sdbusplus object
+ */
+std::shared_ptr<sdbusplus::asio::connection> getSdBus()
+{
+ return sdbusp;
+}
+
+static EInterfaceIndex currentInterfaceIndex = interfaceUnknown;
+static void setInterfaceIndex(const std::string& channel)
+{
+ try
+ {
+ currentInterfaceIndex =
+ static_cast<EInterfaceIndex>(ipmi::getChannelByName(channel));
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Requested {NAME} is not a valid channel name: {ERROR}",
+ "NAME", channel, "ERROR", e);
+ }
+}
+EInterfaceIndex getInterfaceIndex(void)
+{
+ return currentInterfaceIndex;
+}
+
+int main(int argc, char* argv[])
+{
+ CLI::App app("KCS RMCP+ bridge");
+ std::string channel;
+ app.add_option("-c,--channel", channel, "channel name. e.g., eth0");
+ uint16_t port = ipmi::rmcpp::defaultPort;
+ app.add_option("-p,--port", port, "port number");
+ bool verbose = false;
+ app.add_option("-v,--verbose", verbose, "print more verbose output");
+ CLI11_PARSE(app, argc, argv);
+
+ // Connect to system bus
+ auto rc = sd_bus_default_system(&bus);
+ if (rc < 0)
+ {
+ lg2::error("Failed to connect to system bus: {ERROR}", "ERROR",
+ strerror(-rc));
+ return rc;
+ }
+ sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
+
+ auto& loop = eventloop::EventLoop::get();
+ loop.setupSignal();
+
+ ipmi::ipmiChannelInit();
+ if (channel.size())
+ {
+ setInterfaceIndex(channel);
+ }
+
+ session::Manager::get().managerInit(channel);
+ // Register callback to update cache for a GUID change and cache the GUID
+ command::registerGUIDChangeCallback();
+
+ // Register callback to update cache for sol conf change
+ sol::registerSolConfChangeCallbackHandler(channel);
+
+ // Register the phosphor-net-ipmid session setup commands
+ command::sessionSetupCommands();
+
+ // Register the phosphor-net-ipmid SOL commands
+ sol::command::registerCommands();
+
+ if (loop.setupSocket(sdbusp, channel))
+ {
+ return EXIT_FAILURE;
+ }
+
+ // Start Event Loop
+ return loop.startEventLoop();
+}
diff --git a/transport/rmcpbridge/main.hpp b/transport/rmcpbridge/main.hpp
new file mode 100644
index 0000000..a5be04c
--- /dev/null
+++ b/transport/rmcpbridge/main.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <boost/asio/io_context.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <cstddef>
+#include <memory>
+
+// Select call timeout is set arbitrarily set to 30 sec
+static constexpr size_t SELECT_CALL_TIMEOUT = 30;
+static const auto IPMI_STD_PORT = 623;
+
+extern sd_bus* bus;
+
+std::shared_ptr<sdbusplus::asio::connection> getSdBus();
+std::shared_ptr<boost::asio::io_context> getIo();
diff --git a/transport/rmcpbridge/meson.build b/transport/rmcpbridge/meson.build
new file mode 100644
index 0000000..0e449d4
--- /dev/null
+++ b/transport/rmcpbridge/meson.build
@@ -0,0 +1,120 @@
+project(
+ 'phosphor-net-ipmid',
+ 'cpp',
+ version: '1.0.0',
+ meson_version: '>=1.1.1',
+ default_options: [
+ 'warning_level=3',
+ 'werror=true',
+ 'cpp_std=c++23',
+ 'buildtype=debugoptimized',
+ 'b_lto=true',
+ ],
+)
+
+conf_data = configuration_data()
+conf_data.set('RMCP_PING', get_option('rmcp_ping').allowed())
+conf_data.set('PAM_AUTHENTICATE', get_option('pam_authenticate').allowed())
+
+configure_file(output: 'config.h', configuration: conf_data)
+
+sdbusplus_dep = dependency('sdbusplus')
+phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
+phosphor_logging_dep = dependency('phosphor-logging')
+libsystemd_dep = dependency('libsystemd')
+libcrypto_dep = dependency('libcrypto')
+ipmid_dep = dependency('libipmid')
+userlayer_dep = dependency('libuserlayer')
+channellayer_dep = dependency('libchannellayer')
+
+# Project Arguments
+cpp = meson.get_compiler('cpp')
+if cpp.has_header('CLI/CLI.hpp')
+ cli11_dep = declare_dependency()
+else
+ cli11_dep = dependency('CLI11')
+endif
+
+add_project_arguments(
+ cpp.get_supported_arguments(
+ [
+ '-DBOOST_ERROR_CODE_HEADER_ONLY',
+ '-DBOOST_SYSTEM_NO_DEPRECATED',
+ '-DBOOST_COROUTINES_NO_DEPRECATION_WARNING',
+ '-DBOOST_ASIO_DISABLE_THREADS',
+ '-DBOOST_ALL_NO_LIB',
+ ],
+ ),
+ language: 'cpp',
+)
+
+deps = [
+ cli11_dep,
+ ipmid_dep,
+ userlayer_dep,
+ channellayer_dep,
+ libcrypto_dep,
+ libsystemd_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+]
+
+sources = [
+ 'auth_algo.cpp',
+ 'sessions_manager.cpp',
+ 'message_parsers.cpp',
+ 'message_handler.cpp',
+ 'command_table.cpp',
+ 'command/channel_auth.cpp',
+ 'command/guid.cpp',
+ 'command/open_session.cpp',
+ 'command/rakp12.cpp',
+ 'command/rakp34.cpp',
+ 'command/session_cmds.cpp',
+ 'comm_module.cpp',
+ 'main.cpp',
+ 'integrity_algo.cpp',
+ 'crypt_algo.cpp',
+ 'sd_event_loop.cpp',
+ 'sol/sol_manager.cpp',
+ 'sol/sol_context.cpp',
+ 'command/sol_cmds.cpp',
+ 'command/payload_cmds.cpp',
+ 'sol_module.cpp',
+]
+
+executable(
+ 'netipmid',
+ sources,
+ implicit_include_directories: true,
+ include_directories: ['command', 'sol'],
+ dependencies: deps,
+ install: true,
+ install_dir: get_option('bindir'),
+)
+
+systemd = dependency('systemd')
+systemd_system_unit_dir = systemd.get_variable(
+ 'systemdsystemunitdir',
+ pkgconfig_define: ['prefix', get_option('prefix')],
+)
+
+configure_file(
+ input: 'phosphor-ipmi-net@.service',
+ output: 'phosphor-ipmi-net@.service',
+ copy: true,
+ install_dir: systemd_system_unit_dir,
+)
+
+configure_file(
+ input: 'phosphor-ipmi-net@.socket',
+ output: 'phosphor-ipmi-net@.socket',
+ copy: true,
+ install_dir: systemd_system_unit_dir,
+)
+
+build_tests = get_option('tests')
+if build_tests.allowed()
+ subdir('test')
+endif
diff --git a/transport/rmcpbridge/meson.options b/transport/rmcpbridge/meson.options
new file mode 100644
index 0000000..6f9cbaa
--- /dev/null
+++ b/transport/rmcpbridge/meson.options
@@ -0,0 +1,15 @@
+option('tests', type: 'feature', value: 'enabled', description: 'Build tests')
+
+option(
+ 'rmcp_ping',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Enable RMCP Ping support',
+)
+
+option(
+ 'pam_authenticate',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Enable Pam Authenticate',
+)
diff --git a/transport/rmcpbridge/message.hpp b/transport/rmcpbridge/message.hpp
new file mode 100644
index 0000000..04dac27
--- /dev/null
+++ b/transport/rmcpbridge/message.hpp
@@ -0,0 +1,266 @@
+#pragma once
+
+#include "config.h"
+
+#include <cstddef>
+#include <memory>
+#include <numeric>
+#include <vector>
+
+namespace message
+{
+
+enum class PayloadType : uint8_t
+{
+ IPMI = 0x00,
+ SOL = 0x01,
+ OPEN_SESSION_REQUEST = 0x10,
+ OPEN_SESSION_RESPONSE = 0x11,
+ RAKP1 = 0x12,
+ RAKP2 = 0x13,
+ RAKP3 = 0x14,
+ RAKP4 = 0x15,
+ INVALID = 0xFF,
+};
+
+// RMCP Classes of Message as per section 13.1.3.
+enum class ClassOfMsg : uint8_t
+{
+ RESERVED = 0x05,
+ ASF = 0x06,
+ IPMI = 0x07,
+ OEM = 0x08,
+};
+
+#ifdef RMCP_PING
+// RMCP Message Type as per section 13.1.3.
+enum class RmcpMsgType : uint8_t
+{
+ PING = 0x80,
+ PONG = 0x40,
+};
+#endif // RMCP_PING
+
+namespace LAN
+{
+
+constexpr uint8_t requesterBMCAddress = 0x20;
+constexpr uint8_t responderBMCAddress = 0x81;
+
+namespace header
+{
+
+/**
+ * @struct IPMI LAN Message Request Header
+ */
+struct Request
+{
+ uint8_t rsaddr;
+ uint8_t netfn;
+ uint8_t cs;
+ uint8_t rqaddr;
+ uint8_t rqseq;
+ uint8_t cmd;
+} __attribute__((packed));
+
+/**
+ * @struct IPMI LAN Message Response Header
+ */
+struct Response
+{
+ uint8_t rqaddr;
+ uint8_t netfn;
+ uint8_t cs;
+ uint8_t rsaddr;
+ uint8_t rqseq;
+ uint8_t cmd;
+} __attribute__((packed));
+
+} // namespace header
+
+namespace trailer
+{
+
+/**
+ * @struct IPMI LAN Message Trailer
+ */
+struct Request
+{
+ uint8_t checksum;
+} __attribute__((packed));
+
+using Response = Request;
+
+} // namespace trailer
+
+} // namespace LAN
+
+/**
+ * @brief Calculate 8 bit 2's complement checksum
+ *
+ * Initialize checksum to 0. For each byte, checksum = (checksum + byte)
+ * modulo 256. Then checksum = - checksum. When the checksum and the
+ * bytes are added together, modulo 256, the result should be 0.
+ */
+static inline uint8_t crc8bit(const uint8_t* ptr, const size_t len)
+{
+ return (0x100 - std::accumulate(ptr, ptr + len, 0));
+}
+
+/**
+ * @struct Message
+ *
+ * IPMI message is data encapsulated in an IPMI Session packet. The IPMI
+ * Session packets are encapsulated in RMCP packets, which are encapsulated in
+ * UDP datagrams. Refer Section 13.5 of IPMI specification(IPMI Messages
+ * Encapsulation Under RMCP). IPMI payload is a special class of data
+ * encapsulated in an IPMI session packet.
+ */
+struct Message
+{
+ static constexpr uint32_t MESSAGE_INVALID_SESSION_ID = 0xBADBADFF;
+
+ Message() :
+ payloadType(PayloadType::INVALID),
+ rcSessionID(Message::MESSAGE_INVALID_SESSION_ID),
+ bmcSessionID(Message::MESSAGE_INVALID_SESSION_ID),
+ rmcpMsgClass(ClassOfMsg::RESERVED)
+ {}
+
+ /**
+ * @brief Special behavior for copy constructor
+ *
+ * Based on incoming message state, the resulting message will have a
+ * pre-baked state. This is used to simplify the flows for creating a
+ * response message. For each pre-session state, the response message is
+ * actually a different type of message. Once the session has been
+ * established, the response type is the same as the request type.
+ */
+ Message(const Message& other) :
+ isPacketEncrypted(other.isPacketEncrypted),
+ isPacketAuthenticated(other.isPacketAuthenticated),
+ payloadType(other.payloadType), rcSessionID(other.rcSessionID),
+ bmcSessionID(other.bmcSessionID), rmcpMsgClass(other.rmcpMsgClass)
+ {
+ // special behavior for rmcp+ session creation
+ if (PayloadType::OPEN_SESSION_REQUEST == other.payloadType)
+ {
+ payloadType = PayloadType::OPEN_SESSION_RESPONSE;
+ }
+ else if (PayloadType::RAKP1 == other.payloadType)
+ {
+ payloadType = PayloadType::RAKP2;
+ }
+ else if (PayloadType::RAKP3 == other.payloadType)
+ {
+ payloadType = PayloadType::RAKP4;
+ }
+ }
+ Message& operator=(const Message&) = default;
+ Message(Message&&) = default;
+ Message& operator=(Message&&) = default;
+ ~Message() = default;
+
+ /**
+ * @brief Extract the command from the IPMI payload
+ *
+ * @return Command ID in the incoming message
+ */
+ uint32_t getCommand()
+ {
+ uint32_t command = 0;
+
+ command |= (static_cast<uint32_t>(payloadType) << 16);
+ if (payloadType == PayloadType::IPMI)
+ {
+ auto request =
+ reinterpret_cast<LAN::header::Request*>(payload.data());
+ command |= request->netfn << 8;
+ command |= static_cast<uint32_t>(request->cmd);
+ }
+ return command;
+ }
+
+ /**
+ * @brief Create the response IPMI message
+ *
+ * The IPMI outgoing message is constructed out of payload and the
+ * corresponding fields are populated. For the payload type IPMI, the
+ * LAN message header and trailer are added.
+ *
+ * @param[in] output - Payload for outgoing message
+ *
+ * @return Outgoing message on success and nullptr on failure
+ */
+ std::shared_ptr<Message> createResponse(std::vector<uint8_t>& output)
+ {
+ // SOL packets don't reply; return NULL
+ if (payloadType == PayloadType::SOL)
+ {
+ return nullptr;
+ }
+ auto outMessage = std::make_shared<Message>(*this);
+
+ if (payloadType == PayloadType::IPMI)
+ {
+ outMessage->payloadType = PayloadType::IPMI;
+
+ outMessage->payload.resize(
+ sizeof(LAN::header::Response) + output.size() +
+ sizeof(LAN::trailer::Response));
+
+ auto reqHeader =
+ reinterpret_cast<LAN::header::Request*>(payload.data());
+ auto respHeader = reinterpret_cast<LAN::header::Response*>(
+ outMessage->payload.data());
+
+ // Add IPMI LAN Message Response Header
+ respHeader->rqaddr = reqHeader->rqaddr;
+ respHeader->netfn = reqHeader->netfn | 0x04;
+ respHeader->cs = crc8bit(&(respHeader->rqaddr), 2);
+ respHeader->rsaddr = reqHeader->rsaddr;
+ respHeader->rqseq = reqHeader->rqseq;
+ respHeader->cmd = reqHeader->cmd;
+
+ auto assembledSize = sizeof(LAN::header::Response);
+
+ // Copy the output by the execution of the command
+ std::copy(output.begin(), output.end(),
+ outMessage->payload.begin() + assembledSize);
+ assembledSize += output.size();
+
+ // Add the IPMI LAN Message Trailer
+ auto trailer = reinterpret_cast<LAN::trailer::Response*>(
+ outMessage->payload.data() + assembledSize);
+ trailer->checksum = crc8bit(&respHeader->rsaddr, assembledSize - 3);
+ }
+ else
+ {
+ outMessage->payload = output;
+ }
+ return outMessage;
+ }
+
+ bool isPacketEncrypted; // Message's Encryption Status
+ bool isPacketAuthenticated; // Message's Authentication Status
+ PayloadType payloadType; // Type of message payload (IPMI,SOL ..etc)
+ uint32_t rcSessionID; // Remote Client's Session ID
+ uint32_t bmcSessionID; // BMC's session ID
+ uint32_t sessionSeqNum; // Session Sequence Number
+ ClassOfMsg rmcpMsgClass; // Class of Message
+#ifdef RMCP_PING
+ uint8_t asfMsgTag; // ASF Message Tag
+#endif // RMCP_PING
+
+ /** @brief Message payload
+ *
+ * “Payloads” are a capability specified for RMCP+ that enable an IPMI
+ * session to carry types of traffic that are in addition to IPMI Messages.
+ * Payloads can be ‘standard’ or ‘OEM’.Standard payload types include IPMI
+ * Messages, messages for session setup under RMCP+, and the payload for
+ * the “Serial Over LAN” capability introduced in IPMI v2.0.
+ */
+ std::vector<uint8_t> payload;
+};
+
+} // namespace message
diff --git a/transport/rmcpbridge/message_handler.cpp b/transport/rmcpbridge/message_handler.cpp
new file mode 100644
index 0000000..b7365bb
--- /dev/null
+++ b/transport/rmcpbridge/message_handler.cpp
@@ -0,0 +1,233 @@
+#include "message_handler.hpp"
+
+#include "command_table.hpp"
+#include "main.hpp"
+#include "message.hpp"
+#include "message_parsers.hpp"
+#include "sessions_manager.hpp"
+
+#include <sys/socket.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace message
+{
+
+bool Handler::receive()
+{
+ std::vector<uint8_t> packet;
+ auto readStatus = 0;
+
+ // Read the packet
+ std::tie(readStatus, packet) = channel->read();
+
+ // Read of the packet failed
+ if (readStatus < 0)
+ {
+ lg2::error("Error in Read status: {STATUS}", "STATUS", readStatus);
+ return false;
+ }
+
+ // Unflatten the packet
+ std::tie(inMessage, sessionHeader) = parser::unflatten(packet);
+
+ return true;
+}
+
+void Handler::updSessionData(std::shared_ptr<Message>& inMessage)
+{
+ session = session::Manager::get().getSession(inMessage->bmcSessionID);
+
+ sessionID = inMessage->bmcSessionID;
+ inMessage->rcSessionID = session->getRCSessionID();
+ session->updateLastTransactionTime();
+ session->channelPtr = channel;
+ session->remotePort(channel->getPort());
+ uint32_t ipAddr = 0;
+ channel->getRemoteAddress(ipAddr);
+ session->remoteIPAddr(ipAddr);
+}
+
+Handler::~Handler()
+{
+ try
+ {
+#ifdef RMCP_PING
+ if (inMessage && (ClassOfMsg::ASF == inMessage->rmcpMsgClass))
+ {
+ sendASF();
+ }
+ else
+#endif // RMCP_PING
+ {
+ if (outPayload)
+ {
+ std::shared_ptr<Message> outMessage =
+ inMessage->createResponse(*outPayload);
+ if (!outMessage)
+ {
+ return;
+ }
+ send(outMessage);
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ // send failed, most likely due to a session closure
+ lg2::info("Async RMCP+ reply failed: {ERROR}", "ERROR", e);
+ }
+}
+
+void Handler::processIncoming()
+{
+ // Read the incoming IPMI packet
+ if (!receive())
+ {
+ return;
+ }
+
+#ifdef RMCP_PING
+ // Execute the Command, possibly asynchronously
+ if (inMessage && (ClassOfMsg::ASF != inMessage->rmcpMsgClass))
+#endif // RMCP_PING
+ {
+ updSessionData(inMessage);
+ executeCommand();
+ }
+
+ // send happens during the destructor if a payload was set
+}
+
+void Handler::executeCommand()
+{
+ // Get the CommandID to map into the command table
+ auto command = inMessage->getCommand();
+ if (inMessage->payloadType == PayloadType::IPMI)
+ {
+ // Process PayloadType::IPMI only if ipmi is enabled or for sessionless
+ // or for session establisbment command
+ if (this->sessionID == session::sessionZero ||
+ session->sessionUserPrivAccess.ipmiEnabled)
+ {
+ if (inMessage->payload.size() <
+ (sizeof(LAN::header::Request) + sizeof(LAN::trailer::Request)))
+ {
+ return;
+ }
+
+ auto start = inMessage->payload.begin() +
+ sizeof(LAN::header::Request);
+ auto end = inMessage->payload.end() - sizeof(LAN::trailer::Request);
+ std::vector<uint8_t> inPayload(start, end);
+ command::Table::get().executeCommand(command, inPayload,
+ shared_from_this());
+ }
+ else
+ {
+ std::vector<uint8_t> payload{IPMI_CC_INSUFFICIENT_PRIVILEGE};
+ outPayload = std::move(payload);
+ }
+ }
+ else
+ {
+ command::Table::get().executeCommand(command, inMessage->payload,
+ shared_from_this());
+ }
+}
+
+void Handler::writeData(const std::vector<uint8_t>& packet)
+{
+ auto writeStatus = channel->write(packet);
+ if (writeStatus < 0)
+ {
+ throw std::runtime_error("Error in writing to socket");
+ }
+}
+
+#ifdef RMCP_PING
+void Handler::sendASF()
+{
+ // Flatten the packet
+ auto packet = asfparser::flatten(inMessage->asfMsgTag);
+
+ // Write the packet
+ writeData(packet);
+}
+#endif // RMCP_PING
+
+void Handler::send(std::shared_ptr<Message> outMessage)
+{
+ // Flatten the packet
+ auto packet = parser::flatten(outMessage, sessionHeader, session);
+
+ // Write the packet
+ writeData(packet);
+}
+
+void Handler::setChannelInSession() const
+{
+ session->channelPtr = channel;
+}
+
+void Handler::sendSOLPayload(const std::vector<uint8_t>& input)
+{
+ auto outMessage = std::make_shared<Message>();
+ outMessage->payloadType = PayloadType::SOL;
+ outMessage->payload = input;
+ outMessage->isPacketEncrypted = session->isCryptAlgoEnabled();
+ outMessage->isPacketAuthenticated = session->isIntegrityAlgoEnabled();
+ outMessage->rcSessionID = session->getRCSessionID();
+ outMessage->bmcSessionID = sessionID;
+
+ send(outMessage);
+}
+
+void Handler::sendUnsolicitedIPMIPayload(uint8_t netfn, uint8_t cmd,
+ const std::vector<uint8_t>& output)
+{
+ auto outMessage = std::make_shared<Message>();
+ outMessage->payloadType = PayloadType::IPMI;
+ outMessage->isPacketEncrypted = session->isCryptAlgoEnabled();
+ outMessage->isPacketAuthenticated = session->isIntegrityAlgoEnabled();
+ outMessage->rcSessionID = session->getRCSessionID();
+ outMessage->bmcSessionID = sessionID;
+
+ outMessage->payload.resize(sizeof(LAN::header::Request) + output.size() +
+ sizeof(LAN::trailer::Request));
+
+ auto respHeader =
+ reinterpret_cast<LAN::header::Request*>(outMessage->payload.data());
+
+ // Add IPMI LAN Message Request Header
+ respHeader->rsaddr = LAN::requesterBMCAddress;
+ respHeader->netfn = (netfn << 0x02);
+ respHeader->cs = crc8bit(&(respHeader->rsaddr), 2);
+ respHeader->rqaddr = LAN::responderBMCAddress;
+ respHeader->rqseq = 0;
+ respHeader->cmd = cmd;
+
+ auto assembledSize = sizeof(LAN::header::Request);
+
+ // Copy the output by the execution of the command
+ std::copy(output.begin(), output.end(),
+ outMessage->payload.begin() + assembledSize);
+ assembledSize += output.size();
+
+ // Add the IPMI LAN Message Trailer
+ auto trailer = reinterpret_cast<LAN::trailer::Request*>(
+ outMessage->payload.data() + assembledSize);
+
+ // Calculate the checksum for the field rqaddr in the header to the
+ // command data, 3 corresponds to size of the fields before rqaddr( rsaddr,
+ // netfn, cs).
+ trailer->checksum = crc8bit(&respHeader->rqaddr, assembledSize - 3);
+
+ send(outMessage);
+}
+
+} // namespace message
diff --git a/transport/rmcpbridge/message_handler.hpp b/transport/rmcpbridge/message_handler.hpp
new file mode 100644
index 0000000..c1a7032
--- /dev/null
+++ b/transport/rmcpbridge/message_handler.hpp
@@ -0,0 +1,155 @@
+#pragma once
+
+#include "message.hpp"
+#include "message_parsers.hpp"
+#include "session.hpp"
+#include "sessions_manager.hpp"
+#include "sol/console_buffer.hpp"
+
+#include <memory>
+
+namespace message
+{
+
+class Handler : public std::enable_shared_from_this<Handler>
+{
+ public:
+ /**
+ * @brief Create a Handler intended for a full transaction
+ * that may or may not use asynchronous responses
+ */
+ Handler(std::shared_ptr<udpsocket::Channel> channel,
+ std::shared_ptr<boost::asio::io_context> io,
+ uint32_t sessionID = message::Message::MESSAGE_INVALID_SESSION_ID) :
+ sessionID(sessionID), channel(channel), io(io)
+ {
+ if (sessionID != message::Message::MESSAGE_INVALID_SESSION_ID)
+ {
+ session = session::Manager::get().getSession(sessionID);
+ }
+ }
+
+ /**
+ * @brief Create a Handler intended for a send only (SOL)
+ */
+ Handler(std::shared_ptr<udpsocket::Channel> channel,
+ uint32_t sessionID = message::Message::MESSAGE_INVALID_SESSION_ID) :
+ sessionID(sessionID), channel(channel), io(nullptr)
+ {
+ if (sessionID != message::Message::MESSAGE_INVALID_SESSION_ID)
+ {
+ session = session::Manager::get().getSession(sessionID);
+ }
+ }
+
+ ~Handler();
+ Handler() = delete;
+ Handler(const Handler&) = delete;
+ Handler& operator=(const Handler&) = delete;
+ Handler(Handler&&) = delete;
+ Handler& operator=(Handler&&) = delete;
+
+ /**
+ * @brief Process the incoming IPMI message
+ *
+ * The incoming payload is read from the channel. If a message is read, it
+ * is passed onto executeCommand, which may or may not execute the command
+ * asynchrounously. If the command is executed asynchrounously, a shared_ptr
+ * of self via shared_from_this will keep this object alive until the
+ * response is ready. Then on the destructor, the response will be sent.
+ */
+ void processIncoming();
+
+ /** @brief Set socket channel in session object */
+ void setChannelInSession() const;
+
+ /** @brief Send the SOL payload
+ *
+ * The SOL payload is flattened and sent out on the socket
+ *
+ * @param[in] input - SOL Payload
+ */
+ void sendSOLPayload(const std::vector<uint8_t>& input);
+
+ /** @brief Send the unsolicited IPMI payload to the remote console.
+ *
+ * This is used by commands like SOL activating, in which case the BMC
+ * has to notify the remote console that a SOL payload is activating
+ * on another channel.
+ *
+ * @param[in] netfn - Net function.
+ * @param[in] cmd - Command.
+ * @param[in] input - Command request data.
+ */
+ void sendUnsolicitedIPMIPayload(uint8_t netfn, uint8_t cmd,
+ const std::vector<uint8_t>& input);
+
+ // BMC Session ID for the Channel
+ session::SessionID sessionID;
+
+ /** @brief response to send back */
+ std::optional<std::vector<uint8_t>> outPayload;
+
+ private:
+ /**
+ * @brief Receive the IPMI packet
+ *
+ * Read the data on the socket, get the parser based on the Session
+ * header type and flatten the payload and generate the IPMI message
+ */
+ bool receive();
+
+ /**
+ * @brief Get Session data from the IPMI packet
+ *
+ */
+ void updSessionData(std::shared_ptr<Message>& inMessage);
+
+ /**
+ * @brief Process the incoming IPMI message
+ *
+ * The incoming message payload is handled and the command handler for
+ * the Network function and Command is executed and the response message
+ * is returned
+ */
+ void executeCommand();
+
+ /** @brief Send the outgoing message
+ *
+ * The payload in the outgoing message is flattened and sent out on the
+ * socket
+ *
+ * @param[in] outMessage - Outgoing Message
+ */
+ void send(std::shared_ptr<Message> outMessage);
+
+#ifdef RMCP_PING
+ /** @brief Send the outgoing ASF message
+ *
+ * The outgoing ASF message contains only ASF message header
+ * which is flattened and sent out on the socket
+ */
+ void sendASF();
+#endif // RMCP_PING
+
+ /** @brief Write the packet to the socket
+ *
+ * @param[in] packet - Outgoing packet
+ */
+ void writeData(const std::vector<uint8_t>& packet);
+
+ /** @brief Socket channel for communicating with the remote client.*/
+ std::shared_ptr<udpsocket::Channel> channel;
+
+ /** @brief asio io context to run asynchrounously */
+ std::shared_ptr<boost::asio::io_context> io;
+
+ parser::SessionHeader sessionHeader = parser::SessionHeader::IPMI20;
+
+ std::shared_ptr<message::Message> inMessage{};
+
+ /** @brief The IPMI session of the handler */
+ std::shared_ptr<session::Session> session{};
+};
+
+} // namespace message
diff --git a/transport/rmcpbridge/message_parsers.cpp b/transport/rmcpbridge/message_parsers.cpp
new file mode 100644
index 0000000..c4e4bd4
--- /dev/null
+++ b/transport/rmcpbridge/message_parsers.cpp
@@ -0,0 +1,466 @@
+#include "message_parsers.hpp"
+
+#include "endian.hpp"
+#include "main.hpp"
+#include "message.hpp"
+#include "sessions_manager.hpp"
+
+#include <memory>
+
+namespace message
+{
+
+namespace parser
+{
+
+std::tuple<std::shared_ptr<Message>, SessionHeader> unflatten(
+ std::vector<uint8_t>& inPacket)
+{
+ // Check if the packet has atleast the size of the RMCP Header
+ if (inPacket.size() < sizeof(RmcpHeader_t))
+ {
+ throw std::runtime_error("RMCP Header missing");
+ }
+
+ auto rmcpHeaderPtr = reinterpret_cast<RmcpHeader_t*>(inPacket.data());
+
+ // Verify if the fields in the RMCP header conforms to the specification
+ if ((rmcpHeaderPtr->version != RMCP_VERSION) ||
+ (rmcpHeaderPtr->rmcpSeqNum != RMCP_SEQ) ||
+ (rmcpHeaderPtr->classOfMsg < static_cast<uint8_t>(ClassOfMsg::ASF) &&
+ rmcpHeaderPtr->classOfMsg > static_cast<uint8_t>(ClassOfMsg::OEM)))
+ {
+ throw std::runtime_error("RMCP Header is invalid");
+ }
+
+ if (rmcpHeaderPtr->classOfMsg == static_cast<uint8_t>(ClassOfMsg::ASF))
+ {
+#ifndef RMCP_PING
+ throw std::runtime_error("RMCP Ping is not supported");
+#else
+ return std::make_tuple(asfparser::unflatten(inPacket),
+ SessionHeader::IPMI15);
+#endif // RMCP_PING
+ }
+
+ auto sessionHeaderPtr = reinterpret_cast<BasicHeader_t*>(inPacket.data());
+
+ // Read the Session Header and invoke the parser corresponding to the
+ // header type
+ switch (static_cast<SessionHeader>(sessionHeaderPtr->format.formatType))
+ {
+ case SessionHeader::IPMI15:
+ {
+ return std::make_tuple(ipmi15parser::unflatten(inPacket),
+ SessionHeader::IPMI15);
+ }
+ case SessionHeader::IPMI20:
+ {
+ return std::make_tuple(ipmi20parser::unflatten(inPacket),
+ SessionHeader::IPMI20);
+ }
+ default:
+ {
+ throw std::runtime_error("Invalid Session Header");
+ }
+ }
+}
+
+std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
+ SessionHeader authType,
+ const std::shared_ptr<session::Session>& session)
+{
+ // Call the flatten routine based on the header type
+ switch (authType)
+ {
+ case SessionHeader::IPMI15:
+ {
+ return ipmi15parser::flatten(outMessage, session);
+ }
+ case SessionHeader::IPMI20:
+ {
+ return ipmi20parser::flatten(outMessage, session);
+ }
+ default:
+ {
+ return {};
+ }
+ }
+}
+
+} // namespace parser
+
+namespace ipmi15parser
+{
+
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
+{
+ if (inPacket.size() < sizeof(SessionHeader_t))
+ {
+ throw std::runtime_error("IPMI1.5 Session Header Missing");
+ }
+
+ auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
+
+ uint32_t sessionID = endian::from_ipmi(header->sessId);
+ if (sessionID != session::sessionZero)
+ {
+ throw std::runtime_error("IPMI1.5 session packets are unsupported");
+ }
+
+ auto message = std::make_shared<Message>();
+
+ message->payloadType = PayloadType::IPMI;
+ message->bmcSessionID = session::sessionZero;
+ message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
+ message->isPacketEncrypted = false;
+ message->isPacketAuthenticated = false;
+ message->rmcpMsgClass =
+ static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
+
+ // Confirm the number of data bytes received correlates to
+ // the packet length in the header
+ size_t payloadLen = header->payloadLength;
+ if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
+ {
+ throw std::runtime_error("Invalid data length");
+ }
+
+ (message->payload)
+ .assign(inPacket.data() + sizeof(SessionHeader_t),
+ inPacket.data() + sizeof(SessionHeader_t) + payloadLen);
+
+ return message;
+}
+
+std::vector<uint8_t> flatten(
+ const std::shared_ptr<Message>& outMessage,
+ const std::shared_ptr<session::Session>& /* session */)
+{
+ std::vector<uint8_t> packet(sizeof(SessionHeader_t));
+
+ // Insert Session Header into the Packet
+ auto header = reinterpret_cast<SessionHeader_t*>(packet.data());
+ header->base.rmcp.version = parser::RMCP_VERSION;
+ header->base.rmcp.reserved = 0x00;
+ header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
+ header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
+ header->base.format.formatType =
+ static_cast<uint8_t>(parser::SessionHeader::IPMI15);
+ header->sessSeqNum = 0;
+ header->sessId = endian::to_ipmi(outMessage->rcSessionID);
+
+ header->payloadLength = static_cast<uint8_t>(outMessage->payload.size());
+
+ // Insert the Payload into the Packet
+ packet.insert(packet.end(), outMessage->payload.begin(),
+ outMessage->payload.end());
+
+ // Insert the Session Trailer
+ packet.resize(packet.size() + sizeof(SessionTrailer_t));
+ auto trailer =
+ reinterpret_cast<SessionTrailer_t*>(packet.data() + packet.size());
+ trailer->legacyPad = 0x00;
+
+ return packet;
+}
+
+} // namespace ipmi15parser
+
+namespace ipmi20parser
+{
+
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
+{
+ // Check if the packet has atleast the Session Header
+ if (inPacket.size() < sizeof(SessionHeader_t))
+ {
+ throw std::runtime_error("IPMI2.0 Session Header Missing");
+ }
+
+ auto header = reinterpret_cast<SessionHeader_t*>(inPacket.data());
+
+ uint32_t sessionID = endian::from_ipmi(header->sessId);
+
+ auto session = session::Manager::get().getSession(sessionID);
+ if (!session)
+ {
+ throw std::runtime_error("RMCP+ message from unknown session");
+ }
+
+ auto message = std::make_shared<Message>();
+
+ message->payloadType = static_cast<PayloadType>(header->payloadType & 0x3F);
+ message->bmcSessionID = sessionID;
+ message->sessionSeqNum = endian::from_ipmi(header->sessSeqNum);
+ message->isPacketEncrypted =
+ ((header->payloadType & PAYLOAD_ENCRYPT_MASK) ? true : false);
+ message->isPacketAuthenticated =
+ ((header->payloadType & PAYLOAD_AUTH_MASK) ? true : false);
+ message->rmcpMsgClass =
+ static_cast<ClassOfMsg>(header->base.rmcp.classOfMsg);
+
+ // Confirm the number of data bytes received correlates to
+ // the packet length in the header
+ size_t payloadLen = endian::from_ipmi(header->payloadLength);
+ if ((payloadLen == 0) || (inPacket.size() < (sizeof(*header) + payloadLen)))
+ {
+ throw std::runtime_error("Invalid data length");
+ }
+
+ bool integrityMismatch =
+ session->isIntegrityAlgoEnabled() && !message->isPacketAuthenticated;
+ bool encryptMismatch = session->isCryptAlgoEnabled() &&
+ !message->isPacketEncrypted;
+
+ if (sessionID != session::sessionZero &&
+ (integrityMismatch || encryptMismatch))
+ {
+ throw std::runtime_error("unencrypted or unauthenticated message");
+ }
+
+ if (message->isPacketAuthenticated)
+ {
+ if (!(internal::verifyPacketIntegrity(inPacket, message, payloadLen,
+ session)))
+ {
+ throw std::runtime_error("Packet Integrity check failed");
+ }
+ }
+
+ // Decrypt the payload if the payload is encrypted
+ if (message->isPacketEncrypted)
+ {
+ // Assign the decrypted payload to the IPMI Message
+ message->payload =
+ internal::decryptPayload(inPacket, message, payloadLen, session);
+ }
+ else
+ {
+ message->payload.assign(
+ inPacket.begin() + sizeof(SessionHeader_t),
+ inPacket.begin() + sizeof(SessionHeader_t) + payloadLen);
+ }
+
+ return message;
+}
+
+std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
+ const std::shared_ptr<session::Session>& session)
+{
+ std::vector<uint8_t> packet(sizeof(SessionHeader_t));
+
+ SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
+ header->base.rmcp.version = parser::RMCP_VERSION;
+ header->base.rmcp.reserved = 0x00;
+ header->base.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
+ header->base.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::IPMI);
+ header->base.format.formatType =
+ static_cast<uint8_t>(parser::SessionHeader::IPMI20);
+ header->payloadType = static_cast<uint8_t>(outMessage->payloadType);
+ header->sessId = endian::to_ipmi(outMessage->rcSessionID);
+
+ // Add session sequence number
+ internal::addSequenceNumber(packet, session);
+
+ size_t payloadLen = 0;
+
+ // Encrypt the payload if needed
+ if (outMessage->isPacketEncrypted)
+ {
+ header->payloadType |= PAYLOAD_ENCRYPT_MASK;
+ auto cipherPayload = internal::encryptPayload(outMessage, session);
+ payloadLen = cipherPayload.size();
+ header->payloadLength = endian::to_ipmi<uint16_t>(cipherPayload.size());
+
+ // Insert the encrypted payload into the outgoing IPMI packet
+ packet.insert(packet.end(), cipherPayload.begin(), cipherPayload.end());
+ }
+ else
+ {
+ header->payloadLength =
+ endian::to_ipmi<uint16_t>(outMessage->payload.size());
+ payloadLen = outMessage->payload.size();
+
+ // Insert the Payload into the Packet
+ packet.insert(packet.end(), outMessage->payload.begin(),
+ outMessage->payload.end());
+ }
+
+ if (outMessage->isPacketAuthenticated)
+ {
+ header = reinterpret_cast<SessionHeader_t*>(packet.data());
+ header->payloadType |= PAYLOAD_AUTH_MASK;
+ internal::addIntegrityData(packet, outMessage, payloadLen, session);
+ }
+
+ return packet;
+}
+
+namespace internal
+{
+
+void addSequenceNumber(std::vector<uint8_t>& packet,
+ const std::shared_ptr<session::Session>& session)
+{
+ SessionHeader_t* header = reinterpret_cast<SessionHeader_t*>(packet.data());
+
+ if (header->sessId == session::sessionZero)
+ {
+ header->sessSeqNum = 0x00;
+ }
+ else
+ {
+ auto seqNum = session->sequenceNums.increment();
+ header->sessSeqNum = endian::to_ipmi(seqNum);
+ }
+}
+
+bool verifyPacketIntegrity(const std::vector<uint8_t>& packet,
+ const std::shared_ptr<Message>& /* message */,
+ size_t payloadLen,
+ const std::shared_ptr<session::Session>& session)
+{
+ /*
+ * Padding bytes are added to cause the number of bytes in the data range
+ * covered by the AuthCode(Integrity Data) field to be a multiple of 4 bytes
+ * .If present each integrity Pad byte is set to FFh. The following logic
+ * calculates the number of padding bytes added in the IPMI packet.
+ */
+ auto paddingLen = 4 - ((payloadLen + 2) & 3);
+
+ auto sessTrailerPos = sizeof(SessionHeader_t) + payloadLen + paddingLen;
+
+ // verify packet size includes trailer struct starts at sessTrailerPos
+ if (packet.size() < (sessTrailerPos + sizeof(SessionTrailer_t)))
+ {
+ return false;
+ }
+
+ auto trailer = reinterpret_cast<const SessionTrailer_t*>(
+ packet.data() + sessTrailerPos);
+
+ // Check trailer->padLength against paddingLen, both should match up,
+ // return false if the lengths don't match
+ if (trailer->padLength != paddingLen)
+ {
+ return false;
+ }
+
+ auto integrityAlgo = session->getIntegrityAlgo();
+
+ // Check if Integrity data length is as expected, check integrity data
+ // length is same as the length expected for the Integrity Algorithm that
+ // was negotiated during the session open process.
+ if ((packet.size() - sessTrailerPos - sizeof(SessionTrailer_t)) !=
+ integrityAlgo->authCodeLength)
+ {
+ return false;
+ }
+
+ auto integrityIter = packet.cbegin();
+ std::advance(integrityIter, sessTrailerPos + sizeof(SessionTrailer_t));
+
+ // The integrity data is calculated from the AuthType/Format field up to and
+ // including the field that immediately precedes the AuthCode field itself.
+ size_t length = packet.size() - integrityAlgo->authCodeLength -
+ message::parser::RMCP_SESSION_HEADER_SIZE;
+
+ return integrityAlgo->verifyIntegrityData(packet, length, integrityIter,
+ packet.cend());
+}
+
+void addIntegrityData(std::vector<uint8_t>& packet,
+ const std::shared_ptr<Message>& /* message */,
+ size_t payloadLen,
+ const std::shared_ptr<session::Session>& session)
+{
+ // The following logic calculates the number of padding bytes to be added to
+ // IPMI packet. If needed each integrity Pad byte is set to FFh.
+ auto paddingLen = 4 - ((payloadLen + 2) & 3);
+ packet.insert(packet.end(), paddingLen, 0xFF);
+
+ packet.resize(packet.size() + sizeof(SessionTrailer_t));
+
+ auto trailer = reinterpret_cast<SessionTrailer_t*>(
+ packet.data() + packet.size() - sizeof(SessionTrailer_t));
+
+ trailer->padLength = paddingLen;
+ trailer->nextHeader = parser::RMCP_MESSAGE_CLASS_IPMI;
+
+ auto integrityData =
+ session->getIntegrityAlgo()->generateIntegrityData(packet);
+
+ packet.insert(packet.end(), integrityData.begin(), integrityData.end());
+}
+
+std::vector<uint8_t> decryptPayload(
+ const std::vector<uint8_t>& packet,
+ const std::shared_ptr<Message>& /* message */, size_t payloadLen,
+ const std::shared_ptr<session::Session>& session)
+{
+ return session->getCryptAlgo()->decryptPayload(
+ packet, sizeof(SessionHeader_t), payloadLen);
+}
+
+std::vector<uint8_t> encryptPayload(
+ const std::shared_ptr<Message>& message,
+ const std::shared_ptr<session::Session>& session)
+{
+ return session->getCryptAlgo()->encryptPayload(message->payload);
+}
+
+} // namespace internal
+
+} // namespace ipmi20parser
+
+#ifdef RMCP_PING
+namespace asfparser
+{
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket)
+{
+ auto message = std::make_shared<Message>();
+
+ auto header = reinterpret_cast<AsfMessagePing_t*>(inPacket.data());
+
+ message->payloadType = PayloadType::IPMI;
+ message->rmcpMsgClass = ClassOfMsg::ASF;
+ message->asfMsgTag = header->msgTag;
+
+ return message;
+}
+
+std::vector<uint8_t> flatten(uint8_t asfMsgTag)
+{
+ std::vector<uint8_t> packet(sizeof(AsfMessagePong_t));
+
+ // Insert RMCP header into the Packet
+ auto header = reinterpret_cast<AsfMessagePong_t*>(packet.data());
+ header->ping.rmcp.version = parser::RMCP_VERSION;
+ header->ping.rmcp.reserved = 0x00;
+ header->ping.rmcp.rmcpSeqNum = parser::RMCP_SEQ;
+ header->ping.rmcp.classOfMsg = static_cast<uint8_t>(ClassOfMsg::ASF);
+
+ // No OEM-specific capabilities exist, therefore the second
+ // IANA Enterprise Number contains the same IANA(4542)
+ header->ping.iana = header->iana = endian::to_ipmi(parser::ASF_IANA);
+ header->ping.msgType = static_cast<uint8_t>(RmcpMsgType::PONG);
+ header->ping.msgTag = asfMsgTag;
+ header->ping.reserved = 0x00;
+ header->ping.dataLen =
+ parser::RMCP_ASF_PONG_DATA_LEN; // as per spec 13.2.4,
+
+ header->iana = parser::ASF_IANA;
+ header->oemDefined = 0x00;
+ header->suppEntities = parser::ASF_SUPP_ENT;
+ header->suppInteract = parser::ASF_SUPP_INT;
+ header->reserved1 = 0x00;
+ header->reserved2 = 0x00;
+
+ return packet;
+}
+
+} // namespace asfparser
+#endif // RMCP_PING
+
+} // namespace message
diff --git a/transport/rmcpbridge/message_parsers.hpp b/transport/rmcpbridge/message_parsers.hpp
new file mode 100644
index 0000000..ed75ce2
--- /dev/null
+++ b/transport/rmcpbridge/message_parsers.hpp
@@ -0,0 +1,307 @@
+#pragma once
+
+#include "message.hpp"
+#include "session.hpp"
+
+#include <cstddef>
+
+namespace message
+{
+
+namespace parser
+{
+
+constexpr size_t RMCP_VERSION = 6;
+
+// RMCP Messages with class=IPMI should be sent with an RMCP Sequence
+// Number of FFh to indicate that an RMCP ACK message should not be
+// generated by the message receiver.
+constexpr size_t RMCP_SEQ = 0xFF;
+
+// RMCP Message Class 7h is for IPMI
+constexpr size_t RMCP_MESSAGE_CLASS_IPMI = 7;
+
+// RMCP Session Header Size
+constexpr size_t RMCP_SESSION_HEADER_SIZE = 4;
+
+// RMCP/ASF Pong Message ASF Header Data Length
+// as per IPMI spec 13.2.4
+constexpr size_t RMCP_ASF_PONG_DATA_LEN = 16;
+
+// ASF IANA
+constexpr uint32_t ASF_IANA = 4542;
+
+// ASF Supported Entities
+constexpr uint32_t ASF_SUPP_ENT = 0x81;
+
+// ASF Supported Entities
+constexpr uint32_t ASF_SUPP_INT = 0x00;
+
+// Maximum payload size
+constexpr size_t MAX_PAYLOAD_SIZE = 255;
+
+enum class SessionHeader
+{
+ IPMI15 = 0x00,
+ IPMI20 = 0x06,
+ INVALID = 0xFF,
+};
+
+// RMCP Header
+struct RmcpHeader_t
+{
+ // RMCP Header
+ uint8_t version;
+ uint8_t reserved;
+ uint8_t rmcpSeqNum;
+ uint8_t classOfMsg;
+} __attribute__((packed));
+
+struct BasicHeader_t
+{
+ // RMCP Header
+ struct RmcpHeader_t rmcp;
+
+ // IPMI partial session header
+ union
+ {
+ uint8_t reserved1:4;
+ uint8_t authType:4;
+ uint8_t formatType;
+ } format;
+} __attribute__((packed));
+
+/**
+ * @brief Unflatten an incoming packet and prepare the IPMI message
+ *
+ * @param[in] inPacket - Incoming IPMI packet
+ *
+ * @return A tuple with IPMI message and the session header type to sent the
+ * response packet. In case of success incoming message and session
+ * header type. In case of failure nullptr and session header type
+ * would be invalid.
+ */
+std::tuple<std::shared_ptr<Message>, SessionHeader> unflatten(
+ std::vector<uint8_t>& inPacket);
+
+/**
+ * @brief Flatten an IPMI message and generate the IPMI packet with the
+ * session header
+ *
+ * @param[in] outMessage - IPMI message to be flattened
+ * @param[in] authType - Session header type to be added to the IPMI
+ * packet
+ *
+ * @return IPMI packet on success
+ */
+std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
+ SessionHeader authType,
+ const std::shared_ptr<session::Session>& session);
+
+} // namespace parser
+
+namespace ipmi15parser
+{
+
+struct SessionHeader_t
+{
+ struct parser::BasicHeader_t base;
+ uint32_t sessSeqNum;
+ uint32_t sessId;
+ // <Optional Field: AuthCode>
+ uint8_t payloadLength;
+} __attribute__((packed));
+
+struct SessionTrailer_t
+{
+ uint8_t legacyPad;
+} __attribute__((packed));
+
+/**
+ * @brief Unflatten an incoming packet and prepare the IPMI message
+ *
+ * @param[in] inPacket - Incoming IPMI packet
+ *
+ * @return IPMI message in the packet on success
+ */
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket);
+
+/**
+ * @brief Flatten an IPMI message and generate the IPMI packet with the
+ * session header
+ *
+ * @param[in] outMessage - IPMI message to be flattened
+ *
+ * @return IPMI packet on success
+ */
+std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
+ const std::shared_ptr<session::Session>& session);
+
+} // namespace ipmi15parser
+
+namespace ipmi20parser
+{
+
+constexpr size_t MAX_INTEGRITY_DATA_LENGTH = 12;
+constexpr size_t PAYLOAD_ENCRYPT_MASK = 0x80;
+constexpr size_t PAYLOAD_AUTH_MASK = 0x40;
+
+struct SessionHeader_t
+{
+ struct parser::BasicHeader_t base;
+
+ uint8_t payloadType;
+
+ uint32_t sessId;
+ uint32_t sessSeqNum;
+ uint16_t payloadLength;
+} __attribute__((packed));
+
+struct SessionTrailer_t
+{
+ // Integrity Pad
+ uint8_t padLength;
+ uint8_t nextHeader;
+} __attribute__((packed));
+
+/**
+ * @brief Unflatten an incoming packet and prepare the IPMI message
+ *
+ * @param[in] inPacket - Incoming IPMI packet
+ *
+ * @return IPMI message in the packet on success
+ */
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket);
+
+/**
+ * @brief Flatten an IPMI message and generate the IPMI packet with the
+ * session header
+ *
+ * @param[in] outMessage - IPMI message to be flattened
+ * @param[in] session - session handle
+ *
+ * @return IPMI packet on success
+ */
+std::vector<uint8_t> flatten(const std::shared_ptr<Message>& outMessage,
+ const std::shared_ptr<session::Session>& session);
+
+namespace internal
+{
+
+/**
+ * @brief Add sequence number to the message
+ *
+ * @param[in] packet - outgoing packet to which to add sequence number
+ * @param[in] session - session handle
+ *
+ */
+void addSequenceNumber(std::vector<uint8_t>& packet,
+ const std::shared_ptr<session::Session>& session);
+
+/**
+ * @brief Verify the integrity data of the incoming IPMI packet
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] message - IPMI Message populated from the incoming packet
+ * @param[in] payloadLen - Length of the IPMI payload
+ * @param[in] session - session handle
+ *
+ */
+bool verifyPacketIntegrity(const std::vector<uint8_t>& packet,
+ const std::shared_ptr<Message>& message,
+ size_t payloadLen,
+ const std::shared_ptr<session::Session>& session);
+
+/**
+ * @brief Add Integrity data to the outgoing IPMI packet
+ *
+ * @param[in] packet - Outgoing IPMI packet
+ * @param[in] message - IPMI Message populated for the outgoing packet
+ * @param[in] payloadLen - Length of the IPMI payload
+ */
+void addIntegrityData(std::vector<uint8_t>& packet,
+ const std::shared_ptr<Message>& message,
+ size_t payloadLen,
+ const std::shared_ptr<session::Session>& session);
+
+/**
+ * @brief Decrypt the encrypted payload in the incoming IPMI packet
+ *
+ * @param[in] packet - Incoming IPMI packet
+ * @param[in] message - IPMI Message populated from the incoming packet
+ * @param[in] payloadLen - Length of encrypted IPMI payload
+ * @param[in] session - session handle
+ *
+ * @return on successful completion, return the plain text payload
+ */
+std::vector<uint8_t> decryptPayload(
+ const std::vector<uint8_t>& packet, const std::shared_ptr<Message>& message,
+ size_t payloadLen, const std::shared_ptr<session::Session>& session);
+
+/**
+ * @brief Encrypt the plain text payload for the outgoing IPMI packet
+ *
+ * @param[in] message - IPMI Message populated for the outgoing packet
+ * @param[in] session - session handle
+ *
+ * @return on successful completion, return the encrypted payload
+ */
+std::vector<uint8_t> encryptPayload(
+ const std::shared_ptr<Message>& message,
+ const std::shared_ptr<session::Session>& session);
+
+} // namespace internal
+
+} // namespace ipmi20parser
+
+#ifdef RMCP_PING
+namespace asfparser
+{
+
+// ASF message fields for RMCP Ping message
+struct AsfMessagePing_t
+{
+ struct parser::RmcpHeader_t rmcp;
+
+ uint32_t iana;
+ uint8_t msgType;
+ uint8_t msgTag;
+ uint8_t reserved;
+ uint8_t dataLen;
+} __attribute__((packed));
+
+// ASF message fields for RMCP Pong message
+struct AsfMessagePong_t
+{
+ struct AsfMessagePing_t ping;
+
+ uint32_t iana;
+ uint32_t oemDefined;
+ uint8_t suppEntities;
+ uint8_t suppInteract;
+ uint32_t reserved1;
+ uint16_t reserved2;
+} __attribute__((packed));
+
+/**
+ * @brief Unflatten an incoming packet and prepare the ASF message
+ *
+ * @param[in] inPacket - Incoming ASF packet
+ *
+ * @return ASF message in the packet on success
+ */
+std::shared_ptr<Message> unflatten(std::vector<uint8_t>& inPacket);
+
+/**
+ * @brief Generate the ASF packet with the RMCP header
+ *
+ * @param[in] asfMsgTag - ASF Message Tag from Ping request
+ *
+ * @return ASF packet on success
+ */
+std::vector<uint8_t> flatten(uint8_t asfMsgTag);
+
+} // namespace asfparser
+#endif // RMCP_PING
+
+} // namespace message
diff --git a/transport/rmcpbridge/phosphor-ipmi-net@.service b/transport/rmcpbridge/phosphor-ipmi-net@.service
new file mode 100644
index 0000000..beb3785
--- /dev/null
+++ b/transport/rmcpbridge/phosphor-ipmi-net@.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=Network IPMI daemon
+After=phosphor-ipmi-host.service
+Requires=sys-subsystem-net-devices-%i.device
+After=sys-subsystem-net-devices-%i.device
+ConditionPathExists=/sys/class/net/%i
+
+[Service]
+ExecStart=/usr/bin/netipmid -c %i
+SyslogIdentifier=netipmid-%i
+Restart=always
+RuntimeDirectory = ipmi
+RuntimeDirectoryPreserve = yes
+StateDirectory = ipmi
+
+[Install]
+DefaultInstance=eth0
+WantedBy=multi-user.target
+RequiredBy=
diff --git a/transport/rmcpbridge/phosphor-ipmi-net@.socket b/transport/rmcpbridge/phosphor-ipmi-net@.socket
new file mode 100644
index 0000000..7dddfb6
--- /dev/null
+++ b/transport/rmcpbridge/phosphor-ipmi-net@.socket
@@ -0,0 +1,10 @@
+[Unit]
+ConditionPathExists=/sys/class/net/%i
+
+[Socket]
+ListenDatagram=623
+BindToDevice=sys-subsystem-net-devices-%i.device
+
+[Install]
+WantedBy=sockets.target
+
diff --git a/transport/rmcpbridge/prng.hpp b/transport/rmcpbridge/prng.hpp
new file mode 100644
index 0000000..95946f7
--- /dev/null
+++ b/transport/rmcpbridge/prng.hpp
@@ -0,0 +1,16 @@
+#include <openssl/rand.h>
+
+namespace crypto
+{
+
+struct prng
+{
+ static unsigned int rand()
+ {
+ unsigned int v;
+ RAND_bytes(reinterpret_cast<unsigned char*>(&v), sizeof(v));
+ return v;
+ }
+};
+
+} // namespace crypto
diff --git a/transport/rmcpbridge/rmcp.hpp b/transport/rmcpbridge/rmcp.hpp
new file mode 100644
index 0000000..b18809a
--- /dev/null
+++ b/transport/rmcpbridge/rmcp.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+
+namespace rmcp
+{
+
+/*
+ * RSP needs more keying material than can be provided by session
+ * integrity key alone. As a result all keying material for the RSP
+ * confidentiality algorithms will be generated by processing a
+ * pre-defined set of constants using HMAC per [RFC2104], keyed by SIK.
+ * These constants are constructed using a hexadecimal octet value
+ * repeated up to the HMAC block size in length starting with the
+ * constant 01h. This mechanism can be used to derive up to 255
+ * HMAC-block-length pieces of keying material from a single SIK.For the
+ * mandatory confidentiality algorithm AES-CBC-128, processing the
+ * following constant will generate the required amount of keying
+ * material.
+ */
+constexpr size_t CONST_N_SIZE = 20;
+using Const_n = std::array<uint8_t, CONST_N_SIZE>;
+
+static constexpr Const_n const_1 = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
+
+static constexpr Const_n const_2 = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02};
+
+} // namespace rmcp
diff --git a/transport/rmcpbridge/sd_event_loop.cpp b/transport/rmcpbridge/sd_event_loop.cpp
new file mode 100644
index 0000000..4d55977
--- /dev/null
+++ b/transport/rmcpbridge/sd_event_loop.cpp
@@ -0,0 +1,262 @@
+#include "sd_event_loop.hpp"
+
+#include "main.hpp"
+#include "message_handler.hpp"
+
+#include <error.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <systemd/sd-daemon.h>
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/signal_set.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <user_channel/channel_layer.hpp>
+
+namespace eventloop
+{
+
+void EventLoop::handleRmcpPacket()
+{
+ try
+ {
+ auto channelPtr = std::make_shared<udpsocket::Channel>(udpSocket);
+
+ // Initialize the Message Handler with the socket channel
+ auto msgHandler = std::make_shared<message::Handler>(channelPtr, io);
+
+ msgHandler->processIncoming();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Executing the IPMI message failed: {ERROR}", "ERROR", e);
+ }
+}
+
+void EventLoop::startRmcpReceive()
+{
+ udpSocket->async_wait(
+ boost::asio::socket_base::wait_read,
+ [this](const boost::system::error_code& ec) {
+ if (!ec)
+ {
+ boost::asio::post(*io, [this]() { startRmcpReceive(); });
+ handleRmcpPacket();
+ }
+ });
+}
+
+int EventLoop::getVLANID(const std::string channel)
+{
+ int vlanid = 0;
+ if (channel.empty())
+ {
+ return 0;
+ }
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ // Enumerate all VLAN + ETHERNET interfaces
+ auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
+ "GetSubTree");
+ req.append(PATH_ROOT, 0,
+ std::vector<std::string>{INTF_VLAN, INTF_ETHERNET});
+ ObjectTree objs;
+ try
+ {
+ auto reply = bus.call(req);
+ reply.read(objs);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("getVLANID: failed to execute/read GetSubTree: {ERROR}",
+ "ERROR", e);
+ return 0;
+ }
+
+ std::string ifService, logicalPath;
+ for (const auto& [path, impls] : objs)
+ {
+ if (path.find(channel) == path.npos)
+ {
+ continue;
+ }
+ for (const auto& [service, intfs] : impls)
+ {
+ bool vlan = false;
+ bool ethernet = false;
+ for (const auto& intf : intfs)
+ {
+ if (intf == INTF_VLAN)
+ {
+ vlan = true;
+ }
+ else if (intf == INTF_ETHERNET)
+ {
+ ethernet = true;
+ }
+ }
+ if (ifService.empty() && (vlan || ethernet))
+ {
+ ifService = service;
+ }
+ if (logicalPath.empty() && vlan)
+ {
+ logicalPath = path;
+ }
+ }
+ }
+
+ // VLAN devices will always have a separate logical object
+ if (logicalPath.empty())
+ {
+ return 0;
+ }
+
+ Value value;
+ auto method = bus.new_method_call(ifService.c_str(), logicalPath.c_str(),
+ PROP_INTF, METHOD_GET);
+ method.append(INTF_VLAN, "Id");
+ try
+ {
+ auto method_reply = bus.call(method);
+ method_reply.read(value);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("getVLANID: failed to execute/read VLAN Id: {ERROR}",
+ "ERROR", e);
+ return 0;
+ }
+
+ vlanid = std::get<uint32_t>(value);
+ if ((vlanid & VLAN_VALUE_MASK) != vlanid)
+ {
+ lg2::error("networkd returned an invalid vlan: {VLAN}", "VLAN", vlanid);
+ return 0;
+ }
+
+ return vlanid;
+}
+
+int EventLoop::setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
+ std::string channel, uint16_t reqPort)
+{
+ std::string iface = channel;
+ static constexpr const char* unboundIface = "rmcpp";
+ if (channel == "")
+ {
+ iface = channel = unboundIface;
+ }
+ else
+ {
+ // If VLANID of this channel is set, bind the socket to this
+ // VLAN logic device
+ auto vlanid = getVLANID(channel);
+ if (vlanid)
+ {
+ iface = iface + "." + std::to_string(vlanid);
+ lg2::debug("This channel has VLAN id: {VLAN}", "VLAN", vlanid);
+ }
+ }
+ // Create our own socket if SysD did not supply one.
+ int listensFdCount = sd_listen_fds(0);
+ if (listensFdCount > 1)
+ {
+ lg2::error("Too many file descriptors received, listensFdCount: {FD}",
+ "FD", listensFdCount);
+ return EXIT_FAILURE;
+ }
+ if (listensFdCount == 1)
+ {
+ int openFd = SD_LISTEN_FDS_START;
+ if (!sd_is_socket(openFd, AF_UNSPEC, SOCK_DGRAM, -1))
+ {
+ lg2::error("Failed to set up systemd-passed socket: {ERROR}",
+ "ERROR", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ udpSocket = std::make_shared<boost::asio::ip::udp::socket>(
+ *io, boost::asio::ip::udp::v6(), openFd);
+ }
+ else
+ {
+ // asio does not natively offer a way to bind to an interface
+ // so it must be done in steps
+ boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v6(), reqPort);
+ udpSocket = std::make_shared<boost::asio::ip::udp::socket>(*io);
+ udpSocket->open(ep.protocol());
+ // bind
+ udpSocket->set_option(
+ boost::asio::ip::udp::socket::reuse_address(true));
+ udpSocket->bind(ep);
+ }
+ // SO_BINDTODEVICE
+ char nameout[IFNAMSIZ];
+ unsigned int lenout = sizeof(nameout);
+ if ((::getsockopt(udpSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
+ nameout, &lenout) == -1))
+ {
+ lg2::error("Failed to read bound device: {ERROR}", "ERROR",
+ strerror(errno));
+ }
+ if (iface != nameout && iface != unboundIface)
+ {
+ // SO_BINDTODEVICE
+ if ((::setsockopt(udpSocket->native_handle(), SOL_SOCKET,
+ SO_BINDTODEVICE, iface.c_str(), iface.size() + 1) ==
+ -1))
+ {
+ lg2::error("Failed to bind to requested interface: {ERROR}",
+ "ERROR", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ lg2::info("Bind to interface: {INTERFACE}", "INTERFACE", iface);
+ }
+ // cannot be constexpr because it gets passed by address
+ const int option_enabled = 1;
+ // common socket stuff; set options to get packet info (DST addr)
+ ::setsockopt(udpSocket->native_handle(), IPPROTO_IP, IP_PKTINFO,
+ &option_enabled, sizeof(option_enabled));
+ ::setsockopt(udpSocket->native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &option_enabled, sizeof(option_enabled));
+
+ // set the dbus name
+ std::string busName = "xyz.openbmc_project.Ipmi.Channel." + channel;
+ try
+ {
+ bus->request_name(busName.c_str());
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to acquire D-Bus name: {NAME}: {ERROR}", "NAME",
+ busName, "ERROR", e);
+ return EXIT_FAILURE;
+ }
+ return 0;
+}
+
+int EventLoop::startEventLoop()
+{
+ startRmcpReceive();
+
+ io->run();
+
+ return EXIT_SUCCESS;
+}
+
+void EventLoop::setupSignal()
+{
+ static boost::asio::signal_set signals(*io, SIGINT, SIGTERM);
+ signals.async_wait([this](const boost::system::error_code& /* error */,
+ int /* signalNumber */) {
+ if (udpSocket)
+ {
+ udpSocket->cancel();
+ udpSocket->close();
+ }
+ io->stop();
+ });
+}
+
+} // namespace eventloop
diff --git a/transport/rmcpbridge/sd_event_loop.hpp b/transport/rmcpbridge/sd_event_loop.hpp
new file mode 100644
index 0000000..b40782c
--- /dev/null
+++ b/transport/rmcpbridge/sd_event_loop.hpp
@@ -0,0 +1,112 @@
+#pragma once
+
+#include "main.hpp"
+#include "sol/sol_manager.hpp"
+
+#include <systemd/sd-event.h>
+
+#include <boost/asio/io_context.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <chrono>
+#include <map>
+#include <string>
+
+namespace ipmi
+{
+namespace rmcpp
+{
+constexpr uint16_t defaultPort = 623;
+} // namespace rmcpp
+} // namespace ipmi
+
+namespace eventloop
+{
+using DbusObjectPath = std::string;
+using DbusService = std::string;
+using DbusInterface = std::string;
+using ObjectTree =
+ std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
+using Value = std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
+ int64_t, uint64_t, double, std::string>;
+// VLANs are a 12-bit value
+constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
+constexpr auto MAPPER_BUS_NAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_OBJ = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_INTF = "xyz.openbmc_project.ObjectMapper";
+constexpr auto PATH_ROOT = "/xyz/openbmc_project/network";
+constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
+constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface";
+constexpr auto METHOD_GET = "Get";
+constexpr auto PROP_INTF = "org.freedesktop.DBus.Properties";
+
+class EventLoop
+{
+ private:
+ struct Private
+ {};
+
+ public:
+ EventLoop(std::shared_ptr<boost::asio::io_context>& io, const Private&) :
+ io(io)
+ {}
+ EventLoop() = delete;
+ ~EventLoop() = default;
+ EventLoop(const EventLoop&) = delete;
+ EventLoop& operator=(const EventLoop&) = delete;
+ EventLoop(EventLoop&&) = delete;
+ EventLoop& operator=(EventLoop&&) = delete;
+
+ /**
+ * @brief Get a reference to the singleton EventLoop
+ *
+ * @return EventLoop reference
+ */
+ static EventLoop& get()
+ {
+ static std::shared_ptr<EventLoop> ptr = nullptr;
+ if (!ptr)
+ {
+ std::shared_ptr<boost::asio::io_context> io = getIo();
+ ptr = std::make_shared<EventLoop>(io, Private());
+ }
+ return *ptr;
+ }
+
+ /** @brief Initialise the event loop and add the handler for incoming
+ * IPMI packets.
+ *
+ * @return EXIT_SUCCESS on success and EXIT_FAILURE on failure.
+ */
+ int startEventLoop();
+
+ /** @brief Set up the socket (if systemd has not already) and
+ * make sure that the bus name matches the specified channel
+ */
+ int setupSocket(std::shared_ptr<sdbusplus::asio::connection>& bus,
+ std::string iface,
+ uint16_t reqPort = ipmi::rmcpp::defaultPort);
+
+ /** @brief set up boost::asio signal handling */
+ void setupSignal();
+
+ private:
+ /** @brief async handler for incoming udp packets */
+ void handleRmcpPacket();
+
+ /** @brief register the async handler for incoming udp packets */
+ void startRmcpReceive();
+
+ /** @brief get vlanid */
+ int getVLANID(const std::string channel);
+
+ /** @brief boost::asio io context to run with
+ */
+ std::shared_ptr<boost::asio::io_context> io;
+
+ /** @brief boost::asio udp socket
+ */
+ std::shared_ptr<boost::asio::ip::udp::socket> udpSocket = nullptr;
+};
+
+} // namespace eventloop
diff --git a/transport/rmcpbridge/session.hpp b/transport/rmcpbridge/session.hpp
new file mode 100644
index 0000000..c12ec2c
--- /dev/null
+++ b/transport/rmcpbridge/session.hpp
@@ -0,0 +1,312 @@
+#pragma once
+
+#include "auth_algo.hpp"
+#include "crypt_algo.hpp"
+#include "endian.hpp"
+#include "integrity_algo.hpp"
+#include "prng.hpp"
+#include "socket_channel.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/sessiondef.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <user_channel/channel_layer.hpp>
+#include <user_channel/user_layer.hpp>
+#include <xyz/openbmc_project/Ipmi/SessionInfo/server.hpp>
+
+#include <chrono>
+#include <exception>
+#include <list>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace session
+{
+
+using namespace std::chrono_literals;
+using SessionID = uint32_t;
+
+enum class Privilege : uint8_t
+{
+ HIGHEST_MATCHING,
+ CALLBACK,
+ USER,
+ OPERATOR,
+ ADMIN,
+ OEM,
+};
+
+// Mask to get only the privilege from requested maximum privlege (RAKP message
+// 1)
+constexpr uint8_t reqMaxPrivMask = 0xF;
+
+/**
+ * @struct SequenceNumbers Session Sequence Numbers
+ *
+ * IPMI v2.0 RMCP+ Session Sequence Numbers are used for rejecting packets that
+ * may have been duplicated by the network or intentionally replayed. There are
+ * two sets of Session SequenceNumbers for a given session.One set of inbound
+ * and outbound sequence numbers is used for authenticated (signed) packets,
+ * and the other set is used for unauthenticated packets.
+ *
+ * The individual Session Sequence Numbers is are initialized to zero whenever
+ * a session is created and incremented by one at the start of outbound
+ * processing for a given packet (i.e. the first transmitted packet has a ‘1’
+ * as the sequence number, not 0). Session Sequence numbers are incremented for
+ * every packet that is transmitted by a given sender, regardless of whether
+ * the payload for the packet is a ‘retry’ or not.
+ */
+struct SequenceNumbers
+{
+ auto get(bool inbound = true) const
+ {
+ return inbound ? in : out;
+ }
+
+ void set(uint32_t seqNumber, bool inbound = true)
+ {
+ inbound ? (in = seqNumber) : (out = seqNumber);
+ }
+
+ auto increment()
+ {
+ return ++out;
+ }
+
+ private:
+ uint32_t in = 0;
+ uint32_t out = 0;
+};
+/**
+ * @class Session
+ *
+ * Encapsulates the data related to an IPMI Session
+ *
+ * Authenticated IPMI communication to the BMC is accomplished by establishing
+ * a session. Once established, a session is identified by a Session ID. The
+ * Session ID may be thought of as a handle that identifies a connection between
+ * a given remote user and the BMC. The specification supports having multiple
+ * active sessions established with the BMC. It is recommended that a BMC
+ * implementation support at least four simultaneous sessions
+ */
+
+using SessionIface = sdbusplus::server::object_t<
+ sdbusplus::xyz::openbmc_project::Ipmi::server::SessionInfo>;
+
+class Session : public SessionIface
+{
+ public:
+ Session() = delete;
+ ~Session() = default;
+ Session(const Session&) = delete;
+ Session& operator=(const Session&) = delete;
+ Session(Session&&) = delete;
+ Session& operator=(Session&&) = delete;
+
+ /**
+ * @brief Session Constructor
+ *
+ * This is issued by the Session Manager when a session is started for
+ * the Open SessionRequest command
+ *
+ * @param[in] inRemoteConsoleSessID - Remote Console Session ID
+ * @param[in] priv - Privilege Level requested in the Command
+ */
+ Session(sdbusplus::bus_t& bus, const char* path,
+ SessionID inRemoteConsoleSessID, SessionID BMCSessionID,
+ char priv) : SessionIface(bus, path)
+ {
+ reqMaxPrivLevel = static_cast<session::Privilege>(priv);
+ bmcSessionID = BMCSessionID;
+ remoteConsoleSessionID = inRemoteConsoleSessID;
+ }
+
+ auto getBMCSessionID() const
+ {
+ return bmcSessionID;
+ }
+
+ auto getRCSessionID() const
+ {
+ return remoteConsoleSessionID;
+ }
+
+ auto getAuthAlgo() const
+ {
+ if (authAlgoInterface)
+ {
+ return authAlgoInterface.get();
+ }
+ else
+ {
+ throw std::runtime_error("Authentication Algorithm Empty");
+ }
+ }
+
+ void setAuthAlgo(std::unique_ptr<cipher::rakp_auth::Interface>&& inAuthAlgo)
+ {
+ authAlgoInterface = std::move(inAuthAlgo);
+ }
+
+ /**
+ * @brief Get Session's Integrity Algorithm
+ *
+ * @return pointer to the integrity algorithm
+ */
+ auto getIntegrityAlgo() const
+ {
+ if (integrityAlgoInterface)
+ {
+ return integrityAlgoInterface.get();
+ }
+ else
+ {
+ throw std::runtime_error("Integrity Algorithm Empty");
+ }
+ }
+
+ /**
+ * @brief Set Session's Integrity Algorithm
+ *
+ * @param[in] integrityAlgo - unique pointer to integrity algorithm
+ * instance
+ */
+ void setIntegrityAlgo(
+ std::unique_ptr<cipher::integrity::Interface>&& integrityAlgo)
+ {
+ integrityAlgoInterface = std::move(integrityAlgo);
+ }
+
+ /** @brief Check if integrity algorithm is enabled for this session.
+ *
+ * @return true if integrity algorithm is enabled else false.
+ */
+ auto isIntegrityAlgoEnabled()
+ {
+ return integrityAlgoInterface ? true : false;
+ }
+
+ /**
+ * @brief Get Session's Confidentiality Algorithm
+ *
+ * @return pointer to the confidentiality algorithm
+ */
+ auto getCryptAlgo() const
+ {
+ if (cryptAlgoInterface)
+ {
+ return cryptAlgoInterface.get();
+ }
+ else
+ {
+ throw std::runtime_error("Confidentiality Algorithm Empty");
+ }
+ }
+
+ /**
+ * @brief Set Session's Confidentiality Algorithm
+ *
+ * @param[in] confAlgo - unique pointer to confidentiality algorithm
+ * instance
+ */
+ void setCryptAlgo(std::unique_ptr<cipher::crypt::Interface>&& cryptAlgo)
+ {
+ cryptAlgoInterface = std::move(cryptAlgo);
+ }
+
+ /** @brief Check if confidentiality algorithm is enabled for this
+ * session.
+ *
+ * @return true if confidentiality algorithm is enabled else false.
+ */
+ auto isCryptAlgoEnabled()
+ {
+ return cryptAlgoInterface ? true : false;
+ }
+
+ void updateLastTransactionTime()
+ {
+ lastTime = std::chrono::steady_clock::now();
+ }
+
+ /**
+ * @brief Session Active Status
+ *
+ * Session Active status is decided upon the Session State and the last
+ * transaction time is compared against the session inactivity timeout.
+ *
+ * @param[in] activeGrace - microseconds of idle time for active sessions
+ * @param[in] setupGrace - microseconds of idle time for sessions in setup
+ *
+ */
+ bool isSessionActive(const std::chrono::microseconds& activeGrace,
+ const std::chrono::microseconds& setupGrace)
+ {
+ auto currentTime = std::chrono::steady_clock::now();
+ auto elapsedMicros =
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ currentTime - lastTime);
+
+ State state = static_cast<session::State>(this->state());
+
+ switch (state)
+ {
+ case State::active:
+ if (elapsedMicros < activeGrace)
+ {
+ return true;
+ }
+ break;
+ case State::setupInProgress:
+ if (elapsedMicros < setupGrace)
+ {
+ return true;
+ }
+ break;
+ case State::tearDownInProgress:
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * @brief Session's Requested Maximum Privilege Level
+ */
+ Privilege reqMaxPrivLevel;
+
+ /**
+ * @brief session's user & channel access details
+ */
+ ipmi::PrivAccess sessionUserPrivAccess{};
+ ipmi::ChannelAccess sessionChannelAccess{};
+
+ SequenceNumbers sequenceNums; // Session Sequence Numbers
+ std::string userName{}; // User Name
+
+ /** @brief Socket channel for communicating with the remote client.*/
+ std::shared_ptr<udpsocket::Channel> channelPtr;
+
+ private:
+ SessionID bmcSessionID = 0; // BMC Session ID
+ SessionID remoteConsoleSessionID = 0; // Remote Console Session ID
+
+ // Authentication Algorithm Interface for the Session
+ std::unique_ptr<cipher::rakp_auth::Interface> authAlgoInterface;
+
+ // Integrity Algorithm Interface for the Session
+ std::unique_ptr<cipher::integrity::Interface> integrityAlgoInterface =
+ nullptr;
+
+ // Confidentiality Algorithm Interface for the Session
+ std::unique_ptr<cipher::crypt::Interface> cryptAlgoInterface = nullptr;
+
+ // Last Transaction Time
+ decltype(std::chrono::steady_clock::now()) lastTime;
+};
+
+} // namespace session
diff --git a/transport/rmcpbridge/sessions_manager.cpp b/transport/rmcpbridge/sessions_manager.cpp
new file mode 100644
index 0000000..8c18cba
--- /dev/null
+++ b/transport/rmcpbridge/sessions_manager.cpp
@@ -0,0 +1,353 @@
+#include "sessions_manager.hpp"
+
+#include "main.hpp"
+#include "session.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <user_channel/channel_layer.hpp>
+
+#include <algorithm>
+#include <cstdlib>
+#include <iomanip>
+#include <memory>
+
+namespace session
+{
+
+static std::array<uint8_t, session::maxNetworkInstanceSupported>
+ ipmiNetworkChannelNumList = {0};
+
+void Manager::setNetworkInstance(void)
+{
+ uint8_t index = 0, ch = 1;
+ // Constructing net-ipmid instances list based on channel info
+ // valid channel start from 1 to 15 and assuming max 4 LAN channel
+ // supported
+
+ while (ch < ipmi::maxIpmiChannels &&
+ index < session::maxNetworkInstanceSupported)
+ {
+ ipmi::ChannelInfo chInfo;
+ ipmi::getChannelInfo(ch, chInfo);
+ if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
+ ipmi::EChannelMediumType::lan8032)
+ {
+ if (getInterfaceIndex() == ch)
+ {
+ ipmiNetworkInstance = index;
+ }
+
+ ipmiNetworkChannelNumList[index] = ch;
+ index++;
+ }
+ ch++;
+ }
+}
+
+uint8_t Manager::getNetworkInstance(void)
+{
+ return ipmiNetworkInstance;
+}
+
+void Manager::managerInit(const std::string& channel)
+{
+ /*
+ * Session ID is 0000_0000h for messages that are sent outside the session.
+ * The session setup commands are sent on this session, so when the session
+ * manager comes up, is creates the Session ID 0000_0000h. It is active
+ * through the lifetime of the Session Manager.
+ */
+
+ objManager = std::make_unique<sdbusplus::server::manager_t>(
+ *getSdBus(), session::sessionManagerRootPath);
+
+ auto objPath = std::string(session::sessionManagerRootPath) + "/" +
+ channel + "/0";
+
+ chName = channel;
+ setNetworkInstance();
+ sessionsMap.emplace(
+ 0, std::make_shared<Session>(*getSdBus(), objPath.c_str(), 0, 0, 0));
+
+ // set up the timer for clearing out stale sessions
+ scheduleSessionCleaner(std::chrono::microseconds(3 * 1000 * 1000));
+}
+
+std::shared_ptr<Session> Manager::startSession(
+ SessionID remoteConsoleSessID, Privilege priv,
+ cipher::rakp_auth::Algorithms authAlgo,
+ cipher::integrity::Algorithms intAlgo, cipher::crypt::Algorithms cryptAlgo)
+{
+ std::shared_ptr<Session> session = nullptr;
+ SessionID bmcSessionID = 0;
+ cleanStaleEntries();
+ // set up the timer for monitoring this session
+ scheduleSessionCleaner(std::chrono::microseconds(1 * 1000 * 1000));
+
+ uint8_t sessionHandle = 0;
+
+ auto activeSessions = sessionsMap.size() - session::maxSessionlessCount;
+
+ if (activeSessions < maxSessionHandles)
+ {
+ do
+ {
+ bmcSessionID = (crypto::prng::rand());
+ bmcSessionID &= session::multiIntfaceSessionIDMask;
+ // In sessionID , BIT 31 BIT30 are used for netipmid instance
+ bmcSessionID |= static_cast<uint32_t>(ipmiNetworkInstance) << 30;
+ /*
+ * Every IPMI Session has two ID's attached to it Remote Console
+ * Session ID and BMC Session ID. The remote console ID is passed
+ * along with the Open Session request command. The BMC session ID
+ * is the key for the session map and is generated using std::rand.
+ * There is a rare chance for collision of BMC session ID, so the
+ * following check validates that. In the case of collision the
+ * created session is reset and a new session is created for
+ * validating collision.
+ */
+ auto iterator = sessionsMap.find(bmcSessionID);
+ if (iterator != sessionsMap.end())
+ {
+ // Detected BMC Session ID collisions
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ } while (1);
+
+ sessionHandle = storeSessionHandle(bmcSessionID);
+
+ if (!sessionHandle)
+ {
+ throw std::runtime_error(
+ "Invalid sessionHandle - No sessionID slot ");
+ }
+ sessionHandle &= session::multiIntfaceSessionHandleMask;
+ // In sessionID , BIT 31 BIT30 are used for netipmid instance
+ sessionHandle |= static_cast<uint8_t>(ipmiNetworkInstance) << 6;
+ std::stringstream sstream;
+ sstream << std::hex << bmcSessionID;
+ std::stringstream shstream;
+ shstream << std::hex << (int)sessionHandle;
+ auto objPath = std::string(session::sessionManagerRootPath) + "/" +
+ chName + "/" + sstream.str() + "_" + shstream.str();
+ session = std::make_shared<Session>(*getSdBus(), objPath.c_str(),
+ remoteConsoleSessID, bmcSessionID,
+ static_cast<uint8_t>(priv));
+
+ // Set the Authentication Algorithm
+ switch (authAlgo)
+ {
+ case cipher::rakp_auth::Algorithms::RAKP_HMAC_SHA1:
+ {
+ session->setAuthAlgo(
+ std::make_unique<cipher::rakp_auth::AlgoSHA1>(intAlgo,
+ cryptAlgo));
+ break;
+ }
+ case cipher::rakp_auth::Algorithms::RAKP_HMAC_SHA256:
+ {
+ session->setAuthAlgo(
+ std::make_unique<cipher::rakp_auth::AlgoSHA256>(intAlgo,
+ cryptAlgo));
+ break;
+ }
+ default:
+ {
+ throw std::runtime_error("Invalid Authentication Algorithm");
+ }
+ }
+
+ sessionsMap.emplace(bmcSessionID, session);
+ session->sessionHandle(sessionHandle);
+
+ return session;
+ }
+
+ lg2::info("No free RMCP+ sessions left");
+
+ throw std::runtime_error("No free sessions left");
+}
+
+bool Manager::stopSession(SessionID bmcSessionID)
+{
+ auto iter = sessionsMap.find(bmcSessionID);
+ if (iter != sessionsMap.end())
+ {
+ iter->second->state(
+ static_cast<uint8_t>(session::State::tearDownInProgress));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+std::shared_ptr<Session> Manager::getSession(SessionID sessionID,
+ RetrieveOption option)
+{
+ switch (option)
+ {
+ case RetrieveOption::BMC_SESSION_ID:
+ {
+ auto iter = sessionsMap.find(sessionID);
+ if (iter != sessionsMap.end())
+ {
+ return iter->second;
+ }
+ break;
+ }
+ case RetrieveOption::RC_SESSION_ID:
+ {
+ auto iter = std::find_if(
+ sessionsMap.begin(), sessionsMap.end(),
+ [sessionID](
+ const std::pair<const uint32_t, std::shared_ptr<Session>>&
+ in) -> bool {
+ return sessionID == in.second->getRCSessionID();
+ });
+
+ if (iter != sessionsMap.end())
+ {
+ return iter->second;
+ }
+ break;
+ }
+ default:
+ throw std::runtime_error("Invalid retrieval option");
+ }
+
+ throw std::runtime_error("Session ID not found");
+}
+
+void Manager::cleanStaleEntries()
+{
+ // with overflow = min(1, max - active sessions)
+ // active idle time in seconds = 60 / overflow^3
+ constexpr int baseIdleMicros = 60 * 1000 * 1000;
+ // no +1 for the zero session here because this is just active sessions
+ int sessionDivisor =
+ getActiveSessionCount() - session::maxSessionCountPerChannel;
+ sessionDivisor = std::max(0, sessionDivisor) + 1;
+ sessionDivisor = sessionDivisor * sessionDivisor * sessionDivisor;
+ int activeMicros = baseIdleMicros / sessionDivisor;
+
+ // with overflow = min(1, max - total sessions)
+ // setup idle time in seconds = max(3, 60 / overflow^3)
+
+ // +1 for the zero session here because size() counts that too
+ int setupDivisor =
+ sessionsMap.size() - (session::maxSessionCountPerChannel + 1);
+ setupDivisor = std::max(0, setupDivisor) + 1;
+ setupDivisor = setupDivisor * setupDivisor * setupDivisor;
+ constexpr int maxSetupMicros = 3 * 1000 * 1000;
+ int setupMicros = std::min(maxSetupMicros, baseIdleMicros / setupDivisor);
+
+ std::chrono::microseconds activeGrace(activeMicros);
+ std::chrono::microseconds setupGrace(setupMicros);
+
+ for (auto iter = sessionsMap.begin(); iter != sessionsMap.end();)
+ {
+ auto session = iter->second;
+ // special handling for sessionZero
+ if (session->getBMCSessionID() == session::sessionZero)
+ {
+ iter++;
+ continue;
+ }
+ if (!(session->isSessionActive(activeGrace, setupGrace)))
+ {
+ lg2::info(
+ "Removing idle IPMI LAN session, id: {ID}, handler: {HANDLE}",
+ "ID", session->getBMCSessionID(), "HANDLE",
+ getSessionHandle(session->getBMCSessionID()));
+ sessionHandleMap[getSessionHandle(session->getBMCSessionID())] = 0;
+ iter = sessionsMap.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+ if (sessionsMap.size() > 1)
+ {
+ constexpr int maxCleanupDelay = 1 * 1000 * 1000;
+ std::chrono::microseconds cleanupDelay(
+ std::min(setupMicros, maxCleanupDelay));
+ scheduleSessionCleaner(cleanupDelay);
+ }
+}
+
+uint8_t Manager::storeSessionHandle(SessionID bmcSessionID)
+{
+ // Handler index 0 is reserved for invalid session.
+ // index starts with 1, for direct usage. Index 0 reserved
+ for (size_t i = 1; i < session::maxSessionHandles; i++)
+ {
+ if (sessionHandleMap[i] == 0)
+ {
+ sessionHandleMap[i] = bmcSessionID;
+ return i;
+ }
+ }
+ return 0;
+}
+
+uint32_t Manager::getSessionIDbyHandle(uint8_t sessionHandle) const
+{
+ if (sessionHandle < session::maxSessionHandles)
+ {
+ return sessionHandleMap[sessionHandle];
+ }
+ return 0;
+}
+
+uint8_t Manager::getSessionHandle(SessionID bmcSessionID) const
+{
+ // Handler index 0 is reserved for invalid session.
+ // index starts with 1, for direct usage. Index 0 reserved
+ for (size_t i = 1; i < session::maxSessionHandles; i++)
+ {
+ if (sessionHandleMap[i] == bmcSessionID)
+ {
+ return (i);
+ }
+ }
+ return 0;
+}
+uint8_t Manager::getActiveSessionCount() const
+{
+ return (std::count_if(
+ sessionsMap.begin(), sessionsMap.end(),
+ [](const std::pair<const uint32_t, std::shared_ptr<Session>>& in)
+ -> bool {
+ return in.second->state() ==
+ static_cast<uint8_t>(session::State::active);
+ }));
+}
+
+void Manager::scheduleSessionCleaner(const std::chrono::microseconds& when)
+{
+ std::chrono::duration expTime =
+ timer.expiry() - boost::asio::steady_timer::clock_type::now();
+ if (expTime > std::chrono::microseconds(0) && expTime < when)
+ {
+ // if timer has not already expired AND requested timeout is greater
+ // than current timeout then ignore this new requested timeout
+ return;
+ }
+ timer.expires_after(when);
+ timer.async_wait([this](const boost::system::error_code& ec) {
+ if (!ec)
+ {
+ cleanStaleEntries();
+ }
+ });
+}
+
+} // namespace session
diff --git a/transport/rmcpbridge/sessions_manager.hpp b/transport/rmcpbridge/sessions_manager.hpp
new file mode 100644
index 0000000..d6f847b
--- /dev/null
+++ b/transport/rmcpbridge/sessions_manager.hpp
@@ -0,0 +1,167 @@
+#pragma once
+
+#include "main.hpp"
+#include "session.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/sessiondef.hpp>
+
+#include <chrono>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+
+namespace session
+{
+
+enum class RetrieveOption
+{
+ BMC_SESSION_ID,
+ RC_SESSION_ID,
+};
+
+static constexpr size_t maxSessionHandles = multiIntfaceSessionHandleMask;
+
+/**
+ * @class Manager
+ *
+ * Manager class acts a manager for the IPMI sessions and provides interfaces
+ * to start a session, stop a session and get reference to the session objects.
+ *
+ */
+
+class Manager
+{
+ private:
+ struct Private
+ {};
+
+ public:
+ // BMC Session ID is the key for the map
+ using SessionMap = std::map<SessionID, std::shared_ptr<Session>>;
+
+ Manager() = delete;
+ Manager(std::shared_ptr<boost::asio::io_context>& io, const Private&) :
+ io(io), timer(*io) {};
+ ~Manager() = default;
+ Manager(const Manager&) = delete;
+ Manager& operator=(const Manager&) = delete;
+ Manager(Manager&&) = default;
+ Manager& operator=(Manager&&) = default;
+
+ /**
+ * @brief Get a reference to the singleton Manager
+ *
+ * @return Manager reference
+ */
+ static Manager& get()
+ {
+ static std::shared_ptr<Manager> ptr = nullptr;
+ if (!ptr)
+ {
+ std::shared_ptr<boost::asio::io_context> io = getIo();
+ ptr = std::make_shared<Manager>(io, Private());
+ if (!ptr)
+ {
+ throw std::runtime_error("failed to create session manager");
+ }
+ }
+ return *ptr;
+ }
+
+ /**
+ * @brief Start an IPMI session
+ *
+ * @param[in] remoteConsoleSessID - Remote Console Session ID mentioned
+ * in the Open SessionRequest Command
+ * @param[in] priv - Privilege level requested
+ * @param[in] authAlgo - Authentication Algorithm
+ * @param[in] intAlgo - Integrity Algorithm
+ * @param[in] cryptAlgo - Confidentiality Algorithm
+ *
+ * @return session handle on success and nullptr on failure
+ *
+ */
+ std::shared_ptr<Session> startSession(
+ SessionID remoteConsoleSessID, Privilege priv,
+ cipher::rakp_auth::Algorithms authAlgo,
+ cipher::integrity::Algorithms intAlgo,
+ cipher::crypt::Algorithms cryptAlgo);
+
+ /**
+ * @brief Stop IPMI Session
+ *
+ * @param[in] bmcSessionID - BMC Session ID
+ *
+ * @return true on success and failure if session ID is invalid
+ *
+ */
+ bool stopSession(SessionID bmcSessionID);
+
+ /**
+ * @brief Get Session Handle
+ *
+ * @param[in] sessionID - Session ID
+ * @param[in] option - Select between BMC Session ID and Remote Console
+ * Session ID, Default option is BMC Session ID
+ *
+ * @return session handle on success and nullptr on failure
+ *
+ */
+ std::shared_ptr<Session> getSession(
+ SessionID sessionID,
+ RetrieveOption option = RetrieveOption::BMC_SESSION_ID);
+ uint8_t getActiveSessionCount() const;
+ uint8_t getSessionHandle(SessionID bmcSessionID) const;
+ uint8_t storeSessionHandle(SessionID bmcSessionID);
+ uint32_t getSessionIDbyHandle(uint8_t sessionHandle) const;
+
+ void managerInit(const std::string& channel);
+
+ uint8_t getNetworkInstance(void);
+
+ /**
+ * @brief Clean Session Stale Entries
+ *
+ * Schedules cleaning the inactive sessions entries from the Session Map
+ */
+ void scheduleSessionCleaner(const std::chrono::microseconds& grace);
+
+ private:
+ /**
+ * @brief reclaim system resources by limiting idle sessions
+ *
+ * Limits on active, authenticated sessions are calculated independently
+ * from in-setup sessions, which are not required to be authenticated. This
+ * will prevent would-be DoS attacks by calling a bunch of Open Session
+ * requests to fill up all available sessions. Too many active sessions will
+ * trigger a shorter timeout, but is unaffected by setup session counts.
+ *
+ * For active sessions, grace time is inversely proportional to (the number
+ * of active sessions beyond max sessions per channel)^3
+ *
+ * For sessions in setup, grace time is inversely proportional to (the
+ * number of total sessions beyond max sessions per channel)^3, with a max
+ * of 3 seconds
+ */
+ void cleanStaleEntries();
+
+ std::shared_ptr<boost::asio::io_context> io;
+ boost::asio::steady_timer timer;
+
+ std::array<uint32_t, session::maxSessionHandles> sessionHandleMap = {0};
+
+ /**
+ * @brief Session Manager keeps the session objects as a sorted
+ * associative container with Session ID as the unique key
+ */
+ SessionMap sessionsMap;
+ std::unique_ptr<sdbusplus::server::manager_t> objManager = nullptr;
+ std::string chName{}; // Channel Name
+ uint8_t ipmiNetworkInstance = 0;
+ void setNetworkInstance(void);
+};
+
+} // namespace session
diff --git a/transport/rmcpbridge/socket_channel.hpp b/transport/rmcpbridge/socket_channel.hpp
new file mode 100644
index 0000000..0edfcd5
--- /dev/null
+++ b/transport/rmcpbridge/socket_channel.hpp
@@ -0,0 +1,245 @@
+#pragma once
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <boost/asio/ip/udp.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <variant>
+#include <vector>
+
+namespace udpsocket
+{
+static constexpr uint8_t v4v6Index = 12;
+
+/** @class Channel
+ *
+ * @brief Provides encapsulation for UDP socket operations like Read, Peek,
+ * Write, Remote peer's IP Address and Port.
+ */
+class Channel
+{
+ public:
+ Channel() = delete;
+ ~Channel() = default;
+ Channel(const Channel& right) = delete;
+ Channel& operator=(const Channel& right) = delete;
+ Channel(Channel&&) = delete;
+ Channel& operator=(Channel&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * Initialize the IPMI socket object with the socket descriptor
+ *
+ * @param [in] pointer to a boost::asio udp socket object
+ *
+ * @return None
+ */
+ explicit Channel(std::shared_ptr<boost::asio::ip::udp::socket> socket) :
+ socket(socket)
+ {}
+ /**
+ * @brief Check if ip address is ipv4 mapped ipv6
+ *
+ * @param v6Addr : in6_addr obj
+ *
+ * @return true if ipv4 mapped ipv6 else return false
+ */
+ bool isIpv4InIpv6(const struct in6_addr& v6Addr) const
+ {
+ constexpr uint8_t prefix[v4v6Index] = {0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0xff, 0xff};
+ return 0 == std::memcmp(&v6Addr.s6_addr[0], &prefix[0], sizeof(prefix));
+ }
+ /**
+ * @brief Fetch the IP address of the remote peer
+ *
+ * @param remoteIpv4Addr : ipv4 address is assigned to it.
+ *
+ * Returns the IP address of the remote peer which is connected to this
+ * socket
+ *
+ * @return IP address of the remote peer
+ */
+ std::string getRemoteAddress(uint32_t& remoteIpv4Addr) const
+ {
+ const char* retval = nullptr;
+ if (sockAddrSize == sizeof(sockaddr_in))
+ {
+ char ipv4addr[INET_ADDRSTRLEN];
+ const sockaddr_in* sa =
+ reinterpret_cast<const sockaddr_in*>(&remoteSockAddr);
+ remoteIpv4Addr = sa->sin_addr.s_addr;
+ retval =
+ inet_ntop(AF_INET, &(sa->sin_addr), ipv4addr, sizeof(ipv4addr));
+ }
+ else if (sockAddrSize == sizeof(sockaddr_in6))
+ {
+ char ipv6addr[INET6_ADDRSTRLEN];
+ const sockaddr_in6* sa =
+ reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr);
+
+ if (isIpv4InIpv6(sa->sin6_addr))
+ {
+ std::copy_n(&sa->sin6_addr.s6_addr[v4v6Index],
+ sizeof(remoteIpv4Addr),
+ reinterpret_cast<uint8_t*>(&remoteIpv4Addr));
+ }
+ retval = inet_ntop(AF_INET6, &(sa->sin6_addr), ipv6addr,
+ sizeof(ipv6addr));
+ }
+
+ if (retval)
+ {
+ return retval;
+ }
+ lg2::error("Error in inet_ntop: {ERROR}", "ERROR", strerror(errno));
+ return std::string();
+ }
+
+ /**
+ * @brief Fetch the port number of the remote peer
+ *
+ * Returns the port number of the remote peer
+ *
+ * @return Port number
+ *
+ */
+ uint16_t getPort() const
+ {
+ if (sockAddrSize == sizeof(sockaddr_in))
+ {
+ return ntohs(reinterpret_cast<const sockaddr_in*>(&remoteSockAddr)
+ ->sin_port);
+ }
+ if (sockAddrSize == sizeof(sockaddr_in6))
+ {
+ return ntohs(reinterpret_cast<const sockaddr_in6*>(&remoteSockAddr)
+ ->sin6_port);
+ }
+ return 0;
+ }
+
+ /**
+ * @brief Read the incoming packet
+ *
+ * Reads the data available on the socket
+ *
+ * @return A tuple with return code and vector with the buffer
+ * In case of success, the vector is populated with the data
+ * available on the socket and return code is 0.
+ * In case of error, the return code is < 0 and vector is set
+ * to size 0.
+ */
+ std::tuple<int, std::vector<uint8_t>> read()
+ {
+ // cannot use the standard asio reading mechanism because it does not
+ // provide a mechanism to reach down into the depths and use a msghdr
+ std::vector<uint8_t> packet(socket->available());
+ iovec iov = {packet.data(), packet.size()};
+ char msgCtrl[1024];
+ msghdr msg = {&remoteSockAddr, sizeof(remoteSockAddr), &iov, 1,
+ msgCtrl, sizeof(msgCtrl), 0};
+
+ ssize_t bytesReceived = recvmsg(socket->native_handle(), &msg, 0);
+ // Read of the packet failed
+ if (bytesReceived < 0)
+ {
+ // something bad happened; bail
+ lg2::error("Error in recvmsg: {ERROR}", "ERROR",
+ strerror(-bytesReceived));
+ return std::make_tuple(-errno, std::vector<uint8_t>());
+ }
+ // save the size of either ipv4 or i4v6 sockaddr
+ sockAddrSize = msg.msg_namelen;
+
+ // extract the destination address from the message
+ cmsghdr* cmsg;
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0;
+ cmsg = CMSG_NXTHDR(&msg, cmsg))
+ {
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
+ {
+ // save local address from the pktinfo4
+ pktinfo4 = *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg));
+ }
+ if (cmsg->cmsg_level == IPPROTO_IPV6 &&
+ cmsg->cmsg_type == IPV6_PKTINFO)
+ {
+ // save local address from the pktinfo6
+ pktinfo6 = *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg));
+ }
+ }
+ return std::make_tuple(0, packet);
+ }
+
+ /**
+ * @brief Write the outgoing packet
+ *
+ * Writes the data in the vector to the socket
+ *
+ * @param [in] inBuffer
+ * The vector would be the buffer of data to write to the socket.
+ *
+ * @return In case of success the return code is the number of bytes
+ * written and return code is < 0 in case of failure.
+ */
+ int write(const std::vector<uint8_t>& inBuffer)
+ {
+ // in order to make sure packets go back out from the same
+ // IP address they came in on, sendmsg must be used instead
+ // of the boost::asio::ip::send or sendto
+ iovec iov = {const_cast<uint8_t*>(inBuffer.data()), inBuffer.size()};
+ char msgCtrl[1024];
+ msghdr msg = {&remoteSockAddr, sockAddrSize, &iov, 1,
+ msgCtrl, sizeof(msgCtrl), 0};
+ int cmsg_space = 0;
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ if (pktinfo6)
+ {
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ *reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo6;
+ cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
+ }
+ else if (pktinfo4)
+ {
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ *reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)) = *pktinfo4;
+ cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
+ }
+ msg.msg_controllen = cmsg_space;
+ int ret = sendmsg(socket->native_handle(), &msg, 0);
+ if (ret < 0)
+ {
+ lg2::error("Error in sendmsg: {ERROR}", "ERROR", strerror(-ret));
+ }
+ return ret;
+ }
+
+ /**
+ * @brief Returns file descriptor for the socket
+ */
+ auto getHandle(void) const
+ {
+ return socket->native_handle();
+ }
+
+ private:
+ std::shared_ptr<boost::asio::ip::udp::socket> socket;
+ sockaddr_storage remoteSockAddr;
+ socklen_t sockAddrSize;
+ std::optional<in_pktinfo> pktinfo4;
+ std::optional<in6_pktinfo> pktinfo6;
+};
+
+} // namespace udpsocket
diff --git a/transport/rmcpbridge/sol/console_buffer.hpp b/transport/rmcpbridge/sol/console_buffer.hpp
new file mode 100644
index 0000000..dbb75c9
--- /dev/null
+++ b/transport/rmcpbridge/sol/console_buffer.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <deque>
+#include <vector>
+
+namespace sol
+{
+
+using ConsoleBuffer = std::deque<uint8_t>;
+
+/** @class ConsoleData
+ *
+ * The console data is the buffer that holds the data that comes from the host
+ * console which is to be sent to the remote console. The buffer is needed due
+ * to the latency with the IPMI remote client. The current support for the
+ * buffer is to support one instance of the SOL payload.
+ */
+class ConsoleData
+{
+ public:
+ /** @brief Get the current size of the host console buffer.
+ *
+ * @return size of the host console buffer.
+ */
+ auto size() const noexcept
+ {
+ return data.size();
+ }
+
+ /** @brief Read host console data.
+ *
+ * This API would return the iterator to the read data from the
+ * console data buffer.
+ *
+ * @return iterator to read data from the buffer
+ */
+ auto read() const
+ {
+ return data.cbegin();
+ }
+
+ /** @brief Write host console data.
+ *
+ * This API would append the input data to the host console buffer.
+ *
+ * @param[in] input - data to be written to the console buffer.
+ */
+ void write(const std::vector<uint8_t>& input)
+ {
+ data.insert(data.end(), input.begin(), input.end());
+ }
+
+ /** @brief Erase console buffer.
+ *
+ * @param[in] size - the number of bytes to be erased from the console
+ * buffer.
+ *
+ * @note If the console buffer has less bytes that that was requested,
+ * then the available size is erased.
+ */
+ void erase(size_t size) noexcept
+ {
+ data.erase(data.begin(), data.begin() + std::min(data.size(), size));
+ }
+
+ private:
+ /** @brief Storage for host console data. */
+ ConsoleBuffer data;
+};
+
+} // namespace sol
diff --git a/transport/rmcpbridge/sol/sol_context.cpp b/transport/rmcpbridge/sol/sol_context.cpp
new file mode 100644
index 0000000..21632a4
--- /dev/null
+++ b/transport/rmcpbridge/sol/sol_context.cpp
@@ -0,0 +1,348 @@
+#include "sol_context.hpp"
+
+#include "main.hpp"
+#include "message_handler.hpp"
+#include "sd_event_loop.hpp"
+#include "sessions_manager.hpp"
+#include "sol_manager.hpp"
+
+#include <errno.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace sol
+{
+using namespace phosphor::logging;
+
+Context::Context(std::shared_ptr<boost::asio::io_context> io,
+ uint8_t maxRetryCount, uint8_t sendThreshold, uint8_t instance,
+ session::SessionID sessionID) :
+ accumulateTimer(*io), retryTimer(*io), maxRetryCount(maxRetryCount),
+ retryCounter(maxRetryCount), sendThreshold(sendThreshold),
+ payloadInstance(instance), sessionID(sessionID)
+{
+ session = session::Manager::get().getSession(sessionID);
+}
+
+std::shared_ptr<Context> Context::makeContext(
+ std::shared_ptr<boost::asio::io_context> io, uint8_t maxRetryCount,
+ uint8_t sendThreshold, uint8_t instance, session::SessionID sessionID)
+{
+ auto ctx = std::make_shared<Context>(io, maxRetryCount, sendThreshold,
+ instance, sessionID);
+ ctx->enableAccumulateTimer(true);
+ return ctx;
+}
+
+void Context::enableAccumulateTimer(bool enable)
+{
+ // fetch the timeout from the SOL manager
+ std::chrono::microseconds interval = sol::Manager::get().accumulateInterval;
+
+ if (enable)
+ {
+ auto bufferSize = sol::Manager::get().dataBuffer.size();
+ if (bufferSize > sendThreshold)
+ {
+ try
+ {
+ int rc = sendOutboundPayload();
+ if (rc == 0)
+ {
+ return;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to call the sendOutboundPayload method: {ERROR}",
+ "ERROR", e);
+ return;
+ }
+ }
+ accumulateTimer.expires_after(interval);
+ std::weak_ptr<Context> weakRef = weak_from_this();
+ accumulateTimer.async_wait(
+ [weakRef](const boost::system::error_code& ec) {
+ std::shared_ptr<Context> self = weakRef.lock();
+ if (!ec && self)
+ {
+ self->charAccTimerHandler();
+ }
+ });
+ }
+ else
+ {
+ accumulateTimer.cancel();
+ }
+}
+
+void Context::enableRetryTimer(bool enable)
+{
+ if (enable)
+ {
+ // fetch the timeout from the SOL manager
+ std::chrono::microseconds interval = sol::Manager::get().retryInterval;
+ retryTimer.expires_after(interval);
+ std::weak_ptr<Context> weakRef = weak_from_this();
+ retryTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+ std::shared_ptr<Context> self = weakRef.lock();
+ if (!ec && self)
+ {
+ self->retryTimerHandler();
+ }
+ });
+ }
+ else
+ {
+ retryTimer.cancel();
+ }
+}
+
+void Context::processInboundPayload(uint8_t seqNum, uint8_t ackSeqNum,
+ uint8_t count, bool status, bool isBreak,
+ const std::vector<uint8_t>& input)
+{
+ uint8_t respAckSeqNum = 0;
+ uint8_t acceptedCount = 0;
+ auto ack = false;
+
+ /*
+ * Check if the Inbound sequence number is same as the expected one.
+ * If the Packet Sequence Number is 0, it is an ACK-Only packet. Multiple
+ * outstanding sequence numbers are not supported in this version of the SOL
+ * specification. Retried packets use the same sequence number as the first
+ * packet.
+ */
+ if (seqNum && (seqNum != seqNums.get(true)))
+ {
+ lg2::info("Out of sequence SOL packet - packet is dropped");
+ return;
+ }
+
+ /*
+ * Check if the expected ACK/NACK sequence number is same as the
+ * ACK/NACK sequence number in the packet. If packet ACK/NACK sequence
+ * number is 0, then it is an informational packet. No request packet being
+ * ACK'd or NACK'd.
+ */
+ if (ackSeqNum && (ackSeqNum != seqNums.get(false)))
+ {
+ lg2::info("Out of sequence ack number - SOL packet is dropped");
+ return;
+ }
+
+ /*
+ * Retry the SOL payload packet in the following conditions:
+ *
+ * a) NACK in Operation/Status
+ * b) Accepted Character Count does not match with the sent out SOL payload
+ * c) Non-zero Packet ACK/NACK Sequence Number
+ */
+ if (status || ((count != expectedCharCount) && ackSeqNum))
+ {
+ resendPayload(noClear);
+ enableRetryTimer(false);
+ enableRetryTimer(true);
+ return;
+ }
+ /*
+ * Clear the sent data once the acknowledgment sequence number matches
+ * and the expected character count matches.
+ */
+ else if ((count == expectedCharCount) && ackSeqNum)
+ {
+ // Clear the Host Console Buffer
+ sol::Manager::get().dataBuffer.erase(count);
+
+ // Once it is acknowledged stop the retry interval timer
+ enableRetryTimer(false);
+
+ retryCounter = maxRetryCount;
+ expectedCharCount = 0;
+ payloadCache.clear();
+ }
+
+ if (isBreak && seqNum)
+ {
+ lg2::info("Writing break to console socket descriptor");
+ constexpr uint8_t sysrqValue = 72; // use this to notify sol server
+ const std::vector<uint8_t> test{sysrqValue};
+ auto ret = sol::Manager::get().writeConsoleSocket(test, isBreak);
+ if (ret)
+ {
+ lg2::error("Writing to console socket descriptor failed: {ERROR}",
+ "ERROR", strerror(errno));
+ }
+ }
+
+ isBreak = false;
+ // Write character data to the Host Console
+ if (!input.empty() && seqNum)
+ {
+ auto rc = sol::Manager::get().writeConsoleSocket(input, isBreak);
+ if (rc)
+ {
+ lg2::error("Writing to console socket descriptor failed: {ERROR}",
+ "ERROR", strerror(errno));
+ ack = true;
+ }
+ else
+ {
+ respAckSeqNum = seqNum;
+ ack = false;
+ acceptedCount = input.size();
+ }
+ }
+ /*
+ * SOL payload with no character data and valid sequence number can be used
+ * as method to keep the SOL session active.
+ */
+ else if (input.empty() && seqNum)
+ {
+ respAckSeqNum = seqNum;
+ }
+
+ if (seqNum != 0)
+ {
+ seqNums.incInboundSeqNum();
+ prepareResponse(respAckSeqNum, acceptedCount, ack);
+ }
+ else
+ {
+ enableAccumulateTimer(true);
+ }
+}
+
+void Context::prepareResponse(uint8_t ackSeqNum, uint8_t count, bool ack)
+{
+ auto bufferSize = sol::Manager::get().dataBuffer.size();
+
+ /* Sent a ACK only response */
+ if (payloadCache.size() != 0 || (bufferSize < sendThreshold))
+ {
+ enableAccumulateTimer(true);
+
+ std::vector<uint8_t> outPayload(sizeof(Payload));
+ auto response = reinterpret_cast<Payload*>(outPayload.data());
+ response->packetSeqNum = 0;
+ response->packetAckSeqNum = ackSeqNum;
+ response->acceptedCharCount = count;
+ response->outOperation.ack = ack;
+ sendPayload(outPayload);
+ return;
+ }
+
+ auto readSize = std::min(bufferSize, MAX_PAYLOAD_SIZE);
+ payloadCache.resize(sizeof(Payload) + readSize);
+ auto response = reinterpret_cast<Payload*>(payloadCache.data());
+ response->packetAckSeqNum = ackSeqNum;
+ response->acceptedCharCount = count;
+ response->outOperation.ack = ack;
+ response->packetSeqNum = seqNums.incOutboundSeqNum();
+
+ auto handle = sol::Manager::get().dataBuffer.read();
+ std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload));
+ expectedCharCount = readSize;
+
+ enableRetryTimer(true);
+ enableAccumulateTimer(false);
+
+ sendPayload(payloadCache);
+}
+
+int Context::sendOutboundPayload()
+{
+ if (payloadCache.size() != 0)
+ {
+ return -1;
+ }
+
+ auto bufferSize = sol::Manager::get().dataBuffer.size();
+ auto readSize = std::min(bufferSize, MAX_PAYLOAD_SIZE);
+
+ payloadCache.resize(sizeof(Payload) + readSize);
+ auto response = reinterpret_cast<Payload*>(payloadCache.data());
+ response->packetAckSeqNum = 0;
+ response->acceptedCharCount = 0;
+ response->outOperation.ack = false;
+ response->packetSeqNum = seqNums.incOutboundSeqNum();
+
+ auto handle = sol::Manager::get().dataBuffer.read();
+ std::copy_n(handle, readSize, payloadCache.data() + sizeof(Payload));
+ expectedCharCount = readSize;
+
+ enableRetryTimer(true);
+ enableAccumulateTimer(false);
+
+ sendPayload(payloadCache);
+
+ return 0;
+}
+
+void Context::resendPayload(bool clear)
+{
+ sendPayload(payloadCache);
+
+ if (clear)
+ {
+ payloadCache.clear();
+ expectedCharCount = 0;
+ sol::Manager::get().dataBuffer.erase(expectedCharCount);
+ }
+}
+
+void Context::sendPayload(const std::vector<uint8_t>& out) const
+{
+ message::Handler msgHandler(session->channelPtr, sessionID);
+
+ msgHandler.sendSOLPayload(out);
+}
+
+void Context::charAccTimerHandler()
+{
+ auto bufferSize = sol::Manager::get().dataBuffer.size();
+
+ try
+ {
+ if (bufferSize > 0)
+ {
+ int rc = sendOutboundPayload();
+ if (rc == 0)
+ {
+ return;
+ }
+ }
+ enableAccumulateTimer(true);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to call the sendOutboundPayload method: {ERROR}",
+ "ERROR", e);
+ }
+}
+
+void Context::retryTimerHandler()
+{
+ try
+ {
+ if (retryCounter)
+ {
+ --retryCounter;
+ enableRetryTimer(true);
+ resendPayload(sol::Context::noClear);
+ }
+ else
+ {
+ retryCounter = maxRetryCount;
+ resendPayload(sol::Context::clear);
+ enableRetryTimer(false);
+ enableAccumulateTimer(true);
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to retry timer: {ERROR}", "ERROR", e);
+ }
+}
+} // namespace sol
diff --git a/transport/rmcpbridge/sol/sol_context.hpp b/transport/rmcpbridge/sol/sol_context.hpp
new file mode 100644
index 0000000..981360e
--- /dev/null
+++ b/transport/rmcpbridge/sol/sol_context.hpp
@@ -0,0 +1,312 @@
+#pragma once
+
+#include "console_buffer.hpp"
+#include "session.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+
+#include <cstddef>
+
+namespace sol
+{
+
+/** @struct Outbound
+ *
+ * Operation/Status in an outbound SOL payload format(BMC to Remote Console).
+ */
+struct Outbound
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t testMode:2; //!< Not supported.
+ uint8_t breakDetected:1; //!< Not supported.
+ uint8_t transmitOverrun:1; //!< Not supported.
+ uint8_t SOLDeactivating:1; //!< 0 : SOL is active, 1 : SOL deactivated.
+ uint8_t charUnavailable:1; //!< 0 : Available, 1 : Unavailable.
+ uint8_t ack:1; //!< 0 : ACK, 1 : NACK.
+ uint8_t reserved:1; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved:1; //!< Reserved.
+ uint8_t ack:1; //!< 0 : ACK, 1 : NACK.
+ uint8_t charUnavailable:1; //!< 0 : Available, 1 : Unavailable.
+ uint8_t SOLDeactivating:1; //!< 0 : SOL is active, 1 : SOL deactivated.
+ uint8_t transmitOverrun:1; //!< Not supported.
+ uint8_t breakDetected:1; //!< Not supported.
+ uint8_t testMode:2; //!< Not supported.
+#endif
+} __attribute__((packed));
+
+/** @struct Inbound
+ *
+ * Operation/Status in an Inbound SOL Payload format(Remote Console to BMC).
+ */
+struct Inbound
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t flushOut:1; //!< Not supported.
+ uint8_t flushIn:1; //!< Not supported.
+ uint8_t dcd:1; //!< Not supported.
+ uint8_t cts:1; //!< Not supported.
+ uint8_t generateBreak:1; //!< Not supported.
+ uint8_t ring:1; //!< Not supported.
+ uint8_t ack:1; //!< 0 : ACK, 1 : NACK.
+ uint8_t reserved:1; //!< Reserved.
+#endif
+
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved:1; //!< Reserved.
+ uint8_t ack:1; //!< 0 : ACK, 1 : NACK.
+ uint8_t ring:1; //!< Not supported.
+ uint8_t generateBreak:1; //!< Not supported.
+ uint8_t cts:1; //!< Not supported.
+ uint8_t dcd:1; //!< Not supported.
+ uint8_t flushIn:1; //!< Not supported.
+ uint8_t flushOut:1; //!< Not supported.
+#endif
+} __attribute__((packed));
+
+/** @struct Payload
+ *
+ * SOL Payload Data Format.The following fields make up the SOL payload in an
+ * RMCP+ packet, followed by the console character data.
+ */
+struct Payload
+{
+ uint8_t packetSeqNum; //!< Packet sequence number
+ uint8_t packetAckSeqNum; //!< Packet ACK/NACK sequence number
+ uint8_t acceptedCharCount; //!< Accepted character count
+ union
+ {
+ uint8_t operation; //!< Operation/Status
+ struct Outbound outOperation; //!< BMC to Remote Console
+ struct Inbound inOperation; //!< Remote Console to BMC
+ };
+} __attribute__((packed));
+
+namespace internal
+{
+
+/** @struct SequenceNumbers
+ *
+ * SOL sequence numbers. At the session level, SOL Payloads share the session
+ * sequence numbers for authenticated and unauthenticated packets with other
+ * packets under the IPMI session. At the payload level, SOL packets include
+ * their own message sequence numbers that are used for tracking missing and
+ * retried SOL messages. The sequence numbers must be non-zero. Retried
+ * packets use the same sequence number as the first packet.
+ */
+struct SequenceNumbers
+{
+ static constexpr uint8_t MAX_SOL_SEQUENCE_NUMBER = 0x10;
+
+ /** @brief Get the SOL sequence number.
+ *
+ * @param[in] inbound - true for inbound sequence number and false for
+ * outbound sequence number
+ *
+ * @return sequence number
+ */
+ auto get(bool inbound = true) const
+ {
+ return inbound ? in : out;
+ }
+
+ /** @brief Increment the inbound SOL sequence number. */
+ void incInboundSeqNum()
+ {
+ if ((++in) == MAX_SOL_SEQUENCE_NUMBER)
+ {
+ in = 1;
+ }
+ }
+
+ /** @brief Increment the outbound SOL sequence number.
+ *
+ * @return outbound sequence number to populate the SOL payload.
+ */
+ auto incOutboundSeqNum()
+ {
+ if ((++out) == MAX_SOL_SEQUENCE_NUMBER)
+ {
+ out = 1;
+ }
+
+ return out;
+ }
+
+ private:
+ uint8_t in = 1; //!< Inbound sequence number.
+ uint8_t out = 0; //!< Outbound sequence number, since the first
+ //!< operation is increment, it is initialised to 0
+};
+
+} // namespace internal
+
+/** @class Context
+ *
+ * Context keeps the state of the SOL session. The information needed to
+ * maintain the state of the SOL is part of this class. This class provides
+ * interfaces to handle incoming SOL payload, send response and send outbound
+ * SOL payload.
+ */
+class Context : public std::enable_shared_from_this<Context>
+{
+ public:
+ Context() = delete;
+ ~Context() = default;
+ Context(const Context&) = delete;
+ Context& operator=(const Context&) = delete;
+ Context(Context&&) = delete;
+ Context& operator=(Context&&) = delete;
+
+ /** @brief Context Factory
+ *
+ * This is called by the SOL Manager when a SOL payload instance is
+ * started for the activate payload command. Its purpose is to be able
+ * to perform post-creation tasks on the object without changing the
+ * code flow
+ *
+ * @param[in] io - boost::asio io context for event scheduling.
+ * @param[in] maxRetryCount - Retry count max value.
+ * @param[in] sendThreshold - Character send threshold.
+ * @param[in] instance - SOL payload instance.
+ * @param[in] sessionID - BMC session ID.
+ */
+ static std::shared_ptr<Context> makeContext(
+ std::shared_ptr<boost::asio::io_context> io, uint8_t maxRetryCount,
+ uint8_t sendThreshold, uint8_t instance, session::SessionID sessionID);
+
+ /** @brief Context Constructor.
+ *
+ * This should only be used by the Context factory makeContext
+ * or the accumulate timer will not be initialized properly
+ *
+ * @param[in] io - boost::asio io context for event scheduling.
+ * @param[in] maxRetryCount - Retry count max value.
+ * @param[in] sendThreshold - Character send threshold.
+ * @param[in] instance - SOL payload instance.
+ * @param[in] sessionID - BMC session ID.
+ */
+ Context(std::shared_ptr<boost::asio::io_context> io, uint8_t maxRetryCount,
+ uint8_t sendThreshold, uint8_t instance,
+ session::SessionID sessionID);
+
+ static constexpr auto clear = true;
+ static constexpr auto noClear = false;
+
+ /** @brief accumulate timer */
+ boost::asio::steady_timer accumulateTimer;
+
+ /** @brief retry timer */
+ boost::asio::steady_timer retryTimer;
+
+ /** @brief Retry count max value. */
+ const uint8_t maxRetryCount = 0;
+
+ /** @brief Retry counter. */
+ uint8_t retryCounter = 0;
+
+ /** @brief Character send threshold. */
+ const uint8_t sendThreshold = 0;
+
+ /** @brief SOL payload instance. */
+ const uint8_t payloadInstance = 0;
+
+ /** @brief Session ID. */
+ const session::SessionID sessionID = 0;
+
+ /** @brief session pointer
+ */
+ std::shared_ptr<session::Session> session;
+
+ /** @brief enable/disable accumulate timer
+ *
+ * The timeout interval is managed by the SOL Manager;
+ * this function only enables or disable the timer
+ *
+ * @param[in] enable - enable(true) or disable(false) accumulation timer
+ */
+ void enableAccumulateTimer(bool enable);
+
+ /** @brief enable/disable retry timer
+ *
+ * The timeout interval is managed by the SOL Manager;
+ * this function only enables or disable the timer
+ *
+ * @param[in] enable - enable(true) or disable(false) retry timer
+ */
+ void enableRetryTimer(bool enable);
+
+ /** @brief Process the Inbound SOL payload.
+ *
+ * The SOL payload from the remote console is processed and the
+ * acknowledgment handling is done.
+ *
+ * @param[in] seqNum - Packet sequence number.
+ * @param[in] ackSeqNum - Packet ACK/NACK sequence number.
+ * @param[in] count - Accepted character count.
+ * @param[in] operation - ACK is false, NACK is true
+ * @param[in] input - Incoming SOL character data.
+ */
+ void processInboundPayload(uint8_t seqNum, uint8_t ackSeqNum, uint8_t count,
+ bool status, bool isBreak,
+ const std::vector<uint8_t>& input);
+
+ /** @brief Send the outbound SOL payload.
+ *
+ * @return zero on success and negative value if condition for sending
+ * the payload fails.
+ */
+ int sendOutboundPayload();
+
+ /** @brief Resend the SOL payload.
+ *
+ * @param[in] clear - if true then send the payload and clear the
+ * cached payload, if false only send the payload.
+ */
+ void resendPayload(bool clear);
+
+ /** @brief accumlate timer handler called by timer */
+ void charAccTimerHandler();
+
+ /** @brief retry timer handler called by timer */
+ void retryTimerHandler();
+
+ private:
+ /** @brief Expected character count.
+ *
+ * Expected Sequence number and expected character count is set before
+ * sending the SOL payload. The check is done against these values when
+ * an incoming SOL payload is received.
+ */
+ size_t expectedCharCount = 0;
+
+ /** @brief Inbound and Outbound sequence numbers. */
+ internal::SequenceNumbers seqNums;
+
+ /** @brief Copy of the last sent SOL payload.
+ *
+ * A copy of the SOL payload is kept here, so that when a retry needs
+ * to be attempted the payload is sent again.
+ */
+ std::vector<uint8_t> payloadCache;
+
+ /**
+ * @brief Send Response for Incoming SOL payload.
+ *
+ * @param[in] ackSeqNum - Packet ACK/NACK Sequence Number.
+ * @param[in] count - Accepted Character Count.
+ * @param[in] ack - Set ACK/NACK in the Operation.
+ */
+ void prepareResponse(uint8_t ackSeqNum, uint8_t count, bool ack);
+
+ /** @brief Send the outgoing SOL payload.
+ *
+ * @param[in] out - buffer containing the SOL payload.
+ */
+ void sendPayload(const std::vector<uint8_t>& out) const;
+};
+
+} // namespace sol
diff --git a/transport/rmcpbridge/sol/sol_manager.cpp b/transport/rmcpbridge/sol/sol_manager.cpp
new file mode 100644
index 0000000..ccfbc3c
--- /dev/null
+++ b/transport/rmcpbridge/sol/sol_manager.cpp
@@ -0,0 +1,366 @@
+#include "sol_manager.hpp"
+
+#include "main.hpp"
+#include "sol_context.hpp"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <boost/asio/basic_stream_socket.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/local/stream_protocol.hpp>
+#include <boost/asio/write.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+
+#include <chrono>
+#include <cmath>
+
+constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL";
+constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/";
+
+namespace sol
+{
+
+std::unique_ptr<sdbusplus::bus::match_t> matchPtrSOL(nullptr);
+std::unique_ptr<sdbusplus::bus::match_t> solConfPropertiesSignal(nullptr);
+
+void Manager::initConsoleSocket()
+{
+ // explicit length constructor for NUL-prefixed abstract path
+ std::string path(CONSOLE_SOCKET_PATH, CONSOLE_SOCKET_PATH_LEN);
+ boost::asio::local::stream_protocol::endpoint ep(path);
+ consoleSocket =
+ std::make_unique<boost::asio::local::stream_protocol::socket>(*io);
+ consoleSocket->connect(ep);
+}
+
+void Manager::consoleInputHandler()
+{
+ boost::system::error_code ec;
+ boost::asio::socket_base::bytes_readable cmd(true);
+ consoleSocket->io_control(cmd, ec);
+ size_t readSize;
+ if (!ec)
+ {
+ readSize = cmd.get();
+ }
+ else
+ {
+ lg2::error(
+ "Reading ready count from host console socket failed: {ERROR}",
+ "ERROR", ec.value());
+ return;
+ }
+ std::vector<uint8_t> buffer(readSize);
+ ec.clear();
+ size_t readDataLen =
+ consoleSocket->read_some(boost::asio::buffer(buffer), ec);
+ if (ec)
+ {
+ lg2::error("Reading from host console socket failed: {ERROR}", "ERROR",
+ ec.value());
+ return;
+ }
+
+ // Update the Console buffer with data read from the socket
+ buffer.resize(readDataLen);
+ dataBuffer.write(buffer);
+}
+
+int Manager::writeConsoleSocket(const std::vector<uint8_t>& input,
+ bool breakFlag) const
+{
+ boost::system::error_code ec;
+ if (breakFlag)
+ {
+ consoleSocket->send(boost::asio::buffer(input), MSG_OOB, ec);
+ }
+ else
+ {
+ consoleSocket->send(boost::asio::buffer(input), 0, ec);
+ }
+
+ return ec.value();
+}
+
+void Manager::startHostConsole()
+{
+ if (!consoleSocket)
+ {
+ initConsoleSocket();
+ }
+
+ // Register callback to close SOL session for disable SSH SOL
+ if (matchPtrSOL == nullptr)
+ {
+ registerSOLServiceChangeCallback();
+ }
+
+ consoleSocket->async_wait(boost::asio::socket_base::wait_read,
+ [this](const boost::system::error_code& ec) {
+ if (!ec)
+ {
+ consoleInputHandler();
+ startHostConsole();
+ }
+ });
+} // namespace sol
+
+void Manager::stopHostConsole()
+{
+ if (consoleSocket)
+ {
+ consoleSocket->cancel();
+ consoleSocket.reset();
+ }
+}
+
+void Manager::updateSOLParameter(uint8_t channelNum)
+{
+ sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
+ static std::string solService{};
+ ipmi::PropertyMap properties;
+ std::string ethdevice = ipmi::getChannelName(channelNum);
+ std::string solPathWitheEthName = solPath + ethdevice;
+ if (solService.empty())
+ {
+ try
+ {
+ solService =
+ ipmi::getService(dbus, solInterface, solPathWitheEthName);
+ }
+ catch (const std::runtime_error& e)
+ {
+ solService.clear();
+ lg2::error("Get SOL service failed: {ERROR}", "ERROR", e);
+ return;
+ }
+ }
+ try
+ {
+ properties = ipmi::getAllDbusProperties(
+ dbus, solService, solPathWitheEthName, solInterface);
+ }
+ catch (const std::runtime_error& e)
+ {
+ lg2::error("Setting sol parameter: {ERROR}", "ERROR", e);
+ return;
+ }
+
+ progress = std::get<uint8_t>(properties["Progress"]);
+
+ enable = std::get<bool>(properties["Enable"]);
+
+ forceEncrypt = std::get<bool>(properties["ForceEncryption"]);
+
+ forceAuth = std::get<bool>(properties["ForceAuthentication"]);
+
+ solMinPrivilege = static_cast<session::Privilege>(
+ std::get<uint8_t>(properties["Privilege"]));
+
+ accumulateInterval =
+ std::get<uint8_t>((properties["AccumulateIntervalMS"])) *
+ sol::accIntervalFactor * 1ms;
+
+ sendThreshold = std::get<uint8_t>(properties["Threshold"]);
+
+ retryCount = std::get<uint8_t>(properties["RetryCount"]);
+
+ retryInterval = std::get<uint8_t>(properties["RetryIntervalMS"]) *
+ sol::retryIntervalFactor * 1ms;
+
+ return;
+}
+
+void Manager::startPayloadInstance(uint8_t payloadInstance,
+ session::SessionID sessionID)
+{
+ if (payloadMap.empty())
+ {
+ try
+ {
+ startHostConsole();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Encountered exception when starting host console. Hence stopping host console: {ERROR}",
+ "ERROR", e);
+ stopHostConsole();
+ throw;
+ }
+ }
+
+ // Create the SOL Context data for payload instance
+ std::shared_ptr<Context> context = Context::makeContext(
+ io, retryCount, sendThreshold, payloadInstance, sessionID);
+
+ payloadMap.emplace(payloadInstance, std::move(context));
+}
+
+void Manager::stopPayloadInstance(uint8_t payloadInstance)
+{
+ auto iter = payloadMap.find(payloadInstance);
+ if (iter == payloadMap.end())
+ {
+ throw std::runtime_error("SOL Payload instance not found ");
+ }
+
+ payloadMap.erase(iter);
+
+ if (payloadMap.empty())
+ {
+ stopHostConsole();
+
+ dataBuffer.erase(dataBuffer.size());
+ }
+}
+
+void Manager::stopAllPayloadInstance()
+{
+ // Erase all payload instance
+ payloadMap.erase(payloadMap.begin(), payloadMap.end());
+
+ stopHostConsole();
+
+ dataBuffer.erase(dataBuffer.size());
+}
+
+void registerSOLServiceChangeCallback()
+{
+ using namespace sdbusplus::bus::match::rules;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ try
+ {
+ auto servicePath = ipmi::getDbusObject(
+ bus, "xyz.openbmc_project.Control.Service.Attributes",
+ "/xyz/openbmc_project/control/service", "_6fbmc_2dconsole");
+
+ if (!std::empty(servicePath.first))
+ {
+ matchPtrSOL = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ path_namespace(servicePath.first) +
+ "arg0namespace='xyz.openbmc_project.Control.Service."
+ "Attributes'"
+ ", " +
+ type::signal() + member("PropertiesChanged") +
+ interface("org.freedesktop.DBus.Properties"),
+ [](sdbusplus::message_t& msg) {
+ std::string intfName;
+ std::map<std::string, std::variant<bool>> properties;
+ msg.read(intfName, properties);
+
+ const auto it = properties.find("Enabled");
+ if (it != properties.end())
+ {
+ const bool* state = std::get_if<bool>(&it->second);
+
+ if (state != nullptr && *state == false)
+ {
+ // Stop all the payload session.
+ sol::Manager::get().stopAllPayloadInstance();
+ }
+ }
+ });
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error(
+ "Failed to get service path in registerSOLServiceChangeCallback: {ERROR}",
+ "ERROR", e);
+ }
+}
+
+void procSolConfChange(sdbusplus::message_t& msg)
+{
+ using SolConfVariant = std::variant<bool, uint8_t>;
+ using SolConfProperties =
+ std::vector<std::pair<std::string, SolConfVariant>>;
+
+ std::string iface;
+ SolConfProperties properties;
+
+ try
+ {
+ msg.read(iface, properties);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("procSolConfChange get properties FAIL: {ERROR}", "ERROR",
+ e);
+ return;
+ }
+
+ for (const auto& prop : properties)
+ {
+ if (prop.first == "Progress")
+ {
+ sol::Manager::get().progress = std::get<uint8_t>(prop.second);
+ }
+ else if (prop.first == "Enable")
+ {
+ sol::Manager::get().enable = std::get<bool>(prop.second);
+ }
+ else if (prop.first == "ForceEncryption")
+ {
+ sol::Manager::get().forceEncrypt = std::get<bool>(prop.second);
+ }
+ else if (prop.first == "ForceAuthentication")
+ {
+ sol::Manager::get().forceAuth = std::get<bool>(prop.second);
+ }
+ else if (prop.first == "Privilege")
+ {
+ sol::Manager::get().solMinPrivilege =
+ static_cast<session::Privilege>(std::get<uint8_t>(prop.second));
+ }
+ else if (prop.first == "AccumulateIntervalMS")
+ {
+ sol::Manager::get().accumulateInterval =
+ std::get<uint8_t>(prop.second) * sol::accIntervalFactor * 1ms;
+ }
+ else if (prop.first == "Threshold")
+ {
+ sol::Manager::get().sendThreshold = std::get<uint8_t>(prop.second);
+ }
+ else if (prop.first == "RetryCount")
+ {
+ sol::Manager::get().retryCount = std::get<uint8_t>(prop.second);
+ }
+ else if (prop.first == "RetryIntervalMS")
+ {
+ sol::Manager::get().retryInterval =
+ std::get<uint8_t>(prop.second) * sol::retryIntervalFactor * 1ms;
+ }
+ }
+}
+
+void registerSolConfChangeCallbackHandler(std::string channel)
+{
+ if (solConfPropertiesSignal == nullptr)
+ {
+ using namespace sdbusplus::bus::match::rules;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ try
+ {
+ auto servicePath = solPath + channel;
+
+ solConfPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus, propertiesChangedNamespace(servicePath, solInterface),
+ procSolConfChange);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error(
+ "Failed to get service path in registerSolConfChangeCallbackHandler, channel: {CHANNEL}, error: {ERROR}",
+ "CHANNEL", channel, "ERROR", e);
+ }
+ }
+ return;
+}
+
+} // namespace sol
diff --git a/transport/rmcpbridge/sol/sol_manager.hpp b/transport/rmcpbridge/sol/sol_manager.hpp
new file mode 100644
index 0000000..4d7652a
--- /dev/null
+++ b/transport/rmcpbridge/sol/sol_manager.hpp
@@ -0,0 +1,301 @@
+#pragma once
+
+#include "console_buffer.hpp"
+#include "main.hpp"
+#include "session.hpp"
+#include "sol_context.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/local/stream_protocol.hpp>
+
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+
+namespace sol
+{
+
+constexpr size_t MAX_PAYLOAD_SIZE = 255;
+constexpr uint8_t MAJOR_VERSION = 0x01;
+constexpr uint8_t MINOR_VERSION = 0x00;
+
+constexpr char CONSOLE_SOCKET_PATH[] = "\0obmc-console.default";
+constexpr size_t CONSOLE_SOCKET_PATH_LEN = sizeof(CONSOLE_SOCKET_PATH) - 1;
+
+constexpr uint8_t accIntervalFactor = 5;
+constexpr uint8_t retryIntervalFactor = 10;
+
+using Instance = uint8_t;
+
+using namespace std::chrono_literals;
+
+/** @class Manager
+ *
+ * Manager class acts a manager for the SOL payload instances and provides
+ * interfaces to start a payload instance, stop a payload instance and get
+ * reference to the context object.
+ */
+class Manager
+{
+ private:
+ struct Private
+ {};
+
+ public:
+ /** @brief SOL Payload Instance is the key for the map, the value is the
+ * SOL context.
+ */
+ using SOLPayloadMap = std::map<Instance, std::shared_ptr<Context>>;
+
+ Manager() = delete;
+ ~Manager() = default;
+ Manager(const Manager&) = delete;
+ Manager& operator=(const Manager&) = delete;
+ Manager(Manager&&) = default;
+ Manager& operator=(Manager&&) = default;
+
+ Manager(std::shared_ptr<boost::asio::io_context>& io, const Private&) :
+ io(io)
+ {}
+
+ /**
+ * @brief Get a reference to the singleton Manager
+ *
+ * @return Manager reference
+ */
+ static Manager& get()
+ {
+ static std::shared_ptr<Manager> ptr = nullptr;
+ if (!ptr)
+ {
+ std::shared_ptr<boost::asio::io_context> io = getIo();
+ ptr = std::make_shared<Manager>(io, Private());
+ }
+ return *ptr;
+ }
+
+ /** @brief io context to add events to */
+ std::shared_ptr<boost::asio::io_context> io;
+
+ /** @brief Host Console Buffer. */
+ ConsoleData dataBuffer;
+
+ /** @brief Set in Progress.
+ *
+ * This parameter is used to indicate when any of the SOL parameters
+ * are being updated, and when the changes are completed. The bit is
+ * primarily provided to alert software than some other software or
+ * utility is in the process of making changes to the data. This field
+ * is initialized to set complete.
+ */
+ uint8_t progress = 0;
+
+ /** @brief SOL enable
+ *
+ * This controls whether the SOL payload can be activated. By default
+ * the SOL is enabled.
+ */
+ bool enable = true;
+
+ /** @brief SOL payload encryption.
+ *
+ * Force encryption: if the cipher suite for the session supports
+ * encryption, then this setting will force the use of encryption for
+ * all SOL payload data. Encryption controlled by remote console:
+ * Whether SOL packets are encrypted or not is selectable by the remote
+ * console at the time the payload is activated. The default is force
+ * encryption.
+ */
+ bool forceEncrypt = true;
+
+ /** @brief SOL payload authentication.
+ *
+ * Force authentication: if the cipher suite for the session supports
+ * authentication, then this setting will force the use of for
+ * authentication for all SOL payload data. Authentication controlled
+ * by remote console: Note that for the standard Cipher Suites,
+ * if encryption is used authentication must also be used. Therefore,
+ * while encryption is being used software will not be able to select
+ * using unauthenticated payloads.
+ */
+ bool forceAuth = true;
+
+ /** @brief SOL privilege level.
+ *
+ * Sets the minimum operating privilege level that is required to be
+ * able to activate SOL using the Activate Payload command.
+ */
+ session::Privilege solMinPrivilege = session::Privilege::USER;
+
+ /** @brief Character Accumulate Interval
+ *
+ * This sets the typical amount of time that the BMC will wait before
+ * transmitting a partial SOL character data packet. (Where a partial
+ * packet is defined as a packet that has fewer characters to transmit
+ * than the number of characters specified by the character send
+ * threshold. This parameter can be modified by the set SOL
+ * configuration parameters command. The SOL configuration parameter,
+ * Character Accumulate Interval is 5 ms increments, 1-based value. The
+ * parameter value is accumulateInterval/5. The accumulateInterval
+ * needs to be a multiple of 5.
+ */
+ std::chrono::milliseconds accumulateInterval = 100ms;
+
+ /** @brief Character Send Threshold
+ *
+ * The BMC will automatically send an SOL character data packet
+ * containing this number of characters as soon as this number of
+ * characters (or greater) has been accepted from the baseboard serial
+ * controller into the BMC. This provides a mechanism to tune the
+ * buffer to reduce latency to when the first characters are received
+ * after an idle interval. In the degenerate case, setting this value
+ * to a ‘1’ would cause the BMC to send a packet as soon as the first
+ * character was received. This parameter can be modified by the set
+ * SOL configuration parameters command.
+ */
+ uint8_t sendThreshold = 1;
+
+ /** @brief Retry Count
+ *
+ * 1-based. 0 = no retries after packet is transmitted. Packet will be
+ * dropped if no ACK/NACK received by time retries expire. The maximum
+ * value for retry count is 7. This parameter can be modified by the
+ * set SOL configuration parameters command.
+ */
+ uint8_t retryCount = 7;
+
+ /** @brief Retry Interval
+ *
+ * Sets the time that the BMC will wait before the first retry and the
+ * time between retries when sending SOL packets to the remote console.
+ * This parameter can be modified by the set SOL configuration
+ * parameters command. The SOL configuration parameter Retry Interval
+ * is 10 ms increments, 1-based value. The parameter value is
+ * retryInterval/10. The retryInterval needs to be a multiple of 10.
+ */
+ std::chrono::milliseconds retryInterval = 100ms;
+
+ /** @brief Channel Number
+ *
+ * This parameter indicates which IPMI channel is being used for the
+ * communication parameters (e.g. IP address, MAC address) for the SOL
+ * Payload. Typically, these parameters will come from the same channel
+ * that the Activate Payload command for SOL was accepted over. The
+ * network channel number is defaulted to 1.
+ */
+ uint8_t channel = 1;
+
+ /** @brief Add host console I/O event source to the event loop. */
+ void startHostConsole();
+
+ /** @brief Remove host console I/O event source. */
+ void stopHostConsole();
+
+ /** @brief Start a SOL payload instance.
+ *
+ * Starting a payload instance involves creating the context object,
+ * add the accumulate interval timer and retry interval timer to the
+ * event loop.
+ *
+ * @param[in] payloadInstance - SOL payload instance.
+ * @param[in] sessionID - BMC session ID.
+ */
+ void startPayloadInstance(uint8_t payloadInstance,
+ session::SessionID sessionID);
+
+ /** @brief Stop SOL payload instance.
+ *
+ * Stopping a payload instance involves stopping and removing the
+ * accumulate interval timer and retry interval timer from the event
+ * loop, delete the context object.
+ *
+ * @param[in] payloadInstance - SOL payload instance
+ */
+ void stopPayloadInstance(uint8_t payloadInstance);
+
+ /* @brief Stop all the active SOL payload instances */
+ void stopAllPayloadInstance();
+
+ /** @brief Get SOL Context by Payload Instance.
+ *
+ * @param[in] payloadInstance - SOL payload instance.
+ *
+ * @return reference to the SOL payload context.
+ */
+ Context& getContext(uint8_t payloadInstance)
+ {
+ auto iter = payloadMap.find(payloadInstance);
+
+ if (iter != payloadMap.end())
+ {
+ return *(iter->second);
+ }
+
+ std::string msg =
+ "Invalid SOL payload instance " + std::to_string(payloadInstance);
+ throw std::runtime_error(msg.c_str());
+ }
+
+ /** @brief Get SOL Context by Session ID.
+ *
+ * @param[in] sessionID - IPMI Session ID.
+ *
+ * @return reference to the SOL payload context.
+ */
+ Context& getContext(session::SessionID sessionID)
+ {
+ for (const auto& kv : payloadMap)
+ {
+ if (kv.second->sessionID == sessionID)
+ {
+ return *kv.second;
+ }
+ }
+
+ std::string msg = "Invalid SOL SessionID " + std::to_string(sessionID);
+ throw std::runtime_error(msg.c_str());
+ }
+
+ /** @brief Check if SOL payload is active.
+ *
+ * @param[in] payloadInstance - SOL payload instance.
+ *
+ * @return true if the instance is active and false it is not active.
+ */
+ auto isPayloadActive(uint8_t payloadInstance) const
+ {
+ return (0 != payloadMap.count(payloadInstance));
+ }
+
+ /** @brief Write data to the host console unix socket.
+ *
+ * @param[in] input - Data from the remote console.
+ *
+ * @return 0 on success and errno on failure.
+ */
+ int writeConsoleSocket(const std::vector<uint8_t>& input,
+ bool breakFlag) const;
+ void updateSOLParameter(uint8_t channelNum);
+
+ private:
+ SOLPayloadMap payloadMap;
+
+ /** @brief Local stream socket for the host console. */
+ std::unique_ptr<boost::asio::local::stream_protocol::socket> consoleSocket =
+ nullptr;
+
+ /** @brief Initialize the host console file descriptor. */
+ void initConsoleSocket();
+
+ /** @brief Handle incoming console data on the console socket */
+ void consoleInputHandler();
+};
+
+/** @brief Callback method to close SOL sessions for SOL service change */
+void registerSOLServiceChangeCallback();
+
+/** @brief Callback register method to SOL conf parameters change */
+void registerSolConfChangeCallbackHandler(std::string channel);
+
+} // namespace sol
diff --git a/transport/rmcpbridge/sol_module.cpp b/transport/rmcpbridge/sol_module.cpp
new file mode 100644
index 0000000..21196d8
--- /dev/null
+++ b/transport/rmcpbridge/sol_module.cpp
@@ -0,0 +1,57 @@
+#include "command/payload_cmds.hpp"
+#include "command/sol_cmds.hpp"
+#include "command_table.hpp"
+#include "session.hpp"
+
+namespace sol
+{
+
+namespace command
+{
+
+void registerCommands()
+{
+ static const ::command::CmdDetails commands[] = {
+ // SOL Payload Handler
+ {{(static_cast<uint32_t>(message::PayloadType::SOL) << 16)},
+ &payloadHandler,
+ session::Privilege::HIGHEST_MATCHING,
+ false},
+ // Activate Payload Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(::command::NetFns::APP) | 0x48},
+ &activatePayload,
+ session::Privilege::USER,
+ false},
+ // Deactivate Payload Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(::command::NetFns::APP) | 0x49},
+ &deactivatePayload,
+ session::Privilege::USER,
+ false},
+ // Get Payload Activation Status
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(::command::NetFns::APP) | 0x4A},
+ &getPayloadStatus,
+ session::Privilege::USER,
+ false},
+ // Get Payload Instance Info Command
+ {{(static_cast<uint32_t>(message::PayloadType::IPMI) << 16) |
+ static_cast<uint16_t>(::command::NetFns::APP) | 0x4B},
+ &getPayloadInfo,
+ session::Privilege::USER,
+ false},
+ };
+
+ for (const auto& iter : commands)
+ {
+ ::command::Table::get().registerCommand(
+ iter.command,
+ std::make_unique<::command::NetIpmidEntry>(
+ iter.command, iter.functor, iter.privilege, iter.sessionless));
+ }
+}
+
+} // namespace command
+
+} // namespace sol
diff --git a/transport/rmcpbridge/sol_module.hpp b/transport/rmcpbridge/sol_module.hpp
new file mode 100644
index 0000000..f1a4beb
--- /dev/null
+++ b/transport/rmcpbridge/sol_module.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+namespace sol
+{
+
+namespace command
+{
+
+/** @brief Register SOL commands to the Command Table */
+void registerCommands();
+
+} // namespace command
+
+} // namespace sol
diff --git a/transport/rmcpbridge/subprojects/CLI11.wrap b/transport/rmcpbridge/subprojects/CLI11.wrap
new file mode 100644
index 0000000..2e5a95b
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/CLI11.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/CLIUtils/CLI11.git
+revision = HEAD
+
+[provide]
+CLI11 = CLI11_dep
diff --git a/transport/rmcpbridge/subprojects/googletest.wrap b/transport/rmcpbridge/subprojects/googletest.wrap
new file mode 100644
index 0000000..766a562
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/googletest.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/google/googletest.git
+revision = HEAD
diff --git a/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap b/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap
new file mode 100644
index 0000000..346aa0c
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-dbus-interfaces.git
+revision = HEAD
+
+[provide]
+phosphor-dbus-interfaces = phosphor_dbus_interfaces_dep
diff --git a/transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap b/transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap
new file mode 100644
index 0000000..29bb550
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap
@@ -0,0 +1,8 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-host-ipmid.git
+revision = HEAD
+
+[provide]
+libipmid = ipmid_dep
+libchannellayer = channellayer_dep
+libuserlayer = userlayer_dep
diff --git a/transport/rmcpbridge/subprojects/phosphor-logging.wrap b/transport/rmcpbridge/subprojects/phosphor-logging.wrap
new file mode 100644
index 0000000..71eee8b
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-logging.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-logging.git
+revision = HEAD
+
+[provide]
+phosphor-logging = phosphor_logging_dep
diff --git a/transport/rmcpbridge/subprojects/sdbusplus.wrap b/transport/rmcpbridge/subprojects/sdbusplus.wrap
new file mode 100644
index 0000000..7b076d0
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/sdbusplus.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/sdbusplus.git
+revision = HEAD
+
+[provide]
+sdbusplus = sdbusplus_dep
diff --git a/transport/rmcpbridge/test/cipher.cpp b/transport/rmcpbridge/test/cipher.cpp
new file mode 100644
index 0000000..d7720d4
--- /dev/null
+++ b/transport/rmcpbridge/test/cipher.cpp
@@ -0,0 +1,486 @@
+#include "crypt_algo.hpp"
+#include "integrity_algo.hpp"
+#include "message_parsers.hpp"
+#include "rmcp.hpp"
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+TEST(IntegrityAlgo, HMAC_SHA1_96_GenerateIntegrityDataCheck)
+{
+ /*
+ * Step-1 Generate Integrity Data for the packet, using the implemented API
+ */
+ // Packet = RMCP Session Header (4 bytes) + Packet (8 bytes)
+ std::vector<uint8_t> packet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ auto algoPtr = std::make_unique<cipher::integrity::AlgoSHA1>(sik);
+
+ ASSERT_EQ(true, (algoPtr != NULL));
+
+ // Generate the Integrity Data
+ auto response = algoPtr->generateIntegrityData(packet);
+
+ EXPECT_EQ(true, (response.size() ==
+ cipher::integrity::AlgoSHA1::SHA1_96_AUTHCODE_LENGTH));
+
+ /*
+ * Step-2 Generate Integrity data using OpenSSL SHA1 algorithm
+ */
+ std::vector<uint8_t> k1(SHA_DIGEST_LENGTH);
+ constexpr rmcp::Const_n const1 = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
+
+ // Generated K1 for the integrity algorithm with the additional key keyed
+ // with SIK.
+ unsigned int mdLen = 0;
+ if (HMAC(EVP_sha1(), sik.data(), sik.size(), const1.data(), const1.size(),
+ k1.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating Key1 failed";
+ }
+
+ mdLen = 0;
+ std::vector<uint8_t> output(SHA_DIGEST_LENGTH);
+ size_t length = packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE;
+
+ if (HMAC(EVP_sha1(), k1.data(), k1.size(),
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length,
+ output.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating integrity data failed";
+ }
+
+ output.resize(cipher::integrity::AlgoSHA1::SHA1_96_AUTHCODE_LENGTH);
+
+ /*
+ * Step-3 Check if the integrity data we generated using the implemented API
+ * matches with one generated by OpenSSL SHA1 algorithm.
+ */
+ auto check = std::equal(output.begin(), output.end(), response.begin());
+ EXPECT_EQ(true, check);
+}
+
+TEST(IntegrityAlgo, HMAC_SHA1_96_VerifyIntegrityDataPass)
+{
+ /*
+ * Step-1 Generate Integrity data using OpenSSL SHA1 algorithm
+ */
+
+ // Packet = RMCP Session Header (4 bytes) + Packet (8 bytes)
+ std::vector<uint8_t> packet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ std::vector<uint8_t> k1(SHA_DIGEST_LENGTH);
+ constexpr rmcp::Const_n const1 = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
+
+ // Generated K1 for the integrity algorithm with the additional key keyed
+ // with SIK.
+ unsigned int mdLen = 0;
+ if (HMAC(EVP_sha1(), sik.data(), sik.size(), const1.data(), const1.size(),
+ k1.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating Key1 failed";
+ }
+
+ mdLen = 0;
+ std::vector<uint8_t> output(SHA_DIGEST_LENGTH);
+ size_t length = packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE;
+
+ if (HMAC(EVP_sha1(), k1.data(), k1.size(),
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length,
+ output.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating integrity data failed";
+ }
+
+ output.resize(cipher::integrity::AlgoSHA1::SHA1_96_AUTHCODE_LENGTH);
+
+ /*
+ * Step-2 Insert the integrity data into the packet
+ */
+ auto packetSize = packet.size();
+ packet.insert(packet.end(), output.begin(), output.end());
+
+ // Point to the integrity data in the packet
+ auto integrityIter = packet.cbegin();
+ std::advance(integrityIter, packetSize);
+
+ /*
+ * Step-3 Invoke the verifyIntegrityData API and validate the response
+ */
+
+ auto algoPtr = std::make_unique<cipher::integrity::AlgoSHA1>(sik);
+ ASSERT_EQ(true, (algoPtr != NULL));
+
+ auto check = algoPtr->verifyIntegrityData(
+ packet, packetSize - message::parser::RMCP_SESSION_HEADER_SIZE,
+ integrityIter, packet.cend());
+
+ EXPECT_EQ(true, check);
+}
+
+TEST(IntegrityAlgo, HMAC_SHA1_96_VerifyIntegrityDataFail)
+{
+ /*
+ * Step-1 Add hardcoded Integrity data to the packet
+ */
+
+ // Packet = RMCP Session Header (4 bytes) + Packet (8 bytes)
+ std::vector<uint8_t> packet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ std::vector<uint8_t> integrity = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ packet.insert(packet.end(), integrity.begin(), integrity.end());
+
+ // Point to the integrity data in the packet
+ auto integrityIter = packet.cbegin();
+ std::advance(integrityIter, integrity.size());
+
+ /*
+ * Step-2 Invoke the verifyIntegrityData API and validate the response
+ */
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ auto algoPtr = std::make_unique<cipher::integrity::AlgoSHA1>(sik);
+
+ ASSERT_EQ(true, (algoPtr != NULL));
+
+ // Verify the Integrity Data
+ auto check = algoPtr->verifyIntegrityData(
+ packet, packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE,
+ integrityIter, packet.cend());
+
+ EXPECT_EQ(false, check);
+}
+
+TEST(IntegrityAlgo, HMAC_SHA256_128_GenerateIntegrityDataCheck)
+{
+ /*
+ * Step-1 Generate Integrity Data for the packet, using the implemented API
+ */
+ // Packet = RMCP Session Header (4 bytes) + Packet (8 bytes)
+ std::vector<uint8_t> packet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
+
+ auto algoPtr = std::make_unique<cipher::integrity::AlgoSHA256>(sik);
+
+ ASSERT_EQ(true, (algoPtr != NULL));
+
+ // Generate the Integrity Data
+ auto response = algoPtr->generateIntegrityData(packet);
+
+ EXPECT_EQ(true,
+ (response.size() ==
+ cipher::integrity::AlgoSHA256::SHA256_128_AUTHCODE_LENGTH));
+
+ /*
+ * Step-2 Generate Integrity data using OpenSSL SHA256 algorithm
+ */
+ std::vector<uint8_t> k1(SHA256_DIGEST_LENGTH);
+ constexpr rmcp::Const_n const1 = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
+
+ // Generated K1 for the integrity algorithm with the additional key keyed
+ // with SIK.
+ unsigned int mdLen = 0;
+ if (HMAC(EVP_sha256(), sik.data(), sik.size(), const1.data(), const1.size(),
+ k1.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating Key1 failed";
+ }
+
+ mdLen = 0;
+ std::vector<uint8_t> output(SHA256_DIGEST_LENGTH);
+ size_t length = packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE;
+
+ if (HMAC(EVP_sha256(), k1.data(), k1.size(),
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length,
+ output.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating integrity data failed";
+ }
+
+ output.resize(cipher::integrity::AlgoSHA256::SHA256_128_AUTHCODE_LENGTH);
+
+ /*
+ * Step-3 Check if the integrity data we generated using the implemented API
+ * matches with one generated by OpenSSL SHA256 algorithm.
+ */
+ auto check = std::equal(output.begin(), output.end(), response.begin());
+ EXPECT_EQ(true, check);
+}
+
+TEST(IntegrityAlgo, HMAC_SHA256_128_VerifyIntegrityDataPass)
+{
+ /*
+ * Step-1 Generate Integrity data using OpenSSL SHA256 algorithm
+ */
+
+ // Packet = RMCP Session Header (4 bytes) + Packet (8 bytes)
+ std::vector<uint8_t> packet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
+
+ std::vector<uint8_t> k1(SHA256_DIGEST_LENGTH);
+ constexpr rmcp::Const_n const1 = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
+
+ // Generated K1 for the integrity algorithm with the additional key keyed
+ // with SIK.
+ unsigned int mdLen = 0;
+ if (HMAC(EVP_sha256(), sik.data(), sik.size(), const1.data(), const1.size(),
+ k1.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating Key1 failed";
+ }
+
+ mdLen = 0;
+ std::vector<uint8_t> output(SHA256_DIGEST_LENGTH);
+ size_t length = packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE;
+
+ if (HMAC(EVP_sha256(), k1.data(), k1.size(),
+ packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length,
+ output.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating integrity data failed";
+ }
+
+ output.resize(cipher::integrity::AlgoSHA256::SHA256_128_AUTHCODE_LENGTH);
+
+ /*
+ * Step-2 Insert the integrity data into the packet
+ */
+ auto packetSize = packet.size();
+ packet.insert(packet.end(), output.begin(), output.end());
+
+ // Point to the integrity data in the packet
+ auto integrityIter = packet.cbegin();
+ std::advance(integrityIter, packetSize);
+
+ /*
+ * Step-3 Invoke the verifyIntegrityData API and validate the response
+ */
+
+ auto algoPtr = std::make_unique<cipher::integrity::AlgoSHA256>(sik);
+ ASSERT_EQ(true, (algoPtr != NULL));
+
+ auto check = algoPtr->verifyIntegrityData(
+ packet, packetSize - message::parser::RMCP_SESSION_HEADER_SIZE,
+ integrityIter, packet.cend());
+
+ EXPECT_EQ(true, check);
+}
+
+TEST(IntegrityAlgo, HMAC_SHA256_128_VerifyIntegrityDataFail)
+{
+ /*
+ * Step-1 Add hardcoded Integrity data to the packet
+ */
+
+ // Packet = RMCP Session Header (4 bytes) + Packet (8 bytes)
+ std::vector<uint8_t> packet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ std::vector<uint8_t> integrity = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ packet.insert(packet.end(), integrity.begin(), integrity.end());
+
+ // Point to the integrity data in the packet
+ auto integrityIter = packet.cbegin();
+ std::advance(integrityIter, integrity.size());
+
+ /*
+ * Step-2 Invoke the verifyIntegrityData API and validate the response
+ */
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
+
+ auto algoPtr = std::make_unique<cipher::integrity::AlgoSHA256>(sik);
+
+ ASSERT_EQ(true, (algoPtr != NULL));
+
+ // Verify the Integrity Data
+ auto check = algoPtr->verifyIntegrityData(
+ packet, packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE,
+ integrityIter, packet.cend());
+
+ EXPECT_EQ(false, check);
+}
+
+TEST(CryptAlgo, AES_CBC_128_EncryptPayloadValidate)
+{
+ /*
+ * Step-1 Generate the encrypted data using the implemented API for
+ * AES-CBC-128
+ */
+ std::vector<uint8_t> payload = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+
+ std::vector<uint8_t> k2(SHA_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+ constexpr rmcp::Const_n const1 = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02};
+
+ // Generated K2 for the confidentiality algorithm with the additional key
+ // keyed with SIK.
+ if (HMAC(EVP_sha1(), sik.data(), sik.size(), const1.data(), const1.size(),
+ k2.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating K2 for confidentiality algorithm failed";
+ }
+
+ auto cryptPtr = std::make_unique<cipher::crypt::AlgoAES128>(k2);
+
+ ASSERT_EQ(true, (cryptPtr != NULL));
+
+ auto cipher = cryptPtr->encryptPayload(payload);
+
+ /*
+ * Step-2 Decrypt the encrypted payload using OpenSSL EVP_aes_128_cbc()
+ * implementation
+ */
+
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+ if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, k2.data(),
+ cipher.data()))
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ FAIL() << "EVP_DecryptInit_ex failed for type AES-CBC-128";
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+ std::vector<uint8_t> output(
+ cipher.size() + cipher::crypt::AlgoAES128::AESCBC128BlockSize);
+ int outputLen = 0;
+
+ if (!EVP_DecryptUpdate(
+ ctx, output.data(), &outputLen,
+ cipher.data() + cipher::crypt::AlgoAES128::AESCBC128ConfHeader,
+ cipher.size() - cipher::crypt::AlgoAES128::AESCBC128ConfHeader))
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ FAIL() << "EVP_DecryptUpdate failed";
+ }
+
+ output.resize(outputLen);
+ EVP_CIPHER_CTX_free(ctx);
+
+ /*
+ * Step -3 Check if the plain payload matches with the decrypted one
+ */
+ auto check = std::equal(payload.begin(), payload.end(), output.begin());
+ EXPECT_EQ(true, check);
+}
+
+TEST(CryptAlgo, AES_CBC_128_DecryptPayloadValidate)
+{
+ /*
+ * Step-1 Encrypt the payload using OpenSSL EVP_aes_128_cbc()
+ * implementation
+ */
+
+ std::vector<uint8_t> payload = {1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16};
+ payload.resize(payload.size() + 1);
+ payload.back() = 0;
+
+ // Hardcoded Session Integrity Key
+ std::vector<uint8_t> sik = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+ EVP_CIPHER_CTX* ctx;
+ ctx = EVP_CIPHER_CTX_new();
+ std::vector<uint8_t> k2(SHA_DIGEST_LENGTH);
+ unsigned int mdLen = 0;
+ constexpr rmcp::Const_n const1 = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02};
+ std::vector<uint8_t> output(
+ payload.size() + cipher::crypt::AlgoAES128::AESCBC128BlockSize);
+
+ if (!RAND_bytes(output.data(),
+ cipher::crypt::AlgoAES128::AESCBC128ConfHeader))
+ {
+ FAIL() << "RAND_bytes failed";
+ }
+
+ // Generated K2 for the confidentiality algorithm with the additional key
+ // keyed with SIK.
+ if (HMAC(EVP_sha1(), sik.data(), sik.size(), const1.data(), const1.size(),
+ k2.data(), &mdLen) == NULL)
+ {
+ FAIL() << "Generating K2 for confidentiality algorithm failed";
+ }
+
+ if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, k2.data(),
+ output.data()))
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ FAIL() << "EVP_EncryptInit_ex failed for type AES-CBC-128";
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+ int outputLen = 0;
+
+ if (!EVP_EncryptUpdate(
+ ctx, output.data() + cipher::crypt::AlgoAES128::AESCBC128ConfHeader,
+ &outputLen, payload.data(), payload.size()))
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ FAIL() << "EVP_EncryptUpdate failed";
+ }
+
+ output.resize(cipher::crypt::AlgoAES128::AESCBC128ConfHeader + outputLen);
+ EVP_CIPHER_CTX_free(ctx);
+
+ /*
+ * Step-2 Decrypt the encrypted payload using the implemented API for
+ * AES-CBC-128
+ */
+
+ auto cryptPtr = std::make_unique<cipher::crypt::AlgoAES128>(k2);
+
+ ASSERT_EQ(true, (cryptPtr != NULL));
+
+ auto plain = cryptPtr->decryptPayload(output, 0, output.size());
+
+ /*
+ * Step -3 Check if the plain payload matches with the decrypted one
+ */
+ auto check = std::equal(payload.begin(), payload.end(), plain.begin());
+ EXPECT_EQ(true, check);
+}
diff --git a/transport/rmcpbridge/test/meson.build b/transport/rmcpbridge/test/meson.build
new file mode 100644
index 0000000..6fcfd04
--- /dev/null
+++ b/transport/rmcpbridge/test/meson.build
@@ -0,0 +1,38 @@
+gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
+gmock_dep = dependency('gmock', disabler: true, required: false)
+if not gtest_dep.found() or not gmock_dep.found()
+ gtest_proj = import('cmake').subproject('googletest', required: false)
+ if gtest_proj.found()
+ gtest_dep = declare_dependency(
+ dependencies: [
+ dependency('threads'),
+ gtest_proj.dependency('gtest'),
+ gtest_proj.dependency('gtest_main'),
+ ],
+ )
+ gmock_dep = gtest_proj.dependency('gmock')
+ else
+ assert(
+ not get_option('tests').enabled(),
+ 'Googletest is required if tests are enabled',
+ )
+ endif
+endif
+
+test_sources = ['../integrity_algo.cpp', '../crypt_algo.cpp']
+
+tests = ['cipher.cpp']
+
+foreach t : tests
+ test(
+ t,
+ executable(
+ t.underscorify(),
+ t,
+ test_sources,
+ include_directories: ['..'],
+ dependencies: [gtest_dep, gmock_dep, libcrypto_dep],
+ ),
+ workdir: meson.current_source_dir(),
+ )
+endforeach