Implement GetPLDMVersion command

This commit implements the GetPLDMVersion which is required
as part of the base PLDM support to know the version of a
given PLDM type.

Change-Id: I1bcbd938c5b6833f62e0ee2f474b04d76b6970d9
Signed-off-by: Sampa Misra <sampmisr@in.ibm.com>
diff --git a/libpldm/base.c b/libpldm/base.c
index c85aead..7354fb1 100644
--- a/libpldm/base.c
+++ b/libpldm/base.c
@@ -161,3 +161,100 @@
 
 	return PLDM_SUCCESS;
 }
+
+int encode_get_version_req(uint8_t instance_id, uint32_t transfer_handle,
+			   uint8_t transfer_opflag, uint8_t type,
+			   struct pldm_msg *msg)
+{
+	struct pldm_header_info header = {0};
+	int rc = PLDM_SUCCESS;
+
+	if (NULL == msg) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	header.msg_type = PLDM_REQUEST;
+	header.instance = instance_id;
+	header.pldm_type = PLDM_BASE;
+	header.command = PLDM_GET_PLDM_VERSION;
+
+	if ((rc = pack_pldm_header(&header, &(msg->hdr))) > PLDM_SUCCESS) {
+		return rc;
+	}
+
+	uint8_t *dst = msg->body.payload;
+	transfer_handle = htole32(transfer_handle);
+	memcpy(dst, &transfer_handle, sizeof(transfer_handle));
+	dst += sizeof(transfer_handle);
+
+	memcpy(dst, &transfer_opflag, sizeof(transfer_opflag));
+	dst += sizeof(transfer_opflag);
+
+	memcpy(dst, &type, sizeof(type));
+
+	return PLDM_SUCCESS;
+}
+
+int encode_get_version_resp(uint8_t instance_id, uint8_t completion_code,
+			    uint32_t next_transfer_handle,
+			    uint8_t transfer_flag,
+			    const struct pldm_version *version_data,
+			    size_t version_size, struct pldm_msg *msg)
+{
+	struct pldm_header_info header = {0};
+	int rc = PLDM_SUCCESS;
+
+	msg->body.payload[0] = completion_code;
+	if (msg->body.payload[0] == PLDM_SUCCESS) {
+
+		header.msg_type = PLDM_RESPONSE;
+		header.instance = instance_id;
+		header.pldm_type = PLDM_BASE;
+		header.command = PLDM_GET_PLDM_VERSION;
+
+		if ((rc = pack_pldm_header(&header, &(msg->hdr))) >
+		    PLDM_SUCCESS) {
+			return rc;
+		}
+		uint8_t *dst = msg->body.payload + sizeof(msg->body.payload[0]);
+
+		next_transfer_handle = htole32(next_transfer_handle);
+
+		memcpy(dst, &next_transfer_handle,
+		       sizeof(next_transfer_handle));
+		dst += sizeof(next_transfer_handle);
+		memcpy(dst, &transfer_flag, sizeof(transfer_flag));
+
+		dst += sizeof(transfer_flag);
+		memcpy(dst, version_data, version_size);
+	}
+	return PLDM_SUCCESS;
+}
+
+int decode_get_version_req(const struct pldm_msg_payload *msg,
+			   uint32_t *transfer_handle, uint8_t *transfer_opflag,
+			   uint8_t *type)
+{
+	const uint8_t *start = msg->payload;
+	*transfer_handle = le32toh(*((uint32_t *)start));
+	*transfer_opflag = *(start + sizeof(*transfer_handle));
+	*type = *(start + sizeof(*transfer_handle) + sizeof(*transfer_opflag));
+
+	return PLDM_SUCCESS;
+}
+
+int decode_get_version_resp(const struct pldm_msg_payload *msg,
+			    uint32_t *next_transfer_handle,
+			    uint8_t *transfer_flag,
+			    struct pldm_version *version)
+{
+	const uint8_t *start = msg->payload + sizeof(uint8_t);
+	*next_transfer_handle = le32toh(*((uint32_t *)start));
+	*transfer_flag = *(start + sizeof(*next_transfer_handle));
+
+	*version =
+	    *((struct pldm_version *)(start + sizeof(*next_transfer_handle) +
+				      sizeof(*transfer_flag)));
+
+	return PLDM_SUCCESS;
+}
diff --git a/libpldm/base.h b/libpldm/base.h
index 65be648..b2050a5 100644
--- a/libpldm/base.h
+++ b/libpldm/base.h
@@ -18,6 +18,7 @@
 /** @brief PLDM Commands
  */
 enum pldm_supported_commands {
+	PLDM_GET_PLDM_VERSION = 0x3,
 	PLDM_GET_PLDM_TYPES = 0x4,
 	PLDM_GET_PLDM_COMMANDS = 0x5
 };
