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