Add Get Channel Cipher Suites Command

Get Channel Cipher Suites Command is already implemented in
phosphor-ipmi-host, but it needs to be implemented in phosphor-ipmi-net
to be able to provide it as a pre-session command for discovering the
available cipher suites before initiating the session.

Without this, ipmitool will have a ten second timeout while attempting
to get the list of available cipher suites. At the same time, netipmid
will show the following messages in the journal:

    netipmid[8261]: Table: refuse to forward session-zero command

Tested: ran ipmitool and saw that it did not require a timeout
    ipmitool -U <user> -P <pw> -I lanplus -H <host> mc info

Change-Id: Iec1b6fc3262647b82acce0cba607b99e86b74985
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/comm_module.cpp b/comm_module.cpp
index 7b6b496..e34ab26 100644
--- a/comm_module.cpp
+++ b/comm_module.cpp
@@ -40,6 +40,12 @@
          &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},
diff --git a/command/channel_auth.cpp b/command/channel_auth.cpp
index ee19a3b..fd1fa5b 100644
--- a/command/channel_auth.cpp
+++ b/command/channel_auth.cpp
@@ -2,12 +2,25 @@
 
 #include <ipmid/api.h>
 
+#include <fstream>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <set>
+#include <string>
 #include <user_channel/channel_layer.hpp>
 #include <user_channel/user_layer.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
 
 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)
@@ -72,4 +85,198 @@
     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())
+    {
+        log<level::ERR>("Channel Cipher suites file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("Parsing channel cipher suites JSON failed");
+        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)))
+    {
+        log<level::DEBUG>("Get channel cipher suites - Invalid payload type");
+        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)))
+    {
+        log<level::DEBUG>("Get channel cipher suites - Device does not exist");
+        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/command/channel_auth.hpp b/command/channel_auth.hpp
index b78fdb2..c518ba7 100644
--- a/command/channel_auth.hpp
+++ b/command/channel_auth.hpp
@@ -122,4 +122,22 @@
     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