@@ -34,6 +35,18 @@
 	PLDM_ERROR_INVALID_PLDM_TYPE = 0x20
 };
 
+enum transfer_op_flag {
+	PLDM_GET_NEXTPART = 0,
+	PLDM_GET_FIRSTPART = 1,
+};
+
+enum transfer_resp_flag {
+	PLDM_START = 0x01,
+	PLDM_MIDDLE = 0x02,
+	PLDM_END = 0x04,
+	PLDM_START_AND_END = 0x05,
+};
+
 /** @enum MessageType
  *
  *  The different message types supported by the PLDM specification.
@@ -51,10 +64,13 @@
 
 /* Message payload lengths */
 #define PLDM_GET_COMMANDS_REQ_BYTES 5
+#define PLDM_GET_VERSION_REQ_BYTES 6
 
 /* Response lengths are inclusive of completion code */
 #define PLDM_GET_TYPES_RESP_BYTES 9
 #define PLDM_GET_COMMANDS_RESP_BYTES 33
+/* Response data has only one version and does not contain the checksum */
+#define PLDM_GET_VERSION_RESP_BYTES 10
 
 /** @struct pldm_msg_hdr
  *
@@ -197,6 +213,38 @@
 int decode_get_commands_resp(const struct pldm_msg_payload *msg,
 			     uint8_t *commands);
 
+/* GetPLDMVersion */
+
+/** @brief Create a PLDM request for GetPLDMVersion
+ *
+ *  @param[in] instance_id - Message's instance id
+ *  @param[in] transfer_handle - Handle to identify PLDM version data transfer.
+ *         This handle is ignored by the responder when the
+ *         transferop_flag is set to getFirstPart.
+ *  @param[in] transfer_opflag - flag to indicate whether it is start of
+ *         transfer
+ *  @param[in] type -  PLDM Type for which version is requested
+ *  @param[in,out] msg - Message will be written to this
+ *  @return pldm_completion_codes
+ *  @note  Caller is responsible for memory alloc and dealloc of param
+ *         'msg.body.payload'
+ */
+int encode_get_version_req(uint8_t instance_id, uint32_t transfer_handle,
+			   uint8_t transfer_opflag, uint8_t type,
+			   struct pldm_msg *msg);
+
+/** @brief Decode a GetPLDMVersion response message
+ *
+ *  @param[in] msg - Response message payload
+ *  @param[out] next_transfer_handle - the next handle for the next part of data
+ *  @param[out] transfer_flag - flag to indicate the part of data
+ *  @return pldm_completion_codes
+ */
+int decode_get_version_resp(const struct pldm_msg_payload *msg,
+			    uint32_t *next_transfer_handle,
+			    uint8_t *transfer_flag,
+			    struct pldm_version *version);
+
 /* Responder */
 
 /* GetPLDMTypes */
@@ -241,6 +289,40 @@
 int encode_get_commands_resp(uint8_t instance_id, uint8_t completion_code,
 			     const uint8_t *commands, struct pldm_msg *msg);
 
+/* GetPLDMVersion */
+
+/** @brief Create a PLDM response for GetPLDMVersion
+ *
+ *  @param[in] instance_id - Message's instance id
+ *  @param[in] completion_code - PLDM completion code
+ *  @param[in] next_transfer_handle - Handle to identify next portion of
+ *              data transfer
+ *  @param[in] transfer_flag - Represents the part of transfer
+ *  @param[in] version_data - the version data
+ *  @param[in] version_size - size of version data
+ *  @param[in,out] msg - Message will be written to this
+ *  @return pldm_completion_codes
+ *  @note  Caller is responsible for memory alloc and dealloc of param
+ *         'msg.body.payload'
+ */
+int encode_get_version_resp(uint8_t instance_id, uint8_t completion_code,
+			    uint32_t next_transfer_handle,
+			    uint8_t transfer_flag,
+			    const struct pldm_version *version_data,
+			    size_t version_size, struct pldm_msg *msg);
+
+/** @brief Decode a GetPLDMVersion request message
+ *
+ *  @param[in] msg - Request message payload
+ *  @param[out] transfer_handle - the handle of data
+ *  @param[out] transfer_opflag - Transfer Flag
+ *  @param[out] type - PLDM type for which version is requested
+ *  @return pldm_completion_codes
+ */
+int decode_get_version_req(const struct pldm_msg_payload *msg,
+			   uint32_t *transfer_handle, uint8_t *transfer_opflag,
+			   uint8_t *type);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libpldmresponder/base.cpp b/libpldmresponder/base.cpp
index f3bb8f4..9489d47 100644
--- a/libpldmresponder/base.cpp
+++ b/libpldmresponder/base.cpp
@@ -3,6 +3,7 @@
 #include "base.hpp"
 
 #include <array>
+#include <cstring>
 #include <map>
 #include <stdexcept>
 #include <vector>
@@ -17,6 +18,10 @@
 static const std::map<Type, Cmd> capabilities{
     {PLDM_BASE, {PLDM_GET_PLDM_TYPES, PLDM_GET_PLDM_COMMANDS}}};
 
+static const std::map<Type, pldm_version> versions{
+    {PLDM_BASE, {0xF1, 0xF0, 0xF0, 0x00}},
+};
+
 void getPLDMTypes(const pldm_msg_payload* request, pldm_msg* response)
 {
     // DSP0240 has this as a bitfield8[N], where N = 0 to 7
@@ -66,5 +71,36 @@
     encode_get_commands_resp(0, PLDM_SUCCESS, cmds.data(), response);
 }
 
+void getPLDMVersion(const pldm_msg_payload* request, pldm_msg* response)
+{
+    uint32_t transferHandle;
+    Type type;
+    uint8_t transferFlag;
+
+    if (request->payload_length !=
+        (sizeof(transferHandle) + sizeof(type) + sizeof(transferFlag)))
+    {
+        encode_get_version_resp(0, PLDM_ERROR_INVALID_LENGTH, 0, 0, nullptr, 0,
+                                response);
+        return;
+    }
+
+    decode_get_version_req(request, &transferHandle, &transferFlag, &type);
+
+    pldm_version version{};
+    auto search = versions.find(type);
+
+    if (search == versions.end())
+    {
+        encode_get_version_resp(0, PLDM_ERROR_INVALID_PLDM_TYPE, 0, 0, nullptr,
+                                0, response);
+        return;
+    }
+
+    memcpy(&version, &(search->second), sizeof(version));
+    encode_get_version_resp(0, PLDM_SUCCESS, 0, PLDM_START_AND_END, &version,
+                            sizeof(pldm_version), response);
+}
+
 } // namespace responder
 } // namespace pldm
diff --git a/libpldmresponder/base.hpp b/libpldmresponder/base.hpp
index e4351d4..74bc4af 100644
--- a/libpldmresponder/base.hpp
+++ b/libpldmresponder/base.hpp
@@ -28,5 +28,12 @@
  */
 void getPLDMCommands(const pldm_msg_payload* request, pldm_msg* response);
 
+/** @brief Handler for getPLDMCommands
+ *
+ *  @param[in] request - Request message payload
+ *  @param[out] response - Response messsage written here
+ */
+void getPLDMVersion(const pldm_msg_payload* request, pldm_msg* response);
+
 } // namespace responder
 } // namespace pldm
diff --git a/test/libpldm_base_test.cpp b/test/libpldm_base_test.cpp
index 04d14c0..9a37ec6 100644
--- a/test/libpldm_base_test.cpp
+++ b/test/libpldm_base_test.cpp
@@ -300,3 +300,116 @@
     ASSERT_EQ(response.payload[2], outTypes[1]);
     ASSERT_EQ(response.payload[3], outTypes[2]);
 }
+
+TEST(GetPLDMVersion, testEncodeRequest)
+{
+    std::array<uint8_t, PLDM_GET_VERSION_REQ_BYTES> requestMsg{};
+    pldm_msg request{};
+    request.body.payload = requestMsg.data();
+    request.body.payload_length = requestMsg.size();
+    uint8_t pldmType = 0x03;
+    uint32_t transferHandle = 0x0;
+    uint8_t opFlag = 0x01;
+
+    auto rc =
+        encode_get_version_req(0, transferHandle, opFlag, pldmType, &request);
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(request.body.payload, &transferHandle,
+                        sizeof(transferHandle)));
+    ASSERT_EQ(0, memcmp(request.body.payload + sizeof(transferHandle), &opFlag,
+                        sizeof(opFlag)));
+    ASSERT_EQ(0, memcmp(request.body.payload + sizeof(transferHandle) +
+                            sizeof(opFlag),
+                        &pldmType, sizeof(pldmType)));
+}
+
+TEST(GetPLDMVersion, testEncodeResponse)
+{
+    pldm_msg response{};
+    uint8_t completionCode = 0;
+    uint32_t transferHandle = 0;
+    uint8_t flag = PLDM_START_AND_END;
+    std::array<uint8_t, PLDM_GET_VERSION_RESP_BYTES> responseMsg{};
+    response.body.payload = responseMsg.data();
+    response.body.payload_length = responseMsg.size();
+    struct pldm_version version = {0xFF, 0xFF, 0xFF, 0xFF};
+
+    auto rc =
+        encode_get_version_resp(0, PLDM_SUCCESS, 0, PLDM_START_AND_END,
+                                &version, sizeof(pldm_version), &response);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(completionCode, response.body.payload[0]);
+    ASSERT_EQ(0,
+              memcmp(response.body.payload + sizeof(response.body.payload[0]),
+                     &transferHandle, sizeof(transferHandle)));
+    ASSERT_EQ(0,
+              memcmp(response.body.payload + sizeof(response.body.payload[0]) +
+                         sizeof(transferHandle),
+                     &flag, sizeof(flag)));
+    ASSERT_EQ(0,
+              memcmp(response.body.payload + sizeof(response.body.payload[0]) +
+                         sizeof(transferHandle) + sizeof(flag),
+                     &version, sizeof(version)));
+}
+
+TEST(GetPLDMVersion, testDecodeRequest)
+{
+    std::array<uint8_t, PLDM_GET_VERSION_REQ_BYTES> requestMsg{};
+    pldm_msg_payload request{};
+    request.payload = requestMsg.data();
+    request.payload_length = requestMsg.size();
+    uint32_t transferHandle = 0x0;
+    uint32_t retTransferHandle = 0x0;
+    uint8_t flag = PLDM_GET_FIRSTPART;
+    uint8_t retFlag = PLDM_GET_FIRSTPART;
+    uint8_t pldmType = PLDM_BASE;
+    uint8_t retType = PLDM_BASE;
+
+    memcpy(request.payload, &transferHandle, sizeof(transferHandle));
+    memcpy(request.payload + sizeof(transferHandle), &flag, sizeof(flag));
+    memcpy(request.payload + sizeof(transferHandle) + sizeof(flag), &pldmType,
+           sizeof(pldmType));
+
+    auto rc = decode_get_version_req(&request, &retTransferHandle, &retFlag,
+                                     &retType);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(transferHandle, retTransferHandle);
+    ASSERT_EQ(flag, retFlag);
+    ASSERT_EQ(pldmType, retType);
+}
+
+TEST(GetPLDMVersion, testDecodeResponse)
+{
+    std::array<uint8_t, PLDM_GET_VERSION_RESP_BYTES> responseMsg{};
+    pldm_msg_payload response{};
+    response.payload = responseMsg.data();
+    response.payload_length = responseMsg.size();
+    uint32_t transferHandle = 0x0;
+    uint32_t retTransferHandle = 0x0;
+    uint8_t flag = PLDM_START_AND_END;
+    uint8_t retFlag = PLDM_START_AND_END;
+    uint8_t completionCode = 0;
+    struct pldm_version version = {0xFF, 0xFF, 0xFF, 0xFF};
+    struct pldm_version versionOut;
+
+    memcpy(response.payload + sizeof(completionCode), &transferHandle,
+           sizeof(transferHandle));
+    memcpy(response.payload + sizeof(completionCode) + sizeof(transferHandle),
+           &flag, sizeof(flag));
+    memcpy(response.payload + sizeof(completionCode) + sizeof(transferHandle) +
+               sizeof(flag),
+           &version, sizeof(version));
+
+    auto rc = decode_get_version_resp(&response, &retTransferHandle, &retFlag,
+                                      &versionOut);
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(transferHandle, retTransferHandle);
+    ASSERT_EQ(flag, retFlag);
+
+    ASSERT_EQ(versionOut.major, version.major);
+    ASSERT_EQ(versionOut.minor, version.minor);
+    ASSERT_EQ(versionOut.update, version.update);
+    ASSERT_EQ(versionOut.alpha, version.alpha);
+}
diff --git a/test/libpldmresponder_base_test.cpp b/test/libpldmresponder_base_test.cpp
index 2bd6ebd..a34d80f 100644
--- a/test/libpldmresponder_base_test.cpp
+++ b/test/libpldmresponder_base_test.cpp
@@ -57,3 +57,41 @@
     getPLDMCommands(&request, &response);
     ASSERT_EQ(response.body.payload[0], PLDM_ERROR_INVALID_PLDM_TYPE);
 }
+
+TEST(GetPLDMVersion, testGoodRequest)
+{
+    pldm_msg response{};
+    std::array<uint8_t, PLDM_GET_VERSION_RESP_BYTES> responseMsg{};
+    response.body.payload = responseMsg.data();
+    response.body.payload_length = responseMsg.size();
+    pldm_msg request{};
+    std::array<uint8_t, PLDM_GET_VERSION_REQ_BYTES> requestPayload{};
+    request.body.payload = requestPayload.data();
+    request.body.payload_length = requestPayload.size();
+
+    uint8_t pldmType = PLDM_BASE;
+    uint32_t transferHandle = 0x0;
+    uint8_t flag = PLDM_GET_FIRSTPART;
+    uint8_t retFlag = PLDM_START_AND_END;
+    struct pldm_version version = {0xF1, 0xF0, 0xF0, 0x00};
+
+    auto rc =
+        encode_get_version_req(0, transferHandle, flag, pldmType, &request);
+
+    ASSERT_EQ(0, rc);
+
+    getPLDMVersion(&(request.body), &response);
+
+    ASSERT_EQ(response.body.payload[0], 0);
+    ASSERT_EQ(0,
+              memcmp(response.body.payload + sizeof(response.body.payload[0]),
+                     &transferHandle, sizeof(transferHandle)));
+    ASSERT_EQ(0,
+              memcmp(response.body.payload + sizeof(response.body.payload[0]) +
+                         sizeof(transferHandle),
+                     &retFlag, sizeof(flag)));
+    ASSERT_EQ(0,
+              memcmp(response.body.payload + sizeof(response.body.payload[0]) +
+                         sizeof(transferHandle) + sizeof(flag),
+                     &version, sizeof(version)));
+}